@selesai/code 0.8.6 → 0.8.7

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 (192) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/core/agent-session.js +13 -1
  3. package/dist/core/extensions/types.d.ts +2 -0
  4. package/dist/core/provider-composer.js +17 -4
  5. package/dist/core/settings-manager.d.ts +2 -0
  6. package/dist/core/settings-manager.js +3 -0
  7. package/dist/core/system-prompt.js +2 -8
  8. package/dist/core/system-prompt.test.js +6 -0
  9. package/dist/core/tools/index.d.ts +1 -0
  10. package/dist/core/tools/index.js +1 -0
  11. package/dist/core/tools/schema-prune.d.ts +16 -0
  12. package/dist/core/tools/schema-prune.js +39 -0
  13. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  14. package/dist/core/tools/schema-prune.test.js +111 -0
  15. package/dist/defaults/settings.json +1 -0
  16. package/dist/extensions/model-prompt-injector/config.json +10 -0
  17. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  18. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  19. package/dist/extensions/package.json +1 -0
  20. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  21. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  22. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  23. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  24. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  25. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  26. package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
  27. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  28. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  29. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  30. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  31. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  32. package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
  33. package/dist/extensions/pi-subagents/install.mjs +2 -2
  34. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  35. package/dist/extensions/pi-subagents/package.json +1 -1
  36. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  37. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  38. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
  39. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  40. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  41. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  42. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  43. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  44. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  45. package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
  46. package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
  47. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  48. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  49. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  50. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  51. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  52. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  53. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  54. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  55. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  56. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  57. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  58. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  59. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  60. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  61. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  62. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
  63. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  64. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  65. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
  66. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  67. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  68. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  69. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  70. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +205 -41
  71. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  72. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  73. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
  74. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
  75. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  76. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  77. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  78. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  79. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  80. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  81. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  82. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  83. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  84. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
  85. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  86. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  87. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  88. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  89. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  90. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  91. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  92. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  93. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  94. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  95. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  96. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  97. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  98. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  99. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  100. package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
  101. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  102. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  103. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  104. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  105. package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
  106. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  107. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  108. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  109. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  110. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  111. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  112. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  113. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  114. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  115. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  116. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  117. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  118. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  119. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  120. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  121. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
  122. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  123. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  124. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  125. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  126. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  127. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  128. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  129. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  130. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  131. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  132. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  133. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  134. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  135. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  136. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  137. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  138. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  139. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  140. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
  141. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  142. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  143. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  144. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  145. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  146. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  147. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  148. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  149. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  150. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  151. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  152. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  153. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  154. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  155. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  156. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  157. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  158. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  159. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  160. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  161. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  162. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  163. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  164. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
  165. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
  166. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
  167. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
  168. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
  169. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
  170. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
  171. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
  172. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
  173. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  174. package/dist/extensions/question/index.ts +21 -6
  175. package/dist/extensions/question/question-list.ts +7 -1
  176. package/dist/extensions/question/row-layout.ts +3 -0
  177. package/dist/extensions/question/tests/exp.test.ts +70 -0
  178. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  179. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  180. package/dist/extensions/question/tests/helpers.test.ts +7 -0
  181. package/dist/extensions/question/tests/question-list.test.ts +140 -0
  182. package/dist/extensions/question/tests/row-layout.test.ts +13 -0
  183. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  184. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  185. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  186. package/dist/skills/pi-subagents/SKILL.md +1 -1
  187. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  188. package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
  189. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  190. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  191. package/docs/settings.md +6 -0
  192. package/package.json +3 -3
@@ -4,6 +4,7 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { describe, it } from "node:test";
6
6
  import { visibleWidth, type MarkdownTheme } from "@earendil-works/pi-tui";
7
+ import { EXTERNAL_RUN_REGISTRY_KEY, EXTERNAL_RUN_REGISTRY_VERSION, registerExternalRun } from "../../src/api/external-runs.ts";
7
8
  import { collectFleetSnapshot, openSubagentFleet, SubagentFleetComponent } from "../../src/tui/fleet.ts";
8
9
  import { persistForegroundRunHistory, restoreForegroundRunHistory } from "../../src/runs/foreground/foreground-history.ts";
9
10
  import { FLEET_STATUS_WIDGET_KEY } from "../../src/tui/fleet-status.ts";
@@ -13,6 +14,10 @@ import type { SubagentState } from "../../src/shared/types.ts";
13
14
  import { fauxAssistantMessage } from "@earendil-works/pi-ai/providers/faux";
14
15
  import { createAssistantMessageEventStream } from "@earendil-works/pi-ai";
15
16
 
