@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
@@ -10,8 +10,8 @@ import { randomUUID } from "node:crypto";
10
10
  import * as fs from "node:fs";
11
11
  import * as os from "node:os";
12
12
  import * as path from "node:path";
13
- import { exactReplaceRetained, exactUnlink } from "@gajae-code/natives";
14
- import { $which, APP_NAME, isCompiledBinary, isEnoent, redactCrashSecrets, VERSION } from "@gajae-code/utils";
13
+ import { exactReplaceRetained, exactUnlink, exactUnlinkDirect } from "@gajae-code/natives";
14
+ import { $which, APP_NAME, isCompiledBinary, isEnoent, logger, redactCrashSecrets, VERSION } from "@gajae-code/utils";
15
15
  import { $ } from "bun";
16
16
  import chalk from "chalk";
17
17
  import { acquireFileLock } from "../config/file-lock";
@@ -38,6 +38,7 @@ import {
38
38
  createActivationRecordFile,
39
39
  type DirectoryIdentity,
40
40
  type FileIdentity,
41
+ type ReconcileResult,
41
42
  readActivationRecord,
42
43
  reconcileActivationRecord,
43
44
  sameDirectoryIdentity,
@@ -108,6 +109,60 @@ export interface BinaryReplacementOptions {
108
109
  verifyInstalledVersion: (expectedVersion: string) => Promise<InstalledVersionVerification>;
109
110
  }
110
111
 
112
+ function formatRetainedActivationPaths(
113
+ paths:
114
+ | Pick<
115
+ ReconcileResult,
116
+ "detachedPath" | "retainedSuccessorPath" | "retainedPlaceholderPath" | "retainedUnknownPath"
117
+ >
118
+ | undefined,
119
+ ): string {
120
+ if (!paths) return "";
121
+ const entries = [
122
+ paths.detachedPath !== undefined ? `detached=${paths.detachedPath}` : undefined,
123
+ paths.retainedSuccessorPath !== undefined ? `successor=${paths.retainedSuccessorPath}` : undefined,
124
+ paths.retainedPlaceholderPath !== undefined ? `placeholder=${paths.retainedPlaceholderPath}` : undefined,
125
+ paths.retainedUnknownPath !== undefined ? `unknown=${paths.retainedUnknownPath}` : undefined,
126
+ ].filter((entry): entry is string => entry !== undefined);
127
+ return entries.length > 0 ? `; retained paths: ${entries.join("|")}` : "";
128
+ }
129
+
130
+ export class InstallationAccessDeniedError extends Error {
131
+ readonly code = "access_denied";
132
+
133
+ constructor(
134
+ readonly targetPath: string,
135
+ readonly windowsErrorCode?: string,
136
+ readonly stagingCleanupWarning?: string,
137
+ readonly retainedPaths?: Pick<
138
+ ReconcileResult,
139
+ "detachedPath" | "retainedSuccessorPath" | "retainedPlaceholderPath" | "retainedUnknownPath"
140
+ >,
141
+ ) {
142
+ super(
143
+ `access_denied during exact replacement of ${targetPath}${windowsErrorCode ? ` (${windowsErrorCode})` : ""}${stagingCleanupWarning ? `; ${stagingCleanupWarning}` : ""}${formatRetainedActivationPaths(retainedPaths)}`,
144
+ );
145
+ this.name = "InstallationAccessDeniedError";
146
+ }
147
+ }
148
+
149
+ export class ActivationRetryCandidateRepointError extends Error {
150
+ readonly reasonCode: string;
151
+
152
+ constructor(
153
+ readonly candidatePath: string,
154
+ cause: unknown,
155
+ ) {
156
+ const reasonCode =
157
+ cause instanceof Error && /^[a-z][a-z0-9_]{0,63}$/.test(cause.message)
158
+ ? cause.message
159
+ : "activation_record_repoint_failed";
160
+ super(`${reasonCode}; retained retry candidate: ${candidatePath}`, { cause });
161
+ this.name = "ActivationRetryCandidateRepointError";
162
+ this.reasonCode = reasonCode;
163
+ }
164
+ }
165
+
111
166
  /**
112
167
  * Parse update subcommand arguments.
113
168
  * Returns undefined if not an update command.
@@ -232,6 +287,40 @@ function resolveNpmManagedTarget(
232
287
  return undefined;
233
288
  }
234
289
 
290
+ /**
291
+ * Detect a project-local package install: `<...>/node_modules/.bin/<name>` with
292
+ * or without an executable extension.
293
+ *
294
+ * On Windows a dependency-installed CLI is a real `.exe` copied into
295
+ * `node_modules/.bin`, so the `.cmd`/`.ps1` shim rules never match it and
296
+ * `gjc update` used to treat it as a standalone binary and try to replace it
297
+ * (#6842). That file belongs to the package manager and to the project's
298
+ * dependency tree: replacing it breaks the lockfile contract and, on Windows,
299
+ * fails on the file's ACL. The caller must redirect to the package manager
300
+ * instead of downloading a replacement.
301
+ */
302
+ function resolveProjectLocalPackageInstall(
303
+ ompPath: string,
304
+ platform: PathPlatform = process.platform,
305
+ ): { packageRoot: string } | undefined {
306
+ const pathApi = pathApiForPlatform(platform);
307
+ const resolved = pathApi.normalize(ompPath);
308
+ const binDir = pathApi.dirname(resolved);
309
+ if (pathApi.basename(binDir).toLowerCase() !== ".bin") return undefined;
310
+ if (pathApi.basename(pathApi.dirname(binDir)).toLowerCase() !== "node_modules") return undefined;
311
+ const extension = pathApi.extname(resolved).toLowerCase();
312
+ const basename = extension ? pathApi.basename(resolved, extension) : pathApi.basename(resolved);
313
+ if (basename.toLowerCase() !== APP_NAME.toLowerCase()) return undefined;
314
+ return { packageRoot: pathApi.dirname(binDir) };
315
+ }
316
+
317
+ export function resolveProjectLocalPackageInstallForTest(
318
+ ompPath: string,
319
+ platform: PathPlatform,
320
+ ): { packageRoot: string } | undefined {
321
+ return resolveProjectLocalPackageInstall(ompPath, platform);
322
+ }
323
+
235
324
  function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "npm" | "binary" {
236
325
  if (resolveNpmManagedTarget(ompPath)) return "npm";
237
326
  if (!bunBinDir) return "binary";
@@ -346,6 +435,15 @@ async function resolveUpdateTarget(): Promise<UpdateTarget> {
346
435
  );
347
436
  }
348
437
 
438
+ const projectLocalInstall = ompPath ? resolveProjectLocalPackageInstall(ompPath) : undefined;
439
+ if (ompPath && projectLocalInstall) {
440
+ throw new Error(
441
+ formatUnsupportedTargetMessage(
442
+ `${APP_NAME} at ${ompPath} is installed by a package manager in this project's node_modules; update the dependency instead of replacing that file`,
443
+ ),
444
+ );
445
+ }
446
+
349
447
  if (ompPath && isShimPath(ompPath, bunBinDir)) {
350
448
  if (isProtectedSourcePath(userPath)) {
351
449
  throw new Error(formatUnsupportedTargetMessage(`Refusing to install over a source checkout at ${userPath}`));
@@ -782,6 +880,60 @@ export async function recoverWindowsUpdateJournal(journalPath: string): Promise<
782
880
  throw new Error("legacy_update_journal_requires_manual_review");
783
881
  }
784
882
 
883
+ async function removeExactStagingFile(filePath: string, identity: FileIdentity): Promise<void> {
884
+ const quarantineName = `.${path.basename(filePath)}.cleanup.${randomUUID()}`;
885
+ const removed = exactUnlink(filePath, toNativeIdentity(identity, quarantineName));
886
+ let pathStillExists = false;
887
+ let snapshotFailure: string | undefined;
888
+ try {
889
+ pathStillExists = (await snapshotRegularFile(filePath)) !== undefined;
890
+ } catch (error) {
891
+ snapshotFailure = error instanceof Error ? error.message : String(error);
892
+ }
893
+ const recoveryPaths = [
894
+ removed.detachedPath ? `detached=${removed.detachedPath}` : undefined,
895
+ removed.retainedPlaceholderPath ? `placeholder=${removed.retainedPlaceholderPath}` : undefined,
896
+ removed.retainedUnknownPath ? `unknown=${removed.retainedUnknownPath}` : undefined,
897
+ removed.retainedSuccessorPath ? `successor=${removed.retainedSuccessorPath}` : undefined,
898
+ ].filter((value): value is string => value !== undefined);
899
+ if (
900
+ (!removed.ok && removed.code !== "not_found") ||
901
+ pathStillExists ||
902
+ snapshotFailure !== undefined ||
903
+ recoveryPaths.length > 0
904
+ ) {
905
+ const reason =
906
+ removed.code ?? (pathStillExists ? "path_still_exists" : snapshotFailure ? "snapshot_failed" : "unverified");
907
+ const details = [
908
+ `path=${filePath}`,
909
+ ...recoveryPaths,
910
+ ...(snapshotFailure !== undefined ? [`snapshot=${snapshotFailure}`] : []),
911
+ ].join("|");
912
+ throw new Error(`activation_retry_staging_cleanup_failed:${reason}:${details}`);
913
+ }
914
+ }
915
+
916
+ async function tryRemoveExactStagingFile(filePath: string, identity: FileIdentity): Promise<string | undefined> {
917
+ try {
918
+ await removeExactStagingFile(filePath, identity);
919
+ return undefined;
920
+ } catch (error) {
921
+ return error instanceof Error ? error.message : String(error);
922
+ }
923
+ }
924
+
925
+ async function repointActivationRecordToRetryCandidate(
926
+ record: ActivationRecordV1,
927
+ recordIdentity: FileIdentity,
928
+ candidatePath: string,
929
+ ): Promise<FileIdentity> {
930
+ try {
931
+ return await updateActivationRecordFile(record, recordIdentity);
932
+ } catch (error) {
933
+ throw new ActivationRetryCandidateRepointError(candidatePath, error);
934
+ }
935
+ }
936
+
785
937
  /**
786
938
  * Atomically replace the installed binary and roll back if version verification fails.
787
939
  */
@@ -830,6 +982,7 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
830
982
  }
831
983
  let record: ActivationRecordV1;
832
984
  let recordIdentity: FileIdentity;
985
+ let redundantStage: { readonly path: string; readonly identity: FileIdentity } | undefined;
833
986
  if (existing.status === "valid" && existing.record.phase !== "verified" && existing.record.phase !== "rolled_back") {
834
987
  if (
835
988
  existing.record.candidate.digest !== (options.candidate?.sha256.toLowerCase() ?? staged?.identity.sha256) ||
@@ -839,8 +992,43 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
839
992
  existing.record.candidate.channel !== options.candidate.channel))
840
993
  )
