@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
@@ -0,0 +1,184 @@
1
+ /**
2
+ * `report_issue` — local auto-QA for tool behavior.
3
+ *
4
+ * The model records a tool whose output contradicts that tool's own
5
+ * description. Reports are appended to a local SQLite database
6
+ * (`~/.gjc/tool-issues.db`); nothing leaves the machine. Inspect them with the
7
+ * read tool's SQLite support, e.g.
8
+ * `gjc read '~/.gjc/tool-issues.db?q=SELECT tool, count(*) FROM tool_issues GROUP BY tool'`.
9
+ *
10
+ * Inspired by oh-my-pi's `xd://report_issue` auto-QA device; collector push
11
+ * and consent prompts are intentionally not part of this local-only port.
12
+ */
13
+ import { Database } from "bun:sqlite";
14
+ import * as fs from "node:fs/promises";
15
+ import * as path from "node:path";
16
+ import type { AgentTool, AgentToolResult } from "@gajae-code/agent-core";
17
+ import { getToolIssuesDbPath, logger, prompt, VERSION } from "@gajae-code/utils";
18
+ import * as z from "zod/v4";
19
+ import reportIssueDescription from "../prompts/tools/report-issue.md" with { type: "text" };
20
+ import type { ToolSession } from "./index";
21
+ import { ToolError } from "./tool-errors";
22
+
23
+ /** Upper bound on the stored tool name, in UTF-8 bytes. */
24
+ const MAX_TOOL_BYTES = 128;
25
+ /** Upper bound on the stored report body, in characters. */
26
+ const MAX_REPORT_CHARS = 4_000;
27
+
28
+ const reportIssueSchema = z.object({
29
+ tool: z.string().describe("name of the tool that misbehaved"),
30
+ report: z.string().describe("what was called, what was expected, what happened"),
31
+ });
32
+
33
+ export type ReportIssueToolInput = z.infer<typeof reportIssueSchema>;
34
+
35
+ export interface ReportIssueToolDetails {
36
+ recorded: boolean;
37
+ tool: string;
38
+ }
39
+
40
+ export interface ToolIssueEntry {
41
+ tool: string;
42
+ report: string;
43
+ model: string | null;
44
+ sessionId: string | null;
45
+ }
46
+
47
+ /**
48
+ * Open the database for a single report. Reports are rare, so no connection is cached: a file the
49
+ * user deleted is recreated on the next report, and no handle keeps the file locked between reports.
50
+ */
51
+ async function openToolIssueDb(dbPath: string): Promise<Database> {
52
+ // Only a directory created here gets 0700; an existing shared config/data root keeps its mode.
53
+ await fs.mkdir(path.dirname(dbPath), { recursive: true, mode: 0o700 });
54
+ // Create (or tighten) the database file before SQLite opens it. SQLite's unix VFS creates the
55
+ // WAL/SHM sidecars with the main file's permissions, so they are private from their first byte.
56
+ const handle = await fs.open(dbPath, "a", 0o600);
57
+ try {
58
+ if (process.platform !== "win32") await handle.chmod(0o600);
59
+ } finally {
60
+ await handle.close();
61
+ }
62
+ const db = new Database(dbPath);
63
+ try {
64
+ db.run(`
65
+ PRAGMA busy_timeout=5000;
66
+ PRAGMA journal_mode=WAL;
67
+ CREATE TABLE IF NOT EXISTS tool_issues (
68
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
69
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
70
+ tool TEXT NOT NULL,
71
+ report TEXT NOT NULL,
72
+ model TEXT,
73
+ gjc_version TEXT NOT NULL,
74
+ session_id TEXT
75
+ );
76
+ CREATE INDEX IF NOT EXISTS idx_tool_issues_tool_created ON tool_issues(tool, created_at);
77
+ `);
78
+ } catch (error) {
79
+ db.close();
80
+ throw error;
81
+ }
82
+ return db;
83
+ }
84
+
85
+ function truncateUtf8(text: string, maxBytes: number): string {
86
+ const bytes = Buffer.from(text, "utf8");
87
+ if (bytes.length <= maxBytes) return text;
88
+ // Decoding a cut multi-byte sequence yields U+FFFD; drop it.
89
+ return bytes
90
+ .subarray(0, maxBytes)
91
+ .toString("utf8")
92
+ .replace(/\uFFFD+$/u, "");
93
+ }
94
+
95
+ /**
96
+ * Truncate text by complete Unicode code points, avoiding incomplete surrogate pairs.
97
+ * JavaScript strings use UTF-16 encoding, where astral characters use two code units
98
+ * (high surrogate + low surrogate). We must count actual code points, not UTF-16 code units.
99
+ */
100
+ function truncateByCodePoint(text: string, maxChars: number): string {
101
+ let codePointCount = 0;
102
+ let result = "";
103
+ // Iterate through actual code points using for-of, which handles surrogate pairs correctly
104
+ for (const codePoint of text) {
105
+ if (codePointCount >= maxChars) break;
106
+ result += codePoint;
107
+ codePointCount++;
108
+ }
109
+ return result;
110
+ }
111
+
112
+ function codePointLength(text: string): number {
113
+ let count = 0;
114
+ for (const _ of text) count++;
115
+ return count;
116
+ }
117
+
118
+ /** Normalize and clamp a report to the stored field limits. */
119
+ export function normalizeToolIssue(tool: string, report: string): { tool: string; report: string } {
120
+ const normalizedTool = truncateUtf8(tool.trim(), MAX_TOOL_BYTES);
121
+ const trimmedReport = report.trim();
122
+ const normalizedReport =
123
+ codePointLength(trimmedReport) > MAX_REPORT_CHARS
124
+ ? `${truncateByCodePoint(trimmedReport, MAX_REPORT_CHARS - 1)}…`
125
+ : trimmedReport;
126
+ return { tool: normalizedTool, report: normalizedReport };
127
+ }
128
+
129
+ /** Append one report to the tool-issue database at `dbPath`. */
130
+ export async function recordToolIssue(dbPath: string, entry: ToolIssueEntry): Promise<void> {
131
+ const db = await openToolIssueDb(dbPath);
132
+ try {
133
+ db.run("INSERT INTO tool_issues (tool, report, model, gjc_version, session_id) VALUES (?, ?, ?, ?, ?)", [
134
+ entry.tool,
135
+ entry.report,
136
+ entry.model,
137
+ VERSION,
138
+ entry.sessionId,
139
+ ]);
140
+ } finally {
141
+ db.close();
142
+ }
143
+ }
144
+
145
+ export class ReportIssueTool implements AgentTool<typeof reportIssueSchema, ReportIssueToolDetails> {
146
+ readonly name = "report_issue";
147
+ readonly label = "Report Issue";
148
+ readonly loadMode = "essential" as const;
149
+ readonly description = prompt.render(reportIssueDescription);
150
+ readonly parameters = reportIssueSchema;
151
+ readonly strict = true;
152
+ readonly intent = "omit" as const;
153
+ readonly #session: ToolSession;
154
+ readonly #dbPath: string;
155
+
156
+ constructor(session: ToolSession, dbPath: string = getToolIssuesDbPath()) {
157
+ this.#session = session;
158
+ this.#dbPath = dbPath;
159
+ }
160
+
161
+ async execute(_toolCallId: string, params: ReportIssueToolInput): Promise<AgentToolResult<ReportIssueToolDetails>> {
162
+ const { tool, report } = normalizeToolIssue(params.tool, params.report);
163
+ if (!tool) throw new ToolError("tool must name the tool that misbehaved.");
164
+ if (!report) throw new ToolError("report must describe the unexpected behavior.");
165
+ try {
166
+ await recordToolIssue(this.#dbPath, {
167
+ tool,
168
+ report,
169
+ model: this.#session.getActiveModelString?.() ?? null,
170
+ sessionId: this.#session.getSessionId?.() ?? null,
171
+ });
172
+ } catch (error) {
173
+ logger.warn("report_issue: failed to record tool issue", { error: String(error), dbPath: this.#dbPath });
174
+ return {
175
+ content: [{ type: "text", text: "Report could not be stored locally; continue the task." }],
176
+ details: { recorded: false, tool },
177
+ };
178
+ }
179
+ return {
180
+ content: [{ type: "text", text: "Recorded. Continue the task." }],
181
+ details: { recorded: true, tool },
182
+ };
183
+ }
184
+ }
@@ -19,6 +19,7 @@ import {
19
19
  openArchive,
20
20
  parseArchivePathCandidates,
21
21
  } from "./archive-reader";
22
+ import { wrapUntrustedContent } from "./fetch";
22
23
  import { createFileRecorder, formatResultPath } from "./file-recorder";
23
24
  import { formatGroupedFiles } from "./grouped-file-output";
24
25
  import { formatMatchLine } from "./match-line-format";
@@ -552,7 +553,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
552
553
  missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
553
554
  };
