@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
@@ -62,6 +62,7 @@ import {
62
62
  validateAutoroutingEffective,
63
63
  validateAutoroutingLocal,
64
64
  } from "./autorouting-contract";
65
+ import type { ConfigHotReloadFileSnapshot } from "./config-hot-reload";
65
66
  import { isModelSelectorValue, type ModelSelectorValue, normalizeModelSelectorValue } from "./model-selector-value";
66
67
 
67
68
  import {
@@ -92,6 +93,53 @@ export interface RawSettings {
92
93
  [key: string]: unknown;
93
94
  }
94
95
 
96
+ /** Validated candidate view. `get` includes local patches and effective overlays. */
97
+ export interface SettingsGlobalConfigCandidate {
98
+ readonly configPath: string;
99
+ readonly diagnostics: SettingsSchemaReport;
100
+ get<P extends SettingPath>(settingPath: P): SettingValue<P>;
101
+ /** Candidate global values include pending local patches, but exclude other layers. */
102
+ getGlobal<P extends SettingPath>(settingPath: P): SettingValue<P> | undefined;
103
+ }
104
+
105
+ export interface SettingsConfigReloadPublication {
106
+ finalize(): void;
107
+ rollback(): void;
108
+ }
109
+
110
+ export class SettingsGlobalConfigReloadError extends Error {
111
+ constructor(
112
+ message: string,
113
+ readonly configPath: string | null,
114
+ readonly diagnostics?: SettingsSchemaReport,
115
+ ) {
116
+ super(message);
117
+ this.name = "SettingsGlobalConfigReloadError";
118
+ }
119
+ }
120
+
121
+ interface SettingsGlobalConfigCandidateState {
122
+ owner: Settings;
123
+ configPath: string;
124
+ globalWithPendingPatches: RawSettings;
125
+ effective: RawSettings;
126
+ diagnostics: SettingsSchemaReport;
127
+ rawConfig: RawSettings;
128
+ currentGlobal: RawSettings;
129
+ currentProject: RawSettings;
130
+ currentOverrides: RawSettings;
131
+ currentMerged: RawSettings;
132
+ cwd: string;
133
+ sourcePresent: boolean;
134
+ futureSchemaVersion: boolean;
135
+ published: boolean;
136
+ }
137
+
138
+ const settingsGlobalConfigCandidateStates = new WeakMap<
139
+ SettingsGlobalConfigCandidate,
140
+ SettingsGlobalConfigCandidateState
141
+ >();
142
+
95
143
  const UNSAFE_EDIT_VARIANT_PATTERNS = new Set(["__proto__", "constructor", "prototype"]);
96
144
 
97
145
  const CONFIG_ROOT_WORKFLOW_MIGRATION_KEYS: readonly WorkflowSettingKey[] = [
@@ -263,6 +311,12 @@ export class NotificationSettingsOverrideError extends Error {
263
311
  }
264
312
 
265
313
  const LOCAL_NOTIFICATION_SETTING_KEYS = new Set(["terminalBell", "bellOnComplete", "bellOnApproval", "bellOnAsk"]);
314
+ /** `auth.*` keys that select credentials; startup reads them from global config only, so project copies are dropped. */
315
+ const GLOBAL_ONLY_AUTH_SETTING_KEYS = new Set([
316
+ "credentialPins",
317
+ "credentialPinStoreIdentity",
318
+ "credentialPreferences",
319
+ ]);
266
320
  const LOCAL_NOTIFICATION_SETTING_PATHS = new Set(
267
321
  [...LOCAL_NOTIFICATION_SETTING_KEYS].map(key => `notifications.${key}`),
268
322
  );
@@ -336,6 +390,13 @@ const GLOBAL_ONLY_SETTINGS = new Set<SettingPath>([
336
390
  "paseo.autoImport",
337
391
  "ui.language",
338
392
  "telemetry.enabled",
393
+ // Destination of the operator Hindsight server. A project file must not redirect it.
394
+ "hindsight.apiUrl",
395
+ // Destination of generic remote compaction. A project file must not redirect the transcript POST.
396
+ "compaction.remoteEndpoint",
397
+ // Operator consent for repository skills. A project file must not flip it.
398
+ "skills.trustProjectSkills",
399
+ "skills.enablePiProject",
339
400
  ]);
340
401
  const LEGACY_THEME_NAME_REPLACEMENTS = {
341
402
  dark: "red-claw",
@@ -544,6 +605,8 @@ export class Settings implements NotificationSettingsReader {
544
605
  #schemaMigrationPending = false;
545
606
  /** A newer config schema must never be rewritten by legacy migrations. */
546
607
  #futureSchemaVersion = false;
608
+ /** Set only after a valid disk-backed global config has been loaded or published. */
609
+ #hasAcceptedGlobalConfigFile = false;
547
610
  #hasMalformedConfigRoot = false;
548
611
  /** YAML syntax was unrecoverable, so the loaded defaults are read-only until config.yml is repaired. */
549
612
  #hasRecoveredConfigSyntax = false;
@@ -745,6 +808,357 @@ export class Settings implements NotificationSettingsReader {
745
808
  return () => this.#changeListeners.delete(listener);
746
809
  }
747
810
 
811
+ /** The resolved global config.yml path, or null for in-memory settings. */
812
+ getGlobalConfigPath(): string | null {
813
+ return this.#configPath;
814
+ }
815
+
816
+ /**
817
+ * Validate the watcher's exact snapshot without writing or rereading it.
818
+ * Rebase pending local patches after prior durable operations settle.
819
+ */
820
+ async stageGlobalConfigReload(snapshot: ConfigHotReloadFileSnapshot): Promise<SettingsGlobalConfigCandidate> {
821
+ const configPath = this.#configPath;
822
+ if (!configPath) {
823
+ throw new SettingsGlobalConfigReloadError(
824
+ "Global config reload requires a disk-backed Settings instance.",
825
+ null,
826
+ );
827
+ }
828
+
829
+ if (path.resolve(snapshot.path) !== path.resolve(configPath)) {
830
+ throw new SettingsGlobalConfigReloadError("Global config snapshot path does not match Settings.", configPath);
831
+ }
832
+ const pendingAtStage = structuredClone(this.#pendingPatchesInGenerationOrder());
833
+ return enqueueAtomicYamlOperation(configPath, async () => {
834
+ const sourcePresent = snapshot.text !== null;
835
+ const content = snapshot.text ?? "";
836
+ if (!sourcePresent) {
837
+ if (this.#hasAcceptedGlobalConfigFile) {
838
+ const diagnostics: SettingsSchemaReport = {
839
+ valid: false,
840
+ issues: [
841
+ {
842
+ path: "config.yml",
843
+ kind: "invalid",
844
+ detail: "The previously accepted global config.yml is missing.",
845
+ },
846
+ ],
847
+ };
848
+ throw new SettingsGlobalConfigReloadError(
849
+ "Cannot stage global config reload because the previously accepted config.yml is missing.",
850
+ configPath,
851
+ diagnostics,
852
+ );
853
+ }
854
+ }
855
+
856
+ let rawConfig: RawSettings = {};
857
+ if (content.trim() !== "") {
858
+ let parsed: unknown;
859
+ try {
860
+ parsed = YAML.parse(content);
861
+ } catch {
862
+ const diagnostics: SettingsSchemaReport = {
863
+ valid: false,
864
+ issues: [
865
+ {
866
+ path: "config.yml",
867
+ kind: "invalid",
868
+ detail: "Configuration YAML syntax is invalid; repair config.yml before reloading.",
869
+ },
870
+ ],
871
+ };
872
+ throw new SettingsGlobalConfigReloadError(
873
+ "Cannot stage global config reload because config.yml has invalid YAML syntax.",
874
+ configPath,
875
+ diagnostics,
876
+ );
877
+ }
878
+ if (parsed !== undefined) {
879
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
880
+ const diagnostics: SettingsSchemaReport = {
881
+ valid: false,
882
+ issues: [
883
+ {
884
+ path: "config.yml",
885
+ kind: "invalid",
886
+ detail: "Configuration root must be a YAML mapping.",
887
+ },
888
+ ],
889
+ };
890
+ throw new SettingsGlobalConfigReloadError(
891
+ "Cannot stage global config reload because the config.yml root is not a mapping.",
892
+ configPath,
893
+ diagnostics,
894
+ );
895
+ }
896
+ rawConfig = parsed as RawSettings;
897
+ }
898
+ }
899
+
900
+ const configSchemaVersion = normalizeConfigSchemaVersion(rawConfig.configSchemaVersion);
901
+ const futureSchemaVersion =
902
+ typeof configSchemaVersion === "number" && configSchemaVersion > CONFIG_SCHEMA_VERSION;
903
+ const migratedRawConfig = this.#migrateRawSettings(structuredClone(rawConfig));
904
+ const reconciled = reconcileSettingsSchema(migratedRawConfig);
905
+ const diagnostics = reconciled.report;
906
+ for (const [settingPath, pathSegments] of [
907
+ ["modelRoles", ["modelRoles"]],
908
+ ["task.agentModelOverrides", ["task", "agentModelOverrides"]],
909
+ ] as const) {
910
+ const value = getByPath(reconciled.settings, [...pathSegments]);
911
+ if (value === undefined) continue;
912
+ const record = rawSettingsRecord(value);
913
+ if (!record || Object.keys(shallowModelSelectorRecord(record)).length !== Object.keys(record).length) {
914
+ diagnostics.issues.push({
915
+ path: settingPath,
916
+ kind: "invalid",
917
+ detail: "Model selector records contain invalid values.",
918
+ });
919
+ diagnostics.valid = false;
920
+ }
921
+ }
922
+ if (futureSchemaVersion) {
923
+ diagnostics.issues.push({
924
+ path: "configSchemaVersion",
925
+ kind: "pending-migration",
926
+ detail: `Configuration requires schema version ${configSchemaVersion}.`,
927
+ });
928
+ }
929
+ if (sourcePresent && hasLegacyCustomImageProvider(rawConfig)) {
930
+ diagnostics.issues.push({
931
+ path: "providers.image",
932
+ kind: "invalid",
933
+ detail: LEGACY_CUSTOM_IMAGE_PROVIDER_DIAGNOSTIC,
934
+ });
935
+ diagnostics.valid = false;
936
+ }
937
+ try {
938
+ parseNotificationSettingsSnapshot(rawConfig);
939
+ } catch (error) {
940
+ if (!(error instanceof Error) || error.message !== "gjc_notify_daemon_invalid_configuration") throw error;
941
+ diagnostics.issues.push({
942
+ path: "notifications",
943
+ kind: "invalid",
944
+ detail: "Global notification settings are invalid.",
945
+ });
946
+ diagnostics.valid = false;
947
+ }
948
+ const currentGlobal = structuredClone(this.#global);
949
+ const currentProject = structuredClone(this.#project);
950
+ const currentOverrides = structuredClone(this.#overrides);
951
+ const currentMerged = structuredClone(this.#merged);
952
+ const globalWithPendingPatches = structuredClone(reconciled.settings) as RawSettings;
953
+ const rawWithPendingPatches = structuredClone(rawConfig);
954
+ for (const patch of [...pendingAtStage, ...this.#pendingPatchesInGenerationOrder()]) {
955
+ applySettingsPatch(globalWithPendingPatches, { ...patch, value: structuredClone(patch.value) });
956
+ applySettingsPatch(rawWithPendingPatches, { ...patch, value: structuredClone(patch.value) });
957
+ }
958
+ const effective = this.#mergeSettingsLayers(globalWithPendingPatches, currentProject, currentOverrides);
959
+ diagnostics.issues = diagnostics.issues.filter(
960
+ issue =>
961
+ !(
962
+ (issue.kind === "invalid" || issue.kind === "unknown") &&
963
+ (issue.path === "task.autorouting" || issue.path.startsWith("task.autorouting."))
964
+ ),
965
+ );
966
+ for (const source of [globalWithPendingPatches, currentProject, currentOverrides]) {
967
+ const fragment = getByPath(source, ["task", "autorouting"]);
968
+ diagnostics.issues.push(
969
+ ...validateAutoroutingLocal(fragment).map(localIssue => ({
970
+ path: localIssue.path ? `task.autorouting.${localIssue.path}` : "task.autorouting",
971
+ kind: "invalid" as const,
972
+ detail: localIssue.detail,
973
+ })),
974
+ );
975
+ }
976
+ const effectiveAutorouting = validateAutoroutingEffective(getByPath(effective, ["task", "autorouting"]));
977
+ if (!effectiveAutorouting.active && effectiveAutorouting.issue) {
978
+ diagnostics.issues.push({
979
+ path: "task.autorouting",
980
+ kind: "invalid",
981
+ detail: effectiveAutorouting.issue.detail,
982
+ });
983
+ }
984
+ diagnostics.valid = !diagnostics.issues.some(issue => issue.kind === "invalid");
985
+ if (!diagnostics.valid) {
986
+ throw new SettingsGlobalConfigReloadError(
987
+ "Cannot stage global config reload because settings validation failed.",
988
+ configPath,
989
+ structuredClone(diagnostics),
990
+ );
991
+ }
992
+ const state: SettingsGlobalConfigCandidateState = {
993
+ owner: this,
994
+ configPath,
995
+ globalWithPendingPatches,
996
+ effective,
997
+ diagnostics: structuredClone(diagnostics),
998
+ rawConfig: rawWithPendingPatches,
999
+ currentGlobal,
1000
+ currentProject,
1001
+ currentOverrides,
1002
+ currentMerged,
1003
+ cwd: this.#cwd,
1004
+ sourcePresent,
1005
+ futureSchemaVersion,
1006
+ published: false,
1007
+ };
1008
+ const candidate: SettingsGlobalConfigCandidate = {
1009
+ configPath,
1010
+ diagnostics: structuredClone(diagnostics),
1011
+ get<P extends SettingPath>(settingPath: P): SettingValue<P> {
1012
+ const value = getByPath(state.effective, settingPath.split("."));
1013
+ if (value === undefined) return structuredClone(getDefault(settingPath));
1014
+ const pathScopedValue = resolvePathScopedStringArray(settingPath, value, state.cwd);
1015
+ return structuredClone((pathScopedValue ?? value) as SettingValue<P>);
1016
+ },
1017
+ getGlobal<P extends SettingPath>(settingPath: P): SettingValue<P> | undefined {
1018
+ const value = getByPath(state.globalWithPendingPatches, settingPath.split("."));
1019
+ return value === undefined ? undefined : structuredClone(value as SettingValue<P>);
1020
+ },
1021
+ };
1022
+ settingsGlobalConfigCandidateStates.set(candidate, state);
1023
+ return Object.freeze(candidate);
1024
+ });
1025
+ }
1026
+
1027
+ /**
1028
+ * Publish a staged global config and its dependent synchronous model commit as
1029
+ * one in-memory transaction. A thrown commit restores settings without hooks.
1030
+ */
1031
+ publishGlobalConfigReload(
1032
+ candidate: SettingsGlobalConfigCandidate,
1033
+ commitSync: () => void = () => {},
1034
+ ): SettingsConfigReloadPublication {
1035
+ const state = settingsGlobalConfigCandidateStates.get(candidate);
1036
+ if (!state || state.owner !== this) {
1037
+ throw new SettingsGlobalConfigReloadError(
1038
+ "The global config candidate does not belong to this Settings instance.",
1039
+ this.#configPath,
1040
+ );
1041
+ }
1042
+ if (state.published) {
1043
+ throw new SettingsGlobalConfigReloadError(
1044
+ "The global config candidate has already been published.",
1045
+ state.configPath,
1046
+ );
1047
+ }
1048
+ if (
1049
+ this.#configPath !== state.configPath ||
1050
+ !util.isDeepStrictEqual(this.#global, state.currentGlobal) ||
1051
+ !util.isDeepStrictEqual(this.#project, state.currentProject) ||
1052
+ !util.isDeepStrictEqual(this.#overrides, state.currentOverrides) ||
1053
+ !util.isDeepStrictEqual(this.#merged, state.currentMerged)
1054
+ ) {
1055
+ throw new SettingsGlobalConfigReloadError(
1056
+ "Settings changed while the global config candidate was being prepared; stage it again.",
1057
+ state.configPath,
1058
+ structuredClone(state.diagnostics),
1059
+ );
1060
+ }
1061
+
1062
+ const previousValues = new Map<SettingPath, unknown>();
1063
+ for (const settingPath of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
1064
+ previousValues.set(settingPath, structuredClone(this.get(settingPath)));
1065
+ }
1066
+ const previous = {
1067
+ global: this.#global,
1068
+ overrides: structuredClone(this.#overrides),
1069
+ rawNotificationConfig: this.#rawNotificationConfig,
1070
+ durableRawNotificationConfig: this.#durableRawNotificationConfig,
1071
+ durableNotificationFingerprint: this.#durableNotificationFingerprint,
1072
+ merged: this.#merged,
1073
+ schemaReport: this.#schemaReport,
1074
+ autoroutingEffective: this.#autoroutingEffective,
1075
+ autoroutingLocalIssues: this.#autoroutingLocalIssues,
1076
+ futureSchemaVersion: this.#futureSchemaVersion,
1077
+ hasAcceptedGlobalConfigFile: this.#hasAcceptedGlobalConfigFile,
1078
+ hasMalformedConfigRoot: this.#hasMalformedConfigRoot,
1079
+ hasRecoveredConfigSyntax: this.#hasRecoveredConfigSyntax,
1080
+ hasInvalidNotificationGlobal: this.#hasInvalidNotificationGlobal,
1081
+ notificationValidationGeneration: this.#notificationValidationGeneration,
1082
+ };
1083
+ let publicationState: "pending" | "finalized" | "rolled-back" = "pending";
1084
+ const rollback = () => {
1085
+ if (publicationState === "rolled-back") return;
1086
+ if (publicationState === "finalized") throw new Error("Cannot roll back finalized configuration.");
1087
+ this.#global = previous.global;
1088
+ this.#overrides = previous.overrides;
1089
+ this.#rawNotificationConfig = previous.rawNotificationConfig;
1090
+ this.#durableRawNotificationConfig = previous.durableRawNotificationConfig;
1091
+ this.#durableNotificationFingerprint = previous.durableNotificationFingerprint;
1092
+ this.#merged = previous.merged;
1093
+ this.#schemaReport = previous.schemaReport;
1094
+ this.#autoroutingEffective = previous.autoroutingEffective;
1095
+ this.#autoroutingLocalIssues = previous.autoroutingLocalIssues;
1096
+ this.#futureSchemaVersion = previous.futureSchemaVersion;
1097
+ this.#hasAcceptedGlobalConfigFile = previous.hasAcceptedGlobalConfigFile;
1098
+ this.#hasMalformedConfigRoot = previous.hasMalformedConfigRoot;
1099
+ this.#hasRecoveredConfigSyntax = previous.hasRecoveredConfigSyntax;
1100
+ this.#hasInvalidNotificationGlobal = previous.hasInvalidNotificationGlobal;
1101
+ this.#notificationValidationGeneration = previous.notificationValidationGeneration;
1102
+ publicationState = "rolled-back";
1103
+ };
1104
+ this.#global = structuredClone(state.globalWithPendingPatches);
1105
+ const previousNotificationFingerprint = this.#durableNotificationFingerprint;
1106
+ this.#captureRawNotificationConfig(state.rawConfig);
1107
+ for (const patch of this.#pendingPatchesInGenerationOrder()) {
1108
+ this.#applyNotificationMutationToRaw(patch.path, patch.value);
1109
+ }
1110
+ if (previousNotificationFingerprint !== this.#durableNotificationFingerprint) {
1111
+ this.#notificationValidationGeneration++;
1112
+ }
1113
+ this.#schemaReport = structuredClone(state.diagnostics);
1114
+ this.#futureSchemaVersion = state.futureSchemaVersion;
1115
+ this.#hasMalformedConfigRoot = false;
1116
+ this.#hasRecoveredConfigSyntax = false;
1117
+ this.#hasInvalidNotificationGlobal = false;
1118
+ this.#rebuildMerged();
1119
+ this.#recomputeNotificationValidationFromRaw();
1120
+
1121
+ try {
1122
+ const result: unknown = commitSync();
1123
+ if (result && typeof result === "object" && "then" in result) {
1124
+ throw new TypeError("Global config reload commit callback must be synchronous.");
1125
+ }
1126
+ } catch (error) {
1127
+ rollback();
1128
+ throw error;
1129
+ }
1130
+
1131
+ this.#hasAcceptedGlobalConfigFile ||= state.sourcePresent;
1132
+ state.published = true;
1133
+ return {
1134
+ rollback,
1135
+ finalize: () => {
1136
+ if (publicationState !== "pending") return;
1137
+ publicationState = "finalized";
1138
+ for (const settingPath of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
1139
+ const previousValue = previousValues.get(settingPath);
1140
+ const nextValue = this.get(settingPath);
1141
+ if (util.isDeepStrictEqual(previousValue, nextValue)) continue;
1142
+ const hook = SETTING_HOOKS[settingPath];
1143
+ if (hook) {
1144
+ try {
1145
+ hook(nextValue, previousValue);
1146
+ } catch {
1147
+ logger.warn("Configuration reload setting hook failed", { settingPath });
1148
+ }
1149
+ }
1150
+ for (const listener of this.#changeListeners) {
1151
+ try {
1152
+ listener(settingPath);
1153
+ } catch {
1154
+ logger.warn("Configuration reload listener failed", { settingPath });
1155
+ }
1156
+ }
1157
+ }
1158
+ },
1159
+ };
1160
+ }
1161
+
748
1162
  /** Whether durable settings mutations are permitted for the loaded configuration. */
749
1163
  canWriteDurableConfig(): boolean {
750
1164
  return !this.#persist || !this.#hasRecoveredConfigSyntax;
@@ -1156,6 +1570,7 @@ export class Settings implements NotificationSettingsReader {
1156
1570
  cloned.#autoroutingLocalIssues = structuredClone(this.#autoroutingLocalIssues);
1157
1571
  cloned.#schemaMigrationPending = this.#schemaMigrationPending;
1158
1572
  cloned.#futureSchemaVersion = this.#futureSchemaVersion;
1573
+ cloned.#hasAcceptedGlobalConfigFile = this.#hasAcceptedGlobalConfigFile;
1159
1574
  cloned.#hasMalformedConfigRoot = this.#hasMalformedConfigRoot;
1160
1575
  cloned.#hasRecoveredConfigSyntax = this.#hasRecoveredConfigSyntax;
1161
1576
  cloned.#hasInvalidNotificationGlobal = this.#hasInvalidNotificationGlobal;
@@ -1206,10 +1621,12 @@ export class Settings implements NotificationSettingsReader {
1206
1621
  }
1207
1622
 
1208
1623
  /**
1209
- * Get shell configuration based on settings.
1624
+ * Shell configuration for bash and the Python executor.
1625
+ * `shellPath` comes from the user/global config only, so a project
1626
+ * `.gjc/settings.json` cannot select the binary those executors run.
1210
1627
  */
1211
1628
  getShellConfig() {
1212
- const shell = this.get("shellPath");
1629
+ const shell = this.getGlobal("shellPath");
1213
1630
  return resolveShellConfig(shell);
1214
1631
  }
1215
1632
 
@@ -1516,7 +1933,10 @@ export class Settings implements NotificationSettingsReader {
1516
1933
  throw error;
1517
1934
  }
1518
1935
  this.#resetYamlLoadState();
1519
- if (content.trim() === "") return {};
1936
+ if (content.trim() === "") {
1937
+ if (filePath === this.#configPath) this.#hasAcceptedGlobalConfigFile = true;
1938
+ return {};
1939
+ }
1520
1940
  let parsed: unknown;
1521
1941
  try {
1522
1942
  parsed = YAML.parse(content);
@@ -1536,7 +1956,10 @@ export class Settings implements NotificationSettingsReader {
1536
1956
  this.#captureRawNotificationConfig(undefined);
1537
1957
  return {};
1538
1958
  }
1539
- if (parsed === undefined) return {};
1959
+ if (parsed === undefined) {
1960
+ if (filePath === this.#configPath) this.#hasAcceptedGlobalConfigFile = true;
1961
+ return {};
1962
+ }
1540
1963
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1541
1964
  this.#hasMalformedConfigRoot = true;
1542
1965
  this.#schemaReport = {
@@ -1604,6 +2027,14 @@ export class Settings implements NotificationSettingsReader {
1604
2027
  });
1605
2028
  }
1606
2029
  this.#schemaReport = reconciled.report;
2030
+ if (
2031
+ filePath === this.#configPath &&
2032
+ reconciled.report.valid &&
2033
+ !this.#hasInvalidNotificationGlobal &&
2034
+ !hasLegacyCustomImageProvider(parsedRaw)
2035
+ ) {
2036
+ this.#hasAcceptedGlobalConfigFile = true;
2037
+ }
1607
2038
  return reconciled.settings;
1608
2039
  }
1609
2040
 
@@ -1625,13 +2056,16 @@ export class Settings implements NotificationSettingsReader {
1625
2056
  const data = item.path.endsWith(`${path.sep}settings.json`)
1626
2057
  ? await this.#stripRetiredWorkflowKeys(item.path, structuredClone(item.data as RawSettings))
1627
2058
  : (item.data as RawSettings);
1628
- const { settings, rejectedNotifications, rejectedCredentialPins } =
2059
+ const { settings, rejectedNotifications, rejectedCredentialSelection } =
1629
2060
  this.#stripProjectNotificationSettings(data);
1630
2061
  if (rejectedNotifications) {
1631
2062
  logger.warn("Settings: ignoring project notification settings", { path: item.path });
1632
2063
  }
1633
- if (rejectedCredentialPins) {
1634
- logger.warn("Settings: ignoring project auth.credentialPins; pins are global-only", { path: item.path });
2064
+ if (rejectedCredentialSelection) {
2065
+ logger.warn(
2066
+ "Settings: ignoring project auth credential pins/preferences; credential selection is global-only",
2067
+ { path: item.path },
2068
+ );
1635
2069
  }
1636
2070
  merged = this.#deepMerge(merged, settings);
1637
2071
  }
@@ -6022,16 +6456,20 @@ export class Settings implements NotificationSettingsReader {
6022
6456
  }
6023
6457
 
6024
6458
  #rebuildMerged(): void {
6025
- const project = structuredClone(this.#project);
6026
- const overrides = structuredClone(this.#overrides);
6459
+ this.#merged = this.#mergeSettingsLayers(this.#global, this.#project, this.#overrides);
6460
+ this.#recomputeAutoroutingDiagnostic();
6461
+ }
6462
+
6463
+ #mergeSettingsLayers(global: RawSettings, projectLayer: RawSettings, overrideLayer: RawSettings): RawSettings {
6464
+ const project = structuredClone(projectLayer);
6465
+ const overrides = structuredClone(overrideLayer);
6027
6466
  for (const settingPath of GLOBAL_ONLY_SETTINGS) {
6028
6467
  const segments = settingPath.split(".");
6029
6468
  deleteByPath(project, segments);
6030
6469
  deleteByPath(overrides, segments);
6031
6470
  }
6032
- this.#merged = this.#deepMerge(this.#deepMerge({}, this.#global), project);
6033
- this.#merged = this.#deepMerge(this.#merged, overrides);
6034
- this.#recomputeAutoroutingDiagnostic();
6471
+ const merged = this.#deepMerge(this.#deepMerge({}, global), project);
6472
+ return this.#deepMerge(merged, overrides);
6035
6473
  }
6036
6474
 
6037
6475
  #fireAllHooks(): void {
@@ -6047,27 +6485,25 @@ export class Settings implements NotificationSettingsReader {
6047
6485
  #stripProjectNotificationSettings(settings: RawSettings): {
6048
6486
  settings: RawSettings;
6049
6487
  rejectedNotifications: boolean;
6050
- rejectedCredentialPins: boolean;
6488
+ rejectedCredentialSelection: boolean;
6051
6489
  } {
6052
6490
  let rejectedNotifications = false;
6053
- let rejectedCredentialPins = false;
6491
+ let rejectedCredentialSelection = false;
6054
6492
  const sanitized: RawSettings = {};
6055
6493
  for (const [key, value] of Object.entries(settings)) {
6056
6494
  if (key === "auth" && value && typeof value === "object" && !Array.isArray(value)) {
6057
6495
  const authSettings = { ...(value as Record<string, unknown>) };
6058
- if (Object.hasOwn(authSettings, "credentialPins")) {
6059
- delete authSettings.credentialPins;
6060
- rejectedCredentialPins = true;
6061
- }
6062
- if (Object.hasOwn(authSettings, "credentialPinStoreIdentity")) {
6063
- delete authSettings.credentialPinStoreIdentity;
6064
- rejectedCredentialPins = true;
6496
+ for (const key of GLOBAL_ONLY_AUTH_SETTING_KEYS) {
6497
+ if (Object.hasOwn(authSettings, key)) {
6498
+ delete authSettings[key];
6499
+ rejectedCredentialSelection = true;
6500
+ }
6065
6501
  }
6066
6502
  if (Object.keys(authSettings).length > 0) sanitized[key] = authSettings;
6067
6503
  continue;
6068
6504
  }
6069
- if (key === "auth.credentialPins" || key === "auth.credentialPinStoreIdentity") {
6070
- rejectedCredentialPins = true;
6505
+ if (key.startsWith("auth.") && GLOBAL_ONLY_AUTH_SETTING_KEYS.has(key.slice("auth.".length))) {
6506
+ rejectedCredentialSelection = true;
6071
6507
  continue;
6072
6508
  }
6073
6509
  if (key === "notifications" && value && typeof value === "object" && !Array.isArray(value)) {
@@ -6088,7 +6524,7 @@ export class Settings implements NotificationSettingsReader {
6088
6524
  }
6089
6525
  sanitized[key] = value;
6090
6526
  }
6091
- return { settings: sanitized, rejectedNotifications, rejectedCredentialPins };
6527
+ return { settings: sanitized, rejectedNotifications, rejectedCredentialSelection };
6092
6528
  }
6093
6529
 
6094
6530
  #deepMerge(base: RawSettings, overrides: RawSettings): RawSettings {
@@ -3,6 +3,7 @@ import * as nodeFs from "node:fs";
3
3
  import * as fs from "node:fs/promises";
4
4
  import * as os from "node:os";
5
5
  import * as path from "node:path";
6
+ import { canonicalEnvKey, projectEnvSnapshot } from "@gajae-code/utils/env-file";
6
7
  import { coordinatorMcpStateRoot, gjcRoot } from "../gjc-runtime/session-layout";
7
8
  import {
8
9
  DEFAULT_SESSION_IDLE_TTL_MS,
@@ -121,6 +122,30 @@ function parseRootList(value: string | undefined): string[] {
121
122
  .filter(Boolean);
122
123
  }
123
124
 
125
+ const MANAGED_WORKTREE_DIR_ENV = "GJC_WORKTREE_DIR";
126
+ /** Captured at load so a later chdir or dotenv delete cannot hide the declaration. */
127
+ const startupManagedWorktreeSnapshot = projectEnvSnapshot();
128
+
129
+ /**
130
+ * Operator value only. A project dotenv declaration, including a dynamic one,
131
+ * must not widen managed worktree roots. An operator value the project does
132
+ * not declare is kept.
133
+ */
134
+ function trustedManagedWorktreeDir(env: NodeJS.ProcessEnv): string | undefined {
135
+ const raw = env[MANAGED_WORKTREE_DIR_ENV];
136
+ if (!raw) return undefined;
137
+ const key = canonicalEnvKey(MANAGED_WORKTREE_DIR_ENV);
138
+ const declared = startupManagedWorktreeSnapshot.values[key];
139
+ const trimmed = raw.trim();
140
+ if (
141
+ declared !== undefined &&
142
+ (startupManagedWorktreeSnapshot.dynamic.has(key) || declared === raw || declared === trimmed)
143
+ ) {
144
+ return undefined;
145
+ }
146
+ return trimmed.length > 0 ? trimmed : undefined;
147
+ }
148
+
124
149
  function resolveManagedWorktreeRoot(root: string, configured: string | undefined): string {
125
150
  // Function replacers so a `$` in the home or repository path is kept verbatim.
126
151
  const template = (configured?.trim() || "{repo}/.worktrees").replace(/^~(?=\/|$)/, () => os.homedir());
@@ -168,7 +193,7 @@ export function buildCoordinatorMcpConfig(env: NodeJS.ProcessEnv = process.env):
168
193
  return {
169
194
  allowedRoots: parseRootList(env.GJC_COORDINATOR_MCP_WORKDIR_ROOTS).map(root => path.resolve(root)),
170
195
  managedWorktreeRoots: parseRootList(env.GJC_COORDINATOR_MCP_WORKDIR_ROOTS).map(root =>
171
- resolveManagedWorktreeRoot(path.resolve(root), env.GJC_WORKTREE_DIR),
196
+ resolveManagedWorktreeRoot(path.resolve(root), trustedManagedWorktreeDir(env)),
172
197
  ),
173
198
  mutationClasses: parseMutationClasses(
174
199
  env.GJC_COORDINATOR_MCP_MUTATIONS ?? env.GJC_COORDINATOR_MCP_ENABLE_MUTATION_CLASSES,