@gajae-code/coding-agent 0.11.0 → 0.11.2

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 (418) hide show
  1. package/CHANGELOG.md +63 -1
  2. package/dist/types/async/job-manager.d.ts +46 -7
  3. package/dist/types/cli/config-cli.d.ts +11 -1
  4. package/dist/types/cli/notify-cli.d.ts +1 -0
  5. package/dist/types/cli/session-picker.d.ts +2 -1
  6. package/dist/types/cli/setup-cli.d.ts +1 -1
  7. package/dist/types/commands/mcp-serve.d.ts +33 -0
  8. package/dist/types/commands/mcp.d.ts +1 -1
  9. package/dist/types/commands/notify.d.ts +0 -2
  10. package/dist/types/commands/sdk.d.ts +1 -1
  11. package/dist/types/config/atomic-yaml-patch.d.ts +18 -0
  12. package/dist/types/config/file-lock.d.ts +14 -4
  13. package/dist/types/config/keybindings.d.ts +42 -1
  14. package/dist/types/config/model-auth.d.ts +3 -0
  15. package/dist/types/config/model-bindings-applier.d.ts +13 -0
  16. package/dist/types/config/model-discovery-manager.d.ts +49 -0
  17. package/dist/types/config/model-equivalence.d.ts +20 -0
  18. package/dist/types/config/model-registry.d.ts +16 -15
  19. package/dist/types/config/model-resolver.d.ts +28 -18
  20. package/dist/types/config/models-config-schema.d.ts +11 -0
  21. package/dist/types/config/settings-schema.d.ts +56 -1
  22. package/dist/types/config/settings.d.ts +14 -11
  23. package/dist/types/coordinator-mcp/policy.d.ts +5 -0
  24. package/dist/types/coordinator-mcp/question-gate-codec.d.ts +129 -0
  25. package/dist/types/coordinator-mcp/question-state.d.ts +356 -0
  26. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  27. package/dist/types/discovery/helpers.d.ts +0 -6
  28. package/dist/types/edit/index.d.ts +3 -0
  29. package/dist/types/eval/py/env.d.ts +9 -0
  30. package/dist/types/extensibility/extensions/loader.d.ts +2 -1
  31. package/dist/types/extensibility/extensions/runner.d.ts +2 -2
  32. package/dist/types/extensibility/extensions/types.d.ts +34 -3
  33. package/dist/types/extensibility/hooks/runner.d.ts +1 -1
  34. package/dist/types/extensibility/shared-events.d.ts +2 -0
  35. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +10 -1
  36. package/dist/types/gjc-runtime/deep-interview-state.d.ts +44 -0
  37. package/dist/types/gjc-runtime/goal-mode-request.d.ts +11 -1
  38. package/dist/types/gjc-runtime/launch-worktree.d.ts +4 -2
  39. package/dist/types/gjc-runtime/linux-proc.d.ts +29 -0
  40. package/dist/types/gjc-runtime/session-resolution.d.ts +2 -2
  41. package/dist/types/gjc-runtime/state-migrations.d.ts +1 -1
  42. package/dist/types/gjc-runtime/state-writer.d.ts +2 -1
  43. package/dist/types/gjc-runtime/team-launch.d.ts +59 -0
  44. package/dist/types/gjc-runtime/team-notify.d.ts +45 -0
  45. package/dist/types/gjc-runtime/team-runtime.d.ts +29 -121
  46. package/dist/types/gjc-runtime/team-store.d.ts +160 -0
  47. package/dist/types/gjc-runtime/team-workers.d.ts +78 -0
  48. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +8 -0
  49. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +13 -0
  50. package/dist/types/gjc-runtime/ultragoal-evidence.d.ts +38 -0
  51. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +7 -0
  52. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +23 -0
  53. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +11 -0
  54. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +55 -33
  55. package/dist/types/gjc-runtime/workflow-cli-common.d.ts +8 -0
  56. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -0
  57. package/dist/types/goals/continuation-timeout-guard.d.ts +22 -0
  58. package/dist/types/goals/runtime.d.ts +1 -0
  59. package/dist/types/goals/state.d.ts +8 -0
  60. package/dist/types/harness-control-plane/lifecycle-reconciliation.d.ts +17 -0
  61. package/dist/types/harness-control-plane/phase-rollup.d.ts +4 -7
  62. package/dist/types/harness-control-plane/receipt-ingest.d.ts +5 -0
  63. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  64. package/dist/types/hindsight/state.d.ts +11 -4
  65. package/dist/types/lsp/lspmux.d.ts +5 -3
  66. package/dist/types/lsp/path-trust.d.ts +1 -0
  67. package/dist/types/main.d.ts +3 -2
  68. package/dist/types/memories/storage.d.ts +1 -0
  69. package/dist/types/modes/action-registry.d.ts +29 -0
  70. package/dist/types/modes/components/chrome.d.ts +14 -0
  71. package/dist/types/modes/components/command-palette.d.ts +27 -0
  72. package/dist/types/modes/components/custom-provider-wizard.d.ts +1 -0
  73. package/dist/types/modes/components/index.d.ts +2 -1
  74. package/dist/types/modes/components/pet-selector.d.ts +1 -1
  75. package/dist/types/modes/components/plan-preview-overlay.d.ts +43 -0
  76. package/dist/types/modes/components/queue-mode-selector.d.ts +1 -1
  77. package/dist/types/modes/components/queue-pane.d.ts +12 -0
  78. package/dist/types/modes/components/read-tool-group.d.ts +6 -0
  79. package/dist/types/modes/components/session-observer-overlay.d.ts +3 -4
  80. package/dist/types/modes/components/sessions-dashboard.d.ts +32 -0
  81. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  82. package/dist/types/modes/components/show-images-selector.d.ts +1 -1
  83. package/dist/types/modes/components/status-line/types.d.ts +1 -1
  84. package/dist/types/modes/components/status-line.d.ts +1 -94
  85. package/dist/types/modes/components/tasks-pane.d.ts +15 -0
  86. package/dist/types/modes/components/theme-selector.d.ts +1 -1
  87. package/dist/types/modes/components/tool-execution.d.ts +15 -0
  88. package/dist/types/modes/components/tool-status-header.d.ts +111 -0
  89. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +42 -0
  90. package/dist/types/modes/controllers/command-controller.d.ts +2 -2
  91. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -0
  92. package/dist/types/modes/controllers/goal-mode-controller.d.ts +62 -0
  93. package/dist/types/modes/controllers/input-controller.d.ts +8 -1
  94. package/dist/types/modes/controllers/mode-gate.d.ts +8 -0
  95. package/dist/types/modes/controllers/plan-mode-controller.d.ts +65 -0
  96. package/dist/types/modes/controllers/selector-controller.d.ts +15 -3
  97. package/dist/types/modes/controllers/stt-controller.d.ts +26 -0
  98. package/dist/types/modes/interactive-mode.d.ts +21 -19
  99. package/dist/types/modes/jobs-observer.d.ts +2 -0
  100. package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +4 -11
  101. package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +21 -2
  102. package/dist/types/modes/shared/agent-wire/workflow-gate-types.d.ts +31 -0
  103. package/dist/types/modes/tasks-aggregator.d.ts +39 -0
  104. package/dist/types/modes/transcript-item-registry.d.ts +60 -0
  105. package/dist/types/modes/types.d.ts +22 -15
  106. package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
  107. package/dist/types/runtime-mcp/manager.d.ts +1 -1
  108. package/dist/types/sdk/broker/broker.d.ts +78 -12
  109. package/dist/types/sdk/broker/ensure.d.ts +9 -0
  110. package/dist/types/sdk/broker/lifecycle-codec.d.ts +4 -0
  111. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +15 -1
  112. package/dist/types/sdk/broker/lifecycle.d.ts +6 -2
  113. package/dist/types/sdk/broker/process-incarnation.d.ts +2 -0
  114. package/dist/types/sdk/broker/session-index.d.ts +0 -1
  115. package/dist/types/sdk/broker/state-version.d.ts +2 -0
  116. package/dist/types/sdk/bus/chat-effect-journal.d.ts +1 -0
  117. package/dist/types/sdk/bus/config-commands.d.ts +1 -1
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
  120. package/dist/types/sdk/bus/discord-daemon.d.ts +5 -0
  121. package/dist/types/sdk/bus/html-format.d.ts +3 -2
  122. package/dist/types/sdk/bus/index.d.ts +27 -0
  123. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  124. package/dist/types/sdk/bus/notification-service.d.ts +20 -0
  125. package/dist/types/sdk/bus/rate-limit-pool.d.ts +13 -2
  126. package/dist/types/sdk/bus/recent-activity.d.ts +21 -12
  127. package/dist/types/sdk/bus/rich-document.d.ts +7 -0
  128. package/dist/types/sdk/bus/rich-render.d.ts +18 -41
  129. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -4
  130. package/dist/types/sdk/bus/telegram-daemon.d.ts +69 -3
  131. package/dist/types/sdk/bus/threaded-inbound.d.ts +1 -1
  132. package/dist/types/sdk/bus/threaded-render.d.ts +13 -0
  133. package/dist/types/sdk/bus/topic-registry.d.ts +24 -2
  134. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  135. package/dist/types/sdk/host/host.d.ts +2 -0
  136. package/dist/types/sdk/index.d.ts +1 -0
  137. package/dist/types/sdk/session-directory.d.ts +56 -0
  138. package/dist/types/session/agent-session.d.ts +82 -47
  139. package/dist/types/session/agent-storage.d.ts +7 -0
  140. package/dist/types/session/fallback-chain-controller.d.ts +10 -2
  141. package/dist/types/session/internal/managed-session-scope.d.ts +102 -0
  142. package/dist/types/session/internal/managed-session-storage.d.ts +38 -0
  143. package/dist/types/session/messages.d.ts +16 -0
  144. package/dist/types/session/session-manager-internal.d.ts +14 -0
  145. package/dist/types/session/session-manager.d.ts +82 -12
  146. package/dist/types/session/session-storage.d.ts +43 -3
  147. package/dist/types/session/volatile-context-pruning.d.ts +15 -0
  148. package/dist/types/skill-state/{deep-interview-mutation-guard.d.ts → workflow-mutation-guard.d.ts} +24 -8
  149. package/dist/types/skill-state/workflow-state-contract.d.ts +1 -3
  150. package/dist/types/slash-commands/builtin-registry.d.ts +4 -1
  151. package/dist/types/system-prompt.d.ts +2 -11
  152. package/dist/types/task/executor.d.ts +3 -1
  153. package/dist/types/thinking.d.ts +7 -0
  154. package/dist/types/tool-discovery/tool-index.d.ts +1 -50
  155. package/dist/types/tools/ask.d.ts +64 -2
  156. package/dist/types/tools/bash.d.ts +3 -0
  157. package/dist/types/tools/browser/dead-tab-recovery.d.ts +24 -0
  158. package/dist/types/tools/browser/tab-supervisor.d.ts +21 -0
  159. package/dist/types/tools/cron.d.ts +2 -0
  160. package/dist/types/tools/fetch.d.ts +1 -0
  161. package/dist/types/tools/index.d.ts +30 -15
  162. package/dist/types/tools/output-meta.d.ts +1 -0
  163. package/dist/types/tools/read.d.ts +3 -0
  164. package/dist/types/tools/search-tool-bm25.d.ts +3 -4
  165. package/dist/types/tools/subagent-render.d.ts +1 -0
  166. package/dist/types/tools/subagent.d.ts +6 -1
  167. package/dist/types/utils/event-bus.d.ts +1 -1
  168. package/dist/types/web/search/index.d.ts +3 -1
  169. package/package.json +21 -9
  170. package/scripts/build-sdk-package-smoke.ts +39 -1
  171. package/scripts/generate-hotkeys-docs.ts +74 -0
  172. package/scripts/generate-sdk-operation-inventory.ts +64 -20
  173. package/scripts/verify-gjc-sdk-canonicalization.ts +182 -2
  174. package/src/async/job-manager.ts +455 -69
  175. package/src/cli/config-cli.ts +75 -2
  176. package/src/cli/notify-cli.ts +92 -26
  177. package/src/cli/session-picker.ts +20 -3
  178. package/src/cli/setup-cli.ts +1 -1
  179. package/src/cli.ts +1 -1
  180. package/src/commands/config.ts +1 -1
  181. package/src/commands/harness.ts +34 -106
  182. package/src/commands/launch.ts +6 -4
  183. package/src/commands/mcp-serve.ts +85 -8
  184. package/src/commands/mcp.ts +2 -2
  185. package/src/commands/notify.ts +8 -4
  186. package/src/commands/sdk.ts +61 -40
  187. package/src/commands/ultragoal.ts +4 -1
  188. package/src/config/atomic-yaml-patch.ts +80 -13
  189. package/src/config/file-lock.ts +105 -43
  190. package/src/config/keybindings.ts +64 -20
  191. package/src/config/model-auth.ts +9 -0
  192. package/src/config/model-bindings-applier.ts +139 -0
  193. package/src/config/model-discovery-manager.ts +224 -0
  194. package/src/config/model-equivalence.ts +66 -0
  195. package/src/config/model-registry.ts +169 -302
  196. package/src/config/model-resolver.ts +361 -369
  197. package/src/config/models-config-schema.ts +5 -4
  198. package/src/config/settings-schema.ts +159 -2
  199. package/src/config/settings.ts +458 -482
  200. package/src/coordinator-mcp/policy.ts +76 -11
  201. package/src/coordinator-mcp/question-gate-codec.ts +274 -0
  202. package/src/coordinator-mcp/question-state.ts +778 -0
  203. package/src/coordinator-mcp/server.ts +1702 -238
  204. package/src/debug/index.ts +1 -1
  205. package/src/defaults/gjc/skills/deep-interview/SKILL.md +13 -1
  206. package/src/defaults/gjc/skills/team/SKILL.md +16 -0
  207. package/src/discovery/helpers.ts +1 -50
  208. package/src/edit/index.ts +64 -0
  209. package/src/eval/py/env.ts +26 -0
  210. package/src/eval/py/kernel.ts +5 -3
  211. package/src/extensibility/extensions/loader.ts +9 -1
  212. package/src/extensibility/extensions/runner.ts +12 -3
  213. package/src/extensibility/extensions/types.ts +39 -1
  214. package/src/extensibility/gjc-plugins/installer.ts +16 -16
  215. package/src/extensibility/hooks/runner.ts +2 -2
  216. package/src/extensibility/runtime-skill-discovery.ts +33 -13
  217. package/src/extensibility/shared-events.ts +2 -0
  218. package/src/gjc-runtime/deep-interview-recorder.ts +63 -3
  219. package/src/gjc-runtime/deep-interview-runtime.ts +76 -27
  220. package/src/gjc-runtime/deep-interview-state.ts +317 -5
  221. package/src/gjc-runtime/goal-mode-request.ts +55 -8
  222. package/src/gjc-runtime/launch-tmux.ts +2 -5
  223. package/src/gjc-runtime/launch-worktree.ts +5 -5
  224. package/src/gjc-runtime/linux-proc.ts +71 -0
  225. package/src/gjc-runtime/ralplan-runtime.ts +74 -90
  226. package/src/gjc-runtime/session-resolution.ts +18 -3
  227. package/src/gjc-runtime/session-state-sidecar.ts +6 -107
  228. package/src/gjc-runtime/state-migrations.ts +1 -1
  229. package/src/gjc-runtime/state-runtime.ts +320 -249
  230. package/src/gjc-runtime/state-writer.ts +36 -40
  231. package/src/gjc-runtime/team-launch.ts +319 -0
  232. package/src/gjc-runtime/team-notify.ts +326 -0
  233. package/src/gjc-runtime/team-runtime.ts +1099 -1482
  234. package/src/gjc-runtime/team-store.ts +828 -0
  235. package/src/gjc-runtime/team-workers.ts +962 -0
  236. package/src/gjc-runtime/tmux-owner-isolation-cli.ts +2 -11
  237. package/src/gjc-runtime/tmux-owner-isolation.ts +14 -9
  238. package/src/gjc-runtime/tmux-sessions.ts +4 -21
  239. package/src/gjc-runtime/ultragoal-change-set.ts +172 -0
  240. package/src/gjc-runtime/ultragoal-evidence.ts +874 -0
  241. package/src/gjc-runtime/ultragoal-guard.ts +83 -12
  242. package/src/gjc-runtime/ultragoal-pipeline.ts +306 -0
  243. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +102 -1
  244. package/src/gjc-runtime/ultragoal-runtime.ts +156 -1319
  245. package/src/gjc-runtime/workflow-cli-common.ts +30 -0
  246. package/src/gjc-runtime/workflow-manifest.generated.json +58 -0
  247. package/src/gjc-runtime/workflow-manifest.ts +18 -0
  248. package/src/goals/continuation-timeout-guard.ts +79 -0
  249. package/src/goals/runtime.ts +7 -5
  250. package/src/goals/state.ts +18 -0
  251. package/src/harness-control-plane/lifecycle-reconciliation.ts +64 -0
  252. package/src/harness-control-plane/phase-rollup.ts +4 -7
  253. package/src/harness-control-plane/receipt-ingest.ts +5 -0
  254. package/src/harness-control-plane/receipt-spool.ts +51 -7
  255. package/src/harness-control-plane/session-lease.ts +12 -130
  256. package/src/hindsight/backend.ts +26 -27
  257. package/src/hindsight/state.ts +101 -32
  258. package/src/hooks/skill-state.ts +17 -11
  259. package/src/internal-urls/docs-index.generated.ts +21 -18
  260. package/src/lsp/client.ts +1 -1
  261. package/src/lsp/config.ts +145 -26
  262. package/src/lsp/index.ts +1 -1
  263. package/src/lsp/lspmux.ts +39 -10
  264. package/src/lsp/path-trust.ts +81 -0
  265. package/src/main.ts +28 -8
  266. package/src/memories/index.ts +1 -0
  267. package/src/memories/storage.ts +6 -2
  268. package/src/modes/action-registry.ts +165 -0
  269. package/src/modes/components/chrome.ts +40 -0
  270. package/src/modes/components/command-palette.ts +168 -0
  271. package/src/modes/components/custom-editor.ts +1 -1
  272. package/src/modes/components/custom-provider-wizard.ts +51 -14
  273. package/src/modes/components/index.ts +2 -1
  274. package/src/modes/components/pet-selector.ts +11 -17
  275. package/src/modes/components/plan-preview-overlay.ts +295 -0
  276. package/src/modes/components/queue-mode-selector.ts +10 -27
  277. package/src/modes/components/queue-pane.ts +79 -0
  278. package/src/modes/components/read-tool-group.ts +13 -0
  279. package/src/modes/components/session-observer-overlay.ts +207 -772
  280. package/src/modes/components/sessions-dashboard.ts +181 -0
  281. package/src/modes/components/settings-selector.ts +1 -1
  282. package/src/modes/components/show-images-selector.ts +10 -24
  283. package/src/modes/components/status-line/types.ts +1 -1
  284. package/src/modes/components/status-line.ts +1 -969
  285. package/src/modes/components/tasks-pane.ts +73 -0
  286. package/src/modes/components/theme-selector.ts +11 -31
  287. package/src/modes/components/tool-execution.ts +22 -0
  288. package/src/modes/components/tool-status-header.ts +1056 -0
  289. package/src/modes/components/transcript-viewer-overlay.ts +384 -0
  290. package/src/modes/controllers/btw-controller.ts +1 -0
  291. package/src/modes/controllers/command-controller.ts +9 -15
  292. package/src/modes/controllers/event-controller.ts +3 -2
  293. package/src/modes/controllers/extension-ui-controller.ts +72 -25
  294. package/src/modes/controllers/goal-mode-controller.ts +474 -0
  295. package/src/modes/controllers/input-controller.ts +764 -229
  296. package/src/modes/controllers/mode-gate.ts +24 -0
  297. package/src/modes/controllers/plan-mode-controller.ts +517 -0
  298. package/src/modes/controllers/selector-controller.ts +225 -10
  299. package/src/modes/controllers/stt-controller.ts +125 -0
  300. package/src/modes/interactive-mode.ts +302 -1151
  301. package/src/modes/jobs-observer.ts +3 -0
  302. package/src/modes/runtime-init.ts +8 -0
  303. package/src/modes/shared/agent-wire/deep-interview-gate.ts +31 -106
  304. package/src/modes/shared/agent-wire/workflow-gate-broker.ts +78 -19
  305. package/src/modes/shared/agent-wire/workflow-gate-types.ts +177 -0
  306. package/src/modes/tasks-aggregator.ts +199 -0
  307. package/src/modes/transcript-item-registry.ts +229 -0
  308. package/src/modes/types.ts +47 -20
  309. package/src/modes/utils/ui-helpers.ts +17 -9
  310. package/src/prompts/agent-fragments/ralplan-persistence.md +6 -0
  311. package/src/prompts/agent-fragments/restricted-bash.md +1 -0
  312. package/src/prompts/agents/architect.md +2 -8
  313. package/src/prompts/agents/critic.md +2 -8
  314. package/src/prompts/agents/executor.md +3 -0
  315. package/src/prompts/agents/planner.md +4 -12
  316. package/src/prompts/system/custom-system-prompt.md +1 -23
  317. package/src/prompts/system/plan-mode-approved.md +8 -0
  318. package/src/prompts/system/system-prompt.md +17 -96
  319. package/src/prompts/tools/bash.md +4 -4
  320. package/src/prompts/tools/browser.md +15 -52
  321. package/src/prompts/tools/find.md +2 -2
  322. package/src/prompts/tools/hashline.md +13 -55
  323. package/src/prompts/tools/read.md +3 -6
  324. package/src/prompts/tools/search-tool-bm25.md +0 -2
  325. package/src/prompts/tools/search.md +2 -4
  326. package/src/prompts/tools/skill-discovery.md +1 -2
  327. package/src/prompts/tools/task.md +1 -1
  328. package/src/runtime-mcp/manager.ts +1 -1
  329. package/src/sdk/broker/broker.ts +206 -78
  330. package/src/sdk/broker/discovery.ts +21 -2
  331. package/src/sdk/broker/ensure.ts +85 -25
  332. package/src/sdk/broker/lifecycle-codec.ts +11 -0
  333. package/src/sdk/broker/lifecycle-ledger.ts +371 -74
  334. package/src/sdk/broker/lifecycle.ts +1399 -142
  335. package/src/sdk/broker/process-incarnation.ts +5 -13
  336. package/src/sdk/broker/session-index.ts +145 -56
  337. package/src/sdk/broker/state-version.ts +21 -0
  338. package/src/sdk/broker/transport.ts +1 -1
  339. package/src/sdk/bus/chat-daemon-cli.ts +2 -0
  340. package/src/sdk/bus/chat-daemon-control.ts +8 -28
  341. package/src/sdk/bus/chat-effect-journal.ts +14 -4
  342. package/src/sdk/bus/config-commands.ts +5 -4
  343. package/src/sdk/bus/config.ts +98 -0
  344. package/src/sdk/bus/conversation-store.ts +5 -29
  345. package/src/sdk/bus/daemon-paths.ts +2 -0
  346. package/src/sdk/bus/discord-daemon.ts +25 -8
  347. package/src/sdk/bus/html-format.ts +20 -3
  348. package/src/sdk/bus/index.ts +834 -113
  349. package/src/sdk/bus/lifecycle-control-runtime.ts +91 -33
  350. package/src/sdk/bus/native-runtime-compatibility.ts +1 -1
  351. package/src/sdk/bus/natives-augment.d.ts +26 -1
  352. package/src/sdk/bus/notification-service.ts +168 -20
  353. package/src/sdk/bus/rate-limit-pool.ts +58 -14
  354. package/src/sdk/bus/recent-activity.ts +188 -74
  355. package/src/sdk/bus/rich-document.ts +258 -0
  356. package/src/sdk/bus/rich-render.ts +62 -70
  357. package/src/sdk/bus/telegram-daemon-cli.ts +15 -59
  358. package/src/sdk/bus/telegram-daemon-contract.ts +9 -4
  359. package/src/sdk/bus/telegram-daemon.ts +1491 -247
  360. package/src/sdk/bus/threaded-inbound.ts +8 -3
  361. package/src/sdk/bus/threaded-render.ts +57 -1
  362. package/src/sdk/bus/topic-registry.ts +120 -13
  363. package/src/sdk/host/control/dispatch.ts +14 -2
  364. package/src/sdk/host/control/operations.ts +6 -1
  365. package/src/sdk/host/host.ts +31 -1
  366. package/src/sdk/host/query/handlers.ts +20 -22
  367. package/src/sdk/host/query/revision-store.ts +80 -57
  368. package/src/sdk/index.ts +1 -0
  369. package/src/sdk/protocol/operation-inventory.generated.json +943 -1907
  370. package/src/sdk/session-directory.ts +141 -0
  371. package/src/sdk/session.ts +458 -479
  372. package/src/session/agent-session.ts +1901 -637
  373. package/src/session/agent-storage.ts +18 -0
  374. package/src/session/cache-economics.ts +25 -8
  375. package/src/session/fallback-chain-controller.ts +53 -3
  376. package/src/session/internal/managed-session-scope.ts +2226 -0
  377. package/src/session/internal/managed-session-storage.ts +453 -0
  378. package/src/session/messages.ts +91 -2
  379. package/src/session/session-manager-internal.ts +33 -0
  380. package/src/session/session-manager.ts +1112 -333
  381. package/src/session/session-storage.ts +444 -39
  382. package/src/session/volatile-context-pruning.ts +78 -0
  383. package/src/setup/credential-auto-import.ts +2 -1
  384. package/src/setup/hermes/templates/operator-instructions.v1.md +1 -1
  385. package/src/setup/provider-onboarding.ts +36 -61
  386. package/src/setup/provider-presets.json +5 -0
  387. package/src/skill-state/active-state.ts +3 -13
  388. package/src/skill-state/{deep-interview-mutation-guard.ts → workflow-mutation-guard.ts} +47 -29
  389. package/src/skill-state/workflow-state-contract.ts +6 -41
  390. package/src/slash-commands/builtin-registry.ts +46 -11
  391. package/src/system-prompt.ts +37 -44
  392. package/src/task/agents.ts +19 -1
  393. package/src/task/executor.ts +90 -6
  394. package/src/task/index.ts +62 -37
  395. package/src/thinking.ts +21 -6
  396. package/src/tool-discovery/tool-index.ts +2 -134
  397. package/src/tools/ask.ts +157 -21
  398. package/src/tools/ast-edit.ts +1 -1
  399. package/src/tools/bash.ts +38 -0
  400. package/src/tools/browser/dead-tab-recovery.ts +78 -0
  401. package/src/tools/browser/tab-supervisor.ts +282 -36
  402. package/src/tools/computer.ts +10 -3
  403. package/src/tools/cron.ts +30 -16
  404. package/src/tools/eval.ts +1 -2
  405. package/src/tools/fetch.ts +11 -4
  406. package/src/tools/index.ts +76 -24
  407. package/src/tools/output-meta.ts +13 -5
  408. package/src/tools/read.ts +37 -1
  409. package/src/tools/resource-gc.ts +2 -2
  410. package/src/tools/search-tool-bm25.ts +11 -74
  411. package/src/tools/subagent-render.ts +19 -8
  412. package/src/tools/subagent.ts +84 -32
  413. package/src/utils/event-bus.ts +9 -4
  414. package/src/web/search/index.ts +3 -3
  415. package/dist/types/discovery/plugin-dir-roots.d.ts +0 -15
  416. package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +0 -7
  417. package/src/discovery/plugin-dir-roots.ts +0 -28
  418. package/src/runtime-mcp/discoverable-tool-metadata.ts +0 -25