554
555
  const text = warningNote ? `No matches found\n${warningNote}` : "No matches found";
555
- const noMatchResult = toolResult(details).text(text).done();
556
+ const noMatchResult = toolResult(details).text(wrapUntrustedContent(text)).done();
556
557
  await cleanupArchiveScratch();
557
558
  throwIfSearchCancelled();
558
559
  return noMatchResult;
@@ -676,7 +677,7 @@ export class SearchTool implements AgentTool<typeof searchSchema, SearchToolDeta
676
677
  if (truncation.truncated) details.truncation = truncation;
677
678
  if (linesTruncated) details.linesTruncated = true;
678
679
  const resultBuilder = toolResult(details)
679
- .text(output)
680
+ .text(wrapUntrustedContent(output))
680
681
  .limits({ columnMax: linesTruncated ? DEFAULT_MAX_COLUMN : undefined });
681
682
  if (truncation.truncated) {
682
683
  resultBuilder.truncation(truncation, { direction: "head" });
package/src/tools/ssh.ts CHANGED
@@ -5,18 +5,35 @@ import { prompt } from "@gajae-code/utils";
5
5
  import * as z from "zod/v4";
6
6
  import type { SSHHost } from "../capability/ssh";
7
7
  import { sshCapability } from "../capability/ssh";
8
+ import { getDefault, type Settings } from "../config/settings";
8
9
  import { loadCapability } from "../discovery";
9
10
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
10
11
  import type { Theme } from "../modes/theme/theme";
11
12
  import sshDescriptionBase from "../prompts/tools/ssh.md" with { type: "text" };
12
- import { DEFAULT_MAX_BYTES, streamTailUpdates, TailBuffer } from "../session/streaming-output";
13
+ import type { SessionArtifactPublication } from "../session/session-manager";
14
+ import {
15
+ DEFAULT_MAX_BYTES,
16
+ streamTailUpdates,
17
+ TailBuffer,
18
+ type TerminalArtifactPublisher,
19
+ type TruncationResult,
20
+ truncateMiddle,
21
+ truncateMiddleWindows,
22
+ truncateTail,
23
+ } from "../session/streaming-output";
13
24
  import type { SSHHostInfo } from "../ssh/connection-manager";
14
25
  import { ensureHostInfo, getHostInfoForHost } from "../ssh/connection-manager";
15
- import { executeSSH } from "../ssh/ssh-executor";
26
+ import { executeSSH, type SSHResult } from "../ssh/ssh-executor";
16
27
  import { renderStatusLine } from "../tui";
17
28
  import { CachedOutputBlock } from "../tui/output-block";
18
29
  import type { ToolSession } from ".";
19
- import { formatStyledTruncationWarning, type OutputMeta, stripOutputNotice } from "./output-meta";
30
+ import { wrapUntrustedContent } from "./fetch";
31
+ import {
32
+ formatArtifactReference,
33
+ formatStyledTruncationWarning,
34
+ type OutputMeta,
35
+ stripOutputNotice,
36
+ } from "./output-meta";
20
37
  import { ToolError } from "./tool-errors";
21
38
  import { toolResult } from "./tool-result";
22
39
  import { clampTimeout } from "./tool-timeouts";
@@ -89,6 +106,181 @@ function quoteCmdPath(value: string): string {
89
106
  return `"${escaped}"`;
90
107
  }
91
108
 
109
+ const FENCE_OPEN = "<untrusted-content>\n";
110
+ const FENCE_CLOSE = "\n</untrusted-content>";
111
+ const UNTRUSTED_FENCE_OVERHEAD = Buffer.byteLength(`${FENCE_OPEN}${FENCE_CLOSE}`, "utf-8");
112
+ const INLINE_MARKER_RESERVE = 256;
113
+ /** `</untrusted-content>` grows when `wrapUntrustedContent` escapes it. */
114
+ const CLOSER_RAW_BYTES = "</untrusted-content>".length;
115
+ const CLOSER_ESCAPED_BYTES = "&lt;/untrusted-content&gt;".length;
116
+
117
+ type OutputBudgetPath = "tools.maxInlineResultBytes" | "tools.artifactHeadBytes" | "tools.artifactTailLines";
118
+
119
+ function settingNumber(settings: Settings | undefined, path: OutputBudgetPath): number {
120
+ return settings?.get(path) ?? getDefault(path);
121
+ }
122
+
123
+ type RemoteFrame = {
124
+ text: string;
125
+ truncation?: TruncationResult;
126
+ direction?: "head" | "tail" | "middle";
127
+ /** True only after the full remote text is already stored. */
128
+ shrunk: boolean;
129
+ visibleLines: number;
130
+ visibleBytes: number;
131
+ };
132
+
133
+ function inlineCapBytes(settings: Settings | undefined): number {
134
+ return settingNumber(settings, "tools.maxInlineResultBytes") * 1024;
135
+ }
136
+
137
+ function exceedsInlineCap(output: string, settings: Settings | undefined): boolean {
138
+ const cap = inlineCapBytes(settings);
139
+ if (cap <= 0) return false;
140
+ // Measure the escaped frame. A raw-size check misses closer expansion.
141
+ return Buffer.byteLength(wrapUntrustedContent(output), "utf-8") > cap;
142
+ }
143
+
144
+ function frameFullRemoteOutput(output: string): RemoteFrame {
145
+ return { text: wrapUntrustedContent(output), shrunk: false, visibleLines: 0, visibleBytes: 0 };
146
+ }
147
+
148
+ /**
149
+ * Cut to the inline window, then frame that excerpt. Call only when the full
150
+ * remote text is already stored, so a tail cut cannot drop the opening fence
151
+ * or the only copy of the output.
152
+ */
153
+ function frameShrunkRemoteOutput(output: string, settings: Settings | undefined): RemoteFrame {
154
+ const cap = inlineCapBytes(settings);
155
+ const escapedRoom = Math.max(1, cap - UNTRUSTED_FENCE_OVERHEAD - INLINE_MARKER_RESERVE);
156
+ // Budget in remote-output bytes so framing and closer escaping cannot distort
157
+ // the source coordinates reported by truncation metadata.
158
+ let budget = Math.max(1, Math.floor((escapedRoom * CLOSER_RAW_BYTES) / CLOSER_ESCAPED_BYTES));
159
+ const headBytes = settingNumber(settings, "tools.artifactHeadBytes") * 1024;
160
+ const tailLines = settingNumber(settings, "tools.artifactTailLines");
161
+ const headBudget = Math.max(
162
+ 1,
163
+ Math.floor((Math.min(headBytes, escapedRoom / 2) * CLOSER_RAW_BYTES) / CLOSER_ESCAPED_BYTES),
164
+ );
165
+ const cutRemoteOutput = (maxBytes: number) => {
166
+ if (headBytes > 0) {
167
+ const options = {
168
+ maxBytes,
169
+ maxLines: tailLines * 2,
170
+ maxHeadBytes: Math.min(headBudget, Math.floor(maxBytes / 2)),
171
+ maxHeadLines: tailLines,
172
+ };
173
+ return {
174
+ truncation: truncateMiddle(output, options),
175
+ windows: truncateMiddleWindows(output, options),
176
+ };
177
+ }
178
+ const options = { maxBytes, maxLines: tailLines };
179
+ return {
180
+ truncation: truncateTail(output, options),
181
+ windows: truncateMiddleWindows(output, { ...options, maxHeadBytes: 0, maxHeadLines: 0 }),
182
+ };
183
+ };
184
+ let { truncation, windows } = cutRemoteOutput(budget);
185
+ let excerpt = truncation.truncated ? truncation.content : output;
186
+ let text = wrapUntrustedContent(excerpt);
187
+ let textBytes = Buffer.byteLength(text, "utf-8");
188
+ while (textBytes > cap && budget > 1) {
189
+ const scaledBudget = Math.floor((budget * cap) / textBytes);
190
+ budget = Math.max(1, Math.min(budget - 1, scaledBudget));
191
+ ({ truncation, windows } = cutRemoteOutput(budget));
192
+ excerpt = truncation.truncated ? truncation.content : output;
193
+ text = wrapUntrustedContent(excerpt);
194
+ textBytes = Buffer.byteLength(text, "utf-8");
195
+ }
196
+ if (text === wrapUntrustedContent(output)) return frameFullRemoteOutput(output);
197
+ if (windows.kind === "full") return frameFullRemoteOutput(output);
198
+ const direction = windows.kind === "middle" ? "middle" : windows.kind === "head-only" ? "head" : "tail";
199
+ return {
200
+ text,
201
+ truncation,
202
+ direction,
203
+ shrunk: true,
204
+ visibleLines: (windows.head?.lines ?? 0) + (windows.tail?.lines ?? 0),
205
+ visibleBytes: (windows.head?.bytes ?? 0) + (windows.tail?.bytes ?? 0),
206
+ };
207
+ }
208
+
209
+ /** Store the full remote text. Never write an already-shortened excerpt.
210
+ * The command has already finished, so a rejected reservation must not discard it.
211
+ */
212
+ async function storeFullRemoteOutput(
213
+ session: ToolSession,
214
+ output: string,
215
+ artifactPublication: SessionArtifactPublication | undefined,
216
+ ): Promise<string | undefined> {
217
+ let allocated: { id?: string; path?: string } | undefined;
218
+ try {
219
+ allocated = await session.allocateOutputArtifact?.("ssh");
220
+ } catch {
221
+ allocated = undefined;
222
+ }
223
+ if (allocated?.path && allocated.id) {
224
+ try {
225
+ await Bun.write(allocated.path, output);
226
+ return allocated.id;
227
+ } catch {
228
+ // Fall through to the session-owned publisher when a writable path failed.
229
+ }
230
+ }
231
+ if (!artifactPublication) return undefined;
232
+ try {
233
+ return await artifactPublication(output, "ssh");
234
+ } catch {
235
+ return undefined;
236
+ }
237
+ }
238
+
239
+ function createSshArtifactPublisher(
240
+ artifactPublication: SessionArtifactPublication | undefined,
241
+ ): TerminalArtifactPublisher | undefined {
242
+ if (!artifactPublication) return undefined;
243
+ return async (content, info) => {
244
+ try {
245
+ const artifactId = await artifactPublication(content, "ssh");
246
+ if (!artifactId) return { status: "unavailable" };
247
+ return info.omittedBytes > 0
248
+ ? { status: "published", artifactId, omittedBytes: info.omittedBytes }
249
+ : { status: "published", artifactId };
250
+ } catch (error) {
251
+ return { status: "failed", diagnostic: error instanceof Error ? error.message : String(error) };
252
+ }
253
+ };
254
+ }
255
+
256
+ function hasCompleteRemoteOutput(result: SSHResult, output: string): boolean {
257
+ return (
258
+ !result.truncated &&
259
+ result.outputBytes === result.totalBytes &&
260
+ Buffer.byteLength(output, "utf-8") === result.outputBytes
261
+ );
262
+ }
263
+
264
+ function incompleteOutputNotice(result: SSHResult, artifactId: string | undefined): string | undefined {
265
+ const retainedWindowIncomplete = result.truncated || result.outputBytes < result.totalBytes;
266
+ if (!retainedWindowIncomplete) return undefined;
267
+
268
+ const artifactIncomplete =
269
+ (result.artifactTruncatedBytes ?? 0) > 0 || result.artifactFailureDiagnostic !== undefined;
270
+ const completeArtifactAvailable = artifactId !== undefined && !artifactIncomplete;
271
+ if (completeArtifactAvailable) return undefined;
272
+
273
+ const notes = [`SSH output is incomplete: retained ${result.outputBytes} of ${result.totalBytes} bytes`];
274
+ notes.push("no complete output artifact is available");
275
+ if ((result.artifactTruncatedBytes ?? 0) > 0) {
276
+ notes.push(`artifact storage omitted at least ${result.artifactTruncatedBytes} bytes`);
277
+ }
278
+ if (result.artifactFailureDiagnostic) {
279
+ notes.push(`Artifact storage failed: ${result.artifactFailureDiagnostic}`);
280
+ }
281
+ return notes.join("; ");
282
+ }
283
+
92
284
  function buildRemoteCommand(command: string, cwd: string | undefined, info: SSHHostInfo): string {
93
285
  if (!cwd) return command;
94
286
 
@@ -161,6 +353,8 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
161
353
  // Clamp to reasonable range: 1s - 3600s (1 hour)
162
354
  const timeoutSec = clampTimeout("ssh", rawTimeout);
163
355
  const timeoutMs = timeoutSec * 1000;
356
+ const outputSettings = _ctx?.settings ?? this.session.settings;
357
+ const artifactPublication = this.session.captureArtifactPublication?.();
164
358
 
165
359
  const tailBuffer = new TailBuffer(DEFAULT_MAX_BYTES);
166
360
  const { path: artifactPath, id: artifactId } = (await this.session.allocateOutputArtifact?.("ssh")) ?? {};
@@ -172,22 +366,94 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
172
366
  compatEnabled: hostInfo.compatEnabled,
173
367
  artifactPath,
174
368
  artifactId,
369
+ artifactPublisher: createSshArtifactPublisher(artifactPublication),
175
370
  onChunk: streamTailUpdates(tailBuffer, onUpdate),
176
371
  });
