mixdog 0.7.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 (404) hide show
  1. package/.claude-plugin/marketplace.json +31 -0
  2. package/.claude-plugin/plugin.json +20 -0
  3. package/.gitattributes +34 -0
  4. package/.mcp.json +14 -0
  5. package/ARCHITECTURE.md +77 -0
  6. package/CHANGELOG.md +7 -0
  7. package/CONTRIBUTING.md +45 -0
  8. package/DATA-FLOW.md +79 -0
  9. package/LICENSE +21 -0
  10. package/README.md +389 -0
  11. package/SECURITY.md +138 -0
  12. package/UNINSTALL.md +112 -0
  13. package/agents/maintenance.md +5 -0
  14. package/agents/memory-classification.md +30 -0
  15. package/agents/scheduler-task.md +18 -0
  16. package/agents/webhook-handler.md +27 -0
  17. package/agents/worker.md +24 -0
  18. package/bin/bridge +133 -0
  19. package/bin/statusline-launcher.mjs +78 -0
  20. package/bin/statusline-lib.mjs +550 -0
  21. package/bin/statusline.mjs +607 -0
  22. package/bun.lock +802 -0
  23. package/commands/config.md +16 -0
  24. package/commands/doctor.md +13 -0
  25. package/commands/setup.md +17 -0
  26. package/defaults/cycle3-review-prompt.md +90 -0
  27. package/defaults/hidden-roles.json +65 -0
  28. package/defaults/memory-chunk-prompt.md +63 -0
  29. package/defaults/memory-promote-prompt.md +135 -0
  30. package/defaults/mixdog-config.template.json +27 -0
  31. package/defaults/user-workflow.json +8 -0
  32. package/defaults/user-workflow.md +12 -0
  33. package/hooks/hooks.json +73 -0
  34. package/hooks/lib/active-instance.cjs +77 -0
  35. package/hooks/lib/permission-evaluator.cjs +411 -0
  36. package/hooks/lib/permission-route.cjs +63 -0
  37. package/hooks/lib/permission-rules.cjs +170 -0
  38. package/hooks/lib/settings-loader.cjs +116 -0
  39. package/hooks/post-tool-use.cjs +84 -0
  40. package/hooks/pre-mcp-sandbox.cjs +158 -0
  41. package/hooks/pre-tool-subagent.cjs +253 -0
  42. package/hooks/session-start.cjs +1372 -0
  43. package/hooks/turn-timer.cjs +82 -0
  44. package/lib/claude-md-writer.cjs +386 -0
  45. package/lib/config-cjs.cjs +61 -0
  46. package/lib/hook-pipe-path.cjs +10 -0
  47. package/lib/keychain-cjs.cjs +263 -0
  48. package/lib/plugin-paths.cjs +61 -0
  49. package/lib/rules-builder.cjs +241 -0
  50. package/lib/text-utils.cjs +61 -0
  51. package/native/README.md +117 -0
  52. package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
  53. package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
  54. package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
  55. package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
  56. package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
  57. package/package.json +107 -0
  58. package/prompts/code-review.txt +16 -0
  59. package/prompts/security-audit.txt +17 -0
  60. package/rules/bridge/00-common.md +39 -0
  61. package/rules/bridge/20-skip-protocol.md +18 -0
  62. package/rules/bridge/30-explorer.md +33 -0
  63. package/rules/bridge/40-cycle1-agent.md +52 -0
  64. package/rules/bridge/41-cycle2-agent.md +62 -0
  65. package/rules/bridge/42-cycle3-agent.md +44 -0
  66. package/rules/lead/00-tool-lead.md +61 -0
  67. package/rules/lead/01-general.md +23 -0
  68. package/rules/lead/02-channels.md +49 -0
  69. package/rules/lead/03-team.md +27 -0
  70. package/rules/lead/04-workflow.md +20 -0
  71. package/rules/shared/00-language.md +14 -0
  72. package/rules/shared/01-tool.md +138 -0
  73. package/scripts/bootstrap.mjs +184 -0
  74. package/scripts/bridge-unify-smoke.mjs +308 -0
  75. package/scripts/build-runtime-linux.sh +348 -0
  76. package/scripts/build-runtime-macos.sh +217 -0
  77. package/scripts/build-runtime-windows.ps1 +242 -0
  78. package/scripts/builtin-utils-smoke.mjs +392 -0
  79. package/scripts/check-json.mjs +45 -0
  80. package/scripts/check-syntax-changed.mjs +102 -0
  81. package/scripts/check-syntax.mjs +58 -0
  82. package/scripts/code-graph-batch.test.mjs +33 -0
  83. package/scripts/config-preserve-smoke.mjs +180 -0
  84. package/scripts/doctor.mjs +484 -0
  85. package/scripts/edit-normalize-fuzz.mjs +130 -0
  86. package/scripts/edit-normalize-smoke.mjs +401 -0
  87. package/scripts/edit-operation-smoke.mjs +369 -0
  88. package/scripts/edit2-smoke.mjs +63 -0
  89. package/scripts/fuzzy-e2e.mjs +28 -0
  90. package/scripts/fuzzy-smoke.mjs +26 -0
  91. package/scripts/generate-runtime-manifest.mjs +166 -0
  92. package/scripts/guard-smoke.mjs +66 -0
  93. package/scripts/hidden-role-schema-smoke.mjs +162 -0
  94. package/scripts/hook-routing-smoke.mjs +29 -0
  95. package/scripts/inject-input.ps1 +204 -0
  96. package/scripts/io-complex-smoke.mjs +667 -0
  97. package/scripts/io-explore-bench.mjs +424 -0
  98. package/scripts/io-guardrails-smoke.mjs +205 -0
  99. package/scripts/io-mini-bench-baseline.json +11 -0
  100. package/scripts/io-mini-bench.mjs +216 -0
  101. package/scripts/io-route-harness.mjs +933 -0
  102. package/scripts/io-telemetry-report.mjs +691 -0
  103. package/scripts/mutation-bench.mjs +564 -0
  104. package/scripts/mutation-io-smoke.mjs +1081 -0
  105. package/scripts/native-patch-bridge-smoke.mjs +288 -0
  106. package/scripts/native-patch-smoke.mjs +304 -0
  107. package/scripts/patch-interior-context-smoke.mjs +49 -0
  108. package/scripts/patch-newline-utf8-smoke.mjs +157 -0
  109. package/scripts/perf-hook-smoke.mjs +71 -0
  110. package/scripts/permission-eval-smoke.mjs +426 -0
  111. package/scripts/prep-patch.mjs +53 -0
  112. package/scripts/prep-shim.mjs +96 -0
  113. package/scripts/provider-cache-smoke.mjs +687 -0
  114. package/scripts/report-runtime-health.mjs +132 -0
  115. package/scripts/run-mcp.mjs +1547 -0
  116. package/scripts/salvage-v4a-shatter.test.mjs +58 -0
  117. package/scripts/scoped-cache-io-smoke.mjs +103 -0
  118. package/scripts/shell-policy-round3-smoke.mjs +46 -0
  119. package/scripts/smoke-runtime-negative.ps1 +100 -0
  120. package/scripts/smoke-runtime-negative.sh +95 -0
  121. package/scripts/stall-policy-smoke.mjs +50 -0
  122. package/scripts/start-memory-worker.mjs +23 -0
  123. package/scripts/statusline-launcher-smoke.mjs +82 -0
  124. package/scripts/stress-atomic-write.mjs +1028 -0
  125. package/scripts/test-config-rmw-restore.mjs +122 -0
  126. package/scripts/test-fault-inject.mjs +164 -0
  127. package/scripts/test-large-file.mjs +174 -0
  128. package/scripts/tool-edge-smoke.mjs +209 -0
  129. package/scripts/uninstall.mjs +201 -0
  130. package/scripts/webhook-selfheal-smoke.mjs +29 -0
  131. package/scripts/write-overwrite-guard-smoke.mjs +56 -0
  132. package/server-main.mjs +3055 -0
  133. package/server.mjs +468 -0
  134. package/setup/config-merge.mjs +254 -0
  135. package/setup/install.mjs +120 -0
  136. package/setup/launch-core.mjs +507 -0
  137. package/setup/launch.mjs +101 -0
  138. package/setup/setup-server.mjs +3206 -0
  139. package/setup/setup.html +3693 -0
  140. package/skills/retro-skill-proposer/SKILL.md +92 -0
  141. package/skills/schedule-add/SKILL.md +77 -0
  142. package/skills/setup/SKILL.md +346 -0
  143. package/skills/webhook-add/SKILL.md +81 -0
  144. package/src/agent/bridge-stall-watchdog.mjs +337 -0
  145. package/src/agent/index.mjs +2138 -0
  146. package/src/agent/orchestrator/activity-bus.mjs +38 -0
  147. package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
  148. package/src/agent/orchestrator/bridge-retry.mjs +220 -0
  149. package/src/agent/orchestrator/bridge-trace.mjs +583 -0
  150. package/src/agent/orchestrator/cache-mtime.mjs +58 -0
  151. package/src/agent/orchestrator/config.mjs +358 -0
  152. package/src/agent/orchestrator/context/collect.mjs +651 -0
  153. package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
  154. package/src/agent/orchestrator/drain-registry.mjs +50 -0
  155. package/src/agent/orchestrator/explore-validator.mjs +8 -0
  156. package/src/agent/orchestrator/internal-roles.mjs +118 -0
  157. package/src/agent/orchestrator/internal-tools.mjs +88 -0
  158. package/src/agent/orchestrator/jobs.mjs +116 -0
  159. package/src/agent/orchestrator/mcp/client.mjs +364 -0
  160. package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
  161. package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
  162. package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
  163. package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
  164. package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
  165. package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
  166. package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
  167. package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
  168. package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
  169. package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
  170. package/src/agent/orchestrator/providers/registry.mjs +192 -0
  171. package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
  172. package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
  173. package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
  174. package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
  175. package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
  176. package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
  177. package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
  178. package/src/agent/orchestrator/session/cache/util.mjs +49 -0
  179. package/src/agent/orchestrator/session/loop.mjs +1478 -0
  180. package/src/agent/orchestrator/session/manager.mjs +1975 -0
  181. package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
  182. package/src/agent/orchestrator/session/result-classification.mjs +65 -0
  183. package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
  184. package/src/agent/orchestrator/session/store.mjs +624 -0
  185. package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
  186. package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
  187. package/src/agent/orchestrator/session/trim.mjs +491 -0
  188. package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
  189. package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
  190. package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
  191. package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
  192. package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
  193. package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
  194. package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
  195. package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
  196. package/src/agent/orchestrator/stall-policy.mjs +195 -0
  197. package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
  198. package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
  199. package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
  200. package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
  201. package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
  202. package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
  203. package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
  204. package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
  205. package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
  206. package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
  207. package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
  208. package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
  209. package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
  210. package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
  211. package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
  212. package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
  213. package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
  214. package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
  215. package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
  216. package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
  217. package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
  218. package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
  219. package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
  220. package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
  221. package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
  222. package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
  223. package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
  224. package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
  225. package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
  226. package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
  227. package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
  228. package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
  229. package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
  230. package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
  231. package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
  232. package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
  233. package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
  234. package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
  235. package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
  236. package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
  237. package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
  238. package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
  239. package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
  240. package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
  241. package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
  242. package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
  243. package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
  244. package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
  245. package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
  246. package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
  247. package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
  248. package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
  249. package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
  250. package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
  251. package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
  252. package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
  253. package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
  254. package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
  255. package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
  256. package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
  257. package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
  258. package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
  259. package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
  260. package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
  261. package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
  262. package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
  263. package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
  264. package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
  265. package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
  266. package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
  267. package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
  268. package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
  269. package/src/agent/orchestrator/tools/builtin.mjs +500 -0
  270. package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
  271. package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
  272. package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
  273. package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
  274. package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
  275. package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
  276. package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
  277. package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
  278. package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
  279. package/src/agent/orchestrator/tools/host-input.mjs +204 -0
  280. package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
  281. package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
  282. package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
  283. package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
  284. package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
  285. package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
  286. package/src/agent/orchestrator/tools/patch.mjs +2754 -0
  287. package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
  288. package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
  289. package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
  290. package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
  291. package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
  292. package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
  293. package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
  294. package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
  295. package/src/agent/orchestrator/workflow-store.mjs +93 -0
  296. package/src/agent/tool-defs.mjs +103 -0
  297. package/src/channels/backends/discord.mjs +784 -0
  298. package/src/channels/data/voice-runtime-manifest.json +138 -0
  299. package/src/channels/index.mjs +3229 -0
  300. package/src/channels/lib/cli-worker-host.mjs +12 -0
  301. package/src/channels/lib/config-lock.mjs +13 -0
  302. package/src/channels/lib/config.mjs +292 -0
  303. package/src/channels/lib/drop-trace.mjs +71 -0
  304. package/src/channels/lib/event-pipeline.mjs +81 -0
  305. package/src/channels/lib/event-queue.mjs +345 -0
  306. package/src/channels/lib/executor.mjs +168 -0
  307. package/src/channels/lib/format.mjs +188 -0
  308. package/src/channels/lib/holidays.mjs +138 -0
  309. package/src/channels/lib/hook-pipe-server.mjs +802 -0
  310. package/src/channels/lib/interaction-workflows.mjs +184 -0
  311. package/src/channels/lib/memory-client.mjs +149 -0
  312. package/src/channels/lib/output-forwarder.mjs +765 -0
  313. package/src/channels/lib/runtime-paths.mjs +479 -0
  314. package/src/channels/lib/scheduler.mjs +723 -0
  315. package/src/channels/lib/session-control.mjs +36 -0
  316. package/src/channels/lib/session-discovery.mjs +103 -0
  317. package/src/channels/lib/settings.mjs +11 -0
  318. package/src/channels/lib/state-file.mjs +68 -0
  319. package/src/channels/lib/status-snapshot.mjs +219 -0
  320. package/src/channels/lib/tool-format.mjs +140 -0
  321. package/src/channels/lib/transcript-discovery.mjs +195 -0
  322. package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
  323. package/src/channels/lib/webhook.mjs +1179 -0
  324. package/src/channels/lib/whisper-server.mjs +477 -0
  325. package/src/channels/tool-defs.mjs +170 -0
  326. package/src/daemon/host.mjs +118 -0
  327. package/src/daemon/mcp-transport.mjs +47 -0
  328. package/src/daemon/session.mjs +100 -0
  329. package/src/daemon/thin-client.mjs +71 -0
  330. package/src/daemon/transport.mjs +163 -0
  331. package/src/memory/data/runtime-manifest.json +40 -0
  332. package/src/memory/index.mjs +3305 -0
  333. package/src/memory/lib/agent-ipc.mjs +93 -0
  334. package/src/memory/lib/bridge-trace-queries.mjs +120 -0
  335. package/src/memory/lib/core-memory-store.mjs +330 -0
  336. package/src/memory/lib/embedding-provider.mjs +269 -0
  337. package/src/memory/lib/embedding-worker.mjs +323 -0
  338. package/src/memory/lib/llm-worker-host.mjs +17 -0
  339. package/src/memory/lib/memory-cycle.mjs +11 -0
  340. package/src/memory/lib/memory-cycle1.mjs +641 -0
  341. package/src/memory/lib/memory-cycle2.mjs +1284 -0
  342. package/src/memory/lib/memory-cycle3.mjs +540 -0
  343. package/src/memory/lib/memory-embed.mjs +299 -0
  344. package/src/memory/lib/memory-extraction.mjs +5 -0
  345. package/src/memory/lib/memory-maintenance-store.mjs +32 -0
  346. package/src/memory/lib/memory-ops-policy.mjs +190 -0
  347. package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
  348. package/src/memory/lib/memory-recall-read-query.mjs +7 -0
  349. package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
  350. package/src/memory/lib/memory-recall-store.mjs +621 -0
  351. package/src/memory/lib/memory-retrievers.mjs +112 -0
  352. package/src/memory/lib/memory-score.mjs +71 -0
  353. package/src/memory/lib/memory-text-utils.mjs +58 -0
  354. package/src/memory/lib/memory.mjs +412 -0
  355. package/src/memory/lib/model-profile.mjs +85 -0
  356. package/src/memory/lib/pg/adapter.mjs +308 -0
  357. package/src/memory/lib/pg/process.mjs +360 -0
  358. package/src/memory/lib/pg/supervisor.mjs +396 -0
  359. package/src/memory/lib/project-id-resolver.mjs +86 -0
  360. package/src/memory/lib/runtime-fetcher.mjs +442 -0
  361. package/src/memory/lib/trace-store.mjs +728 -0
  362. package/src/memory/tool-defs.mjs +79 -0
  363. package/src/search/index.mjs +1173 -0
  364. package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
  365. package/src/search/lib/backends/exa.mjs +50 -0
  366. package/src/search/lib/backends/firecrawl.mjs +61 -0
  367. package/src/search/lib/backends/gemini-api.mjs +83 -0
  368. package/src/search/lib/backends/grok-oauth.mjs +86 -0
  369. package/src/search/lib/backends/index.mjs +150 -0
  370. package/src/search/lib/backends/openai-api.mjs +144 -0
  371. package/src/search/lib/backends/openai-oauth.mjs +98 -0
  372. package/src/search/lib/backends/openai-web-search.mjs +76 -0
  373. package/src/search/lib/backends/tavily.mjs +55 -0
  374. package/src/search/lib/backends/xai-api.mjs +113 -0
  375. package/src/search/lib/cache.mjs +131 -0
  376. package/src/search/lib/config.mjs +192 -0
  377. package/src/search/lib/formatter.mjs +115 -0
  378. package/src/search/lib/provider-usage.mjs +67 -0
  379. package/src/search/lib/providers.mjs +47 -0
  380. package/src/search/lib/search-intent.mjs +109 -0
  381. package/src/search/lib/setup-handler.mjs +261 -0
  382. package/src/search/lib/state.mjs +201 -0
  383. package/src/search/lib/web-tools.mjs +1207 -0
  384. package/src/search/tool-defs.mjs +83 -0
  385. package/src/setup/defender-exclusion.mjs +183 -0
  386. package/src/shared/abort-controller.mjs +15 -0
  387. package/src/shared/atomic-file.mjs +420 -0
  388. package/src/shared/config.mjs +350 -0
  389. package/src/shared/daemon-recycle.mjs +108 -0
  390. package/src/shared/disable-claude-builtins.mjs +88 -0
  391. package/src/shared/err-text.mjs +12 -0
  392. package/src/shared/llm/cost.mjs +66 -0
  393. package/src/shared/llm/http-agent.mjs +123 -0
  394. package/src/shared/llm/index.mjs +41 -0
  395. package/src/shared/llm/pid-cleanup.mjs +27 -0
  396. package/src/shared/llm/usage-log.mjs +47 -0
  397. package/src/shared/plugin-paths.mjs +58 -0
  398. package/src/shared/schedules-store.mjs +70 -0
  399. package/src/shared/seed.mjs +119 -0
  400. package/src/shared/user-cwd.mjs +213 -0
  401. package/src/shared/user-data-guard.mjs +238 -0
  402. package/src/status/aggregator.mjs +584 -0
  403. package/src/status/server.mjs +413 -0
  404. package/tools.json +1653 -0
