@tangle-network/agent-app 0.44.27 → 0.44.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,113 @@
1
1
  import { CompletionRequirement, RuntimeEventLike } from '@tangle-network/agent-eval';
2
2
  export { CompletionRequirement, CompletionVerdict, CorrectnessChecker, ProducedState, RuntimeEventLike, SatisfiedBy, TaskGold, createLlmCorrectnessChecker, extractProducedState, verifyCompletion, weightedComposite } from '@tangle-network/agent-eval';
3
- import { A as AppToolProducedEvent } from '../types-BCxK0wyS.js';
3
+ import { c as AppToolProducedEvent } from '../types-DbU-oO5h.js';
4
+
5
+ /**
6
+ * Calibration — prove a gate can FAIL before believing that it PASSED, and
7
+ * prove a probe can SEE before believing the zero it reported.
8
+ *
9
+ * Every expensive failure this package has shipped was a check that verified
10
+ * its own input instead of the world, and reported green:
11
+ *
12
+ * - `quote_verification 16/16` passed a work product whose nine citations
13
+ * claimed a figure the cited document never mentions.
14
+ * - `audit_form passed=3 failed=0` passed a form fill whose values landed in
15
+ * "Combat zone" — the audit re-read the field paths the writer had just
16
+ * invented, so a wrong path could not be wrong.
17
+ * - `evidence_coverage 0/0` passed vacuously because the artifact it was
18
+ * counting targets from was null.
19
+ * - A benchmark reported 6/8 for the product while a bare
20
+ * "You are a helpful assistant." scored 8/8 on the same cases.
21
+ *
22
+ * A gate is only evidence if it REJECTS something. A metric is only evidence
23
+ * if a worse system scores worse on it. Neither property is implied by a
24
+ * green run, and neither is visible in the output — which is why both have to
25
+ * be asserted separately, in code, next to the gate.
26
+ *
27
+ * This module is deliberately domain-free: a "gate" is any predicate over any
28
+ * input. Products supply the known-good and known-bad cases, because only the
29
+ * product knows what bad looks like in its domain.
30
+ */
31
+ /** A case whose verdict is known in advance, used to calibrate a gate. */
32
+ interface CalibrationCase<TInput> {
33
+ /** What this case represents, e.g. `'quote that does not occur in the source'`. */
34
+ readonly label: string;
35
+ readonly input: TInput;
36
+ /** `'reject'` — the gate MUST refuse this. `'accept'` — it MUST allow it. */
37
+ readonly expect: 'accept' | 'reject';
38
+ }
39
+ interface CalibrationOutcome {
40
+ readonly label: string;
41
+ readonly expected: 'accept' | 'reject';
42
+ readonly actual: 'accept' | 'reject';
43
+ readonly ok: boolean;
44
+ /** Set when the gate threw; a throw counts as `'reject'`. */
45
+ readonly threw?: string;
46
+ }
47
+ interface CalibrationReport {
48
+ /** True only when every case matched AND both controls were present. */
49
+ readonly discriminates: boolean;
50
+ readonly outcomes: readonly CalibrationOutcome[];
51
+ readonly failures: readonly CalibrationOutcome[];
52
+ /** Why the gate is not trustworthy. Absent when `discriminates` is true. */
53
+ readonly reason?: string;
54
+ }
55
+ /**
56
+ * A gate under calibration. Returning `false` OR throwing both count as a
57
+ * rejection — a fail-loud gate (`ToolInputError`) and a boolean gate calibrate
58
+ * through the same path.
59
+ */
60
+ type GateFn<TInput> = (input: TInput) => boolean | Promise<boolean>;
61
+ /**
62
+ * Run a gate against cases whose verdicts are known, and report whether it
63
+ * actually discriminates.
64
+ *
65
+ * Requires BOTH controls:
66
+ * - at least one `'reject'` case — without it a gate that returns `true`
67
+ * unconditionally is indistinguishable from a working one. This is the
68
+ * control that all four failures above were missing.
69
+ * - at least one `'accept'` case — without it a gate that refuses everything
70
+ * scores perfectly, and an unsatisfiable gate does not stop bad work, it
71
+ * selects for invented work (measured: 38 fabricated citations written to
72
+ * clear a coverage gate no honest answer could satisfy).
73
+ */
74
+ declare function calibrateGate<TInput>(gate: GateFn<TInput>, cases: readonly CalibrationCase<TInput>[]): Promise<CalibrationReport>;
75
+ /**
76
+ * `calibrateGate`, but throws instead of reporting. Use in a test or at wiring
77
+ * time so an uncalibrated gate cannot ship silently.
78
+ */
79
+ declare function assertGateDiscriminates<TInput>(name: string, gate: GateFn<TInput>, cases: readonly CalibrationCase<TInput>[]): Promise<CalibrationReport>;
80
+ interface ProbeReport<TValue> {
81
+ /** True when the positive control registered something, so a zero is real. */
82
+ readonly canSee: boolean;
83
+ readonly measured: number;
84
+ readonly control: number;
85
+ readonly value: TValue;
86
+ readonly reason?: string;
87
+ }
88
+ /**
89
+ * Measure something, but only after proving the instrument can register a
90
+ * non-zero — because an absence is a claim about the measurement first.
91
+ *
92
+ * Six blind probes were mistaken for real zeros in a single day: a SQL `LIKE`
93
+ * over a column the product encrypts (which equally returned 0 for `"text"`
94
+ * across every row — the tell), a `grep` run against worktrees on stale
95
+ * branches, a status-code comparison that never read the response bodies, a
96
+ * live event count taken from a run that was never dispatched, `rg --hidden`
97
+ * silently exiting 2 because `rg` was aliased to `grep`, and an intercepted
98
+ * `git show` returning an empty diff. Each nearly caused a wrong fix.
99
+ *
100
+ * `control` must count something that MUST exist. If it counts zero, the
101
+ * measurement is unusable regardless of what `measure` returned.
102
+ */
103
+ declare function measureWithControl<TValue>(opts: {
104
+ readonly measure: () => TValue | Promise<TValue>;
105
+ /** Must return a value whose count is non-zero, or the probe is blind. */
106
+ readonly control: () => TValue | Promise<TValue>;
107
+ readonly count: (value: TValue) => number;
108
+ /** Describes what the control counts, for the failure message. */
109
+ readonly controlLabel: string;
110
+ }): Promise<ProbeReport<TValue>>;
4
111
 
