@gajae-code/coding-agent 0.18.11 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (462) hide show
  1. package/CHANGELOG.md +760 -0
  2. package/dist/types/async/job-manager.d.ts +7 -3
  3. package/dist/types/async/result-formatting.d.ts +2 -0
  4. package/dist/types/autoresearch/git.d.ts +1 -1
  5. package/dist/types/capability/fs.d.ts +2 -2
  6. package/dist/types/capability/rule.d.ts +7 -2
  7. package/dist/types/cli/accounts-cli.d.ts +2 -2
  8. package/dist/types/cli/append-system-prompt.d.ts +6 -0
  9. package/dist/types/cli/args.d.ts +7 -2
  10. package/dist/types/cli/customize-doctor-precedence.d.ts +16 -0
  11. package/dist/types/cli/customize-doctor.d.ts +4 -3
  12. package/dist/types/cli/doctor/install-repairs.d.ts +4 -1
  13. package/dist/types/cli/install-activation.d.ts +10 -4
  14. package/dist/types/cli/public-command-errors.d.ts +2 -0
  15. package/dist/types/cli/root-flags.d.ts +0 -12
  16. package/dist/types/cli/update-cli.d.ts +21 -3
  17. package/dist/types/cli-main.d.ts +0 -6
  18. package/dist/types/cli-ordinary.d.ts +2 -2
  19. package/dist/types/commands/accounts.d.ts +2 -1
  20. package/dist/types/commands/launch.d.ts +0 -6
  21. package/dist/types/commands/migrate.d.ts +1 -1
  22. package/dist/types/commands/sdk.d.ts +13 -0
  23. package/dist/types/commit/changelog/index.d.ts +1 -0
  24. package/dist/types/commit/map-reduce/index.d.ts +4 -4
  25. package/dist/types/config/autorouting-tier-map.d.ts +53 -1
  26. package/dist/types/config/config-hot-reload-trust.d.ts +12 -0
  27. package/dist/types/config/config-hot-reload.d.ts +44 -0
  28. package/dist/types/config/file-lock.d.ts +52 -3
  29. package/dist/types/config/model-discovery-manager.d.ts +2 -0
  30. package/dist/types/config/model-profile-activation.d.ts +79 -9
  31. package/dist/types/config/model-registry.d.ts +72 -8
  32. package/dist/types/config/model-resolver.d.ts +7 -4
  33. package/dist/types/config/models-config-schema.d.ts +10 -0
  34. package/dist/types/config/settings-schema.d.ts +57 -4
  35. package/dist/types/config/settings.d.ts +33 -1
  36. package/dist/types/coordinator-mcp/server.d.ts +4 -0
  37. package/dist/types/customization/import.d.ts +2 -0
  38. package/dist/types/customization/types.d.ts +1 -1
  39. package/dist/types/dap/config.d.ts +8 -4
  40. package/dist/types/dap/session.d.ts +11 -2
  41. package/dist/types/debug/system-info.d.ts +6 -1
  42. package/dist/types/discovery/helpers.d.ts +7 -2
  43. package/dist/types/discovery/mcp-compat.d.ts +3 -1
  44. package/dist/types/edit/index.d.ts +5 -0
  45. package/dist/types/edit/modes/patch.d.ts +14 -0
  46. package/dist/types/edit/modes/replace.d.ts +3 -0
  47. package/dist/types/eval/py/runtime.d.ts +9 -0
  48. package/dist/types/exec/bash-executor.d.ts +12 -0
  49. package/dist/types/export/ttsr.d.ts +19 -0
  50. package/dist/types/extensibility/extensions/types.d.ts +5 -1
  51. package/dist/types/extensibility/slash-commands.d.ts +1 -0
  52. package/dist/types/gjc-runtime/managed-owner-admission.d.ts +3 -0
  53. package/dist/types/gjc-runtime/ralplan-current-final.d.ts +54 -0
  54. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +5 -0
  55. package/dist/types/gjc-runtime/session-state-lock.d.ts +2 -0
  56. package/dist/types/gjc-runtime/state-runtime.d.ts +0 -2
  57. package/dist/types/gjc-runtime/tmux-sessions.d.ts +24 -0
  58. package/dist/types/gjc-runtime/ultragoal-change-set.d.ts +6 -0
  59. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +1 -0
  60. package/dist/types/harness-control-plane/receipt-spool.d.ts +4 -0
  61. package/dist/types/hindsight/content.d.ts +5 -6
  62. package/dist/types/internal-urls/types.d.ts +2 -0
  63. package/dist/types/lsp/clients/swiftlint-client.d.ts +0 -4
  64. package/dist/types/main.d.ts +23 -18
  65. package/dist/types/migrate/action-planner.d.ts +10 -1
  66. package/dist/types/migrate/adapters/codex.d.ts +2 -1
  67. package/dist/types/migrate/adapters/index.d.ts +42 -1
  68. package/dist/types/migrate/executor.d.ts +3 -1
  69. package/dist/types/migrate/hook-module.d.ts +23 -0
  70. package/dist/types/migrate/report.d.ts +2 -1
  71. package/dist/types/migrate/types.d.ts +82 -3
  72. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  73. package/dist/types/modes/components/model-selector.d.ts +43 -0
  74. package/dist/types/modes/components/settings-selector.d.ts +6 -0
  75. package/dist/types/modes/controllers/selector-controller.d.ts +2 -0
  76. package/dist/types/modes/prompt-suggestion-controller.d.ts +1 -1
  77. package/dist/types/modes/theme/theme.d.ts +1 -0
  78. package/dist/types/modes/utils/host-status.d.ts +15 -11
  79. package/dist/types/runtime-mcp/content-limits.d.ts +4 -0
  80. package/dist/types/runtime-mcp/manager.d.ts +7 -0
  81. package/dist/types/runtime-mcp/plugin-mcp-name-filter.d.ts +22 -0
  82. package/dist/types/runtime-mcp/project-host-values.d.ts +11 -5
  83. package/dist/types/runtime-mcp/tool-bridge.d.ts +4 -0
  84. package/dist/types/sdk/broker/broker-exit.d.ts +3 -3
  85. package/dist/types/sdk/broker/broker.d.ts +29 -2
  86. package/dist/types/sdk/broker/daemon-entry.d.ts +11 -1
  87. package/dist/types/sdk/broker/doctor-restart.d.ts +1 -1
  88. package/dist/types/sdk/broker/ensure.d.ts +141 -9
  89. package/dist/types/sdk/broker/hop.d.ts +45 -0
  90. package/dist/types/sdk/broker/lifecycle-ledger.d.ts +18 -10
  91. package/dist/types/sdk/broker/lifecycle.d.ts +22 -0
  92. package/dist/types/sdk/broker/lock-host-identity.d.ts +5 -0
  93. package/dist/types/sdk/broker/session-index.d.ts +6 -1
  94. package/dist/types/sdk/broker/transport.d.ts +2 -0
  95. package/dist/types/sdk/bus/chat-command-policy.d.ts +2 -1
  96. package/dist/types/sdk/bus/index.d.ts +44 -0
  97. package/dist/types/sdk/bus/reconciliation-store.d.ts +10 -0
  98. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +9 -1
  99. package/dist/types/sdk/bus/telegram-daemon.d.ts +2 -0
  100. package/dist/types/sdk/bus/threaded-render.d.ts +2 -1
  101. package/dist/types/sdk/cli/session-cli.d.ts +0 -1
  102. package/dist/types/sdk/host/control/operations.d.ts +5 -1
  103. package/dist/types/sdk/host/events.d.ts +4 -0
  104. package/dist/types/sdk/host/host.d.ts +2 -1
  105. package/dist/types/sdk/host/prompt-image-upload.d.ts +43 -0
  106. package/dist/types/sdk/host/session-runtime.d.ts +40 -8
  107. package/dist/types/sdk/host/surface-policy.d.ts +2 -1
  108. package/dist/types/sdk/lifecycle/service.d.ts +12 -0
  109. package/dist/types/sdk/prompt-deadline-flush.d.ts +42 -4
  110. package/dist/types/sdk/prompt-deadline-manager.d.ts +2 -0
  111. package/dist/types/sdk/prompt-status.d.ts +11 -4
  112. package/dist/types/sdk/protocol/adapter-validation.d.ts +10 -4
  113. package/dist/types/sdk/reconciliation-extensions.d.ts +1 -1
  114. package/dist/types/sdk/session-list.d.ts +1 -1
  115. package/dist/types/session/agent-session.d.ts +107 -9
  116. package/dist/types/session/internal/managed-session-scope.d.ts +2 -2
  117. package/dist/types/session/internal/managed-session-storage.d.ts +1 -1
  118. package/dist/types/session/internal/native-publish-outcome.d.ts +1 -1
  119. package/dist/types/session/messages.d.ts +5 -1
  120. package/dist/types/session/session-manager.d.ts +29 -3
  121. package/dist/types/session/startup-auth-config.d.ts +24 -2
  122. package/dist/types/session/streaming-output.d.ts +11 -0
  123. package/dist/types/session/terminal-abort.d.ts +3 -1
  124. package/dist/types/session-import/service.d.ts +1 -1
  125. package/dist/types/skill-state/workflow-mutation-guard.d.ts +20 -4
  126. package/dist/types/slash-commands/helpers/mcp.d.ts +5 -0
  127. package/dist/types/slash-commands/helpers/provider-auth-status.d.ts +8 -0
  128. package/dist/types/ssh/ssh-executor.d.ts +8 -1
  129. package/dist/types/system-prompt.d.ts +3 -2
  130. package/dist/types/task/executor.d.ts +19 -2
  131. package/dist/types/task/role-body.d.ts +11 -0
  132. package/dist/types/tools/ast-edit-write-path.d.ts +10 -0
  133. package/dist/types/tools/atomic-file-write.d.ts +9 -7
  134. package/dist/types/tools/awaiting-input.d.ts +28 -0
  135. package/dist/types/tools/bash-allowed-prefixes.d.ts +17 -0
  136. package/dist/types/tools/bash-skill-urls.d.ts +4 -0
  137. package/dist/types/tools/bash.d.ts +7 -0
  138. package/dist/types/tools/descriptors.d.ts +15 -2
  139. package/dist/types/tools/fetch.d.ts +7 -1
  140. package/dist/types/tools/index.d.ts +2 -0
  141. package/dist/types/tools/irc.d.ts +5 -3
  142. package/dist/types/tools/python.d.ts +1 -1
  143. package/dist/types/tools/report-issue.d.ts +41 -0
  144. package/dist/types/tools/subagent.d.ts +23 -0
  145. package/dist/types/tools/tmux-self-injection-guard.d.ts +2 -0
  146. package/dist/types/tools/ultragoal-ask-guard.d.ts +1 -1
  147. package/dist/types/tools/write.d.ts +2 -0
  148. package/dist/types/utils/file-mentions.d.ts +5 -0
  149. package/dist/types/utils/image-limits.d.ts +7 -0
  150. package/dist/types/utils/image-loading.d.ts +0 -1
  151. package/dist/types/utils/open.d.ts +0 -1
  152. package/dist/types/utils/pasted-image-loading.d.ts +1 -5
  153. package/dist/types/utils/prompt-suggestion.d.ts +2 -2
  154. package/dist/types/utils/shell-snapshot.d.ts +2 -0
  155. package/dist/types/utils/title-generator.d.ts +13 -3
  156. package/dist/types/web/scrapers/types.d.ts +8 -1
  157. package/dist/types/web/search/providers/codex.d.ts +6 -7
  158. package/dist/types/web/search/providers/insane.d.ts +7 -1
  159. package/dist/types/web/search/providers/searxng.d.ts +5 -0
  160. package/package.json +10 -9
  161. package/scripts/acp-conformance-agent.ts +12 -2
  162. package/scripts/build-binary.ts +2 -1
  163. package/scripts/capture-subagent-compact-panel-showcase.ts +105 -0
  164. package/scripts/generate-docs-index.ts +8 -1
  165. package/scripts/generate-sdk-operation-inventory.ts +38 -4
  166. package/src/async/job-manager.ts +356 -56
  167. package/src/async/result-formatting.ts +40 -0
  168. package/src/autoresearch/git.ts +9 -2
  169. package/src/autoresearch/prompts/prompt-setup.md +1 -1
  170. package/src/capability/fs.ts +58 -22
  171. package/src/capability/index.ts +2 -2
  172. package/src/capability/rule.ts +11 -2
  173. package/src/cli/accounts-cli.ts +176 -8
  174. package/src/cli/append-system-prompt.ts +12 -0
  175. package/src/cli/args.ts +11 -7
  176. package/src/cli/auth-broker-cli.ts +21 -11
  177. package/src/cli/customize-doctor-precedence.ts +31 -0
  178. package/src/cli/customize-doctor.ts +49 -39
  179. package/src/cli/doctor/install-repairs.ts +35 -5
  180. package/src/cli/fast-help.ts +1 -3
  181. package/src/cli/install-activation.ts +47 -4
  182. package/src/cli/migrate-cli.ts +19 -6
  183. package/src/cli/public-command-errors.ts +6 -0
  184. package/src/cli/root-flags.ts +2 -4
  185. package/src/cli/stats-cli.ts +9 -5
  186. package/src/cli/update-cli.ts +471 -13
  187. package/src/cli-ordinary.ts +7 -2
  188. package/src/commands/accounts.ts +10 -5
  189. package/src/commands/harness.ts +25 -15
  190. package/src/commands/migrate.ts +3 -3
  191. package/src/commands/sdk.ts +277 -50
  192. package/src/commands/ultragoal.ts +16 -8
  193. package/src/commit/agentic/agent.ts +6 -6
  194. package/src/commit/analysis/conventional.ts +14 -5
  195. package/src/commit/changelog/index.ts +39 -1
  196. package/src/commit/map-reduce/index.ts +7 -7
  197. package/src/commit/message.ts +10 -3
  198. package/src/commit/pipeline.ts +12 -12
  199. package/src/config/autorouting-tier-map.ts +50 -1
  200. package/src/config/config-file.ts +106 -3
  201. package/src/config/config-hot-reload-trust.ts +148 -0
  202. package/src/config/config-hot-reload.ts +742 -0
  203. package/src/config/file-lock.ts +1615 -328
  204. package/src/config/keybindings.ts +154 -7
  205. package/src/config/model-bindings-applier.ts +85 -47
  206. package/src/config/model-discovery-manager.ts +10 -0
  207. package/src/config/model-profile-activation.ts +889 -148
  208. package/src/config/model-registry.ts +1706 -205
  209. package/src/config/model-resolver.ts +52 -22
  210. package/src/config/models-config-schema.ts +6 -0
  211. package/src/config/settings-schema.ts +73 -4
  212. package/src/config/settings.ts +460 -24
  213. package/src/coordinator-mcp/policy.ts +26 -1
  214. package/src/coordinator-mcp/server.ts +37 -2
  215. package/src/crash/sanitize.ts +7 -1
  216. package/src/customization/import.ts +279 -8
  217. package/src/customization/inventory.ts +24 -8
  218. package/src/customization/types.ts +1 -0
  219. package/src/dap/client.ts +3 -2
  220. package/src/dap/config.ts +31 -11
  221. package/src/dap/defaults.json +1 -1
  222. package/src/dap/session.ts +161 -36
  223. package/src/debug/crash-diagnostics.ts +89 -6
  224. package/src/debug/report-bundle.ts +90 -2
  225. package/src/debug/system-info.ts +8 -3
  226. package/src/defaults/gjc/extensions/grok-cli-vendor/src/shared/base-url.ts +21 -7
  227. package/src/discovery/agents-md.ts +1 -1
  228. package/src/discovery/builtin.ts +4 -3
  229. package/src/discovery/helpers.ts +25 -16
  230. package/src/discovery/mcp-compat.ts +120 -17
  231. package/src/edit/apply-patch/index.ts +12 -1
  232. package/src/edit/index.ts +204 -22
  233. package/src/edit/modes/patch.ts +214 -8
  234. package/src/edit/modes/replace.ts +136 -0
  235. package/src/eval/js/context-manager.ts +29 -3
  236. package/src/eval/js/worker-entry.ts +51 -18
  237. package/src/eval/py/executor.ts +14 -4
  238. package/src/eval/py/runner.py +144 -5
  239. package/src/eval/py/runtime.ts +44 -4
  240. package/src/exec/bash-executor.ts +81 -4
  241. package/src/exec/bash-shell-guardian.ts +4 -1
  242. package/src/exec/bash-shell-supervisor.ts +8 -4
  243. package/src/exec/isolated-shell.ts +5 -1
  244. package/src/exec/non-interactive-env.ts +0 -1
  245. package/src/export/html/index.ts +86 -6
  246. package/src/export/ttsr.ts +176 -1
  247. package/src/extensibility/custom-tools/wrapper.ts +3 -1
  248. package/src/extensibility/extensions/loader.ts +10 -1
  249. package/src/extensibility/extensions/ouroboros-ooo-bridge.ts +63 -1
  250. package/src/extensibility/extensions/types.ts +5 -1
  251. package/src/extensibility/hooks/loader.ts +10 -2
  252. package/src/extensibility/plugins/marketplace/fetcher.ts +2 -2
  253. package/src/extensibility/plugins/marketplace/manager.ts +36 -10
  254. package/src/extensibility/slash-commands.ts +5 -0
  255. package/src/gjc-runtime/gc-runtime.ts +37 -7
  256. package/src/gjc-runtime/launch-tmux.ts +30 -15
  257. package/src/gjc-runtime/launch-worktree.test.ts +148 -0
  258. package/src/gjc-runtime/launch-worktree.ts +101 -14
  259. package/src/gjc-runtime/managed-owner-admission.ts +46 -2
  260. package/src/gjc-runtime/ralplan-current-final.ts +294 -0
  261. package/src/gjc-runtime/ralplan-runtime.ts +95 -1
  262. package/src/gjc-runtime/session-resolution.ts +38 -6
  263. package/src/gjc-runtime/session-state-lock.ts +59 -14
  264. package/src/gjc-runtime/state-runtime.ts +23 -19
  265. package/src/gjc-runtime/state-writer.ts +109 -5
  266. package/src/gjc-runtime/tmux-sessions.ts +93 -13
  267. package/src/gjc-runtime/ultragoal-change-set.ts +90 -11
  268. package/src/gjc-runtime/ultragoal-guard.ts +9 -1
  269. package/src/gjc-runtime/ultragoal-runtime.ts +15 -5
  270. package/src/harness-control-plane/finalize.ts +11 -0
  271. package/src/harness-control-plane/receipt-spool.ts +105 -28
  272. package/src/harness-control-plane/storage.ts +124 -6
  273. package/src/hindsight/content.ts +116 -16
  274. package/src/hindsight/mental-models.ts +22 -10
  275. package/src/hindsight/state.ts +7 -4
  276. package/src/hooks/native-skill-hook.ts +17 -12
  277. package/src/internal-urls/artifact-protocol.ts +3 -1
  278. package/src/internal-urls/docs-index.generated.ts +23 -22
  279. package/src/internal-urls/local-protocol.ts +167 -8
  280. package/src/internal-urls/types.ts +2 -0
  281. package/src/lsp/client.ts +2 -1
  282. package/src/lsp/clients/biome-client.ts +2 -0
  283. package/src/lsp/clients/swiftlint-client.ts +2 -0
  284. package/src/lsp/index.ts +29 -0
  285. package/src/lsp/workspace-path.ts +23 -0
  286. package/src/main.ts +330 -92
  287. package/src/memories/index.ts +25 -2
  288. package/src/migrate/action-planner.ts +574 -2
  289. package/src/migrate/adapters/claude-code.ts +162 -5
  290. package/src/migrate/adapters/codex.ts +77 -7
  291. package/src/migrate/adapters/index.ts +258 -5
  292. package/src/migrate/adapters/opencode.ts +10 -1
  293. package/src/migrate/executor.ts +222 -7
  294. package/src/migrate/hook-module.ts +193 -0
  295. package/src/migrate/report.ts +63 -2
  296. package/src/migrate/types.ts +84 -4
  297. package/src/modes/DESIGN.md +51 -0
  298. package/src/modes/acp/acp-agent.ts +10 -1
  299. package/src/modes/acp/permission-mode.ts +18 -1
  300. package/src/modes/components/agent-dashboard.ts +20 -4
  301. package/src/modes/components/assistant-message.ts +55 -1
  302. package/src/modes/components/customization/customization-dashboard.ts +7 -0
  303. package/src/modes/components/customization/import-wizard.ts +29 -3
  304. package/src/modes/components/model-selector.ts +157 -103
  305. package/src/modes/components/settings-selector.ts +103 -37
  306. package/src/modes/components/welcome.ts +3 -1
  307. package/src/modes/controllers/command-controller.ts +300 -9
  308. package/src/modes/controllers/event-controller.ts +1 -1
  309. package/src/modes/controllers/extension-ui-controller.ts +31 -1
  310. package/src/modes/controllers/goal-mode-controller.ts +79 -10
  311. package/src/modes/controllers/input-controller.ts +2 -0
  312. package/src/modes/controllers/plan-mode-controller.ts +66 -11
  313. package/src/modes/controllers/runtime-mcp-command-controller.ts +7 -6
  314. package/src/modes/controllers/selector-controller.ts +76 -7
  315. package/src/modes/interactive-mode.ts +123 -4
  316. package/src/modes/prompt-suggestion-controller.ts +1 -1
  317. package/src/modes/runtime-init.ts +5 -1
  318. package/src/modes/theme/theme.ts +15 -0
  319. package/src/modes/utils/host-status.ts +21 -10
  320. package/src/prompts/agent-fragments/browser-aside.md +2 -2
  321. package/src/prompts/system/irc-incoming.md +3 -1
  322. package/src/prompts/tools/debug.md +1 -1
  323. package/src/prompts/tools/irc.md +1 -1
  324. package/src/prompts/tools/read.md +1 -1
  325. package/src/prompts/tools/report-issue.md +13 -0
  326. package/src/prompts/tools/task.md +1 -1
  327. package/src/runtime-credential-selector.ts +2 -2
  328. package/src/runtime-mcp/content-limits.ts +7 -0
  329. package/src/runtime-mcp/manager-pool.test.ts +5 -4
  330. package/src/runtime-mcp/manager.ts +40 -3
  331. package/src/runtime-mcp/oauth-flow.ts +32 -6
  332. package/src/runtime-mcp/plugin-mcp-name-filter.ts +62 -0
  333. package/src/runtime-mcp/project-host-values.ts +12 -8
  334. package/src/runtime-mcp/render.ts +16 -1
  335. package/src/runtime-mcp/tool-bridge.ts +194 -21
  336. package/src/runtime-mcp/transports/stdio.ts +2 -1
  337. package/src/sdk/broker/broker-exit.ts +10 -5
  338. package/src/sdk/broker/broker.ts +484 -85
  339. package/src/sdk/broker/daemon-entry.ts +338 -116
  340. package/src/sdk/broker/doctor-restart.ts +2 -1
  341. package/src/sdk/broker/ensure.ts +1214 -268
  342. package/src/sdk/broker/hop.ts +286 -0
  343. package/src/sdk/broker/lifecycle-ledger.ts +38 -11
  344. package/src/sdk/broker/lifecycle.ts +102 -6
  345. package/src/sdk/broker/lock-host-identity.ts +29 -0
  346. package/src/sdk/broker/session-index.ts +91 -16
  347. package/src/sdk/broker/transport.ts +17 -1
  348. package/src/sdk/bus/chat-command-policy.ts +16 -3
  349. package/src/sdk/bus/chat-daemon-control.ts +2 -2
  350. package/src/sdk/bus/index.ts +460 -35
  351. package/src/sdk/bus/reconciliation-store.ts +201 -8
  352. package/src/sdk/bus/session-control.ts +2 -0
  353. package/src/sdk/bus/slack-daemon.ts +105 -9
  354. package/src/sdk/bus/telegram-daemon-contract.ts +9 -1
  355. package/src/sdk/bus/telegram-daemon.ts +10 -2
  356. package/src/sdk/bus/threaded-render.ts +20 -1
  357. package/src/sdk/cli/session-cli.ts +23 -6
  358. package/src/sdk/host/control/dispatch.ts +13 -1
  359. package/src/sdk/host/control/operations.ts +10 -1
  360. package/src/sdk/host/events.ts +67 -4
  361. package/src/sdk/host/host.ts +8 -2
  362. package/src/sdk/host/prompt-image-upload.ts +438 -0
  363. package/src/sdk/host/sdk-surface-parity.test.ts +15 -0
  364. package/src/sdk/host/session-runtime.test.ts +1737 -129
  365. package/src/sdk/host/session-runtime.ts +684 -83
  366. package/src/sdk/host/surface-policy.ts +4 -0
  367. package/src/sdk/host/websocket-transport.lifecycle.test.ts +90 -2
  368. package/src/sdk/host/websocket-transport.ts +19 -7
  369. package/src/sdk/lifecycle/service.ts +15 -1
  370. package/src/sdk/prompt-deadline-flush.ts +78 -5
  371. package/src/sdk/prompt-deadline-manager.ts +68 -14
  372. package/src/sdk/prompt-status.ts +6 -4
  373. package/src/sdk/protocol/adapter-validation.ts +81 -12
  374. package/src/sdk/protocol/operation-inventory.generated.json +261 -2
  375. package/src/sdk/protocol/operation-registry.ts +33 -3
  376. package/src/sdk/reconciliation-extensions.ts +1 -0
  377. package/src/sdk/session-list.ts +1 -0
  378. package/src/sdk/session.ts +237 -97
  379. package/src/session/agent-session.ts +2726 -479
  380. package/src/session/history-storage.ts +182 -12
  381. package/src/session/internal/managed-session-scope.ts +173 -57
  382. package/src/session/internal/managed-session-storage.ts +85 -12
  383. package/src/session/internal/native-publish-outcome.ts +5 -1
  384. package/src/session/messages.ts +47 -6
  385. package/src/session/session-manager.ts +326 -19
  386. package/src/session/startup-auth-config.ts +116 -8
  387. package/src/session/streaming-output.ts +262 -4
  388. package/src/session/terminal-abort.ts +14 -2
  389. package/src/session-import/redact.ts +53 -2
  390. package/src/session-import/service.ts +1 -0
  391. package/src/skill-state/workflow-mutation-guard.ts +2145 -206
  392. package/src/slash-commands/builtin-registry.ts +21 -6
  393. package/src/slash-commands/helpers/credential-switch.ts +10 -5
  394. package/src/slash-commands/helpers/mcp.ts +11 -3
  395. package/src/slash-commands/helpers/provider-auth-status.ts +456 -0
  396. package/src/ssh/ssh-executor.ts +9 -2
  397. package/src/stt/recorder.ts +26 -9
  398. package/src/system-prompt.ts +61 -16
  399. package/src/task/executor.ts +115 -20
  400. package/src/task/index.ts +389 -175
  401. package/src/task/role-body.ts +84 -0
  402. package/src/task/token-log-mode.test.ts +162 -0
  403. package/src/task/token-log.ts +91 -2
  404. package/src/tools/archive-reader.ts +650 -37
  405. package/src/tools/ask.ts +1 -0
  406. package/src/tools/ast-edit-write-path.ts +64 -0
  407. package/src/tools/ast-edit.ts +18 -1
  408. package/src/tools/ast-grep.ts +7 -2
  409. package/src/tools/atomic-file-write.ts +46 -15
  410. package/src/tools/auto-generated-guard.ts +15 -3
  411. package/src/tools/awaiting-input.ts +114 -0
  412. package/src/tools/bash-allowed-prefixes.ts +214 -6
  413. package/src/tools/bash-interactive.ts +14 -2
  414. package/src/tools/bash-skill-urls.ts +58 -5
  415. package/src/tools/bash.ts +113 -26
  416. package/src/tools/browser/tab-worker.ts +39 -13
  417. package/src/tools/browser.ts +17 -7
  418. package/src/tools/cron.ts +10 -1
  419. package/src/tools/debug.ts +16 -8
  420. package/src/tools/descriptors.test.ts +44 -0
  421. package/src/tools/descriptors.ts +50 -6
  422. package/src/tools/eval.ts +15 -0
  423. package/src/tools/fetch.ts +93 -4
  424. package/src/tools/gh.ts +27 -12
  425. package/src/tools/image-gen.ts +20 -6
  426. package/src/tools/index.ts +2 -0
  427. package/src/tools/irc.ts +5 -3
  428. package/src/tools/monitor.ts +9 -1
  429. package/src/tools/output-meta.ts +74 -5
  430. package/src/tools/python.ts +1 -1
  431. package/src/tools/read.ts +26 -37
  432. package/src/tools/recipe/runner.ts +23 -12
  433. package/src/tools/render-utils.ts +2 -2
  434. package/src/tools/report-issue.ts +184 -0
  435. package/src/tools/search.ts +3 -2
  436. package/src/tools/ssh.ts +274 -8
  437. package/src/tools/subagent-render.ts +114 -12
  438. package/src/tools/subagent.ts +118 -5
  439. package/src/tools/tmux-self-injection-guard.ts +3301 -122
  440. package/src/tools/todo-write.ts +41 -3
  441. package/src/tools/tool-catalog.generated.ts +32 -4
  442. package/src/tools/ultragoal-ask-guard.ts +13 -1
  443. package/src/tools/write.ts +42 -3
  444. package/src/tui/tree-list.ts +15 -1
  445. package/src/utils/commit-message-generator.ts +12 -4
  446. package/src/utils/external-editor.ts +21 -7
  447. package/src/utils/file-mentions.ts +75 -7
  448. package/src/utils/git.ts +38 -7
  449. package/src/utils/image-limits.ts +8 -0
  450. package/src/utils/image-loading.ts +1 -1
  451. package/src/utils/open.ts +73 -4
  452. package/src/utils/pasted-image-loading.ts +9 -8
  453. package/src/utils/prompt-suggestion.ts +5 -4
  454. package/src/utils/shell-snapshot.ts +9 -1
  455. package/src/utils/title-generator.ts +72 -22
  456. package/src/web/scrapers/index.ts +3 -1
  457. package/src/web/scrapers/types.ts +8 -0
  458. package/src/web/scrapers/youtube.ts +3 -2
  459. package/src/web/search/index.ts +2 -1
  460. package/src/web/search/providers/codex.ts +29 -32
  461. package/src/web/search/providers/insane.ts +91 -28
  462. package/src/web/search/providers/searxng.ts +62 -10
