/** * Agent ask ledger — the return path of agent-to-agent messages. * * When one agent hands work to another with a plain message (send-message / * `/terminal/:to/write`), nothing brings the answer back: no WorkItem, no * completion event. On 2026-10-14 Pia passed the owner's "replicate this X * video" to Dex; Dex rendered the films in 24 minutes, wrote "both parts are * done" into its own terminal and stopped. Nobody saw it for seven hours. * * This ledger remembers each agent message once it is delivered. When the * receiver ends a turn that worked on it for a while and it neither wrote * back to the sender nor posted anywhere, the harness passes the receiver's * last words to the sender. The receiver's open asks also hold the owner's * ticket open (an agent that handed the work on has not answered yet). * * Cheap by construction: no model call, one transcript read per forwarded * ask. Off switch: `CREWLY_AGENT_ASK_RETURN=off`. * * @module services/messaging/agent-ask-ledger.service */ /** Tunables. */ export declare const AGENT_ASK_CONSTANTS: { /** Store file under CREWLY_HOME */ readonly FILE_NAME: "agent-asks.json"; /** A turn this long after the delivery counts as having worked on the message (ms) */ readonly MIN_WORK_MS: number; /** * A turn that ends sooner than MIN_WORK_MS but at least this long after * the delivery read it and let it go (an FYI, an "ok"): the ask is dropped * without a word. Sooner than this, the turn that ended is the previous * one, not the message's (ms). */ readonly MIN_READ_MS: 15000; /** An ask nobody answered is forgotten after this (ms) */ readonly TTL_MS: number; /** Asks kept in the store */ readonly MAX_ASKS: 300; /** Characters of the asked message quoted back to the sender */ readonly EXCERPT_CHARS: 200; /** Characters of the receiver's last words passed on */ readonly RESULT_CHARS: 3000; /** Env switch: `off` disables the ledger */ readonly ENV_SWITCH: "CREWLY_AGENT_ASK_RETURN"; }; /** One delivered agent-to-agent message. */ export interface AgentAsk { /** Sending agent */ sender: string; /** Receiving agent */ target: string; /** Start of the message */ excerpt: string; /** When it reached the receiver's turn (epoch ms) */ deliveredAt: number; } /** Collaborators. */ export interface AgentAskLedgerDeps { /** Store file; omit to keep the ledger in memory only */ file?: string; /** What the receiver last wrote in its own transcript since `sinceMs`, or null */ readLastWords: (session: string, sinceMs: number) => Promise; /** Deliver a harness message to an agent */ deliver: (session: string, text: string) => Promise; /** Readable name of an agent (default: the member part of its session name) */ displayName?: (session: string) => string; now?: () => number; } /** * The member name in a session id: `flopost-dex-7f17ecbc` → `Dex`. * * @param session - Session name * @returns Readable name */ export declare function memberName(session: string): string; /** * Text handed to the sender in place of the answer it never got. * * @param input - Names, the asked excerpt, minutes worked, the last words * @returns Message for the sender */ export declare function renderReturnedAnswer(input: { target: string; excerpt: string; minutes: number; lastWords: string | null; }): string; /** * Remembers delivered agent messages and returns unsent answers. See module docs. */ export declare class AgentAskLedger { private readonly deps; private asks; private posts; private readonly logger; private readonly now; private saving; constructor(deps: AgentAskLedgerDeps); /** * A message from `sender` reached `target`'s turn. * * @param target - Receiving agent * @param sender - Sending agent * @param text - What was delivered */ noteDelivered(target: string, sender: string, text: string): void; /** * `from` wrote to `to`: every open ask of `to` to `from` is answered. * * @param from - Writing agent * @param to - Agent written to */ noteAnswered(from: string, to: string): void; /** * An agent posted in chat (an answer, not an interim note): asks delivered * to it before now count as answered where they were asked to be. * * @param session - Posting agent */ notePost(session: string): void; /** * Asks `sender` made that are still unanswered. * * @param sender - Asking agent * @returns Open asks, oldest first */ openAsksBy(sender: string): AgentAsk[]; /** * Every ask still unanswered (copies), for "where is it now" views. * * @returns Open asks, oldest first */ openAsks(): AgentAsk[]; /** * `target` finished a turn. An ask it worked on for at least * {@link AGENT_ASK_CONSTANTS.MIN_WORK_MS} with no word back is answered * for it: its last words go to the sender. One it answered in chat, or * read in a short turn, is dropped. Never throws. * * @param target - Agent whose turn ended * @returns Asks returned to their senders */ onTurnEnded(target: string): Promise; private prune; private load; private save; /** Wait for pending writes (tests). */ flush(): Promise; } /** * The agent's last message in a transcript since a time: the text of the * newest assistant entry that has text (Claude Code JSONL or Codex rollout). * Subagent (sidechain), meta and API-error entries are not the agent's words. * * @param text - Whole JSONL lines (a tail of the file is fine) * @param kind - Transcript format * @param sinceMs - Ignore entries older than this (epoch ms) * @returns The text, or null when there is none */ export declare function lastWordsFromTranscript(text: string, kind: 'claude' | 'codex', sinceMs: number): string | null; /** @returns The wired ledger, or null before boot (and when switched off) */ export declare function getAgentAskLedger(): AgentAskLedger | null; /** @param ledger - The ledger to expose (null clears) */ export declare function setAgentAskLedger(ledger: AgentAskLedger | null): void; /** * Whether the ledger is switched on. * * @param env - Environment * @returns False when `CREWLY_AGENT_ASK_RETURN=off` */ export declare function agentAskReturnEnabled(env?: NodeJS.ProcessEnv): boolean; //# sourceMappingURL=agent-ask-ledger.service.d.ts.map