17
+ function clearExternalRuns(): void {
18
+ delete (globalThis as Record<PropertyKey, unknown>)[Symbol.for(EXTERNAL_RUN_REGISTRY_KEY)];
19
+ }
20
+
16
21
  function stateForTest(): SubagentState {
17
22
  return {
18
23
  baseCwd: process.cwd(),
@@ -168,6 +173,138 @@ describe("native subagent fleet", () => {
168
173
  }
169
174
  });
170
175
 
176
+ it("shows cached external jobs as display-only without reading paths or invoking controls", async () => {
177
+ clearExternalRuns();
178
+ registerExternalRun({
179
+ id: "external-review",
180
+ sessionId: "session-current",
181
+ source: "interactive-shell",
182
+ label: "Dependency review",
183
+ state: "completed",
184
+ startedAt: 100,
185
+ updatedAt: 200,
186
+ currentAction: "Inspecting package metadata",
187
+ preview: "No dependency blockers found.",
188
+ reportPath: "/outside/trusted/roots/report.md",
189
+ transcriptPath: "/outside/trusted/roots/transcript.jsonl",
190
+ });
191
+ const state = stateForTest();
192
+ const snapshot = collectFleetSnapshot(state);
193
+ assert.equal(snapshot.error, undefined);
194
+ assert.deepEqual(snapshot.items.map((item) => ({ key: item.key, kind: item.kind, agent: item.agent })), [{
195
+ key: "external:external-review",
196
+ kind: "external",
197
+ agent: "Dependency review",
198
+ }]);
199
+ let steerCalls = 0;
200
+ let stopCalls = 0;
201
+ let inspectCalls = 0;
202
+ const component = new SubagentFleetComponent(
203
+ { terminal: { rows: 32, columns: 120 }, requestRender() {} } as never,
204
+ theme as never,
205
+ state,
206
+ () => {},
207
+ {
208
+ refreshMs: 60_000,
209
+ markdownTheme,
210
+ actions: {
211
+ async steer() { steerCalls++; return { text: "unexpected" }; },
212
+ stop() { stopCalls++; return { text: "unexpected" }; },
213
+ async inspect() { inspectCalls++; return { text: "unexpected" }; },
214
+ },
215
+ },
216
+ );
217
+ try {
218
+ const initial = component.render(120).join("\n");
219
+ assert.match(initial, /Fleet inspector.*external display-only/);
220
+ assert.match(initial, /Source: external · display-only/);
221
+ assert.match(initial, /Owner: interactive-shell/);
222
+ assert.match(initial, /Inspecting package metadata/);
223
+ assert.match(initial, /Elapsed: 100ms/);
224
+ assert.match(initial, /No dependency blockers found/);
225
+ assert.match(initial, /Report path: \/outside\/trusted\/roots\/report\.md/);
226
+ assert.match(initial, /Transcript path: \/outside\/trusted\/roots\/transcript\.jsonl/);
227
+ assert.doesNotMatch(initial, /Herdr ·|steer ·|stop ·/);
228
+ component.handleInput("H");
229
+ assert.match(component.render(120).join("\n"), /display-only and have no Herdr controls/);
230
+ component.handleInput("s");
231
+ assert.match(component.render(120).join("\n"), /display-only and remain controlled/);
232
+ component.handleInput("D");
233
+ assert.match(component.render(120).join("\n"), /display-only and remain controlled/);
234
+ await new Promise((resolve) => setImmediate(resolve));
235
+ assert.deepEqual({ steerCalls, stopCalls, inspectCalls }, { steerCalls: 0, stopCalls: 0, inspectCalls: 0 });
236
+ } finally {
237
+ component.dispose();
238
+ clearExternalRuns();
239
+ }
240
+ });
241
+
242
+ it("keeps running external elapsed time live when endedAt is present", () => {
243
+ clearExternalRuns();
244
+ try {
245
+ const startedAt = Date.now() - 5_000;
246
+ registerExternalRun({
247
+ id: "external-running",
248
+ sessionId: "session-current",
249
+ source: "interactive-shell",
250
+ label: "Running external job",
251
+ state: "running",
252
+ startedAt,
253
+ updatedAt: startedAt + 100,
254
+ endedAt: startedAt + 100,
255
+ });
256
+ const component = new SubagentFleetComponent(
257
+ { terminal: { rows: 32, columns: 120 }, requestRender() {} } as never,
258
+ theme as never,
259
+ stateForTest(),
260
+ () => {},
261
+ { refreshMs: 60_000, markdownTheme },
262
+ );
263
+ try {
264
+ const rendered = component.render(120).join("\n");
265
+ assert.match(rendered, /Elapsed: 5\.0s/);
266
+ assert.doesNotMatch(rendered, /Elapsed: 100ms/);
267
+ } finally {
268
+ component.dispose();
269
+ }
270
+ } finally {
271
+ clearExternalRuns();
272
+ }
273
+ });
274
+
275
+ it("ignores malformed cached external jobs while rendering Fleet", () => {
276
+ clearExternalRuns();
277
+ const warnings: string[] = [];
278
+ const originalWarn = console.warn;
279
+ console.warn = (message?: unknown) => warnings.push(String(message));
280
+ (globalThis as Record<PropertyKey, unknown>)[Symbol.for(EXTERNAL_RUN_REGISTRY_KEY)] = {
281
+ version: EXTERNAL_RUN_REGISTRY_VERSION,
282
+ runs: new Map<string, unknown>([[
283
+ "session-current\0bad",
284
+ { id: "bad", sessionId: "session-current", source: "tool", label: "Bad external", state: "running", startedAt: Number.NaN },
285
+ ]]),
286
+ };
287
+ const state = stateForTest();
288
+ const component = new SubagentFleetComponent(
289
+ { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
290
+ theme as never,
291
+ state,
292
+ () => {},
293
+ { refreshMs: 60_000, markdownTheme },
294
+ );
295
+ try {
296
+ const snapshot = collectFleetSnapshot(state);
297
+ assert.equal(snapshot.error, undefined);
298
+ assert.deepEqual(snapshot.items, []);
299
+ assert.match(warnings[0]!, /Removed Malformed cached external run/);
300
+ assert.match(component.render(100).join("\n"), /No current-session Fleet jobs/);
301
+ } finally {
302
+ console.warn = originalWarn;
303
+ component.dispose();
304
+ clearExternalRuns();
305
+ }
306
+ });
307
+
171
308
  it("shows live prompt summaries and opens Prompt Audit with the authored prompt visible", async () => {
172
309
  const sentinel = "PROMPT_AUDIT_SENTINEL_1021";
173
310
  const secondSentinel = "PROMPT_AUDIT_SECOND_CHILD";
@@ -367,16 +504,26 @@ describe("native subagent fleet", () => {
367
504
  assert.equal(snapshot.items[0]?.agent, "workflow");
368
505
  });
369
506
 
370
- it("labels workflow-owned foreground children and opens their parent Herdr inspector", async () => {
507
+ it("hides workflow-owned foreground duplicates and opens the workflow parent inspector", async () => {
371
508
  const state = stateForTest();
372
509
  state.asyncJobs.set("workflow-1", {
373
510
  asyncId: "workflow-1",
374
511
  asyncDir: "/tmp/workflow-1",
512
+ sessionId: "session-current",
375
513
  status: "running",
376
514
  mode: "workflow",
377
515
  startedAt: 10,
378
516
  updatedAt: 20,
379
517
  });
518
+ state.asyncJobs.set("unrelated", {
519
+ asyncId: "unrelated",
520
+ asyncDir: "/tmp/unrelated",
521
+ sessionId: "session-current",
522
+ status: "running",
523
+ mode: "single",
524
+ startedAt: 5,
525
+ updatedAt: 30,
526
+ });
380
527
  state.foregroundControls.set("child-1", {
381
528
  runId: "child-1",
382
529
  parentWorkflowRunId: "workflow-1",
@@ -387,8 +534,7 @@ describe("native subagent fleet", () => {
387
534
  activeChildren: new Map([[0, { index: 0, agent: "reviewer", startedAt: 10, updatedAt: 20 }]]),
388
535
  });
389
536
  const snapshot = collectFleetSnapshot(state);
390
- const child = snapshot.items.find((item) => item.key === "foreground-active:child-1:0");
391
- assert.equal(child?.kind, "foreground-active");
537
+ assert.deepEqual(snapshot.items.map((item) => item.key), ["async:unrelated", "async:workflow-1"]);
392
538
  const calls: Array<{ runId: string; asyncDir: string; index?: number }> = [];
393
539
  const component = new SubagentFleetComponent(
394
540
  { terminal: { rows: 28, columns: 100 }, requestRender() {} } as never,
@@ -406,7 +552,7 @@ describe("native subagent fleet", () => {
406
552
  },
407
553
  );
408
554
  try {
409
- assert.ok(component.render(100).some((line) => line.includes("Workflow child of: workflow-1 (review)")));
555
+ assert.ok(component.render(100).some((line) => line.includes("workflow")));
410
556
  component.handleInput("H");
411
557
  await new Promise((resolve) => setImmediate(resolve));
412
558
  assert.deepEqual(calls, [{ runId: "workflow-1", asyncDir: "/tmp/workflow-1" }]);
@@ -536,6 +682,7 @@ describe("native subagent fleet", () => {
536
682
  fs.writeFileSync(outputPath, "restored foreground output", "utf-8");
537
683
 
538
684
  const state = stateForTest();
685
+ state.artifactDirPreference = "project";
539
686
  state.foregroundRuns!.set("restored", {
540
687
  runId: "restored",
541
688
  mode: "single",
@@ -556,6 +703,7 @@ describe("native subagent fleet", () => {
556
703
 
557
704
  const restored = stateForTest();
558
705
  restored.baseCwd = cwd;
706
+ restored.artifactDirPreference = "project";
559
707
  assert.equal(restoreForegroundRunHistory(restored, { resultsDir }), 1);
560
708
  const snapshot = collectFleetSnapshot(restored);
561
709
  assert.deepEqual(snapshot.items.map((item) => item.key), ["foreground-recent:restored:0"]);
@@ -636,6 +784,7 @@ describe("native subagent fleet", () => {
636
784
  const cwd = path.join(root, "project");
637
785
  const artifactsRoot = getProjectArtifactsDir(cwd);
638
786
  const state = stateForTest();
787
+ state.artifactDirPreference = "project";
639
788
  for (let index = 0; index < 3; index++) {
640
789
  const outputPath = path.join(artifactsRoot, "outputs", `run-${index}`, "output.md");
641
790
  fs.mkdirSync(path.dirname(outputPath), { recursive: true });
@@ -654,6 +803,7 @@ describe("native subagent fleet", () => {
654
803
 
655
804
  const restored = stateForTest();
656
805
  restored.baseCwd = cwd;
806
+ restored.artifactDirPreference = "project";
657
807
  assert.equal(restoreForegroundRunHistory(restored, { resultsDir, limit: 2 }), 2);
658
808
  assert.deepEqual([...restored.foregroundRuns!.keys()], ["run-2", "run-1"]);
659
809
 
@@ -802,6 +952,7 @@ describe("native subagent fleet", () => {
802
952
  fs.writeFileSync(path.join(asyncDir, "status.json"), "{in-flight status", "utf-8");
803
953
  const state = stateForTest();
804
954
  state.baseCwd = path.join(root, "parent-cwd");
955
+ state.artifactDirPreference = "project";
805
956
  state.asyncJobs.set("async-custom-cwd", {
806
957
  asyncId: "async-custom-cwd",
807
958
  asyncDir,
@@ -883,6 +1034,7 @@ describe("native subagent fleet", () => {
883
1034
  try {
884
1035
  const state = stateForTest();
885
1036
  state.baseCwd = path.join(root, "parent-cwd");
1037
+ state.artifactDirPreference = "project";
886
1038
  const effectiveCwd = path.join(root, "effective-cwd");
887
1039
  const now = Date.now();
888
1040
  state.foregroundControls.set("foreground-live", {
@@ -1282,6 +1434,40 @@ describe("native subagent fleet", () => {
1282
1434
  }
1283
1435
  });
1284
1436
 
1437
+ it("periodically redraws only while the overlay remains open", (t) => {
1438
+ t.mock.timers.enable({ apis: ["setTimeout"] });
1439
+ const state = stateForTest();
1440
+ let renderRequests = 0;
1441
+ let closed = false;
1442
+ const tui = { terminal: { rows: 28, columns: 90 }, requestRender: () => { renderRequests++; } };
1443
+ const component = new SubagentFleetComponent(
1444
+ tui as never,
1445
+ theme as never,
1446
+ state,
1447
+ () => { closed = true; },
1448
+ { refreshMs: 10 },
1449
+ );
1450
+ t.mock.timers.tick(249);
1451
+ assert.equal(renderRequests, 0, "refresh cadence is bounded away from a hot loop");
1452
+ t.mock.timers.tick(1);
1453
+ assert.equal(renderRequests, 1);
1454
+ component.handleInput("\x1b");
1455
+ assert.equal(closed, true);
1456
+ t.mock.timers.tick(1_000);
1457
+ assert.equal(renderRequests, 1, "closing cancels periodic redraws");
1458
+
1459
+ const disposed = new SubagentFleetComponent(
1460
+ tui as never,
1461
+ theme as never,
1462
+ state,
1463
+ () => {},
1464
+ { refreshMs: 250 },
1465
+ );
1466
+ disposed.dispose();
1467
+ t.mock.timers.tick(1_000);
1468
+ assert.equal(renderRequests, 1, "disposing cancels periodic redraws");
1469
+ });
1470
+
1285
1471
  it("refreshes the roster while the overlay remains open", async () => {
1286
1472
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-fleet-refresh-"));
1287
1473
  try {
@@ -1293,7 +1479,7 @@ describe("native subagent fleet", () => {
1293
1479
  theme as never,
1294
1480
  state,
1295
1481
  () => {},
1296
- { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 10 },
1482
+ { asyncDirRoot: root, resultsDir: path.join(root, "results"), refreshMs: 250 },
1297
1483
  );
1298
1484
  let invalidations = 0;
1299
1485
  const originalInvalidate = component.invalidate.bind(component);
@@ -1305,7 +1491,7 @@ describe("native subagent fleet", () => {
1305
1491
  assert.ok(component.render(90).some((line) => line.includes("No tracked children")));
1306
1492
  const initialOutput = Array.from({ length: 40 }, (_, index) => `output line ${index}`).join("\n");
1307
1493
  const asyncDir = writeAsyncRun(root, { id: "appeared-live", output: initialOutput });
1308
- await new Promise((resolve) => setTimeout(resolve, 35));
1494
+ await new Promise((resolve) => setTimeout(resolve, 275));
1309
1495
  let lines = component.render(90);
1310
1496
  assert.ok(lines.some((line) => line.includes("appeared")));
1311
1497
  assert.ok(lines.some((line) => line.includes("output line 39")));
@@ -54,6 +54,38 @@ describe("Herdr inspector bootstrap", () => {
54
54
  }
55
55
  });
56
56
 
57
+ it("uses PATH node when Pi is a standalone executable", async () => {
58
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-herdr-bootstrap-standalone-"));
59
+ const originalExecPath = process.execPath;
60
+ try {
61
+ writeCompletedRun(root);
62
+ process.execPath = path.join(root, process.platform === "win32" ? "pi.exe" : "pi");
63
+ const calls: string[][] = [];
64
+ const client: HerdrClient = {
65
+ run: async <T>(args: string[]) => {
66
+ calls.push(args);
67
+ if (args[0] === "--version") return { ok: true, data: "herdr 0.7.5" as T };
68
+ if (args[0] === "pane" && args[1] === "split") return { ok: true, data: { pane: { pane_id: "w1:p9" } } as T };
69
+ return { ok: true, data: {} as T };
70
+ },
71
+ };
72
+ const opened = await handleHerdrInspectorAction("inspector.open", { id: "run-123" }, {
73
+ cwd: root,
74
+ asyncDirRoot: root,
75
+ resultsDir: path.join(root, "results"),
76
+ client,
77
+ });
78
+ assert.equal(opened.isError, undefined);
79
+ const command = calls.find((args) => args[0] === "pane" && args[1] === "run")?.[3] ?? "";
80
+ assert.equal(command.startsWith("& "), process.platform === "win32");
81
+ assert.match(command, process.platform === "win32" ? /^& "node\.exe" / : /^node /);
82
+ assert.doesNotMatch(command, /(?:^|[\\/])pi(?:\.exe)?'/);
83
+ } finally {
84
+ process.execPath = originalExecPath;
85
+ fs.rmSync(root, { recursive: true, force: true });
86
+ }
87
+ });
88
+
57
89
  it("starts the packaged bootstrap with ordinary Node", () => {
58
90
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-herdr-bootstrap-run-"));
59
91
  try {
@@ -0,0 +1,59 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawnSync } from "node:child_process";
3
+ import * as fs from "node:fs";
4
+ import * as os from "node:os";
5
+ import * as path from "node:path";
6
+ import { describe, it } from "node:test";
7
+ import { formatShellCommand } from "../../src/inspectors/herdr/shell-command.ts";
8
+
9
+ describe("formatShellCommand on POSIX shells", () => {
10
+ const platform: NodeJS.Platform = "linux";
11
+
12
+ it("keeps a shell-safe executable bare so Nushell parses it as a command", () => {
13
+ assert.equal(
14
+ formatShellCommand("/usr/bin/node", ["/home/u/.pi/inspector-runner.mjs", "--async-dir", "/tmp/run-1|fc_abc"], platform),
15
+ "/usr/bin/node '/home/u/.pi/inspector-runner.mjs' '--async-dir' '/tmp/run-1|fc_abc'",
16
+ );
17
+ });
18
+
19
+ it("keeps bare command names bare", () => {
20
+ assert.equal(formatShellCommand("pi", ["review the diff"], platform), "pi 'review the diff'");
21
+ });
22
+
23
+ it("uses a bare sh invoker for an executable containing spaces and equals", () => {
24
+ assert.equal(
25
+ formatShellCommand("/opt/My Tools/node=bin", ["--flag"], platform),
26
+ `sh -c 'exec "$0" "$@"' '/opt/My Tools/node=bin' '--flag'`,
27
+ );
28
+ });
29
+
30
+ it("quotes executable paths and arguments containing quotes", () => {
31
+ assert.equal(
32
+ formatShellCommand("/opt/My Tools/it's node", ["it's", "a|b"], platform),
33
+ "sh -c 'exec \"$0\" \"$@\"' '/opt/My Tools/it'\\''s node' 'it'\\''s' 'a|b'",
34
+ );
35
+ });
36
+
37
+ it("executes an equals-sign path through the shell wrapper", { skip: process.platform === "win32" }, () => {
38
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi herdr shell="));
39
+ try {
40
+ const executable = path.join(root, "tool=' value");
41
+ fs.writeFileSync(executable, "#!/bin/sh\nprintf '%s' \"$1\"\n", "utf-8");
42
+ fs.chmodSync(executable, 0o755);
43
+ const launched = spawnSync("/bin/sh", ["-c", formatShellCommand(executable, ["arg=' value"], platform)], { encoding: "utf-8" });
44
+ assert.equal(launched.status, 0, launched.stderr);
45
+ assert.equal(launched.stdout, "arg=' value");
46
+ } finally {
47
+ fs.rmSync(root, { recursive: true, force: true });
48
+ }
49
+ });
50
+ });
51
+
52
+ describe("formatShellCommand on Windows PowerShell", () => {
53
+ it("prefixes the call operator and preserves argument quoting", () => {
54
+ assert.equal(
55
+ formatShellCommand("C:\\Program Files\\nodejs\\node.exe", ["two words", 'say "hi"'], "win32"),
56
+ '& "C:\\Program Files\\nodejs\\node.exe" "two words" "say \\"hi\\""',
57
+ );
58
+ });
59
+ });
@@ -368,6 +368,75 @@ describe("subagent extension child mode", () => {
368
368
  }
369
369
  });
370
370
 
371
+ it("uses configured main-window renderer spacing for call rows", () => {
372
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-renderer-density-config-"));
373
+ try {
374
+ const configDir = path.join(agentDir, "extensions", "subagent");
375
+ fs.mkdirSync(configDir, { recursive: true });
376
+ fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ mainWindowRenderer: { horizontalSpacing: 0 } }), "utf-8");
377
+
378
+ const script = String.raw`
379
+ import registerSubagentExtension from "./index.ts";
380
+ const events = { on() { return () => {}; }, emit() {} };
381
+ let registeredTool;
382
+ const fakePi = new Proxy({
383
+ events,
384
+ registerTool(tool) { if (tool.name === "subagent") registeredTool = tool; },
385
+ registerCommand() {}, registerShortcut() {}, registerMessageRenderer() {}, sendMessage() {}, getSessionName() {},
386
+ }, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
387
+ registerSubagentExtension(fakePi);
388
+ if (!registeredTool) throw new Error("tool not registered");
389
+ const theme = { fg(_name, text) { return text; }, bold(text) { return text; } };
390
+ const call = registeredTool.renderCall({ agent: "worker", async: true }, theme).render(120).map((line) => line.trimEnd());
391
+ if (call.length !== 1 || call[0] !== "subagentworker[async]") throw new Error("unexpected call row: " + JSON.stringify(call));
392
+ `;
393
+ const env = parentToolEnv();
394
+ env.SELESAI_CODING_AGENT_DIR = agentDir;
395
+ execFileSync(process.execPath, ["--experimental-strip-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script], { cwd: projectRoot, env, stdio: "pipe" });
396
+ } finally {
397
+ fs.rmSync(agentDir, { recursive: true, force: true });
398
+ }
399
+ });
400
+
401
+ it("uses configured main-window renderer density for slash results", () => {
402
+ const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-slash-renderer-density-config-"));
403
+ try {
404
+ const configDir = path.join(agentDir, "extensions", "subagent");
405
+ fs.mkdirSync(configDir, { recursive: true });
406
+ fs.writeFileSync(path.join(configDir, "config.json"), JSON.stringify({ mainWindowRenderer: { horizontalSpacing: 0, compactResultMaxLines: 3 } }), "utf-8");
407
+
408
+ const script = String.raw`
409
+ import registerSubagentExtension from "./index.ts";
410
+ const events = { on() { return () => {}; }, emit() {} };
411
+ let slashRenderer;
412
+ const fakePi = new Proxy({
413
+ events,
414
+ registerTool() {}, registerCommand() {}, registerShortcut() {}, sendMessage() {}, getSessionName() {},
415
+ registerMessageRenderer(type, renderer) { if (type === "subagent-slash-result") slashRenderer = renderer; },
416
+ }, { get(target, prop) { return prop in target ? target[prop] : () => undefined; } });
417
+ registerSubagentExtension(fakePi);
418
+ if (!slashRenderer) throw new Error("slash renderer not registered");
419
+ const result = slashRenderer({ details: {
420
+ requestId: "slash-density",
421
+ result: { content: [{ type: "text", text: "done" }], details: { mode: "parallel", results: [
422
+ { agent: "scout", task: "a", exitCode: 0, messages: [], usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 } },
423
+ { agent: "reviewer", task: "b", exitCode: 0, messages: [], usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 } },
424
+ { agent: "writer", task: "c", exitCode: 0, messages: [], usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, turns: 0 } },
425
+ ] } },
426
+ } }, { expanded: false }, { fg(_name, text) { return text; }, bg(_name, text) { return text; }, bold(text) { return text; } });
427
+ const lines = result.render(120);
428
+ if (lines.length !== 6) throw new Error("expected outer spacer, box rows, and three capped result rows: " + JSON.stringify(lines));
429
+ if (!lines[4].includes("rows hidden")) throw new Error("compact cap was not applied: " + JSON.stringify(lines));
430
+ if (!lines[3].includes(" ✓ Agent 1/3: scout")) throw new Error("zero spacing was not applied: " + JSON.stringify(lines));
431
+ `;
432
+ const env = parentToolEnv();
433
+ env.SELESAI_CODING_AGENT_DIR = agentDir;
434
+ execFileSync(process.execPath, ["--experimental-strip-types", "--import", "./test/support/register-loader.mjs", "--input-type=module", "--eval", script], { cwd: projectRoot, env, stdio: "pipe" });
435
+ } finally {
436
+ fs.rmSync(agentDir, { recursive: true, force: true });
437
+ }
438
+ });
439
+
371
440
  it("registers only subagent_wait and honors waitTool disabled config", () => {
372
441
  const agentDir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-wait-tool-config-"));
373
442
  try {
@@ -104,6 +104,19 @@ describe("resolveIntercomBridge", () => {
104
104
  assert.equal(bridge.extensionDir, NATIVE_INTERCOM_EXTENSION_DIR);
105
105
  });
106
106
 
107
+ it("uses a per-run override instead of the global config", () => {
108
+ const bridge = resolveIntercomBridge({
109
+ config: { mode: "always", resultDelivery: true },
110
+ override: { mode: "off" },
111
+ context: "fresh",
112
+ orchestratorTarget: "main",
113
+ });
114
+
115
+ assert.equal(bridge.active, false);
116
+ assert.equal(bridge.mode, "off");
117
+ assert.equal(bridge.resultDelivery, false);
118
+ });
119
+
107
120
  it("can disable external grouped-result delivery without disabling supervisor coordination", () => {
108
121
  const bridge = resolveIntercomBridge({
109
122
  config: { mode: "always", resultDelivery: false },
@@ -166,17 +179,18 @@ describe("applyIntercomBridgeToAgent", () => {
166
179
  instruction: "Intercom orchestration channel:\n- Need a decision or blocked: contact_supervisor({ reason: \"need_decision\", message: \"<question>\" })\n- Blocked/update: contact_supervisor({ reason: \"progress_update\", message: \"UPDATE: <summary>\" })",
167
180
  };
168
181
 
169
- it("injects intercom tool and prompt instructions", () => {
182
+ it("injects contact_supervisor and prompt instructions", () => {
170
183
  const updated = applyIntercomBridgeToAgent(makeAgent({ tools: ["read", "bash"] }), activeBridge);
171
- assert.deepEqual(updated.tools, ["read", "bash", "intercom", "contact_supervisor"]);
184
+ assert.deepEqual(updated.tools, ["read", "bash", "contact_supervisor"]);
172
185
  assert.match(updated.systemPrompt, /Intercom orchestration channel:/);
173
186
  assert.match(updated.systemPrompt, /contact_supervisor/);
187
+ assert.doesNotMatch(updated.systemPrompt ?? "", /Generic intercom/);
174
188
  });
175
189
 
176
190
  it("is idempotent", () => {
177
191
  const first = applyIntercomBridgeToAgent(makeAgent({ tools: ["read"] }), activeBridge);
178
192
  const second = applyIntercomBridgeToAgent(first, activeBridge);
179
- assert.equal(second.tools?.filter((tool) => tool === "intercom").length, 1);
193
+ assert.equal(second.tools?.filter((tool) => tool === "intercom").length, 0);
180
194
  assert.equal(second.tools?.filter((tool) => tool === "contact_supervisor").length, 1);
181
195
  assert.equal(second.systemPrompt, first.systemPrompt);
182
196
  });
@@ -184,10 +198,15 @@ describe("applyIntercomBridgeToAgent", () => {
184
198
  it("does not block native supervisor tools for agents with explicit extension allowlists", () => {
185
199
  const agent = makeAgent({ tools: ["read"], extensions: ["/tmp/other-extension/index.ts"] });
186
200
  const updated = applyIntercomBridgeToAgent(agent, activeBridge);
187
- assert.deepEqual(updated.tools, ["read", "intercom", "contact_supervisor"]);
201
+ assert.deepEqual(updated.tools, ["read", "contact_supervisor"]);
188
202
  assert.match(updated.systemPrompt, /contact_supervisor/);
189
203
  });
190
204
 
205
+ it("preserves explicitly requested external intercom tools", () => {
206
+ const updated = applyIntercomBridgeToAgent(makeAgent({ tools: ["read", "intercom"] }), activeBridge);
207
+ assert.deepEqual(updated.tools, ["read", "intercom", "contact_supervisor"]);
208
+ });
209
+
191
210
  it("does not widen explicit empty or MCP-only builtin allowlists", () => {
192
211
  const emptyTools = applyIntercomBridgeToAgent(makeAgent({ tools: [] }), activeBridge);
193
212
  assert.deepEqual(emptyTools.tools, []);
@@ -4,6 +4,7 @@ import {
4
4
  arbitrateCompletionGuardRescue,
5
5
  createTaskMutationArbiter,
6
6
  isCompletionGuardFailure,
7
+ mapArbiterDecision,
7
8
  maybeRescueCompletionGuardFailure,
8
9
  type TaskMutationArbiter,
9
10
  } from "../../src/runs/shared/llm-intent-arbiter.ts";
@@ -30,7 +31,7 @@ function stubArbiter(verdict: "read-only" | "implementation" | "unavailable" | "
30
31
  }
31
32
 
32
33
  describe("arbitrateCompletionGuardRescue", () => {
33
- it("rescues only a confident read-only verdict", async () => {
34
+ it("rescues on a read-only verdict, keeps the failure otherwise", async () => {
34
35
  const rescued = await arbitrateCompletionGuardRescue({
35
36
  guardTriggered: true,
36
37
  task: "t",
@@ -47,6 +48,18 @@ describe("arbitrateCompletionGuardRescue", () => {
47
48
  }
48
49
  });
49
50
 
51
+ it("never arbitrates from partial evidence on tasks over 8000 chars", async () => {
52
+ const middle = "And now apply the fix in the middle of this long task. ";
53
+ const longTask = "Review the setup. ".repeat(400) + middle + "Review more. ".repeat(400);
54
+ assert.ok(longTask.length > 8000);
55
+ const kept = await arbitrateCompletionGuardRescue({
56
+ guardTriggered: true,
57
+ task: longTask,
58
+ arbiter: stubArbiter("read-only"),
59
+ });
60
+ assert.deepEqual(kept, { triggered: true, rescued: false });
61
+ });
62
+
50
63
  it("keeps the guard verdict without an arbiter or on arbiter failure", async () => {
51
64
  assert.deepEqual(
52
65
  await arbitrateCompletionGuardRescue({ guardTriggered: true, task: "t" }),
@@ -63,6 +76,48 @@ describe("arbitrateCompletionGuardRescue", () => {
63
76
  });
64
77
  });
65
78
 
79
+ describe("mapArbiterDecision", () => {
80
+ it("only a high-confidence read_only rescues", () => {
81
+ assert.equal(mapArbiterDecision({ classification: "read_only", confidence: "high" }), "read-only");
82
+ for (const confidence of ["low", "medium", undefined] as const) {
83
+ assert.equal(
84
+ mapArbiterDecision({ classification: "read_only", confidence }),
85
+ "implementation",
86
+ String(confidence),
87
+ );
88
+ }
89
+ assert.equal(mapArbiterDecision({ classification: "implementation", confidence: "high" }), "implementation");
90
+ assert.equal(mapArbiterDecision(undefined), "unavailable");
91
+ });
92
+ });
93
+
94
+ describe("arbiter auth receiver", () => {
95
+ it("resolves registry credentials through the registry receiver (class instance state)", async () => {
96
+ // Prototype method reading instance state: a detached call would lose
97
+ // `this` and fail auth. The fix must call it as a method on the registry.
98
+ class FakeRegistry {
99
+ readonly key = "instance-key";
100
+ async getApiKeyAndHeaders(_model: unknown) {
101
+ return { ok: true, apiKey: this.key };
102
+ }
103
+ }
104
+ let captured: Record<string, unknown> | undefined;
105
+ const ctx = {
106
+ model: { provider: "test", id: "model-1", api: "test-api" },
107
+ modelRegistry: new FakeRegistry(),
108
+ } as never;
109
+ const arbiter = createTaskMutationArbiter(ctx, {
110
+ streamFn: async (_model: unknown, _context: unknown, opts: Record<string, unknown>) => {
111
+ captured = opts;
112
+ throw new Error("stream fail");
113
+ },
114
+ })!;
115
+ const verdict = await arbiter("t");
116
+ assert.equal(verdict, "unavailable"); // stream deliberately fails
117
+ assert.equal(captured?.apiKey, "instance-key", "registry credential must reach the stream function");
118
+ });
119
+ });
120
+
66
121
  describe("isCompletionGuardFailure", () => {
67
122
  it("recognizes the guard error prefix", () => {
68
123
  assert.equal(isCompletionGuardFailure({ error: GUARD_ERROR }), true);