mixdog 1.2.5 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (493) hide show
  1. package/LICENSES/simple-icons-CC0-1.0.txt +30 -0
  2. package/NOTICE.md +19 -0
  3. package/package.json +4 -1
  4. package/scripts/check-model-recommendations.mjs +89 -0
  5. package/scripts/gate-local.mjs +7 -0
  6. package/scripts/lib/gate-scheduler.mjs +2 -2
  7. package/scripts/lib/isolated-test-env.mjs +16 -0
  8. package/scripts/lib/run-node-tests.mjs +5 -14
  9. package/scripts/provider-pingpong-live.mjs +9 -9
  10. package/src/agents/advisor/AGENT.md +4 -4
  11. package/src/agents/advisor/agent.json +1 -1
  12. package/src/agents/reviewer/AGENT.md +5 -4
  13. package/src/agents/reviewer/agent.json +1 -1
  14. package/src/defaults/shipped-history.json +17 -0
  15. package/src/defaults/skills/browser-use/SKILL.md +7 -1
  16. package/src/defaults/skills/computer-use/SKILL.md +3 -1
  17. package/src/defaults/skills/memory-management/SKILL.md +4 -1
  18. package/src/defaults/skills/setup/references/actions.md +11 -4
  19. package/src/defaults/skills/setup-guide/SKILL.md +175 -0
  20. package/src/defaults/skills/setup-guide/model-recommendations.json +333 -0
  21. package/src/defaults/skills/visualize/SKILL.md +3 -2
  22. package/src/lib/rules-builder.cjs +56 -7
  23. package/src/output-styles/common.md +22 -6
  24. package/src/output-styles/detailed.md +7 -5
  25. package/src/output-styles/minimal.md +7 -3
  26. package/src/output-styles/simple.md +2 -3
  27. package/src/rules/agent/AGENT.md +7 -1
  28. package/src/rules/agent-mode/AGENT_MODE.md +24 -0
  29. package/src/rules/agent-mode/MIXY.md +21 -0
  30. package/src/rules/agent-mode/ROUTINE.md +6 -0
  31. package/src/rules/lead/LEAD.md +9 -4
  32. package/src/rules/shared/10-tool-workflow.md +2 -1
  33. package/src/rules/shared/60-verification.md +4 -5
  34. package/src/rules/shared/70-delivery.md +3 -0
  35. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -1
  36. package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +86 -0
  37. package/src/runtime/agent/orchestrator/config.mjs +6 -0
  38. package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +77 -14
  39. package/src/runtime/agent/orchestrator/context/role-instructions.mjs +182 -13
  40. package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +5 -3
  41. package/src/runtime/agent/orchestrator/internal-agents.mjs +10 -0
  42. package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +215 -0
  43. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +58 -64
  44. package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +10 -1
  45. package/src/runtime/agent/orchestrator/mcp/client.mjs +122 -15
  46. package/src/runtime/agent/orchestrator/mcp/oauth-account.mjs +97 -0
  47. package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +2 -3
  48. package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +49 -0
  49. package/src/runtime/agent/orchestrator/mcp/oauth-credentials.mjs +59 -9
  50. package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +32 -7
  51. package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +45 -6
  52. package/src/runtime/agent/orchestrator/mcp/oauth.mjs +195 -6
  53. package/src/runtime/agent/orchestrator/mcp/server-connectors.mjs +17 -0
  54. package/src/runtime/agent/orchestrator/providers/account-pool.mjs +9 -2
  55. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
  56. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +41 -19
  57. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +3 -2
  58. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events/content-block-start.mjs +4 -1
  59. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn/turn-events.mjs +2 -0
  60. package/src/runtime/agent/orchestrator/providers/anthropic-sse-turn.mjs +3 -0
  61. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +2 -1
  62. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +3 -1
  63. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +11 -5
  64. package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +4 -0
  65. package/src/runtime/agent/orchestrator/providers/catalog-deadline.mjs +163 -0
  66. package/src/runtime/agent/orchestrator/providers/catalog-failures.mjs +34 -0
  67. package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
  68. package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +38 -14
  69. package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +100 -26
  70. package/src/runtime/agent/orchestrator/providers/lib/send-callbacks.mjs +24 -0
  71. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +13 -0
  72. package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +2 -1
  73. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +1 -0
  74. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/chunk-events.mjs +4 -0
  75. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
  76. package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +2 -0
  77. package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +49 -4
  78. package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +0 -7
  79. package/src/runtime/agent/orchestrator/providers/openai-compat-responses-events.mjs +7 -1
  80. package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +1 -0
  81. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +2 -0
  82. package/src/runtime/agent/orchestrator/providers/openai-compat-xai-send.mjs +1 -0
  83. package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +17 -12
  84. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events.mjs +5 -1
  85. package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state.mjs +11 -2
  86. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +1 -0
  87. package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +45 -16
  88. package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +1 -0
  90. package/src/runtime/agent/orchestrator/providers/openai-ws-connect.mjs +196 -0
  91. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +74 -9
  92. package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +14 -1
  93. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -0
  94. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-login.mjs +194 -0
  95. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth-tokens.mjs +353 -0
  96. package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +293 -0
  97. package/src/runtime/agent/orchestrator/providers/opencode-go-usage.mjs +33 -100
  98. package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +23 -14
  99. package/src/runtime/agent/orchestrator/providers/registry.mjs +41 -27
  100. package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +98 -4
  101. package/src/runtime/agent/orchestrator/runtime-core/context-status-shape.mjs +4 -0
  102. package/src/runtime/agent/orchestrator/runtime-core/extension-scopes.mjs +6 -4
  103. package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +15 -8
  104. package/src/runtime/agent/orchestrator/runtime-core/plugin-mcp.mjs +1 -0
  105. package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +12 -6
  106. package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +32 -5
  107. package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +10 -4
  108. package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +4 -1
  109. package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +50 -20
  110. package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +2 -0
  111. package/src/runtime/agent/orchestrator/session/agent-home.d.mts +4 -0
  112. package/src/runtime/agent/orchestrator/session/agent-home.mjs +33 -0
  113. package/src/runtime/agent/orchestrator/session/agent-mode.mjs +62 -0
  114. package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +15 -3
  115. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +31 -0
  116. package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +241 -0
  117. package/src/runtime/agent/orchestrator/session/agent-room.d.mts +162 -0
  118. package/src/runtime/agent/orchestrator/session/agent-room.mjs +641 -0
  119. package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +7 -1
  120. package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +74 -13
  121. package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +102 -0
  122. package/src/runtime/agent/orchestrator/session/compact/runner.mjs +8 -2
  123. package/src/runtime/agent/orchestrator/session/compact/summary.mjs +16 -0
  124. package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +2 -0
  125. package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +6 -2
  126. package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +165 -0
  127. package/src/runtime/agent/orchestrator/session/human-gate/held-message.mjs +95 -0
  128. package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +221 -0
  129. package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +195 -0
  130. package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +167 -0
  131. package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +9 -0
  132. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +3 -1
  133. package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +39 -2
  134. package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +10 -0
  135. package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +16 -1
  136. package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +82 -33
  137. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +11 -4
  138. package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +5 -4
  139. package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +13 -3
  140. package/src/runtime/agent/orchestrator/session/loop/usage.mjs +7 -0
  141. package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +1 -0
  142. package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +6 -1
  143. package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
  144. package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -0
  145. package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +13 -5
  146. package/src/runtime/agent/orchestrator/session/manager/pending-lifecycle-epoch.mjs +0 -8
  147. package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +10 -7
  148. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +62 -22
  149. package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +44 -16
  150. package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +22 -5
  151. package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +23 -1
  152. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +81 -34
  153. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +39 -4
  154. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +27 -5
  155. package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +55 -8
  156. package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +16 -0
  157. package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +3 -2
  158. package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +6 -0
  159. package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +58 -0
  160. package/src/runtime/agent/orchestrator/session/manager.mjs +2 -2
  161. package/src/runtime/agent/orchestrator/session/mixy-name.mjs +24 -0
  162. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +32 -5
  163. package/src/runtime/agent/orchestrator/session/request-fork.mjs +47 -0
  164. package/src/runtime/agent/orchestrator/session/routine-result.d.mts +70 -0
  165. package/src/runtime/agent/orchestrator/session/routine-result.mjs +277 -0
  166. package/src/runtime/agent/orchestrator/session/side-question-store.mjs +16 -1
  167. package/src/runtime/agent/orchestrator/session/side-question.mjs +5 -43
  168. package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +10 -0
  169. package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +73 -6
  170. package/src/runtime/agent/orchestrator/session/store/load-session.mjs +40 -0
  171. package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +15 -0
  172. package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +12 -0
  173. package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +8 -0
  174. package/src/runtime/agent/orchestrator/session/store-agent-worker-header.mjs +36 -0
  175. package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +258 -43
  176. package/src/runtime/agent/orchestrator/session/store-record-strict-scan.mjs +325 -0
  177. package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +84 -21
  178. package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -12
  179. package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +46 -0
  180. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +46 -30
  181. package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +0 -4
  182. package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +144 -45
  183. package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +32 -13
  184. package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +364 -38
  185. package/src/runtime/agent/orchestrator/session/store.mjs +9 -2
  186. package/src/runtime/agent/orchestrator/session/task-wait-control.mjs +43 -12
  187. package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +23 -6
  188. package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +44 -1
  189. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -6
  190. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +13 -7
  191. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/bashism-rewrite.mjs +1 -1
  192. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/benign-exit.mjs +7 -1
  193. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +40 -7
  194. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/deadline-plan.mjs +37 -34
  195. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +1 -1
  196. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/spawn-env.mjs +0 -4
  197. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +6 -0
  198. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +29 -27
  199. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +1 -1
  200. package/src/runtime/agent/orchestrator/tools/builtin/find-files-tool.mjs +28 -11
  201. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +12 -3
  202. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +203 -101
  203. package/src/runtime/agent/orchestrator/tools/builtin/git-repo-rw-lock.mjs +90 -41
  204. package/src/runtime/agent/orchestrator/tools/builtin/lib/list-helpers.mjs +17 -1
  205. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-delivery.mjs +199 -0
  206. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +12 -0
  207. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +165 -39
  208. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +25 -13
  209. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +113 -5
  210. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +7 -6
  211. package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +19 -4
  212. package/src/runtime/agent/orchestrator/tools/builtin/read-line-collector.mjs +5 -3
  213. package/src/runtime/agent/orchestrator/tools/builtin/read-single-render.mjs +25 -7
  214. package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +2 -1
  215. package/src/runtime/agent/orchestrator/tools/builtin/read-tool/batch-render.mjs +3 -1
  216. package/src/runtime/agent/orchestrator/tools/builtin/search-glob-tool.mjs +12 -5
  217. package/src/runtime/agent/orchestrator/tools/builtin/search-grep-tool.mjs +61 -22
  218. package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +32 -8
  219. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +193 -19
  220. package/src/runtime/agent/orchestrator/tools/builtin/shell-direct-exe.mjs +57 -7
  221. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +123 -32
  222. package/src/runtime/agent/orchestrator/tools/builtin/shell-lossless-compact.mjs +26 -1
  223. package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +13 -28
  224. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +90 -11
  225. package/src/runtime/agent/orchestrator/tools/builtin/wait-budgets.mjs +23 -0
  226. package/src/runtime/agent/orchestrator/tools/builtin.mjs +54 -10
  227. package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +22 -28
  228. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +11 -7
  229. package/src/runtime/agent/orchestrator/tools/destructive-warning.mjs +126 -9
  230. package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
  231. package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +15 -6
  232. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +64 -2
  233. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +10 -2
  234. package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promotion-results.mjs +13 -3
  235. package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +8 -0
  236. package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -1
  237. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +1 -1
  238. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +104 -55
  239. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +7 -2
  240. package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +39 -23
  241. package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +6 -2
  242. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
  243. package/src/runtime/attachments/office-extract.mjs +11 -9
  244. package/src/runtime/browser-bridge/client.mjs +24 -0
  245. package/src/runtime/channels/lib/scheduler.mjs +40 -12
  246. package/src/runtime/channels/lib/schedules-db.mjs +71 -12
  247. package/src/runtime/channels/lib/webhook/session-dispatch.mjs +36 -16
  248. package/src/runtime/channels/lib/webhook.mjs +0 -1
  249. package/src/runtime/channels/lib/webhooks-db.mjs +100 -10
  250. package/src/runtime/channels/lib/worker-main.mjs +17 -20
  251. package/src/runtime/computer-bridge/client.mjs +28 -1
  252. package/src/runtime/connectors/gmail-app-password.mjs +690 -0
  253. package/src/runtime/connectors/google-mcp.mjs +446 -0
  254. package/src/runtime/memory/lib/core-memory-access.mjs +69 -0
  255. package/src/runtime/memory/lib/core-memory-file.mjs +16 -8
  256. package/src/runtime/memory/lib/core-memory-index.mjs +15 -9
  257. package/src/runtime/memory/lib/core-memory-management.mjs +10 -4
  258. package/src/runtime/memory/lib/core-memory-store.mjs +53 -30
  259. package/src/runtime/memory/lib/core-memory-uniqueness.mjs +10 -6
  260. package/src/runtime/memory/lib/embedding-tokenizer.mjs +32 -18
  261. package/src/runtime/memory/lib/http-router/session-routes.mjs +3 -2
  262. package/src/runtime/memory/lib/memory-action-handlers/core-actions.mjs +44 -14
  263. package/src/runtime/memory/lib/memory-schema/core-and-meta.mjs +11 -0
  264. package/src/runtime/memory/lib/memory.mjs +7 -1
  265. package/src/runtime/memory/lib/query-core-recall.mjs +9 -1
  266. package/src/runtime/memory/lib/query-hybrid-search.mjs +2 -0
  267. package/src/runtime/memory/lib/runtime-fetcher.mjs +1 -1
  268. package/src/runtime/memory/lib/tool-call-handler.mjs +9 -5
  269. package/src/runtime/memory/tool-defs.mjs +5 -0
  270. package/src/runtime/office/authoring/pptx-script-normalize.mjs +11 -8
  271. package/src/runtime/office/core/tabular.mjs +1 -1
  272. package/src/runtime/office/design/library/design-template-inspect.mjs +7 -4
  273. package/src/runtime/office/portable/docx-formatting.mjs +1 -1
  274. package/src/runtime/office/portable/docx-runs.mjs +1 -1
  275. package/src/runtime/office/portable/docx-tracked-edits.mjs +2 -2
  276. package/src/runtime/office/portable/portable-docx-edits.mjs +11 -10
  277. package/src/runtime/office/portable/portable-docx-operations.mjs +21 -17
  278. package/src/runtime/office/portable/portable-docx-parts.mjs +15 -9
  279. package/src/runtime/office/portable/portable-docx-styles.mjs +4 -1
  280. package/src/runtime/office/portable/portable-docx-xml.mjs +9 -9
  281. package/src/runtime/office/portable/portable-opc.mjs +27 -13
  282. package/src/runtime/office/portable/portable-pivot.mjs +3 -3
  283. package/src/runtime/office/portable/portable-pptx-chart.mjs +3 -3
  284. package/src/runtime/office/portable/portable-pptx-charts.mjs +24 -18
  285. package/src/runtime/office/portable/portable-pptx-core.mjs +5 -5
  286. package/src/runtime/office/portable/portable-pptx-deck.mjs +8 -4
  287. package/src/runtime/office/portable/portable-pptx-package.mjs +7 -4
  288. package/src/runtime/office/portable/portable-pptx-shapes.mjs +18 -9
  289. package/src/runtime/office/portable/portable-sheet-parts.mjs +9 -3
  290. package/src/runtime/office/portable/portable-sheet-xml.mjs +5 -5
  291. package/src/runtime/office/portable/portable-xlsx-charts.mjs +4 -1
  292. package/src/runtime/office/portable/portable-xlsx-drawings.mjs +10 -6
  293. package/src/runtime/office/portable/portable-xlsx-operations.mjs +8 -5
  294. package/src/runtime/office/portable/portable-xlsx-pivot-ops.mjs +1 -1
  295. package/src/runtime/office/portable/portable-xlsx-reference-shift.mjs +11 -7
  296. package/src/runtime/office/portable/portable-xlsx-sheet-edits.mjs +6 -3
  297. package/src/runtime/office/portable/portable-xml.mjs +1 -1
  298. package/src/runtime/shared/agent-avatar-sprites.mjs +445 -0
  299. package/src/runtime/shared/agent-avatar.d.mts +64 -0
  300. package/src/runtime/shared/agent-avatar.mjs +320 -0
  301. package/src/runtime/shared/agent-route-config.d.mts +3 -0
  302. package/src/runtime/shared/agent-route-config.mjs +16 -0
  303. package/src/runtime/shared/atomic-file.mjs +36 -14
  304. package/src/runtime/shared/background-tasks.mjs +60 -20
  305. package/src/runtime/shared/child-spawn-gate.mjs +26 -16
  306. package/src/runtime/shared/config.mjs +8 -11
  307. package/src/runtime/shared/connector-accounts.mjs +15 -0
  308. package/src/runtime/shared/connector-catalog.d.mts +52 -0
  309. package/src/runtime/shared/connector-catalog.mjs +230 -0
  310. package/src/runtime/shared/connector-triggers.d.mts +3 -0
  311. package/src/runtime/shared/connector-triggers.mjs +43 -0
  312. package/src/runtime/shared/file-lock.mjs +7 -1
  313. package/src/runtime/shared/file-permissions-worker.mjs +38 -0
  314. package/src/runtime/shared/file-permissions.d.mts +19 -4
  315. package/src/runtime/shared/file-permissions.mjs +310 -4
  316. package/src/runtime/shared/git-runner-worker.mjs +6 -78
  317. package/src/runtime/shared/google-oauth-client.mjs +24 -0
  318. package/src/runtime/shared/held-request-host.d.mts +12 -0
  319. package/src/runtime/shared/held-request-host.mjs +71 -0
  320. package/src/runtime/shared/llm/http-agent.mjs +7 -3
  321. package/src/runtime/shared/llm/provider-catalog-registry.mjs +10 -0
  322. package/src/runtime/shared/llm/usage-accounting.mjs +2 -0
  323. package/src/runtime/shared/llm/usage-ledger-import.mjs +32 -3
  324. package/src/runtime/shared/llm/usage-ledger-rollup.mjs +12 -5
  325. package/src/runtime/shared/llm/usage-ledger-worker.mjs +1 -1
  326. package/src/runtime/shared/llm/usage-ledger.mjs +20 -5
  327. package/src/runtime/shared/memory-snapshot.mjs +5 -1
  328. package/src/runtime/shared/notify-trace.mjs +8 -1
  329. package/src/runtime/shared/plain-preview.d.mts +1 -0
  330. package/src/runtime/shared/plain-preview.mjs +28 -0
  331. package/src/runtime/shared/pristine-execution-contract.json +5 -1
  332. package/src/runtime/shared/process-runner-worker.mjs +7 -0
  333. package/src/runtime/shared/process-runner.mjs +38 -0
  334. package/src/runtime/shared/provider-accounts.mjs +2 -1
  335. package/src/runtime/shared/provider-api-key.mjs +0 -1
  336. package/src/runtime/shared/read-row-numbers.mjs +1 -1
  337. package/src/runtime/shared/resource-admission.mjs +60 -18
  338. package/src/runtime/shared/routine-limits.d.mts +11 -0
  339. package/src/runtime/shared/routine-limits.mjs +21 -0
  340. package/src/runtime/shared/routine-trigger.d.mts +10 -0
  341. package/src/runtime/shared/routine-trigger.mjs +44 -0
  342. package/src/runtime/shared/run-child.mjs +84 -0
  343. package/src/runtime/shared/schedule-model-ref.mjs +9 -10
  344. package/src/runtime/shared/session-activity-signal.mjs +23 -0
  345. package/src/runtime/shared/shell-output-summary.mjs +23 -0
  346. package/src/runtime/shared/singleton-owner.mjs +126 -42
  347. package/src/runtime/shared/slow-span.mjs +4 -12
  348. package/src/runtime/shared/statusline/statusline-segments.mjs +13 -1
  349. package/src/runtime/shared/tool-output-limit.mjs +4 -2
  350. package/src/runtime/shared/tool-output-marker.mjs +58 -0
  351. package/src/runtime/shared/transcript-metadata.mjs +10 -0
  352. package/src/runtime/shared/turn-changes.mjs +0 -8
  353. package/src/runtime/shared/usage-readout-totals.mjs +56 -0
  354. package/src/runtime/shared/user-data-guard.mjs +131 -34
  355. package/src/runtime/shared/worktrees/activity-gate.mjs +33 -8
  356. package/src/runtime/shared/worktrees/git-head.mjs +20 -2
  357. package/src/runtime/shared/worktrees/lock.mjs +3 -2
  358. package/src/runtime/terminal-bridge/tool-defs.mjs +1 -1
  359. package/src/runtime/tidy/runners/biome.mjs +3 -1
  360. package/src/runtime/tidy/shadow.mjs +2 -1
  361. package/src/runtime/web-search/index.mjs +5 -7
  362. package/src/runtime/web-search/lib/formatter.mjs +42 -6
  363. package/src/runtime/web-search/tool-defs.mjs +6 -1
  364. package/src/session-runtime/agent-home-delivery.mjs +459 -0
  365. package/src/session-runtime/agent-profile-changes.mjs +21 -0
  366. package/src/session-runtime/agent-room-delivery.mjs +92 -0
  367. package/src/session-runtime/agent-task-sessions.mjs +200 -0
  368. package/src/session-runtime/boot/apis.mjs +50 -18
  369. package/src/session-runtime/boot/facade.mjs +5 -8
  370. package/src/session-runtime/boot/infrastructure.mjs +28 -17
  371. package/src/session-runtime/boot/tools.mjs +3 -0
  372. package/src/session-runtime/channel-config-api.mjs +53 -5
  373. package/src/session-runtime/cwd-plugins/core-memory-context.mjs +36 -20
  374. package/src/session-runtime/cwd-plugins/plugin-status.mjs +6 -1
  375. package/src/session-runtime/held-requests.mjs +228 -0
  376. package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +121 -0
  377. package/src/session-runtime/internal-tool-executor/feature-tools.mjs +5 -1
  378. package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +31 -2
  379. package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +31 -2
  380. package/src/session-runtime/internal-tool-executor.mjs +2 -0
  381. package/src/session-runtime/lifecycle/session-catalog.mjs +9 -0
  382. package/src/session-runtime/lifecycle/session-switch/context-switch.mjs +4 -3
  383. package/src/session-runtime/lifecycle/session-switch/resume.mjs +11 -4
  384. package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +3 -3
  385. package/src/session-runtime/lifecycle/shared.mjs +25 -2
  386. package/src/session-runtime/lifecycle/teardown/session-close.mjs +2 -0
  387. package/src/session-runtime/lifecycle/teardown.mjs +5 -2
  388. package/src/session-runtime/lifecycle-api.mjs +3 -1
  389. package/src/session-runtime/mcp-glue/server-catalog.mjs +8 -0
  390. package/src/session-runtime/mcp-glue/server-input.mjs +3 -0
  391. package/src/session-runtime/mcp-oauth-login.mjs +112 -15
  392. package/src/session-runtime/model-route/set-route.mjs +14 -3
  393. package/src/session-runtime/owned-automation-run.mjs +229 -0
  394. package/src/session-runtime/provider-auth/login.mjs +10 -6
  395. package/src/session-runtime/provider-auth/setup.mjs +19 -2
  396. package/src/session-runtime/provider-catalog-cache.mjs +101 -25
  397. package/src/session-runtime/provider-models/catalog-load.mjs +14 -3
  398. package/src/session-runtime/provider-models/row-hydration.mjs +4 -0
  399. package/src/session-runtime/provider-models.mjs +76 -10
  400. package/src/session-runtime/provider-readiness.mjs +4 -0
  401. package/src/session-runtime/resource-api.mjs +5 -0
  402. package/src/session-runtime/resource-mcp-api.mjs +107 -13
  403. package/src/session-runtime/resource-skills-api.mjs +12 -0
  404. package/src/session-runtime/room-member-gate.mjs +48 -0
  405. package/src/session-runtime/routine-home-turn.mjs +156 -0
  406. package/src/session-runtime/routine-owner.mjs +48 -0
  407. package/src/session-runtime/schedule-session-run.mjs +30 -64
  408. package/src/session-runtime/services/agent-tool/close-flow.mjs +2 -1
  409. package/src/session-runtime/services/agent-tool/helpers.mjs +10 -0
  410. package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +10 -2
  411. package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +13 -4
  412. package/src/session-runtime/services/agent-tool/notify.mjs +15 -4
  413. package/src/session-runtime/services/agent-tool/restart-notify.mjs +134 -0
  414. package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -1
  415. package/src/session-runtime/services/agent-tool/spawn-flow/deferred-spawn.mjs +2 -1
  416. package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -3
  417. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +19 -4
  418. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +2 -0
  419. package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -1
  420. package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +8 -3
  421. package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -0
  422. package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +4 -0
  423. package/src/session-runtime/services/agent-tool/worker-index.mjs +44 -10
  424. package/src/session-runtime/services/agent-tool.mjs +20 -0
  425. package/src/session-runtime/services/channel-admin.mjs +93 -24
  426. package/src/session-runtime/services/memory-access.mjs +37 -0
  427. package/src/session-runtime/services/provider-admin.mjs +166 -59
  428. package/src/session-runtime/services/schedule-access.mjs +91 -0
  429. package/src/session-runtime/services/usage-dashboard-model.mjs +2 -2
  430. package/src/session-runtime/services/usage-dashboard.mjs +27 -69
  431. package/src/session-runtime/session-create/session-options.mjs +34 -7
  432. package/src/session-runtime/session-hooks.mjs +16 -8
  433. package/src/session-runtime/setup-tool/executor.mjs +68 -6
  434. package/src/session-runtime/setup-tool/extended-actions.mjs +72 -14
  435. package/src/session-runtime/setup-tool/settings-contract.mjs +8 -6
  436. package/src/session-runtime/setup-tool/tool-defs.mjs +8 -0
  437. package/src/session-runtime/skills-api.mjs +16 -1
  438. package/src/session-runtime/tool-policy-refresh.mjs +238 -29
  439. package/src/session-runtime/turn/session-ops.mjs +10 -3
  440. package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +11 -2
  441. package/src/session-runtime/turn/turn-run/turn-prepare.mjs +3 -0
  442. package/src/session-runtime/usage-stats-api.mjs +6 -1
  443. package/src/session-runtime/webhook-session-run.mjs +49 -67
  444. package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +239 -0
  445. package/src/session-runtime/workflow-agents-api/agent-editor.mjs +556 -22
  446. package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +11 -1
  447. package/src/session-runtime/workflow-agents-api.mjs +4 -1
  448. package/src/standalone/channel-transport/http-routes.mjs +10 -9
  449. package/src/standalone/daemon-agent-control.mjs +1 -8
  450. package/src/standalone/daemon-desktop-runtime.mjs +20 -7
  451. package/src/standalone/daemon-owner.mjs +117 -25
  452. package/src/standalone/daemon-session-control.mjs +11 -2
  453. package/src/standalone/daemon.mjs +61 -17
  454. package/src/standalone/session-call-cache.mjs +6 -0
  455. package/src/standalone/session-protocol.mjs +28 -1
  456. package/src/standalone/session-service/retention/busy.mjs +7 -2
  457. package/src/standalone/session-service/session-calls/session-turns.mjs +9 -0
  458. package/src/standalone/session-service.mjs +19 -0
  459. package/src/standalone/session-transport/routes.mjs +7 -6
  460. package/src/tui/app/model-options.mjs +39 -2
  461. package/src/tui/app/model-picker.mjs +16 -2
  462. package/src/tui/app/provider-setup-picker.mjs +1 -2
  463. package/src/tui/app/slash-commands.mjs +20 -0
  464. package/src/tui/dist/index.mjs +487 -128
  465. package/src/tui/session/context-state.mjs +6 -1
  466. package/src/tui/session/notice-surface.mjs +35 -4
  467. package/src/tui/session/notice-text.mjs +0 -1
  468. package/src/tui/session/queue-helpers.mjs +2 -1
  469. package/src/tui/session/session-api/extensions.mjs +4 -0
  470. package/src/tui/session/session-api/intake/submission.mjs +12 -13
  471. package/src/tui/session/session-api/integrations.mjs +9 -5
  472. package/src/tui/session/session-api/lifecycle/session-reset.mjs +13 -3
  473. package/src/tui/session/session-api/lifecycle.mjs +17 -1
  474. package/src/tui/session/session-api/routes.mjs +48 -3
  475. package/src/tui/session/session-flow/drain.mjs +20 -3
  476. package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -1
  477. package/src/tui/session/session-flow/queue/take-entries.mjs +3 -2
  478. package/src/tui/session/session-flow/steering.mjs +104 -24
  479. package/src/tui/session/session-flow.mjs +2 -0
  480. package/src/tui/session/tool-approval.mjs +26 -5
  481. package/src/tui/session/transcript-intake.mjs +6 -2
  482. package/src/tui/session/transcript-spill.mjs +5 -1
  483. package/src/tui/session/tui-steering-persist.mjs +65 -42
  484. package/src/tui/session/turn-live-call.mjs +56 -0
  485. package/src/tui/session/turn-stream.mjs +33 -0
  486. package/src/tui/session/turn.mjs +50 -7
  487. package/src/ui/statusline.mjs +1 -6
  488. package/src/ui/streaming-markdown-heal.mjs +24 -7
  489. package/src/vendor/statusline/bin/statusline-route.mjs +0 -1
  490. package/src/vendor/statusline/src/gateway/route-meta.mjs +0 -1
  491. package/src/workflows/default/WORKFLOW.md +9 -13
  492. package/src/session-runtime/automation-agents.mjs +0 -94
  493. package/src/session-runtime/automation-workflow.mjs +0 -59
