@selesai/code 0.8.6 → 0.8.8

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 (234) hide show
  1. package/CHANGELOG.md +31 -1
  2. package/dist/cli/args.d.ts +1 -0
  3. package/dist/cli/args.js +11 -0
  4. package/dist/cli/args.test.d.ts +1 -0
  5. package/dist/cli/args.test.js +24 -0
  6. package/dist/core/agent-session.d.ts +4 -1
  7. package/dist/core/agent-session.js +18 -7
  8. package/dist/core/experimental.d.ts +4 -0
  9. package/dist/core/experimental.js +4 -0
  10. package/dist/core/extensions/types.d.ts +6 -0
  11. package/dist/core/keybindings.d.ts +24 -0
  12. package/dist/core/model-config.d.ts +5 -0
  13. package/dist/core/model-config.js +1 -0
  14. package/dist/core/project-trust.js +2 -2
  15. package/dist/core/provider-composer.js +17 -4
  16. package/dist/core/sdk.d.ts +5 -3
  17. package/dist/core/sdk.js +2 -1
  18. package/dist/core/session-manager.js +2 -2
  19. package/dist/core/settings-manager.d.ts +8 -0
  20. package/dist/core/settings-manager.js +15 -0
  21. package/dist/core/system-prompt.js +3 -9
  22. package/dist/core/system-prompt.test.js +6 -0
  23. package/dist/core/tools/bash.js +2 -0
  24. package/dist/core/tools/edit.js +2 -0
  25. package/dist/core/tools/find.js +1 -1
  26. package/dist/core/tools/grep.js +1 -1
  27. package/dist/core/tools/index.d.ts +1 -0
  28. package/dist/core/tools/index.js +1 -0
  29. package/dist/core/tools/read.js +2 -0
  30. package/dist/core/tools/schema-prune.d.ts +16 -0
  31. package/dist/core/tools/schema-prune.js +39 -0
  32. package/dist/core/tools/schema-prune.test.d.ts +1 -0
  33. package/dist/core/tools/schema-prune.test.js +111 -0
  34. package/dist/core/tools/write.js +2 -0
  35. package/dist/defaults/settings.json +1 -0
  36. package/dist/extensions/model-prompt-injector/config.json +10 -0
  37. package/dist/extensions/model-prompt-injector/index.test.ts +112 -0
  38. package/dist/extensions/model-prompt-injector/index.ts +151 -0
  39. package/dist/extensions/package.json +1 -0
  40. package/dist/extensions/pi-subagents/CHANGELOG.md +158 -90
  41. package/dist/extensions/pi-subagents/UPSTREAM-V0.50-MAPPING.md +172 -0
  42. package/dist/extensions/pi-subagents/agents/oracle.md +7 -5
  43. package/dist/extensions/pi-subagents/agents/reviewer.md +2 -2
  44. package/dist/extensions/pi-subagents/agents/scout.md +1 -1
  45. package/dist/extensions/pi-subagents/agents/worker.md +1 -1
  46. package/dist/extensions/pi-subagents/docs/configuration.md +78 -19
  47. package/dist/extensions/pi-subagents/docs/extension-api.md +36 -0
  48. package/dist/extensions/pi-subagents/docs/missions.md +5 -3
  49. package/dist/extensions/pi-subagents/docs/models.md +1 -1
  50. package/dist/extensions/pi-subagents/docs/observability.md +42 -2
  51. package/dist/extensions/pi-subagents/docs/tool-reference.md +20 -3
  52. package/dist/extensions/pi-subagents/docs/workflows.md +5 -5
  53. package/dist/extensions/pi-subagents/install.mjs +2 -2
  54. package/dist/extensions/pi-subagents/package-lock.json +1470 -1472
  55. package/dist/extensions/pi-subagents/package.json +1 -1
  56. package/dist/extensions/pi-subagents/skills/pi-subagents/SKILL.md +1 -1
  57. package/dist/extensions/pi-subagents/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  58. package/dist/extensions/pi-subagents/skills/pi-subagents/references/execution-controls.md +15 -14
  59. package/dist/extensions/pi-subagents/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  60. package/dist/extensions/pi-subagents/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  61. package/dist/extensions/pi-subagents/src/agents/agent-serializer.ts +2 -0
  62. package/dist/extensions/pi-subagents/src/agents/agents.ts +37 -12
  63. package/dist/extensions/pi-subagents/src/api/external-runs.ts +174 -84
  64. package/dist/extensions/pi-subagents/src/api/preflight.ts +13 -7
  65. package/dist/extensions/pi-subagents/src/extension/config.ts +53 -0
  66. package/dist/extensions/pi-subagents/src/extension/index.ts +67 -13
  67. package/dist/extensions/pi-subagents/src/extension/public-execution.ts +34 -6
  68. package/dist/extensions/pi-subagents/src/extension/rpc.ts +5 -1
  69. package/dist/extensions/pi-subagents/src/extension/schemas.ts +31 -30
  70. package/dist/extensions/pi-subagents/src/extension/tool-description.ts +10 -9
  71. package/dist/extensions/pi-subagents/src/inspectors/herdr/actions.ts +13 -8
  72. package/dist/extensions/pi-subagents/src/inspectors/herdr/inspector-runner.ts +16 -3
  73. package/dist/extensions/pi-subagents/src/inspectors/herdr/project-panes.ts +2 -6
  74. package/dist/extensions/pi-subagents/src/inspectors/herdr/shell-command.ts +16 -0
  75. package/dist/extensions/pi-subagents/src/intercom/intercom-bridge.ts +5 -4
  76. package/dist/extensions/pi-subagents/src/intercom/native-supervisor-channel.ts +19 -42
  77. package/dist/extensions/pi-subagents/src/missions/goal-driver.ts +3 -1
  78. package/dist/extensions/pi-subagents/src/missions/store.ts +8 -3
  79. package/dist/extensions/pi-subagents/src/runs/background/active-async-capacity.ts +82 -25
  80. package/dist/extensions/pi-subagents/src/runs/background/active-run-index.ts +71 -1
  81. package/dist/extensions/pi-subagents/src/runs/background/async-execution.ts +73 -43
  82. package/dist/extensions/pi-subagents/src/runs/background/async-job-tracker.ts +5 -0
  83. package/dist/extensions/pi-subagents/src/runs/background/async-resume.ts +14 -6
  84. package/dist/extensions/pi-subagents/src/runs/background/async-status-snapshot.ts +277 -0
  85. package/dist/extensions/pi-subagents/src/runs/background/async-status.ts +8 -3
  86. package/dist/extensions/pi-subagents/src/runs/background/chain-root-attachment.ts +2 -2
  87. package/dist/extensions/pi-subagents/src/runs/background/completion-replay.ts +11 -1
  88. package/dist/extensions/pi-subagents/src/runs/background/fleet-view.ts +21 -6
  89. package/dist/extensions/pi-subagents/src/runs/background/result-files.ts +437 -0
  90. package/dist/extensions/pi-subagents/src/runs/background/result-watcher.ts +187 -41
  91. package/dist/extensions/pi-subagents/src/runs/background/resume-guidance.ts +27 -7
  92. package/dist/extensions/pi-subagents/src/runs/background/retained-children.ts +76 -19
  93. package/dist/extensions/pi-subagents/src/runs/background/run-id-resolver.ts +30 -24
  94. package/dist/extensions/pi-subagents/src/runs/background/run-status.ts +101 -4
  95. package/dist/extensions/pi-subagents/src/runs/background/scheduled-runs.ts +54 -28
  96. package/dist/extensions/pi-subagents/src/runs/background/stale-run-reconciler.ts +27 -13
  97. package/dist/extensions/pi-subagents/src/runs/background/subagent-runner.ts +298 -33
  98. package/dist/extensions/pi-subagents/src/runs/background/subagent-wait.ts +2 -0
  99. package/dist/extensions/pi-subagents/src/runs/background/wait-completions.ts +5 -2
  100. package/dist/extensions/pi-subagents/src/runs/foreground/async-dismiss-action.ts +2 -1
  101. package/dist/extensions/pi-subagents/src/runs/foreground/chain-execution.ts +16 -0
  102. package/dist/extensions/pi-subagents/src/runs/foreground/execution.ts +219 -15
  103. package/dist/extensions/pi-subagents/src/runs/foreground/prompt-audit.ts +4 -3
  104. package/dist/extensions/pi-subagents/src/runs/foreground/subagent-executor.ts +333 -45
  105. package/dist/extensions/pi-subagents/src/runs/shared/completion-guard.ts +107 -1
  106. package/dist/extensions/pi-subagents/src/runs/shared/external-cli-runner.ts +4 -0
  107. package/dist/extensions/pi-subagents/src/runs/shared/llm-intent-arbiter.ts +39 -23
  108. package/dist/extensions/pi-subagents/src/runs/shared/model-fallback.ts +16 -2
  109. package/dist/extensions/pi-subagents/src/runs/shared/nested-events.ts +66 -62
  110. package/dist/extensions/pi-subagents/src/runs/shared/orca-progress-tabs.ts +376 -0
  111. package/dist/extensions/pi-subagents/src/runs/shared/parallel-utils.ts +2 -0
  112. package/dist/extensions/pi-subagents/src/runs/shared/subagent-control.ts +15 -0
  113. package/dist/extensions/pi-subagents/src/runs/shared/subagent-prompt-runtime.ts +1 -9
  114. package/dist/extensions/pi-subagents/src/runs/shared/subagent-startup-retry.ts +12 -0
  115. package/dist/extensions/pi-subagents/src/runs/shared/tool-timeout.ts +95 -0
  116. package/dist/extensions/pi-subagents/src/shared/agent-stream-options.ts +5 -0
  117. package/dist/extensions/pi-subagents/src/shared/artifacts.ts +0 -4
  118. package/dist/extensions/pi-subagents/src/shared/display-text.ts +50 -0
  119. package/dist/extensions/pi-subagents/src/shared/node-executable.ts +21 -0
  120. package/dist/extensions/pi-subagents/src/shared/types.ts +50 -4
  121. package/dist/extensions/pi-subagents/src/slash/slash-commands.ts +34 -25
  122. package/dist/extensions/pi-subagents/src/slash/slash-live-state.ts +4 -0
  123. package/dist/extensions/pi-subagents/src/tui/fleet-status.ts +160 -45
  124. package/dist/extensions/pi-subagents/src/tui/fleet-transcript.ts +1 -48
  125. package/dist/extensions/pi-subagents/src/tui/fleet.ts +128 -16
  126. package/dist/extensions/pi-subagents/src/tui/render.ts +122 -44
  127. package/dist/extensions/pi-subagents/src/watchdog/permission-arbiter.ts +2 -1
  128. package/dist/extensions/pi-subagents/src/watchdog/review.ts +4 -3
  129. package/dist/extensions/pi-subagents/src/workflows/chat-progress.ts +10 -2
  130. package/dist/extensions/pi-subagents/src/workflows/scripted-workflow.ts +272 -76
  131. package/dist/extensions/pi-subagents/test/fixtures/pi-coding-agent-shim/dist/core/extensions/types.d.ts +2 -0
  132. package/dist/extensions/pi-subagents/test/integration/async-execution.test.ts +2 -2
  133. package/dist/extensions/pi-subagents/test/integration/async-job-tracker.test.ts +68 -9
  134. package/dist/extensions/pi-subagents/test/integration/async-status.test.ts +27 -1
  135. package/dist/extensions/pi-subagents/test/integration/chain-execution.test.ts +1 -1
  136. package/dist/extensions/pi-subagents/test/integration/error-handling.test.ts +49 -0
  137. package/dist/extensions/pi-subagents/test/integration/external-cli-runner.test.ts +1 -0
  138. package/dist/extensions/pi-subagents/test/integration/intercom-result-delivery.test.ts +26 -1
  139. package/dist/extensions/pi-subagents/test/integration/orca-progress-tabs.test.ts +144 -0
  140. package/dist/extensions/pi-subagents/test/integration/render-widget.test.ts +26 -0
  141. package/dist/extensions/pi-subagents/test/integration/result-watcher.test.ts +395 -98
  142. package/dist/extensions/pi-subagents/test/integration/single-execution.test.ts +111 -78
  143. package/dist/extensions/pi-subagents/test/integration/slash-commands.test.ts +49 -1
  144. package/dist/extensions/pi-subagents/test/integration/slash-live-state.test.ts +12 -0
  145. package/dist/extensions/pi-subagents/test/support/node-command.ts +15 -0
  146. package/dist/extensions/pi-subagents/test/unit/active-async-capacity.test.ts +50 -0
  147. package/dist/extensions/pi-subagents/test/unit/agent-frontmatter.test.ts +28 -0
  148. package/dist/extensions/pi-subagents/test/unit/agent-overrides.test.ts +61 -0
  149. package/dist/extensions/pi-subagents/test/unit/agent-stream-options.test.ts +14 -0
  150. package/dist/extensions/pi-subagents/test/unit/async-interrupt-action.test.ts +76 -0
  151. package/dist/extensions/pi-subagents/test/unit/async-resume.test.ts +2 -0
  152. package/dist/extensions/pi-subagents/test/unit/async-status-snapshot.test.ts +162 -0
  153. package/dist/extensions/pi-subagents/test/unit/completion-guard.test.ts +209 -10
  154. package/dist/extensions/pi-subagents/test/unit/completion-replay.test.ts +41 -1
  155. package/dist/extensions/pi-subagents/test/unit/external-runs.test.ts +135 -42
  156. package/dist/extensions/pi-subagents/test/unit/fleet-status.test.ts +252 -1
  157. package/dist/extensions/pi-subagents/test/unit/fleet.test.ts +192 -6
  158. package/dist/extensions/pi-subagents/test/unit/herdr-inspector-bootstrap.test.ts +32 -0
  159. package/dist/extensions/pi-subagents/test/unit/herdr-shell-command.test.ts +59 -0
  160. package/dist/extensions/pi-subagents/test/unit/index-child-registration.test.ts +69 -0
  161. package/dist/extensions/pi-subagents/test/unit/intercom-bridge.test.ts +23 -4
  162. package/dist/extensions/pi-subagents/test/unit/llm-intent-arbiter.test.ts +56 -1
  163. package/dist/extensions/pi-subagents/test/unit/mission-goal-driver.test.ts +67 -3
  164. package/dist/extensions/pi-subagents/test/unit/mission-lifecycle.test.ts +1 -1
  165. package/dist/extensions/pi-subagents/test/unit/mission-store.test.ts +14 -0
  166. package/dist/extensions/pi-subagents/test/unit/model-fallback.test.ts +18 -0
  167. package/dist/extensions/pi-subagents/test/unit/native-supervisor-channel.test.ts +2 -2
  168. package/dist/extensions/pi-subagents/test/unit/nested-events.test.ts +25 -2
  169. package/dist/extensions/pi-subagents/test/unit/node-executable.test.ts +26 -0
  170. package/dist/extensions/pi-subagents/test/unit/notify.test.ts +8 -0
  171. package/dist/extensions/pi-subagents/test/unit/orca-progress-tabs.test.ts +348 -0
  172. package/dist/extensions/pi-subagents/test/unit/package-manifest.test.ts +5 -2
  173. package/dist/extensions/pi-subagents/test/unit/pi-args.test.ts +1 -1
  174. package/dist/extensions/pi-subagents/test/unit/pi-coding-agent-dir.test.ts +34 -0
  175. package/dist/extensions/pi-subagents/test/unit/public-execution.test.ts +6 -1
  176. package/dist/extensions/pi-subagents/test/unit/render-helpers.test.ts +92 -0
  177. package/dist/extensions/pi-subagents/test/unit/result-files.test.ts +175 -0
  178. package/dist/extensions/pi-subagents/test/unit/retained-children.test.ts +127 -17
  179. package/dist/extensions/pi-subagents/test/unit/run-id-resolver.test.ts +44 -0
  180. package/dist/extensions/pi-subagents/test/unit/run-status.test.ts +247 -0
  181. package/dist/extensions/pi-subagents/test/unit/scheduled-runs.test.ts +107 -2
  182. package/dist/extensions/pi-subagents/test/unit/schemas.test.ts +5 -8
  183. package/dist/extensions/pi-subagents/test/unit/scripted-workflow.test.ts +456 -4
  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-chat-progress.test.ts +94 -1
  193. package/dist/extensions/pi-subagents/test/unit/workflow-launch-params.test.ts +42 -2
  194. package/dist/extensions/question/index.ts +21 -6
  195. package/dist/extensions/question/question-list.ts +7 -1
  196. package/dist/extensions/question/row-layout.ts +3 -0
  197. package/dist/extensions/question/tests/exp.test.ts +70 -0
  198. package/dist/extensions/question/tests/exp2.test.ts +59 -0
  199. package/dist/extensions/question/tests/exp3.test.ts +47 -0
  200. package/dist/extensions/question/tests/helpers.test.ts +7 -0
  201. package/dist/extensions/question/tests/question-list.test.ts +140 -0
  202. package/dist/extensions/question/tests/row-layout.test.ts +13 -0
  203. package/dist/extensions/question/tests/selection-mode.test.ts +54 -0
  204. package/dist/extensions/question/tests/zz-ig3.test.ts +20 -0
  205. package/dist/extensions/question/tests/zz-probe.test.ts +29 -0
  206. package/dist/extensions/tokenin-onboarding.ts +1 -1
  207. package/dist/index.d.ts +1 -1
  208. package/dist/main.js +4 -0
  209. package/dist/modes/interactive/components/model-selector.js +2 -1
  210. package/dist/modes/interactive/components/settings-selector.d.ts +3 -1
  211. package/dist/modes/interactive/components/settings-selector.js +10 -0
  212. package/dist/modes/interactive/components/tool-execution.js +10 -1
  213. package/dist/modes/interactive/interactive-mode.d.ts +8 -2
  214. package/dist/modes/interactive/interactive-mode.js +81 -25
  215. package/dist/modes/interactive/model-catalog-refresh.d.ts +6 -0
  216. package/dist/modes/interactive/model-catalog-refresh.js +33 -0
  217. package/dist/modes/interactive/theme/dark.json +2 -0
  218. package/dist/modes/interactive/theme/light.json +2 -0
  219. package/dist/modes/interactive/theme/theme-controller.d.ts +10 -2
  220. package/dist/modes/interactive/theme/theme-controller.js +25 -11
  221. package/dist/modes/interactive/theme/theme-schema.json +9 -1
  222. package/dist/modes/interactive/theme/theme.d.ts +5 -3
  223. package/dist/modes/interactive/theme/theme.js +12 -2
  224. package/dist/skills/pi-subagents/SKILL.md +1 -1
  225. package/dist/skills/pi-subagents/references/constraints-and-recipes.md +5 -4
  226. package/dist/skills/pi-subagents/references/execution-controls.md +15 -14
  227. package/dist/skills/pi-subagents/references/management-authoring-rpc.md +5 -5
  228. package/dist/skills/pi-subagents/references/prompting-and-roles.md +46 -26
  229. package/dist/utils/abort.d.ts +4 -0
  230. package/dist/utils/abort.js +47 -0
  231. package/dist/utils/tools-manager.d.ts +5 -1
  232. package/dist/utils/tools-manager.js +12 -21
  233. package/docs/settings.md +6 -0
  234. package/package.json +6 -6