5
112
  /**
6
113
  * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.
@@ -47,4 +154,4 @@ declare function createTokenRecallChecker(opts?: {
47
154
  reason: string;
48
155
  }>;
49
156
 
50
- export { createTokenRecallChecker, producedFromToolEvents };
157
+ export { type CalibrationCase, type CalibrationOutcome, type CalibrationReport, type GateFn, type ProbeReport, assertGateDiscriminates, calibrateGate, createTokenRecallChecker, measureWithControl, producedFromToolEvents };
@@ -1,5 +1,46 @@
1
1
  // src/eval/index.ts
2
2
  import { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from "@tangle-network/agent-eval";
3
+
4
+ // src/eval/calibration.ts
5
+ async function calibrateGate(gate, cases) {
6
+ const outcomes = [];
7
+ for (const c of cases) {
8
+ let actual;
9
+ let threw;
10
+ try {
11
+ actual = await gate(c.input) ? "accept" : "reject";
12
+ } catch (err) {
13
+ actual = "reject";
14
+ threw = err instanceof Error ? err.message : String(err);
15
+ }
16
+ outcomes.push({ label: c.label, expected: c.expect, actual, ok: actual === c.expect, ...threw ? { threw } : {} });
17
+ }
18
+ const failures = outcomes.filter((o) => !o.ok);
19
+ const hasNegative = cases.some((c) => c.expect === "reject");
20
+ const hasPositive = cases.some((c) => c.expect === "accept");
21
+ const reason = !hasNegative ? "no negative control: every case expects acceptance, so a gate that never refuses would score perfectly" : !hasPositive ? "no positive control: every case expects rejection, so a gate that refuses everything would score perfectly" : failures.length > 0 ? `${failures.length}/${outcomes.length} cases disagreed: ${failures.map((f) => `${f.label} expected ${f.expected}, got ${f.actual}`).join("; ")}` : void 0;
22
+ return { discriminates: reason === void 0, outcomes, failures, ...reason ? { reason } : {} };
23
+ }
24
+ async function assertGateDiscriminates(name, gate, cases) {
25
+ const report = await calibrateGate(gate, cases);
26
+ if (!report.discriminates) throw new Error(`gate "${name}" is not evidence \u2014 ${report.reason}`);
27
+ return report;
28
+ }
29
+ async function measureWithControl(opts) {
30
+ const value = await opts.measure();
31
+ const controlValue = await opts.control();
32
+ const measured = opts.count(value);
33
+ const control = opts.count(controlValue);
34
+ return control > 0 ? { canSee: true, measured, control, value } : {
35
+ canSee: false,
36
+ measured,
37
+ control,
38
+ value,
39
+ reason: `probe is blind: the positive control (${opts.controlLabel}) counted 0, so the measured ${measured} carries no information`
40
+ };
41
+ }
42
+
43
+ // src/eval/index.ts
3
44
  function producedFromToolEvents(events) {
4
45
  return events.map(
5
46
  (e) => e.type === "proposal_created" ? { type: "proposal_created", proposalId: e.proposalId, title: e.title, status: e.status, content: e.content } : { type: "artifact", artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: "text/markdown", content: e.content }
@@ -21,9 +62,12 @@ function createTokenRecallChecker(opts = {}) {
21
62
  };
22
63
  }
23
64
  export {
65
+ assertGateDiscriminates,
66
+ calibrateGate,
24
67
  createLlmCorrectnessChecker,
25
68
  createTokenRecallChecker,
26
69
  extractProducedState,
70
+ measureWithControl,
27
71
  producedFromToolEvents,
28
72
  verifyCompletion,
29
73
  weightedComposite
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/eval/index.ts"],"sourcesContent":["/**\n * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.\n *\n * The completion/scoring ENGINE lives in agent-eval (a peer dependency):\n * `verifyCompletion`, `extractProducedState`, `weightedComposite`,\n * `createLlmCorrectnessChecker`, and the `CompletionRequirement` / `TaskGold` /\n * `ProducedState` types — all re-exported here so a consumer has one import\n * root. This module adds only what agent-eval doesn't have and what is\n * app-shell-specific:\n *\n * 1. {@link producedFromToolEvents} — the bridge: turn the structured app-tool\n * side channel's `AppToolProducedEvent`s (from a tool runtime executor's\n * `onProduced`) into the `RuntimeEventLike`s agent-eval's\n * `extractProducedState` consumes. This is the one piece that knows about\n * the app-tool channel, so it belongs here, not in the engine.\n * 2. {@link createTokenRecallChecker} — a deterministic, no-LLM\n * `CorrectnessChecker` (agent-eval ships only the LLM one). For apps/tests\n * that gate completion without a judge call.\n *\n * Full campaigns (persona simulation, traces, scorecards, held-out gates) are\n * agent-eval's `runEvalCampaign` / `AgentDriver` / `BenchmarkRunner` — use them\n * directly; this module composes with them.\n */\nimport type { RuntimeEventLike, CompletionRequirement } from '@tangle-network/agent-eval'\nimport type { AppToolProducedEvent } from '../tools/types'\n\n// Re-export the engine so consumers import completion + scoring from one place.\nexport { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from '@tangle-network/agent-eval'\nexport type {\n CompletionRequirement,\n TaskGold,\n ProducedState,\n SatisfiedBy,\n CompletionVerdict,\n CorrectnessChecker,\n RuntimeEventLike,\n} from '@tangle-network/agent-eval'\n\n/**\n * Bridge the app-tool side channel's produced events into the runtime-event\n * shape agent-eval's `extractProducedState` reads. Pipe it:\n * `verifyCompletion(taskGold, extractProducedState(producedFromToolEvents(events)), checker)`\n */\nexport function producedFromToolEvents(events: readonly AppToolProducedEvent[]): RuntimeEventLike[] {\n return events.map((e) =>\n e.type === 'proposal_created'\n ? { type: 'proposal_created', proposalId: e.proposalId, title: e.title, status: e.status, content: e.content }\n : { type: 'artifact', artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: 'text/markdown', content: e.content },\n )\n}\n\nconst STOPWORDS = new Set(['the', 'a', 'an', 'and', 'or', 'for', 'to', 'of', 'in', 'on', 'with', 'review', 'update', 'new', 'proposed'])\n\n/**\n * A deterministic `CorrectnessChecker` (agent-eval exports only\n * `createLlmCorrectnessChecker`). A produced item fulfils a requirement when\n * its content is substantive and recalls ≥ `minRecall` of the requirement\n * title's significant tokens. No network — the default gate for apps/tests\n * without an LLM judge. Pass to `verifyCompletion` as the checker.\n */\nexport function createTokenRecallChecker(opts: { minRecall?: number; minContentLength?: number } = {}): (\n requirement: CompletionRequirement,\n content: string,\n) => Promise<{ correct: boolean; reason: string }> {\n const minRecall = opts.minRecall ?? 0.5\n const minLen = opts.minContentLength ?? 120\n return async (requirement, content) => {\n const body = content.trim()\n if (body.length < minLen) return { correct: false, reason: `content too thin (${body.length} chars) to be the deliverable` }\n const tokens = requirement.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t))\n if (tokens.length === 0) return { correct: true, reason: 'requirement title has no significant tokens — structural match accepted' }\n const lower = body.toLowerCase()\n const hits = tokens.filter((t) => lower.includes(t)).length\n const recall = hits / tokens.length\n return recall >= minRecall\n ? { correct: true, reason: `content recalls ${hits}/${tokens.length} requirement tokens` }\n : { correct: false, reason: `content recalls only ${hits}/${tokens.length} requirement tokens` }\n }\n}\n"],"mappings":";AA2BA,SAAS,kBAAkB,sBAAsB,mBAAmB,mCAAmC;AAgBhG,SAAS,uBAAuB,QAA6D;AAClG,SAAO,OAAO;AAAA,IAAI,CAAC,MACjB,EAAE,SAAS,qBACP,EAAE,MAAM,oBAAoB,YAAY,EAAE,YAAY,OAAO,EAAE,OAAO,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,IAC3G,EAAE,MAAM,YAAY,YAAY,SAAS,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,KAAK,WAAW,EAAE,IAAI,IAAI,UAAU,iBAAiB,SAAS,EAAE,QAAQ;AAAA,EAC/I;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,OAAO,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,QAAQ,UAAU,UAAU,OAAO,UAAU,CAAC;AAShI,SAAS,yBAAyB,OAA0D,CAAC,GAGjD;AACjD,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,oBAAoB;AACxC,SAAO,OAAO,aAAa,YAAY;AACrC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,SAAS,OAAQ,QAAO,EAAE,SAAS,OAAO,QAAQ,qBAAqB,KAAK,MAAM,gCAAgC;AAC3H,UAAM,SAAS,YAAY,MAAM,YAAY,EAAE,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;AAClH,QAAI,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM,QAAQ,+EAA0E;AACnI,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,OAAO,OAAO,OAAO,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE;AACrD,UAAM,SAAS,OAAO,OAAO;AAC7B,WAAO,UAAU,YACb,EAAE,SAAS,MAAM,QAAQ,mBAAmB,IAAI,IAAI,OAAO,MAAM,sBAAsB,IACvF,EAAE,SAAS,OAAO,QAAQ,wBAAwB,IAAI,IAAI,OAAO,MAAM,sBAAsB;AAAA,EACnG;AACF;","names":[]}
1
+ {"version":3,"sources":["../../src/eval/index.ts","../../src/eval/calibration.ts"],"sourcesContent":["/**\n * Eval — the app-shell BRIDGE to `@tangle-network/agent-eval`, not a reimpl.\n *\n * The completion/scoring ENGINE lives in agent-eval (a peer dependency):\n * `verifyCompletion`, `extractProducedState`, `weightedComposite`,\n * `createLlmCorrectnessChecker`, and the `CompletionRequirement` / `TaskGold` /\n * `ProducedState` types — all re-exported here so a consumer has one import\n * root. This module adds only what agent-eval doesn't have and what is\n * app-shell-specific:\n *\n * 1. {@link producedFromToolEvents} — the bridge: turn the structured app-tool\n * side channel's `AppToolProducedEvent`s (from a tool runtime executor's\n * `onProduced`) into the `RuntimeEventLike`s agent-eval's\n * `extractProducedState` consumes. This is the one piece that knows about\n * the app-tool channel, so it belongs here, not in the engine.\n * 2. {@link createTokenRecallChecker} — a deterministic, no-LLM\n * `CorrectnessChecker` (agent-eval ships only the LLM one). For apps/tests\n * that gate completion without a judge call.\n *\n * Full campaigns (persona simulation, traces, scorecards, held-out gates) are\n * agent-eval's `runEvalCampaign` / `AgentDriver` / `BenchmarkRunner` — use them\n * directly; this module composes with them.\n */\nimport type { RuntimeEventLike, CompletionRequirement } from '@tangle-network/agent-eval'\nimport type { AppToolProducedEvent } from '../tools/types'\n\n// Re-export the engine so consumers import completion + scoring from one place.\nexport { verifyCompletion, extractProducedState, weightedComposite, createLlmCorrectnessChecker } from '@tangle-network/agent-eval'\n\n// Calibration: prove a gate can FAIL before believing it PASSED, and prove a\n// probe can SEE before believing the zero it reported. Domain-free, so it\n// calibrates a citation gate, a form audit, or a benchmark equally.\nexport { calibrateGate, assertGateDiscriminates, measureWithControl } from './calibration'\nexport type { CalibrationCase, CalibrationOutcome, CalibrationReport, GateFn, ProbeReport } from './calibration'\nexport type {\n CompletionRequirement,\n TaskGold,\n ProducedState,\n SatisfiedBy,\n CompletionVerdict,\n CorrectnessChecker,\n RuntimeEventLike,\n} from '@tangle-network/agent-eval'\n\n/**\n * Bridge the app-tool side channel's produced events into the runtime-event\n * shape agent-eval's `extractProducedState` reads. Pipe it:\n * `verifyCompletion(taskGold, extractProducedState(producedFromToolEvents(events)), checker)`\n */\nexport function producedFromToolEvents(events: readonly AppToolProducedEvent[]): RuntimeEventLike[] {\n return events.map((e) =>\n e.type === 'proposal_created'\n ? { type: 'proposal_created', proposalId: e.proposalId, title: e.title, status: e.status, content: e.content }\n : { type: 'artifact', artifactId: `vault:${e.path}`, name: e.path, uri: `vault://${e.path}`, mimeType: 'text/markdown', content: e.content },\n )\n}\n\nconst STOPWORDS = new Set(['the', 'a', 'an', 'and', 'or', 'for', 'to', 'of', 'in', 'on', 'with', 'review', 'update', 'new', 'proposed'])\n\n/**\n * A deterministic `CorrectnessChecker` (agent-eval exports only\n * `createLlmCorrectnessChecker`). A produced item fulfils a requirement when\n * its content is substantive and recalls ≥ `minRecall` of the requirement\n * title's significant tokens. No network — the default gate for apps/tests\n * without an LLM judge. Pass to `verifyCompletion` as the checker.\n */\nexport function createTokenRecallChecker(opts: { minRecall?: number; minContentLength?: number } = {}): (\n requirement: CompletionRequirement,\n content: string,\n) => Promise<{ correct: boolean; reason: string }> {\n const minRecall = opts.minRecall ?? 0.5\n const minLen = opts.minContentLength ?? 120\n return async (requirement, content) => {\n const body = content.trim()\n if (body.length < minLen) return { correct: false, reason: `content too thin (${body.length} chars) to be the deliverable` }\n const tokens = requirement.title.toLowerCase().split(/[^a-z0-9]+/).filter((t) => t.length > 2 && !STOPWORDS.has(t))\n if (tokens.length === 0) return { correct: true, reason: 'requirement title has no significant tokens — structural match accepted' }\n const lower = body.toLowerCase()\n const hits = tokens.filter((t) => lower.includes(t)).length\n const recall = hits / tokens.length\n return recall >= minRecall\n ? { correct: true, reason: `content recalls ${hits}/${tokens.length} requirement tokens` }\n : { correct: false, reason: `content recalls only ${hits}/${tokens.length} requirement tokens` }\n }\n}\n","/**\n * Calibration — prove a gate can FAIL before believing that it PASSED, and\n * prove a probe can SEE before believing the zero it reported.\n *\n * Every expensive failure this package has shipped was a check that verified\n * its own input instead of the world, and reported green:\n *\n * - `quote_verification 16/16` passed a work product whose nine citations\n * claimed a figure the cited document never mentions.\n * - `audit_form passed=3 failed=0` passed a form fill whose values landed in\n * \"Combat zone\" — the audit re-read the field paths the writer had just\n * invented, so a wrong path could not be wrong.\n * - `evidence_coverage 0/0` passed vacuously because the artifact it was\n * counting targets from was null.\n * - A benchmark reported 6/8 for the product while a bare\n * \"You are a helpful assistant.\" scored 8/8 on the same cases.\n *\n * A gate is only evidence if it REJECTS something. A metric is only evidence\n * if a worse system scores worse on it. Neither property is implied by a\n * green run, and neither is visible in the output — which is why both have to\n * be asserted separately, in code, next to the gate.\n *\n * This module is deliberately domain-free: a \"gate\" is any predicate over any\n * input. Products supply the known-good and known-bad cases, because only the\n * product knows what bad looks like in its domain.\n */\n\n/** A case whose verdict is known in advance, used to calibrate a gate. */\nexport interface CalibrationCase<TInput> {\n /** What this case represents, e.g. `'quote that does not occur in the source'`. */\n readonly label: string\n readonly input: TInput\n /** `'reject'` — the gate MUST refuse this. `'accept'` — it MUST allow it. */\n readonly expect: 'accept' | 'reject'\n}\n\nexport interface CalibrationOutcome {\n readonly label: string\n readonly expected: 'accept' | 'reject'\n readonly actual: 'accept' | 'reject'\n readonly ok: boolean\n /** Set when the gate threw; a throw counts as `'reject'`. */\n readonly threw?: string\n}\n\nexport interface CalibrationReport {\n /** True only when every case matched AND both controls were present. */\n readonly discriminates: boolean\n readonly outcomes: readonly CalibrationOutcome[]\n readonly failures: readonly CalibrationOutcome[]\n /** Why the gate is not trustworthy. Absent when `discriminates` is true. */\n readonly reason?: string\n}\n\n/**\n * A gate under calibration. Returning `false` OR throwing both count as a\n * rejection — a fail-loud gate (`ToolInputError`) and a boolean gate calibrate\n * through the same path.\n */\nexport type GateFn<TInput> = (input: TInput) => boolean | Promise<boolean>\n\n/**\n * Run a gate against cases whose verdicts are known, and report whether it\n * actually discriminates.\n *\n * Requires BOTH controls:\n * - at least one `'reject'` case — without it a gate that returns `true`\n * unconditionally is indistinguishable from a working one. This is the\n * control that all four failures above were missing.\n * - at least one `'accept'` case — without it a gate that refuses everything\n * scores perfectly, and an unsatisfiable gate does not stop bad work, it\n * selects for invented work (measured: 38 fabricated citations written to\n * clear a coverage gate no honest answer could satisfy).\n */\nexport async function calibrateGate<TInput>(\n gate: GateFn<TInput>,\n cases: readonly CalibrationCase<TInput>[],\n): Promise<CalibrationReport> {\n const outcomes: CalibrationOutcome[] = []\n for (const c of cases) {\n let actual: 'accept' | 'reject'\n let threw: string | undefined\n try {\n actual = (await gate(c.input)) ? 'accept' : 'reject'\n } catch (err) {\n actual = 'reject'\n threw = err instanceof Error ? err.message : String(err)\n }\n outcomes.push({ label: c.label, expected: c.expect, actual, ok: actual === c.expect, ...(threw ? { threw } : {}) })\n }\n\n const failures = outcomes.filter((o) => !o.ok)\n const hasNegative = cases.some((c) => c.expect === 'reject')\n const hasPositive = cases.some((c) => c.expect === 'accept')\n\n const reason = !hasNegative\n ? 'no negative control: every case expects acceptance, so a gate that never refuses would score perfectly'\n : !hasPositive\n ? 'no positive control: every case expects rejection, so a gate that refuses everything would score perfectly'\n : failures.length > 0\n ? `${failures.length}/${outcomes.length} cases disagreed: ${failures.map((f) => `${f.label} expected ${f.expected}, got ${f.actual}`).join('; ')}`\n : undefined\n\n return { discriminates: reason === undefined, outcomes, failures, ...(reason ? { reason } : {}) }\n}\n\n/**\n * `calibrateGate`, but throws instead of reporting. Use in a test or at wiring\n * time so an uncalibrated gate cannot ship silently.\n */\nexport async function assertGateDiscriminates<TInput>(\n name: string,\n gate: GateFn<TInput>,\n cases: readonly CalibrationCase<TInput>[],\n): Promise<CalibrationReport> {\n const report = await calibrateGate(gate, cases)\n if (!report.discriminates) throw new Error(`gate \"${name}\" is not evidence — ${report.reason}`)\n return report\n}\n\nexport interface ProbeReport<TValue> {\n /** True when the positive control registered something, so a zero is real. */\n readonly canSee: boolean\n readonly measured: number\n readonly control: number\n readonly value: TValue\n readonly reason?: string\n}\n\n/**\n * Measure something, but only after proving the instrument can register a\n * non-zero — because an absence is a claim about the measurement first.\n *\n * Six blind probes were mistaken for real zeros in a single day: a SQL `LIKE`\n * over a column the product encrypts (which equally returned 0 for `\"text\"`\n * across every row — the tell), a `grep` run against worktrees on stale\n * branches, a status-code comparison that never read the response bodies, a\n * live event count taken from a run that was never dispatched, `rg --hidden`\n * silently exiting 2 because `rg` was aliased to `grep`, and an intercepted\n * `git show` returning an empty diff. Each nearly caused a wrong fix.\n *\n * `control` must count something that MUST exist. If it counts zero, the\n * measurement is unusable regardless of what `measure` returned.\n */\nexport async function measureWithControl<TValue>(opts: {\n readonly measure: () => TValue | Promise<TValue>\n /** Must return a value whose count is non-zero, or the probe is blind. */\n readonly control: () => TValue | Promise<TValue>\n readonly count: (value: TValue) => number\n /** Describes what the control counts, for the failure message. */\n readonly controlLabel: string\n}): Promise<ProbeReport<TValue>> {\n const value = await opts.measure()\n const controlValue = await opts.control()\n const measured = opts.count(value)\n const control = opts.count(controlValue)\n return control > 0\n ? { canSee: true, measured, control, value }\n : {\n canSee: false,\n measured,\n control,\n value,\n reason: `probe is blind: the positive control (${opts.controlLabel}) counted 0, so the measured ${measured} carries no information`,\n }\n}\n"],"mappings":";AA2BA,SAAS,kBAAkB,sBAAsB,mBAAmB,mCAAmC;;;AC+CvG,eAAsB,cACpB,MACA,OAC4B;AAC5B,QAAM,WAAiC,CAAC;AACxC,aAAW,KAAK,OAAO;AACrB,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,eAAU,MAAM,KAAK,EAAE,KAAK,IAAK,WAAW;AAAA,IAC9C,SAAS,KAAK;AACZ,eAAS;AACT,cAAQ,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,IACzD;AACA,aAAS,KAAK,EAAE,OAAO,EAAE,OAAO,UAAU,EAAE,QAAQ,QAAQ,IAAI,WAAW,EAAE,QAAQ,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,EAAG,CAAC;AAAA,EACpH;AAEA,QAAM,WAAW,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE;AAC7C,QAAM,cAAc,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ;AAC3D,QAAM,cAAc,MAAM,KAAK,CAAC,MAAM,EAAE,WAAW,QAAQ;AAE3D,QAAM,SAAS,CAAC,cACZ,2GACA,CAAC,cACC,+GACA,SAAS,SAAS,IAChB,GAAG,SAAS,MAAM,IAAI,SAAS,MAAM,qBAAqB,SAAS,IAAI,CAAC,MAAM,GAAG,EAAE,KAAK,aAAa,EAAE,QAAQ,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,IAAI,CAAC,KAC9I;AAER,SAAO,EAAE,eAAe,WAAW,QAAW,UAAU,UAAU,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG;AAClG;AAMA,eAAsB,wBACpB,MACA,MACA,OAC4B;AAC5B,QAAM,SAAS,MAAM,cAAc,MAAM,KAAK;AAC9C,MAAI,CAAC,OAAO,cAAe,OAAM,IAAI,MAAM,SAAS,IAAI,4BAAuB,OAAO,MAAM,EAAE;AAC9F,SAAO;AACT;AA0BA,eAAsB,mBAA2B,MAOhB;AAC/B,QAAM,QAAQ,MAAM,KAAK,QAAQ;AACjC,QAAM,eAAe,MAAM,KAAK,QAAQ;AACxC,QAAM,WAAW,KAAK,MAAM,KAAK;AACjC,QAAM,UAAU,KAAK,MAAM,YAAY;AACvC,SAAO,UAAU,IACb,EAAE,QAAQ,MAAM,UAAU,SAAS,MAAM,IACzC;AAAA,IACE,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,yCAAyC,KAAK,YAAY,gCAAgC,QAAQ;AAAA,EAC5G;AACN;;;ADpHO,SAAS,uBAAuB,QAA6D;AAClG,SAAO,OAAO;AAAA,IAAI,CAAC,MACjB,EAAE,SAAS,qBACP,EAAE,MAAM,oBAAoB,YAAY,EAAE,YAAY,OAAO,EAAE,OAAO,QAAQ,EAAE,QAAQ,SAAS,EAAE,QAAQ,IAC3G,EAAE,MAAM,YAAY,YAAY,SAAS,EAAE,IAAI,IAAI,MAAM,EAAE,MAAM,KAAK,WAAW,EAAE,IAAI,IAAI,UAAU,iBAAiB,SAAS,EAAE,QAAQ;AAAA,EAC/I;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,OAAO,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,MAAM,QAAQ,UAAU,UAAU,OAAO,UAAU,CAAC;AAShI,SAAS,yBAAyB,OAA0D,CAAC,GAGjD;AACjD,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,SAAS,KAAK,oBAAoB;AACxC,SAAO,OAAO,aAAa,YAAY;AACrC,UAAM,OAAO,QAAQ,KAAK;AAC1B,QAAI,KAAK,SAAS,OAAQ,QAAO,EAAE,SAAS,OAAO,QAAQ,qBAAqB,KAAK,MAAM,gCAAgC;AAC3H,UAAM,SAAS,YAAY,MAAM,YAAY,EAAE,MAAM,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;AAClH,QAAI,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,MAAM,QAAQ,+EAA0E;AACnI,UAAM,QAAQ,KAAK,YAAY;AAC/B,UAAM,OAAO,OAAO,OAAO,CAAC,MAAM,MAAM,SAAS,CAAC,CAAC,EAAE;AACrD,UAAM,SAAS,OAAO,OAAO;AAC7B,WAAO,UAAU,YACb,EAAE,SAAS,MAAM,QAAQ,mBAAmB,IAAI,IAAI,OAAO,MAAM,sBAAsB,IACvF,EAAE,SAAS,OAAO,QAAQ,wBAAwB,IAAI,IAAI,OAAO,MAAM,sBAAsB;AAAA,EACnG;AACF;","names":[]}
@@ -1,5 +1,5 @@
1
- import { a as AppToolContext, b as AppToolName, c as AppToolDefinition } from './types-BCxK0wyS.js';
2
- import { T as ToolHeaderNames } from './auth-anc7mv2W.js';
1
+ import { A as AppToolContext, a as AppToolName, b as AppToolDefinition } from './types-DbU-oO5h.js';
2
+ import { T as ToolHeaderNames } from './auth-DJs6lfAs.js';
3
3
 