841
994
  throw new Error("activation_transaction_requires_reconciliation");
842
- record = existing.record;
843
- recordIdentity = existing.identity;
995
+ const currentStagingPath = path.resolve(options.tempPath);
996
+ const recordedStagingPath = path.resolve(existing.record.stagingPath);
997
+ if (currentStagingPath !== recordedStagingPath) {
998
+ const retryCandidate = staged ?? (await snapshotRegularFile(currentStagingPath));
999
+ if (retryCandidate && retryCandidate.identity.sha256 !== existing.record.candidate.digest)
1000
+ throw new Error("activation_retry_candidate_digest_mismatch");
1001
+ const activationNeedsStagedCandidate =
1002
+ existing.record.phase === "staged" || existing.record.phase === "pending_activation";
1003
+ const recordedStage = activationNeedsStagedCandidate
1004
+ ? await snapshotRegularFile(recordedStagingPath)
1005
+ : undefined;
1006
+ const recordedStageIsValid =
1007
+ recordedStage &&
1008
+ existing.record.stagingIdentity &&
1009
+ sameFileIdentity(existing.record.stagingIdentity, recordedStage.identity) &&
1010
+ recordedStage.identity.sha256 === existing.record.candidate.digest;
1011
+ if (activationNeedsStagedCandidate && !recordedStageIsValid) {
1012
+ if (!retryCandidate) throw new Error("activation_retry_candidate_missing");
1013
+ record = {
1014
+ ...existing.record,
1015
+ stagingPath: currentStagingPath,
1016
+ stagingIdentity: retryCandidate.identity,
1017
+ };
1018
+ recordIdentity = await repointActivationRecordToRetryCandidate(
1019
+ record,
1020
+ existing.identity,
1021
+ currentStagingPath,
1022
+ );
1023
+ } else {
1024
+ if (retryCandidate) redundantStage = { path: currentStagingPath, identity: retryCandidate.identity };
1025
+ record = existing.record;
1026
+ recordIdentity = existing.identity;
1027
+ }
1028
+ } else {
1029
+ record = existing.record;
1030
+ recordIdentity = existing.identity;
1031
+ }
844
1032
  } else {
845
1033
  if (!staged) throw new Error("installation_staging_unavailable");
846
1034
  record = {
@@ -873,26 +1061,97 @@ export async function replaceBinaryForUpdate(options: BinaryReplacementOptions):
873
1061
  allowPromotion: true,
874
1062
  expectedRecordIdentity: recordIdentity,
875
1063
  });
