@selesai/code 0.8.5 → 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 (231) hide show
  1. package/CHANGELOG.md +28 -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/models.json +5 -0
  16. package/dist/defaults/settings.json +1 -0
  17. package/dist/extensions/agent-browser.test.ts +178 -2
  18. package/dist/extensions/agent-browser.ts +2 -0
  19. package/dist/extensions/copy-turn.test.ts +229 -0
  20. package/dist/extensions/copy-turn.ts +11 -0
  21. package/dist/extensions/grep-app/index.test.ts +356 -1
  22. package/dist/extensions/grep-app/index.ts +6 -1
  23. package/dist/extensions/handoff-new.test.ts +351 -161
  24. package/dist/extensions/handoff-new.ts +3 -0
  25. package/dist/extensions/inline-skills.test.ts +93 -0
  26. package/dist/extensions/inline-skills.ts +3 -0
  27. package/dist/extensions/model-prompt-injector/config.json +10 -0
  28. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  29. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  30. package/dist/extensions/package.json +1 -0
  31. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  32. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  33. package/dist/extensions/pi-subagents/agents/builder.md +5 -2
  34. package/dist/extensions/pi-subagents/agents/commentator.md +1 -1
  35. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  36. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  37. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  38. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  39. package/dist/extensions/pi-subagents/docs/configuration.md +78 -11
  40. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  41. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  42. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  43. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  44. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  45. package/dist/extensions/pi-subagents/docs/workflows.md +4 -4
  46. package/dist/extensions/pi-subagents/install.mjs +2 -2
  47. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  48. package/dist/extensions/pi-subagents/package.json +1 -1
  49. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  50. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  51. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +16 -15
  52. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  53. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  54. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  55. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  56. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  57. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  58. package/dist/extensions/pi-subagents/src/extension/config.ts +59 -0
  59. package/dist/extensions/pi-subagents/src/extension/index.ts +146 -23
  60. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  61. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  62. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  63. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  64. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  65. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  66. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  67. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  68. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  69. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  70. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  71. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  72. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  73. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  74. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  75. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +17 -6
  76. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  77. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  78. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +11 -3
  79. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  80. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  81. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  82. package/dist/extensions/pi-subagents/src/runs/background/notify.ts +5 -2
  83. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  84. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +224 -42
  85. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  86. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  87. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +35 -26
  88. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +108 -9
  89. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  90. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  91. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  92. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  93. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  94. package/dist/extensions/pi-subagents/src/runs/background/wait-subscriptions.ts +2 -1
  95. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  96. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  97. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  98. package/dist/extensions/pi-subagents/src/runs/foreground/foreground-history.ts +7 -4
  99. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  100. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +368 -50
  101. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  102. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  103. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  104. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  105. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  106. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  107. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  108. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  109. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  110. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  112. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  113. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  114. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  115. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  116. package/dist/extensions/pi-subagents/src/shared/session-lineage.ts +71 -0
  117. package/dist/extensions/pi-subagents/src/shared/types.ts +66 -4
  118. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  119. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  120. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  121. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  122. package/dist/extensions/pi-subagents/src/tui/fleet.ts +129 -17
  123. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  124. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  125. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  126. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  127. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  128. package/dist/extensions/pi-subagents/src/workflows/workflow-auto-relaunch.ts +28 -0
  129. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  130. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  131. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  132. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  133. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  134. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  135. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  136. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  137. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  138. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  139. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +369 -98
  140. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  141. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  142. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  143. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  144. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  145. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  146. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  147. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  148. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  149. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  150. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  151. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  152. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  153. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  154. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  155. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  156. package/dist/extensions/pi-subagents/test/unit/handoff-adoption.test.ts +103 -0
  157. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  158. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  159. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +91 -0
  160. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  161. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  162. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  163. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  164. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  165. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  166. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  167. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  168. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  169. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  170. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  171. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  172. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  173. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  174. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  175. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  176. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  177. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  178. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  179. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  180. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  181. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  182. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  183. package/dist/extensions/pi-subagents/test/unit/session-lineage.test.ts +73 -0
  184. package/dist/extensions/pi-subagents/test/unit/stale-run-reconciler.test.ts +128 -0
  185. package/dist/extensions/pi-subagents/test/unit/subagent-control.test.ts +28 -0
  186. package/dist/extensions/pi-subagents/test/unit/subagent-prompt-runtime.test.ts +16 -7
  187. package/dist/extensions/pi-subagents/test/unit/subagent-startup-retry.test.ts +15 -0
  188. package/dist/extensions/pi-subagents/test/unit/subagent-wait.test.ts +79 -15
  189. package/dist/extensions/pi-subagents/test/unit/tool-description.test.ts +12 -12
  190. package/dist/extensions/pi-subagents/test/unit/tool-timeout.test.ts +109 -0
  191. package/dist/extensions/pi-subagents/test/unit/wait-subscriptions.test.ts +34 -0
  192. package/dist/extensions/pi-subagents/test/unit/workflow-auto-relaunch.test.ts +45 -0
  193. package/dist/extensions/pi-subagents/test/unit/workflow-chat-progress.test.ts +94 -1
  194. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  195. package/dist/extensions/ponytail/index.js +11 -0
  196. package/dist/extensions/ponytail/ponytail-config.cjs +2 -0
  197. package/dist/extensions/ponytail/ponytail-instructions.cjs +6 -0
  198. package/dist/extensions/ponytail/test/extension.test.js +274 -140
  199. package/dist/extensions/ponytail/test/helpers.test.js +280 -92
  200. package/dist/extensions/question/index.ts +33 -0
  201. package/dist/extensions/question/question-list.ts +28 -0
  202. package/dist/extensions/question/row-layout.ts +3 -0
  203. package/dist/extensions/question/tests/batch.test.ts +103 -65
  204. package/dist/extensions/question/tests/exp.test.ts +70 -0
  205. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  206. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  207. package/dist/extensions/question/tests/helpers.test.ts +75 -35
  208. package/dist/extensions/question/tests/question-list.test.ts +640 -198
  209. package/dist/extensions/question/tests/row-layout.test.ts +193 -114
  210. package/dist/extensions/question/tests/schemas.test.ts +42 -19
  211. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  212. package/dist/extensions/question/tests/shortcuts.test.ts +106 -84
  213. package/dist/extensions/question/tests/wizard.test.ts +771 -50
  214. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  215. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  216. package/dist/extensions/rtk.test.ts +180 -1
  217. package/dist/extensions/tokenin-onboarding.ts +3 -0
  218. package/dist/extensions/undo.test.ts +720 -0
  219. package/dist/extensions/undo.ts +6 -0
  220. package/dist/extensions/web-agent-onboarding.test.ts +415 -0
  221. package/dist/extensions/workflow/extension.ts +3 -3
  222. package/dist/extensions/workflow/modes.ts +41 -19
  223. package/dist/skills/pi-subagents/SKILL.md +1 -1
  224. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  225. package/dist/skills/pi-subagents/references/execution-controls.md +16 -15
  226. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  227. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  228. package/docs/plans/workflow-autoloop-reference.md +1 -2
  229. package/docs/plans/workflow-handoff-carryover.md +108 -0
  230. package/docs/settings.md +6 -0
  231. package/package.json +4 -1
