Initial release

This commit is contained in:
lesetong
2026-07-22 23:35:04 +08:00
commit 281f3c1615
17 changed files with 5001 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
import net from "node:net";
import type { Task } from "./types.ts";
import { orderedTreeRows } from "./ui.ts";
const SOURCE = "pi-todo-herdr";
const TOKEN = "task";
let reportSeq = Date.now() * 1000;
function nextSeq(): number {
reportSeq += 1;
return reportSeq;
}
export function isHerdrEnvironment(): boolean {
return process.env.HERDR_ENV === "1" && !!process.env.HERDR_SOCKET_PATH && !!process.env.HERDR_PANE_ID;
}
function hasDescendantWithStatus(task: Task, status: Task["status"], byId: Map<number, Task>): boolean {
for (const childId of task.childrenIds) {
const child = byId.get(childId);
if (!child) continue;
if (child.status === status || hasDescendantWithStatus(child, status, byId)) return true;
}
return false;
}
export function currentTaskLabel(tasks: Task[]): string | null {
const byId = new Map(tasks.map((task) => [task.id, task]));
const ordered = orderedTreeRows(tasks).map(({ task }) => task);
const inProgress = ordered.filter(
(task) => task.status === "in-progress" && !hasDescendantWithStatus(task, "in-progress", byId),
);
const candidates =
inProgress.length > 0
? inProgress
: ordered.filter((task) => task.status === "blocked" && !hasDescendantWithStatus(task, "blocked", byId));
if (candidates.length === 0) return null;
const suffix = candidates.length > 1 ? ` +${candidates.length - 1}` : "";
return `#${candidates[0]!.id} ${candidates[0]!.name}${suffix}`;
}
function sendAttempt(request: unknown, timeoutMs: number): Promise<boolean> {
if (!isHerdrEnvironment()) return Promise.resolve(true);
const socketPath = process.env.HERDR_SOCKET_PATH!;
const endpoint = process.platform === "win32" ? `\\\\.\\pipe\\${socketPath}` : socketPath;
return new Promise((resolve) => {
let settled = false;
let timer: ReturnType<typeof setTimeout> | undefined;
const socket = net.createConnection(endpoint);
const finish = (delivered: boolean) => {
if (settled) return;
settled = true;
if (timer) clearTimeout(timer);
socket.destroy();
resolve(delivered);
};
socket.on("error", () => finish(false));
socket.on("connect", () => socket.write(`${JSON.stringify(request)}\n`));
socket.on("data", () => finish(true));
socket.on("end", () => finish(false));
timer = setTimeout(() => finish(false), timeoutMs);
timer.unref?.();
});
}
async function send(request: unknown): Promise<void> {
if (await sendAttempt(request, 500)) return;
await sendAttempt(request, 1500);
}
async function reportToken(value: string | null): Promise<void> {
if (!isHerdrEnvironment()) return;
await send({
id: `${SOURCE}:${Date.now()}:${Math.random().toString(36).slice(2)}`,
method: "pane.report_metadata",
params: {
pane_id: process.env.HERDR_PANE_ID,
source: SOURCE,
tokens: { [TOKEN]: value },
seq: nextSeq(),
},
});
}
export function reportCurrentTask(tasks: Task[]): Promise<void> {
return reportToken(currentTaskLabel(tasks));
}
export function clearCurrentTask(): Promise<void> {
return reportToken(null);
}