UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

113 lines (112 loc) • 7.54 kB
import { type Json } from "./protocol.js"; import type { FinalTurn } from "./schedule-watch.js"; import { type TurnRecord } from "./turn-records.js"; /** * What a live session's row says about itself beyond Herdr's own status and * title, from the pane's turn record (turn-records.ts) and the dispatch label * (launch-brief.ts): * * - A session whose turn ended while work it started still runs is still * working: running sub-agents, teammates, workflow agents and fan-out jobs * (its child tree, Claude and Codex alike), and for Claude the background * shells and monitors started since the owner's last prompt that are not * endless streams (`FinalTurn.awaited`, bounded by its Stop's count less the * tasks finished since, for at most BACKGROUND_STALE_MS). A log tail, a * watcher or a shell left over from an earlier exchange runs on without * anyone waiting for it, so it does not count. Herdr reports such a session * idle, so the phone listed it under IDLE with a moon badge. The tab keeps * `agentStatus: "working"` and gains `backgroundTasks`, the count. * - A dispatched worker is named by its dispatch label. Claude and Codex title * a session after its first prompt, which for a brief launch is * "Read and follow the brief in <path>" (or "Brief d35c6189" once they * shorten it), so those titles are never shown. */ /** The record, only when it belongs to this pane's terminal and conversation * (the guard dispatch returns use): a leftover record of an earlier terminal * or session in the same pane must not speak for this one. */ export declare function ownRecord(record: TurnRecord | undefined, pane: Json, source: unknown, session: string | undefined): TurnRecord | undefined; /** The pane's own turn record. OpenCode has no Stop hook to report background work or a dispatch id here. */ export declare function paneRecord(server: string, pane: Json, session: string | undefined): Promise<TurnRecord | undefined>; /** How long after its Stop a background count may keep a row working. A task * whose finish the transcript never shows (a dev server left running, a * persistent monitor, a notification the tail no longer holds) would * otherwise keep it working forever. Matches dispatch returns' wait. */ export declare const BACKGROUND_STALE_MS: number; /** Background tasks the recorded turn ended with that are still running: * the Stop's count less those `finishedTasks` (FinalTurn's) says finished * after it. Undefined once none are left or the Stop is BACKGROUND_STALE_MS old. */ export declare function recordedBackground(record: TurnRecord | undefined, finishedTasks?: readonly string[], now?: number): number | undefined; /** The background shells and monitors a turn still awaits, from its transcript * final turn. A recorded ended turn bounds `FinalTurn.awaited` by the Stop's * count less the tasks that finished since, and by the 2-hour cap; a Stop that * named no count leaves the transcript's own awaited shells, and a Stop the * Hook never saw falls back to them too. Undefined once none is awaited. A log * tail, a watcher or a shell left from an earlier exchange is not awaited, so * it never holds a turn open. */ export declare function awaitedShells(record: TurnRecord | undefined, final: FinalTurn | undefined, now?: number): number | undefined; /** The live work a finished turn still waits on: its awaited shells and * monitors plus the running children (sub-agents, teammates, workflows, * fan-out jobs) the caller counted. Leftover shells are awaited by neither, so * they do not hold a session or a dispatched worker open. */ export declare function liveWork(record: TurnRecord | undefined, final: FinalTurn | undefined, runningChildren?: number, now?: number): number | undefined; /** Every background task the finished turn left running, awaited or not, less * the endless streams (a log tail, a dev server): the Stop's count less the * tasks finished since, else the transcript's own count, and nothing once the * Stop is BACKGROUND_STALE_MS old. A turn whose reply says it waits on such a * task is still working, and a pane running one is not closed. */ export declare function runningTasks(record: TurnRecord | undefined, final: FinalTurn | undefined, now?: number): number; /** The background shells and monitors a pane's ended Claude turn is waiting * on (`FinalTurn.awaited`), at most `recordedBackground`. Reads the transcript * (`readFinalTurn`, passed in: schedule-watch imports herdr, which imports * this) only when the Stop left background work; only Claude's Stop reports * a count. Sub-agents are not in it: the overview adds its running children. * An unreadable transcript counts nothing, since the Stop's count alone * cannot tell a build from a log tail. */ export declare function liveBackground(record: TurnRecord | undefined, readFinal: (source: "claude", session: string) => Promise<FinalTurn | undefined>, now?: number): Promise<number | undefined>; /** * Marks a tab whose main turn ended but whose background work continues: * an idle or done tab becomes `working` with `backgroundTasks: count`. Called * again with a fuller count (the awaited shells plus the running children, * once the child tree is read), it keeps the larger. A tab that is working, * blocked or waiting on its own is left alone, so `backgroundTasks` never * sits on a tab with a live turn. */ export declare function markBackground(tab: Json, count: number | undefined): void; /** A tab or workspace label worth showing as a name: not empty, not Herdr's bare numbering, not an id. */ export declare function meaningfulLabel(value: unknown): string | undefined; /** A terminal title without the spinner or status glyphs around it, nor the * separator they leave dangling (Codex's "⠸ | phren" before its thread has a * title); undefined when nothing else is left. */ export declare function plainTitle(value: unknown): string | undefined; /** A harness title made from the brief launch's first prompt rather than the work: the prompt itself, the brief path, or "Brief <id>". */ export declare function isBriefTitle(title: string): boolean; /** * The name a session is shown under, first that applies: * 1. the agent pane's own label, when meaningful (a rename from the phone or * from Herdr or tmux itself; a pane has none until someone sets one); * 2. the dispatch label its dispatcher gave it; * 3. for a dispatch with no stored label (sent before labels were kept), the * tab label, else the workspace label, when meaningful; * 4. the harness's own title, unless it is brief boilerplate, which is * replaced by a meaningful tab or workspace label or dropped (the phone * then falls back to the label). * Never leaves a row blank: when none applies and `fallbackLabel` (passed, even * if undefined) is the label the phone shows without a title and is blank, the raw harness title stands. */ export declare function sessionTitle(input: { dispatched: boolean; paneLabel?: unknown; dispatchLabel?: string; harnessTitle?: unknown; tabLabel?: unknown; workspaceLabel?: unknown; fallbackLabel?: unknown; }): string | undefined; /** `sessionTitle` for a pane, given its own turn record (the guarded one from `paneRecord`). */ export declare function recordTitle(record: TurnRecord | undefined, input: { paneLabel?: unknown; harnessTitle?: unknown; tabLabel?: unknown; workspaceLabel?: unknown; fallbackLabel?: unknown; }): Promise<string | undefined>;