@gajae-code/coding-agent 0.11.11 → 0.12.1

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 (344) hide show
  1. package/CHANGELOG.md +131 -4
  2. package/dist/types/async/job-manager.d.ts +17 -3
  3. package/dist/types/cli/read-cli.d.ts +1 -0
  4. package/dist/types/commands/deep-interview.d.ts +9 -0
  5. package/dist/types/commands/harness.d.ts +22 -0
  6. package/dist/types/commands/read.d.ts +6 -0
  7. package/dist/types/config/model-registry.d.ts +4 -0
  8. package/dist/types/config/provider-auth-health.d.ts +14 -0
  9. package/dist/types/config/provider-ranking.d.ts +59 -0
  10. package/dist/types/config/settings-schema.d.ts +83 -1
  11. package/dist/types/config/settings.d.ts +2 -0
  12. package/dist/types/exa/mcp-client.d.ts +10 -1
  13. package/dist/types/exec/bash-executor.d.ts +45 -12
  14. package/dist/types/extensibility/extensions/types.d.ts +38 -4
  15. package/dist/types/extensibility/gjc-plugins/index.d.ts +12 -2
  16. package/dist/types/extensibility/gjc-plugins/installer.d.ts +63 -7
  17. package/dist/types/extensibility/gjc-plugins/lifecycle-reconciliation.d.ts +55 -0
  18. package/dist/types/extensibility/gjc-plugins/lifecycle.d.ts +64 -0
  19. package/dist/types/extensibility/gjc-plugins/runtime-quarantine.d.ts +67 -0
  20. package/dist/types/extensibility/gjc-plugins/session-validation.d.ts +3 -1
  21. package/dist/types/extensibility/gjc-plugins/types.d.ts +124 -0
  22. package/dist/types/extensibility/shared-events.d.ts +6 -0
  23. package/dist/types/gjc-runtime/deep-interview-ambiguity.d.ts +5 -12
  24. package/dist/types/gjc-runtime/deep-interview-recorder.d.ts +17 -32
  25. package/dist/types/gjc-runtime/deep-interview-stage.d.ts +37 -0
  26. package/dist/types/gjc-runtime/deep-interview-state.d.ts +4 -135
  27. package/dist/types/gjc-runtime/gc-runtime.d.ts +1 -1
  28. package/dist/types/gjc-runtime/launch-tmux.d.ts +1 -0
  29. package/dist/types/gjc-runtime/memory-guard-owner-claims.d.ts +47 -0
  30. package/dist/types/gjc-runtime/ralplan-runtime.d.ts +70 -1
  31. package/dist/types/gjc-runtime/state-writer.d.ts +5 -46
  32. package/dist/types/gjc-runtime/team-launch.d.ts +1 -1
  33. package/dist/types/gjc-runtime/team-runtime.d.ts +27 -1
  34. package/dist/types/gjc-runtime/team-store.d.ts +12 -0
  35. package/dist/types/gjc-runtime/team-worker-heartbeat.d.ts +43 -0
  36. package/dist/types/gjc-runtime/team-worker-memory-guard.d.ts +92 -0
  37. package/dist/types/gjc-runtime/tmux-owner-isolation.d.ts +17 -0
  38. package/dist/types/gjc-runtime/ultragoal-receipt-freshness.d.ts +8 -0
  39. package/dist/types/gjc-runtime/ultragoal-runtime.d.ts +31 -80
  40. package/dist/types/gjc-runtime/workflow-manifest.d.ts +3 -5
  41. package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
  42. package/dist/types/main.d.ts +1 -1
  43. package/dist/types/memories/index.d.ts +2 -0
  44. package/dist/types/modes/acp/acp-agent.d.ts +10 -19
  45. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  46. package/dist/types/modes/components/gjc-bundle-settings.d.ts +34 -0
  47. package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
  48. package/dist/types/modes/components/notifications-settings-editor.d.ts +1 -0
  49. package/dist/types/modes/components/read-tool-group.d.ts +4 -4
  50. package/dist/types/modes/components/session-observer-overlay.d.ts +22 -0
  51. package/dist/types/modes/components/settings-selector.d.ts +11 -0
  52. package/dist/types/modes/components/status-line/gh.d.ts +5 -0
  53. package/dist/types/modes/components/tool-execution.d.ts +8 -0
  54. package/dist/types/modes/components/transcript-viewer-overlay.d.ts +18 -1
  55. package/dist/types/modes/controllers/event-controller.d.ts +3 -0
  56. package/dist/types/modes/interactive-mode.d.ts +2 -0
  57. package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
  58. package/dist/types/modes/types.d.ts +2 -0
  59. package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
  60. package/dist/types/reminders/star-reminder.d.ts +3 -13
  61. package/dist/types/runtime-mcp/manager.d.ts +1 -0
  62. package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
  63. package/dist/types/sdk/acp/adapter.d.ts +3 -1
  64. package/dist/types/sdk/broker/lifecycle.d.ts +5 -0
  65. package/dist/types/sdk/broker/transport.d.ts +2 -0
  66. package/dist/types/sdk/bus/attachment-registry.d.ts +1 -0
  67. package/dist/types/sdk/bus/chat-daemon-control.d.ts +4 -1
  68. package/dist/types/sdk/bus/config.d.ts +6 -2
  69. package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
  70. package/dist/types/sdk/bus/index.d.ts +44 -1
  71. package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +44 -0
  72. package/dist/types/sdk/bus/lifecycle-control-runtime.d.ts +4 -0
  73. package/dist/types/sdk/bus/prompt-reconciliation.d.ts +2 -1
  74. package/dist/types/sdk/bus/reconciliation-store.d.ts +69 -0
  75. package/dist/types/sdk/bus/rich-draft.d.ts +1 -0
  76. package/dist/types/sdk/bus/rich-render.d.ts +2 -0
  77. package/dist/types/sdk/bus/telegram-adoption-intent.d.ts +165 -0
  78. package/dist/types/sdk/bus/telegram-cli.d.ts +12 -0
  79. package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +11 -4
  80. package/dist/types/sdk/bus/telegram-daemon.d.ts +25 -1
  81. package/dist/types/sdk/bus/telegram-reference.d.ts +11 -1
  82. package/dist/types/sdk/bus/threaded-render.d.ts +3 -2
  83. package/dist/types/sdk/bus/topic-registry.d.ts +11 -1
  84. package/dist/types/sdk/host/control/operations.d.ts +1 -1
  85. package/dist/types/sdk/host/host.d.ts +2 -0
  86. package/dist/types/sdk/host/query/handlers.d.ts +5 -0
  87. package/dist/types/sdk/host/query/revision-store.d.ts +2 -0
  88. package/dist/types/sdk/host/reverse-leases.d.ts +1 -1
  89. package/dist/types/sdk/prompt-status.d.ts +23 -3
  90. package/dist/types/sdk/session.d.ts +6 -0
  91. package/dist/types/session/agent-session.d.ts +82 -12
  92. package/dist/types/session/artifacts.d.ts +8 -0
  93. package/dist/types/session/blob-store.d.ts +57 -4
  94. package/dist/types/session/internal/managed-session-storage.d.ts +8 -1
  95. package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
  96. package/dist/types/session/session-manager.d.ts +48 -19
  97. package/dist/types/session/session-storage.d.ts +4 -0
  98. package/dist/types/session/streaming-output.d.ts +96 -12
  99. package/dist/types/setup/credential-import.d.ts +12 -0
  100. package/dist/types/skill-state/active-state.d.ts +12 -4
  101. package/dist/types/skill-state/workflow-hud.d.ts +9 -0
  102. package/dist/types/task/executor.d.ts +3 -2
  103. package/dist/types/task/index.d.ts +3 -1
  104. package/dist/types/task/provider-retry-status.d.ts +8 -0
  105. package/dist/types/task/receipt.d.ts +7 -1
  106. package/dist/types/task/types.d.ts +23 -0
  107. package/dist/types/tools/ask-answer-registry.d.ts +2 -2
  108. package/dist/types/tools/bash-interactive.d.ts +4 -1
  109. package/dist/types/tools/bash.d.ts +3 -1
  110. package/dist/types/tools/browser/launch.d.ts +7 -0
  111. package/dist/types/tools/fetch.d.ts +17 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -0
  113. package/dist/types/tools/index.d.ts +4 -0
  114. package/dist/types/tools/output-meta.d.ts +44 -3
  115. package/dist/types/tools/path-utils.d.ts +1 -0
  116. package/dist/types/tools/read-internals.d.ts +12 -0
  117. package/dist/types/tools/read.d.ts +45 -7
  118. package/dist/types/tools/resource-gc.d.ts +4 -0
  119. package/dist/types/tools/review.d.ts +5 -7
  120. package/dist/types/tools/subagent.d.ts +2 -0
  121. package/dist/types/tools/tool-result.d.ts +7 -1
  122. package/dist/types/utils/gh.d.ts +13 -0
  123. package/dist/types/utils/image-resize.d.ts +1 -0
  124. package/dist/types/web/search/providers/kimi.d.ts +2 -0
  125. package/dist/types/web/search/providers/searxng.d.ts +7 -0
  126. package/dist/types/web/search/providers/xai.d.ts +2 -0
  127. package/package.json +11 -13
  128. package/scripts/acp-conformance-agent.ts +218 -0
  129. package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
  130. package/scripts/capture-gjc-bundle-settings.ts +306 -0
  131. package/scripts/capture-sticky-viewport-showcase.ts +292 -0
  132. package/scripts/generate-sdk-operation-inventory.ts +5 -0
  133. package/scripts/resident-memory-bench.ts +1341 -0
  134. package/scripts/run-acp-conformance.ts +285 -0
  135. package/scripts/verify-gjc-sdk-canonicalization.ts +122 -18
  136. package/scripts/verify-sticky-viewport-showcase.ts +611 -0
  137. package/src/async/job-manager.ts +78 -20
  138. package/src/capability/index.ts +8 -0
  139. package/src/cli/fast-help.ts +1 -1
  140. package/src/cli/plugin-cli.ts +176 -18
  141. package/src/cli/read-cli.ts +2 -1
  142. package/src/cli.ts +1 -0
  143. package/src/commands/deep-interview.ts +29 -9
  144. package/src/commands/harness.ts +35 -12
  145. package/src/commands/read.ts +11 -2
  146. package/src/commands/team.ts +20 -2
  147. package/src/config/file-lock.ts +11 -2
  148. package/src/config/model-registry.ts +21 -2
  149. package/src/config/provider-auth-health.ts +42 -0
  150. package/src/config/provider-ranking.ts +119 -0
  151. package/src/config/settings-schema.ts +74 -1
  152. package/src/config/settings.ts +246 -108
  153. package/src/coordinator-mcp/question-gate-codec.ts +1 -1
  154. package/src/coordinator-mcp/server.ts +5 -12
  155. package/src/defaults/gjc/skills/deep-interview/SKILL.md +230 -82
  156. package/src/defaults/gjc/skills/ralplan/SKILL.md +77 -41
  157. package/src/defaults/gjc/skills/team/SKILL.md +3 -1
  158. package/src/defaults/gjc/skills/ultragoal/SKILL.md +62 -39
  159. package/src/defaults/gjc/skills/ultragoal/validation-batch-contracts.md +12 -0
  160. package/src/defaults/gjc-defaults.ts +4 -4
  161. package/src/eval/py/executor.ts +7 -2
  162. package/src/exa/mcp-client.ts +12 -3
  163. package/src/exec/bash-executor.ts +144 -27
  164. package/src/extensibility/extensions/runner.ts +15 -0
  165. package/src/extensibility/extensions/types.ts +46 -5
  166. package/src/extensibility/gjc-plugins/constrained-hooks.ts +8 -2
  167. package/src/extensibility/gjc-plugins/index.ts +19 -2
  168. package/src/extensibility/gjc-plugins/installer.ts +189 -50
  169. package/src/extensibility/gjc-plugins/lifecycle-reconciliation.ts +199 -0
  170. package/src/extensibility/gjc-plugins/lifecycle.ts +626 -0
  171. package/src/extensibility/gjc-plugins/observability.ts +3 -3
  172. package/src/extensibility/gjc-plugins/prompt-appendix.ts +5 -1
  173. package/src/extensibility/gjc-plugins/runtime-adapters.ts +12 -2
  174. package/src/extensibility/gjc-plugins/runtime-quarantine.ts +121 -0
  175. package/src/extensibility/gjc-plugins/schema.ts +84 -12
  176. package/src/extensibility/gjc-plugins/session-validation.ts +9 -3
  177. package/src/extensibility/gjc-plugins/types.ts +141 -0
  178. package/src/extensibility/gjc-plugins/validation.ts +9 -0
  179. package/src/extensibility/shared-events.ts +5 -0
  180. package/src/gjc-runtime/deep-interview-ambiguity.ts +47 -82
  181. package/src/gjc-runtime/deep-interview-recorder.ts +227 -310
  182. package/src/gjc-runtime/deep-interview-runtime.ts +109 -270
  183. package/src/gjc-runtime/deep-interview-stage.ts +1016 -0
  184. package/src/gjc-runtime/deep-interview-state.ts +12 -943
  185. package/src/gjc-runtime/gc-render.ts +1 -0
  186. package/src/gjc-runtime/gc-runtime.ts +11 -1
  187. package/src/gjc-runtime/launch-tmux.ts +25 -6
  188. package/src/gjc-runtime/managed-owner-admission.ts +1 -1
  189. package/src/gjc-runtime/memory-guard-owner-claims.ts +389 -0
  190. package/src/gjc-runtime/ralplan-runtime.ts +1149 -98
  191. package/src/gjc-runtime/state-runtime.ts +2 -1
  192. package/src/gjc-runtime/state-writer.ts +70 -222
  193. package/src/gjc-runtime/team-launch.ts +42 -5
  194. package/src/gjc-runtime/team-runtime.ts +1085 -40
  195. package/src/gjc-runtime/team-store.ts +32 -0
  196. package/src/gjc-runtime/team-worker-heartbeat.ts +149 -0
  197. package/src/gjc-runtime/team-worker-memory-guard.ts +363 -0
  198. package/src/gjc-runtime/team-workers.ts +1 -0
  199. package/src/gjc-runtime/tmux-owner-isolation.ts +28 -0
  200. package/src/gjc-runtime/tmux-sessions.ts +58 -13
  201. package/src/gjc-runtime/ultragoal-receipt-freshness.ts +20 -0
  202. package/src/gjc-runtime/ultragoal-runtime.ts +414 -618
  203. package/src/gjc-runtime/workflow-manifest.generated.json +105 -449
  204. package/src/gjc-runtime/workflow-manifest.ts +38 -247
  205. package/src/hooks/native-skill-hook.ts +17 -4
  206. package/src/hooks/skill-state.ts +1 -23
  207. package/src/internal-urls/docs-index.generated.ts +21 -22
  208. package/src/internal-urls/local-root-gc.ts +127 -0
  209. package/src/main.ts +9 -5
  210. package/src/memories/index.ts +10 -0
  211. package/src/modes/DESIGN.md +98 -0
  212. package/src/modes/acp/acp-agent.ts +339 -191
  213. package/src/modes/acp/acp-event-mapper.ts +14 -7
  214. package/src/modes/acp/acp-mode.ts +39 -3
  215. package/src/modes/components/assistant-message.ts +123 -22
  216. package/src/modes/components/gjc-bundle-settings.ts +508 -0
  217. package/src/modes/components/irc-sidebar.ts +176 -62
  218. package/src/modes/components/model-selector.ts +61 -4
  219. package/src/modes/components/notifications-settings-editor.ts +12 -1
  220. package/src/modes/components/oauth-selector.ts +96 -28
  221. package/src/modes/components/read-tool-group.ts +82 -14
  222. package/src/modes/components/session-observer-overlay.ts +457 -83
  223. package/src/modes/components/settings-selector.ts +116 -23
  224. package/src/modes/components/status-line/gh.ts +16 -0
  225. package/src/modes/components/tool-execution.ts +138 -27
  226. package/src/modes/components/tool-status-header.ts +3 -14
  227. package/src/modes/components/transcript-viewer-overlay.ts +99 -24
  228. package/src/modes/controllers/command-controller.ts +4 -0
  229. package/src/modes/controllers/event-controller.ts +109 -27
  230. package/src/modes/controllers/extension-ui-controller.ts +26 -2
  231. package/src/modes/controllers/input-controller.ts +18 -3
  232. package/src/modes/controllers/selector-controller.ts +127 -36
  233. package/src/modes/interactive-mode.ts +62 -3
  234. package/src/modes/irc-observation-ledger.ts +71 -15
  235. package/src/modes/runtime-init.ts +22 -2
  236. package/src/modes/types.ts +2 -0
  237. package/src/modes/utils/ui-helpers.ts +94 -1
  238. package/src/prompts/agent-fragments/ralplan-persistence.md +4 -3
  239. package/src/prompts/agents/architect.md +9 -1
  240. package/src/prompts/agents/critic.md +9 -0
  241. package/src/prompts/system/eager-todo.md +8 -4
  242. package/src/prompts/system/system-prompt.md +11 -5
  243. package/src/prompts/tools/bash.md +5 -5
  244. package/src/prompts/tools/read.md +4 -2
  245. package/src/prompts/tools/search-tool-bm25.md +5 -0
  246. package/src/prompts/tools/task.md +1 -1
  247. package/src/reminders/star-reminder.ts +4 -42
  248. package/src/runtime-mcp/manager.ts +19 -4
  249. package/src/runtime-mcp/smithery-auth.ts +17 -2
  250. package/src/runtime-mcp/smithery-connect.ts +11 -1
  251. package/src/sdk/acp/adapter.ts +22 -6
  252. package/src/sdk/broker/lifecycle.ts +25 -6
  253. package/src/sdk/broker/transport.ts +35 -3
  254. package/src/sdk/bus/attachment-registry.ts +5 -1
  255. package/src/sdk/bus/chat-daemon-cli.ts +4 -39
  256. package/src/sdk/bus/chat-daemon-control.ts +6 -3
  257. package/src/sdk/bus/config.ts +18 -2
  258. package/src/sdk/bus/control-drain-lease.ts +104 -0
  259. package/src/sdk/bus/index.ts +984 -146
  260. package/src/sdk/bus/kind-aware-reconciliation.ts +317 -0
  261. package/src/sdk/bus/lifecycle-commands.ts +4 -1
  262. package/src/sdk/bus/lifecycle-control-runtime.ts +403 -88
  263. package/src/sdk/bus/prompt-reconciliation.ts +2 -1
  264. package/src/sdk/bus/reconciliation-store.ts +303 -0
  265. package/src/sdk/bus/rich-draft.ts +1 -1
  266. package/src/sdk/bus/rich-render.ts +2 -2
  267. package/src/sdk/bus/telegram-adoption-intent.ts +574 -0
  268. package/src/sdk/bus/telegram-cli.ts +33 -8
  269. package/src/sdk/bus/telegram-daemon-cli.ts +1 -0
  270. package/src/sdk/bus/telegram-daemon-contract.ts +11 -4
  271. package/src/sdk/bus/telegram-daemon.ts +916 -23
  272. package/src/sdk/bus/telegram-reference.ts +40 -3
  273. package/src/sdk/bus/threaded-render.ts +48 -3
  274. package/src/sdk/bus/topic-registry.ts +20 -0
  275. package/src/sdk/host/control/dispatch.ts +5 -1
  276. package/src/sdk/host/control/operations.ts +1 -1
  277. package/src/sdk/host/host.ts +23 -1
  278. package/src/sdk/host/query/handlers.ts +55 -0
  279. package/src/sdk/host/query/revision-store.ts +36 -2
  280. package/src/sdk/host/reverse-leases.ts +44 -7
  281. package/src/sdk/mcp/server.ts +46 -18
  282. package/src/sdk/prompt-status.ts +16 -3
  283. package/src/sdk/protocol/operation-inventory.generated.json +51 -0
  284. package/src/sdk/protocol/operation-registry.ts +7 -2
  285. package/src/sdk/session.ts +101 -10
  286. package/src/session/agent-session.ts +1286 -220
  287. package/src/session/artifacts.ts +22 -0
  288. package/src/session/auth-broker-config.ts +10 -3
  289. package/src/session/blob-store.ts +915 -24
  290. package/src/session/internal/managed-session-scope.ts +44 -6
  291. package/src/session/internal/managed-session-storage.ts +48 -6
  292. package/src/session/memory-guard-checkpoint-participant.ts +131 -0
  293. package/src/session/session-manager.ts +2364 -438
  294. package/src/session/session-storage.ts +9 -1
  295. package/src/session/streaming-output.ts +500 -111
  296. package/src/setup/credential-import.ts +75 -13
  297. package/src/setup/provider-onboarding.ts +19 -4
  298. package/src/skill-state/active-state.ts +32 -10
  299. package/src/skill-state/workflow-hud.ts +42 -2
  300. package/src/skill-state/workflow-mutation-guard.ts +560 -8
  301. package/src/task/executor.ts +122 -39
  302. package/src/task/index.ts +23 -14
  303. package/src/task/provider-retry-status.ts +16 -0
  304. package/src/task/receipt.ts +23 -7
  305. package/src/task/render.ts +6 -2
  306. package/src/task/types.ts +108 -0
  307. package/src/tools/ask-answer-registry.ts +12 -6
  308. package/src/tools/ask.ts +169 -118
  309. package/src/tools/ast-edit.ts +1 -0
  310. package/src/tools/ast-grep.ts +1 -0
  311. package/src/tools/bash-interactive.ts +8 -3
  312. package/src/tools/bash.ts +503 -60
  313. package/src/tools/browser/launch.ts +41 -6
  314. package/src/tools/fetch.ts +114 -23
  315. package/src/tools/find.ts +1 -0
  316. package/src/tools/image-gen.ts +29 -5
  317. package/src/tools/index.ts +4 -0
  318. package/src/tools/output-meta.ts +281 -20
  319. package/src/tools/path-utils.ts +8 -3
  320. package/src/tools/read-internals.ts +44 -0
  321. package/src/tools/read.ts +1560 -310
  322. package/src/tools/resource-gc.ts +182 -18
  323. package/src/tools/review.ts +5 -7
  324. package/src/tools/search.ts +1 -0
  325. package/src/tools/subagent-render.ts +6 -2
  326. package/src/tools/subagent.ts +6 -0
  327. package/src/tools/telegram-send.ts +6 -3
  328. package/src/tools/tool-result.ts +14 -1
  329. package/src/utils/gh.ts +39 -0
  330. package/src/utils/image-resize.ts +3 -0
  331. package/src/web/search/providers/anthropic.ts +4 -4
  332. package/src/web/search/providers/codex.ts +1 -1
  333. package/src/web/search/providers/kimi.ts +15 -5
  334. package/src/web/search/providers/searxng.ts +47 -4
  335. package/src/web/search/providers/xai.ts +7 -2
  336. package/dist/types/gjc-runtime/deep-interview-draft.d.ts +0 -8
  337. package/dist/types/gjc-runtime/deep-interview-payload.d.ts +0 -41
  338. package/dist/types/gjc-runtime/deep-interview-repair.d.ts +0 -17
  339. package/dist/types/gjc-runtime/ultragoal-pipeline.d.ts +0 -23
  340. package/src/defaults/gjc/skills/ultragoal/pipeline-validation-contracts.md +0 -30
  341. package/src/gjc-runtime/deep-interview-draft.ts +0 -843
  342. package/src/gjc-runtime/deep-interview-payload.ts +0 -262
  343. package/src/gjc-runtime/deep-interview-repair.ts +0 -1674
  344. package/src/gjc-runtime/ultragoal-pipeline.ts +0 -306
