@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
@@ -11,7 +11,7 @@ import type { AgentMessage } from "@gajae-code/agent-core";
11
11
  import type { ImageContent } from "@gajae-code/ai/core";
12
12
  import type { glob as globFn } from "@gajae-code/natives";
13
13
  import { fuzzyMatch } from "@gajae-code/tui";
14
- import { formatAge, formatBytes, readImageMetadata } from "@gajae-code/utils";
14
+ import { formatAge, formatBytes, pathIsWithin, readImageMetadata, resolveEquivalentPath } from "@gajae-code/utils";
15
15
  import { formatHashLines } from "../hashline/hash";
16
16
  import type { FileMentionMessage } from "../session/messages";
17
17
  import {
@@ -80,6 +80,7 @@ type MentionCandidate = {
80
80
  path: string;
81
81
  pathLower: string;
82
82
  normalizedPath: string;
83
+ segments: string[];
83
84
  };
84
85
 
85
86
  function normalizeMentionQuery(query: string): string {
@@ -117,11 +118,37 @@ async function listMentionCandidates(cwd: string): Promise<MentionCandidate[]> {
117
118
  if (normalizedPath.length === 0) {
118
119
  continue;
119
120
  }
120
- candidates.push({ path: entry, pathLower, normalizedPath });
121
+ const segments = pathLower.split(PATH_SEPARATOR_REGEX).filter(segment => segment.length > 0);
122
+ candidates.push({ path: entry, pathLower, normalizedPath, segments });
121
123
  }
122
124
  return candidates;
123
125
  }
124
126
 
127
+ /**
128
+ * True when `query` can be spelled as prefixes of path segments taken in order
129
+ * (segments may be skipped), e.g. `httpserap` → `http_server_api_tests`.
130
+ * Plain subsequence matching would also accept letters scattered mid-word across
131
+ * any long path, so a stray `@handle` or `@word` would resolve to an unrelated file.
132
+ */
133
+ function matchesSegmentPrefixes(query: string, segments: string[]): boolean {
134
+ const failed = new Set<number>();
135
+ const visit = (segmentIndex: number, queryIndex: number): boolean => {
136
+ if (queryIndex === query.length) return true;
137
+ if (segmentIndex === segments.length) return false;
138
+ const key = segmentIndex * (query.length + 1) + queryIndex;
139
+ if (failed.has(key)) return false;
140
+ const segment = segments[segmentIndex]!;
141
+ for (let taken = 1; taken <= segment.length && queryIndex + taken <= query.length; taken++) {
142
+ if (segment[taken - 1] !== query[queryIndex + taken - 1]) break;
143
+ if (visit(segmentIndex + 1, queryIndex + taken)) return true;
144
+ }
145
+ if (visit(segmentIndex + 1, queryIndex)) return true;
146
+ failed.add(key);
147
+ return false;
148
+ };
149
+ return visit(0, 0);
150
+ }
151
+
125
152
  async function resolveMentionPath(
126
153
  filePath: string,
127
154
  cwd: string,
@@ -148,6 +175,7 @@ async function resolveMentionPath(
148
175
  }
149
176
 
150
177
  const scored = candidates
178
+ .filter(candidate => matchesSegmentPrefixes(normalizedQuery, candidate.segments))
151
179
  .map(candidate => ({ candidate, match: fuzzyMatch(normalizedQuery, candidate.normalizedPath) }))
152
180
  .filter(entry => entry.match.matches)
153
181
  .sort((a, b) => {
@@ -285,6 +313,26 @@ export function extractFileMentions(text: string): string[] {
285
313
  return [...new Set(mentions)];
286
314
  }
287
315
 
316
+ /**
317
+ * Compare canonical Windows paths without folding directory names: individual
318
+ * directories can opt into case-sensitive names.
319
+ */
320
+ export function isCanonicalWindowsPathWithin(root: string, candidate: string): boolean {
321
+ if (!path.win32.isAbsolute(root) || !path.win32.isAbsolute(candidate)) return false;
322
+
323
+ const canonicalRoot = path.win32.resolve(root);
324
+ const canonicalCandidate = path.win32.resolve(candidate);
325
+ const rootParts = path.win32.parse(canonicalRoot);
326
+ const candidateParts = path.win32.parse(canonicalCandidate);
327
+ if (rootParts.root.toLowerCase() !== candidateParts.root.toLowerCase()) return false;
328
+
329
+ const rootSegments = canonicalRoot.slice(rootParts.root.length).split(path.win32.sep).filter(Boolean);
330
+ const candidateSegments = canonicalCandidate.slice(candidateParts.root.length).split(path.win32.sep).filter(Boolean);
331
+ if (candidateSegments.length < rootSegments.length) return false;
332
+
333
+ return rootSegments.every((segment, index) => segment === candidateSegments[index]);
334
+ }
335
+
288
336
  /**
289
337
  * Generate a FileMentionMessage containing the contents of mentioned files.
290
338
  * Returns empty array if no files could be read.
@@ -308,6 +356,8 @@ export async function generateFileMentionMessages(
308
356
  if (filePaths.length === 0) return [];
309
357
 
310
358
  const autoResizeImages = options?.autoResizeImages ?? true;
359
+ const canonicalWindowsCwd = process.platform === "win32" ? await fs.realpath(cwd).catch(() => undefined) : undefined;
360
+ if (process.platform === "win32" && canonicalWindowsCwd === undefined) return [];
311
361
 
312
362
  const files: FileMentionMessage["files"] = [];
313
363
  let mentionCandidatesPromise: Promise<MentionCandidate[]> | null = null;
@@ -323,6 +373,24 @@ export async function generateFileMentionMessages(
323
373
  continue;
324
374
  }
325
375
  const absolutePath = resolveReadPath(resolvedPath, cwd);
376
+ // Skip `..`, `~`, and outside absolute paths. On Windows, realpath both
377
+ // paths and compare exact components because directories can be case-sensitive.
378
+ // Fail closed if either real path is unavailable. Read the canonical path so
379
+ // replacing the original mention symlink does not retarget this read.
380
+ const canonicalPath =
381
+ process.platform === "win32"
382
+ ? await fs.realpath(absolutePath).catch(() => undefined)
383
+ : resolveEquivalentPath(absolutePath);
384
+ if (!canonicalPath) {
385
+ continue;
386
+ }
387
+ const staysWithinSession =
388
+ process.platform === "win32"
389
+ ? canonicalWindowsCwd !== undefined && isCanonicalWindowsPathWithin(canonicalWindowsCwd, canonicalPath)
390
+ : pathIsWithin(cwd, canonicalPath);
391
+ if (!staysWithinSession) {
392
+ continue;
393
+ }
326
394
  // Duplicate suppression, keyed by absolute path: same path shown earlier this
327
395
  // batch or recently in the conversation → compact note, no re-read (the path
328
396
  // stays referenced so the model can re-fetch via the read tool).
@@ -336,14 +404,14 @@ export async function generateFileMentionMessages(
336
404
  }
337
405
  shownInBatch.add(absolutePath);
338
406
  try {
339
- const stat = await Bun.file(absolutePath).stat();
407
+ const stat = await Bun.file(canonicalPath).stat();
340
408
  if (stat.isDirectory()) {
341
- const { output, lineCount } = await buildDirectoryListing(absolutePath);
409
+ const { output, lineCount } = await buildDirectoryListing(canonicalPath);
342
410
  files.push({ path: resolvedPath, content: output, lineCount });
343
411
  continue;
344
412
  }
345
413
 
346
- const imageMetadata = await readImageMetadata(absolutePath);
414
+ const imageMetadata = await readImageMetadata(canonicalPath);
347
415
  const mimeType = imageMetadata?.mimeType;
348
416
  if (mimeType) {
349
417
  if (stat.size > MAX_AUTO_READ_IMAGE_BYTES) {
@@ -355,7 +423,7 @@ export async function generateFileMentionMessages(
355
423
  });
356
424
  continue;
357
425
  }
358
- const buffer = await fs.readFile(absolutePath);
426
+ const buffer = await fs.readFile(canonicalPath);
359
427
  if (buffer.length === 0) {
360
428
  continue;
361
429
  }
@@ -392,7 +460,7 @@ export async function generateFileMentionMessages(
392
460
  continue;
393
461
  }
394
462
 
395
- const content = await Bun.file(absolutePath).text();
463
+ const content = await Bun.file(canonicalPath).text();
396
464
  let { output, lineCount } = buildTextOutput(content, options?.maxInlineBytes);
397
465
  if (options?.useHashLines) {
398
466
  output = formatHashLines(output);
package/src/utils/git.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
- import { $which, hasFsCode, isEnoent, Snowflake } from "@gajae-code/utils";
4
+ import { $which, hasFsCode, isEnoent } from "@gajae-code/utils";
5
+ import { projectSafeSpawnEnv } from "@gajae-code/utils/shell-config";
5
6
  import {
6
7
  parseDiffHunks as parseCommitDiffHunks,
7
8
  parseFileDiffs,
@@ -198,7 +199,10 @@ async function runCommand(
198
199
  const commandArgs = withShortLivedGitConfig(options.readOnly ? withNoOptionalLocks(args) : [...args]);
199
200
  const child = Bun.spawn(["git", ...commandArgs], {
200
201
  cwd,
201
- env: options.env ? { ...process.env, GIT_OPTIONAL_LOCKS: "0", ...options.env } : undefined,
202
+ // The inherited environment holds the autoloaded project `.env`; without
203
+ // filtering, a repository could set e.g. `GIT_CONFIG_*` (`core.hooksPath`)
204
+ // or `GIT_SSH_COMMAND` and have GJC's own commit/fetch/push run its code.
205
+ env: options.env ? { ...projectSafeSpawnEnv(), GIT_OPTIONAL_LOCKS: "0", ...options.env } : projectSafeSpawnEnv(),
202
206
  signal: options.signal,
203
207
  stdin: normalizeStdin(options.stdin),
204
208
  stdout: "pipe",
@@ -365,9 +369,24 @@ function buildApplyArgs(patchPath: string, options: PatchOptions): string[] {
365
369
  }
366
370
 
367
371
  async function writeTempPatch(content: string): Promise<string> {
368
- const tempPath = path.join(os.tmpdir(), `gjc-git-patch-${Snowflake.next()}.patch`);
369
- await Bun.write(tempPath, content);
370
- return tempPath;
372
+ // Random directory plus an exclusive create: a preexisting symlink under the
373
+ // shared temp directory is not opened, and the patch is not left world-readable.
374
+ const tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "gjc-git-patch-"));
375
+ try {
376
+ if (process.platform !== "win32") await fs.promises.chmod(tempDir, 0o700);
377
+ const tempPath = path.join(tempDir, "patch");
378
+ const handle = await fs.promises.open(tempPath, "wx", 0o600);
379
+ try {
380
+ await handle.writeFile(content);
381
+ } finally {
382
+ await handle.close();
383
+ }
384
+ if (process.platform !== "win32") await fs.promises.chmod(tempPath, 0o600);
385
+ return tempPath;
386
+ } catch (error) {
387
+ await fs.promises.rm(tempDir, { recursive: true, force: true });
388
+ throw error;
389
+ }
371
390
  }
372
391
 
373
392
  // ════════════════════════════════════════════════════════════════════════════
@@ -1232,7 +1251,7 @@ export const patch = {
1232
1251
  try {
1233
1252
  await patch.apply(cwd, tempPath, options);
1234
1253
  } finally {
1235
- await fs.promises.rm(tempPath, { force: true });
1254
+ await fs.promises.rm(path.dirname(tempPath), { recursive: true, force: true });
1236
1255
  }
1237
1256
  },
1238
1257
 
@@ -1253,7 +1272,7 @@ export const patch = {
1253
1272
  try {
1254
1273
  return await patch.canApply(cwd, tempPath, options);
1255
1274
  } finally {
1256
- await fs.promises.rm(tempPath, { force: true });
1275
+ await fs.promises.rm(path.dirname(tempPath), { recursive: true, force: true });
1257
1276
  }
1258
1277
  },
1259
1278
 
@@ -1594,6 +1613,14 @@ function formatGhFailure(args: readonly string[], stdout: string, stderr: string
1594
1613
  return `GitHub CLI command failed: gh ${args.join(" ")}`;
1595
1614
  }
1596
1615
 
1616
+ /** Captured `gh` output never wants a pager or forced-terminal behavior. */
1617
+ function ghSpawnEnv(): Record<string, string> {
1618
+ const env = projectSafeSpawnEnv();
1619
+ delete env.GH_FORCE_TTY;
1620
+ env.GH_PAGER = "cat";
1621
+ return env;
1622
+ }
1623
+
1597
1624
  export const github = {
1598
1625
  /** Check if `gh` CLI is installed. */
1599
1626
  available(): boolean {
@@ -1609,6 +1636,10 @@ export const github = {
1609
1636
  try {
1610
1637
  const child = Bun.spawn(["gh", ...args], {
1611
1638
  cwd,
1639
+ // Same project-dotenv filtering as git: `gh` runs git for checkout
1640
+ // and push. Output is captured, so gh must never page or act as if
1641
+ // on a terminal; pin both so a project `.env` cannot select a pager.
1642
+ env: ghSpawnEnv(),
1612
1643
  stdin: "ignore",
1613
1644
  stdout: "pipe",
1614
1645
  stderr: "pipe",
@@ -0,0 +1,8 @@
1
+ // Shared input policy must remain independent of file loading, tools and sessions.
2
+ export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
3
+ export const MAX_STAGED_PROMPT_IMAGES = 16;
4
+ export const MAX_PASTED_IMAGE_SOURCE_BYTES = 64 * 1024 * 1024;
5
+ export const MAX_PASTED_IMAGE_OUTPUT_BYTES = 64 * 1024 * 1024;
6
+ export const MAX_PASTED_IMAGE_DIMENSION = 20_000;
7
+ export const MAX_PASTED_IMAGE_PIXELS = 40_000_000;
8
+ export const MAX_PASTED_IMAGE_DECODED_BYTES = 160 * 1024 * 1024;
@@ -2,9 +2,9 @@ import * as fs from "node:fs/promises";
2
2
  import type { ImageContent } from "@gajae-code/ai/core";
3
3
  import { formatBytes, readImageMetadata, SUPPORTED_IMAGE_MIME_TYPES } from "@gajae-code/utils";
4
4
  import { resolveReadPath } from "../tools/path-utils";
5
+ import { MAX_IMAGE_INPUT_BYTES } from "./image-limits";
5
6
  import { formatDimensionNote, resizeImageBuffer } from "./image-resize";
6
7
 
7
- export const MAX_IMAGE_INPUT_BYTES = 20 * 1024 * 1024;
8
8
  export const SUPPORTED_INPUT_IMAGE_MIME_TYPES = SUPPORTED_IMAGE_MIME_TYPES;
9
9
 
10
10
  const SVG_MIME_TYPE = "image/svg+xml";
package/src/utils/open.ts CHANGED
@@ -1,15 +1,84 @@
1
- /** Open a URL or file path in the default browser/application. Best-effort, never throws. */
1
+ /** Open an http(s) URL or local path. Best-effort, never throws. */
2
+ const ALLOWED_OPEN_PROTOCOLS = new Set(["http:", "https:"]);
3
+
4
+ /**
5
+ * Zero-width and non-ASCII spaces. ASCII controls are rejected, not stripped:
6
+ * the URL parser drops some of them and would turn a hostile string into a clean href.
7
+ */
8
+ const OUTER_FORMAT = /[\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF\u200B\u2060]/;
9
+
10
+ function hasAsciiControl(value: string): boolean {
11
+ for (let i = 0; i < value.length; i++) {
12
+ const code = value.charCodeAt(i);
13
+ if (code <= 0x1f || code === 0x7f) return true;
14
+ }
15
+ return false;
16
+ }
17
+
18
+ function isOuterPadding(ch: string): boolean {
19
+ return ch.trim() === "" || OUTER_FORMAT.test(ch);
20
+ }
21
+
22
+ /** Drop every outer space or format character. Classification only; local paths stay raw. */
23
+ function peelPadding(value: string): string {
24
+ let start = 0;
25
+ let end = value.length;
26
+ while (start < end && isOuterPadding(value.charAt(start))) start++;
27
+ while (end > start && isOuterPadding(value.charAt(end - 1))) end--;
28
+ return start === 0 && end === value.length ? value : value.slice(start, end);
29
+ }
30
+
31
+ /** Drive paths parse as one-letter schemes (`c:`). Recognize them before that check. */
32
+ function isWindowsDrivePath(value: string): boolean {
33
+ return /^[A-Za-z]:[\\/]/.test(value);
34
+ }
35
+
36
+ const SCHEME_PREFIX = /^[A-Za-z][A-Za-z0-9+.-]*:/;
37
+
38
+ /** http(s) href, "reject" for any other URL, or undefined when `value` is not a URL. */
39
+ function classifyUrl(value: string): string | "reject" | undefined {
40
+ if (isWindowsDrivePath(value) || isWindowsDrivePath(value.trim())) return undefined;
41
+ try {
42
+ const parsed = new URL(value);
43
+ if (!ALLOWED_OPEN_PROTOCOLS.has(parsed.protocol) || hasAsciiControl(parsed.href)) return "reject";
44
+ return parsed.href;
45
+ } catch {
46
+ return undefined;
47
+ }
48
+ }
49
+
50
+ function resolveOpenTarget(raw: string): string | undefined {
51
+ if (hasAsciiControl(raw) || raw.length === 0) return undefined;
52
+ // Local paths, including surrounding spaces, are spawned unchanged.
53
+ if (isWindowsDrivePath(raw)) return raw;
54
+ const direct = classifyUrl(raw);
55
+ if (direct === "reject") return undefined;
56
+ if (direct !== undefined) return direct;
57
+
58
+ const peeled = peelPadding(raw);
59
+ if (peeled.length === 0) return undefined;
60
+ // Padding can hide a scheme or a flag. Refuse that instead of opening a different string.
61
+ if (peeled !== raw && !isWindowsDrivePath(peeled)) {
62
+ const hidden = classifyUrl(peeled);
63
+ if (hidden !== undefined || peeled.startsWith("-") || SCHEME_PREFIX.test(peeled)) return undefined;
64
+ }
65
+ if (raw.startsWith("-") || SCHEME_PREFIX.test(raw)) return undefined;
66
+ return raw;
67
+ }
68
+
2
69
  export function openPath(urlOrPath: string): void {
70
+ const target = resolveOpenTarget(urlOrPath);
71
+ if (target === undefined) return;
3
72
  let cmd: string[];
4
73
  switch (process.platform) {
5
74
  case "darwin":
6
- cmd = ["open", urlOrPath];
75
+ cmd = ["open", target];
7
76
  break;
8
77
  case "win32":
9
- cmd = ["rundll32", "url.dll,FileProtocolHandler", urlOrPath];
78
+ cmd = ["rundll32", "url.dll,FileProtocolHandler", target];
10
79
  break;
11
80
  default:
12
- cmd = ["xdg-open", urlOrPath];
81
+ cmd = ["xdg-open", target];
13
82
  break;
14
83
  }
15
84
  try {
@@ -4,10 +4,17 @@ import * as os from "node:os";
4
4
  import * as path from "node:path";
5
5
  import type { ImageContent } from "@gajae-code/ai/core";
6
6
  import { formatBytes, parseImageMetadata } from "@gajae-code/utils";
7
+ import {
8
+ MAX_IMAGE_INPUT_BYTES,
9
+ MAX_PASTED_IMAGE_DECODED_BYTES,
10
+ MAX_PASTED_IMAGE_DIMENSION,
11
+ MAX_PASTED_IMAGE_OUTPUT_BYTES,
12
+ MAX_PASTED_IMAGE_PIXELS,
13
+ MAX_PASTED_IMAGE_SOURCE_BYTES,
14
+ } from "./image-limits";
7
15
  import {
8
16
  ImageInputTooLargeError,
9
17
  type LoadedImageInput,
10
- MAX_IMAGE_INPUT_BYTES,
11
18
  materializeImageInput,
12
19
  type TransformedImageInput,
13
20
  transformImageInputBytes,
@@ -15,12 +22,6 @@ import {
15
22
  import { DEFAULT_IMAGE_RESIZE_MAX_BYTES } from "./image-resize";
16
23
  import { MAX_PASTED_IMAGE_COUNT } from "./pasted-image-path";
17
24
 
18
- export const MAX_PASTED_IMAGE_SOURCE_BYTES = 64 * 1024 * 1024;
19
- export const MAX_PASTED_IMAGE_OUTPUT_BYTES = 64 * 1024 * 1024;
20
- export const MAX_PASTED_IMAGE_DIMENSION = 20_000;
21
- export const MAX_PASTED_IMAGE_PIXELS = 40_000_000;
22
- export const MAX_PASTED_IMAGE_DECODED_BYTES = 160 * 1024 * 1024;
23
-
24
25
  export type PastedImageBatchErrorCode =
25
26
  | "too-many"
26
27
  | "unsafe-path"
@@ -319,7 +320,7 @@ function hasWebpAnimation(bytes: Buffer): boolean {
319
320
  return false;
320
321
  }
321
322
 
322
- function isAnimatedImage(bytes: Buffer, mimeType: string): boolean | undefined {
323
+ export function isAnimatedImage(bytes: Buffer, mimeType: string): boolean | undefined {
323
324
  if (mimeType === "image/gif") {
324
325
  const frames = gifFrameCount(bytes);
325
326
  return frames === undefined ? undefined : frames > 1;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Predict the user's likely next prompt after an agent turn using a smol,
3
- * fast model. Native GJC port of the ghost-text prompt-suggestion behavior
2
+ * Predict the user's likely next prompt after an agent turn with the `default`
3
+ * model role. Native GJC port of the ghost-text prompt-suggestion behavior
4
4
  * from Claude Code: the prediction renders as dim ghost text in the empty
5
5
  * composer and Tab accepts it.
6
6
  */
@@ -169,8 +169,9 @@ function getSuggestionModel(
169
169
  registry: ModelRegistry,
170
170
  settings: Settings,
171
171
  currentModel?: Model<Api>,
172
+ credentialSessionId?: string,
172
173
  ): Model<Api> | undefined {
173
- const availableModels = registry.getAvailable();
174
+ const availableModels = registry.getAvailable(credentialSessionId);
174
175
  if (availableModels.length === 0) return undefined;
175
176
  const model = resolveRoleSelection(["default"], settings, availableModels, registry)?.model;
176
177
  if (model) return model;
@@ -203,7 +204,7 @@ export async function generatePromptSuggestion(
203
204
  const context = buildPromptSuggestionContext(messages);
204
205
  if (!context) return null;
205
206
 
206
- const model = getSuggestionModel(registry, settings, currentModel);
207
+ const model = getSuggestionModel(registry, settings, currentModel, sessionId);
207
208
  if (!model) {
208
209
  logger.debug("prompt-suggestion: no model available");
209
210
  return null;
@@ -31,6 +31,11 @@ function getShellConfigFile(shell: string, home: string): string {
31
31
  return path.join(home, ".profile");
32
32
  }
33
33
 
34
+ /** One POSIX shell word. Double quotes would still run `$()` and backticks. */
35
+ export function quotePosixShell(value: string): string {
36
+ return `'${value.replace(/'/g, `'\\''`)}'`;
37
+ }
38
+
34
39
  /**
35
40
  * Generate the snapshot creation script.
36
41
  * This script sources the user's rc file and extracts functions, aliases, and options.
@@ -43,6 +48,9 @@ function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: str
43
48
 
44
49
  // Escape the snapshot path for shell
45
50
  const escapedPath = snapshotPath.replace(/'/g, "'\\''");
51
+ const rcSource = hasRcFile
52
+ ? `source ${quotePosixShell(rcFile)} < /dev/null 2>/dev/null`
53
+ : "# No user config file to source";
46
54
 
47
55
  // Function extraction differs between bash and zsh
48
56
  const functionScript = isZsh
@@ -83,7 +91,7 @@ umask 077
83
91
  SNAPSHOT_FILE='${escapedPath}'
84
92
 
85
93
  # Source user's rc file if it exists
86
- ${hasRcFile ? `source "${rcFile}" < /dev/null 2>/dev/null` : "# No user config file to source"}
94
+ ${rcSource}
87
95
 
88
96
  # Create/clear the snapshot file
89
97
  echo "# Shell snapshot - generated by gjc agent" >| "$SNAPSHOT_FILE"
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Generate session titles using a smol, fast model.
2
+ * Generate session titles with the `default` model role.
3
3
  */
4
4
  import * as path from "node:path";
5
5
 
6
6
  import type { AgentMessage } from "@gajae-code/agent-core";
7
7
  import { type Api, type AssistantMessage, completeSimple, type Model, type Tool } from "@gajae-code/ai/core";
8
- import { logger, prompt } from "@gajae-code/utils";
8
+ import { logger, prompt, redactCrashSecrets } from "@gajae-code/utils";
9
9
  import type { ModelRegistry } from "../config/model-registry";
10
10
  import { resolveRoleSelection } from "../config/model-resolver";
11
11
  import type { Settings } from "../config/settings";
12
12
  import titleSystemPrompt from "../prompts/system/title-system.md" with { type: "text" };
13
+ import { collectEnvSecrets, createSecretObfuscator, loadSecrets } from "../secrets";
13
14
  import { syncCmuxWorkspaceTitle } from "./cmux-workspace";
14
15
  import { syncHerdrPaneTitle } from "./herdr-pane";
15
16
 
@@ -37,8 +38,13 @@ const MAX_TITLE_WORDS = 12;
37
38
  * Build a bounded conversation digest for title regeneration. Regeneration
38
39
  * reads several messages so a throwaway first prompt can be corrected, while
39
40
  * the automatic first-message path intentionally uses only its initial input.
41
+ * `obfuscateText`, when set, runs on each kept message's full text before the
42
+ * per-message character cut so a secret cannot be split by that cut.
40
43
  */
41
- export function buildConversationTitleInput(messages: readonly AgentMessage[]): string | undefined {
44
+ export function buildConversationTitleInput(
45
+ messages: readonly AgentMessage[],
46
+ obfuscateText?: (text: string) => string,
47
+ ): string | undefined {
42
48
  const userMessages: string[] = [];
43
49
  for (const message of messages) {
44
50
  if (message.role !== "user") continue;
@@ -51,7 +57,10 @@ export function buildConversationTitleInput(messages: readonly AgentMessage[]):
51
57
  .map(block => block.text)
52
58
  .join("\n");
53
59
  const trimmed = text.trim();
54
- if (trimmed) userMessages.push(trimmed);
60
+ if (!trimmed) continue;
61
+ // Emptiness uses the trimmed text. With an obfuscator, keep the original
62
+ // so a secret that includes edge whitespace still matches, then trim.
63
+ userMessages.push(obfuscateText ? text : trimmed);
55
64
  }
56
65
 
57
66
  if (userMessages.length === 0) return undefined;
@@ -60,13 +69,19 @@ export function buildConversationTitleInput(messages: readonly AgentMessage[]):
60
69
  userMessages.length > MAX_TITLE_CONVERSATION_MESSAGES
61
70
  ? [userMessages[0]!, ...userMessages.slice(-(MAX_TITLE_CONVERSATION_MESSAGES - 1))]
62
71
  : userMessages;
72
+ // Obfuscate the full kept text first. The 400-character cut below, and the
73
+ // later 2,000-character cap, then keep their existing limits.
74
+ const preparedMessages = obfuscateText
75
+ ? keptMessages.map(message => obfuscateText(message).trim()).filter(message => message.length > 0)
76
+ : keptMessages;
77
+ if (preparedMessages.length === 0) return undefined;
63
78
  // Reserve separator and ellipsis room so the digest is already within the
64
79
  // generator's MAX_INPUT_CHARS bound; its existing truncation is a no-op here.
65
80
  const maxMessageChars = Math.min(
66
81
  MAX_TITLE_MESSAGE_CHARS,
67
- Math.floor((MAX_INPUT_CHARS - (keptMessages.length - 1) - keptMessages.length) / keptMessages.length),
82
+ Math.floor((MAX_INPUT_CHARS - (preparedMessages.length - 1) - preparedMessages.length) / preparedMessages.length),
68
83
  );
69
- return keptMessages
84
+ return preparedMessages
70
85
  .map(message => (message.length > maxMessageChars ? `${message.slice(0, maxMessageChars)}…` : message))
71
86
  .join("\n");
72
87
  }
@@ -87,8 +102,13 @@ const setTitleTool: Tool = {
87
102
  },
88
103
  };
89
104
 
90
- function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel?: Model<Api>): Model<Api> | undefined {
91
- const availableModels = registry.getAvailable();
105
+ function getTitleModel(
106
+ registry: ModelRegistry,
107
+ settings: Settings,
108
+ currentModel?: Model<Api>,
109
+ credentialSessionId?: string,
110
+ ): Model<Api> | undefined {
111
+ const availableModels = registry.getAvailable(credentialSessionId);
92
112
  if (availableModels.length === 0) return undefined;
93
113
 
94
114
  const titleModel = resolveRoleSelection(["default"], settings, availableModels, registry)?.model;
@@ -99,6 +119,32 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
99
119
  return undefined;
100
120
  }
101
121
 
122
+ /**
123
+ * Obfuscate the full title input, then truncate, then wrap.
124
+ * A secret that crosses the character cap is replaced before the cut, and the
125
+ * `<user-message>` fence stays outside the truncated body.
126
+ */
127
+ export function prepareTitleOutboundText(firstMessage: string, obfuscateText?: (text: string) => string): string {
128
+ const source = obfuscateText ? obfuscateText(firstMessage) : firstMessage;
129
+ const truncated = source.length > MAX_INPUT_CHARS ? `${source.slice(0, MAX_INPUT_CHARS)}…` : source;
130
+ return `<user-message>\n${truncated}\n</user-message>`;
131
+ }
132
+
133
+ export async function titleInputObfuscator(
134
+ settings: Settings,
135
+ activeCwd?: string,
136
+ ): Promise<((text: string) => string) | undefined> {
137
+ const reader = settings as Partial<Pick<Settings, "get" | "getCwd" | "getAgentDir">>;
138
+ if (typeof reader.get !== "function" || reader.get("secrets.enabled") !== true) return undefined;
139
+ const cwd = activeCwd ?? (typeof reader.getCwd === "function" ? reader.getCwd() : process.cwd());
140
+ const agentDir = typeof reader.getAgentDir === "function" ? reader.getAgentDir() : cwd;
141
+ const entries = [...collectEnvSecrets(), ...(await loadSecrets(cwd, agentDir))];
142
+ if (entries.length === 0) return undefined;
143
+ const obfuscator = createSecretObfuscator(entries);
144
+ if (!obfuscator.hasSecrets()) return undefined;
145
+ return text => obfuscator.obfuscate(text);
146
+ }
147
+
102
148
  /**
103
149
  * Generate a title for a session based on the provided user-message input.
104
150
  * Automatic titles pass the first user message; regeneration passes a bounded
@@ -106,13 +152,14 @@ function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel
106
152
  *
107
153
  * @param firstMessage The first user message or bounded conversation digest
108
154
  * @param registry Model registry
109
- * @param settings Settings used to resolve the smol role
155
+ * @param settings Settings used to resolve the `default` model role
110
156
  * @param sessionId Optional session id for sticky API key selection
111
157
  * @param currentModel Current model (used to derive title model)
112
158
  * @param metadataResolver Optional resolver evaluated after credential selection
113
159
  * to produce request metadata (e.g. user_id for session attribution). Using a
114
160
  * resolver instead of a pre-evaluated value ensures the metadata's account_uuid
115
161
  * reflects the credential actually selected for this request.
162
+ * @param activeCwd Active session cwd for project-scoped secret lookup.
116
163
  */
117
164
  export async function generateSessionTitle(
118
165
  firstMessage: string,
@@ -121,23 +168,17 @@ export async function generateSessionTitle(
121
168
  sessionId?: string,
122
169
  currentModel?: Model<Api>,
123
170
  metadataResolver?: (provider: string) => Record<string, unknown> | undefined,
171
+ activeCwd?: string,
124
172
  ): Promise<string | null> {
125
- const model = getTitleModel(registry, settings, currentModel);
173
+ const model = getTitleModel(registry, settings, currentModel, sessionId);
126
174
  if (!model) {
127
175
  logger.debug("title-generator: no title model found");
128
176
  return null;
129
177
  }
130
178
 
131
- // Truncate message if too long
132
- const truncatedMessage =
133
- firstMessage.length > MAX_INPUT_CHARS ? `${firstMessage.slice(0, MAX_INPUT_CHARS)}…` : firstMessage;
134
- const userMessage = `<user-message>
135
- ${truncatedMessage}
136
- </user-message>`;
137
-
138
179
  const apiKey = await registry.getApiKey(model, sessionId);
139
180
  if (!apiKey) {
140
- logger.debug("title-generator: no API key for smol model", {
181
+ logger.debug("title-generator: no API key for title model", {
141
182
  provider: model.provider,
142
183
  id: model.id,
143
184
  });
@@ -147,6 +188,7 @@ ${truncatedMessage}
147
188
  // request is already recorded; metadataResolver can then return the correct
148
189
  // account_uuid rather than the snapshot-at-call-site value.
149
190
  const metadata = metadataResolver?.(model.provider);
191
+ const userMessage = prepareTitleOutboundText(firstMessage, await titleInputObfuscator(settings, activeCwd));
150
192
 
151
193
  // Title generation is a 3-6 word task, but some reasoning backends ignore
152
194
  // disableReasoning. Keep the normal cheap budget for non-reasoning models
@@ -159,7 +201,12 @@ ${truncatedMessage}
159
201
  userMessage,
160
202
  maxTokens,
161
203
  };
162
- logger.debug("title-generator: request", request);
204
+ logger.debug("title-generator: request", {
205
+ model: request.model,
206
+ systemPrompt: request.systemPrompt,
207
+ userMessage: redactCrashSecrets(request.userMessage),
208
+ maxTokens: request.maxTokens,
209
+ });
163
210
 
164
211
  try {
165
212
  const response = await completeSimple(
@@ -182,7 +229,10 @@ ${truncatedMessage}
182
229
  logger.debug("title-generator: response error", {
183
230
  model: request.model,
184
231
  stopReason: response.stopReason,
185
- errorMessage: response.errorMessage,
232
+ errorMessage:
233
+ typeof response.errorMessage === "string"
234
+ ? redactCrashSecrets(response.errorMessage)
235
+ : response.errorMessage,
186
236
  });
187
237
  return null;
188
238
  }
@@ -191,7 +241,7 @@ ${truncatedMessage}
191
241
 
192
242
  logger.debug("title-generator: response", {
193
243
  model: request.model,
194
- title,
244
+ title: redactCrashSecrets(title),
195
245
  usage: response.usage,
196
246
  stopReason: response.stopReason,
197
247
  });
@@ -204,7 +254,7 @@ ${truncatedMessage}
204
254
  } catch (err) {
205
255
  logger.debug("title-generator: error", {
206
256
  model: request.model,
207
- error: err instanceof Error ? err.message : String(err),
257
+ error: redactCrashSecrets(err instanceof Error ? err.message : String(err)),
208
258
  });
209
259
  return null;
210
260
  }