@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
@@ -80,6 +80,7 @@ export interface SdkSurfacePolicy {
80
80
  export interface SdkSurfacePolicyOptions {
81
81
  bindings: Iterable<string>;
82
82
  workflowGateAvailable: boolean;
83
+ imageUploadsAvailable?: boolean;
83
84
  isBindingInstalled?: (binding: string) => boolean;
84
85
  }
85
86
 
@@ -96,6 +97,7 @@ export function createSdkSurfacePolicy(options: SdkSurfacePolicyOptions): SdkSur
96
97
  operation.kind === kind &&
97
98
  !BROKER_LIFECYCLE_CONTROL_OPERATIONS.has(operation.sdkId) &&
98
99
  !UNINSTALLED_CONTROL_OPERATIONS.has(operation.sdkId) &&
100
+ (!operation.sdkId.startsWith("turn.image.") || options.imageUploadsAvailable === true) &&
99
101
  (!WORKFLOW_GATE_CONTROL_OPERATIONS.has(operation.sdkId) || options.workflowGateAvailable) &&
100
102
  (!required[operation.sdkId] || hasBinding(required[operation.sdkId]!)),
101
103
  ).map(operation => operation.sdkId),
@@ -129,11 +131,13 @@ export function createSdkCapabilities(
129
131
  export function createSdkSurfacePolicyForContext(
130
132
  ctx: ExtensionContext,
131
133
  workflowGateAvailable = false,
134
+ imageUploadsAvailable = false,
132
135
  ): SdkSurfacePolicy {
133
136
  const bindings = ctx.sdkBindings?.() ?? [];
134
137
  return createSdkSurfacePolicy({
135
138
  bindings,
136
139
  workflowGateAvailable,
140
+ imageUploadsAvailable,
137
141
  isBindingInstalled: binding => typeof (ctx as unknown as Record<string, unknown>)[binding] === "function",
138
142
  });
139
143
  }
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
3
3
  import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import { SdkClient } from "../client/client";
6
+ import { SESSION_HOST_OBSERVER_CAPABILITY, TURN_STREAM_CAPABILITY } from "./host";
6
7
  import { SessionSdkSessionRuntime, type SessionSdkTransport } from "./session-runtime";
7
8
  import { createSdkWebSocketTransport, type SdkWebSocketTransportDependencies } from "./websocket-transport";
8
9
 
@@ -29,6 +30,28 @@ async function probeWebSocketEndpoint(url: string, token: string): Promise<void>
29
30
  }
30
31
  }
31
32
 
