@sema-agent/core 7.50.2 → 7.51.0

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.
Files changed (188) hide show
  1. package/CHANGELOG.md +263 -0
  2. package/dist/agents/child-failure.d.ts +5 -16
  3. package/dist/agents/child-failure.js +4 -24
  4. package/dist/agents/subagent-steps.js +2 -2
  5. package/dist/agents/subagent.d.ts +4 -13
  6. package/dist/agents/subagent.js +94 -204
  7. package/dist/agents/tool-filter.d.ts +2 -2
  8. package/dist/brain/anthropic.d.ts +1 -1
  9. package/dist/brain/anthropic.js +7 -5
  10. package/dist/brain/open-responses.d.ts +1 -1
  11. package/dist/brain/open-responses.js +3 -2
  12. package/dist/brain/openai.d.ts +1 -1
  13. package/dist/brain/openai.js +3 -2
  14. package/dist/brain/request-route.d.ts +2 -2
  15. package/dist/brain/route-adjudicator.d.ts +16 -7
  16. package/dist/brain/route-adjudicator.js +23 -7
  17. package/dist/brain/route-conformance.js +23 -5
  18. package/dist/brain/stream-engine.js +9 -3
  19. package/dist/core/a2a.js +6 -8
  20. package/dist/core/allow-entry.d.ts +45 -26
  21. package/dist/core/allow-entry.js +48 -24
  22. package/dist/core/arg-summary.d.ts +8 -85
  23. package/dist/core/arg-summary.js +4 -386
  24. package/dist/core/ask-origin.d.ts +35 -33
  25. package/dist/core/ask-origin.js +9 -11
  26. package/dist/core/checkpoint-requirements.d.ts +6 -0
  27. package/dist/core/checkpoint-requirements.js +1 -0
  28. package/dist/core/checkpoint-store.d.ts +15 -34
  29. package/dist/core/checkpoint-store.js +5 -4
  30. package/dist/core/classifier-unavailable-arm.d.ts +12 -8
  31. package/dist/core/classifier-unavailable-arm.js +7 -2
  32. package/dist/core/credential-vocabulary.d.ts +3 -2
  33. package/dist/core/credential-vocabulary.js +27 -18
  34. package/dist/core/delegation-frames.d.ts +40 -15
  35. package/dist/core/delegation-frames.js +34 -0
  36. package/dist/core/engine-notice.d.ts +35 -11
  37. package/dist/core/gate-fold.js +9 -7
  38. package/dist/core/gate-lanes.js +6 -4
  39. package/dist/core/gate-outcome.d.ts +81 -15
  40. package/dist/core/gate-outcome.js +168 -67
  41. package/dist/core/governance-codes.d.ts +7 -1
  42. package/dist/core/governance-codes.js +9 -0
  43. package/dist/core/hooks.d.ts +4 -4
  44. package/dist/core/hooks.js +4 -3
  45. package/dist/core/mcp-connection-pool.d.ts +2 -0
  46. package/dist/core/mcp-connection-pool.js +2 -1
  47. package/dist/core/mcp-failure.js +2 -1
  48. package/dist/core/mcp-server-spec.d.ts +27 -5
  49. package/dist/core/mcp.d.ts +13 -38
  50. package/dist/core/mcp.js +28 -23
  51. package/dist/core/media-byte-cap.d.ts +25 -0
  52. package/dist/core/media-byte-cap.js +18 -2
  53. package/dist/core/memory-engine/consolidation-driver.js +1 -1
  54. package/dist/core/mode-policy.d.ts +25 -1
  55. package/dist/core/mode-policy.js +28 -13
  56. package/dist/core/own-property-receipt.d.ts +18 -0
  57. package/dist/core/own-property-receipt.js +39 -0
  58. package/dist/core/permission-rule-model.d.ts +32 -11
  59. package/dist/core/permission-rule-model.js +12 -7
  60. package/dist/core/persisted-rule-arms.d.ts +3 -3
  61. package/dist/core/prompt-section-seat.d.ts +54 -0
  62. package/dist/core/prompt-section-seat.js +49 -0
  63. package/dist/core/protocol-http-auth.d.ts +44 -0
  64. package/dist/core/protocol-http-auth.js +45 -0
  65. package/dist/core/read-face-seats.d.ts +30 -0
  66. package/dist/core/read-face-seats.js +38 -0
  67. package/dist/core/redaction-passes.d.ts +82 -0
  68. package/dist/core/redaction-passes.js +378 -0
  69. package/dist/core/remote-env.d.ts +4 -3
  70. package/dist/core/resume-startup-cause.d.ts +56 -0
  71. package/dist/core/resume-startup-cause.js +43 -0
  72. package/dist/core/rule-denial-sentence.d.ts +2 -2
  73. package/dist/core/runner/announce-once-ledger.js +2 -2
  74. package/dist/core/runner/classifier-ladder.d.ts +17 -13
  75. package/dist/core/runner/classifier-ladder.js +12 -10
  76. package/dist/core/runner/construction-doors.js +9 -20
  77. package/dist/core/runner/contracts.d.ts +4 -4
  78. package/dist/core/runner/gate-exit.js +2 -2
  79. package/dist/core/runner/memory-consolidation-write-judge.d.ts +53 -9
  80. package/dist/core/runner/memory-consolidation-write-judge.js +39 -1
  81. package/dist/core/runner/permission-rule-lanes.d.ts +8 -13
  82. package/dist/core/runner/person-path-rows.d.ts +4 -3
  83. package/dist/core/runner/policy-throw.js +4 -4
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +4 -3
  85. package/dist/core/runner/prepare-caps-and-workflow.js +8 -6
  86. package/dist/core/runner/prepare-gate-stations.js +4 -4
  87. package/dist/core/runner/prepare-hands-readface.js +1 -19
  88. package/dist/core/runner/prepare-inherited-gate.js +10 -9
  89. package/dist/core/runner/prepare-policy-chain.d.ts +3 -5
  90. package/dist/core/runner/prepare-policy-chain.js +10 -13
  91. package/dist/core/runner/prepare-project-context.d.ts +4 -4
  92. package/dist/core/runner/prepare-project-context.js +2 -1
  93. package/dist/core/runner/prepare-prompt-assembly.js +1 -0
  94. package/dist/core/runner/prepare-prompt-inputs.d.ts +1 -1
  95. package/dist/core/runner/prepare-prompt-inputs.js +4 -4
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +4 -3
  97. package/dist/core/runner/prepare-protocol-tools.js +3 -2
  98. package/dist/core/runner/prepare-task.js +6 -4
  99. package/dist/core/runner/prepare-turn-wiring.d.ts +16 -13
  100. package/dist/core/runner/prepare-turn-wiring.js +8 -7
  101. package/dist/core/runner/run-harness-handlers.js +2 -2
  102. package/dist/core/runner/run-leg.js +10 -9
  103. package/dist/core/runner/run-settle-and-teardown.js +5 -5
  104. package/dist/core/runner/run-turn-boundary.js +3 -2
  105. package/dist/core/runner/runtask.d.ts +1 -2
  106. package/dist/core/runner/runtask.js +8 -12
  107. package/dist/core/runner/session-rule-policy.d.ts +15 -11
  108. package/dist/core/runner/session-rule-policy.js +43 -33
  109. package/dist/core/runner/stream-lifecycle-verbs.js +2 -2
  110. package/dist/core/runner/stream-settle-backstop.js +2 -1
  111. package/dist/core/runner/sub-agent-deny-lists.d.ts +12 -8
  112. package/dist/core/runner/sub-agent-deny-lists.js +23 -12
  113. package/dist/core/runner/terminal-account.js +3 -3
  114. package/dist/core/runner/tool-output-projection.js +3 -8
  115. package/dist/core/runner/tool-result-media-seat.d.ts +19 -0
  116. package/dist/core/runner/tool-result-media-seat.js +13 -0
  117. package/dist/core/runner/turn-attachments.d.ts +13 -12
  118. package/dist/core/runner/turn-attachments.js +11 -7
  119. package/dist/core/runner-deps.d.ts +4 -3
  120. package/dist/core/safety-merge-corpus.js +21 -21
  121. package/dist/core/secret-env.js +1 -1
  122. package/dist/core/session-policy-store.d.ts +89 -72
  123. package/dist/core/session-policy-store.js +100 -93
  124. package/dist/core/shell-read-silence.d.ts +38 -4
  125. package/dist/core/shell-read-silence.js +102 -0
  126. package/dist/core/side-query.d.ts +7 -8
  127. package/dist/core/skill-grant-binding.d.ts +32 -11
  128. package/dist/core/skill-grant-binding.js +11 -3
  129. package/dist/core/skill-grant.d.ts +4 -3
  130. package/dist/core/skill-grant.js +5 -4
  131. package/dist/core/skills-directory.d.ts +12 -1
  132. package/dist/core/skills-directory.js +17 -8
  133. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  134. package/dist/core/store-contracts/checkpoint-store-contract.js +3 -0
  135. package/dist/core/swappable-deps.d.ts +15 -1
  136. package/dist/core/swappable-deps.js +25 -1
  137. package/dist/core/synthetic-read-verdict.d.ts +16 -14
  138. package/dist/core/synthetic-read-verdict.js +28 -8
  139. package/dist/core/task-notification.d.ts +13 -3
  140. package/dist/core/task-notification.js +12 -1
  141. package/dist/core/task-spec.d.ts +14 -15
  142. package/dist/core/tool-name-readings.d.ts +0 -4
  143. package/dist/core/tool-name-readings.js +0 -1
  144. package/dist/core/tool-policy.d.ts +40 -9
  145. package/dist/core/tool-policy.js +25 -7
  146. package/dist/core/types.d.ts +3 -3
  147. package/dist/core/types.js +1 -1
  148. package/dist/core/untrusted-egress.d.ts +60 -11
  149. package/dist/core/untrusted-egress.js +120 -9
  150. package/dist/core/untrusted-text.js +16 -0
  151. package/dist/core/wiring-manifest.d.ts +22 -8
  152. package/dist/core/wiring-manifest.js +12 -4
  153. package/dist/engine/harness/agent-harness.d.ts +2 -1
  154. package/dist/engine/harness/agent-harness.js +4 -1
  155. package/dist/engine/harness/types.d.ts +3 -0
  156. package/dist/engine/llm/types.d.ts +7 -9
  157. package/dist/engine/loop/agent-loop.d.ts +8 -3
  158. package/dist/engine/loop/agent-loop.js +26 -9
  159. package/dist/engine/loop/types.d.ts +10 -0
  160. package/dist/index.d.ts +17 -5
  161. package/dist/index.js +14 -5
  162. package/dist/orchestration/run-workflow-tool.js +8 -8
  163. package/dist/orchestration/workflow.js +2 -2
  164. package/dist/prompt-assembly/assemble.d.ts +3 -1
  165. package/dist/prompt-assembly/assemble.js +6 -0
  166. package/dist/prompts/cowork.d.ts +42 -0
  167. package/dist/prompts/cowork.js +529 -0
  168. package/dist/prompts/default.d.ts +14 -1
  169. package/dist/prompts/default.js +3 -0
  170. package/dist/stores/file/session-policy-store.d.ts +6 -3
  171. package/dist/stores/file/session-policy-store.js +5 -16
  172. package/dist/tools/artifact/artifact-tool.js +2 -0
  173. package/dist/tools/fs/image-admission.d.ts +57 -4
  174. package/dist/tools/fs/image-admission.js +85 -0
  175. package/dist/tools/fs/path-containment.d.ts +2 -0
  176. package/dist/tools/fs/path-containment.js +5 -0
  177. package/dist/tools/fs/shell-read-check.d.ts +31 -6
  178. package/dist/tools/fs/shell-read-check.js +2 -4
  179. package/dist/tools/fs/shell-read-environment.d.ts +10 -25
  180. package/dist/tools/fs/shell-read-environment.js +6 -18
  181. package/dist/tools/fs/shell-read-establish.d.ts +3 -2
  182. package/dist/tools/fs/shell-read-establish.js +5 -5
  183. package/dist/tools/fs/shell-read-judge.d.ts +38 -8
  184. package/dist/tools/fs/shell-read-judge.js +12 -10
  185. package/dist/tools/web.js +2 -2
  186. package/package.json +1 -1
  187. package/test/export-surface.snapshot.json +94 -10
  188. package/wire-changes/core@7.51.0.json +2421 -0