@@ -1,7 +1,7 @@
1
1
  import { INTENT_FIELD, type ThinkingLevel } from "@gajae-code/agent-core";
2
2
  import type { Api, Model } from "@gajae-code/ai/core";
3
3
  import { Markdown } from "@gajae-code/tui";
4
- import { prompt } from "@gajae-code/utils";
4
+ import { prompt, sanitizeText } from "@gajae-code/utils";
5
5
  import chalk from "chalk";
6
6
  import typesDescriptionPrompt from "../../commit/prompts/types-description.md" with { type: "text" };
7
7
  import type { ModelRegistry } from "../../config/model-registry";
@@ -81,7 +81,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
81
81
  const toolArgsById = new Map<string, { name: string; args?: Record<string, unknown> }>();
82
82
  const writeThinkingLine = (text: string) => {
83
83
  if (!process.stdout.isTTY) return;
84
- const line = chalk.dim(`… ${text}`);
84
+ const line = chalk.dim(`… ${sanitizeText(text)}`);
85
85
  process.stdout.write(`\r\x1b[2K${line}`);
86
86
  thinkingLineActive = true;
87
87
  };
@@ -118,7 +118,7 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
118
118
  clearThinkingLine();
119
119
  const assistantMessage = event.message as { stopReason?: string; errorMessage?: string };