@@ -0,0 +1,530 @@
1
+ import { readFileSync } from 'fs';
2
+ import { executeSingleReadTool } from './read-single-tool.mjs';
3
+ import { imageMimeForPath, readImageAsContent } from './read-image.mjs';
4
+ import { readEntryCoalescedDiskWindow } from './read-batch.mjs';
5
+ import { readPathStringGuardError } from './read-open.mjs';
6
+ import { parseReadLineNumberArg } from './read-args.mjs';
7
+ import { assertPathsReachable } from './fs-reachability.mjs';
8
+
9
+ function hasLineCoordinate(path) {
10
+ return typeof path === 'string' && /(?:#L\d+|:\d+(?:-\d+)?(?::|$))/i.test(path);
11
+ }
12
+
13
+ // Pure-regex strip of a trailing line coordinate (`:N`, `:N-M`, `#LN`) — NO
14
+ // filesystem access. Used only to derive a statable base path for the async
15
+ // reachability preflight; the real read path does precise line-vs-colon
16
+ // disambiguation later (which uses existsSync and would itself block on a dead
17
+ // mount). A Windows drive colon `C:\...` is not a trailing `:digits`.
18
+ function _stripLineCoordForReach(s) {
19
+ // Mirror the real resolver's coordinate suffix shapes (read-args.mjs):
20
+ // `:N`, `:N-M`, `:N:C` (line:col / trailing detail), and `#LN`/`#LN-M`/`#LN...`.
21
+ return String(s)
22
+ .replace(/#L\d+(?:-L?\d+)?(?:\b.*)?$/i, '')
23
+ .replace(/:\d+(?:-\d+)?(?::.*)?$/, '');
24
+ }
25
+
26
+ function _collectReachCandidates(p) {
27
+ const out = [];
28
+ const push = (s) => { if (typeof s === 'string' && s) out.push(s); };
29
+ if (typeof p === 'string') push(p);
30
+ else if (Array.isArray(p)) for (const e of p) push((e && typeof e === 'object') ? (e.path ?? e.file_path) : e);
31
+ return out;
32
+ }
33
+
34
+ // Same messages the inline string guards emit (image fast-path / single path).
35
+ // Used by the preflight to REJECT guarded paths up front so the later sync
36
+ // existsSync line-coordinate disambiguation never touches a UNC/device path.
37
+ function _guardedReadError(p, helpers) {
38
+ const { isUncPath, isWindowsDevicePath, hasUnsafeWin32Component, isBlockedDevicePath, normalizeOutputPath } = helpers;
39
+ const o = (x) => (typeof normalizeOutputPath === 'function' ? normalizeOutputPath(x) : x);
40
+ if (typeof isUncPath === 'function' && isUncPath(p))
41
+ return `Error: cannot read UNC / SMB path (network credential leak risk): ${o(p)}`;
42
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(p))
43
+ return `Error: cannot read Windows device path (reserved name or raw-device namespace): ${o(p)}`;
44
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(p))
45
+ return `Error: cannot read Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${o(p)}`;
46
+ if (typeof isBlockedDevicePath === 'function' && isBlockedDevicePath(p))
47
+ return `Error: cannot read device file (would block or produce infinite output): ${o(p)}`;
48
+ return null;
49
+ }
50
+
51
+ // Reachability preflight for EVERY read shape (scalar / array / reads[]). MUST
52
+ // run before any sync FS — including line-coordinate disambiguation (existsSync
53
+ // in readPathStringGuardError / normaliseReadLineWindowArgs) and the image
54
+ // stat/read. A dead mount would otherwise freeze the event loop, defeating even
55
+ // the 630s dispatch ceiling.
56
+ async function _readReachPreflight(rawPath, workDir, helpers) {
57
+ const {
58
+ normalizeInputPath, resolveAgainstCwd,
59
+ } = helpers;
60
+ // A guarded path (UNC/SMB, Windows device, ADS, /dev/* block) must be
61
+ // REJECTED here, not skipped: skipping would let the later sync existsSync
62
+ // line-coordinate disambiguation (normaliseReadLineWindowArgs /
63
+ // readPathStringGuardError) touch it and trigger NTLM/raw-device access or
64
+ // hang. Reject up front with the same message the inline guards emit.
65
+ // normalizeInputPath FIRST (FS-pure) so we stat the same path the real read
66
+ // opens (e.g. /mnt/z/... -> Z:\...). Reachability is per-mount/dir, so the
67
+ // line-coordinate strip only needs to land in the right directory — exact
68
+ // suffix parsing is not required for the stat to be representative.
69
+ const candidates = [];
70
+ for (const raw of _collectReachCandidates(rawPath)) {
71
+ const stripped = _stripLineCoordForReach(normalizeInputPath(raw));
72
+ const full = resolveAgainstCwd(stripped, workDir);
73
+ const guardMsg = _guardedReadError(stripped, helpers) || _guardedReadError(full, helpers);
74
+ if (guardMsg) return guardMsg;
75
+ candidates.push(full);
76
+ }
77
+ if (candidates.length === 0) return null;
78
+ try { await assertPathsReachable(candidates); return null; }
79
+ catch (e) { return `Error: ${e?.message || e}`; }
80
+ }
81
+
82
+ function capPositiveNumber(value, max, fallback = max) {
83
+ const n = Number(value);
84
+ if (!Number.isFinite(n) || n <= 0) return fallback;
85
+ return Math.min(max, Math.max(1, Math.trunc(n)));
86
+ }
87
+
88
+ function applyCompactReadBudget(entry) {
89
+ if (!entry || String(entry.budget || '').toLowerCase() !== 'compact') return entry;
90
+ const next = { ...entry };
91
+ const isFullMode = !next.mode || next.mode === 'full';
92
+ const hasLine = next.line !== undefined && next.line !== null;
93
+ const hasRange = next.offset !== undefined || next.limit !== undefined;
94
+ const hasPathLine = hasLineCoordinate(next.path);
95
+ if (hasLine && (next.context === undefined || next.context === null || Number(next.context) > 20)) {
96
+ next.context = 20;
97
+ }
98
+ if (hasRange && (next.limit === undefined || next.limit === null || Number(next.limit) > 120)) {
99
+ next.limit = 120;
100
+ }
101
+ if ((next.mode === 'head' || next.mode === 'tail') && (next.n === undefined || next.n === null || Number(next.n) > 80)) {
102
+ next.n = capPositiveNumber(next.n, 80, 80);
103
+ }
104
+ if (isFullMode && !hasLine && !hasRange && !hasPathLine && next.full !== true) {
105
+ next.mode = 'count';
106
+ }
107
+ return next;
108
+ }
109
+
110
+ export async function executeReadTool(args, workDir, readStateScope, executeChildBuiltinTool, options = {}, helpers = {}) {
111
+ const {
112
+ classifyResultKind,
113
+ coalesceObjectReadEntries,
114
+ coerceShapeFlex,
115
+ isBlockedDevicePath,
116
+ isUncPath,
117
+ isWindowsDevicePath,
118
+ hasUnsafeWin32Component,
119
+ normalizeInputPath,
120
+ normalizeOutputPath,
121
+ normaliseReadLineWindowArgs,
122
+ resolveAgainstCwd,
123
+ sliceReadBodyByLines,
124
+ _hashText,
125
+ _isFullModeReadEntry,
126
+ _mergeReadRanges,
127
+ _rangeHashesFromRenderedReadText,
128
+ _readEntryLineWindow,
129
+ _recordReadSnapshot,
130
+ } = helpers;
131
+ // CC `file_path` alias — official SDK schema uses `file_path`;
132
+ // mixdog has historically used `path`. Honor `file_path` so a
133
+ // CC-trained agent's call shape works without translation.
134
+ const usedFilePathAlias = typeof args.file_path === 'string' && !args.path;
135
+ if (usedFilePathAlias) {
136
+ args.path = args.file_path;
137
+ const ccOffset = Number(args.offset);
138
+ if (args.offset !== undefined && args.offset !== null && Number.isFinite(ccOffset) && ccOffset > 0) {
139
+ args.offset = Math.trunc(ccOffset) - 1;
140
+ }
141
+ }
142
+ args.path = coerceShapeFlex(args.path);
143
+ // Reachability preflight up front (all shapes) — before readPathStringGuardError /
144
+ // normaliseReadLineWindowArgs / image stat, all of which can touch sync FS.
145
+ {
146
+ const _reErr = await _readReachPreflight(args.path, workDir, helpers);
147
+ if (_reErr) return _reErr;
148
+ }
149
+ // Image files (png/jpg/jpeg/gif/webp): return an MCP image block so the
150
+ // model can actually SEE the image. native Read does this, but mixdog's
151
+ // Read is shim-blocked, so this is the only image-view path. Single string
152
+ // path only — batch and head/tail/count modes stay text.
153
+ // options.mediaTextOnly is set by the batch dispatcher (child reads are
154
+ // assembled into a flat string), so an image content-block object would
155
+ // stringify to "[object Object]". Skip the image fast-path in that context
156
+ // and let the file fall through to the normal text/binary read, which
157
+ // emits a string. Scalar reads (no mediaTextOnly) get the rich image block.
158
+ if (options?.mediaTextOnly !== true && typeof args.path === 'string' && imageMimeForPath(args.path)) {
159
+ const _imgNorm = normalizeInputPath(args.path);
160
+ // W1 H: device-file / UNC / Windows-device / ADS guards must run
161
+ // BEFORE the image fast-path so statSync/readFileSync of a
162
+ // UNC/device path can't bypass the checks the normal read path
163
+ // enforces (NTLM hash leak, raw-device access, ADS).
164
+ if (typeof isUncPath === 'function' && isUncPath(_imgNorm))
165
+ return `Error: cannot read UNC / SMB path (network credential leak risk): ${normalizeOutputPath(_imgNorm)}`;
166
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(_imgNorm))
167
+ return `Error: cannot read Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(_imgNorm)}`;
168
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(_imgNorm))
169
+ return `Error: cannot read Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(_imgNorm)}`;
170
+ if (isBlockedDevicePath(_imgNorm))
171
+ return `Error: cannot read device file (would block or produce infinite output): ${normalizeOutputPath(_imgNorm)}`;
172
+ const _imgFull = resolveAgainstCwd(_imgNorm, workDir);
173
+ if (typeof isUncPath === 'function' && isUncPath(_imgFull))
174
+ return `Error: cannot read UNC / SMB path (network credential leak risk): ${normalizeOutputPath(_imgFull)}`;
175
+ if (typeof isWindowsDevicePath === 'function' && isWindowsDevicePath(_imgFull))
176
+ return `Error: cannot read Windows device path (reserved name or raw-device namespace): ${normalizeOutputPath(_imgFull)}`;
177
+ if (typeof hasUnsafeWin32Component === 'function' && hasUnsafeWin32Component(_imgFull))
178
+ return `Error: cannot read Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${normalizeOutputPath(_imgFull)}`;
179
+ const _imgResult = await readImageAsContent(_imgFull, normalizeOutputPath(_imgNorm));
180
+ if (_imgResult) return _imgResult;
181
+ }
182
+ // Unified-read dispatch (v0.6.283+):
183
+ // reads: [{path, mode?, n?, offset?, limit?, full?}]
184
+ // → per-file batch (different
185
+ // ranges per file in one call)
186
+ // path: string[] | object[] → parallel per-file batch
187
+ // (top-level opts apply uniformly)
188
+ // mode: 'head'|'tail'|'count' → head / tail / wc handlers
189
+ // else → single-file read below.
190
+ // Single turn can touch many files or swap modes without
191
+ // the agent iterating across multiple tool names.
192
+ if (Array.isArray(args.path) && args.path.length > 0 && args.path[0] && typeof args.path[0] === 'object') {
193
+ // Per-file batch: each entry carries its own options.
194
+ // Coalesce same-path entries: multiple chunks for the same
195
+ // file are merged into a single wider read (min offset to max
196
+ // offset+limit) so the file is only opened once. The merged
197
+ // result is sliced back into the original per-entry windows
198
+ // for response assembly. Non-same-path entries are untouched.
199
+ const rawEntries = args.path.map((r) => {
200
+ // CC `file_path` alias on a per-entry batch: file_path is
201
+ // 1-based (CC schema), so decrement a positive offset to
202
+ // match the 0-based `path` form. Mirrors the scalar
203
+ // alias adjustment at line 57.
204
+ const entryUsesFilePathAlias = typeof r?.file_path === 'string' && !r?.path;
205
+ let entry = { path: normalizeInputPath(r?.path ?? r?.file_path ?? '') };
206
+ if (r?.mode !== undefined) entry.mode = r.mode;
207
+ if (r?.n !== undefined) entry.n = r.n;
208
+ if (r?.offset !== undefined) {
209
+ if (entryUsesFilePathAlias) {
210
+ const ccOff = Number(r.offset);
211
+ entry.offset = (Number.isFinite(ccOff) && ccOff > 0) ? Math.trunc(ccOff) - 1 : r.offset;
212
+ } else {
213
+ entry.offset = r.offset;
214
+ }
215
+ }
216
+ if (r?.limit !== undefined) entry.limit = r.limit;
217
+ if (r?.line !== undefined) entry.line = r.line;
218
+ if (r?.context !== undefined) entry.context = r.context;
219
+ if (r?.full !== undefined) entry.full = r.full;
220
+ if (r?.budget !== undefined) entry.budget = r.budget;
221
+ entry = applyCompactReadBudget(entry);
222
+ entry = normaliseReadLineWindowArgs(entry, workDir);
223
+ entry = applyCompactReadBudget(entry);
224
+ return entry;
225
+ });
226
+ const _invertedRawEntry = rawEntries.find((e) => e && e._invertedRangeError);
227
+ if (_invertedRawEntry) return _invertedRawEntry._invertedRangeError;
228
+ // Cluster nearby same-file ranges instead of merging every
229
+ // range into one huge window. Far-apart reads stay separate,
230
+ // which avoids scanning and then slicing thousands of lines
231
+ // just to return two tiny windows.
232
+ const entries = coalesceObjectReadEntries(rawEntries);
233
+ // Deduplicate so the same union-range is read only once per path.
234
+ const _seen = new Map(); // cacheKey → dedupedEntries index
235
+ const dedupedEntries = [];
236
+ const entryToDeduped = []; // entries[i] → dedupedEntries index
237
+ for (let i = 0; i < entries.length; i++) {
238
+ const e = entries[i];
239
+ const _diskWin = readEntryCoalescedDiskWindow(e);
240
+ const key = `${e.path}|${_diskWin?.offset ?? e.offset ?? ''}|${_diskWin?.limit ?? e.limit ?? ''}|${e.mode ?? ''}|${e.n ?? ''}|${e.full ?? ''}`;
241
+ if (_seen.has(key)) { entryToDeduped.push(_seen.get(key)); }
242
+ else { _seen.set(key, dedupedEntries.length); entryToDeduped.push(dedupedEntries.length); dedupedEntries.push(e); }
243
+ }
244
+ if (entries.length === 0) return 'Error: reads array must not be empty';
245
+ // Dispatch deduplicated reads in parallel; re-assemble in original order.
246
+ args = { ...args, path: dedupedEntries.map(e => e.path) };
247
+ args._readsEntries = dedupedEntries;
248
+ args._readsOrigEntries = entries;
249
+ args._readsEntryToDeduped = entryToDeduped;
250
+ args.mode = undefined; args.n = undefined; args.offset = undefined; args.limit = undefined; args.full = undefined;
251
+ }
252
+ if (Array.isArray(args.path)) {
253
+ // Schema is `path: string | string[]` — array entries are
254
+ // strings only. Top-level mode / n / offset / limit / full
255
+ // apply uniformly to every entry in the batch (the only
256
+ // caller is the manager prefetch helper, which already
257
+ // shapes its calls that way). When _readsEntries is set,
258
+ // per-entry options override the uniform set.
259
+ const overrides = Array.isArray(args._readsEntries) ? args._readsEntries : null;
260
+ const entries = args.path.map((p, i) => {
261
+ if (overrides && overrides[i]) return overrides[i];
262
+ let entry = (p && typeof p === 'object')
263
+ ? { path: normalizeInputPath(p.path ?? p.file_path ?? '') }
264
+ : { path: normalizeInputPath(p) };
265
+ if (args.mode !== undefined) entry.mode = args.mode;
266
+ if (args.n !== undefined) entry.n = args.n;
267
+ if (args.offset !== undefined) entry.offset = args.offset;
268
+ if (args.limit !== undefined) entry.limit = args.limit;
269
+ if (args.line !== undefined) entry.line = args.line;
270
+ if (args.context !== undefined) entry.context = args.context;
271
+ if (args.full !== undefined) entry.full = args.full;
272
+ if (args.budget !== undefined) entry.budget = args.budget;
273
+ entry = applyCompactReadBudget(entry);
274
+ entry = normaliseReadLineWindowArgs(entry, workDir);
275
+ entry = applyCompactReadBudget(entry);
276
+ return entry;
277
+ });
278
+ const _invertedStrEntry = entries.find((e) => e && e._invertedRangeError);
279
+ if (_invertedStrEntry) return _invertedStrEntry._invertedRangeError;
280
+ if (entries.length === 0) return 'Error: path array must not be empty';
281
+ // Parallel dispatch of the individual reads via the same case
282
+ // above — reuses size cap, line-number formatting.
283
+ // Per-file errors come back as their own string and are pasted
284
+ // into the aggregate rather than aborting the whole batch.
285
+ // When origEntries/entryToDeduped set (reads[] coalesce path),
286
+ // re-order results to match the caller's original entry order.
287
+ const _origEntries2 = Array.isArray(args._readsOrigEntries) ? args._readsOrigEntries : null;
288
+ const _entryMap2 = Array.isArray(args._readsEntryToDeduped) ? args._readsEntryToDeduped : null;
289
+ const tasks = entries.map((entry, index) => ({
290
+ entry,
291
+ index,
292
+ offset: _isFullModeReadEntry(entry) ? _readEntryLineWindow(entry).offset : 0,
293
+ })).sort((a, b) => {
294
+ const ap = a.entry?.path || '';
295
+ const bp = b.entry?.path || '';
296
+ if (ap !== bp) return ap < bp ? -1 : 1;
297
+ if (a.offset !== b.offset) return a.offset - b.offset;
298
+ return a.index - b.index;
299
+ });
300
+ const results = new Array(entries.length);
301
+ const readChains = new Map();
302
+ await Promise.all(tasks.map(({ entry, index }) => {
303
+ if (!entry || !entry.path) {
304
+ results[index] = { path: '(missing-path)', mode: 'full', body: 'Error: path is required.' };
305
+ return Promise.resolve();
306
+ }
307
+ const run = async () => {
308
+ const _diskWin = readEntryCoalescedDiskWindow(entry);
309
+ const readEntry = _diskWin
310
+ ? { ...entry, offset: _diskWin.offset, limit: _diskWin.limit }
311
+ : entry;
312
+ // mediaTextOnly: a batch aggregate is assembled as a flat
313
+ // string (String(r.body) + join), so media branches must
314
+ // return text (pdf-parse text / notebook text), never a
315
+ // document/image content-block object that would stringify to
316
+ // "[object Object]" and drop the payload. Scalar reads (the
317
+ // single-file path below) keep the rich block shapes.
318
+ const body = await executeChildBuiltinTool('read', readEntry, workDir, { suppressReadUnchangedStub: true, mediaTextOnly: true });
319
+ results[index] = { path: entry.path, mode: entry.mode || 'full', n: entry.n, body };
320
+ };
321
+ const key = entry.path || `#missing-${index}`;
322
+ const prev = readChains.get(key) ?? Promise.resolve();
323
+ const next = prev.then(run);
324
+ readChains.set(key, next.catch(() => {}));
325
+ return next;
326
+ }));
327
+ const orderedResults = _origEntries2
328
+ ? _origEntries2.map((orig, i) => {
329
+ const r = results[_entryMap2 ? _entryMap2[i] : i] || { path: orig.path, mode: orig.mode || 'full', body: 'Error: dedup mapping failed' };
330
+ const isFullMode = !orig.mode || orig.mode === 'full';
331
+ // Coalesced batch reads fetch the union window from disk; every
332
+ // caller slot must be sliced back to its original request window
333
+ // (_orig*), not the coalesced union offset/limit fields.
334
+ const needsSlice = isFullMode && orig._needsPerEntrySlice === true;
335
+ const origOffset = typeof orig._origOffset === 'number' ? orig._origOffset : 0;
336
+ const origLimit = typeof orig._origLimit === 'number'
337
+ ? orig._origLimit
338
+ : 2000;
339
+ const body = needsSlice
340
+ ? sliceReadBodyByLines(r.body, origOffset, origLimit)
341
+ : r.body;
342
+ return { ...r, mode: orig.mode || 'full', n: orig.n, body };
343
+ })
344
+ : results;
345
+ if (_origEntries2) {
346
+ const exactRangesByPath = new Map();
347
+ const rangeHashesByPath = new Map();
348
+ for (const r of orderedResults) {
349
+ if (!r || r.mode !== 'full' || classifyResultKind(String(r.body || '')) === 'error') continue;
350
+ const m = String(r.body || '').match(/\[lines\s+(\d+)-(\d+)\s+of\s+(\d+)/);
351
+ if (!m) continue;
352
+ const startLine = Number(m[1]);
353
+ const endLineRaw = Number(m[2]);
354
+ if (!Number.isFinite(startLine) || !Number.isFinite(endLineRaw)) continue;
355
+ const endLine = endLineRaw;
356
+ const fullPath = resolveAgainstCwd(r.path, workDir);
357
+ if (!exactRangesByPath.has(fullPath)) exactRangesByPath.set(fullPath, []);
358
+ const range = { startLine, endLine };
359
+ exactRangesByPath.get(fullPath).push(range);
360
+ const renderedHashes = _rangeHashesFromRenderedReadText(r.body, [range]);
361
+ if (renderedHashes.length > 0) {
362
+ if (!rangeHashesByPath.has(fullPath)) rangeHashesByPath.set(fullPath, []);
363
+ rangeHashesByPath.get(fullPath).push(...renderedHashes);
364
+ }
365
+ }
366
+ for (const [fullPath, ranges] of exactRangesByPath) {
367
+ const mergedRanges = _mergeReadRanges(ranges);
368
+ let rangeHashes = rangeHashesByPath.get(fullPath) || [];
369
+ if (rangeHashes.length === 0 && mergedRanges.length > 0) {
370
+ try {
371
+ const rawLines = readFileSync(fullPath, 'utf-8').split('\n');
372
+ rangeHashes = mergedRanges.map((range) => {
373
+ const startIdx = Math.max(0, range.startLine - 1);
374
+ const endIdx = Math.min(rawLines.length, range.endLine);
375
+ return { ...range, hash: _hashText(rawLines.slice(startIdx, endIdx).join('\n')) };
376
+ });
377
+ } catch { /* best-effort range hashes */ }
378
+ }
379
+ _recordReadSnapshot(fullPath, undefined, readStateScope, {
380
+ source: 'read_batch_sliced',
381
+ ranges: mergedRanges,
382
+ rangeHashes,
383
+ replaceExisting: true,
384
+ });
385
+ }
386
+ }
387
+ // Header path → forward slash; error bodies already normalised
388
+ // inside the read case's catch blocks. When `read` emitted a
389
+ // smart-cap marker, surface the truncation state in the header
390
+ // so downstream skimming spots it without parsing the body.
391
+ const summaries = [];
392
+ for (const r of orderedResults) {
393
+ if (r.mode === 'count') {
394
+ const m = String(r.body || '').match(/lines\t(\d+)/);
395
+ if (m) summaries.push(`${normalizeOutputPath(r.path)} has ${m[1]} lines`);
396
+ }
397
+ }
398
+ const summaryLine = summaries.length ? ` ${summaries.join('; ')}` : '';
399
+ const failedReads = orderedResults.filter((r) => classifyResultKind(String(r.body || '')) === 'error').length;
400
+ // reject_partial:true — when the caller asked for all-or-none,
401
+ // refuse to return a mixed payload that downstream parsers
402
+ // would have to disambiguate per-entry.
403
+ if (failedReads > 0 && args.reject_partial === true) {
404
+ const reasons = orderedResults
405
+ .filter((r) => classifyResultKind(String(r.body || '')) === 'error')
406
+ .map((r) => `${normalizeOutputPath(r.path)}: ${String(r.body || '').split('\n')[0]}`)
407
+ .join('; ');
408
+ return `Error: batch read rejected (${failedReads} of ${orderedResults.length} failed; reject_partial:true) — ${reasons}`;
409
+ }
410
+ // Default: surface per-entry status tags ([ok]/[error]) so a
411
+ // downstream classifyResultKind treats the aggregate as a
412
+ // structured report rather than a single error string. The
413
+ // header avoids the leading `Error:` prefix because some
414
+ // entries succeeded; failure count is reported in parens.
415
+ const header = failedReads > 0
416
+ ? `read ${orderedResults.length} (${failedReads} failed)${summaryLine}`
417
+ : `read ${orderedResults.length}${summaryLine}`;
418
+ // Identical-entry dedup: when the caller puts the exact same window
419
+ // twice in the path array, coalesceObjectReadEntries already merges
420
+ // the disk read, but the 1:1 request/response contract still renders
421
+ // every index. Emit a reference placeholder for byte-identical repeats
422
+ // (same path + same mode + same body) so the duplicate body is not
423
+ // materialised twice -- the entry keeps its index, only the body is
424
+ // elided. With no duplicates the output is byte-for-byte unchanged.
425
+ const _seenEntryBody = new Map();
426
+ const body = orderedResults.map((r, _i) => {
427
+ const path = normalizeOutputPath(r.path);
428
+ const mode = r.n !== undefined ? `${r.mode} n=${r.n}` : r.mode;
429
+ const status = classifyResultKind(String(r.body || '')) === 'error' ? 'error' : 'ok';
430
+ const dupKey = JSON.stringify([path, mode, r.body || '']);
431
+ const priorIdx = _seenEntryBody.get(dupKey);
432
+ if (priorIdx !== undefined) {
433
+ return `${path} [${mode}] [${status}] [= entry #${priorIdx + 1}, identical result omitted]`;
434
+ }
435
+ _seenEntryBody.set(dupKey, _i);
436
+ const match = /\[TRUNCATED (?:—|-) file is (\d+) lines \/ (\d+) KB\./.exec(r.body || '');
437
+ const suffix = match ? ` (truncated ${match[1]}L/${match[2]}KB)` : '';
438
+ return `${path} [${mode}] [${status}]${suffix}\n${r.body}`;
439
+ }).join('\n\n');
440
+ return `${header}\n\n${body}`;
441
+ }
442
+ // W1 H: device-file / UNC / scope guards must run BEFORE mode
443
+ // dispatches so head/tail/wc internal readers can't bypass the
444
+ // /dev/* block that the default-mode branch enforces.
445
+ if (typeof args.path === 'string' && args.path) {
446
+ const _modeGuardErr = readPathStringGuardError(args.path, workDir);
447
+ if (_modeGuardErr) return `Error: ${_modeGuardErr}`;
448
+ }
449
+ if (typeof args.path === 'string') {
450
+ args.path = normalizeInputPath(args.path);
451
+ // Symbol reads are span-driven. Models (notably gpt-5.5) co-send the whole
452
+ // schema filled with placeholder/zero values (offset:0, limit:0, line:0,
453
+ // context:0, pages:'', max_lines:0, mode, budget) alongside symbol, which
454
+ // otherwise makes the symbol branch think a window was requested and falls
455
+ // back to a 0-window read. Treat those zero/empty/mode/budget params as
456
+ // absent so the whole symbol body returns in ONE call; a MEANINGFUL window
457
+ // (offset>0 / limit>0 / non-empty pages / real line) is kept and overrides.
458
+ if (typeof args.symbol === 'string' && args.symbol.trim()) {
459
+ if (args.offset === 0) delete args.offset;
460
+ if (args.limit === 0) delete args.limit;
461
+ if (args.line === 0) delete args.line;
462
+ if (args.pages === '') delete args.pages;
463
+ if (args.max_lines === 0) delete args.max_lines;
464
+ if (args.budget !== undefined) delete args.budget;
465
+ if (args.mode !== undefined) delete args.mode;
466
+ }
467
+ args = applyCompactReadBudget(args);
468
+ const sym = typeof args.symbol === 'string' ? args.symbol.trim() : '';
469
+ if (sym) {
470
+ const hasOffset = args.offset !== undefined && args.offset !== null;
471
+ const hasLimit = args.limit !== undefined && args.limit !== null;
472
+ const hasPages = args.pages !== undefined && args.pages !== null;
473
+ const disambigLine = parseReadLineNumberArg(args.line);
474
+ const pathHasLine = hasLineCoordinate(args.path);
475
+ const explicitLineWindow = pathHasLine || (disambigLine != null && (
476
+ (args.context !== undefined && args.context !== null)
477
+ || (args.limit !== undefined && args.limit !== null)
478
+ ));
479
+ if (!hasPages && !explicitLineWindow) {
480
+ const { resolveSymbolReadSpan } = await import('../code-graph.mjs');
481
+ const span = await resolveSymbolReadSpan(workDir, {
482
+ symbol: sym,
483
+ path: args.path,
484
+ language: typeof args.language === 'string' ? args.language.trim() || null : null,
485
+ line: disambigLine,
486
+ });
487
+ if (span.error) return `Error: ${span.error}`;
488
+ // offset/limit COMPOSE INSIDE the symbol body ("lines N..M of
489
+ // the definition"). The previous behavior silently dropped
490
+ // symbol= whenever a window arg was present, so
491
+ // `symbol:X, limit:15` returned the FILE's first 15 lines —
492
+ // a wasted call that looks like a successful read.
493
+ const innerOffset = hasOffset ? Math.max(0, Math.trunc(Number(args.offset)) || 0) : 0;
494
+ const spanRemaining = span.limit - innerOffset;
495
+ if (spanRemaining <= 0) {
496
+ return `Error: offset ${innerOffset} is beyond symbol "${sym}" body (${span.limit} lines)`;
497
+ }
498
+ args.offset = span.offset + innerOffset;
499
+ const innerLimit = hasLimit ? Math.max(1, Math.trunc(Number(args.limit)) || 1) : spanRemaining;
500
+ args.limit = Math.min(innerLimit, spanRemaining);
501
+ if (span.note) args._symbolReadNote = `symbol ${sym}: ${span.note}`;
502
+ }
503
+ }
504
+ // A window (offset/limit/line or a path:line coordinate) beats a glance
505
+ // mode (head/tail/summary), which would otherwise read from a file end and
506
+ // silently drop the window. Drop the glance mode BEFORE line-window
507
+ // normalization so a line / path:line coordinate is actually converted to
508
+ // offset/limit (normaliseReadLineWindowArgs only converts when no mode is
509
+ // set). count/hex are not text-window ops and keep their mode.
510
+ {
511
+ const _win = args.offset != null || args.limit != null || args.line != null || hasLineCoordinate(args.path);
512
+ if (_win && (args.mode === 'head' || args.mode === 'tail' || args.mode === 'summary')) {
513
+ args = { ...args, mode: undefined };
514
+ }
515
+ }
516
+ args = normaliseReadLineWindowArgs(args, workDir);
517
+ if (args._invertedRangeError) return args._invertedRangeError;
518
+ args = applyCompactReadBudget(args);
519
+ }
520
+ // Mode routing. A window already dropped any conflicting head/tail/summary
521
+ // glance above (so the window is served by executeSingleReadTool); what
522
+ // remains here is a mode-only read, or count/hex which are not text windows.
523
+ if (args.mode === 'head') return executeChildBuiltinTool('head', { path: args.path, n: args.n }, workDir);
524
+ if (args.mode === 'tail') return executeChildBuiltinTool('tail', { path: args.path, n: args.n }, workDir);
525
+ if (args.mode === 'count') return executeChildBuiltinTool('wc', { path: args.path }, workDir);
526
+ if (args.mode === 'summary') return executeChildBuiltinTool('summary', { path: args.path, n: args.n, limit: args.limit }, workDir);
527
+ if (args.mode === 'hex') return executeChildBuiltinTool('hex', { path: args.path, n: args.n, offset: args.offset }, workDir);
528
+ return executeSingleReadTool(args, workDir, readStateScope, options, helpers);
529
+
530
+ }
@@ -0,0 +1,107 @@
1
+ import { closeSync, openSync, readSync } from 'fs';
2
+ import { hashText } from './hash-utils.mjs';
3
+ import { displayLineForRead } from './read-lines.mjs';
4
+ import { READ_LARGE_TAIL_MAX_BYTES } from './read-constants.mjs';
5
+
6
+ export function readLargeTailWindowSync(fullPath, st, n) {
7
+ const targetLines = Math.max(1, Math.trunc(n || 20));
8
+ const fd = openSync(fullPath, 'r');
9
+ let tailBytes = Math.min(st.size, Math.max(4096, targetLines * 256));
10
+ let buf = Buffer.allocUnsafe(0);
11
+ let bytesRead = 0;
12
+ try {
13
+ while (true) {
14
+ buf = Buffer.allocUnsafe(tailBytes);
15
+ bytesRead = readSync(fd, buf, 0, tailBytes, st.size - tailBytes);
16
+ let lfCount = 0;
17
+ for (let i = 0; i < bytesRead; i++) {
18
+ if (buf[i] === 10) lfCount++;
19
+ }
20
+ if (tailBytes >= st.size || lfCount > targetLines || tailBytes >= READ_LARGE_TAIL_MAX_BYTES) break;
21
+ tailBytes = Math.min(st.size, READ_LARGE_TAIL_MAX_BYTES, tailBytes * 2);
22
+ }
23
+ } finally {
24
+ closeSync(fd);
25
+ }
26
+ const readWindow = buf.subarray(0, bytesRead);
27
+ const approximate = tailBytes < st.size;
28
+ // Advance past a leading partial UTF-8 codepoint (continuation bytes
29
+ // 0b10xxxxxx) when we did not start at the file head; otherwise the
30
+ // toString decode emits a U+FFFD or splits a multibyte char in two.
31
+ // Bounded by 4 since UTF-8 sequences are at most 4 bytes long.
32
+ let tOff = 0;
33
+ if (approximate) {
34
+ const padding = 4;
35
+ while (tOff < readWindow.length && tOff < padding && (readWindow[tOff] & 0xC0) === 0x80) tOff++;
36
+ }
37
+ const text = readWindow.subarray(tOff).toString('utf-8');
38
+ const lines = text.split('\n');
39
+ // Drop the (likely partial) first line only when we actually started
40
+ // mid-file AND the slice still contains more than one line. Whether
41
+ // the boundary advance consumed bytes or not, the first line in an
42
+ // approximate window can never be trusted to start at a real BOL.
43
+ if (approximate && lines.length > 1) lines.shift();
44
+ if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
45
+ const sliced = lines.slice(-targetLines);
46
+ return {
47
+ lines: sliced,
48
+ approximate,
49
+ capped: approximate && tailBytes >= READ_LARGE_TAIL_MAX_BYTES,
50
+ bytesRead,
51
+ };
52
+ }
53
+
54
+ export function readLargeHeadWindowSync(fullPath, st, n) {
55
+ const targetLines = Math.max(1, Math.trunc(n || 20));
56
+ const fd = openSync(fullPath, 'r');
57
+ let headBytes = Math.min(st.size, Math.max(65536, targetLines * 256));
58
+ let buf = Buffer.allocUnsafe(0);
59
+ let bytesRead = 0;
60
+ let prefixHash = '';
61
+ try {
62
+ while (true) {
63
+ buf = Buffer.allocUnsafe(headBytes);
64
+ bytesRead = readSync(fd, buf, 0, headBytes, 0);
65
+ if (!prefixHash && bytesRead > 0) {
66
+ prefixHash = hashText(buf.subarray(0, Math.min(bytesRead, 65536)));
67
+ }
68
+ let lfCount = 0;
69
+ for (let i = 0; i < bytesRead; i++) {
70
+ if (buf[i] === 10) lfCount++;
71
+ }
72
+ if (headBytes >= st.size || lfCount >= targetLines || headBytes >= READ_LARGE_TAIL_MAX_BYTES) break;
73
+ headBytes = Math.min(st.size, READ_LARGE_TAIL_MAX_BYTES, headBytes * 2);
74
+ }
75
+ } finally {
76
+ closeSync(fd);
77
+ }
78
+ if (headBytes < st.size && bytesRead > 0 && buf.subarray(0, bytesRead).indexOf(10) === -1) {
79
+ return { lines: [], prefixHash, capped: true };
80
+ }
81
+ // Cut the head on a UTF-8 codepoint boundary when we did not reach EOF;
82
+ // otherwise the trailing decode can produce a U+FFFD glyph and emit a
83
+ // partial trailing codepoint into the rendered head window. Trim any
84
+ // trailing continuation bytes (0b10xxxxxx) within buf[0..bytesRead),
85
+ // then drop a lead byte whose declared sequence runs past bytesRead.
86
+ let endByte = bytesRead;
87
+ if (headBytes < st.size) {
88
+ while (endByte > 0 && (buf[endByte - 1] & 0xC0) === 0x80) endByte--;
89
+ if (endByte > 0) {
90
+ const lead = buf[endByte - 1];
91
+ const seqLen = lead >= 0xF0 ? 4 : lead >= 0xE0 ? 3 : lead >= 0xC0 ? 2 : 1;
92
+ if (seqLen > 1 && (endByte - 1) + seqLen > bytesRead) endByte--;
93
+ }
94
+ }
95
+ const text = buf.subarray(0, endByte).toString('utf-8');
96
+ const lines = text.split('\n');
97
+ if (headBytes >= st.size && lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
98
+ // When the head window is approximate (did not reach EOF), the final
99
+ // line is partial by definition — its bytes were arbitrarily cut at
100
+ // the read window edge. Drop it so callers never see a half-line.
101
+ if (headBytes < st.size && lines.length > 1) lines.pop();
102
+ return {
103
+ lines: lines.slice(0, targetLines).map((line, i) => displayLineForRead(line, i)),
104
+ prefixHash,
105
+ capped: headBytes >= READ_LARGE_TAIL_MAX_BYTES && headBytes < st.size,
106
+ };
107
+ }