@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
@@ -3,7 +3,6 @@ import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
 
5
5
  import { untilAborted } from "@gajae-code/utils/abortable";
6
- import { Snowflake } from "@gajae-code/utils/snowflake";
7
6
  import type { HTMLElement } from "linkedom";
8
7
  import type {
9
8
  Browser,
@@ -414,6 +413,31 @@ interface ActiveRun {
414
413
  pendingTools: Map<string, { resolve(value: unknown): void; reject(error: Error): void }>;
415
414
  }
416
415
 
416
+ async function writePrivateScreenshot(bytes: Uint8Array): Promise<string> {
417
+ // Random directory plus an exclusive create: a preexisting symlink under the
418
+ // shared temp directory is not opened, and the screenshot is not left world-readable.
419
+ // Create the temp root first: base created it with a recursive mkdir, and
420
+ // mkdtemp fails when that root does not exist yet. Do not change the root's mode.
421
+ const root = os.tmpdir();
422
+ await fs.promises.mkdir(root, { recursive: true });
423
+ const tempDir = await fs.promises.mkdtemp(path.join(root, "gjc-sshots-"));
424
+ try {
425
+ if (process.platform !== "win32") await fs.promises.chmod(tempDir, 0o700);
426
+ const dest = path.join(tempDir, "screenshot.png");
427
+ const handle = await fs.promises.open(dest, "wx", 0o600);
428
+ try {
429
+ await handle.writeFile(bytes);
430
+ } finally {
431
+ await handle.close();
432
+ }
433
+ if (process.platform !== "win32") await fs.promises.chmod(dest, 0o600);
434
+ return dest;
435
+ } catch (error) {
436
+ await fs.promises.rm(tempDir, { recursive: true, force: true });
437
+ throw error;
438
+ }
439
+ }
440
+
417
441
  export class WorkerCore {
418
442
  #transport: Transport;
419
443
  #browser?: Browser;
@@ -852,20 +876,22 @@ export class WorkerCore {
852
876
  { maxWidth: 1024, maxHeight: 1024, maxBytes: 150 * 1024, jpegQuality: 70 },
853
877
  );
854
878
  const explicitPath = opts.save ? resolveTabWorkerPath(opts.save, session.cwd) : undefined;
855
-
856
- const dest =
857
- explicitPath ??
858
- (session.browserScreenshotDir
859
- ? path.join(
860
- session.browserScreenshotDir,
861
- `screenshot-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}.png`,
862
- )
863
- : path.join(os.tmpdir(), `gjc-sshots-${Snowflake.next()}.png`));
864
- await fs.promises.mkdir(path.dirname(dest), { recursive: true });
865
- const saveFullRes = !!(explicitPath || session.browserScreenshotDir);
879
+ const configuredDir = session.browserScreenshotDir;
880
+ const saveFullRes = !!(explicitPath || configuredDir);
866
881
  const savedBuffer = saveFullRes ? buffer : resized.buffer;
867
882
  const savedMimeType = saveFullRes ? "image/png" : resized.mimeType;
868
- await Bun.write(dest, savedBuffer);
883
+ // Caller-chosen destinations stay as they were. Only the shared temp
884
+ // fallback is an exclusive private file: a preexisting symlink at the old
885
+ // predictable name is not opened, and the bytes are not left world-readable.
886
+ const dest = explicitPath
887
+ ? explicitPath
888
+ : configuredDir
889
+ ? path.join(configuredDir, `screenshot-${new Date().toISOString().replace(/[:.]/g, "-").slice(0, -1)}.png`)
890
+ : await writePrivateScreenshot(savedBuffer);
891
+ if (explicitPath || configuredDir) {
892
+ await fs.promises.mkdir(path.dirname(dest), { recursive: true });
893
+ await Bun.write(dest, savedBuffer);
894
+ }
869
895
  const info: ScreenshotResult = {
870
896
  dest,
871
897
  mimeType: savedMimeType,
@@ -10,8 +10,9 @@ import { assertLoopbackCdpUrl } from "./browser/cdp-url";
10
10
  import { isChromeProfileExecutableForLaunch, isEdgeExecutable, resolveSystemChromeForProfile } from "./browser/launch";
11
11
  import { chromeUserDataRoots, type DiscoveryEnv, defaultDiscoveryEnv } from "./browser/profile-discovery";
12
12
  import { acquireBrowser, type BrowserHandle, type BrowserKind, type BrowserKindTag } from "./browser/registry";
13
- import type { Observation, ScreenshotResult } from "./browser/tab-protocol";
13
+ import type { Observation, RunResultOk, ScreenshotResult } from "./browser/tab-protocol";
14
14
  import { acquireTab, dropHeadlessTabs, getTab, releaseAllTabs, releaseTab, runInTab } from "./browser/tab-supervisor";
15
+ import { wrapUntrustedContent } from "./fetch";
15
16
  import type { OutputMeta } from "./output-meta";
16
17
  import { resolveToCwd } from "./path-utils";
17
18
  import { ToolAbortError, ToolError, throwIfAborted } from "./tool-errors";
@@ -392,8 +393,8 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
392
393
  const verb = result.created ? "Opened" : "Reused";
393
394
  const lines = [
394
395
  `${verb} tab ${JSON.stringify(name)} on ${describeBrowser(browser)}`,
395
- `URL: ${url}`,
396
- title ? `Title: ${title}` : null,
396
+ `URL: ${frameBrowserPageText(url)}`,
397
+ title ? `Title: ${frameBrowserPageText(title)}` : null,
397
398
  ].filter((l): l is string => typeof l === "string");
398
399
  details.result = lines.join("\n");
399
400
  return toolResult(details).text(lines.join("\n")).done();
@@ -446,9 +447,9 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
446
447
 
447
448
  if (screenshots.length) details.screenshots = screenshots;
448
449
 
449
- const content = [...displays];
450
+ const content = frameBrowserPageDisplays(displays);
450
451
  if (returnValue !== undefined) {
451
- content.push({ type: "text", text: stringifyReturnValue(returnValue) });
452
+ content.push({ type: "text", text: frameBrowserPageText(stringifyReturnValue(returnValue)) });
452
453
  }
453
454
  if (!content.length) {
454
455
  content.push({ type: "text", text: `Ran code on tab ${JSON.stringify(name)}` });
@@ -496,9 +497,9 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
496
497
  });
497
498
 
498
499
  if (screenshots.length) details.screenshots = screenshots;
499
- const content = [...displays];
500
+ const content = frameBrowserPageDisplays(displays);
500
501
  if (returnValue !== undefined) {
501
- content.push({ type: "text", text: stringifyReturnValue(returnValue) });
502
+ content.push({ type: "text", text: frameBrowserPageText(stringifyReturnValue(returnValue)) });
502
503
  }
503
504
  if (!content.length) {
504
505
  content.push({ type: "text", text: `Ran ${steps.length} action(s) on tab ${JSON.stringify(name)}` });
@@ -549,6 +550,15 @@ function sameBrowserKind(a: BrowserKind, b: BrowserKind): boolean {
549
550
  return false;
550
551
  }
551
552
 
553
+ /** Page URL, title, extract/observe text, and script return values are external content. */
554
+ function frameBrowserPageText(text: string): string {
555
+ return wrapUntrustedContent(text);
556
+ }
557
+
558
+ function frameBrowserPageDisplays(displays: RunResultOk["displays"]): RunResultOk["displays"] {
559
+ return displays.map(block => (block.type === "text" ? { ...block, text: frameBrowserPageText(block.text) } : block));
560
+ }
561
+
552
562
  /** Max chars of a browser return value surfaced into the tool result (F22). */
553
563
  const MAX_BROWSER_RETURN_CHARS = 256 * 1024;
554
564
 
package/src/tools/cron.ts CHANGED
@@ -443,7 +443,16 @@ function toCronListJobDetails(snapshot: CronJobSnapshot): CronListJobDetails {
443
443
  }
444
444
 
445
445
  function formatCronFireContent(snapshot: CronJobSnapshot): string {
446
- return `<task-notification>\nScheduled task ${snapshot.id} fired (${snapshot.humanSchedule}).\n\n${snapshot.prompt}\n</task-notification>`;
446
+ // A fired cron prompt is an instruction to execute, not a notification to
447
+ // acknowledge. Wrapping it in a notification-only envelope made models
448
+ // restate the prompt and end the turn without calling a tool.
449
+ return [
450
+ "<system-reminder>",
451
+ `Scheduled task ${snapshot.id} fired (${snapshot.humanSchedule}). The text below is this turn's task instruction: execute it now, using tools as needed. Do not merely restate, summarize, or acknowledge it.`,
452
+ "</system-reminder>",
453
+ "",
454
+ snapshot.prompt,
455
+ ].join("\n");
447
456
  }
448
457
 
449
458
  function deliverCronFire(record: CronScheduleRecord): Promise<void> | undefined {
@@ -466,8 +466,8 @@ function buildOutcomeText(outcome: DapContinueOutcome, timeoutSec: number, verb:
466
466
  return lines.join("\n");
467
467
  }
468
468
 
469
- function getConfiguredAdapters(cwd: string): string {
470
- const adapters = getAvailableAdapters(cwd).map(adapter => adapter.name);
469
+ function getConfiguredAdapters(cwd: string, trustCwds?: readonly string[]): string {
470
+ const adapters = getAvailableAdapters(cwd, trustCwds).map(adapter => adapter.name);
471
471
  return adapters.length > 0 ? adapters.join(", ") : "none";
472
472
  }
473
473
  async function validateLaunchProgram(program: string, cwd: string): Promise<void> {
@@ -637,6 +637,7 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
637
637
  const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
638
638
  const details: DebugToolDetails = { action: params.action, success: true };
639
639
  const result = toolResult(details);
640
+ const trustCwds = [this.session.cwd];
640
641
  switch (params.action) {
641
642
  case "launch": {
642
643
  if (!params.program) {
@@ -645,14 +646,14 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
645
646
  const commandCwd = params.cwd ? resolveToCwd(params.cwd, this.session.cwd) : this.session.cwd;
646
647
  const program = resolveToCwd(params.program, commandCwd);
647
648
  await validateLaunchProgram(program, commandCwd);
648
- const adapter = selectLaunchAdapter(program, commandCwd, params.adapter);
649
+ const adapter = selectLaunchAdapter(program, commandCwd, params.adapter, trustCwds);
649
650
  if (!adapter) {
650
651
  throw new ToolError(
651
- `No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
652
+ `No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd, trustCwds)}`,
652
653
  );
653
654
  }
654
655
  const snapshot = await dapSessionManager.launch(
655
- { adapter, program, args: params.args, cwd: commandCwd },
656
+ { adapter, program, args: params.args, cwd: commandCwd, workspace: this.session.cwd },
656
657
  combinedSignal,
657
658
  timeoutSec * 1000,
658
659
  );
@@ -665,14 +666,21 @@ export class DebugTool implements AgentTool<typeof debugSchema, DebugToolDetails
665
666
  throw new ToolError("attach requires pid or port");
666
667
  }
667
668
  const commandCwd = params.cwd ? resolveToCwd(params.cwd, this.session.cwd) : this.session.cwd;
668
- const adapter = selectAttachAdapter(commandCwd, params.adapter, params.port);
669
+ const adapter = selectAttachAdapter(commandCwd, params.adapter, params.port, trustCwds);
669
670
  if (!adapter) {
670
671
  throw new ToolError(
671
- `No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd)}`,
672
+ `No debugger adapter available. Installed adapters: ${getConfiguredAdapters(commandCwd, trustCwds)}`,
672
673
  );
673
674
  }
674
675
  const snapshot = await dapSessionManager.attach(
675
- { adapter, cwd: commandCwd, pid: params.pid, port: params.port, host: params.host },
676
+ {
677
+ adapter,
678
+ cwd: commandCwd,
679
+ workspace: this.session.cwd,
680
+ pid: params.pid,
681
+ port: params.port,
682
+ host: params.host,
683
+ },
676
684
  combinedSignal,
677
685
  timeoutSec * 1000,
678
686
  );
@@ -35,6 +35,7 @@ function makeSession(overrides: Record<string, unknown> = {}): any {
35
35
  "astGrep.enabled": true,
36
36
  "astEdit.enabled": true,
37
37
  "renderMermaid.enabled": true,
38
+ "reportIssue.enabled": true,
38
39
  "web_search.enabled": true,
39
40
  "calc.enabled": true,
40
41
  "skill.enabled": true,
@@ -128,6 +129,7 @@ describe("tool descriptor compatibility gate", () => {
128
129
  "skill",
129
130
  "goal",
130
131
  "move_session",
132
+ "report_issue",
131
133
  ];
132
134
  expect(Object.keys(BUILTIN_TOOLS)).toEqual(expectedBuiltin);
133
135
  expect(Object.keys(BUILTIN_TOOL_DESCRIPTORS)).toEqual(expectedBuiltin);
@@ -291,6 +293,7 @@ describe("tool descriptor compatibility gate", () => {
291
293
  "astGrep.enabled": false,
292
294
  "astEdit.enabled": false,
293
295
  "renderMermaid.enabled": false,
296
+ "reportIssue.enabled": false,
294
297
  "web_search.enabled": false,
295
298
  "calc.enabled": false,
296
299
  "skill.enabled": false,
@@ -313,6 +316,7 @@ describe("tool descriptor compatibility gate", () => {
313
316
  "ast_grep",
314
317
  "ast_edit",
315
318
  "render_mermaid",
319
+ "report_issue",
316
320
  "web_search",
317
321
  "calc",
318
322
  "skill",
@@ -961,6 +965,46 @@ describe("tool descriptor compatibility gate", () => {
961
965
  expect((normalizedBisect.parameters as any).properties?._i).toBeUndefined();
962
966
  expect((normalizedWrite.parameters as any).properties?._i).toBeDefined();
963
967
  });
968
+ for (const staleOutcome of ["resolved", "rejected"] as const) {
969
+ test(`session move invalidation keeps a newer load when the old load ${staleOutcome}`, async () => {
970
+ const first = Promise.withResolvers<AgentTool>();
971
+ const second = Promise.withResolvers<AgentTool>();
972
+ const oldTool = syntheticTool();
973
+ const newTool = syntheticTool();
974
+ let loads = 0;
975
+ const session = makeSession();
976
+ const descriptor: ToolDescriptor = {
977
+ metadata: { name: "synthetic" },
978
+ presentation: { label: "Synthetic" },
979
+ isAvailable: () => true,
980
+ load: () => (++loads === 1 ? first.promise : second.promise),
981
+ };
982
+ const lazy = new LazyAgentTool(descriptor, undefined, () => descriptor.load(session), session);
983
+ const oldLoad = lazy.materialize().then(
984
+ tool => ({ tool, error: undefined }),
985
+ error => ({ tool: undefined, error }),
986
+ );
987
+ await Promise.resolve();
988
+ expect(loads).toBe(1);
989
+ lazy.invalidateForSessionMove();
990
+ const newLoad = lazy.materialize();
991
+ await Promise.resolve();
992
+ expect(loads).toBe(2);
993
+ if (staleOutcome === "resolved") first.resolve(oldTool);
994
+ else first.reject(new Error("old context load failed"));
995
+ const oldResult = await oldLoad;
996
+ if (staleOutcome === "resolved") expect(oldResult.tool).toBe(oldTool);
997
+ else expect(String(oldResult.error)).toContain("old context load failed");
998
+ const joinedLoad = lazy.materialize();
999
+ await Promise.resolve();
1000
+ expect(loads).toBe(2);
1001
+ second.resolve(newTool);
1002
+ expect(await newLoad).toBe(newTool);
1003
+ expect(await joinedLoad).toBe(newTool);
1004
+ expect(await lazy.materialize()).toBe(newTool);
1005
+ });
1006
+ }
1007
+
964
1008
  test("concurrent lazy first use shares one load and cleanup registration", async () => {
965
1009
  let loads = 0;
966
1010
  let cleanupRegistrations = 0;
@@ -84,6 +84,9 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
84
84
  #loader?: () => ToolDescriptorLoadResult;
85
85
  #session?: ToolSession;
86
86
  #loadPromise?: Promise<AgentTool<any, any, any>>;
87
+ #loadGeneration = 0;
88
+ #loadToken?: object;
89
+ #expectedCwd?: string;
87
90
 
88
91
  constructor(
89
92
  descriptor: ToolDescriptor,
@@ -97,9 +100,31 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
97
100
  this.#session = session;
98
101
  }
99
102
 
103
+ /** Clear a session-bound materialization after its owning session moves. */
104
+ invalidateForSessionMove(expectedCwd?: string): void {
105
+ this.#loadGeneration++;
106
+ this.#tool = undefined;
107
+ this.#loadPromise = undefined;
108
+ this.#loadToken = undefined;
109
+ this.#expectedCwd = expectedCwd;
110
+ if (!this.#loader && this.#session) {
111
+ const session = this.#session;
112
+ this.#loader = () => this.descriptor.load(session);
113
+ }
114
+ }
115
+
100
116
  async #get(): Promise<AgentTool<any, any, any>> {
117
+ const sessionCwd = this.#session?.cwd;
118
+ if (this.#expectedCwd !== undefined && sessionCwd !== this.#expectedCwd) {
119
+ throw new ToolError(
120
+ `Tool "${this.descriptor.metadata.name}" session cwd changed outside a committed move (expected ${this.#expectedCwd}, got ${sessionCwd ?? "unknown"})`,
121
+ );
122
+ }
101
123
  if (this.#tool) return this.#tool;
102
124
  if (!this.#loadPromise) {
125
+ const generation = this.#loadGeneration;
126
+ const token = {};
127
+ this.#loadToken = token;
103
128
  const load = this.#loader;
104
129
  this.#loadPromise = Promise.resolve()
105
130
  .then(() => {
@@ -108,11 +133,14 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
108
133
  })
109
134
  .then(tool => {
110
135
  if (!tool) throw new ToolError(`Tool "${this.descriptor.metadata.name}" failed to load`);
111
- this.#tool = tool;
136
+ if (generation === this.#loadGeneration && this.#loadToken === token) this.#tool = tool;
112
137
  return tool;
113
138
  })
114
139
  .catch(error => {
115
- this.#loadPromise = undefined;
140
+ if (generation === this.#loadGeneration && this.#loadToken === token) {
141
+ this.#loadPromise = undefined;
142
+ this.#loadToken = undefined;
143
+ }
116
144
  if (error instanceof ToolError) throw error;
117
145
  throw new ToolError(
118
146
  `Tool "${this.descriptor.metadata.name}" failed to load implementation: ${error instanceof Error ? error.message : String(error)}`,
@@ -123,10 +151,14 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
123
151
  return this.#loadPromise;
124
152
  }
125
153
 
126
- /** Materialize this facade for contract tests without exposing it in production dispatch. */
127
- async materializeForTests(): Promise<AgentTool<any, any, any>> {
128
- return await this.#get();
129
- }
154
+ /**
155
+ * Resolve the underlying implementation. Pre-dispatch hooks (TTSR `astCondition`
156
+ * matching) call this to read implementation-only members such as the matcher
157
+ * hooks before the first `execute` would otherwise load the tool.
158
+ * An arrow property (like `execute`) so session guard proxies can forward it
159
+ * without losing access to the private loader state.
160
+ */
161
+ readonly materialize = async (): Promise<AgentTool<any, any, any>> => await this.#get();
130
162
 
131
163
  get name(): string {
132
164
  return this.#tool?.name ?? this.descriptor.metadata.name;
@@ -219,6 +251,14 @@ export class LazyAgentTool implements AgentTool<any, any, any> {
219
251
  get mode() {
220
252
  return (this.#tool as any)?.mode;
221
253
  }
254
+ get matcherDigest() {
255
+ const digest = this.#tool?.matcherDigest;
256
+ return digest ? digest.bind(this.#tool) : undefined;
257
+ }
258
+ get matcherEntries() {
259
+ const entries = this.#tool?.matcherEntries;
260
+ return entries ? entries.bind(this.#tool) : undefined;
261
+ }
222
262
 
223
263
  readonly execute: AgentTool<any, any, any>["execute"] = async (...args) => {
224
264
  const tool = await this.#get();
@@ -270,6 +310,7 @@ function availableFor(name: string, session: ToolSession, context = defaultAvail
270
310
  if (name === "ast_grep") return Boolean(session.settings.get("astGrep.enabled"));
271
311
  if (name === "ast_edit") return Boolean(session.settings.get("astEdit.enabled"));
272
312
  if (name === "render_mermaid") return Boolean(session.settings.get("renderMermaid.enabled"));
313
+ if (name === "report_issue") return Boolean(session.settings.get("reportIssue.enabled"));
273
314
  if (name === "web_search") return Boolean(session.settings.get("web_search.enabled"));
274
315
  if (name === "search_tool_bm25") return context.discoveryActive ?? resolveDiscoveryActive(session);
275
316
  if (name === "calc") return Boolean(session.settings.get("calc.enabled"));
@@ -389,6 +430,8 @@ const loaders: Record<string, Loader> = {
389
430
  cached("goal", () => import("../goals/tools/goal-tool")).then(module => new module.GoalTool(session)),
390
431
  move_session: session =>
391
432
  cached("move_session", () => import("./move-session")).then(module => new module.MoveSessionTool(session)),
433
+ report_issue: session =>
434
+ cached("report_issue", () => import("./report-issue")).then(module => new module.ReportIssueTool(session)),
392
435
  yield: session => cached("yield", () => import("./yield")).then(module => new module.YieldTool(session)),
393
436
  report_finding: _session => cached("review", () => import("./review")).then(module => module.reportFindingTool),
394
437
  resolve: session => cached("resolve", () => import("./resolve")).then(module => new module.ResolveTool(session)),
@@ -481,6 +524,7 @@ const names: Array<[name: string, label: string, summary: string | undefined, lo
481
524
  ["skill", "Skill", "Chain into another available skill", "essential"],
482
525
  ["goal", "Goal", undefined, "essential"],
483
526
  ["move_session", "Move Session", undefined, "essential"],
527
+ ["report_issue", "Report Issue", "Record a tool that behaved inconsistently with its description", "essential"],
484
528
  ];
485
529
 
486
530
  const sessionDescriptions: Readonly<Record<string, ToolDescriptorMetadata["descriptionForSession"]>> = {
package/src/tools/eval.ts CHANGED
@@ -139,6 +139,18 @@ export interface EvalToolOptions {
139
139
  proxyExecutor?: EvalProxyExecutor;
140
140
  }
141
141
 
142
+ /**
143
+ * In-process eval is not a shell command, so a bash allowlist cannot authorize it.
144
+ * The same flags that turn the bash tool's allowlist on refuse eval outright.
145
+ * Blank prefixes match `checkBashAllowedPrefixes`: they do not restrict.
146
+ */
147
+ function bashRestrictionBlocksEval(
148
+ session: Pick<ToolSession, "bashAllowedPrefixes" | "bashRestrictionProfile">,
149
+ ): boolean {
150
+ if (session.bashRestrictionProfile === "read-only") return true;
151
+ return (session.bashAllowedPrefixes ?? []).some(prefix => prefix.trim().length > 0);
152
+ }
153
+
142
154
  interface ResolvedBackend {
143
155
  backend: ExecutorBackend;
144
156
  notice?: string;
@@ -225,6 +237,9 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
225
237
  onUpdate?: AgentToolUpdateCallback,
226
238
  _ctx?: AgentToolContext,
227
239
  ): Promise<AgentToolResult<EvalToolDetails | undefined>> {
240
+ if (this.session && bashRestrictionBlocksEval(this.session)) {
241
+ throw new ToolError("Eval is unavailable while this session's bash allowlist or read-only profile is active.");
242
+ }
228
243
  if (this.#proxyExecutor) {
229
244
  return this.#proxyExecutor(params, signal);
230
245
  }
@@ -1,3 +1,5 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import * as nodeFs from "node:fs";
1
3
  import * as fs from "node:fs/promises";
2
4
  import * as path from "node:path";
3
5
  import type { AgentToolResult } from "@gajae-code/agent-core";
@@ -677,12 +679,13 @@ async function handleSpecialUrls(
677
679
  timeout: number,
678
680
  signal: AbortSignal | undefined,
679
681
  storage: AgentStorage | null,
682
+ settings: Settings,
680
683
  ): Promise<FetchRenderResult | null> {
681
684
  for (const handler of getSpecialHandlers()) {
682
685
  if (signal?.aborted) {
683
686
  throw new ToolAbortError();
684
687
  }
685
- const result = await handler(url, timeout, signal, storage);
688
+ const result = await handler(url, timeout, signal, storage, settings);
686
689
  if (result) return result;
687
690
  }
688
691
  return null;
@@ -766,7 +769,7 @@ async function renderUrl(
766
769
 
767
770
  // Step 1: Try special handlers for known sites (unless raw mode)
768
771
  if (!raw) {
769
- const specialResult = await handleSpecialUrls(url, timeout, signal, storage);
772
+ const specialResult = await handleSpecialUrls(url, timeout, signal, storage, settings);
770
773
  if (specialResult) return specialResult;
771
774
  }
772
775
 
@@ -1384,10 +1387,93 @@ async function materializeReadUrlCacheEntry(
1384
1387
  return null;
1385
1388
  }
1386
1389
 
1390
+ const ARTIFACT_NOFOLLOW = process.platform === "win32" ? 0 : (nodeFs.constants.O_NOFOLLOW ?? 0);
1391
+
1392
+ async function unlinkOwnedStaging(staging: string, dev: bigint, ino: bigint): Promise<void> {
1393
+ try {
1394
+ const stat = await fs.lstat(staging, { bigint: true });
1395
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.dev !== dev || stat.ino !== ino) return;
1396
+ await fs.unlink(staging);
1397
+ } catch (error) {
1398
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1399
+ }
1400
+ }
1401
+
1402
+ /**
1403
+ * Publish text at an explicit artifact path the way `ArtifactManager.save` does:
1404
+ * a temp file opened with `O_NOFOLLOW` and mode 0600, then rename. A symlink is
1405
+ * not followed. A pre-existing regular file is replaced, so mode 0644 is not kept.
1406
+ */
1407
+ export async function publishExplicitArtifactText(destination: string, text: string): Promise<void> {
1408
+ const bytes = Buffer.from(text, "utf8");
1409
+ const parent = path.dirname(destination);
1410
+ // Bun.write created a missing parent. Match that when the allocated path's
1411
+ // directory does not exist yet.
1412
+ try {
1413
+ await fs.lstat(parent);
1414
+ } catch (error) {
1415
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1416
+ await fs.mkdir(parent, { recursive: true, mode: 0o700 });
1417
+ }
1418
+ // Keep the staging component a fixed length. A destination basename near NAME_MAX
1419
+ // plus a UUID would make the temp name longer than the filesystem allows.
1420
+ const staging = path.join(parent, `.artifact-${randomUUID()}.staging`);
1421
+ let handle: fs.FileHandle | undefined;
1422
+ let stagedDev: bigint | undefined;
1423
+ let stagedIno: bigint | undefined;
1424
+ let published = false;
1425
+ try {
1426
+ handle = await fs.open(
1427
+ staging,
1428
+ nodeFs.constants.O_CREAT | nodeFs.constants.O_EXCL | nodeFs.constants.O_WRONLY | ARTIFACT_NOFOLLOW,
1429
+ 0o600,
1430
+ );
1431
+ const opened = await handle.stat({ bigint: true });
1432
+ stagedDev = opened.dev;
1433
+ stagedIno = opened.ino;
1434
+ if (process.platform !== "win32") await handle.chmod(0o600);
1435
+ await handle.writeFile(bytes);
1436
+ await handle.sync();
1437
+ const staged = await handle.stat({ bigint: true });
1438
+ if (process.platform !== "win32" && (Number(staged.mode) & 0o777) !== 0o600) {
1439
+ throw new Error("artifact_publish_mode");
1440
+ }
1441
+ await handle.close();
1442
+ handle = undefined;
1443
+
1444
+ let existing: nodeFs.Stats | undefined;
1445
+ try {
1446
+ existing = await fs.lstat(destination);
1447
+ } catch (error) {
1448
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
1449
+ }
1450
+ if (existing?.isSymbolicLink()) throw new Error("artifact_destination_symlink");
1451
+ if (existing && !existing.isFile()) throw new Error("artifact_destination_not_file");
1452
+
1453
+ await fs.rename(staging, destination);
1454
+ const named = await fs.lstat(destination, { bigint: true });
1455
+ if (
1456
+ !named.isFile() ||
1457
+ named.isSymbolicLink() ||
1458
+ named.dev !== stagedDev ||
1459
+ named.ino !== stagedIno ||
1460
+ (process.platform !== "win32" && (Number(named.mode) & 0o777) !== 0o600)
1461
+ ) {
1462
+ throw new Error("artifact_destination_identity_changed");
1463
+ }
1464
+ published = true;
1465
+ } finally {
1466
+ if (handle) await handle.close().catch(() => undefined);
1467
+ if (!published && stagedDev !== undefined && stagedIno !== undefined) {
1468
+ await unlinkOwnedStaging(staging, stagedDev, stagedIno);
1469
+ }
1470
+ }
1471
+ }
1472
+
1387
1473
  async function persistReadUrlArtifact(session: ToolSession, output: string): Promise<string | undefined> {
1388
1474
  const { path: artifactPath, id } = (await session.allocateOutputArtifact?.("read")) ?? {};
1389
1475
  if (!artifactPath) return undefined;
1390
- await Bun.write(artifactPath, wrapUntrustedContent(output));
1476
+ await publishExplicitArtifactText(artifactPath, wrapUntrustedContent(output));
1391
1477
  return id;
1392
1478
  }
1393
1479
 
@@ -1474,7 +1560,10 @@ export const WRAP_PREFIX_CHARS = `${UNTRUSTED_CONTENT_OPEN}\n`.length;
1474
1560
  const WRAP_SUFFIX = `\n${UNTRUSTED_CONTENT_CLOSE}`;
1475
1561
 
1476
1562
  export function escapeUntrustedContent(content: string): string {
1477
- return content.replace(/<\/untrusted-content>/gi, "&lt;/untrusted-content>");
1563
+ // HTML and XML accept whitespace between the tag name and ">".
1564
+ return content
1565
+ .replace(/<\/untrusted-content>/gi, "&lt;/untrusted-content>")
1566
+ .replace(/<\/untrusted-content[ \t\n\r\f]+>/gi, "&lt;/untrusted-content>");
1478
1567
  }
1479
1568
 
1480
1569
  export function wrapUntrustedContent(content: string): string {