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,168 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import {
|
|
3
|
+
diagnoseFoldTierAmbiguity,
|
|
4
|
+
findActualString,
|
|
5
|
+
formatStageInline,
|
|
6
|
+
} from '../edit-normalize.mjs';
|
|
7
|
+
import { assertEditTargetUtf8 } from './edit-utf8-guard.mjs';
|
|
8
|
+
import { hashText } from './hash-utils.mjs';
|
|
9
|
+
import { normalizeOutputPath } from './path-utils.mjs';
|
|
10
|
+
import { recordReadSnapshot } from './read-snapshot-runtime.mjs';
|
|
11
|
+
import {
|
|
12
|
+
countLiteralOccurrences,
|
|
13
|
+
findCrlfNormalisedMatches,
|
|
14
|
+
findLiteralOccurrenceState,
|
|
15
|
+
formatMatchLines,
|
|
16
|
+
occurrenceLinesCrlf,
|
|
17
|
+
occurrenceLinesPlain,
|
|
18
|
+
} from './edit-match-utils.mjs';
|
|
19
|
+
import { buildStaleEditRecovery } from './edit-failure-context.mjs';
|
|
20
|
+
|
|
21
|
+
function normalizeOldStringEntries(oldStrings) {
|
|
22
|
+
const out = [];
|
|
23
|
+
const src = Array.isArray(oldStrings) ? oldStrings : [];
|
|
24
|
+
for (let i = 0; i < src.length; i++) {
|
|
25
|
+
const entry = src[i];
|
|
26
|
+
if (typeof entry === 'string') {
|
|
27
|
+
out.push({ old_string: entry, replace_all: false, label: `edit ${i}` });
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (!entry || typeof entry.old_string !== 'string') continue;
|
|
31
|
+
out.push({
|
|
32
|
+
old_string: entry.old_string,
|
|
33
|
+
replace_all: entry.replace_all === true,
|
|
34
|
+
label: entry.label || `edit ${i}`,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function foldTierAmbiguityError(content, oldString, filePath, editPrefix = '') {
|
|
41
|
+
const amb = diagnoseFoldTierAmbiguity(content, oldString);
|
|
42
|
+
if (!amb || amb.count <= 1) return null;
|
|
43
|
+
const stageNote = formatStageInline(amb.stage);
|
|
44
|
+
return `Error [code 9]: ${editPrefix}old_string found ${amb.count} times in ${filePath}${stageNote};${formatMatchLines(amb.lines, amb.count)} set replace_all:true or provide more unique context`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function oldStringMatchableOnCurrent(content, oldStr, replaceAll, { filePath, editPrefix }) {
|
|
48
|
+
const literal = findLiteralOccurrenceState(content, oldStr);
|
|
49
|
+
if (literal.count === 1) return { ok: true };
|
|
50
|
+
if (literal.count > 1) {
|
|
51
|
+
if (replaceAll) return { ok: true };
|
|
52
|
+
const count = countLiteralOccurrences(content, oldStr);
|
|
53
|
+
return {
|
|
54
|
+
ok: false,
|
|
55
|
+
error: `Error [code 9]: ${editPrefix}old_string found ${count} times in ${filePath} (exact);${formatMatchLines(occurrenceLinesPlain(content, oldStr), count)} set replace_all:true or provide more unique context`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const matchInfo = {};
|
|
60
|
+
const matched = findActualString(content, oldStr, matchInfo) || null;
|
|
61
|
+
if (matched) {
|
|
62
|
+
const occ = findLiteralOccurrenceState(content, matched);
|
|
63
|
+
if (occ.count === 1 || replaceAll) return { ok: true };
|
|
64
|
+
const count = countLiteralOccurrences(content, matched);
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: `Error [code 9]: ${editPrefix}old_string found ${count} times in ${filePath}${formatStageInline(matchInfo.stage)};${formatMatchLines(occurrenceLinesPlain(content, matched), count)} set replace_all:true or provide more unique context`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const crlfMatch = findCrlfNormalisedMatches(content, oldStr);
|
|
72
|
+
const crlfCount = crlfMatch ? crlfMatch.ranges.length : 0;
|
|
73
|
+
if (crlfCount === 1 || (crlfCount > 1 && replaceAll)) return { ok: true };
|
|
74
|
+
if (crlfCount > 1) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: `Error [code 9]: ${editPrefix}old_string found ${crlfCount} times in ${filePath} (via crlf-fold);${formatMatchLines(occurrenceLinesCrlf(content, crlfMatch.ranges), crlfCount)} set replace_all:true or provide more unique context`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const foldAmb = foldTierAmbiguityError(content, oldStr, filePath, editPrefix);
|
|
82
|
+
if (foldAmb) return { ok: false, error: foldAmb };
|
|
83
|
+
|
|
84
|
+
return { ok: false, notFound: true };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* When a read snapshot is stale and bytes no longer match the snapshot hash,
|
|
89
|
+
* re-load current disk and allow the edit only if every old_string is
|
|
90
|
+
* matchable on current content (unique unless replace_all). On success,
|
|
91
|
+
* refresh the read snapshot to current bytes.
|
|
92
|
+
*/
|
|
93
|
+
export function attemptStaleEditAutoRefresh({
|
|
94
|
+
fullPath,
|
|
95
|
+
filePath,
|
|
96
|
+
scope = null,
|
|
97
|
+
stat = null,
|
|
98
|
+
readRanges = [],
|
|
99
|
+
oldStrings = [],
|
|
100
|
+
readCache = null,
|
|
101
|
+
recordPreviewSnapshot = false,
|
|
102
|
+
} = {}) {
|
|
103
|
+
if (!fullPath) return null;
|
|
104
|
+
|
|
105
|
+
let rawBuf;
|
|
106
|
+
try {
|
|
107
|
+
rawBuf = readFileSync(fullPath);
|
|
108
|
+
} catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
if (!Buffer.isBuffer(rawBuf)) return null;
|
|
112
|
+
|
|
113
|
+
const displayPath = filePath || normalizeOutputPath(fullPath);
|
|
114
|
+
const utf8Err = assertEditTargetUtf8(rawBuf, displayPath);
|
|
115
|
+
if (utf8Err) return { ok: false, error: utf8Err };
|
|
116
|
+
|
|
117
|
+
const content = rawBuf.toString('utf-8');
|
|
118
|
+
const entries = normalizeOldStringEntries(oldStrings);
|
|
119
|
+
if (entries.length === 0) return null;
|
|
120
|
+
|
|
121
|
+
for (const entry of entries) {
|
|
122
|
+
const editPrefix = entries.length > 1 ? `${entry.label} — ` : '';
|
|
123
|
+
const verdict = oldStringMatchableOnCurrent(content, entry.old_string, entry.replace_all, {
|
|
124
|
+
filePath: displayPath,
|
|
125
|
+
editPrefix,
|
|
126
|
+
});
|
|
127
|
+
if (verdict.error) return { ok: false, error: verdict.error };
|
|
128
|
+
if (verdict.notFound) {
|
|
129
|
+
// Friction fix (Codex-style): the snapshot is stale AND the
|
|
130
|
+
// old_string is absent from CURRENT disk bytes. Don't force a
|
|
131
|
+
// wasteful re-read turn — report a plain not-found against current
|
|
132
|
+
// content (code 8) with recovery context, so the model can correct
|
|
133
|
+
// the string in place without a separate Read call. Commit-time
|
|
134
|
+
// race protection (validatePreparedEditBase / atomicWrite) is
|
|
135
|
+
// unchanged.
|
|
136
|
+
const recovery = buildStaleEditRecovery({
|
|
137
|
+
fullPath,
|
|
138
|
+
scope,
|
|
139
|
+
oldStrings: entries,
|
|
140
|
+
recordPreviewSnapshot,
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
error: `Error [code 8]: old_string not found in ${displayPath} (file changed since read; matched against current content)${recovery}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
recordReadSnapshot(fullPath, stat || undefined, scope, {
|
|
151
|
+
source: 'stale_auto_refresh',
|
|
152
|
+
contentHash: hashText(content),
|
|
153
|
+
ranges: Array.isArray(readRanges) ? readRanges : [],
|
|
154
|
+
replaceExisting: true,
|
|
155
|
+
});
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (readCache && typeof readCache.seedBuffer === 'function') {
|
|
161
|
+
readCache.seedBuffer(fullPath, rawBuf);
|
|
162
|
+
} else if (readCache) {
|
|
163
|
+
readCache.rawBuf = rawBuf;
|
|
164
|
+
readCache.content = content;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return { ok: true, content, rawBuf };
|
|
168
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendMutationPlanFailure,
|
|
3
|
+
executeMutationPlan,
|
|
4
|
+
isMutationPlanRoutable,
|
|
5
|
+
planEditMutationRoute,
|
|
6
|
+
} from '../mutation-planner.mjs';
|
|
7
|
+
import { resolveAgainstCwd, normalizeInputPath } from './path-utils.mjs';
|
|
8
|
+
import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
9
|
+
import { assertPathsReachable } from './fs-reachability.mjs';
|
|
10
|
+
import { isUncPath, isWindowsDevicePath, hasUnsafeWin32Component, isBlockedDevicePath } from './device-paths.mjs';
|
|
11
|
+
|
|
12
|
+
function editItemsFromBatchArgs(args) {
|
|
13
|
+
return args.edits.map((edit) => ({
|
|
14
|
+
path: edit?.path || args.path,
|
|
15
|
+
old_string: edit?.old_string,
|
|
16
|
+
new_string: edit?.new_string,
|
|
17
|
+
replace_all: edit?.replace_all,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function editItemFromSingleArgs(args) {
|
|
22
|
+
return {
|
|
23
|
+
path: args.path,
|
|
24
|
+
old_string: args.old_string,
|
|
25
|
+
new_string: args.new_string,
|
|
26
|
+
replace_all: args.replace_all,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function maybeExecutePlannedMutation(items, workDir, readStateScope, options, routeOptions) {
|
|
31
|
+
const plan = planEditMutationRoute(items, routeOptions);
|
|
32
|
+
if (!isMutationPlanRoutable(plan)) return null;
|
|
33
|
+
return executeMutationPlan(plan, { workDir, readStateScope, options });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Strip a trailing line coordinate (`:N`, `:N-M`, `#LN`) using a PURE regex —
|
|
37
|
+
// no filesystem access. The real edit path does precise line-vs-colon
|
|
38
|
+
// disambiguation later (which needs FS); the reachability preflight only needs
|
|
39
|
+
// a statable base path, and using existsSync here would itself block on a dead
|
|
40
|
+
// mount BEFORE the async reachability check could run. A Windows drive colon
|
|
41
|
+
// (`C:\...`) is not a trailing `:digits`, so it is unaffected.
|
|
42
|
+
function stripLineCoordForReach(p) {
|
|
43
|
+
return String(p)
|
|
44
|
+
.replace(/#L\d+(?:-L?\d+)?(?:\b.*)?$/i, '')
|
|
45
|
+
.replace(/:\d+(?:-\d+)?(?::.*)?$/, '');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Reject UNC/device/ADS/blocked paths BEFORE the reachability stat — statting
|
|
49
|
+
// a UNC/device path would itself trigger the network/raw-device access (NTLM
|
|
50
|
+
// leak) the edit engine's own guards prevent. edit-engine guards run only
|
|
51
|
+
// after this preflight, so we mirror them here to avoid the stat entirely.
|
|
52
|
+
function _guardedEditError(p) {
|
|
53
|
+
if (isUncPath(p)) return `Error: cannot edit UNC / SMB path (network credential leak risk): ${p}`;
|
|
54
|
+
if (isWindowsDevicePath(p)) return `Error: cannot edit Windows device path (reserved name or raw-device namespace): ${p}`;
|
|
55
|
+
if (hasUnsafeWin32Component(p)) return `Error: cannot edit Windows path with trailing dot/space or NTFS ADS suffix (bypasses device guard): ${p}`;
|
|
56
|
+
if (isBlockedDevicePath(p)) return `Error: cannot edit device file: ${p}`;
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function assertEditTargetsReachable(args, workDir) {
|
|
61
|
+
const fullPaths = [];
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
let guardErr = null;
|
|
64
|
+
const addPath = (raw) => {
|
|
65
|
+
if (typeof raw !== 'string' || !raw) return;
|
|
66
|
+
// normalizeInputPath FIRST (FS-pure: trim/~/win32-mount/NFC) so we stat
|
|
67
|
+
// the same path the edit engine later opens; then strip line coord.
|
|
68
|
+
const norm = stripLineCoordForReach(normalizeInputPath(raw));
|
|
69
|
+
if (!norm) return;
|
|
70
|
+
const fullPath = resolveAgainstCwd(norm, workDir);
|
|
71
|
+
if (seen.has(fullPath)) return;
|
|
72
|
+
seen.add(fullPath);
|
|
73
|
+
// Guarded paths are rejected (not stat'd) — see _guardedEditError.
|
|
74
|
+
if (!guardErr) guardErr = _guardedEditError(norm) || _guardedEditError(fullPath);
|
|
75
|
+
fullPaths.push(fullPath);
|
|
76
|
+
};
|
|
77
|
+
if (Array.isArray(args.edits) && args.edits.length > 0) {
|
|
78
|
+
for (const item of editItemsFromBatchArgs(args)) addPath(item.path);
|
|
79
|
+
} else {
|
|
80
|
+
addPath(args.path);
|
|
81
|
+
}
|
|
82
|
+
if (guardErr) return guardErr;
|
|
83
|
+
try {
|
|
84
|
+
await assertPathsReachable(fullPaths);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function executeEditTool(args, workDir, readStateScope, executeChildBuiltinTool, options = {}, handlers = {}) {
|
|
92
|
+
const result = await _executeEditToolImpl(args, workDir, readStateScope, executeChildBuiltinTool, options, handlers);
|
|
93
|
+
// ② completion progress (claude "Found N" parity). Best-effort, no-op when
|
|
94
|
+
// onProgress is absent (no progressToken). Never throws — only emits on a
|
|
95
|
+
// successful edit (an "Error:" body is left to the tool result alone).
|
|
96
|
+
if (typeof options?.onProgress === 'function') {
|
|
97
|
+
try {
|
|
98
|
+
const _body = String(result);
|
|
99
|
+
if (!/^Error[\s[]/.test(_body)) {
|
|
100
|
+
const _rep = /\((\d+) replacements applied\)/.exec(_body);
|
|
101
|
+
if (_rep) options.onProgress(`edited ${_rep[1]} replacements`);
|
|
102
|
+
else {
|
|
103
|
+
const _one = /^Edited:\s+(.+?)(?:\s+\((?:native|\d+)\))?$/m.exec(_body);
|
|
104
|
+
if (_one) options.onProgress(`edited ${_one[1].trim()}`);
|
|
105
|
+
else {
|
|
106
|
+
const _ok = (_body.match(/^OK\s/gm) || []).length;
|
|
107
|
+
if (_ok) options.onProgress(`edited ${_ok} files`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch { /* best-effort */ }
|
|
112
|
+
}
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function _executeEditToolImpl(args, workDir, readStateScope, executeChildBuiltinTool, options = {}, handlers = {}) {
|
|
117
|
+
if (typeof args.file_path === 'string' && !args.path) args.path = args.file_path;
|
|
118
|
+
if (Array.isArray(args.edits)) {
|
|
119
|
+
for (const edit of args.edits) {
|
|
120
|
+
if (edit && typeof edit.file_path === 'string' && !edit.path) edit.path = edit.file_path;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (Array.isArray(args.edits) && args.edits.length > 0) {
|
|
125
|
+
const items = editItemsFromBatchArgs(args);
|
|
126
|
+
const paths = new Set(items.map((item) => item.path).filter(Boolean));
|
|
127
|
+
if (paths.size === 0) return 'Error: each edit requires a path (either on the item or at top level)';
|
|
128
|
+
|
|
129
|
+
const reachErr = await assertEditTargetsReachable(args, workDir);
|
|
130
|
+
if (reachErr) return reachErr;
|
|
131
|
+
|
|
132
|
+
const planned = await maybeExecutePlannedMutation(items, workDir, readStateScope, options);
|
|
133
|
+
if (planned?.ok) return planned.text;
|
|
134
|
+
|
|
135
|
+
let result;
|
|
136
|
+
if (paths.size === 1) {
|
|
137
|
+
const onePath = [...paths][0];
|
|
138
|
+
result = await handlers.runMultiEdit({
|
|
139
|
+
path: onePath,
|
|
140
|
+
edits: items.map(({ path: _path, ...rest }) => rest),
|
|
141
|
+
}, workDir, readStateScope, null, options);
|
|
142
|
+
} else {
|
|
143
|
+
result = await handlers.runBatchEdit({
|
|
144
|
+
edits: items.map((item) => ({
|
|
145
|
+
path: item.path,
|
|
146
|
+
old_string: item.old_string,
|
|
147
|
+
new_string: item.new_string,
|
|
148
|
+
replace_all: item.replace_all,
|
|
149
|
+
})),
|
|
150
|
+
}, workDir, readStateScope, null, executeChildBuiltinTool, options);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const fallback = await maybeExecutePlannedMutation(items, workDir, readStateScope, options, {
|
|
154
|
+
priorResult: result,
|
|
155
|
+
});
|
|
156
|
+
if (fallback?.ok) return fallback.text;
|
|
157
|
+
return appendMutationPlanFailure(appendMutationPlanFailure(result, planned), fallback);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const item = editItemFromSingleArgs(args);
|
|
161
|
+
const reachErr = await assertEditTargetsReachable(args, workDir);
|
|
162
|
+
if (reachErr) return reachErr;
|
|
163
|
+
|
|
164
|
+
const planned = await maybeExecutePlannedMutation([item], workDir, readStateScope, options);
|
|
165
|
+
if (planned?.ok) return planned.text;
|
|
166
|
+
|
|
167
|
+
const result = await handlers.runSingleEdit(args, workDir, readStateScope, options);
|
|
168
|
+
const fallback = await maybeExecutePlannedMutation([item], workDir, readStateScope, options, {
|
|
169
|
+
priorResult: result,
|
|
170
|
+
});
|
|
171
|
+
if (fallback?.ok) return fallback.text;
|
|
172
|
+
return appendMutationPlanFailure(appendMutationPlanFailure(result, planned), fallback);
|
|
173
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { isValidUtf8Buffer } from '../mutation-content-cache.mjs';
|
|
2
|
+
|
|
3
|
+
// Shared guard. Centralises the UTF-8 check so every byte-exact write
|
|
4
|
+
// path (native-exact dispatch, single-edit byte-exact buffer,
|
|
5
|
+
// multi-edit byte-exact buffer, AND the stale-auto-refresh path)
|
|
6
|
+
// refuses non-UTF-8 targets with identical wording. Without a single
|
|
7
|
+
// shared hook the stale-refresh path read with 'utf-8' encoding,
|
|
8
|
+
// silently replacing invalid sequences with U+FFFD and bypassing the
|
|
9
|
+
// downstream guards that only saw the re-encoded decoded text.
|
|
10
|
+
export function assertEditTargetUtf8(rawBuf, filePath) {
|
|
11
|
+
if (isValidUtf8Buffer(rawBuf)) return null;
|
|
12
|
+
// UTF-16 BOM detection BEFORE the generic message: a UTF-16 file is a
|
|
13
|
+
// recognizable, recoverable case (read decodes it; write preserves the
|
|
14
|
+
// BOM round-trip) — calling it "Shift-JIS/Latin-1/binary mix" sends the
|
|
15
|
+
// caller down the wrong recovery path.
|
|
16
|
+
if (rawBuf.length >= 2 && rawBuf[0] === 0xFF && rawBuf[1] === 0xFE) {
|
|
17
|
+
return `Error: file is UTF-16LE (BOM FF FE) — edit only supports UTF-8; use write (preserves UTF-16) or convert the file. Path: ${filePath}`;
|
|
18
|
+
}
|
|
19
|
+
if (rawBuf.length >= 2 && rawBuf[0] === 0xFE && rawBuf[1] === 0xFF) {
|
|
20
|
+
return `Error: file is UTF-16BE (BOM FE FF) — edit only supports UTF-8; convert the file first. Path: ${filePath}`;
|
|
21
|
+
}
|
|
22
|
+
// Strict manual UTF-8 walk for Node <18 / environments where
|
|
23
|
+
// Buffer.isUtf8 is unavailable. Rejects overlong sequences,
|
|
24
|
+
// surrogates, out-of-range code points, and 5/6-byte sequences
|
|
25
|
+
// (Unicode §3.9 Table 3-7).
|
|
26
|
+
let idx2 = 0;
|
|
27
|
+
while (idx2 < rawBuf.length) {
|
|
28
|
+
const b0 = rawBuf[idx2];
|
|
29
|
+
if (b0 < 0x80) { idx2++; continue; }
|
|
30
|
+
let seqLen = 0;
|
|
31
|
+
if ((b0 & 0xE0) === 0xC0) seqLen = 2;
|
|
32
|
+
else if ((b0 & 0xF0) === 0xE0) seqLen = 3;
|
|
33
|
+
else if ((b0 & 0xF8) === 0xF0) seqLen = 4;
|
|
34
|
+
else return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
35
|
+
if (idx2 + seqLen > rawBuf.length) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
36
|
+
if (seqLen === 2 && b0 <= 0xC1) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
37
|
+
const b1 = rawBuf[idx2 + 1];
|
|
38
|
+
if (seqLen === 3 && b0 === 0xE0 && b1 < 0xA0) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
39
|
+
if (seqLen === 3 && b0 === 0xED && b1 >= 0xA0) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
40
|
+
if (seqLen === 4 && b0 === 0xF0 && b1 < 0x90) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
41
|
+
if (seqLen === 4 && (b0 > 0xF4 || (b0 === 0xF4 && b1 >= 0x90))) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
42
|
+
for (let k = 1; k < seqLen; k++) {
|
|
43
|
+
if ((rawBuf[idx2 + k] & 0xC0) !== 0x80) return `Error: file appears to be non-UTF-8 (Shift-JIS/Latin-1/binary mix). Edit aborted to prevent silent corruption. Path: ${filePath}`;
|
|
44
|
+
}
|
|
45
|
+
idx2 += seqLen;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// fs-reachability.mjs — async reachability preflight for tools that then do
|
|
2
|
+
// synchronous filesystem work (read / write / edit / apply_patch).
|
|
3
|
+
//
|
|
4
|
+
// WHY: a synchronous `statSync` / `readFileSync` / `realpathSync` on a dead
|
|
5
|
+
// mount or hung network path BLOCKS the Node main thread. Because the event
|
|
6
|
+
// loop is frozen, even the 630s dispatch ceiling (a main-loop setTimeout)
|
|
7
|
+
// cannot fire — the tool call hangs indefinitely. An async `fsPromises.stat`
|
|
8
|
+
// runs on the libuv threadpool, so a per-path deadline CAN fire on the main
|
|
9
|
+
// loop and surface a clean error BEFORE the blocking sync call is reached.
|
|
10
|
+
//
|
|
11
|
+
// This is a preflight gate, not a full sync->async rewrite: the existing sync
|
|
12
|
+
// logic is unchanged; we only refuse to enter it when the path is unreachable.
|
|
13
|
+
import { stat } from 'node:fs/promises';
|
|
14
|
+
|
|
15
|
+
export const FS_REACHABILITY_DEADLINE_MS = 5000;
|
|
16
|
+
|
|
17
|
+
// Resolve true when the path is reachable (exists OR cleanly absent — ENOENT,
|
|
18
|
+
// EACCES, etc. are "the FS answered", let the real sync logic produce its own
|
|
19
|
+
// error). Reject with EFSUNREACHABLE only when the stat itself exceeds the
|
|
20
|
+
// deadline, which is the dead-mount / hung-FS signature.
|
|
21
|
+
export async function assertPathReachable(path, deadlineMs = FS_REACHABILITY_DEADLINE_MS) {
|
|
22
|
+
if (typeof path !== 'string' || path.length === 0) return;
|
|
23
|
+
const ms = Number(deadlineMs) > 0 ? Number(deadlineMs) : FS_REACHABILITY_DEADLINE_MS;
|
|
24
|
+
let timer = null;
|
|
25
|
+
const probe = stat(path).then(() => true, () => true); // any answer = reachable
|
|
26
|
+
const deadline = new Promise((resolve) => {
|
|
27
|
+
timer = setTimeout(() => resolve('TIMEOUT'), ms);
|
|
28
|
+
});
|
|
29
|
+
const result = await Promise.race([
|
|
30
|
+
probe.finally(() => { if (timer) clearTimeout(timer); }),
|
|
31
|
+
deadline,
|
|
32
|
+
]);
|
|
33
|
+
if (result === 'TIMEOUT') {
|
|
34
|
+
const err = new Error(
|
|
35
|
+
`path unreachable: stat exceeded ${ms}ms (possible dead mount / hung filesystem): ${path}`,
|
|
36
|
+
);
|
|
37
|
+
err.code = 'EFSUNREACHABLE';
|
|
38
|
+
throw err;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Batch variant: reject if ANY path is unreachable. Runs probes concurrently so
|
|
43
|
+
// the wall-clock cost is one deadline, not N.
|
|
44
|
+
export async function assertPathsReachable(paths, deadlineMs = FS_REACHABILITY_DEADLINE_MS) {
|
|
45
|
+
const list = Array.isArray(paths) ? paths.filter((p) => typeof p === 'string' && p.length) : [];
|
|
46
|
+
if (list.length === 0) return;
|
|
47
|
+
await Promise.all(list.map((p) => assertPathReachable(p, deadlineMs)));
|
|
48
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// fuzzy-match.mjs — lightweight subsequence fuzzy scorer for filename / path
|
|
2
|
+
// search (codex file-search / nucleo style, in JS). Returns a score where a
|
|
3
|
+
// higher value is a better match, or null when the query is not a subsequence
|
|
4
|
+
// of the candidate.
|
|
5
|
+
//
|
|
6
|
+
// Scoring favors, in rough order of weight:
|
|
7
|
+
// - matches at the START of the basename (last path segment)
|
|
8
|
+
// - matches at a word boundary (/, \, _, -, ., space, or camelCase hump)
|
|
9
|
+
// - contiguous runs of matched characters
|
|
10
|
+
// - exact-case hits (small tie-break)
|
|
11
|
+
// - shorter / earlier candidates (mild brevity + earliness pull)
|
|
12
|
+
//
|
|
13
|
+
// Matching is case-insensitive. The query is matched as an ordered subsequence
|
|
14
|
+
// (the chars must appear in order but need not be contiguous), which is what
|
|
15
|
+
// lets "edeng" find "edit-engine.mjs".
|
|
16
|
+
|
|
17
|
+
function isBoundaryChar(ch) {
|
|
18
|
+
return ch === '/' || ch === '\\' || ch === '_' || ch === '-' || ch === '.' || ch === ' ';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A camelCase hump (lower/digit followed by upper) is also a word boundary.
|
|
22
|
+
function isHump(prevCh, ch) {
|
|
23
|
+
return prevCh !== undefined
|
|
24
|
+
&& /[a-z0-9]/.test(prevCh)
|
|
25
|
+
&& /[A-Z]/.test(ch);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} query user query (partial name)
|
|
30
|
+
* @param {string} str candidate path (relative)
|
|
31
|
+
* @returns {number|null} score, or null if `query` is not a subsequence
|
|
32
|
+
*/
|
|
33
|
+
export function fuzzyScore(query, str) {
|
|
34
|
+
if (!query) return 0;
|
|
35
|
+
const q = query.toLowerCase();
|
|
36
|
+
const s = str.toLowerCase();
|
|
37
|
+
const qlen = q.length;
|
|
38
|
+
const slen = s.length;
|
|
39
|
+
if (qlen === 0) return 0;
|
|
40
|
+
if (qlen > slen) return null;
|
|
41
|
+
|
|
42
|
+
const lastSep = Math.max(str.lastIndexOf('/'), str.lastIndexOf('\\'));
|
|
43
|
+
|
|
44
|
+
let score = 0;
|
|
45
|
+
let si = 0;
|
|
46
|
+
let prevMatch = -2;
|
|
47
|
+
let firstMatchIdx = -1;
|
|
48
|
+
|
|
49
|
+
for (let qi = 0; qi < qlen; qi++) {
|
|
50
|
+
const qc = q[qi];
|
|
51
|
+
let found = -1;
|
|
52
|
+
for (let k = si; k < slen; k++) {
|
|
53
|
+
if (s[k] === qc) { found = k; break; }
|
|
54
|
+
}
|
|
55
|
+
if (found === -1) return null;
|
|
56
|
+
if (firstMatchIdx === -1) firstMatchIdx = found;
|
|
57
|
+
|
|
58
|
+
score += 1; // base point per matched char
|
|
59
|
+
|
|
60
|
+
if (found === prevMatch + 1) score += 5; // contiguous run
|
|
61
|
+
|
|
62
|
+
const prevCh = found > 0 ? str[found - 1] : undefined;
|
|
63
|
+
if (prevCh === undefined || isBoundaryChar(prevCh) || isHump(prevCh, str[found])) {
|
|
64
|
+
score += 8; // word-boundary start
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (str[found] === query[qi]) score += 1; // exact-case tie-break
|
|
68
|
+
|
|
69
|
+
prevMatch = found;
|
|
70
|
+
si = found + 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Matches that begin inside the basename (after the last separator) are far
|
|
74
|
+
// more relevant than ones buried in directory components.
|
|
75
|
+
if (firstMatchIdx > lastSep) score += 10;
|
|
76
|
+
|
|
77
|
+
// Mild pulls: shorter candidates and earlier first matches rank higher.
|
|
78
|
+
score -= Math.floor(slen / 16);
|
|
79
|
+
score -= Math.floor(firstMatchIdx / 8);
|
|
80
|
+
|
|
81
|
+
return score;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Rank candidates by fuzzy score against `query`, dropping non-matches.
|
|
86
|
+
* @param {string} query
|
|
87
|
+
* @param {Array<{path:string}>} items each must expose a `path` string
|
|
88
|
+
* @param {number} [limit]
|
|
89
|
+
* @returns {Array<{item:object, score:number}>} sorted desc, then path asc
|
|
90
|
+
*/
|
|
91
|
+
export function fuzzyRank(query, items, limit = 0) {
|
|
92
|
+
const scored = [];
|
|
93
|
+
for (const item of items) {
|
|
94
|
+
const sc = fuzzyScore(query, item.path);
|
|
95
|
+
if (sc !== null) scored.push({ item, score: sc });
|
|
96
|
+
}
|
|
97
|
+
scored.sort((a, b) => (b.score - a.score) || (a.item.path < b.item.path ? -1 : a.item.path > b.item.path ? 1 : 0));
|
|
98
|
+
return limit > 0 ? scored.slice(0, limit) : scored;
|
|
99
|
+
}
|