@@ -1,11 +1,34 @@
1
+ // Incremental delivery of a shell task's output. Each task (the authorized
2
+ // task object) owns one cursor per stream; every wait, read, background move
3
+ // and completion notice delivers only the bytes after it, in the shared
4
+ // delivery shape (lib/shell-delivery.mjs). The cursor always advances over the
5
+ // whole unseen range, even when its middle is omitted: omitted bytes are never
6
+ // re-sent, the marker's log path is how to get them back.
1
7
  import { closeSync, fstatSync, openSync, readSync } from 'node:fs';
2
8
  import { stripAnsi } from '../../shell-exec-output.mjs';
3
9
  import { FileEncodingSniffer, ShellStreamDecoder, alignedOffset, oemTextEncoding } from '../../shell-codepage.mjs';
10
+ import {
11
+ DELIVERY_FULL_CHARS,
12
+ countNewlines,
13
+ createCarriageReturnFolder,
14
+ edgesPiece,
15
+ escapeHarnessTags,
16
+ fitPieces,
17
+ formatKb,
18
+ omissionMarker,
19
+ persistDeliveryLog,
20
+ prepareShellText,
21
+ textPiece,
22
+ } from './shell-delivery.mjs';
4
23
 
5
- import { TOOL_OUTPUT_PAGE_BYTES, TOOL_OUTPUT_TAIL_BYTES } from '../../../../../shared/tool-output-budget.mjs';
6
24
  const cursors = new WeakMap();