177
372
 
373
+ const remote = result.output ?? "";
374
+ let storedArtifactId = result.artifactId;
375
+ let framed: RemoteFrame | undefined;
376
+ if (!remote) {
377
+ framed = undefined;
378
+ } else if (!exceedsInlineCap(remote, outputSettings)) {
379
+ framed = frameFullRemoteOutput(remote);
380
+ } else {
381
+ if (!storedArtifactId && hasCompleteRemoteOutput(result, remote)) {
382
+ storedArtifactId = await storeFullRemoteOutput(this.session, remote, artifactPublication);
383
+ }
384
+ framed = storedArtifactId ? frameShrunkRemoteOutput(remote, outputSettings) : frameFullRemoteOutput(remote);
385
+ }
386
+ // The artifact id stays in truncation metadata. A line after the closing
387
+ // fence makes the inline backstop drop the opening fence.
388
+ const modelText = framed?.text;
389
+ const storedNote =
390
+ (framed?.shrunk || result.truncated) && storedArtifactId
391
+ ? `\n\n${formatArtifactReference(storedArtifactId, result.artifactTruncatedBytes)}`
392
+ : "";
393
+ const incompleteNote = incompleteOutputNotice(result, storedArtifactId);
394
+ const artifactFailureNote =
395
+ result.artifactFailureDiagnostic && !incompleteNote
396
+ ? `SSH streaming artifact storage failed: ${result.artifactFailureDiagnostic}`
397
+ : undefined;
178
398
  if (result.cancelled) {
179
- throw new ToolError(result.output || "Command aborted");
399
+ const message = [
400
+ modelText || "Command aborted",
401
+ storedNote.trim() || undefined,
402
+ incompleteNote,
403
+ artifactFailureNote,
404
+ ]
405
+ .filter((part): part is string => part !== undefined)
406
+ .join("\n\n");
407
+ throw new ToolError(message);
180
408
  }