120
120
  if (assistantMessage.stopReason === "error" && assistantMessage.errorMessage) {
121
- process.stdout.write(`● Error: ${assistantMessage.errorMessage}\n`);
121
+ process.stdout.write(`● Error: ${sanitizeText(assistantMessage.errorMessage)}\n`);
122
122
  }
123
123
  const messageText = extractMessageText(event.message?.content ?? []);
124
124
  if (messageText) {
@@ -131,10 +131,10 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
131
131
  const stored = toolArgsById.get(event.toolCallId) ?? { name: event.toolName };
132
132
  toolArgsById.delete(event.toolCallId);
133
133
  clearThinkingLine();
134
- const toolLabel = formatToolLabel(stored.name);
134
+ const toolLabel = sanitizeText(formatToolLabel(stored.name));
135
135
  const symbol = event.isError ? "" : "";
136
136
  process.stdout.write(`${symbol} ${toolLabel}\n`);
137
- const argsLines = formatToolArgs(stored.args);
137
+ const argsLines = formatToolArgs(stored.args).map(line => sanitizeText(line));
138
138
  if (argsLines.length > 0) {
139
139
  process.stdout.write(`${formatToolArgsBlock(argsLines)}\n`);
140
140
  }
@@ -201,7 +201,7 @@ function extractMessageText(content: Array<{ type: string; text?: string }>): st
201
201
  }