4
4
  /** Default route path each app tool is served at. A product mounts its routes
5
5
  * at these paths (or supplies its own via {@link BuildMcpServerOptions.paths}). */
@@ -1,6 +1,6 @@
1
1
  import { KeyProvisioner, KeyCrypto, WorkspaceKeyManager, WorkspaceKeyStore } from '../billing/index.js';
2
2
  import { KnowledgeStateAccessor } from '../knowledge/index.js';
3
- import { d as AppToolHandlers } from '../types-BCxK0wyS.js';
3
+ import { d as AppToolHandlers } from '../types-DbU-oO5h.js';
4
4
  import { KvLike } from '../web/index.js';
5
5
  import '@tangle-network/agent-eval';
6
6
 
@@ -4,9 +4,9 @@ import * as _tangle_network_agent_runtime from '@tangle-network/agent-runtime';
4
4
  import { ToolLoopMessage } from '@tangle-network/agent-runtime';
5
5
  export { RunToolLoopOptions as AppToolLoopOptions, ToolLoopAssistantToolCall as LoopAssistantToolCall, ToolLoopMessage as LoopMessage, ToolLoopCall as LoopToolCall, StreamToolLoopOptions as StreamAppToolLoopOptions, StreamToolLoopYield as StreamLoopYield, ToolLoopEvent, ToolLoopResult, ToolLoopStopReason, runToolLoop as runAppToolLoop, streamToolLoop as streamAppToolLoop } from '@tangle-network/agent-runtime';
