/** * Plan B — the host's own background sub-agent, with our context governance. * * WHY THIS FILE EXISTS * The built-in `task { background: true }` is the only sub-agent path the * desktop can SHOW: its card links to the live child session, and on completion * the host wakes the parent by prompting it with a synthetic text part holding * the child's FULL reply (`TaskTool.injectBackgroundResult` → `renderOutput` → * `ops.prompt({parts:[{type:"text", synthetic:true, text}]})`, read out of the * desktop binary). Nothing a plugin returns can ever be that card — the * renderer's registry holds only its own tool names. * * So we take the other half of the deal: the injection is a message arriving at * the parent session, and `chat.message` hands plugins that message's `parts` * array BEFORE the host persists them (the binary keeps iterating the same * `resolvedParts` after the trigger). We swap an oversized body for a preview * and a pointer, and the parent gets visibility + wake from the host while the * full text stays out of the context window. * * WHAT THIS MUST NEVER DO * Rewrite something the user typed. Three independent locks, all required: * 1. `part.synthetic === true` — a typed message is never synthetic; * 2. the text must match the host's own `` * envelope exactly, `` included; * 3. the body must exceed the same text-tier offload threshold every other * governed result passes. * Miss any one and the part is left byte-for-byte alone: a false negative costs * a big message, a false positive silently deletes someone's content. * * NO DISK WRITE. The full text already lives in the child session — copying it * into our store would put the user's most private payload (this is how the * feature gets used) on disk for no gain. The pointer says: `tm_join {ids:[…]}` * to pull it deliberately, or open the card. */ export interface TaskEnvelope { /** The child session the host spawned. */ sessionId: string; summary: string; body: string; } /** Parse the host's completed-task envelope; null when it is anything else. */ export declare function parseTaskEnvelope(text: unknown): TaskEnvelope | null; /** The replacement: same envelope, same summary, preview + a way to the whole. */ export declare function renderOffloadedTask(env: TaskEnvelope, preview: string, tokens: number, previewTokens: number): string; export interface HostEnvelope { sessionId: string; state: string; description: string; body: string; /** which host emitted it — the two render differently and must not drift */ form: "v1-task" | "v2-subagent"; } /** Recognise either host spelling. Returns null for anything else, including a * v2 envelope whose state is not a completed-looking word — an interrupted or * failed child has nothing to offload, and rewriting it would hide why it failed. */ export declare function parseHostEnvelope(text: unknown): HostEnvelope | null; /** The v2 replacement: SAME wrapper, same attributes, preview + a pointer in * place of the body. The envelope is what the host and the UI key on, so it is * reproduced rather than replaced by our own object. */ export declare function renderOffloadedSubagent(env: HostEnvelope, preview: string, tokens: number): string; export interface TaskOffloadDeps { /** TM_TASK_OFFLOAD — off restores the host's verbatim injection. */ enabled: boolean; /** Same text-tier threshold every other governed result obeys. */ thresholdTokens: number; previewLines: number; previewMaxTokens: number; /** Observability: the hook reports EVERY envelope it recognises, not only * the ones it rewrites. A counter that moves only on a rewrite cannot tell * "the channel is alive and nothing was big enough" apart from "the host * stopped routing injections through chat.message" — and the second case is * exactly what a future OpenCode upgrade would do silently. */ log?: (event: Record) => void; } export declare function createTaskOffload(deps: TaskOffloadDeps): (input: { sessionID?: string; }, output: { message?: { role?: unknown; }; parts?: Array>; }) => void; //# sourceMappingURL=task-offload.d.ts.map