876
- if (promoted.status === "pending_activation") throw new Error("installation_pending_activation");
1064
+ if (promoted.reason === "candidate_changed" && redundantStage) {
1065
+ logger.warn("Activation staging path changed during reconciliation; leaving it untouched", {
1066
+ changedStagingPath: record.stagingPath,
1067
+ retryCandidatePath: redundantStage.path,
1068
+ });
1069
+ await repointActivationRecordToRetryCandidate(
1070
+ {
1071
+ ...record,
1072
+ phase: "pending_activation",
1073
+ stagingPath: redundantStage.path,
1074
+ stagingIdentity: redundantStage.identity,
1075
+ },
1076
+ recordIdentity,
1077
+ redundantStage.path,
1078
+ );
1079
+ throw new Error("installation_pending_activation");
1080
+ }
1081
+ if (promoted.status === "pending_activation") {
1082
+ if (promoted.reason === "candidate_missing" && redundantStage) {
1083
+ await repointActivationRecordToRetryCandidate(
1084
+ {
1085
+ ...record,
1086
+ phase: "pending_activation",
1087
+ stagingPath: redundantStage.path,
1088
+ stagingIdentity: redundantStage.identity,
1089
+ },
1090
+ recordIdentity,
1091
+ redundantStage.path,
1092
+ );
1093
+ } else {
1094
+ const stagingCleanupWarning = redundantStage
1095
+ ? await tryRemoveExactStagingFile(redundantStage.path, redundantStage.identity)
1096
+ : undefined;
1097
+ if (stagingCleanupWarning)
1098
+ logger.warn("Update staging cleanup failed while activation is pending", {
1099
+ warning: stagingCleanupWarning,
1100
+ });
1101
+ }
1102
+ throw new Error("installation_pending_activation");
1103
+ }
877
1104
  if (
878
1105
  (promoted.status !== "reconciled" &&
879
1106
  promoted.status !== "applied_unverified" &&
880
1107
  promoted.status !== "verified") ||
881
1108
  !promoted.record ||
882
1109
  !promoted.recordIdentity
883
- )
1110
+ ) {
1111
+ const stagingCleanupWarning = redundantStage
1112
+ ? await tryRemoveExactStagingFile(redundantStage.path, redundantStage.identity)
1113
+ : undefined;
1114
+ if (promoted.reason === "access_denied")
1115
+ throw new InstallationAccessDeniedError(targetPath, promoted.windowsErrorCode, stagingCleanupWarning, {
1116
+ detachedPath: promoted.detachedPath,
1117
+ retainedSuccessorPath: promoted.retainedSuccessorPath,
1118
+ retainedPlaceholderPath: promoted.retainedPlaceholderPath,
1119
+ retainedUnknownPath: promoted.retainedUnknownPath,
1120
+ });
1121
+ if (stagingCleanupWarning)
1122
+ logger.warn("Update staging cleanup failed after activation reconciliation failed", {
1123
+ reason: promoted.reason ?? promoted.status,
1124
+ warning: stagingCleanupWarning,
1125
+ });
884
1126
  throw new Error(promoted.reason ?? "installation_publication_unverified");
1127
+ }
885
1128
  record = promoted.record;
