@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
@@ -16,7 +16,15 @@ import { getDefault, type Settings } from "../config/settings";
16
16
  import { formatGroupedDiagnosticMessages } from "../lsp/utils";
17
17
  import type { Theme } from "../modes/theme/theme";
18
18
  import { sessionArtifactCapability } from "../session/session-manager";
19
- import { type OutputSummary, type TruncationResult, truncateMiddle, truncateTail } from "../session/streaming-output";
19
+ import {
20
+ formatMiddleElisionMarker,
21
+ type OutputSummary,
22
+ type ReadWindow,
23
+ type TruncationResult,
24
+ truncateMiddle,
25
+ truncateTail,
26
+ } from "../session/streaming-output";
27
+
20
28
  import { formatBytes, wrapBrackets } from "./render-utils";
21
29
  import { renderError } from "./tool-errors";
22
30
 
@@ -25,6 +33,9 @@ import { renderError } from "./tool-errors";
25
33
  */
26
34
  export interface TruncationMeta {
27
35
  direction: "head" | "tail" | "middle";
36
+ /** Coordinate basis for shown/head/tail ranges. Omitted means file coordinates; "window" is the selected-range coordinate system. */
37
+ rangeBase?: "file" | "window";
38
+ noticeOwner?: "body";
28
39
  truncatedBy: "lines" | "bytes" | "middle";
29
40
  totalLines: number;
30
41
  totalBytes: number;
@@ -36,12 +47,18 @@ export interface TruncationMeta {
36
47
  /** Head/tail line ranges shown when direction === "middle". */
37
48
  headRange?: { start: number; end: number };
38
49
  tailRange?: { start: number; end: number };
50
+ /** Partial source-line preview retained by a directional window. */
51
+ partialLine?: { line: number; bytes: number; sourceBytes: number };
39
52
  /** Bytes elided from the middle. */
40
53
  elidedBytes?: number;
41
54
  /** Lines elided from the middle. */
42
55
  elidedLines?: number;
43
- /** Artifact ID if full output was saved */
56
+ /** Artifact ID when output was persisted; completeness is tracked separately below. */
44
57
  artifactId?: string;
58
+ /** Bytes omitted from an artifact after its hard storage cap was reached. */
59
+ artifactTruncatedBytes?: number;
60
+ /** Bounded diagnostic when artifact writer creation, write, or finalization failed. */
61
+ artifactFailureDiagnostic?: string;
45
62
  /** Next offset for pagination (head truncation only) */
46
63
  nextOffset?: number;
47
64
  }
@@ -91,12 +108,15 @@ export interface TruncationOptions {
91
108
  startLine?: number;
92
109
  totalFileLines?: number;
93
110
  artifactId?: string;
111
+ maxBytes?: number;
112
+ noticeOwner?: "body";
94
113
  }
95
114
 
96
115
  export interface TruncationSummaryOptions {
97
116
  direction: "head" | "tail" | "middle";
98
117
  startLine?: number;
99
118
  totalFileLines?: number;
119
+ noticeOwner?: "body";
100
120
  }
101
121
 
102
122
  export interface TruncationTextOptions {
@@ -105,6 +125,7 @@ export interface TruncationTextOptions {
105
125
  totalBytes?: number;
106
126
  maxBytes?: number;
107
127
  artifactId?: string;
128
+ noticeOwner?: "body";
108
129
  }
109
130
 
110
131
  /**
@@ -126,7 +147,7 @@ export class OutputMetaBuilder {
126
147
  truncation(result: TruncationResult, options: TruncationOptions): this {
127
148
  if (!result.truncated) return this;
128
149
 
129
- const { direction, startLine = 1, totalFileLines, artifactId } = options;
150
+ const { direction, startLine = 1, totalFileLines, artifactId, noticeOwner, maxBytes } = options;
130
151
  const outputLines = result.outputLines ?? result.totalLines;
131
152
  const outputBytes = result.outputBytes ?? result.totalBytes;
132
153
  const isMiddle = direction === "middle" || result.truncatedBy === "middle";
@@ -137,6 +158,7 @@ export class OutputMetaBuilder {
137
158
  : "bytes";
138
159
 
139
160
  const effectiveTotalLines = totalFileLines ?? result.totalLines;
161
+ const owner = noticeOwner !== undefined ? { noticeOwner } : {};
140
162
 
141
163
  if (isMiddle) {
142
164
  const elidedLines = result.elidedLines ?? Math.max(0, effectiveTotalLines - outputLines);
@@ -150,10 +172,12 @@ export class OutputMetaBuilder {
150
172
  this.#meta.truncation = {
151
173
  direction: "middle",
152
174
  truncatedBy: "middle",
175
+ ...owner,
153
176
  totalLines: effectiveTotalLines,
154
177
  totalBytes: result.totalBytes,
155
178
  outputLines,
156
179
  outputBytes,
180
+ ...(maxBytes !== undefined ? { maxBytes } : {}),
157
181
  headRange: headLines > 0 ? { start: 1, end: headLines } : undefined,
158
182
  tailRange:
159
183
  tailLines > 0 ? { start: effectiveTotalLines - tailLines + 1, end: effectiveTotalLines } : undefined,
@@ -178,10 +202,12 @@ export class OutputMetaBuilder {
178
202
  this.#meta.truncation = {
179
203
  direction,
180
204
  truncatedBy,
205
+ ...owner,
181
206
  totalLines: effectiveTotalLines,
182
207
  totalBytes: result.totalBytes,
183
208
  outputLines,
184
209
  outputBytes,
210
+ ...(maxBytes !== undefined ? { maxBytes } : {}),
185
211
  shownRange: { start: shownStart, end: shownEnd },
186
212
  artifactId,
187
213
  nextOffset: direction === "head" ? shownEnd + 1 : undefined,
@@ -190,12 +216,141 @@ export class OutputMetaBuilder {
190
216
  return this;
191
217
  }
192
218
 
193
- /** Add truncation info from OutputSummary. No-op if not truncated. */
194
- truncationFromSummary(summary: OutputSummary, options: TruncationSummaryOptions): this {
195
- if (!summary.truncated) return this;
219
+ /** Add metadata from the actual head/tail windows retained by middle truncation. */
220
+ truncationWindows(
221
+ windows: ReadWindow,
222
+ options: {
223
+ artifactId?: string;
224
+ noticeOwner?: "body";
225
+ maxBytes?: number;
226
+ rangeBase?: "file" | "window";
227
+ } = {},
228
+ ): this {
229
+ if (windows.kind === "full") return this;
230
+
231
+ const { artifactId, noticeOwner, rangeBase } = options;
232
+ const maxBytes = options.maxBytes ?? (windows as ReadWindow & { maxBytes?: number }).maxBytes;
233
+ const outputLinesOverride = (windows as ReadWindow & { outputLinesOverride?: number }).outputLinesOverride;
234
+ const outputBytesOverride = (windows as ReadWindow & { outputBytesOverride?: number }).outputBytesOverride;
235
+ const owner = noticeOwner !== undefined ? { noticeOwner } : {};
236
+ const rangeBaseMeta = rangeBase !== undefined ? { rangeBase } : {};
237
+ const hiddenReason = (windows as ReadWindow & { truncatedBy?: "lines" | "bytes" | "middle" }).truncatedBy;
238
+ const fallbackTruncatedBy: "lines" | "bytes" = hiddenReason === "bytes" ? "bytes" : "lines";
239
+ const partialTail = windows.tail?.kind === "partial-line" ? windows.tail : undefined;
240
+ if (partialTail) {
241
+ const headBytes = windows.head?.bytes ?? 0;
242
+ const separatorBytes = windows.head ? 1 : 0;
243
+ const markerBytes =
244
+ windows.elidedLines > 0
245
+ ? Buffer.byteLength(formatMiddleElisionMarker(windows.elidedLines, windows.elidedBytes), "utf-8") + 1
246
+ : 0;
247
+ this.#meta.truncation = {
248
+ direction: windows.kind === "tail-only" ? "tail" : "middle",
249
+ truncatedBy: "bytes",
250
+ ...owner,
251
+ ...rangeBaseMeta,
252
+ totalLines: windows.totalLines,
253
+ totalBytes: windows.totalBytes,
254
+ outputLines:
255
+ outputLinesOverride ??
256
+ (windows.head
257
+ ? windows.head.lines + partialTail.lines + (windows.elidedLines > 0 ? 1 : 0)
258
+ : partialTail.lines),
259
+ outputBytes: outputBytesOverride ?? headBytes + partialTail.bytes + separatorBytes + markerBytes,
260
+ ...(maxBytes !== undefined ? { maxBytes } : {}),
261
+ partialLine: {
262
+ line: partialTail.origin.startLine,
263
+ bytes: partialTail.bytes,
264
+ sourceBytes: partialTail.sourceLineBytes,
265
+ },
266
+ artifactId,
267
+ };
268
+ return this;
269
+ }
196
270
 
197
- const { direction, startLine = 1, totalFileLines } = options;
271
+ if (windows.kind === "head-only" && windows.head) {
272
+ const { head } = windows;
273
+ this.#meta.truncation = {
274
+ direction: "head",
275
+ truncatedBy: fallbackTruncatedBy,
276
+ ...owner,
277
+ ...rangeBaseMeta,
278
+ totalLines: windows.totalLines,
279
+ totalBytes: windows.totalBytes,
280
+ outputLines: outputLinesOverride ?? head.lines,
281
+ outputBytes: outputBytesOverride ?? head.bytes,
282
+ ...(maxBytes !== undefined ? { maxBytes } : {}),
283
+ shownRange: { start: head.origin.startLine, end: head.origin.endLine },
284
+ artifactId,
285
+ nextOffset: head.origin.endLine + 1,
286
+ };
287
+ return this;
288
+ }
289
+
290
+ if (windows.kind === "tail-only" && windows.tail) {
291
+ const { tail } = windows;
292
+ this.#meta.truncation = {
293
+ direction: "tail",
294
+ truncatedBy: fallbackTruncatedBy,
295
+ ...owner,
296
+ ...rangeBaseMeta,
297
+ totalLines: windows.totalLines,
298
+ totalBytes: windows.totalBytes,
299
+ outputLines: outputLinesOverride ?? tail.lines,
300
+ outputBytes: outputBytesOverride ?? tail.bytes,
301
+ ...(maxBytes !== undefined ? { maxBytes } : {}),
302
+ shownRange: { start: tail.origin.startLine, end: tail.origin.endLine },
303
+ artifactId,
304
+ };
305
+ return this;
306
+ }
307
+
308
+ const { head, tail } = windows;
309
+ if (!head || !tail) return this;
310
+ const marker = formatMiddleElisionMarker(windows.elidedLines, windows.elidedBytes);
311
+ this.#meta.truncation = {
312
+ direction: "middle",
313
+ truncatedBy: "middle",
314
+ ...owner,
315
+ ...rangeBaseMeta,
316
+ totalLines: windows.totalLines,
317
+ totalBytes: windows.totalBytes,
318
+ outputLines: head.lines + tail.lines + 1,
319
+ outputBytes: head.bytes + tail.bytes + Buffer.byteLength(marker, "utf-8") + 2,
320
+ ...(maxBytes !== undefined ? { maxBytes } : {}),
321
+ headRange: { start: head.origin.startLine, end: head.origin.endLine },
322
+ tailRange: { start: tail.origin.startLine, end: tail.origin.endLine },
323
+ elidedLines: windows.elidedLines,
324
+ elidedBytes: windows.elidedBytes,
325
+ artifactId,
326
+ };
327
+ return this;
328
+ }
329
+
330
+ /** Add truncation info from OutputSummary. No-op if not truncated or artifact evidence is absent. */
331
+ truncationFromSummary(summary: OutputSummary, options: TruncationSummaryOptions): this {
332
+ const artifactFailureDiagnostic = summary.artifactFailureDiagnostic;
333
+ const artifactTruncatedBytes =
334
+ summary.artifactTruncatedBytes != null && summary.artifactTruncatedBytes > 0
335
+ ? summary.artifactTruncatedBytes
336
+ : undefined;
337
+ const hasArtifactEvidence =
338
+ summary.artifactId !== undefined ||
339
+ artifactFailureDiagnostic !== undefined ||
340
+ artifactTruncatedBytes !== undefined;
341
+ if (!summary.truncated && !hasArtifactEvidence) return this;
342
+
343
+ const { direction, startLine = 1, totalFileLines, noticeOwner } = options;
198
344
  const totalLines = totalFileLines ?? summary.totalLines;
345
+ const bodyHasArtifact =
346
+ summary.artifactId !== undefined && summary.output.includes(`artifact://${summary.artifactId}`);
347
+ const bodyOwnsArtifact =
348
+ bodyHasArtifact &&
349
+ (artifactTruncatedBytes === undefined ||
350
+ (summary.artifactId !== undefined &&
351
+ summary.output.includes(formatArtifactReference(summary.artifactId, artifactTruncatedBytes))));
352
+ const owner =
353
+ noticeOwner !== undefined ? { noticeOwner } : bodyOwnsArtifact ? { noticeOwner: "body" as const } : {};
199
354
 
200
355
  // Middle elision: the sink retained head + tail with an elision marker.
201
356
  if (summary.elidedBytes != null && summary.elidedBytes > 0) {
@@ -206,6 +361,7 @@ export class OutputMetaBuilder {
206
361
  this.#meta.truncation = {
207
362
  direction: "middle",
208
363
  truncatedBy: "middle",
364
+ ...owner,
209
365
  totalLines,
210
366
  totalBytes: summary.totalBytes,
211
367
  outputLines: summary.outputLines,
@@ -215,6 +371,8 @@ export class OutputMetaBuilder {
215
371
  elidedBytes: summary.elidedBytes,
216
372
  elidedLines,
217
373
  artifactId: summary.artifactId,
374
+ artifactTruncatedBytes,
375
+ artifactFailureDiagnostic,
218
376
  };
219
377
  return this;
220
378
  }
@@ -240,12 +398,15 @@ export class OutputMetaBuilder {
240
398
  this.#meta.truncation = {
241
399
  direction,
242
400
  truncatedBy,
401
+ ...owner,
243
402
  totalLines,
244
403
  totalBytes: summary.totalBytes,
245
404
  outputLines: summary.outputLines,
246
405
  outputBytes: summary.outputBytes,
247
406
  shownRange: { start: shownStart, end: shownEnd },
248
407
  artifactId: summary.artifactId,
408
+ artifactTruncatedBytes,
409
+ artifactFailureDiagnostic,
249
410
  nextOffset: direction === "head" ? shownEnd + 1 : undefined,
250
411
  };
251
412
 
@@ -285,6 +446,7 @@ export class OutputMetaBuilder {
285
446
  this.#meta.truncation = {
286
447
  direction: options.direction,
287
448
  truncatedBy,
449
+ ...(options.noticeOwner !== undefined ? { noticeOwner: options.noticeOwner } : {}),
288
450
  totalLines,
289
451
  totalBytes,
290
452
  outputLines,
@@ -387,7 +549,56 @@ export function formatFullOutputReference(artifactId: string): string {
387
549
  return `Read artifact://${artifactId} for full output`;
388
550
  }
389
551
 
552
+ /**
553
+ * Format an artifact reference without claiming completeness when storage was hard-capped.
554
+ */
555
+ export function formatArtifactReference(artifactId: string, artifactTruncatedBytes?: number): string {
556
+ if (artifactTruncatedBytes != null && artifactTruncatedBytes > 0) {
557
+ return `Read artifact://${artifactId} for retained output (at least ${formatBytes(artifactTruncatedBytes)} omitted by the artifact storage cap)`;
558
+ }
559
+ return formatFullOutputReference(artifactId);
560
+ }
561
+
562
+ function formatTruncationArtifactNotice(truncation: TruncationMeta): string {
563
+ const reference = truncation.artifactId
564
+ ? formatArtifactReference(truncation.artifactId, truncation.artifactTruncatedBytes)
565
+ : undefined;
566
+ if (truncation.artifactFailureDiagnostic) {
567
+ const failure = `Artifact storage failed: ${truncation.artifactFailureDiagnostic}`;
568
+ if (reference) return `${reference}; ${failure}`;
569
+ return failure;
570
+ }
571
+ if (reference) return reference;
572
+ return `Artifact storage omitted at least ${formatBytes(truncation.artifactTruncatedBytes ?? 0)}; no artifact reference is available`;
573
+ }
574
+
575
+ function hasArtifactNotice(truncation: TruncationMeta): boolean {
576
+ return (
577
+ truncation.artifactId != null ||
578
+ (truncation.artifactTruncatedBytes ?? 0) > 0 ||
579
+ truncation.artifactFailureDiagnostic != null
580
+ );
581
+ }
582
+
583
+ function formatTruncationRangeTotal(truncation: TruncationMeta): string {
584
+ return truncation.rangeBase === "window"
585
+ ? `the selected ${truncation.totalLines}-line range`
586
+ : `${truncation.totalLines}`;
587
+ }
588
+
390
589
  export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
590
+ const rangeTotal = formatTruncationRangeTotal(truncation);
591
+ if (truncation.partialLine) {
592
+ let notice = `Showing last ${formatBytes(truncation.partialLine.bytes)} of line ${truncation.partialLine.line} of ${rangeTotal}`;
593
+ if (truncation.partialLine.sourceBytes > truncation.partialLine.bytes) {
594
+ notice += ` (line is ${formatBytes(truncation.partialLine.sourceBytes)})`;
595
+ }
596
+ if (hasArtifactNotice(truncation)) {
597
+ notice += `. ${formatTruncationArtifactNotice(truncation)}`;
598
+ }
599
+ return notice;
600
+ }
601
+
391
602
  let notice: string;
392
603
 
393
604
  if (truncation.direction === "middle") {
@@ -398,22 +609,24 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
398
609
  const elidedLines = truncation.elidedLines ?? Math.max(0, totalLines - truncation.outputLines);
399
610
  const headPart = head ? `lines ${head.start}-${head.end}` : "";
400
611
  const tailPart = tail ? `${tail.start}-${tail.end}` : "";
401
- if (headPart && tailPart) {
402
- notice = `Showing ${headPart} and ${tailPart} of ${totalLines}; ${elidedLines.toLocaleString()} middle line${elidedLines === 1 ? "" : "s"} (${formatBytes(elidedBytes)}) elided`;
612
+ if (head && tail && elidedLines === 0 && head.start === tail.start && head.end === tail.end) {
613
+ notice = `Showing head and tail of line ${head.start} of ${rangeTotal}; ${formatBytes(elidedBytes)} middle bytes elided`;
614
+ } else if (headPart && tailPart) {
615
+ notice = `Showing ${headPart} and ${tailPart} of ${rangeTotal}; ${elidedLines.toLocaleString()} middle line${elidedLines === 1 ? "" : "s"} (${formatBytes(elidedBytes)}) elided`;
403
616
  } else {
404
- notice = `Showing ${truncation.outputLines} of ${totalLines} lines; middle elided`;
617
+ notice = `Showing ${truncation.outputLines} of ${rangeTotal}${truncation.rangeBase === "window" ? "" : " lines"}; ${formatBytes(elidedBytes)} middle bytes elided`;
405
618
  }
406
- if (truncation.artifactId != null) {
407
- notice += `. ${formatFullOutputReference(truncation.artifactId)}`;
619
+ if (hasArtifactNotice(truncation)) {
620
+ notice += `. ${formatTruncationArtifactNotice(truncation)}`;
408
621
  }
409
622
  return notice;
410
623
  }
411
624
 
412
625
  const range = truncation.shownRange;
413
626
  if (range && range.end >= range.start) {
414
- notice = `Showing lines ${range.start}-${range.end} of ${truncation.totalLines}`;
627
+ notice = `Showing lines ${range.start}-${range.end} of ${rangeTotal}`;
415
628
  } else {
416
- notice = `Showing ${truncation.outputLines} of ${truncation.totalLines} lines`;
629
+ notice = `Showing ${truncation.outputLines} of ${rangeTotal}${truncation.rangeBase === "window" ? "" : " lines"}`;
417
630
  }
418
631
 
419
632
  if (truncation.truncatedBy === "bytes") {
@@ -425,8 +638,8 @@ export function formatTruncationMetaNotice(truncation: TruncationMeta): string {
425
638
  notice += `. Use :${truncation.nextOffset} to continue`;
426
639
  }
427
640
 
428
- if (truncation.artifactId != null) {
429
- notice += `. ${formatFullOutputReference(truncation.artifactId)}`;
641
+ if (hasArtifactNotice(truncation)) {
642
+ notice += `. ${formatTruncationArtifactNotice(truncation)}`;
430
643
  }
431
644
 
432
645
  return notice;
@@ -450,7 +663,7 @@ export function formatOutputNotice(meta: OutputMeta | undefined): string {
450
663
  const parts: string[] = [];
451
664
 
452
665
  // Truncation notice
453
- if (meta.truncation) {
666
+ if (meta.truncation && meta.truncation.noticeOwner !== "body") {
454
667
  parts.push(formatTruncationMetaNotice(meta.truncation));
455
668
  }
456
669
 
@@ -487,8 +700,25 @@ export function formatOutputNotice(meta: OutputMeta | undefined): string {
487
700
  * Returns null if no truncation metadata present.
488
701
  */
489
702
  export function formatStyledTruncationWarning(meta: OutputMeta | undefined, theme: Theme): string | null {
490
- if (!meta?.truncation) return null;
491
- const message = formatTruncationMetaNotice(meta.truncation);
703
+ const truncation = meta?.truncation;
704
+ if (!truncation) return null;
705
+ if (
706
+ truncation.noticeOwner === "body" &&
707
+ truncation.totalBytes === truncation.outputBytes &&
708
+ truncation.totalLines === truncation.outputLines
709
+ ) {
710
+ return null;
711
+ }
712
+ const warningMeta =
713
+ truncation.noticeOwner === "body"
714
+ ? {
715
+ ...truncation,
716
+ artifactId: undefined,
717
+ artifactTruncatedBytes: undefined,
718
+ artifactFailureDiagnostic: undefined,
719
+ }
720
+ : truncation;
721
+ const message = formatTruncationMetaNotice(warningMeta);
492
722
  return theme.fg("warning", wrapBrackets(message, theme));
493
723
  }
494
724
 
@@ -580,6 +810,25 @@ export function resolveOutputSinkHeadBytes(s: Settings | undefined): number {
580
810
  return getSpillConfig(s).headBytes;
581
811
  }
582
812
 
813
+ export const BASH_DEFAULT_OUTPUT_TAIL_BYTES = 1024;
814
+
815
+ /**
816
+ * Bash uses a deliberately small tail window to nudge callers toward focused
817
+ * commands and dedicated search tools. An explicitly configured shared tail
818
+ * budget still wins.
819
+ */
820
+ export function resolveBashOutputSinkTailBytes(s: Settings): number {
821
+ return s.has("tools.artifactTailBytes") ? s.get("tools.artifactTailBytes") * 1024 : BASH_DEFAULT_OUTPUT_TAIL_BYTES;
822
+ }
823
+ /**
824
+ * Bash keeps only the tail unless the user explicitly opts into the shared
825
+ * head-retention setting. Schema defaults still apply to other streaming
826
+ * tools without silently turning Bash back into middle-elision mode.
827
+ */
828
+ export function resolveBashOutputSinkHeadBytes(s: Settings): number {
829
+ return s.has("tools.artifactHeadBytes") ? resolveOutputSinkHeadBytes(s) : 0;
830
+ }
831
+
583
832
  /**
584
833
  * Resolve the per-line column cap from session settings. Shared by streaming
585
834
  * executors (bash/python/ssh/eval via OutputSink) and the `read` tool's
@@ -710,6 +959,17 @@ async function spillLargeResultToArtifact(
710
959
  return { ...result, content: newContent, details: newDetails };
711
960
  }
712
961
 
962
+ const BODY_TRUNCATION_FOOTER_KEY = "__bodyTruncationFooter";
963
+
964
+ function stripBodyOwnedTruncationFooter(text: string, details: unknown): string {
965
+ const footer = (details as { [BODY_TRUNCATION_FOOTER_KEY]?: unknown } | undefined)?.[BODY_TRUNCATION_FOOTER_KEY];
966
+ if (typeof footer !== "string" || footer.length === 0) return text;
967
+ const trimmedText = text.trimEnd();
968
+ const trimmedFooter = footer.trim();
969
+ if (!trimmedText.endsWith(trimmedFooter)) return text;
970
+ return trimmedText.slice(0, -trimmedFooter.length).trimEnd();
971
+ }
972
+
713
973
  /**
714
974
  * Absolute inline-size backstop enforced after {@link spillLargeResultToArtifact}.
715
975
  *
@@ -738,7 +998,8 @@ async function enforceInlineResultBackstop(
738
998
  }
739
999
  if (textParts.length === 0) return result;
740
1000
 
741
- const fullText = textParts.length === 1 ? textParts[0] : textParts.join("\n");
1001
+ const renderedText = textParts.length === 1 ? textParts[0] : textParts.join("\n");
1002
+ const fullText = stripBodyOwnedTruncationFooter(renderedText, result.details);
742
1003
  const totalBytes = Buffer.byteLength(fullText, "utf-8");
743
1004
  if (totalBytes <= maxInlineBytes) return result;
744
1005
 
@@ -33,7 +33,11 @@ function normalizeUnicodeSpaces(str: string): string {
33
33
  }
34
34
 
35
35
  function tryMacOSScreenshotPath(filePath: string): string {
36
- return filePath.replace(/ (AM|PM)\./g, `${NARROW_NO_BREAK_SPACE}$1.`);
36
+ // macOS writes a narrow no-break space before AM/PM, but the model normalizes it
37
+ // to a plain space. The original name is not always `…PM.png`: attachment paths
38
+ // append a timestamp (`…PM-1785075812409.png`), so match any non-alphanumeric
39
+ // separator or end of segment rather than a literal dot.
40
+ return filePath.replace(/ (AM|PM)(?=[^A-Za-z0-9/]|$)/g, `${NARROW_NO_BREAK_SPACE}$1`);
37
41
  }
38
42
 
39
43
  function tryNFDVariant(filePath: string): string {
@@ -633,6 +637,7 @@ export interface ToolScopeOptions {
633
637
  rawPaths: string[];
634
638
  cwd: string;
635
639
  getArtifactsDir?: () => string | null;
640
+ getAuthorizedArtifactsDirs?: () => readonly string[];
636
641
  /** Verb used in the "Cannot {action} internal URL without a backing file: …" message. */
637
642
  internalUrlAction: string;
638
643
  /** Collect absolute paths flagged immutable by their internal-URL handler. */
@@ -663,7 +668,7 @@ export interface ToolScopeResolution {
663
668
  * 5. stat the resolved base path so callers can branch on directory vs file scope.
664
669
  */
665
670
  export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<ToolScopeResolution> {
666
- const { rawPaths: inputs, cwd, internalUrlAction, getArtifactsDir } = opts;
671
+ const { rawPaths: inputs, cwd, internalUrlAction, getArtifactsDir, getAuthorizedArtifactsDirs } = opts;
667
672
  const rawPaths = inputs.map(normalizePathLikeInput);
668
673
  if (rawPaths.some(rawPath => rawPath.length === 0)) {
669
674
  throw new ToolError("`paths` must contain non-empty paths or globs");
@@ -679,7 +684,7 @@ export async function resolveToolSearchScope(opts: ToolScopeOptions): Promise<To
679
684
  if (hasGlobPathChars(rawPath)) {
680
685
  throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
681
686
  }
682
- const resource = await internalRouter.resolve(rawPath, { cwd, getArtifactsDir });
687
+ const resource = await internalRouter.resolve(rawPath, { cwd, getArtifactsDir, getAuthorizedArtifactsDirs });
683
688
  if (!resource.sourcePath) {
684
689
  throw new ToolError(`Cannot ${internalUrlAction} internal URL without a backing file: ${rawPath}`);
685
690
  }
@@ -0,0 +1,44 @@
1
+ import type { Settings } from "../config/settings";
2
+ import type { TruncationDirection } from "../session/streaming-output";
3
+
4
+ /**
5
+ * Read execution routes that have distinct truncation-default contracts.
6
+ * Routes that share the same default are grouped only when their truncation
7
+ * policy is identical; selection and rendering remain owned by read.ts.
8
+ */
9
+ export type ReadRoute =
10
+ | "local-bare-stream"
11
+ | "local-bare-acp"
12
+ | "archive-member-bare"
13
+ | "local-range"
14
+ | "local-multi-range"
15
+ | "local-raw"
16
+ | "local-summary"
17
+ | "url-reader"
18
+ | "url-cache-page"
19
+ | "dir-local"
20
+ | "dir-archive"
21
+ | "sqlite-list"
22
+ | "sqlite-rows"
23
+ | "converted";
24
+
25
+ /** Resolve the configured default for a route; explicit parameters bypass it. */
26
+ export function pathDefault(route: ReadRoute, settings: Settings): TruncationDirection {
27
+ switch (route) {
28
+ case "local-bare-stream":
29
+ case "local-bare-acp":
30
+ case "archive-member-bare":
31
+ return settings.get("read.truncation") ?? "last";
32
+ default:
33
+ return "head";
34
+ }
35
+ }
36
+
37
+ /** Resolve the effective caller-facing direction with explicit precedence. */
38
+ export function resolveEffectiveDirection(
39
+ explicitParam: TruncationDirection | undefined,
40
+ route: ReadRoute,
41
+ settings: Settings,
42
+ ): TruncationDirection {
43
+ return explicitParam !== undefined ? explicitParam : pathDefault(route, settings);
44
+ }