@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
@@ -268,6 +268,14 @@ function removeTasks(phases: TodoPhase[], entry: TodoOpEntryValue, errors: strin
268
268
  return phases;
269
269
  }
270
270
 
271
+ /** Type of a value that was expected to be a string, with its article. */
272
+ function describeArgument(value: unknown): string {
273
+ if (value === null) return "null";
274
+ if (Array.isArray(value)) return "an array";
275
+ if (typeof value === "object") return "an object";
276
+ return `a ${typeof value}`;
277
+ }
278
+
271
279
  function applyEntry(phases: TodoPhase[], entry: TodoOpEntryValue, errors: string[]): TodoPhase[] {
272
280
  switch (entry.op) {
273
281
  case "init":
@@ -302,6 +310,16 @@ function applyEntry(phases: TodoPhase[], entry: TodoOpEntryValue, errors: string
302
310
  case "note": {
303
311
  const hit = resolveTaskOrError(phases, entry.task, errors);
304
312
  if (!hit) return phases;
313
+ // The schema types `text`, but bridges and replays reach `execute` without
314
+ // raw validation, and `.replace` on a non-string body used to crash the
315
+ // call. Reject it with a correction instead of storing it.
316
+ if (entry.text !== undefined && typeof entry.text !== "string") {
317
+ errors.push(
318
+ `Note text must be a string; got ${describeArgument(entry.text)}. ` +
319
+ 'Pass the note body as "text", for example { op: "note", task: "<task content>", text: "why" }.',
320
+ );
321
+ return phases;
322
+ }
305
323
  const text = (entry.text ?? "").replace(/\s+$/u, "");
306
324
  if (!text) {
307
325
  errors.push("Missing text for note operation");
@@ -337,6 +355,26 @@ export function applyOpsToPhases(
337
355
  // Markdown round-trip
338
356
  // =============================================================================
339
357
 
358
+ /**
359
+ * Notes are declared as `string[]`, but the array is only typed, never validated:
360
+ * a session persisted elsewhere or a bridged caller that reached `execute`
361
+ * without raw validation can hand over any element, and every render path splits
362
+ * each note on newlines. One non-string element used to crash the call with a
363
+ * minified `.split is not a function` TypeError instead of a tool result. String
364
+ * notes render exactly as before.
365
+ */
366
+ function noteDisplayText(note: unknown): string {
367
+ if (typeof note === "string") return note;
368
+ if (note !== null && typeof note === "object") {
369
+ try {
370
+ return JSON.stringify(note) ?? "[unrenderable note]";
371
+ } catch {
372
+ return "[unrenderable note]";
373
+ }
374
+ }
375
+ return String(note);
376
+ }
377
+
340
378
  const STATUS_TO_MARKER: Record<TodoStatus, string> = {
341
379
  pending: " ",
342
380
  in_progress: "/",
@@ -356,7 +394,7 @@ export function phasesToMarkdown(phases: TodoPhase[]): string {
356
394
  if (task.notes && task.notes.length > 0) {
357
395
  for (let j = 0; j < task.notes.length; j++) {
358
396
  if (j > 0) out.push(" >");
359
- for (const noteLine of task.notes[j].split("\n")) {
397
+ for (const noteLine of noteDisplayText(task.notes[j]).split("\n")) {
360
398
  out.push(noteLine === "" ? " >" : ` > ${noteLine}`);
361
399
  }
362
400
  }
@@ -504,7 +542,7 @@ function formatSummary(phases: TodoPhase[], errors: string[]): string {
504
542
  if (task.status === "in_progress" && task.notes && task.notes.length > 0) {
505
543
  for (let j = 0; j < task.notes.length; j++) {
506
544
  if (j > 0) lines.push(" ---");
507
- for (const noteLine of task.notes[j].split("\n")) {
545
+ for (const noteLine of noteDisplayText(task.notes[j]).split("\n")) {
508
546
  lines.push(` ${noteLine}`);
509
547
  }
510
548
  }
@@ -685,7 +723,7 @@ function renderNoteAttachments(phases: TodoPhase[], uiTheme: Theme): string[] {
685
723
  lines.push(` ${title}`);
686
724
  for (let j = 0; j < task.notes.length; j++) {
687
725
  if (j > 0) lines.push(` ${bar}`);
688
- for (const noteLine of task.notes[j].split("\n")) {
726
+ for (const noteLine of noteDisplayText(task.notes[j]).split("\n")) {
689
727
  lines.push(` ${bar} ${uiTheme.fg("dim", noteLine)}`);
690
728
  }
691
729
  }
@@ -27,7 +27,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
27
27
  "read": {
28
28
  "name": "read",
29
29
  "label": "Read",
30
- "description": "Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.\n\n<instruction>\n- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).\n- You SHOULD parallelize independent reads when exploring related files.\n- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.\n</instruction>\n\n## Parameters\n\n- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).\n- `truncation` — optional `head` | `last` | `both`; selects which end of an over-budget result to retain. Configured default: last (factory default: `last`); structural summaries and routes such as URLs, directories and converted documents default to `head`. A line-range selector still bounds the selection — this only picks which end of that selection survives the byte/line cap. SQLite row queries page via their own `limit`/`offset` and ignore it.\n## Selectors\nAppend `:<sel>` to `path`. The bare path falls back to the default mode.\n\n- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); a plain text file → a bounded receipt of about 50 lines or 10 KiB, whichever is smaller; the configured truncation direction is last (factory default: last). Line+hash anchors keep their real file line numbers and a footer names the omitted range. Archive members use the larger 3000-line / 50 KiB budget. Converted documents, notebooks, URLs and directory listings still start from the beginning.\n- `:50` / `:50-` — read from line 50 onward.\n- `:50-200` — lines 50–200 inclusive.\n- `:50+150` — 150 lines starting at line 50.\n- `:20+1` — exactly one line.\n- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged).\n- `:raw` — verbatim text; no anchors, no summary, no line prefixes.\n- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.\n- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.\n\n# Files\n\n- Reading a directory path returns a depth-limited dirent listing.\n- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:\n\n `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`\n\n If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.\n- Structural summaries honor explicit `truncation`: `head` retains the beginning, `last` the end, and `both` both ends within the summary byte budget. Complete summary units and their original source anchors stay intact and in source order; budget omissions are marked where they occur. This does not switch to raw source. Summaries that fit are unchanged by direction.\n- Directional windows identify the retained first/last lines and the omitted range; use the `re-read <path>:1-<total>` or `:raw` hint in the footer to recover the full content.\n\n# Documents & Notebooks\n\nExtracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.\n\n# Images\n\nReading an image path returns the image itself for visual inspection by a vision-capable model.\n\n# Archives\n\nSupports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.\n\n# SQLite\n\nFor `.sqlite`, `.sqlite3`, `.db`, `.db3`:\n- `file.db` — list tables with row counts\n- `file.db:table` — schema + sample rows\n- `file.db:table:key` — single row by primary key\n- `file.db:table?limit=50&offset=100` — paginated rows\n- `file.db:table?where=status='active'&order=created:desc` — filtered rows\n- `file.db?q=SELECT …` — read-only SELECT query\n\n# URLs\n\n- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.\n- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.\n- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.\n\n# Internal URIs\n\n`agent://<id>`, `artifact://<id>`, `rule://<name>`, and `local://<name>.md` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.\n\nBundled skills have no filesystem home, so the skill tool and skill discovery report them as `embedded:gjc/skills/<name>/SKILL.md`; read that identifier verbatim. Bundled skill fragments, when surfaced, likewise use `embedded:gjc/skill-fragments/...` identifiers.\n\n<critical>\n- Always include `path`; never call `read` with `{}`.\n- For line ranges, append the selector to `path`.\n- Re-issue the selector named by a summary footer before relying on elided content.\n</critical>",
30
+ "description": "Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string.\n\n<instruction>\n- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default).\n- You SHOULD parallelize independent reads when exploring related files.\n- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content.\n</instruction>\n\n## Parameters\n\n- `path` — required. Local path, internal URI (`agent://`, `artifact://`, `rule://`, `local://`), or URL. Append `:<sel>` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`).\n- `truncation` — optional `head` | `last` | `both`; selects which end of an over-budget result to retain. Configured default: last (factory default: `last`); structural summaries and routes such as URLs, directories and converted documents default to `head`. A line-range selector still bounds the selection — this only picks which end of that selection survives the byte/line cap. SQLite row queries page via their own `limit`/`offset` and ignore it.\n## Selectors\nAppend `:<sel>` to `path`. The bare path falls back to the default mode.\n\n- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); a plain text file → a bounded receipt of about 50 lines or 10 KiB, whichever is smaller; the configured truncation direction is last (factory default: last). Line+hash anchors keep their real file line numbers and a footer names the omitted range. Archive members use the larger 3000-line / 50 KiB budget. Converted documents, notebooks, URLs and directory listings still start from the beginning.\n- `:50` / `:50-` — read from line 50 onward.\n- `:50-200` — lines 50–200 inclusive.\n- `:50+150` — 150 lines starting at line 50.\n- `:20+1` — exactly one line.\n- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged).\n- `:raw` — verbatim text; no anchors, no summary, no line prefixes.\n- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order.\n- `:conflicts` — one-line-per-block index of every unresolved git merge conflict.\n\n# Files\n\n- Reading a directory path returns a depth-limited dirent listing.\n- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer of the form:\n\n `[NN lines across MM elided regions; read <path>:raw or a line range like <path>:1-9999 for verbatim content]`\n\n If the elided body is what you actually need, re-issue the **exact selector the footer names**. NEVER guess what's inside `..` / `…` — those markers carry no content.\n- Structural summaries honor explicit `truncation`: `head` retains the beginning, `last` the end, and `both` both ends within the summary byte budget. Complete summary units and their original source anchors stay intact and in source order; budget omissions are marked where they occur. This does not switch to raw source. Summaries that fit are unchanged by direction.\n- Directional windows identify the retained first/last lines and the omitted range; use the `re-read <path>:1-<total>` or `:raw` hint in the footer to recover the full content.\n\n# Documents & Notebooks\n\nExtracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly.\n\n# Images\n\nReading an image path returns the image itself for visual inspection by a vision-capable model.\n\n# Archives\n\nSupports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`.\n\n# SQLite\n\nFor `.sqlite`, `.sqlite3`, `.db`, `.db3`:\n- `file.db` — list tables with row counts\n- `file.db:table` — schema + sample rows\n- `file.db:table:key` — single row by primary key\n- `file.db:table?limit=50&offset=100` — paginated rows\n- `file.db:table?where=status='active'&order=created:desc` — filtered rows\n- `file.db?q=SELECT …` — read-only SELECT query\n\n# URLs\n\n- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown.\n- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output.\n- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`.\n\n# Internal URIs\n\n`agent://<id>`, `artifact://<id>`, `rule://<name>`, and `local://<name>.md` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://<id>` to recover full output that a previous bash/eval/tool result spilled or truncated.\n\nBundled skills have no filesystem home, so the skill tool and skill discovery report them as `embedded:gjc/skills/<name>/SKILL.md`; read that identifier verbatim. Bundled skill fragments, when surfaced, likewise use `embedded:gjc/skill-fragments/…` identifiers.\n\n<critical>\n- Always include `path`; never call `read` with `{}`.\n- For line ranges, append the selector to `path`.\n- Re-issue the selector named by a summary footer before relying on elided content.\n</critical>",
31
31
  "parameters": {
32
32
  "type": "object",
33
33
  "properties": {
@@ -407,7 +407,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
407
407
  "debug": {
408
408
  "name": "debug",
409
409
  "label": "Debug",
410
- "description": "Provides debugger access through the Debug Adapter Protocol (DAP).\nUse for launching or attaching debuggers, setting breakpoints, stepping through execution, inspecting threads/stack/variables, evaluating expressions, capturing output, and interrupting hung programs.\n\n<instruction>\n- Prefer over bash for program state, breakpoints, stepping, thread inspection, or interrupting a running process.\n- `action: \"launch\"` starts a session; `program` is required, `adapter` optional (auto-selected from target path and workspace).\n For Python, set `adapter: \"debugpy\"` and `program` to the target `.py` file; put interpreter/script flags in `args`.\n- `action: \"attach\"` connects to an existing process: `pid` for local attach, `port` for remote attach (where the adapter supports it), `adapter` to force a specific debugger.\n- **Breakpoints**: `set_breakpoint`/`remove_breakpoint` with source (`file`+`line`) or function (`function`); optional `condition` for conditional breakpoints.\n- **Flow control**: `continue` (resumes; briefly waits to observe whether the program stops or keeps running), `step_over`/`step_in`/`step_out` (single-step), `pause` (interrupt a running program so you can inspect state).\n- **Inspect**: `threads` (list), `stack_trace` (frames for current stopped thread), `scopes` (needs `frame_id` or a current stopped frame), `variables` (needs `variable_ref` or `scope_id`), `evaluate` (needs `expression`; `context: \"repl\"` for raw debugger commands when the adapter supports them), `output` (captured stdout/stderr/console), `sessions` (tracked debug sessions), `terminate`.\n- Timeouts apply per-request, not to the full session lifetime.\n</instruction>\n\n<caution>\n- Only one active debug session is supported at a time.\n- Some adapters require a launched session to receive `configurationDone` before the target actually runs; if the tool says configuration is pending, set breakpoints and then call `continue`.\n- Adapter availability depends on local binaries. Common built-ins: `gdb`, `lldb-dap`, `python -m debugpy.adapter`, `dlv dap`.\n- `program` must be an executable file or debug target, not a directory or interpreter name that resolves to a workspace directory.\n</caution>\n\n<examples>\n# Launch and inspect hang\n1. `debug(action: \"launch\", program: \"./my_app\")`\n2. `debug(action: \"set_breakpoint\", file: \"src/main.c\", line: 42)`\n3. `debug(action: \"continue\")`\n4. If the program appears hung: `debug(action: \"pause\")`\n5. Inspect state with `threads`, `stack_trace`, `scopes`, and `variables`\n# Launch a Python script with debugpy\n`debug(action: \"launch\", adapter: \"debugpy\", program: \"scripts/job.py\", args: [\"--flag\"])`\n# Raw debugger command through repl\n`debug(action: \"evaluate\", expression: \"info registers\", context: \"repl\")`\n</examples>",
410
+ "description": "Provides debugger access through the Debug Adapter Protocol (DAP).\nUse for launching or attaching debuggers, setting breakpoints, stepping through execution, inspecting threads/stack/variables, evaluating expressions, capturing output, and interrupting hung programs.\n\n<instruction>\n- Prefer over bash for program state, breakpoints, stepping, thread inspection, or interrupting a running process.\n- `action: \"launch\"` starts a session; `program` is required, `adapter` optional (auto-selected from target path and workspace).\n For Python, set `adapter: \"debugpy\"` and `program` to the target `.py` file; put interpreter/script flags in `args`.\n- `action: \"attach\"` connects to an existing process: `pid` for local attach, `port` for remote attach (where the adapter supports it), `adapter` to force a specific debugger.\n- **Breakpoints**: `set_breakpoint`/`remove_breakpoint` with source (`file`+`line`) or function (`function`); optional `condition` for conditional breakpoints.\n- **Flow control**: `continue` (resumes; briefly waits to observe whether the program stops or keeps running), `step_over`/`step_in`/`step_out` (single-step), `pause` (interrupt a running program so you can inspect state).\n- **Inspect**: `threads` (list), `stack_trace` (frames for current stopped thread), `scopes` (needs `frame_id` or a current stopped frame), `variables` (needs `variable_ref` or `scope_id`), `evaluate` (needs `expression`; `context: \"repl\"` for raw debugger commands when the adapter supports them), `output` (captured stdout/stderr/console), `sessions` (tracked debug sessions), `terminate`.\n- Timeouts apply per-request, not to the full session lifetime.\n</instruction>\n\n<caution>\n- Only one active debug session is supported at a time.\n- Some adapters require a launched session to receive `configurationDone` before the target actually runs; if the tool says configuration is pending, set breakpoints and then call `continue`.\n- Adapter availability depends on local binaries. Common built-ins: `gdb`, `lldb-dap`, `python -I -m debugpy.adapter`, `dlv dap`.\n- `program` must be an executable file or debug target, not a directory or interpreter name that resolves to a workspace directory.\n</caution>\n\n<examples>\n# Launch and inspect hang\n1. `debug(action: \"launch\", program: \"./my_app\")`\n2. `debug(action: \"set_breakpoint\", file: \"src/main.c\", line: 42)`\n3. `debug(action: \"continue\")`\n4. If the program appears hung: `debug(action: \"pause\")`\n5. Inspect state with `threads`, `stack_trace`, `scopes`, and `variables`\n# Launch a Python script with debugpy\n`debug(action: \"launch\", adapter: \"debugpy\", program: \"scripts/job.py\", args: [\"--flag\"])`\n# Raw debugger command through repl\n`debug(action: \"evaluate\", expression: \"info registers\", context: \"repl\")`\n</examples>",
411
411
  "parameters": {
412
412
  "type": "object",
413
413
  "properties": {
@@ -2096,7 +2096,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2096
2096
  "task": {
2097
2097
  "name": "task",
2098
2098
  "label": "Task",
2099
- "description": "Launches subagents to parallelize workflows.\n\n- Results are delivered automatically when complete.\n- The tool result lists the assigned task ids (e.g. `0-AuthLoader`) — those are the live agent ids.\n- Coordinate with running tasks via `irc` when available, using those ids. `subagent` cancel terminates a task and **cannot carry a message**.\n- Use `subagent` action `inspect` or `list` to snapshot manager state.\n- To wait or cancel, use the `subagent` tool; its await/cancel doctrine is authoritative.\n\nSubagents have no conversation history. Every fact, file path, and direction they need MUST be explicit in `context` or `assignment`.\nUse `irc` for live coordination when it is available.\n\n<parameters>\n- `agent`: agent type for all tasks\n- `tasks`: tasks to execute in parallel\n - `.id`: filesystem-safe, ≤48 chars, matching `[A-Za-z0-9][A-Za-z0-9_-]*`; prefer CamelCase\n - `.description`: UI label only — subagent never sees it\n - `.assignment`: complete self-contained instructions; one-liners and missing acceptance criteria are PROHIBITED\n- `context`: shared background prepended to every assignment; session-specific only\n- `.inheritContext` (optional): fork-context mode for seeding the subagent with sanitized parent conversation. Omit it or set `\"none\"` for no copied context. `\"receipt\"` copies a minimal receipt-sized snapshot, `\"last-turn\"` copies only the latest exchange, `\"bounded\"` copies the bounded default snapshot, and `\"full\"` copies a larger snapshot up to the configured/model token cap. Non-`none` modes work only when global `task.forkContext.enabled` is true and the target agent declares `forkContext: allowed`; otherwise the call is rejected. Bundled agents that support it: `executor`, `architect`. Use inherited context only when the subagent's value depends on parent context; cloned tokens are billed to the child as fresh input and surfaced in task receipts as fork-context cloned-token accounting.\n\n- `schema`: JTD schema for expected structured output (do not put format rules in assignments)\n- `spawnPlan` (optional): required before any batch with more than 4 tasks; include whyParallel, whyNotLocal, independence, expectedReceiptShape, and maxInlineTokens.\n\n- `repositoryBinding`: optional authoritative repository/worktree identity. It must match the active session and cannot authorize a foreign repository from this session. For another approved repository, start a fresh session rooted there (for example, `gjc --cwd <approved-worktree>`); do not use `/move` because materialized tasks retain their original binding.\n</parameters>\n\n<rules>\n- HARD runtime gate: calls with more than 4 tasks are rejected before any child launches unless `spawnPlan` is complete.\n- NEVER assign tasks to run project-wide build/test/lint. Caller verifies after the batch.\n- **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates and formatters. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.\n- Each task: ≤3–5 explicit files. No globs, no \"update all\", no package-wide scope. Use `irc` to coordinate overlapping work when it is available; otherwise keep file sets independent.\n- Pass large payloads via `local://<path>` URIs, not inline.\n- Put shared constraints in `context` once; do not duplicate across assignments.\n- Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown.\n</rules>\n\n<parallelization>\nTest: can task B run correctly without seeing A's output? If no, sequence A → B, unless `irc` is available and A can answer a small clarification.\nStill sequence when one task produces a large, evolving contract (generated types, schema migration, core module API); live coordination is not a substitute for a finished contract.\nParallel when tasks touch disjoint files, are independent refactors/tests, or need only occasional peer clarification.\n</parallelization>\n\n<context-fmt>\n# Goal ← one sentence: what the batch accomplishes\n# Constraints ← MUST/NEVER rules and session decisions\n# Contract ← exact types/signatures if tasks share an interface\n</context-fmt>\n\n<assignment-fmt>\n# Target ← exact files and symbols; explicit non-goals\n# Change ← step-by-step add/remove/rename; APIs and patterns\n# Acceptance ← observable result; no project-wide commands\n</assignment-fmt>\n\n<agents>\nBundled role names: executor, architect, planner, critic. A configured agent may override a bundled role name and takes precedence.\nOther configured agents (project, user, plugin) may also be available; calling with an unknown `agent` lists the agents callable in this session.\n</agents>",
2099
+ "description": "Launches subagents to parallelize workflows.\n\n- Results are delivered automatically when complete.\n- The tool result lists the assigned task ids (e.g. `0-AuthLoader`) — those are the live agent ids.\n- Coordinate with running tasks via `irc` when available, using those ids. `subagent` cancel terminates a task and **cannot carry a message**.\n- Use `subagent` action `inspect` or `list` to snapshot manager state.\n- To wait or cancel, use the `subagent` tool; its await/cancel doctrine is authoritative.\n\nSubagents have no conversation history. Every fact, file path, and direction they need MUST be explicit in `context` or `assignment`.\nUse `irc` for live coordination when it is available.\n\n<parameters>\n- `agent`: agent type for all tasks\n- `tasks`: tasks to execute in parallel\n - `.id`: filesystem-safe, ≤48 chars, matching `[A-Za-z0-9][A-Za-z0-9_-]*`; prefer CamelCase\n - `.description`: UI label only — subagent never sees it\n - `.assignment`: complete self-contained instructions; one-liners and missing acceptance criteria are PROHIBITED\n- `context`: shared background prepended to every assignment; session-specific only\n- `.inheritContext` (optional): fork-context mode for seeding the subagent with sanitized parent conversation. Omit it or set `\"none\"` for no copied context. `\"receipt\"` copies a minimal receipt-sized snapshot, `\"last-turn\"` copies only the latest exchange, `\"bounded\"` copies the bounded default snapshot, and `\"full\"` copies a larger snapshot up to the configured/model token cap. Non-`none` modes work only when global `task.forkContext.enabled` is true and the target agent declares `forkContext: allowed`; otherwise the call is rejected. Bundled agents that support it: `executor`, `architect`. Use inherited context only when the subagent's value depends on parent context; cloned tokens are billed to the child as fresh input and surfaced in task receipts as fork-context cloned-token accounting.\n\n- `schema`: JTD schema for expected structured output (do not put format rules in assignments)\n- `spawnPlan` (optional): required before any batch with more than 4 tasks; include whyParallel, whyNotLocal, independence, expectedReceiptShape, and maxInlineTokens.\n\n- `repositoryBinding`: optional authoritative repository/worktree identity. It must match the session's committed cwd and cannot itself authorize relocation. After an authorized session move, new work uses the committed target; already-admitted work retains its source execution inputs. Verified storage relocation of that same parent does not change child execution cwd; a logical session switch does not substitute successor storage. For another approved repository, start a fresh session rooted there (for example, `gjc --cwd <approved-worktree>`) or use the existing user-authorized relocation surface; `move_session` still only narrows to a descendant directory.\n</parameters>\n\n<rules>\n- HARD runtime gate: calls with more than 4 tasks are rejected before any child launches unless `spawnPlan` is complete.\n- NEVER assign tasks to run project-wide build/test/lint. Caller verifies after the batch.\n- **Subagents do not verify, lint, or format.** Every assignment MUST instruct the subagent to skip all gates and formatters. You run them once at the end across the union of changed files — avoids redundant runs and racing formatter passes.\n- Each task: ≤3–5 explicit files. No globs, no \"update all\", no package-wide scope. Use `irc` to coordinate overlapping work when it is available; otherwise keep file sets independent.\n- Pass large payloads via `local://<path>` URIs, not inline.\n- Put shared constraints in `context` once; do not duplicate across assignments.\n- Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown.\n</rules>\n\n<parallelization>\nTest: can task B run correctly without seeing A's output? If no, sequence A → B, unless `irc` is available and A can answer a small clarification.\nStill sequence when one task produces a large, evolving contract (generated types, schema migration, core module API); live coordination is not a substitute for a finished contract.\nParallel when tasks touch disjoint files, are independent refactors/tests, or need only occasional peer clarification.\n</parallelization>\n\n<context-fmt>\n# Goal ← one sentence: what the batch accomplishes\n# Constraints ← MUST/NEVER rules and session decisions\n# Contract ← exact types/signatures if tasks share an interface\n</context-fmt>\n\n<assignment-fmt>\n# Target ← exact files and symbols; explicit non-goals\n# Change ← step-by-step add/remove/rename; APIs and patterns\n# Acceptance ← observable result; no project-wide commands\n</assignment-fmt>\n\n<agents>\nBundled role names: executor, architect, planner, critic. A configured agent may override a bundled role name and takes precedence.\nOther configured agents (project, user, plugin) may also be available; calling with an unknown `agent` lists the agents callable in this session.\n</agents>",
2100
2100
  "parameters": {
2101
2101
  "type": "object",
2102
2102
  "properties": {
@@ -2509,7 +2509,7 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2509
2509
  "irc": {
2510
2510
  "name": "irc",
2511
2511
  "label": "IRC",
2512
- "description": "Sends short text messages to other live agents in this process and receives their prose replies.\n\n<instruction>\n- The main agent is addressable as `0-Main`. Subagents reuse their task id (e.g. `0-AuthLoader`).\n- `op: \"list\"` returns the current set of visible peers. Use it before sending if you are not sure who is live.\n- `op: \"send\"` delivers `message` to `to`. `to` may be a specific id or `\"all\"` to broadcast.\n- `awaitReply` (optional): wait for a prose reply. Defaults to `true` for DMs and `false` for `to: \"all\"` broadcasts.\n- The recipient generates the reply via an ephemeral side-channel turn that uses their current model, system prompt, and history — it does **not** wait for the recipient's main loop to be free, so it is safe to IRC an agent that is currently inside a long-running tool call.\n- The exchange (incoming question + auto-reply) is queued for injection into the recipient's persisted history; the recipient sees it on its next turn and can follow up if needed.\n</instruction>\n\n<conditions>\nYou SHOULD reach for `irc` proactively when continuing alone is wasteful or wrong. When in doubt, prefer messaging.\n- **Unexpected state.** You hit something the original task did not describe — a missing file, a config that contradicts the assignment, an API behaving differently than you were told, a tool failing in a way that suggests the spec is wrong. DM `0-Main` (or the spawning agent) for guidance instead of guessing.\n- **Blocked by another agent.** A peer holds the file/branch/resource you need, has already started the change you are about to make, or owns a decision you depend on. DM that peer (or broadcast to discover who) before duplicating or stepping on work.\n- **Decision points outside your scope.** A genuine fork in the road that the assignment did not pre-decide (e.g. which of two viable APIs to use, whether to refactor adjacent code). Ask the requester rather than picking unilaterally.\n- **Coordination opportunities.** Before editing a shared file or relying on another agent's in-flight API, message the relevant peer; proactively share state that affects their work.\n\nDo **not** use `irc` for: routine progress updates, things you can verify with a tool call, or questions whose answer is already in your assignment / repo / docs.\n</conditions>\n\n<etiquette>\nThese rules apply to both sending and replying.\n- **Plain prose only.** Do not send structured JSON status payloads (e.g. `{\"type\":\"task_completed\",…}`). Write a normal sentence: \"Done with the auth refactor — left a TODO in `src/server/auth.ts` for the rate limiter.\"\n- **Do not quote the message you are replying to.** The sender already saw it; the TUI already renders it. Lead with the answer.\n- **Use IRC, not terminal tools, to learn about peers.** Do not `search` artifacts, read other sessions' JSONL files, or shell-poke around to figure out what another agent is doing. DM them — they have the live answer and you do not.\n- **One round-trip is enough.** Replies arrive synchronously when the recipient is reachable. Do not follow up with \"did you get my message?\" — they did. If `delivered` is empty or the result was `failed`, the peer is unavailable; move on or report the blocker, do not retry in a loop.\n- **Stay terse.** A DM is a chat message, not a memo. One question per send when you can. Share file paths and artifacts via `local://` / `artifact://` URLs instead of pasting blobs.\n- **Address peers by id.** Use the exact id from `op: \"list\"` (e.g. `0-AuthLoader`, `0-Main`). Do not invent friendly names.\n- **Do not IRC for things a tool would answer.** If a `read`, `search`, or build command would resolve the question, do that first.\n- **When you receive an IRC message, answer it before continuing.** The recipient injects the question + your auto-reply into your history; address it directly, do not repeat it back to the user.\n</etiquette>\n\n<output>\n- `send`: returns each recipient that received the message and any prose replies that arrived.\n- `list`: returns peers and channels visible to the caller.\n</output>\n\n<examples>\n# List peers\n`{\"op\": \"list\"}`\n# Direct message to the main agent (waits for prose reply)\n`{\"op\": \"send\", \"to\": \"0-Main\", \"message\": \"Should I prefer JWT or session cookies for the auth flow?\"}`\n# Unexpected state — ask the originator\n`{\"op\": \"send\", \"to\": \"0-Main\", \"message\": \"Assignment says edit src/auth/jwt.ts but the file does not exist. Is the new path src/server/auth/jwt.ts?\"}`\n# Blocked by a peer — ask them directly\n`{\"op\": \"send\", \"to\": \"0-AuthLoader\", \"message\": \"Are you still touching src/server/auth.ts? I need to add a 401 path; OK to proceed or should I wait?\"}`\n# Broadcast to discover who owns something (no replies, just informs them)\n`{\"op\": \"send\", \"to\": \"all\", \"message\": \"About to refactor src/server/middleware/*. Anyone already in there?\", \"awaitReply\": false}`\n</examples>",
2512
+ "description": "Sends short text messages to other live agents in this process and receives their prose replies.\n\n<instruction>\n- The main agent is addressable as `0-Main`. Subagents reuse their task id (e.g. `0-AuthLoader`).\n- `op: \"list\"` returns the current set of visible peers. Use it before sending if you are not sure who is live.\n- `op: \"send\"` delivers `message` to `to`. `to` may be a specific id or `\"all\"` to broadcast.\n- `awaitReply` (optional): wait for a prose reply. Defaults to `true` for DMs and `false` for `to: \"all\"` broadcasts.\n- The recipient generates the reply via an ephemeral side-channel turn that uses their current model, system prompt, and history — it does **not** wait for the recipient's main loop to be free, so it is safe to IRC an agent that is currently inside a long-running tool call.\n- The exchange (incoming question + auto-reply) is injected into the recipient's persisted history as soon as it can be delivered: an idle recipient gets it immediately, and a recipient that is already streaming receives it at its next step boundary instead of at the end of its turn. The recipient can then follow up if needed.\n</instruction>\n\n<conditions>\nYou SHOULD reach for `irc` proactively when continuing alone is wasteful or wrong. When in doubt, prefer messaging.\n- **Unexpected state.** You hit something the original task did not describe — a missing file, a config that contradicts the assignment, an API behaving differently than you were told, a tool failing in a way that suggests the spec is wrong. DM `0-Main` (or the spawning agent) for guidance instead of guessing.\n- **Blocked by another agent.** A peer holds the file/branch/resource you need, has already started the change you are about to make, or owns a decision you depend on. DM that peer (or broadcast to discover who) before duplicating or stepping on work.\n- **Decision points outside your scope.** A genuine fork in the road that the assignment did not pre-decide (e.g. which of two viable APIs to use, whether to refactor adjacent code). Ask the requester rather than picking unilaterally.\n- **Coordination opportunities.** Before editing a shared file or relying on another agent's in-flight API, message the relevant peer; proactively share state that affects their work.\n\nDo **not** use `irc` for: routine progress updates, things you can verify with a tool call, or questions whose answer is already in your assignment / repo / docs.\n</conditions>\n\n<etiquette>\nThese rules apply to both sending and replying.\n- **Plain prose only.** Do not send structured JSON status payloads (e.g. `{\"type\":\"task_completed\",…}`). Write a normal sentence: \"Done with the auth refactor — left a TODO in `src/server/auth.ts` for the rate limiter.\"\n- **Do not quote the message you are replying to.** The sender already saw it; the TUI already renders it. Lead with the answer.\n- **Use IRC, not terminal tools, to learn about peers.** Do not `search` artifacts, read other sessions' JSONL files, or shell-poke around to figure out what another agent is doing. DM them — they have the live answer and you do not.\n- **One round-trip is enough.** Replies arrive synchronously when the recipient is reachable. Do not follow up with \"did you get my message?\" — they did. If `delivered` is empty or the result was `failed`, the peer is unavailable; move on or report the blocker, do not retry in a loop.\n- **Stay terse.** A DM is a chat message, not a memo. One question per send when you can. Share file paths and artifacts via `local://` / `artifact://` URLs instead of pasting blobs.\n- **Address peers by id.** Use the exact id from `op: \"list\"` (e.g. `0-AuthLoader`, `0-Main`). Do not invent friendly names.\n- **Do not IRC for things a tool would answer.** If a `read`, `search`, or build command would resolve the question, do that first.\n- **When you receive an IRC message, answer it before continuing.** The recipient injects the question + your auto-reply into your history; address it directly, do not repeat it back to the user.\n</etiquette>\n\n<output>\n- `send`: returns each recipient that received the message and any prose replies that arrived.\n- `list`: returns peers and channels visible to the caller.\n</output>\n\n<examples>\n# List peers\n`{\"op\": \"list\"}`\n# Direct message to the main agent (waits for prose reply)\n`{\"op\": \"send\", \"to\": \"0-Main\", \"message\": \"Should I prefer JWT or session cookies for the auth flow?\"}`\n# Unexpected state — ask the originator\n`{\"op\": \"send\", \"to\": \"0-Main\", \"message\": \"Assignment says edit src/auth/jwt.ts but the file does not exist. Is the new path src/server/auth/jwt.ts?\"}`\n# Blocked by a peer — ask them directly\n`{\"op\": \"send\", \"to\": \"0-AuthLoader\", \"message\": \"Are you still touching src/server/auth.ts? I need to add a 401 path; OK to proceed or should I wait?\"}`\n# Broadcast to discover who owns something (no replies, just informs them)\n`{\"op\": \"send\", \"to\": \"all\", \"message\": \"About to refactor src/server/middleware/*. Anyone already in there?\", \"awaitReply\": false}`\n</examples>",
2513
2513
  "parameters": {
2514
2514
  "type": "object",
2515
2515
  "properties": {
@@ -2955,6 +2955,34 @@ export const TOOL_CATALOG: Readonly<Record<string, ToolCatalogEntry>> = {
2955
2955
  "concurrency": "exclusive",
2956
2956
  "intent": "omit"
2957
2957
  },
2958
+ "report_issue": {
2959
+ "name": "report_issue",
2960
+ "label": "Report Issue",
2961
+ "description": "Records a tool that behaved inconsistently with its own description, for local quality tracking.\n\n<instruction>\n- Call this when a tool's output contradicts what its description or parameters promise: ignored or misapplied parameters, wrong or truncated results, misleading errors, silent no-ops.\n- `tool` is the misbehaving tool's name; `report` states what you called, what you expected, and what actually happened, in one or two sentences.\n- Do not report your own mistakes, user-caused failures, or expected errors (missing files, failing tests, network outages).\n- Do not paste secrets, tokens, credentials, or raw tool output into report; summarize it instead.\n- False positives are acceptable. Report once per distinct problem, then continue the task; this never fixes or retries anything.\n</instruction>\n\n<output>\nConfirms whether the report was recorded. Reports stay on this machine.\n</output>",
2962
+ "parameters": {
2963
+ "type": "object",
2964
+ "properties": {
2965
+ "tool": {
2966
+ "type": "string",
2967
+ "description": "name of the tool that misbehaved"
2968
+ },
2969
+ "report": {
2970
+ "type": "string",
2971
+ "description": "what was called, what was expected, what happened"
2972
+ }
2973
+ },
2974
+ "required": [
2975
+ "tool",
2976
+ "report"
2977
+ ],
2978
+ "additionalProperties": false
2979
+ },
2980
+ "strict": true,
2981
+ "deferrable": false,
2982
+ "loadMode": "essential",
2983
+ "summary": "Record a tool that behaved inconsistently with its description",
2984
+ "intent": "omit"
2985
+ },
2958
2986
  "yield": {
2959
2987
  "name": "yield",
2960
2988
  "label": "Submit Result",
@@ -1,4 +1,5 @@
1
1
  import type { AgentTool } from "@gajae-code/agent-core";
2
+ import { resolveRalplanCurrentFinal, selectsRalplanApprovalGate } from "../gjc-runtime/ralplan-current-final";
2
3
  import {
3
4
  consumeUltragoalAskNudge,
4
5
  isUltragoalAskBlocked,
@@ -33,6 +34,7 @@ export async function assertUltragoalAskAllowed(
33
34
  cwd: string,
34
35
  context: UltragoalAskGuardContext = {},
35
36
  agentDir?: string,
37
+ pendingQuestions?: unknown,
36
38
  ): Promise<void> {
37
39
  // Durable Ultragoal state is session-scoped, so only the caller's own run may
38
40
  // block `ask`. An anonymous caller has no run attributable to it, so the guard
@@ -43,6 +45,16 @@ export async function assertUltragoalAskAllowed(
43
45
  if (!sessionId) return;
44
46
  const diagnostic = await isUltragoalAskBlocked(cwd, { sessionId });
45
47
  if (!diagnostic.active) return;
48
+ // The one ask that may proceed while a goal is blocked is the execution
49
+ // approval for an authenticated current ralplan final (issue #6578). The
50
+ // request must select that gate and the final must validate on disk; a
51
+ // caller-declared tag alone never lifts the guard. AskTool still captures and
52
+ // authenticates the publication it records consent against.
53
+ if (selectsRalplanApprovalGate(pendingQuestions)) {
54
+ const resolution = await resolveRalplanCurrentFinal({ cwd, sessionId, agentDir });
55
+ if (resolution.ok) return;
56
+ throw new ToolError(`Ultragoal ask guard refused the ralplan execution approval: ${resolution.refusal.reason}`);
57
+ }
46
58
  const nudge = await consumeUltragoalAskNudge(cwd, sessionId, agentDir);
47
59
  if (nudge.nudged) throw new ToolError(nudge.message);
48
60
  throw new ToolError(formatUltragoalAskBlockMessage(diagnostic));
@@ -66,7 +78,7 @@ export function guardToolForUltragoalAsk<T extends AgentTool>(
66
78
  // budget against the SESSION profile, never the process-global one.
67
79
  // The caller session id is the only binding, so a foreign session's
68
80
  // durable run can never gate this ask.
69
- await assertUltragoalAskAllowed(getCwd(), getContext(), getSessionAgentDir());
81
+ await assertUltragoalAskAllowed(getCwd(), getContext(), getSessionAgentDir(), args[1]);
70
82
  return Reflect.apply(target.execute, target, args);
71
83
  };
72
84
  },
@@ -3,7 +3,7 @@ import * as fs from "node:fs/promises";
3
3
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@gajae-code/agent-core";
4
4
  import type { Component } from "@gajae-code/tui";
5
5
  import { Text } from "@gajae-code/tui";
6
- import { isEnoent, isRecord, prompt, untilAborted } from "@gajae-code/utils";
6
+ import { isEnoent, isRecord, pathIsWithin, prompt, untilAborted } from "@gajae-code/utils";
7
7
  import * as z from "zod/v4";
8
8
  import { stripHashlinePrefixes } from "../edit";
9
9
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
@@ -177,6 +177,31 @@ function parseSqliteWriteTarget(subPath: string, queryString: string): { table:
177
177
  return { table, key };
178
178
  }
179
179
 
180
+ /**
181
+ * `new Database` follows symlinks. A lexical path inside the workspace can
182
+ * still name a file outside it, so open only the real path that stays inside
183
+ * the same session workspace an ordinary write resolves against.
184
+ */
185
+ async function realSqliteDatabaseInWorkspace(
186
+ workspaceRoot: string,
187
+ absolutePath: string,
188
+ signal?: AbortSignal,
189
+ ): Promise<string> {
190
+ signal?.throwIfAborted();
191
+ let realPath: string;
192
+ try {
193
+ realPath = await fs.realpath(absolutePath);
194
+ } catch (error) {
195
+ if (isEnoent(error)) throw error;
196
+ throw new ToolError(error instanceof Error ? error.message : String(error));
197
+ }
198
+ signal?.throwIfAborted();
199
+ if (!pathIsWithin(workspaceRoot, realPath)) {
200
+ throw new ToolError(`SQLite write target resolves outside the workspace: ${absolutePath}`);
201
+ }
202
+ return realPath;
203
+ }
204
+
180
205
  /**
181
206
  * Write tool implementation.
182
207
  *
@@ -193,6 +218,13 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
193
218
  readonly loadMode = "discoverable";
194
219
  readonly summary = "Write content to a file (creates or overwrites)";
195
220
 
221
+ /** Stream matchers should see the real file content, not its JSON-escaped argument encoding. */
222
+ matcherDigest(args: unknown): string | undefined {
223
+ if (!args || typeof args !== "object" || Array.isArray(args)) return undefined;
224
+ const content = (args as Partial<WriteParams>).content;
225
+ return typeof content === "string" ? content : undefined;
226
+ }
227
+
196
228
  readonly #writethrough: WritethroughCallback;
197
229
 
198
230
  constructor(private readonly session: ToolSession) {
@@ -372,6 +404,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
372
404
  displayPath: string,
373
405
  content: string,
374
406
  resolvedSqlitePath: ResolvedSqliteWritePath,
407
+ signal?: AbortSignal,
375
408
  ): Promise<AgentToolResult<WriteToolDetails>> {
376
409
  let db: Database | null = null;
377
410
  try {
@@ -379,7 +412,13 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
379
412
  throw new ToolError(`SQLite database '${displayPath}' not found`);
380
413
  }
381
414
 
382
- db = new Database(resolvedSqlitePath.absolutePath, { create: false, strict: true });
415
+ const realDatabasePath = await realSqliteDatabaseInWorkspace(
416
+ this.session.cwd,
417
+ resolvedSqlitePath.absolutePath,
418
+ signal,
419
+ );
420
+ signal?.throwIfAborted();
421
+ db = new Database(realDatabasePath, { create: false, strict: true });
383
422
  db.run("PRAGMA busy_timeout = 3000");
384
423
 
385
424
  const trimmedContent = content.trim();
@@ -718,7 +757,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
718
757
  if (resolvedSqlitePath) {
719
758
  enforcePlanModeWrite(this.session, resolvedSqlitePath.sqlitePath, { op: "update" });
720
759
 
721
- const sqliteResult = await this.#writeSqliteRow(path, cleanContent, resolvedSqlitePath);
760
+ const sqliteResult = await this.#writeSqliteRow(path, cleanContent, resolvedSqlitePath, signal);
722
761
  if (stripped) {
723
762
  const firstText = sqliteResult.content.find(
724
763
  (block): block is { type: "text"; text: string } =>
@@ -3,11 +3,25 @@
3
3
  */
4
4
 
5
5
  import { replaceTabs } from "@gajae-code/tui";
6
+ import { sanitizeText } from "@gajae-code/utils";
6
7
  import type { Theme } from "../modes/theme/theme";
7
8
  import { formatMoreItems } from "../tools/render-utils";
8
9
  import type { TreeContext } from "./types";
9
10
  import { getTreeBranch, getTreeContinuePrefix } from "./utils";
10
11
 
12
+ /**
13
+ * Tool renderers pass raw match lines or line groups into `renderItem`, which
14
+ * then applies theme colors. Strip controls first so OSC bytes are not wrapped
15
+ * into the styled string. Objects (file entries, todos) are left unchanged.
16
+ */
17
+ function sanitizeTreeItem<T>(item: T): T {
18
+ if (typeof item === "string") return sanitizeText(item) as T;
19
+ if (Array.isArray(item)) {
20
+ return item.map(part => (typeof part === "string" ? sanitizeText(part) : part)) as T;
21
+ }
22
+ return item;
23
+ }
24
+
11
25
  export interface TreeListOptions<T> {
12
26
  items: T[];
13
27
  expanded?: boolean;
@@ -32,7 +46,7 @@ export function renderTreeList<T>(options: TreeListOptions<T>, theme: Theme): st
32
46
  // renderItem implementations MUST NOT vary line count based on isLast.
33
47
  const preRendered: string[][] = [];
34
48
  for (let i = 0; i < maxItems; i++) {
35
- const rendered = renderItem(items[i], {
49
+ const rendered = renderItem(sanitizeTreeItem(items[i]!), {
36
50
  index: i,
37
51
  isLast: false,
38
52
  depth: 0,
@@ -5,7 +5,8 @@
5
5
  import type { ThinkingLevel } from "@gajae-code/agent-core";
6
6
  import type { Api, Model } from "@gajae-code/ai/core";
7
7
  import { completeSimple } from "@gajae-code/ai/core";
8
- import { logger, prompt } from "@gajae-code/utils";
8
+ import { logger, prompt, redactCrashSecrets } from "@gajae-code/utils";
9
+ import { redactChangelogEntry } from "../commit/changelog";
9
10
  import type { ModelRegistry } from "../config/model-registry";
10
11
  import { resolveModelRoleValue } from "../config/model-resolver";
11
12
  import type { Settings } from "../config/settings";
@@ -35,11 +36,17 @@ function filterDiffNoise(diff: string): string {
35
36
  return filtered.join("\n");
36
37
  }
37
38
 
39
+ /** Keep changelog patterns, then scrub known credential shapes on the whole string. */
40
+ function redactCommitMessageText(text: string): string {
41
+ return redactCrashSecrets(redactChangelogEntry(text));
42
+ }
43
+
38
44
  function getModelCandidates(
39
45
  registry: ModelRegistry,
40
46
  settings: Settings,
47
+ credentialSessionId?: string,
41
48
  ): Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }> {
42
- const availableModels = registry.getAvailable();
49
+ const availableModels = registry.getAvailable(credentialSessionId);
43
50
  if (availableModels.length === 0) return [];
44
51
 
45
52
  const candidates: Array<{ model: Model<Api>; thinkingLevel?: ThinkingLevel }> = [];
@@ -74,7 +81,7 @@ export async function generateCommitMessage(
74
81
  settings: Settings,
75
82
  sessionId?: string,
76
83
  ): Promise<string | null> {
77
- const candidates = getModelCandidates(registry, settings);
84
+ const candidates = getModelCandidates(registry, settings, sessionId);
78
85
  if (candidates.length === 0) {
79
86
  logger.debug("commit-msg-generator: no model found");
80
87
  return null;
@@ -87,7 +94,7 @@ export async function generateCommitMessage(
87
94
  logger.debug("commit-msg-generator: diff is empty after noise filtering");
88
95
  return null;
89
96
  }
90
- const userMessage = `<diff>\n${truncatedDiff}\n</diff>`;
97
+ const userMessage = `<diff>\n${redactCommitMessageText(truncatedDiff)}\n</diff>`;
91
98
 
92
99
  for (const candidate of candidates) {
93
100
  const apiKey = await registry.getApiKey(candidate.model, sessionId);
@@ -120,6 +127,7 @@ export async function generateCommitMessage(
120
127
 
121
128
  // Clean up: remove wrapping quotes, backticks, trailing period
122
129
  msg = msg.replace(/^[`"']|[`"']$/g, "").replace(/\.$/, "");
130
+ msg = redactCommitMessageText(msg);
123
131
 
124
132
  logger.debug("commit-msg-generator: generated", { model: candidate.model.id, msg });
125
133
  return msg;
@@ -6,7 +6,7 @@ import { spawn } from "node:child_process";
6
6
  import * as fs from "node:fs/promises";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
- import { $pickCredentialEnv, Snowflake } from "@gajae-code/utils";
9
+ import { $pickCredentialEnv } from "@gajae-code/utils";
10
10
 
11
11
  /** Returns the user's preferred editor command, or undefined if not configured. */
12
12
  export function getEditorCommand(): string | undefined {
@@ -119,10 +119,22 @@ export async function openInEditor(
119
119
  options?: OpenInEditorOptions,
120
120
  ): Promise<string | null> {
121
121
  const ext = options?.extension ?? ".md";
122
- const tmpFile = path.join(os.tmpdir(), `gjc-editor-${Snowflake.next()}${ext}`);
122
+ // Random directory plus an exclusive create: a preexisting symlink under the
123
+ // temp directory is not opened, and the draft is not left world-readable.
124
+ let tempDir: string | undefined;
125
+ let tmpFile = "";
123
126
 
124
127
  try {
125
- await Bun.write(tmpFile, content);
128
+ tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "gjc-editor-"));
129
+ if (process.platform !== "win32") await fs.chmod(tempDir, 0o700);
130
+ tmpFile = path.join(tempDir, `draft${ext}`);
131
+ const handle = await fs.open(tmpFile, "wx", 0o600);
132
+ try {
133
+ await handle.writeFile(content);
134
+ } finally {
135
+ await handle.close();
136
+ }
137
+ if (process.platform !== "win32") await fs.chmod(tmpFile, 0o600);
126
138
 
127
139
  const [editor, ...editorArgs] = editorCmd.split(" ");
128
140
  const stdio = options?.stdio ?? ["inherit", "inherit", "inherit"];
@@ -147,10 +159,12 @@ export async function openInEditor(
147
159
  }
148
160
  return null;
149
161
  } finally {
150
- try {
151
- await fs.rm(tmpFile, { force: true });
152
- } catch {
153
- // Ignore cleanup errors
162
+ if (tempDir) {
163
+ try {
164
+ await fs.rm(tempDir, { recursive: true, force: true });
165
+ } catch {
166
+ // Ignore cleanup errors
167
+ }
154
168
  }
155
169
  }
156
170
  }