UNPKG

@phren/cli

Version:

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

397 lines (396 loc) • 15.8 kB
import type { ConductorSuccessionService } from "./conductor-succession.js"; import { z } from "zod"; import { moveToSchema } from "./dispatch.js"; import { type HarnessInventory } from "./harnesses.js"; import { type Json, type Provider, type StartingTarget, type Target } from "./protocol.js"; import type { TerminalProvider } from "./terminal.js"; export { moveToSchema }; export type MoveTo = z.infer<typeof moveToSchema>; export declare const moveRequestSchema: 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>; to: z.ZodObject<{ computer: z.ZodOptional<z.ZodString>; harness: z.ZodEnum<{ claude: "claude"; codex: "codex"; copilot: "copilot"; opencode: "opencode"; }>; account: z.ZodOptional<z.ZodString>; model: z.ZodOptional<z.ZodString>; effort: z.ZodOptional<z.ZodEnum<{ high: "high"; low: "low"; max: "max"; medium: "medium"; minimal: "minimal"; xhigh: "xhigh"; }>>; }, z.core.$strict>; mode: z.ZodDefault<z.ZodEnum<{ "new-chat": "new-chat"; replace: "replace"; }>>; id: z.ZodOptional<z.ZodString>; handoffTimeoutMs: z.ZodOptional<z.ZodNumber>; }, z.core.$strict>; declare const moveRecordSchema: z.ZodObject<{ id: z.ZodString; state: z.ZodEnum<{ exiting: "exiting"; failed: "failed"; "handing-off": "handing-off"; launching: "launching"; linking: "linking"; moved: "moved"; uncertain: "uncertain"; }>; createdAt: z.ZodString; updatedAt: z.ZodString; from: 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>; account: z.ZodOptional<z.ZodString>; label: z.ZodOptional<z.ZodString>; }, z.core.$strict>; to: z.ZodObject<{ computer: z.ZodOptional<z.ZodString>; harness: z.ZodEnum<{ claude: "claude"; codex: "codex"; copilot: "copilot"; opencode: "opencode"; }>; account: z.ZodOptional<z.ZodString>; model: z.ZodOptional<z.ZodString>; effort: z.ZodOptional<z.ZodEnum<{ high: "high"; low: "low"; max: "max"; medium: "medium"; minimal: "minimal"; xhigh: "xhigh"; }>>; }, z.core.$strict>; computerFingerprint: z.ZodOptional<z.ZodString>; computerId: z.ZodOptional<z.ZodString>; accountChoice: z.ZodOptional<z.ZodString>; project: z.ZodOptional<z.ZodString>; mode: z.ZodOptional<z.ZodEnum<{ "new-chat": "new-chat"; replace: "replace"; }>>; modeForced: z.ZodOptional<z.ZodLiteral<"conductor">>; handedOffTo: z.ZodOptional<z.ZodObject<{ computer: z.ZodOptional<z.ZodString>; target: z.ZodUnion<readonly [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>, 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"; }>; starting: z.ZodLiteral<true>; startingToken: z.ZodString; }, z.core.$strip>]>; }, z.core.$strict>>; handedOffFrom: z.ZodOptional<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>; title: z.ZodString; }, z.core.$strict>>; cwd: z.ZodString; dispatch: z.ZodOptional<z.ZodString>; workerDispatch: z.ZodOptional<z.ZodString>; handoff: z.ZodOptional<z.ZodObject<{ path: z.ZodString; source: z.ZodEnum<{ agent: "agent"; fallback: "fallback"; }>; reason: z.ZodOptional<z.ZodString>; chars: z.ZodNumber; }, z.core.$strict>>; exit: z.ZodOptional<z.ZodEnum<{ clean: "clean"; interrupted: "interrupted"; killed: "killed"; }>>; placement: z.ZodOptional<z.ZodEnum<{ "adjacent-pane": "adjacent-pane"; peer: "peer"; "same-pane": "same-pane"; }>>; target: z.ZodOptional<z.ZodUnion<readonly [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>, 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"; }>; starting: z.ZodLiteral<true>; startingToken: z.ZodString; }, z.core.$strip>]>>; paneTarget: 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"; }>; }, z.core.$strip>>; linkPending: z.ZodOptional<z.ZodBoolean>; notice: z.ZodOptional<z.ZodString>; movedAt: z.ZodOptional<z.ZodString>; error: z.ZodOptional<z.ZodString>; recovery: z.ZodOptional<z.ZodObject<{ attempts: z.ZodNumber; nextAt: z.ZodString; }, z.core.$strict>>; }, z.core.$strict>; export type MoveRecord = z.infer<typeof moveRecordSchema>; /** What a dispatching Hook records on its receipt when its worker moved. */ export interface MovedWorker { id: string; at: string; handoff?: string; computerFingerprint?: string; computerId?: string; from: { harness: Provider; account?: string; }; to: MoveTo; target: Target | StartingTarget; } export declare function movesRoot(): string; /** The hand-off sits beside the new agent's brief, in a folder Claude is given read access to. */ export declare const handoffPath: (id: string) => string; export declare function readMove(id: string): Promise<MoveRecord | undefined>; /** Recent moves, newest first; old ones are removed on the way. */ export declare function listMoves(now?: number): Promise<MoveRecord[]>; /** The move that took the agent out of `target`'s pane: a target with a * conversation matches only that conversation; a starting one, the pane. */ export declare function moveFrom(records: readonly MoveRecord[], target: { server: string; pane: string; source: string; session?: string; }): MoveRecord | undefined; /** Conversation-bound annotations shared by tab and pane overview rows. */ export declare function moveSessionFields(records: readonly MoveRecord[], target: Target, title?: string): Json; /** The receipt fields for a finished move. */ export declare function movedWorker(record: MoveRecord): MovedWorker | undefined; /** A harness as people name it, with its account and model: "Claude (work, opus)". */ export declare function harnessName(to: { harness: string; account?: string; model?: string; effort?: string; }): string; /** What the moving agent is asked to write. */ export declare function handoffRequest(id: string, to: MoveTo, mode?: "replace" | "new-chat"): string; /** The hand-off between the markers in the agent's replies after the request, * or undefined while its end marker has not arrived. Code fences around the * block are dropped. */ export declare function extractHandoff(replies: readonly string[], id: string): string | undefined; /** One message from a conversation's tail. */ export interface TailEntry { role: "owner" | "agent"; text: string; } /** The owner's prompts and the agent's replies in a transcript's lines, oldest first. */ export declare function conversationEntries(lines: readonly string[], source: Provider): TailEntry[]; /** The agent's replies after the move's request: the request names the move, so an older hand-off never counts. */ export declare function repliesAfterRequest(entries: readonly TailEntry[], id: string): string[]; /** What `git` shows in the checkout at the move. */ export interface GitState { root: string; branch?: string; head?: string; upstream?: string; ahead?: number; behind?: number; /** `git status --porcelain` lines: every change, tracked or not. */ changes: string[]; } export declare function readGitState(directory: string): Promise<GitState | undefined>; /** The commands of every process below `pids` (the agent's own children), not the agent itself. */ export declare function processesBelow(pids: readonly number[]): Promise<string[]>; /** What Phren itself saw at the move, appended to every hand-off. */ export declare function observedState(git: GitState | undefined, processes: readonly string[], processCommands?: boolean): string; /** A hand-off built without the agent: the conversation's tail, the original brief and the git state. */ export declare function fallbackHandoff(input: { reason: string; from: string; entries: readonly TailEntry[]; original?: string; dispatch?: string; label?: string; }): string; /** The hand-off file: who moved where and when, the body, then what Phren saw. */ export declare function handoffDocument(record: MoveRecord, body: string, observed: string, at: string, conductor?: boolean): string; /** The new agent's first prompt: the whole hand-off when it fits the brief, else where to read it. */ export declare function continuePrompt(document: string, file: string): string; export interface MoverDeps { succession?: ConductorSuccessionService; snapshot: (server: string) => Promise<Json>; /** The conversation the pane runs now. */ identity: (server: string, pane: Json) => Promise<string | undefined>; terminal: Pick<TerminalProvider, "prompt" | "sendKeys" | "processes" | "closePane">; /** The `hand_off` delivery path: queued while the agent is busy. */ deliver: (target: Target, text: string, deliveryId: string) => Promise<Json>; /** Starts the new agent (`startSession`), in `into` when given. */ launch: (server: string, data: Json, options: { into?: { workspaceId: string; tabId: string; paneId: string; }; dispatchId?: string; }) => Promise<Json>; inventory: () => Promise<HarnessInventory | undefined>; /** The conversation's transcript lines, its tail is enough. */ transcript: (target: Target) => Promise<string[]>; git?: (directory: string) => Promise<GitState | undefined>; processesBelow?: (pids: readonly number[]) => Promise<string[]>; /** The Claude account the pane runs under, when known. */ paneAccount?: (server: string, pane: Json) => string | undefined; /** The dispatch the pane's agent was launched for. */ paneDispatch?: (server: string, pane: Json) => Promise<string | undefined>; /** The text of a brief this computer wrote. */ brief?: (id: string) => Promise<string | undefined>; /** Whether the pane holds this computer's conductor. */ isConductor?: (server: string, pane: Json) => Promise<boolean>; /** Herdr gives a pane its variables only when it is created; tmux sets them on every start. */ envAtStart?: (server: string) => boolean; /** Cleanup after the old agent exited (a Codex pane's app-server). */ afterExit?: (server: string, pane: string) => Promise<void>; kill?: (pid: number, signal: NodeJS.Signals) => void; now?: () => number; sleep?: (ms: number) => Promise<void>; pollMs?: number; /** How long a clean exit, then an interrupt, may take before the agent counts as hung. */ exitMs?: number; } /** Runs moves on the computer that runs the session. A move outlives the * request that started it (the Hook answers in seconds); its record says where it is. */ export declare class SessionMover { private readonly deps; private readonly cancelled; private readonly admissions; private readonly running; constructor(deps: MoverDeps); private get now(); private sleep; private get poll(); /** Checks the move can happen, records it and starts it. Refuses before anything is typed. */ start(input: unknown, caller?: { owner?: boolean; }): Promise<MoveRecord>; private admit; /** For tests: the move under way in a pane, until it settles. */ settled(server: string, pane: string): Promise<void> | undefined; private update; private run; abort(id: string): Promise<MoveRecord>; private assertNotAborted; private continueOnPeer; /** Startup and tick recovery reuse the receiving Hook's durable move id. * No fresh id, brief or pane is created after an uncertain launch. */ reconcile(): Promise<void>; /** An ordinary move never acquires permission to stop a conductor merely * because the owner promoted its source while the hand-off was running. */ private checkPromotion; /** Uses the same durable prompt path as the hand-off and its first brief. */ private conductorNotice; /** The agent's own hand-off, or why there is none. Fails only when the session itself went away. */ private askForHandoff; private fallback; /** Ends the old agent with its own exit command; an interrupt, then a kill, only when it hangs. */ private exit; /** Starts the target in the old pane when its shell has what the target needs, else in a new tab beside it. */ private relaunch; /** New chats remain open as linking while the harness establishes its conversation. */ private newTarget; } /** The last `bytes` of a transcript as lines, the first (cut) one dropped. */ export declare function transcriptTail(file: string, bytes?: number): Promise<string[]>;