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,201 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { isAbsolute, relative, resolve } from 'path';
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
4
|
+
|
|
5
|
+
// Restore the on-disk casing of a path (win32 only). rg relativizes candidate
|
|
6
|
+
// paths against its process cwd with a CASE-SENSITIVE prefix strip before
|
|
7
|
+
// matching slash-anchored --glob overrides; a casing mismatch between the
|
|
8
|
+
// spawn cwd and the walked root silently defeats every anchored glob.
|
|
9
|
+
// realpathSync.native resolves the real casing; non-existent paths (and any
|
|
10
|
+
// resolution failure) fall back to the input unchanged.
|
|
11
|
+
export function trueCasePath(p) {
|
|
12
|
+
if (process.platform !== 'win32' || typeof p !== 'string' || !p) return p;
|
|
13
|
+
try { return realpathSync.native(p); } catch { return p; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function posixPathToWindowsPath(posixPath) {
|
|
17
|
+
if (process.platform !== 'win32') return posixPath;
|
|
18
|
+
if (typeof posixPath !== 'string') return posixPath;
|
|
19
|
+
const cyg = posixPath.match(/^\/cygdrive\/([a-zA-Z])\//);
|
|
20
|
+
if (cyg) return `${cyg[1]}:\\${posixPath.slice(11).replace(/\//g, '\\')}`;
|
|
21
|
+
// WSL drive mount: /mnt/c/... → C:\... (Windows drives surface under
|
|
22
|
+
// /mnt/<letter> inside WSL). Mirrors the /cygdrive/ mapping.
|
|
23
|
+
const wsl = posixPath.match(/^\/mnt\/([a-zA-Z])\//);
|
|
24
|
+
if (wsl) return `${wsl[1]}:\\${posixPath.slice(7).replace(/\//g, '\\')}`;
|
|
25
|
+
const m = posixPath.match(/^\/([a-zA-Z])\//);
|
|
26
|
+
if (m) return `${m[1]}:\\${posixPath.slice(3).replace(/\//g, '\\')}`;
|
|
27
|
+
if (posixPath.startsWith('//')) return posixPath.replace(/\//g, '\\');
|
|
28
|
+
return posixPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function normalizeInputPath(p) {
|
|
32
|
+
if (typeof p !== 'string') return p;
|
|
33
|
+
// Trim leading/trailing whitespace — LLMs intermittently emit paths with
|
|
34
|
+
// stray spaces inside tool_args, which would otherwise hit ENOENT, force
|
|
35
|
+
// a self-correction turn, and silently invalidate the cache prefix on
|
|
36
|
+
// subsequent iterations (measured as single-iter cold cache misses).
|
|
37
|
+
// Also strip embedded ASCII control chars (\x00-\x1F, \x7F) — interpolation
|
|
38
|
+
// bugs sometimes inject \t/\f/\r/\n mid-path; trim() only touches the ends.
|
|
39
|
+
// Literal SP (0x20) is preserved so paths like "Program Files" still work.
|
|
40
|
+
let out = p.trim().replace(/[\x00-\x1F\x7F]/g, '');
|
|
41
|
+
if (out === '~' || out.startsWith('~/') || out.startsWith('~\\')) {
|
|
42
|
+
out = homedir() + out.slice(1);
|
|
43
|
+
}
|
|
44
|
+
if (process.platform === 'win32') {
|
|
45
|
+
const looksPosixDrive = /^\/[a-zA-Z]\//.test(out);
|
|
46
|
+
const looksCygdrive = /^\/cygdrive\/[a-zA-Z]\//.test(out);
|
|
47
|
+
const looksWsl = /^\/mnt\/[a-zA-Z]\//.test(out);
|
|
48
|
+
const looksUnc = out.startsWith('//');
|
|
49
|
+
if (looksPosixDrive || looksCygdrive || looksWsl || looksUnc) {
|
|
50
|
+
out = posixPathToWindowsPath(out);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try { out = out.normalize('NFC'); } catch {}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function normalizeSearchPattern(p) {
|
|
58
|
+
if (typeof p !== 'string') return p;
|
|
59
|
+
try { return p.normalize('NFC'); } catch { return p; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Glob filters fed to ripgrep (`--glob`) use forward slashes as the path
|
|
63
|
+
// separator on every platform. A Windows-trained agent naturally writes
|
|
64
|
+
// `**\*.ts`, which ripgrep treats as an escape sequence rather than a path
|
|
65
|
+
// separator, so the pattern silently matches nothing. Canonicalize `\`→`/`
|
|
66
|
+
// so backslash globs match. Gated to win32 only — on POSIX a backslash in a
|
|
67
|
+
// glob is a legitimate escape character and must be preserved.
|
|
68
|
+
export function canonicalizeGlobSlashes(p) {
|
|
69
|
+
if (typeof p !== 'string') return p;
|
|
70
|
+
if (process.platform !== 'win32') return p;
|
|
71
|
+
return p.replace(/\\/g, '/');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function firstPresentArg(args, names) {
|
|
75
|
+
for (const name of names) {
|
|
76
|
+
if (!args || args[name] === undefined || args[name] === null || args[name] === '') continue;
|
|
77
|
+
return args[name];
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function normalizeGrepArgs(args) {
|
|
83
|
+
if (!args || typeof args !== 'object') return args;
|
|
84
|
+
if (args.pattern === undefined || args.pattern === null || args.pattern === '') {
|
|
85
|
+
const alias = firstPresentArg(args, ['query', 'regex', 'regexp', 'needle', 'search', 'literal']);
|
|
86
|
+
if (alias !== undefined) args.pattern = alias;
|
|
87
|
+
}
|
|
88
|
+
if (args.glob === undefined || args.glob === null || args.glob === '') {
|
|
89
|
+
const alias = firstPresentArg(args, ['file_pattern', 'filePattern', 'include', 'includes', 'files']);
|
|
90
|
+
if (alias !== undefined) args.glob = alias;
|
|
91
|
+
}
|
|
92
|
+
if (args.path === undefined || args.path === null || args.path === '') {
|
|
93
|
+
const alias = firstPresentArg(args, ['root', 'directory', 'dir']);
|
|
94
|
+
if (alias !== undefined) args.path = alias;
|
|
95
|
+
}
|
|
96
|
+
if ((args.output_mode === undefined || args.output_mode === null || args.output_mode === '') && typeof args.mode === 'string') {
|
|
97
|
+
const mode = args.mode.trim();
|
|
98
|
+
if (['files_with_matches', 'content', 'count'].includes(mode)) args.output_mode = mode;
|
|
99
|
+
}
|
|
100
|
+
return args;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function normalizeGlobArgs(args) {
|
|
104
|
+
if (!args || typeof args !== 'object') return args;
|
|
105
|
+
if (args.pattern === undefined || args.pattern === null || args.pattern === '') {
|
|
106
|
+
const alias = firstPresentArg(args, ['glob', 'file_pattern', 'filePattern', 'name', 'include', 'includes', 'files']);
|
|
107
|
+
if (alias !== undefined) args.pattern = alias;
|
|
108
|
+
}
|
|
109
|
+
if (args.path === undefined || args.path === null || args.path === '') {
|
|
110
|
+
const alias = firstPresentArg(args, ['root', 'directory', 'dir']);
|
|
111
|
+
if (alias !== undefined) args.path = alias;
|
|
112
|
+
}
|
|
113
|
+
// Internal-only passthrough: `_extraIgnoreDirs` is an array of basenames
|
|
114
|
+
// appended as `!**/<name>/**` ignore globs by executeGlobTool for the
|
|
115
|
+
// duration of one invocation. Not exposed in tools.json. Sanitize to a
|
|
116
|
+
// string[] of non-empty basenames so executeGlobTool can trust it.
|
|
117
|
+
if (args._extraIgnoreDirs !== undefined) {
|
|
118
|
+
const raw = Array.isArray(args._extraIgnoreDirs) ? args._extraIgnoreDirs : [];
|
|
119
|
+
const sanitized = raw
|
|
120
|
+
.filter((n) => typeof n === 'string')
|
|
121
|
+
.map((n) => n.trim())
|
|
122
|
+
.filter((n) => n.length > 0 && !/[\\/]/.test(n));
|
|
123
|
+
args._extraIgnoreDirs = sanitized;
|
|
124
|
+
}
|
|
125
|
+
return args;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function normalizeOutputPath(p) {
|
|
129
|
+
if (typeof p !== 'string') return p;
|
|
130
|
+
if (process.platform !== 'win32') return p;
|
|
131
|
+
return p.replace(/\\/g, '/').replace(/^([a-z]):/, (_, d) => d.toUpperCase() + ':');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function resolveAgainstCwd(filePath, cwd) {
|
|
135
|
+
return resolve(cwd, filePath);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function cwdRelativePath(fullPath, workDir) {
|
|
139
|
+
try {
|
|
140
|
+
const rel = relative(workDir, fullPath);
|
|
141
|
+
if (!rel || rel.startsWith('..') || isAbsolute(rel)) return fullPath;
|
|
142
|
+
return rel;
|
|
143
|
+
} catch { return fullPath; }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function extractGlobBaseDirectory(pattern) {
|
|
147
|
+
const wildcardIdx = pattern.search(/[\*\?\[\{]/);
|
|
148
|
+
const staticPrefix = wildcardIdx === -1 ? pattern : pattern.slice(0, wildcardIdx);
|
|
149
|
+
const lastSep = Math.max(
|
|
150
|
+
staticPrefix.lastIndexOf('/'),
|
|
151
|
+
staticPrefix.lastIndexOf('\\'),
|
|
152
|
+
);
|
|
153
|
+
if (lastSep === -1) return { baseDir: null, relativePattern: pattern };
|
|
154
|
+
let baseDir = staticPrefix.slice(0, lastSep);
|
|
155
|
+
const remainder = pattern.slice(lastSep + 1);
|
|
156
|
+
const relativePattern = remainder.startsWith('/') ? remainder : '/' + remainder;
|
|
157
|
+
if (process.platform === 'win32' && /^[A-Za-z]:$/.test(baseDir)) {
|
|
158
|
+
baseDir = baseDir + '\\';
|
|
159
|
+
}
|
|
160
|
+
return { baseDir: baseDir || null, relativePattern };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function hasGlobMagic(value) {
|
|
164
|
+
return typeof value === 'string' && /[\*\?\[\{]/.test(value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function countSplitLines(text) {
|
|
168
|
+
if (!text) return 0;
|
|
169
|
+
let lines = 1;
|
|
170
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
171
|
+
if (text.charCodeAt(i) === 10) lines += 1;
|
|
172
|
+
}
|
|
173
|
+
return lines;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function countDisplayLines(text) {
|
|
177
|
+
if (!text) return 0;
|
|
178
|
+
let lines = text.endsWith('\n') ? 0 : 1;
|
|
179
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
180
|
+
if (text.charCodeAt(i) === 10) lines += 1;
|
|
181
|
+
}
|
|
182
|
+
return lines;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function coerceShapeFlex(value) {
|
|
186
|
+
if (typeof value !== 'string') return value;
|
|
187
|
+
const trimmed = value.trim();
|
|
188
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
189
|
+
try {
|
|
190
|
+
const parsed = JSON.parse(trimmed);
|
|
191
|
+
if (Array.isArray(parsed)) return parsed;
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
if (trimmed.length >= 2 && trimmed.startsWith('"') && trimmed.endsWith('"')) {
|
|
195
|
+
try {
|
|
196
|
+
const parsed = JSON.parse(trimmed);
|
|
197
|
+
if (typeof parsed === 'string') return parsed;
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { existsSync } from 'fs';
|
|
2
|
+
import { normalizeInputPath, resolveAgainstCwd } from './path-utils.mjs';
|
|
3
|
+
import { parseLineLimitArg } from './read-formatting.mjs';
|
|
4
|
+
|
|
5
|
+
const READ_LINE_CONTEXT_DEFAULT = 20;
|
|
6
|
+
const READ_LINE_CONTEXT_MAX = 200;
|
|
7
|
+
|
|
8
|
+
export function parseReadLineNumberArg(value) {
|
|
9
|
+
const n = Number(value);
|
|
10
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function parseReadLineContextArg(value) {
|
|
14
|
+
const n = Number(value);
|
|
15
|
+
if (!Number.isFinite(n)) return READ_LINE_CONTEXT_DEFAULT;
|
|
16
|
+
return Math.min(READ_LINE_CONTEXT_MAX, Math.max(0, Math.trunc(n)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function parseReadPathLineSpec(rawPath) {
|
|
20
|
+
if (typeof rawPath !== 'string' || rawPath.length === 0) return null;
|
|
21
|
+
const text = rawPath.trim();
|
|
22
|
+
let m = /^(.+?)#L(\d+)(?:-L?(\d+))?(?:\b.*)?$/i.exec(text);
|
|
23
|
+
if (!m) m = /^(.+):(\d+)(?:-(\d+))?(?::.*)?$/.exec(text);
|
|
24
|
+
if (!m) return null;
|
|
25
|
+
const lineNo = parseReadLineNumberArg(m[2]);
|
|
26
|
+
const endLineRaw = parseReadLineNumberArg(m[3]);
|
|
27
|
+
if (!lineNo || !m[1]) return null;
|
|
28
|
+
// Inverted range (e.g. file#L20-L10) is a caller mistake, not a
|
|
29
|
+
// silent fallback to a context window — surface it as an error
|
|
30
|
+
// flag so the read tool can return a structured error.
|
|
31
|
+
const inverted = endLineRaw !== null && endLineRaw < lineNo;
|
|
32
|
+
const endLine = endLineRaw && endLineRaw >= lineNo ? endLineRaw : null;
|
|
33
|
+
return { path: m[1], lineNo, endLine, inverted, invertedEnd: inverted ? endLineRaw : null };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function resolveExistingPathLineCoordinate(rawPath, workDir) {
|
|
37
|
+
const spec = parseReadPathLineSpec(rawPath);
|
|
38
|
+
if (!spec) return null;
|
|
39
|
+
const normalizedRawPath = normalizeInputPath(rawPath);
|
|
40
|
+
const normalizedSpecPath = normalizeInputPath(spec.path);
|
|
41
|
+
const rawFull = resolveAgainstCwd(normalizedRawPath, workDir);
|
|
42
|
+
const specFull = resolveAgainstCwd(normalizedSpecPath, workDir);
|
|
43
|
+
if (existsSync(rawFull) || !existsSync(specFull)) return null;
|
|
44
|
+
return { ...spec, path: normalizedSpecPath };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function normalizePathAndStripLineCoordinate(rawPath, workDir) {
|
|
48
|
+
const resolved = resolveExistingPathLineCoordinate(rawPath, workDir);
|
|
49
|
+
return resolved ? resolved.path : normalizeInputPath(rawPath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function normaliseReadLineWindowArgs(inputArgs, workDir) {
|
|
53
|
+
const args = { ...inputArgs };
|
|
54
|
+
let lineNo = parseReadLineNumberArg(args.line);
|
|
55
|
+
let pathLineRange = null;
|
|
56
|
+
if (typeof args.path === 'string' && args.path) {
|
|
57
|
+
const spec = resolveExistingPathLineCoordinate(args.path, workDir);
|
|
58
|
+
if (spec) {
|
|
59
|
+
if (spec.inverted) {
|
|
60
|
+
args._invertedRangeError = `Error: inverted range in path "${inputArgs.path}" — end (${spec.invertedEnd}) precedes start (${spec.lineNo}); use file:${spec.invertedEnd}-${spec.lineNo} or remove the range`;
|
|
61
|
+
return args;
|
|
62
|
+
}
|
|
63
|
+
args.path = spec.path;
|
|
64
|
+
if (!lineNo) lineNo = spec.lineNo;
|
|
65
|
+
if (spec.endLine) pathLineRange = { startLine: spec.lineNo, endLine: spec.endLine };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const isFullMode = !args.mode || args.mode === 'full';
|
|
69
|
+
// line= and offset= are ALTERNATIVE window anchors. Silently preferring
|
|
70
|
+
// offset used to discard line/context and (with a bare low offset) fall
|
|
71
|
+
// through to a whole-file smart-truncate dump — the exact read explosion
|
|
72
|
+
// a 7-line `line+context` request was trying to avoid. Make it a hard
|
|
73
|
+
// conflict instead.
|
|
74
|
+
if (isFullMode && lineNo && args.offset !== undefined && args.offset !== null) {
|
|
75
|
+
args._invertedRangeError = `Error: conflicting window args — both line (${lineNo}) and offset (${args.offset}) were given; line/context and offset/limit are alternative window forms, pass only one`;
|
|
76
|
+
return args;
|
|
77
|
+
}
|
|
78
|
+
if (isFullMode && lineNo) {
|
|
79
|
+
if (pathLineRange && args.context === undefined && (args.limit === undefined || args.limit === null)) {
|
|
80
|
+
args.offset = Math.max(0, pathLineRange.startLine - 1);
|
|
81
|
+
args.limit = Math.max(1, pathLineRange.endLine - pathLineRange.startLine + 1);
|
|
82
|
+
} else {
|
|
83
|
+
const contextExplicit = args.context !== undefined && args.context !== null;
|
|
84
|
+
const limitExplicit = args.limit !== undefined && args.limit !== null;
|
|
85
|
+
const context = parseReadLineContextArg(args.context);
|
|
86
|
+
if (limitExplicit && !contextExplicit) {
|
|
87
|
+
// Explicit limit, no explicit context: anchor the window AT the
|
|
88
|
+
// requested line so it is always included. (Was: offset centered
|
|
89
|
+
// by the default context, which a small limit then truncated to
|
|
90
|
+
// exclude the very line the caller asked for.)
|
|
91
|
+
args.offset = Math.max(0, lineNo - 1);
|
|
92
|
+
args.limit = parseLineLimitArg(args.limit, (context * 2) + 1);
|
|
93
|
+
} else {
|
|
94
|
+
const limit = limitExplicit
|
|
95
|
+
? parseLineLimitArg(args.limit, (context * 2) + 1)
|
|
96
|
+
: (context * 2) + 1;
|
|
97
|
+
args.offset = Math.max(0, lineNo - context - 1);
|
|
98
|
+
args.limit = limit;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return args;
|
|
103
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { READ_BATCH_RANGE_COALESCE_GAP_LINES } from './read-constants.mjs';
|
|
2
|
+
|
|
3
|
+
// Slices a `read` body (full mode) to the line window [origOffset+1,
|
|
4
|
+
// origOffset+origLimit] (1-based line numbers in the body). Lines whose
|
|
5
|
+
// leading `^(\d+)│` prefix falls outside the window are dropped; the
|
|
6
|
+
// footer line is rebuilt to reflect the new range.
|
|
7
|
+
export function sliceReadBodyByLines(body, origOffset, origLimit) {
|
|
8
|
+
if (typeof body !== 'string') return body;
|
|
9
|
+
const off = typeof origOffset === 'number' ? origOffset : 0;
|
|
10
|
+
// limit:0 = unlimited (matches single-form parseLineLimitArg invariant)
|
|
11
|
+
const lim = typeof origLimit === 'number' && origLimit !== 0 ? origLimit : (origLimit === 0 ? Infinity : 2000);
|
|
12
|
+
const firstLine = off + 1; // 1-based inclusive
|
|
13
|
+
const lastLine = off + lim; // 1-based inclusive
|
|
14
|
+
const lines = body.split('\n');
|
|
15
|
+
const kept = [];
|
|
16
|
+
let footerIdx = -1;
|
|
17
|
+
for (let i = 0; i < lines.length; i++) {
|
|
18
|
+
const line = lines[i];
|
|
19
|
+
if (line.startsWith('[lines ') || line.startsWith('[read complete')) {
|
|
20
|
+
footerIdx = i;
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const m = /^(\d+)[\t│→]/.exec(line);
|
|
24
|
+
if (m) {
|
|
25
|
+
const ln = parseInt(m[1], 10);
|
|
26
|
+
if (ln >= firstLine && ln <= lastLine) kept.push(line);
|
|
27
|
+
} else if (kept.length === 0 && footerIdx === -1) {
|
|
28
|
+
kept.push(line);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// Mirror the scalar read footer shape — only emit finite hints.
|
|
32
|
+
// `of ?` / `pass offset:Infinity to continue` is noise that
|
|
33
|
+
// confuses the caller; drop those fields when totals are unknown
|
|
34
|
+
// or the slice is unbounded (limit:0/Infinity).
|
|
35
|
+
const totalLine = footerIdx >= 0 ? lines[footerIdx] : '';
|
|
36
|
+
const totalM = /of (\d+)/.exec(totalLine);
|
|
37
|
+
const totalNum = totalM ? parseInt(totalM[1], 10) : NaN;
|
|
38
|
+
const haveTotal = Number.isFinite(totalNum);
|
|
39
|
+
const finiteLast = Number.isFinite(lastLine);
|
|
40
|
+
const emittedLast = haveTotal && finiteLast
|
|
41
|
+
? Math.min(lastLine, totalNum)
|
|
42
|
+
: (finiteLast ? lastLine : (kept.length > 0
|
|
43
|
+
? (() => {
|
|
44
|
+
// Read the last kept line's number prefix.
|
|
45
|
+
const lastKept = kept[kept.length - 1];
|
|
46
|
+
const km = /^(\d+)[\t│→]/.exec(lastKept);
|
|
47
|
+
return km ? parseInt(km[1], 10) : firstLine;
|
|
48
|
+
})()
|
|
49
|
+
: firstLine));
|
|
50
|
+
const totalPart = haveTotal ? ` of ${totalNum}` : '';
|
|
51
|
+
const moreToRead = haveTotal ? emittedLast < totalNum : finiteLast;
|
|
52
|
+
const continuationPart = moreToRead && Number.isFinite(emittedLast)
|
|
53
|
+
? `; pass offset:${emittedLast} to continue`
|
|
54
|
+
: '';
|
|
55
|
+
const newFooter = `[lines ${firstLine}-${emittedLast}${totalPart}${continuationPart}]`;
|
|
56
|
+
return kept.join('\n') + (kept.length ? '\n' : '') + newFooter;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isFullModeReadEntry(entry) {
|
|
60
|
+
return !entry?.mode || entry.mode === 'full';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Caller omitted offset/limit — must use standalone read shaping, not union slice. */
|
|
64
|
+
function entryIsImplicitDefaultRead(entry) {
|
|
65
|
+
return typeof entry?.limit !== 'number' && typeof entry?.offset !== 'number';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function readEntryLineWindow(entry) {
|
|
69
|
+
const offset = typeof entry.offset === 'number' && Number.isFinite(entry.offset)
|
|
70
|
+
? Math.max(0, Math.trunc(entry.offset))
|
|
71
|
+
: 0;
|
|
72
|
+
// full:true uncaps the read (read-single-tool wantFull), so the coalescing
|
|
73
|
+
// window must extend to EOF too — otherwise the union read is capped at the
|
|
74
|
+
// default 2000 lines while the entry actually reads the whole file, and the
|
|
75
|
+
// per-window slice returns truncated/empty results.
|
|
76
|
+
const limit = entry.full === true
|
|
77
|
+
? Infinity
|
|
78
|
+
: (typeof entry.limit === 'number'
|
|
79
|
+
? (entry.limit === 0 ? Infinity : Math.max(1, Math.trunc(entry.limit)))
|
|
80
|
+
: 2000);
|
|
81
|
+
return {
|
|
82
|
+
offset,
|
|
83
|
+
end: limit === Infinity ? Infinity : offset + limit,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function coalesceObjectReadEntries(rawEntries) {
|
|
88
|
+
const out = new Array(rawEntries.length);
|
|
89
|
+
const groups = new Map();
|
|
90
|
+
for (let i = 0; i < rawEntries.length; i++) {
|
|
91
|
+
const entry = rawEntries[i];
|
|
92
|
+
if (!isFullModeReadEntry(entry)) {
|
|
93
|
+
out[i] = entry;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const win = readEntryLineWindow(entry);
|
|
97
|
+
const key = entry.path || '';
|
|
98
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
99
|
+
groups.get(key).push({ index: i, entry, offset: win.offset, end: win.end });
|
|
100
|
+
}
|
|
101
|
+
for (const items of groups.values()) {
|
|
102
|
+
items.sort((a, b) => {
|
|
103
|
+
if (a.offset !== b.offset) return a.offset - b.offset;
|
|
104
|
+
if (a.end === b.end) return 0;
|
|
105
|
+
if (a.end === Infinity) return 1;
|
|
106
|
+
if (b.end === Infinity) return -1;
|
|
107
|
+
return a.end - b.end;
|
|
108
|
+
});
|
|
109
|
+
const clusters = [];
|
|
110
|
+
let current = null;
|
|
111
|
+
for (const item of items) {
|
|
112
|
+
const itemImplicit = entryIsImplicitDefaultRead(item.entry);
|
|
113
|
+
const itemExplicit = !itemImplicit;
|
|
114
|
+
const canMerge = current
|
|
115
|
+
&& (current.end === Infinity || item.offset <= current.end + READ_BATCH_RANGE_COALESCE_GAP_LINES)
|
|
116
|
+
&& !(current.hasImplicit && itemExplicit)
|
|
117
|
+
&& !(itemImplicit && current.hasExplicit)
|
|
118
|
+
&& !(itemImplicit && current.hasImplicit);
|
|
119
|
+
if (!canMerge) {
|
|
120
|
+
current = {
|
|
121
|
+
offset: item.offset,
|
|
122
|
+
end: item.end,
|
|
123
|
+
items: [item],
|
|
124
|
+
hasImplicit: itemImplicit,
|
|
125
|
+
hasExplicit: itemExplicit,
|
|
126
|
+
};
|
|
127
|
+
clusters.push(current);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
current.items.push(item);
|
|
131
|
+
current.hasImplicit = current.hasImplicit || itemImplicit;
|
|
132
|
+
current.hasExplicit = current.hasExplicit || itemExplicit;
|
|
133
|
+
if (current.end !== Infinity) current.end = item.end === Infinity ? Infinity : Math.max(current.end, item.end);
|
|
134
|
+
}
|
|
135
|
+
for (const cluster of clusters) {
|
|
136
|
+
if (cluster.items.length === 1) {
|
|
137
|
+
const item = cluster.items[0];
|
|
138
|
+
out[item.index] = item.entry;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const unionLimit = cluster.end === Infinity ? 0 : cluster.end - cluster.offset;
|
|
142
|
+
for (const item of cluster.items) {
|
|
143
|
+
const entryOffset = typeof item.entry.offset === 'number' && Number.isFinite(item.entry.offset)
|
|
144
|
+
? Math.max(0, Math.trunc(item.entry.offset))
|
|
145
|
+
: item.offset;
|
|
146
|
+
const entryLimit = typeof item.entry.limit === 'number'
|
|
147
|
+
? item.entry.limit
|
|
148
|
+
: (item.end === Infinity ? 0 : Math.max(1, item.end - item.offset));
|
|
149
|
+
out[item.index] = {
|
|
150
|
+
...item.entry,
|
|
151
|
+
offset: entryOffset,
|
|
152
|
+
limit: entryLimit,
|
|
153
|
+
_unionOffset: cluster.offset,
|
|
154
|
+
_unionLimit: unionLimit,
|
|
155
|
+
_needsPerEntrySlice: true,
|
|
156
|
+
_origOffset: item.entry.offset,
|
|
157
|
+
_origLimit: item.entry.limit,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Disk-read window when batch coalescing merged nearby ranges. */
|
|
166
|
+
export function readEntryCoalescedDiskWindow(entry) {
|
|
167
|
+
if (!entry || entry._unionOffset === undefined) return null;
|
|
168
|
+
return {
|
|
169
|
+
offset: entry._unionOffset,
|
|
170
|
+
limit: entry._unionLimit,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SMART_READ_MAX_BYTES } from './read-formatting.mjs';
|
|
2
|
+
|
|
3
|
+
// Read tool caps.
|
|
4
|
+
//
|
|
5
|
+
// READ_MAX_SIZE_BYTES (10 MB) — fast-path file-size threshold mirroring
|
|
6
|
+
// Claude Code's FAST_PATH_MAX_SIZE (readFileInRange.ts:44). Files at or below
|
|
7
|
+
// this size use readFile + in-memory split by default, which CC measured at
|
|
8
|
+
// ~2x faster than createReadStream + readline for typical source. Explicit
|
|
9
|
+
// offset/limit windows on files above READ_STREAM_RANGE_MIN_BYTES take the
|
|
10
|
+
// streaming path too, so a targeted read avoids materialising a whole
|
|
11
|
+
// medium/large document just to return a few lines. Default no-range reads
|
|
12
|
+
// switch to the streaming smart-summary path once the file is large enough
|
|
13
|
+
// to trigger output elision anyway.
|
|
14
|
+
//
|
|
15
|
+
// READ_WHOLE_FILE_MAX_BYTES (256 KiB) — soft threshold: default whole-file
|
|
16
|
+
// reads larger than this prefer stream smart-elide + READ_MAX_OUTPUT_BYTES
|
|
17
|
+
// truncation (Codex-style proceed) rather than refusing. Hard in-memory cap
|
|
18
|
+
// for loading a full file remains READ_MAX_SIZE_BYTES (10 MiB).
|
|
19
|
+
//
|
|
20
|
+
// READ_MAX_OUTPUT_BYTES (30 KB) — output-truncation cap. Lead-facing default tightened from 50k. Mirrors CC's
|
|
21
|
+
// MAX_OUTPUT_SIZE intent (utils/file.ts:48) and the throw-vs-truncate
|
|
22
|
+
// trade-off Anthropic chose in #21841 (throw is more token-efficient).
|
|
23
|
+
// NOTE: CC's parallel output budget is maxTokens = 25,000 tokens
|
|
24
|
+
// (limits.ts:18), enforced post-read via a tokenizer throw. mixdog
|
|
25
|
+
// caps on BYTES at read time instead (no tokenizer in the hot path);
|
|
26
|
+
// 30 KB ≈ well under a 25k-token budget, so the byte cap is left as-is
|
|
27
|
+
// rather than converted to a token count that can't be expressed
|
|
28
|
+
// cleanly without a tokenizer.
|
|
29
|
+
export const READ_MAX_SIZE_BYTES = 10 * 1024 * 1024;
|
|
30
|
+
export const READ_WHOLE_FILE_MAX_BYTES = 256 * 1024;
|
|
31
|
+
export const READ_MAX_OUTPUT_BYTES = 30_000;
|
|
32
|
+
export const READ_STREAM_RANGE_MIN_BYTES = 128 * 1024;
|
|
33
|
+
export const READ_SMART_STREAM_MIN_BYTES = SMART_READ_MAX_BYTES;
|
|
34
|
+
export const READ_BATCH_RANGE_COALESCE_GAP_LINES = 256;
|
|
35
|
+
export const READ_LARGE_TAIL_MAX_BYTES = 4 * 1024 * 1024;
|
|
36
|
+
export const READ_STREAM_TIMEOUT_MS = 20_000;
|
|
37
|
+
// Hard cap on bytes scanned in a single ranged streaming read (~512 MiB).
|
|
38
|
+
export const READ_MAX_SCAN_BYTES = 512 * 1024 * 1024;
|
|
39
|
+
// Per-line accumulation cap while streaming (before renderReadLine truncates).
|
|
40
|
+
export const READ_MAX_LINE_COLLECT_BYTES = READ_MAX_OUTPUT_BYTES;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { mergeReadRanges } from './read-ranges.mjs';
|
|
2
|
+
|
|
3
|
+
export const SMART_READ_MAX_BYTES = 30 * 1024;
|
|
4
|
+
export const SMART_READ_MAX_LINES = 600;
|
|
5
|
+
export const SMART_READ_HEAD_LINES = 200;
|
|
6
|
+
export const SMART_READ_TAIL_LINES = 100;
|
|
7
|
+
// Only the genuinely large full reads warrant the anti-re-read advisory; below
|
|
8
|
+
// this the smart-truncate path (30 KB) already caps normal reads, so a 16 KB
|
|
9
|
+
// floor mostly fired on full:true mid-size reads where the advisory was pure
|
|
10
|
+
// tail bloat. Raised to 40 KB to keep the guidance only where re-reading a big
|
|
11
|
+
// file actually hurts.
|
|
12
|
+
export const READ_CONTEXT_ADVISORY_BYTES = 40 * 1024;
|
|
13
|
+
export const READ_MAX_RENDERED_LINE_CHARS = 2_000;
|
|
14
|
+
// Claude-Code parity: the read line-prefix separator is `→` (the `→`
|
|
15
|
+
// arrow), matching Claude Code's default cat -n format `<n>→<content>`. It
|
|
16
|
+
// MUST be a NON-WHITESPACE glyph: a tab/space separator collides with the
|
|
17
|
+
// content's own leading indentation, so a model hand-reconstructing an edit
|
|
18
|
+
// old_string cannot tell the separator from the indent and produces
|
|
19
|
+
// byte-mismatched anchors (grep finds the substring, edit cannot). All
|
|
20
|
+
// read/edit parsers accept `[\t│→]`, so any in-flight tab/pipe-rendered
|
|
21
|
+
// output stays backward-compatible.
|
|
22
|
+
export const LINE_NO_SEP = '→';
|
|
23
|
+
|
|
24
|
+
export function buildSmartReadTruncationMarker(totalLines, fileBytes, filePath = '') {
|
|
25
|
+
const kb = Math.max(1, Math.round((Number(fileBytes) || 0) / 1024));
|
|
26
|
+
return `... [TRUNCATED - ${totalLines} lines / ${kb} KB] ...`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rangeFromRenderedReadRows(rows, fallbackStartLine = 1) {
|
|
30
|
+
if (!Array.isArray(rows) || rows.length === 0) return null;
|
|
31
|
+
const nums = [];
|
|
32
|
+
for (const row of rows) {
|
|
33
|
+
const m = /^(\d+)[\t│→]/.exec(String(row));
|
|
34
|
+
if (m) nums.push(Number(m[1]));
|
|
35
|
+
}
|
|
36
|
+
if (nums.length > 0) {
|
|
37
|
+
return { startLine: Math.min(...nums), endLine: Math.max(...nums) };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
startLine: Math.max(1, Number(fallbackStartLine) || 1),
|
|
41
|
+
endLine: Math.max(1, (Number(fallbackStartLine) || 1) + rows.length - 1),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Optional `budget` { maxLines, headLines, tailLines } lets a caller request a
|
|
46
|
+
// TIGHTER head+tail elision than the 600-line / 200+100 default (e.g. read
|
|
47
|
+
// budget:'compact' or max_lines:N) to bound lead-context cost. Omitted -> the
|
|
48
|
+
// standard caps, so existing 4-arg callers are byte-for-byte unchanged.
|
|
49
|
+
export function smartReadTruncate(renderedWithLineNos, totalLines, fileBytes, filePath = '', budget = null) {
|
|
50
|
+
const maxLines = budget?.maxLines ?? SMART_READ_MAX_LINES;
|
|
51
|
+
const headLines = budget?.headLines ?? SMART_READ_HEAD_LINES;
|
|
52
|
+
const tailLines = budget?.tailLines ?? SMART_READ_TAIL_LINES;
|
|
53
|
+
const overByBytes = fileBytes > SMART_READ_MAX_BYTES;
|
|
54
|
+
const overByLines = totalLines > maxLines;
|
|
55
|
+
if (!overByBytes && !overByLines) {
|
|
56
|
+
return { text: renderedWithLineNos, truncated: false, totalLines, ranges: null };
|
|
57
|
+
}
|
|
58
|
+
const rows = renderedWithLineNos.split('\n');
|
|
59
|
+
const headCount = Math.min(headLines, rows.length);
|
|
60
|
+
const tailStart = Math.max(headCount, rows.length - tailLines);
|
|
61
|
+
const elidedRows = tailStart - headCount;
|
|
62
|
+
if (elidedRows <= 0) {
|
|
63
|
+
return { text: renderedWithLineNos, truncated: false, totalLines, ranges: null };
|
|
64
|
+
}
|
|
65
|
+
const headRows = rows.slice(0, headCount);
|
|
66
|
+
const tailRows = rows.slice(tailStart);
|
|
67
|
+
const head = headRows.join('\n');
|
|
68
|
+
const tail = tailRows.join('\n');
|
|
69
|
+
const marker = buildSmartReadTruncationMarker(totalLines, fileBytes, filePath);
|
|
70
|
+
return {
|
|
71
|
+
text: `${head}\n${marker}\n${tail}`,
|
|
72
|
+
truncated: true,
|
|
73
|
+
totalLines,
|
|
74
|
+
ranges: mergeReadRanges([
|
|
75
|
+
rangeFromRenderedReadRows(headRows, 1),
|
|
76
|
+
rangeFromRenderedReadRows(tailRows, tailStart + 1),
|
|
77
|
+
].filter(Boolean)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function appendReadContextAdvisory(out, { filePath, lineCount, bytes }) {
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function formatPaginationHint(remaining, nextOffset) {
|
|
86
|
+
const n = Number(remaining);
|
|
87
|
+
const label = Number.isFinite(n) && n > 0 ? `${n} more entries` : 'more entries';
|
|
88
|
+
return `... [${label}; next offset: ${nextOffset}]`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function parseOffsetArg(value) {
|
|
92
|
+
const n = Number(value);
|
|
93
|
+
return Number.isFinite(n) && n > 0 ? Math.trunc(n) : 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function parseLineLimitArg(value, defaultValue) {
|
|
97
|
+
const n = Number(value);
|
|
98
|
+
if (!Number.isFinite(n)) return defaultValue;
|
|
99
|
+
if (n === 0) return Infinity;
|
|
100
|
+
return Math.max(1, Math.trunc(n));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function truncateReadLineText(line, { truncateLongLine = true } = {}) {
|
|
104
|
+
let text = String(line ?? '');
|
|
105
|
+
const originalLength = text.length;
|
|
106
|
+
if (truncateLongLine && text.length > READ_MAX_RENDERED_LINE_CHARS) {
|
|
107
|
+
const cps = [...text];
|
|
108
|
+
const head = cps.slice(0, 1_500).join('');
|
|
109
|
+
const tail = cps.slice(-300).join('');
|
|
110
|
+
text = `${head} ... [line truncated: ${originalLength} chars total] ... ${tail}`;
|
|
111
|
+
}
|
|
112
|
+
return text;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function renderReadLine(lineNo, line, { truncateLongLine = true } = {}) {
|
|
116
|
+
const text = truncateReadLineText(line, { truncateLongLine });
|
|
117
|
+
return `${lineNo}${LINE_NO_SEP}${text}`;
|
|
118
|
+
}
|