@@ -0,0 +1,378 @@
1
+ export function summarizeRedactions(findings) {
2
+ if (findings.length === 0)
3
+ return "no redactions";
4
+ const counts = new Map();
5
+ for (const f of findings) {
6
+ const label = `${f.kind} (${f.confidence})`;
7
+ counts.set(label, (counts.get(label) ?? 0) + 1);
8
+ }
9
+ const parts = [...counts].map(([label, n]) => `${label} x${n}`);
10
+ return `${findings.length} redaction${findings.length === 1 ? "" : "s"}: ${parts.join(", ")}`;
11
+ }
12
+ function passZeroMapper(batches) {
13
+ const cursors = batches.map(() => ({ i: 0, delta: 0 }));
14
+ return (pos) => {
15
+ let p = pos;
16
+ for (let b = batches.length - 1; b >= 0; b--) {
17
+ const edits = batches[b];
18
+ const c = cursors[b];
19
+ let mapped;
20
+ while (mapped === undefined && c.i < edits.length) {
21
+ const e = edits[c.i];
22
+ const outStart = e.at + c.delta;
23
+ if (p < outStart)
24
+ mapped = p - c.delta;
25
+ else if (p < outStart + e.insertedLen)
26
+ mapped = e.at;
27
+ else {
28
+ c.delta += e.insertedLen - e.removedLen;
29
+ c.i++;
30
+ }
31
+ }
32
+ p = mapped ?? p - c.delta;
33
+ }
34
+ return p;
35
+ };
36
+ }
37
+ const PREEXISTING_MARKER_RE = /\[redacted(?:-[a-z]+)?\]/g;
38
+ const FORMAT_CHAR_RE = /\p{Cf}/u;
39
+ const MAX_FORMAT_SLOT_SCANS = 64;
40
+ const REDACTED_WHOLE = "[redacted]";
41
+ function finding(pass, span, family) {
42
+ const f = { kind: pass.kind, confidence: pass.confidence, span, marker: pass.marker, source: pass.source };
43
+ if (family !== undefined)
44
+ f.family = family;
45
+ return f;
46
+ }
47
+ function insertedFor(pass, match, groups) {
48
+ return typeof pass.replace === "string"
49
+ ? pass.replace.replace(/\$(\d)/g, (_m, d) => groups[Number(d) - 1] ?? "")
50
+ : pass.replace(match, ...groups);
51
+ }
52
+ function removedRegion(pass, match, groups, inserted) {
53
+ if (typeof pass.replace === "string") {
54
+ const parts = pass.replace.split(/(\$\d)/).filter((p) => p !== "");
55
+ const isRef = (p) => /^\$\d$/.test(p);
56
+ const refText = (p) => groups[Number(p[1]) - 1] ?? "";
57
+ let head = "";
58
+ for (const p of parts) {
59
+ if (!isRef(p))
60
+ break;
61
+ head += refText(p);
62
+ }
63
+ let tail = "";
64
+ for (let i = parts.length - 1; i >= 0 && isRef(parts[i]); i--)
65
+ tail = refText(parts[i]) + tail;
66
+ if (parts.every(isRef))
67
+ return [0, match.length];
68
+ if (!match.startsWith(head))
69
+ head = "";
70
+ if (!match.endsWith(tail) || head.length + tail.length > match.length)
71
+ tail = "";
72
+ return [head.length, match.length - tail.length];
73
+ }
74
+ const markerAt = pass.marker === "" ? -1 : inserted.lastIndexOf(pass.marker);
75
+ const headStop = markerAt < 0 ? inserted.length : markerAt;
76
+ const tailStop = markerAt < 0 ? inserted.length : inserted.length - markerAt - pass.marker.length;
77
+ let head = 0;
78
+ while (head < match.length && head < headStop && match[head] === inserted[head])
79
+ head++;
80
+ let tail = 0;
81
+ while (tail < match.length - head && tail < inserted.length - head && tail < tailStop && match[match.length - 1 - tail] === inserted[inserted.length - 1 - tail])
82
+ tail++;
83
+ return [head, match.length - tail];
84
+ }
85
+ const SOLO_SCAN_WORK_PER_CHAR = 8;
86
+ const SOLO_SCAN_WORK_FLOOR = 1 << 17;
87
+ function soloHits(input, passes) {
88
+ const hits = [];
89
+ passes.forEach((pass, i) => {
90
+ const re = new RegExp(pass.re.source, pass.re.flags.includes("g") ? pass.re.flags : `${pass.re.flags}g`);
91
+ const rescans = pass.family !== undefined;
92
+ let work = Math.max(SOLO_SCAN_WORK_PER_CHAR * input.length, SOLO_SCAN_WORK_FLOOR);
93
+ for (let m = re.exec(input); m !== null; m = re.exec(input)) {
94
+ const at = m.index;
95
+ const familyAt = () => (typeof pass.family === "function" ? pass.family(input, at) : pass.family);
96
+ if (rescans) {
97
+ work -= m[0].length;
98
+ if (work < 0) {
99
+ hits.push({ start: at, end: input.length, marker: pass.marker === "" ? REDACTED_WHOLE : pass.marker, pass: i, family: familyAt(), matchStart: at, matchEnd: input.length });
100
+ break;
101
+ }
102
+ re.lastIndex = at + 1;
103
+ }
104
+ else
105
+ re.lastIndex = at + Math.max(m[0].length, 1);
106
+ const inserted = insertedFor(pass, m[0], m.slice(1));
107
+ if (inserted === m[0])
108
+ continue;
109
+ const [a, b] = removedRegion(pass, m[0], m.slice(1), inserted);
110
+ if (b <= a)
111
+ continue;
112
+ const family = familyAt();
113
+ hits.push({ start: at + a, end: at + b, marker: inserted.slice(a, inserted.length - (m[0].length - b)), pass: i, family, matchStart: at, matchEnd: at + m[0].length });
114
+ }
115
+ });
116
+ return hits;
117
+ }
118
+ function unremovedHits(hits, removals) {
119
+ const gone = [];
120
+ for (const r of [...removals].filter((x) => x.end > x.start).sort((x, y) => x.start - y.start || y.end - x.end)) {
121
+ const last = gone[gone.length - 1];
122
+ if (last !== undefined && r.start <= last[1])
123
+ last[1] = Math.max(last[1], r.end);
124
+ else
125
+ gone.push([r.start, r.end]);
126
+ }
127
+ return hits.filter((h) => {
128
+ let lo = 0;
129
+ let hi = gone.length - 1;
130
+ let at = -1;
131
+ while (lo <= hi) {
132
+ const mid = (lo + hi) >> 1;
133
+ if (gone[mid][0] <= h.start) {
134
+ at = mid;
135
+ lo = mid + 1;
136
+ }
137
+ else
138
+ hi = mid - 1;
139
+ }
140
+ return at === -1 || gone[at][1] < h.end;
141
+ });
142
+ }
143
+ function sequentialRemovals(batches) {
144
+ const removals = [];
145
+ for (let j = 0; j < batches.length; j++) {
146
+ const toPassZero = passZeroMapper(batches.slice(0, j));
147
+ for (const e of batches[j]) {
148
+ const start = toPassZero(e.at + e.head);
149
+ const end = toPassZero(e.at + e.removedLen - e.tail);
150
+ removals.push({ start, end: Math.max(start, end), marker: e.inserted.slice(e.head, e.insertedLen - e.tail), pass: e.pass, family: e.family });
151
+ }
152
+ }
153
+ return removals;
154
+ }
155
+ function renderRemovals(input, removals) {
156
+ const sorted = removals.filter((r) => r.end > r.start || r.marker !== "").sort((x, y) => x.start - y.start || y.end - x.end);
157
+ const edits = [];
158
+ let out = "";
159
+ let cursor = 0;
160
+ for (let i = 0; i < sorted.length;) {
161
+ const start = sorted[i].start;
162
+ let end = sorted[i].end;
163
+ let widest;
164
+ for (; i < sorted.length && (sorted[i].start < end || sorted[i].start === start || (sorted[i].marker === "" && sorted[i].start === end)); i++) {
165
+ const r = sorted[i];
166
+ if (r.end > end)
167
+ end = r.end;
168
+ if (r.marker !== "" && (widest === undefined || r.end - r.start > widest.end - widest.start))
169
+ widest = r;
170
+ }
171
+ const marker = widest?.marker ?? REDACTED_WHOLE;
172
+ out += input.slice(cursor, start) + marker;
173
+ cursor = end;
174
+ edits.push({ at: start, removedLen: end - start, insertedLen: marker.length, inserted: marker, family: widest?.family, head: 0, tail: 0, pass: widest?.pass ?? sorted[i - 1].pass });
175
+ }
176
+ return { out: out + input.slice(cursor), edits };
177
+ }
178
+ function runPasses(input, passes, findings, toOriginal) {
179
+ const batches = [];
180
+ let cur = input;
181
+ passes.forEach((pass, passIndex) => {
182
+ const edits = [];
183
+ const passInput = cur;
184
+ const toPassZero = passZeroMapper(batches.slice());
185
+ cur = cur.replace(pass.re, (...args) => {
186
+ const match = args[0];
187
+ const offset = args[args.length - 2];
188
+ const groups = args.slice(1, -2);
189
+ const inserted = insertedFor(pass, match, groups);
190
+ if (inserted === match)
191
+ return inserted;
192
+ const family = typeof pass.family === "function" ? pass.family(passInput, offset) : pass.family;
193
+ if (findings !== undefined)
194
+ findings.push({ pass: passIndex, f: finding(pass, [toOriginal(toPassZero(offset)), toOriginal(toPassZero(offset + match.length))], family) });
195
+ const [a, b] = removedRegion(pass, match, groups, inserted);
196
+ edits.push({ at: offset, removedLen: match.length, insertedLen: inserted.length, inserted, family, head: a, tail: match.length - b, pass: passIndex });
197
+ return inserted;
198
+ });
199
+ batches.push(edits);
200
+ });
201
+ return { out: cur, batches };
202
+ }
203
+ function applyPasses(input, passes, report, toOriginal, disjoint = false) {
204
+ const found = report === undefined ? undefined : [];
205
+ const plain = runPasses(input, passes, found, toOriginal);
206
+ const solo = soloHits(input, passes);
207
+ const sequential = solo.length === 0 ? [] : sequentialRemovals(plain.batches);
208
+ const outer = unremovedHits(solo, sequential);
209
+ const mappable = !disjoint || replacedSpans(plain.batches) !== undefined;
210
+ const whole = outer.length === 0 && mappable ? undefined : renderRemovals(input, [...sequential, ...outer]);
211
+ if (whole === undefined || (whole.out === plain.out && mappable)) {
212
+ if (report !== undefined)
213
+ report.findings.push(...found.map((x) => x.f));
214
+ return plain;
215
+ }
216
+ if (report !== undefined) {
217
+ const seen = new Set(found.map((x) => `${x.pass}:${x.f.span[0]}:${x.f.span[1]}`));
218
+ for (const h of outer) {
219
+ const span = [toOriginal(h.matchStart), toOriginal(h.matchEnd)];
220
+ const key = `${h.pass}:${span[0]}:${span[1]}`;
221
+ if (seen.has(key))
222
+ continue;
223
+ seen.add(key);
224
+ found.push({ pass: h.pass, f: finding(passes[h.pass], span, h.family) });
225
+ }
226
+ found.sort((x, y) => x.pass - y.pass || x.f.span[0] - y.f.span[0]);
227
+ report.findings.push(...found.map((x) => x.f));
228
+ }
229
+ return { out: whole.out, batches: [whole.edits] };
230
+ }
231
+ function replacedSpans(batches) {
232
+ const spans = [];
233
+ for (let j = 0; j < batches.length; j++) {
234
+ const toPassZero = passZeroMapper(batches.slice(0, j));
235
+ for (const e of batches[j]) {
236
+ const s0 = toPassZero(e.at);
237
+ const e0 = toPassZero(e.at + e.removedLen);
238
+ if (e0 - s0 !== e.removedLen)
239
+ return undefined;
240
+ spans.push({ start: s0, end: e0, inserted: e.inserted, pass: e.pass, family: e.family });
241
+ }
242
+ }
243
+ spans.sort((a, b) => a.start - b.start);
244
+ for (let i = 1; i < spans.length; i++)
245
+ if (spans[i].start < spans[i - 1].end)
246
+ return undefined;
247
+ return spans;
248
+ }
249
+ export function runRedactionPasses(input, passes, report) {
250
+ if (report !== undefined && report.preexistingMarkers === undefined) {
251
+ report.preexistingMarkers = input.match(PREEXISTING_MARKER_RE)?.length ?? 0;
252
+ }
253
+ if (!FORMAT_CHAR_RE.test(input))
254
+ return applyPasses(input, passes, report, (pos) => pos).out;
255
+ const viewChars = [];
256
+ const viewToOriginal = [];
257
+ const formatAtSlot = [];
258
+ const charStartOffsets = [];
259
+ {
260
+ let orig = 0;
261
+ let viewOff = 0;
262
+ let slotBuf = "";
263
+ for (const ch of input) {
264
+ if (FORMAT_CHAR_RE.test(ch)) {
265
+ slotBuf += ch;
266
+ }
267
+ else {
268
+ formatAtSlot.push(slotBuf);
269
+ slotBuf = "";
270
+ viewToOriginal.push(orig);
271
+ charStartOffsets.push(viewOff);
272
+ viewChars.push(ch);
273
+ viewOff += ch.length;
274
+ }
275
+ orig += ch.length;
276
+ }
277
+ formatAtSlot.push(slotBuf);
278
+ viewToOriginal.push(input.length);
279
+ charStartOffsets.push(viewOff);
280
+ }
281
+ const view = viewChars.join("");
282
+ const offsetToCharIndex = new Map();
283
+ charStartOffsets.forEach((off, idx) => offsetToCharIndex.set(off, idx));
284
+ const toOriginal = (pos) => {
285
+ const idx = offsetToCharIndex.get(pos);
286
+ return idx === undefined ? input.length : viewToOriginal[idx];
287
+ };
288
+ const { batches } = applyPasses(view, passes, report, toOriginal, true);
289
+ const viewSpans = replacedSpans(batches);
290
+ if (viewSpans === undefined)
291
+ return REDACTED_WHOLE;
292
+ const spans = viewSpans.map((sp) => {
293
+ const startIdx = offsetToCharIndex.get(sp.start) ?? viewChars.length;
294
+ const endIdx = offsetToCharIndex.get(sp.end) ?? viewChars.length;
295
+ const lastIdx = endIdx - 1;
296
+ const endOrig = lastIdx >= startIdx && lastIdx < viewChars.length ? viewToOriginal[lastIdx] + viewChars[lastIdx].length : viewToOriginal[startIdx];
297
+ return { start: viewToOriginal[startIdx], end: endOrig, inserted: sp.inserted };
298
+ });
299
+ const viewOffsetToOriginal = (viewStart, viewEnd) => {
300
+ const startIdx = offsetToCharIndex.get(viewStart) ?? viewChars.length;
301
+ const endIdx = offsetToCharIndex.get(viewEnd) ?? viewChars.length;
302
+ const lastIdx = endIdx - 1;
303
+ const endOrig = lastIdx >= startIdx && lastIdx < viewChars.length ? viewToOriginal[lastIdx] + viewChars[lastIdx].length : viewToOriginal[startIdx];
304
+ return { start: viewToOriginal[startIdx], end: endOrig };
305
+ };
306
+ const cutOffsets = [];
307
+ for (let k = 1; k < viewChars.length; k++)
308
+ if (formatAtSlot[k] !== "")
309
+ cutOffsets.push(charStartOffsets[k]);
310
+ if (cutOffsets.length > MAX_FORMAT_SLOT_SCANS)
311
+ return REDACTED_WHOLE;
312
+ const extra = [];
313
+ const rawSpans = replacedSpans(applyPasses(input, passes, undefined, (pos) => pos, true).batches);
314
+ if (rawSpans !== undefined)
315
+ extra.push(...rawSpans);
316
+ for (const cut of cutOffsets) {
317
+ const cutSpans = replacedSpans(applyPasses(view.slice(cut), passes, undefined, (pos) => pos, true).batches);
318
+ if (cutSpans === undefined)
319
+ continue;
320
+ for (const c of cutSpans) {
321
+ const o = viewOffsetToOriginal(c.start + cut, c.end + cut);
322
+ extra.push({ ...c, start: o.start, end: o.end });
323
+ }
324
+ }
325
+ extra.sort((a, b) => a.start - b.start);
326
+ const firstEndingAfter = (pos) => {
327
+ let lo = 0;
328
+ let hi = spans.length;
329
+ while (lo < hi) {
330
+ const mid = (lo + hi) >> 1;
331
+ if (spans[mid].end > pos)
332
+ hi = mid;
333
+ else
334
+ lo = mid + 1;
335
+ }
336
+ return lo;
337
+ };
338
+ for (const r of extra) {
339
+ const at = firstEndingAfter(r.start);
340
+ const hit = spans[at];
341
+ if (hit !== undefined && hit.start < r.end) {
342
+ if (r.start < hit.start)
343
+ hit.start = r.start;
344
+ if (r.end > hit.end)
345
+ hit.end = r.end;
346
+ let next = at + 1;
347
+ while (next < spans.length && spans[next].start < hit.end)
348
+ hit.end = Math.max(hit.end, spans[next++].end);
349
+ if (next > at + 1)
350
+ spans.splice(at + 1, next - at - 1);
351
+ continue;
352
+ }
353
+ spans.splice(at, 0, { start: r.start, end: r.end, inserted: r.inserted });
354
+ if (report !== undefined)
355
+ report.findings.push(finding(passes[r.pass], [r.start, r.end], r.family));
356
+ }
357
+ let result = "";
358
+ let cursor = 0;
359
+ for (const sp of spans) {
360
+ result += input.slice(cursor, sp.start) + sp.inserted;
361
+ cursor = sp.end;
362
+ }
363
+ result += input.slice(cursor);
364
+ return result;
365
+ }
366
+ const BRACKET_MARKER = { "url-credentials": "[redacted-credentials]" };
367
+ const bracketMarker = (kind) => BRACKET_MARKER[kind] ?? "[redacted]";
368
+ const capturesLabel = (re) => new RegExp(`${re.source}|`).exec("").length > 1;
369
+ export function egressPasses(groups, replacers = {}) {
370
+ return groups.map((rows) => {
371
+ const head = rows[0];
372
+ const marker = bracketMarker(head.kind);
373
+ const re = rows.length === 1 ? head.match : new RegExp(rows.map((r) => `(?:${r.match.source})`).join("|"), head.match.flags);
374
+ const sticky = rows.map((r) => ({ id: r.id, re: new RegExp(r.match.source, r.match.flags.replace("g", "") + "y") }));
375
+ const family = rows.length === 1 ? head.id : (input, at) => sticky.find((s) => ((s.re.lastIndex = at), s.re.test(input)))?.id ?? head.id;
376
+ return { kind: head.kind, confidence: head.confidence, marker, source: head.source, re, replace: replacers[head.kind] ?? (capturesLabel(head.match) ? `$1${marker}` : marker), family };
377
+ });
378
+ }
@@ -319,9 +319,10 @@ export interface RemoteExecutionEnv extends ExecutionEnv {
319
319
  /** True iff this adapter's {@link FileSystem.canonicalPath} is the AUTHORITY on where a path lands —
320
320
  * it answers for the very filesystem the commands run on, and a failure to answer is a failure of
321
321
  * the resolver rather than a namespace it cannot see into. Optional, and absent is not a
322
- * declaration: an adapter that says nothing keeps the reading every adapter has had, in which a
323
- * chain nothing could be established about leaves the lexical verdict standing. Declaring it says
324
- * the opposite — that an unanswerable chain is a question, not an absence — and the read
322
+ * declaration: an adapter that says nothing keeps the lexical verdict for a chain its resolver answers
323
+ * `not_supported` (no file face); a resolver that FAILS (any other non-answer) is a failure either way —
324
+ * the read station records it and the command is unanswered. Declaring it says that even an
325
+ * unanswerable chain is a question, not an absence — and the read
325
326
  * boundary's execute-time recheck then refuses rather than passing the written spelling through.
326
327
  * Read STRICTLY through {@link canonicalPathIsAuthoritative}, like every other capability whose
327
328
  * `true` changes what a containment leg does; a wrong-SHAPE value reads absent and is announced. */
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Why a resume could not run its approved action — ONE reading of the failure's code, shared by every
3
+ * consumer that has to act on it: the reopen compensation (which {@link ReopenReason} a consumed
4
+ * checkpoint returns to pending under) and the parked-resume receipt (the `cause` its caller branches on).
5
+ * The answer is read from the failure's CODE — a thrown value's `cause` chain, or a failed result's
6
+ * terminal code — never from its sentence; a code this table does not name reads as ABSENT (not known),
7
+ * which each consumer answers in its own documented way rather than with a guessed cause.
8
+ *
9
+ * · `environment` — the leg's infrastructure failed (the workspace restore, an abort): the decision is
10
+ * untouched, and a retry replays it;
11
+ * · `session_gone` — the resumed agent's transcript session is missing;
12
+ * · `tool_unavailable` — the approved tool is not available to the resumed leg (gone, replaced by a
13
+ * different contract, or an inherited tool the drive cannot hand over): this approval cannot run, and a
14
+ * fresh decision resolves it;
15
+ * · `configuration` — the deployment's configuration refuses the resumed leg (a `config.*` door, the
16
+ * pre-claim configuration screen, a re-supplied constraint chain that is missing or does not match its
17
+ * installation record): a retry under the same configuration fails the same way;
18
+ * · `decision_rejected` — the supplied decision does not fit the checkpoint (another gate's answer, an
19
+ * unsafe payload, an input the constraints refuse): correct the decision and resubmit.
20
+ */
21
+ /**
22
+ * design/80 D-1: why a consumed checkpoint was {@link import("./checkpoint-store.js").CheckpointStore.reopen | reopened}
23
+ * (resolved → pending). The reason drives re-resume validation (§3 invariant #1): an `env_failed` reopen is a SYSTEM
24
+ * RETRY of the ALREADY-APPROVED action — the re-resume MUST replay the persisted
25
+ * {@link import("./checkpoint-store.js").ResolvedOutcome} winner, never a new vote — while a `tool_unavailable` reopen
26
+ * (the action could not run, may now be invalid — P-7) lets a human RE-DECIDE with the tool present, so a fresh
27
+ * operator decision IS allowed.
28
+ *
29
+ * `constraint_rejected` (F-012 L3) carries the same FRESH-REDECISION semantics as `tool_unavailable`:
30
+ * a resolve was retracted because its decided INPUT failed a constraint re-adjudication (a frozen
31
+ * ancestor projection / resume-edit policy denied the approver's edit) — the ask itself is unconsumed,
32
+ * so the returned-to-pending row takes a fresh operator decision, never a forced replay of the
33
+ * rejected one. Core's own edit re-adjudication runs PRE-CAS (nothing to reopen there); this reason
34
+ * exists for a deployment lane that discovers the same rejection only after its own resolve landed
35
+ * and must return the row to pending without inventing an out-of-vocabulary reason.
36
+ *
37
+ * `config_refused` carries the same FRESH-REDECISION semantics: the deployment's configuration refused
38
+ * the resumed leg before its approved action started, so a retry under the same configuration fails the
39
+ * same way — it is not a system retry, and the row takes a fresh decision (or the same one again once the
40
+ * configuration is corrected). Which reason a failure reopens under is {@link reopenReasonOf}'s answer.
41
+ */
42
+ export declare const REOPEN_REASONS: readonly ["env_failed", "tool_unavailable", "constraint_rejected", "config_refused"];
43
+ export type ReopenReason = (typeof REOPEN_REASONS)[number];
44
+ export declare const RESUME_STARTUP_CAUSES: readonly ["environment", "session_gone", "tool_unavailable", "configuration", "decision_rejected"];
45
+ export type ResumeStartupCause = (typeof RESUME_STARTUP_CAUSES)[number];
46
+ /** The cause a resume failure's code names; `undefined` when the code is absent or the table does not
47
+ * name it. Every `config.*` code is a configuration door's refusal. */
48
+ export declare function resumeStartupCauseOf(code: string | undefined): ResumeStartupCause | undefined;
49
+ /**
50
+ * The reason a consumed checkpoint whose approved action never started returns to pending under. A failure
51
+ * whose cause is not known reopens as `env_failed`: the leg went away before the action ran, the human's
52
+ * decision is untouched, and the retry replays it.
53
+ */
54
+ export declare function reopenReasonOf(cause: ResumeStartupCause | undefined): ReopenReason;
55
+ /** What the result of a returned resume leg says after its checkpoint was reopened, per reason. */
56
+ export declare function reopenedUnstartedSentence(reason: ReopenReason): string;
@@ -0,0 +1,43 @@
1
+ export const REOPEN_REASONS = Object.freeze(["env_failed", "tool_unavailable", "constraint_rejected", "config_refused"]);
2
+ export const RESUME_STARTUP_CAUSES = Object.freeze(["environment", "session_gone", "tool_unavailable", "configuration", "decision_rejected"]);
3
+ const CAUSE_OF_CODE = new Map([
4
+ ["resume.env_failed", "environment"],
5
+ ["checkpoint.resume_aborted", "environment"],
6
+ ["resume.session_not_found", "session_gone"],
7
+ ["resume.tool_unavailable", "tool_unavailable"],
8
+ ["resume.tool_contract_mismatch", "tool_unavailable"],
9
+ ["checkpoint.config_refused", "configuration"],
10
+ ["resume.parent_constraint_missing", "configuration"],
11
+ ["resume.parent_constraint_mismatch", "configuration"],
12
+ ["checkpoint.gate_mismatch", "decision_rejected"],
13
+ ["checkpoint.invalid_outcome", "decision_rejected"],
14
+ ["resume.constraint_rejected", "decision_rejected"],
15
+ ["resume.constraint_unprojectable", "decision_rejected"],
16
+ ]);
17
+ export function resumeStartupCauseOf(code) {
18
+ if (code === undefined)
19
+ return undefined;
20
+ return CAUSE_OF_CODE.get(code) ?? (code.startsWith("config.") ? "configuration" : undefined);
21
+ }
22
+ const REOPEN_REASON_OF_CAUSE = {
23
+ environment: "env_failed",
24
+ session_gone: "env_failed",
25
+ tool_unavailable: "tool_unavailable",
26
+ configuration: "config_refused",
27
+ decision_rejected: "constraint_rejected",
28
+ };
29
+ export function reopenReasonOf(cause) {
30
+ return cause === undefined ? "env_failed" : REOPEN_REASON_OF_CAUSE[cause];
31
+ }
32
+ export function reopenedUnstartedSentence(reason) {
33
+ switch (reason) {
34
+ case "env_failed":
35
+ return "the approved action never started, so the checkpoint was REOPENED (pending): the same approval can be resumed again, do not ask for a fresh one";
36
+ case "config_refused":
37
+ return "the approved action never started, so the checkpoint was REOPENED (pending): the deployment's configuration refused the resumed leg, so resuming under the same configuration fails the same way — correct the configuration, then resume, or decide afresh";
38
+ case "tool_unavailable":
39
+ return "the approved action never started, so the checkpoint was REOPENED (pending): the approved tool is not available to the resumed leg, so this approval cannot run — decide afresh";
40
+ case "constraint_rejected":
41
+ return "the approved action never started, so the checkpoint was REOPENED (pending): the decided input was refused, so decide afresh";
42
+ }
43
+ }
@@ -59,8 +59,8 @@ export declare function ruleSourcePhrase(source: RuleDenialSource): string;
59
59
  * head rather than spelling a second grammar.