package/src/task/types.ts CHANGED
@@ -212,6 +212,14 @@ export interface ReviewFinding {
212
212
  line_end: number;
213
213
  }
214
214
 
215
+ /** Durable full-fidelity review findings artifact associated with a task result. */
216
+ export interface ReviewFindingsArtifactRef {
217
+ uri: `artifact://${string}`;
218
+ sizeBytes: number;
219
+ sha256: string;
220
+ findingCount: number;
221
+ }
222
+
215
223
  /** Review summary submitted by the reviewer agent */
216
224
  export interface ReviewSummary {
217
225
  overall_correctness: "correct" | "incorrect";
@@ -283,6 +291,8 @@ export interface AgentProgress {
283
291
  durationMs: number;
284
292
  modelOverride?: string | string[];
285
293
  modelSubstitutionWarning?: ModelSubstitutionWarning;
294
+ /** Whether the resolved subagent model runs under the effective fast service tier. */
295
+ fastMode?: boolean;
286
296
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
287
297
  extractedToolData?: Record<string, unknown[]>;
288
298
  /**
@@ -313,6 +323,8 @@ export interface AgentProgress {
313
323
  attempt: number;
314
324
  errorMessage: string;
315
325
  };
326
+ /** Safe diagnostic retained in terminal progress when setup fails before the first LLM request. */
327
+ setupFailure?: SetupFailureSummary;
316
328
  /**
317
329
  * Snapshot of the most recent `task` tool call's in-flight `TaskToolDetails`,
318
330
  * captured from `tool_execution_update`. Lets the parent UI surface live
@@ -323,6 +335,96 @@ export interface AgentProgress {
323
335
  inflightTaskDetails?: TaskToolDetails;
324
336
  }
325
337
 
338
+ /** Bounded diagnostic retained when subagent setup fails before an LLM request starts. */
339
+ export interface SetupFailureSummary {
340
+ summary: string;
341
+ }
342
+
343
+ const SETUP_FAILURE_SUMMARY_MAX_CHARS = 280;
344
+ const SETUP_FAILURE_SUMMARY_MAX_BYTES = 1_024;
345
+ const AUTHORIZATION_HEADER_VALUE_PATTERN = /(["']?(?:Proxy-)?Authorization\b["']?\s*:\s*)[^\r\n]*/gi;
346
+ const COOKIE_HEADER_VALUE_PATTERN = /(["']?(?:Set-)?Cookie\b["']?\s*:\s*)[^\r\n]*/gi;
347
+ const URL_CREDENTIAL_PATTERN = /([a-z][a-z0-9+.-]*:\/\/)[^/?#\s:@]+:[^@/?#\s]+@/gi;
348
+ const API_KEY_LABEL_VALUE_PATTERN = /(["']?api\s+key["']?\s*:\s*)(?:"[^"]*"|'[^']*'|[^\s&]+)/gi;
349
+ const SENSITIVE_SETUP_FAILURE_VALUE_PATTERN =
350
+ /(["']?(?:(?:[A-Za-z][A-Za-z0-9]*[_.-])*?)(?:access[_-]?token|refresh[_-]?token|session[_-]?token|id[_-]?token|api[_-]?key|client[_-]?secret|private[_-]?key|signing[_-]?key|secret|password|passwd|pwd|authorization|credential|token)(?:[_.-][A-Za-z0-9]+)*["']?)(\s*[=:]\s*)(?:"[^"]*"|'[^']*'|[^\s&]+)/gi;
351
+ const BARE_PROVIDER_TOKEN_PATTERN =
352
+ /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{20,}|sk-(?:proj-)?[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|AIza[A-Za-z0-9_-]{35}|AKIA[A-Z0-9]{16})\b/g;
353
+ const LOCAL_ABSOLUTE_PATH_PATTERN = /(^|[\s("'`=])((?:\/(?!\/)[^\s/:?"'`()[\]{},;<>]+){2,})/g;
354
+ const WINDOWS_ABSOLUTE_PATH_PATTERN =
355
+ /(^|[\s("'`=])([A-Za-z]:\\(?:[^\\/:?"'`()[\]{},;<>\s]+\\)+[^\\/:?"'`()[\]{},;<>\s]+)/g;
356
+ const LOCAL_FILE_URI_PATTERN = /\bfile:\/\/(?:localhost)?(?:\/[^\s/:?"'`()[\]{},;<>]+)+/gi;
357
+ const SENSITIVE_PATH_BASENAME_PATTERN =
358
+ /(?:^|[_.-])(?:access[_-]?token|refresh[_-]?token|session[_-]?token|id[_-]?token|api[_-]?key|client[_-]?secret|private[_-]?key|signing[_-]?key|secrets?|password|passwd|pwd|authorization|credential|token)(?:[_.-]|$)|^(?:\.env(?:\..*)?|credentials?(?:\.[A-Za-z0-9_-]+)?|id_(?:rsa|ed25519)|.+\.(?:pem|p12|pfx|key))$|^(?:sk|pk|rk|gh[opsur]|github_pat|xox[baprs])[_-][A-Za-z0-9_-]+$/i;
359
+
360
+ function redactPathBasename(path: string): string {
361
+ const basename = path.slice(Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")) + 1);
362
+ return SENSITIVE_PATH_BASENAME_PATTERN.test(basename) ? "[redacted]" : basename;
363
+ }
364
+
365
+ function redactLocalAbsolutePath(_match: string, prefix: string, absolutePath: string): string {
366
+ return `${prefix}<path>/${redactPathBasename(absolutePath)}`;
367
+ }
368
+
369
+ function redactLocalFileUri(uri: string): string {
370
+ return `file://<path>/${redactPathBasename(uri)}`;
371
+ }
372
+
373
+ function normalizeSetupFailureText(value: string): string {
374
+ return value
375
+ .normalize("NFKC")
376
+ .replace(/\u001B(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007\u001B]*(?:\u0007|\u001B\\))/g, "")
377
+ .replace(/[\u200B-\u200D\u2060\uFEFF]/g, "")
378
+ .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, "")
379
+ .replace(/\t/g, " ");
380
+ }
381
+
382
+ function capSetupFailureSummary(value: string): string {
383
+ const ellipsis = "…";
384
+ const ellipsisBytes = Buffer.byteLength(ellipsis, "utf8");
385
+ const chunks: string[] = [];
386
+ let chars = 0;
387
+ let bytes = 0;
388
+ for (const codePoint of value) {
389
+ const codePointBytes = Buffer.byteLength(codePoint, "utf8");
390
+ if (chars + 1 > SETUP_FAILURE_SUMMARY_MAX_CHARS || bytes + codePointBytes > SETUP_FAILURE_SUMMARY_MAX_BYTES) {
391
+ while (
392
+ chunks.length > 0 &&
393
+ (chars + 1 > SETUP_FAILURE_SUMMARY_MAX_CHARS || bytes + ellipsisBytes > SETUP_FAILURE_SUMMARY_MAX_BYTES)
394
+ ) {
395
+ const removed = chunks.pop()!;
396
+ chars -= 1;
397
+ bytes -= Buffer.byteLength(removed, "utf8");
398
+ }
399
+ return `${chunks.join("")}${ellipsis}`;
400
+ }
401
+ chunks.push(codePoint);
402
+ chars += 1;
403
+ bytes += codePointBytes;
404
+ }
405
+ return chunks.join("");
406
+ }
407
+
408
+ /** Create a bounded diagnostic that preserves a setup failure's cause without exposing credentials or local paths. */
409
+ export function createSetupFailureSummary(error: unknown): SetupFailureSummary {
410
+ const message = normalizeSetupFailureText(error instanceof Error ? error.message : String(error));
411
+ const summary = message
412
+ .replace(AUTHORIZATION_HEADER_VALUE_PATTERN, "$1[redacted]")
413
+ .replace(COOKIE_HEADER_VALUE_PATTERN, "$1[redacted]")
414
+ .replace(URL_CREDENTIAL_PATTERN, "$1[redacted]@")
415
+ .replace(API_KEY_LABEL_VALUE_PATTERN, "$1[redacted]")
416
+ .replace(LOCAL_FILE_URI_PATTERN, redactLocalFileUri)
417
+ .replace(LOCAL_ABSOLUTE_PATH_PATTERN, redactLocalAbsolutePath)
418
+ .replace(WINDOWS_ABSOLUTE_PATH_PATTERN, redactLocalAbsolutePath)
419
+ .replace(SENSITIVE_SETUP_FAILURE_VALUE_PATTERN, "$1$2[redacted]")
420
+ .replace(BARE_PROVIDER_TOKEN_PATTERN, "[redacted]")
421
+ .replace(/\s+/g, " ")
422
+ .trim();
423
+ return {
424
+ summary: capSetupFailureSummary(summary) || "Subagent setup failed.",
425
+ };
426
+ }
427
+
326
428
  /** Result from a single agent execution */
327
429
  export interface SingleResult {
328
430
  index: number;
@@ -346,7 +448,11 @@ export interface SingleResult {
346
448
  contextWindow?: number;
347
449
  modelOverride?: string | string[];
348
450
  modelSubstitutionWarning?: ModelSubstitutionWarning;
451
+ /** Whether the resolved subagent model ran under the effective fast service tier. */
452
+ fastMode?: boolean;
349
453
  error?: string;
454
+ /** Safe diagnostic for a failure before the subagent sent its first LLM request. */
455
+ setupFailure?: SetupFailureSummary;
350
456
  aborted?: boolean;
351
457
  abortReason?: string;
352
458
  paused?: boolean;
@@ -366,6 +472,8 @@ export interface SingleResult {
366
472
  producedChanges?: boolean;
367
473
  /** Data extracted by registered subprocess tool handlers (keyed by tool name) */
368
474
  extractedToolData?: Record<string, unknown[]>;
475
+ /** Full wrapper-owned review evidence, kept separate from caller completion data. */
476
+ reviewFindingsRef?: ReviewFindingsArtifactRef;
369
477
  /**
370
478
  * Terminal retry failure, when the subagent exited because the auto-retry
371
479
  * loop gave up (retry-after exceeded the cap, or all attempts exhausted).
@@ -11,21 +11,27 @@ import { logger } from "@gajae-code/utils";
11
11
  import type { WorkflowGateEmitter } from "../modes/shared/agent-wire/workflow-gate-broker";
12
12
  import type { AskAnswerSource } from "./index";
13
13
 
14
- const sources = new Map<string, AskAnswerSource>();
14
+ const sources = new Map<string, AskAnswerSource[]>();
15
15
  const workflowGateEmitters = new Map<string, WorkflowGateEmitter>();
16
16
  const workflowGateListeners = new Map<string, Set<(emitter: WorkflowGateEmitter | undefined) => void>>();
17
17
 
18
- /** Register `source` for `sessionId`. Returns a disposer that clears it. */
18
+ /** Register `source` for `sessionId`. Returns a disposer that restores the prior source. */
19
19
  export function registerAskAnswerSource(sessionId: string, source: AskAnswerSource): () => void {
20
- sources.set(sessionId, source);
20
+ const stack = sources.get(sessionId) ?? [];
21
+ stack.push(source);
22
+ sources.set(sessionId, stack);
21
23
  return () => {
22
- if (sources.get(sessionId) === source) sources.delete(sessionId);
24
+ const current = sources.get(sessionId);
25
+ if (!current) return;
26
+ const index = current.lastIndexOf(source);
27
+ if (index >= 0) current.splice(index, 1);
28
+ if (current.length === 0) sources.delete(sessionId);
23
29
  };
24
30
  }
25
31
 
26
- /** The answer source for `sessionId`, if one is registered. */
32
+ /** The highest-priority answer source for `sessionId`, if one is registered. */
27
33
  export function getAskAnswerSource(sessionId: string): AskAnswerSource | undefined {
28
- return sources.get(sessionId);
34
+ return sources.get(sessionId)?.at(-1);
29
35
  }
30
36
 
31
37
  /** Publish a session's current workflow-gate emitter after mode initialization. */
package/src/tools/ask.ts CHANGED
@@ -351,15 +351,23 @@ function parseEncodedContainer(value: unknown): unknown {
351
351
  function isRoundZeroRecoveryCandidate(value: unknown): boolean {
352
352
  const root = parseEncodedContainer(value);
353
353
  if (typeof root !== "object" || root === null || !Object.hasOwn(root, "questions")) return false;
354
- const questionsValue = parseEncodedContainer((root as Record<string, unknown>).questions);
354
+ const rawQuestions = (root as Record<string, unknown>).questions;
355
+ const questionsValue = parseEncodedContainer(rawQuestions);
355
356
  if (!Array.isArray(questionsValue)) return questionsValue === null;
357
+ const rootEncoded = typeof value === "string" || typeof rawQuestions === "string";
356
358
  return questionsValue.some(rawQuestion => {
357
359
  const question = parseEncodedContainer(rawQuestion);
358
360
  if (typeof question !== "object" || question === null || !Object.hasOwn(question, "deepInterview")) return false;
359
- const deepInterview = parseEncodedContainer((question as Record<string, unknown>).deepInterview);
361
+ const rawDeepInterview = (question as Record<string, unknown>).deepInterview;
362
+ const deepInterview = parseEncodedContainer(rawDeepInterview);
360
363
  if (typeof deepInterview !== "object" || deepInterview === null) return false;
361
364
  const metadata = deepInterview as Record<string, unknown>;
362
- return Object.hasOwn(metadata, "intent_contract") || Object.hasOwn(metadata, "intent_review");
365
+ const hasContract = Object.hasOwn(metadata, "intent_contract");
366
+ const hasReview = Object.hasOwn(metadata, "intent_review");
367
+ // A JSON-string container is terminal only for the retired contract+review
368
+ // pair; canonical single-sided asks stay eligible for generic JSON coercion.
369
+ const encoded = rootEncoded || typeof rawQuestion === "string" || typeof rawDeepInterview === "string";
370
+ return encoded ? hasContract && hasReview : hasContract || hasReview;
363
371
  });
364
372
  }
365
373
 
@@ -1252,6 +1260,14 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1252
1260
  receipt: AskRemoteReceipt;
1253
1261
  settlement?: AskSettlement;
1254
1262
  };
1263
+ const ignoreRemoteAnswerFailure = (error: unknown): Promise<never> => {
1264
+ if (!(error instanceof Error && error.name === "AbortError")) {
1265
+ logger.warn("Ask remote answer source failed", {
1266
+ error: error instanceof Error ? error.message : String(error),
1267
+ });
1268
+ }
1269
+ return new Promise<never>(() => {});
1270
+ };
1255
1271
  const settleActiveRemote = async (settlement: AskSettlement): Promise<void> => {
1256
1272
  const receipt = activeRemoteReceipt;
1257
1273
  activeRemoteReceipt = undefined;
@@ -1265,9 +1281,11 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1265
1281
  // attended TUI asks would route to emitGate() and hang forever waiting on
1266
1282
  // a remote responder.
1267
1283
  const hasInteractiveUi = context?.hasUI === true && !!context.ui;
1268
- const canUseWorkflowGate = !hasInteractiveUi && gateEmitter?.supportsRemoteGateAnswers() === true;
1269
- // Headless fallback: SDK workflow gates are the non-TUI answer path.
1270
- if (!canUseWorkflowGate && (!context?.hasUI || !context.ui)) {
1284
+ const initialAnswerSource = this.session.getAskAnswerSource?.();
1285
+ const canUseWorkflowGate =
1286
+ !hasInteractiveUi && !initialAnswerSource && gateEmitter?.supportsRemoteGateAnswers() === true;
1287
+ // Headless fallback: an SDK answer source or workflow gate is the non-TUI answer path.
1288
+ if (!hasInteractiveUi && !initialAnswerSource && !canUseWorkflowGate) {
1271
1289
  context?.abort();
1272
1290
  throw new ToolAbortError("Ask tool requires interactive mode");
1273
1291
  }
@@ -1275,9 +1293,11 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1275
1293
  const extensionUi = context?.ui;
1276
1294
  const ui: UIContext = {
1277
1295
  select: (prompt, options, dialogOptions) => {
1278
- if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1279
1296
  const source = this.session.getAskAnswerSource?.();
1280
- if (!source) return extensionUi.select(prompt, options, dialogOptions);
1297
+ if (!source) {
1298
+ if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1299
+ return extensionUi.select(prompt, options, dialogOptions);
1300
+ }
1281
1301
  // Race the local UI against a remote answer (e.g. an SDK reply) so asks
1282
1302
  // can be answered without local UI interaction. The first valid answer
1283
1303
  // wins; the loser is aborted so neither side is left hanging:
@@ -1304,93 +1324,107 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1304
1324
  remoteController.signal,
1305
1325
  )
1306
1326
  : source.awaitAnswer(prompt, options, remoteController.signal)
1307
- ).then((answer): RemoteRaceResult | Promise<RemoteRaceResult> => {
1308
- if (answer === undefined) return new Promise<never>(() => {});
1309
- const receipt = typeof answer === "string" ? legacyAskReceipt(answer) : answer;
1310
- if (generation !== remoteGeneration) {
1311
- return receipt
1312
- .settle({ kind: "resolve_without_commit", reason: "aborted" })
1313
- .then(() => new Promise<never>(() => {}));
1314
- }
1315
- const remoteValue = receipt.interaction.kind === "value" ? receipt.interaction.value : undefined;
1316
- const value = remoteValue ?? REMOTE_NAVIGATION_FORWARD;
1317
- const selectedValue =
1318
- remoteValue === undefined
1319
- ? value
1320
- : (options.find(
1321
- option =>
1322
- option === remoteValue ||
1323
- option === `${theme.checkbox.checked} ${remoteValue}` ||
1324
- option === `${theme.checkbox.unchecked} ${remoteValue}`,
1325
- ) ?? value);
1326
- const normalizedRemoteValue = remoteValue?.startsWith(`${theme.checkbox.checked} `)
1327
- ? remoteValue.slice(`${theme.checkbox.checked} `.length)
1328
- : remoteValue?.startsWith(`${theme.checkbox.unchecked} `)
1329
- ? remoteValue.slice(`${theme.checkbox.unchecked} `.length)
1330
- : remoteValue;
1331
- const semanticRemoteValue = normalizedRemoteValue?.replace(/^\s*\d+[.)]\s+/, "");
1332
- const transitionReason =
1333
- semanticRemoteValue === OTHER_OPTION
1334
- ? "other_transition"
1335
- : semanticRemoteValue === ASK_CLARIFICATION_OPTION
1336
- ? "clarification_transition"
1337
- : undefined;
1338
- if (transitionReason) {
1339
- return {
1340
- winner: "remote" as const,
1341
- value: selectedValue,
1342
- receipt,
1343
- settlement: { kind: "resolve_without_commit", reason: transitionReason },
1344
- };
1345
- }
1346
- if (
1347
- remoteValue !== undefined &&
1348
- activeRemoteRequest?.interaction === "selector" &&
1349
- activeRemoteRequest.controls.length > 0 &&
1350
- activeRemoteRequest.options.includes(remoteValue)
1351
- ) {
1352
- return {
1353
- winner: "remote" as const,
1354
- value: selectedValue,
1355
- receipt,
1356
- settlement: { kind: "resolve_without_commit", reason: "toggle" },
1357
- };
1358
- }
1359
- return { winner: "remote" as const, value: selectedValue, receipt };
1360
- });
1361
-
1362
- const local = extensionUi
1363
- .select(prompt, options, { ...dialogOptions, signal: localController.signal })
1364
- .then(answer => {
1365
- if (generation === remoteGeneration) remoteGeneration++;
1366
- remoteController.abort();
1367
- return { winner: "local" as const, value: answer };
1327
+ )
1328
+ .then((answer): RemoteRaceResult | Promise<RemoteRaceResult> => {
1329
+ if (answer === undefined) {
1330
+ if (!extensionUi) throw new ToolAbortError("Ask was cancelled by the remote client");
1331
+ return new Promise<never>(() => {});
1332
+ }
1333
+ const receipt = typeof answer === "string" ? legacyAskReceipt(answer) : answer;
1334
+ if (generation !== remoteGeneration) {
1335
+ return receipt
1336
+ .settle({ kind: "resolve_without_commit", reason: "aborted" })
1337
+ .then(() => new Promise<never>(() => {}));
1338
+ }
1339
+ const remoteValue = receipt.interaction.kind === "value" ? receipt.interaction.value : undefined;
1340
+ const value = remoteValue ?? REMOTE_NAVIGATION_FORWARD;
1341
+ const selectedValue =
1342
+ remoteValue === undefined
1343
+ ? value
1344
+ : (options.find(
1345
+ option =>
1346
+ option === remoteValue ||
1347
+ option === `${theme.checkbox.checked} ${remoteValue}` ||
1348
+ option === `${theme.checkbox.unchecked} ${remoteValue}`,
1349
+ ) ?? value);
1350
+ const normalizedRemoteValue = remoteValue?.startsWith(`${theme.checkbox.checked} `)
1351
+ ? remoteValue.slice(`${theme.checkbox.checked} `.length)
1352
+ : remoteValue?.startsWith(`${theme.checkbox.unchecked} `)
1353
+ ? remoteValue.slice(`${theme.checkbox.unchecked} `.length)
1354
+ : remoteValue;
1355
+ const semanticRemoteValue = normalizedRemoteValue?.replace(/^\s*\d+[.)]\s+/, "");
1356
+ const transitionReason =
1357
+ semanticRemoteValue === OTHER_OPTION
1358
+ ? "other_transition"
1359
+ : semanticRemoteValue === ASK_CLARIFICATION_OPTION
1360
+ ? "clarification_transition"
1361
+ : undefined;
1362
+ if (transitionReason) {
1363
+ return {
1364
+ winner: "remote" as const,
1365
+ value: selectedValue,
1366
+ receipt,
1367
+ settlement: { kind: "resolve_without_commit", reason: transitionReason },
1368
+ };
1369
+ }
1370
+ if (
1371
+ remoteValue !== undefined &&
1372
+ activeRemoteRequest?.interaction === "selector" &&
1373
+ activeRemoteRequest.controls.length > 0 &&
1374
+ activeRemoteRequest.options.includes(remoteValue)
1375
+ ) {
1376
+ return {
1377
+ winner: "remote" as const,
1378
+ value: selectedValue,
1379
+ receipt,
1380
+ settlement: { kind: "resolve_without_commit", reason: "toggle" },
1381
+ };
1382
+ }
1383
+ return { winner: "remote" as const, value: selectedValue, receipt };
1368
1384
  })
1369
1385
  .catch(error => {
1370
- if (generation === remoteGeneration) remoteGeneration++;
1371
- remoteController.abort();
1372
- throw error;
1386
+ if (!extensionUi) throw error;
1387
+ return ignoreRemoteAnswerFailure(error);
1373
1388
  });
1389
+
1390
+ const local = extensionUi
1391
+ ? extensionUi
1392
+ .select(prompt, options, { ...dialogOptions, signal: localController.signal })
1393
+ .then(answer => {
1394
+ if (generation === remoteGeneration) remoteGeneration++;
1395
+ remoteController.abort();
1396
+ return { winner: "local" as const, value: answer };
1397
+ })
1398
+ .catch(error => {
1399
+ if (generation === remoteGeneration) remoteGeneration++;
1400
+ remoteController.abort();
1401
+ throw error;
1402
+ })
1403
+ : new Promise<never>(() => {});
1374
1404
  // The losing selector may reject when aborted after the race already settled;
1375
1405
  // swallow that so it is not an unhandled rejection (the race result is unaffected).
1376
1406
  void local.catch(() => undefined);
1377
- return Promise.race([local, remote]).then(async result => {
1378
- if (result.winner === "remote") {
1379
- localController.abort();
1380
- if (result.settlement) await result.receipt.settle(result.settlement);
1381
- else activeRemoteReceipt = result.receipt;
1382
- } else {
1383
- void remote.then(remoteResult =>
1384
- remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1385
- );
1386
- }
1387
- return result.value;
1388
- });
1407
+ return Promise.race([local, remote])
1408
+ .then(async result => {
1409
+ if (result.winner === "remote") {
1410
+ localController.abort();
1411
+ if (result.settlement) await result.receipt.settle(result.settlement);
1412
+ else activeRemoteReceipt = result.receipt;
1413
+ } else {
1414
+ void remote.then(remoteResult =>
1415
+ remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1416
+ );
1417
+ }
1418
+ return result.value;
1419
+ })
1420
+ .finally(() => toolSignal?.removeEventListener("abort", abortRace));
1389
1421
  },
1390
1422
  editor: (title, prefill, dialogOptions, editorOptions) => {
1391
- if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1392
1423
  const source = this.session.getAskAnswerSource?.();
1393
- if (!source) return extensionUi.editor(title, prefill, dialogOptions, editorOptions);
1424
+ if (!source) {
1425
+ if (!extensionUi) throw new ToolAbortError("Ask tool requires interactive mode");
1426
+ return extensionUi.editor(title, prefill, dialogOptions, editorOptions);
1427
+ }
1394
1428
  // Race the local editor against a remote free-text answer so "Other / type
1395
1429
  // your own" custom input can be provided remotely (e.g. a typed Telegram
1396
1430
  // reply) instead of blocking on the local-only editor. Mirrors `select`.
@@ -1419,42 +1453,59 @@ export class AskTool implements AgentTool<AskParametersSchema, AskToolDetails> {
1419
1453
  remoteController.signal,
1420
1454
  )
1421
1455
  : source.awaitAnswer(title, [], remoteController.signal)
1422
- ).then((answer): RemoteRaceResult | Promise<RemoteRaceResult> => {
1423
- if (answer === undefined) return new Promise<never>(() => {});
1424
- const receipt = typeof answer === "string" ? legacyAskReceipt(answer) : answer;
1425
- if (generation !== remoteGeneration) {
1426
- return receipt
1427
- .settle({ kind: "resolve_without_commit", reason: "aborted" })
1428
- .then(() => new Promise<never>(() => {}));
1429
- }
1430
- const value =
1431
- receipt.interaction.kind === "control" ? REMOTE_NAVIGATION_FORWARD : receipt.interaction.value;
1432
- return { winner: "remote" as const, value, receipt };
1433
- });
1434
- const local = extensionUi
1435
- .editor(title, prefill, { ...(dialogOptions ?? {}), signal: localController.signal }, editorOptions)
1436
- .then(answer => {
1437
- if (generation === remoteGeneration) remoteGeneration++;
1438
- remoteController.abort();
1439
- return { winner: "local" as const, value: answer };
1456
+ )
1457
+ .then((answer): RemoteRaceResult | Promise<RemoteRaceResult> => {
1458
+ if (answer === undefined) {
1459
+ if (!extensionUi) throw new ToolAbortError("Ask was cancelled by the remote client");
1460
+ return new Promise<never>(() => {});
1461
+ }
1462
+ const receipt = typeof answer === "string" ? legacyAskReceipt(answer) : answer;
1463
+ if (generation !== remoteGeneration) {
1464
+ return receipt
1465
+ .settle({ kind: "resolve_without_commit", reason: "aborted" })
1466
+ .then(() => new Promise<never>(() => {}));
1467
+ }
1468
+ const value =
1469
+ receipt.interaction.kind === "control" ? REMOTE_NAVIGATION_FORWARD : receipt.interaction.value;
1470
+ return { winner: "remote" as const, value, receipt };
1440
1471
  })
1441
1472
  .catch(error => {
1442
- if (generation === remoteGeneration) remoteGeneration++;
1443
- remoteController.abort();
1444
- throw error;
1473
+ if (!extensionUi) throw error;
1474
+ return ignoreRemoteAnswerFailure(error);
1445
1475
  });
1476
+ const local = extensionUi
1477
+ ? extensionUi
1478
+ .editor(
1479
+ title,
1480
+ prefill,
1481
+ { ...(dialogOptions ?? {}), signal: localController.signal },
1482
+ editorOptions,
1483
+ )
1484
+ .then(answer => {
1485
+ if (generation === remoteGeneration) remoteGeneration++;
1486
+ remoteController.abort();
1487
+ return { winner: "local" as const, value: answer };
1488
+ })
1489
+ .catch(error => {
1490
+ if (generation === remoteGeneration) remoteGeneration++;
1491
+ remoteController.abort();
1492
+ throw error;
1493
+ })
1494
+ : new Promise<never>(() => {});
1446
1495
  void local.catch(() => undefined);
1447
- return Promise.race([local, remote]).then(result => {
1448
- if (result.winner === "remote") {
1449
- activeRemoteReceipt = result.receipt;
1450
- localController.abort();
1451
- } else {
1452
- void remote.then(remoteResult =>
1453
- remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1454
- );
1455
- }
1456
- return result.value;
1457
- });
1496
+ return Promise.race([local, remote])
1497
+ .then(result => {
1498
+ if (result.winner === "remote") {
1499
+ activeRemoteReceipt = result.receipt;
1500
+ localController.abort();
1501
+ } else {
1502
+ void remote.then(remoteResult =>
1503
+ remoteResult.receipt.settle({ kind: "resolve_without_commit", reason: "aborted" }),
1504
+ );
1505
+ }
1506
+ return result.value;
1507
+ })
1508
+ .finally(() => toolSignal?.removeEventListener("abort", abortRace));
1458
1509
  },
1459
1510
  };
1460
1511
 
@@ -205,6 +205,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
205
205
  rawPaths: params.paths,
206
206
  cwd: this.session.cwd,
207
207
  getArtifactsDir: this.session.getArtifactsDir,
208
+ getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
208
209
  internalUrlAction: "rewrite",
209
210
  });
210
211
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
@@ -152,6 +152,7 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
152
152
  rawPaths: params.paths,
153
153
  cwd: this.session.cwd,
154
154
  getArtifactsDir: this.session.getArtifactsDir,
155
+ getAuthorizedArtifactsDirs: this.session.getAuthorizedArtifactsDirs,
155
156
  internalUrlAction: "search",
156
157
  });
157
158
  const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
@@ -15,9 +15,9 @@ import type { Terminal as XtermTerminalType } from "@xterm/headless";
15
15
  import { Settings } from "../config/settings";
16
16
  import { NON_INTERACTIVE_ENV } from "../exec/non-interactive-env";
17
17
  import type { Theme } from "../modes/theme/theme";
18
- import { OutputSink, type OutputSummary } from "../session/streaming-output";
18
+ import { OutputSink, type OutputSummary, type TerminalArtifactPublisher } from "../session/streaming-output";
19
19
  import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
20
- import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "./output-meta";
20
+ import { resolveBashOutputSinkHeadBytes, resolveBashOutputSinkTailBytes, resolveOutputMaxColumns } from "./output-meta";
21
21
  import { formatStatusIcon, replaceTabs } from "./render-utils";
22
22
 
23
23
  export interface BashInteractiveResult extends OutputSummary {
@@ -293,6 +293,9 @@ export async function runInteractiveBashPty(
293
293
  env?: Record<string, string>;
294
294
  artifactPath?: string;
295
295
  artifactId?: string;
296
+ artifactPublisher?: TerminalArtifactPublisher;
297
+ spillThreshold?: number;
298
+ headBytes?: number;
296
299
  },
297
300
  ): Promise<BashInteractiveResult> {
298
301
  const settings = await Settings.init();
@@ -300,7 +303,9 @@ export async function runInteractiveBashPty(
300
303
  const sink = new OutputSink({
301
304
  artifactPath: options.artifactPath,
302
305
  artifactId: options.artifactId,
303
- headBytes: resolveOutputSinkHeadBytes(settings),
306
+ artifactPublisher: options.artifactPublisher,
307
+ spillThreshold: options.spillThreshold ?? resolveBashOutputSinkTailBytes(settings),
308
+ headBytes: options.headBytes ?? resolveBashOutputSinkHeadBytes(settings),
304
309
  maxColumns: resolveOutputMaxColumns(settings),
305
310
  });
306
311
  const { default: xterm } = await import("@xterm/headless");