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,171 @@
|
|
|
1
|
+
// Cross-process advisory lock for write/edit operations. Uses a sibling
|
|
2
|
+
// lockfile written exclusively (`wx`) and a process.kill(pid, 0) liveness
|
|
3
|
+
// check to clean up after crashed holders. Pair with the in-process
|
|
4
|
+
// withPathLock for the same target: in-process serialises async callers
|
|
5
|
+
// in this Node, advisory lock serialises across Node processes.
|
|
6
|
+
import { openSync, closeSync, writeSync, readFileSync, unlinkSync, mkdirSync } from 'fs';
|
|
7
|
+
import { dirname, basename, join } from 'path';
|
|
8
|
+
import { randomBytes } from 'crypto';
|
|
9
|
+
|
|
10
|
+
function lockFileFor(targetPath) {
|
|
11
|
+
return join(dirname(targetPath), `.${basename(targetPath)}.mixdog-lock`);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isPidAlive(pid) {
|
|
15
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
16
|
+
// Triple-check: process.kill(pid, 0) can return transient ESRCH under
|
|
17
|
+
// heavy multi-process spawn pressure on Windows even when the target
|
|
18
|
+
// process is actually alive. Any one of three rapid probes returning
|
|
19
|
+
// success (or EPERM = exists-but-no-access) classifies as alive. Three
|
|
20
|
+
// consecutive ESRCH replies classify as dead. Each probe is a single
|
|
21
|
+
// syscall (microseconds) so total cost is negligible vs the race risk.
|
|
22
|
+
for (let i = 0; i < 3; i += 1) {
|
|
23
|
+
try {
|
|
24
|
+
process.kill(pid, 0);
|
|
25
|
+
return true;
|
|
26
|
+
} catch (err) {
|
|
27
|
+
if (err && err.code === 'EPERM') return true;
|
|
28
|
+
// ESRCH or other → continue probing.
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readLockInfo(lockFile) {
|
|
35
|
+
try {
|
|
36
|
+
const raw = readFileSync(lockFile, 'utf-8').trim();
|
|
37
|
+
// Token format: `${pid}.${hex}`. Fall back to legacy bare-pid contents.
|
|
38
|
+
const dot = raw.indexOf('.');
|
|
39
|
+
if (dot > 0) {
|
|
40
|
+
const pid = parseInt(raw.slice(0, dot), 10);
|
|
41
|
+
return { pid: Number.isInteger(pid) ? pid : 0, token: raw };
|
|
42
|
+
}
|
|
43
|
+
const pid = parseInt(raw, 10);
|
|
44
|
+
return { pid: Number.isInteger(pid) ? pid : 0, token: raw };
|
|
45
|
+
} catch {
|
|
46
|
+
return { pid: 0, token: '' };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function unlinkIfOwned(lockFile, expectedToken) {
|
|
51
|
+
try {
|
|
52
|
+
const { token } = readLockInfo(lockFile);
|
|
53
|
+
if (token !== expectedToken) return false;
|
|
54
|
+
unlinkSync(lockFile);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function tryCreateLock(lockFile, token) {
|
|
62
|
+
let fd;
|
|
63
|
+
try {
|
|
64
|
+
fd = openSync(lockFile, 'wx');
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err && err.code === 'EEXIST') return false;
|
|
67
|
+
throw err;
|
|
68
|
+
}
|
|
69
|
+
// Once the lockfile exists on disk, any failure in writeSync/closeSync
|
|
70
|
+
// would leak the lockfile if we returned without cleanup.
|
|
71
|
+
try {
|
|
72
|
+
writeSync(fd, token);
|
|
73
|
+
closeSync(fd);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
try { closeSync(fd); } catch { /* fd may already be closed */ }
|
|
76
|
+
try { unlinkSync(lockFile); } catch { /* best-effort cleanup */ }
|
|
77
|
+
throw err;
|
|
78
|
+
}
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function acquireAdvisoryLock(targetPath, { timeoutMs = 5000, pollMs = 25 } = {}) {
|
|
83
|
+
const lockFile = lockFileFor(targetPath);
|
|
84
|
+
// Ensure parent directory exists before openSync('wx'); the lockfile lives
|
|
85
|
+
// next to the target, so writes to new files in missing parent dirs would
|
|
86
|
+
// otherwise fail with ENOENT here.
|
|
87
|
+
mkdirSync(dirname(lockFile), { recursive: true });
|
|
88
|
+
const token = `${process.pid}.${randomBytes(8).toString('hex')}`;
|
|
89
|
+
const deadline = Date.now() + timeoutMs;
|
|
90
|
+
for (;;) {
|
|
91
|
+
if (tryCreateLock(lockFile, token)) {
|
|
92
|
+
return {
|
|
93
|
+
lockFile,
|
|
94
|
+
release() {
|
|
95
|
+
// Verify ownership token before unlinking to avoid
|
|
96
|
+
// unlinking another process's live lock after a stale
|
|
97
|
+
// cleanup race.
|
|
98
|
+
unlinkIfOwned(lockFile, token);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Lock exists — check liveness for stale cleanup.
|
|
103
|
+
const { pid: holderPid, token: holderToken } = readLockInfo(lockFile);
|
|
104
|
+
if (!isPidAlive(holderPid)) {
|
|
105
|
+
// DOUBLE-CHECK: under heavy multi-process load, process.kill(pid,0)
|
|
106
|
+
// can return false-negative (transient ESRCH) for an active
|
|
107
|
+
// holder, causing this branch to unlink an in-use lock and break
|
|
108
|
+
// mutual exclusion. Verified empirically by s13 stress test —
|
|
109
|
+
// two workers were observed holding the same lock for 119ms.
|
|
110
|
+
//
|
|
111
|
+
// Mitigation: re-read the lockfile after a short delay; if pid
|
|
112
|
+
// or token changed, another contender already handled cleanup
|
|
113
|
+
// and we should re-poll. Otherwise re-test liveness; only when
|
|
114
|
+
// BOTH checks agree the holder is dead do we cleanup. The
|
|
115
|
+
// sleep also absorbs the OS-level race window where the
|
|
116
|
+
// original kill(0) returned ESRCH before the holder fully
|
|
117
|
+
// exited the spawn/registration window.
|
|
118
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
119
|
+
const second = readLockInfo(lockFile);
|
|
120
|
+
if (second.pid !== holderPid || second.token !== holderToken) {
|
|
121
|
+
// Another contender already touched the lock; resync.
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!isPidAlive(holderPid)) {
|
|
125
|
+
unlinkIfOwned(lockFile, holderToken);
|
|
126
|
+
// Small jitter to avoid avalanche of contenders racing into
|
|
127
|
+
// tryCreateLock simultaneously after the unlink.
|
|
128
|
+
await new Promise((r) => setTimeout(r, Math.floor(Math.random() * 5) + 1));
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (Date.now() >= deadline) {
|
|
133
|
+
const err = new Error(`advisory lock timeout: ${lockFile} held by pid ${holderPid}`);
|
|
134
|
+
err.code = 'EAGAIN';
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
await new Promise((r) => setTimeout(r, pollMs));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function withAdvisoryLocks(paths, fn) {
|
|
142
|
+
const seen = new Set();
|
|
143
|
+
const ordered = [];
|
|
144
|
+
for (const p of Array.isArray(paths) ? paths : [paths]) {
|
|
145
|
+
if (!p) continue;
|
|
146
|
+
const key = process.platform === 'win32' ? String(p).toLowerCase() : String(p);
|
|
147
|
+
if (seen.has(key)) continue;
|
|
148
|
+
seen.add(key);
|
|
149
|
+
ordered.push(String(p));
|
|
150
|
+
}
|
|
151
|
+
// Acquire in a casing-canonical order so two callers locking the same set
|
|
152
|
+
// with different path casing on Windows cannot acquire in opposite orders
|
|
153
|
+
// and deadlock. (The dedup above already keys on the canonical form.)
|
|
154
|
+
const _lockSortKey = (s) => (process.platform === 'win32' ? s.toLowerCase() : s);
|
|
155
|
+
ordered.sort((a, b) => {
|
|
156
|
+
const ka = _lockSortKey(a), kb = _lockSortKey(b);
|
|
157
|
+
return ka < kb ? -1 : ka > kb ? 1 : 0;
|
|
158
|
+
});
|
|
159
|
+
const acquired = [];
|
|
160
|
+
try {
|
|
161
|
+
for (const p of ordered) {
|
|
162
|
+
acquired.push(await acquireAdvisoryLock(p));
|
|
163
|
+
}
|
|
164
|
+
return await fn();
|
|
165
|
+
} finally {
|
|
166
|
+
// Release in reverse acquisition order.
|
|
167
|
+
for (let i = acquired.length - 1; i >= 0; i -= 1) {
|
|
168
|
+
try { acquired[i].release(); } catch { /* best-effort */ }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
// Lightweight argument shape validator for builtin tools.
|
|
2
|
+
//
|
|
3
|
+
// Goal: catch obvious schema violations (wrong types, missing required
|
|
4
|
+
// fields, out-of-range numeric clamps) BEFORE the underlying executor
|
|
5
|
+
// touches them and crashes the MCP child. Returns null on success, or
|
|
6
|
+
// a clean human-readable error string on failure (never throws).
|
|
7
|
+
//
|
|
8
|
+
// Validation operates on the alias family, not the canonical key, so
|
|
9
|
+
// callers may pass either spelling (e.g. `glob` alias for grep, or
|
|
10
|
+
// `file_path` alias for read.path).
|
|
11
|
+
|
|
12
|
+
const MAX_INT = 100000;
|
|
13
|
+
export const GREP_CONTEXT_MAX = 12;
|
|
14
|
+
|
|
15
|
+
const GREP_CONTEXT_KEY_GROUPS = [
|
|
16
|
+
['-A', ['-A', 'A', 'after', 'after_context', 'afterContext']],
|
|
17
|
+
['-B', ['-B', 'B', 'before', 'before_context', 'beforeContext']],
|
|
18
|
+
['-C', ['-C', 'C', 'context', 'context_lines', 'contextLines']],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
function grepContextKeyPresent(a, k) {
|
|
22
|
+
return a && Object.prototype.hasOwnProperty.call(a, k)
|
|
23
|
+
&& a[k] !== undefined && a[k] !== null && a[k] !== '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function firstGrepContextArg(args, keys) {
|
|
27
|
+
for (const k of keys) {
|
|
28
|
+
if (grepContextKeyPresent(args, k)) return { key: k, value: args[k] };
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function firstNonZeroGrepContextArg(args, keys) {
|
|
34
|
+
for (const k of keys) {
|
|
35
|
+
if (grepContextKeyPresent(args, k) && !isGrepContextZero(args[k])) return { key: k, value: args[k] };
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isGrepContextZero(value) {
|
|
41
|
+
const n = Number(value);
|
|
42
|
+
return Number.isFinite(n) && n === 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function clearGrepContextKeys(args, keys) {
|
|
46
|
+
for (const k of keys) delete args[k];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Lead-facing grep context: canonicalize aliases and clamp explicit values (overrides still apply). */
|
|
50
|
+
export function applyGrepContextLeadPolicy(args) {
|
|
51
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) return;
|
|
52
|
+
for (const [canonical, keys] of GREP_CONTEXT_KEY_GROUPS) {
|
|
53
|
+
const found = firstNonZeroGrepContextArg(args, keys) || firstGrepContextArg(args, keys);
|
|
54
|
+
if (!found) continue;
|
|
55
|
+
const n = Number(found.value);
|
|
56
|
+
const shaped = Number.isFinite(n) && n > GREP_CONTEXT_MAX ? GREP_CONTEXT_MAX : found.value;
|
|
57
|
+
clearGrepContextKeys(args, keys);
|
|
58
|
+
args[canonical] = shaped;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isString(v) {
|
|
63
|
+
return typeof v === 'string';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isNonEmptyString(v) {
|
|
67
|
+
return typeof v === 'string' && v.length > 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isStringOrStringArray(v) {
|
|
71
|
+
if (typeof v === 'string') return true;
|
|
72
|
+
if (!Array.isArray(v) || v.length === 0) return false;
|
|
73
|
+
for (const x of v) {
|
|
74
|
+
if (typeof x !== 'string') return false;
|
|
75
|
+
}
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isFiniteInt(v) {
|
|
80
|
+
return typeof v === 'number' && Number.isFinite(v) && Math.floor(v) === v;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function checkIntInRange(field, value, min, max) {
|
|
84
|
+
if (value === undefined || value === null) return null;
|
|
85
|
+
if (!isFiniteInt(value)) {
|
|
86
|
+
return `Error: builtin arg "${field}" must be a finite integer (got ${describeType(value)})`;
|
|
87
|
+
}
|
|
88
|
+
if (value < min) {
|
|
89
|
+
return `Error: builtin arg "${field}" must be >= ${min} (got ${value})`;
|
|
90
|
+
}
|
|
91
|
+
if (value > max) {
|
|
92
|
+
return `Error: builtin arg "${field}" must be <= ${max} (got ${value})`;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function describeType(v) {
|
|
98
|
+
if (v === null) return 'null';
|
|
99
|
+
if (Array.isArray(v)) return 'array';
|
|
100
|
+
return typeof v;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function hasOwn(o, k) {
|
|
104
|
+
return o && Object.prototype.hasOwnProperty.call(o, k);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---- per-tool guards ----
|
|
108
|
+
|
|
109
|
+
function guardGrep(a) {
|
|
110
|
+
// pattern aliases
|
|
111
|
+
const patternKeys = ['pattern', 'query', 'regex', 'needle'];
|
|
112
|
+
// glob (file filter) aliases
|
|
113
|
+
const globKeys = ['glob', 'file_pattern', 'include', 'files'];
|
|
114
|
+
|
|
115
|
+
const hasPattern = patternKeys.some((k) => hasOwn(a, k));
|
|
116
|
+
const hasGlob = globKeys.some((k) => hasOwn(a, k));
|
|
117
|
+
if (!hasPattern && !hasGlob) {
|
|
118
|
+
return 'Error: grep requires pattern (or alias query/regex/needle) or glob.';
|
|
119
|
+
}
|
|
120
|
+
for (const k of patternKeys) {
|
|
121
|
+
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
122
|
+
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
for (const k of globKeys) {
|
|
126
|
+
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
127
|
+
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// path/root (optional, string or string[])
|
|
131
|
+
for (const k of ['path', 'root']) {
|
|
132
|
+
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
133
|
+
return `Error: grep arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// numeric clamps (grep context 0..12, mirrors read line-context tightening)
|
|
137
|
+
for (const k of ['head_limit', 'offset']) {
|
|
138
|
+
const err = checkIntInRange(k, a[k], 0, MAX_INT);
|
|
139
|
+
if (err) return err;
|
|
140
|
+
}
|
|
141
|
+
for (const k of ['-A', '-B', '-C', 'context']) {
|
|
142
|
+
const err = checkIntInRange(k, a[k], 0, GREP_CONTEXT_MAX);
|
|
143
|
+
if (err) return err;
|
|
144
|
+
}
|
|
145
|
+
// output_mode / mode enum
|
|
146
|
+
const modeKeys = ['output_mode', 'mode'];
|
|
147
|
+
const allowed = new Set(['files_with_matches', 'content', 'count']);
|
|
148
|
+
for (const k of modeKeys) {
|
|
149
|
+
if (hasOwn(a, k)) {
|
|
150
|
+
if (!isString(a[k]) || !allowed.has(a[k])) {
|
|
151
|
+
return `Error: grep arg "${k}" must be one of files_with_matches|content|count (got ${JSON.stringify(a[k])})`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function guardRead(a) {
|
|
159
|
+
// path / file_path alias OR path may itself be array
|
|
160
|
+
const hasPath = hasOwn(a, 'path') || hasOwn(a, 'file_path');
|
|
161
|
+
if (!hasPath) {
|
|
162
|
+
return 'Error: read requires "path" (or alias file_path).';
|
|
163
|
+
}
|
|
164
|
+
// path can be string | string[] | object[]; file_path is string
|
|
165
|
+
if (hasOwn(a, 'path')) {
|
|
166
|
+
const p = a.path;
|
|
167
|
+
const ok = typeof p === 'string' || (Array.isArray(p) && p.length > 0);
|
|
168
|
+
if (!ok) {
|
|
169
|
+
return `Error: read arg "path" must be string, string[], or object[] (got ${describeType(p)})`;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (hasOwn(a, 'file_path') && !isNonEmptyString(a.file_path)) {
|
|
173
|
+
return `Error: read arg "file_path" must be a non-empty string (got ${describeType(a.file_path)})`;
|
|
174
|
+
}
|
|
175
|
+
// offset >=0
|
|
176
|
+
{
|
|
177
|
+
const err = checkIntInRange('offset', a.offset, 0, MAX_INT);
|
|
178
|
+
if (err) return err;
|
|
179
|
+
}
|
|
180
|
+
// limit: >=1 = explicit cap; 0 = unlimited sentinel (read-formatting maps 0 to
|
|
181
|
+
// Infinity; read-batch coalescing forwards limit:0 for an unbounded union
|
|
182
|
+
// window). Allow 0 through rather than erroring — deleting/rejecting it would
|
|
183
|
+
// break that unbounded-batch contract. A placeholder limit:0 from a symbol
|
|
184
|
+
// read is stripped on the symbol path. Negatives still error.
|
|
185
|
+
{
|
|
186
|
+
const err = checkIntInRange('limit', a.limit, 0, MAX_INT);
|
|
187
|
+
if (err) return err;
|
|
188
|
+
}
|
|
189
|
+
// context 0..200
|
|
190
|
+
{
|
|
191
|
+
const err = checkIntInRange('context', a.context, 0, 200);
|
|
192
|
+
if (err) return err;
|
|
193
|
+
}
|
|
194
|
+
// n 0..10000 — accept 0 rather than erroring: the read-mode handlers coerce
|
|
195
|
+
// n<1 to their default (head/tail 20, summary 200), and when a window
|
|
196
|
+
// (offset/limit/line) is also present the glance mode is dropped entirely, so
|
|
197
|
+
// n is moot. Rejecting 0 only forced a wasted retry turn (the whole point of
|
|
198
|
+
// these reads is to land in one shot). Negatives remain a real error.
|
|
199
|
+
if (hasOwn(a, 'n') && a.n !== undefined && a.n !== null) {
|
|
200
|
+
const err = checkIntInRange('n', a.n, 0, 10000);
|
|
201
|
+
if (err) return err;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function guardEdit(a) {
|
|
207
|
+
const op = hasOwn(a, 'operation') ? a.operation : 'replace';
|
|
208
|
+
if (op !== undefined && op !== null) {
|
|
209
|
+
if (!isString(op) || (op !== 'replace' && op !== 'notebook' && op !== 'rename')) {
|
|
210
|
+
return `Error: edit arg "operation" must be one of replace|notebook|rename (got ${JSON.stringify(op)})`;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
if (op === 'notebook') {
|
|
214
|
+
return guardNotebookEdit(a);
|
|
215
|
+
}
|
|
216
|
+
if (op === 'rename') {
|
|
217
|
+
return guardRename(a);
|
|
218
|
+
}
|
|
219
|
+
const hasSingle = hasOwn(a, 'old_string') && hasOwn(a, 'new_string');
|
|
220
|
+
const hasBatch = hasOwn(a, 'edits') && Array.isArray(a.edits) && a.edits.length > 0;
|
|
221
|
+
if (hasSingle && hasBatch) {
|
|
222
|
+
return 'Error: edit requires either (old_string + new_string) OR edits[], not both';
|
|
223
|
+
}
|
|
224
|
+
if (!hasSingle && !hasBatch) {
|
|
225
|
+
if (hasOwn(a, 'edits') && !hasBatch) {
|
|
226
|
+
return 'Error: edit arg "edits" must be a non-empty array';
|
|
227
|
+
}
|
|
228
|
+
return 'Error: edit requires either (old_string + new_string) OR a non-empty edits[]';
|
|
229
|
+
}
|
|
230
|
+
if (hasSingle) {
|
|
231
|
+
if (typeof a.old_string !== 'string') {
|
|
232
|
+
return `Error: edit arg "old_string" must be a string (got ${describeType(a.old_string)})`;
|
|
233
|
+
}
|
|
234
|
+
if (typeof a.new_string !== 'string') {
|
|
235
|
+
return `Error: edit arg "new_string" must be a string (got ${describeType(a.new_string)})`;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function guardWrite(a) {
|
|
242
|
+
const hasSingle = (hasOwn(a, 'path') || hasOwn(a, 'file_path')) && hasOwn(a, 'content');
|
|
243
|
+
const hasBatch = hasOwn(a, 'writes') && Array.isArray(a.writes) && a.writes.length > 0;
|
|
244
|
+
if (hasSingle && hasBatch) {
|
|
245
|
+
return 'Error: write requires either (path + content) OR writes[], not both';
|
|
246
|
+
}
|
|
247
|
+
if (!hasSingle && !hasBatch) {
|
|
248
|
+
if (hasOwn(a, 'writes') && !hasBatch) {
|
|
249
|
+
return 'Error: write arg "writes" must be a non-empty array';
|
|
250
|
+
}
|
|
251
|
+
return 'Error: write requires either (path + content) OR a non-empty writes[]';
|
|
252
|
+
}
|
|
253
|
+
if (hasSingle) {
|
|
254
|
+
const p = hasOwn(a, 'path') ? a.path : a.file_path;
|
|
255
|
+
if (typeof p !== 'string' || !p) {
|
|
256
|
+
return `Error: write arg "path" must be a non-empty string (got ${describeType(p)})`;
|
|
257
|
+
}
|
|
258
|
+
if (typeof a.content !== 'string') {
|
|
259
|
+
return `Error: write arg "content" must be a string (got ${describeType(a.content)})`;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (hasBatch) {
|
|
263
|
+
for (let i = 0; i < a.writes.length; i++) {
|
|
264
|
+
const w = a.writes[i];
|
|
265
|
+
if (!w || typeof w !== 'object') {
|
|
266
|
+
return `Error: write writes[${i}] must be an object with path + content`;
|
|
267
|
+
}
|
|
268
|
+
if (typeof w.path !== 'string' || !w.path) {
|
|
269
|
+
return `Error: write writes[${i}].path must be a non-empty string (got ${describeType(w.path)})`;
|
|
270
|
+
}
|
|
271
|
+
if (typeof w.content !== 'string') {
|
|
272
|
+
return `Error: write writes[${i}].content must be a string (got ${describeType(w.content)})`;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function guardNotebookEdit(a) {
|
|
280
|
+
const hasPath = hasOwn(a, 'notebook_path') || hasOwn(a, 'path') || hasOwn(a, 'file_path');
|
|
281
|
+
if (!hasPath) {
|
|
282
|
+
return 'Error: notebook_edit requires "notebook_path" (or alias path/file_path).';
|
|
283
|
+
}
|
|
284
|
+
for (const k of ['notebook_path', 'path', 'file_path']) {
|
|
285
|
+
if (hasOwn(a, k) && !isNonEmptyString(a[k])) {
|
|
286
|
+
return `Error: notebook_edit arg "${k}" must be a non-empty string (got ${describeType(a[k])})`;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const editMode = hasOwn(a, 'edit_mode') ? a.edit_mode : 'replace';
|
|
290
|
+
if (editMode !== undefined && editMode !== null) {
|
|
291
|
+
const allowedModes = new Set(['replace', 'insert', 'delete']);
|
|
292
|
+
if (!isString(editMode) || !allowedModes.has(editMode)) {
|
|
293
|
+
return `Error: notebook_edit arg "edit_mode" must be one of replace|insert|delete (got ${JSON.stringify(editMode)})`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
if (hasOwn(a, 'cell_type') && a.cell_type !== undefined && a.cell_type !== null) {
|
|
297
|
+
if (!isString(a.cell_type) || (a.cell_type !== 'code' && a.cell_type !== 'markdown')) {
|
|
298
|
+
return `Error: notebook_edit arg "cell_type" must be code or markdown (got ${JSON.stringify(a.cell_type)})`;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (hasOwn(a, 'cell_id') && a.cell_id !== undefined && a.cell_id !== null && !isString(a.cell_id)) {
|
|
302
|
+
return `Error: notebook_edit arg "cell_id" must be a string (got ${describeType(a.cell_id)})`;
|
|
303
|
+
}
|
|
304
|
+
// new_source is required except for delete; the executor enforces the
|
|
305
|
+
// mode-specific requirement, but reject a non-string up front when present.
|
|
306
|
+
if (hasOwn(a, 'new_source') && a.new_source !== undefined && a.new_source !== null && typeof a.new_source !== 'string') {
|
|
307
|
+
return `Error: notebook_edit arg "new_source" must be a string (got ${describeType(a.new_source)})`;
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function guardDiagnostics(a) {
|
|
313
|
+
if (hasOwn(a, 'path') && a.path !== undefined && a.path !== null && !isNonEmptyString(a.path)) {
|
|
314
|
+
return `Error: diagnostics arg "path" must be a non-empty string (got ${describeType(a.path)})`;
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function guardRename(a) {
|
|
320
|
+
if (!hasOwn(a, 'symbol') || !isNonEmptyString(a.symbol)) {
|
|
321
|
+
return `Error: rename requires "symbol" (non-empty string) (got ${describeType(a.symbol)})`;
|
|
322
|
+
}
|
|
323
|
+
if (!hasOwn(a, 'new_name') || !isNonEmptyString(a.new_name)) {
|
|
324
|
+
return `Error: rename requires "new_name" (non-empty string) (got ${describeType(a.new_name)})`;
|
|
325
|
+
}
|
|
326
|
+
if (hasOwn(a, 'file') && a.file !== undefined && a.file !== null && !isNonEmptyString(a.file)) {
|
|
327
|
+
return `Error: rename arg "file" must be a non-empty string (got ${describeType(a.file)})`;
|
|
328
|
+
}
|
|
329
|
+
if (hasOwn(a, 'apply') && a.apply !== undefined && a.apply !== null && typeof a.apply !== 'boolean') {
|
|
330
|
+
return `Error: rename arg "apply" must be a boolean (got ${describeType(a.apply)})`;
|
|
331
|
+
}
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function guardBash(a) {
|
|
336
|
+
if (!hasOwn(a, 'command')) {
|
|
337
|
+
return 'Error: bash requires "command"';
|
|
338
|
+
}
|
|
339
|
+
if (typeof a.command !== 'string') {
|
|
340
|
+
return `Error: bash arg "command" must be a string (got ${describeType(a.command)})`;
|
|
341
|
+
}
|
|
342
|
+
if (a.command.length === 0) {
|
|
343
|
+
return 'Error: bash arg "command" must be a non-empty string';
|
|
344
|
+
}
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function guardJobWait(a) {
|
|
349
|
+
if (!hasOwn(a, 'job_id')) {
|
|
350
|
+
return 'Error: job_wait requires "job_id"';
|
|
351
|
+
}
|
|
352
|
+
if (typeof a.job_id !== 'string') {
|
|
353
|
+
return `Error: job_wait arg "job_id" must be a string (got ${describeType(a.job_id)})`;
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function guardList(a) {
|
|
359
|
+
if (hasOwn(a, 'path') && !isStringOrStringArray(a.path)) {
|
|
360
|
+
return `Error: list arg "path" must be string or string[] (got ${describeType(a.path)})`;
|
|
361
|
+
}
|
|
362
|
+
if (hasOwn(a, 'pattern') && !isStringOrStringArray(a.pattern)) {
|
|
363
|
+
return `Error: list arg "pattern" must be string or string[] (got ${describeType(a.pattern)})`;
|
|
364
|
+
}
|
|
365
|
+
if (hasOwn(a, 'head_limit') && a.head_limit !== undefined && a.head_limit !== null) {
|
|
366
|
+
if (!isFiniteInt(a.head_limit)) {
|
|
367
|
+
return `Error: list arg "head_limit" must be a finite integer (got ${describeType(a.head_limit)})`;
|
|
368
|
+
}
|
|
369
|
+
// 0 is the valid "no cap" sentinel; a negative value is nonsensical
|
|
370
|
+
// and downstream produces a degenerate window (clamps to 0 → empty).
|
|
371
|
+
if (a.head_limit < 0) {
|
|
372
|
+
return `Error: list arg "head_limit" must be >= 0 (0 means no cap); got ${a.head_limit}`;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function guardGlob(a) {
|
|
379
|
+
// path alias root; pattern aliases glob/name/file_pattern
|
|
380
|
+
for (const k of ['path', 'root']) {
|
|
381
|
+
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
382
|
+
return `Error: glob arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
for (const k of ['pattern', 'glob', 'name', 'file_pattern']) {
|
|
386
|
+
if (hasOwn(a, k) && !isStringOrStringArray(a[k])) {
|
|
387
|
+
return `Error: glob arg "${k}" must be string or string[] (got ${describeType(a[k])})`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (hasOwn(a, 'head_limit') && a.head_limit !== undefined && a.head_limit !== null) {
|
|
391
|
+
if (!isFiniteInt(a.head_limit)) {
|
|
392
|
+
return `Error: glob arg "head_limit" must be a finite integer (got ${describeType(a.head_limit)})`;
|
|
393
|
+
}
|
|
394
|
+
// 0 is the valid "no cap" sentinel; a negative value is nonsensical
|
|
395
|
+
// and downstream produces a degenerate window (clamps to 0 → empty).
|
|
396
|
+
if (a.head_limit < 0) {
|
|
397
|
+
return `Error: glob arg "head_limit" must be >= 0 (0 means no cap); got ${a.head_limit}`;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return null;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Valid code_graph modes — mirrors the enum in code-graph-tool-defs.mjs.
|
|
404
|
+
// Covers the work the removed standalone find_* tools used to do:
|
|
405
|
+
// find_symbol, references, callers, imports, dependents.
|
|
406
|
+
const CODE_GRAPH_MODES = new Set([
|
|
407
|
+
'overview', 'imports', 'dependents', 'related', 'impact',
|
|
408
|
+
'symbols', 'find_symbol', 'search', 'references', 'callers', 'callees', 'prewarm',
|
|
409
|
+
]);
|
|
410
|
+
|
|
411
|
+
function guardCodeGraph(a) {
|
|
412
|
+
if (!hasOwn(a, 'mode') || a.mode === undefined || a.mode === null) {
|
|
413
|
+
return 'Error: code_graph requires "mode"';
|
|
414
|
+
}
|
|
415
|
+
if (!isString(a.mode)) {
|
|
416
|
+
return `Error: code_graph arg "mode" must be a string (got ${describeType(a.mode)})`;
|
|
417
|
+
}
|
|
418
|
+
const mode = a.mode.trim();
|
|
419
|
+
if (!CODE_GRAPH_MODES.has(mode)) {
|
|
420
|
+
return `Error: code_graph arg "mode" must be one of ${[...CODE_GRAPH_MODES].join('|')} (got ${JSON.stringify(a.mode)})`;
|
|
421
|
+
}
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const GUARDS = {
|
|
426
|
+
grep: guardGrep,
|
|
427
|
+
read: guardRead,
|
|
428
|
+
edit: guardEdit,
|
|
429
|
+
write: guardWrite,
|
|
430
|
+
diagnostics: guardDiagnostics,
|
|
431
|
+
bash: guardBash,
|
|
432
|
+
job_wait: guardJobWait,
|
|
433
|
+
list: guardList,
|
|
434
|
+
glob: guardGlob,
|
|
435
|
+
code_graph: guardCodeGraph,
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
export function validateBuiltinArgs(toolName, args) {
|
|
439
|
+
const guard = GUARDS[toolName];
|
|
440
|
+
if (!guard) return null;
|
|
441
|
+
if (args === null || args === undefined) {
|
|
442
|
+
return `Error: ${toolName} requires arguments object (got ${describeType(args)})`;
|
|
443
|
+
}
|
|
444
|
+
if (typeof args !== 'object' || Array.isArray(args)) {
|
|
445
|
+
return `Error: ${toolName} arguments must be an object (got ${describeType(args)})`;
|
|
446
|
+
}
|
|
447
|
+
if (toolName === 'grep') applyGrepContextLeadPolicy(args);
|
|
448
|
+
try {
|
|
449
|
+
return guard(args) || null;
|
|
450
|
+
} catch (err) {
|
|
451
|
+
return `Error: ${toolName} argument validation failed: ${err && err.message ? err.message : String(err)}`;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
export default validateBuiltinArgs;
|