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,256 @@
|
|
|
1
|
+
// --- Tool definitions for external models ---
|
|
2
|
+
//
|
|
3
|
+
// Ordered to match the previous hand-maintained tools.json entries
|
|
4
|
+
// (read / edit / write / bash / grep / glob) so
|
|
5
|
+
// build-tools-manifest reproduces the legacy ordering.
|
|
6
|
+
// CANONICAL SOURCE for all tool annotations (compressible, readOnlyHint,
|
|
7
|
+
// destructiveHint, etc.). tools.json is GENERATED from this array by
|
|
8
|
+
// dev/scripts/build-tools-manifest.mjs — do not edit annotations in tools.json
|
|
9
|
+
// directly. To verify sync: node dev/scripts/check-tools-sync.mjs
|
|
10
|
+
export const BUILTIN_TOOLS = [
|
|
11
|
+
{
|
|
12
|
+
name: 'read',
|
|
13
|
+
title: 'Mixdog Read',
|
|
14
|
+
annotations: { title: 'Mixdog Read', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: false },
|
|
15
|
+
description: 'Read file contents (path required). symbol=NAME = whole definition; offset/limit or line/context = window; mode/max_lines = whole-file glance. Batch all regions of a file in one call. Output is the file text verbatim (line-number prefix only, no character escaping) — a \\uXXXX you see is literally in the file.',
|
|
16
|
+
inputSchema: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
path: { type: 'string' },
|
|
20
|
+
offset: { type: 'number', description: 'Start line for a windowed read (1-based).' },
|
|
21
|
+
limit: { type: 'number', description: 'Max lines to read (default 2000; full:true uncaps).' },
|
|
22
|
+
line: { type: 'number' },
|
|
23
|
+
context: { type: 'number', description: 'Lines around `line` (default 20).' },
|
|
24
|
+
pages: { type: 'string' },
|
|
25
|
+
symbol: { type: 'string', description: 'Read a whole symbol body (function/class/const) via the code graph.' },
|
|
26
|
+
language: { type: 'string', description: 'Language hint for symbol resolution.' },
|
|
27
|
+
mode: { type: 'string', enum: ['head', 'tail', 'count', 'summary', 'hex'], description: 'Whole-file glance: head/tail (first/last n), count (line/word/byte stats), summary (stats+head), hex (binary preview). For a window use offset/limit.' },
|
|
28
|
+
n: { type: 'number', description: 'Line count for mode head/tail/summary (default 20).' },
|
|
29
|
+
full: { type: 'boolean', description: 'Default false. true = return whole file (bypass 2000-line cap; still byte-capped).' },
|
|
30
|
+
max_lines: { type: 'number', description: 'Whole-file read: cap output to ~N lines. Ignored with offset/limit/mode.' },
|
|
31
|
+
budget: { type: 'string', enum: ['compact'], description: 'Auto-shrink: whole-file -> count; line -> context<=20; range -> limit<=120.' },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: 'edit',
|
|
37
|
+
title: 'Mixdog Edit',
|
|
38
|
+
annotations: { title: 'Mixdog Edit', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
39
|
+
description: 'Exact-string editor for small known substitutions; for large or structural changes prefer apply_patch. operation: replace (default) | notebook | rename.',
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: 'object',
|
|
42
|
+
properties: {
|
|
43
|
+
operation: { type: 'string', enum: ['replace', 'notebook', 'rename'], description: 'Edit mode: "replace" (default, exact text edit), "notebook" (Jupyter cell edit), or "rename" (symbol rename).' },
|
|
44
|
+
path: { type: 'string' },
|
|
45
|
+
old_string: { type: 'string' },
|
|
46
|
+
new_string: { type: 'string' },
|
|
47
|
+
replace_all: { type: 'boolean' },
|
|
48
|
+
edits: {
|
|
49
|
+
type: 'array',
|
|
50
|
+
items: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
properties: {
|
|
53
|
+
path: { type: 'string' },
|
|
54
|
+
old_string: { type: 'string' },
|
|
55
|
+
new_string: { type: 'string' },
|
|
56
|
+
replace_all: { type: 'boolean' },
|
|
57
|
+
},
|
|
58
|
+
required: ['old_string', 'new_string'],
|
|
59
|
+
additionalProperties: false,
|
|
60
|
+
},
|
|
61
|
+
minItems: 1,
|
|
62
|
+
description: 'Batch replacements (operation:"replace").',
|
|
63
|
+
},
|
|
64
|
+
notebook_path: { type: 'string', description: 'operation:"notebook" — path to the .ipynb notebook to edit.' },
|
|
65
|
+
cell_id: { type: 'string', description: 'operation:"notebook" — target cell: a real cell.id or a cell-N index. For insert, the new cell is placed after this cell (or at the start if omitted).' },
|
|
66
|
+
new_source: { type: 'string', description: 'operation:"notebook" — new source for the cell (not required for delete).' },
|
|
67
|
+
cell_type: { type: 'string', enum: ['code', 'markdown'], description: 'operation:"notebook" — cell type. Required for insert; otherwise defaults to the current cell type.' },
|
|
68
|
+
edit_mode: { type: 'string', enum: ['replace', 'insert', 'delete'], description: 'operation:"notebook" — replace (default), insert, or delete.' },
|
|
69
|
+
symbol: { type: 'string', description: 'operation:"rename" — identifier to rename.' },
|
|
70
|
+
new_name: { type: 'string', description: 'operation:"rename" — new identifier.' },
|
|
71
|
+
file: { type: 'string', description: 'operation:"rename" — optional file to scope the reference search.' },
|
|
72
|
+
apply: { type: 'boolean', description: 'operation:"rename" — default false (preview). Set true to perform the rename.' },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'write',
|
|
78
|
+
title: 'Mixdog Write',
|
|
79
|
+
annotations: { title: 'Mixdog Write', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
80
|
+
description: 'Write a whole file — new, or a full rewrite of one already read. For partial edits use apply_patch; never overwrite an unread file.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
path: { type: 'string' },
|
|
85
|
+
content: { type: 'string' },
|
|
86
|
+
allow_unread_overwrite: { type: 'boolean', description: 'Default false. true = skip ONLY the read-before-overwrite snapshot gate when you already know the full intended content; all other write safety checks still apply.' },
|
|
87
|
+
writes: {
|
|
88
|
+
type: 'array',
|
|
89
|
+
items: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
path: { type: 'string' },
|
|
93
|
+
content: { type: 'string' },
|
|
94
|
+
},
|
|
95
|
+
required: ['path', 'content'],
|
|
96
|
+
additionalProperties: false,
|
|
97
|
+
},
|
|
98
|
+
minItems: 1,
|
|
99
|
+
description: 'Batch writes.',
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'diagnostics',
|
|
106
|
+
title: 'Mixdog Diagnostics',
|
|
107
|
+
annotations: { title: 'Mixdog Diagnostics', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
108
|
+
description: 'Run the matching type/lint checker under path (tsc/eslint/ruff/etc.). Default cwd; no LSP.',
|
|
109
|
+
inputSchema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
properties: {
|
|
112
|
+
path: { type: 'string', description: 'File or directory to diagnose. Defaults to cwd.' },
|
|
113
|
+
},
|
|
114
|
+
required: [],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'open_config',
|
|
119
|
+
title: 'Open Config UI',
|
|
120
|
+
annotations: { title: 'Open Config UI', readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
121
|
+
description: 'Open the mixdog settings UI (Providers + Presets) in the browser. Starts the resident config server if needed and returns the UI URL. No params.',
|
|
122
|
+
inputSchema: { type: 'object', properties: {} },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'bash',
|
|
126
|
+
title: 'Mixdog Shell',
|
|
127
|
+
annotations: { title: 'Mixdog Shell', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true, compressible: true },
|
|
128
|
+
description: "Shell for git/build/test/run. Use current-OS syntax: Windows default = PowerShell; POSIX default = /bin/sh. Always pass shell matching your syntax: 'bash' = POSIX via Git Bash, 'powershell' = PS cmdlets; omitting uses the OS default and mis-parses the other. run_in_background works for both shells, including Windows shell:'bash' (Git Bash). Single shell entry point; not for inline code you were asked to return.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: {
|
|
132
|
+
command: { type: 'string' },
|
|
133
|
+
cwd: { type: 'string' },
|
|
134
|
+
timeout: { type: 'number', description: 'Timeout in ms, or seconds when <=600.' },
|
|
135
|
+
merge_stderr: { type: 'boolean' },
|
|
136
|
+
run_in_background: { type: 'boolean' },
|
|
137
|
+
persistent: { type: 'boolean' },
|
|
138
|
+
session_id: { type: 'string' },
|
|
139
|
+
create: { type: 'boolean', description: 'Allow creating a new persistent session for an explicit session_id.' },
|
|
140
|
+
close: { type: 'boolean' },
|
|
141
|
+
shell: { type: 'string', enum: ['bash', 'powershell'], description: "Force the shell. On Windows: 'bash' runs the command through Git Bash (POSIX syntax), 'powershell' forces PowerShell. On POSIX: 'powershell' resolves pwsh if installed (errors if absent); 'bash' is /bin/sh (already the default). Always set this explicitly; omitting uses the OS default (PowerShell on Windows), where POSIX syntax fails to parse." },
|
|
142
|
+
},
|
|
143
|
+
required: ['command'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'job_wait',
|
|
148
|
+
title: 'Background Job Control',
|
|
149
|
+
annotations: { title: 'Background Job Control', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
150
|
+
description: 'Control a bash run_in_background job by job_id. action: wait (default) | peek | kill. Not for bridge_*/sess_* ids.',
|
|
151
|
+
inputSchema: {
|
|
152
|
+
type: 'object',
|
|
153
|
+
properties: {
|
|
154
|
+
job_id: { type: 'string', description: 'job_id from bash with run_in_background:true.' },
|
|
155
|
+
action: { type: 'string', enum: ['wait', 'peek', 'kill'], description: 'wait (default) = block until done; peek = non-blocking status + output tail; kill = terminate.' },
|
|
156
|
+
timeout_ms: { type: 'number' },
|
|
157
|
+
poll_ms: { type: 'number' },
|
|
158
|
+
},
|
|
159
|
+
required: ['job_id'],
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'grep',
|
|
164
|
+
title: 'Mixdog Grep',
|
|
165
|
+
annotations: { title: 'Mixdog Grep', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
166
|
+
description: 'Search file contents (ripgrep): free-text / non-symbol content. For a symbol by name use code_graph, not grep. pattern (regex) or glob. output_mode: content (default) | files_with_matches | count. Array pattern matches several in ONE call. -A/-B/-C is line-based — useless on minified/single-line files; use -o or multiline.',
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: {
|
|
170
|
+
pattern: {
|
|
171
|
+
anyOf: [
|
|
172
|
+
{ type: 'string' },
|
|
173
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
174
|
+
],
|
|
175
|
+
description: 'Ripgrep regex, or an array of regexes (OR-matched; <=20, <=5 with multiline) to match several in one call.',
|
|
176
|
+
},
|
|
177
|
+
path: { type: 'string' },
|
|
178
|
+
glob: {
|
|
179
|
+
anyOf: [
|
|
180
|
+
{ type: 'string' },
|
|
181
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
182
|
+
],
|
|
183
|
+
description: 'Glob, or an array of globs, to filter files.',
|
|
184
|
+
},
|
|
185
|
+
output_mode: { type: 'string', enum: ['files_with_matches', 'content', 'count'], description: 'Default content (matching lines). files_with_matches = paths only; count = per-file counts.' },
|
|
186
|
+
head_limit: { type: 'number', description: 'Max result lines (default 80; 0 = unlimited). Truncated output says how many results remain.' },
|
|
187
|
+
offset: { type: 'number', description: 'Skip N result lines before head_limit applies — for paging large result sets.' },
|
|
188
|
+
'-i': { type: 'boolean' },
|
|
189
|
+
'-n': { type: 'boolean' },
|
|
190
|
+
'-A': { type: 'number' },
|
|
191
|
+
'-B': { type: 'number' },
|
|
192
|
+
'-C': { type: 'number', description: 'Context lines (with -A/-B). Line-based; no effect on single-line/minified files — use -o.' },
|
|
193
|
+
context: { type: 'number' },
|
|
194
|
+
multiline: { type: 'boolean', description: 'Dot matches newlines; patterns span lines (rg -U). Pair with -o on single-line/minified files.' },
|
|
195
|
+
'-o': { type: 'boolean', description: 'Only matching parts (rg --only-matching).' },
|
|
196
|
+
type: { type: 'string' },
|
|
197
|
+
},
|
|
198
|
+
required: [],
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
name: 'glob',
|
|
203
|
+
title: 'Mixdog Glob',
|
|
204
|
+
annotations: { title: 'Mixdog Glob', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
205
|
+
description: 'Find files by glob pattern (mtime-sorted). For name/size/date filters use list mode:find.',
|
|
206
|
+
inputSchema: {
|
|
207
|
+
type: 'object',
|
|
208
|
+
properties: {
|
|
209
|
+
pattern: {
|
|
210
|
+
anyOf: [
|
|
211
|
+
{ type: 'string' },
|
|
212
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
213
|
+
],
|
|
214
|
+
description: 'Glob or array.',
|
|
215
|
+
},
|
|
216
|
+
path: {
|
|
217
|
+
anyOf: [
|
|
218
|
+
{ type: 'string' },
|
|
219
|
+
{ type: 'array', items: { type: 'string' }, minItems: 1 },
|
|
220
|
+
],
|
|
221
|
+
description: 'Base dir or array.',
|
|
222
|
+
},
|
|
223
|
+
head_limit: { type: 'number', description: 'Max entries returned (mtime-sorted). A truncated listing ends with "pass offset:N to continue" — re-call with that offset to page.' },
|
|
224
|
+
offset: { type: 'number', description: 'Skip N entries before head_limit applies; use the footer-suggested value to page.' },
|
|
225
|
+
},
|
|
226
|
+
required: ['pattern'],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'list',
|
|
231
|
+
title: 'Mixdog List Directory',
|
|
232
|
+
annotations: { title: 'Mixdog List Directory', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false, compressible: true },
|
|
233
|
+
description: 'List or find directory entries. mode: list | tree | find (name/size/date filter). fuzzy ranks by partial name. Default cwd.',
|
|
234
|
+
inputSchema: {
|
|
235
|
+
type: 'object',
|
|
236
|
+
properties: {
|
|
237
|
+
path: { type: 'string' },
|
|
238
|
+
mode: { type: 'string', enum: ['list', 'tree', 'find'], description: 'Default list (flat dir). tree = recursive tree; find = name/size/date filter.' },
|
|
239
|
+
depth: { type: 'number' },
|
|
240
|
+
hidden: { type: 'boolean' },
|
|
241
|
+
sort: { type: 'string', enum: ['name', 'mtime', 'size'] },
|
|
242
|
+
type: { type: 'string', enum: ['any', 'file', 'dir'] },
|
|
243
|
+
head_limit: { type: 'number', description: 'Max entries returned. A truncated listing ends with "[entries X-Y of T; pass offset:N to continue]" — re-call with that offset to page.' },
|
|
244
|
+
offset: { type: 'number', description: 'Skip N entries before head_limit applies; use the footer-suggested value to page.' },
|
|
245
|
+
include_noise: { type: 'boolean' },
|
|
246
|
+
name: { type: 'string' },
|
|
247
|
+
min_size: { type: 'number' },
|
|
248
|
+
max_size: { type: 'number' },
|
|
249
|
+
modified_after: { type: 'string' },
|
|
250
|
+
modified_before: { type: 'string' },
|
|
251
|
+
fuzzy: { type: 'string', description: 'Rank files by subsequence match of this partial name; overrides mode. e.g. "edeng" -> edit-engine.mjs.' },
|
|
252
|
+
},
|
|
253
|
+
required: [],
|
|
254
|
+
},
|
|
255
|
+
},
|
|
256
|
+
];
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { statSync } from 'fs';
|
|
2
|
+
import * as fsPromises from 'fs/promises';
|
|
3
|
+
import { isAbsolute, normalize, resolve, sep } from 'path';
|
|
4
|
+
import { deleteReadRangeIndexForPath } from './read-range-index.mjs';
|
|
5
|
+
import { resolveAgainstCwd } from './path-utils.mjs';
|
|
6
|
+
|
|
7
|
+
const RESULT_CACHE = new Map(); // key → { ts, value, paths, scopes, readSnapshotMeta, contentPrefixHash, bytes }
|
|
8
|
+
const RESULT_CACHE_TTL_MS = 30_000;
|
|
9
|
+
const RESULT_CACHE_MAX_ENTRIES = 200;
|
|
10
|
+
const RESULT_CACHE_MAX_BYTES = (() => {
|
|
11
|
+
const rawBytes = Number(process.env.MIXDOG_RESULT_CACHE_MAX_BYTES);
|
|
12
|
+
if (Number.isFinite(rawBytes) && rawBytes > 0) return Math.trunc(rawBytes);
|
|
13
|
+
const rawMb = Number(process.env.MIXDOG_RESULT_CACHE_MAX_MB);
|
|
14
|
+
if (Number.isFinite(rawMb) && rawMb > 0) return Math.trunc(rawMb * 1024 * 1024);
|
|
15
|
+
return 32 * 1024 * 1024;
|
|
16
|
+
})();
|
|
17
|
+
let RESULT_CACHE_BYTES = 0;
|
|
18
|
+
function estimateResultBytes(value) {
|
|
19
|
+
if (value == null) return 0;
|
|
20
|
+
if (typeof value === 'string') return Buffer.byteLength(value, 'utf-8');
|
|
21
|
+
if (Buffer.isBuffer(value)) return value.length;
|
|
22
|
+
try { return Buffer.byteLength(JSON.stringify(value) ?? '', 'utf-8'); }
|
|
23
|
+
catch { return 0; }
|
|
24
|
+
}
|
|
25
|
+
function resultCacheDelete(key) {
|
|
26
|
+
const entry = RESULT_CACHE.get(key);
|
|
27
|
+
if (entry && typeof entry.bytes === 'number') {
|
|
28
|
+
RESULT_CACHE_BYTES = Math.max(0, RESULT_CACHE_BYTES - entry.bytes);
|
|
29
|
+
}
|
|
30
|
+
RESULT_CACHE.delete(key);
|
|
31
|
+
}
|
|
32
|
+
const STAT_CACHE = new Map(); // fullPath → { ts, stat }
|
|
33
|
+
const STAT_CACHE_TTL_MS = 5_000;
|
|
34
|
+
const STAT_CACHE_MAX_ENTRIES = 2_000;
|
|
35
|
+
const RAW_CONTENT_CACHE = new Map(); // fullPath → { ts, mtimeMs, ctimeMs, size, rawBuf }
|
|
36
|
+
const RAW_CONTENT_CACHE_TTL_MS = 30_000;
|
|
37
|
+
const RAW_CONTENT_CACHE_MAX_ENTRIES = 16;
|
|
38
|
+
const PATH_MUTATION_GENERATIONS = new Map(); // canonical path/root → monotonic generation
|
|
39
|
+
const PATH_MUTATION_GENERATION_MAX_ENTRIES = 4096;
|
|
40
|
+
let PATH_MUTATION_GLOBAL_GENERATION = 0;
|
|
41
|
+
const RAW_CONTENT_CACHE_MAX_BYTES = (() => {
|
|
42
|
+
const rawBytes = Number(process.env.MIXDOG_RAW_CONTENT_CACHE_MAX_BYTES);
|
|
43
|
+
if (Number.isFinite(rawBytes) && rawBytes > 0) return Math.trunc(rawBytes);
|
|
44
|
+
const rawMb = Number(process.env.MIXDOG_RAW_CONTENT_CACHE_MAX_MB);
|
|
45
|
+
if (Number.isFinite(rawMb) && rawMb > 0) return Math.trunc(rawMb * 1024 * 1024);
|
|
46
|
+
return 64 * 1024 * 1024;
|
|
47
|
+
})();
|
|
48
|
+
let RAW_CONTENT_CACHE_BYTES = 0;
|
|
49
|
+
|
|
50
|
+
function canonicalCachePath(p) {
|
|
51
|
+
const full = normalize(resolve(String(p || '')));
|
|
52
|
+
return process.platform === 'win32' ? full.toLowerCase() : full;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeCacheMetaPaths(values) {
|
|
56
|
+
if (!Array.isArray(values)) return [];
|
|
57
|
+
return Array.from(new Set(
|
|
58
|
+
values
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.map((v) => canonicalCachePath(v)),
|
|
61
|
+
));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function cachePathsOverlap(a, b) {
|
|
65
|
+
if (!a || !b) return false;
|
|
66
|
+
if (a === b) return true;
|
|
67
|
+
return a.startsWith(b.endsWith(sep) ? b : `${b}${sep}`)
|
|
68
|
+
|| b.startsWith(a.endsWith(sep) ? a : `${a}${sep}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function bumpPathMutationGeneration(path) {
|
|
72
|
+
const key = canonicalCachePath(path);
|
|
73
|
+
PATH_MUTATION_GENERATIONS.set(key, (PATH_MUTATION_GENERATIONS.get(key) || 0) + 1);
|
|
74
|
+
while (PATH_MUTATION_GENERATIONS.size > PATH_MUTATION_GENERATION_MAX_ENTRIES) {
|
|
75
|
+
const oldest = PATH_MUTATION_GENERATIONS.keys().next().value;
|
|
76
|
+
if (!oldest) break;
|
|
77
|
+
PATH_MUTATION_GENERATIONS.delete(oldest);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function getPathMutationGeneration(path) {
|
|
82
|
+
const key = canonicalCachePath(path);
|
|
83
|
+
let generation = PATH_MUTATION_GLOBAL_GENERATION;
|
|
84
|
+
for (const [changedPath, value] of PATH_MUTATION_GENERATIONS) {
|
|
85
|
+
if (cachePathsOverlap(key, changedPath)) generation += value;
|
|
86
|
+
}
|
|
87
|
+
return generation;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cacheEntryOverlapsPaths(entry, affectedPaths) {
|
|
91
|
+
const entryPaths = Array.isArray(entry?.paths) ? entry.paths : [];
|
|
92
|
+
const entryScopes = Array.isArray(entry?.scopes) ? entry.scopes : [];
|
|
93
|
+
for (const affected of affectedPaths) {
|
|
94
|
+
for (const p of entryPaths) {
|
|
95
|
+
if (cachePathsOverlap(p, affected)) return true;
|
|
96
|
+
}
|
|
97
|
+
for (const scope of entryScopes) {
|
|
98
|
+
if (cachePathsOverlap(scope, affected)) return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function cacheGetEntry(key) {
|
|
105
|
+
const entry = RESULT_CACHE.get(key);
|
|
106
|
+
if (!entry) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
if (Date.now() - entry.ts > RESULT_CACHE_TTL_MS) {
|
|
110
|
+
resultCacheDelete(key);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return entry;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function cacheGet(key) {
|
|
117
|
+
return cacheGetEntry(key)?.value ?? null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function cacheSet(key, value, meta = {}) {
|
|
121
|
+
// Replace-in-place: clear old entry's byte accounting before write.
|
|
122
|
+
if (RESULT_CACHE.has(key)) resultCacheDelete(key);
|
|
123
|
+
const bytes = estimateResultBytes(value);
|
|
124
|
+
RESULT_CACHE.set(key, {
|
|
125
|
+
ts: Date.now(),
|
|
126
|
+
value,
|
|
127
|
+
paths: normalizeCacheMetaPaths(meta.paths),
|
|
128
|
+
scopes: normalizeCacheMetaPaths(meta.scopes),
|
|
129
|
+
readSnapshotMeta: meta.readSnapshotMeta || null,
|
|
130
|
+
contentPrefixHash: meta.contentPrefixHash || '',
|
|
131
|
+
bytes,
|
|
132
|
+
});
|
|
133
|
+
RESULT_CACHE_BYTES += bytes;
|
|
134
|
+
// Evict oldest (insertion-order) entries until under both the entry
|
|
135
|
+
// count cap and the byte budget. Mirrors RAW_CONTENT_CACHE shape.
|
|
136
|
+
while (RESULT_CACHE.size > RESULT_CACHE_MAX_ENTRIES || RESULT_CACHE_BYTES > RESULT_CACHE_MAX_BYTES) {
|
|
137
|
+
const oldest = RESULT_CACHE.keys().next().value;
|
|
138
|
+
if (!oldest || oldest === key) break;
|
|
139
|
+
resultCacheDelete(oldest);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function rawContentCacheDelete(key) {
|
|
144
|
+
const entry = RAW_CONTENT_CACHE.get(key);
|
|
145
|
+
if (entry?.rawBuf) RAW_CONTENT_CACHE_BYTES = Math.max(0, RAW_CONTENT_CACHE_BYTES - entry.rawBuf.length);
|
|
146
|
+
RAW_CONTENT_CACHE.delete(key);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function rawContentCacheGet(fullPath, stat, now = Date.now()) {
|
|
150
|
+
if (!fullPath || !stat) return null;
|
|
151
|
+
const key = canonicalCachePath(fullPath);
|
|
152
|
+
const entry = RAW_CONTENT_CACHE.get(key);
|
|
153
|
+
if (!entry) return null;
|
|
154
|
+
if (now - entry.ts > RAW_CONTENT_CACHE_TTL_MS) {
|
|
155
|
+
rawContentCacheDelete(key);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (entry.size !== stat.size
|
|
159
|
+
|| Math.abs(entry.mtimeMs - stat.mtimeMs) > 1
|
|
160
|
+
|| Math.abs(entry.ctimeMs - stat.ctimeMs) > 1) {
|
|
161
|
+
rawContentCacheDelete(key);
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
RAW_CONTENT_CACHE.delete(key);
|
|
165
|
+
RAW_CONTENT_CACHE.set(key, entry);
|
|
166
|
+
return entry.rawBuf;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function rawContentCacheSet(fullPath, stat, rawBuf, now = Date.now()) {
|
|
170
|
+
if (!fullPath || !stat || !Buffer.isBuffer(rawBuf)) return;
|
|
171
|
+
if (rawBuf.length > RAW_CONTENT_CACHE_MAX_BYTES) return;
|
|
172
|
+
const key = canonicalCachePath(fullPath);
|
|
173
|
+
rawContentCacheDelete(key);
|
|
174
|
+
RAW_CONTENT_CACHE.set(key, {
|
|
175
|
+
ts: now,
|
|
176
|
+
mtimeMs: stat.mtimeMs,
|
|
177
|
+
ctimeMs: stat.ctimeMs,
|
|
178
|
+
size: stat.size,
|
|
179
|
+
rawBuf,
|
|
180
|
+
});
|
|
181
|
+
RAW_CONTENT_CACHE_BYTES += rawBuf.length;
|
|
182
|
+
while (RAW_CONTENT_CACHE.size > RAW_CONTENT_CACHE_MAX_ENTRIES || RAW_CONTENT_CACHE_BYTES > RAW_CONTENT_CACHE_MAX_BYTES) {
|
|
183
|
+
const oldest = RAW_CONTENT_CACHE.keys().next().value;
|
|
184
|
+
if (!oldest) break;
|
|
185
|
+
rawContentCacheDelete(oldest);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export function seedRawContentCacheAfterWrite(fullPath, content, st = null) {
|
|
190
|
+
try {
|
|
191
|
+
const rawBuf = Buffer.isBuffer(content) ? content : Buffer.from(String(content ?? ''), 'utf-8');
|
|
192
|
+
const writtenStat = st && typeof st.size === 'number' ? st : statSync(fullPath);
|
|
193
|
+
rawContentCacheSet(fullPath, writtenStat, rawBuf);
|
|
194
|
+
return writtenStat;
|
|
195
|
+
} catch { return st || null; }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function statCacheGet(fullPath, now = Date.now()) {
|
|
199
|
+
const entry = STAT_CACHE.get(fullPath);
|
|
200
|
+
if (!entry) return null;
|
|
201
|
+
if (now - entry.ts > STAT_CACHE_TTL_MS) {
|
|
202
|
+
STAT_CACHE.delete(fullPath);
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return entry.stat;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function statCacheSet(fullPath, stat, now = Date.now()) {
|
|
209
|
+
if (STAT_CACHE.size >= STAT_CACHE_MAX_ENTRIES) {
|
|
210
|
+
const oldest = STAT_CACHE.keys().next().value;
|
|
211
|
+
if (oldest) STAT_CACHE.delete(oldest);
|
|
212
|
+
}
|
|
213
|
+
STAT_CACHE.set(fullPath, { ts: now, stat });
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export function getCachedReadOnlyStat(fullPath, loader = statSync, now = Date.now()) {
|
|
217
|
+
const cached = statCacheGet(fullPath, now);
|
|
218
|
+
if (cached) return cached;
|
|
219
|
+
const stat = loader(fullPath);
|
|
220
|
+
statCacheSet(fullPath, stat, now);
|
|
221
|
+
return stat;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function statPathsForMtime(paths, workDir, concurrency = 64, opts = {}) {
|
|
225
|
+
const items = Array.isArray(paths) ? paths : [];
|
|
226
|
+
const out = new Array(items.length);
|
|
227
|
+
const now = Date.now();
|
|
228
|
+
const inflight = new Map();
|
|
229
|
+
let next = 0;
|
|
230
|
+
// Hard per-stat deadline (0 = disabled, legacy behaviour). A hung stat
|
|
231
|
+
// (dead mount / unresponsive network path) must not pin a worker forever;
|
|
232
|
+
// on expiry the entry resolves to null (stat-failed) so glob's post-rg stat
|
|
233
|
+
// phase is bounded instead of running to the 600s bridge watchdog.
|
|
234
|
+
const deadlineMs = Number(opts.deadlineMs) > 0 ? Number(opts.deadlineMs) : 0;
|
|
235
|
+
// Injectable stat impl for testing hung-FS behaviour deterministically.
|
|
236
|
+
const statImpl = typeof opts._statImpl === 'function' ? opts._statImpl : fsPromises.stat;
|
|
237
|
+
|
|
238
|
+
async function resolveStat(full) {
|
|
239
|
+
let pending = inflight.get(full);
|
|
240
|
+
if (!pending) {
|
|
241
|
+
const statBase = statImpl(full)
|
|
242
|
+
.then((stat) => {
|
|
243
|
+
statCacheSet(full, stat, now);
|
|
244
|
+
return stat;
|
|
245
|
+
})
|
|
246
|
+
.catch(() => null);
|
|
247
|
+
let base = statBase;
|
|
248
|
+
if (deadlineMs > 0) {
|
|
249
|
+
// ref timer (not unref): the deadline MUST fire even when the
|
|
250
|
+
// hung stat is the only pending work — that is exactly the case
|
|
251
|
+
// we bound. clearTimeout on the normal path keeps a fast stat
|
|
252
|
+
// from holding the loop for the full deadline window.
|
|
253
|
+
base = new Promise((resolve) => {
|
|
254
|
+
let settled = false;
|
|
255
|
+
const timer = setTimeout(() => { if (!settled) { settled = true; resolve(null); } }, deadlineMs);
|
|
256
|
+
statBase.then((v) => { if (!settled) { settled = true; clearTimeout(timer); resolve(v); } });
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
pending = base;
|
|
260
|
+
inflight.set(full, pending);
|
|
261
|
+
}
|
|
262
|
+
return pending;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function worker() {
|
|
266
|
+
while (true) {
|
|
267
|
+
const index = next++;
|
|
268
|
+
if (index >= items.length) return;
|
|
269
|
+
const p = items[index];
|
|
270
|
+
const full = isAbsolute(p) ? p : resolveAgainstCwd(p, workDir);
|
|
271
|
+
try {
|
|
272
|
+
const stat = await resolveStat(full);
|
|
273
|
+
if (!stat) throw new Error('stat failed');
|
|
274
|
+
out[index] = { path: p, full, stat, size: stat.size, mtime: stat.mtimeMs, mtimeMs: stat.mtimeMs };
|
|
275
|
+
} catch {
|
|
276
|
+
out[index] = { path: p, full, stat: null, size: 0, mtime: 0, mtimeMs: 0 };
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const workerCount = Math.min(Math.max(1, concurrency), Math.max(1, items.length));
|
|
281
|
+
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
282
|
+
return out;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// lstat variant — does NOT follow symlinks. Use for directory-listing
|
|
286
|
+
// surfaces where a symlink to a 200 GB file should report as a symlink,
|
|
287
|
+
// not as the target's size/mtime.
|
|
288
|
+
export async function lstatPathsForMtime(paths, workDir, concurrency = 64, opts = {}) {
|
|
289
|
+
const items = Array.isArray(paths) ? paths : [];
|
|
290
|
+
const out = new Array(items.length);
|
|
291
|
+
const inflight = new Map();
|
|
292
|
+
let next = 0;
|
|
293
|
+
// Hard per-lstat deadline (0 = disabled, legacy behaviour). A hung lstat
|
|
294
|
+
// (dead mount / unresponsive network path) must not pin a worker forever;
|
|
295
|
+
// on expiry the entry resolves to null (stat-failed) so list's stat phase
|
|
296
|
+
// is bounded instead of running to the 600s bridge watchdog.
|
|
297
|
+
const deadlineMs = Number(opts.deadlineMs) > 0 ? Number(opts.deadlineMs) : 0;
|
|
298
|
+
// Injectable lstat impl for testing hung-FS behaviour deterministically.
|
|
299
|
+
const lstatImpl = typeof opts._lstatImpl === 'function' ? opts._lstatImpl : fsPromises.lstat;
|
|
300
|
+
|
|
301
|
+
async function resolveLstat(full) {
|
|
302
|
+
let pending = inflight.get(full);
|
|
303
|
+
if (!pending) {
|
|
304
|
+
const lstatBase = lstatImpl(full).catch(() => null);
|
|
305
|
+
let base = lstatBase;
|
|
306
|
+
if (deadlineMs > 0) {
|
|
307
|
+
// ref timer (not unref): the deadline MUST fire even when the
|
|
308
|
+
// hung lstat is the only pending work — that is exactly the case
|
|
309
|
+
// we bound. clearTimeout on the normal path keeps a fast lstat
|
|
310
|
+
// from holding the loop for the full deadline window.
|
|
311
|
+
base = new Promise((resolve) => {
|
|
312
|
+
let settled = false;
|
|
313
|
+
const timer = setTimeout(() => { if (!settled) { settled = true; resolve(null); } }, deadlineMs);
|
|
314
|
+
lstatBase.then((v) => { if (!settled) { settled = true; clearTimeout(timer); resolve(v); } });
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
pending = base;
|
|
318
|
+
inflight.set(full, pending);
|
|
319
|
+
}
|
|
320
|
+
return pending;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function worker() {
|
|
324
|
+
while (true) {
|
|
325
|
+
const index = next++;
|
|
326
|
+
if (index >= items.length) return;
|
|
327
|
+
const p = items[index];
|
|
328
|
+
const full = isAbsolute(p) ? p : resolveAgainstCwd(p, workDir);
|
|
329
|
+
try {
|
|
330
|
+
const stat = await resolveLstat(full);
|
|
331
|
+
if (!stat) throw new Error('lstat failed');
|
|
332
|
+
out[index] = { path: p, full, stat, size: stat.size, mtime: stat.mtimeMs, mtimeMs: stat.mtimeMs };
|
|
333
|
+
} catch {
|
|
334
|
+
out[index] = { path: p, full, stat: null, size: 0, mtime: 0, mtimeMs: 0 };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
const workerCount = Math.min(Math.max(1, concurrency), Math.max(1, items.length));
|
|
339
|
+
await Promise.all(Array.from({ length: workerCount }, worker));
|
|
340
|
+
return out;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function cacheInvalidateAll() {
|
|
344
|
+
RESULT_CACHE.clear();
|
|
345
|
+
RESULT_CACHE_BYTES = 0;
|
|
346
|
+
STAT_CACHE.clear();
|
|
347
|
+
RAW_CONTENT_CACHE.clear();
|
|
348
|
+
RAW_CONTENT_CACHE_BYTES = 0;
|
|
349
|
+
PATH_MUTATION_GENERATIONS.clear();
|
|
350
|
+
PATH_MUTATION_GLOBAL_GENERATION += 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function cacheInvalidatePaths(paths) {
|
|
354
|
+
const affectedPaths = normalizeCacheMetaPaths(Array.isArray(paths) ? paths : [paths]);
|
|
355
|
+
if (affectedPaths.length === 0) {
|
|
356
|
+
cacheInvalidateAll();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
for (const [key, entry] of RESULT_CACHE) {
|
|
360
|
+
if (cacheEntryOverlapsPaths(entry, affectedPaths)) {
|
|
361
|
+
resultCacheDelete(key);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
for (const key of [...STAT_CACHE.keys()]) {
|
|
365
|
+
if (affectedPaths.some((affected) => cachePathsOverlap(canonicalCachePath(key), affected))) {
|
|
366
|
+
STAT_CACHE.delete(key);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
for (const key of [...RAW_CONTENT_CACHE.keys()]) {
|
|
370
|
+
if (affectedPaths.some((affected) => cachePathsOverlap(key, affected))) {
|
|
371
|
+
rawContentCacheDelete(key);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
for (const affected of affectedPaths) {
|
|
375
|
+
deleteReadRangeIndexForPath(affected);
|
|
376
|
+
bumpPathMutationGeneration(affected);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function invalidateBuiltinResultCache(paths = null) {
|
|
381
|
+
if (Array.isArray(paths) ? paths.length > 0 : Boolean(paths)) {
|
|
382
|
+
cacheInvalidatePaths(paths);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
cacheInvalidateAll();
|
|
386
|
+
}
|