@gajae-code/coding-agent 0.13.2 → 0.14.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 (610) hide show
  1. package/CHANGELOG.md +215 -8
  2. package/README.md +2 -0
  3. package/bin/gajaessi.js +4 -0
  4. package/dist/types/async/job-manager.d.ts +55 -2
  5. package/dist/types/async/support.d.ts +14 -0
  6. package/dist/types/capability/mcp.d.ts +6 -0
  7. package/dist/types/capability/types.d.ts +6 -0
  8. package/dist/types/cli/accounts-cli.d.ts +19 -0
  9. package/dist/types/cli/args.d.ts +3 -0
  10. package/dist/types/cli/config-cli.d.ts +6 -0
  11. package/dist/types/cli/crash-cli.d.ts +10 -0
  12. package/dist/types/cli/customize-doctor.d.ts +145 -0
  13. package/dist/types/cli/daemon-cli.d.ts +3 -1
  14. package/dist/types/cli/mcp-cli.d.ts +11 -0
  15. package/dist/types/cli/notify-cli.d.ts +1 -1
  16. package/dist/types/cli/root-flags.d.ts +12 -0
  17. package/dist/types/cli/setup-cli.d.ts +3 -1
  18. package/dist/types/cli/skills-cli.d.ts +2 -1
  19. package/dist/types/cli/update-cli.d.ts +19 -1
  20. package/dist/types/cli.d.ts +6 -0
  21. package/dist/types/commands/accounts.d.ts +40 -0
  22. package/dist/types/commands/crash.d.ts +24 -0
  23. package/dist/types/commands/customize.d.ts +22 -0
  24. package/dist/types/commands/daemon.d.ts +1 -1
  25. package/dist/types/commands/launch.d.ts +6 -0
  26. package/dist/types/commands/setup.d.ts +6 -0
  27. package/dist/types/commands/skills.d.ts +6 -1
  28. package/dist/types/commands/update.d.ts +7 -0
  29. package/dist/types/config/atomic-yaml-patch-worker.d.ts +25 -0
  30. package/dist/types/config/atomic-yaml-patch.d.ts +62 -2
  31. package/dist/types/config/config-schema-version.d.ts +10 -0
  32. package/dist/types/config/file-lock.d.ts +18 -0
  33. package/dist/types/config/model-profile-activation.d.ts +3 -1
  34. package/dist/types/config/model-registry.d.ts +10 -1
  35. package/dist/types/config/models-config-schema.d.ts +2 -0
  36. package/dist/types/config/settings-schema.d.ts +73 -9
  37. package/dist/types/config/settings.d.ts +63 -0
  38. package/dist/types/config/skill-settings-defaults.d.ts +26 -0
  39. package/dist/types/config/task-delegation.d.ts +39 -0
  40. package/dist/types/config/update-channel.d.ts +10 -0
  41. package/dist/types/coordinator-mcp/server.d.ts +5 -1
  42. package/dist/types/crash/index-store.d.ts +87 -0
  43. package/dist/types/crash/nudge.d.ts +50 -0
  44. package/dist/types/crash/record-loader.d.ts +17 -0
  45. package/dist/types/crash/report.d.ts +134 -0
  46. package/dist/types/crash/sanitize.d.ts +25 -0
  47. package/dist/types/cursor.d.ts +13 -2
  48. package/dist/types/customization/import.d.ts +37 -0
  49. package/dist/types/customization/index.d.ts +4 -0
  50. package/dist/types/customization/inventory.d.ts +25 -0
  51. package/dist/types/customization/mutations.d.ts +32 -0
  52. package/dist/types/customization/types.d.ts +115 -0
  53. package/dist/types/daemon/control-types.d.ts +8 -1
  54. package/dist/types/daemon/operator-contract.d.ts +4 -4
  55. package/dist/types/discovery/claude.d.ts +39 -1
  56. package/dist/types/discovery/codex.d.ts +39 -1
  57. package/dist/types/discovery/index.d.ts +2 -0
  58. package/dist/types/discovery/mcp-compat.d.ts +30 -0
  59. package/dist/types/edit/renderer.d.ts +23 -0
  60. package/dist/types/extensibility/extensions/runner.d.ts +3 -0
  61. package/dist/types/extensibility/extensions/types.d.ts +21 -4
  62. package/dist/types/extensibility/gjc-plugins/observability.d.ts +6 -1
  63. package/dist/types/extensibility/gjc-plugins/registry.d.ts +8 -1
  64. package/dist/types/extensibility/hooks/loader.d.ts +19 -5
  65. package/dist/types/extensibility/runtime-skill-discovery.d.ts +22 -1
  66. package/dist/types/extensibility/skill-management.d.ts +107 -0
  67. package/dist/types/gjc-runtime/deep-interview-runtime.d.ts +3 -1
  68. package/dist/types/gjc-runtime/launch-worktree.d.ts +8 -0
  69. package/dist/types/gjc-runtime/linux-proc.d.ts +3 -0
  70. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +10 -6
  71. package/dist/types/gjc-runtime/session-state-lock.d.ts +104 -0
  72. package/dist/types/gjc-runtime/session-state-sidecar.d.ts +120 -2
  73. package/dist/types/gjc-runtime/state-writer.d.ts +10 -1
  74. package/dist/types/gjc-runtime/ultragoal-guard.d.ts +4 -2
  75. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +8 -6
  76. package/dist/types/gjc-runtime/workflow-settings.d.ts +109 -0
  77. package/dist/types/hooks/events.d.ts +60 -0
  78. package/dist/types/hooks/normalize.d.ts +83 -0
  79. package/dist/types/hooks/skill-state.d.ts +9 -2
  80. package/dist/types/main.d.ts +2 -1
  81. package/dist/types/modes/acp/abort-scope.d.ts +13 -0
  82. package/dist/types/modes/acp/acp-agent.d.ts +7 -4
  83. package/dist/types/modes/components/customization/customization-dashboard.d.ts +44 -0
  84. package/dist/types/modes/components/customization/import-wizard.d.ts +21 -0
  85. package/dist/types/modes/components/customization/index.d.ts +1 -0
  86. package/dist/types/modes/components/dynamic-theme-text.d.ts +9 -0
  87. package/dist/types/modes/components/gajae-pet-widget.d.ts +6 -28
  88. package/dist/types/modes/components/irc-sidebar.d.ts +1 -0
  89. package/dist/types/modes/components/iterm-pet-transport.d.ts +122 -0
  90. package/dist/types/modes/components/oauth-selector.d.ts +10 -4
  91. package/dist/types/modes/components/pet-capability.d.ts +7 -21
  92. package/dist/types/modes/components/settings-selector.d.ts +5 -0
  93. package/dist/types/modes/components/status-line/gh.d.ts +6 -2
  94. package/dist/types/modes/controllers/extension-ui-controller.d.ts +1 -1
  95. package/dist/types/modes/controllers/selector-controller.d.ts +7 -8
  96. package/dist/types/modes/controllers/stt-controller.d.ts +1 -1
  97. package/dist/types/modes/interactive-mode.d.ts +1 -0
  98. package/dist/types/modes/theme/defaults/index.d.ts +118 -0
  99. package/dist/types/modes/types.d.ts +1 -0
  100. package/dist/types/modes/utils/injected-user-submission.d.ts +10 -0
  101. package/dist/types/runtime-mcp/config.d.ts +9 -0
  102. package/dist/types/runtime-mcp/manager.d.ts +26 -1
  103. package/dist/types/runtime-mcp/oauth-discovery.d.ts +4 -0
  104. package/dist/types/runtime-mcp/oauth-flow.d.ts +24 -1
  105. package/dist/types/runtime-mcp/pool-key.d.ts +2 -0
  106. package/dist/types/runtime-mcp/protocol.d.ts +278 -0
  107. package/dist/types/runtime-mcp/scope-config.d.ts +29 -0
  108. package/dist/types/runtime-mcp/tool-bridge.d.ts +15 -13
  109. package/dist/types/runtime-mcp/transports/http.d.ts +9 -0
  110. package/dist/types/runtime-mcp/types.d.ts +102 -10
  111. package/dist/types/sdk/acp/adapter.d.ts +26 -5
  112. package/dist/types/sdk/broker/broker.d.ts +3 -55
  113. package/dist/types/sdk/broker/lifecycle.d.ts +18 -12
  114. package/dist/types/sdk/broker/session-index.d.ts +14 -5
  115. package/dist/types/sdk/broker/startup-failure.d.ts +16 -1
  116. package/dist/types/sdk/bus/chat-daemon-control.d.ts +51 -1
  117. package/dist/types/sdk/bus/chat-daemon-runtime.d.ts +4 -41
  118. package/dist/types/sdk/bus/config.d.ts +11 -0
  119. package/dist/types/sdk/bus/conversation-store.d.ts +0 -1
  120. package/dist/types/sdk/bus/daemon-paths.d.ts +6 -0
  121. package/dist/types/sdk/bus/discord-conversation.d.ts +2 -0
  122. package/dist/types/sdk/bus/discord-daemon.d.ts +17 -12
  123. package/dist/types/sdk/bus/existing-thread-readiness.d.ts +4 -34
  124. package/dist/types/sdk/bus/inbound-reaction-ordering.d.ts +61 -0
  125. package/dist/types/sdk/bus/index.d.ts +85 -99
  126. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +29 -7
  127. package/dist/types/sdk/bus/managed-daemon.d.ts +2 -32
  128. package/dist/types/sdk/bus/notification-service.d.ts +52 -0
  129. package/dist/types/sdk/bus/operator-runtime.d.ts +1 -1
  130. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +16 -5
  131. package/dist/types/sdk/bus/reconciliation-store.d.ts +113 -5
  132. package/dist/types/sdk/bus/session-control.d.ts +25 -1
  133. package/dist/types/sdk/bus/slack-conversation.d.ts +4 -0
  134. package/dist/types/sdk/bus/slack-daemon.d.ts +9 -17
  135. package/dist/types/sdk/bus/slack-live-provider.d.ts +3 -1
  136. package/dist/types/sdk/bus/slack-provider.d.ts +4 -0
  137. package/dist/types/sdk/bus/slack-thread-binding.d.ts +6 -40
  138. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +30 -104
  139. package/dist/types/sdk/bus/telegram-daemon-cli.d.ts +10 -0
  140. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +262 -19
  141. package/dist/types/sdk/bus/telegram-daemon-orphan-reap.d.ts +125 -0
  142. package/dist/types/sdk/bus/telegram-daemon-owner-registry.d.ts +22 -0
  143. package/dist/types/sdk/bus/telegram-daemon.d.ts +119 -310
  144. package/dist/types/sdk/bus/telegram-reference.d.ts +1 -47
  145. package/dist/types/sdk/bus/threaded-render.d.ts +1 -0
  146. package/dist/types/sdk/bus/topic-registry.d.ts +33 -17
  147. package/dist/types/sdk/cli/rows.d.ts +2 -11
  148. package/dist/types/sdk/cli/session-cli.d.ts +15 -25
  149. package/dist/types/sdk/client/client.d.ts +97 -1
  150. package/dist/types/sdk/host/control/dispatch.d.ts +0 -7
  151. package/dist/types/sdk/host/control/index.d.ts +1 -1
  152. package/dist/types/sdk/host/control/operations.d.ts +18 -5
  153. package/dist/types/sdk/host/control/runtime-gate.d.ts +19 -0
  154. package/dist/types/sdk/host/host.d.ts +15 -1
  155. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  156. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  157. package/dist/types/sdk/host/session-runtime.d.ts +127 -7
  158. package/dist/types/sdk/host/types.d.ts +1 -1
  159. package/dist/types/sdk/host/websocket-transport.d.ts +1 -1
  160. package/dist/types/sdk/index.d.ts +2 -1
  161. package/dist/types/sdk/lifecycle/broker-client.d.ts +8 -0
  162. package/dist/types/sdk/lifecycle/client.d.ts +59 -0
  163. package/dist/types/sdk/lifecycle/index.d.ts +3 -0
  164. package/dist/types/sdk/lifecycle/service.d.ts +232 -0
  165. package/dist/types/sdk/mcp/server.d.ts +9 -6
  166. package/dist/types/sdk/prompt-deadline-lease.d.ts +43 -0
  167. package/dist/types/sdk/prompt-deadline-manager.d.ts +28 -0
  168. package/dist/types/sdk/prompt-status.d.ts +10 -3
  169. package/dist/types/sdk/protocol/operation-registry.d.ts +5 -0
  170. package/dist/types/sdk/receipt-state.d.ts +17 -0
  171. package/dist/types/sdk/reconciliation-extensions.d.ts +2 -0
  172. package/dist/types/sdk/router/index.d.ts +1 -0
  173. package/dist/types/sdk/router/session-router.d.ts +152 -0
  174. package/dist/types/sdk/session-list.d.ts +22 -0
  175. package/dist/types/sdk/session-reconnect.d.ts +43 -1
  176. package/dist/types/sdk/session.d.ts +34 -1
  177. package/dist/types/sdk/transport/serve-cli.d.ts +1 -4
  178. package/dist/types/session/account-inventory.d.ts +86 -0
  179. package/dist/types/session/agent-session.d.ts +145 -6
  180. package/dist/types/session/agent-storage.d.ts +10 -0
  181. package/dist/types/session/coordinator-tool-label.d.ts +28 -0
  182. package/dist/types/session/internal/managed-session-storage.d.ts +23 -4
  183. package/dist/types/session/messages.d.ts +1 -1
  184. package/dist/types/session/secure-token-file.d.ts +3 -0
  185. package/dist/types/session/session-manager.d.ts +47 -1
  186. package/dist/types/session/startup-auth-config.d.ts +44 -0
  187. package/dist/types/session/terminal-abort.d.ts +378 -0
  188. package/dist/types/session/yield-queue.d.ts +7 -0
  189. package/dist/types/session-import/claude.d.ts +26 -0
  190. package/dist/types/session-import/codex.d.ts +88 -0
  191. package/dist/types/session-import/command.d.ts +35 -0
  192. package/dist/types/session-import/detect.d.ts +22 -0
  193. package/dist/types/session-import/index.d.ts +7 -0
  194. package/dist/types/session-import/provider-codex.d.ts +22 -0
  195. package/dist/types/session-import/provider-service.d.ts +38 -0
  196. package/dist/types/session-import/redact.d.ts +23 -0
  197. package/dist/types/session-import/service.d.ts +47 -0
  198. package/dist/types/session-import/types.d.ts +120 -0
  199. package/dist/types/setup/paseo/check.d.ts +16 -0
  200. package/dist/types/setup/paseo/install-saga.d.ts +115 -0
  201. package/dist/types/setup/paseo/json-publisher.d.ts +76 -0
  202. package/dist/types/setup/paseo/orchestration-preferences.d.ts +24 -0
  203. package/dist/types/setup/paseo/paseo-ownership.d.ts +87 -0
  204. package/dist/types/setup/paseo/paseo-setup.d.ts +29 -0
  205. package/dist/types/setup/paseo/provider-config.d.ts +35 -0
  206. package/dist/types/setup/paseo/remove.d.ts +14 -0
  207. package/dist/types/setup/paseo/result-types.d.ts +59 -0
  208. package/dist/types/setup/paseo/setup-deps.d.ts +73 -0
  209. package/dist/types/setup/paseo/skills-bridge.d.ts +44 -0
  210. package/dist/types/skill-state/active-state.d.ts +5 -1
  211. package/dist/types/slash-commands/acp-builtins.d.ts +5 -1
  212. package/dist/types/slash-commands/builtin-registry.d.ts +2 -0
  213. package/dist/types/slash-commands/helpers/credential-switch.d.ts +12 -0
  214. package/dist/types/slash-commands/helpers/usage-report.d.ts +5 -6
  215. package/dist/types/slash-commands/types.d.ts +7 -1
  216. package/dist/types/task/executor.d.ts +10 -0
  217. package/dist/types/task/index.d.ts +3 -1
  218. package/dist/types/tools/ask-answer-registry.d.ts +6 -3
  219. package/dist/types/tools/bash-sleep-advisory.d.ts +14 -0
  220. package/dist/types/tools/bash.d.ts +2 -6
  221. package/dist/types/tools/browser/attach.d.ts +11 -0
  222. package/dist/types/tools/browser/launch.d.ts +20 -0
  223. package/dist/types/tools/browser/profile-discovery.d.ts +6 -0
  224. package/dist/types/tools/browser/render.d.ts +1 -0
  225. package/dist/types/tools/browser.d.ts +3 -1
  226. package/dist/types/tools/image-gen.d.ts +10 -2
  227. package/dist/types/tools/index.d.ts +8 -0
  228. package/dist/types/tools/monitor.d.ts +1 -1
  229. package/dist/types/tools/skill-discovery.d.ts +8 -1
  230. package/dist/types/tools/subagent.d.ts +2 -0
  231. package/dist/types/tools/ultragoal-ask-guard.d.ts +2 -2
  232. package/dist/types/utils/herdr-pane.d.ts +76 -0
  233. package/package.json +19 -9
  234. package/scripts/build-binary.ts +2 -1
  235. package/scripts/build-sdk-package-smoke.ts +2 -13
  236. package/scripts/capture-sticky-viewport-showcase.ts +0 -1
  237. package/scripts/compile-args.ts +2 -0
  238. package/scripts/generate-sdk-adapter-parity-manifest.ts +31 -16
  239. package/scripts/generate-sdk-operation-inventory.ts +28 -0
  240. package/scripts/generate-telegram-baseline-manifest.ts +1 -0
  241. package/scripts/generate-tool-catalog.ts +6 -4
  242. package/scripts/verify-gjc-sdk-canonicalization.ts +5 -39
  243. package/src/async/job-manager.ts +352 -17
  244. package/src/async/support.ts +25 -0
  245. package/src/autoresearch/dashboard.ts +28 -13
  246. package/src/capability/index.ts +3 -1
  247. package/src/capability/mcp.ts +6 -0
  248. package/src/capability/types.ts +6 -0
  249. package/src/cli/accounts-cli.ts +395 -0
  250. package/src/cli/args.ts +23 -0
  251. package/src/cli/auth-broker-cli.ts +136 -66
  252. package/src/cli/auth-gateway-cli.ts +94 -70
  253. package/src/cli/config-cli.ts +27 -2
  254. package/src/cli/crash-cli.ts +78 -0
  255. package/src/cli/customize-doctor.ts +1605 -0
  256. package/src/cli/daemon-cli.ts +12 -3
  257. package/src/cli/fast-help.ts +1 -0
  258. package/src/cli/mcp-cli.ts +128 -60
  259. package/src/cli/notify-cli.ts +8 -14
  260. package/src/cli/root-flags.ts +8 -0
  261. package/src/cli/setup-cli.ts +145 -0
  262. package/src/cli/skills-cli.ts +38 -2
  263. package/src/cli/update-cli.ts +167 -12
  264. package/src/cli.ts +4 -0
  265. package/src/commands/accounts.ts +71 -0
  266. package/src/commands/crash.ts +35 -0
  267. package/src/commands/customize.ts +84 -0
  268. package/src/commands/daemon.ts +2 -2
  269. package/src/commands/deep-interview.ts +2 -0
  270. package/src/commands/launch.ts +1 -0
  271. package/src/commands/mcp.ts +12 -7
  272. package/src/commands/ralplan.ts +2 -0
  273. package/src/commands/sdk.ts +65 -32
  274. package/src/commands/setup.ts +5 -0
  275. package/src/commands/skills.ts +14 -4
  276. package/src/commands/ultragoal.ts +10 -1
  277. package/src/commands/update.ts +26 -15
  278. package/src/config/atomic-yaml-patch-worker.ts +57 -0
  279. package/src/config/atomic-yaml-patch.ts +769 -29
  280. package/src/config/config-schema-version.ts +10 -0
  281. package/src/config/file-lock.ts +70 -1
  282. package/src/config/mcp-schema.json +52 -14
  283. package/src/config/model-profile-activation.ts +23 -2
  284. package/src/config/model-profiles.ts +88 -21
  285. package/src/config/model-registry.ts +147 -21
  286. package/src/config/models-config-schema.ts +1 -1
  287. package/src/config/settings-schema.ts +77 -7
  288. package/src/config/settings.ts +4038 -199
  289. package/src/config/skill-settings-defaults.ts +41 -3
  290. package/src/config/task-delegation.ts +87 -0
  291. package/src/config/update-channel.ts +23 -0
  292. package/src/coordinator-mcp/server.ts +377 -308
  293. package/src/crash/index-store.ts +723 -0
  294. package/src/crash/nudge.ts +89 -0
  295. package/src/crash/record-loader.ts +149 -0
  296. package/src/crash/report.ts +551 -0
  297. package/src/crash/sanitize.ts +129 -0
  298. package/src/cursor.ts +36 -10
  299. package/src/customization/import.ts +946 -0
  300. package/src/customization/index.ts +4 -0
  301. package/src/customization/inventory.ts +335 -0
  302. package/src/customization/mutations.ts +141 -0
  303. package/src/customization/types.ts +223 -0
  304. package/src/daemon/control-types.ts +8 -1
  305. package/src/daemon/operator-contract.ts +6 -6
  306. package/src/defaults/gjc/extensions/grok-cli-vendor/src/models/catalog.ts +31 -6
  307. package/src/defaults/gjc/skills/deep-interview/SKILL.md +11 -22
  308. package/src/defaults/gjc/skills/ralplan/SKILL.md +36 -23
  309. package/src/defaults/gjc/skills/ultragoal/SKILL.md +16 -0
  310. package/src/discovery/claude-hooks.ts +38 -0
  311. package/src/discovery/claude.ts +142 -209
  312. package/src/discovery/codex-hooks.ts +38 -0
  313. package/src/discovery/codex.ts +132 -198
  314. package/src/discovery/helpers.ts +8 -1
  315. package/src/discovery/index.ts +2 -0
  316. package/src/discovery/mcp-compat.ts +234 -0
  317. package/src/discovery/mcp-json.ts +6 -0
  318. package/src/edit/renderer.ts +40 -0
  319. package/src/edit/streaming.ts +4 -2
  320. package/src/exec/bash-executor.ts +24 -20
  321. package/src/export/html/index.ts +51 -2
  322. package/src/extensibility/extensions/loader.ts +9 -1
  323. package/src/extensibility/extensions/runner.ts +17 -4
  324. package/src/extensibility/extensions/types.ts +31 -6
  325. package/src/extensibility/gjc-plugins/constrained-hooks.ts +33 -0
  326. package/src/extensibility/gjc-plugins/installer.ts +9 -5
  327. package/src/extensibility/gjc-plugins/observability.ts +8 -2
  328. package/src/extensibility/gjc-plugins/registry.ts +13 -2
  329. package/src/extensibility/gjc-plugins/runtime-adapters.ts +3 -1
  330. package/src/extensibility/gjc-plugins/schema.ts +222 -39
  331. package/src/extensibility/hooks/loader.ts +154 -10
  332. package/src/extensibility/runtime-skill-discovery.ts +257 -60
  333. package/src/extensibility/skill-management.ts +368 -0
  334. package/src/extensibility/skills.ts +52 -16
  335. package/src/gjc-runtime/deep-interview-runtime.ts +45 -61
  336. package/src/gjc-runtime/launch-worktree.ts +56 -2
  337. package/src/gjc-runtime/linux-proc.ts +7 -1
  338. package/src/gjc-runtime/ralplan-runtime.ts +100 -164
  339. package/src/gjc-runtime/session-state-lock.ts +992 -0
  340. package/src/gjc-runtime/session-state-sidecar.ts +644 -142
  341. package/src/gjc-runtime/state-writer.ts +39 -14
  342. package/src/gjc-runtime/tmux-owner-isolation.ts +5 -4
  343. package/src/gjc-runtime/ultragoal-guard.ts +11 -4
  344. package/src/gjc-runtime/ultragoal-runtime.ts +44 -41
  345. package/src/gjc-runtime/workflow-settings.ts +596 -0
  346. package/src/goals/tools/goal-tool.ts +6 -1
  347. package/src/hooks/events.ts +291 -0
  348. package/src/hooks/native-skill-hook.ts +4 -0
  349. package/src/hooks/normalize.ts +369 -0
  350. package/src/hooks/skill-state.ts +165 -46
  351. package/src/internal-urls/docs-index.generated.ts +41 -32
  352. package/src/main.ts +36 -5
  353. package/src/migrate/mcp-mapper.ts +25 -2
  354. package/src/modes/acp/abort-scope.ts +29 -0
  355. package/src/modes/acp/acp-agent.ts +423 -98
  356. package/src/modes/components/assistant-message.ts +6 -1
  357. package/src/modes/components/chrome.ts +4 -3
  358. package/src/modes/components/customization/customization-dashboard.ts +443 -0
  359. package/src/modes/components/customization/import-wizard.ts +372 -0
  360. package/src/modes/components/customization/index.ts +1 -0
  361. package/src/modes/components/dynamic-theme-text.ts +22 -0
  362. package/src/modes/components/gajae-pet-widget.ts +607 -193
  363. package/src/modes/components/irc-sidebar.ts +11 -0
  364. package/src/modes/components/iterm-pet-transport.ts +585 -0
  365. package/src/modes/components/model-selector.ts +1 -6
  366. package/src/modes/components/oauth-selector.ts +277 -86
  367. package/src/modes/components/pet-capability.ts +35 -31
  368. package/src/modes/components/settings-selector.ts +29 -5
  369. package/src/modes/components/status-line/gh.ts +45 -1
  370. package/src/modes/components/status-line/segments.ts +19 -4
  371. package/src/modes/components/tool-status-header.ts +13 -3
  372. package/src/modes/controllers/command-controller.ts +20 -8
  373. package/src/modes/controllers/extension-ui-controller.ts +56 -257
  374. package/src/modes/controllers/input-controller.ts +10 -0
  375. package/src/modes/controllers/runtime-mcp-command-controller.ts +16 -3
  376. package/src/modes/controllers/selector-controller.ts +238 -77
  377. package/src/modes/controllers/stt-controller.ts +3 -3
  378. package/src/modes/interactive-mode.ts +88 -16
  379. package/src/modes/print-mode.ts +35 -6
  380. package/src/modes/runtime-init.ts +21 -68
  381. package/src/modes/theme/defaults/index.ts +2 -0
  382. package/src/modes/theme/defaults/ouroboros.json +118 -0
  383. package/src/modes/types.ts +1 -0
  384. package/src/modes/utils/injected-user-submission.ts +25 -0
  385. package/src/modes/utils/terminal-bell.ts +15 -1
  386. package/src/prompts/agent-fragments/ralplan-persistence.md +1 -1
  387. package/src/prompts/agent-fragments/restricted-bash.md +1 -1
  388. package/src/prompts/agents/critic.md +1 -1
  389. package/src/prompts/system/rlm-research.md +1 -1
  390. package/src/prompts/system/system-prompt.md +0 -2
  391. package/src/prompts/tools/bash.md +7 -1
  392. package/src/prompts/tools/browser.md +1 -1
  393. package/src/prompts/tools/cron.md +6 -6
  394. package/src/prompts/tools/read.md +0 -2
  395. package/src/prompts/tools/skill-discovery.md +4 -2
  396. package/src/runtime-mcp/client.ts +427 -23
  397. package/src/runtime-mcp/config.ts +37 -5
  398. package/src/runtime-mcp/manager-pool.test.ts +56 -0
  399. package/src/runtime-mcp/manager.ts +52 -2
  400. package/src/runtime-mcp/oauth-discovery.ts +13 -0
  401. package/src/runtime-mcp/oauth-flow.ts +46 -1
  402. package/src/runtime-mcp/pool-key.ts +5 -1
  403. package/src/runtime-mcp/pool.test.ts +9 -0
  404. package/src/runtime-mcp/protocol.test.ts +353 -0
  405. package/src/runtime-mcp/protocol.ts +624 -0
  406. package/src/runtime-mcp/scope-config.ts +78 -0
  407. package/src/runtime-mcp/tool-bridge.ts +44 -10
  408. package/src/runtime-mcp/transports/http.ts +84 -13
  409. package/src/runtime-mcp/types.ts +123 -12
  410. package/src/sdk/acp/adapter.ts +328 -97
  411. package/src/sdk/broker/broker.ts +160 -741
  412. package/src/sdk/broker/ensure.ts +9 -1
  413. package/src/sdk/broker/lifecycle.ts +510 -116
  414. package/src/sdk/broker/operation-receipt-ledger.ts +11 -2
  415. package/src/sdk/broker/process-incarnation.ts +19 -0
  416. package/src/sdk/broker/session-index.ts +516 -117
  417. package/src/sdk/broker/startup-failure.ts +28 -5
  418. package/src/sdk/broker/transport.ts +1 -13
  419. package/src/sdk/bus/chat-daemon-cli.ts +4 -2
  420. package/src/sdk/bus/chat-daemon-control.ts +67 -6
  421. package/src/sdk/bus/chat-daemon-runtime.ts +183 -859
  422. package/src/sdk/bus/config.ts +13 -0
  423. package/src/sdk/bus/conversation-store.ts +59 -171
  424. package/src/sdk/bus/daemon-paths.ts +20 -0
  425. package/src/sdk/bus/discord-conversation.ts +2 -0
  426. package/src/sdk/bus/discord-daemon.ts +524 -126
  427. package/src/sdk/bus/discord-live-provider.ts +206 -79
  428. package/src/sdk/bus/existing-thread-readiness.ts +4 -97
  429. package/src/sdk/bus/inbound-reaction-ordering.ts +122 -0
  430. package/src/sdk/bus/index.ts +2480 -456
  431. package/src/sdk/bus/kind-aware-reconciliation.ts +204 -34
  432. package/src/sdk/bus/lifecycle-commands.ts +2 -2
  433. package/src/sdk/bus/managed-daemon.ts +2 -145
  434. package/src/sdk/bus/notification-service.ts +196 -0
  435. package/src/sdk/bus/operator-runtime.ts +1 -1
  436. package/src/sdk/bus/prompt-reconciliation.ts +24 -15
  437. package/src/sdk/bus/reconciliation-store.ts +553 -24
  438. package/src/sdk/bus/session-control.ts +49 -3
  439. package/src/sdk/bus/slack-conversation.ts +4 -0
  440. package/src/sdk/bus/slack-daemon.ts +733 -134
  441. package/src/sdk/bus/slack-live-provider.ts +65 -15
  442. package/src/sdk/bus/slack-provider.ts +4 -0
  443. package/src/sdk/bus/slack-thread-binding.ts +9 -104
  444. package/src/sdk/bus/telegram-adoption-intent.ts +299 -209
  445. package/src/sdk/bus/telegram-daemon-cli.ts +194 -7
  446. package/src/sdk/bus/telegram-daemon-contract.ts +265 -19
  447. package/src/sdk/bus/telegram-daemon-control.ts +3 -13
  448. package/src/sdk/bus/telegram-daemon-orphan-reap.ts +498 -0
  449. package/src/sdk/bus/telegram-daemon-owner-registry.ts +176 -0
  450. package/src/sdk/bus/telegram-daemon.ts +2552 -2280
  451. package/src/sdk/bus/telegram-reference.ts +1 -226
  452. package/src/sdk/bus/telegram-setup.ts +2 -2
  453. package/src/sdk/bus/threaded-render.ts +1 -0
  454. package/src/sdk/bus/topic-registry.ts +279 -146
  455. package/src/sdk/cli/rows.ts +2 -35
  456. package/src/sdk/cli/session-cli.ts +776 -700
  457. package/src/sdk/client/client.ts +892 -1
  458. package/src/sdk/host/control/dispatch.ts +93 -58
  459. package/src/sdk/host/control/index.ts +1 -1
  460. package/src/sdk/host/control/operations.ts +18 -10
  461. package/src/sdk/host/control/runtime-gate.ts +40 -0
  462. package/src/sdk/host/host.ts +63 -12
  463. package/src/sdk/host/query/handlers.ts +66 -7
  464. package/src/sdk/host/reverse-leases.ts +2 -2
  465. package/src/sdk/host/sdk-surface-parity.test.ts +1 -1
  466. package/src/sdk/host/session-runtime.test.ts +2787 -34
  467. package/src/sdk/host/session-runtime.ts +1489 -101
  468. package/src/sdk/host/surface-policy.ts +12 -8
  469. package/src/sdk/host/types.ts +1 -1
  470. package/src/sdk/host/websocket-transport.lifecycle.test.ts +31 -59
  471. package/src/sdk/host/websocket-transport.ts +85 -50
  472. package/src/sdk/index.ts +2 -1
  473. package/src/sdk/lifecycle/broker-client.ts +53 -0
  474. package/src/sdk/lifecycle/client.ts +234 -0
  475. package/src/sdk/lifecycle/index.ts +3 -0
  476. package/src/sdk/{bus/recent-activity.ts → lifecycle/recent-sessions.ts} +5 -2
  477. package/src/sdk/lifecycle/service.ts +720 -0
  478. package/src/sdk/mcp/server.ts +226 -188
  479. package/src/sdk/prompt-deadline-lease.ts +55 -0
  480. package/src/sdk/prompt-deadline-manager.ts +148 -0
  481. package/src/sdk/prompt-status.ts +11 -3
  482. package/src/sdk/protocol/operation-inventory.generated.json +272 -129
  483. package/src/sdk/protocol/operation-registry.ts +71 -8
  484. package/src/sdk/receipt-state.ts +43 -0
  485. package/src/sdk/reconciliation-extensions.ts +14 -0
  486. package/src/sdk/router/index.ts +1 -0
  487. package/src/sdk/router/session-router.ts +1658 -0
  488. package/src/sdk/session-list.ts +93 -0
  489. package/src/sdk/session-reconnect.ts +45 -1
  490. package/src/sdk/session.ts +716 -120
  491. package/src/sdk/transport/serve-cli.ts +18 -34
  492. package/src/session/account-inventory.ts +603 -0
  493. package/src/session/agent-session.ts +2873 -559
  494. package/src/session/agent-storage.ts +35 -0
  495. package/src/session/blob-store.ts +32 -1
  496. package/src/session/coordinator-tool-label.ts +40 -0
  497. package/src/session/internal/managed-session-scope.ts +4 -0
  498. package/src/session/internal/managed-session-storage.ts +199 -31
  499. package/src/session/messages.ts +1 -1
  500. package/src/session/secure-token-file.ts +80 -0
  501. package/src/session/session-manager.ts +757 -57
  502. package/src/session/session-storage.ts +25 -16
  503. package/src/session/startup-auth-config.ts +297 -0
  504. package/src/session/terminal-abort.ts +1253 -0
  505. package/src/session/yield-queue.ts +57 -15
  506. package/src/session-import/claude.ts +334 -0
  507. package/src/session-import/codex.ts +1033 -0
  508. package/src/session-import/command.ts +77 -0
  509. package/src/session-import/detect.ts +133 -0
  510. package/src/session-import/index.ts +7 -0
  511. package/src/session-import/provider-codex.ts +327 -0
  512. package/src/session-import/provider-service.ts +457 -0
  513. package/src/session-import/redact.ts +128 -0
  514. package/src/session-import/service.ts +1264 -0
  515. package/src/session-import/types.ts +148 -0
  516. package/src/setup/paseo/check.ts +213 -0
  517. package/src/setup/paseo/install-saga.ts +293 -0
  518. package/src/setup/paseo/json-publisher.ts +263 -0
  519. package/src/setup/paseo/orchestration-preferences.ts +86 -0
  520. package/src/setup/paseo/paseo-ownership.ts +224 -0
  521. package/src/setup/paseo/paseo-setup.ts +253 -0
  522. package/src/setup/paseo/provider-config.ts +139 -0
  523. package/src/setup/paseo/remove.ts +183 -0
  524. package/src/setup/paseo/result-types.ts +79 -0
  525. package/src/setup/paseo/setup-deps.ts +175 -0
  526. package/src/setup/paseo/skills-bridge.ts +260 -0
  527. package/src/skill-state/active-state.ts +21 -11
  528. package/src/slash-commands/acp-builtins.ts +9 -1
  529. package/src/slash-commands/builtin-registry.ts +117 -19
  530. package/src/slash-commands/helpers/credential-switch.ts +143 -0
  531. package/src/slash-commands/helpers/mcp.ts +9 -3
  532. package/src/slash-commands/helpers/usage-report.ts +61 -56
  533. package/src/slash-commands/types.ts +7 -2
  534. package/src/task/executor.ts +30 -11
  535. package/src/task/index.ts +66 -7
  536. package/src/task/render.ts +23 -9
  537. package/src/tools/ask-answer-registry.ts +23 -7
  538. package/src/tools/ask.ts +8 -4
  539. package/src/tools/bash-sleep-advisory.ts +115 -0
  540. package/src/tools/bash.ts +98 -18
  541. package/src/tools/browser/attach.ts +71 -3
  542. package/src/tools/browser/launch.ts +86 -13
  543. package/src/tools/browser/profile-discovery.ts +57 -7
  544. package/src/tools/browser/render.ts +8 -1
  545. package/src/tools/browser.ts +130 -21
  546. package/src/tools/computer.enforcement.test.ts +1 -1
  547. package/src/tools/computer.ts +3 -2
  548. package/src/tools/cron.ts +12 -1
  549. package/src/tools/image-gen.ts +349 -142
  550. package/src/tools/index.ts +8 -0
  551. package/src/tools/job.ts +33 -6
  552. package/src/tools/monitor.ts +41 -2
  553. package/src/tools/skill-discovery.ts +21 -29
  554. package/src/tools/subagent.ts +142 -24
  555. package/src/tools/todo-contract.ts +55 -3
  556. package/src/tools/todo-write.ts +4 -3
  557. package/src/tools/tool-catalog.generated.ts +6 -6
  558. package/src/tools/ultragoal-ask-guard.ts +10 -3
  559. package/src/utils/herdr-pane.ts +345 -0
  560. package/src/utils/title-generator.ts +2 -0
  561. package/src/web/scrapers/index.ts +6 -4
  562. package/dist/types/cli/malloc-env-guard.test.d.ts +0 -1
  563. package/dist/types/defaults/gjc-defaults.test.d.ts +0 -1
  564. package/dist/types/memory-backend/lazy-loading.test.d.ts +0 -1
  565. package/dist/types/runtime/boot-ordering.test.d.ts +0 -1
  566. package/dist/types/runtime/lazy-service.test.d.ts +0 -1
  567. package/dist/types/runtime/network-prewarm-service.test.d.ts +0 -1
  568. package/dist/types/runtime/workspace-tree-service.test.d.ts +0 -1
  569. package/dist/types/runtime-mcp/config.test.d.ts +0 -1
  570. package/dist/types/runtime-mcp/http.test.d.ts +0 -1
  571. package/dist/types/runtime-mcp/manager-pool.test.d.ts +0 -1
  572. package/dist/types/runtime-mcp/pool-key.test.d.ts +0 -1
  573. package/dist/types/runtime-mcp/pool.test.d.ts +0 -1
  574. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +0 -117
  575. package/dist/types/sdk/bus/lifecycle-orchestrator.d.ts +0 -141
  576. package/dist/types/sdk/bus/telegram-cli.d.ts +0 -31
  577. package/dist/types/sdk/elevation/allowlist.d.ts +0 -20
  578. package/dist/types/sdk/elevation/audit.d.ts +0 -93
  579. package/dist/types/sdk/elevation/capability.d.ts +0 -3
  580. package/dist/types/sdk/elevation/digest.d.ts +0 -72
  581. package/dist/types/sdk/elevation/dispatch-receipt.d.ts +0 -85
  582. package/dist/types/sdk/elevation/grant-ledger.d.ts +0 -163
  583. package/dist/types/sdk/elevation/owner.d.ts +0 -48
  584. package/dist/types/sdk/host/import-graph.test.d.ts +0 -1
  585. package/dist/types/sdk/host/sdk-surface-parity.test.d.ts +0 -1
  586. package/dist/types/sdk/host/session-runtime.test.d.ts +0 -1
  587. package/dist/types/sdk/host/websocket-transport.lifecycle.test.d.ts +0 -1
  588. package/dist/types/session/auth-broker-config.d.ts +0 -13
  589. package/dist/types/session/heap-eviction-artifacts.test.d.ts +0 -1
  590. package/dist/types/skills/skills.test.d.ts +0 -1
  591. package/dist/types/slash-commands/notify-command.test.d.ts +0 -1
  592. package/dist/types/task/token-log.test.d.ts +0 -1
  593. package/dist/types/tools/computer.enforcement.test.d.ts +0 -1
  594. package/dist/types/tools/descriptors.test.d.ts +0 -1
  595. package/scripts/g005-daemon-smoke.ts +0 -180
  596. package/scripts/g011-daemon-path-smoke.ts +0 -153
  597. package/src/sdk/bus/lifecycle-control-runtime.ts +0 -1733
  598. package/src/sdk/bus/lifecycle-orchestrator.ts +0 -489
  599. package/src/sdk/bus/telegram-cli.ts +0 -212
  600. package/src/sdk/elevation/allowlist.ts +0 -54
  601. package/src/sdk/elevation/audit.ts +0 -208
  602. package/src/sdk/elevation/capability.ts +0 -41
  603. package/src/sdk/elevation/digest.ts +0 -129
  604. package/src/sdk/elevation/dispatch-receipt.ts +0 -184
  605. package/src/sdk/elevation/grant-ledger.ts +0 -1142
  606. package/src/sdk/elevation/owner.ts +0 -109
  607. package/src/session/auth-broker-config.ts +0 -109
  608. /package/dist/types/{ai-core-import-gate.test.d.ts → discovery/claude-hooks.d.ts} +0 -0
  609. /package/dist/types/{cli/fixture-report.test.d.ts → discovery/codex-hooks.d.ts} +0 -0
  610. /package/dist/types/sdk/{bus/recent-activity.d.ts → lifecycle/recent-sessions.d.ts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import type { AgentTelemetryConfig, AgentTool } from "@gajae-code/agent-core";
2
2
  import type { Model, ServiceTier, ToolChoice } from "@gajae-code/ai/core";
3
3
  import { $env, logger } from "@gajae-code/utils";
4
+ import type { AsyncJobManager } from "../async";
4
5
  import type { PromptTemplate } from "../config/prompt-templates";
5
6
  import type { Settings } from "../config/settings";
6
7
  import type { Skill } from "../extensibility/skills";
@@ -217,6 +218,8 @@ export interface ToolSession {
217
218
  hasForegroundBashBackgroundRequestHandler?: () => boolean;
218
219
  /** Request that the active managed foreground bash call fold into a background job, if supported. */
219
220
  requestForegroundBashBackground?: () => boolean;
221
+ /** Get the session-owned or inherited async job manager. */
222
+ getAsyncJobManager?: () => AsyncJobManager | undefined;
220
223
  /** Get session ID */
221
224
  getSessionId?: () => string | null;
222
225
  /** Get credential-selection session identity. */
@@ -279,6 +282,11 @@ export interface ToolSession {
279
282
  agentOutputManager?: AgentOutputManager;
280
283
  /** Settings instance for passing to subagents */
281
284
  settings: Settings;
285
+ /**
286
+ * The session's REQUESTED effective agent directory, independent of the
287
+ * global Settings singleton (which can be reused across sessions).
288
+ */
289
+ getSessionAgentDir?: () => string;
282
290
  /** Live service-tier intent of the parent session, inherited by `inherit` subagents. */
283
291
  serviceTier?: ServiceTier;
284
292
  /** Whether the effective subagent tier grants fast mode for a resolved provider. */
package/src/tools/job.ts CHANGED
@@ -7,6 +7,7 @@ import { type AsyncJob, AsyncJobManager, isBackgroundJobSupportEnabled, jobElaps
7
7
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
8
8
  import type { Theme } from "../modes/theme/theme";
9
9
  import jobDescription from "../prompts/tools/job.md" with { type: "text" };
10
+ import { lookupOwnedRegistration, unregisterOwnedRegistration } from "../session/terminal-abort";
10
11
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
11
12
  import type { ToolSession } from "./index";
12
13
  import {
@@ -100,7 +101,10 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
100
101
  onUpdate?: AgentToolUpdateCallback<JobToolDetails>,
101
102
  _context?: AgentToolContext,
102
103
  ): Promise<AgentToolResult<JobToolDetails>> {
103
- const manager = AsyncJobManager.instance();
104
+ const manager =
105
+ this.session.getAsyncJobManager?.() ??
106
+ AsyncJobManager.forEndpoint(this.session.getSessionId?.() ?? undefined) ??
107
+ AsyncJobManager.instance();
104
108
  if (!manager) {
105
109
  return {
106
110
  content: [{ type: "text", text: "Async execution is disabled; no background jobs are available." }],
@@ -237,7 +241,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
237
241
  const PROGRESS_INTERVAL_MS = 500;
238
242
  const emitProgress = () => {
239
243
  if (!onUpdate) return;
240
- const snapshot = this.#snapshotJobs(allTrackedJobs);
244
+ const snapshot = this.#snapshotJobs(manager, allTrackedJobs);
241
245
  onUpdate({
242
246
  content: [{ type: "text", text: "" }],
243
247
  details: {
@@ -318,6 +322,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
318
322
  }
319
323
 
320
324
  #snapshotJobs(
325
+ manager: AsyncJobManager,
321
326
  jobs: {
322
327
  id: string;
323
328
  type: "bash" | "task";
@@ -331,7 +336,7 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
331
336
  ): JobSnapshot[] {
332
337
  const now = Date.now();
333
338
  return jobs.map(j => {
334
- const current = AsyncJobManager.instance()?.getJob(j.id);
339
+ const current = manager.getJob(j.id);
335
340
  const latest = current ?? j;
336
341
  return {
337
342
  id: latest.id,
@@ -366,9 +371,31 @@ export class JobTool implements AgentTool<typeof jobSchema, JobToolDetails> {
366
371
  seen.add(j.id);
367
372
  return true;
368
373
  });
369
- const jobResults = this.#snapshotJobs(uniqueJobs);
370
-
371
- manager.acknowledgeDeliveries(jobResults.filter(j => j.status !== "running").map(j => j.id));
374
+ const jobResults = this.#snapshotJobs(manager, uniqueJobs);
375
+
376
+ // Only COMPLETED/FAILED/CANCELLED jobs are terminal for ownership
377
+ // retirement: a paused subagent is not quiescent (settleOwnedWork
378
+ // cancels paused jobs before it can claim stopped_owned), so its
379
+ // ownership tuple must stay registered until it is actually cancelled
380
+ // — otherwise a scope:"owned" abort could find an empty causal set and
381
+ // report stopped_owned while the job remains visibly paused (review
382
+ // thread P2).
383
+ const terminalJobs = jobResults.filter(
384
+ j => j.status === "completed" || j.status === "failed" || j.status === "cancelled",
385
+ );
386
+ manager.acknowledgeDeliveries(terminalJobs.map(j => j.id));
387
+ // A terminal job whose deliveries were just acknowledged is
388
+ // synchronously consumed: its owned registration is settled and must
389
+ // not occupy the registry, otherwise the retained-policy tombstone
390
+ // treats it as a live policy occupant forever and its FIFO fallback can
391
+ // evict a genuinely running job's policy (review thread P2).
392
+ for (const job of terminalJobs) {
393
+ const generation = manager.getJob?.(job.id)?.generation;
394
+ if (!generation) continue;
395
+ // The endpoint disambiguates concurrent sessions' same job ids (review P1).
396
+ const registration = lookupOwnedRegistration(job.id, generation, this.session.getSessionId?.() ?? "local");
397
+ if (registration) unregisterOwnedRegistration(registration);
398
+ }
372
399
 
373
400
  const completed = jobResults.filter(j => j.status !== "running");
374
401
  const running = jobResults.filter(j => j.status === "running");
@@ -4,6 +4,7 @@ import * as z from "zod/v4";
4
4
  import { AsyncJobManager, isBackgroundJobSupportEnabled } from "../async";
5
5
  import monitorDescription from "../prompts/tools/monitor.md" with { type: "text" };
6
6
  import { truncateTail } from "../session/streaming-output";
7
+ import { lookupOwnedRegistration } from "../session/terminal-abort";
7
8
  import { BashTool } from "./bash";
8
9
  import type { ToolSession } from "./index";
9
10
  import { ToolError } from "./tool-errors";
@@ -106,13 +107,20 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
106
107
  }
107
108
 
108
109
  async execute(
109
- _toolCallId: string,
110
+ toolCallId: string,
110
111
  params: MonitorParams,
111
112
  _signal?: AbortSignal,
112
113
  _onUpdate?: AgentToolUpdateCallback<MonitorToolDetails>,
113
114
  context?: AgentToolContext,
114
115
  ): Promise<AgentToolResult<MonitorToolDetails>> {
115
- const manager = AsyncJobManager.instance();
116
+ // The session's ENDPOINT-owned manager first: the monitor job is created
117
+ // in it, its generation is read from it, and non-persistent cancel must
118
+ // stop that exact job — the process-global instance may belong to a
119
+ // different concurrent top-level session (review thread P1).
120
+ const manager =
121
+ this.session.getAsyncJobManager?.() ??
122
+ AsyncJobManager.forEndpoint(this.session.getSessionId?.() ?? undefined) ??
123
+ AsyncJobManager.instance();
116
124
  if (!manager) {
117
125
  throw new ToolError("Async execution is disabled; the monitor tool is unavailable in this session.");
118
126
  }
@@ -169,8 +177,38 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
169
177
  const suffix = count > 0 ? `\n(+${count} earlier lines)` : "";
170
178
  const notificationLine = formatMonitorNotificationLine(line);
171
179
  const content = `<task-notification>\nMonitor task ${jobId} (${params.kind}: ${params.description}) emitted latest state:\n${notificationLine.content}${suffix}\n</task-notification>`;
180
+ // Route the notification through the SAME owned-completion envelope
181
+ // and fresh-admission path as async results: a scope:"turn" abort
182
+ // that left this monitor running must resume the agent with a FRESH
183
+ // lineage (never the aborted attempt's), and scope:"owned" must drop
184
+ // it. The envelope is built from the tool-call lineage binding and
185
+ // the exact registered tuple; monitors without a registration stay
186
+ // ordinary. The standalone generation is NOT persisted — it lives in
187
+ // the envelope, which INTERNAL_DETAILS_FIELDS strips (review threads
188
+ // P1/P2).
189
+ const generation = manager.getJob(jobId)?.generation;
190
+ // Build the envelope from the RETAINED registration (which carries the
191
+ // complete lineage tuple), not the tool-call binding: the binding map
192
+ // is separately bounded and can evict a long-lived monitor's entry,
193
+ // but the registration keeps the lineage for owned classification
194
+ // (review thread P2).
195
+ // The lookup is ENDPOINT-scoped to THIS monitor's session: concurrent
196
+ // sessions have monitors with the same manager-local job id, and an
197
+ // endpoint-less fallback scan could attach a foreign session's
198
+ // registration (review thread P2).
199
+ const registration = generation
200
+ ? lookupOwnedRegistration(jobId, generation, this.session.getSessionId?.() ?? "local")
201
+ : undefined;
202
+ const ownedCompletion = registration
203
+ ? {
204
+ lineageIdHash: registration.lineageIdHash,
205
+ promptAttemptEpoch: registration.promptAttemptEpoch,
206
+ registration,
207
+ }
208
+ : undefined;
172
209
  const details = {
173
210
  taskId: jobId,
211
+ ...(ownedCompletion ? { ownedCompletions: [ownedCompletion] } : {}),
174
212
  kind: params.kind,
175
213
  description: params.description,
176
214
  monitor: true,
@@ -219,6 +257,7 @@ export class MonitorTool implements AgentTool<typeof monitorSchema, MonitorToolD
219
257
  ownerId,
220
258
  label,
221
259
  ctx: context,
260
+ toolCallId,
222
261
  shouldAcceptRawLine: () => !controller.closed,
223
262
  lifecycle: {
224
263
  onCancel: () => closeMonitor("purge"),
@@ -1,7 +1,11 @@
1
1
  import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
2
2
  import { prompt, untilAborted } from "@gajae-code/utils";
3
3
  import * as z from "zod/v4";
4
- import { discoverRuntimeSkills, type RuntimeSkillDiscoveryCandidate } from "../extensibility/runtime-skill-discovery";
4
+ import {
5
+ describeDisabledSkillScopes,
6
+ discoverRuntimeSkills,
7
+ type RuntimeSkillDiscoveryCandidate,
8
+ } from "../extensibility/runtime-skill-discovery";
5
9
  import skillDiscoveryDescription from "../prompts/tools/skill-discovery.md" with { type: "text" };
6
10
  import type { ToolSession } from ".";
7
11
 
@@ -23,11 +27,18 @@ export interface SkillDiscoveryToolDetails {
23
27
  count: number;
24
28
  /**
25
29
  * Present only when zero candidates were returned AND discovery config gates
26
- * (`skills.enabled` / `skills.enablePiProject` / `skills.enablePiUser`)
30
+ * (`skills.enabled` / `skills.trustProjectSkills` / `skills.trustUserSkills`)
27
31
  * prevented some or all of the requested scope from being searched. Without
28
32
  * this, a disabled config is indistinguishable from "no skills exist".
29
33
  */
30
34
  notice?: string;
35
+ /**
36
+ * Human-readable diagnostics for skills that were scanned but not
37
+ * advertised (protected-name collisions with bundled workflow skills,
38
+ * include/ignore/disable policy filters, invalid frontmatter, shadowing,
39
+ * scan failures). Bounded; empty when nothing was filtered.
40
+ */
41
+ diagnostics?: string[];
31
42
  }
32
43
 
33
44
  export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema, SkillDiscoveryToolDetails> {
@@ -65,16 +76,20 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
65
76
  ): Promise<AgentToolResult<SkillDiscoveryToolDetails>> {
66
77
  return untilAborted(signal, async () => {
67
78
  const source = input.source ?? "all";
68
- const candidates = await discoverRuntimeSkills({
79
+ const result = await discoverRuntimeSkills({
69
80
  cwd: this.#session.cwd,
70
81
  query: input.query,
71
82
  source,
72
83
  limit: input.limit,
73
84
  policy: this.#getRuntimeSkillPolicy(),
74
85
  });
75
- const details: SkillDiscoveryToolDetails = { candidates, count: candidates.length };
76
- if (candidates.length === 0) {
77
- const notice = this.#disabledPolicyNotice(source);
86
+ const details: SkillDiscoveryToolDetails = {
87
+ candidates: result.candidates,
88
+ count: result.candidates.length,
89
+ };
90
+ if (result.diagnostics.messages.length > 0) details.diagnostics = result.diagnostics.messages;
91
+ if (result.candidates.length === 0) {
92
+ const notice = describeDisabledSkillScopes(source, this.#getRuntimeSkillPolicy());
78
93
  if (notice) details.notice = notice;
79
94
  }
80
95
  return {
@@ -83,27 +98,4 @@ export class SkillDiscoveryTool implements AgentTool<typeof skillDiscoverySchema
83
98
  };
84
99
  });
85
100
  }
86
-
87
- /**
88
- * Explain an empty result that is caused by disabled discovery config rather
89
- * than by an actually empty skill catalog.
90
- */
91
- #disabledPolicyNotice(source: "all" | "project" | "user"): string | undefined {
92
- const policy = this.#getRuntimeSkillPolicy();
93
- if (policy.enabled !== true) {
94
- return "Runtime skill discovery is disabled: `skills.enabled` is false, so no skill directories were searched. Enable it with `gjc config set skills.enabled true` (project and user scopes additionally require `skills.enablePiProject` / `skills.enablePiUser`).";
95
- }
96
- const skipped: string[] = [];
97
- const commands: string[] = [];
98
- if ((source === "all" || source === "project") && policy.enablePiProject !== true) {
99
- skipped.push("project (`skills.enablePiProject` is false)");
100
- commands.push("`gjc config set skills.enablePiProject true`");
101
- }
102
- if ((source === "all" || source === "user") && policy.enablePiUser !== true) {
103
- skipped.push("user (`skills.enablePiUser` is false)");
104
- commands.push("`gjc config set skills.enablePiUser true`");
105
- }
106
- if (skipped.length === 0) return undefined;
107
- return `Skill discovery skipped disabled scope(s): ${skipped.join(", ")}. Enable them with ${commands.join(" and ")}.`;
108
- }
109
101
  }
@@ -1,6 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
3
- import { prompt } from "@gajae-code/utils";
3
+ import { formatDuration, logger, prompt } from "@gajae-code/utils";
4
4
  import * as z from "zod/v4";
5
5
  import { type AsyncJob, AsyncJobManager, jobElapsedMs, type SubagentRecord } from "../async";
6
6
  import subagentDescription from "../prompts/tools/subagent.md" with { type: "text" };
@@ -12,6 +12,15 @@ import { ToolError } from "./tool-errors";
12
12
 
13
13
  const DEFAULT_AWAIT_TIMEOUT_MS = 30_000;
14
14
  const MAX_AWAIT_TIMEOUT_MS = 60 * 60 * 1000;
15
+ /**
16
+ * Bounded cadence for await liveness: even when the rendered-state signature is
17
+ * stable (a running subagent emitting no new progress), the await panel re-emits
18
+ * so a healthy wait never looks like a hung session (issue #4465). Derived from
19
+ * `heartbeat_ms` so callers keep one knob, floor-bounded so a small heartbeat
20
+ * cannot turn liveness into transcript spam, and injected for tests.
21
+ */
22
+ const AWAIT_LIVENESS_MULTIPLIER = 30;
23
+ const AWAIT_LIVENESS_MIN_INTERVAL_MS = 15_000;
15
24
  const DEFAULT_LIST_LIMIT = 10;
16
25
  const MAX_LIST_LIMIT = 50;
17
26
  const RECEIPT_PREVIEW_WIDTH = 280;
@@ -123,6 +132,22 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
123
132
  readonly parameters = subagentSchema;
124
133
  readonly strict = true;
125
134
  readonly loadMode = "discoverable";
135
+ /**
136
+ * Injected monotonic clock for await-liveness tests. Production callers leave
137
+ * this undefined and the await falls back to `Date.now`; tests substitute a
138
+ * virtual clock so a 15s+ liveness interval is exercised without any real
139
+ * multi-minute sleep (#4465).
140
+ */
141
+ #livenessNowMs?: () => number;
142
+
143
+ /** Test-only seam: substitute the liveness clock. Returns a restore function. */
144
+ withLivenessClock(nowMs: () => number): () => void {
145
+ const prior = this.#livenessNowMs;
146
+ this.#livenessNowMs = nowMs;
147
+ return () => {
148
+ this.#livenessNowMs = prior;
149
+ };
150
+ }
126
151
 
127
152
  constructor(private readonly session: ToolSession) {
128
153
  this.description = prompt.render(subagentDescription);
@@ -135,7 +160,16 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
135
160
  onUpdate?: AgentToolUpdateCallback<SubagentToolDetails>,
136
161
  _context?: AgentToolContext,
137
162
  ): Promise<AgentToolResult<SubagentToolDetails>> {
138
- const manager = AsyncJobManager.instance();
163
+ // The session's ENDPOINT-owned manager first: concurrent top-level
164
+ // sessions register their manager under their endpoint (sdk/session.ts),
165
+ // so list/pause/resume/message/cancel/await must inspect THIS session's
166
+ // manager — the process-global instance belongs to the last-created
167
+ // session, where this session's subagent records and jobs are absent
168
+ // or belong to a same-id subagent of another session (review thread P1).
169
+ const manager =
170
+ this.session.getAsyncJobManager?.() ??
171
+ AsyncJobManager.forEndpoint(this.session.getSessionId?.() ?? undefined) ??
172
+ AsyncJobManager.instance();
139
173
  if (!manager) {
140
174
  return {
141
175
  content: [{ type: "text", text: "No subagent manager is available in this session." }],
@@ -241,7 +275,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
241
275
  records.push(record);
242
276
  terminalGuidanceIds.add(record.subagentId);
243
277
  } else {
244
- const result = manager.resumeSubagent(record.subagentId, ownerFilter, params.message);
278
+ const result = manager.resumeSubagent(record.subagentId, ownerFilter, params.message, _toolCallId);
245
279
  if (!result.ok && result.reason === "context_unavailable") throw new ToolError("context unavailable");
246
280
  if (!result.ok && result.reason === "not_found") {
247
281
  missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
@@ -293,7 +327,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
293
327
  records.push(manager.getSubagentRecord(record.subagentId, ownerFilter) ?? record);
294
328
  steerStates.set(record.subagentId, "queued");
295
329
  } else {
296
- const result = manager.resumeSubagent(record.subagentId, ownerFilter, message);
330
+ const result = manager.resumeSubagent(record.subagentId, ownerFilter, message, _toolCallId);
297
331
  if (!result.ok && result.reason === "not_found") {
298
332
  missing.push(this.#missingSnapshot(id, "not_found", "No visible detached subagent matches this id."));
299
333
  } else if (!result.ok) {
@@ -402,23 +436,71 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
402
436
  });
403
437
  }
404
438
  const watchedJobIds = targets.map(target => target.jobId).filter((jobId): jobId is string => jobId !== null);
405
- manager.watchJobs(watchedJobIds);
439
+ const watchHandle = manager.watchJobGenerations(watchedJobIds);
406
440
  const heartbeatMs = params.heartbeat_ms === undefined ? 500 : params.heartbeat_ms;
441
+ // Liveness interval: force a re-emit even when the rendered-state signature
442
+ // is stable so a healthy wait never looks like a hung session (#4465). The
443
+ // `#livenessNowMs` clock is injected for deterministic tests; `Date.now`
444
+ // is the production fallback. The interval is floor-bounded so a caller who
445
+ // sets a tiny `heartbeat_ms` cannot turn liveness into transcript spam.
446
+ const livenessIntervalMs = Math.max(AWAIT_LIVENESS_MIN_INTERVAL_MS, heartbeatMs * AWAIT_LIVENESS_MULTIPLIER);
447
+ const nowMs = this.#livenessNowMs ?? Date.now;
448
+ let lastEmitMs = nowMs();
407
449
  let lastEmittedSignature: string | undefined;
450
+ let streamingDisabled = false;
451
+ let stopProgressTimer = (): void => {};
452
+ const disableStreaming = (error: unknown): void => {
453
+ if (streamingDisabled) return;
454
+ streamingDisabled = true;
455
+ stopProgressTimer();
456
+ try {
457
+ logger.warn("Subagent await progress update failed; streaming disabled for this wait", {
458
+ error: safeThrownValue(error),
459
+ });
460
+ } catch {
461
+ // Diagnostics must never turn a recoverable progress-channel failure
462
+ // into an await or process-lifecycle failure.
463
+ }
464
+ };
408
465
  const emitIfChanged = (force: boolean): void => {
409
- if (!onUpdate) return;
410
- const result = this.#progressResult(manager, records, true);
411
- const signature = subagentAwaitRenderedStateSignature(result.details?.subagents ?? []);
412
- if (!force && signature === lastEmittedSignature) return;
413
- lastEmittedSignature = signature;
414
- onUpdate(result);
466
+ if (!onUpdate || streamingDisabled) return;
467
+ try {
468
+ const result = this.#progressResult(manager, records, true);
469
+ const signature = subagentAwaitRenderedStateSignature(result.details?.subagents ?? []);
470
+ const t = nowMs();
471
+ const livenessDue = force || t - lastEmitMs >= livenessIntervalMs;
472
+ if (!livenessDue && signature === lastEmittedSignature) return;
473
+ lastEmittedSignature = signature;
474
+ lastEmitMs = t;
475
+ // A TypeScript void-return callback may still be implemented by an
476
+ // async function. Observe its runtime result so a rejected promise or
477
+ // thenable cannot become an unhandled rejection outside this span.
478
+ const updateResult = (onUpdate as (update: AgentToolResult<SubagentToolDetails>) => unknown)(result);
479
+ if (isPromiseLike(updateResult)) void Promise.resolve(updateResult).catch(disableStreaming);
480
+ } catch (error) {
481
+ // The liveness timer fires outside this function's promise chain, so a
482
+ // throwing progress consumer would otherwise escape `setInterval` as an
483
+ // uncaught exception, leave the wait's cleanup unreached, and let the
484
+ // interval keep firing and rethrowing for the rest of the await. Retire
485
+ // the streaming channel instead and keep waiting: losing progress output
486
+ // is recoverable, losing the timer and the watch registration is not.
487
+ disableStreaming(error);
488
+ }
415
489
  };
416
- const progressTimer =
417
- onUpdate && heartbeatMs > 0 ? setInterval(() => emitIfChanged(false), heartbeatMs) : undefined;
418
- emitIfChanged(true);
490
+ // The initial emission and the timer own process-lifetime resources together
491
+ // with the watch registration and the terminal-wait handle, so they all live
492
+ // inside one guarded span rather than straddling it.
493
+ let progressTimer: Timer | undefined;
419
494
  let waitOutcome: "completed" | "timed_out_wait" | "interrupted";
420
495
  let onAbort: (() => void) | undefined;
496
+ let terminalJobIds: string[] | undefined;
421
497
  try {
498
+ progressTimer = onUpdate && heartbeatMs > 0 ? setInterval(() => emitIfChanged(false), heartbeatMs) : undefined;
499
+ stopProgressTimer = () => {
500
+ if (progressTimer) clearInterval(progressTimer);
501
+ progressTimer = undefined;
502
+ };
503
+ emitIfChanged(true);
422
504
  const abortPromise = new Promise<"interrupted">(resolve => {
423
505
  onAbort = () => resolve("interrupted");
424
506
  if (signal?.aborted) onAbort();
@@ -432,14 +514,16 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
432
514
  Bun.sleep(timeoutMs).then(() => "timed_out_wait" as const),
433
515
  abortPromise,
434
516
  ]);
517
+ if (waitOutcome === "completed") {
518
+ terminalJobIds = (await handle.result).terminalJobIds;
519
+ handle.acknowledge(terminalJobIds);
520
+ }
435
521
  } finally {
436
522
  if (signal && onAbort) signal.removeEventListener("abort", onAbort);
437
- if (progressTimer) clearInterval(progressTimer);
523
+ stopProgressTimer();
524
+ watchHandle.close();
525
+ handle.close();
438
526
  }
439
- const waitResult = waitOutcome === "completed" ? await handle.result : undefined;
440
- if (waitOutcome === "completed") handle.acknowledge(waitResult?.terminalJobIds);
441
- manager.unwatchJobs(watchedJobIds);
442
- handle.close();
443
527
  const awaitOutcome: SubagentAwaitOutcome =
444
528
  waitOutcome === "completed" ? "completed" : waitOutcome === "timed_out_wait" ? "timed_out" : "interrupted";
445
529
  const finalRecords = this.#visibleRecordsByIds(
@@ -456,7 +540,7 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
456
540
  awaitOutcome,
457
541
  waitOutcome,
458
542
  condition,
459
- terminalIds: waitResult?.terminalJobIds,
543
+ terminalIds: terminalJobIds,
460
544
  });
461
545
  }
462
546
 
@@ -558,11 +642,10 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
558
642
  records: SubagentRecord[],
559
643
  attachLiveProgress = false,
560
644
  ): AgentToolResult<SubagentToolDetails> {
645
+ const subagents = this.#recordSnapshots(manager, records, false, "receipt", new Set(), attachLiveProgress);
561
646
  return {
562
- content: [{ type: "text", text: "" }],
563
- details: {
564
- subagents: this.#recordSnapshots(manager, records, false, "receipt", new Set(), attachLiveProgress),
565
- },
647
+ content: [{ type: "text", text: awaitProgressSummary(subagents) }],
648
+ details: { subagents },
566
649
  };
567
650
  }
568
651
 
@@ -810,6 +893,41 @@ export class SubagentTool implements AgentTool<typeof subagentSchema, SubagentTo
810
893
  }
811
894
  }
812
895
 
896
+ /**
897
+ * Human-readable line for streamed `await` progress.
898
+ *
899
+ * Consumers that render structured content (ACP) previously received an empty
900
+ * text block here, which made `extractStructuredText` fail and the mapper fall
901
+ * back to serializing the whole result object as the human-readable content. A
902
+ * real sentence keeps the elapsed time legible there and in any log that only
903
+ * shows tool text.
904
+ */
905
+ function awaitProgressSummary(subagents: readonly SubagentSnapshot[]): string {
906
+ const waiting = subagents.filter(snapshot => !isTerminalStatus(snapshot.status));
907
+ if (waiting.length === 0) return "";
908
+ // Accumulate rather than spreading into Math.max: explicit `ids` are not
909
+ // capped by MAX_LIST_LIMIT, so a large await would hit the argument limit.
910
+ let longestMs = 0;
911
+ for (const snapshot of waiting) if (snapshot.durationMs > longestMs) longestMs = snapshot.durationMs;
912
+ return `Awaiting ${waiting.length} subagent(s) for ${formatDuration(longestMs)}: ${waiting
913
+ .map(snapshot => snapshot.id)
914
+ .join(", ")}`;
915
+ }
916
+
917
+ function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
918
+ if ((typeof value !== "object" || value === null) && typeof value !== "function") return false;
919
+ return typeof (value as { then?: unknown }).then === "function";
920
+ }
921
+
922
+ function safeThrownValue(error: unknown): string {
923
+ try {
924
+ if (error instanceof Error) return error.message;
925
+ return String(error);
926
+ } catch {
927
+ return "[unprintable thrown value]";
928
+ }
929
+ }
930
+
813
931
  function isTerminalStatus(status: SubagentStatus): boolean {
814
932
  return status === "completed" || status === "failed" || status === "cancelled";
815
933
  }
@@ -50,6 +50,8 @@ const TODO_OP_KEYS = new Set(["op", "list", "task", "phase", "items", "text"]);
50
50
  const TODO_INIT_ENTRY_KEYS = new Set(["phase", "items"]);
51
51
  /** `content` is accepted only as a synonym the schema rewrites to `task`. */
52
52
  const TODO_OP_KEYS_WITH_ALIASES = new Set([...TODO_OP_KEYS, "content"]);
53
+ const MAX_REJECTED_KEYS = 8;
54
+ const POSITIONAL_HANDLE_KEYS = new Set(["id", "ids", "index", "taskId", "task_id"]);
53
55
 
54
56
  /**
55
57
  * Exact key corrections. `note` is the one repeatable confusion: it is an `op`
@@ -60,11 +62,27 @@ const TODO_OP_KEYS_WITH_ALIASES = new Set([...TODO_OP_KEYS, "content"]);
60
62
  * an entry here when the replacement is unambiguous — never from fuzzy or
61
63
  * edit-distance matching, because a wrong suggestion costs more turns than no
62
64
  * suggestion.
65
+ *
66
+ * The positional-handle family (`id`, `index`, and spellings of them) is the
67
+ * other repeatable confusion: the tool result renders tasks as a list, so a
68
+ * caller assumes the list is addressable by ordinal. No op has ever taken a
69
+ * handle - a task is addressed by its verbatim content - and the executor
70
+ * already says so, but on the validated model-facing path this validator
71
+ * rejects the key first, so that message never gets its turn. One shared
72
+ * correction keeps both layers telling the same story.
63
73
  */
74
+ const POSITIONAL_HANDLE_CORRECTION =
75
+ 'tasks have no id or index; target a task with "task" set to its exact content, or a whole phase with "phase"';
76
+
64
77
  const TODO_KEY_CORRECTIONS = new Map<string, string>([
65
78
  ["note", 'note is an op, not a key; note operations require both "task" and "text"'],
66
79
  ["newTask", "there is no rename op; re-run init with the corrected list to rename a task"],
67
80
  ["tasks", 'tasks is not a key; append operations take "items"'],
81
+ ["id", POSITIONAL_HANDLE_CORRECTION],
82
+ ["ids", POSITIONAL_HANDLE_CORRECTION],
83
+ ["index", POSITIONAL_HANDLE_CORRECTION],
84
+ ["taskId", POSITIONAL_HANDLE_CORRECTION],
85
+ ["task_id", POSITIONAL_HANDLE_CORRECTION],
68
86
  ]);
69
87
 
70
88
  function unknownKeys(value: object, allowed: Set<string>): string[] {
@@ -72,15 +90,42 @@ function unknownKeys(value: object, allowed: Set<string>): string[] {
72
90
  }
73
91
 
74
92
  function keyRejectionDetail(keys: readonly string[], location?: string): RawArgumentRejectionDetail {
75
- const hints = keys.map(key => TODO_KEY_CORRECTIONS.get(key)).filter((hint): hint is string => hint !== undefined);
76
- const parts = location === undefined ? hints : [location, ...hints];
77
- return parts.length > 0 ? { rejectedKeys: keys, hint: parts.join("; ") } : { rejectedKeys: keys };
93
+ const rejectedKeys = keys.slice(0, MAX_REJECTED_KEYS);
94
+ // Distinct corrections only. A family that shares one correction (every
95
+ // positional handle) would otherwise repeat it once per rejected key and blow
96
+ // past the caller's hint clamp, which truncates the advice mid-sentence.
97
+ const hints = [
98
+ ...new Set(
99
+ keys
100
+ .map(key => TODO_KEY_CORRECTIONS.get(key) ?? TODO_KEY_CORRECTIONS.get(key.split(".").at(-1) ?? ""))
101
+ .filter((hint): hint is string => hint !== undefined),
102
+ ),
103
+ ];
104
+ const omitted = keys.length - rejectedKeys.length;
105
+ const parts = [
106
+ ...(location === undefined ? [] : [location]),
107
+ ...hints,
108
+ ...(omitted > 0 ? [`${omitted} additional rejected keys omitted`] : []),
109
+ ];
110
+ return parts.length > 0 ? { rejectedKeys, hint: parts.join("; ") } : { rejectedKeys };
78
111
  }
79
112
 
80
113
  function isPlainRecord(value: unknown): value is Record<string, unknown> {
81
114
  return typeof value === "object" && value !== null && !Array.isArray(value);
82
115
  }
83
116
 
117
+ function nestedPositionalHandleKeys(entry: Record<string, unknown>): string[] {
118
+ const keys: string[] = [];
119
+ for (const alias of ["task", "content"] as const) {
120
+ const value = entry[alias];
121
+ if (!isPlainRecord(value)) continue;
122
+ for (const key of Object.keys(value)) {
123
+ if (POSITIONAL_HANDLE_KEYS.has(key)) keys.push(`${alias}.${key}`);
124
+ }
125
+ }
126
+ return keys;
127
+ }
128
+
84
129
  /**
85
130
  * Single source of truth for todo_write pre-coercion validation. Every
86
131
  * rejection carries a code so the caller can surface an actionable correction
@@ -119,6 +164,13 @@ export function validateRawTodoArguments(arguments_: Record<string, unknown>): R
119
164
  code: "todo-write-unknown-op-entry-key",
120
165
  detail: keyRejectionDetail(unknownEntryKeys, location),
121
166
  };
167
+ const nestedHandles = nestedPositionalHandleKeys(entry);
168
+ if (nestedHandles.length > 0)
169
+ return {
170
+ outcome: "reject",
171
+ code: "todo-write-unknown-op-entry-key",
172
+ detail: keyRejectionDetail(nestedHandles, location),
173
+ };
122
174
  const target = entry.task ?? entry.content;
123
175
  if ((op === "done" || op === "drop") && !target && !entry.phase) {
124
176
  return { outcome: "reject", code: "todo-write-done-drop-requires-target", detail: { hint: location } };
@@ -539,9 +539,10 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
539
539
  entry => (entry.op === "done" || entry.op === "drop") && !entry.task && !entry.phase,
540
540
  );
541
541
  if (missingTarget) {
542
- // Models reach for a positional handle here (`id: "1"`, `index: 2`). Those keys
543
- // are stripped before this point, leaving a targetless op, so the message has to
544
- // name the one thing that does work: the task's content, verbatim.
542
+ // Model-issued calls never arrive here with a positional handle (`id: "1"`,
543
+ // `index: 2`): raw validation rejects the key first and carries the same
544
+ // correction. Bridges that call execute directly (eval's callSessionTool)
545
+ // skip that validation, so this still has to name what actually works.
545
546
  const errors = [
546
547
  `Missing task or phase for ${missingTarget.op} operation. ` +
547
548
  `Pass "task" with the task's exact content, or "phase" with the phase name; ` +