@@ -34,6 +34,7 @@ import { validateChainInput } from "./chain-validation.ts";
34
34
  import { createSubagentExecutor, type SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
35
35
  import { createAsyncJobTracker } from "../runs/background/async-job-tracker.ts";
36
36
  import { getActiveAsyncCapacitySnapshot, resolveMaxActiveAsyncRunsPerSession } from "../runs/background/active-async-capacity.ts";
37
+ import { cleanupResultIndexes } from "../runs/background/result-files.ts";
37
38
  import { createResultWatcher } from "../runs/background/result-watcher.ts";
38
39
  import { createScheduledRunManager } from "../runs/background/scheduled-runs.ts";
39
40
  import { registerSlashCommands } from "../slash/slash-commands.ts";
@@ -62,6 +63,7 @@ import { resolveMissionStoreLocation } from "../missions/store.ts";
62
63
  import { listRetainedChildren } from "../runs/background/retained-children.ts";
63
64
  import {
64
65
  type Details,
66
+ type MainWindowRendererConfig,
65
67
  type SubagentState,
66
68
  DIRS,
67
69
  DEFAULT_ARTIFACT_CONFIG,
@@ -84,6 +86,13 @@ import {
84
86
 
85
87
  export { loadConfig, resolveAsyncByDefault } from "./config.ts";
86
88
 
89
+ const SLOW_RELOAD_PHASE_MS = 250;
90
+
91
+ function logSlowPhase(label: string, startedAt: number): void {
92
+ const elapsed = Date.now() - startedAt;
93
+ if (elapsed >= SLOW_RELOAD_PHASE_MS) console.error(`Subagent reload phase '${label}' took ${elapsed}ms.`);
94
+ }
95
+
87
96
  function workflowLaneKeys(script: string): string[] {
88
97
  const keys: string[] = [];
89
98
  const seen = new Set<string>();
@@ -260,12 +269,14 @@ function rebuildSlashResultContainer(
260
269
  result: AgentToolResult<Details>,
261
270
  options: { expanded: boolean },
262
271
  theme: ExtensionContext["ui"]["theme"],
272
+ rendererConfig?: MainWindowRendererConfig,
273
+ foregroundDetachShortcut?: string,
263
274
  ): void {
264
275
  container.clear();
265
276
  container.addChild(new Spacer(1));
266
277
  const boxTheme = isSlashResultRunning(result) ? "toolPendingBg" : isSlashResultError(result) ? "toolErrorBg" : "toolSuccessBg";
267
278
  const box = new Box(1, 1, (text: string) => theme.bg(boxTheme, text));
268
- box.addChild(renderSubagentResult(result, options, theme));
279
+ box.addChild(renderSubagentResult(result, options, theme, undefined, rendererConfig, foregroundDetachShortcut));
269
280
  container.addChild(box);
270
281
  }
271
282
 
@@ -273,6 +284,8 @@ function createSlashResultComponent(
273
284
  details: SlashMessageDetails,
274
285
  options: { expanded: boolean },
275
286
  theme: ExtensionContext["ui"]["theme"],
287
+ rendererConfig?: MainWindowRendererConfig,
288
+ foregroundDetachShortcut?: string,
276
289
  ): Container {
277
290
  const container = new Container();
278
291
  let lastVersion = -1;
@@ -280,7 +293,7 @@ function createSlashResultComponent(
280
293
  const snapshot = getSlashRenderableSnapshot(details);
281
294
  if (snapshot.version !== lastVersion || isSlashResultRunning(snapshot.result)) {
282
295
  lastVersion = snapshot.version;
283
- rebuildSlashResultContainer(container, snapshot.result, options, theme);
296
+ rebuildSlashResultContainer(container, snapshot.result, options, theme, rendererConfig, foregroundDetachShortcut);
284
297
  }
285
298
  return Container.prototype.render.call(container, width);
286
299
  };
@@ -377,6 +390,14 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
377
390
  const tempArtifactsDir = getArtifactsDir(null);
378
391
  const artifactCleanupDays = config.artifactConfig?.cleanupDays ?? DEFAULT_ARTIFACT_CONFIG.cleanupDays;
379
392
  cleanupAllArtifactDirs(artifactCleanupDays);
393
+ const resultIndexCleanupTimer = setTimeout(() => {
394
+ try {
395
+ cleanupResultIndexes(DIRS.results);
396
+ } catch (error) {
397
+ console.error("Failed to clean stale subagent result indexes:", error);
398
+ }
399
+ }, 30_000);
400
+ resultIndexCleanupTimer.unref?.();
380
401
 
381
402
  // Replay guard for lineage result adoption: accept result files written at
382
403
  // most this long before the adoption window opened (covers completions that
@@ -391,6 +412,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
391
412
  ...(config.authorityPolicy ? { authorityPolicy: config.authorityPolicy } : {}),
392
413
  ...(config.missions ? { missionStoreConfig: config.missions } : {}),
393
414
  parentSessionFile: null,
415
+ trustedSessionRoots: [],
394
416
  subagentInProgress: false,
395
417
  subagentSpawns: {
396
418
  sessionId: null,
@@ -464,6 +486,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
464
486
  );
465
487
 
466
488
  const runtimeCleanup = () => {
489
+ clearTimeout(resultIndexCleanupTimer);
467
490
  stopResultWatcher();
468
491
  state.currentSessionId = null;
469
492
  completionNotifier.dispose();
@@ -494,7 +517,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
494
517
  pi.registerMessageRenderer<SlashMessageDetails>(SLASH_RESULT_TYPE, (message, options, theme) => {
495
518
  const details = resolveSlashMessageDetails(message.details);
496
519
  if (!details) return undefined;
497
- return createSlashResultComponent(details, options, theme);
520
+ return createSlashResultComponent(details, options, theme, config.mainWindowRenderer, config.foregroundDetachShortcut);
498
521
  });
499
522
 
500
523
  pi.registerMessageRenderer<undefined>(SLASH_TEXT_RESULT_TYPE, (message, _options, _theme) => {
@@ -588,28 +611,31 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
588
611
  label: "Subagent",
589
612
  description: buildSubagentToolDescription(config),
590
613
  parameters,
614
+ keepParameterDescriptions: true,
591
615
 
592
616
  execute(id, params, signal, onUpdate, ctx) {
593
617
  return executeSubagentCollapsed(id, params as SubagentParamsLike, signal ?? new AbortController().signal, onUpdate, ctx);
594
618
  },
595
619
 
596
620
  renderCall(args, theme) {
621
+ const gap = " ".repeat(config.mainWindowRenderer?.horizontalSpacing ?? 1);
622
+ const title = theme.fg("toolTitle", theme.bold("subagent"));
597
623
  if (args.action) {
598
624
  const target = args.agent || args.chainName || "";
599
625
  return new Text(
600
- `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
626
+ `${title}${gap}${args.action}${target ? `${gap}${theme.fg("accent", target)}` : ""}`,
601
627
  0, 0,
602
628
  );
603
629
  }
604
630
  if (args.workflowScript)
605
631
  return new Text(
606
- `${theme.fg("toolTitle", theme.bold("subagent "))}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,
632
+ `${title}${gap}${formatWorkflowManifest(args.workflowScript, args.async, false)}`,
607
633
  0,
608
634
  0,
609
635
  );
610
- const asyncLabel = args.async === true ? theme.fg("warning", " [async]") : "";
636
+ const asyncLabel = args.async === true ? `${gap}${theme.fg("warning", "[async]")}` : "";
611
637
  return new Text(
612
- `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
638
+ `${title}${gap}${theme.fg("accent", args.agent || "?")}${asyncLabel}`,
613
639
  0,
614
640
  0,
615
641
  );
@@ -620,7 +646,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
620
646
  const renderedResult = { ...result, isError: context.isError };
621
647
  return summaryInlineToolDisplay
622
648
  ? renderSubagentSummary(renderedResult, options, theme)
623
- : renderSubagentResult(renderedResult, options, theme);
649
+ : renderSubagentResult(renderedResult, options, theme, undefined, config.mainWindowRenderer, config.foregroundDetachShortcut);
624
650
  },
625
651
 
626
652
  };
@@ -664,7 +690,10 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
664
690
  }
665
691
  });
666
692
 
667
- registerSlashCommands(pi, state, { fleetKeybindings: config.fleetKeybindings });
693
+ registerSlashCommands(pi, state, {
694
+ fleetKeybindings: config.fleetKeybindings,
695
+ foregroundDetachShortcut: config.foregroundDetachShortcut,
696
+ });
668
697
  pi.on("resources_discover", () => {
669
698
  return {
670
699
  promptPaths: [fileURLToPath(new URL("../../prompts", import.meta.url))],
@@ -810,15 +839,19 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
810
839
  };
811
840
 
812
841
  const resetSessionState = (ctx: ExtensionContext, recovering: boolean, previousSessionFile?: string | null) => {
842
+ state.sessionLineage = resolveSessionLineage({ sessionManager: ctx.sessionManager }, { parentHint: previousSessionFile ?? null });
843
+ state.handoffResumePending = false;
844
+ if (state.sessionLineage.length > 1) state.adoptedResultWindowStart = Date.now() - ADOPTED_RESULT_SLACK_MS;
845
+ else state.adoptedResultWindowStart = undefined;
813
846
  state.widgetsSuspended = false;
814
847
  state.baseCwd = ctx.cwd;
815
848
  goalTurnId = 0;
816
849
  state.currentSessionId = resolveCurrentSessionId(ctx.sessionManager);
817
850
  state.parentSessionFile = ctx.sessionManager.getSessionFile();
818
- state.sessionLineage = resolveSessionLineage({ sessionManager: ctx.sessionManager }, { parentHint: previousSessionFile ?? null });
819
- state.handoffResumePending = false;
820
- if (state.sessionLineage.length > 1) state.adoptedResultWindowStart = Date.now() - ADOPTED_RESULT_SLACK_MS;
821
- else state.adoptedResultWindowStart = undefined;
851
+ state.trustedSessionRoots = [...new Set([
852
+ ...(config.defaultSessionDir ? [path.resolve(expandTilde(config.defaultSessionDir))] : []),
853
+ ...(state.parentSessionFile ? [getSubagentSessionRoot(state.parentSessionFile)] : []),
854
+ ])];
822
855
  state.subagentSpawns = {
823
856
  sessionId: state.currentSessionId,
824
857
  count: 0,
@@ -838,19 +871,39 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
838
871
  }
839
872
  }
840
873
  state.lastUiContext = ctx;
874
+ let phaseStartedAt = Date.now();
841
875
  refreshActiveAsyncCapacity();
876
+ logSlowPhase("active-capacity", phaseStartedAt);
877
+ phaseStartedAt = Date.now();
842
878
  cleanupSessionArtifacts(ctx);
879
+ logSlowPhase("session-artifact-cleanup", phaseStartedAt);
843
880
  state.foregroundControls.clear();
844
881
  state.lastForegroundControlId = null;
882
+ phaseStartedAt = Date.now();
845
883
  resetJobs(ctx);
884
+ logSlowPhase("reset-jobs", phaseStartedAt);
885
+ phaseStartedAt = Date.now();
846
886
  restoreForegroundRunHistory(state, { resultsDir: DIRS.results });
887
+ logSlowPhase("foreground-history", phaseStartedAt);
888
+ phaseStartedAt = Date.now();
847
889
  const adoptedJobs = restoreActiveJobs(ctx);
890
+ logSlowPhase("active-job-restore", phaseStartedAt);
848
891
  state.handoffResumePending = adoptedJobs > 0 && state.lastUiContext?.hasUI === true;
892
+ phaseStartedAt = Date.now();
849
893
  scheduledRunManager.bindSession(ctx);
894
+ logSlowPhase("scheduled-runs", phaseStartedAt);
895
+ phaseStartedAt = Date.now();
850
896
  restoreSlashFinalSnapshots(ctx.sessionManager.getEntries());
897
+ logSlowPhase("slash-snapshots", phaseStartedAt);
898
+ phaseStartedAt = Date.now();
851
899
  waitSubscriptionManager.restore();
900
+ logSlowPhase("wait-subscriptions", phaseStartedAt);
901
+ phaseStartedAt = Date.now();
852
902
  startResultWatcher();
903
+ logSlowPhase("result-watcher-start", phaseStartedAt);
904
+ phaseStartedAt = Date.now();
853
905
  primeExistingResults({ triggerTurn: !recovering });
906
+ logSlowPhase("result-prime", phaseStartedAt);
854
907
  fleetStatus?.setContext(ctx);
855
908
  };
856
909
 
@@ -905,6 +958,7 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
905
958
 
906
959
  pi.on("session_shutdown", async () => {
907
960
  state.widgetsSuspended = false;
961
+ clearTimeout(resultIndexCleanupTimer);
908
962
  stopResultWatcher();
909
963
  state.currentSessionId = null;
910
964
  state.parentSessionFile = null;
@@ -9,6 +9,7 @@ export interface PublicSubagentExecutionParams {
9
9
  concurrency?: unknown;
10
10
  chainDir?: unknown;
11
11
  workflowScript?: unknown;
12
+ output?: unknown;
12
13
  resume?: unknown;
13
14
  clarify?: unknown;
14
15
  runFanoutBudget?: unknown;
@@ -33,7 +34,7 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
33
34
  }
34
35
  const action = params.action;
35
36
  if (action !== undefined && (typeof action !== "string" || !action.trim())) {
36
- return { ok: false, error: "action must be a non-empty management/control action, or omit action and execute directly.", mode: "management" };
37
+ return { ok: false, error: "action must be a non-empty management/control action, or omit action and use workflowScript.", mode: "management" };
37
38
  }
38
39
  const normalizedAction = typeof action === "string" ? action.trim() : undefined;
39
40
  if (params.clarify === true && params.workflowScript !== undefined) {
@@ -59,10 +60,16 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
59
60
  if (params.workflowScript !== undefined) {
60
61
  return { ok: false, error: "workflowScript execution must omit action; only schedule.create accepts action with workflowScript.", mode: "management" };
61
62
  }
63
+ if (params.task !== undefined) {
64
+ return { ok: false, error: "Structured single-child task cannot be combined with a management/control action.", mode: "management" };
65
+ }
62
66
  return { ok: true, params: { ...params, action: normalizedAction } };
63
67
  }
64
- if (typeof params.workflowScript === "string" && params.workflowScript.trim()) {
65
- return { ok: true, params };
68
+ if (params.step !== undefined) {
69
+ return { ok: false, error: "step is only available with action='append-step'; it is not an execution mode.", mode: "workflow" };
70
+ }
71
+ if (params.workflowScript !== undefined && (params.agent !== undefined || params.task !== undefined)) {
72
+ return { ok: false, error: "Structured single-child execution cannot be combined with workflowScript.", mode: "workflow" };
66
73
  }
67
74
  if (params.chain !== undefined) {
68
75
  return { ok: true, params };
@@ -70,8 +77,29 @@ export function normalizePublicSubagentExecution<T extends PublicSubagentExecuti
70
77
  if (params.tasks !== undefined) {
71
78
  return { ok: true, params };
72
79
  }
73
- if (params.agent !== undefined || params.task !== undefined || params.step !== undefined) {
74
- return { ok: true, params };
80
+ if (params.agent !== undefined || params.task !== undefined) {
81
+ if (typeof params.agent !== "string" || !params.agent.trim()) {
82
+ return { ok: false, error: "Structured single-child execution requires agent to be a non-empty string.", mode: "workflow" };
83
+ }
84
+ if (params.task !== undefined && typeof params.task !== "string") {
85
+ return { ok: false, error: "Structured single-child task must be a string when provided.", mode: "workflow" };
86
+ }
87
+ const { agent: _agent, task: _task, output, ...workflowDefaults } = params;
88
+ const child = {
89
+ agent: params.agent.trim(),
90
+ ...(params.task !== undefined ? { task: params.task } : {}),
91
+ output: output === undefined ? true : output,
92
+ };
93
+ return {
94
+ ok: true,
95
+ params: {
96
+ ...workflowDefaults,
97
+ workflowScript: `console.info("Converted structured single-child request to workflow runs.run('main', ...)."); return runs.run("main", ${JSON.stringify(child)})`,
98
+ } as T,
99
+ };
100
+ }
101
+ if (typeof params.workflowScript !== "string" || !params.workflowScript.trim()) {
102
+ return { ok: false, error: "Execution requires one mode: { agent, task? }, { chain: [...] }, { tasks: [...] }, or { workflowScript }.", mode: "workflow" };
75
103
  }
76
- return { ok: false, error: "Execution requires one mode: { agent, task? }, { chain: [...] }, { tasks: [...] }, or { workflowScript }.", mode: "workflow" };
104
+ return { ok: true, params };
77
105
  }
@@ -21,6 +21,7 @@ import { sanitizeDisplayText, truncateDisplayText } from "../shared/display-text
21
21
  import { readStatus } from "../shared/utils.ts";
22
22
  import { SubagentParams } from "./schemas.ts";
23
23
  import { normalizePublicSubagentExecution } from "./public-execution.ts";
24
+ import { ASYNC_STATUS_SNAPSHOT_KIND, ASYNC_STATUS_SNAPSHOT_VERSION, buildAsyncStatusSnapshotForState } from "../runs/background/async-status-snapshot.ts";
24
25
 
25
26
  export const SUBAGENT_RPC_PROTOCOL_VERSION = 1;
26
27
  export const SUBAGENT_RPC_REQUEST_EVENT = "subagents:rpc:v1:request";
@@ -381,6 +382,7 @@ function pingData(ctx: ExtensionContext | null) {
381
382
  capabilities: {
382
383
  status: true,
383
384
  fleetStatus: { version: 1 },
385
+ asyncStatusSnapshot: { kind: ASYNC_STATUS_SNAPSHOT_KIND, version: ASYNC_STATUS_SNAPSHOT_VERSION },
384
386
  asyncSpawn: true,
385
387
  steer: true,
386
388
  nonRecoveringSteer: true,
@@ -550,13 +552,15 @@ async function handleRequest(
550
552
  request.method,
551
553
  { action: "status", ...normalizeTargetParams(request.params, "status") },
552
554
  );
555
+ const sessionId = resolveCurrentSessionId(ctx.sessionManager);
553
556
  return {
554
557
  ...status,
555
558
  fleet: buildFleetStatus(
556
559
  options.state,
557
560
  fleetKeys,
558
- resolveCurrentSessionId(ctx.sessionManager),
561
+ sessionId,
559
562
  ),
563
+ asyncSnapshot: buildAsyncStatusSnapshotForState(options.state, sessionId),
560
564
  };
561
565
  }
562
566
  if (request.method === "steer") {
@@ -89,11 +89,11 @@ const AcceptanceOverride = Type.Unsafe({
89
89
  { type: "boolean", enum: [false] },
90
90
  { type: "object", additionalProperties: true },
91
91
  ],
92
- description: `Optional acceptance policy. For reviewer/read-only calls, omit acceptance. Example: { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(", ")}. Evidence levels end at verified; use acceptance.review.required for review. Omitted means auto-inferred unless agentContract compatibility behavior is enabled.`,
92
+ description: `Optional acceptance policy. Omit for read-only calls. Example: { level: "checked", evidence: ["commands-run", "changed-files"] }. Supported evidence kinds: ${AcceptanceEvidenceKinds.join(", ")}. Evidence levels end at verified; use acceptance.review.required for review.`,
93
93
  });
94
94
 
95
95
  const AgentContractOverride = Type.Object({
96
- version: Type.Integer({ enum: [1], description: "Enable compatibility behavior for this run/child." }),
96
+ version: Type.Integer({ minimum: 1, maximum: 1, description: "Enable compatibility behavior for this run/child." }),
97
97
  }, { additionalProperties: false, description: "Compatibility behavior. Omit for the default behavior." });
98
98
 
99
99
  const ChainGateOverride = Type.String({
@@ -255,49 +255,49 @@ const ControlOverrides = Type.Object({
255
255
  });
256
256
 
257
257
  const SubagentParamProperties = {
258
- agent: Type.Optional(Type.String({ description: "Agent target for management actions such as get, update, delete, and models." })),
259
- task: Type.Optional(Type.String({ description: "Task (SINGLE mode, optional for self-contained agents)" })),
258
+ agent: Type.Optional(Type.String({ description: "Agent for one-child execution, or target for agent management actions." })),
259
+ task: Type.Optional(Type.String({ description: "Optional one-child task. Requires agent; cannot combine with action or workflowScript." })),
260
260
  resume: Type.Optional(Type.String({ description: "Retained child run id for a workflowScript runs.run/runs.all item. Mutually exclusive with agent; task supplies the follow-up." })),
261
261
  // Management action (when present, tool operates in management mode)
262
262
  action: Type.Optional(Type.String({ minLength: 1,
263
- description: "Optional management/control action. Omit this field for workflowScript execution; use it only for management/control actions."
263
+ description: "Optional management/control action. Omit this field for structured single-child or workflowScript execution; use it only for management/control actions."
264
264
  })),
265
265
  name: Type.Optional(Type.String({ description: "Human-readable name for action='schedule.create'." })),
266
266
  id: Type.Optional(Type.String({
267
- description: "Run id or prefix for status, interrupt, stop, dismiss, resume, steer, append-step, approve-checkpoint, reject-checkpoint, mission.attach-run, or the decision id for mission.resolve-decision."
267
+ description: "Run id/prefix for status/debug.run, interrupt, steer, append-step, approve-checkpoint, reject-checkpoint, or mission."
268
268
  })),
269
269
  runId: Type.Optional(Type.String({
270
- description: "Target run ID for interrupt, stop, dismiss, resume, steer, append-step, approve-checkpoint, reject-checkpoint, or mission.attach-run. Prefer id for new calls."
270
+ description: "Target run ID for debug.run, interrupt, steer, append-step, or mission.attach-run. Prefer id."
271
271
  })),
272
272
  dir: Type.Optional(Type.String({
273
- description: "Async run directory for action='status', action='stop', action='resume', or action='steer'."
273
+ description: "Async run directory for status/debug.run, stop, resume, or steer."
274
274
  })),
275
275
  handoffPath: Type.Optional(Type.String({ description: "worktree.discard manifest." })),
276
276
  index: Type.Optional(Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child or transcript." })),
277
277
  view: Type.Optional(Type.String({
278
278
  enum: ["fleet", "transcript"],
279
- description: "Optional status view. Use view='fleet' for a read-only active foreground/async fleet surface, or view='transcript' with id/dir (and optional index) to tail a run transcript.",
279
+ description: "Optional status view: 'fleet' for a read-only fleet surface, or 'transcript' with id/dir (and optional index) to tail a run transcript.",
280
280
  })),
281
281
  lines: Type.Optional(Type.Integer({ minimum: 1, maximum: 500, description: "Maximum transcript lines for action='status', view='transcript'. Defaults to 80." })),
282
282
  topic: Type.Optional(Type.String()),
283
283
  message: Type.Optional(Type.String({ description: "Follow-up message for resume, live guidance for steer, or optional startup prompt for project.open." })),
284
- mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer'. steer interrupts at the next safe point (default), follow_up waits for the next turn boundary, and auto follows up mid-turn but delivers immediately between turns." })),
285
- steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls in steer mode; extension RPC steering forces false so callers retain exact child ownership." })),
286
- additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only; total grants cannot exceed the original configured cap." })),
287
- scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session to avoid persistent settings writes unless user/project is explicit." })),
284
+ mode: Type.Optional(Type.String({ enum: ["steer", "follow_up", "auto"], description: "Delivery mode for action='steer': steer interrupts at the next safe point (default), follow_up waits for the next turn boundary, auto follows up mid-turn." })),
285
+ steeringRecovery: Type.Optional(Type.Boolean({ description: "For action='steer', allow pause-and-revive recovery after a missed acknowledgment. Defaults true for direct tool calls; false for extension RPC steering." })),
286
+ additional: Type.Optional(Type.Integer({ minimum: 1, description: "Positive launches to add with action='grant-spawn-budget'. Root interactive parent with native user confirmation only." })),
287
+ scope: Type.Optional(Type.String({ enum: ["session", "user", "project"], description: "Scope for action='watchdog.configure'. Defaults to session." })),
288
288
  target: Type.Optional(Type.String({ enum: ["main", "children", "child"], description: "Target for watchdog actions." })),
289
289
  focus: Type.Optional(Type.Boolean({ description: "Focus the new Herdr pane for inspector.open or project.open." })),
290
290
  thinking: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "boolean", enum: [false] }], description: "Thinking level for action='watchdog.configure' (off/minimal/low/medium/high/xhigh/max, inherit, or false for off)." })),
291
- schedule: Type.Optional(Type.String({ deprecated: true, description: "Removed one-shot schedule field. Use action='schedule.create' with at." })),
292
- scheduleName: Type.Optional(Type.String({ deprecated: true, description: "Removed schedule display field. Use name." })),
291
+ schedule: Type.Optional(Type.String({ deprecated: true, description: "Deprecated. Use action='schedule.create' with at." })),
292
+ scheduleName: Type.Optional(Type.String({ deprecated: true, description: "Deprecated. Use name." })),
293
293
  at: Type.Optional(Type.String({ description: "One-shot trigger for action='schedule.create': a relative delay such as '+10m' or an ISO timestamp with timezone." })),
294
294
  every: Type.Optional(Type.String({ description: "Fixed recurring interval for action='schedule.create', such as '30m', '6h', '2d', or '2w'." })),
295
- on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Calendar selector reserved for a later schedule slice." })),
296
- timezone: Type.Optional(Type.String({ description: "IANA timezone reserved for a later calendar schedule slice." })),
295
+ on: Type.Optional(Type.Unsafe({ anyOf: [{ type: "string" }, { type: "integer" }], description: "Reserved for a later calendar schedule slice." })),
296
+ timezone: Type.Optional(Type.String({ description: "Reserved for a later calendar schedule slice." })),
297
297
  overlap: Type.Optional(Type.String({ enum: ["skip"], description: "Overlap policy. This slice supports skip only." })),
298
298
  catchUp: Type.Optional(Type.String({ enum: ["none", "latest"], description: "Missed occurrence policy for recurring schedules. Defaults to latest." })),
299
299
  missionId: Type.Optional(Type.String({ description: "Mission id." })),
300
- mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission. Set exactly one non-empty title or summary; objective and labels are optional. goal may only be true and then requires budget.tokens." })),
300
+ mission: Type.Optional(Type.Unsafe({ ...MissionLaunchOverride, description: "Mission object, or false for no mission. Requires exactly one non-empty title or summary; goal may only be true and then requires budget.tokens." })),
301
301
  missionUpdate: Type.Optional(Type.Unsafe({ ...MissionUpdateOverride, description: "Mission update: objective, goal false or {paused:boolean}, budget, summary, labels, decisions, artifacts, or delivery receipts." })),
302
302
  missionStatus: Type.Optional(Type.String({ description: "Mission status." })),
303
303
  missionScope: Type.Optional(Type.String({ description: "Mission list scope: project (default) or global pointer index." })),
@@ -316,10 +316,10 @@ const SubagentParamProperties = {
316
316
  ],
317
317
  description: "Agent/chain config for create/update. Object or JSON string; presence of steps creates a chain."
318
318
  })),
319
- workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body. Starts async by default; pass async:false for a small foreground run. Use explicit return for output. Use await prompts.render(ref, vars?) for task text. Use await runs.run(key, {agent, task, worktree?, gate?}) or runs.run(key, {resume, task}), runs.all([...]), runs.status(id), runs.ref(s), emit(value), console, and return. Mission workflows also have async state.get(key) and state.set(key, JSONValue). Compose sequential and parallel phases dynamically. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. gate is one host-run command and cannot be combined with acceptance. runs.run accepts one child only. No filesystem, shell, Pi tools, or host globals." })),
319
+ workflowScript: Type.Optional(Type.String({ minLength: 1, description: "Trusted inline JavaScript statement body; use explicit return for output. Available: runs.run(key, {agent, task, ...}) and runs.run(key, {resume, task}), runs.all([...]), runs.status(id), runs.ref(s), await prompts.render(ref, vars?), emit, console; mission workflows also have async state.get/set. Scripts start async by default. Set worktree:true at workflow or child level for a separate managed worktree; child fields override workflow defaults. Compose sequential and parallel phases dynamically. No filesystem, shell, Pi tools, or host globals." })),
320
320
  chain: Type.Optional(Type.Unsafe({
321
321
  anyOf: [Type.Array(ChainItem, { minItems: 1, description: "Sequential chain of steps" })],
322
- description: "Sequential chain of steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: \"name\" on a step to bind {outputs.name} for later steps, phase/label for status readability, outputSchema for structured results, and { checkpoint: \"name\", message?: \"...\" } for human gates paused until approve-checkpoint / reject-checkpoint. Set gateOn: \"acceptance\" to advance on acceptance instead of execution.",
322
+ description: "Sequential chain of steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: \"name\" on a step to bind {outputs.name} for later steps; { checkpoint: \"name\", message?: \"...\" } pauses for approve-checkpoint / reject-checkpoint; gateOn: \"acceptance\" advances on acceptance instead of execution.",
323
323
  })),
324
324
  tasks: Type.Optional(Type.Unsafe({
325
325
  anyOf: [
@@ -328,27 +328,28 @@ const SubagentParamProperties = {
328
328
  ],
329
329
  description: "Top-level parallel tasks array, or a single dynamic fanout child template when expand/collect are present.",
330
330
  })),
331
- chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "terminal", "milestones", "live-card"], description: "WorkflowScript chat progress projection. auto shows a live in-chat card for watched foreground workflows in the same Git repository, milestones for background runs, and terminal for other-repo work." })),
332
- worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; an individual runs.run/runs.all item can override a workflow default with worktree:false." })),
331
+ chatProgress: Type.Optional(Type.String({ enum: ["auto", "off", "terminal", "milestones", "live-card"], description: "WorkflowScript chat progress projection: auto shows a live in-chat card for watched foreground workflows in the same Git repository, milestones for background runs, terminal otherwise." })),
332
+ worktree: Type.Optional(Type.Boolean({ description: "Managed child isolation. true gives each workflow child a separate git worktree; individual runs.run/runs.all items can override with worktree:false." })),
333
333
  step: Type.Optional(Type.Unsafe({ ...ChainItem, description: "One chain step for action='append-step' only. Not an execution mode." })),
334
334
  context: Type.Optional(Type.String({
335
335
  enum: ["fresh", "fork"],
336
- description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without defaultContext: 'fork' run fresh.",
336
+ description: "'fresh' or 'fork' to branch from parent session. Explicit context overrides every child in the invocation. If omitted, each requested agent uses its own defaultContext; agents without one run 'fork' fresh.",
337
337
  })),
338
338
  async: Type.Optional(Type.Boolean({ description: "Run in background (default: false, or per config)" })),
339
- clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution. Explicit clarify: true keeps the run foreground for the clarify UI; omitted clarify can still run in the background when async: true is set." })),
340
- timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline. Alias maxRuntimeMs." })),
341
- maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias timeoutMs. Foreground and single async runs use config timeoutMs, else 30m; async composites have no default parent deadline." })),
339
+ clarify: Type.Optional(Type.Boolean({ description: "Show TUI to preview/edit before execution; explicit true keeps the run foreground." })),
340
+ timeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Timeout. Defaults to config timeoutMs or 30m for foreground/single async runs. Alias maxRuntimeMs." })),
341
+ maxRuntimeMs: Type.Optional(Type.Integer({ minimum: 1, description: "Alias of timeoutMs." })),
342
+ toolTimeoutMs: Type.Optional(Type.Integer({ minimum: 1, description: "Optional hard per-tool-call timeout in milliseconds." })),
342
343
  turnBudget: Type.Optional(TurnBudgetOverride),
343
344
  toolBudget: Type.Optional(ToolBudgetOverride),
344
345
  usageBudget: Type.Optional(UsageBudgetOverride),
345
- agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default: 'both'; project wins on name collisions)" })),
346
+ agentScope: Type.Optional(Type.String({ description: "Agent discovery scope: 'user', 'project', or 'both' (default 'both')." })),
346
347
  cwd: Type.Optional(Type.String({ description: "Execution cwd, or target project directory for project.open/status/close." })),
347
348
  artifacts: Type.Optional(Type.Boolean({ description: "Write debug artifacts (default: true)" })),
348
349
  includeProgress: Type.Optional(Type.Boolean({ description: "Include full progress in result (default: false)" })),
349
350
  share: Type.Optional(Type.Boolean({ description: "Upload session to GitHub Gist for sharing (default: false)" })),
350
351
  sessionDir: Type.Optional(
351
- Type.String({ description: "Directory to store session logs (default: temp; enables sessions even if share=false)" }),
352
+ Type.String({ description: "Directory to store session logs (default: temp)." }),
352
353
  ),
353
354
  control: Type.Optional(ControlOverrides),
354
355
  // Workflow defaults forwarded to each runs.run/runs.all child unless overridden there.
@@ -372,10 +373,10 @@ const { step: _legacyChainStep, ...subagentParamPropertiesWithoutStep } = Subage
372
373
  const trimmedSubagentParamProperties = {
373
374
  ...subagentParamPropertiesWithoutStep,
374
375
  id: Type.Optional(Type.String({
375
- description: "Run id or prefix for status, interrupt, stop, resume, steer, mission.attach-run, or the decision id for mission.resolve-decision."
376
+ description: "Run id/prefix for status/debug.run, interrupt, steer, or mission.attach-run."
376
377
  })),
377
378
  runId: Type.Optional(Type.String({
378
- description: "Target run ID for interrupt, stop, resume, steer, or mission.attach-run. Prefer id for new calls."
379
+ description: "Target run ID for debug.run, interrupt, steer, or mission.attach-run. Prefer id."
379
380
  })),
380
381
  };
381
382
  const SubagentParamsSchema = Type.Object(SubagentParamProperties);
@@ -8,11 +8,12 @@ const CUSTOM_TOOL_DESCRIPTION_MAX_BYTES = 50 * 1024;
8
8
 
9
9
  export const SUBAGENT_SAFETY_GUIDANCE = `SAFETY-CRITICAL SUBAGENT GUIDANCE:
10
10
  • Use { action: "list" } before execution and only run executable/non-disabled agents.
11
- • Keep execution and management separate: omit action for workflowScript execution; use action only for management/control.
12
- • Async/background runs are the default. Use async:false only when a blocking foreground result is needed. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
11
+ • Keep execution and management separate: omit action for structured single-child or workflowScript execution; use action only for management/control.
12
+ • Async/background runs are the default. Use async:false only when a blocking foreground result is needed. After an async launch, continue independent work only until its next dependency barrier; consume the result before work that depends on it. Do not sleep or poll status just to wait; use subagent_wait only when the current request must finish in this turn.
13
13
  • Ordinary child subagents are not orchestrators. Only explicitly configured fanout children may use the child-safe subagent tool, still bounded by depth/session limits.
14
+ • Oracle/advisor consultations should use supervisor dialogue for material unknowns when available; request one-shot only when desired.
14
15
  • Keep one writer for the same cwd/worktree. Use fresh-context read-only reviewers for independent review, then have the parent synthesize and apply fixes.
15
- • Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, and status via { action: "status", id }. Include output paths and residual risks when reporting results.`;
16
+ • Async runs expose asyncId/asyncDir with status.json, events.jsonl, output logs, status via { action: "status", id }, and lifecycle diagnostics via { action: "debug.run", id }. Include output paths and residual risks when reporting results.`;
16
17
 
17
18
  export const FULL_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agent, task }, compose steps with { chain }, fan out with { tasks }, or orchestrate with { workflowScript }; omit action. Use action only for management/control actions.
18
19
 
@@ -21,7 +22,7 @@ EXECUTION (use exactly one mode):
21
22
  • SINGLE: { agent, task? } launches one child. Omit task for a self-contained agent.
22
23
  • CHAIN: { chain: [{ agent: "explorer", task: "..." }, { agent: "builder", task: "Implement from {previous}" }] } runs sequential steps. Steps support template variables {task}, {previous}, {chain_dir}, and {outputs.name}; use as: "name" on a step to bind {outputs.name} for later steps, phase/label for status readability, outputSchema for structured results, and { checkpoint: "name", message?: "..." } for human gates paused until approve-checkpoint / reject-checkpoint. Set gateOn: "acceptance" to advance on acceptance instead of execution. agentContract: { version: 1 } gives generic result projections.
23
24
  • PARALLEL: { tasks: [{ agent, task }, ...], concurrency? } runs top-level parallel children (count: N repeats one template). Parallel groups also work inside chain steps with { parallel: [...] }, plus dynamic fanout via { expand: { from: { output, path }, item?, maxItems, parallel: {...}, collect: { as, outputSchema? } } }.
24
- • SCRIPTED WORKFLOW: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Scripts start asynchronously by default; pass async:false only for a small foreground run. Same-repo foreground workflows default to a live in-chat card; set chatProgress to auto, off, terminal, milestones, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable plain task text, then pass the result explicitly as task. Use {action:"children.list"} to list up to 10 completed retained children from this parent session, then continue one with runs.run(key, {resume:"run-id", task:"follow-up"}); resume and agent are mutually exclusive, resume keeps the stored agent/model/tool contract, workflow resumes wait for completed output, and loops must continue from each latest returned runId. For repository mutation lanes, set worktree:true on a direct single child, chain, workflow, or individual runs.run/runs.all item for managed isolation; each parallel child gets a separate worktree and handoff artifact. A workflow usageBudget is enforced once across the workflow. Available globals are runs.run, runs.all, runs.status, runs.ref/refs, prompts.render, emit, console, and standard JavaScript only. Workflows get async state.get(key) and state.set(key, JSONValue) through their automatic or explicit mission; mission:false workflows do not have a state global. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
25
+ • SCRIPTED WORKFLOW: { workflowScript: "return runs.run('main', {agent:'worker', task:'...'})" }. Use stable-key runs.run for one child and runs.all for parallel children; ordinary JavaScript provides sequence, branching, filtering, retries, and aggregation. workflowScript is an ordinary JavaScript statement body, so use an explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\n")\`. Scripts start asynchronously by default; pass async:false only for a small foreground run. Same-repo foreground workflows default to a live in-chat card; set chatProgress to auto, off, terminal, milestones, or live-card to control that projection. Workflow-level child controls default onto each runs.run launch, and explicit child fields override them. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable plain task text, then pass the result explicitly as task. Use {action:"children.list"} to list recent retained children with resumable/not-resumable states. Resume only rows reported resumable; {action:"resume", id:"run-id", message:"..."} keeps the stored agent/model/tool contract, or use runs.run(key, {resume:"run-id", task:"follow-up"}) inside workflowScript. Workflow resumes wait for completed output, and loops must continue from each latest returned runId. If no resumable child is listed, launch a same-role fallback challenge and label it as fallback. For repository mutation lanes, set worktree:true on a direct single child, chain, workflow, or individual runs.run/runs.all item for managed isolation; each parallel child gets a separate worktree and handoff artifact. A workflow usageBudget is enforced once across the workflow. Available globals are runs.run, runs.all, runs.status, runs.ref/refs, prompts.render, emit, console, and standard JavaScript only. Workflows get async state.get(key) and state.set(key, JSONValue) through their automatic or explicit mission; mission:false workflows do not have a state global. Scripts cannot access filesystem, shell, arbitrary Pi tools, or host globals.
25
26
  • Sequential example: { workflowScript: "const a = await runs.run('analyze', {agent:'agent-a', task:'Analyze the request'}); return (await runs.run('plan', {agent:'agent-b', task:'Plan from: '+a.output})).output" }
26
27
  • Parallel example: { workflowScript: "const [a,b] = await runs.all([{key:'correctness',agent:'agent-a',task:'Review correctness'},{key:'tests',agent:'agent-b',task:'Review tests'}]); return {correctness:a.output,tests:b.output}" }
27
28
  • Optional context is "fresh" or "fork". timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground defaults to 30 minutes absent call/agent. Omit acceptance for read-only calls; evidence levels end at verified, and acceptance.review.required requests independent writer review. For one host-run verification command, gate: "npm test" on a runs.run/runs.all item is shorthand; it cannot be combined with acceptance.
@@ -41,7 +42,7 @@ export const COMPACT_SUBAGENT_TOOL_DESCRIPTION = `Delegate one child with { agen
41
42
  EXECUTE:
42
43
  • Call { action:"list" } first and use only executable/non-disabled agents.
43
44
  • SINGLE {agent, task?}; CHAIN {chain:[{agent,task,...}]} with {previous}/{outputs.name} templates and { checkpoint } human gates; PARALLEL {tasks:[...], concurrency?} with optional count and in-chain { parallel: [...] } / dynamic { expand }+{ collect } fanout.
44
- • SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for the last 10 retained children in this parent session, then runs.run(key,{resume:"run-id",task:"follow-up"}) to continue one with its stored contract; workflow resumes wait for completion and loops continue from the latest returned runId. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on a direct single child, chain, or runs.run/runs.all item for managed isolation. Scripts start async by default; async:false is the foreground escape hatch and auto-enables a same-repo live chat card unless chatProgress is off/terminal/milestones.
45
+ • SCRIPT {workflowScript:"return runs.run('main', {agent:'worker', task:'...'})"}. Use stable-key runs.run for one child and runs.all for parallel work. Use await prompts.render("package:name" | "user:name" | "project:name", vars?) for reusable task text and pass it explicitly to runs.run. Use {action:"children.list"} for recent retained children and resume only rows reported resumable, with {action:"resume", id, message} or runs.run(key,{resume:"run-id",task:"follow-up"}); workflow resumes wait for completion and loops continue from the latest returned runId. Workflows get async state.get/state.set through their automatic or explicit mission; mission:false does not. Scripts are ordinary JavaScript statement bodies; use explicit return for a useful result. For task text with Markdown fences or shell blocks, build quoted lines instead of nesting raw template literals: \`const task=["Run:","\`\`\`bash","npm test","\`\`\`"].join("\n")\`. Use JavaScript for sequence, branching, retries, and aggregation. For repository mutation lanes, use worktree:true on a direct single child, chain, or runs.run/runs.all item for managed isolation. Scripts start async by default; async:false is the foreground escape hatch and auto-enables a same-repo live chat card unless chatProgress is off/terminal/milestones.
45
46
  • Example: {workflowScript:"const [a,b]=await runs.all([{key:'a',agent:'agent-a',task:'Implement A',worktree:true},{key:'b',agent:'agent-b',task:'Implement B',worktree:true}]); return [a.output,b.output]"}
46
47
  • context can be fresh or fork. timeoutMs/maxRuntimeMs apply to foreground and async runs; foreground defaults to 30 minutes absent call/agent. Omit acceptance for read-only calls.
47
48
 
@@ -72,10 +73,10 @@ export interface ToolDescriptionOptions {
72
73
 
73
74
  export function resolveToolDescriptionMode(config: Pick<ExtensionConfig, "toolDescriptionMode">, options?: ToolDescriptionOptions): ToolDescriptionMode {
74
75
  const mode = config.toolDescriptionMode;
75
- if (mode === undefined) return "full";
76
+ if (mode === undefined) return "compact";
76
77
  if (isToolDescriptionMode(mode)) return mode;
77
78
  warn(options, `Ignoring invalid toolDescriptionMode ${JSON.stringify(mode)}; expected "full", "compact", or "custom".`);
78
- return "full";
79
+ return "compact";
79
80
  }
80
81
 
81
82
  function customDescriptionPaths(options?: ToolDescriptionOptions): string[] {
@@ -178,8 +179,8 @@ export function buildSubagentToolDescription(config: Pick<ExtensionConfig, "tool
178
179
  const custom = loadCustomToolDescription(options);
179
180
  if (custom) description = withMandatorySafetyGuidance(custom);
180
181
  else {
181
- warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using full description.`);
182
- description = FULL_SUBAGENT_TOOL_DESCRIPTION;
182
+ warn(options, `${CUSTOM_TOOL_DESCRIPTION_FILE} was not found or valid for toolDescriptionMode "custom"; using compact description.`);
183
+ description = COMPACT_SUBAGENT_TOOL_DESCRIPTION;
183
184
  }
184
185
  } else description = FULL_SUBAGENT_TOOL_DESCRIPTION;
185
186
  return config.legacyChainControls === false ? withoutLegacyChainControlGuidance(description) : description;
@@ -10,7 +10,9 @@ import { writeAtomicJson } from "../../shared/atomic-json.ts";
10
10
  import { DIRS, type Details, type SubagentState } from "../../shared/types.ts";
11
11
  import { readStatus } from "../../shared/utils.ts";
12
12
  import { resolveSubagentRunId } from "../../runs/background/run-id-resolver.ts";
13
+ import { resolveNodeExecutable } from "../../shared/node-executable.ts";
13
14
  import { createHerdrClient, detectHerdr, type HerdrClient, type HerdrErrorCode, type HerdrResult } from "./client.ts";
15
+ import { formatShellCommand } from "./shell-command.ts";
14
16
 
15
17
  export const HERDR_INSPECTOR_ACTIONS = ["inspector.open", "inspector.status", "inspector.close"] as const;
16
18
  export type HerdrInspectorAction = typeof HERDR_INSPECTOR_ACTIONS[number];
@@ -45,6 +47,7 @@ interface InspectorDeps {
45
47
  client?: HerdrClient;
46
48
  missions?: MissionStoreConfig;
47
49
  authorityPolicy?: AuthorityPolicyConfig;
50
+ sessionRoots?: string[];
48
51
  cwd: string;
49
52
  signal?: AbortSignal;
50
53
  now?: () => Date;
@@ -84,16 +87,11 @@ function extractPaneId(value: unknown): string | undefined {
84
87
  return undefined;
85
88
  }
86
89
 
87
- function shellQuote(value: string): string {
88
- if (process.platform === "win32") return `"${value.replaceAll('"', '\\"')}"`;
89
- return `'${value.replaceAll("'", "'\\''")}'`;
90
- }
91
-
92
- function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean }): string {
93
- const args = [process.execPath, input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop)];
90
+ function inspectorCommand(input: { runnerPath: string; asyncDir: string; runId: string; index?: number; missionPath?: string; allowSteer: boolean; allowStop: boolean; sessionRoots: string[] }): string {
91
+ const args = [input.runnerPath, "--async-dir", input.asyncDir, "--run-id", input.runId, "--allow-steer", String(input.allowSteer), "--allow-stop", String(input.allowStop), "--session-roots", JSON.stringify(input.sessionRoots)];
94
92
  if (input.index !== undefined) args.push("--index", String(input.index));
95
93
  if (input.missionPath) args.push("--mission-path", input.missionPath);
96
- return `${process.platform === "win32" ? "& " : ""}${args.map(shellQuote).join(" ")}`;
94
+ return formatShellCommand(resolveNodeExecutable(), args);
97
95
  }
98
96
 
99
97
  function missionForRun(asyncDir: string, cwd: string, config: MissionStoreConfig | undefined, runId: string): { id: string; path: string } | undefined {
@@ -114,6 +112,12 @@ function pathWithin(base: string, candidate: string): boolean {
114
112
  return resolvedCandidate === resolvedBase || resolvedCandidate.startsWith(`${resolvedBase}${path.sep}`);
115
113
  }
116
114
 
115
+ function herdrSessionRoots(target: { runId: string }, deps: InspectorDeps): string[] {
116
+ const roots = deps.sessionRoots ?? deps.state?.trustedSessionRoots ?? [];
117
+ const job = deps.state?.asyncJobs.get(target.runId) ?? deps.state?.fleetJobs?.get(target.runId);
118
+ return [...new Set([...roots, ...(job?.sessionRoot ? [job.sessionRoot] : [])])];
119
+ }
120
+
117
121
  function isTrustedAsyncDir(asyncDir: string, deps: InspectorDeps): boolean {
118
122
  try {
119
123
  if (fs.lstatSync(asyncDir).isSymbolicLink() || !fs.statSync(asyncDir).isDirectory()) return false;
@@ -204,6 +208,7 @@ export async function handleHerdrInspectorAction(action: HerdrInspectorAction, p
204
208
  missionPath: mission?.path,
205
209
  allowSteer: resolveAuthorityDecision({ action: "steerRun", policy: deps.authorityPolicy }) === "auto",
206
210
  allowStop: resolveAuthorityDecision({ action: "stopRun", policy: deps.authorityPolicy }) === "auto",
211
+ sessionRoots: herdrSessionRoots(target, deps),
207
212
  });
208
213
  const started = await client.run(["pane", "run", paneId, command], { timeoutMs: 15_000, signal: deps.signal });
209
214
  if (started.ok === false) {