@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
113 lines (112 loc) • 7.54 kB
TypeScript
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>;