886
1129
  recordIdentity = promoted.recordIdentity;
1130
+ const cleanupWarnings: string[] = [];
887
1131
  try {
888
- const verification = await options.verifyInstalledVersion(options.expectedVersion);
1132
+ let verification = await options.verifyInstalledVersion(options.expectedVersion);
889
1133
  if (!verification.ok) throw new Error(formatVerificationFailure(verification, options.expectedVersion));
890
1134
  const after = await snapshotRegularFile(targetPath);
891
1135
  if (!after || !record.targetIdentity || !sameFileIdentity(record.targetIdentity, after.identity))
892
1136
  throw new Error("installation_postcheck_identity_changed");
1137
+ if (redundantStage) {
1138
+ const cleanupWarning = await tryRemoveExactStagingFile(redundantStage.path, redundantStage.identity);
1139
+ if (cleanupWarning) {
1140
+ cleanupWarnings.push(cleanupWarning);
1141
+ verification = { ...verification, cleanupWarning };
1142
+ }
1143
+ }
893
1144
  await updateActivationRecordFile({ ...record, phase: "verified" }, recordIdentity);
894
1145
  return verification;
895
1146
  } catch (primary) {
1147
+ const rollbackCleanupWarning = redundantStage
1148
+ ? await tryRemoveExactStagingFile(redundantStage.path, redundantStage.identity)
1149
+ : undefined;
1150
+ if (rollbackCleanupWarning) cleanupWarnings.push(rollbackCleanupWarning);
1151
+ if (cleanupWarnings.length > 0)
1152
+ logger.warn("Update staging cleanup failed before activation rollback", {
1153
+ warning: cleanupWarnings.join("\n"),
1154
+ });
896
1155
  try {
897
1156
  const after = await snapshotRegularFile(targetPath);
898
1157
  if (!after || !record.targetIdentity || !sameFileIdentity(record.targetIdentity, after.identity))
@@ -1135,14 +1394,179 @@ function binaryStagingPath(targetPath: string, stamp: string): string {
1135
1394
  return `${targetPath.slice(0, -extension.length)}.new.${stamp}${extension}`;
1136
1395
  }
1137
1396
 
1397
+ const UPDATE_FILE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
1398
+ const EXACT_REPLACE_DESTINATION_NAME = /^\.gjc-exact-replace-destination-([0-9a-f]+)-([0-9a-f]+)$/u;
1399
+ // Do not race a separate process that is still completing a native replacement in this directory.
1400
+ const EXACT_REPLACE_CLEANUP_GRACE_MS = 10 * 60 * 1000;
1401
+
1402
+ function isUpdateStagingName(targetPath: string, name: string): boolean {
1403
+ const extension = path.extname(targetPath);
1404
+ const prefix = `${path.basename(targetPath, extension)}.new.`;
1405
+ if (!name.startsWith(prefix)) return false;
1406
+ const suffix = name.slice(prefix.length);
1407
+ if (extension && !suffix.endsWith(extension)) return false;
1408
+ const id = extension ? suffix.slice(0, -extension.length) : suffix;
1409
+ return UPDATE_FILE_ID.test(id);
1410
+ }
1411
+
1412
+ function isUpdateBackupName(targetPath: string, name: string): boolean {
1413
+ const prefix = `${path.basename(targetPath)}.bak.`;
1414
+ if (name.startsWith(prefix) && UPDATE_FILE_ID.test(name.slice(prefix.length))) return true;
1415
+ const legacyPrefix = `${path.basename(targetPath)}-`;
1416
+ if (!name.startsWith(legacyPrefix)) return false;
1417
+ const legacyName = name.slice(legacyPrefix.length);
1418
+ const marker = legacyName.indexOf(".bak.");
1419
+ if (marker < 0) return false;
1420
+ const version = legacyName.slice(0, marker);
1421
+ const timestamp = legacyName.slice(marker + ".bak.".length);
1422
+ return (
1423
+ /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/u.test(version) && /^[0-9A-Za-z._-]+$/u.test(timestamp)
1424
+ );
1425
+ }
1426
+
1427
+ export function isUpdateStagingNameForTest(targetPath: string, name: string): boolean {
1428
+ return isUpdateStagingName(targetPath, name);
1429
+ }
1430
+
1431
+ export function isUpdateBackupNameForTest(targetPath: string, name: string): boolean {
1432
+ return isUpdateBackupName(targetPath, name);
1433
+ }
1434
+
1435
+ async function removeExactUpdateArtifact(filePath: string, parent: DirectoryIdentity): Promise<string | undefined> {
1436
+ try {
1437
+ const snapshot = await snapshotRegularFile(filePath);
1438
+ if (!snapshot) return undefined;
1439
+ if (snapshot.identity.parentDev !== parent.dev || snapshot.identity.parentIno !== parent.ino)
1440
+ return `parent_identity_changed:${filePath}`;
1441
+ const removed = exactUnlinkDirect(
1442
+ filePath,
1443
+ toNativeIdentity(snapshot.identity, `.gjc-update-cleanup-${randomUUID()}`),
1444
+ );
1445
+ if (removed.ok || removed.code === "not_found") return undefined;
1446
+ const retained = [
1447
+ removed.detachedPath ? `detached=${removed.detachedPath}` : undefined,
1448
+ removed.retainedPlaceholderPath ? `placeholder=${removed.retainedPlaceholderPath}` : undefined,
1449
+ removed.retainedUnknownPath ? `unknown=${removed.retainedUnknownPath}` : undefined,
1450
+ removed.retainedSuccessorPath ? `successor=${removed.retainedSuccessorPath}` : undefined,
1451
+ ].filter((entry): entry is string => entry !== undefined);
1452
+ return `exact_unlink_${removed.code ?? "failed"}:${filePath}${retained.length ? `|${retained.join("|")}` : ""}`;
1453
+ } catch (error) {
1454
+ return `snapshot_failed:${filePath}:${error instanceof Error ? error.message : String(error)}`;
1455
+ }
1456
+ }
1457
+
1458
+ /**
1459
+ * Reclaim only generated update artifacts while holding the install lock.
1460
+ * Keep a stage only while an active activation record owns it, and retain at
1461
+ * most the rollback backup the record can still need.
1462
+ */
1463
+ async function cleanupBinaryUpdateArtifacts(
1464
+ targetPath: string,
1465
+ originalParent: DirectoryIdentity,
1466
+ retainedRetryCandidatePath?: string,
1467
+ ): Promise<string | undefined> {
1468
+ const parentPath = path.dirname(path.resolve(targetPath));
1469
+ const warnings: string[] = [];
1470
+ try {
1471
+ const currentParent = await snapshotDirectory(parentPath);
1472
+ if (!sameDirectoryIdentity(originalParent, currentParent)) return "installation_parent_changed";
1473
+ const entries = await fs.promises.readdir(parentPath, { withFileTypes: true });
1474
+ const activation = await readActivationRecord(targetPath);
1475
+ if (activation.status === "malformed" || activation.status === "foreign") {
1476
+ warnings.push(`activation_record_untrusted:${activation.reason}`);
1477
+ } else {
1478
+ const record = activation.status === "valid" ? activation.record : undefined;
1479
+ const terminal = record?.phase === "verified" || record?.phase === "rolled_back";
1480
+ const pinnedStages = new Set<string>();
1481
+ if (record && !terminal) pinnedStages.add(path.resolve(record.stagingPath));
1482
+ if (retainedRetryCandidatePath) pinnedStages.add(path.resolve(retainedRetryCandidatePath));
1483
+ for (const entry of entries) {
1484
+ if (!isUpdateStagingName(targetPath, entry.name)) continue;
1485
+ const stagingPath = path.join(parentPath, entry.name);
1486
+ if (pinnedStages.has(stagingPath)) continue;
1487
+ const warning = await removeExactUpdateArtifact(stagingPath, originalParent);
1488
+ if (warning) warnings.push(warning);
1489
+ }
1490
+
1491
+ const recognizedBackup = record && isUpdateBackupName(targetPath, record.backupName);
1492
+ let preserveBackup: string | undefined;
1493
+ let mayPruneBackups = activation.status === "missing";
1494
+ if (record && recognizedBackup) {
1495
+ if (record.phase === "rolled_back" || !record.baseline.exists) {
1496
+ mayPruneBackups = true;
1497
+ } else if (record.phase === "verified" || record.phase === "published" || record.phase === "uncertain") {
1498
+ if (record.backupIdentity) {
1499
+ const baseline = await snapshotRegularFile(path.join(parentPath, record.backupName));
1500
+ if (baseline && sameFileIdentity(record.backupIdentity, baseline.identity)) {
1501
+ preserveBackup = record.backupName;
1502
+ mayPruneBackups = true;
1503
+ }
1504
+ }
1505
+ } else if (record.phase === "staged" || record.phase === "pending_activation") {
1506
+ const current = await snapshotRegularFile(targetPath);
1507
+ const baselineUnchanged = record.baseline.exists
1508
+ ? !!current && !!record.targetIdentity && sameFileIdentity(record.targetIdentity, current.identity)
1509
+ : current === undefined;
1510
+ if (baselineUnchanged) {
1511
+ const transactionBackup = await snapshotRegularFile(path.join(parentPath, record.backupName));
1512
+ if (!transactionBackup) mayPruneBackups = true;
1513
+ else if (
1514
+ record.backupIdentity &&
1515
+ sameFileIdentity(record.backupIdentity, transactionBackup.identity)
1516
+ ) {
1517
+ preserveBackup = record.backupName;
1518
+ mayPruneBackups = true;
1519
+ }
1520
+ }
1521
+ }
1522
+ }
1523
+ if (mayPruneBackups) {
1524
+ for (const entry of entries) {
1525
+ if (!isUpdateBackupName(targetPath, entry.name) || entry.name === preserveBackup) continue;
1526
+ const warning = await removeExactUpdateArtifact(path.join(parentPath, entry.name), originalParent);
1527
+ if (warning) warnings.push(warning);
1528
+ }
1529
+ }
1530
+ }
1531
+
1532
+ if (process.platform !== "win32") {
1533
+ const staleBefore = BigInt(Date.now() - EXACT_REPLACE_CLEANUP_GRACE_MS) * 1_000_000n;
1534
+ for (const entry of entries) {
1535
+ const match = EXACT_REPLACE_DESTINATION_NAME.exec(entry.name);
1536
+ if (!match) continue;
1537
+ const placeholderPath = path.join(parentPath, entry.name);
1538
+ const placeholder = await snapshotRegularFile(placeholderPath);
1539
+ if (!placeholder) continue;
1540
+ const { identity } = placeholder;
1541
+ if (
1542
+ identity.parentDev !== originalParent.dev ||
1543
+ identity.parentIno !== originalParent.ino ||
1544
+ identity.size !== "0" ||
1545
+ identity.nlink !== "1" ||
1546
+ BigInt(identity.ino).toString(16) !== match[1]
1547
+ )
1548
+ continue;
1549
+ // The current flow has returned from its synchronous native calls; older
1550
+ // process IDs get the grace window above before their residue is reaped.
1551
+ if (match[2] !== process.pid.toString(16) && BigInt(identity.mtimeNs) >= staleBefore) continue;
1552
+ const warning = await removeExactUpdateArtifact(placeholderPath, originalParent);
1553
+ if (warning) warnings.push(warning);
1554
+ }
1555
+ }
1556
+ } catch (error) {
1557
+ warnings.push(error instanceof Error ? error.message : String(error));
1558
+ }
1559
+ return warnings.length > 0 ? warnings.join("\n") : undefined;
1560
+ }
1561
+
1138
1562
  /**
1139
1563
  * Orchestrate download → fsync → replace → verify with a strict ordering
1140
1564
  * contract: the downloaded temp binary MUST be flushed to stable storage
1141
1565
  * before it is published (renamed into place) or exec'd for verification.
1142
1566
  *
1143
1567
  * If fsync fails the temp bytes are not durable, so we abort before
1144
- * replacement/verification and retain the staging artifact rather than risk
1145
- * deleting a substituted pathname or installing a possibly-truncated binary.
1568
+ * replacement/verification. Final cleanup removes the exact staging identity
1569
+ * only when no activation record still owns it.
1146
1570
  */
1147
1571
  export async function runBinaryUpdateFlow(
1148
1572
  targetPath: string,
@@ -1154,15 +1578,19 @@ export async function runBinaryUpdateFlow(
1154
1578
  const tempPath = binaryStagingPath(targetPath, stamp);
1155
1579
  const backupPath = `${targetPath}.bak.${stamp}`;
1156
1580
  const releaseLock = await acquireBinaryUpdateLock(targetPath);
1581
+ let originalParent: DirectoryIdentity | undefined;
1582
+ let verification: InstalledVersionVerification | undefined;
1583
+ let failed = false;
1584
+ let failure: unknown;
1157
1585
  try {
1158
1586
  const originalTarget = await snapshotRegularFile(targetPath);
1159
- const originalParent = await snapshotDirectory(path.dirname(targetPath));
1587
+ originalParent = await snapshotDirectory(path.dirname(targetPath));
1160
1588
  if (!originalParent) throw new Error("installation_parent_unavailable");
1161
1589
  await flow.download(url, tempPath);
1162
1590
  await flow.fsync(tempPath);
1163
1591
 
1164
1592
  flow.beforeReplace?.();
1165
- return await flow.replace({
1593
+ verification = await flow.replace({
1166
1594
  targetPath,
1167
1595
  tempPath,
1168
1596
  backupPath,
@@ -1171,9 +1599,38 @@ export async function runBinaryUpdateFlow(
1171
1599
  originalParent,
1172
1600
  verifyInstalledVersion: flow.verifyInstalledVersion,
1173
1601
  });
1174
- } finally {
1602
+ } catch (error) {
1603
+ failed = true;
1604
+ failure = error;
1605
+ }
1606
+ const retainedRetryCandidatePath =
1607
+ failure instanceof ActivationRetryCandidateRepointError ? failure.candidatePath : undefined;
1608
+ const cleanupWarning = originalParent
1609
+ ? await cleanupBinaryUpdateArtifacts(targetPath, originalParent, retainedRetryCandidatePath)
1610
+ : undefined;
1611
+ try {
1175
1612
  await releaseLock();
1613
+ } catch (error) {
1614
+ if (!failed) {
1615
+ failed = true;
1616
+ failure = error;
1617
+ } else {
1618
+ logger.warn("Update install lock cleanup failed", {
1619
+ warning: error instanceof Error ? error.message : String(error),
1620
+ });
1621
+ }
1176
1622
  }
1623
+ if (failed) {
1624
+ if (cleanupWarning)
1625
+ logger.warn("Update artifact cleanup failed after unsuccessful update", { warning: cleanupWarning });
1626
+ throw failure;
1627
+ }
1628
+ if (!verification) throw new Error("binary_update_verification_missing");
1629
+ if (!cleanupWarning) return verification;
1630
+ return {
1631
+ ...verification,
1632
+ cleanupWarning: [verification.cleanupWarning, cleanupWarning].filter(Boolean).join("\n"),
1633
+ };
1177
1634
  }
1178
1635
 
1179
1636
  export async function acquireBinaryUpdateLock(targetPath: string): Promise<() => Promise<void>> {
@@ -1286,7 +1743,8 @@ async function updateViaBinaryAt(
1286
1743
  beforeReplace: () => console.log(chalk.dim("Installing update...")),
1287
1744
  });
1288
1745
 
1289
- if (verification.cleanupWarning) console.warn(chalk.yellow(verification.cleanupWarning));
1746
+ if (verification.cleanupWarning)
1747
+ logger.warn("Update staging cleanup failed", { warning: verification.cleanupWarning });
1290
1748
  return verification;
1291
1749
  }
1292
1750
 
@@ -3,8 +3,8 @@
3
3
  * This module owns the effectful bootstrap (startup timing, stderr drainer,
4
4
  * bash-shell worker protocol) plus the unchanged ordinary routing order:
5
5
  * bash-shell workers -> darwin malloc re-exec -> internal smoke -> tmux owner
6
- * isolation -> managed-owner supervisor/admission/recovery -> private sdk
7
- * stderr drainer -> public family dispatch -> community app -> doctor worker /
6
+ * isolation -> managed-owner supervisor/admission/recovery -> broker hop ->
7
+ * private sdk stderr drainer -> public family dispatch -> community app -> doctor worker /
8
8
  * probe / cli -> full registry. `cli.ts` imports it lazily, only after argv has
9
9
  * been proven not to be a `sdk diagnostics` observation. */
10
10
 
@@ -16,6 +16,7 @@ import {
16
16
  BASH_SHELL_SUPERVISOR_ARG,
17
17
  BASH_SHELL_WORKER_ARG,
18
18
  } from "./exec/bash-shell-worker-protocol";
19
+ import { runBrokerHopFromArgv } from "./sdk/broker/hop";
19
20
  import { runSdkStderrDrainerFromArgv } from "./sdk/broker/stderr-drainer";
20
21
 
21
22
  export { commands };
@@ -128,6 +129,10 @@ export async function runOrdinaryCli(argv: string[]): Promise<void> {
128
129
  return;
129
130
  }
130
131
  }
132
+ if (argv.length >= 2 && argv[0] === "internal" && argv[1] === "broker-hop") {
133
+ await runBrokerHopFromArgv(argv.slice(2));
134
+ return;
135
+ }
131
136
  if (argv[0] === "sdk" && argv[1] === "stderr-drain-internal") {
132
137
  // Private lifecycle stderr drainer: must stay ahead of the public sdk family
133
138
  // dispatcher, which would otherwise reject the private argv as a usage error.
@@ -8,7 +8,7 @@ import {
8
8
  } from "../cli/accounts-cli";
9
9
 
10
10
  export default class Accounts extends Command {
11
- static description = "List, check, pin, and remove stored OAuth accounts";
11
+ static description = "List, check, pin, prefer, and remove stored OAuth accounts";
12
12
  static strict = true;
13
13
 
14
14
  static args = {
@@ -22,16 +22,18 @@ export default class Accounts extends Command {
22
22
  required: false,
23
23
  }),
24
24
  selector: Args.string({
25
- description: "Account selector (bare email, id:, email:, or account:)",
25
+ description:
26
+ "Account selector (bare email, id:, email:, or account:); prefer takes several in order, each optionally =<max used percent>",
26
27
  required: false,
28
+ multiple: true,
27
29
  }),
28
30
  };
29
31
 
30
32
  static flags = {
31
33
  json: Flags.boolean({ description: "Output safe machine-readable JSON" }),
32
34
  persistent: Flags.boolean({ description: "Required for persistent pin changes" }),
33
- clear: Flags.boolean({ description: "Clear the persistent pin" }),
34
- account: Flags.string({ description: "Logout one account by row id or email" }),
35
+ clear: Flags.boolean({ description: "Clear the persistent pin or preferred accounts" }),
36
+ account: Flags.string({ description: "Logout one account by row id, email, or org:<organization id or name>" }),
35
37
  all: Flags.boolean({ description: "Logout every OAuth account for the provider" }),
36
38
  };
37
39
 
@@ -42,7 +44,10 @@ export default class Accounts extends Command {
42
44
  "gjc accounts check anthropic --json",
43
45
  "gjc accounts pin anthropic me@example.com --persistent",
44
46
  "gjc accounts pin anthropic id:42 --persistent",
47
+ "gjc accounts pin anthropic 'org:Acme Team' --persistent",
45
48
  "gjc accounts pin anthropic --clear --persistent",
49
+ "gjc accounts prefer anthropic cheap@example.com shared@example.com=75",
50
+ "gjc accounts prefer anthropic --clear",
46
51
  "gjc accounts logout anthropic --account me@example.com",
47
52
  "gjc accounts logout anthropic --all",
48
53
  ];
@@ -57,7 +62,7 @@ export default class Accounts extends Command {
57
62
  const cmd: AccountsCommandArgs = {
58
63
  action,
59
64
  provider: args.provider,
60
- selector: args.selector,
65
+ selectors: Array.isArray(args.selector) ? args.selector : args.selector ? [args.selector] : undefined,
61
66
  flags: {
62
67
  json: flags.json,
63
68
  persistent: flags.persistent,