7
25
  const ESC = '\x1b';
8
26
  const PENDING_ANSI_INTRODUCER = new RegExp(`${ESC}(?:\\[[0-?]*[ -/]*|\\](?:(?!${ESC}\\\\)[^\x07\x9c])*)?$`);
27
+ // An unseen range up to this size is read whole; a longer one only at its two
28
+ // ends (the middle is counted, never loaded).
29
+ const FULL_READ_BYTES = 64 * 1024;
30
+ const EDGE_READ_BYTES = 16 * 1024;
31
+ const COUNT_CHUNK_BYTES = 1 << 20;
9
32
 
10
33
  function newCursor() {
11
34
  return {
@@ -13,6 +36,7 @@ function newCursor() {
13
36
  preview: '',
14
37
  decoder: new ShellStreamDecoder(),
15
38
  ansi: '',
39
+ fold: createCarriageReturnFolder(),
16
40
  sniffer: new FileEncodingSniffer(),
17
41
  committed: false,
18
42
  };
@@ -26,13 +50,6 @@ function pendingAnsiIntroducer(sequence) {
26
50
  return '\x1b';
27
51
  }
28
52
 
29
- // First index at or after `start` that does not sit inside a UTF-8 sequence.
30
- function utf8Boundary(bytes, start, end) {
31
- let index = start;
32
- while (index < end && (bytes[index] & 0xc0) === 0x80) index++;
33
- return index;
34
- }
35
-
36
53
  function visibleText(cursor, text) {
37
54
  const value = cursor.ansi + text;
38
55
  // Retain only the introducer of an unfinished CSI/OSC, not its potentially
@@ -45,21 +62,123 @@ function visibleText(cursor, text) {
45
62
  return stripAnsi(value.slice(0, pending.index));
46
63
  }
47
64
 
65
+ function readBytes(fd, start, length) {
66
+ const buffer = Buffer.alloc(Math.max(0, length));
67
+ const count = length > 0 ? readSync(fd, buffer, 0, length, start) : 0;
68
+ return buffer.subarray(0, count);
69
+ }
70
+
71
+ function countNewlinesInRange(fd, start, end) {
72
+ let count = 0;
73
+ const buffer = Buffer.alloc(Math.min(COUNT_CHUNK_BYTES, Math.max(1, end - start)));
74
+ for (let position = start; position < end; ) {
75
+ const bytes = readSync(fd, buffer, 0, Math.min(buffer.length, end - position), position);
76
+ if (bytes <= 0) break;
77
+ for (let i = 0; i < bytes; i += 1) if (buffer[i] === 10) count += 1;
78
+ position += bytes;
79
+ }
80
+ return count;
81
+ }
82
+
83
+ function decodeVisible(cursor, bytes, flush) {
84
+ let decoded = cursor.decoder.write(bytes);
85
+ if (flush) decoded += cursor.decoder.end();
86
+ return prepareShellText(visibleText(cursor, decoded), cursor.fold);
87
+ }
88
+
89
+ // One unseen file range [start, end) in the delivery shape. A long range is
90
+ // read only at its ends; the tail starts a fresh decoder at a character
91
+ // boundary, as the original paging did when it skipped bytes.
92
+ function readRange(fd, cursor, { start, end, label, flush, path }) {
93
+ const total = end - start;
94
+ if (total <= FULL_READ_BYTES) {
95
+ const text = decodeVisible(cursor, readBytes(fd, start, total), flush);
96
+ return { piece: textPiece(text, { path }), bytes: total, lines: countNewlines(text), text };
97
+ }
98
+ const headText = decodeVisible(cursor, readBytes(fd, start, EDGE_READ_BYTES), false);
99
+ const tailStart = alignedOffset(fd, end - EDGE_READ_BYTES, label || 'utf-8');
100
+ cursor.decoder = new ShellStreamDecoder(oemTextEncoding(), label);
101
+ cursor.ansi = '';
102
+ cursor.fold = createCarriageReturnFolder();
103
+ const tailText = decodeVisible(cursor, readBytes(fd, tailStart, end - tailStart), flush);
104
+ const lines = countNewlinesInRange(fd, start, end);
105
+ return {
106
+ piece: edgesPiece(headText, tailText, { totalBytes: total, totalLines: lines, path }),
107
+ bytes: total,
108
+ lines,
109
+ text: tailText,
110
+ };
111
+ }
112
+
113
+ const PERCENT_RE = /(\d{1,3}(?:\.\d+)?)\s?%/g;
114
+ const ETA_RE = /\b(?:ETA|eta|remaining|left)\b[:=\s]*(\d+(?::\d{2}){1,2}|\d+(?:\.\d+)?\s?(?:h|m|s|min|sec)\b)/g;
115
+
116
+ function lastMatch(pattern, text) {
117
+ let found = null;
118
+ for (const match of text.matchAll(pattern)) found = match;
119
+ return found;
120
+ }
121
+
122
+ /** What changed since the agent last saw the job: growth, last line, any
123
+ * visible percent/ETA. Used where a still-running job is reported. */
124
+ function progressDigest(growth) {
125
+ const bytes = growth.reduce((sum, entry) => sum + entry.bytes, 0);
126
+ const lines = growth.reduce((sum, entry) => sum + entry.lines, 0);
127
+ const text = growth
128
+ .map((entry) => entry.text)
129
+ .filter(Boolean)
130
+ .join('\n');
131
+ const parts = [
132
+ bytes > 0 ? `+${formatKb(bytes)}, +${lines} lines since you last saw it` : 'no new output since you last saw it',
133
+ ];
134
+ const last = text
135
+ .split('\n')
136
+ .reverse()
137
+ .find((line) => line.trim());
138
+ if (last) parts.push(`last line: ${JSON.stringify(last.trim().slice(0, 200))}`);
139
+ const percent = lastMatch(PERCENT_RE, text);
140
+ if (percent && Number(percent[1]) <= 100) parts.push(`${percent[1]}%`);
141
+ const eta = lastMatch(ETA_RE, text);
142
+ if (eta) parts.push(`ETA ${eta[1].trim()}`);
143
+ return `[progress] ${parts.join(' · ')} — still running, not an error`;
144
+ }
145
+
146
+ /** A whole finished log as a piece of the delivery (a foreground command whose
147
+ * capture spilled to disk), read with a fresh cursor. */
148
+ export function shellLogPiece(path) {
149
+ const cursor = newCursor();
150
+ let fd;
151
+ try {
152
+ fd = openSync(path, 'r');
153
+ const size = fstatSync(fd).size;
154
+ const label = cursor.sniffer.probe(fd, size, true);
155
+ if (label) cursor.decoder.force(label);
156
+ return readRange(fd, cursor, { start: 0, end: size, label, flush: true, path }).piece;
157
+ } finally {
158
+ if (fd !== undefined) closeSync(fd);
159
+ }
160
+ }
161
+
48
162
  // Cursor ownership follows the authorized task object, not a globally supplied
49
163
  // task id. Bounded synchronous reads make concurrent task reads non-overlapping.
50
164
  // Only bytes actually read advance the cursor; previews never stand in for a
51
165
  // file offset, since a preview may contain an omitted middle.
52
- // Explicit tail reads never change the incremental cursor. A terminal read
53
- // prioritizes the final diagnostics; any skipped bytes remain in the raw file.
54
- export function readShellTaskOutput(task, result = {}, { output = 'new' } = {}) {
166
+ // Explicit tail reads (`output: 'tail'`) never change the incremental cursor.
167
+ // `terminal` flushes the decoders (the producer finished); `digest` prefixes
168
+ // the progress digest for a job reported while still running.
169
+ export function readShellTaskOutput(
170
+ task,
171
+ result = {},
172
+ { output = 'new', terminal = task.status !== 'running', digest = false } = {}
173
+ ) {
55
174
  const replay = output === 'tail';
56
- const terminal = task.status !== 'running';
57
- const limit = terminal || replay ? TOOL_OUTPUT_TAIL_BYTES : TOOL_OUTPUT_PAGE_BYTES;
175
+ const growth = [];
58
176
  let streams = replay ? new Map() : cursors.get(task);
59
177
  if (!streams) {
60
178
  streams = new Map();
61
179
  cursors.set(task, streams);
62
180
  }
181
+ // A note, or a stream's body: stdout and stderr share one delivery budget.
63
182
  const sections = [];
64
183
  for (const stream of ['stdout', 'stderr']) {
65
184
  const path = result[`${stream}_path`];
@@ -73,15 +192,15 @@ export function readShellTaskOutput(task, result = {}, { output = 'new' } = {})
73
192
  if (!path) {
74
193
  const preview = String(result[`${stream}_preview`] || '');
75
194
  const delta = preview.startsWith(cursor.preview) ? preview.slice(cursor.preview.length) : preview;
76
- const bytes = Buffer.from(delta);
77
- const start = utf8Boundary(bytes, Math.max(0, bytes.length - limit), bytes.length);
78
- if (start) {
79
- sections.push(`[${stream}: ${start} preview bytes omitted; no original log path available]`);
80
- cursor.ansi = '';
81
- }
82
- const text = visibleText(cursor, bytes.subarray(start).toString('utf8'));
195
+ const text = prepareShellText(visibleText(cursor, delta), cursor.fold);
83
196
  cursor.preview = preview;
84
- if (text) sections.push(`[${stream}]\n${text}`);
197
+ growth.push({ bytes: Buffer.byteLength(delta), lines: countNewlines(text), text });
198
+ // A preview has no log of its own: a capped one is saved for the marker.
199
+ if (text)
200
+ sections.push({
201
+ prefix: `[${stream}]\n`,
202
+ piece: textPiece(text, { path: () => persistDeliveryLog(text, stream) }),
203
+ });
85
204
  continue;
86
205
  }
87
206
  let fd;
@@ -102,30 +221,37 @@ export function readShellTaskOutput(task, result = {}, { output = 'new' } = {})
102
221
  cursor.decoder.force(label);
103
222
  }
104
223
  const readEnd = label ? size : cursor.sniffer.safeEnd(size);
105
- let start = terminal || replay ? Math.max(cursor.offset, readEnd - limit) : cursor.offset;
106
- if (start > cursor.offset) {
107
- start = Math.min(readEnd, alignedOffset(fd, start, label || 'utf-8'));
108
- sections.push(`[${stream}: ${start - cursor.offset} earlier bytes omitted; read ${path} for the original log]`);
224
+ let start = cursor.offset;
225
+ let earlier = '';
226
+ if (replay && readEnd - start > FULL_READ_BYTES) {
227
+ // A replay shows the recent part; everything before it is counted.
228
+ start = Math.min(readEnd, alignedOffset(fd, readEnd - FULL_READ_BYTES, label || 'utf-8'));
229
+ earlier = omissionMarker({ lines: countNewlinesInRange(fd, 0, start), bytes: start, path });
109
230
  cursor.decoder = new ShellStreamDecoder(oemTextEncoding(), label);
110
- cursor.ansi = '';
111
- }
112
- const buffer = Buffer.alloc(Math.min(limit, Math.max(0, readEnd - start)));
113
- const count = buffer.length ? readSync(fd, buffer, 0, buffer.length, start) : 0;
114
- cursor.offset = start + count;
115
- let decoded = cursor.decoder.write(buffer.subarray(0, count));
116
- if (terminal && cursor.offset === size) decoded += cursor.decoder.end();
117
- const text = visibleText(cursor, decoded);
118
- if (text) sections.push(`[${stream}]\n${text}`);
119
- if (cursor.offset < size) {
120
- sections.push(
121
- `[${stream}: ${size - cursor.offset} unread bytes; task read continues, read ${path} for the original log]`
122
- );
123
231
  }
232
+ const range = readRange(fd, cursor, { start, end: readEnd, label, flush: terminal && readEnd === size, path });
233
+ cursor.offset = readEnd;
234
+ growth.push(range);
235
+ if (range.piece.natural)
236
+ sections.push({ prefix: `[${stream}]\n${earlier ? `${earlier}\n` : ''}`, piece: range.piece });
124
237
  } catch (error) {
125
238
  sections.push(`[${stream} read error: ${error.message}]`);
126
239
  } finally {
127
240
  if (fd !== undefined) closeSync(fd);
128
241
  }
129
242
  }
130
- return sections.join('\n\n') || (replay ? '(no output)' : '(no new output)');
243
+ // Notes and prefixes come off the budget; the streams split what is left.
244
+ const fixed = sections.reduce(
245
+ (sum, item) => sum + (item.prefix ?? item).length,
246
+ 2 * Math.max(0, sections.length - 1)
247
+ );
248
+ const pieces = sections.filter((item) => item.piece);
249
+ const bodies = fitPieces(
250
+ pieces.map((item) => item.piece),
251
+ DELIVERY_FULL_CHARS - fixed
252
+ );
253
+ const body =
254
+ sections.map((item) => (item.piece ? `${item.prefix}${bodies[pieces.indexOf(item)]}` : item)).join('\n\n') ||
255
+ (replay ? '(no output)' : '(no new output)');
256
+ return escapeHarnessTags(digest ? `${progressDigest(growth)}\n\n${body}` : body);
131
257
  }
@@ -6,10 +6,16 @@ import { markScopedCacheIncomplete, markScopedCacheUnsafe } from '../../session/
6
6
  import { cacheGet, cacheSet, lstatPathsForMtime } from './cache-layers.mjs';
7
7
  import { runScopedSearchInFlight } from './lib/scoped-search-flight.mjs';
8
8
  import { NOISE_DIR_NAMES, walkDir } from './glob-walk.mjs';
9
- import { TOOL_OUTPUT_MAX_BYTES } from './tool-output-limit.mjs';
9
+ import { TOOL_OUTPUT_MAX_BYTES, capMarker } from './tool-output-limit.mjs';
10
10
  import { runRgWindowedLines } from './native-search-runner.mjs';
11
11
  import { tryServeListMetadata } from './native-search-client.mjs';
12
- import { countTextFileLines, guardedWalkRoot, normalizeListHeadLimit, pageContinuationLine } from './lib/list-helpers.mjs';
12
+ import {
13
+ countTextFileLines,
14
+ guardedWalkRoot,
15
+ comparePathsStable,
16
+ normalizeListHeadLimit,
17
+ pageContinuationLine,
18
+ } from './lib/list-helpers.mjs';
13
19
  import { reportToolProgress } from './lib/tool-progress.mjs';
14
20
  import { displayRelPath, statWalkRoot } from './list-tool-shared.mjs';
15
21
  import { executeFindFilesTool } from './find-files-tool.mjs';
@@ -338,9 +344,9 @@ async function fillRowMetadata(rows, workDir, options) {
338
344
  }
339
345
 
340
346
  function sortListRows(rows, sort) {
341
- if (sort === 'mtime') rows.sort((a, b) => b.mtimeMs - a.mtimeMs);
342
- else if (sort === 'size') rows.sort((a, b) => b.size - a.size);
343
- else rows.sort((a, b) => a.path.localeCompare(b.path));
347
+ if (sort === 'mtime') rows.sort((a, b) => b.mtimeMs - a.mtimeMs || comparePathsStable(a.path, b.path));
348
+ else if (sort === 'size') rows.sort((a, b) => b.size - a.size || comparePathsStable(a.path, b.path));
349
+ else rows.sort((a, b) => a.path.localeCompare(b.path) || comparePathsStable(a.path, b.path));
344
350
  }
345
351
 
346
352
  // What an empty page means: offset past the end is a windowing condition,
@@ -390,15 +396,17 @@ async function scanList(request, cacheKey, workDir, options) {
390
396
  if (root.error) return root.error;
391
397
  if (!root.stat.isDirectory()) {
392
398
  if (!root.stat.isFile()) return `Error: not a directory — ${normalizeOutputPath(fullPath)}`;
393
- const out = listFileLine(fullPath, root.stat, meta, meta ? await countTextFileLines(fullPath, root.stat.size) : null);
399
+ const out = listFileLine(
400
+ fullPath,
401
+ root.stat,
402
+ meta,
403
+ meta ? await countTextFileLines(fullPath, root.stat.size) : null
404
+ );
394
405
  cacheSet(cacheKey, out, { scopes: [fullPath] });
395
406
  return out;
396
407
  }
397
- const { rows, walkResult, walkWarnings, truncatedByCap, nativeDeep, cacheSafe, nativeMetaMissing } = await collectListRows(
398
- request,
399
- workDir,
400
- options
401
- );
408
+ const { rows, walkResult, walkWarnings, truncatedByCap, nativeDeep, cacheSafe, nativeMetaMissing } =
409
+ await collectListRows(request, workDir, options);
402
410
  const rootFailure = walkRootFailureLine(options, walkResult, walkWarnings, 'list walk');
403
411
  if (rootFailure) return rootFailure;
404
412
  if (!nativeDeep && needsGlobalStat) await fillRowMetadata(rows, workDir, options);
@@ -439,7 +447,11 @@ async function renderListPage({ request, rows, walkWarnings, truncatedByCap, nat
439
447
  if (paged) lines.push(pageContinuationLine(offset, sliced.length, rows.length));
440
448
  if (truncatedByCap) {
441
449
  lines.push(
442
- `... walk truncated at ${LIST_ABSOLUTE_CAP} rows or ${LIST_WALK_TIMEOUT_MS}ms timeout; narrow the path or lower depth for a complete listing`
450
+ capMarker({
451
+ limit: `${LIST_ABSOLUTE_CAP} rows or ${LIST_WALK_TIMEOUT_MS}ms`,
452
+ shown: 'listing incomplete',
453
+ narrow: 'narrow the path or lower depth',
454
+ })
443
455
  );
444
456
  }
445
457
  for (const warning of walkWarnings) lines.push(directoryReadFailureLine(warning, true));
@@ -516,7 +528,7 @@ function renderTreeOutput(lines, walkWarnings, { headLimit, offset }) {
516
528
  outputCharTruncated = true;
517
529
  out =
518
530
  out.slice(0, TOOL_OUTPUT_MAX_BYTES) +
519
- `\n... [output truncated at ${Math.round(TOOL_OUTPUT_MAX_BYTES / 1024)} KB; narrow path or lower depth]`;
531
+ `\n${capMarker({ limit: TOOL_OUTPUT_MAX_BYTES, narrow: 'narrow path or lower depth' })}`;
520
532
  }
521
533
  return { out, outputCharTruncated };
522
534
  }
@@ -8,7 +8,8 @@
8
8
  //
9
9
  // MIXDOG_SEARCH_SERVER=0 disables; MIXDOG_SEARCH_SERVER_BIN overrides the
10
10
  // executable.
11
- import { existsSync } from 'node:fs';
11
+ import { existsSync, lstatSync, mkdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
12
+ import { basename, dirname, isAbsolute, join, resolve as resolvePath, sep } from 'node:path';
12
13
  import { bindChildLifecycle, createNativeSearchTransport } from './native-search-transport.mjs';
13
14
  import { invalidateBuiltinResultCache } from './cache-layers.mjs';
14
15
  import { getPluginData } from '../../config.mjs';
@@ -254,6 +255,32 @@ export async function shutdownNativeSearchServer(reason = 'process-exit', timeou
254
255
 
255
256
  export { bindChildLifecycle as _bindNativeSearchServerLifecycle };
256
257
 
258
+ // Inventories persisted before search roots were canonicalized (see
259
+ // canonicalPath) can hold another spelling of a folder's files under the
260
+ // folder's canonical key; loading one would keep serving every file twice.
261
+ // Drop that snapshot once per data dir, before a server of this process loads
262
+ // it. The marker records that the remaining snapshot was written by servers
263
+ // that only ever saw canonical roots.
264
+ const INVENTORY_SNAPSHOT_FILE = 'file-inventories-v1.bin';
265
+ const CANONICAL_ROOTS_MARKER = 'file-inventories-v1.canonical-roots';
266
+ let _inventorySnapshotChecked = false;
267
+
268
+ function dropAliasSpelledInventorySnapshot() {
269
+ if (_inventorySnapshotChecked) return;
270
+ _inventorySnapshotChecked = true;
271
+ const dir = join(getPluginData(), 'search-index');
272
+ const marker = join(dir, CANONICAL_ROOTS_MARKER);
273
+ if (existsSync(marker)) return;
274
+ try {
275
+ rmSync(join(dir, INVENTORY_SNAPSHOT_FILE), { force: true });
276
+ mkdirSync(dir, { recursive: true });
277
+ writeFileSync(marker, '');
278
+ } catch {
279
+ // A snapshot that cannot be removed now (held open by another server) is
280
+ // retried by the next process; search itself does not depend on it.
281
+ }
282
+ }
283
+
257
284
  function _ensureServer() {
258
285
  if (_server) return _server;
259
286
  if (
@@ -261,6 +288,7 @@ function _ensureServer() {
261
288
  Date.now() - _lastFailureAt < RESTART_BACKOFF_MS
262
289
  )
263
290
  return null;
291
+ dropAliasSpelledInventorySnapshot();
264
292
  let transport;
265
293
  try {
266
294
  transport = createNativeSearchTransport({
@@ -633,15 +661,93 @@ function consumedDeadline(response, deadlineMs) {
633
661
  );
634
662
  }
635
663
 
664
+ // The server walks a root as the request spells it, but its file-change
665
+ // updates carry the resolved path. Any other spelling of the same folder —
666
+ // letter case on Windows, a junction or symlink, a `..` segment — therefore
667
+ // listed every file twice (one copy per spelling). Roots are resolved once,
668
+ // here, so the first walk, later updates and the inventory key agree.
669
+ function canonicalPath(path) {
670
+ try {
671
+ return realpathSync.native(path);
672
+ } catch {
673
+ return path;
674
+ }
675
+ }
676
+
677
+ // One positional operand resolved against the canonical cwd. An operand that
678
+ // is already canonical is sent unchanged. An exact file that is itself a
679
+ // symlink keeps its own name: --glob/--type filter the name the caller asked
680
+ // for. `file` tells restoreOperandSpelling how the server prints the operand.
681
+ function canonicalOperand(cwd, operand) {
682
+ const joined = isAbsolute(operand) ? operand : `${cwd}${sep}${operand}`;
683
+ let canonical;
684
+ let file;
685
+ try {
686
+ file = statSync(joined).isFile();
687
+ canonical =
688
+ file && lstatSync(joined).isSymbolicLink()
689
+ ? join(realpathSync.native(dirname(joined)), basename(joined))
690
+ : realpathSync.native(joined);
691
+ } catch {
692
+ return null;
693
+ }
694
+ if (canonical === (isAbsolute(operand) ? operand : resolvePath(cwd, operand))) return null;
695
+ // The server prints results with `/` when the operand only uses `/`.
696
+ const sent = operand.includes('/') && !operand.includes('\\') ? canonical.replaceAll('\\', '/') : canonical;
697
+ if (sent === operand) return null;
698
+ // A directory is printed with its trailing separators trimmed.
699
+ const printed = (path) => (file ? path : path.replace(/[\\/]+$/, ''));
700
+ return { sent, from: printed(sent), to: printed(operand), file };
701
+ }
702
+
703
+ // Canonical cwd and operands for a search request, plus the operands whose
704
+ // spelling restoreOperandSpelling puts back into the result lines. Positional
705
+ // operands follow the last `--` (search-builders emits it before them; a `--`
706
+ // pattern is always an `-e` value before it).
707
+ function canonicalSearchRequest(cwd, argsList) {
708
+ const root = canonicalPath(cwd);
709
+ const args = argsList.map(String);
710
+ const remaps = [];
711
+ const operandsAt = args.lastIndexOf('--') + 1;
712
+ if (operandsAt > 0) {
713
+ for (let i = operandsAt; i < args.length; i += 1) {
714
+ const remap = canonicalOperand(root, args[i]);
715
+ if (!remap) continue;
716
+ args[i] = remap.sent;
717
+ remaps.push(remap);
718
+ }
719
+ }
720
+ remaps.sort((a, b) => b.from.length - a.from.length);
721
+ return { cwd: root, args, remaps };
722
+ }
723
+
724
+ // Results under a canonicalized operand are printed by the server as
725
+ // `<operand><sep><relative>` (a directory) or as the operand itself followed
726
+ // by `:`/`-` fields (an exact file); show them under the caller's spelling.
727
+ function restoreOperandSpelling(lines, remaps) {
728
+ if (remaps.length === 0) return lines;
729
+ return lines.map((line) => {
730
+ if (typeof line !== 'string') return line;
731
+ for (const { from, to, file } of remaps) {
732
+ if (!line.startsWith(from)) continue;
733
+ const rest = line.slice(from.length);
734
+ if (rest !== '' && !(file ? /^[:-]/ : /^[\\/]/).test(rest)) continue;
735
+ return `${to}${rest}`;
736
+ }
737
+ return line;
738
+ });
739
+ }
740
+
636
741
  /** Returns a runRgWindowedLines-shaped result, or null when the native server
637
742
  * is disabled or the response carries no line list. */
638
743
  export async function tryServeSearch(argsList, execOptions = {}, opts = {}) {
639
744
  if (searchServerDisabled()) return null;
640
745
  const deadlineMs = callerDeadlineMs(execOptions, REQUEST_TIMEOUT_MS);
746
+ const canonical = canonicalSearchRequest(String(execOptions.cwd || process.cwd()), argsList);
641
747
  const buildRequest = (server, remaining) => ({
642
748
  id: ++server.sequence,
643
- cwd: String(execOptions.cwd || process.cwd()),
644
- args: argsList.map(String),
749
+ cwd: canonical.cwd,
750
+ args: [...canonical.args],
645
751
  offset: Math.max(0, Math.floor(Number(opts.offset) || 0)),
646
752
  limit: Number.isFinite(Number(opts.limit)) && Number(opts.limit) > 0 ? Math.floor(Number(opts.limit)) : 0,
647
753
  deadlineMs: softDeadlineMs(remaining),
@@ -701,7 +807,7 @@ export async function tryServeSearch(argsList, execOptions = {}, opts = {}) {
701
807
  ? response.walkErrorDetails.map(String).slice(0, 8)
702
808
  : [];
703
809
  return {
704
- lines: response.lines,
810
+ lines: restoreOperandSpelling(response.lines, canonical.remaps),
705
811
  complete: response.complete === true,
706
812
  totalSeen: Math.max(0, Math.floor(Number(response.totalSeen) || 0)),
707
813
  partial: response.partial === true,
@@ -731,7 +837,8 @@ export async function tryServeFuzzySearch(args, execOptions = {}) {
731
837
  const response = await requestNativeWithRestart(
732
838
  (server, remaining) => ({
733
839
  id: ++server.sequence,
734
- cwd: String(args?.cwd || execOptions.cwd || process.cwd()),
840
+ // Matches are relative to cwd, so only the root needs canonicalizing.
841
+ cwd: canonicalPath(String(args?.cwd || execOptions.cwd || process.cwd())),
735
842
  fuzzy: String(args?.query || ''),
736
843
  limit: Math.max(1, Math.min(1_000, Math.floor(Number(args?.limit) || 25))),
737
844
  hidden: args?.hidden !== false,
@@ -813,4 +920,5 @@ export function _resetNativeSearchClientForTest() {
813
920
  _binaryResolveStarted = false;
814
921
  _warmPromise = null;
815
922
  _lastTimeoutRecycleAt = 0;
923
+ _inventorySnapshotChecked = false;
816
924
  }
@@ -1,4 +1,5 @@
1
1
  import { READ_BATCH_RANGE_COALESCE_GAP_LINES } from './read-constants.mjs';
2
+ import { windowMarker } from './tool-output-limit.mjs';
2
3
 
3
4
  // Slices a `read` body (full mode) to the line window [origOffset+1,
4
5
  // origOffset+origLimit] (1-based line numbers in the body). Lines whose
@@ -81,15 +82,15 @@ export function sliceReadBodyByLines(body, origOffset, origLimit, readOffsetBase
81
82
  else if (finiteLast) requestedLast = lastLine;
82
83
  const emittedStart = keptFirst ?? firstLine;
83
84
  const emittedLast = keptLast !== null ? Math.min(requestedLast, keptLast) : requestedLast;
84
- const totalPart = haveTotal ? ` of ${totalNum}` : '';
85
85
  const moreToRead = haveTotal ? emittedLast < totalNum : finiteLast;
86
86
  // Report the next caller coordinate without prescribing another read;
87
87
  // whatever remains fits one wider read, not a walk window by window.
88
- let continuationPart = '';
89
- if (moreToRead && Number.isFinite(emittedLast)) {
90
- continuationPart = `; pass offset:${emittedLast + readOffsetBase} to continue`;
91
- }
92
- const newFooter = `[lines ${emittedStart}-${emittedLast}${totalPart}${continuationPart}]`;
88
+ const newFooter = windowMarker({
89
+ from: emittedStart,
90
+ to: emittedLast,
91
+ total: haveTotal ? totalNum : null,
92
+ nextOffset: moreToRead && Number.isFinite(emittedLast) ? emittedLast + readOffsetBase : null,
93
+ });
93
94
  return kept.join('\n') + (kept.length ? '\n' : '') + newFooter;
94
95
  }
95
96
 
@@ -1,6 +1,6 @@
1
1
  import { READ_LINE_NO_SEP } from '../../../../shared/read-row-numbers.mjs';
2
2
  import { mergeReadRanges } from './read-ranges.mjs';
3
- import { TOOL_OUTPUT_MAX_BYTES, positiveEnvInt } from './tool-output-limit.mjs';
3
+ import { TOOL_OUTPUT_MAX_BYTES, capMarker, positiveEnvInt } from './tool-output-limit.mjs';
4
4
 
5
5
  // Smart-truncate cap: a no-window read returns the file until this cap, past
6
6
  // which head+tail are shown and the model pages the rest with offset (footer
@@ -18,9 +18,23 @@ const READ_MAX_RENDERED_LINE_CHARS = 2_000;
18
18
  // (runtime/shared/read-row-numbers.mjs).
19
19
  export const LINE_NO_SEP = READ_LINE_NO_SEP;
20
20
 
21
- export function buildSmartReadTruncationMarker(totalLines, fileBytes) {
21
+ // Cap marker for the elided middle of a no-window read. With the omitted
22
+ // line span it names the continuation offset; without, narrowing advice.
23
+ export function buildSmartReadTruncationMarker(totalLines, fileBytes, omitted = null) {
22
24
  const kb = Math.max(1, Math.round((Number(fileBytes) || 0) / 1024));
23
- return `... [TRUNCATED - ${totalLines} lines / ${kb} KB; read located windows with {file_path, offset, limit}] ...`;
25
+ const limit = fileBytes > SMART_READ_MAX_BYTES ? SMART_READ_MAX_BYTES : `${SMART_READ_MAX_LINES} lines`;
26
+ if (!omitted) {
27
+ return capMarker({
28
+ limit,
29
+ shown: `${totalLines} lines / ${kb} KB`,
30
+ narrow: 'read located windows with {file_path, offset, limit}',
31
+ });
32
+ }
33
+ return capMarker({
34
+ limit,
35
+ shown: `lines ${omitted.from}-${omitted.to} of ${totalLines} omitted (${kb} KB file)`,
36
+ nextOffset: omitted.from,
37
+ });
24
38
  }
25
39
 
26
40
  function rangeFromRenderedReadRows(rows, fallbackStartLine = 1) {
@@ -56,9 +70,10 @@ export function smartReadTruncate(renderedWithLineNos, totalLines, fileBytes) {
56
70
  const tailRows = rows.slice(tailStart);
57
71
  const head = headRows.join('\n');
58
72
  const tail = tailRows.join('\n');
59
- const marker = buildSmartReadTruncationMarker(totalLines, fileBytes);
73
+ const marker = buildSmartReadTruncationMarker(totalLines, fileBytes, { from: headCount + 1, to: tailStart });
60
74
  return {
61
75
  text: `${head}\n${marker}\n${tail}`,
76
+ marker,
62
77
  truncated: true,
63
78
  totalLines,
64
79
  ranges: mergeReadRanges(
@@ -5,6 +5,7 @@
5
5
  import { READ_MAX_LINE_COLLECT_BYTES } from './read-constants.mjs';
6
6
  import { renderReadLine } from './read-formatting.mjs';
7
7
  import { maybeRecordReadRangeAnchor } from './read-range-index.mjs';
8
+ import { capMarker, windowMarker } from './tool-output-limit.mjs';
8
9
 
9
10
  export function createReadLineCollector({ offset, limit, bodyOutputBytes, rangeIndex, startLine, fileSize = 0 }) {
10
11
  const collected = [];
@@ -148,10 +149,11 @@ export function createReadLineCollector({ offset, limit, bodyOutputBytes, rangeI
148
149
  let out = collected.join('\n');
149
150
  if (truncated) {
150
151
  const nextOffset = (lastEmitted || offset) + readOffsetBase;
151
- const ofFile = fileSize > 0 ? ` of a ${Math.max(1, Math.round(fileSize / 1024))} KB file` : '';
152
- out += `\n\n... [output truncated at ${Math.max(1, Math.round(maxOutputBytes / 1024))} KB${ofFile}; pass offset:${nextOffset} to continue] ...`;
152
+ const ofFile = fileSize > 0 ? `${Math.max(1, Math.round(fileSize / 1024))} KB file` : '';
153
+ out += `\n\n${capMarker({ limit: maxOutputBytes, shown: ofFile, nextOffset })}`;
153
154
  } else if (stoppedAtLimit) {
154
- out += `${out ? '\n' : ''}... [range limit reached; next offset: ${offset + collected.length + readOffsetBase}]`;
155
+ const nextOffset = offset + collected.length + readOffsetBase;
156
+ out += `${out ? '\n' : ''}${windowMarker({ from: offset + 1, to: offset + collected.length, nextOffset })}`;
155
157
  } else if (!out && offset >= lineIdx) {
156
158
  out = `(no lines in range; file has ${lineIdx} lines)`;
157
159
  }