@gajae-code/coding-agent 0.18.11 → 0.19.0

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 (462) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/capability/rule.d.ts +7 -2
  7. package/dist/types/cli/accounts-cli.d.ts +2 -2
  8. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  9. package/dist/types/cli/args.d.ts +7 -2
  10. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  11. package/dist/types/cli/customize-doctor.d.ts +4 -3
  12. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  13. package/dist/types/cli/install-activation.d.ts +10 -4
  14. package/dist/types/cli/public-command-errors.d.ts +2 -0
  15. package/dist/types/cli/root-flags.d.ts +0 -12
  16. package/dist/types/cli/update-cli.d.ts +21 -3
  17. package/dist/types/cli-main.d.ts +0 -6
  18. package/dist/types/cli-ordinary.d.ts +2 -2
  19. package/dist/types/commands/accounts.d.ts +2 -1
  20. package/dist/types/commands/launch.d.ts +0 -6
  21. package/dist/types/commands/migrate.d.ts +1 -1
  22. package/dist/types/commands/sdk.d.ts +13 -0
  23. package/dist/types/commit/changelog/index.d.ts +1 -0
  24. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  25. package/dist/types/config/autorouting-tier-map.d.ts +53 -1
  26. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  27. package/dist/types/config/config-hot-reload.d.ts +44 -0
  28. package/dist/types/config/file-lock.d.ts +52 -3
  29. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  30. package/dist/types/config/model-profile-activation.d.ts +79 -9
  31. package/dist/types/config/model-registry.d.ts +72 -8
  32. package/dist/types/config/model-resolver.d.ts +7 -4
  33. package/dist/types/config/models-config-schema.d.ts +10 -0
  34. package/dist/types/config/settings-schema.d.ts +57 -4
  35. package/dist/types/config/settings.d.ts +33 -1
  36. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  37. package/dist/types/customization/import.d.ts +2 -0
  38. package/dist/types/customization/types.d.ts +1 -1
  39. package/dist/types/dap/config.d.ts +8 -4
  40. package/dist/types/dap/session.d.ts +11 -2
  41. package/dist/types/debug/system-info.d.ts +6 -1
  42. package/dist/types/discovery/helpers.d.ts +7 -2
  43. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  44. package/dist/types/edit/index.d.ts +5 -0
  45. package/dist/types/edit/modes/patch.d.ts +14 -0
  46. package/dist/types/edit/modes/replace.d.ts +3 -0
  47. package/dist/types/eval/py/runtime.d.ts +9 -0
  48. package/dist/types/exec/bash-executor.d.ts +12 -0
  49. package/dist/types/export/ttsr.d.ts +19 -0
  50. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  51. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  52. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  53. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  54. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  55. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  56. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  57. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  58. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  59. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  60. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  61. package/dist/types/hindsight/content.d.ts +5 -6
  62. package/dist/types/internal-urls/types.d.ts +2 -0
  63. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  64. package/dist/types/main.d.ts +23 -18
  65. package/dist/types/migrate/action-planner.d.ts +10 -1
  66. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  67. package/dist/types/migrate/adapters/index.d.ts +42 -1
  68. package/dist/types/migrate/executor.d.ts +3 -1
  69. package/dist/types/migrate/hook-module.d.ts +23 -0
  70. package/dist/types/migrate/report.d.ts +2 -1
  71. package/dist/types/migrate/types.d.ts +82 -3
  72. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  73. package/dist/types/modes/components/model-selector.d.ts +43 -0
  74. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  75. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  76. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  77. package/dist/types/modes/theme/theme.d.ts +1 -0
  78. package/dist/types/modes/utils/host-status.d.ts +15 -11
  79. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  80. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  81. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  82. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  83. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  84. package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
  85. package/dist/types/sdk/broker/broker.d.ts +29 -2
  86. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  87. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  88. package/dist/types/sdk/broker/ensure.d.ts +141 -9
  89. package/dist/types/sdk/broker/hop.d.ts +45 -0
  90. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  91. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  92. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  93. package/dist/types/sdk/broker/session-index.d.ts +6 -1
  94. package/dist/types/sdk/broker/transport.d.ts +2 -0
  95. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  96. package/dist/types/sdk/bus/index.d.ts +44 -0
  97. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  98. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
  99. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  100. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  101. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  102. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  103. package/dist/types/sdk/host/events.d.ts +4 -0
  104. package/dist/types/sdk/host/host.d.ts +2 -1
  105. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +40 -8
  107. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  109. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  111. package/dist/types/sdk/prompt-status.d.ts +11 -4
  112. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  113. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  114. package/dist/types/sdk/session-list.d.ts +1 -1
  115. package/dist/types/session/agent-session.d.ts +107 -9
  116. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  117. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  118. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  119. package/dist/types/session/messages.d.ts +5 -1
  120. package/dist/types/session/session-manager.d.ts +29 -3
  121. package/dist/types/session/startup-auth-config.d.ts +24 -2
  122. package/dist/types/session/streaming-output.d.ts +11 -0
  123. package/dist/types/session/terminal-abort.d.ts +3 -1
  124. package/dist/types/session-import/service.d.ts +1 -1
  125. package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
  126. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  127. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  128. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  129. package/dist/types/system-prompt.d.ts +3 -2
  130. package/dist/types/task/executor.d.ts +19 -2
  131. package/dist/types/task/role-body.d.ts +11 -0
  132. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  133. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  134. package/dist/types/tools/awaiting-input.d.ts +28 -0
  135. package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
  136. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  137. package/dist/types/tools/bash.d.ts +7 -0
  138. package/dist/types/tools/descriptors.d.ts +15 -2
  139. package/dist/types/tools/fetch.d.ts +7 -1
  140. package/dist/types/tools/index.d.ts +2 -0
  141. package/dist/types/tools/irc.d.ts +5 -3
  142. package/dist/types/tools/python.d.ts +1 -1
  143. package/dist/types/tools/report-issue.d.ts +41 -0
  144. package/dist/types/tools/subagent.d.ts +23 -0
  145. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  146. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  147. package/dist/types/tools/write.d.ts +2 -0
  148. package/dist/types/utils/file-mentions.d.ts +5 -0
  149. package/dist/types/utils/image-limits.d.ts +7 -0
  150. package/dist/types/utils/image-loading.d.ts +0 -1
  151. package/dist/types/utils/open.d.ts +0 -1
  152. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  153. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  154. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  155. package/dist/types/utils/title-generator.d.ts +13 -3
  156. package/dist/types/web/scrapers/types.d.ts +8 -1
  157. package/dist/types/web/search/providers/codex.d.ts +6 -7
  158. package/dist/types/web/search/providers/insane.d.ts +7 -1
  159. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  160. package/package.json +10 -9
  161. package/scripts/acp-conformance-agent.ts +12 -2
  162. package/scripts/build-binary.ts +2 -1
  163. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  164. package/scripts/generate-docs-index.ts +8 -1
  165. package/scripts/generate-sdk-operation-inventory.ts +38 -4
  166. package/src/async/job-manager.ts +356 -56
  167. package/src/async/result-formatting.ts +40 -0
  168. package/src/autoresearch/git.ts +9 -2
  169. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  170. package/src/capability/fs.ts +58 -22
  171. package/src/capability/index.ts +2 -2
  172. package/src/capability/rule.ts +11 -2
  173. package/src/cli/accounts-cli.ts +176 -8
  174. package/src/cli/append-system-prompt.ts +12 -0
  175. package/src/cli/args.ts +11 -7
  176. package/src/cli/auth-broker-cli.ts +21 -11
  177. package/src/cli/customize-doctor-precedence.ts +31 -0
  178. package/src/cli/customize-doctor.ts +49 -39
  179. package/src/cli/doctor/install-repairs.ts +35 -5
  180. package/src/cli/fast-help.ts +1 -3
  181. package/src/cli/install-activation.ts +47 -4
  182. package/src/cli/migrate-cli.ts +19 -6
  183. package/src/cli/public-command-errors.ts +6 -0
  184. package/src/cli/root-flags.ts +2 -4
  185. package/src/cli/stats-cli.ts +9 -5
  186. package/src/cli/update-cli.ts +471 -13
  187. package/src/cli-ordinary.ts +7 -2
  188. package/src/commands/accounts.ts +10 -5
  189. package/src/commands/harness.ts +25 -15
  190. package/src/commands/migrate.ts +3 -3
  191. package/src/commands/sdk.ts +277 -50
  192. package/src/commands/ultragoal.ts +16 -8
  193. package/src/commit/agentic/agent.ts +6 -6
  194. package/src/commit/analysis/conventional.ts +14 -5
  195. package/src/commit/changelog/index.ts +39 -1
  196. package/src/commit/map-reduce/index.ts +7 -7
  197. package/src/commit/message.ts +10 -3
  198. package/src/commit/pipeline.ts +12 -12
  199. package/src/config/autorouting-tier-map.ts +50 -1
  200. package/src/config/config-file.ts +106 -3
  201. package/src/config/config-hot-reload-trust.ts +148 -0
  202. package/src/config/config-hot-reload.ts +742 -0
  203. package/src/config/file-lock.ts +1615 -328
  204. package/src/config/keybindings.ts +154 -7
  205. package/src/config/model-bindings-applier.ts +85 -47
  206. package/src/config/model-discovery-manager.ts +10 -0
  207. package/src/config/model-profile-activation.ts +889 -148
  208. package/src/config/model-registry.ts +1706 -205
  209. package/src/config/model-resolver.ts +52 -22
  210. package/src/config/models-config-schema.ts +6 -0
  211. package/src/config/settings-schema.ts +73 -4
  212. package/src/config/settings.ts +460 -24
  213. package/src/coordinator-mcp/policy.ts +26 -1
  214. package/src/coordinator-mcp/server.ts +37 -2
  215. package/src/crash/sanitize.ts +7 -1
  216. package/src/customization/import.ts +279 -8
  217. package/src/customization/inventory.ts +24 -8
  218. package/src/customization/types.ts +1 -0
  219. package/src/dap/client.ts +3 -2
  220. package/src/dap/config.ts +31 -11
  221. package/src/dap/defaults.json +1 -1
  222. package/src/dap/session.ts +161 -36
  223. package/src/debug/crash-diagnostics.ts +89 -6
  224. package/src/debug/report-bundle.ts +90 -2
  225. package/src/debug/system-info.ts +8 -3
  226. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  227. package/src/discovery/agents-md.ts +1 -1
  228. package/src/discovery/builtin.ts +4 -3
  229. package/src/discovery/helpers.ts +25 -16
  230. package/src/discovery/mcp-compat.ts +120 -17
  231. package/src/edit/apply-patch/index.ts +12 -1
  232. package/src/edit/index.ts +204 -22
  233. package/src/edit/modes/patch.ts +214 -8
  234. package/src/edit/modes/replace.ts +136 -0
  235. package/src/eval/js/context-manager.ts +29 -3
  236. package/src/eval/js/worker-entry.ts +51 -18
  237. package/src/eval/py/executor.ts +14 -4
  238. package/src/eval/py/runner.py +144 -5
  239. package/src/eval/py/runtime.ts +44 -4
  240. package/src/exec/bash-executor.ts +81 -4
  241. package/src/exec/bash-shell-guardian.ts +4 -1
  242. package/src/exec/bash-shell-supervisor.ts +8 -4
  243. package/src/exec/isolated-shell.ts +5 -1
  244. package/src/exec/non-interactive-env.ts +0 -1
  245. package/src/export/html/index.ts +86 -6
  246. package/src/export/ttsr.ts +176 -1
  247. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  248. package/src/extensibility/extensions/loader.ts +10 -1
  249. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  250. package/src/extensibility/extensions/types.ts +5 -1
  251. package/src/extensibility/hooks/loader.ts +10 -2
  252. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  253. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  254. package/src/extensibility/slash-commands.ts +5 -0
  255. package/src/gjc-runtime/gc-runtime.ts +37 -7
  256. package/src/gjc-runtime/launch-tmux.ts +30 -15
  257. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  258. package/src/gjc-runtime/launch-worktree.ts +101 -14
  259. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  260. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  261. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  262. package/src/gjc-runtime/session-resolution.ts +38 -6
  263. package/src/gjc-runtime/session-state-lock.ts +59 -14
  264. package/src/gjc-runtime/state-runtime.ts +23 -19
  265. package/src/gjc-runtime/state-writer.ts +109 -5
  266. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  267. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  268. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  269. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  270. package/src/harness-control-plane/finalize.ts +11 -0
  271. package/src/harness-control-plane/receipt-spool.ts +105 -28
  272. package/src/harness-control-plane/storage.ts +124 -6
  273. package/src/hindsight/content.ts +116 -16
  274. package/src/hindsight/mental-models.ts +22 -10
  275. package/src/hindsight/state.ts +7 -4
  276. package/src/hooks/native-skill-hook.ts +17 -12
  277. package/src/internal-urls/artifact-protocol.ts +3 -1
  278. package/src/internal-urls/docs-index.generated.ts +23 -22
  279. package/src/internal-urls/local-protocol.ts +167 -8
  280. package/src/internal-urls/types.ts +2 -0
  281. package/src/lsp/client.ts +2 -1
  282. package/src/lsp/clients/biome-client.ts +2 -0
  283. package/src/lsp/clients/swiftlint-client.ts +2 -0
  284. package/src/lsp/index.ts +29 -0
  285. package/src/lsp/workspace-path.ts +23 -0
  286. package/src/main.ts +330 -92
  287. package/src/memories/index.ts +25 -2
  288. package/src/migrate/action-planner.ts +574 -2
  289. package/src/migrate/adapters/claude-code.ts +162 -5
  290. package/src/migrate/adapters/codex.ts +77 -7
  291. package/src/migrate/adapters/index.ts +258 -5
  292. package/src/migrate/adapters/opencode.ts +10 -1
  293. package/src/migrate/executor.ts +222 -7
  294. package/src/migrate/hook-module.ts +193 -0
  295. package/src/migrate/report.ts +63 -2
  296. package/src/migrate/types.ts +84 -4
  297. package/src/modes/DESIGN.md +51 -0
  298. package/src/modes/acp/acp-agent.ts +10 -1
  299. package/src/modes/acp/permission-mode.ts +18 -1
  300. package/src/modes/components/agent-dashboard.ts +20 -4
  301. package/src/modes/components/assistant-message.ts +55 -1
  302. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  303. package/src/modes/components/customization/import-wizard.ts +29 -3
  304. package/src/modes/components/model-selector.ts +157 -103
  305. package/src/modes/components/settings-selector.ts +103 -37
  306. package/src/modes/components/welcome.ts +3 -1
  307. package/src/modes/controllers/command-controller.ts +300 -9
  308. package/src/modes/controllers/event-controller.ts +1 -1
  309. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  310. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  311. package/src/modes/controllers/input-controller.ts +2 -0
  312. package/src/modes/controllers/plan-mode-controller.ts +66 -11
  313. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  314. package/src/modes/controllers/selector-controller.ts +76 -7
  315. package/src/modes/interactive-mode.ts +123 -4
  316. package/src/modes/prompt-suggestion-controller.ts +1 -1
  317. package/src/modes/runtime-init.ts +5 -1
  318. package/src/modes/theme/theme.ts +15 -0
  319. package/src/modes/utils/host-status.ts +21 -10
  320. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  321. package/src/prompts/system/irc-incoming.md +3 -1
  322. package/src/prompts/tools/debug.md +1 -1
  323. package/src/prompts/tools/irc.md +1 -1
  324. package/src/prompts/tools/read.md +1 -1
  325. package/src/prompts/tools/report-issue.md +13 -0
  326. package/src/prompts/tools/task.md +1 -1
  327. package/src/runtime-credential-selector.ts +2 -2
  328. package/src/runtime-mcp/content-limits.ts +7 -0
  329. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  330. package/src/runtime-mcp/manager.ts +40 -3
  331. package/src/runtime-mcp/oauth-flow.ts +32 -6
  332. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  333. package/src/runtime-mcp/project-host-values.ts +12 -8
  334. package/src/runtime-mcp/render.ts +16 -1
  335. package/src/runtime-mcp/tool-bridge.ts +194 -21
  336. package/src/runtime-mcp/transports/stdio.ts +2 -1
  337. package/src/sdk/broker/broker-exit.ts +10 -5
  338. package/src/sdk/broker/broker.ts +484 -85
  339. package/src/sdk/broker/daemon-entry.ts +338 -116
  340. package/src/sdk/broker/doctor-restart.ts +2 -1
  341. package/src/sdk/broker/ensure.ts +1214 -268
  342. package/src/sdk/broker/hop.ts +286 -0
  343. package/src/sdk/broker/lifecycle-ledger.ts +38 -11
  344. package/src/sdk/broker/lifecycle.ts +102 -6
  345. package/src/sdk/broker/lock-host-identity.ts +29 -0
  346. package/src/sdk/broker/session-index.ts +91 -16
  347. package/src/sdk/broker/transport.ts +17 -1
  348. package/src/sdk/bus/chat-command-policy.ts +16 -3
  349. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  350. package/src/sdk/bus/index.ts +460 -35
  351. package/src/sdk/bus/reconciliation-store.ts +201 -8
  352. package/src/sdk/bus/session-control.ts +2 -0
  353. package/src/sdk/bus/slack-daemon.ts +105 -9
  354. package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
  355. package/src/sdk/bus/telegram-daemon.ts +10 -2
  356. package/src/sdk/bus/threaded-render.ts +20 -1
  357. package/src/sdk/cli/session-cli.ts +23 -6
  358. package/src/sdk/host/control/dispatch.ts +13 -1
  359. package/src/sdk/host/control/operations.ts +10 -1
  360. package/src/sdk/host/events.ts +67 -4
  361. package/src/sdk/host/host.ts +8 -2
  362. package/src/sdk/host/prompt-image-upload.ts +438 -0
  363. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  364. package/src/sdk/host/session-runtime.test.ts +1737 -129
  365. package/src/sdk/host/session-runtime.ts +684 -83
  366. package/src/sdk/host/surface-policy.ts +4 -0
  367. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  368. package/src/sdk/host/websocket-transport.ts +19 -7
  369. package/src/sdk/lifecycle/service.ts +15 -1
  370. package/src/sdk/prompt-deadline-flush.ts +78 -5
  371. package/src/sdk/prompt-deadline-manager.ts +68 -14
  372. package/src/sdk/prompt-status.ts +6 -4
  373. package/src/sdk/protocol/adapter-validation.ts +81 -12
  374. package/src/sdk/protocol/operation-inventory.generated.json +261 -2
  375. package/src/sdk/protocol/operation-registry.ts +33 -3
  376. package/src/sdk/reconciliation-extensions.ts +1 -0
  377. package/src/sdk/session-list.ts +1 -0
  378. package/src/sdk/session.ts +237 -97
  379. package/src/session/agent-session.ts +2726 -479
  380. package/src/session/history-storage.ts +182 -12
  381. package/src/session/internal/managed-session-scope.ts +173 -57
  382. package/src/session/internal/managed-session-storage.ts +85 -12
  383. package/src/session/internal/native-publish-outcome.ts +5 -1
  384. package/src/session/messages.ts +47 -6
  385. package/src/session/session-manager.ts +326 -19
  386. package/src/session/startup-auth-config.ts +116 -8
  387. package/src/session/streaming-output.ts +262 -4
  388. package/src/session/terminal-abort.ts +14 -2
  389. package/src/session-import/redact.ts +53 -2
  390. package/src/session-import/service.ts +1 -0
  391. package/src/skill-state/workflow-mutation-guard.ts +2145 -206
  392. package/src/slash-commands/builtin-registry.ts +21 -6
  393. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  394. package/src/slash-commands/helpers/mcp.ts +11 -3
  395. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  396. package/src/ssh/ssh-executor.ts +9 -2
  397. package/src/stt/recorder.ts +26 -9
  398. package/src/system-prompt.ts +61 -16
  399. package/src/task/executor.ts +115 -20
  400. package/src/task/index.ts +389 -175
  401. package/src/task/role-body.ts +84 -0
  402. package/src/task/token-log-mode.test.ts +162 -0
  403. package/src/task/token-log.ts +91 -2
  404. package/src/tools/archive-reader.ts +650 -37
  405. package/src/tools/ask.ts +1 -0
  406. package/src/tools/ast-edit-write-path.ts +64 -0
  407. package/src/tools/ast-edit.ts +18 -1
  408. package/src/tools/ast-grep.ts +7 -2
  409. package/src/tools/atomic-file-write.ts +46 -15
  410. package/src/tools/auto-generated-guard.ts +15 -3
  411. package/src/tools/awaiting-input.ts +114 -0
  412. package/src/tools/bash-allowed-prefixes.ts +214 -6
  413. package/src/tools/bash-interactive.ts +14 -2
  414. package/src/tools/bash-skill-urls.ts +58 -5
  415. package/src/tools/bash.ts +113 -26
  416. package/src/tools/browser/tab-worker.ts +39 -13
  417. package/src/tools/browser.ts +17 -7
  418. package/src/tools/cron.ts +10 -1
  419. package/src/tools/debug.ts +16 -8
  420. package/src/tools/descriptors.test.ts +44 -0
  421. package/src/tools/descriptors.ts +50 -6
  422. package/src/tools/eval.ts +15 -0
  423. package/src/tools/fetch.ts +93 -4
  424. package/src/tools/gh.ts +27 -12
  425. package/src/tools/image-gen.ts +20 -6
  426. package/src/tools/index.ts +2 -0
  427. package/src/tools/irc.ts +5 -3
  428. package/src/tools/monitor.ts +9 -1
  429. package/src/tools/output-meta.ts +74 -5
  430. package/src/tools/python.ts +1 -1
  431. package/src/tools/read.ts +26 -37
  432. package/src/tools/recipe/runner.ts +23 -12
  433. package/src/tools/render-utils.ts +2 -2
  434. package/src/tools/report-issue.ts +184 -0
  435. package/src/tools/search.ts +3 -2
  436. package/src/tools/ssh.ts +274 -8
  437. package/src/tools/subagent-render.ts +114 -12
  438. package/src/tools/subagent.ts +118 -5
  439. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  440. package/src/tools/todo-write.ts +41 -3
  441. package/src/tools/tool-catalog.generated.ts +32 -4
  442. package/src/tools/ultragoal-ask-guard.ts +13 -1
  443. package/src/tools/write.ts +42 -3
  444. package/src/tui/tree-list.ts +15 -1
  445. package/src/utils/commit-message-generator.ts +12 -4
  446. package/src/utils/external-editor.ts +21 -7
  447. package/src/utils/file-mentions.ts +75 -7
  448. package/src/utils/git.ts +38 -7
  449. package/src/utils/image-limits.ts +8 -0
  450. package/src/utils/image-loading.ts +1 -1
  451. package/src/utils/open.ts +73 -4
  452. package/src/utils/pasted-image-loading.ts +9 -8
  453. package/src/utils/prompt-suggestion.ts +5 -4
  454. package/src/utils/shell-snapshot.ts +9 -1
  455. package/src/utils/title-generator.ts +72 -22
  456. package/src/web/scrapers/index.ts +3 -1
  457. package/src/web/scrapers/types.ts +8 -0
  458. package/src/web/scrapers/youtube.ts +3 -2
  459. package/src/web/search/index.ts +2 -1
  460. package/src/web/search/providers/codex.ts +29 -32
  461. package/src/web/search/providers/insane.ts +91 -28
  462. package/src/web/search/providers/searxng.ts +62 -10
