import type { V2Registration } from "./v2-types.js"; import type { TeamScope } from "./v2-scope.js"; import { detectContentType } from "../tm/preview.js"; /** * JIT context governance over the HOST's own tools. * * The whole product rests on one promise: an oversized tool result never enters * the context window — it lands in the run store and the model gets an * ≤80-token preview plus a handle. That promise was true only inside our own * `tm_*` tools, which is what made `tm_read`/`tm_grep`/`tm_bash` load-bearing * rather than merely nicer. The moment a role is allowed the native `read` or * `shell` (which is the retirement plan for those three), an un-governed native * result would put the promise back to zero for exactly the tools a user would * most plausibly enable — and the promise would then depend on which tool the * model happened to pick, which is not a guarantee at all. * * So the governance moves to the seam that does not care what the model chose. * `tool.execute.after` hands us the finished result for EVERY tool (measured * live: `{action:"shell"}` reaches `permission.evaluate`, and the same call * reaches `execute.after` with `resultKeys: content / metadata / output`), and * the host documents `result` as mutable. One hook, both worlds. * * Design constraints this file is written under: * - **It only ever shrinks what reaches the context, never what the tool did.** * The bytes on disk are the host's; a preview plus a handle replaces the text * part. `metadata` and any non-text part (an image attachment) pass through * untouched — dropping a screenshot to save tokens would be a bad trade. * - **It reuses the same threshold, preview builder, store and HMAC handles as * `tm_*`**, because a second implementation would drift, and the drift would * show up as "the same 40 KB was free through one tool and offloaded through * another". * - **A native result is rendered as native-shaped text.** `tm_*` returns our own * structured object; overwriting a host tool's text part with `{offloaded:true, * ref:…}` would hand the model a shape it has no reason to understand. The * sentence below says the same facts in prose the tm_* tools already train it * to act on: how much was saved, the handle, how to page it in. * - Nothing here may throw into somebody's tool call: a governance failure * degrades to the host's verbatim result, and says so in the trajectory. */ /** Tools whose NATIVE output we govern. Deliberately a closed list: the built-in * agent/patch/question results have shapes nobody has observed here, and rewriting * a shape you guessed at is content destruction, not governance. * * `execute` is on the list for one specific reason, and it is the reason the * native browser can be governed at all: the Team's direct tool surface on v2 is * six tools (edit / execute / question / shell / subagent / write), so * `tools.browser.*` -- every snapshot, tab list and evaluate result -- reaches the * context window ONLY as the aggregate return of one Code Mode program. There is * no other door, so governing `execute` IS "put JIT on the native browser". */ export declare const NATIVE_GOVERNED_TOOLS: ReadonlySet; /** Is this tool's output inside the governed surface? The host's browser catalog is * 45 tools sharing one namespace (`browser_*`, each registered with * `permission:"browser"`), and a snapshot from any of them is exactly the oversized * payload the offload promise is about — enumerating 45 names would only guarantee * that a host adding the 46th escapes us. Unknown RESULT shapes are still left * untouched; that test is per payload, not per name. */ export declare function isGovernedNative(tool: string): boolean; export interface V2OffloadReport { seen: number; /** every execute.after we resolved to a Team session, governed tool or not — the * denominator the coverage sentence is read against (#23: "most tool calls are * JIT-governed" has to be a number, not a feeling) */ ours: number; /** ours, for a tool whose output we do not touch (unknown tool, or a name the * governed set has not been widened to) — counted rather than invisible */ unmatched: number; considered: number; offloaded: number; /** addressing results (native browser snapshots) that stayed IN context, capped * rather than replaced by a handle — the tokens they cost are counted, and so is * the fact that we chose a usable `ref` over the smaller number */ capped: number; /** report-shaped payloads (tables kept, prose dropped) — a third outcome, because * "offloaded" and "capped" answer different questions about what entered context */ reportCapped: number; /** envelopes RECOGNISED, whether or not they needed rewriting — a counter that * moves only on a rewrite cannot tell "the channel is alive, nothing was big" * apart from "this host's format is not the one we match" (which is exactly how * the v1 `; } interface OffloadDeps { /** the shared tm pipeline instance — one store, one step counter, one threshold table */ pipelines: { nextStepId: () => string; govern: (stepId: string, tool: string, content: string, opts: { contentType: ReturnType; clue?: string; }) => unknown; }; env?: Record; /** The resolved runtime config (nativeOffload + the two token budgets). */ config?: { nativeOffload: string; nativeSnapshotMaxTokens: number; nativeReportMaxTokens: number; }; /** Team-scope isolation (#22): a non-Team session's result is nobody's to rewrite * but the host's, and an unknown owner is treated as not-ours and COUNTED — a * governance layer that silently stopped applying is the same overstated claim * this product exists to refuse. */ scope?: TeamScope; } /** Rendered in the user's tool-output language (the tm_* strings are Chinese and * flow into every reply an agent writes about them). */ /** * Tools whose result IS an addressing table: native `browser_click` / `browser_fill` * take a `ref` out of the snapshot, so a preview that drops the ref tokens does not * save the model a round-trip — it makes it click the wrong element, or spend a call * re-snapshotting. For these two the text stays in context, capped, with the handle * kept for the tail. 1 200 tokens is the budget a native snapshot already * runs at, so the governed door and the native one cost the same window space. */ export declare const ADDRESSING_NATIVE_TOOLS: ReadonlySet; export declare const NATIVE_SNAPSHOT_MAX_TOKENS = 1200; export declare function snapshotTokenBudget(config: { nativeSnapshotMaxTokens: number; } | undefined): number; export declare function renderAddressingCap(tool: string, capped: string, governed: { ref: string; access_token: string; expire_at: number; tokens: number; }): string; /** The budget a report (a `tm_stats`-shaped answer, a `tm_join` summary) keeps in * context: tables win the space, prose is what gets dropped. Pinned separately from * the snapshot budget because a report has more prose per table. */ export declare const NATIVE_REPORT_MAX_TOKENS = 1600; export declare function reportTokenBudget(config: { nativeReportMaxTokens: number; } | undefined): number; export declare function renderReportCap(tool: string, capped: string, governed: { ref: string; access_token: string; expire_at: number; tokens: number; }, tablesDropped: number): string; export declare function renderNativeOffload(tool: string, governed: { offloaded: true; ref: string; access_token: string; expire_at: number; tokens: number; preview: string; }): string; export declare function applyV2NativeOffload(ctx: unknown, deps: OffloadDeps): { registrations: Promise[]; report: V2OffloadReport; active: boolean; }; export {}; //# sourceMappingURL=v2-offload.d.ts.map