6
6
  import { CertifiedProfile } from '@tangle-network/agent-runtime/intelligence';
7
- import { A as AppToolMcpServer } from '../mcp-D3qVzbE1.js';
8
- import '../types-BCxK0wyS.js';
9
- import '../auth-anc7mv2W.js';
7
+ import { A as AppToolMcpServer } from '../mcp-Dt4V4ZLT.js';
8
+ import '../types-DbU-oO5h.js';
9
+ import '../auth-DJs6lfAs.js';
10
10
 
11
11
  /**
12
12
  * Events the app's OpenAI-compat stream adapter ({@link toLoopEvents}) yields.
@@ -1,8 +1,8 @@
1
1
  import { SandboxInstance, ProvisionEvent, StorageConfig, ScopedTokenScope, TurnDriveResult, Sandbox, MintScopedTokenOptions } from '@tangle-network/sandbox';
2
2
  export { StorageConfig } from '@tangle-network/sandbox';
3
3
  import { AgentProfileMcpServer, AgentProfileFileMount, AgentProfile } from '@tangle-network/agent-interface';
4
- import { T as ToolHeaderNames } from '../auth-anc7mv2W.js';
5
- import { b as AppToolName, a as AppToolContext } from '../types-BCxK0wyS.js';
4
+ import { T as ToolHeaderNames } from '../auth-DJs6lfAs.js';
5
+ import { a as AppToolName, A as AppToolContext } from '../types-DbU-oO5h.js';
6
6
  import { Harness } from '../harness/index.js';
7
7
  import { a as TangleExecutionEnvironment } from '../model-CdCDfBA9.js';
8
8
  import { b as ProfileFingerprint, C as ComposeProfileBudget } from '../budget-BOucfcb_.js';
@@ -369,6 +369,29 @@ interface SandboxApiCredentials {
369
369
  baseUrl: string;
370
370
  apiKey: string;
371
371
  }
372
+ /**
373
+ * Build the sandbox API's sidecar-proxy base for a box:
374
+ * `{baseUrl}/v1/sidecar-proxy/{sandboxId}`.
375
+ *
376
+ * This is the ONLY upstream that serves the interactive terminal. Measured on
377
+ * production (`sandbox.tangle.tools`, one box, `ws` client, same credential in
378
+ * every arm):
379
+ *
380
+ * | upstream base | result |
381
+ * |----------------------------------------|---------------------------------|
382
+ * | `/v1/sidecar-proxy/{id}` | 101 -> `ready` 2551ms -> shell |
383
+ * | `/v1/sandboxes/{id}/runtime/` | HTTP 500 |
384
+ * | `connection.runtimeUrl` (the box host) | 101 then close 1000, 0 bytes |
385
+ *
386
+ * The box's own `connection.runtimeUrl` (`https://sandbox-*.tangle.sh`) accepts
387
+ * the upgrade — its Caddy front end upgrades every path, including ones that do
388
+ * not exist — and then hangs up without a PTY. A 101 from that host therefore
389
+ * proves nothing; only a `ready` control frame does. Two products shipped a
390
+ * terminal against it and rendered a permanent spinner.
391
+ *
392
+ * Exported so no product writes the path literal a fourth time.
393
+ */
394
+ declare function sandboxSidecarProxyUrl(baseUrl: string, sandboxId: string): string;
372
395
  /** Define a connection configuration for sandbox runtime including URL and optional server-side auth token */
