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,480 @@
|
|
|
1
|
+
import { getAbortSignalForSession } from '../../session/abort-lookup.mjs';
|
|
2
|
+
import { execShellCommand, stripAnsi, extractPowerShellCommandInner } from '../shell-command.mjs';
|
|
3
|
+
import { wrapCommandWithSnapshot } from '../shell-snapshot.mjs';
|
|
4
|
+
import { stripQuotedAndHeredoc, extractShellCInner, getDestructiveCommandWarning } from '../destructive-warning.mjs';
|
|
5
|
+
import { maybeRewriteWmicProcessCommand } from '../shell-policy.mjs';
|
|
6
|
+
import { buildBashPolicyScanTargets, checkExecPolicyMessage, injectionBlockTargets } from '../bash-policy-scan.mjs';
|
|
7
|
+
import { markCodeGraphDirtyPaths, drainCodeGraphCache } from '../code-graph.mjs';
|
|
8
|
+
import {
|
|
9
|
+
buildJobNotFoundMessage,
|
|
10
|
+
startBackgroundShellJob,
|
|
11
|
+
waitForShellJob,
|
|
12
|
+
peekShellJob,
|
|
13
|
+
killShellJob,
|
|
14
|
+
watchBackgroundShellJob,
|
|
15
|
+
cancelBackgroundShellJobWatch,
|
|
16
|
+
beginShellJobWait,
|
|
17
|
+
endShellJobWait,
|
|
18
|
+
clearShellJobNotifyCtx,
|
|
19
|
+
} from './shell-jobs.mjs';
|
|
20
|
+
import {
|
|
21
|
+
analyzeShellCommandEffects,
|
|
22
|
+
foregroundLongCommandHint,
|
|
23
|
+
preflightShellLargeFileProbe,
|
|
24
|
+
} from './shell-analysis.mjs';
|
|
25
|
+
import { resolveShellFor } from './shell-runtime.mjs';
|
|
26
|
+
import { smartMiddleTruncate } from './shell-output.mjs';
|
|
27
|
+
import { normalizeOutputPath } from './path-utils.mjs';
|
|
28
|
+
import { normalizeErrorMessage } from './path-diagnostics.mjs';
|
|
29
|
+
import { invalidateBuiltinResultCache } from './cache-layers.mjs';
|
|
30
|
+
import { resolveOptionalCwd } from './cwd-utils.mjs';
|
|
31
|
+
import { scrubLoaderVars, scrubProviderSecrets } from '../env-scrub.mjs';
|
|
32
|
+
|
|
33
|
+
// Post-exec drift detection. After a foreground shell command, compare the
|
|
34
|
+
// live mtime+size of files mixdog has already read this session against their
|
|
35
|
+
// pre-command state (captured just before exec). Files this command changed
|
|
36
|
+
// surface as ONE compact reminder so the model re-reads before editing —
|
|
37
|
+
// closing the "external write -> stale old_string -> code 8" gap when shell is
|
|
38
|
+
// routed through this tool. Bounded to the tracked-read set (capped) so cost
|
|
39
|
+
// stays off the whole-cwd path; emits nothing when no read file changed.
|
|
40
|
+
const _DRIFT_SCAN_CAP = 256;
|
|
41
|
+
export function _captureTrackedMtimes(scope) {
|
|
42
|
+
return new Map();
|
|
43
|
+
}
|
|
44
|
+
export function _trackedDriftNoteAfter(scope, pre) {
|
|
45
|
+
return '';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Combine an existing session abort signal with an externally-supplied
|
|
49
|
+
// AbortSignal (e.g. the MCP/request signal threaded through options.abortSignal).
|
|
50
|
+
// Returns null when neither is present so existing session-only behavior is
|
|
51
|
+
// preserved unchanged. Uses AbortSignal.any when available; falls back to a
|
|
52
|
+
// manual controller + listener bridge otherwise. The returned signal aborts as
|
|
53
|
+
// soon as either input signal aborts, which propagates to execShellCommand /
|
|
54
|
+
// executeBashSessionTool and triggers the same child-kill path the session
|
|
55
|
+
// signal already drives.
|
|
56
|
+
function _combineAbortSignals(sessionSignal, externalSignal) {
|
|
57
|
+
const a = sessionSignal || null;
|
|
58
|
+
const b = externalSignal || null;
|
|
59
|
+
if (!a && !b) return null;
|
|
60
|
+
if (!a) return b;
|
|
61
|
+
if (!b) return a;
|
|
62
|
+
if (a === b) return a;
|
|
63
|
+
if (typeof AbortSignal !== 'undefined' && typeof AbortSignal.any === 'function') {
|
|
64
|
+
try { return AbortSignal.any([a, b]); } catch { /* fall through */ }
|
|
65
|
+
}
|
|
66
|
+
const ctl = new AbortController();
|
|
67
|
+
const onAbort = (sig) => {
|
|
68
|
+
if (ctl.signal.aborted) return;
|
|
69
|
+
try { ctl.abort(sig?.reason); } catch { try { ctl.abort(); } catch {} }
|
|
70
|
+
};
|
|
71
|
+
if (a.aborted) { onAbort(a); return ctl.signal; }
|
|
72
|
+
if (b.aborted) { onAbort(b); return ctl.signal; }
|
|
73
|
+
try { a.addEventListener('abort', () => onAbort(a), { once: true }); } catch {}
|
|
74
|
+
try { b.addEventListener('abort', () => onAbort(b), { once: true }); } catch {}
|
|
75
|
+
return ctl.signal;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Decode ANSI-C $'…' and locale $"…" escapes so the blocklist scan sees the
|
|
79
|
+
// literal command (e.g. $'\x72m' → "rm"). Defensive against quoting bypass.
|
|
80
|
+
function _decodeAnsiCQuotes(s) {
|
|
81
|
+
if (typeof s !== 'string') return '';
|
|
82
|
+
if (s.indexOf('$') === -1) return s;
|
|
83
|
+
return s.replace(/\$(['"])((?:\\.|[^\\])*?)\1/g, (_full, _q, body) =>
|
|
84
|
+
body
|
|
85
|
+
.replace(/\\x([0-9a-fA-F]{1,2})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
|
|
86
|
+
.replace(/\\u([0-9a-fA-F]{1,4})/g, (_m, h) => String.fromCharCode(parseInt(h, 16)))
|
|
87
|
+
.replace(/\\0([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
|
|
88
|
+
.replace(/\\([0-7]{1,3})/g, (_m, o) => String.fromCharCode(parseInt(o, 8)))
|
|
89
|
+
.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r')
|
|
90
|
+
.replace(/\\\\/g, '\\').replace(/\\(['"])/g, '$1'),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Extract $(…) and `…` command-substitution bodies so each is re-scanned by
|
|
95
|
+
// isBlockedCommand (e.g. eval $(printf 'rm -rf ~')).
|
|
96
|
+
function _extractSubstitutionBodies(s) {
|
|
97
|
+
if (typeof s !== 'string') return [];
|
|
98
|
+
const out = [];
|
|
99
|
+
const re = /\$\(([^()]*(?:\([^()]*\)[^()]*)*)\)|`([^`]*)`/g;
|
|
100
|
+
let m;
|
|
101
|
+
while ((m = re.exec(s)) !== null) {
|
|
102
|
+
const body = m[1] != null ? m[1] : m[2];
|
|
103
|
+
if (body && body.trim()) out.push(body);
|
|
104
|
+
}
|
|
105
|
+
return out;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Combined injection-aware block targets: decoded form + substitution bodies
|
|
109
|
+
// (and their decoded forms). Used on BOTH the persistent and stateless paths.
|
|
110
|
+
export function _injectionBlockTargets(cmd) {
|
|
111
|
+
return injectionBlockTargets(cmd);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function _prefixPowerShellUtf8(command) {
|
|
115
|
+
const prefix = '[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; $OutputEncoding=[System.Text.Encoding]::UTF8;';
|
|
116
|
+
const text = String(command || '');
|
|
117
|
+
return text.trimStart().startsWith(prefix) ? text : `${prefix}\n${text}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const _unquoteSpansForPolicy = (s) => s.replace(/"([^"\\]|\\.)*"|'([^'\\]|\\.)*'/g, (m) => m.slice(1, -1));
|
|
121
|
+
|
|
122
|
+
// Same normalized + decoded target set as hard-block (strip/unquote/shell -c/PS).
|
|
123
|
+
export { buildBashPolicyScanTargets } from '../bash-policy-scan.mjs';
|
|
124
|
+
|
|
125
|
+
export function getDedupedDestructiveWarnings(command) {
|
|
126
|
+
const seenMsg = new Set();
|
|
127
|
+
const warnings = [];
|
|
128
|
+
for (const t of buildBashPolicyScanTargets(command)) {
|
|
129
|
+
const w = getDestructiveCommandWarning(t);
|
|
130
|
+
if (w && !seenMsg.has(w)) {
|
|
131
|
+
seenMsg.add(w);
|
|
132
|
+
warnings.push(w);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return warnings;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function _prependDestructiveWarning(command, text) {
|
|
139
|
+
const warnings = getDedupedDestructiveWarnings(command);
|
|
140
|
+
if (!warnings.length) return text;
|
|
141
|
+
return `${warnings.map((w) => `⚠️ ${w}`).join('\n')}\n${text}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function executeBashTool(args, workDir, options = {}) {
|
|
145
|
+
const cwdResult = resolveOptionalCwd(args.cwd, workDir);
|
|
146
|
+
if (cwdResult.error) return cwdResult.error;
|
|
147
|
+
const bashWorkDir = cwdResult.cwd;
|
|
148
|
+
const readStateScope = options?.readStateScope ?? options?.sessionId ?? null;
|
|
149
|
+
|
|
150
|
+
// Run hard-block policy BEFORE branching into the persistent-shell tool.
|
|
151
|
+
// The persistent path used to bypass the one-shot block scan because the
|
|
152
|
+
// normalization (stripQuotedAndHeredoc / extractShellCInner / unquoted
|
|
153
|
+
// span sweep) lived only on the one-shot side. Centralised policy in
|
|
154
|
+
// shell-policy.mjs already covers the literal scan + EncodedCommand
|
|
155
|
+
// decode + rm token guard; calling it here applies the same allowlist
|
|
156
|
+
// to both persistent and stateless paths.
|
|
157
|
+
const _rawCmd = String(args && args.command != null ? args.command : '');
|
|
158
|
+
if (_rawCmd) {
|
|
159
|
+
// R5-③: persistent:true used to route into bash_session BEFORE the
|
|
160
|
+
// stripQuotedAndHeredoc / extractShellCInner / unquote sweep ran
|
|
161
|
+
// (that sweep lived only on the stateless one-shot path below at
|
|
162
|
+
// ~:218). Result: `bash -c 'shutdown -h now'` / `sh -c 'mkfs ...'` /
|
|
163
|
+
// dd payloads were rejected stateless but accepted with
|
|
164
|
+
// persistent:true. Run the full sweep here so both paths share the
|
|
165
|
+
// same blocklist before dispatch.
|
|
166
|
+
const _policyBlock = checkExecPolicyMessage(_rawCmd);
|
|
167
|
+
if (_policyBlock) return _policyBlock;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// An empty-string session_id is NOT a persistent-session request: `typeof
|
|
171
|
+
// '' === 'string'` would otherwise route a stateless call into the
|
|
172
|
+
// persistent path and (on Windows) hard-fail with the disabled-sessions
|
|
173
|
+
// error, which models then retry in a loop. Require a non-blank id.
|
|
174
|
+
if (args.persistent === true || (typeof args.session_id === 'string' && args.session_id.trim().length > 0)) {
|
|
175
|
+
if (process.platform === 'win32') {
|
|
176
|
+
return 'Error: persistent shell sessions are disabled on Windows native-shell mode; run one-shot PowerShell commands without persistent/session_id.';
|
|
177
|
+
}
|
|
178
|
+
const { executeBashSessionTool } = await import('../bash-session.mjs');
|
|
179
|
+
let persistAbort = null;
|
|
180
|
+
try { persistAbort = (await getAbortSignalForSession(options?.sessionId)) || null; }
|
|
181
|
+
catch { persistAbort = null; }
|
|
182
|
+
const combinedPersistAbort = _combineAbortSignals(persistAbort, options?.abortSignal || null);
|
|
183
|
+
let effectiveArgs = (args.persistent === true && !args.session_id && options?.sessionId)
|
|
184
|
+
? { ...args, session_id: `__default__${options.sessionId}` }
|
|
185
|
+
: (typeof args.session_id === 'string' && options?.sessionId)
|
|
186
|
+
? { ...args, session_id: `${options.sessionId}__${args.session_id}` }
|
|
187
|
+
: args;
|
|
188
|
+
const userProvidedSession = typeof args.session_id === 'string' && args.session_id.trim().length > 0;
|
|
189
|
+
const shouldCreate = args.create === true || !userProvidedSession;
|
|
190
|
+
effectiveArgs = { ...effectiveArgs, create: shouldCreate };
|
|
191
|
+
return executeBashSessionTool('bash_session', effectiveArgs, bashWorkDir, { abortSignal: combinedPersistAbort, sessionId: options?.sessionId });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
let command = args.command;
|
|
195
|
+
if (!command) return 'Error: command is required';
|
|
196
|
+
|
|
197
|
+
// Resolve the shell up front so shell-type-specific handling (PS-only wmic
|
|
198
|
+
// rewrite, PS UTF-8 prefix) can gate on it. kind 'default' is byte-identical
|
|
199
|
+
// to today's resolveShell(); kind 'bash' on Windows resolves Git Bash, and a
|
|
200
|
+
// null spec means it is genuinely not installed — surface a clear error with
|
|
201
|
+
// NO silent fallback to the other shell.
|
|
202
|
+
const shellKind = args.shell === 'bash' || args.shell === 'powershell' ? args.shell : 'default';
|
|
203
|
+
const resolvedSpec = resolveShellFor(shellKind);
|
|
204
|
+
if (!resolvedSpec) {
|
|
205
|
+
if (shellKind === 'bash') {
|
|
206
|
+
return "Error: Git Bash not found — install Git for Windows or omit shell:'bash'.";
|
|
207
|
+
}
|
|
208
|
+
return "Error: pwsh (PowerShell) not found — install PowerShell or omit shell:'powershell'.";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// wmic→PowerShell rewrite is PowerShell-only; never mangle a command bound
|
|
212
|
+
// for bash (gate on the resolved shell type).
|
|
213
|
+
// Note: gating this to powershell did NOT change POSIX behavior — wmic is a
|
|
214
|
+
// Windows-only tool, so the rewrite was already dead code on POSIX hosts;
|
|
215
|
+
// the gate just makes that explicit.
|
|
216
|
+
const wmicRewrite = resolvedSpec.shellType === 'powershell'
|
|
217
|
+
? maybeRewriteWmicProcessCommand(command)
|
|
218
|
+
: null;
|
|
219
|
+
if (wmicRewrite?.error) return `Error: ${wmicRewrite.error}`;
|
|
220
|
+
if (wmicRewrite?.command) command = wmicRewrite.command;
|
|
221
|
+
|
|
222
|
+
const _execPolicyBlock = checkExecPolicyMessage(command);
|
|
223
|
+
if (_execPolicyBlock) {
|
|
224
|
+
return _execPolicyBlock;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const largeProbe = await preflightShellLargeFileProbe(command, bashWorkDir);
|
|
228
|
+
if (largeProbe) return `Error: ${largeProbe.message}`;
|
|
229
|
+
let shellEffects;
|
|
230
|
+
try {
|
|
231
|
+
shellEffects = await analyzeShellCommandEffects(command, bashWorkDir);
|
|
232
|
+
} catch (err) {
|
|
233
|
+
return `Error: ${normalizeErrorMessage(err instanceof Error ? err.message : String(err))}`;
|
|
234
|
+
}
|
|
235
|
+
// Keep foreground commands on a long tool-owned timeout. The MCP dispatch
|
|
236
|
+
// layer must not add a shorter fallback ceiling when timeout is omitted.
|
|
237
|
+
const DEFAULT_BASH_TIMEOUT_MS = 600_000;
|
|
238
|
+
const DEFAULT_BACKGROUND_BASH_TIMEOUT_MS = 600_000;
|
|
239
|
+
const MAX_BASH_TIMEOUT_MS = 1_800_000;
|
|
240
|
+
const defaultTimeoutMs = args.run_in_background === true
|
|
241
|
+
? DEFAULT_BACKGROUND_BASH_TIMEOUT_MS
|
|
242
|
+
: DEFAULT_BASH_TIMEOUT_MS;
|
|
243
|
+
const rawTimeout = (typeof args.timeout === 'number' && args.timeout > 0)
|
|
244
|
+
? args.timeout : defaultTimeoutMs;
|
|
245
|
+
const timeoutMs = rawTimeout <= 600 ? rawTimeout * 1000 : rawTimeout;
|
|
246
|
+
const timeout = Math.min(timeoutMs, wmicRewrite?.timeoutMs || MAX_BASH_TIMEOUT_MS);
|
|
247
|
+
const mergeStderr = args.merge_stderr === true;
|
|
248
|
+
const longForegroundHint = foregroundLongCommandHint(command, timeout, args);
|
|
249
|
+
if (longForegroundHint) return longForegroundHint;
|
|
250
|
+
// Auto-background threshold (CC ASSISTANT_BLOCKING_BUDGET_MS analogue):
|
|
251
|
+
// a foreground one-shot that is still running after this many ms is
|
|
252
|
+
// detached into a tracked shell-job instead of blocking the tool call
|
|
253
|
+
// indefinitely. Only the foreground one-shot path uses it — never
|
|
254
|
+
// run_in_background (already detached) or persistent sessions (handled
|
|
255
|
+
// far above). Capped below the hard timeout so the 600 s upper bound
|
|
256
|
+
// stays a separate, later ceiling.
|
|
257
|
+
const DEFAULT_AUTO_BACKGROUND_MS = 30_000;
|
|
258
|
+
const autoBackgroundMs = args.run_in_background === true
|
|
259
|
+
? 0
|
|
260
|
+
: Math.min(DEFAULT_AUTO_BACKGROUND_MS, timeout);
|
|
261
|
+
|
|
262
|
+
try {
|
|
263
|
+
const { shell, shellArg, shellArgs, shellType } = resolvedSpec;
|
|
264
|
+
const spawnEnv = { ...process.env, LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' };
|
|
265
|
+
// R5/R11: same scrub as background/persistent spawn sites (env-scrub.mjs).
|
|
266
|
+
scrubProviderSecrets(spawnEnv);
|
|
267
|
+
scrubLoaderVars(spawnEnv);
|
|
268
|
+
let wrappedCommand;
|
|
269
|
+
// PowerShell UTF-8 prefix is PS-only: the Windows Git Bash path
|
|
270
|
+
// (shellType==='posix') must NOT receive it. Snapshot wrapper stays
|
|
271
|
+
// POSIX-host-only for now — no snapshot for Windows Git Bash initially.
|
|
272
|
+
if (process.platform === 'win32' && shellType === 'powershell') {
|
|
273
|
+
wrappedCommand = _prefixPowerShellUtf8(command);
|
|
274
|
+
} else if (process.platform !== 'win32' && (shell.includes('bash') || shell.includes('zsh'))) {
|
|
275
|
+
try {
|
|
276
|
+
wrappedCommand = await wrapCommandWithSnapshot(shell, command);
|
|
277
|
+
} catch (wrapErr) {
|
|
278
|
+
return `Error: shell snapshot wrapper failed — ${normalizeErrorMessage(wrapErr instanceof Error ? wrapErr.message : String(wrapErr))}`;
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
wrappedCommand = command;
|
|
282
|
+
}
|
|
283
|
+
if (args.run_in_background === true) {
|
|
284
|
+
const job = startBackgroundShellJob({
|
|
285
|
+
command: wrappedCommand,
|
|
286
|
+
timeoutMs: timeout,
|
|
287
|
+
workDir: bashWorkDir,
|
|
288
|
+
mergeStderr,
|
|
289
|
+
spawnEnv,
|
|
290
|
+
shell,
|
|
291
|
+
shellArg,
|
|
292
|
+
shellArgs,
|
|
293
|
+
shellType,
|
|
294
|
+
// Per-terminal session stamp: the dispatching terminal's
|
|
295
|
+
// claude.exe pid (server-main threads callerSession.clientHostPid).
|
|
296
|
+
clientHostPid: options?.clientHostPid,
|
|
297
|
+
});
|
|
298
|
+
if (job && job.error) return `Error: ${job.error}`;
|
|
299
|
+
// Wire a one-shot completion push so the dispatching session learns
|
|
300
|
+
// the background job finished (no polling tool is auto-driven). The
|
|
301
|
+
// notify ctx is threaded down from the MCP dispatch frame
|
|
302
|
+
// (server-main agentContext / _dispatchByModule) the same way the
|
|
303
|
+
// explore tool receives notifyFn/routingSessionId/clientHostPid.
|
|
304
|
+
// Missing notifyFn (e.g. a non-MCP caller) degrades to a stderr
|
|
305
|
+
// diagnostic inside watchBackgroundShellJob — never fails the spawn.
|
|
306
|
+
try {
|
|
307
|
+
watchBackgroundShellJob(job.jobId, {
|
|
308
|
+
notifyFn: typeof options?.notifyFn === 'function' ? options.notifyFn : null,
|
|
309
|
+
routingSessionId: options?.routingSessionId,
|
|
310
|
+
clientHostPid: options?.clientHostPid,
|
|
311
|
+
});
|
|
312
|
+
} catch { /* watcher arm is best-effort; never blocks the spawn */ }
|
|
313
|
+
return _prependDestructiveWarning(command, [
|
|
314
|
+
`[job: ${job.jobId}]`,
|
|
315
|
+
`[pid: ${job.pid}]`,
|
|
316
|
+
`[stdout: ${normalizeOutputPath(job.stdoutPath)}]`,
|
|
317
|
+
mergeStderr ? null : `[stderr: ${normalizeOutputPath(job.stderrPath)}]`,
|
|
318
|
+
'',
|
|
319
|
+
`Background job started for command: ${command}`,
|
|
320
|
+
].filter(Boolean).join('\n'));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let bashAbortSignal = null;
|
|
324
|
+
try { bashAbortSignal = (await getAbortSignalForSession(options?.sessionId)) || null; }
|
|
325
|
+
catch { bashAbortSignal = null; }
|
|
326
|
+
const combinedBashAbort = _combineAbortSignals(bashAbortSignal, options?.abortSignal || null);
|
|
327
|
+
const result = await execShellCommand({
|
|
328
|
+
shell, shellArg, shellArgs, command: wrappedCommand,
|
|
329
|
+
env: spawnEnv,
|
|
330
|
+
cwd: bashWorkDir,
|
|
331
|
+
timeoutMs: timeout,
|
|
332
|
+
abortSignal: combinedBashAbort,
|
|
333
|
+
autoBackgroundMs,
|
|
334
|
+
// Threaded so an auto-backgrounded foreground job is stamped with
|
|
335
|
+
// the dispatching terminal's claude.exe pid (per-terminal scope).
|
|
336
|
+
clientHostPid: options?.clientHostPid,
|
|
337
|
+
// MCP live-progress reporter (null unless the client subscribed via
|
|
338
|
+
// callTool onprogress). execShellCommand emits throttled "running
|
|
339
|
+
// Ns" frames while the foreground command runs.
|
|
340
|
+
onProgress: typeof options?.onProgress === 'function' ? options.onProgress : null,
|
|
341
|
+
});
|
|
342
|
+
// Auto-backgrounded: the command outlived autoBackgroundMs and is
|
|
343
|
+
// still running, now adopted as a tracked shell-job. Surface the
|
|
344
|
+
// jobId + partial output so the model can poll via job_wait instead
|
|
345
|
+
// of the tool call hanging until the hard timeout.
|
|
346
|
+
if (result.backgrounded) {
|
|
347
|
+
const partialStdout = smartMiddleTruncate(stripAnsi(result.stdout || ''));
|
|
348
|
+
const partialStderr = stripAnsi(result.stderr || '');
|
|
349
|
+
const lines = [
|
|
350
|
+
result.jobId ? `[job: ${result.jobId}]` : null,
|
|
351
|
+
result.stdoutPath ? `[stdout: ${normalizeOutputPath(result.stdoutPath)}]` : null,
|
|
352
|
+
(!mergeStderr && result.stderrPath) ? `[stderr: ${normalizeOutputPath(result.stderrPath)}]` : null,
|
|
353
|
+
'',
|
|
354
|
+
result.backgroundMessage || 'auto-backgrounded; still running',
|
|
355
|
+
partialStdout ? `\n[partial stdout]\n${partialStdout}` : '',
|
|
356
|
+
(!mergeStderr && partialStderr) ? `\n[partial stderr]\n${partialStderr}` : '',
|
|
357
|
+
].filter((l) => l !== null && l !== '');
|
|
358
|
+
return _prependDestructiveWarning(command, lines.join('\n'));
|
|
359
|
+
}
|
|
360
|
+
const stdout = stripAnsi(result.stdout || '');
|
|
361
|
+
const stderr = stripAnsi(result.stderr || '');
|
|
362
|
+
const signal = result.timedOut
|
|
363
|
+
? 'SIGTERM'
|
|
364
|
+
: (result.killed ? 'SIGKILL' : (result.signal || null));
|
|
365
|
+
const exitCode = signal ? null : result.exitCode;
|
|
366
|
+
const isReallyErrored = !!signal || (exitCode !== 0 && exitCode !== null);
|
|
367
|
+
const _driftNote = '';
|
|
368
|
+
// Distinct timeout marker so callers see "killed by timeout after Nms"
|
|
369
|
+
// vs an external signal (e.g. user Ctrl-C, OOM kill). result.timedOut
|
|
370
|
+
// is the runtime's own timeout escalation (SIGTERM → SIGKILL via
|
|
371
|
+
// treeKill on Windows taskkill), so report the timeout ceiling that
|
|
372
|
+
// fired alongside the actual signal used to kill the tree.
|
|
373
|
+
// Timeout marker carries an inline recovery hint so the caller can
|
|
374
|
+
// act in one round (increase ceiling or detach) instead of repeating
|
|
375
|
+
// the same command and hitting the same wall.
|
|
376
|
+
const statusMarker = result.timedOut
|
|
377
|
+
? `[timeout: ${timeout}ms signal: ${signal || 'SIGTERM'}]`
|
|
378
|
+
: (signal
|
|
379
|
+
? `[signal: ${signal}]`
|
|
380
|
+
: (isReallyErrored ? `[exit code: ${exitCode}]` : ''));
|
|
381
|
+
if (mergeStderr) {
|
|
382
|
+
// Post-exit concatenation. True chunk-level interleaving would
|
|
383
|
+
// require shell-level `2>&1` redirection (bash) or `*>&1`
|
|
384
|
+
// (PowerShell) inside wrappedCommand, or an in-process ordered
|
|
385
|
+
// merged stream in shell-command.mjs. Current implementation
|
|
386
|
+
// preserves stdout/stderr ordering within each stream but loses
|
|
387
|
+
// cross-stream interleaving. Acceptable for most diagnostic
|
|
388
|
+
// outputs; flag in shell-command if exact interleaving is required.
|
|
389
|
+
const merged = stdout + stderr;
|
|
390
|
+
if (statusMarker) return _prependDestructiveWarning(command, smartMiddleTruncate(`${statusMarker}\n\n${merged || '(no output)'}`) + _driftNote);
|
|
391
|
+
return _prependDestructiveWarning(command, smartMiddleTruncate(merged || '(no output)') + _driftNote);
|
|
392
|
+
}
|
|
393
|
+
const truncatedStdout = smartMiddleTruncate(stdout);
|
|
394
|
+
const truncatedStderr = stderr ? smartMiddleTruncate(stderr) : '';
|
|
395
|
+
const body = truncatedStdout || (truncatedStderr ? '' : '(no output)');
|
|
396
|
+
const stderrBlock = truncatedStderr ? `\n\n[stderr]\n${truncatedStderr}` : '';
|
|
397
|
+
let spillBlock = '';
|
|
398
|
+
if (result.stdoutPath) {
|
|
399
|
+
const sizeKb = Math.round((result.stdoutFileSize || 0) / 1024);
|
|
400
|
+
spillBlock += `\n\n[stdout: ${normalizeOutputPath(result.stdoutPath)} (${sizeKb} KB)]`;
|
|
401
|
+
}
|
|
402
|
+
if (result.stderrPath && (result.stderrFileSize || 0) > 0) {
|
|
403
|
+
const sizeKb = Math.round((result.stderrFileSize || 0) / 1024);
|
|
404
|
+
spillBlock += `\n[stderr: ${normalizeOutputPath(result.stderrPath)} (${sizeKb} KB)]`;
|
|
405
|
+
}
|
|
406
|
+
const warningBlock = [
|
|
407
|
+
wmicRewrite?.note || '',
|
|
408
|
+
].filter(Boolean).join('\n');
|
|
409
|
+
const payload = `${body}${stderrBlock}${spillBlock}${_driftNote}`;
|
|
410
|
+
if (statusMarker) return _prependDestructiveWarning(command, `${warningBlock ? `${warningBlock}\n` : ''}${statusMarker}\n\n${payload}`);
|
|
411
|
+
return _prependDestructiveWarning(command, warningBlock ? `${warningBlock}\n${payload}` : payload);
|
|
412
|
+
}
|
|
413
|
+
finally {
|
|
414
|
+
if (shellEffects.mutationMode === 'paths') {
|
|
415
|
+
invalidateBuiltinResultCache(shellEffects.paths);
|
|
416
|
+
markCodeGraphDirtyPaths(shellEffects.paths);
|
|
417
|
+
} else if (shellEffects.mutationMode === 'global') {
|
|
418
|
+
invalidateBuiltinResultCache();
|
|
419
|
+
drainCodeGraphCache();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
export async function executeJobWaitTool(args) {
|
|
425
|
+
const jobId = typeof args.job_id === 'string' ? args.job_id : '';
|
|
426
|
+
if (!jobId) return 'Error: job_id is required';
|
|
427
|
+
// bridge_* / sess_* are bridge-worker / orchestrator session ids, not
|
|
428
|
+
// background shell jobs. job_wait only resolves `bash run_in_background`
|
|
429
|
+
// jobs, so surface a self-correcting hint instead of the bare
|
|
430
|
+
// "job not found" that otherwise invites a wrong-tool retry loop.
|
|
431
|
+
if (/^(?:bridge_|sess_)/.test(jobId)) {
|
|
432
|
+
return `Error: "${jobId}" is a bridge/session id, not a background shell job. job_wait only waits on ids returned by \`bash\` with run_in_background:true. Bridge workers are detached and reply asynchronously — check their status with bridge type=list.`;
|
|
433
|
+
}
|
|
434
|
+
const action = typeof args.action === 'string' ? args.action.toLowerCase() : 'wait';
|
|
435
|
+
if (action === 'peek') {
|
|
436
|
+
const job = peekShellJob(jobId);
|
|
437
|
+
return job ? JSON.stringify(job, null, 2) : buildJobNotFoundMessage(jobId);
|
|
438
|
+
}
|
|
439
|
+
if (action === 'kill') {
|
|
440
|
+
const job = killShellJob(jobId);
|
|
441
|
+
// kill forces completion the caller observes here, so cancel the armed
|
|
442
|
+
// watcher (before/after kill is equivalent) to avoid a double-notify.
|
|
443
|
+
cancelBackgroundShellJobWatch(jobId);
|
|
444
|
+
// Killed entry will never fire, so drop its persistent notify ctx too —
|
|
445
|
+
// cancel keeps the ctx (for re-arm) but kill has no re-arm path.
|
|
446
|
+
clearShellJobNotifyCtx(jobId);
|
|
447
|
+
return job ? JSON.stringify(job, null, 2) : buildJobNotFoundMessage(jobId);
|
|
448
|
+
}
|
|
449
|
+
// Register as a synchronous waiter and cancel the armed watcher BEFORE
|
|
450
|
+
// awaiting: the caller consumes the outcome via this job_wait, so no async
|
|
451
|
+
// push is wanted, and cancelling up front closes the race where the armed
|
|
452
|
+
// watcher (watch callback or 2s poll) fires during the await window. The
|
|
453
|
+
// persistent notify ctx survives the cancel for a possible re-arm.
|
|
454
|
+
beginShellJobWait(jobId);
|
|
455
|
+
cancelBackgroundShellJobWatch(jobId);
|
|
456
|
+
try {
|
|
457
|
+
const job = await waitForShellJob(jobId, {
|
|
458
|
+
timeoutMs: typeof args.timeout_ms === 'number' ? args.timeout_ms : 30_000,
|
|
459
|
+
pollMs: typeof args.poll_ms === 'number' ? args.poll_ms : 250,
|
|
460
|
+
});
|
|
461
|
+
if (!job) return buildJobNotFoundMessage(jobId);
|
|
462
|
+
return JSON.stringify(job, null, 2);
|
|
463
|
+
} finally {
|
|
464
|
+
// Only the LAST concurrent waiter (post-decrement count 0) may re-arm,
|
|
465
|
+
// and only for a still-running job (timed-out wait). Re-arm with no ctx
|
|
466
|
+
// arg — watchBackgroundShellJob falls back to the persistent ctx. This
|
|
467
|
+
// prevents the concurrent-waiter double-deliver: while any other waiter
|
|
468
|
+
// is still synchronously consuming the outcome, the watcher stays off.
|
|
469
|
+
const remaining = endShellJobWait(jobId);
|
|
470
|
+
if (remaining === 0) {
|
|
471
|
+
const latest = peekShellJob(jobId);
|
|
472
|
+
if (latest && latest.status === 'running') watchBackgroundShellJob(jobId);
|
|
473
|
+
// LAST waiter out and the job already finished — the outcome was
|
|
474
|
+
// consumed synchronously, so no re-arm. Drop the persisted ctx here
|
|
475
|
+
// or it leaks (cleanup only runs on a real watcher settle, which
|
|
476
|
+
// never happens for a never-re-armed entry).
|
|
477
|
+
else clearShellJobNotifyCtx(jobId);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { closeSync, openSync, readSync } from 'fs';
|
|
2
|
+
|
|
3
|
+
// Binary detection: reading a PNG / ELF / zip / compressed blob as utf-8
|
|
4
|
+
// pollutes the context with U+FFFD characters and wastes tokens. Sample the
|
|
5
|
+
// head and tail of the file and look for a null byte — the canonical signal
|
|
6
|
+
// that the file is not plain text. Head window scales with file size:
|
|
7
|
+
// min(fileSize, 64KB) head + 4KB tail, so a 250KB file with a null byte at
|
|
8
|
+
// 9KB or 249KB is caught equally. The sampling is synchronous and cheap
|
|
9
|
+
// relative to the 256KB read budget it guards.
|
|
10
|
+
// Callers inside the ≤READ_MAX_SIZE_BYTES branch should pass st.size so the
|
|
11
|
+
// tail probe fires; callers above the cap pass the real size from err.size.
|
|
12
|
+
export function isBinaryFile(fullPath, fileSize = 0) {
|
|
13
|
+
const HEAD_CAP = 64 * 1024; // 64 KB max head window
|
|
14
|
+
const TAIL_SIZE = 4 * 1024; // 4 KB tail probe
|
|
15
|
+
const headBytes = fileSize > 0 ? Math.min(fileSize, HEAD_CAP) : HEAD_CAP;
|
|
16
|
+
let fd = null;
|
|
17
|
+
try {
|
|
18
|
+
fd = openSync(fullPath, 'r');
|
|
19
|
+
// Head probe
|
|
20
|
+
const headBuf = Buffer.allocUnsafe(headBytes);
|
|
21
|
+
const nHead = readSync(fd, headBuf, 0, headBytes, 0);
|
|
22
|
+
if (nHead === 0) return false;
|
|
23
|
+
// UTF-16 text has a null byte in every other position; a leading
|
|
24
|
+
// UTF-16 BOM marks it as text the read path can decode
|
|
25
|
+
// (detectReadEncodingFromBuffer/decodeReadBuffer support utf16le and
|
|
26
|
+
// utf16be), so exempt it rather than reject the file as binary.
|
|
27
|
+
// FF FE = UTF-16LE, FE FF = UTF-16BE.
|
|
28
|
+
if (nHead >= 2
|
|
29
|
+
&& ((headBuf[0] === 0xff && headBuf[1] === 0xfe)
|
|
30
|
+
|| (headBuf[0] === 0xfe && headBuf[1] === 0xff))) return false;
|
|
31
|
+
for (let i = 0; i < nHead; i++) {
|
|
32
|
+
if (headBuf[i] === 0) return true;
|
|
33
|
+
}
|
|
34
|
+
// Tail probe (only when file is larger than head window)
|
|
35
|
+
if (fileSize > headBytes && fileSize > TAIL_SIZE) {
|
|
36
|
+
const tailOffset = fileSize - TAIL_SIZE;
|
|
37
|
+
const tailBuf = Buffer.allocUnsafe(TAIL_SIZE);
|
|
38
|
+
const nTail = readSync(fd, tailBuf, 0, TAIL_SIZE, tailOffset);
|
|
39
|
+
for (let i = 0; i < nTail; i++) {
|
|
40
|
+
if (tailBuf[i] === 0) return true;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
} finally {
|
|
47
|
+
if (fd !== null) { try { closeSync(fd); } catch {} }
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const BINARY_PREVIEW_BYTES = 256;
|
|
52
|
+
|
|
53
|
+
/** Short hex preview for read when null bytes mark the file as binary. */
|
|
54
|
+
export function formatBinaryReadPreview(fullPath, displayPath, fileSize, { previewBytes = BINARY_PREVIEW_BYTES } = {}) {
|
|
55
|
+
const n = Math.max(0, Math.min(previewBytes, fileSize > 0 ? fileSize : previewBytes));
|
|
56
|
+
let fd = null;
|
|
57
|
+
try {
|
|
58
|
+
fd = openSync(fullPath, 'r');
|
|
59
|
+
const buf = Buffer.alloc(n);
|
|
60
|
+
const bytesRead = readSync(fd, buf, 0, n, 0);
|
|
61
|
+
const slice = buf.subarray(0, bytesRead);
|
|
62
|
+
const hex = Array.from(slice).map((b) => b.toString(16).padStart(2, '0')).join(' ');
|
|
63
|
+
const disp = displayPath || fullPath;
|
|
64
|
+
const note = `binary, ${fileSize} byte${fileSize === 1 ? '' : 's'}`;
|
|
65
|
+
const text = `${disp}\n${note}\n${hex || '(empty)'}`;
|
|
66
|
+
return { text, snapshotMeta: { source: 'read_hex', ranges: [] } };
|
|
67
|
+
} catch {
|
|
68
|
+
const disp = displayPath || fullPath;
|
|
69
|
+
return {
|
|
70
|
+
text: `${disp}\nbinary, ${fileSize} bytes\n(preview unavailable)`,
|
|
71
|
+
snapshotMeta: { source: 'read_hex', ranges: [] },
|
|
72
|
+
};
|
|
73
|
+
} finally {
|
|
74
|
+
if (fd !== null) { try { closeSync(fd); } catch {} }
|
|
75
|
+
}
|
|
76
|
+
}
|