33
+ async function waitForFrame(
34
+ frames: readonly Record<string, unknown>[],
35
+ predicate: (frame: Record<string, unknown>) => boolean,
36
+ label: string,
37
+ ): Promise<Record<string, unknown>> {
38
+ const deadline = Date.now() + 5_000;
39
+ while (true) {
40
+ const frame = frames.find(predicate);
41
+ if (frame) return frame;
42
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${label}`);
43
+ await Bun.sleep(5);
44
+ }
45
+ }
46
+
47
+ async function waitFor(predicate: () => boolean, label: string): Promise<void> {
48
+ const deadline = Date.now() + 5_000;
49
+ while (!predicate()) {
50
+ if (Date.now() >= deadline) throw new Error(`Timed out waiting for ${label}`);
51
+ await Bun.sleep(5);
52
+ }
53
+ }
54
+
32
55
  describe("SDK WebSocket transport lifecycle", () => {
33
56
  test("concurrent start calls share one endpoint and one server", async () => {
34
57
  const stateRoot = await tempStateRoot();
@@ -193,7 +216,72 @@ describe("SDK WebSocket transport lifecycle", () => {
193
216
  await fs.rm(stateRoot, { recursive: true, force: true });
194
217
  }
195
218
  });
196
- test("event replay capability claims cannot authorize gated replay", async () => {
219
+ test("loopback replay preserves replay-first, split, and subset capability negotiation", async () => {
220
+ const stateRoot = await tempStateRoot();
221
+ const transport = await createSdkWebSocketTransport({
222
+ sessionId: "loopback-split-capabilities",
223
+ stateRoot,
224
+ token: "token",
225
+ });
226
+ const runtime = new SessionSdkSessionRuntime({ transport });
227
+ const frames: Record<string, unknown>[] = [];
228
+ let socket: WebSocket | undefined;
229
+ const required = [TURN_STREAM_CAPABILITY, SESSION_HOST_OBSERVER_CAPABILITY];
230
+ try {
231
+ const endpoint = await runtime.start();
232
+ socket = new WebSocket(`${endpoint.url}?token=token`);
233
+ socket.addEventListener("message", event => {
234
+ frames.push(JSON.parse(String((event as MessageEvent).data)) as Record<string, unknown>);
235
+ });
236
+ await waitForFrame(frames, frame => frame.type === "hello", "server hello");
237
+ socket.send(
238
+ JSON.stringify({
239
+ type: "event_replay",
240
+ id: "replay-before-hello",
241
+ sinceSeq: 0,
242
+ capabilities: [],
243
+ }),
244
+ );
245
+ await waitForFrame(
246
+ frames,
247
+ frame => frame.type === "event_replay_result" && frame.id === "replay-before-hello",
248
+ "replay before hello",
249
+ );
250
+ socket.send(JSON.stringify({ type: "hello", protocolVersion: 3, capabilities: [TURN_STREAM_CAPABILITY] }));
251
+ await waitFor(
252
+ () => runtime.connectionIdsWithCapabilities([TURN_STREAM_CAPABILITY]).length === 1,
253
+ "hello after replay",
254
+ );
255
+
256
+ const replayIds = ["observer-addition", "observer-subset", "observer-empty"];
257
+ for (const [index, capabilities] of [
258
+ [SESSION_HOST_OBSERVER_CAPABILITY],
259
+ [SESSION_HOST_OBSERVER_CAPABILITY],
260
+ [],
261
+ ].entries()) {
262
+ const id = replayIds[index]!;
263
+ socket.send(JSON.stringify({ type: "event_replay", id, sinceSeq: 0, capabilities }));
264
+ await waitForFrame(frames, frame => frame.type === "event_replay_result" && frame.id === id, id);
265
+ expect(runtime.connectionIdsWithCapabilities(required)).toHaveLength(1);
266
+ }
267
+
268
+ runtime.sendFrameToCapabilities(required, {
269
+ type: "event",
270
+ kind: "tool_activity",
271
+ payload: { phase: "started" },
272
+ });
273
+ await waitForFrame(
274
+ frames,
275
+ frame => frame.type === "event" && frame.kind === "tool_activity",
276
+ "live observer event",
277
+ );
278
+ } finally {
279
+ socket?.close();
280
+ await runtime.stop().catch(() => undefined);
281
+ await fs.rm(stateRoot, { recursive: true, force: true });
282
+ }
283
+ }, 30_000);
284
+ test("event replay keeps tool activity gated without tool_activity_v2", async () => {
197
285
  const stateRoot = await tempStateRoot();
198
286
  const transport = await createSdkWebSocketTransport({
199
287
  sessionId: "replay-capability-claim",
@@ -223,7 +311,7 @@ describe("SDK WebSocket transport lifecycle", () => {
223
311
  id: "forged-capability-claim",
224
312
  sinceGeneration: 1,
225
313
  sinceSeq: 0,
226
- capabilities: ["tool_activity_v2"],
314
+ capabilities: [SESSION_HOST_OBSERVER_CAPABILITY],
227
315
  }),
228
316
  );
229
317
  const frame = await replay;
@@ -3,6 +3,7 @@ import * as fs from "node:fs/promises";
3
3
  import * as path from "node:path";
4
4
  import { exactUnlink, type NativeExactFileIdentity } from "@gajae-code/natives";
5
5
  import {
6
+ AWAITING_INPUT_CAPABILITY,
6
7
  canDeliverSdkEvent,
7
8
  SESSION_HOST_OBSERVER_CAPABILITY,
8
9
  TOOL_ACTIVITY_CAPABILITY,
@@ -167,6 +168,9 @@ export async function createSdkWebSocketTransport(
167
168
  if (!socket) throw new Error("SDK connection is no longer available.");
168
169
  socket.send(JSON.stringify(frame));
169
170
  },
171
+ isConnectionOpen(connectionId) {
172
+ return sockets.has(connectionId);
173
+ },
170
174
  start: async () => {
171
175
  if (stopPromise) await stopPromise;
172
176
  if (startPromise) return await startPromise;
@@ -203,6 +207,7 @@ export async function createSdkWebSocketTransport(
203
207
  protocolVersion: 3,
204
208
  connectionId: socket.data.connectionId,
205
209
  capabilities: [
210
+ AWAITING_INPUT_CAPABILITY,
206
211
  TOOL_ACTIVITY_CAPABILITY,
207
212
  TURN_STREAM_CAPABILITY,
208
213
  SESSION_HOST_OBSERVER_CAPABILITY,
@@ -226,16 +231,23 @@ export async function createSdkWebSocketTransport(
226
231
  );
227
232
  return;
228
233
  }
229
- if (
234
+ const isInitialHello =
230
235
  frame.type === "hello" &&
231
236
  frame.protocolVersion === 3 &&
232
- (frame.capabilities === undefined || Array.isArray(frame.capabilities)) &&
233
- !helloReceived.has(socket.data.connectionId)
237
+ !helloReceived.has(socket.data.connectionId);
238
+ if (
239
+ (isInitialHello || frame.type === "event_replay") &&
240
+ (frame.capabilities === undefined ||
241
+ (Array.isArray(frame.capabilities) &&
242
+ frame.capabilities.every((value: unknown) => typeof value === "string")))
234
243
  ) {
235
- const capabilities = Array.isArray(frame.capabilities)
236
- ? frame.capabilities.filter((value): value is string => typeof value === "string")
237
- : [];
238
- helloReceived.add(socket.data.connectionId);
244
+ const capabilities = [
245
+ ...new Set([
246
+ ...(negotiatedCapabilities.get(socket.data.connectionId) ?? []),
247
+ ...((frame.capabilities as string[] | undefined) ?? []),
248
+ ]),
249
+ ];
250
+ if (isInitialHello) helloReceived.add(socket.data.connectionId);
239
251
  negotiatedCapabilities.set(socket.data.connectionId, new Set(capabilities));
240
252
  capabilitiesHandler?.(socket.data.connectionId, capabilities);
241
253
  }
@@ -101,6 +101,10 @@ export type SessionCreateTarget = SessionLifecycleCoordinatorTarget & {
101
101
  readonly body?: string;
102
102
  readonly modelPreset?: string;
103
103
  readonly mcpServers?: readonly Record<string, unknown>[];
104
+ /** Per-session environment for the hosted session; `GJC_*` names are reserved (#6735). */
105
+ readonly env?: Readonly<Record<string, string>>;
106
+ /** Inherited broker environment names removed from the hosted session (#6735). */
107
+ readonly unsetEnv?: readonly string[];
104
108
  readonly worktree?: SessionLifecycleWorktreeTarget;
105
109
  readonly readiness?: "immediate" | "deferred";
106
110
  readonly readinessTimeoutMs?: number;
@@ -115,6 +119,10 @@ export type SessionForkTarget = SessionLifecycleCoordinatorTarget & {
115
119
  readonly body?: string;
116
120
  readonly modelPreset?: string;
117
121
  readonly mcpServers?: readonly Record<string, unknown>[];
122
+ /** Per-session environment for the hosted session; `GJC_*` names are reserved (#6735). */
123
+ readonly env?: Readonly<Record<string, string>>;
124
+ /** Inherited broker environment names removed from the hosted session (#6735). */
125
+ readonly unsetEnv?: readonly string[];
118
126
  readonly worktree?: SessionLifecycleWorktreeTarget;
119
127
  readonly readinessTimeoutMs?: number;
120
128
  };
@@ -128,6 +136,10 @@ export type SessionResumeTarget = SessionLifecycleCoordinatorTarget & {
128
136
  readonly body?: string;
129
137
  readonly modelPreset?: string;
130
138
  readonly mcpServers?: readonly Record<string, unknown>[];
139
+ /** Per-session environment for the hosted session; `GJC_*` names are reserved (#6735). */
140
+ readonly env?: Readonly<Record<string, string>>;
141
+ /** Inherited broker environment names removed from the hosted session (#6735). */
142
+ readonly unsetEnv?: readonly string[];
131
143
  readonly worktree?: SessionLifecycleWorktreeTarget;
132
144
  readonly readinessTimeoutMs?: number;
133
145
  };
@@ -550,7 +562,7 @@ export function validateSessionLifecycleMutationRequest(request: unknown): Sessi
550
562
  }
551
563
 
552
564
  function certaintyForBrokerCode(code: string): SessionLifecycleCertainty {
553
- if (code === "terminal_uncertain") return "uncertain";
565
+ if (code === "terminal_uncertain" || code === "legacy_lifecycle_ambiguous") return "uncertain";
554
566
  if (code === "cleanup_pending") return "cleanup_pending";
555
567
  if (RETRYABLE_BROKER_ERRORS.has(code)) return "retryable";
556
568
  return "terminal";
@@ -686,6 +698,7 @@ const savedSessionScopeDetailCodes = new Set([
686
698
  "migration_busy",
687
699
  "atomic_unavailable",
688
700
  "invalid_request",
701
+ "permission_denied",
689
702
  "durability_failed",
690
703
  "durability_not_provable",
691
704
  "capacity_exceeded",
@@ -700,6 +713,7 @@ const savedSessionCandidateDetailCodes = new Set([
700
713
  "cwd_not_utf8",
701
714
  "cwd_network_unsupported",
702
715
  "cwd_identity_unavailable",
716
+ "permission_denied",
703
717
  "cwd_io_error",
704
718
  ]);
705
719
 
@@ -44,7 +44,8 @@
44
44
  * WHERE it runs. `sdk.flushWorktreeOnDeadline` still defaults to on, but the
45
45
  * implicit default applies only in a LINKED worktree (`gitDir !== commonDir`) —
46
46
  * what agent/paseo sessions run in, and the #5583 field report. In the user's
47
- * primary checkout an autosave needs an explicit opt-in.
47
+ * primary checkout an autosave needs an explicit opt-in from the user/global
48
+ * config or a runtime override. A project `.gjc` file is not that opt-in.
48
49
  */
49
50
 
50
51
  import * as fsp from "node:fs/promises";
@@ -66,9 +67,10 @@ export interface PromptDeadlineFlushOptions {
66
67
  /** Active session's agent directory, used to keep volatile broker state out of the WIP commit. */
67
68
  readonly agentDir?: string;
68
69
  /**
69
- * Whether the caller resolved `sdk.flushWorktreeOnDeadline` to `true` from a
70
- * value the user actually wrote, rather than from the schema default. Only an
71
- * explicit opt-in may autosave outside a linked worktree.
70
+ * Whether the caller resolved `sdk.flushWorktreeOnDeadline` to `true` from the
71
+ * user/global config or a runtime override, rather than from the schema
72
+ * default or a project `.gjc` file. Only that opt-in may autosave outside a
73
+ * linked worktree.
72
74
  */
73
75
  readonly explicitOptIn?: boolean;
74
76
  /**
@@ -162,6 +164,76 @@ function isLinkedWorktree(repository: git.GitRepository): boolean {
162
164
  return path.resolve(repository.gitDir) !== path.resolve(repository.commonDir);
163
165
  }
164
166
 
167
+ const DEADLINE_FLUSH_SETTING = "sdk.flushWorktreeOnDeadline";
168
+
169
+ /**
170
+ * The settings view both deadline hosts already hold. `get` is the merged value
171
+ * (global, then project, then runtime override, otherwise the schema default).
172
+ * `getGlobal` and `getOverride` are the layers a project file cannot write.
173
+ */
174
+ export interface DeadlineFlushSettings {
175
+ get(path: typeof DEADLINE_FLUSH_SETTING): unknown;
176
+ getAgentDir?(): string | undefined;
177
+ getGlobal?(path: typeof DEADLINE_FLUSH_SETTING): unknown;
178
+ getOverride?(path: typeof DEADLINE_FLUSH_SETTING): unknown;
179
+ }
180
+
181
+ /**
182
+ * True only when the user wrote `sdk.flushWorktreeOnDeadline: true` in a runtime
183
+ * override or the global config. The schema default and a project `.gjc` file
184
+ * do not count: `Settings.has()` sees the merged view, so a repo-shipped `true`
185
+ * would otherwise open the primary-checkout gate.
186
+ */
187
+ export function explicitDeadlineFlushOptIn(settings: DeadlineFlushSettings | undefined): boolean {
188
+ if (!settings) return false;
189
+ if (typeof settings.getOverride === "function") {
190
+ const override = settings.getOverride(DEADLINE_FLUSH_SETTING);
191
+ if (override !== undefined) return override === true;
192
+ }
193
+ if (typeof settings.getGlobal === "function") return settings.getGlobal(DEADLINE_FLUSH_SETTING) === true;
194
+ return false;
195
+ }
196
+
197
+ /**
198
+ * Whether this deadline must not autosave.
199
+ *
200
+ * A runtime override wins, then the user/global value. A project file cannot
201
+ * cancel either one. A project `false` still opts out when the user has not
202
+ * written the key.
203
+ */
204
+ export function deadlineFlushSuppressed(settings: DeadlineFlushSettings | undefined): boolean {
205
+ if (!settings) return false;
206
+ if (typeof settings.getOverride === "function") {
207
+ const override = settings.getOverride(DEADLINE_FLUSH_SETTING);
208
+ if (override === false) return true;
209
+ if (override === true) return false;
210
+ }
211
+ if (typeof settings.getGlobal === "function") {
212
+ const globalValue = settings.getGlobal(DEADLINE_FLUSH_SETTING);
213
+ if (globalValue === false) return true;
214
+ if (globalValue === true) return false;
215
+ }
216
+ return settings.get(DEADLINE_FLUSH_SETTING) === false;
217
+ }
218
+
219
+ /**
220
+ * Deadline-host entry for the worktree autosave. Both the session runtime and
221
+ * the notification bus call this so the opt-in decision cannot drift.
222
+ */
223
+ export async function autosaveWorktreeOnPromptDeadline(
224
+ cwd: string,
225
+ settings: DeadlineFlushSettings | undefined,
226
+ options?: { readonly isCurrent?: () => boolean; readonly signal?: AbortSignal },
227
+ ): Promise<PromptDeadlineFlushResult | undefined> {
228
+ if (deadlineFlushSuppressed(settings)) return undefined;
229
+ return flushWorktreeOnPromptDeadline(cwd, {
230
+ agentDir: settings?.getAgentDir?.(),
231
+ explicitOptIn: explicitDeadlineFlushOptIn(settings),
232
+ isCurrent: options?.isCurrent,
233
+ signal: options?.signal,
234
+ });
235
+ }
236
+
165
237
  /**
166
238
  * Commit any uncommitted work in the worktree owning `cwd`.
167
239
  *
@@ -195,7 +267,8 @@ export async function flushWorktreeOnPromptDeadline(
195
267
  if (!explicitOptIn && !isLinkedWorktree(repository)) {
196
268
  logger.debug(
197
269
  `sdk: prompt deadline worktree autosave skipped; ${worktreeRoot} is a primary checkout the session does ` +
198
- `not own. Set sdk.flushWorktreeOnDeadline to true to autosave here anyway.`,
270
+ `not own. Set sdk.flushWorktreeOnDeadline to true in the user config or a runtime override to ` +
271
+ `autosave here anyway.`,
199
272
  );
200
273
  return undefined;
201
274
  }
@@ -103,10 +103,18 @@ export class PromptDeadlineManager {
103
103
  readonly #uncertaintyRecoveryPending = new Set<string>();
104
104
  readonly #expiring = new Set<string>();
105
105
  readonly #deadlineAttempts = new Set<string>();
106
+ /**
107
+ * The next deadline came due while `#expire` was still inside an await
108
+ * (tool-boundary grace or the bounded flush). The in-flight pass owns the
109
+ * single-flight slot, so the timer callback only records the follow-up and
110
+ * `#onDeadline` runs it after this pass returns.
111
+ */
112
+ readonly #deadlineFollowUps = new Set<string>();
106
113
  readonly #pendingTerminalTransitions = new Set<string>();
107
114
  readonly #deadlineDeferredTerminalTransitions = new Set<string>();
108
115
  readonly #deadlineTerminalizationConfirmed = new Set<string>();
109
116
  readonly #terminalPublicationPending = new Map<string, SdkPromptTerminalOutcome>();
117
+ readonly #toolsStillPendingOnLastCheck = new Set<string>();
110
118
  readonly #deadlineStartCleanup = new Map<string, () => void>();
111
119
  readonly #pendingTerminalFailureReasons = new Map<
112
120
  string,
@@ -187,7 +195,11 @@ export class PromptDeadlineManager {
187
195
  }
188
196
 
189
197
  /** Run the durability hook under the shared bound; see `runBoundedDeadlineFlush`. */
190
- async #runDeadlineFlush(correlation: InvocationCorrelation, lease: PromptDeadlineLease): Promise<void> {
198
+ async #runDeadlineFlush(
199
+ correlation: InvocationCorrelation,
200
+ lease: PromptDeadlineLease,
201
+ generation: number,
202
+ ): Promise<void> {
191
203
  const hook = this.#onDeadlineExceeded;
192
204
  if (hook === undefined) return;
193
205
  const key = leaseKey(correlation);
@@ -195,7 +207,9 @@ export class PromptDeadlineManager {
195
207
  signal =>
196
208
  hook(correlation, signal, () => {
197
209
  const current = this.#leases.get(key);
198
- return current === lease && this.#now() >= promptDeadlineAt(current);
210
+ return (
211
+ current === lease && current.generation === generation && this.#now() >= promptDeadlineAt(current)
212
+ );
199
213
  }),
200
214
  this.#deadlineFlushTimeoutMs,
201
215
  );
@@ -239,12 +253,24 @@ export class PromptDeadlineManager {
239
253
  }
240
254
 
241
255
  async #onDeadline(key: string): Promise<void> {
242
- if (this.#deadlineAttempts.has(key)) return;
256
+ if (this.#deadlineAttempts.has(key)) {
257
+ this.#deadlineFollowUps.add(key);
258
+ return;
259
+ }
243
260
  this.#deadlineAttempts.add(key);
244
261
  try {
245
- await this.#expire(key);
262
+ do {
263
+ this.#deadlineFollowUps.delete(key);
264
+ await this.#expire(key);
265
+ } while (this.#deadlineFollowUps.has(key));
246
266
  } finally {
247
267
  this.#deadlineAttempts.delete(key);
268
+ if (this.#deadlineFollowUps.has(key)) {
269
+ this.#deadlineFollowUps.delete(key);
270
+ queueMicrotask(() => {
271
+ void this.#onDeadline(key);
272
+ });
273
+ }
248
274
  }
249
275
  }
250
276
 
@@ -385,7 +411,9 @@ export class PromptDeadlineManager {
385
411
  terminalization =
386
412
  (await this.#onDeadlineTerminalization?.(correlation, () => {
387
413
  const current = this.#leases.get(key);
388
- return current === lease && this.#now() >= promptDeadlineAt(current);
414
+ return (
415
+ current === lease && current.generation === generation && this.#now() >= promptDeadlineAt(current)
416
+ );
389
417
  })) ?? "settled";
390
418
  } catch {
391
419
  terminalization = "uncertain";
@@ -410,7 +438,7 @@ export class PromptDeadlineManager {
410
438
  // The claim above remains the durable pending marker throughout both
411
439
  // bounded operations, so restart recovery never reports unsaved work as
412
440
  // finished.
413
- await this.#runDeadlineFlush(correlation, lease);
441
+ await this.#runDeadlineFlush(correlation, lease, generation);
414
442
  // Fence again AFTER the flush (#5623 review round 2): progress can land
415
443
  // while the bounded git operation runs and renews the lease.
416
444
  if (this.#backOffIfSuperseded(key, lease, generation)) return;
@@ -532,14 +560,19 @@ export class PromptDeadlineManager {
532
560
  /**
533
561
  * After an awaited reconciliation call, verify the captured lease is still
534
562
  * authoritative. Fresh attributable progress during the await advances the
535
- * lease generation and its deadline; when superseded, cancel this expiry
536
- * instance (release the expiration fence, clear any retry budget, and
537
- * reschedule the deadline) and report it so the caller stops. A cleared or
538
- * re-accepted lease is stale by identity too.
563
+ * lease generation and its deadline. A tool-boundary wait or a flush can
564
+ * outlive that renewed lease, so a generation mismatch is stale even when
565
+ * the new deadline is already due — the time check alone would let this
566
+ * pass publish with the generation it captured, the publisher would refuse,
567
+ * and uncertainty recovery would refuse to re-anchor the same mismatch.
568
+ * Cancel this expiry instance (release the expiration fence, clear any
569
+ * retry budget, and reschedule the deadline) and report it so the caller
570
+ * stops. The follow-up expiry captures the current generation. A cleared
571
+ * or re-accepted lease is stale by identity too.
539
572
  */
540
- #backOffIfSuperseded(key: string, lease: PromptDeadlineLease, _generation: number): boolean {
573
+ #backOffIfSuperseded(key: string, lease: PromptDeadlineLease, generation: number): boolean {
541
574
  const current = this.#leases.get(key);
542
- if (current !== lease || this.#now() < promptDeadlineAt(current)) {
575
+ if (current !== lease || current.generation !== generation || this.#now() < promptDeadlineAt(current)) {
543
576
  this.#expiring.delete(key);
544
577
  this.#expiryRetries.delete(key);
545
578
  if (current) this.#schedule(key);
@@ -578,13 +611,32 @@ export class PromptDeadlineManager {
578
611
  this.clear(correlation);
579
612
  }
580
613
 
581
- #scheduleTerminalPublicationRetry(key: string): void {
614
+ #scheduleTerminalPublicationRetry(key: string, delayMs?: number): void {
582
615
  this.#clearTimer(key);
583
- const timer = setTimeout(() => void this.#onDeadline(key), UNCERTAINTY_RETRY_DELAY_MS);
616
+ // Use immediate retry (0ms) if tools were pending on last check;
617
+ // otherwise use normal retry delay. This allows quick detection when tools drain.
618
+ const effectiveDelayMs =
619
+ delayMs ?? (this.#toolsStillPendingOnLastCheck.has(key) ? 0 : UNCERTAINTY_RETRY_DELAY_MS);
620
+ const timer = setTimeout(() => void this.#onDeadline(key), effectiveDelayMs);
584
621
  (timer as unknown as { unref?: () => void }).unref?.();
585
622
  this.#timers.set(key, timer);
586
623
  }
587
624
 
625
+ /** Notify about termination check results to adjust retry strategy. */
626
+ notifyTerminationResult(
627
+ correlation: InvocationCorrelation,
628
+ _result: PromptDeadlineTerminalization,
629
+ reason: string,
630
+ ): void {
631
+ const key = leaseKey(correlation);
632
+ if (reason.includes("tools-still-pending")) {
633
+ this.#toolsStillPendingOnLastCheck.add(key);
634
+ } else {
635
+ // Tools have settled or are no longer relevant
636
+ this.#toolsStillPendingOnLastCheck.delete(key);
637
+ }
638
+ }
639
+
588
640
  #retry(key: string): void {
589
641
  const attempts = (this.#expiryRetries.get(key) ?? 0) + 1;
590
642
  this.#expiryRetries.set(key, attempts);
@@ -817,6 +869,7 @@ export class PromptDeadlineManager {
817
869
  this.#terminalPublicationPending.delete(key);
818
870
  this.#pendingTerminalFailureReasons.delete(key);
819
871
  this.#pendingTerminalEvidence.delete(key);
872
+ this.#toolsStillPendingOnLastCheck.delete(key);
820
873
  }
821
874
 
822
875
  clearAll(): void {
@@ -839,6 +892,7 @@ export class PromptDeadlineManager {
839
892
  this.#terminalPublicationPending.clear();
840
893
  this.#pendingTerminalFailureReasons.clear();
841
894
  this.#pendingTerminalEvidence.clear();
895
+ this.#toolsStillPendingOnLastCheck.clear();
842
896
  }
843
897
 
844
898
  /** For tests: current deadline or undefined if no lease. */
@@ -40,12 +40,14 @@ export interface TurnPromptImageInput {
40
40
  mimeType?: string;
41
41
  }
42
42
 
43
- /** Public input contract for ordered `turn.prompt`. */
44
- export interface TurnPromptInput {
43
+ /** Public input contract for ordered `turn.prompt`; direct and staged images cannot be mixed. */
44
+ export type TurnPromptInput = {
45
45
  text: string;
46
- images?: TurnPromptImageInput[];
47
46
  clientRef?: string;
48
- }
47
+ } & (
48
+ | { images?: TurnPromptImageInput[]; stagedImages?: never }
49
+ | { images?: never; stagedImages: Array<{ id: string }> }
50
+ );
49
51
 
50
52
  /**
51
53
  * Terminal outcome is preserved exactly; active records never age into terminal. An