@@ -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);
@@ -7,6 +7,7 @@ import { handleMissionAction } from "../../src/missions/actions.ts";
7
7
  import { collectGoalContinuationNotices } from "../../src/missions/goal-driver.ts";
8
8
  import { createMission, readMission, resolveMissionStoreLocation, updateMission } from "../../src/missions/store.ts";
9
9
  import { missionStatePath } from "../../src/missions/workflow-state.ts";
10
+ import type { RetainedChild } from "../../src/runs/background/retained-children.ts";
10
11
 
11
12
  function fixture() {
12
13
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-goal-mission-"));
@@ -46,7 +47,7 @@ describe("goal mission continuation", () => {
46
47
  const resumed = collectGoalContinuationNotices({
47
48
  location: test.location,
48
49
  ownerSessionId: "session-1",
49
- retainedChildren: [{ runId: "child-review", parentRunId: "workflow-1", agent: "reviewer", taskSummary: "Review", completedAt: 1, sessionPath: "/tmp/child.jsonl" }],
50
+ retainedChildren: [{ runId: "child-review", parentRunId: "workflow-1", state: "complete", agent: "reviewer", taskSummary: "Review", completedAt: 1, sessionPath: "/tmp/child.jsonl", resumability: { state: "resumable", sessionPath: "/tmp/child.jsonl" } }],
50
51
  turnId: 3,
51
52
  });
52
53
  assert.equal(resumed.length, 1);
@@ -85,8 +86,8 @@ describe("goal mission continuation", () => {
85
86
  location: test.location,
86
87
  ownerSessionId: "session-1",
87
88
  retainedChildren: [
88
- { runId: "child-old", parentRunId: "workflow-old", agent: "reviewer", taskSummary: "Old", completedAt: 2, sessionPath: "/tmp/old.jsonl" },
89
- { runId: "child-new", parentRunId: "workflow-new", agent: "reviewer", taskSummary: "New", completedAt: 1, sessionPath: "/tmp/new.jsonl" },
89
+ { runId: "child-old", parentRunId: "workflow-old", state: "complete", agent: "reviewer", taskSummary: "Old", completedAt: 2, sessionPath: "/tmp/old.jsonl", resumability: { state: "resumable", sessionPath: "/tmp/old.jsonl" } },
90
+ { runId: "child-new", parentRunId: "workflow-new", state: "complete", agent: "reviewer", taskSummary: "New", completedAt: 1, sessionPath: "/tmp/new.jsonl", resumability: { state: "resumable", sessionPath: "/tmp/new.jsonl" } },
90
91
  ],
91
92
  turnId: 1,
92
93
  });
@@ -99,6 +100,69 @@ describe("goal mission continuation", () => {
99
100
  }
100
101
  });
101
102
 
103
+ it("resumes a mission-linked child older than ten unrelated children", () => {
104
+ const test = fixture();
105
+ try {
106
+ const mission = createMission(test.location, {
107
+ title: "Continue linked work",
108
+ objective: "Resume the linked worker",
109
+ goal: true,
110
+ budget: { tokens: 100 },
111
+ status: "active",
112
+ ownerSessionId: "session-1",
113
+ });
114
+ updateMission(test.location, mission.id, { addRuns: [{ runId: "workflow-mission", mode: "workflow", status: "complete" }] });
115
+ const retainedChildren: RetainedChild[] = [
116
+ ...Array.from({ length: 10 }, (_, offset) => {
117
+ const index = 10 - offset;
118
+ return { runId: `child-${index}`, parentRunId: `workflow-unrelated-${index}`, state: "complete" as const, agent: "worker", taskSummary: "Unrelated", completedAt: index, resumability: { state: "resumable" as const, sessionPath: `/tmp/child-${index}.jsonl` } };
119
+ }),
120
+ { runId: "child-0", parentRunId: "workflow-mission", state: "complete", agent: "worker", taskSummary: "Linked", completedAt: 0, resumability: { state: "resumable", sessionPath: "/tmp/child-0.jsonl" } },
121
+ ];
122
+ const notices = collectGoalContinuationNotices({
123
+ location: test.location,
124
+ ownerSessionId: "session-1",
125
+ retainedChildren,
126
+ turnId: 1,
127
+ });
128
+
129
+ assert.equal(retainedChildren.length, 11);
130
+ assert.equal(notices.length, 1);
131
+ assert.match(notices[0]!.message, /Resume retained child child-0 \(worker\)/);
132
+ assert.doesNotMatch(notices[0]!.message, /child-10/);
133
+ } finally {
134
+ fs.rmSync(test.root, { recursive: true, force: true });
135
+ }
136
+ });
137
+
138
+ it("does not resume non-resumable retained children from goal notices", () => {
139
+ const test = fixture();
140
+ try {
141
+ const mission = createMission(test.location, {
142
+ title: "Ship auth refresh",
143
+ objective: "Implement and validate token refresh",
144
+ goal: true,
145
+ budget: { tokens: 100 },
146
+ status: "active",
147
+ ownerSessionId: "session-1",
148
+ });
149
+ updateMission(test.location, mission.id, { addRuns: [{ runId: "workflow-stopped", mode: "workflow", status: "complete" }] });
150
+
151
+ const notices = collectGoalContinuationNotices({
152
+ location: test.location,
153
+ ownerSessionId: "session-1",
154
+ retainedChildren: [{ runId: "child-stopped", parentRunId: "workflow-stopped", state: "stopped", agent: "worker", taskSummary: "Stopped", completedAt: 1, resumability: { state: "not-resumable", reason: "stopped run" } }],
155
+ turnId: 1,
156
+ });
157
+
158
+ assert.equal(notices.length, 1);
159
+ assert.doesNotMatch(notices[0]!.message, /Resume retained child/);
160
+ assert.match(notices[0]!.message, /Next ready action: Continue objective: Implement and validate token refresh/);
161
+ } finally {
162
+ fs.rmSync(test.root, { recursive: true, force: true });
163
+ }
164
+ });
165
+
102
166
  it("supports pause, resume, and opt-out through mission.update", () => {
103
167
  const test = fixture();
104
168
  try {
@@ -17,7 +17,7 @@ function projectFixture() {
17
17
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-mission-lifecycle-"));
18
18
  const projectRoot = path.join(root, "project");
19
19
  fs.mkdirSync(projectRoot, { recursive: true });
20
- return { root, projectRoot, missionConfig: { globalIndexDir: path.join(root, "global-index") } };
20
+ return { root, projectRoot, missionConfig: { directory: ".pi-subagents/missions", globalIndexDir: path.join(root, "global-index") } };
21
21
  }
22
22
 
23
23
  describe("mission launch lifecycle", () => {
@@ -34,6 +34,20 @@ async function waitForFile(filePath: string, timeoutMs = 10_000): Promise<void>
34
34
  }
35
35
 
36
36
  describe("mission store", () => {
37
+ it("stores default missions outside the project and preserves configured project storage", () => {
38
+ const test = fixture();
39
+ try {
40
+ assert.equal(path.relative(test.projectRoot, test.location.missionDir).startsWith(".."), true);
41
+ assert.equal(test.location.missionDir.startsWith(path.join(test.agentDir, "missions", "projects")), true);
42
+ assert.equal(
43
+ resolveMissionStoreLocation({ projectRoot: test.projectRoot, agentDir: test.agentDir, config: { directory: ".pi/subagents/missions" } }).missionDir,
44
+ path.join(test.projectRoot, ".pi/subagents", "missions"),
45
+ );
46
+ } finally {
47
+ fs.rmSync(test.root, { recursive: true, force: true });
48
+ }
49
+ });
50
+
37
51
  it("creates, reads, updates, lists, and globally indexes project missions", () => {
38
52
  const test = fixture();
39
53
  try {
@@ -68,6 +68,13 @@ describe("model fallback helpers", () => {
68
68
  );
69
69
  });
70
70
 
71
+ it("rejects fallback models that the active registry cannot resolve", () => {
72
+ assert.throws(
73
+ () => buildModelCandidates("gpt-5-mini", ["does-not-exist"], availableModels),
74
+ /Unknown subagent model 'does-not-exist'/,
75
+ );
76
+ });
77
+
71
78
  it("detects retryable provider/model failures", () => {
72
79
  assert.equal(isRetryableModelFailure("rate limit exceeded for provider"), true);
73
80
  assert.equal(isRetryableModelFailure("model unavailable"), true);
@@ -161,6 +168,17 @@ describe("resolveSubagentModelOverride (cross-session inherit, issue #266)", ()
161
168
  );
162
169
  });
163
170
 
171
+ it("rejects explicit models that the active registry cannot resolve", () => {
172
+ assert.throws(
173
+ () => resolveSubagentModelOverride("does-not-exist", parentModel, availableModels),
174
+ /Unknown subagent model 'does-not-exist'/,
175
+ );
176
+ assert.throws(
177
+ () => resolveSubagentModelOverride("does-not-exist:high", parentModel, availableModels),
178
+ /Unknown subagent model 'does-not-exist:high'/,
179
+ );
180
+ });
181
+
164
182
  it("returns undefined when inheriting but no parent model is known", () => {
165
183
  // No parent session model available: fall back to the prior behavior of
166
184
  // emitting no override rather than inventing an invalid one.
@@ -150,7 +150,7 @@ describe("native supervisor channel", () => {
150
150
  channel.start();
151
151
  channel.dispose();
152
152
 
153
- assert.deepEqual(registeredTools, [NATIVE_SUPERVISOR_TOOL_NAME, "intercom"]);
153
+ assert.deepEqual(registeredTools, [NATIVE_SUPERVISOR_TOOL_NAME]);
154
154
  assert.deepEqual(sent.map(({ message }) => message.details?.id), [matchingId]);
155
155
  assert.deepEqual(sent[0]?.options, { triggerTurn: true });
156
156
  assert.equal(channel.pending.has(matchingId), false, "disposed channel clears pending requests");
@@ -553,7 +553,7 @@ describe("native supervisor channel", () => {
553
553
  registeredTools.set(tool.name, tool);
554
554
  },
555
555
  };
556
- registerNativeSupervisorClient(pi as never, { includeIntercomFallback: false });
556
+ registerNativeSupervisorClient(pi as never);
557
557
  const controller = new AbortController();
558
558
  controller.abort();
559
559
 
@@ -39,9 +39,23 @@ const savedEnv = {
39
39
  [SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV]: process.env[SUBAGENT_PARENT_CAPABILITY_TOKEN_ENV],
40
40
  };
41
41
 
42
- afterEach(() => {
42
+ async function removeDirWithRetry(dir: string): Promise<void> {
43
+ let lastError: unknown;
44
+ for (let attempt = 0; attempt < 5; attempt++) {
45
+ try {
46
+ fs.rmSync(dir, { recursive: true, force: true });
47
+ return;
48
+ } catch (error) {
49
+ lastError = error;
50
+ await new Promise((resolve) => setTimeout(resolve, 10));
51
+ }
52
+ }
53
+ throw lastError;
54
+ }
55
+
56
+ afterEach(async () => {
43
57
  for (const route of routes.splice(0)) {
44
- fs.rmSync(path.dirname(route.eventSink), { recursive: true, force: true });
58
+ await removeDirWithRetry(path.dirname(route.eventSink));
45
59
  }
46
60
  for (const [key, value] of Object.entries(savedEnv)) {
47
61
  if (value === undefined) delete process.env[key];
@@ -97,6 +111,15 @@ describe("nested route index", () => {
97
111
  const tokens = new Set([first.capabilityToken, second.capabilityToken]);
98
112
  assert.ok(tokens.has(indexed.capabilityToken), "indexed route must be one of the two created routes");
99
113
  });
114
+
115
+ it("recovers a route from its index when the route file is missing", () => {
116
+ const route = trackRoute("late-route-root");
117
+ const routeFile = path.join(path.dirname(route.eventSink), "route.json");
118
+ fs.rmSync(routeFile);
119
+
120
+ assert.equal(buildNestedRouteIndex().get("late-route-root")?.capabilityToken, route.capabilityToken);
121
+ assert.equal(JSON.parse(fs.readFileSync(routeFile, "utf-8")).capabilityToken, route.capabilityToken);
122
+ });
100
123
  });
101
124
 
102
125
  describe("nested event route validation", () => {
@@ -0,0 +1,26 @@
1
+ import assert from "node:assert/strict";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import { describe, it } from "node:test";
6
+ import { resolveNodeExecutable } from "../../src/shared/node-executable.ts";
7
+
8
+ describe("Node executable resolution", () => {
9
+ it("uses an executable Node path", () => {
10
+ assert.equal(resolveNodeExecutable(process.execPath), process.execPath);
11
+ });
12
+
13
+ it("falls back to PATH node for a standalone Pi executable", () => {
14
+ assert.equal(resolveNodeExecutable(path.join(os.tmpdir(), process.platform === "win32" ? "pi.exe" : "pi")), process.platform === "win32" ? "node.exe" : "node");
15
+ });
16
+
17
+ it("falls back to PATH node for a stale Node path", () => {
18
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-node-executable-"));
19
+ try {
20
+ const staleNode = path.join(root, process.platform === "win32" ? "node.exe" : "node");
21
+ assert.equal(resolveNodeExecutable(staleNode), process.platform === "win32" ? "node.exe" : "node");
22
+ } finally {
23
+ fs.rmSync(root, { recursive: true, force: true });
24
+ }
25
+ });
26
+ });
@@ -134,6 +134,14 @@ describe("registerSubagentNotify", () => {
134
134
  });
135
135
  });
136
136
 
137
+ it("does not attach async status snapshots to subagent-notify details", async () => {
138
+ const { notifier, sent } = createPi("session-a");
139
+ assert.equal(await notifier.deliver(completionResult({ id: "direct-no-snapshot" })), true);
140
+ const message = sent[0]?.message as { customType?: string; details?: Record<string, unknown> } | undefined;
141
+ assert.equal(message?.customType, "subagent-notify");
142
+ assert.equal(message?.details?.asyncSnapshot, undefined);
143
+ });
144
+
137
145
  it("acknowledges direct delivery only after sendMessage accepts it", async () => {
138
146
  const { notifier, sent } = createPi("session-a");
139
147
  assert.equal(await notifier.deliver(completionResult({ id: "direct-accepted" })), true);