@@ -1,6 +1,7 @@
1
1
  import { logger } from "@gajae-code/utils";
2
2
  import {
3
3
  lookupOwnedRegistration,
4
+ migrateOwnedAttemptSaturationsForEndpoint,
4
5
  registerOwnedRegistration,
5
6
  resolveToolLineage,
6
7
  retireOwnedRegistrationForDeadLetter,
@@ -192,7 +193,15 @@ export interface SubagentRecord {
192
193
  * file, followed by a separately available runner (`no_runner` otherwise).
193
194
  */
194
195
  resumable: boolean;
195
- queued?: { ownerId?: string; seq: number; message?: string; resumeToolCallId?: string; createdAt: number };
196
+ queued?: {
197
+ ownerId?: string;
198
+ seq: number;
199
+ message?: string;
200
+ resumeToolCallId?: string;
201
+ admissionEndpointId?: string;
202
+ ownedRegistrationEndpointId?: string;
203
+ createdAt: number;
204
+ };
196
205
  /** Last queued-resume seq for a CANCELLED queued resume (rec.queued is
197
206
  * cleared on cancel): retained on the record so owned settlement's second
198
207
  * proof can still see the generation as provably cancelled, without a
@@ -227,6 +236,7 @@ interface OwnerSubagentShutdownRecordCapture {
227
236
  readonly record: SubagentRecord;
228
237
  readonly currentJobId: string | null;
229
238
  readonly currentJobGeneration?: string;
239
+ readonly queuedSeq?: number;
230
240
  }
231
241
 
232
242
  /** Lightweight, manager-owned resume payload. The async layer treats `data` as opaque. */
@@ -245,6 +255,8 @@ export type ResumeRunner = (
245
255
  message?: string,
246
256
  descriptor?: ResumeDescriptor,
247
257
  resumeToolCallId?: string,
258
+ admissionEndpointId?: string,
259
+ ownedRegistrationEndpointId?: string,
248
260
  ) => string | undefined;
249
261
 
250
262
  function sessionFileFromResumeDescriptorData(data: unknown): string | null {
@@ -275,6 +287,9 @@ interface ResumeQueueEntry {
275
287
  ownerId?: string;
276
288
  seq: number;
277
289
  message?: string;
290
+ resumeToolCallId?: string;
291
+ admissionEndpointId?: string;
292
+ ownedRegistrationEndpointId?: string;
278
293
  createdAt: number;
279
294
  }
280
295
 
@@ -633,7 +648,7 @@ export class AsyncJobManager {
633
648
  * resume can neither resolve its lineage nor register its owned tuple
634
649
  * (review thread P1). Returns TRUE when the mapping was moved (or no move
635
650
  * was needed); returns FALSE when the successor endpoint is owned by a
636
- * FOREIGN live manager — the transition must then abort or roll back
651
+ /**
637
652
  * BEFORE retiring predecessor state, because leaving this manager under
638
653
  * the predecessor while tools resolve the successor to the foreign
639
654
  * manager sends jobs to the wrong session and owned aborts lose their
@@ -653,6 +668,11 @@ export class AsyncJobManager {
653
668
  }
654
669
  AsyncJobManager.#byEndpoint.delete(predecessorEndpointId);
655
670
  AsyncJobManager.#byEndpoint.set(successorEndpointId, manager);
671
+ migrateOwnedAttemptSaturationsForEndpoint(predecessorEndpointId, successorEndpointId);
672
+ // Migrate queued ownership registrations from the predecessor endpoint to
673
+ // the successor. The admission endpoint remains unchanged for exact lineage
674
+ // lookup; each queue entry tracks the separate endpoint used for ownership.
675
+ manager.#migrateQueuedResumeRegistrations(predecessorEndpointId, successorEndpointId);
656
676
  return true;
657
677
  }
658
678
 
@@ -1225,6 +1245,7 @@ export class AsyncJobManager {
1225
1245
  this.#externallySettled.delete(job.generation);
1226
1246
  if (this.#jobs.get(id) === job) this.#settledJobs.add(job);
1227
1247
  this.#settleExecution(execution);
1248
+ if (!this.#disposed && !this.#registrationClosed) this.#drainResumeQueue();
1228
1249
  })
1229
1250
  .then(completion.resolve, completion.reject);
1230
1251
 
@@ -1261,13 +1282,19 @@ export class AsyncJobManager {
1261
1282
  cancel(id: string, filter?: AsyncJobFilter): boolean {
1262
1283
  if (id.startsWith("queued:")) {
1263
1284
  // A queued resume (no real job yet): owned settlement cancels it by
1264
- // removing the queued subagent record and publishing the terminal
1265
- // (review thread P1).
1285
+ // removing the queued subagent record and publishing the terminal.
1286
+ // Preserve generation identity by checking that the requested sequence
1287
+ // matches the current record's queued sequence (review thread P2).
1266
1288
  const colon = id.lastIndexOf(":");
1267
1289
  const subagentId = colon > "queued:".length ? id.slice("queued:".length, colon) : undefined;
1268
1290
  if (!subagentId) return false;
1291
+ const seqStr = id.slice(colon + 1);
1292
+ // Validate that seqStr is exactly a positive integer without leading zeros (e.g., 1, 123, not 01, 1junk, 1.5)
1293
+ if (!/^[1-9][0-9]*$/.test(seqStr)) return false;
1294
+ const seq = parseInt(seqStr, 10);
1295
+ if (Number.isNaN(seq)) return false;
1269
1296
  const rec = this.getSubagentRecord(subagentId);
1270
- if (rec?.status !== "queued") return false;
1297
+ if (rec?.status !== "queued" || rec.queued?.seq !== seq) return false;
1271
1298
  return this.cancelSubagent(subagentId, filter);
1272
1299
  }
1273
1300
  const job = this.#jobs.get(id);
@@ -1383,6 +1410,10 @@ export class AsyncJobManager {
1383
1410
  if (currentJob && record.currentJobGeneration === undefined) record.currentJobGeneration = currentJob.generation;
1384
1411
  this.#subagentRecords.set(record.subagentId, record);
1385
1412
  this.#notifyChange();
1413
+ // Drain stale entries in case this replacement invalidated queued resumes
1414
+ // for the previous owner/generation. Stale-entry retirement is independent
1415
+ // of capacity, so it happens immediately, not waiting for a free slot.
1416
+ this.#drainResumeQueue();
1386
1417
  }
1387
1418
 
1388
1419
  /**
@@ -1564,6 +1595,7 @@ export class AsyncJobManager {
1564
1595
  record,
1565
1596
  currentJobId: record.currentJobId,
1566
1597
  currentJobGeneration: record.currentJobGeneration,
1598
+ queuedSeq: record.status === "queued" ? record.queued?.seq : undefined,
1567
1599
  });
1568
1600
  if (this.#isTerminalSubagentStatus(record.status) && backingExecution?.physicallySettled !== false) continue;
1569
1601
  targets.set(record.subagentId, {
@@ -1675,20 +1707,23 @@ export class AsyncJobManager {
1675
1707
  for (const target of lease.targets) {
1676
1708
  const executions = state.backingExecutions.get(target.subagentId) ?? [];
1677
1709
  const capture = state.records.get(target.subagentId);
1678
- if (
1710
+ const record = capture?.record;
1711
+ const preserveQueuedRecord =
1679
1712
  target.source === "record" &&
1680
- capture &&
1681
- this.#subagentRecords.get(target.subagentId) === capture.record &&
1682
- capture.record.status === "queued"
1683
- ) {
1684
- this.cancelSubagent(target.subagentId, { ownerId: lease.ownerId });
1685
- }
1713
+ target.jobId === null &&
1714
+ capture !== undefined &&
1715
+ record !== undefined &&
1716
+ this.#subagentRecords.get(target.subagentId) === record &&
1717
+ record.ownerId === lease.ownerId &&
1718
+ record.currentJobId === capture.currentJobId &&
1719
+ record.currentJobGeneration === capture.currentJobGeneration &&
1720
+ record.status === "queued" &&
1721
+ record.queued?.seq === capture.queuedSeq;
1686
1722
  for (const execution of executions) {
1687
- this.#cancelExactExecution(execution);
1723
+ if (!preserveQueuedRecord) this.#cancelExactExecution(execution);
1688
1724
  promises.add(execution.physicalCompletion);
1689
1725
  }
1690
1726
  if (target.source !== "record" || executions.length > 0) continue;
1691
- const record = capture?.record;
1692
1727
  if (!capture || !record || this.#subagentRecords.get(target.subagentId) !== record) continue;
1693
1728
  if (record.status === "paused") {
1694
1729
  const row = record.currentJobId ? this.#jobs.get(record.currentJobId) : undefined;
@@ -1737,19 +1772,50 @@ export class AsyncJobManager {
1737
1772
  ): boolean {
1738
1773
  const capture = state.records.get(target.subagentId);
1739
1774
  const record = capture?.record;
1775
+ const physicallySettled = (state.backingExecutions.get(target.subagentId) ?? []).every(
1776
+ execution => execution.job.ownerId === ownerId && execution.physicallySettled,
1777
+ );
1778
+ if (!capture || !record) return false;
1779
+
1780
+ // A queued record is terminal only for the captured owner record and generation:
1781
+ // the lease fence keeps it queued until release or commit, and it must not be
1782
+ // cancelled merely to collect shutdown evidence.
1783
+ if (
1784
+ target.source === "record" &&
1785
+ target.jobId === null &&
1786
+ this.#subagentRecords.get(target.subagentId) === record &&
1787
+ record.ownerId === ownerId &&
1788
+ record.currentJobId === capture.currentJobId &&
1789
+ record.currentJobGeneration === capture.currentJobGeneration &&
1790
+ record.status === "queued" &&
1791
+ record.queued?.seq === capture.queuedSeq
1792
+ ) {
1793
+ return physicallySettled;
1794
+ }
1795
+
1740
1796
  if (
1741
- !record ||
1742
1797
  this.#subagentRecords.get(target.subagentId) !== record ||
1743
1798
  record.ownerId !== ownerId ||
1744
- record.currentJobId !== capture.currentJobId ||
1745
- record.currentJobGeneration !== capture.currentJobGeneration ||
1746
1799
  !this.#isTerminalSubagentStatus(record.status)
1747
1800
  ) {
1748
1801
  return false;
1749
1802
  }
1750
- return (state.backingExecutions.get(target.subagentId) ?? []).every(
1751
- execution => execution.job.ownerId === ownerId && execution.physicallySettled,
1752
- );
1803
+ if (capture.queuedSeq !== undefined) {
1804
+ const queuedGeneration = `queued:${target.subagentId}:${capture.queuedSeq}`;
1805
+ if (
1806
+ record.status !== "cancelled" ||
1807
+ record.terminalQueuedSeq !== capture.queuedSeq ||
1808
+ record.terminalGeneration !== queuedGeneration
1809
+ ) {
1810
+ return false;
1811
+ }
1812
+ } else if (
1813
+ record.currentJobId !== capture.currentJobId ||
1814
+ record.currentJobGeneration !== capture.currentJobGeneration
1815
+ ) {
1816
+ return false;
1817
+ }
1818
+ return physicallySettled;
1753
1819
  }
1754
1820
 
1755
1821
  #ownerSubagentShutdownProof(
@@ -1771,7 +1837,11 @@ export class AsyncJobManager {
1771
1837
  };
1772
1838
  }
1773
1839
 
1774
- finishOwnerSubagentShutdown(lease: OwnerSubagentShutdownLease, outcome: "commit" | "release"): void {
1840
+ finishOwnerSubagentShutdown(
1841
+ lease: OwnerSubagentShutdownLease,
1842
+ outcome: "commit" | "release",
1843
+ options?: { drainResumeQueue?: boolean },
1844
+ ): void {
1775
1845
  const state = this.#ownerSubagentShutdownLeases.get(lease.ownerId);
1776
1846
  if (!state || state.lease.id !== lease.id) return;
1777
1847
  if (outcome === "commit" && state.proof?.confirmed) {
@@ -1780,7 +1850,10 @@ export class AsyncJobManager {
1780
1850
  this.#purgeOwnerSubagentState(lease.ownerId);
1781
1851
  }
1782
1852
  this.#ownerSubagentShutdownLeases.delete(lease.ownerId);
1783
- if (outcome === "release") this.#ensureDeliveryLoop();
1853
+ if (outcome === "release") {
1854
+ if (options?.drainResumeQueue && !this.#disposed && !this.#registrationClosed) this.#drainResumeQueue();
1855
+ this.#ensureDeliveryLoop();
1856
+ }
1784
1857
  }
1785
1858
 
1786
1859
  #recordByJobId(jobId: string, expectedGeneration?: string): SubagentRecord | undefined {
@@ -1851,7 +1924,10 @@ export class AsyncJobManager {
1851
1924
  if (rec.status === "queued") {
1852
1925
  if (message !== undefined && rec.queued) {
1853
1926
  rec.queued.message = message;
1854
- const queued = this.#resumeQueue.find(entry => entry.subagentId === rec.subagentId);
1927
+ const queued = this.#resumeQueue.find(
1928
+ entry =>
1929
+ entry.subagentId === rec.subagentId && entry.ownerId === rec.ownerId && entry.seq === rec.queued?.seq,
1930
+ );
1855
1931
  if (queued) queued.message = message;
1856
1932
  return { ok: true, queued: true, status: "queued" };
1857
1933
  }
@@ -1864,12 +1940,28 @@ export class AsyncJobManager {
1864
1940
  if (!this.#resolveResumeRunner(rec, descriptor)) return { ok: false, reason: "no_runner" };
1865
1941
  if (this.getRunningJobs().length >= this.#maxRunningJobs) {
1866
1942
  const seq = ++this.#resumeSeq;
1943
+ rec.terminalQueuedSeq = undefined;
1867
1944
  rec.status = "queued";
1945
+ // Resolve the admission endpoint before queueing: when the manager is
1946
+ // rekeyed before the queue is drained or the subagent is cancelled, we
1947
+ // need the original endpoint to unregister the queued registration.
1948
+ let admissionEndpointId: string | undefined;
1949
+ let ownedRegistrationEndpointId: string | undefined;
1950
+ if (resumeToolCallId) {
1951
+ const managerEndpointId = AsyncJobManager.endpointIdOf(this);
1952
+ const lineage = resolveToolLineage(resumeToolCallId, managerEndpointId);
1953
+ if (lineage) {
1954
+ admissionEndpointId = lineage.endpointId ?? managerEndpointId;
1955
+ ownedRegistrationEndpointId = managerEndpointId ?? lineage.endpointId;
1956
+ }
1957
+ }
1868
1958
  rec.queued = {
1869
1959
  ownerId: rec.ownerId,
1870
1960
  seq,
1871
1961
  message,
1872
1962
  ...(resumeToolCallId ? { resumeToolCallId } : {}),
1963
+ ...(admissionEndpointId ? { admissionEndpointId } : {}),
1964
+ ...(ownedRegistrationEndpointId ? { ownedRegistrationEndpointId } : {}),
1873
1965
  createdAt: Date.now(),
1874
1966
  };
1875
1967
  this.#resumeQueue.push({
@@ -1878,6 +1970,8 @@ export class AsyncJobManager {
1878
1970
  seq,
1879
1971
  message,
1880
1972
  ...(resumeToolCallId ? { resumeToolCallId } : {}),
1973
+ ...(admissionEndpointId ? { admissionEndpointId } : {}),
1974
+ ...(ownedRegistrationEndpointId ? { ownedRegistrationEndpointId } : {}),
1881
1975
  createdAt: rec.queued.createdAt,
1882
1976
  });
1883
1977
  // Register the QUEUED generation as owned work of the resume request's
@@ -1890,7 +1984,7 @@ export class AsyncJobManager {
1890
1984
  if (lineage) {
1891
1985
  const queuedGeneration = `queued:${rec.subagentId}:${seq}`;
1892
1986
  registerOwnedRegistration({
1893
- ...(lineage.endpointId ? { endpointId: lineage.endpointId } : {}),
1987
+ ...(ownedRegistrationEndpointId ? { endpointId: ownedRegistrationEndpointId } : {}),
1894
1988
  lineageIdHash: lineage.lineageIdHash,
1895
1989
  promptAttemptEpoch: lineage.promptAttemptEpoch,
1896
1990
  endpointGeneration: lineage.endpointGeneration,
@@ -1916,21 +2010,82 @@ export class AsyncJobManager {
1916
2010
  if (registration) unregisterOwnedRegistration(registration);
1917
2011
  }
1918
2012
 
2013
+ #migrateQueuedResumeRegistrations(predecessorEndpointId: string, successorEndpointId: string): void {
2014
+ // Keep the admission endpoint for exact lineage lookup, but migrate each
2015
+ // queued ownership tuple with the manager so cleanup and resumed-job
2016
+ // ownership use the successor endpoint.
2017
+
2018
+ // Iterate through both the resume queue and subagent records to migrate all
2019
+ // entries whose owned registration is still at the predecessor endpoint.
2020
+ const processedSeqs = new Set<number>();
2021
+
2022
+ // First pass: migrate queue entries
2023
+ for (const queueEntry of this.#resumeQueue) {
2024
+ const queuedEndpointId =
2025
+ queueEntry.ownedRegistrationEndpointId ?? queueEntry.admissionEndpointId ?? predecessorEndpointId;
2026
+ if (queuedEndpointId === predecessorEndpointId) {
2027
+ processedSeqs.add(queueEntry.seq);
2028
+
2029
+ // Migrate the owned registration from E1 to E2
2030
+ const queuedGeneration = `queued:${queueEntry.subagentId}:${queueEntry.seq}`;
2031
+ const oldReg = lookupOwnedRegistration(queuedGeneration, queuedGeneration, queuedEndpointId);
2032
+ if (oldReg) {
2033
+ unregisterOwnedRegistration(oldReg);
2034
+ registerOwnedRegistration({
2035
+ endpointId: successorEndpointId,
2036
+ lineageIdHash: oldReg.lineageIdHash,
2037
+ promptAttemptEpoch: oldReg.promptAttemptEpoch,
2038
+ endpointGeneration: oldReg.endpointGeneration,
2039
+ jobId: queuedGeneration,
2040
+ jobGeneration: queuedGeneration,
2041
+ });
2042
+ }
2043
+ queueEntry.ownedRegistrationEndpointId = successorEndpointId;
2044
+ const rec = this.#subagentRecords.get(queueEntry.subagentId);
2045
+ if (rec?.queued?.seq === queueEntry.seq) rec.queued.ownedRegistrationEndpointId = successorEndpointId;
2046
+ }
2047
+ }
2048
+
2049
+ // Second pass: check subagent records for queued entries not in the queue
2050
+ // (e.g., entries that were queued before this rekey)
2051
+ for (const [, rec] of this.#subagentRecords) {
2052
+ if (rec.queued && !processedSeqs.has(rec.queued.seq)) {
2053
+ const queuedEndpointId =
2054
+ rec.queued.ownedRegistrationEndpointId ?? rec.queued.admissionEndpointId ?? predecessorEndpointId;
2055
+ if (queuedEndpointId === predecessorEndpointId) {
2056
+ // Migrate the owned registration
2057
+ const queuedGeneration = `queued:${rec.subagentId}:${rec.queued.seq}`;
2058
+ const oldReg = lookupOwnedRegistration(queuedGeneration, queuedGeneration, queuedEndpointId);
2059
+ if (oldReg) {
2060
+ unregisterOwnedRegistration(oldReg);
2061
+ registerOwnedRegistration({
2062
+ endpointId: successorEndpointId,
2063
+ lineageIdHash: oldReg.lineageIdHash,
2064
+ promptAttemptEpoch: oldReg.promptAttemptEpoch,
2065
+ endpointGeneration: oldReg.endpointGeneration,
2066
+ jobId: queuedGeneration,
2067
+ jobGeneration: queuedGeneration,
2068
+ });
2069
+ }
2070
+ rec.queued.ownedRegistrationEndpointId = successorEndpointId;
2071
+ }
2072
+ }
2073
+ }
2074
+ }
2075
+
1919
2076
  #retireQueuedOwned(rec: SubagentRecord): void {
1920
2077
  const seq = rec.queued?.seq ?? rec.terminalQueuedSeq;
1921
2078
  if (seq === undefined) return;
1922
2079
  const queuedGeneration = `queued:${rec.subagentId}:${seq}`;
1923
- // Resolve the registration with the resume lineage's ENDPOINT identity:
1924
- // task ids are session-scoped and each manager's resume sequence starts
1925
- // locally, so concurrent sessions can both register an identical
1926
- // queued:<subagent>:<seq> generation — an endpoint-less lookup could
1927
- // retrieve and unregister the OTHER session's tuple (review thread P1).
1928
- const endpointId = rec.queued?.resumeToolCallId
1929
- ? (resolveToolLineage(rec.queued.resumeToolCallId, AsyncJobManager.endpointIdOf(this))?.endpointId ??
1930
- // Fall back to the manager's own registered endpoint when the
1931
- // binding itself predates endpoint keying or is not found.
1932
- AsyncJobManager.endpointIdOf(this))
1933
- : AsyncJobManager.endpointIdOf(this);
2080
+ // The ownership endpoint follows the manager during rekey, while the
2081
+ // admission endpoint remains tied to the original tool lineage.
2082
+ const endpointId =
2083
+ rec.queued?.ownedRegistrationEndpointId ??
2084
+ rec.queued?.admissionEndpointId ??
2085
+ (rec.queued?.resumeToolCallId
2086
+ ? resolveToolLineage(rec.queued.resumeToolCallId, AsyncJobManager.endpointIdOf(this))?.endpointId
2087
+ : undefined) ??
2088
+ AsyncJobManager.endpointIdOf(this);
1934
2089
  const registration = lookupOwnedRegistration(queuedGeneration, queuedGeneration, endpointId);
1935
2090
  if (registration) unregisterOwnedRegistration(registration);
1936
2091
  }
@@ -1940,17 +2095,31 @@ export class AsyncJobManager {
1940
2095
  message: string | undefined,
1941
2096
  descriptor: ResumeDescriptor | undefined,
1942
2097
  resumeToolCallId?: string,
2098
+ admissionEndpointId?: string,
2099
+ ownedRegistrationEndpointId?: string,
1943
2100
  ): { ok: boolean; status?: SubagentLifecycle; jobId?: string; reason?: string } {
1944
2101
  if (this.#isOwnerSubagentShutdownFenced(rec.ownerId)) {
1945
2102
  return { ok: false, status: rec.status, reason: "owner_shutdown_in_progress" };
1946
2103
  }
1947
2104
  const prevJobId = rec.currentJobId;
1948
2105
  const queuedGeneration = rec.queued?.seq !== undefined ? `queued:${rec.subagentId}:${rec.queued.seq}` : undefined;
2106
+ const queuedEndpointId =
2107
+ ownedRegistrationEndpointId ??
2108
+ rec.queued?.ownedRegistrationEndpointId ??
2109
+ rec.queued?.admissionEndpointId ??
2110
+ AsyncJobManager.endpointIdOf(this);
1949
2111
  // Clear any retained progress from the previous run so a resumed subagent
1950
2112
  // never renders the prior run's tool/output as live before it emits again.
1951
2113
  this.#subagentProgress.delete(rec.subagentId);
1952
2114
  const runner = this.#resolveResumeRunner(rec, descriptor);
1953
- const newJobId = runner?.(rec.subagentId, message, descriptor, resumeToolCallId);
2115
+ const newJobId = runner?.(
2116
+ rec.subagentId,
2117
+ message,
2118
+ descriptor,
2119
+ resumeToolCallId,
2120
+ admissionEndpointId ?? rec.queued?.admissionEndpointId,
2121
+ ownedRegistrationEndpointId ?? queuedEndpointId,
2122
+ );
1954
2123
  if (!newJobId) {
1955
2124
  // The queued resume FAILED to start: retire its owned registration
1956
2125
  // so the tuple does not accumulate indefinitely (review thread P2).
@@ -1970,17 +2139,10 @@ export class AsyncJobManager {
1970
2139
  // causal set has exactly one tuple per job. The EXACT stored tuple
1971
2140
  // is looked up first — unregisterOwnedRegistration now verifies the
1972
2141
  // full five-tuple before deleting (review thread P1). The lookup is
1973
- // ENDPOINT-qualified via the resume lineage: concurrent sessions
1974
- // mint the same queued:<subagent>:<seq> generation, and the
1975
- // remaining endpoint-less fallback scan could retrieve and
1976
- // unregister the OTHER session's tuple (review thread P1).
1977
- const resumeEndpoint = resumeToolCallId
1978
- ? (resolveToolLineage(resumeToolCallId, AsyncJobManager.endpointIdOf(this))?.endpointId ??
1979
- // The binding may have been evicted (8192-cap FIFO): fall back
1980
- // to the manager's own endpoint so the lookup never degrades
1981
- // into the cross-endpoint scan (review thread P2).
1982
- AsyncJobManager.endpointIdOf(this))
1983
- : undefined;
2142
+ // ENDPOINT-qualified by the saved ownership endpoint. The tool lineage
2143
+ // is looked up at admissionEndpointId, but the queued registration may
2144
+ // have moved with the manager during rekey.
2145
+ const resumeEndpoint = queuedEndpointId;
1984
2146
  const queuedReg = lookupOwnedRegistration(queuedGeneration, queuedGeneration, resumeEndpoint);
1985
2147
  if (queuedReg) unregisterOwnedRegistration(queuedReg);
1986
2148
  }
@@ -1992,24 +2154,101 @@ export class AsyncJobManager {
1992
2154
  #drainResumeQueue(): void {
1993
2155
  if (this.#resumeQueue.length === 0) return;
1994
2156
  this.#resumeQueue.sort((a, b) => a.seq - b.seq);
2157
+
2158
+ // First pass: retire stale entries independently of capacity. This ensures
2159
+ // stale registrations are cleaned up even when the pool is saturated.
1995
2160
  let index = 0;
1996
- while (index < this.#resumeQueue.length && this.getRunningJobs().length < this.#maxRunningJobs) {
2161
+ while (index < this.#resumeQueue.length) {
1997
2162
  const entry = this.#resumeQueue[index];
1998
2163
  const rec = this.#subagentRecords.get(entry.subagentId);
1999
- if (rec?.status !== "queued") {
2164
+ if (
2165
+ rec?.status !== "queued" ||
2166
+ rec.ownerId !== entry.ownerId ||
2167
+ rec.queued?.ownerId !== entry.ownerId ||
2168
+ rec.queued?.seq !== entry.seq
2169
+ ) {
2170
+ // Retire the stale queued registration: the entry no longer matches
2171
+ // the current record state (owner changed, seq mismatch, or record
2172
+ // transitioned away from queued). Unregister its owned tuple so a
2173
+ // later owned abort does not see orphaned work.
2174
+ const staleQueuedGeneration = `queued:${entry.subagentId}:${entry.seq}`;
2175
+ // The tuple follows the manager's ownership endpoint during rekey;
2176
+ // the original admission endpoint remains solely for lineage lookup.
2177
+ const endpointId =
2178
+ entry.ownedRegistrationEndpointId ?? entry.admissionEndpointId ?? AsyncJobManager.endpointIdOf(this);
2179
+ const registration = lookupOwnedRegistration(staleQueuedGeneration, staleQueuedGeneration, endpointId);
2180
+ if (registration) unregisterOwnedRegistration(registration);
2181
+ // Mark the stale generation as terminal so waiters for that exact
2182
+ // generation do not block forever; do NOT update the current record
2183
+ // because it may belong to a different owner/generation.
2184
+ if (!this.#publishedTerminalGenerations.has(staleQueuedGeneration)) {
2185
+ this.#publishedTerminalGenerations.add(staleQueuedGeneration);
2186
+ this.#terminalEvents.set(staleQueuedGeneration, {
2187
+ generation: staleQueuedGeneration,
2188
+ jobId: null,
2189
+ subagentId: entry.subagentId,
2190
+ ownerId: entry.ownerId,
2191
+ status: "cancelled",
2192
+ createdAt: Date.now(),
2193
+ });
2194
+ // Notify any waiters for this exact generation.
2195
+ for (const state of this.#terminalWaits.values()) {
2196
+ if (
2197
+ state.targets.some(
2198
+ target =>
2199
+ target.generation === staleQueuedGeneration ||
2200
+ this.#waitGenerationAliases.get(target.generation) === staleQueuedGeneration,
2201
+ )
2202
+ )
2203
+ this.#maybeResolveWait(state);
2204
+ }
2205
+ }
2000
2206
  this.#resumeQueue.splice(index, 1);
2001
2207
  continue;
2002
2208
  }
2209
+ index += 1;
2210
+ }
2211
+
2212
+ // Second pass: resume valid entries while capacity allows. By this point,
2213
+ // all stale entries have been retired, so we only process entries that
2214
+ // currently match their record state.
2215
+ index = 0;
2216
+ while (index < this.#resumeQueue.length && this.getRunningJobs().length < this.#maxRunningJobs) {
2217
+ const entry = this.#resumeQueue[index];
2218
+ const rec = this.#subagentRecords.get(entry.subagentId);
2219
+ if (rec?.status !== "queued" || rec.ownerId !== entry.ownerId || rec.queued?.seq !== entry.seq) {
2220
+ // This should not happen because we cleaned up all stale entries
2221
+ // in the first pass, but skip if we encounter an unexpected state.
2222
+ index += 1;
2223
+ continue;
2224
+ }
2003
2225
  if (this.#isOwnerSubagentShutdownFenced(entry.ownerId)) {
2004
2226
  index += 1;
2005
2227
  continue;
2006
2228
  }
2229
+ let priorExecutionActive = false;
2230
+ for (const execution of this.#activeExecutions) {
2231
+ if (
2232
+ execution.job.ownerId === entry.ownerId &&
2233
+ execution.job.metadata?.subagent?.id === entry.subagentId &&
2234
+ !execution.physicallySettled
2235
+ ) {
2236
+ priorExecutionActive = true;
2237
+ break;
2238
+ }
2239
+ }
2240
+ if (priorExecutionActive) {
2241
+ index += 1;
2242
+ continue;
2243
+ }
2007
2244
  try {
2008
2245
  const result = this.#startResume(
2009
2246
  rec,
2010
2247
  entry.message,
2011
2248
  this.#descriptorForRecord(rec),
2012
2249
  rec.queued?.resumeToolCallId,
2250
+ entry.admissionEndpointId,
2251
+ entry.ownedRegistrationEndpointId,
2013
2252
  );
2014
2253
  if (!result.ok) {
2015
2254
  if (result.reason === "owner_shutdown_in_progress") {
@@ -2065,8 +2304,10 @@ export class AsyncJobManager {
2065
2304
  return true;
2066
2305
  }
2067
2306
  if (rec.status === "queued") {
2068
- const idx = this.#resumeQueue.findIndex(e => e.subagentId === rec.subagentId);
2069
2307
  const queuedSeq = rec.queued?.seq;
2308
+ const idx = this.#resumeQueue.findIndex(
2309
+ e => e.subagentId === rec.subagentId && e.ownerId === rec.ownerId && e.seq === queuedSeq,
2310
+ );
2070
2311
  if (idx !== -1) this.#resumeQueue.splice(idx, 1);
2071
2312
  rec.status = "cancelled";
2072
2313
  if (queuedSeq !== undefined) {
@@ -2089,16 +2330,57 @@ export class AsyncJobManager {
2089
2330
 
2090
2331
  #purgeOwnerSubagentState(ownerId?: string): void {
2091
2332
  for (let i = this.#resumeQueue.length - 1; i >= 0; i--) {
2092
- if (!ownerId || this.#resumeQueue[i].ownerId === ownerId) this.#resumeQueue.splice(i, 1);
2333
+ const entry = this.#resumeQueue[i];
2334
+ if (!ownerId || entry.ownerId === ownerId) {
2335
+ // Retire stale queued registrations BEFORE removing the queue entry:
2336
+ // the purge removes the entry with no start, cancellation, or delivery
2337
+ // boundary, so the registration would otherwise leak into the global
2338
+ // ownership registries and eventually make later owned aborts fail
2339
+ // closed (review thread P2). Use the saved ownership endpoint after
2340
+ // rekey, not the immutable lineage/admission endpoint.
2341
+ const staleQueuedGeneration = `queued:${entry.subagentId}:${entry.seq}`;
2342
+ const endpointId =
2343
+ entry.ownedRegistrationEndpointId ?? entry.admissionEndpointId ?? AsyncJobManager.endpointIdOf(this);
2344
+ const registration = lookupOwnedRegistration(staleQueuedGeneration, staleQueuedGeneration, endpointId);
2345
+ if (registration) unregisterOwnedRegistration(registration);
2346
+ // Also publish a terminal event for this generation so any waiters know it won't resume.
2347
+ if (!this.#publishedTerminalGenerations.has(staleQueuedGeneration)) {
2348
+ this.#publishedTerminalGenerations.add(staleQueuedGeneration);
2349
+ this.#terminalEvents.set(staleQueuedGeneration, {
2350
+ generation: staleQueuedGeneration,
2351
+ jobId: null,
2352
+ subagentId: entry.subagentId,
2353
+ ownerId: entry.ownerId,
2354
+ status: "cancelled",
2355
+ createdAt: Date.now(),
2356
+ });
2357
+ // Notify any waiters for this exact generation.
2358
+ for (const state of this.#terminalWaits.values()) {
2359
+ if (
2360
+ state.targets.some(
2361
+ target =>
2362
+ target.generation === staleQueuedGeneration ||
2363
+ this.#waitGenerationAliases.get(target.generation) === staleQueuedGeneration,
2364
+ )
2365
+ )
2366
+ this.#maybeResolveWait(state);
2367
+ }
2368
+ }
2369
+ this.#resumeQueue.splice(i, 1);
2370
+ }
2093
2371
  }
2094
2372
  for (const [sid, rec] of this.#subagentRecords) {
2095
2373
  if (!ownerId || rec.ownerId === ownerId) {
2374
+ const queuedSeq = rec.status === "queued" ? rec.queued?.seq : undefined;
2375
+ if (queuedSeq !== undefined) {
2376
+ rec.terminalQueuedSeq = queuedSeq;
2377
+ this.#publishQueuedTerminal(sid, `queued:${sid}:${queuedSeq}`, "cancelled");
2378
+ }
2096
2379
  // Retire the queued resume's owned registration BEFORE deleting the
2097
- // record: the purge removes the resume-queue entry and record with
2098
- // no start, cancellation, or delivery boundary, so the registration
2099
- // would otherwise leak into the global ownership registries and
2100
- // eventually make later owned aborts fail closed (review thread
2101
- // P2). #retireQueuedOwned is a no-op for non-queued records.
2380
+ // record: #publishQueuedTerminal above is the wait-notification
2381
+ // boundary for queued work; this removes the corresponding ownership
2382
+ // tuple so it cannot leak into later owned aborts. The helper is a
2383
+ // no-op for non-queued records.
2102
2384
  this.#retireQueuedOwned(rec);
2103
2385
  this.#liveHandles.delete(sid);
2104
2386
  this.#resumeDescriptors.delete(sid);
@@ -2114,6 +2396,24 @@ export class AsyncJobManager {
2114
2396
  const colon = id.lastIndexOf(":");
2115
2397
  const subagentId = colon > "queued:".length ? id.slice("queued:".length, colon) : undefined;
2116
2398
  if (!subagentId) return undefined;
2399
+
2400
+ // Check for stale queued tombstone in terminal events first.
2401
+ // If a queued generation was retired as stale, it's recorded here
2402
+ // for owned settlement to find even if the subagent record is gone.
2403
+ const tombstoneEvent = this.#terminalEvents.get(id);
2404
+ if (tombstoneEvent && id.startsWith(`queued:${subagentId}:`)) {
2405
+ return {
2406
+ id,
2407
+ generation: id,
2408
+ type: "task",
2409
+ status: tombstoneEvent.status as "cancelled" | "failed",
2410
+ startTime: tombstoneEvent.createdAt,
2411
+ label: `stale queued resume ${subagentId}`,
2412
+ abortController: new AbortController(),
2413
+ promise: Promise.resolve(),
2414
+ };
2415
+ }
2416
+
2117
2417
  const rec = this.getSubagentRecord(subagentId);
2118
2418
  if (!rec) return undefined;
2119
2419
  const liveSeq = rec.queued?.seq;