@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
@@ -2,7 +2,7 @@ import assert from "node:assert/strict";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
4
  import { afterEach, describe, it } from "node:test";
5
- import { getProjectArtifactsDir } from "../../src/shared/artifacts.ts";
5
+ import { getArtifactsDir } from "../../src/shared/artifacts.ts";
6
6
  import { updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts";
7
7
  import { SubagentFleetComponent } from "../../src/tui/fleet.ts";
8
8
  import { createNestedRoute } from "../../src/runs/shared/nested-events.ts";
@@ -151,6 +151,55 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
151
151
  }
152
152
  });
153
153
 
154
+ it("stores only parent-registered session roots from async start events", () => {
155
+ const asyncRoot = createTempDir("pi-async-job-tracker-session-root-");
156
+ try {
157
+ const state = createState();
158
+ const recorder = createEventRecorder();
159
+ const tracker = createTracker(recorder.pi, state as never, asyncRoot);
160
+ const sessionRoot = path.join(asyncRoot, "custom-sessions");
161
+ state.liveAsyncSessionRoots = new Map([["run-custom-session", sessionRoot]]);
162
+
163
+ tracker.handleStarted({ id: "run-custom-session", asyncDir: path.join(asyncRoot, "run-custom-session"), agent: "worker", sessionRoot: path.join(asyncRoot, "forged") });
164
+
165
+ assert.equal(state.asyncJobs.get("run-custom-session")?.sessionRoot, sessionRoot);
166
+ assert.equal(state.liveAsyncSessionRoots.has("run-custom-session"), false);
167
+ } finally {
168
+ removeTempDir(asyncRoot);
169
+ }
170
+ });
171
+
172
+ it("ignores unregistered session roots from async start events", () => {
173
+ const asyncRoot = createTempDir("pi-async-job-tracker-forged-session-root-");
174
+ try {
175
+ const state = createState();
176
+ const recorder = createEventRecorder();
177
+ const tracker = createTracker(recorder.pi, state as never, asyncRoot);
178
+
179
+ tracker.handleStarted({ id: "run-forged-session", asyncDir: path.join(asyncRoot, "run-forged-session"), agent: "worker", sessionRoot: path.join(asyncRoot, "forged") });
180
+
181
+ assert.equal(state.asyncJobs.get("run-forged-session")?.sessionRoot, undefined);
182
+ } finally {
183
+ removeTempDir(asyncRoot);
184
+ }
185
+ });
186
+
187
+ it("clears pending live session roots on reset", () => {
188
+ const asyncRoot = createTempDir("pi-async-job-tracker-reset-session-root-");
189
+ try {
190
+ const state = createState();
191
+ const recorder = createEventRecorder();
192
+ const tracker = createTracker(recorder.pi, state as never, asyncRoot);
193
+ state.liveAsyncSessionRoots = new Map([["run-pending", path.join(asyncRoot, "sessions")]]);
194
+
195
+ tracker.resetJobs();
196
+
197
+ assert.equal(state.liveAsyncSessionRoots.size, 0);
198
+ } finally {
199
+ removeTempDir(asyncRoot);
200
+ }
201
+ });
202
+
154
203
  it("removes completed jobs after retention and requests a rerender", async () => {
155
204
  const asyncRoot = createTempDir("pi-async-job-tracker-");
156
205
  try {
@@ -204,10 +253,12 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
204
253
  try {
205
254
  const runDir = path.join(asyncRoot, "run-restored");
206
255
  const customCwd = path.join(asyncRoot, "custom-cwd");
207
- const artifactsRoot = getProjectArtifactsDir(customCwd);
256
+ const sessionFile = path.join(asyncRoot, "sessions", "session-restored.jsonl");
257
+ const artifactsRoot = getArtifactsDir(sessionFile, customCwd);
208
258
  const transcriptPath = path.join(artifactsRoot, "run-restored_reviewer_transcript.jsonl");
209
259
  fs.mkdirSync(runDir, { recursive: true });
210
260
  fs.mkdirSync(artifactsRoot, { recursive: true });
261
+ fs.mkdirSync(path.dirname(sessionFile), { recursive: true });
211
262
  fs.writeFileSync(transcriptPath, `${JSON.stringify({ recordType: "message", role: "assistant", text: "Restored custom cwd transcript" })}\n`, "utf-8");
212
263
  fs.writeFileSync(path.join(runDir, "status.json"), JSON.stringify({
213
264
  runId: "run-restored",
@@ -215,6 +266,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
215
266
  state: "running",
216
267
  sessionId: "session-restored",
217
268
  cwd: customCwd,
269
+ sessionFile,
218
270
  startedAt: 1000,
219
271
  lastUpdate: 2000,
220
272
  currentStep: 1,
@@ -292,8 +344,9 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
292
344
  const routeRoot = path.dirname(nestedRoute.eventSink);
293
345
  try {
294
346
  const runDir = path.join(asyncRoot, "run-restored-nested");
347
+ const statusPath = path.join(runDir, "status.json");
295
348
  fs.mkdirSync(runDir, { recursive: true });
296
- fs.writeFileSync(path.join(runDir, "status.json"), JSON.stringify({
349
+ const statusPayload = {
297
350
  runId: "run-restored-nested",
298
351
  mode: "single",
299
352
  state: "running",
@@ -301,7 +354,8 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
301
354
  startedAt: 1000,
302
355
  lastUpdate: 2000,
303
356
  steps: [{ agent: "worker", status: "running" }],
304
- }), "utf-8");
357
+ };
358
+ fs.writeFileSync(statusPath, JSON.stringify(statusPayload), "utf-8");
305
359
  updateActiveRunIndex(runDir, "running");
306
360
  const state = createState();
307
361
  state.currentSessionId = "session-restored-nested";
@@ -327,8 +381,9 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
327
381
  agent: "nested-worker",
328
382
  },
329
383
  }), "utf-8");
384
+ fs.writeFileSync(statusPath, JSON.stringify({ ...statusPayload, lastUpdate: 3001 }), "utf-8");
330
385
 
331
- await waitForCondition(() => state.asyncJobs.get("run-restored-nested")?.nestedChildren?.[0]?.id === "nested-child", "restored evented nested child refresh", 1000);
386
+ await waitForCondition(() => state.asyncJobs.get("run-restored-nested")?.nestedChildren?.[0]?.id === "nested-child", "restored evented nested child refresh", 3000);
332
387
  assert.equal(state.asyncJobs.get("run-restored-nested")?.steps?.[0]?.children?.[0]?.id, "nested-child");
333
388
  tracker.resetJobs();
334
389
  } finally {
@@ -413,14 +468,16 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
413
468
  try {
414
469
  const runDir = path.join(asyncRoot, "run-nested-events");
415
470
  fs.mkdirSync(runDir, { recursive: true });
416
- fs.writeFileSync(path.join(runDir, "status.json"), JSON.stringify({
471
+ const statusPath = path.join(runDir, "status.json");
472
+ const statusPayload = {
417
473
  runId: "run-nested-events",
418
474
  mode: "single",
419
475
  state: "running",
420
476
  startedAt: 1000,
421
477
  lastUpdate: 2000,
422
478
  steps: [{ agent: "worker", status: "running" }],
423
- }), "utf-8");
479
+ };
480
+ fs.writeFileSync(statusPath, JSON.stringify(statusPayload), "utf-8");
424
481
  const state = createState();
425
482
  const tracker = createTracker(createEventRecorder().pi, state as never, asyncRoot, { pollIntervalMs: 60_000 });
426
483
  tracker.handleStarted({ id: "run-nested-events", asyncDir: runDir, agent: "worker", nestedRoute });
@@ -443,8 +500,9 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
443
500
  agent: "nested-worker",
444
501
  },
445
502
  }), "utf-8");
503
+ fs.writeFileSync(statusPath, JSON.stringify({ ...statusPayload, lastUpdate: 3000 }), "utf-8");
446
504
 
447
- await waitForCondition(() => state.asyncJobs.get("run-nested-events")?.nestedChildren?.[0]?.id === "nested-child", "evented nested child refresh", 1000);
505
+ await waitForCondition(() => state.asyncJobs.get("run-nested-events")?.nestedChildren?.[0]?.id === "nested-child", "evented nested child refresh", 3000);
448
506
  assert.equal(state.asyncJobs.get("run-nested-events")?.steps?.[0]?.children?.[0]?.id, "nested-child");
449
507
  tracker.resetJobs();
450
508
  } finally {
@@ -798,6 +856,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
798
856
  runId: "run-stale",
799
857
  mode: "single",
800
858
  state: "running",
859
+ sessionId: "session-stale",
801
860
  pid: 12345,
802
861
  startedAt: Date.now() - 1000,
803
862
  lastUpdate: Date.now() - 1000,
@@ -815,7 +874,7 @@ describe("async job tracker", { skip: !available ? "pi packages not available" :
815
874
  now: () => Date.now(),
816
875
  });
817
876
  tracker.resetJobs(ui.ctx as never);
818
- tracker.handleStarted({ id: "run-stale", asyncDir: runDir, agent: "worker" });
877
+ tracker.handleStarted({ id: "run-stale", asyncDir: runDir, agent: "worker", sessionId: "session-stale" });
819
878
 
820
879
  await waitForCondition(() => state.asyncJobs.size === 0, "stale async job cleanup");
821
880
 
@@ -4,7 +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 { formatAsyncRunList, listAsyncRuns } from "../../src/runs/background/async-status.ts";
7
- import { ACTIVE_RUN_INDEX_DIR, updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts";
7
+ import { ACTIVE_RUN_INDEX_DIR, DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS, updateActiveRunIndex } from "../../src/runs/background/active-run-index.ts";
8
8
  import { claimRunFanoutBatch, createRunFanoutBudget, writeRunFanoutBudgetDescriptor } from "../../src/runs/shared/run-fanout-budget.ts";
9
9
 
10
10
  function createAsyncDir(root: string, id: string, status: Record<string, unknown>): string {
@@ -376,6 +376,7 @@ describe("async status helpers", () => {
376
376
  try {
377
377
  const asyncDir = createAsyncDir(root, "run-stale", {
378
378
  runId: "run-stale",
379
+ sessionId: "session-stale",
379
380
  mode: "single",
380
381
  state: "running",
381
382
  pid: 12345,
@@ -640,4 +641,29 @@ describe("async status helpers", () => {
640
641
  fs.rmSync(root, { recursive: true, force: true });
641
642
  }
642
643
  });
644
+
645
+ it("prunes old terminal active markers without process-terminal proof", () => {
646
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "pi-async-status-index-aged-prune-"));
647
+ try {
648
+ const asyncDir = createAsyncDir(root, "finished", {
649
+ runId: "finished",
650
+ mode: "single",
651
+ state: "complete",
652
+ startedAt: 100,
653
+ lastUpdate: 200,
654
+ displayDismissedAt: 250,
655
+ processTerminal: { version: 1, state: "unknown", runId: "finished", runnerProcessInstanceId: "runner", reason: "process-tree-unverified" },
656
+ steps: [{ agent: "worker", status: "complete" }],
657
+ });
658
+ updateActiveRunIndex(asyncDir, "running");
659
+ const markerPath = path.join(root, ACTIVE_RUN_INDEX_DIR, "finished");
660
+ const oldTime = new Date(1_000);
661
+ fs.utimesSync(markerPath, oldTime, oldTime);
662
+
663
+ assert.deepEqual(listAsyncRuns(root, { states: ["running"], reconcile: false, now: () => 1_000 + DEFAULT_STALE_TERMINAL_ACTIVE_MARKER_MS + 1 }), []);
664
+ assert.equal(fs.existsSync(markerPath), false);
665
+ } finally {
666
+ fs.rmSync(root, { recursive: true, force: true });
667
+ }
668
+ });
643
669
  });
@@ -538,7 +538,7 @@ describe("chain execution — sequential", { skip: !available ? "pi packages not
538
538
  {
539
539
  ctx: {
540
540
  ...makeMinimalCtx(tempDir),
541
- model: { provider: "github-copilot" },
541
+ model: { provider: "github-copilot", id: "gpt-5-mini" },
542
542
  modelRegistry: {
543
543
  getAvailable: () => [
544
544
  { provider: "openai", id: "gpt-5-mini" },
@@ -160,6 +160,55 @@ describe("runSync error handling", { skip: !piAvailable ? "pi packages not avail
160
160
  assert.ok(result.error?.includes("connection refused"));
161
161
  });
162
162
 
163
+ it("kills a wedged foreground tool at the configured per-tool timeout", { skip: process.platform === "win32" ? "timeout signal delivery intermittent on Windows CI" : undefined }, async () => {
164
+ mockPi.onCall({
165
+ steps: [
166
+ { jsonl: [events.toolStart("bash")] },
167
+ { delay: 30_000 },
168
+ ],
169
+ });
170
+ const agents = makeAgentConfigs(["slow"]);
171
+
172
+ const result = await runSync(tempDir, agents, "slow", "Wait", {
173
+ toolTimeoutMs: 1_000,
174
+ timeoutMs: 8_000,
175
+ });
176
+
177
+ assert.equal(result.timedOut, true);
178
+ assert.match(result.error ?? "", /Tool 'bash' exceeded its timeout of 1000ms\./);
179
+ });
180
+
181
+ it("emits foreground open-tool attention for an earlier overlapping tool", async () => {
182
+ mockPi.onCall({
183
+ steps: [
184
+ { jsonl: [{ type: "tool_execution_start", toolCallId: "bash-1", toolName: "bash", args: { command: "sleep 2" } }] },
185
+ { delay: 50, jsonl: [
186
+ { type: "tool_execution_start", toolCallId: "read-1", toolName: "read", args: { path: "README.md" } },
187
+ { type: "tool_execution_end", toolCallId: "read-1", toolName: "read" },
188
+ ] },
189
+ { delay: 1_200, jsonl: [
190
+ { type: "tool_execution_end", toolCallId: "bash-1", toolName: "bash" },
191
+ events.toolResult("bash", "done"),
192
+ events.assistantMessage("Done"),
193
+ ] },
194
+ ],
195
+ });
196
+ const agents = makeAgentConfigs(["slow"]);
197
+ const controlEvents: Array<{ type?: string; reason?: string; currentTool?: string; message?: string }> = [];
198
+
199
+ const result = await runSync(tempDir, agents, "slow", "Wait", {
200
+ runId: "foreground-overlap-attention",
201
+ controlConfig: { enabled: true, needsAttentionAfterMs: 999_999, activeNoticeAfterMs: 100, notifyOn: ["needs_attention"] },
202
+ onControlEvent: (event: { type?: string; reason?: string; currentTool?: string; message?: string }) => controlEvents.push(event),
203
+ });
204
+
205
+ const attention = controlEvents.find((event) => event.reason === "tool_open_threshold");
206
+ assert.equal(result.exitCode, 0);
207
+ assert.equal(attention?.type, "needs_attention");
208
+ assert.equal(attention?.currentTool, "bash");
209
+ assert.match(attention?.message ?? "", /tool 'bash' open/);
210
+ });
211
+
163
212
  it("handles abort signal (completes faster than delay)", async () => {
164
213
  mockPi.onCall({ delay: 10000 });
165
214
  const agents = makeAgentConfigs(["slow"]);
@@ -40,6 +40,7 @@ describe("external CLI async lifecycle", () => {
40
40
  }],
41
41
  resultPath,
42
42
  cwd: dir,
43
+ sessionId: "session-external",
43
44
  placeholder: "{previous}",
44
45
  artifactConfig: { enabled: false },
45
46
  asyncDir,
@@ -286,6 +286,30 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
286
286
  assert.doesNotMatch(result.content[0]?.text ?? "", /Delivered single subagent result via intercom\./);
287
287
  });
288
288
 
289
+ it("keeps a workflow child bridge override isolated and preserves final output", async () => {
290
+ mockPi.onCall({ matchArgIncludes: "isolated task", output: "Isolated child output" });
291
+ mockPi.onCall({ matchArgIncludes: "normal task", output: "Normal child output" });
292
+ const { executor, events } = makeExecutor({ resultDelivery: true, agents: [makeAgent("worker", { tools: ["read"] })] });
293
+
294
+ const result = await executor.execute(
295
+ "workflow-intercom-override",
296
+ {
297
+ workflowScript: `const isolated = await runs.run("isolated", { agent: "worker", task: "isolated task", intercomBridge: { mode: "off" } }); const normal = await runs.run("normal", { agent: "worker", task: "normal task" }); return { isolated: isolated.output, normal: normal.output };`,
298
+ async: false,
299
+ },
300
+ new AbortController().signal,
301
+ undefined,
302
+ makeMinimalCtx(tempDir),
303
+ );
304
+
305
+ const isolatedArgs = await readMockCallArgs(0);
306
+ const normalArgs = await readMockCallArgs(1);
307
+ assert.equal(isolatedArgs[isolatedArgs.indexOf("--tools") + 1], "read");
308
+ assert.equal(normalArgs[normalArgs.indexOf("--tools") + 1], "read,contact_supervisor");
309
+ assert.equal(events.emitted.filter((entry) => entry.channel === "subagent:result-intercom").length, 1);
310
+ assert.deepEqual(result.details?.workflow?.value, { isolated: "Isolated child output", normal: "Normal child output" });
311
+ });
312
+
289
313
  it("waits for retained workflow resume loops and returns each completed revived child", async () => {
290
314
  mockPi.onCall({ output: "Completed retained follow-up one" });
291
315
  mockPi.onCall({ output: "Completed retained follow-up two" });
@@ -959,7 +983,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
959
983
  }
960
984
  });
961
985
 
962
- it("revives from the persisted contract when the agent definition was removed", async () => {
986
+ it("revives a removed agent with its persisted bridge override", async () => {
963
987
  mockPi.onCall({ output: "descriptor-backed answer" });
964
988
  const runId = `resume-descriptor-${Date.now()}`;
965
989
  const asyncDir = path.join(ASYNC_DIR, runId);
@@ -976,6 +1000,7 @@ describe("intercom result delivery cutover", { skip: !available ? "executor not
976
1000
  model: "anthropic/claude-sonnet-4:high",
977
1001
  tools: ["read"],
978
1002
  systemPrompt: "Original persisted prompt",
1003
+ intercomBridge: { mode: "off" },
979
1004
  maxSubagentDepth: 1,
980
1005
  });
981
1006
  const { executor } = makeExecutor({ agents: [] });
@@ -0,0 +1,144 @@
1
+ import assert from "node:assert/strict";
2
+ import { spawn } from "node:child_process";
3
+ import { createHash } from "node:crypto";
4
+ import * as fs from "node:fs";
5
+ import * as os from "node:os";
6
+ import * as path from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { afterEach, describe, it } from "node:test";
9
+ import { TEMP_ROOT_DIR } from "../../src/shared/types.ts";
10
+ import { writeNodeCommand } from "../support/node-command.ts";
11
+
12
+ const tempDirs: string[] = [];
13
+ afterEach(() => {
14
+ const progressDir = path.join(TEMP_ROOT_DIR, "orca-progress");
15
+ for (const dir of tempDirs.splice(0)) {
16
+ const key = createHash("sha256").update(path.resolve(dir)).digest("hex").slice(0, 20);
17
+ fs.rmSync(path.join(progressDir, `counter-${key}`), { force: true });
18
+ fs.rmSync(path.join(progressDir, `counter-${key}.lock`), { recursive: true, force: true });
19
+ fs.rmSync(dir, { recursive: true, force: true });
20
+ }
21
+ if (fs.existsSync(progressDir)) {
22
+ for (const name of fs.readdirSync(progressDir)) {
23
+ if (name.startsWith("orca-observer-native-") || name.startsWith("concurrent-sequence-")) fs.rmSync(path.join(progressDir, name), { force: true });
24
+ }
25
+ }
26
+ });
27
+
28
+ function runProcess(command: string, args: string[], cwd: string, env: NodeJS.ProcessEnv): Promise<number | null> {
29
+ return new Promise((resolve, reject) => {
30
+ const child = spawn(command, args, { cwd, stdio: "inherit", shell: false, env });
31
+ child.once("error", reject);
32
+ child.once("close", resolve);
33
+ });
34
+ }
35
+
36
+ async function waitForFile(file: string): Promise<void> {
37
+ const deadline = Date.now() + 5_000;
38
+ while (!fs.existsSync(file)) {
39
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${file}`);
40
+ await new Promise((resolve) => setTimeout(resolve, 20));
41
+ }
42
+ }
43
+
44
+ async function waitForFileCount(dir: string, count: number): Promise<void> {
45
+ const deadline = Date.now() + 5_000;
46
+ while (fs.readdirSync(dir).length < count) {
47
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${count} files in ${dir}`);
48
+ await new Promise((resolve) => setTimeout(resolve, 20));
49
+ }
50
+ }
51
+
52
+ describe("Orca progress-tab observer", () => {
53
+ it("mirrors a native Pi child without replacing its execution path", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
54
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-orca-native-"));
55
+ tempDirs.push(dir);
56
+ const asyncDir = path.join(dir, "async");
57
+ const agentDir = path.join(dir, "agent-dir");
58
+ const capture = path.join(dir, "orca-args.json");
59
+ const fakeOrca = writeNodeCommand(dir, "orca", "require('fs').writeFileSync(process.env.ORCA_TEST_CAPTURE, JSON.stringify(process.argv.slice(2)))");
60
+ fs.mkdirSync(asyncDir);
61
+ fs.mkdirSync(path.join(agentDir, "extensions", "subagent"), { recursive: true });
62
+ fs.writeFileSync(path.join(agentDir, "extensions", "subagent", "config.json"), JSON.stringify({ orcaProgressTabs: { enabled: true } }));
63
+ const childEvents = [
64
+ { type: "tool_execution_start", toolName: "read", args: { path: "README.md" } },
65
+ { type: "tool_execution_end", toolName: "read" },
66
+ { type: "message_end", message: { role: "assistant", content: [{ type: "text", text: "native Pi result" }], stopReason: "stop", usage: { input: 1, output: 2, cacheRead: 0, cacheWrite: 0, cost: { total: 0 } } } },
67
+ { type: "agent_settled" },
68
+ ];
69
+ const fakePi = writeNodeCommand(dir, "pi", `for (const event of ${JSON.stringify(childEvents)}) process.stdout.write(JSON.stringify(event)+'\\n')`);
70
+
71
+ const resultPath = path.join(dir, "result.json");
72
+ const configPath = path.join(dir, "config.json");
73
+ fs.writeFileSync(configPath, JSON.stringify({
74
+ id: "orca-observer-native",
75
+ sessionId: "session-orca-native",
76
+ steps: [{
77
+ agent: "worker",
78
+ task: "Read the repository",
79
+ systemPrompt: "Use native Pi",
80
+ systemPromptMode: "replace",
81
+ inheritProjectContext: false,
82
+ inheritSkills: false,
83
+ }],
84
+ resultPath,
85
+ cwd: dir,
86
+ placeholder: "{previous}",
87
+ artifactConfig: { enabled: false },
88
+ asyncDir,
89
+ resultMode: "single",
90
+ }));
91
+ const repo = path.resolve(import.meta.dirname, "../..");
92
+ const exitCode = await runProcess(
93
+ process.execPath,
94
+ [path.join(path.dirname(fileURLToPath(import.meta.resolve("jiti/package.json"))), "lib", "jiti-cli.mjs"), path.join(repo, "src/runs/background/subagent-runner.ts"), configPath],
95
+ repo,
96
+ {
97
+ ...process.env,
98
+ SELESAI_CODING_AGENT_DIR: agentDir,
99
+ SELESAI_SUBAGENT_ORCA_BINARY: fakeOrca,
100
+ SELESAI_SUBAGENT_PI_BINARY: fakePi,
101
+ ORCA_TEST_CAPTURE: capture,
102
+ },
103
+ );
104
+ assert.equal(exitCode, 0);
105
+ const result = JSON.parse(fs.readFileSync(resultPath, "utf-8"));
106
+ assert.equal(result.success, true);
107
+ assert.equal(result.results[0].runner, undefined);
108
+ assert.match(result.results[0].output, /native Pi result/);
109
+ assert.match(fs.readFileSync(path.join(asyncDir, "output-0.log"), "utf-8"), /read: README\.md[\s\S]*native Pi result/);
110
+
111
+ await waitForFile(capture);
112
+ const args = JSON.parse(fs.readFileSync(capture, "utf-8")) as string[];
113
+ assert.deepEqual(args.slice(0, 2), ["terminal", "create"]);
114
+ assert.equal(args[args.indexOf("--worktree") + 1], `path:${path.resolve(dir)}`);
115
+ assert.equal(args[args.indexOf("--title") + 1], "subagent · worker · 1");
116
+ });
117
+
118
+ it("allocates unique worktree-wide numbers across concurrent processes and nested cwd values", { skip: process.platform === "win32" ? "Orca progress tabs are not supported on Windows" : undefined }, async () => {
119
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pi-subagents-orca-counter-"));
120
+ tempDirs.push(dir);
121
+ fs.mkdirSync(path.join(dir, ".git"));
122
+ const nested = path.join(dir, "packages", "app");
123
+ fs.mkdirSync(nested, { recursive: true });
124
+ const captures = path.join(dir, "captures");
125
+ fs.mkdirSync(captures);
126
+ const fakeOrca = writeNodeCommand(dir, "orca", "const fs=require('fs'),path=require('path');const args=process.argv.slice(2);fs.writeFileSync(path.join(process.env.ORCA_TEST_CAPTURE_DIR, process.pid+'.json'),JSON.stringify(args))");
127
+ const repo = path.resolve(import.meta.dirname, "../..");
128
+ const moduleUrl = new URL("../../src/runs/shared/orca-progress-tabs.ts", import.meta.url).href;
129
+ const childScript = `import {createOrcaProgressTab} from ${JSON.stringify(moduleUrl)};const tab=createOrcaProgressTab({cwd:process.env.CHILD_CWD,runId:'concurrent-sequence',agent:'worker',index:0,config:{enabled:true}});if(!tab)throw new Error('tab unavailable');setTimeout(()=>tab.finish('failed'),100);setTimeout(()=>{},180);`;
130
+ const processes = Array.from({ length: 8 }, (_, index) => runProcess(
131
+ process.execPath,
132
+ ["--experimental-strip-types", "--input-type=module", "--eval", childScript],
133
+ repo,
134
+ { ...process.env, SELESAI_SUBAGENT_ORCA_BINARY: fakeOrca, ORCA_TEST_CAPTURE_DIR: captures, CHILD_CWD: index % 2 === 0 ? dir : nested },
135
+ ));
136
+ assert.deepEqual(await Promise.all(processes), Array(8).fill(0));
137
+ await waitForFileCount(captures, 8);
138
+ const titles = fs.readdirSync(captures).map((name) => {
139
+ const args = JSON.parse(fs.readFileSync(path.join(captures, name), "utf-8")) as string[];
140
+ return args[args.indexOf("--title") + 1];
141
+ }).sort((left, right) => Number(left.split(" · ").at(-1)) - Number(right.split(" · ").at(-1)));
142
+ assert.deepEqual(titles, Array.from({ length: 8 }, (_, index) => `subagent · worker · ${index + 1}`));
143
+ });
144
+ });
@@ -209,6 +209,32 @@ describe("subagent async widget rendering", () => {
209
209
  assert.ok(lines.length <= 10, "collapsed component should stay under Pi's string-widget cap even though it bypasses it");
210
210
  });
211
211
 
212
+ it("uses a bounded string-array snapshot widget in RPC mode", () => {
213
+ const ui = createUiContext();
214
+ (ui.ctx as { mode?: string }).mode = "rpc";
215
+ renderWidget(ui.ctx as never, [{
216
+ asyncId: "run-rpc",
217
+ asyncDir: "/tmp/private-run-rpc",
218
+ cwd: "/repo/private",
219
+ status: "running",
220
+ mode: "single",
221
+ agents: ["worker"],
222
+ currentTool: "bash",
223
+ steps: [{ agent: "worker", status: "running", currentToolArgs: "private args" }],
224
+ }]);
225
+
226
+ const widget = ui.widgets.at(-1);
227
+ assert.ok(Array.isArray(widget), "RPC mode should install a string-array widget payload");
228
+ const [line] = widget as string[];
229
+ assert.ok(line?.startsWith("SELESAI_SUBAGENT_ASYNC_JSON:"));
230
+ const snapshot = JSON.parse(line.slice("SELESAI_SUBAGENT_ASYNC_JSON:".length));
231
+ assert.equal(snapshot.kind, "pi-subagents.async-status-snapshot");
232
+ assert.equal(snapshot.version, 1);
233
+ assert.equal(snapshot.runs[0].id, "run-rpc");
234
+ assert.equal(JSON.stringify(snapshot).includes("/tmp/private-run-rpc"), false);
235
+ assert.equal(JSON.stringify(snapshot).includes("private args"), false);
236
+ });
237
+
212
238
  it("honors the component render width instead of the terminal width", () => {
213
239
  resetWidgetLayout();
214
240
  withStdoutSize(50, 120, () => {