202
202
 
203
203
  function writeAssistantMessage(message: string): void {
204
- const lines = renderMarkdownLines(message);
204
+ const lines = renderMarkdownLines(sanitizeText(message));
205
205
  if (lines.length === 0) return;
206
206
  let firstContentIndex = lines.findIndex(line => line.trim().length > 0);
207
207
  if (firstContentIndex === -1) {
@@ -5,6 +5,7 @@ import { prompt } from "@gajae-code/utils";
5
5
  import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with { type: "text" };
6
6
  import analysisUserPrompt from "../../commit/prompts/analysis-user.md" with { type: "text" };
7
7
  import type { ConventionalAnalysis } from "../../commit/types";
8
+ import { escapePromptMetadata } from "../../session/messages";
8
9
  import { toReasoningEffort } from "../../thinking";
9
10
  import { createConventionalAnalysisTool, parseConventionalAnalysisResponse } from "../shared-llm";
10
11
 
@@ -41,13 +42,21 @@ export async function generateConventionalAnalysis({
41
42
  diff,
42
43
  }: ConventionalAnalysisInput): Promise<ConventionalAnalysis> {
43
44
  const userContent = prompt.render(analysisUserPrompt, {
44
- context_files: contextFiles && contextFiles.length > 0 ? contextFiles : undefined,
45
+ context_files:
46
+ contextFiles && contextFiles.length > 0
47
+ ? contextFiles.map(file => ({
48
+ path: escapePromptMetadata(file.path),
49
+ content: escapePromptMetadata(file.content, { preserveNewlines: true }),
50
+ }))
51
+ : undefined,
45
52
  user_context: userContext,
46
53
  types_description: typesDescription,
47
- recent_commits: recentCommits?.join("\n"),
48
- scope_candidates: scopeCandidates,
49
- stat,
50
- diff,
54
+ recent_commits: recentCommits
55
+ ?.map(subject => escapePromptMetadata(subject, { preserveNewlines: true }))
56
+ .join("\n"),
57
+ scope_candidates: escapePromptMetadata(scopeCandidates, { preserveNewlines: true }),
58
+ stat: escapePromptMetadata(stat, { preserveNewlines: true }),
59
+ diff: escapePromptMetadata(diff, { preserveNewlines: true }),
51
60
  });
52
61
 
53
62
  const response = await completeSimple(
@@ -168,11 +168,49 @@ function applyChangelogEntries(
168
168
  if (deletions) {
169
169
  base = applyDeletions(base, deletions);
170
170
  }
171
- const merged = mergeEntries(base, entries);
171
+ const merged = mergeEntries(base, redactPersistedEntries(entries));
172
172
  const sectionLines = renderUnreleasedSections(merged);
173
173
  return [...before, ...sectionLines, ...after].join("\n");
174
174
  }
175
175
 
176
+ function redactPersistedEntries(entries: Record<string, string[]>): Record<string, string[]> {
177
+ const redacted: Record<string, string[]> = {};
178
+ for (const [section, items] of Object.entries(entries)) {
179
+ redacted[section] = items.map(item => redactChangelogEntry(item));
180
+ }
181
+ return redacted;
182
+ }
183
+
184
+ const AWS_LABELED_KEY =
185
+ "(?:(?:aws[_-]?)?secret[_-]?access[_-]?key|(?:aws[_-]?)?session[_-]?token|x[_-]?amz[_-]?security[_-]?token)";
186
+
187
+ export function redactChangelogEntry(text: string): string {
188
+ let redacted = text.replace(/(^|[^0-9A-Za-z])(?:AKIA|ASIA)[0-9A-Z]{16}(?![0-9A-Za-z])/g, "$1[REDACTED_AWS_KEY_ID]");
189
+ redacted = redacted.replace(
190
+ new RegExp(
191
+ `(^|[^A-Za-z0-9_-])(["']?${AWS_LABELED_KEY}["']?\\s*[=:]\\s*)(\\$?)(["'\`])([^"'\`\\r\\n]{8,})\\4`,
192
+ "gi",
193
+ ),
194
+ "$1$2$3$4[REDACTED_SECRET]$4",
195
+ );
196
+ redacted = redacted.replace(
197
+ new RegExp(`(^|[^A-Za-z0-9_-])(["']?${AWS_LABELED_KEY}["']?\\s*[=:]\\s*)[^\\s"'\`,;{}[\\]()&<>#]{8,}`, "gi"),
198
+ "$1$2[REDACTED_SECRET]",
199
+ );
200
+ redacted = redacted.replace(/\b(?:sk|pk|rk|xox[baprs])-[-_A-Za-z0-9]{12,}\b/g, "[REDACTED_TOKEN]");
201
+ redacted = redacted.replace(/(?<![A-Za-z0-9_-])AIza[0-9A-Za-z_-]{35}(?![A-Za-z0-9_-])/g, "[REDACTED_TOKEN]");
202
+ redacted = redacted.replace(/\b(?:gh[opsur]_[A-Za-z0-9_]{12,}|github_pat_[A-Za-z0-9_]{12,})\b/g, "[REDACTED_TOKEN]");
203
+ redacted = redacted.replace(
204
+ /\b((?:ANTHROPIC|OPENAI|GITHUB|GOOGLE|GEMINI|KAGI|TAVILY|EXA|PERPLEXITY|ZAI|KIMI|BRAVE|SEARXNG|AWS)_[A-Z0-9_]*(?:KEY|TOKEN|SECRET|COOKIE|PASSWORD))\s*=\s*[^\s\n]+/gi,
205
+ "$1=[REDACTED_SECRET]",
206
+ );
207
+ redacted = redacted.replace(
208
+ /\b(Authorization|Proxy-Authorization)\s*:\s*(?:Bearer|Basic|Token)\s+[^\s\n]+/gi,
209
+ "$1: [REDACTED_AUTH_HEADER]",
210
+ );
211
+ return redacted.replace(/\b(Cookie|Set-Cookie)\s*:\s*[^\n]+/gi, "$1: [REDACTED_COOKIE]");
212
+ }
213
+
176
214
  function applyDeletions(
177
215
  existing: Record<string, string[]>,
178
216
  deletions: Record<string, string[]>,
@@ -23,9 +23,9 @@ export interface MapReduceInput {
23
23
  model: Model<Api>;
24
24
  apiKey: string;
25
25
  thinkingLevel?: ThinkingLevel;
26
- smolModel: Model<Api>;
27
- smolApiKey: string;
28
- smolThinkingLevel?: ThinkingLevel;
26
+ secondaryModel: Model<Api>;
27
+ secondaryApiKey: string;
28
+ secondaryThinkingLevel?: ThinkingLevel;
29
29
  diff: string;
30
30
  stat: string;
31
31
  scopeCandidates: string;
@@ -45,15 +45,15 @@ export function shouldUseMapReduce(diff: string, settings?: MapReduceSettings):
45
45
  }
46
46
 
47
47
  /**
48
- * Run map-reduce analysis for large diffs using smol + primary models.
48
+ * Run map-reduce analysis for large diffs using the secondary (map) and primary (reduce) models.
49
49
  */
50
50
 
51
51
  export async function runMapReduceAnalysis(input: MapReduceInput): Promise<ConventionalAnalysis> {
52
52
  const fileDiffs = parseFileDiffs(input.diff).filter(file => !isExcludedFile(file.filename));
53
53
  const observations = await runMapPhase({
54
- model: input.smolModel,
55
- apiKey: input.smolApiKey,
56
- thinkingLevel: input.smolThinkingLevel,
54
+ model: input.secondaryModel,
55
+ apiKey: input.secondaryApiKey,
56
+ thinkingLevel: input.secondaryThinkingLevel,
57
57
  files: fileDiffs,
58
58
  config: input.settings,
59
59
  });
@@ -1,9 +1,16 @@
1
+ import { redactCrashSecrets } from "@gajae-code/utils";
2
+ import { redactChangelogEntry } from "./changelog";
1
3
  import type { ConventionalAnalysis } from "./types";
2
4
 
5
+ /** Same order as generated task messages, so a second pass stays stable. */
6
+ function redactCommitText(text: string): string {
7
+ return redactCrashSecrets(redactChangelogEntry(text));
8
+ }
9
+
3
10
  export function formatCommitMessage(analysis: ConventionalAnalysis, summary: string): string {
4
- const scopePart = analysis.scope ? `(${analysis.scope})` : "";
5
- const header = `${analysis.type}${scopePart}: ${summary}`;
6
- const bodyLines = analysis.details.map(detail => `- ${detail.text.trim()}`);
11
+ const scopePart = analysis.scope ? `(${redactCommitText(analysis.scope)})` : "";
12
+ const header = `${analysis.type}${scopePart}: ${redactCommitText(summary)}`;
13
+ const bodyLines = analysis.details.map(detail => `- ${redactCommitText(detail.text.trim())}`);
7
14
  if (bodyLines.length === 0) {
8
15
  return header;
9
16
  }
@@ -53,9 +53,9 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
53
53
  thinkingLevel: primaryThinkingLevel,
54
54
  } = await resolvePrimaryModel(args.model, settings, modelRegistry);
55
55
  const {
56
- model: smolModel,
57
- apiKey: smolApiKey,
58
- thinkingLevel: smolThinkingLevel,
56
+ model: secondaryModel,
57
+ apiKey: secondaryApiKey,
58
+ thinkingLevel: secondaryThinkingLevel,
59
59
  } = await resolveSecondaryCommitModel(settings, modelRegistry, primaryModel, primaryApiKey);
60
60
 
61
61
  let stagedFiles = await git.diff.changedFiles(cwd, { cached: true });
@@ -102,9 +102,9 @@ async function runLegacyCommitCommand(args: CommitCommandArgs): Promise<void> {
102
102
  primaryModel,
103
103
  primaryApiKey,
104
104
  primaryThinkingLevel,
105
- smolModel,
106
- smolApiKey,
107
- smolThinkingLevel,
105
+ secondaryModel,
106
+ secondaryApiKey,
107
+ secondaryThinkingLevel,
108
108
  commitSettings,
109
109
  });
110
110
 
@@ -148,9 +148,9 @@ async function generateAnalysis(input: {
148
148
  primaryModel: Model<Api>;
149
149
  primaryApiKey: string;
150
150
  primaryThinkingLevel?: ThinkingLevel;
151
- smolModel: Model<Api>;
152
- smolApiKey: string;
153
- smolThinkingLevel?: ThinkingLevel;
151
+ secondaryModel: Model<Api>;
152
+ secondaryApiKey: string;
153
+ secondaryThinkingLevel?: ThinkingLevel;
154
154
  commitSettings: {
155
155
  mapReduceEnabled: boolean;
156
156
  mapReduceMinFiles: number;
@@ -172,9 +172,9 @@ async function generateAnalysis(input: {
172
172
  model: input.primaryModel,
173
173
  apiKey: input.primaryApiKey,
174
174
  thinkingLevel: input.primaryThinkingLevel,
175
- smolModel: input.smolModel,
176
- smolApiKey: input.smolApiKey,
177
- smolThinkingLevel: input.smolThinkingLevel,
175
+ secondaryModel: input.secondaryModel,
176
+ secondaryApiKey: input.secondaryApiKey,
177
+ secondaryThinkingLevel: input.secondaryThinkingLevel,
178
178
  diff: input.diff,
179
179
  stat: input.stat,
180
180
  scopeCandidates: input.scopeCandidates,
@@ -44,7 +44,8 @@ type NormalizedAutoroutingCuratedTierMap = {
44
44
  export const TIER_MAP_VERSION = 1;
45
45
 
46
46
  export const CURATED_TIER_LABELS = {
47
- "anthropic/claude-haiku-4-5": [{ tier: "fast", rank: 1 }],
47
+ "anthropic/claude-haiku-5-5": [{ tier: "fast", rank: 1 }],
48
+ "anthropic/claude-haiku-4-5": [{ tier: "fast", rank: 2 }],
48
49
  "anthropic/claude-sonnet-5-5": [{ tier: "balanced", rank: 1 }],
49
50
  "anthropic/claude-sonnet-5": [{ tier: "balanced", rank: 2 }],
50
51
  "anthropic/claude-sonnet-4-6": [{ tier: "balanced", rank: 3 }],
@@ -77,15 +78,63 @@ export const CURATED_TIER_LABELS = {
77
78
 
78
79
  /** Generated from packages/ai/src/models.json at feature land: 3936 in-scope baseline skips. */
79
80
  export const TIER_MAP_SKIP_LIST = {
81
+ "amazon-bedrock/anthropic.claude-haiku-5-5": {
82
+ rationale: "Haiku 5.5 Bedrock catalog addition; not yet curated",
83
+ },
84
+ "amazon-bedrock/au.anthropic.claude-haiku-5-5": {
85
+ rationale: "Haiku 5.5 Bedrock catalog addition; not yet curated",
86
+ },
87
+ "amazon-bedrock/eu.anthropic.claude-haiku-5-5": {
88
+ rationale: "Haiku 5.5 Bedrock catalog addition; not yet curated",
89
+ },
90
+ "amazon-bedrock/global.anthropic.claude-haiku-5-5": {
91
+ rationale: "Haiku 5.5 Bedrock catalog addition; not yet curated",
92
+ },
93
+ "amazon-bedrock/jp.anthropic.claude-haiku-5-5": {
94
+ rationale: "Haiku 5.5 Bedrock catalog addition; not yet curated",
95
+ },
96
+ "amazon-bedrock/us.anthropic.claude-haiku-5-5": {
97
+ rationale: "Haiku 5.5 Bedrock catalog addition; not yet curated",
98
+ },
80
99
  "amazon-bedrock/au.anthropic.claude-sonnet-5-5": {
81
100
  rationale: "Sonnet 5.5 catalog addition (#6111); not yet curated",
82
101
  },
102
+ "amazon-bedrock/in.anthropic.claude-haiku-4-5-20251001-v1:0": {
103
+ rationale: "India-region Bedrock Anthropic catalog addition (#6731); not yet curated",
104
+ },
105
+ "amazon-bedrock/in.anthropic.claude-opus-5": {
106
+ rationale: "India-region Bedrock Anthropic catalog addition (#6731); not yet curated",
107
+ },
108
+ "amazon-bedrock/in.anthropic.claude-sonnet-5": {
109
+ rationale: "India-region Bedrock Anthropic catalog addition (#6731); not yet curated",
110
+ },
83
111
  "amazon-bedrock/jp.anthropic.claude-sonnet-5-5": {
84
112
  rationale: "Sonnet 5.5 catalog addition (#6111); not yet curated",
85
113
  },
86
114
  "amazon-bedrock/us.anthropic.claude-sonnet-5-5": {
87
115
  rationale: "Sonnet 5.5 catalog addition (#6111); not yet curated",
88
116
  },
117
+ "github-copilot/gemini-4-argon": {
118
+ rationale: "Gemini 4 Argon provider catalog addition (#6731); not yet curated",
119
+ },
120
+ "google/gemini-4-argon": {
121
+ rationale: "Gemini 4 Argon catalog addition (#6731); tier ordering not yet reviewed",
122
+ },
123
+ "kilo/google/gemini-4-argon": {
124
+ rationale: "Gemini 4 Argon provider catalog addition (#6731); not yet curated",
125
+ },
126
+ "opencode-zen/gemini-4-argon": {
127
+ rationale: "Gemini 4 Argon provider catalog addition (#6731); not yet curated",
128
+ },
129
+ "openrouter/google/gemini-4-argon": {
130
+ rationale: "Gemini 4 Argon provider catalog addition (#6731); not yet curated",
131
+ },
132
+ "openrouter/google/gemini-4-argon:batch": {
133
+ rationale: "Gemini 4 Argon batch provider catalog addition (#6731); not yet curated",
134
+ },
135
+ "vercel-ai-gateway/google/gemini-4-argon": {
136
+ rationale: "Gemini 4 Argon provider catalog addition (#6731); not yet curated",
137
+ },
89
138
  "kiro/claude-opus-5-5": { rationale: "post-rebase catalog addition from dev; not yet curated" },
90
139
  "kiro/claude-opus-5.5": { rationale: "post-rebase catalog addition from dev; not yet curated" },
91
140
  "alibaba-token-plan/qwen3.8-flash": { rationale: "post-rebase catalog addition from dev; not yet curated" },
@@ -1,18 +1,121 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  import * as fs from "node:fs";
2
3
  import * as path from "node:path";
3
- import { getAgentDir, isEnoent, logger } from "@gajae-code/utils";
4
+ import { renameNoReplacePath } from "@gajae-code/natives";
5
+ import { getAgentDir, isEnoent, isFsError, logger } from "@gajae-code/utils";
4
6
  import { JSONC, YAML } from "bun";
5
7
  import type { ZodType } from "zod/v4";
6
8
 
9
+ // Volumes without hard links fail `link`. Windows maps FAT ERROR_INVALID_FUNCTION to EISDIR.
10
+ const HARD_LINK_UNAVAILABLE = new Set(["EACCES", "EISDIR", "ENOSYS", "ENOTSUP", "EOPNOTSUPP", "EPERM", "EXDEV"]);
11
+
7
12
  /** Minimal subset of the AJV ConfigSchemaError shape this module actually relies on. */
8
13
  interface ConfigSchemaError {
9
14
  instancePath: string;
10
15
  message: string | undefined;
11
16
  }
12
17
 
18
+ /** True when the final path component exists, including a dangling symlink. */
19
+ function ymlPathPresent(ymlPath: string): boolean {
20
+ try {
21
+ fs.lstatSync(ymlPath);
22
+ return true;
23
+ } catch (error) {
24
+ if (isEnoent(error)) return false;
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ function hardLinkUnavailable(error: unknown): boolean {
30
+ return isFsError(error) && HARD_LINK_UNAVAILABLE.has(error.code);
31
+ }
32
+
33
+ function stagedRegularFile(tmpPath: string): boolean {
34
+ try {
35
+ return fs.lstatSync(tmpPath).isFile();
36
+ } catch {
37
+ return false;
38
+ }
39
+ }
40
+
41
+ function discardStaged(tmpPath: string): void {
42
+ try {
43
+ fs.unlinkSync(tmpPath);
44
+ } catch {
45
+ // Unpublished, or the no-replace rename already moved this name.
46
+ }
47
+ }
48
+
49
+ /** Publish the completed private file when `link` is not supported. Never replaces `ymlPath`. */
50
+ function publishStagedWithoutHardLink(tmpPath: string, ymlPath: string): void {
51
+ const published = renameNoReplacePath(tmpPath, ymlPath);
52
+ if (!published.ok && published.reason !== "destination_exists") {
53
+ discardStaged(tmpPath);
54
+ throw new Error(`config migration could not publish without a hard link (${published.reason})`);
55
+ }
56
+ discardStaged(tmpPath);
57
+ }
58
+
59
+ /**
60
+ * Publish a new regular file as mode 0600. The bytes go to a private inode
61
+ * opened with O_EXCL and O_NOFOLLOW. `link` then attaches that inode to
62
+ * `ymlPath` and fails when the name exists, so a final-component symlink is
63
+ * not followed and an existing file is not replaced. A volume without hard
64
+ * links publishes that same completed file with a no-replace rename. The
65
+ * destination name stays absent until the bytes are complete.
66
+ */
67
+ function writeNewOwnerOnlyNoFollow(ymlPath: string, content: string): void {
68
+ // Ancestor symlinks only. The final component stays unresolved so no-follow still applies.
69
+ ymlPath = path.join(fs.realpathSync(path.dirname(ymlPath)), path.basename(ymlPath));
70
+ const noFollow = process.platform === "win32" ? 0 : (fs.constants.O_NOFOLLOW ?? 0);
71
+ const tmpPath = path.join(
72
+ path.dirname(ymlPath),
73
+ `.${path.basename(ymlPath)}.${process.pid}.${randomBytes(8).toString("hex")}.tmp`,
74
+ );
75
+ let fd: number | undefined;
76
+ try {
77
+ fd = fs.openSync(tmpPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL | noFollow, 0o600);
78
+ if (process.platform !== "win32") fs.fchmodSync(fd, 0o600);
79
+ fs.writeFileSync(fd, content);
80
+ fs.fsyncSync(fd);
81
+ fs.closeSync(fd);
82
+ fd = undefined;
83
+ } catch (error) {
84
+ if (fd !== undefined) {
85
+ try {
86
+ fs.closeSync(fd);
87
+ } catch {
88
+ // The original failure is the one that must surface.
89
+ }
90
+ try {
91
+ fs.unlinkSync(tmpPath);
92
+ } catch {
93
+ // The destination name was not published.
94
+ }
95
+ }
96
+ throw error;
97
+ }
98
+ try {
99
+ fs.linkSync(tmpPath, ymlPath);
100
+ } catch (error) {
101
+ // The name is already present: a finished peer publish, or a file or symlink we must not replace.
102
+ if (isFsError(error) && (error.code === "EEXIST" || error.code === "ELOOP")) {
103
+ discardStaged(tmpPath);
104
+ return;
105
+ }
106
+ if (hardLinkUnavailable(error) && stagedRegularFile(tmpPath)) {
107
+ publishStagedWithoutHardLink(tmpPath, ymlPath);
108
+ return;
109
+ }
110
+ discardStaged(tmpPath);
111
+ throw error;
112
+ }
113
+ discardStaged(tmpPath);
114
+ }
115
+
13
116
  function migrateJsonToYml(jsonPath: string, ymlPath: string) {
14
117
  try {
15
- if (fs.existsSync(ymlPath)) return;
118
+ if (ymlPathPresent(ymlPath)) return;
16
119
  if (!fs.existsSync(jsonPath)) return;
17
120
 
18
121
  const content = fs.readFileSync(jsonPath, "utf-8");
@@ -21,7 +124,7 @@ function migrateJsonToYml(jsonPath: string, ymlPath: string) {
21
124
  logger.warn("migrateJsonToYml: invalid json structure", { path: jsonPath });
22
125
  return;
23
126
  }
24
- fs.writeFileSync(ymlPath, YAML.stringify(parsed, null, 2));
127
+ writeNewOwnerOnlyNoFollow(ymlPath, YAML.stringify(parsed, null, 2));
25
128
  } catch (error) {
26
129
  logger.warn("migrateJsonToYml: migration failed", { error: String(error) });
27
130
  }
@@ -0,0 +1,148 @@
1
+ import * as fs from "node:fs/promises";
2
+ import * as path from "node:path";
3
+
4
+ const PRIVATE_DIRECTORY_MASK = 0o077;
5
+ const GROUP_OTHER_WRITE_MASK = 0o022;
6
+ const STICKY_BIT = 0o1000;
7
+
8
+ interface DirectorySnapshot {
9
+ readonly dev: number;
10
+ readonly ino: number;
11
+ readonly uid: number;
12
+ readonly mode: number;
13
+ }
14
+
15
+ interface CheckedFilePath {
16
+ readonly filePath: string;
17
+ readonly lexicalParent: string;
18
+ readonly canonicalParent: string;
19
+ readonly canonicalTarget: string | undefined;
20
+ }
21
+
22
+ function isStrictDescendant(parent: string, candidate: string): boolean {
23
+ const relative = path.relative(parent, candidate);
24
+ return (
25
+ relative.length > 0 && !path.isAbsolute(relative) && relative !== ".." && !relative.startsWith(`..${path.sep}`)
26
+ );
27
+ }
28
+
29
+ function directoryAncestors(directory: string): string[] {
30
+ const absolute = path.resolve(directory);
31
+ const root = path.parse(absolute).root;
32
+ const ancestors = [root];
33
+ let current = root;
34
+ for (const component of path.relative(root, absolute).split(path.sep).filter(Boolean)) {
35
+ current = path.join(current, component);
36
+ ancestors.push(current);
37
+ }
38
+ return ancestors;
39
+ }
40
+
41
+ function hasExpectedOwner(uid: number, owner: number): boolean {
42
+ return owner === 0 || owner === uid;
43
+ }
44
+
45
+ async function inspectFilePath(filePath: string): Promise<CheckedFilePath> {
46
+ if (!path.isAbsolute(filePath)) throw new Error("Configuration paths must be absolute");
47
+ const absolutePath = path.resolve(filePath);
48
+ const lexicalParent = path.dirname(absolutePath);
49
+ const canonicalParent = await fs.realpath(lexicalParent);
50
+ let canonicalTarget: string | undefined;
51
+ try {
52
+ await fs.lstat(absolutePath);
53
+ canonicalTarget = await fs.realpath(absolutePath);
54
+ } catch (error) {
55
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
56
+ }
57
+ return { filePath: absolutePath, lexicalParent, canonicalParent, canonicalTarget };
58
+ }
59
+
60
+ /**
61
+ * Hot reload requires a privacy check that only Linux can perform: its
62
+ * directory modes include the POSIX ACL mask, so denying group/other access
63
+ * also denies named ACL grants. Node does not expose enough ACL state on other
64
+ * platforms to establish equivalent privacy, so they do not watch at all.
65
+ */
66
+ export function isConfigHotReloadSupported(): boolean {
67
+ return process.platform === "linux" && typeof process.getuid === "function";
68
+ }
69
+
70
+ /** Returns whether both configuration paths live in private, safely owned directories. */
71
+ export async function isConfigHotReloadTrusted(paths: {
72
+ readonly configPath: string;
73
+ readonly modelsPath: string;
74
+ }): Promise<boolean> {
75
+ if (process.platform !== "linux" || typeof process.getuid !== "function") return false;
76
+ const uid = process.getuid();
77
+ try {
78
+ const checkedPaths = await Promise.all([inspectFilePath(paths.configPath), inspectFilePath(paths.modelsPath)]);
79
+ const effectiveDirectories = new Set<string>();
80
+ const directoriesToCheck = new Set<string>();
81
+ for (const checked of checkedPaths) {
82
+ effectiveDirectories.add(checked.canonicalParent);
83
+ directoriesToCheck.add(checked.lexicalParent);
84
+ directoriesToCheck.add(checked.canonicalParent);
85
+ if (checked.canonicalTarget) {
86
+ const targetParent = path.dirname(checked.canonicalTarget);
87
+ effectiveDirectories.add(targetParent);
88
+ directoriesToCheck.add(targetParent);
89
+ }
90
+ }
91
+ for (const directory of [...directoriesToCheck]) {
92
+ for (const ancestor of directoryAncestors(directory)) directoriesToCheck.add(ancestor);
93
+ }
94
+
95
+ const snapshots = new Map<string, DirectorySnapshot>();
96
+ for (const directory of directoriesToCheck) {
97
+ const stat = await fs.stat(directory);
98
+ if (!stat.isDirectory()) return false;
99
+ snapshots.set(directory, { dev: stat.dev, ino: stat.ino, uid: stat.uid, mode: stat.mode });
100
+ }
101
+
102
+ for (const directory of effectiveDirectories) {
103
+ const snapshot = snapshots.get(directory);
104
+ if (!snapshot || snapshot.uid !== uid || (snapshot.mode & PRIVATE_DIRECTORY_MASK) !== 0) return false;
105
+ }
106
+
107
+ for (const directory of directoriesToCheck) {
108
+ const snapshot = snapshots.get(directory)!;
109
+ if (!hasExpectedOwner(uid, snapshot.uid)) return false;
110
+ if ((snapshot.mode & GROUP_OTHER_WRITE_MASK) === 0) continue;
111
+ const safelyContainsPrivateLeaf =
112
+ snapshot.uid === 0 &&
113
+ (snapshot.mode & STICKY_BIT) !== 0 &&
114
+ [...effectiveDirectories].some(leaf => isStrictDescendant(directory, leaf));
115
+ if (!safelyContainsPrivateLeaf) return false;
116
+ }
117
+
118
+ // Confirm symlink resolution and directory identity stayed stable while checking metadata.
119
+ for (const checked of checkedPaths) {
120
+ if ((await fs.realpath(checked.lexicalParent)) !== checked.canonicalParent) return false;
121
+ try {
122
+ await fs.lstat(checked.filePath);
123
+ } catch (error) {
124
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT" || checked.canonicalTarget !== undefined) {
125
+ return false;
126
+ }
127
+ continue;
128
+ }
129
+ if (checked.canonicalTarget === undefined) return false;
130
+ if ((await fs.realpath(checked.filePath)) !== checked.canonicalTarget) return false;
131
+ }
132
+ for (const [directory, snapshot] of snapshots) {
133
+ const stat = await fs.stat(directory);
134
+ if (
135
+ !stat.isDirectory() ||
136
+ stat.dev !== snapshot.dev ||
137
+ stat.ino !== snapshot.ino ||
138
+ stat.uid !== snapshot.uid ||
139
+ stat.mode !== snapshot.mode
140
+ ) {
141
+ return false;
142
+ }
143
+ }
144
+ return true;
145
+ } catch {
146
+ return false;
147
+ }
148
+ }