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.
- package/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +20 -0
- package/.gitattributes +34 -0
- package/.mcp.json +14 -0
- package/ARCHITECTURE.md +77 -0
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +45 -0
- package/DATA-FLOW.md +79 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/SECURITY.md +138 -0
- package/UNINSTALL.md +112 -0
- package/agents/maintenance.md +5 -0
- package/agents/memory-classification.md +30 -0
- package/agents/scheduler-task.md +18 -0
- package/agents/webhook-handler.md +27 -0
- package/agents/worker.md +24 -0
- package/bin/bridge +133 -0
- package/bin/statusline-launcher.mjs +78 -0
- package/bin/statusline-lib.mjs +550 -0
- package/bin/statusline.mjs +607 -0
- package/bun.lock +802 -0
- package/commands/config.md +16 -0
- package/commands/doctor.md +13 -0
- package/commands/setup.md +17 -0
- package/defaults/cycle3-review-prompt.md +90 -0
- package/defaults/hidden-roles.json +65 -0
- package/defaults/memory-chunk-prompt.md +63 -0
- package/defaults/memory-promote-prompt.md +135 -0
- package/defaults/mixdog-config.template.json +27 -0
- package/defaults/user-workflow.json +8 -0
- package/defaults/user-workflow.md +12 -0
- package/hooks/hooks.json +73 -0
- package/hooks/lib/active-instance.cjs +77 -0
- package/hooks/lib/permission-evaluator.cjs +411 -0
- package/hooks/lib/permission-route.cjs +63 -0
- package/hooks/lib/permission-rules.cjs +170 -0
- package/hooks/lib/settings-loader.cjs +116 -0
- package/hooks/post-tool-use.cjs +84 -0
- package/hooks/pre-mcp-sandbox.cjs +158 -0
- package/hooks/pre-tool-subagent.cjs +253 -0
- package/hooks/session-start.cjs +1372 -0
- package/hooks/turn-timer.cjs +82 -0
- package/lib/claude-md-writer.cjs +386 -0
- package/lib/config-cjs.cjs +61 -0
- package/lib/hook-pipe-path.cjs +10 -0
- package/lib/keychain-cjs.cjs +263 -0
- package/lib/plugin-paths.cjs +61 -0
- package/lib/rules-builder.cjs +241 -0
- package/lib/text-utils.cjs +61 -0
- package/native/README.md +117 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +107 -0
- package/prompts/code-review.txt +16 -0
- package/prompts/security-audit.txt +17 -0
- package/rules/bridge/00-common.md +39 -0
- package/rules/bridge/20-skip-protocol.md +18 -0
- package/rules/bridge/30-explorer.md +33 -0
- package/rules/bridge/40-cycle1-agent.md +52 -0
- package/rules/bridge/41-cycle2-agent.md +62 -0
- package/rules/bridge/42-cycle3-agent.md +44 -0
- package/rules/lead/00-tool-lead.md +61 -0
- package/rules/lead/01-general.md +23 -0
- package/rules/lead/02-channels.md +49 -0
- package/rules/lead/03-team.md +27 -0
- package/rules/lead/04-workflow.md +20 -0
- package/rules/shared/00-language.md +14 -0
- package/rules/shared/01-tool.md +138 -0
- package/scripts/bootstrap.mjs +184 -0
- package/scripts/bridge-unify-smoke.mjs +308 -0
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/builtin-utils-smoke.mjs +392 -0
- package/scripts/check-json.mjs +45 -0
- package/scripts/check-syntax-changed.mjs +102 -0
- package/scripts/check-syntax.mjs +58 -0
- package/scripts/code-graph-batch.test.mjs +33 -0
- package/scripts/config-preserve-smoke.mjs +180 -0
- package/scripts/doctor.mjs +484 -0
- package/scripts/edit-normalize-fuzz.mjs +130 -0
- package/scripts/edit-normalize-smoke.mjs +401 -0
- package/scripts/edit-operation-smoke.mjs +369 -0
- package/scripts/edit2-smoke.mjs +63 -0
- package/scripts/fuzzy-e2e.mjs +28 -0
- package/scripts/fuzzy-smoke.mjs +26 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/guard-smoke.mjs +66 -0
- package/scripts/hidden-role-schema-smoke.mjs +162 -0
- package/scripts/hook-routing-smoke.mjs +29 -0
- package/scripts/inject-input.ps1 +204 -0
- package/scripts/io-complex-smoke.mjs +667 -0
- package/scripts/io-explore-bench.mjs +424 -0
- package/scripts/io-guardrails-smoke.mjs +205 -0
- package/scripts/io-mini-bench-baseline.json +11 -0
- package/scripts/io-mini-bench.mjs +216 -0
- package/scripts/io-route-harness.mjs +933 -0
- package/scripts/io-telemetry-report.mjs +691 -0
- package/scripts/mutation-bench.mjs +564 -0
- package/scripts/mutation-io-smoke.mjs +1081 -0
- package/scripts/native-patch-bridge-smoke.mjs +288 -0
- package/scripts/native-patch-smoke.mjs +304 -0
- package/scripts/patch-interior-context-smoke.mjs +49 -0
- package/scripts/patch-newline-utf8-smoke.mjs +157 -0
- package/scripts/perf-hook-smoke.mjs +71 -0
- package/scripts/permission-eval-smoke.mjs +426 -0
- package/scripts/prep-patch.mjs +53 -0
- package/scripts/prep-shim.mjs +96 -0
- package/scripts/provider-cache-smoke.mjs +687 -0
- package/scripts/report-runtime-health.mjs +132 -0
- package/scripts/run-mcp.mjs +1547 -0
- package/scripts/salvage-v4a-shatter.test.mjs +58 -0
- package/scripts/scoped-cache-io-smoke.mjs +103 -0
- package/scripts/shell-policy-round3-smoke.mjs +46 -0
- package/scripts/smoke-runtime-negative.ps1 +100 -0
- package/scripts/smoke-runtime-negative.sh +95 -0
- package/scripts/stall-policy-smoke.mjs +50 -0
- package/scripts/start-memory-worker.mjs +23 -0
- package/scripts/statusline-launcher-smoke.mjs +82 -0
- package/scripts/stress-atomic-write.mjs +1028 -0
- package/scripts/test-config-rmw-restore.mjs +122 -0
- package/scripts/test-fault-inject.mjs +164 -0
- package/scripts/test-large-file.mjs +174 -0
- package/scripts/tool-edge-smoke.mjs +209 -0
- package/scripts/uninstall.mjs +201 -0
- package/scripts/webhook-selfheal-smoke.mjs +29 -0
- package/scripts/write-overwrite-guard-smoke.mjs +56 -0
- package/server-main.mjs +3055 -0
- package/server.mjs +468 -0
- package/setup/config-merge.mjs +254 -0
- package/setup/install.mjs +120 -0
- package/setup/launch-core.mjs +507 -0
- package/setup/launch.mjs +101 -0
- package/setup/setup-server.mjs +3206 -0
- package/setup/setup.html +3693 -0
- package/skills/retro-skill-proposer/SKILL.md +92 -0
- package/skills/schedule-add/SKILL.md +77 -0
- package/skills/setup/SKILL.md +346 -0
- package/skills/webhook-add/SKILL.md +81 -0
- package/src/agent/bridge-stall-watchdog.mjs +337 -0
- package/src/agent/index.mjs +2138 -0
- package/src/agent/orchestrator/activity-bus.mjs +38 -0
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
- package/src/agent/orchestrator/bridge-retry.mjs +220 -0
- package/src/agent/orchestrator/bridge-trace.mjs +583 -0
- package/src/agent/orchestrator/cache-mtime.mjs +58 -0
- package/src/agent/orchestrator/config.mjs +358 -0
- package/src/agent/orchestrator/context/collect.mjs +651 -0
- package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
- package/src/agent/orchestrator/drain-registry.mjs +50 -0
- package/src/agent/orchestrator/explore-validator.mjs +8 -0
- package/src/agent/orchestrator/internal-roles.mjs +118 -0
- package/src/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/agent/orchestrator/jobs.mjs +116 -0
- package/src/agent/orchestrator/mcp/client.mjs +364 -0
- package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
- package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
- package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
- package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
- package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
- package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
- package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
- package/src/agent/orchestrator/providers/registry.mjs +192 -0
- package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
- package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
- package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
- package/src/agent/orchestrator/session/cache/util.mjs +49 -0
- package/src/agent/orchestrator/session/loop.mjs +1478 -0
- package/src/agent/orchestrator/session/manager.mjs +1975 -0
- package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
- package/src/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
- package/src/agent/orchestrator/session/store.mjs +624 -0
- package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
- package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/agent/orchestrator/session/trim.mjs +491 -0
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
- package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
- package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
- package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
- package/src/agent/orchestrator/stall-policy.mjs +195 -0
- package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
- package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
- package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
- package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
- package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
- package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
- package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
- package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
- package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
- package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
- package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
- package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
- package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
- package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
- package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
- package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
- package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
- package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
- package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
- package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
- package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
- package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
- package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
- package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
- package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
- package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
- package/src/agent/orchestrator/tools/builtin.mjs +500 -0
- package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
- package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
- package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
- package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
- package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/agent/orchestrator/tools/host-input.mjs +204 -0
- package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
- package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
- package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
- package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
- package/src/agent/orchestrator/tools/patch.mjs +2754 -0
- package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
- package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
- package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
- package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
- package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
- package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
- package/src/agent/orchestrator/workflow-store.mjs +93 -0
- package/src/agent/tool-defs.mjs +103 -0
- package/src/channels/backends/discord.mjs +784 -0
- package/src/channels/data/voice-runtime-manifest.json +138 -0
- package/src/channels/index.mjs +3229 -0
- package/src/channels/lib/cli-worker-host.mjs +12 -0
- package/src/channels/lib/config-lock.mjs +13 -0
- package/src/channels/lib/config.mjs +292 -0
- package/src/channels/lib/drop-trace.mjs +71 -0
- package/src/channels/lib/event-pipeline.mjs +81 -0
- package/src/channels/lib/event-queue.mjs +345 -0
- package/src/channels/lib/executor.mjs +168 -0
- package/src/channels/lib/format.mjs +188 -0
- package/src/channels/lib/holidays.mjs +138 -0
- package/src/channels/lib/hook-pipe-server.mjs +802 -0
- package/src/channels/lib/interaction-workflows.mjs +184 -0
- package/src/channels/lib/memory-client.mjs +149 -0
- package/src/channels/lib/output-forwarder.mjs +765 -0
- package/src/channels/lib/runtime-paths.mjs +479 -0
- package/src/channels/lib/scheduler.mjs +723 -0
- package/src/channels/lib/session-control.mjs +36 -0
- package/src/channels/lib/session-discovery.mjs +103 -0
- package/src/channels/lib/settings.mjs +11 -0
- package/src/channels/lib/state-file.mjs +68 -0
- package/src/channels/lib/status-snapshot.mjs +219 -0
- package/src/channels/lib/tool-format.mjs +140 -0
- package/src/channels/lib/transcript-discovery.mjs +195 -0
- package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/channels/lib/webhook.mjs +1179 -0
- package/src/channels/lib/whisper-server.mjs +477 -0
- package/src/channels/tool-defs.mjs +170 -0
- package/src/daemon/host.mjs +118 -0
- package/src/daemon/mcp-transport.mjs +47 -0
- package/src/daemon/session.mjs +100 -0
- package/src/daemon/thin-client.mjs +71 -0
- package/src/daemon/transport.mjs +163 -0
- package/src/memory/data/runtime-manifest.json +40 -0
- package/src/memory/index.mjs +3305 -0
- package/src/memory/lib/agent-ipc.mjs +93 -0
- package/src/memory/lib/bridge-trace-queries.mjs +120 -0
- package/src/memory/lib/core-memory-store.mjs +330 -0
- package/src/memory/lib/embedding-provider.mjs +269 -0
- package/src/memory/lib/embedding-worker.mjs +323 -0
- package/src/memory/lib/llm-worker-host.mjs +17 -0
- package/src/memory/lib/memory-cycle.mjs +11 -0
- package/src/memory/lib/memory-cycle1.mjs +641 -0
- package/src/memory/lib/memory-cycle2.mjs +1284 -0
- package/src/memory/lib/memory-cycle3.mjs +540 -0
- package/src/memory/lib/memory-embed.mjs +299 -0
- package/src/memory/lib/memory-extraction.mjs +5 -0
- package/src/memory/lib/memory-maintenance-store.mjs +32 -0
- package/src/memory/lib/memory-ops-policy.mjs +190 -0
- package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
- package/src/memory/lib/memory-recall-read-query.mjs +7 -0
- package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
- package/src/memory/lib/memory-recall-store.mjs +621 -0
- package/src/memory/lib/memory-retrievers.mjs +112 -0
- package/src/memory/lib/memory-score.mjs +71 -0
- package/src/memory/lib/memory-text-utils.mjs +58 -0
- package/src/memory/lib/memory.mjs +412 -0
- package/src/memory/lib/model-profile.mjs +85 -0
- package/src/memory/lib/pg/adapter.mjs +308 -0
- package/src/memory/lib/pg/process.mjs +360 -0
- package/src/memory/lib/pg/supervisor.mjs +396 -0
- package/src/memory/lib/project-id-resolver.mjs +86 -0
- package/src/memory/lib/runtime-fetcher.mjs +442 -0
- package/src/memory/lib/trace-store.mjs +728 -0
- package/src/memory/tool-defs.mjs +79 -0
- package/src/search/index.mjs +1173 -0
- package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
- package/src/search/lib/backends/exa.mjs +50 -0
- package/src/search/lib/backends/firecrawl.mjs +61 -0
- package/src/search/lib/backends/gemini-api.mjs +83 -0
- package/src/search/lib/backends/grok-oauth.mjs +86 -0
- package/src/search/lib/backends/index.mjs +150 -0
- package/src/search/lib/backends/openai-api.mjs +144 -0
- package/src/search/lib/backends/openai-oauth.mjs +98 -0
- package/src/search/lib/backends/openai-web-search.mjs +76 -0
- package/src/search/lib/backends/tavily.mjs +55 -0
- package/src/search/lib/backends/xai-api.mjs +113 -0
- package/src/search/lib/cache.mjs +131 -0
- package/src/search/lib/config.mjs +192 -0
- package/src/search/lib/formatter.mjs +115 -0
- package/src/search/lib/provider-usage.mjs +67 -0
- package/src/search/lib/providers.mjs +47 -0
- package/src/search/lib/search-intent.mjs +109 -0
- package/src/search/lib/setup-handler.mjs +261 -0
- package/src/search/lib/state.mjs +201 -0
- package/src/search/lib/web-tools.mjs +1207 -0
- package/src/search/tool-defs.mjs +83 -0
- package/src/setup/defender-exclusion.mjs +183 -0
- package/src/shared/abort-controller.mjs +15 -0
- package/src/shared/atomic-file.mjs +420 -0
- package/src/shared/config.mjs +350 -0
- package/src/shared/daemon-recycle.mjs +108 -0
- package/src/shared/disable-claude-builtins.mjs +88 -0
- package/src/shared/err-text.mjs +12 -0
- package/src/shared/llm/cost.mjs +66 -0
- package/src/shared/llm/http-agent.mjs +123 -0
- package/src/shared/llm/index.mjs +41 -0
- package/src/shared/llm/pid-cleanup.mjs +27 -0
- package/src/shared/llm/usage-log.mjs +47 -0
- package/src/shared/plugin-paths.mjs +58 -0
- package/src/shared/schedules-store.mjs +70 -0
- package/src/shared/seed.mjs +119 -0
- package/src/shared/user-cwd.mjs +213 -0
- package/src/shared/user-data-guard.mjs +238 -0
- package/src/status/aggregator.mjs +584 -0
- package/src/status/server.mjs +413 -0
- 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
|
+
}
|