373
396
  interface SandboxRuntimeConnection {
374
397
  runtimeUrl: string;
@@ -464,6 +487,42 @@ interface WorkspaceSandboxTerminalUpgradeHandlerOptions {
464
487
  * ```
465
488
  */
466
489
  declare function createWorkspaceSandboxTerminalUpgradeHandler(opts: WorkspaceSandboxTerminalUpgradeHandlerOptions): (request: Request) => Promise<Response | null>;
490
+ /** A response-like shape carrying just what the subprotocol echo decision reads. */
491
+ interface TerminalUpgradeResponseLike {
492
+ status: number;
493
+ statusText?: string;
494
+ headers: Headers;
495
+ }
496
+ /**
497
+ * Decide whether a terminal upgrade's 101 needs the browser's own subprotocol
498
+ * echoed back onto it, and return the headers to answer with. `null` means
499
+ * "pass the upstream response through untouched".
500
+ *
501
+ * Why this exists: the browser's terminal credential rides in a
502
+ * `bearer.<base64url>` WebSocket subprotocol, because a browser cannot set
503
+ * `Authorization` on a WS handshake. That subprotocol is a browser-to-Worker
504
+ * credential, so it is stripped before the upstream hop — and the upstream then
505
+ * answers the 101 selecting nothing. A browser MUST fail the connection when a
506
+ * 101 selects no subprotocol after it offered one (RFC 6455 s4.1), so the socket
507
+ * dies on open and the terminal renders a spinner forever.
508
+ *
509
+ * Kept as a pure function because a 101 `Response` cannot be constructed off
510
+ * Workers, so this is the only part of the decision a test can drive directly.
511
+ */
512
+ declare function terminalUpgradeSubprotocolEcho(upstream: TerminalUpgradeResponseLike, browserProtocol: string | null): {
513
+ status: number;
514
+ statusText: string;
515
+ headers: Headers;
516
+ } | null;
517
+ /**
518
+ * The exact `bearer.*` subprotocol string the browser offered, so it can be
519
+ * echoed verbatim on the 101. Returns null when the browser offered none.
520
+ *
521
+ * Takes the raw `Sec-WebSocket-Protocol` value rather than the `Headers`, to
522
+ * match its siblings `bearerSubprotocolToken` and `stripBearerSubprotocol` —
523
+ * one shape for the whole family, and the caller reads the header once.
524
+ */
525
+ declare function selectedBearerSubprotocol(value: string | null): string | null;
467
526
  /** Build proxy headers for sandbox runtime including authorization and forwarded headers */
468
527
  declare function buildSandboxRuntimeProxyHeaders(source: Headers, sandboxApiKey: string, forwardHeaders?: string[]): Headers;
469
528
  /** Encode a runtime path by URI-encoding each valid segment and returning null for invalid segments */
@@ -475,6 +534,199 @@ declare function bearerSubprotocolToken(value: string | null): string | null;
475
534
  /** Resolve the terminal token from request headers using Authorization or Sec-WebSocket-Protocol fields */
476
535
  declare function terminalTokenFromRequest(headers: Headers): string | null;
477
536
 
537
+ /**
538
+ * `createSandboxPrewarmer` — "this user just opened this project; start warming
539
+ * their box" as a shell primitive, so every agent-app product gets the same
540
+ * answer instead of forking one.
541
+ *
542
+ * WHY THIS IS SHELL, NOT ENGINE. The engine rule asks whether the capability
543
+ * makes sense without a specific app's side channel. "Warm a box" does — but
544
+ * `@tangle-network/sandbox` has no notion of a WORKSPACE. It keys boxes by an
545
+ * opaque sandbox id; the workspace→box mapping, the harness match, and the
546
+ * profile materialisation all live in `ensureWorkspaceSandbox` here. A
547
+ * prewarmer is that mapping plus a scheduling policy, so it belongs beside it.
548
+ * It is deliberately NOT a new subpath: it composes `peekWorkspaceSandbox` and
549
+ * `ensureWorkspaceSandbox` directly and needs exactly the peers `/sandbox`
550
+ * already needs, so a separate entry would add a second place to look for "how
551
+ * do I get a box" and buy no peer isolation (the reason `/work-product-react`
552
+ * is split out).
553
+ *
554
+ * WHAT IT IS NOT. It does not make cold starts fast — they already are.
555
+ * Measured on the real platform (staging-sandbox, n=5, 2026-07-28): a box goes
556
+ * from nothing to terminal-ready in 2.34–3.19 s (median 2.73 s), and an
557
+ * already-running box answers in 1.16–2.29 s (median 1.35 s). Prewarming buys
558
+ * ~1.4 s. The reason it matters is not latency: it is that a product which
559
+ * only ever provisions lazily, on a path whose guard never passes, never
560
+ * provisions AT ALL — and the UI then shows a spinner over a box that does not
561
+ * exist and is not being created. That is the failure this primitive removes.
562
+ *
563
+ * ── COST POSTURE (read before adopting) ────────────────────────────────────
564
+ * A warmed box is a REAL charge. It bills from creation until the platform's
565
+ * idle timeout reclaims it — `SandboxRuntimeConfig`'s create-time
566
+ * `idleTimeoutSeconds`, not anything this module sets. A product warming on
567
+ * every project open pays that timeout for every user who opens and bounces.
568
+ * With a 3600 s idle timeout against a ~131 s mean session life, a bounce
569
+ * costs an hour of box time to save ~1.4 s. THAT TRADE IS USUALLY WRONG.
570
+ *
571
+ * So the levers are explicit and the defaults are the cheap ones:
572
+ * - `mode: 'resume-only'` (DEFAULT) never creates a box that does not exist.
573
+ * It only revives one the user already has, so the spend is bounded by
574
+ * boxes the user already caused. This is the safe fleet default.
575
+ * - `mode: 'create-or-resume'` is the owner-requested behaviour — warm on
576
+ * open even for a first-time user. Opt in per product, and lower the
577
+ * shell's `idleTimeoutSeconds` when you do.
578
+ * - `shouldPrewarm(scope)` is the product's own policy hook (paid tier only,
579
+ * returning user only, has-documents only …). Returning false costs nothing.
580
+ * - `failureCooldownMs` stops a hard-failing workspace from retry-storming;
581
+ * every retry is another create attempt, which is more spend.
582
+ * Warm with the SAME harness the next turn will use. `ensureWorkspaceSandbox`
583
+ * DELETES and recreates a name-matched box whose harness differs, so warming
584
+ * `opencode` and then turning `claude-code` pays for two boxes and is slower
585
+ * than not warming at all. The prewarm key includes the harness so the two are
586
+ * never deduped into one.
587
+ *
588
+ * ── SINGLE-FLIGHT (measured, not assumed) ──────────────────────────────────
589
+ * The sandbox platform does NOT dedupe by box name. Two concurrent
590
+ * `POST /v1/sandboxes` with an identical name both returned HTTP 201 and left
591
+ * two running boxes (verified against staging-sandbox, 2026-07-28). So two
592
+ * tabs, or two isolates, racing a warm genuinely leak a box — a prewarm that
593
+ * races is worse than no prewarm. Hence two layers:
594
+ * 1. an in-process map, which is free and catches same-isolate races
595
+ * (double-mount, two requests on one isolate);
596
+ * 2. a `claim` store the product supplies, which is the only thing that can
597
+ * make this correct ACROSS isolates — the usual deployment target here is
598
+ * Cloudflare Workers, where "same isolate" guarantees nothing.
599
+ * `claim` is REQUIRED, with `'single-isolate-only'` as the explicit opt-out,
600
+ * so nobody gets the unsafe behaviour by forgetting a field. Say it out loud
601
+ * or supply a store.
602
+ *
603
+ * ── FAILURE IS LOUD, NEVER FATAL ───────────────────────────────────────────
604
+ * A failed warm degrades to exactly today's lazy path: the next real request
605
+ * calls `ensureWorkspaceSandbox` itself. It never throws into the caller's
606
+ * render path — `completion` RESOLVES with `{ ok: false }` rather than
607
+ * rejecting, because an unhandled rejection handed to `waitUntil` can fail the
608
+ * request it rode in on. But it is never silent: every failure fires
609
+ * `onEvent({ type: 'failed' })` and is readable afterwards through
610
+ * `readiness()` as `{ status: 'failed' }`. The bug class this whole module
611
+ * exists to kill is a soft failure that surfaces as an unusable panel ten
612
+ * minutes later, so a warm that dies must leave a trace a product can render.
613
+ */
614
+
615
+ /** The workspace a warm targets. Mirrors `EnsureWorkspaceSandboxOptions`'
616
+ * identity fields — the prewarmer forwards them verbatim so a warmed box is
617
+ * byte-identical to the one the lazy path would have built. */
618
+ interface SandboxPrewarmScope {
619
+ workspaceId: string;
620
+ userId?: string;
621
+ /** Must match the harness the next turn will use — see the cost note above. */
622
+ harness: Harness;
623
+ billingOwnerId?: string;
624
+ }
625
+ /**
626
+ * Cross-isolate claim. `acquire` must be atomic (a D1 conditional insert, a DO,
627
+ * a KV `put` with `onlyIf`) — a read-then-write is exactly the race this exists
628
+ * to close. `ttlSeconds` bounds a claim leaked by an isolate that died
629
+ * mid-warm; without expiry a single crash wedges a workspace forever.
630
+ */
631
+ interface PrewarmClaimStore {
632
+ /** True when THIS caller now owns the right to warm `key`. */
633
+ acquire(key: string, ttlSeconds: number): Promise<boolean>;
634
+ /** Best-effort release. A throw here is swallowed — the TTL is the backstop. */
635
+ release(key: string): Promise<void>;
636
+ /** Optional: lets `readiness()` report `warming` for a warm running in
637
+ * ANOTHER isolate. Without it, `warming` is only visible in the isolate
638
+ * that started it, and every other one reports `absent`. */
639
+ isHeld?(key: string): Promise<boolean>;
640
+ }
641
+ /** What `prewarm()` decided. Every value except `started` means no box was
642
+ * created and nothing was spent on this call. */
643
+ type PrewarmOutcome = 'started' | 'already-running' | 'already-warming' | 'warming-elsewhere' | 'declined-by-policy' | 'cooling-down' | 'absent-and-resume-only';
644
+ /** Terminal result of a warm this caller owns. Never a rejection. */
645
+ interface PrewarmResult {
646
+ ok: boolean;
647
+ boxId?: string;
648
+ error?: string;
649
+ /** Wall time of the warm itself, for the product's own timing trace. */
650
+ ms: number;
651
+ }
652
+ interface PrewarmDecision {
653
+ outcome: PrewarmOutcome;
654
+ /** Present ONLY when `outcome === 'started'`. Hand it to `ctx.waitUntil` so a
655
+ * client disconnect cannot kill the warm. Never rejects. */
656
+ completion?: Promise<PrewarmResult>;
657
+ }
658
+ /** Readiness for the UI. `ready`/`warming` reuse the vocabulary
659
+ * `createSandboxFileIndexRoute` (`/chat-routes`) and `useFileMentions`
660
+ * (`/web-react`) already speak, so a product renders ONE warming state rather
661
+ * than inventing a second spinner for boxes. */
662
+ type SandboxReadiness = {
663
+ status: 'ready';
664
+ boxId: string;
665
+ } | {
666
+ status: 'warming';
667
+ } | {
668
+ status: 'absent';
669
+ } | {
670
+ status: 'failed';
671
+ error: string;
672
+ retryAfterMs: number;
673
+ };
674
+ type PrewarmEvent = {
675
+ type: 'started';
676
+ key: string;
677
+ workspaceId: string;
678
+ } | {
679
+ type: 'succeeded';
680
+ key: string;
681
+ workspaceId: string;
682
+ boxId: string;
683
+ ms: number;
684
+ } | {
685
+ type: 'failed';
686
+ key: string;
687
+ workspaceId: string;
688
+ error: string;
689
+ ms: number;
690
+ } | {
691
+ type: 'skipped';
692
+ key: string;
693
+ workspaceId: string;
694
+ outcome: PrewarmOutcome;
695
+ };
696
+ interface SandboxPrewarmerOptions {
697
+ /** Cross-isolate single-flight, or the explicit acknowledgement that you are
698
+ * accepting per-isolate dedupe only. No default — see the header. */
699
+ claim: PrewarmClaimStore | 'single-isolate-only';
700
+ /** `'resume-only'` (default) never creates a box that does not exist.
701
+ * `'create-or-resume'` warms from nothing — the expensive one. */
702
+ mode?: 'resume-only' | 'create-or-resume';
703
+ /** Product policy gate. Not called when a box is already running. */
704
+ shouldPrewarm?(scope: SandboxPrewarmScope): boolean | Promise<boolean>;
705
+ /** Observability seam. A failed warm MUST be visible somewhere. */
706
+ onEvent?(event: PrewarmEvent): void;
707
+ /** Claim lifetime. Default 180 s — comfortably over a cold create. */
708
+ claimTtlSeconds?: number;
709
+ /** Suppress re-warming a workspace that just failed. Default 60_000 ms. */
710
+ failureCooldownMs?: number;
711
+ /** Clock seam for tests. */
712
+ now?(): number;
713
+ }
714
+ interface SandboxPrewarmer {
715
+ /**
716
+ * Non-blocking warm. The returned promise settles as soon as the DECISION is
717
+ * known (at most one `list()` against the platform, plus a claim `acquire`);
718
+ * the provisioning itself rides on `completion`. On a render path either
719
+ * ignore the returned promise or run the whole call inside `waitUntil` — do
720
+ * not await `completion` before responding.
721
+ */
722
+ prewarm(scope: SandboxPrewarmScope): Promise<PrewarmDecision>;
723
+ /** Zero-provisioning status read for a UI. Never creates or resumes. */
724
+ readiness(scope: SandboxPrewarmScope): Promise<SandboxReadiness>;
725
+ /** Clear a recorded failure so the next `prewarm` retries immediately. */
726
+ clearFailure(scope: SandboxPrewarmScope): void;
727
+ }
728
+ declare function createSandboxPrewarmer(shell: SandboxRuntimeConfig, options: SandboxPrewarmerOptions): SandboxPrewarmer;
729
+
478
730
  /** Define client credentials for accessing the sandbox environment with API key and base URL */
479
731
  interface SandboxClientCredentials {
480
732
  apiKey: string;
@@ -976,4 +1228,4 @@ declare function isTerminalPromptEvent(event: unknown): boolean;
976
1228
  /** Resolve the interactive question text from a structured event or return null if none found */
977
1229
  declare function detectInteractiveQuestion(event: unknown): string | null;
978
1230
 
979
- export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, DEFAULT_SANDBOX_RESOURCES, type DriveSandboxTurnOptions, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type ModelSelection, type ModelSelectionError, type ModelSelectionFailure, type ModelSelectionSource, type Outcome, PROVISION_PAYLOAD_MAX_BYTES, type PeekWorkspaceSandboxOutcome, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, type SandboxExecChannel, type SandboxExecOptions, type SandboxFileBytesOutcome, type SandboxFileSizeOutcome, SandboxModelResolutionError, type SandboxPermissionLevel, SandboxRecoveryFailedError, type SandboxRecoveryPhase, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, type WriteProfileFilesOptions, assertEnvWithinLimits, assertProvisionPayloadWithinCap, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, collectSandboxPromptText, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deferredCorpusHash, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, mintTerminalProxyToken, peekWorkspaceSandbox, readSandboxBinaryBytes, readSecret, requireTransportableModel, resetClientCache, resolveModel, resolveModelSelection, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, shellQuote, splitDeferredProfileFiles, statSandboxFileSize, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };
1231
+ export { type AppToolDescriptor, type AuthenticatedSandboxUser, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, DEFAULT_SANDBOX_RESOURCES, type DriveSandboxTurnOptions, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type ModelSelection, type ModelSelectionError, type ModelSelectionFailure, type ModelSelectionSource, type Outcome, PROVISION_PAYLOAD_MAX_BYTES, type PeekWorkspaceSandboxOutcome, type PrewarmClaimStore, type PrewarmDecision, type PrewarmEvent, type PrewarmOutcome, type PrewarmResult, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SandboxApiCredentials, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, type SandboxExecChannel, type SandboxExecOptions, type SandboxFileBytesOutcome, type SandboxFileSizeOutcome, SandboxModelResolutionError, type SandboxPermissionLevel, type SandboxPrewarmScope, type SandboxPrewarmer, type SandboxPrewarmerOptions, type SandboxReadiness, SandboxRecoveryFailedError, type SandboxRecoveryPhase, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxRuntimeConnection, type SandboxScope, type SandboxStepTransition, type SandboxTerminalTokenOptions, type SandboxTerminalTokenResult, type SandboxTerminalTokenSubject, type SandboxTerminalWsMatch, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalProxyIdentity, type TerminalUpgradeResponseLike, type WorkspaceSandboxConnectionArgs, type WorkspaceSandboxConnectionHandlerOptions, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRuntimeProxyArgs, type WorkspaceSandboxRuntimeProxyHandlerOptions, type WorkspaceSandboxTerminalUpgradeHandlerOptions, type WriteProfileFilesOptions, assertEnvWithinLimits, assertProvisionPayloadWithinCap, attachReasoningEffort, bearerSubprotocolToken, bearerToken, buildAppToolMcpServers, buildSandboxRuntimeProxyHeaders, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, collectSandboxPromptText, createSandboxPrewarmer, createSandboxTerminalToken, createWorkspaceSandboxConnectionHandler, createWorkspaceSandboxManager, createWorkspaceSandboxRuntimeProxyHandler, createWorkspaceSandboxTerminalUpgradeHandler, deferredCorpusHash, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, encodeSandboxRuntimePath, ensureWorkspaceSandbox, flattenHistory, getClient, isSandboxTerminalWsUpgrade, isTerminalPromptEvent, matchSandboxTerminalWsPath, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, mintTerminalProxyToken, peekWorkspaceSandbox, readSandboxBinaryBytes, readSecret, requireTransportableModel, resetClientCache, resolveModel, resolveModelSelection, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxSidecarProxyUrl, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, selectedBearerSubprotocol, shellQuote, splitDeferredProfileFiles, statSandboxFileSize, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, terminalTokenFromRequest, terminalUpgradeSubprotocolEcho, verifySandboxTerminalToken, verifyTerminalProxyToken, writeProfileFilesToBox };
@@ -17,6 +17,7 @@ import {
17
17
  buildSandboxToolPathSetupScript,
18
18
  classifySeveredStream,
19
19
  collectSandboxPromptText,
20
+ createSandboxPrewarmer,
20
21
  createSandboxTerminalToken,
21
22
  createWorkspaceSandboxConnectionHandler,
22
23
  createWorkspaceSandboxManager,
@@ -47,10 +48,12 @@ import {
47
48
  resolveSandboxClientCredentials,
48
49
  runSandboxPrompt,
49
50
  runSandboxToolPathSetup,
51
+ sandboxSidecarProxyUrl,
50
52
  sandboxToolBinDir,
51
53
  sandboxToolPath,
52
54
  sandboxToolRootDir,
53
55
  secretStoreFromClient,
56
+ selectedBearerSubprotocol,
54
57
  shellQuote,
55
58
  splitDeferredProfileFiles,
56
59
  statSandboxFileSize,
@@ -60,10 +63,11 @@ import {
60
63
  syncSandboxMemberRemove,
61
64
  syncSandboxMemberRole,
62
65
  terminalTokenFromRequest,
66
+ terminalUpgradeSubprotocolEcho,
63
67
  verifySandboxTerminalToken,
64
68
  verifyTerminalProxyToken,
65
69
  writeProfileFilesToBox
66
- } from "../chunk-7775L5NN.js";
70
+ } from "../chunk-BAC2B2KI.js";
67
71
  import "../chunk-LWSJK546.js";
68
72
  import "../chunk-CQZSAR77.js";
69
73
  import "../chunk-ICOHEZK6.js";
@@ -90,6 +94,7 @@ export {
90
94
  buildSandboxToolPathSetupScript,
91
95
  classifySeveredStream,
92
96
  collectSandboxPromptText,
97
+ createSandboxPrewarmer,
93
98
  createSandboxTerminalToken,
94
99
  createWorkspaceSandboxConnectionHandler,
95
100
  createWorkspaceSandboxManager,
@@ -120,10 +125,12 @@ export {
120
125
  resolveSandboxClientCredentials,
121
126
  runSandboxPrompt,
122
127
  runSandboxToolPathSetup,
128
+ sandboxSidecarProxyUrl,
123
129
  sandboxToolBinDir,
124
130
  sandboxToolPath,
125
131
  sandboxToolRootDir,
126
132
  secretStoreFromClient,
133
+ selectedBearerSubprotocol,
127
134
  shellQuote,
128
135
  splitDeferredProfileFiles,
129
136
  statSandboxFileSize,
@@ -133,6 +140,7 @@ export {
133
140
  syncSandboxMemberRemove,
134
141
  syncSandboxMemberRole,
135
142
  terminalTokenFromRequest,
143
+ terminalUpgradeSubprotocolEcho,
136
144
  verifySandboxTerminalToken,
137
145
  verifyTerminalProxyToken,
138
146
  writeProfileFilesToBox
@@ -1,10 +1,10 @@
1
1
  import { j as SequenceMediaKind, f as SequenceTimeline, k as TimelineInterval, b as SequenceStore } from '../store-B7LLlk9p.js';
2
2
  export { M as MIN_SEQUENCE_CLIP_FRAMES, N as NewSequenceClip, l as NewSequenceDecision, m as NewSequenceTrack, g as SequenceClip, a as SequenceClipMedia, n as SequenceClipPatch, o as SequenceDecision, d as SequenceExportFormat, h as SequenceExportRecord, p as SequenceExportStatus, q as SequenceFrameSnapshot, i as SequenceMeta, r as SequenceStatus, S as SequenceStoreScope, e as SequenceTrack, c as SequenceTrackKind, T as TimelineClipBounds, s as assertClipFitsSequence, t as chooseCaptionPlacement, u as clampClipDuration, v as clampClipStart, w as formatSeconds, x as formatTimecode, y as framesToSeconds, z as secondsToFrames, A as snapshotFrame, B as trackIntervals } from '../store-B7LLlk9p.js';
3
3
  export { A as AddCaptionOperation, C as CaptionTargetResolution, a as CreateTrackOperation, D as DeleteClipOperation, E as ExtendSequenceOperation, M as MoveClipOperation, P as PlaceClipOperation, Q as QueueExportOperation, S as SEQUENCE_OPERATION_TYPES, b as SequenceApplyResult, c as SequenceOperation, d as SequenceOperationContext, e as SequenceOperationType, f as SequencePlan, g as SetClipDisabledOperation, h as SetClipTextOperation, i as SplitClipOperation, T as TrimClipOperation, j as applySequenceOperation, k as applySequenceOperations, l as assertSequenceMediaUrl, m as captionTrackNameForLanguage, n as lastClipEndFrame, p as parseSequenceOperations, r as resolveCaptionPlacement, o as resolveCaptionTarget, q as resolvePlaceClipTrack, v as validateAddCaption, s as validateCreateTrack, t as validateDeleteClip, u as validateExtendSequence, w as validateMoveClip, x as validatePlaceClip, y as validateQueueExport, z as validateSequenceOperation, B as validateSequenceOperations, F as validateSetClipDisabled, G as validateSetClipText, H as validateSplitClip, I as validateTrimClip } from '../apply-6wlMOLf8.js';
4
- import { S as ScopedMcpServerEntryOptions, A as AppToolMcpServer } from '../mcp-D3qVzbE1.js';
4
+ import { S as ScopedMcpServerEntryOptions, A as AppToolMcpServer } from '../mcp-Dt4V4ZLT.js';
5
5
  export { M as SEQUENCES_MCP_PROTOCOL_VERSIONS } from '../mcp-rpc-CzU5LWWT.js';
6
- import '../types-BCxK0wyS.js';
7
- import '../auth-anc7mv2W.js';
6
+ import '../types-DbU-oO5h.js';
7
+ import '../auth-DJs6lfAs.js';
8
8
 
9
9
  /**
10
10
  * Pure interchange-format builders over `SequenceTimeline` — SRT, WebVTT,