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,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon host — one shared backend process per plugin data dir.
|
|
3
|
+
*
|
|
4
|
+
* `ensureDaemon(dataDir)` is the thin-client entry: connect to a running
|
|
5
|
+
* daemon, or spawn one (serialized so N cold-start terminals don't storm) and
|
|
6
|
+
* connect. `runDaemon(dataDir)` is the daemon process entry: it claims the
|
|
7
|
+
* owner lock (singleton invariant — a loser exits), listens on the shared pipe,
|
|
8
|
+
* and serves every connected client.
|
|
9
|
+
*
|
|
10
|
+
* The daemon process delegates per-session request routing to
|
|
11
|
+
* `serveDaemon` in server-main.mjs, so each connection is a real
|
|
12
|
+
* Claude Code session.
|
|
13
|
+
*
|
|
14
|
+
* Ownership is the owner lock + pipe ownership, both liveness-based (ESRCH ⇒
|
|
15
|
+
* dead). No time-boxed "run anyway" escape: a loser attaches or exits.
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from 'child_process'
|
|
18
|
+
import * as fs from 'fs'
|
|
19
|
+
import * as path from 'path'
|
|
20
|
+
import * as os from 'os'
|
|
21
|
+
import { fileURLToPath } from 'url'
|
|
22
|
+
import { connect } from './transport.mjs'
|
|
23
|
+
|
|
24
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
25
|
+
// The daemon BODY lives in server-main (reached via `server.mjs --daemon`); the
|
|
26
|
+
// host only spawns it and connects clients. server.mjs is two levels up from
|
|
27
|
+
// src/daemon/. Pipe ownership (transport.listen → null) is the singleton arbiter.
|
|
28
|
+
const SERVER_ENTRY = path.join(path.dirname(__filename), '..', '..', 'server.mjs')
|
|
29
|
+
const RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT || path.join(os.tmpdir(), 'mixdog')
|
|
30
|
+
|
|
31
|
+
function pidAlive(pid) {
|
|
32
|
+
if (!Number.isFinite(pid) || pid <= 0) return false
|
|
33
|
+
try { process.kill(pid, 0); return true }
|
|
34
|
+
catch (e) { return e.code !== 'ESRCH' } // ESRCH ⇒ dead; EPERM/other ⇒ alive
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function lockDir(dataDir) {
|
|
38
|
+
const dir = dataDir && dataDir.length ? dataDir : RUNTIME_ROOT
|
|
39
|
+
try { fs.mkdirSync(dir, { recursive: true }) } catch {}
|
|
40
|
+
return dir
|
|
41
|
+
}
|
|
42
|
+
const spawnLockPath = (dataDir) => path.join(lockDir(dataDir), '.daemon-spawn.lock')
|
|
43
|
+
|
|
44
|
+
// Atomic create-or-fail lock holding our pid. Reclaims a lock whose holder pid
|
|
45
|
+
// is dead. Returns true if we now hold it.
|
|
46
|
+
function tryAcquireLock(file) {
|
|
47
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
48
|
+
try {
|
|
49
|
+
fs.writeFileSync(file, String(process.pid), { flag: 'wx' })
|
|
50
|
+
return true
|
|
51
|
+
} catch (e) {
|
|
52
|
+
if (e.code !== 'EEXIST') return false
|
|
53
|
+
let holder = 0
|
|
54
|
+
try { holder = Number(fs.readFileSync(file, 'utf8')) } catch {}
|
|
55
|
+
if (holder === process.pid) return true
|
|
56
|
+
if (pidAlive(holder)) return false
|
|
57
|
+
try { fs.unlinkSync(file) } catch {} // stale holder dead — reclaim and retry
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
function releaseLock(file) {
|
|
63
|
+
try {
|
|
64
|
+
if (Number(fs.readFileSync(file, 'utf8')) === process.pid) fs.unlinkSync(file)
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
69
|
+
|
|
70
|
+
// Poll-connect until a daemon is listening or the budget expires.
|
|
71
|
+
async function waitForDaemon(dataDir, budgetMs) {
|
|
72
|
+
const deadline = Date.now() + budgetMs
|
|
73
|
+
for (;;) {
|
|
74
|
+
try { return await connect(dataDir, { timeoutMs: 1000 }) } catch {}
|
|
75
|
+
if (Date.now() >= deadline) throw new Error('daemon did not come up within budget')
|
|
76
|
+
await sleep(150)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function spawnDaemonProcess(dataDir) {
|
|
81
|
+
const child = spawn(process.env.BUN_EXEC_PATH || process.execPath, [SERVER_ENTRY, '--daemon', dataDir], {
|
|
82
|
+
detached: true,
|
|
83
|
+
stdio: 'ignore',
|
|
84
|
+
env: { ...process.env, MIXDOG_DAEMON_MODE: '1', MIXDOG_DAEMON_DATA_DIR: dataDir },
|
|
85
|
+
...(process.platform === 'win32' ? { windowsHide: true } : {}),
|
|
86
|
+
})
|
|
87
|
+
child.unref()
|
|
88
|
+
return child
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Thin-client entry: return a framed connection to the shared daemon, spawning
|
|
92
|
+
// it if needed. Spawning is serialized via the spawn lock so N co-booting
|
|
93
|
+
// terminals produce one daemon; the owner lock inside runDaemon is the final
|
|
94
|
+
// singleton arbiter if two ever race through.
|
|
95
|
+
export async function ensureDaemon(dataDir) {
|
|
96
|
+
const deadline = Date.now() + 30000 // final safety bound, not the recovery path
|
|
97
|
+
for (;;) {
|
|
98
|
+
// 1) Already running?
|
|
99
|
+
try { return await connect(dataDir, { timeoutMs: 1200 }) } catch {}
|
|
100
|
+
// 2) Become the spawner. tryAcquireLock reclaims a DEAD holder, so a
|
|
101
|
+
// spawner that died before its daemon listened is recovered here on the
|
|
102
|
+
// next pass — liveness-based, not a fixed wait that wedges all waiters.
|
|
103
|
+
const spawnLock = spawnLockPath(dataDir)
|
|
104
|
+
if (tryAcquireLock(spawnLock)) {
|
|
105
|
+
try {
|
|
106
|
+
spawnDaemonProcess(dataDir)
|
|
107
|
+
return await waitForDaemon(dataDir, 30000)
|
|
108
|
+
} finally {
|
|
109
|
+
releaseLock(spawnLock)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// 3) A live client is spawning — briefly wait for its pipe, then loop back
|
|
113
|
+
// to retry both (1) connect and (2) dead-holder reclaim.
|
|
114
|
+
try { return await connect(dataDir, { timeoutMs: 800 }) } catch {}
|
|
115
|
+
if (Date.now() >= deadline) throw new Error('daemon did not come up within budget')
|
|
116
|
+
await sleep(150)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Transport adapter over a daemon framed connection.
|
|
3
|
+
*
|
|
4
|
+
* Lets the daemon hand each accepted client connection to its own MCP SDK
|
|
5
|
+
* `Server` instance, so the SDK does protocol-correct initialize / capabilities
|
|
6
|
+
* / tools negotiation per connection while we only supply the wire. The SDK
|
|
7
|
+
* `Transport` contract is minimal: start / send / close + onmessage / onclose /
|
|
8
|
+
* onerror. Our framed connection already speaks NDJSON JSON-RPC objects, so the
|
|
9
|
+
* adapter is a thin pass-through.
|
|
10
|
+
*/
|
|
11
|
+
export class FramedServerTransport {
|
|
12
|
+
constructor(conn, opts = {}) {
|
|
13
|
+
this._conn = conn
|
|
14
|
+
// Out-of-band control-frame sink (session-identity handshake). Control
|
|
15
|
+
// frames carry a `__mixdog` marker, no jsonrpc, and must NOT reach the SDK.
|
|
16
|
+
this._onControl = typeof opts.onControl === 'function' ? opts.onControl : null
|
|
17
|
+
this._started = false
|
|
18
|
+
// SDK assigns these before/after start(); call them if present.
|
|
19
|
+
this.onmessage = undefined
|
|
20
|
+
this.onclose = undefined
|
|
21
|
+
this.onerror = undefined
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async start() {
|
|
25
|
+
if (this._started) return
|
|
26
|
+
this._started = true
|
|
27
|
+
this._conn.onMessage((msg) => {
|
|
28
|
+
if (msg && msg.__mixdog) {
|
|
29
|
+
try { this._onControl?.(msg) } catch (e) { this.onerror?.(e) }
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
try { this.onmessage?.(msg) }
|
|
33
|
+
catch (e) { this.onerror?.(e) }
|
|
34
|
+
})
|
|
35
|
+
this._conn.onClose(() => {
|
|
36
|
+
try { this.onclose?.() } catch {}
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async send(message) {
|
|
41
|
+
this._conn.send(message)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async close() {
|
|
45
|
+
this._conn.close()
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-connection Session — the unit of isolation in the shared daemon.
|
|
3
|
+
*
|
|
4
|
+
* Today `server-main` is one process per Claude Code terminal, so session
|
|
5
|
+
* identity (sessionId, cwd, transcript, instanceId, lead pid) lives in process
|
|
6
|
+
* globals / env (`SESSION_ID`, `MIXDOG_SESSION_CWD`, …). In the daemon, one
|
|
7
|
+
* process serves N terminals, so that state must move OFF process globals and
|
|
8
|
+
* onto a Session keyed by the client connection. `cwd` is the highest-risk
|
|
9
|
+
* field: it is mutable (the `cwd` tool changes it) and must never leak from one
|
|
10
|
+
* client to another — the old `MIXDOG_SESSION_CWD` env is replaced by
|
|
11
|
+
* `session.cwd`, threaded per call into `dispatchTool` and worker payloads.
|
|
12
|
+
*
|
|
13
|
+
* Identity-only and install/process-scoped state (shared DB, plugin config,
|
|
14
|
+
* the single memory/channels services) deliberately stays OUT of Session.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { _safeProcessCwd } from '../shared/user-cwd.mjs'
|
|
18
|
+
|
|
19
|
+
// Fields a thin client sends in its `initialize` handshake. Everything is
|
|
20
|
+
// optional so a minimal client still gets a usable session.
|
|
21
|
+
// { sessionId, cwd, transcriptPath, leadPid, instanceId, env }
|
|
22
|
+
export class Session {
|
|
23
|
+
constructor(conn, init = {}) {
|
|
24
|
+
this.conn = conn
|
|
25
|
+
this.sessionId = init.sessionId || null
|
|
26
|
+
// Mutable per-session working dir. Resolution order at call time:
|
|
27
|
+
// session.cwd ?? init default ?? process.cwd(). NEVER read a global env.
|
|
28
|
+
this.cwd = init.cwd || null
|
|
29
|
+
// Optional live cwd resolver. The boot session (single stdio client) sets
|
|
30
|
+
// this to pwd() so it keeps tracking the legacy MIXDOG_SESSION_CWD env and
|
|
31
|
+
// stays behaviour-identical; daemon sessions leave it null and own a static
|
|
32
|
+
// per-connection `cwd` instead.
|
|
33
|
+
this.cwdFn = typeof init.cwdFn === 'function' ? init.cwdFn : null
|
|
34
|
+
// Optional write-side hook for cwd. The boot session points this at the
|
|
35
|
+
// legacy MIXDOG_SESSION_CWD env (which cwdFn=pwd reads back); daemon
|
|
36
|
+
// sessions leave it null and `setCwd` mutates this.cwd directly.
|
|
37
|
+
this.setCwdFn = typeof init.setCwdFn === 'function' ? init.setCwdFn : null
|
|
38
|
+
this.transcriptPath = init.transcriptPath || null
|
|
39
|
+
this.leadPid = Number.isFinite(init.leadPid) ? init.leadPid : null
|
|
40
|
+
this.clientHostPid = Number.isFinite(init.clientHostPid) ? init.clientHostPid : null
|
|
41
|
+
// Per-session runtime seat (was server.mjs `instanceId` = process.pid).
|
|
42
|
+
this.instanceId = init.instanceId || init.sessionId || null
|
|
43
|
+
// Opaque per-session scratch for state that is process-global today and
|
|
44
|
+
// gets migrated incrementally (lead guard, recent-bash tracker, recap
|
|
45
|
+
// status). Keyed by feature name so each wiring step claims its own slot
|
|
46
|
+
// without widening this constructor.
|
|
47
|
+
this.scratch = new Map()
|
|
48
|
+
this.createdAt = init.now ?? null // stamped by caller; daemon avoids Date.now at construct
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The cwd a tool call should resolve against. Falls back to the process cwd
|
|
52
|
+
// only when the session never advertised one (never another session's cwd).
|
|
53
|
+
resolveCwd() {
|
|
54
|
+
// Fallback uses _safeProcessCwd(), NOT captureOriginalUserCwd(): a daemon
|
|
55
|
+
// serves N terminals, so the fallback must consult NO per-session signal
|
|
56
|
+
// (MIXDOG_SESSION_CWD / user-cwd.txt) — that would leak one terminal's cwd
|
|
57
|
+
// into another. _safeProcessCwd() reads only the install-scoped
|
|
58
|
+
// CLAUDE_PLUGIN_ROOT to guard the daemon-launch-dir == plugin-cache case
|
|
59
|
+
// down to homedir(), surfacing a missing cwd as ENOENT (absolute paths
|
|
60
|
+
// required) instead of a silent stale read under the plugin cache.
|
|
61
|
+
return (this.cwdFn ? this.cwdFn() : this.cwd) || _safeProcessCwd()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Per-session cwd write (the `cwd set` tool). The boot session routes to its
|
|
65
|
+
// legacy env via setCwdFn so cwdFn=pwd keeps reading it; daemon sessions
|
|
66
|
+
// store it on the session so one terminal's cwd never leaks into another.
|
|
67
|
+
setCwd(value) {
|
|
68
|
+
if (this.setCwdFn) this.setCwdFn(value)
|
|
69
|
+
else this.cwd = value
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Registry of live sessions keyed by connection. One Session per connection;
|
|
75
|
+
* a reconnecting client (daemon restart) re-initializes and gets a fresh
|
|
76
|
+
* Session carrying the same sessionId (resume semantics land with M4 reconnect).
|
|
77
|
+
*/
|
|
78
|
+
export class SessionRegistry {
|
|
79
|
+
constructor() {
|
|
80
|
+
this._byConn = new Map()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
open(conn, init) {
|
|
84
|
+
const session = new Session(conn, init)
|
|
85
|
+
this._byConn.set(conn, session)
|
|
86
|
+
return session
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get(conn) {
|
|
90
|
+
return this._byConn.get(conn) || null
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
close(conn) {
|
|
94
|
+
this._byConn.delete(conn)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get size() {
|
|
98
|
+
return this._byConn.size
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin client — bridges one Claude Code terminal's stdio to the shared daemon.
|
|
3
|
+
*
|
|
4
|
+
* Every per-terminal `server.mjs` invocation (spawned by run-mcp) runs this
|
|
5
|
+
* instead of the heavy in-process backend. It connects to the shared daemon
|
|
6
|
+
* (spawning it if needed) and pipes NDJSON JSON-RPC both ways: run-mcp/CC stdin
|
|
7
|
+
* → daemon, daemon → stdout. When the daemon connection drops, the thin client
|
|
8
|
+
* exits; run-mcp then respawns this server.mjs (which re-attaches or re-spawns
|
|
9
|
+
* the daemon), reusing run-mcp's existing initialize cache/replay.
|
|
10
|
+
*
|
|
11
|
+
* Session identity is advertised out-of-band via a `__mixdog` control frame
|
|
12
|
+
* that the daemon transport filters BEFORE the MCP SDK sees it, so it has no
|
|
13
|
+
* ordering dependency on the MCP initialize handshake.
|
|
14
|
+
*/
|
|
15
|
+
import { ensureDaemon } from './host.mjs'
|
|
16
|
+
import { explicitSessionCwd, readLastSessionCwd } from '../shared/user-cwd.mjs'
|
|
17
|
+
|
|
18
|
+
export async function runThinClient(dataDir) {
|
|
19
|
+
const conn = await ensureDaemon(dataDir)
|
|
20
|
+
|
|
21
|
+
// daemon → CC (stdout)
|
|
22
|
+
conn.onMessage((msg) => {
|
|
23
|
+
try { process.stdout.write(JSON.stringify(msg) + '\n') } catch {}
|
|
24
|
+
})
|
|
25
|
+
// daemon gone → exit so run-mcp respawns us and we re-attach/re-spawn it
|
|
26
|
+
conn.onClose(() => process.exit(0))
|
|
27
|
+
|
|
28
|
+
// Advertise this terminal's identity (filtered out-of-band on the daemon).
|
|
29
|
+
try {
|
|
30
|
+
const rawLeadPid = Number(process.env.MIXDOG_SUPERVISOR_PID)
|
|
31
|
+
const leadPid = Number.isFinite(rawLeadPid) && rawLeadPid > 0 ? rawLeadPid : undefined
|
|
32
|
+
const hasEnvSessionCwd = typeof process.env.MIXDOG_SESSION_CWD === 'string' && process.env.MIXDOG_SESSION_CWD.length > 0
|
|
33
|
+
const advertisedCwd = hasEnvSessionCwd
|
|
34
|
+
? (explicitSessionCwd() || readLastSessionCwd(leadPid) || undefined)
|
|
35
|
+
: (readLastSessionCwd(leadPid) || explicitSessionCwd() || undefined)
|
|
36
|
+
conn.send({
|
|
37
|
+
__mixdog: 'session',
|
|
38
|
+
sessionId: process.env.MIXDOG_SESSION_ID || undefined,
|
|
39
|
+
// Advertise an explicit session cwd so the daemon Session never falls
|
|
40
|
+
// back to its own process.cwd() (the plugin CACHE dir), which would make
|
|
41
|
+
// every bridge worker resolve paths under ...\plugins\cache\...\<ver>.
|
|
42
|
+
// explicitSessionCwd() = MIXDOG_SESSION_CWD (if it exists) ?? user-cwd.txt
|
|
43
|
+
// (rewritten every session start); it NEVER returns process.cwd().
|
|
44
|
+
cwd: advertisedCwd,
|
|
45
|
+
leadPid,
|
|
46
|
+
transcriptPath: process.env.MIXDOG_TRANSCRIPT_PATH || undefined,
|
|
47
|
+
clientHostPid: Number(process.env.MIXDOG_OWNER_HOST_PID) || undefined,
|
|
48
|
+
})
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
// CC/run-mcp (stdin, NDJSON) → daemon
|
|
52
|
+
let buf = ''
|
|
53
|
+
process.stdin.setEncoding('utf8')
|
|
54
|
+
process.stdin.on('data', (chunk) => {
|
|
55
|
+
buf += chunk
|
|
56
|
+
let nl
|
|
57
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
58
|
+
const line = buf.slice(0, nl)
|
|
59
|
+
buf = buf.slice(nl + 1)
|
|
60
|
+
if (!line) continue
|
|
61
|
+
let obj
|
|
62
|
+
try { obj = JSON.parse(line) } catch { continue }
|
|
63
|
+
conn.send(obj)
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
process.stdin.on('end', () => process.exit(0))
|
|
67
|
+
process.stdin.on('close', () => process.exit(0))
|
|
68
|
+
|
|
69
|
+
// Keep the process alive; the pipes drive everything.
|
|
70
|
+
await new Promise(() => {})
|
|
71
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Daemon transport — NDJSON JSON-RPC over a per-data-dir named pipe.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the per-terminal `run-mcp ↔ server.mjs` stdio link with a single
|
|
5
|
+
* shared endpoint: one daemon process listens; N thin clients (one per Claude
|
|
6
|
+
* Code terminal) connect. This module is PURE transport — framing, accept,
|
|
7
|
+
* connect, liveness. It carries no session or JSON-RPC semantics; the daemon
|
|
8
|
+
* host layers those on top via the `onConnection` callback.
|
|
9
|
+
*
|
|
10
|
+
* Pipe ownership is the singleton primitive: on Windows a second `listen()`
|
|
11
|
+
* fails with EADDRINUSE, on Unix we probe-then-unlink. `listen()` returning
|
|
12
|
+
* null means "a live daemon already owns this pipe — attach as a client".
|
|
13
|
+
*/
|
|
14
|
+
import net from 'net'
|
|
15
|
+
import { createHash } from 'crypto'
|
|
16
|
+
import { unlinkSync, mkdirSync } from 'fs'
|
|
17
|
+
import path from 'path'
|
|
18
|
+
import os from 'os'
|
|
19
|
+
|
|
20
|
+
// Inbound frame guardrail (mirrors run-mcp's MAX_LINE_BYTES). A JSON-RPC line
|
|
21
|
+
// is newline-terminated; an unterminated line growing past this cap signals a
|
|
22
|
+
// runaway producer. 4 MB comfortably fits any legitimate tool result.
|
|
23
|
+
const MAX_LINE_BYTES = 4 * 1024 * 1024
|
|
24
|
+
|
|
25
|
+
const RUNTIME_ROOT = process.env.MIXDOG_RUNTIME_ROOT || path.join(os.tmpdir(), 'mixdog')
|
|
26
|
+
|
|
27
|
+
// One pipe per plugin data dir so distinct installs / data dirs never collide.
|
|
28
|
+
function daemonPipePath(dataDir) {
|
|
29
|
+
const hash = createHash('sha1').update(String(dataDir || 'default')).digest('hex').slice(0, 12)
|
|
30
|
+
if (process.platform === 'win32') return `\\\\.\\pipe\\mixdog-daemon-${hash}`
|
|
31
|
+
return path.join(RUNTIME_ROOT, `mixdog-daemon-${hash}.sock`)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Wrap a net.Socket in NDJSON framing. `send(obj)` writes one JSON line;
|
|
35
|
+
// `onMessage(cb)` fires per parsed object; `onClose(cb)` fires once on
|
|
36
|
+
// socket end/error. Non-JSON lines are dropped (never throw on the socket).
|
|
37
|
+
function frameConnection(socket) {
|
|
38
|
+
let buf = ''
|
|
39
|
+
let closed = false
|
|
40
|
+
const msgHandlers = []
|
|
41
|
+
const closeHandlers = []
|
|
42
|
+
socket.setEncoding('utf8')
|
|
43
|
+
socket.on('data', (chunk) => {
|
|
44
|
+
buf += chunk
|
|
45
|
+
let nl
|
|
46
|
+
while ((nl = buf.indexOf('\n')) >= 0) {
|
|
47
|
+
const line = buf.slice(0, nl)
|
|
48
|
+
buf = buf.slice(nl + 1)
|
|
49
|
+
if (!line) continue
|
|
50
|
+
let obj
|
|
51
|
+
try { obj = JSON.parse(line) } catch { continue }
|
|
52
|
+
for (const h of msgHandlers) { try { h(obj) } catch (e) { try { process.stderr.write(`[transport] message handler failed: ${e?.message ?? e}\n`) } catch {} } }
|
|
53
|
+
}
|
|
54
|
+
if (buf.length > MAX_LINE_BYTES) {
|
|
55
|
+
// Runaway unterminated frame — drop the connection rather than grow
|
|
56
|
+
// unbounded. A well-behaved peer never produces a 4 MB line without a \n.
|
|
57
|
+
try { socket.destroy() } catch {}
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
const fireClose = () => {
|
|
61
|
+
if (closed) return
|
|
62
|
+
closed = true
|
|
63
|
+
for (const h of closeHandlers) { try { h() } catch {} }
|
|
64
|
+
}
|
|
65
|
+
socket.on('close', fireClose)
|
|
66
|
+
socket.on('error', fireClose)
|
|
67
|
+
return {
|
|
68
|
+
socket,
|
|
69
|
+
send(obj) {
|
|
70
|
+
if (closed) return false
|
|
71
|
+
try { return socket.write(JSON.stringify(obj) + '\n') } catch { return false }
|
|
72
|
+
},
|
|
73
|
+
onMessage(cb) { msgHandlers.push(cb) },
|
|
74
|
+
onClose(cb) { closeHandlers.push(cb) },
|
|
75
|
+
close() { try { socket.end() } catch {} },
|
|
76
|
+
get closed() { return closed },
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Start the daemon endpoint. Resolves the net.Server on success, or `null`
|
|
81
|
+
// when another live daemon already owns the pipe (this process must attach as
|
|
82
|
+
// a client instead). `onConnection(conn)` is invoked with a framed connection
|
|
83
|
+
// per accepted client.
|
|
84
|
+
export async function listen({ dataDir, onConnection }) {
|
|
85
|
+
const pipePath = daemonPipePath(dataDir)
|
|
86
|
+
if (process.platform !== 'win32') {
|
|
87
|
+
// Unix domain sockets need the parent dir to exist; RUNTIME_ROOT may not.
|
|
88
|
+
try { mkdirSync(path.dirname(pipePath), { recursive: true }) } catch {}
|
|
89
|
+
}
|
|
90
|
+
// Fresh server per attempt: a net.Server that emitted 'error' cannot be
|
|
91
|
+
// re-listened reliably, so the Unix retry below recreates it each time.
|
|
92
|
+
const attempt = () => new Promise((resolve, reject) => {
|
|
93
|
+
const server = net.createServer((socket) => {
|
|
94
|
+
try { onConnection(frameConnection(socket)) } catch {}
|
|
95
|
+
})
|
|
96
|
+
const onErr = (e) => { server.removeListener('listening', onOk); reject(e) }
|
|
97
|
+
const onOk = () => { server.removeListener('error', onErr); resolve(server) }
|
|
98
|
+
server.once('error', onErr)
|
|
99
|
+
server.once('listening', onOk)
|
|
100
|
+
server.listen(pipePath)
|
|
101
|
+
})
|
|
102
|
+
if (process.platform === 'win32') {
|
|
103
|
+
// Windows named pipes reject a second listener with EADDRINUSE on their own,
|
|
104
|
+
// so no pre-listen probe is needed.
|
|
105
|
+
try { return await attempt() }
|
|
106
|
+
catch (e) { if (isAddrInUse(e)) return null; throw e }
|
|
107
|
+
}
|
|
108
|
+
// Unix: a leftover socket file from a crashed daemon makes bind fail with
|
|
109
|
+
// EADDRINUSE. Only unlink when a probe says nothing answers — and re-probe
|
|
110
|
+
// after every failed bind, so if a sibling rebinds in the probe→unlink
|
|
111
|
+
// window we see it alive on the next pass and attach instead of stealing it.
|
|
112
|
+
// (runDaemon calls this under the owner lock, which already serializes
|
|
113
|
+
// dataDir entry; this loop closes the residual TOCTOU window.)
|
|
114
|
+
for (let i = 0; i < 3; i++) {
|
|
115
|
+
try { return await attempt() }
|
|
116
|
+
catch (e) {
|
|
117
|
+
if (!isAddrInUse(e)) throw e
|
|
118
|
+
if (await probeAlive(pipePath)) return null
|
|
119
|
+
try { unlinkSync(pipePath) } catch {}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return null
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Connect to an existing daemon. Resolves a framed connection or rejects
|
|
126
|
+
// (ENOENT / ECONNREFUSED when no daemon is listening, or on timeout).
|
|
127
|
+
export function connect(dataDir, { timeoutMs = 2000 } = {}) {
|
|
128
|
+
const pipePath = daemonPipePath(dataDir)
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
const socket = net.connect(pipePath)
|
|
131
|
+
const timer = setTimeout(() => {
|
|
132
|
+
try { socket.destroy() } catch {}
|
|
133
|
+
reject(new Error('daemon connect timeout'))
|
|
134
|
+
}, timeoutMs)
|
|
135
|
+
socket.once('connect', () => { clearTimeout(timer); resolve(frameConnection(socket)) })
|
|
136
|
+
socket.once('error', (e) => { clearTimeout(timer); reject(e) })
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function isAddrInUse(e) {
|
|
141
|
+
const msg = String(e?.message || e || '')
|
|
142
|
+
return e?.code === 'EADDRINUSE' || msg.includes('EADDRINUSE') || msg.includes('Failed to listen')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Resolves true if something is listening on the pipe (daemon alive), false on
|
|
146
|
+
// ECONNREFUSED / ENOENT (dead/absent). Other errors / timeout resolve true so
|
|
147
|
+
// we err on NOT stealing a possibly-live peer's socket.
|
|
148
|
+
function probeAlive(pipePath) {
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
let done = false
|
|
151
|
+
const finish = (alive) => {
|
|
152
|
+
if (done) return
|
|
153
|
+
done = true
|
|
154
|
+
try { c.destroy() } catch {}
|
|
155
|
+
clearTimeout(t)
|
|
156
|
+
resolve(alive)
|
|
157
|
+
}
|
|
158
|
+
const c = net.connect(pipePath)
|
|
159
|
+
const t = setTimeout(() => finish(true), 500)
|
|
160
|
+
c.once('connect', () => finish(true))
|
|
161
|
+
c.once('error', (e) => finish(!(e?.code === 'ECONNREFUSED' || e?.code === 'ENOENT')))
|
|
162
|
+
})
|
|
163
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"generated_at": "2026-06-11T13:16:44.033Z",
|
|
4
|
+
"release_tag": "runtime-v0.4.0",
|
|
5
|
+
"pg": {
|
|
6
|
+
"major": 16,
|
|
7
|
+
"minor": 4
|
|
8
|
+
},
|
|
9
|
+
"pgvector": {
|
|
10
|
+
"version": "0.8.2"
|
|
11
|
+
},
|
|
12
|
+
"assets": {
|
|
13
|
+
"darwin-arm64": {
|
|
14
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/runtime-v0.4.0/mixdog-runtime-darwin-arm64-pg16.4-pgvector0.8.2.tar.gz",
|
|
15
|
+
"sha256": "7a642006ef60ccf8c385ba89c761c2b6f4927f6f01a1c4fb152604e6b41fe3d2",
|
|
16
|
+
"size": 23397361
|
|
17
|
+
},
|
|
18
|
+
"darwin-x64": {
|
|
19
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/runtime-v0.4.0/mixdog-runtime-darwin-x64-pg16.4-pgvector0.8.2.tar.gz",
|
|
20
|
+
"sha256": "8e39bfc3f06d88e9854946d5d83f7d77d9530f99284d7c1c51872c12f389f7e7",
|
|
21
|
+
"size": 23687666
|
|
22
|
+
},
|
|
23
|
+
"linux-x64": {
|
|
24
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/runtime-v0.4.0/mixdog-runtime-linux-x64-pg16.4-pgvector0.8.2.tar.gz",
|
|
25
|
+
"sha256": "28ee64125a0125ff36c47560c08ce8f38e49aef034c010c7e4867e8cdd783385",
|
|
26
|
+
"size": 23276106
|
|
27
|
+
},
|
|
28
|
+
"win32-x64": {
|
|
29
|
+
"url": "https://github.com/trib-plugin/mixdog/releases/download/runtime-v0.4.0/mixdog-runtime-win32-x64-pg16.4-pgvector0.8.2.tar.gz",
|
|
30
|
+
"sha256": "6faed8a49b3303b0adb1c5715b2b19c5cc47df2e738d586d0d0bd432f1ca035d",
|
|
31
|
+
"size": 44036958
|
|
32
|
+
},
|
|
33
|
+
"linux-arm64": {
|
|
34
|
+
"unsupported": true,
|
|
35
|
+
"url": "TBD",
|
|
36
|
+
"sha256": "TBD",
|
|
37
|
+
"size": 0
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|