@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
97 lines (96 loc) • 4.03 kB
TypeScript
import { z } from "zod";
import { type Json, type Target } from "./protocol.js";
export declare const queuedHandOffSchema: z.ZodObject<{
target: z.ZodObject<{
server: z.ZodString;
workspace: z.ZodString;
tab: z.ZodString;
pane: z.ZodString;
source: z.ZodEnum<{
claude: "claude";
codex: "codex";
copilot: "copilot";
opencode: "opencode";
phren: "phren";
}>;
session: z.ZodUnion<readonly [z.ZodString, z.ZodString]>;
}, z.core.$strip>;
text: z.ZodString;
deliveryId: z.ZodOptional<z.ZodString>;
origin: z.ZodOptional<z.ZodObject<{
server: z.ZodString;
workspace: z.ZodString;
tab: z.ZodString;
pane: z.ZodString;
source: z.ZodEnum<{
claude: "claude";
codex: "codex";
copilot: "copilot";
opencode: "opencode";
phren: "phren";
}>;
session: z.ZodUnion<readonly [z.ZodString, z.ZodString]>;
}, z.core.$strip>>;
originComputer: z.ZodOptional<z.ZodString>;
}, z.core.$strict>;
export interface QueueOptions {
validate: (target: Target) => Promise<Json>;
send: (target: Target, text: string, deliveryId: string, typing: () => Promise<void>) => Promise<Json>;
forward?: (target: Target, text: string, deliveryId: string) => Promise<{
delivered: boolean;
} | undefined>;
notify?: (target: Target, text: string, deliveryId: string, computer: string) => Promise<Json>;
/** The conversation the target's pane shows now, and its terminal. */
current?: (target: Target) => Promise<{
session: string;
terminal: unknown;
} | undefined>;
now?: () => number;
root?: string;
}
/** Settled rows (and notice tombstones) are kept this long, so a late retry of
* an old delivery id still replays instead of typing again. */
export declare const HAND_OFF_RETENTION_MS: number;
/** At most this many rows stay on disk; the oldest settled rows go first. */
export declare const HAND_OFF_MAX_ROWS = 512;
/** A durable outbox on the receiving Hook. Persist 'attempting' before input,
* so a crash in the input/ack gap recovers as uncertain, never as a retry.
* Tombstones are retained for HAND_OFF_RETENTION_MS, so a retried delivery id
* replays instead of typing a second time; a queued or attempting row is never
* pruned. The sweep runs on enqueue and at most daily from tick(). */
export declare class HandOffQueue {
private options;
private pending;
private root;
private now;
private lastPrune;
private ticking?;
constructor(options: QueueOptions);
private serial;
private file;
private save;
private read;
private rows;
/** Drops settled rows past retention, then the oldest settled rows over the
* cap (those still owing their sender a notice last). Returns what is kept. */
private prune;
private reply;
enqueue(input: unknown): Promise<Json>;
status(id: string, target: Target): Promise<Json>;
/** Validates the row's target. When the pane's conversation was replaced
* in the same terminal (a Codex thread change, a Claude /clear), the row
* follows it: a queued message is for the pane, not for a conversation
* that is no longer there. Saved by the caller's next state change. */
private validate;
private attempt;
/** Coalesced: the Hook calls this every 5 s whether or not the last tick
* finished, and queuing each behind the lock would hold every enqueue and
* status behind a backlog that grows faster than a slow tick drains it. */
tick(): Promise<void>;
private sweep;
private noticeId;
/** Hold enqueues and delivery while the Hook validates and closes a worker.
* A new hand-off cannot be accepted between the pending check and closure. */
whenNoPending(target: Target, close: () => Promise<Json>): Promise<Json>;
hasPending(target: Target): Promise<boolean>;
}