181
409
 
182
- const outputText = result.output || "(no output)";
410
+ const outputText = modelText ?? "(no output)";
183
411
  const details: SSHToolDetails = {};
184
- const resultBuilder = toolResult(details).text(outputText).truncationFromSummary(result, { direction: "tail" });
412
+ const resultBuilder = toolResult(details).text(outputText);
413
+ if (framed?.shrunk && framed.truncation && storedArtifactId) {
414
+ resultBuilder.truncation(framed.truncation, {
415
+ direction: framed.direction ?? "tail",
416
+ artifactId: storedArtifactId,
417
+ });
418
+ } else {
419
+ resultBuilder.truncationFromSummary(result, { direction: "tail" });
420
+ }
185
421
 
186
422
  if (result.exitCode !== 0 && result.exitCode !== undefined) {
187
- throw new ToolError(`${outputText}\n\nCommand exited with code ${result.exitCode}`);
423
+ const message = [
424
+ outputText,
425
+ storedNote.trim() || undefined,
426
+ incompleteNote,
427
+ artifactFailureNote,
428
+ `Command exited with code ${result.exitCode}`,
429
+ ]
430
+ .filter((part): part is string => part !== undefined)
431
+ .join("\n\n");
432
+ throw new ToolError(message);
188
433
  }