@@ -12,6 +12,7 @@ const DELIVERY_MAX_TEXT_BYTES = 64 * 1024;
12
12
  const DELIVERY_PREVIEW_HEAD_BYTES = 32 * 1024;
13
13
  const DELIVERY_PREVIEW_TAIL_BYTES = 32 * 1024;
14
14
  const DELIVERY_MAX_ATTEMPTS = 3;
15
+ const MAX_DEAD_LETTERED_DELIVERIES = 50;
15
16
 
16
17
  export interface AsyncJob {
17
18
  id: string;
@@ -75,6 +76,47 @@ export type SubagentRunOutcome = { kind: "completed"; text: string } | { kind: "
75
76
  /** Canonical lifecycle of a subagent across pause/resume cycles. */
76
77
  export type SubagentLifecycle = "running" | "paused" | "queued" | "completed" | "failed" | "cancelled";
77
78
 
79
+ /** Maximum time allowed to prove owned subagents have stopped before replacement. */
80
+ export const OWNER_SUBAGENT_SHUTDOWN_TIMEOUT_MS = 5_000;
81
+
82
+ export class OwnerSubagentShutdownError extends Error {
83
+ readonly code = "owner_shutdown_in_progress";
84
+
85
+ constructor() {
86
+ super("Cannot start subagent while owner shutdown is in progress.");
87
+ this.name = "OwnerSubagentShutdownError";
88
+ }
89
+ }
90
+
91
+ export interface OwnerSubagentShutdownTarget {
92
+ subagentId: string;
93
+ jobId: string | null;
94
+ source: "record" | "metadata_job";
95
+ }
96
+
97
+ export interface OwnerSubagentShutdownLease {
98
+ ownerId: string;
99
+ id: string;
100
+ targets: readonly OwnerSubagentShutdownTarget[];
101
+ }
102
+
103
+ export interface OwnerSubagentShutdownProof {
104
+ ownerId: string;
105
+ leaseId: string;
106
+ confirmed: boolean;
107
+ reason: "confirmed" | "deadline_exceeded" | "missing_terminal_evidence" | "lease_lost";
108
+ targets: readonly OwnerSubagentShutdownTarget[];
109
+ terminalIds: readonly string[];
110
+ unresolvedIds: readonly string[];
111
+ }
112
+
113
+ interface OwnerSubagentShutdownLeaseState {
114
+ lease: OwnerSubagentShutdownLease;
115
+ backingJobIds: ReadonlyMap<string, readonly string[]>;
116
+ phase: "active" | "proving" | "proved";
117
+ proof?: OwnerSubagentShutdownProof;
118
+ }
119
+
78
120
  /**
79
121
  * Live, executor-owned control handle for a RUNNING subagent. Registered when a
80
122
  * subagent run starts and removed on pause/terminal so a paused subagent retains
@@ -122,6 +164,12 @@ export interface ResumeDescriptor {
122
164
  data: unknown;
123
165
  }
124
166
 
167
+ /**
168
+ * In-memory resume runner bound to the session that originally launched a
169
+ * subagent. Never serialized: process restart drops it so resume fails closed.
170
+ */
171
+ export type ResumeRunner = (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined;
172
+
125
173
  function sessionFileFromResumeDescriptorData(data: unknown): string | null {
126
174
  if (typeof data !== "object" || data === null) return null;
127
175
  const sessionFile = (data as { sessionFile?: unknown }).sessionFile;
@@ -160,6 +208,12 @@ interface AsyncJobDelivery {
160
208
  promise?: Promise<void>;
161
209
  }
162
210
 
211
+ interface DeadLetteredDelivery {
212
+ jobId: string;
213
+ attempt: number;
214
+ lastError?: string;
215
+ }
216
+
163
217
  export interface AsyncJobDeliveryState {
164
218
  queued: number;
165
219
  delivering: boolean;
@@ -324,9 +378,20 @@ export class AsyncJobManager {
324
378
  readonly #subagentProgress = new Map<string, AgentProgress>();
325
379
  readonly #resumeQueue: ResumeQueueEntry[] = [];
326
380
  #resumeSeq = 0;
327
- #resumeRunner?: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined;
381
+ #resumeRunner?: ResumeRunner;
328
382
  readonly #resumeDescriptors = new Map<string, ResumeDescriptor>();
329
- readonly #deadLetteredDeliveries = new Map<string, AsyncJobDelivery>();
383
+ /**
384
+ * Per-descriptor in-memory resume runners, keyed by subagentId, captured at
385
+ * registerResumeDescriptor time so each resume executes under the authority of
386
+ * the session that originally launched that subagent. Fixes #2303's global
387
+ * last-writer-wins slot. In-memory only: a process restart drops these and
388
+ * resume fails closed with reason "no_runner".
389
+ */
390
+ readonly #descriptorResumeRunners = new Map<string, ResumeRunner>();
391
+ readonly #deadLetteredDeliveries = new Map<string, DeadLetteredDelivery>();
392
+ readonly #deadLetteredDeliveryOwners = new Map<string, string | undefined>();
393
+ readonly #ownerSubagentShutdownLeases = new Map<string, OwnerSubagentShutdownLeaseState>();
394
+ #ownerSubagentShutdownSeq = 0;
330
395
  #lastDisposeDiagnostics: AsyncJobDisposeDiagnostics = { stuckJobIds: [], deliveriesDrained: true };
331
396
  /**
332
397
  * Change listeners notified on any mutation that can alter the live job set
@@ -387,6 +452,9 @@ export class AsyncJobManager {
387
452
  if (this.#disposed) {
388
453
  throw new Error("Async job manager is disposed");
389
454
  }
455
+ if (options?.ownerId && this.#isOwnerSubagentShutdownFenced(options.ownerId)) {
456
+ throw new OwnerSubagentShutdownError();
457
+ }
390
458
  const runningCount = this.getRunningJobs().length;
391
459
  if (runningCount >= this.#maxRunningJobs) {
392
460
  throw new Error(
@@ -687,14 +755,23 @@ export class AsyncJobManager {
687
755
  }
688
756
 
689
757
  /** Install the TaskTool-owned resume runner. Returns the new job id, or undefined on failure. */
690
- setResumeRunner(
691
- runner: (subagentId: string, message?: string, descriptor?: ResumeDescriptor) => string | undefined,
692
- ): void {
758
+ setResumeRunner(runner: ResumeRunner): void {
693
759
  this.#resumeRunner = runner;
694
760
  }
695
761
 
696
- registerResumeDescriptor(descriptor: ResumeDescriptor): void {
762
+ registerResumeDescriptor(descriptor: ResumeDescriptor, runner?: ResumeRunner): void {
697
763
  this.#resumeDescriptors.set(descriptor.subagentId, descriptor);
764
+ if (runner) this.#descriptorResumeRunners.set(descriptor.subagentId, runner);
765
+ }
766
+
767
+ /**
768
+ * Resolve the resume runner for a subagent: prefer the per-descriptor runner
769
+ * captured at registration time (the originating parent's execution authority),
770
+ * falling back to the process-global runner only for descriptors registered
771
+ * without one.
772
+ */
773
+ #resolveResumeRunner(subagentId: string): ResumeRunner | undefined {
774
+ return this.#descriptorResumeRunners.get(subagentId) ?? this.#resumeRunner;
698
775
  }
699
776
 
700
777
  getResumeDescriptor(subagentId: string, filter?: AsyncJobFilter): ResumeDescriptor | undefined {
@@ -704,6 +781,224 @@ export class AsyncJobManager {
704
781
  return descriptor;
705
782
  }
706
783
 
784
+ #isOwnerSubagentShutdownFenced(ownerId: string | undefined): boolean {
785
+ return ownerId !== undefined && this.#ownerSubagentShutdownLeases.has(ownerId);
786
+ }
787
+
788
+ #isTerminalSubagentStatus(status: SubagentLifecycle): boolean {
789
+ return status === "completed" || status === "failed" || status === "cancelled";
790
+ }
791
+
792
+ beginOwnerSubagentShutdown(ownerId: string): OwnerSubagentShutdownLease | undefined {
793
+ if (!ownerId || this.#ownerSubagentShutdownLeases.has(ownerId)) return undefined;
794
+ const targets = new Map<string, OwnerSubagentShutdownTarget>();
795
+ const backingJobIds = new Map<string, Set<string>>();
796
+ const addBackingJob = (subagentId: string, jobId: string | null): void => {
797
+ if (!jobId) return;
798
+ const ids = backingJobIds.get(subagentId) ?? new Set<string>();
799
+ ids.add(jobId);
800
+ backingJobIds.set(subagentId, ids);
801
+ };
802
+ for (const record of this.#subagentRecords.values()) {
803
+ if (record.ownerId !== ownerId || this.#isTerminalSubagentStatus(record.status)) continue;
804
+ targets.set(record.subagentId, {
805
+ subagentId: record.subagentId,
806
+ jobId: record.status === "queued" ? null : record.currentJobId,
807
+ source: "record",
808
+ });
809
+ if (record.status !== "queued") addBackingJob(record.subagentId, record.currentJobId);
810
+ }
811
+ for (const job of this.#jobs.values()) {
812
+ const subagentId = job.metadata?.subagent?.id;
813
+ if (
814
+ job.ownerId !== ownerId ||
815
+ !subagentId ||
816
+ (job.status !== "running" && job.status !== "paused" && job.status !== "cancelled")
817
+ ) {
818
+ continue;
819
+ }
820
+ if (!targets.has(subagentId)) {
821
+ targets.set(subagentId, { subagentId, jobId: job.id, source: "metadata_job" });
822
+ }
823
+ addBackingJob(subagentId, job.id);
824
+ }
825
+ const lease: OwnerSubagentShutdownLease = {
826
+ ownerId,
827
+ id: `owner_shutdown_${++this.#ownerSubagentShutdownSeq}`,
828
+ targets: Array.from(targets.values()),
829
+ };
830
+ this.#ownerSubagentShutdownLeases.set(ownerId, {
831
+ lease,
832
+ backingJobIds: new Map<string, readonly string[]>(
833
+ Array.from(backingJobIds, ([subagentId, jobIds]): [string, readonly string[]] => [
834
+ subagentId,
835
+ Array.from(jobIds),
836
+ ]),
837
+ ),
838
+ phase: "active",
839
+ });
840
+ return lease;
841
+ }
842
+
843
+ runOwnerProducerCleanups(filter?: AsyncJobFilter): void {
844
+ this.#runOwnerProducerCleanups(filter, false);
845
+ }
846
+
847
+ runOwnerProducerCleanupsStrict(filter?: AsyncJobFilter): void {
848
+ this.#runOwnerProducerCleanups(filter, true);
849
+ }
850
+
851
+ #runOwnerProducerCleanups(filter: AsyncJobFilter | undefined, strict: boolean): void {
852
+ const ownerId = filter?.ownerId;
853
+ const targets: Array<[string, Set<() => void>]> = [];
854
+ if (ownerId) {
855
+ const bag = this.#ownerCleanups.get(ownerId);
856
+ if (bag) targets.push([ownerId, bag]);
857
+ } else {
858
+ for (const entry of this.#ownerCleanups.entries()) targets.push(entry);
859
+ }
860
+ const errors: unknown[] = [];
861
+ for (const [id, bag] of targets) {
862
+ const callbacks = Array.from(bag);
863
+ bag.clear();
864
+ this.#ownerCleanups.delete(id);
865
+ for (const cleanup of callbacks) {
866
+ try {
867
+ cleanup();
868
+ } catch (error) {
869
+ errors.push(error);
870
+ if (strict) {
871
+ let retryBag = this.#ownerCleanups.get(id);
872
+ if (!retryBag) {
873
+ retryBag = new Set();
874
+ this.#ownerCleanups.set(id, retryBag);
875
+ }
876
+ retryBag.add(cleanup);
877
+ }
878
+ logger.warn("Async job owner cleanup failed", {
879
+ ownerId: id,
880
+ error: error instanceof Error ? error.message : String(error),
881
+ });
882
+ }
883
+ }
884
+ }
885
+ if (strict && errors.length > 0) throw new AggregateError(errors, "Async job owner cleanup failed");
886
+ }
887
+
888
+ async cancelAndProveOwnerSubagents(
889
+ lease: OwnerSubagentShutdownLease,
890
+ options?: { timeoutMs?: number },
891
+ ): Promise<OwnerSubagentShutdownProof> {
892
+ const state = this.#ownerSubagentShutdownLeases.get(lease.ownerId);
893
+ if (!state || state.lease.id !== lease.id) {
894
+ return this.#ownerSubagentShutdownProof(
895
+ lease,
896
+ "lease_lost",
897
+ lease.targets.map(target => target.subagentId),
898
+ );
899
+ }
900
+ if (state.phase === "proved" && state.proof) return state.proof;
901
+ state.phase = "proving";
902
+ const settled = new Set<string>();
903
+ const promises: Promise<void>[] = [];
904
+ for (const target of lease.targets) {
905
+ const backingJobs = (state.backingJobIds.get(target.subagentId) ?? []).map(jobId => ({
906
+ jobId,
907
+ job: this.#jobs.get(jobId),
908
+ }));
909
+ if (target.source === "record") this.cancelSubagent(target.subagentId, { ownerId: lease.ownerId });
910
+ for (const { jobId, job } of backingJobs) {
911
+ this.cancel(jobId, { ownerId: lease.ownerId });
912
+ if (!job || job.ownerId !== lease.ownerId) continue;
913
+ promises.push(
914
+ job.promise.then(
915
+ () => {
916
+ settled.add(jobId);
917
+ },
918
+ () => {
919
+ settled.add(jobId);
920
+ },
921
+ ),
922
+ );
923
+ }
924
+ }
925
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? OWNER_SUBAGENT_SHUTDOWN_TIMEOUT_MS);
926
+ let deadlineExceeded = false;
927
+ await Promise.race([
928
+ Promise.allSettled(promises),
929
+ Bun.sleep(timeoutMs).then(() => {
930
+ deadlineExceeded = true;
931
+ }),
932
+ ]);
933
+ const current = this.#ownerSubagentShutdownLeases.get(lease.ownerId);
934
+ if (!current || current.lease.id !== lease.id || current.phase !== "proving") {
935
+ return this.#ownerSubagentShutdownProof(
936
+ lease,
937
+ "lease_lost",
938
+ lease.targets.map(target => target.subagentId),
939
+ );
940
+ }
941
+ const unresolvedIds = lease.targets
942
+ .filter(target => !this.#hasTerminalShutdownEvidence(target, lease.ownerId, current.backingJobIds, settled))
943
+ .map(target => target.subagentId);
944
+ const reason =
945
+ unresolvedIds.length === 0
946
+ ? "confirmed"
947
+ : deadlineExceeded
948
+ ? "deadline_exceeded"
949
+ : "missing_terminal_evidence";
950
+ const proof = this.#ownerSubagentShutdownProof(lease, reason, unresolvedIds);
951
+ current.phase = "proved";
952
+ current.proof = proof;
953
+ return proof;
954
+ }
955
+
956
+ #hasTerminalShutdownEvidence(
957
+ target: OwnerSubagentShutdownTarget,
958
+ ownerId: string,
959
+ backingJobIds: ReadonlyMap<string, readonly string[]>,
960
+ settled: ReadonlySet<string>,
961
+ ): boolean {
962
+ const record = this.#subagentRecords.get(target.subagentId);
963
+ if (!record || record.ownerId !== ownerId || !this.#isTerminalSubagentStatus(record.status)) return false;
964
+ return (backingJobIds.get(target.subagentId) ?? []).every(jobId => {
965
+ if (!settled.has(jobId)) return false;
966
+ const job = this.#jobs.get(jobId);
967
+ return job === undefined || job.ownerId === ownerId;
968
+ });
969
+ }
970
+
971
+ #ownerSubagentShutdownProof(
972
+ lease: OwnerSubagentShutdownLease,
973
+ reason: OwnerSubagentShutdownProof["reason"],
974
+ unresolvedIds: readonly string[],
975
+ ): OwnerSubagentShutdownProof {
976
+ const unresolved = new Set(unresolvedIds);
977
+ return {
978
+ ownerId: lease.ownerId,
979
+ leaseId: lease.id,
980
+ confirmed: reason === "confirmed",
981
+ reason,
982
+ targets: lease.targets,
983
+ terminalIds: lease.targets
984
+ .filter(target => !unresolved.has(target.subagentId))
985
+ .map(target => target.subagentId),
986
+ unresolvedIds: [...unresolvedIds],
987
+ };
988
+ }
989
+
990
+ finishOwnerSubagentShutdown(lease: OwnerSubagentShutdownLease, outcome: "commit" | "release"): void {
991
+ const state = this.#ownerSubagentShutdownLeases.get(lease.ownerId);
992
+ if (!state || state.lease.id !== lease.id) return;
993
+ if (outcome === "commit" && state.proof?.confirmed) {
994
+ const pendingJobIds = this.getDeliveryState({ ownerId: lease.ownerId }).pendingJobIds;
995
+ this.acknowledgeDeliveries(pendingJobIds);
996
+ this.#purgeOwnerSubagentState(lease.ownerId);
997
+ }
998
+ this.#ownerSubagentShutdownLeases.delete(lease.ownerId);
999
+ if (outcome === "release") this.#ensureDeliveryLoop();
1000
+ }
1001
+
707
1002
  #recordByJobId(jobId: string): SubagentRecord | undefined {
708
1003
  for (const rec of this.#subagentRecords.values()) {
709
1004
  if (rec.currentJobId === jobId) return rec;
@@ -758,6 +1053,9 @@ export class AsyncJobManager {
758
1053
  ): { ok: boolean; status?: SubagentLifecycle; jobId?: string; queued?: boolean; reason?: string } {
759
1054
  const rec = this.getSubagentRecord(subagentId, filter);
760
1055
  if (!rec) return { ok: false, reason: "not_found" };
1056
+ if (this.#isOwnerSubagentShutdownFenced(rec.ownerId)) {
1057
+ return { ok: false, status: rec.status, reason: "owner_shutdown_in_progress" };
1058
+ }
761
1059
  if (rec.status === "running") return { ok: false, status: "running", reason: "already_running" };
762
1060
  if (rec.status === "queued") {
763
1061
  if (message !== undefined && rec.queued) {
@@ -769,7 +1067,7 @@ export class AsyncJobManager {
769
1067
  return { ok: false, status: "queued", reason: "already_queued" };
770
1068
  }
771
1069
  if (!rec.resumable || !rec.sessionFile) return { ok: false, reason: "context_unavailable" };
772
- if (!this.#resumeRunner) return { ok: false, reason: "no_runner" };
1070
+ if (!this.#resolveResumeRunner(rec.subagentId)) return { ok: false, reason: "no_runner" };
773
1071
  if (this.getRunningJobs().length >= this.#maxRunningJobs) {
774
1072
  const seq = ++this.#resumeSeq;
775
1073
  rec.status = "queued";
@@ -790,11 +1088,15 @@ export class AsyncJobManager {
790
1088
  rec: SubagentRecord,
791
1089
  message?: string,
792
1090
  ): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
1091
+ if (this.#isOwnerSubagentShutdownFenced(rec.ownerId)) {
1092
+ return { ok: false, status: rec.status, reason: "owner_shutdown_in_progress" };
1093
+ }
793
1094
  const prevJobId = rec.currentJobId;
794
1095
  // Clear any retained progress from the previous run so a resumed subagent
795
1096
  // never renders the prior run's tool/output as live before it emits again.
796
1097
  this.#subagentProgress.delete(rec.subagentId);
797
- const newJobId = this.#resumeRunner?.(rec.subagentId, message, this.#resumeDescriptors.get(rec.subagentId));
1098
+ const runner = this.#resolveResumeRunner(rec.subagentId);
1099
+ const newJobId = runner?.(rec.subagentId, message, this.#resumeDescriptors.get(rec.subagentId));
798
1100
  if (!newJobId) return { ok: false, reason: "resume_failed" };
799
1101
  if (prevJobId && prevJobId !== newJobId) rec.historicalJobIds.push(prevJobId);
800
1102
  rec.currentJobId = newJobId;
@@ -803,16 +1105,36 @@ export class AsyncJobManager {
803
1105
  return { ok: true, status: rec.status, jobId: newJobId };
804
1106
  }
805
1107
 
806
- /** Drain queued resumes (FIFO by seq) while concurrency slots are available. */
1108
+ /** Drain queued resumes while preserving fenced owners and allowing foreign progress. */
807
1109
  #drainResumeQueue(): void {
808
1110
  if (this.#resumeQueue.length === 0) return;
809
1111
  this.#resumeQueue.sort((a, b) => a.seq - b.seq);
810
- while (this.#resumeQueue.length > 0 && this.getRunningJobs().length < this.#maxRunningJobs) {
811
- const entry = this.#resumeQueue.shift();
812
- if (!entry) return;
1112
+ let index = 0;
1113
+ while (index < this.#resumeQueue.length && this.getRunningJobs().length < this.#maxRunningJobs) {
1114
+ const entry = this.#resumeQueue[index];
813
1115
  const rec = this.#subagentRecords.get(entry.subagentId);
814
- if (rec?.status !== "queued") continue;
815
- this.#startResume(rec, entry.message);
1116
+ if (rec?.status !== "queued") {
1117
+ this.#resumeQueue.splice(index, 1);
1118
+ continue;
1119
+ }
1120
+ if (this.#isOwnerSubagentShutdownFenced(entry.ownerId)) {
1121
+ index += 1;
1122
+ continue;
1123
+ }
1124
+ try {
1125
+ const result = this.#startResume(rec, entry.message);
1126
+ if (result.reason === "owner_shutdown_in_progress") {
1127
+ index += 1;
1128
+ continue;
1129
+ }
1130
+ this.#resumeQueue.splice(index, 1);
1131
+ } catch (error) {
1132
+ if (error instanceof OwnerSubagentShutdownError) {
1133
+ index += 1;
1134
+ continue;
1135
+ }
1136
+ throw error;
1137
+ }
816
1138
  }
817
1139
  }
818
1140
 
@@ -854,6 +1176,7 @@ export class AsyncJobManager {
854
1176
  if (!ownerId || rec.ownerId === ownerId) {
855
1177
  this.#liveHandles.delete(sid);
856
1178
  this.#resumeDescriptors.delete(sid);
1179
+ this.#descriptorResumeRunners.delete(sid);
857
1180
  this.#subagentRecords.delete(sid);
858
1181
  this.#subagentProgress.delete(sid);
859
1182
  }
@@ -1011,45 +1334,23 @@ export class AsyncJobManager {
1011
1334
  };
1012
1335
  }
1013
1336
 
1014
- /**
1015
- * Run and clear every registered cleanup for the given filter. Idempotent
1016
- * and error-isolated: a throwing cleanup does not prevent siblings from
1017
- * running and never escalates to the caller.
1018
- */
1337
+ /** Run producer cleanups, then perform the legacy destructive subagent purge. */
1019
1338
  runOwnerCleanups(filter?: AsyncJobFilter): void {
1020
- const ownerId = filter?.ownerId;
1021
- const targets: Array<[string, Set<() => void>]> = [];
1022
- if (ownerId) {
1023
- const bag = this.#ownerCleanups.get(ownerId);
1024
- if (bag) targets.push([ownerId, bag]);
1025
- } else {
1026
- for (const entry of this.#ownerCleanups.entries()) targets.push(entry);
1027
- }
1028
- for (const [id, bag] of targets) {
1029
- const callbacks = Array.from(bag);
1030
- bag.clear();
1031
- this.#ownerCleanups.delete(id);
1032
- for (const cleanup of callbacks) {
1033
- try {
1034
- cleanup();
1035
- } catch (error) {
1036
- logger.warn("Async job owner cleanup failed", {
1037
- ownerId: id,
1038
- error: error instanceof Error ? error.message : String(error),
1039
- });
1040
- }
1041
- }
1042
- }
1043
- this.#purgeOwnerSubagentState(ownerId);
1339
+ this.runOwnerProducerCleanups(filter);
1340
+ this.#purgeOwnerSubagentState(filter?.ownerId);
1044
1341
  }
1045
1342
 
1046
1343
  getDeliveryState(filter?: AsyncJobFilter): AsyncJobDeliveryState {
1344
+ this.#expireMonitorTombstones();
1345
+ this.#pruneEvictedDeadLetters();
1047
1346
  const deliveries = this.#filterDeliveries(filter);
1048
1347
  const inFlightDeliveries = this.#filterInFlightDeliveries(filter);
1049
1348
  const ownerId = filter?.ownerId;
1050
- const deadLettered = Array.from(this.#deadLetteredDeliveries.values()).filter(
1051
- delivery => !ownerId || delivery.ownerId === ownerId,
1052
- ).length;
1349
+ const deadLettered = ownerId
1350
+ ? Array.from(this.#deadLetteredDeliveries.keys()).filter(
1351
+ jobId => this.#deadLetteredDeliveryOwners.get(jobId) === ownerId,
1352
+ ).length
1353
+ : this.#deadLetteredDeliveries.size;
1053
1354
  const nextRetryAt = deliveries.reduce<number | undefined>((next, delivery) => {
1054
1355
  if (next === undefined) return delivery.nextAttemptAt;
1055
1356
  return Math.min(next, delivery.nextAttemptAt);
@@ -1084,6 +1385,7 @@ export class AsyncJobManager {
1084
1385
  removed += 1;
1085
1386
  }
1086
1387
  }
1388
+ if (removed > 0) this.#ensureDeliveryLoop();
1087
1389
  return removed;
1088
1390
  }
1089
1391
 
@@ -1099,7 +1401,7 @@ export class AsyncJobManager {
1099
1401
  this.#deliveries.splice(
1100
1402
  0,
1101
1403
  this.#deliveries.length,
1102
- ...this.#deliveries.filter(delivery => !this.isDeliverySuppressed(delivery.jobId)),
1404
+ ...this.#deliveries.filter(delivery => !this.#isDeliveryAcknowledged(delivery.jobId)),
1103
1405
  );
1104
1406
  return before - this.#deliveries.length;
1105
1407
  }
@@ -1119,6 +1421,48 @@ export class AsyncJobManager {
1119
1421
  }
1120
1422
  }
1121
1423
 
1424
+ async waitForOwnerInFlightDeliveries(ownerId: string, options?: { timeoutMs?: number }): Promise<boolean> {
1425
+ const inFlight = this.#inFlightDeliveries
1426
+ .filter(delivery => delivery.ownerId === ownerId)
1427
+ .map(delivery => delivery.promise)
1428
+ .filter((promise): promise is Promise<void> => promise !== undefined);
1429
+ if (inFlight.length === 0) return true;
1430
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? OWNER_SUBAGENT_SHUTDOWN_TIMEOUT_MS);
1431
+ let timedOut = false;
1432
+ await Promise.race([
1433
+ Promise.allSettled(inFlight),
1434
+ Bun.sleep(timeoutMs).then(() => {
1435
+ timedOut = true;
1436
+ }),
1437
+ ]);
1438
+ return !timedOut;
1439
+ }
1440
+ async cancelAndSettleOwnerJobs(ownerId: string, options?: { timeoutMs?: number }): Promise<boolean> {
1441
+ const jobs = this.getAllJobs({ ownerId });
1442
+ for (const job of jobs) this.cancel(job.id, { ownerId });
1443
+ const timeoutMs = Math.max(0, options?.timeoutMs ?? OWNER_SUBAGENT_SHUTDOWN_TIMEOUT_MS);
1444
+ let timedOut = false;
1445
+ await Promise.race([
1446
+ Promise.allSettled(jobs.map(job => job.promise)),
1447
+ Bun.sleep(timeoutMs).then(() => {
1448
+ timedOut = true;
1449
+ }),
1450
+ ]);
1451
+ const inFlight = this.#inFlightDeliveries
1452
+ .filter(delivery => delivery.ownerId === ownerId)
1453
+ .map(delivery => delivery.promise)
1454
+ .filter((promise): promise is Promise<void> => promise !== undefined);
1455
+ if (inFlight.length === 0) return !timedOut;
1456
+ let deliveryTimedOut = false;
1457
+ await Promise.race([
1458
+ Promise.allSettled(inFlight),
1459
+ Bun.sleep(timeoutMs).then(() => {
1460
+ deliveryTimedOut = true;
1461
+ }),
1462
+ ]);
1463
+ return !timedOut && !deliveryTimedOut;
1464
+ }
1465
+
1122
1466
  getLastDisposeDiagnostics(): AsyncJobDisposeDiagnostics {
1123
1467
  return { ...this.#lastDisposeDiagnostics, stuckJobIds: [...this.#lastDisposeDiagnostics.stuckJobIds] };
1124
1468
  }
@@ -1221,6 +1565,7 @@ export class AsyncJobManager {
1221
1565
  this.#deliveries.length = 0;
1222
1566
  this.#inFlightDeliveries.length = 0;
1223
1567
  this.#deadLetteredDeliveries.clear();
1568
+ this.#deadLetteredDeliveryOwners.clear();
1224
1569
  this.#suppressedDeliveries.clear();
1225
1570
  this.#watchedJobs.clear();
1226
1571
  this.#outputState.clear();
@@ -1229,7 +1574,9 @@ export class AsyncJobManager {
1229
1574
  this.#liveHandles.clear();
1230
1575
  this.#subagentProgress.clear();
1231
1576
  this.#resumeDescriptors.clear();
1577
+ this.#descriptorResumeRunners.clear();
1232
1578
  this.#resumeQueue.length = 0;
1579
+ this.#ownerSubagentShutdownLeases.clear();
1233
1580
  this.#notifyChange();
1234
1581
  this.#changeListeners.clear();
1235
1582
  return drained && waitResult.completed;
@@ -1281,12 +1628,15 @@ export class AsyncJobManager {
1281
1628
  }
1282
1629
 
1283
1630
  #evictJob(jobId: string): void {
1631
+ this.#expireMonitorTombstones();
1284
1632
  this.#recordMonitorTombstone(jobId);
1285
1633
  this.#runLifecycle(jobId, "evict");
1286
1634
  this.#purgeTerminalSubagentStateForJob(jobId);
1287
1635
  this.#jobs.delete(jobId);
1288
1636
  this.#lifecycles.delete(jobId);
1289
1637
  this.#lifecyclePhases.delete(jobId);
1638
+ this.#deadLetteredDeliveries.delete(jobId);
1639
+ this.#deadLetteredDeliveryOwners.delete(jobId);
1290
1640
  this.#suppressedDeliveries.delete(jobId);
1291
1641
  this.#watchedJobs.delete(jobId);
1292
1642
  this.#outputState.delete(jobId);
@@ -1315,12 +1665,22 @@ export class AsyncJobManager {
1315
1665
  );
1316
1666
  }
1317
1667
 
1668
+ #isDeliveryFenced(delivery: AsyncJobDelivery): boolean {
1669
+ return Boolean(delivery.ownerId && this.#isOwnerSubagentShutdownFenced(delivery.ownerId));
1670
+ }
1671
+
1672
+ #hasDeliverable(): boolean {
1673
+ return this.#deliveries.some(
1674
+ delivery => !this.isDeliverySuppressed(delivery.jobId) && !this.#isDeliveryFenced(delivery),
1675
+ );
1676
+ }
1677
+
1318
1678
  async #deliverNextFiltered(filter: AsyncJobFilter, deadline: number): Promise<boolean> {
1319
1679
  while (true) {
1320
1680
  let selected: AsyncJobDelivery | undefined;
1321
1681
  for (const delivery of this.#deliveries) {
1322
1682
  if (delivery.ownerId !== filter.ownerId) continue;
1323
- if (this.isDeliverySuppressed(delivery.jobId)) continue;
1683
+ if (this.isDeliverySuppressed(delivery.jobId) || this.#isDeliveryFenced(delivery)) continue;
1324
1684
  if (!selected || delivery.nextAttemptAt < selected.nextAttemptAt) {
1325
1685
  selected = delivery;
1326
1686
  }
@@ -1328,7 +1688,7 @@ export class AsyncJobManager {
1328
1688
 
1329
1689
  if (!selected) {
1330
1690
  const inFlight = this.#filterInFlightDeliveries(filter);
1331
- if (inFlight.length === 0) return true;
1691
+ if (inFlight.length === 0) return this.#filterDeliveries(filter).length === 0;
1332
1692
  return this.#waitForDeliveryPromise(inFlight[0]?.promise, deadline);
1333
1693
  }
1334
1694
 
@@ -1348,13 +1708,44 @@ export class AsyncJobManager {
1348
1708
  }
1349
1709
  }
1350
1710
 
1711
+ #isDeliveryAcknowledged(jobId: string): boolean {
1712
+ return this.#suppressedDeliveries.has(jobId);
1713
+ }
1714
+
1351
1715
  isDeliverySuppressed(jobId: string): boolean {
1352
- return this.#suppressedDeliveries.has(jobId) || this.#watchedJobs.has(jobId);
1716
+ return this.#isDeliveryAcknowledged(jobId) || this.#watchedJobs.has(jobId);
1717
+ }
1718
+
1719
+ #pruneEvictedDeadLetters(): void {
1720
+ for (const jobId of this.#deadLetteredDeliveries.keys()) {
1721
+ if (this.#jobs.has(jobId)) continue;
1722
+ this.#deadLetteredDeliveries.delete(jobId);
1723
+ this.#deadLetteredDeliveryOwners.delete(jobId);
1724
+ }
1725
+ }
1726
+
1727
+ #recordDeadLetter(delivery: AsyncJobDelivery): void {
1728
+ this.#pruneEvictedDeadLetters();
1729
+ if (!this.#jobs.has(delivery.jobId)) return;
1730
+ this.#deadLetteredDeliveries.delete(delivery.jobId);
1731
+ this.#deadLetteredDeliveryOwners.delete(delivery.jobId);
1732
+ this.#deadLetteredDeliveries.set(delivery.jobId, {
1733
+ jobId: delivery.jobId,
1734
+ attempt: delivery.attempt,
1735
+ lastError: delivery.lastError,
1736
+ });
1737
+ this.#deadLetteredDeliveryOwners.set(delivery.jobId, delivery.ownerId);
1738
+ while (this.#deadLetteredDeliveries.size > MAX_DEAD_LETTERED_DELIVERIES) {
1739
+ const oldestJobId = this.#deadLetteredDeliveries.keys().next().value;
1740
+ if (oldestJobId === undefined) return;
1741
+ this.#deadLetteredDeliveries.delete(oldestJobId);
1742
+ this.#deadLetteredDeliveryOwners.delete(oldestJobId);
1743
+ }
1353
1744
  }
1354
1745
 
1355
1746
  #enqueueDelivery(jobId: string, text: string): void {
1356
1747
  // Skip delivery if already acknowledged
1357
- if (this.isDeliverySuppressed(jobId)) {
1748
+ if (this.#isDeliveryAcknowledged(jobId)) {
1358
1749
  return;
1359
1750
  }
1360
1751
  const deliveryText = this.#boundedDeliveryText(text);
@@ -1369,7 +1760,7 @@ export class AsyncJobManager {
1369
1760
  });
1370
1761
  while (this.#deliveries.length > DEFAULT_MAX_DELIVERY_QUEUE) {
1371
1762
  const dropped = this.#deliveries.shift();
1372
- if (dropped) this.#deadLetteredDeliveries.set(dropped.jobId, dropped);
1763
+ if (dropped) this.#recordDeadLetter(dropped);
1373
1764
  }
1374
1765
  this.#ensureDeliveryLoop();
1375
1766
  }
@@ -1399,7 +1790,7 @@ export class AsyncJobManager {
1399
1790
  })
1400
1791
  .finally(() => {
1401
1792
  this.#deliveryLoop = undefined;
1402
- if (!this.#disposed && this.#deliveries.length > 0) {
1793
+ if (!this.#disposed && this.#hasDeliverable()) {
1403
1794
  this.#ensureDeliveryLoop();
1404
1795
  }
1405
1796
  });
@@ -1407,24 +1798,19 @@ export class AsyncJobManager {
1407
1798
 
1408
1799
  async #runDeliveryLoop(): Promise<void> {
1409
1800
  while (this.#deliveries.length > 0) {
1410
- const delivery = this.#deliveries[0];
1411
- if (this.isDeliverySuppressed(delivery.jobId)) {
1412
- this.#deliveries.shift();
1413
- continue;
1414
- }
1801
+ const delivery = this.#deliveries.find(
1802
+ candidate => !this.isDeliverySuppressed(candidate.jobId) && !this.#isDeliveryFenced(candidate),
1803
+ );
1804
+ if (!delivery) return;
1415
1805
  const waitMs = delivery.nextAttemptAt - Date.now();
1416
1806
  if (waitMs > 0) {
1417
1807
  await Bun.sleep(waitMs);
1418
1808
  }
1419
- if (this.#deliveries[0] !== delivery) {
1420
- continue;
1421
- }
1422
- if (this.isDeliverySuppressed(delivery.jobId)) {
1423
- this.#deliveries.shift();
1424
- continue;
1425
- }
1809
+ const index = this.#deliveries.indexOf(delivery);
1810
+ if (index === -1) continue;
1811
+ if (this.isDeliverySuppressed(delivery.jobId) || this.#isDeliveryFenced(delivery)) continue;
1426
1812
 
1427
- this.#deliveries.shift();
1813
+ this.#deliveries.splice(index, 1);
1428
1814
  await this.#deliverDelivery(delivery);
1429
1815
  }
1430
1816
  }
@@ -1438,7 +1824,7 @@ export class AsyncJobManager {
1438
1824
  delivery.attempt += 1;
1439
1825
  delivery.lastError = error instanceof Error ? error.message : String(error);
1440
1826
  if (delivery.attempt >= DELIVERY_MAX_ATTEMPTS) {
1441
- this.#deadLetteredDeliveries.set(delivery.jobId, delivery);
1827
+ this.#recordDeadLetter(delivery);
1442
1828
  logger.warn("Async job completion delivery reached retry cap", {
1443
1829
  jobId: delivery.jobId,
1444
1830
  attempt: delivery.attempt,
@@ -1446,7 +1832,7 @@ export class AsyncJobManager {
1446
1832
  });
1447
1833
  } else {
1448
1834
  delivery.nextAttemptAt = Date.now() + this.#getRetryDelay(delivery.attempt);
1449
- if (!this.isDeliverySuppressed(delivery.jobId)) {
1835
+ if (!this.#isDeliveryAcknowledged(delivery.jobId)) {
1450
1836
  this.#deliveries.push(delivery);
1451
1837
  }
1452
1838
  logger.warn("Async job completion delivery failed", {
@@ -1459,7 +1845,7 @@ export class AsyncJobManager {
1459
1845
  } finally {
1460
1846
  const index = this.#inFlightDeliveries.indexOf(delivery);
1461
1847
  if (index !== -1) this.#inFlightDeliveries.splice(index, 1);
1462
- if (!this.#disposed && this.#deliveries.length > 0) this.#ensureDeliveryLoop();
1848
+ if (!this.#disposed && this.#hasDeliverable()) this.#ensureDeliveryLoop();
1463
1849
  }
1464
1850
  })();
1465
1851
  delivery.promise = promise;