60
60
  *
61
61
  * - `tool` — named when the sentence stands alone; omitted when a carrier sentence already named the call.
62
- * - `rule` — the rule's own text, when the source has one to quote (a permission rule does; a session
63
- * policy row is structured fields, not a rule DSL, so it does not).
62
+ * - `rule` — the rule's own text, when the source has one to quote (a permission rule does; a session rule's entry is
63
+ * stored data and is not quoted onto a sentence that reaches the model).
64
64
  * - `origin` — where in that store's layering the row was written (e.g. a settings layer's key path).
65
65
  * - `detail` — WHY this call failed the rule, when the source word alone does not say it (an allowlist
66
66
  * miss, a write the confinement cannot place, the refusing layer's own sentence).
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { redactSecrets } from "../untrusted-egress.js";
2
+ import { egressText } from "../untrusted-egress.js";
3
3
  import { inlineUntrusted } from "../untrusted-text.js";
4
4
  import { thrownText } from "../thrown-value.js";
5
5
  export const ANNOUNCE_ONCE_MAX_DIGESTS = 200;
@@ -11,7 +11,7 @@ export function warningDigest(err) {
11
11
  const e = err;
12
12
  const code = typeof e?.code === "string" ? e.code : "";
13
13
  const message = thrownText(err);
14
- return announceDigest(code, redactSecrets(message));
14
+ return announceDigest(code, egressText("same-principal", message));
15
15
  }
16
16
  export function droppedToolDigest(d) {
17
17
  return announceDigest(DROPPED_TOOL_MODEL_LANE, `${d.server}\n${d.tool}\n${d.reason}`);
@@ -34,7 +34,8 @@
34
34
  */
35
35
  import type { AssistantMessage, Model } from "../../internal/llm.js";
36
36
  import { type AutoModeClassified } from "../auto-mode.js";
37
- import type { ClassifierUnavailableInfo, ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
37
+ import { type ClassifierUnavailableInfo, type ClassifierUnavailableSeat } from "../classifier-unavailable-arm.js";
38
+ import type { ApprovalRoute } from "../wiring-manifest.js";
38
39
  import type { EngineNotice, RunnerDeps } from "../types.js";
39
40
  /**
40
41
  * The rungs a classification walks, in order: the classifier seat, and — under `classifierFallback: "main"` (the
@@ -112,9 +113,13 @@ export declare function announceClassifierRoleDefaulted(args: {
112
113
  /**
113
114
  * The `classifier.unavailable` notice — a classification that could not RULE (the classifier leg failed, its per-level
114
115
  * budget ran out, or it replied with no verdict — `parse_error`; the code name predates that third cause) and where the
115
- * call went: `"asked"` = on down the ask chain to this leg's approval seat (a person or a durable park decides; the
116
- * handed-on ask carries `requiresRealApproval` and `classifierNoRuling`, so no automatic lane answers it), `"denied"` =
117
- * refused (no approval seat: nobody to ask — a reply with no verdict is then also counted on the denial limit). Audience `user`: the refusal is the session user's own
116
+ * call went, read off the station's ONE route word (`ApprovalRoute`): `live_approver` = on to the live approver now,
117
+ * `durable_park` = on to the durable park (the notice is minted at the fork: it states the route, never whether the park
118
+ * then commits) — both are `disposition: "asked"` (the handed-on ask carries
119
+ * `requiresRealApproval` and `classifierNoRuling`, so no automatic lane answers it), `none` = `"denied"`, refused — no
120
+ * approver and no park facility (a reply with no verdict is then also counted on the denial limit). The sentence names the
121
+ * route; the detail carries its projection (`disposition`), and the route itself is the run's published seat word
122
+ * (`wiring_manifest.ask.effective`). Audience `user`: the refusal is the session user's own
118
123
  * call, and on a lane with no approval face (`-p`) this line is the only place they hear why it did not run. Once
119
124
  * per classification that could not answer, from both classifier stations (the leg's gate and an ancestor's frozen
120
125
  * classifier in the policy fold) — a call judged by more than one classifier can carry one line per classifier. The line
@@ -131,10 +136,10 @@ export declare function classifierUnavailableNotice(args: ClassifierUnavailableI
131
136
  sessionId?: string;
132
137
  runId?: string;
133
138
  }): EngineNotice;
134
- /** A classifier station's fork facts, wired once per leg: whether an ask the station hands on reaches a person, and
135
- * the `classifier.unavailable` notice through the one throat. Both stations (the gate, the inherited fold) take it. */
139
+ /** A classifier station's fork facts, wired once per leg: the route an ask the station hands on takes, and the
140
+ * `classifier.unavailable` notice through the one throat. Both stations (the gate, the inherited fold) take it. */
136
141
  export declare function classifierUnavailableSeat(args: ClassifierUnavailableStation & {
137
- approvalSeat: boolean;
142
+ route: ApprovalRoute;
138
143
  onNotice: RunnerDeps["onNotice"];
139
144
  sessionId?: string;
140
145
  runId?: string;
@@ -142,12 +147,11 @@ export declare function classifierUnavailableSeat(args: ClassifierUnavailableSta
142
147
  /** Which station forked (`leg` — the leg's gate, on its own approval seat; `ancestor` — an ancestor's frozen classifier in
143
148
  * the policy fold, on that ancestor's frozen approver) and the per-level budget its classifier held, when stated.
144
149
  *
145
- * ONE question at both stations — does the route a handed-on ask takes from HERE end at a person — asked of two routes:
146
- * the leg's gate hands its ask down the leg's own chain (`askReachesPerson(wiring_manifest.ask)`: a live approver, or the
147
- * durable park); the inherited station resolves at the ancestor's frozen approver, whose only road to a person is being
148
- * a live function (`isLiveApproverSeat`: a blanket or absent seat there ends in a refusal, and the leg's park is reached
149
- * only through a live approver's `unavailable`). Reading the leg's route at the inherited station would hand the ask to a
150
- * frozen seat that cannot answer it. */
150
+ * ONE question at both stations, ONE answer function (`approvalRouteOf`) — where does a handed-on ask go from HERE — asked
151
+ * of two seats: the leg's gate hands its ask down the leg's own chain (`approvalRouteOf(wiring_manifest.ask)`: its live
152
+ * approver, or the durable park); the inherited station resolves at the ancestor's frozen approver
153
+ * (`frozenApproverRoute`: the same table with no park — a blanket or absent seat there ends in a refusal, and the leg's
154
+ * park is reached only through a live approver's `unavailable`). */
151
155
  export interface ClassifierUnavailableStation {
152
156
  station: "leg" | "ancestor";
153
157
  budgetMs?: number;
@@ -2,6 +2,7 @@ import { sameRouteIdentity } from "../../brain/route-adjudicator.js";
2
2
  import { thinkingOffSpelled } from "../../brain/reasoning.js";
3
3
  import { AUTO_MODE_CLASSIFIER_CONTRACT_BROKEN_AFTER, AUTO_MODE_CLASSIFIER_LOW_REASONING_BUDGET_TOKENS, AUTO_MODE_CLASSIFIER_MAX_TOKENS, AUTO_MODE_CLASSIFIER_THINK_BUDGET_TOKENS } from "../auto-mode-defaults.js";
4
4
  import { autoModeClassifierFallbackOf, parseAutoModeResponse } from "../auto-mode.js";
5
+ import { classifierUnavailableDispositionOf } from "../classifier-unavailable-arm.js";
5
6
  import { DEFAULT_TIER_ORDER, expandTiers, resolveTaskModel, roleSpecNamesModel } from "../roles.js";
6
7
  import { emitTrace } from "../trace.js";
7
8
  import { deliverEngineNotice } from "../types.js";
@@ -101,13 +102,14 @@ export function classifierUnavailableNotice(args) {
101
102
  : args.cause === "parse_error"
102
103
  ? args.station === "ancestor" ? `${whose} replied without a verdict` : "the classifier's reply carried no verdict"
103
104
  : `${whose} could not run (error)`;
104
- const next = args.disposition === "asked"
105
- ? args.station === "ancestor"
106
- ? "so the call was handed on to that layer's approver rather than refused by the classifier."
107
- : "so the call was handed on to a person (a live approver or a durable park) rather than refused by the classifier."
108
- : (args.station === "ancestor" ? "and that layer has no live approver" : "and this run has no approval seat") +
109
- ", so the call was refused and did not run." +
110
- (args.cause === "timeout" ? " A deployment on a slow gateway raises the budget." : "");
105
+ const seatOwner = args.station === "ancestor" ? "that layer's" : "this run's";
106
+ const next = args.route === "live_approver"
107
+ ? `so the call was handed on to ${seatOwner} live approver rather than refused by the classifier.`
108
+ : args.route === "durable_park"
109
+ ? `so the call was handed on to ${seatOwner} durable approval park, for a person to answer later, rather than refused by the classifier.`
110
+ : (args.station === "ancestor" ? "and that layer has no live approver" : "and this run has no approver and no park facility") +
111
+ ", so the call was refused and did not run." +
112
+ (args.cause === "timeout" ? " A deployment on a slow gateway raises the budget." : "");
111
113
  return {
112
114
  code: "classifier.unavailable",
113
115
  message: `auto mode could not judge "${args.toolName}": ${why}, ${next}`,
@@ -115,15 +117,15 @@ export function classifierUnavailableNotice(args) {
115
117
  toolName: args.toolName,
116
118
  toolCallId: args.toolCallId,
117
119
  cause: args.cause,
118
- disposition: args.disposition,
120
+ disposition: classifierUnavailableDispositionOf(args.route),
119
121
  ...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
120
122
  ...(args.runId !== undefined ? { runId: args.runId } : {}),
121
123
  },
122
124
  };
123
125
  }
124
126
  export function classifierUnavailableSeat(args) {
125
- const { approvalSeat, onNotice, sessionId, runId, station, budgetMs } = args;
126
- return { approvalSeat, onUnavailable: (info) => deliverEngineNotice(onNotice, classifierUnavailableNotice({ ...info, station, ...(budgetMs !== undefined ? { budgetMs } : {}), ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) })) };
127
+ const { route, onNotice, sessionId, runId, station, budgetMs } = args;
128
+ return { route, onUnavailable: (info) => deliverEngineNotice(onNotice, classifierUnavailableNotice({ ...info, station, ...(budgetMs !== undefined ? { budgetMs } : {}), ...(sessionId !== undefined ? { sessionId } : {}), ...(runId !== undefined ? { runId } : {}) })) };
127
129
  }
128
130
  function classifierFallbackAnnouncer(args) {
129
131
  const announced = new Set();