189
434
 
190
- return resultBuilder.done();
435
+ const built = resultBuilder.done();
436
+ const truncation = built.details?.meta?.truncation;
437
+ if (framed?.shrunk && truncation) {
438
+ // The cut is a window of an earlier excerpt. Keep the stream totals
439
+ // and do not present that window's coordinates as the original range.
440
+ truncation.totalLines = result.totalLines;
441
+ truncation.totalBytes = result.totalBytes;
442
+ truncation.outputLines = framed.visibleLines;
443
+ truncation.outputBytes = framed.visibleBytes;
444
+ truncation.artifactId = storedArtifactId;
445
+ truncation.artifactTruncatedBytes = result.artifactTruncatedBytes;
446
+ truncation.artifactFailureDiagnostic = result.artifactFailureDiagnostic;
447
+ delete truncation.shownRange;
448
+ delete truncation.headRange;
449
+ delete truncation.tailRange;
450
+ delete truncation.nextOffset;
451
+ if (framed.direction === "middle") {
452
+ truncation.elidedLines = Math.max(0, result.totalLines - framed.visibleLines);
453
+ truncation.elidedBytes = Math.max(0, result.totalBytes - framed.visibleBytes);
454
+ }
455
+ }
456
+ return built;
191
457
  }
192
458
  }
193
459