@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
@@ -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);
@@ -0,0 +1,348 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import { afterEach, test } from "node:test";
5
+ import * as fs from "node:fs";
6
+ import * as os from "node:os";
7
+ import * as path from "node:path";
8
+ import { createOrcaProgressTab, resolveOrcaCommand, resolvePiSessionId } from "../../src/runs/shared/orca-progress-tabs.ts";
9
+ import { TEMP_ROOT_DIR } from "../../src/shared/types.ts";
10
+ import { writeNodeCommand } from "../support/node-command.ts";
11
+
12
+ const tempDirs: string[] = [];
13
+
14
+ function removeProgressFiles(prefix: string): void {
15
+ const root = path.join(TEMP_ROOT_DIR, "orca-progress");
16
+ if (!fs.existsSync(root)) return;
17
+ for (const name of fs.readdirSync(root)) {
18
+ if (name.startsWith(prefix)) fs.rmSync(path.join(root, name), { force: true });
19
+ }
20
+ }
21
+
22
+ afterEach(() => {
23
+ const progressRoot = path.join(TEMP_ROOT_DIR, "orca-progress");
24
+ for (const dir of tempDirs.splice(0)) {
25
+ const key = createHash("sha256").update(path.resolve(dir)).digest("hex").slice(0, 20);
26
+ fs.rmSync(path.join(progressRoot, `counter-${key}`), { force: true });
27
+ fs.rmSync(path.join(progressRoot, `counter-${key}.lock`), { recursive: true, force: true });
28
+ fs.rmSync(dir, { recursive: true, force: true });
29
+ }
30
+ removeProgressFiles("progress-");
31
+ removeProgressFiles("disabled-run-");
32
+ removeProgressFiles("standalone-pi-");
33
+ });
34
+
35
+ function tempDir(): string {
36
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-orca-tabs-test-"));
37
+ tempDirs.push(dir);
38
+ return dir;
39
+ }
40
+
41
+ async function waitForFile(file: string): Promise<void> {
42
+ const deadline = Date.now() + 5_000;
43
+ while (!fs.existsSync(file)) {
44
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${file}`);
45
+ await new Promise((resolve) => setTimeout(resolve, 20));
46
+ }
47
+ }
48
+
49
+ function progressFile(prefix: string, suffix: ".log" | ".done"): string {
50
+ const root = path.join(TEMP_ROOT_DIR, "orca-progress");
51
+ const name = fs.readdirSync(root).find((candidate) => candidate.startsWith(prefix) && candidate.endsWith(suffix));
52
+ assert.ok(name, `Expected ${suffix} file for ${prefix}`);
53
+ return path.join(root, name);
54
+ }
55
+
56
+ function captureCommand(command: string, cwd: string): Promise<string> {
57
+ return new Promise((resolve, reject) => {
58
+ const child = spawn(command, { cwd, shell: true, stdio: ["ignore", "pipe", "pipe"] });
59
+ let output = "";
60
+ child.stdout.setEncoding("utf-8");
61
+ child.stdout.on("data", (chunk: string) => { output += chunk; });
62
+ child.once("error", reject);
63
+ child.once("close", (code) => code === 0 ? resolve(output) : reject(new Error(`Viewer exited ${code}: ${output}`)));
64
+ });
65
+ }
66
+
67
+ function writeCaptureOrca(dir: string): string {
68
+ return writeNodeCommand(dir, "orca", "require('fs').writeFileSync(process.env.ORCA_TEST_CAPTURE, JSON.stringify(process.argv.slice(2)))");
69
+ }
70
+
71
+ test("Orca progress tabs are disabled on Windows", { skip: process.platform === "win32" ? undefined : "Windows-only platform boundary" }, () => {
72
+ const dir = tempDir();
73
+ assert.equal(createOrcaProgressTab({
74
+ cwd: dir,
75
+ runId: "windows-disabled",
76
+ agent: "worker",
77
+ index: 0,
78
+ config: { enabled: true },
79
+ command: process.execPath,
80
+ }), undefined);
81
+ });
82
+
83
+ test("resolveOrcaCommand only returns executable commands", () => {
84
+ const dir = tempDir();
85
+ const executable = writeNodeCommand(dir, "orca", "process.exit(0)");
86
+ assert.equal(resolveOrcaCommand({ PATH: dir, PATHEXT: ".CMD" }), executable);
87
+ assert.equal(resolveOrcaCommand({ PATH: "", SELESAI_SUBAGENT_ORCA_BINARY: path.join(dir, "missing") }), undefined);
88
+ });
89
+
90
+ test("an unavailable Orca command leaves native execution untouched", () => {
91
+ const dir = tempDir();
92
+ assert.equal(createOrcaProgressTab({
93
+ cwd: dir,
94
+ runId: "missing-orca",
95
+ agent: "worker",
96
+ index: 0,
97
+ config: { enabled: true },
98
+ env: { PATH: "", SELESAI_SUBAGENT_ORCA_BINARY: path.join(dir, "missing") },
99
+ }), undefined);
100
+ });
101
+
102
+ test("standalone Pi executables use PATH Node for the watchdog and viewer", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
103
+ const dir = tempDir();
104
+ const capture = path.join(dir, "capture.json");
105
+ const fakeOrca = writeCaptureOrca(dir);
106
+ const fakePi = path.join(dir, "pi");
107
+ const originalExecPath = process.execPath;
108
+ try {
109
+ process.execPath = fakePi;
110
+ const tab = createOrcaProgressTab({
111
+ cwd: dir,
112
+ runId: "standalone-pi",
113
+ agent: "worker",
114
+ index: 0,
115
+ config: { enabled: true },
116
+ command: fakeOrca,
117
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
118
+ });
119
+ assert.ok(tab);
120
+ tab.finish("failed");
121
+ await waitForFile(capture);
122
+ const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
123
+ const viewer = args[args.indexOf("--command") + 1]!;
124
+ assert.match(viewer, /^'node' '-e' /);
125
+ assert.equal(viewer.includes(fakePi), false);
126
+ assert.match(await captureCommand(viewer, dir), /failed/);
127
+ } finally {
128
+ process.execPath = originalExecPath;
129
+ }
130
+ });
131
+
132
+ test("hung Orca terminal creation does not delay the owning process", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
133
+ const dir = tempDir();
134
+ const fakeOrca = writeNodeCommand(dir, "orca", "require('fs').writeFileSync(process.env.ORCA_TEST_PID, String(process.pid));setInterval(()=>{},1000)");
135
+ const pidFile = path.join(dir, "orca.pid");
136
+ const moduleUrl = new URL("../../src/runs/shared/orca-progress-tabs.ts", import.meta.url).href;
137
+ const ownerScript = `import {createOrcaProgressTab} from ${JSON.stringify(moduleUrl)};const tab=createOrcaProgressTab({cwd:${JSON.stringify(dir)},runId:'progress-hung-owner',agent:'worker',index:0,config:{enabled:true},command:${JSON.stringify(fakeOrca)},env:{...process.env,ORCA_TEST_PID:${JSON.stringify(pidFile)}}});if(!tab)throw new Error('tab unavailable');`;
138
+ const startedAt = Date.now();
139
+ const owner = spawn(process.execPath, ["--experimental-strip-types", "--input-type=module", "--eval", ownerScript], { cwd: dir, stdio: "ignore" });
140
+ const ownerClosed = new Promise<number | null>((resolve, reject) => {
141
+ owner.once("error", reject);
142
+ owner.once("close", resolve);
143
+ });
144
+ let fakePid: number | undefined;
145
+ try {
146
+ assert.equal(await ownerClosed, 0);
147
+ assert.ok(Date.now() - startedAt < 2_000, "the Orca observer delayed runner completion");
148
+ await waitForFile(pidFile);
149
+ fakePid = Number.parseInt(fs.readFileSync(pidFile, "utf-8"), 10);
150
+ process.kill(fakePid, 0);
151
+ } finally {
152
+ if (fakePid !== undefined) {
153
+ try { process.kill(fakePid, "SIGKILL"); } catch { /* already stopped */ }
154
+ }
155
+ if (owner.exitCode === null) owner.kill("SIGKILL");
156
+ }
157
+ });
158
+
159
+ test("malformed optional observer metadata cannot break child execution", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
160
+ const dir = tempDir();
161
+ const capture = path.join(dir, "capture.json");
162
+ const fakeOrca = writeCaptureOrca(dir);
163
+ const tab = createOrcaProgressTab({
164
+ cwd: dir,
165
+ runId: undefined,
166
+ agent: undefined,
167
+ index: undefined,
168
+ config: { enabled: true },
169
+ command: fakeOrca,
170
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
171
+ } as never);
172
+ assert.ok(tab);
173
+ tab.finish("completed");
174
+ await waitForFile(capture);
175
+ const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
176
+ assert.equal(args[args.indexOf("--title") + 1], "subagent · subagent · 1");
177
+ removeProgressFiles("run-0-");
178
+ });
179
+
180
+ test("disabled Orca progress tabs do not invoke Orca", async () => {
181
+ const dir = tempDir();
182
+ const capture = path.join(dir, "capture.json");
183
+ const fakeOrca = writeCaptureOrca(dir);
184
+ const tab = createOrcaProgressTab({
185
+ cwd: dir,
186
+ runId: "disabled-run",
187
+ agent: "worker",
188
+ index: 0,
189
+ config: { enabled: false },
190
+ command: fakeOrca,
191
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
192
+ });
193
+ assert.equal(tab, undefined);
194
+ await new Promise((resolve) => setTimeout(resolve, 100));
195
+ assert.equal(fs.existsSync(capture), false);
196
+ });
197
+
198
+ test("enabled tabs use a worktree sequence and successful Pi sessions get cleanup guidance", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
199
+ const dir = tempDir();
200
+ const capture = path.join(dir, "capture.json");
201
+ const secondCapture = path.join(dir, "capture-2.json");
202
+ const fakeOrca = writeCaptureOrca(dir);
203
+ fs.mkdirSync(path.join(dir, ".git"));
204
+ const runId = `progress-${Date.now()}`;
205
+ const tab = createOrcaProgressTab({
206
+ cwd: dir,
207
+ runId,
208
+ agent: "worker",
209
+ index: 2,
210
+ config: { enabled: true },
211
+ command: fakeOrca,
212
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
213
+ });
214
+ assert.ok(tab);
215
+ tab.append("starting\n");
216
+ tab.event({ type: "tool_execution_start", toolName: "read", args: { path: "README.md" } });
217
+ tab.event({ type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "done output" }] } as never });
218
+ const sessionId = "019ffd40-4859-7015-94e4-7d15c31885ef";
219
+ const sessionFile = path.join(dir, `session file's ${sessionId}.jsonl`);
220
+ fs.writeFileSync(sessionFile, `${JSON.stringify({ type: "session", version: 3, id: sessionId })}\n`);
221
+ assert.equal(resolvePiSessionId(sessionFile), sessionId);
222
+ assert.equal(resolvePiSessionId(path.join(dir, `missing_${sessionId}.jsonl`)), undefined);
223
+ tab.finish("completed", sessionFile);
224
+
225
+ await waitForFile(capture);
226
+ const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
227
+ assert.deepEqual(args.slice(0, 2), ["terminal", "create"]);
228
+ assert.equal(args[args.indexOf("--worktree") + 1], `path:${path.resolve(dir)}`);
229
+ assert.equal(args[args.indexOf("--title") + 1], "subagent · worker · 1");
230
+ const viewer = args[args.indexOf("--command") + 1];
231
+ assert.ok(viewer.includes(process.execPath));
232
+ assert.doesNotMatch(viewer, /(?:^|;)\s*exec\s/);
233
+ assert.doesNotMatch(viewer, /(?:&|;)\s*exit(?:\s|$)/);
234
+
235
+ const progressDir = path.join(TEMP_ROOT_DIR, "orca-progress");
236
+ const log = fs.readdirSync(progressDir).find((name) => name.startsWith(`${runId}-2-`) && name.endsWith(".log"));
237
+ assert.ok(log);
238
+ const text = fs.readFileSync(path.join(progressDir, log), "utf-8");
239
+ assert.match(text, /starting/);
240
+ assert.match(text, /› read: README\.md/);
241
+ assert.match(text, /done output/);
242
+ const quotedSessionFile = `'${fs.realpathSync(sessionFile).replace(/'/g, `'"'"'`)}'`;
243
+ assert.ok(text.includes(`completed. To remove the Pi session of this subagent, run rm -- ${quotedSessionFile}`));
244
+ assert.doesNotMatch(text, /find ~\/\.pi\/agent\/sessions/);
245
+
246
+ const nestedCwd = path.join(dir, "packages", "app");
247
+ fs.mkdirSync(nestedCwd, { recursive: true });
248
+ const secondTab = createOrcaProgressTab({
249
+ cwd: nestedCwd,
250
+ runId: `${runId}-second`,
251
+ agent: "worker",
252
+ index: 0,
253
+ config: { enabled: true },
254
+ command: fakeOrca,
255
+ env: { ...process.env, ORCA_TEST_CAPTURE: secondCapture },
256
+ });
257
+ assert.ok(secondTab);
258
+ secondTab.finish("failed", sessionFile);
259
+ await waitForFile(secondCapture);
260
+ const secondArgs = JSON.parse(fs.readFileSync(secondCapture, "utf-8")) as string[];
261
+ assert.equal(secondArgs[secondArgs.indexOf("--title") + 1], "subagent · worker · 2");
262
+ const secondLog = fs.readdirSync(progressDir).find((name) => name.startsWith(`${runId}-second-0-`) && name.endsWith(".log"));
263
+ assert.ok(secondLog);
264
+ const secondText = fs.readFileSync(path.join(progressDir, secondLog), "utf-8");
265
+ assert.match(secondText, /failed/);
266
+ assert.doesNotMatch(secondText, /To remove the Pi session/);
267
+ });
268
+
269
+ test("viewer strips split terminal control sequences across poll ticks", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
270
+ const dir = tempDir();
271
+ const capture = path.join(dir, "capture.json");
272
+ const fakeOrca = writeCaptureOrca(dir);
273
+ const runId = `progress-sanitize-${Date.now()}`;
274
+ const tab = createOrcaProgressTab({
275
+ cwd: dir,
276
+ runId,
277
+ agent: "worker",
278
+ index: 0,
279
+ config: { enabled: true },
280
+ command: fakeOrca,
281
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
282
+ });
283
+ assert.ok(tab);
284
+ await waitForFile(capture);
285
+ const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
286
+ const viewer = args[args.indexOf("--command") + 1]!;
287
+ const outputPromise = captureCommand(viewer, dir);
288
+ await new Promise((resolve) => setTimeout(resolve, 200));
289
+ tab.append("safe CSI \u001b[");
290
+ await new Promise((resolve) => setTimeout(resolve, 200));
291
+ tab.append("31mred OSC \u001b]0;secret");
292
+ await new Promise((resolve) => setTimeout(resolve, 200));
293
+ tab.append(" title\u0007visible\u0000\u0001\r\t\u007f\n");
294
+ tab.finish("failed");
295
+ const output = await outputPromise;
296
+ assert.match(output, /safe CSI red OSC visible\n/);
297
+ assert.doesNotMatch(output, /\u001b|31m|secret|title|\u0000|\u0001|\r|\t|\u007f/);
298
+ });
299
+
300
+ test("mirror output keeps small writes that hit stream backpressure before the byte limit", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
301
+ const dir = tempDir();
302
+ const capture = path.join(dir, "capture.json");
303
+ const fakeOrca = writeCaptureOrca(dir);
304
+ const runId = `progress-backpressure-${Date.now()}`;
305
+ const tab = createOrcaProgressTab({
306
+ cwd: dir,
307
+ runId,
308
+ agent: "worker",
309
+ index: 0,
310
+ config: { enabled: true },
311
+ command: fakeOrca,
312
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
313
+ });
314
+ assert.ok(tab);
315
+ for (let index = 0; index < 2_000; index++) tab.append(`line-${index.toString().padStart(4, "0")} ${"x".repeat(80)}\n`);
316
+ tab.finish("completed");
317
+ const log = progressFile(`${runId}-0-`, ".log");
318
+ await waitForFile(log.replace(/\.log$/, ".done"));
319
+ const text = fs.readFileSync(log, "utf-8");
320
+ assert.match(text, /line-1999/);
321
+ assert.doesNotMatch(text, /progress mirror truncated/);
322
+ });
323
+
324
+ test("mirror output truncates at a finite byte bound", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
325
+ const dir = tempDir();
326
+ const capture = path.join(dir, "capture.json");
327
+ const fakeOrca = writeCaptureOrca(dir);
328
+ const runId = `progress-bounded-${Date.now()}`;
329
+ const tab = createOrcaProgressTab({
330
+ cwd: dir,
331
+ runId,
332
+ agent: "worker",
333
+ index: 0,
334
+ config: { enabled: true },
335
+ command: fakeOrca,
336
+ env: { ...process.env, ORCA_TEST_CAPTURE: capture },
337
+ });
338
+ assert.ok(tab);
339
+ tab.append("x".repeat(2 * 1024 * 1024));
340
+ tab.append("must be dropped");
341
+ tab.finish("completed");
342
+ const log = progressFile(`${runId}-0-`, ".log");
343
+ await waitForFile(log.replace(/\.log$/, ".done"));
344
+ assert.ok(fs.statSync(log).size <= 1024 * 1024);
345
+ const text = fs.readFileSync(log, "utf-8");
346
+ assert.match(text, /progress mirror truncated at 1048576 bytes/);
347
+ assert.doesNotMatch(text, /must be dropped/);
348
+ });
@@ -69,8 +69,11 @@ test("published extension APIs use supported package entrypoints", async () => {
69
69
  assert.equal(backgroundWork.BACKGROUND_WORK_PROTOCOL_VERSION, 1);
70
70
  assert.equal(backgroundWork.BACKGROUND_WORK_REGISTRY_KEY, "pi-subagents.background-work.v1");
71
71
  const externalRuns = await import("pi-subagents/external-runs");
72
- assert.equal(externalRuns.EXTERNAL_RUN_REGISTRY_VERSION, 1);
73
- assert.equal(typeof externalRuns.registerExternalRunProvider, "function");
72
+ assert.equal(externalRuns.EXTERNAL_RUN_REGISTRY_VERSION, 2);
73
+ assert.equal(typeof externalRuns.registerExternalRun, "function");
74
+ assert.equal(typeof externalRuns.updateExternalRun, "function");
75
+ assert.equal(typeof externalRuns.snapshotExternalRuns, "function");
76
+ assert.equal(typeof externalRuns.unregisterExternalRun, "function");
74
77
  const capability = await import("pi-subagents/capability-ceiling");
75
78
  assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_VERSION, 1);
76
79
  assert.equal(capability.SUBAGENT_CAPABILITY_CEILING_REGISTRY_KEY, "pi-subagents.capability-ceiling.v1");
@@ -825,7 +825,7 @@ describe("buildPiArgs system prompt mode wiring", () => {
825
825
  tools: reviewer.tools,
826
826
  });
827
827
 
828
- assert.equal(args[args.indexOf("--tools") + 1], "read,grep,find,ls,intercom");
828
+ assert.equal(args[args.indexOf("--tools") + 1], "read,grep,find,ls");
829
829
  assert.doesNotMatch(args[args.indexOf("--tools") + 1] ?? "", /\b(?:bash|edit|write)\b/);
830
830
  });
831
831