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
package/server.mjs
ADDED
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// Thin entry. A node:-builtin-only prelude registers active-instance.json
|
|
3
|
+
// as early as possible, then dynamically imports server-main.mjs.
|
|
4
|
+
//
|
|
5
|
+
// ES module import hoisting causes statically-imported heavy modules to
|
|
6
|
+
// push the prelude back. Dynamic import is the only way to isolate it.
|
|
7
|
+
import { createServer } from 'node:http'
|
|
8
|
+
import * as fs from 'node:fs'
|
|
9
|
+
import * as path from 'node:path'
|
|
10
|
+
import * as os from 'node:os'
|
|
11
|
+
|
|
12
|
+
process.stderr.write(`[boot-time] tag=server-prelude-entry tMs=${Date.now()}\n`)
|
|
13
|
+
|
|
14
|
+
// ── Daemon model fork ───────────────────────────────────────────────────────
|
|
15
|
+
// Only the `--daemon` process runs the heavy backend (server-main → serveDaemon,
|
|
16
|
+
// booting memory/channels/PG ONCE per data dir). Every other invocation is a
|
|
17
|
+
// THIN CLIENT that bridges its stdio (from run-mcp) to the shared daemon over a
|
|
18
|
+
// pipe. When the daemon connection drops, the thin client exits and run-mcp
|
|
19
|
+
// respawns it (re-attaching or re-spawning the daemon), reusing run-mcp's
|
|
20
|
+
// initialize cache/replay. Replaces the per-terminal in-process server +
|
|
21
|
+
// fork-proxy/election model (legacy deleted post-cutover).
|
|
22
|
+
const __DAEMON_MODE = process.argv.includes('--daemon') || process.env.MIXDOG_DAEMON_MODE === '1'
|
|
23
|
+
if (!__DAEMON_MODE) {
|
|
24
|
+
const __dataDir = process.env.CLAUDE_PLUGIN_DATA
|
|
25
|
+
|| path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin')
|
|
26
|
+
const { runThinClient } = await import('./src/daemon/thin-client.mjs')
|
|
27
|
+
await runThinClient(__dataDir)
|
|
28
|
+
process.exit(0) // the bridge keeps us alive; if it ever returns, never run the daemon prelude
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ── Resolve PLUGIN_DATA (same rule as lib/plugin-paths.cjs: env wins) ─
|
|
32
|
+
const PLUGIN_DATA = process.env.CLAUDE_PLUGIN_DATA
|
|
33
|
+
|| path.join(os.homedir(), '.claude', 'plugins', 'data', 'mixdog-trib-plugin')
|
|
34
|
+
fs.mkdirSync(PLUGIN_DATA, { recursive: true })
|
|
35
|
+
const __parsePositivePid = (value) => {
|
|
36
|
+
const pid = Number(value)
|
|
37
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null
|
|
38
|
+
}
|
|
39
|
+
const __ownerLeadPid = __parsePositivePid(process.env.MIXDOG_SUPERVISOR_PID) ?? process.pid
|
|
40
|
+
const __runtimeRoot = process.env.MIXDOG_RUNTIME_ROOT
|
|
41
|
+
? path.resolve(process.env.MIXDOG_RUNTIME_ROOT)
|
|
42
|
+
: path.join(os.tmpdir(), 'mixdog')
|
|
43
|
+
|
|
44
|
+
// ── Singleton lock (hoisted from server.mjs:46~74) ──────────────────
|
|
45
|
+
const LOCK_PATH = path.join(PLUGIN_DATA, 'server.lock')
|
|
46
|
+
function _isPidAlive(pid) {
|
|
47
|
+
if (!pid || pid === process.pid) return false
|
|
48
|
+
try { process.kill(pid, 0); return true }
|
|
49
|
+
catch (err) { return err?.code === 'EPERM' }
|
|
50
|
+
}
|
|
51
|
+
// Atomic lock acquisition: open with 'wx' (exclusive create) so two racing
|
|
52
|
+
// processes cannot both believe they won. If the file already exists, 'wx'
|
|
53
|
+
// throws EEXIST — read the PID, decide if the owner is still alive, and
|
|
54
|
+
// either retry (stale) or exit (live).
|
|
55
|
+
let _lockFd = null
|
|
56
|
+
function _acquireLock() {
|
|
57
|
+
// Multi-instance escape hatch. When MIXDOG_MULTI_INSTANCE=1, multiple
|
|
58
|
+
// server.mjs processes can coexist in the same data dir — each one is its
|
|
59
|
+
// own CC stdio MCP server, and downstream singletons (PG, memory worker
|
|
60
|
+
// via fork-proxy mode, channels via proxyMode, status server via
|
|
61
|
+
// per-instance advert) handle the shared state. Skip the lock entirely so
|
|
62
|
+
// CC's plugin loader doesn't see "already running" rejections every time
|
|
63
|
+
// it spawns a second terminal.
|
|
64
|
+
if (process.env.MIXDOG_MULTI_INSTANCE === '1') return
|
|
65
|
+
// First try: atomic exclusive create.
|
|
66
|
+
try {
|
|
67
|
+
_lockFd = fs.openSync(LOCK_PATH, 'wx')
|
|
68
|
+
fs.writeSync(_lockFd, String(process.pid))
|
|
69
|
+
fs.closeSync(_lockFd)
|
|
70
|
+
_lockFd = null
|
|
71
|
+
return // success — fast path
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (err?.code !== 'EEXIST') {
|
|
74
|
+
// Unexpected error — log and abort. Continuing without a valid lock risks
|
|
75
|
+
// two concurrent instances sharing the same data directory.
|
|
76
|
+
try { if (_lockFd !== null) { fs.closeSync(_lockFd); _lockFd = null } } catch {}
|
|
77
|
+
process.stderr.write(`[server] failed to create server.lock (${err?.code ?? err?.message}); exiting.\n`)
|
|
78
|
+
process.exit(1)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// EEXIST — inspect the current owner.
|
|
83
|
+
const _LOCK_RETRIES = 3
|
|
84
|
+
const _LOCK_RETRY_MS = 50
|
|
85
|
+
for (let attempt = 0; attempt <= _LOCK_RETRIES; attempt++) {
|
|
86
|
+
let existingPid = NaN
|
|
87
|
+
try { existingPid = Number.parseInt(fs.readFileSync(LOCK_PATH, 'utf-8').trim(), 10) } catch {}
|
|
88
|
+
if (Number.isFinite(existingPid) && _isPidAlive(existingPid)) {
|
|
89
|
+
process.stderr.write(`[server] another mixdog instance already running (pid=${existingPid}); exiting.\n`)
|
|
90
|
+
process.exit(0)
|
|
91
|
+
}
|
|
92
|
+
// Stale lock — unlink then re-acquire atomically via 'wx'.
|
|
93
|
+
try { fs.unlinkSync(LOCK_PATH) } catch {}
|
|
94
|
+
try {
|
|
95
|
+
_lockFd = fs.openSync(LOCK_PATH, 'wx')
|
|
96
|
+
fs.writeSync(_lockFd, String(process.pid))
|
|
97
|
+
fs.closeSync(_lockFd)
|
|
98
|
+
_lockFd = null
|
|
99
|
+
return // atomic re-acquire succeeded
|
|
100
|
+
} catch (retryErr) {
|
|
101
|
+
if (retryErr?.code !== 'EEXIST') {
|
|
102
|
+
// Unexpected error on retry — log and abort.
|
|
103
|
+
try { if (_lockFd !== null) { fs.closeSync(_lockFd); _lockFd = null } } catch {}
|
|
104
|
+
process.stderr.write(`[server] unexpected lock error on retry (${retryErr?.code ?? retryErr?.message}); exiting.\n`)
|
|
105
|
+
process.exit(1)
|
|
106
|
+
}
|
|
107
|
+
// Another process beat us to 'wx' — wait and retry.
|
|
108
|
+
if (attempt < _LOCK_RETRIES) {
|
|
109
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, _LOCK_RETRY_MS)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// All retries exhausted — another process won the race.
|
|
114
|
+
let finalPid = NaN
|
|
115
|
+
try { finalPid = Number.parseInt(fs.readFileSync(LOCK_PATH, 'utf-8').trim(), 10) } catch {}
|
|
116
|
+
process.stderr.write(`[server] failed to acquire server.lock after ${_LOCK_RETRIES} retries (owner pid=${finalPid}); exiting.\n`)
|
|
117
|
+
process.exit(0)
|
|
118
|
+
}
|
|
119
|
+
_acquireLock()
|
|
120
|
+
|
|
121
|
+
// ── Process-level crash guards (mirror CC gracefulShutdown.ts:301/313) ──
|
|
122
|
+
// uncaughtException: best-effort append to crash log then exit(1).
|
|
123
|
+
// unhandledRejection: log-only; do NOT exit (avoid killing supervisor on
|
|
124
|
+
// transient promise rejections).
|
|
125
|
+
// Soft-net policy: log only. server-main.mjs installs its own
|
|
126
|
+
// uncaughtException listener (around line 594) that classifies recoverable
|
|
127
|
+
// vs fatal conditions and only exits on the fatal subset. Unconditionally
|
|
128
|
+
// exiting here would preempt that policy for any exception that lands
|
|
129
|
+
// before/while server-main loads. Real fatals (EPIPE on stdio, EADDRINUSE,
|
|
130
|
+
// AssertionError, ERR_INTERNAL_ASSERTION) still terminate via the
|
|
131
|
+
// server-main classifier or the explicit stdout/stdin guards there.
|
|
132
|
+
// Pre-load exception: if an exception lands BEFORE server-main has wired
|
|
133
|
+
// its classifier and connected the MCP transport, swallowing it would leave
|
|
134
|
+
// a half-initialised process that neither serves requests nor exits — the
|
|
135
|
+
// supervisor would never restart. server-main sets
|
|
136
|
+
// globalThis.__mixdogServerReady = true immediately after server.connect();
|
|
137
|
+
// any uncaught seen before that flag must exit(1) so the supervisor
|
|
138
|
+
// respawns. Once ready, fall through to soft-net (server-main classifies).
|
|
139
|
+
process.on('uncaughtException', (err) => {
|
|
140
|
+
try {
|
|
141
|
+
const __crashLog = path.join(__runtimeRoot, 'crash.log')
|
|
142
|
+
fs.appendFileSync(__crashLog, `[${new Date().toISOString()}] uncaughtException pid=${process.pid}: ${err && (err.stack || err.message || err)}\n`)
|
|
143
|
+
} catch {}
|
|
144
|
+
try { process.stderr.write(`[server.mjs] uncaughtException: ${err && (err.stack || err.message || err)}\n`) } catch {}
|
|
145
|
+
if (!globalThis.__mixdogServerReady) {
|
|
146
|
+
try { process.stderr.write(`[server.mjs] uncaughtException before server ready — exiting for supervisor restart\n`) } catch {}
|
|
147
|
+
process.exit(1)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
// unhandledRejection is ALWAYS log-only (soft-net), even pre-ready. Many
|
|
151
|
+
// pre-ready code paths use fire-and-forget imports / IPC dispatch that can
|
|
152
|
+
// transiently reject; exiting on those would cause supervisor restart
|
|
153
|
+
// loops during normal boot. Only uncaughtException keeps the pre-ready
|
|
154
|
+
// exit(1) gate above — that path indicates a synchronous throw with no
|
|
155
|
+
// promise owner, which truly cannot be recovered.
|
|
156
|
+
process.on('unhandledRejection', (reason) => {
|
|
157
|
+
try {
|
|
158
|
+
const __crashLog = path.join(__runtimeRoot, 'crash.log')
|
|
159
|
+
fs.appendFileSync(__crashLog, `[${new Date().toISOString()}] unhandledRejection pid=${process.pid}: ${reason && (reason.stack || reason.message || reason)}\n`)
|
|
160
|
+
} catch {}
|
|
161
|
+
try { process.stderr.write(`[server.mjs] unhandledRejection: ${reason && (reason.stack || reason.message || reason)}\n`) } catch {}
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
const _releaseLock = () => {
|
|
165
|
+
try {
|
|
166
|
+
const raw = fs.readFileSync(LOCK_PATH, 'utf-8').trim()
|
|
167
|
+
if (Number.parseInt(raw, 10) === process.pid) fs.unlinkSync(LOCK_PATH)
|
|
168
|
+
} catch {}
|
|
169
|
+
// Clean up active-instance.json if we own it (graceful shutdown is
|
|
170
|
+
// normally handled by channels' clearActiveInstance; this guards crash / early-exit cases).
|
|
171
|
+
try {
|
|
172
|
+
const __af = path.join(__runtimeRoot, 'active-instance.json')
|
|
173
|
+
const cur = JSON.parse(fs.readFileSync(__af, 'utf-8'))
|
|
174
|
+
const curServerPid = __parsePositivePid(cur?.server_pid)
|
|
175
|
+
if (cur && (cur.instanceId === String(process.pid) || curServerPid === process.pid)) fs.unlinkSync(__af)
|
|
176
|
+
} catch {}
|
|
177
|
+
}
|
|
178
|
+
process.on('exit', _releaseLock)
|
|
179
|
+
// Do NOT call process.exit() directly on SIGTERM/SIGINT — that preempts the
|
|
180
|
+
// graceful shutdown chain in server-main.mjs (worker ACK, etc.).
|
|
181
|
+
// _releaseLock is registered on 'exit' above so it always runs; the signal
|
|
182
|
+
// handlers here just ensure the lock file is released even when server-main's
|
|
183
|
+
// shutdown() calls process.exit(), which itself fires 'exit'. SIGHUP/SIGBREAK
|
|
184
|
+
// are non-standard on Windows; keep them as exit-only guards.
|
|
185
|
+
for (const sig of ['SIGHUP', 'SIGBREAK']) {
|
|
186
|
+
try { process.on(sig, () => { _releaseLock(); process.exit(0) }) } catch {}
|
|
187
|
+
}
|
|
188
|
+
// SIGTERM and SIGINT: let server-main's handlers own graceful shutdown.
|
|
189
|
+
// _releaseLock fires automatically via 'exit' when server-main calls process.exit().
|
|
190
|
+
|
|
191
|
+
// ── Graceful supervisor-control shutdown (fd-3) ──────────────────
|
|
192
|
+
// Supervisor (run-mcp.mjs) sends "shutdown\n" or closes fd-3 to request
|
|
193
|
+
// graceful shutdown. fd-3 is dedicated to lifecycle control and is
|
|
194
|
+
// independent of MCP stdio transport — so transient stdin events from
|
|
195
|
+
// the MCP host can never trigger shutdown. Registered before
|
|
196
|
+
// server-main loads so no boot window is missed; server-main re-reads
|
|
197
|
+
// __mixdogSupervisorShutdownRequested when its shutdown() is wired.
|
|
198
|
+
const SUPERVISOR_CONTROL_FD = Number(process.env.MIXDOG_SUPERVISOR_CONTROL_FD || '0') || 0;
|
|
199
|
+
if (SUPERVISOR_CONTROL_FD > 0) {
|
|
200
|
+
try {
|
|
201
|
+
const ctrl = fs.createReadStream(null, { fd: SUPERVISOR_CONTROL_FD });
|
|
202
|
+
ctrl.setEncoding('utf8');
|
|
203
|
+
let ctrlBuf = '';
|
|
204
|
+
const fire = () => {
|
|
205
|
+
if (typeof globalThis.__mixdogShutdownFromSupervisor === 'function') {
|
|
206
|
+
globalThis.__mixdogShutdownFromSupervisor();
|
|
207
|
+
} else {
|
|
208
|
+
globalThis.__mixdogSupervisorShutdownRequested = true;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
ctrl.on('data', (chunk) => {
|
|
212
|
+
ctrlBuf += chunk;
|
|
213
|
+
if (ctrlBuf.includes('shutdown')) fire();
|
|
214
|
+
});
|
|
215
|
+
ctrl.on('end', fire);
|
|
216
|
+
// Swallow stream errors so the supervisor stays up; control fd errors
|
|
217
|
+
// are non-fatal — graceful shutdown will still arrive via SIGTERM/SIGINT.
|
|
218
|
+
ctrl.on('error', () => {});
|
|
219
|
+
} catch (e) {
|
|
220
|
+
process.stderr.write(`[server.mjs] supervisor control fd setup failed: ${e && e.message}\n`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ── Preload/refresh active-instance.json when safe ──────────────────
|
|
225
|
+
// Same schema as channels' buildActiveInstanceState. instanceId follows
|
|
226
|
+
// makeInstanceId() = String(pid). (Sanitize regex /[^a-zA-Z0-9._-]/g
|
|
227
|
+
// is a no-op for numeric PIDs.)
|
|
228
|
+
const __activeDir = __runtimeRoot
|
|
229
|
+
fs.mkdirSync(__activeDir, { recursive: true })
|
|
230
|
+
const __activeFile = path.join(__activeDir, 'active-instance.json')
|
|
231
|
+
const __instanceId = String(process.pid)
|
|
232
|
+
const __multiInstanceMode = process.env.MIXDOG_MULTI_INSTANCE === '1'
|
|
233
|
+
const __renameRetryCodes = new Set(['EPERM', 'EBUSY', 'EACCES', 'EEXIST'])
|
|
234
|
+
function __sleepSync(ms) {
|
|
235
|
+
try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms) }
|
|
236
|
+
catch {
|
|
237
|
+
const deadline = Date.now() + ms
|
|
238
|
+
while (Date.now() < deadline) {}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function __renameSyncWithRetry(src, dest) {
|
|
242
|
+
const waits = [20, 50, 100, 200, 400, 800]
|
|
243
|
+
for (let i = 0; i <= waits.length; i++) {
|
|
244
|
+
try {
|
|
245
|
+
fs.renameSync(src, dest)
|
|
246
|
+
return
|
|
247
|
+
} catch (err) {
|
|
248
|
+
if (!__renameRetryCodes.has(err?.code) || i === waits.length) throw err
|
|
249
|
+
__sleepSync(waits[i])
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Split-brain guard. In single-instance mode, server.lock proves we are the
|
|
255
|
+
// owner and this prelude may refresh active-instance.json. In multi-instance
|
|
256
|
+
// mode, server.lock is intentionally skipped, so the prelude must not create
|
|
257
|
+
// or steal ownership; channels' ownership loop is the only writer that may
|
|
258
|
+
// claim a new active owner.
|
|
259
|
+
let __shouldWriteActiveInstance = !__multiInstanceMode
|
|
260
|
+
try {
|
|
261
|
+
if (fs.existsSync(__activeFile)) {
|
|
262
|
+
const prev = JSON.parse(fs.readFileSync(__activeFile, 'utf-8'))
|
|
263
|
+
const prevOwnerPid = __parsePositivePid(prev?.ownerLeadPid)
|
|
264
|
+
?? __parsePositivePid(prev?.terminalLeadPid)
|
|
265
|
+
?? __parsePositivePid(prev?.supervisor_pid)
|
|
266
|
+
?? __parsePositivePid(prev?.instanceId)
|
|
267
|
+
let prevOwnerLive = false
|
|
268
|
+
if (prevOwnerPid) {
|
|
269
|
+
try { process.kill(prevOwnerPid, 0); prevOwnerLive = true }
|
|
270
|
+
catch (err) { prevOwnerLive = err?.code === 'EPERM' }
|
|
271
|
+
}
|
|
272
|
+
if (__multiInstanceMode) {
|
|
273
|
+
__shouldWriteActiveInstance = prevOwnerPid === __ownerLeadPid
|
|
274
|
+
if (!__shouldWriteActiveInstance && prevOwnerPid && prevOwnerLive) {
|
|
275
|
+
process.stderr.write(
|
|
276
|
+
`[server] active owner lead=${prevOwnerPid} is live; ` +
|
|
277
|
+
`starting standby lead=${__ownerLeadPid} without overwriting active-instance.json\n`
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
} else if (prevOwnerPid && prevOwnerPid !== __ownerLeadPid && prevOwnerLive) {
|
|
281
|
+
process.stderr.write(
|
|
282
|
+
`[server] split-brain warning: active-instance.json points to live owner PID ${prevOwnerPid} ` +
|
|
283
|
+
`but server.lock is ours — overwriting\n`
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} catch {
|
|
288
|
+
// Malformed active-instance.json should not let a multi-instance prelude
|
|
289
|
+
// seize ownership. channels will treat it as unreadable and claim only if
|
|
290
|
+
// this terminal is actually a channel owner.
|
|
291
|
+
__shouldWriteActiveInstance = !__multiInstanceMode
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const __nowMs = Date.now()
|
|
295
|
+
if (__shouldWriteActiveInstance) {
|
|
296
|
+
// Atomic write: write to .tmp then rename so readers never observe partial JSON.
|
|
297
|
+
// POSIX rename is atomic per directory entry; on Windows fs.renameSync uses
|
|
298
|
+
// MoveFileEx with REPLACE_EXISTING which is atomic-equivalent for same-volume.
|
|
299
|
+
const __activeTmp = `${__activeFile}.${process.pid}.tmp`
|
|
300
|
+
// B1: read-modify-write so pg_* fields written by supervisor-pg.mjs survive
|
|
301
|
+
// the initial prelude write. Spread order: cur first so new fields win.
|
|
302
|
+
const __curActive = (() => { try { return JSON.parse(fs.readFileSync(__activeFile, 'utf-8')) } catch { return {} } })()
|
|
303
|
+
// Strip stale field names written by older server versions; no callers — destructured to discard only.
|
|
304
|
+
const { pid: _legacyPid, startedAt: _legacyStartedAt, httpPort: _legacyHttpPort, ...__curActiveRest } = __curActive ?? {}
|
|
305
|
+
fs.writeFileSync(__activeTmp, JSON.stringify({
|
|
306
|
+
...__curActiveRest,
|
|
307
|
+
instanceId: __instanceId,
|
|
308
|
+
ownerLeadPid: __ownerLeadPid,
|
|
309
|
+
terminalLeadPid: __ownerLeadPid,
|
|
310
|
+
supervisor_pid: __ownerLeadPid,
|
|
311
|
+
server_pid: process.pid,
|
|
312
|
+
supervisor_started_at: __nowMs,
|
|
313
|
+
updatedAt: __nowMs,
|
|
314
|
+
turnEndFile: path.join(__activeDir, `turn-end-${__instanceId}`),
|
|
315
|
+
statusFile: path.join(__activeDir, `status-${__instanceId}.json`),
|
|
316
|
+
backendReady: false,
|
|
317
|
+
bootPhase: 'prelude',
|
|
318
|
+
}))
|
|
319
|
+
__renameSyncWithRetry(__activeTmp, __activeFile)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ── Supervisor cache-version advert ────────────────────────────────
|
|
323
|
+
// dev-sync reads this to know which cache version is in active use without
|
|
324
|
+
// having to introspect supervisor cmdline. Lives in server.mjs (not the
|
|
325
|
+
// proxy supervisor run-mcp.mjs) so future updates land via child-only
|
|
326
|
+
// restart and never sever the stdio bridge. Validity is verified at read
|
|
327
|
+
// time (pid alive + cacheDir exists) so a stale file from a dead supervisor
|
|
328
|
+
// is naturally rejected — no explicit cleanup required.
|
|
329
|
+
//
|
|
330
|
+
// run-mcp.mjs passes its identity (pid + cacheDir) via env vars at spawn.
|
|
331
|
+
// If they're missing (e.g. someone runs server.mjs standalone without the
|
|
332
|
+
// supervisor wrapper), skip — there's no supervisor to advertise.
|
|
333
|
+
try {
|
|
334
|
+
const _supPid = Number(process.env.MIXDOG_SUPERVISOR_PID)
|
|
335
|
+
const _supCacheDir = process.env.MIXDOG_SUPERVISOR_CACHE_DIR
|
|
336
|
+
if (Number.isFinite(_supPid) && _supPid > 0 && _supCacheDir) {
|
|
337
|
+
const _writeAdvert = (_file, _payload) => {
|
|
338
|
+
const _tmp = `${_file}.${process.pid}.${Date.now()}.tmp`
|
|
339
|
+
fs.writeFileSync(_tmp, JSON.stringify(_payload))
|
|
340
|
+
__renameSyncWithRetry(_tmp, _file)
|
|
341
|
+
}
|
|
342
|
+
const _readAdvert = (_file) => {
|
|
343
|
+
try { return JSON.parse(fs.readFileSync(_file, 'utf8')) } catch { return null }
|
|
344
|
+
}
|
|
345
|
+
const _advertFile = path.join(__activeDir, 'supervisor-advert.json')
|
|
346
|
+
const _advertByPidFile = path.join(__activeDir, `supervisor-advert.${_supPid}.json`)
|
|
347
|
+
const _advertIndexFile = path.join(__activeDir, 'supervisor-adverts.json')
|
|
348
|
+
const _supVer = (() => {
|
|
349
|
+
try { return JSON.parse(fs.readFileSync(path.join(_supCacheDir, '.claude-plugin', 'plugin.json'), 'utf8')).version }
|
|
350
|
+
catch { return null }
|
|
351
|
+
})()
|
|
352
|
+
const _record = {
|
|
353
|
+
pid: _supPid,
|
|
354
|
+
ownerLeadPid: _supPid,
|
|
355
|
+
serverPid: process.pid,
|
|
356
|
+
cacheDir: _supCacheDir,
|
|
357
|
+
version: _supVer,
|
|
358
|
+
startedAt: __nowMs,
|
|
359
|
+
updatedAt: Date.now(),
|
|
360
|
+
}
|
|
361
|
+
_writeAdvert(_advertFile, _record)
|
|
362
|
+
_writeAdvert(_advertByPidFile, _record)
|
|
363
|
+
|
|
364
|
+
const _recordsByPid = new Map()
|
|
365
|
+
for (const _ent of fs.readdirSync(__activeDir, { withFileTypes: true })) {
|
|
366
|
+
if (!_ent.isFile()) continue
|
|
367
|
+
const _name = _ent.name
|
|
368
|
+
if (!/^supervisor-advert(?:\.\d+)?\.json$/.test(_name)) continue
|
|
369
|
+
const _entry = _readAdvert(path.join(__activeDir, _name))
|
|
370
|
+
const _pid = __parsePositivePid(_entry?.ownerLeadPid) ?? __parsePositivePid(_entry?.pid)
|
|
371
|
+
if (!_pid || !_isPidAlive(_pid) || !_entry?.cacheDir || !fs.existsSync(_entry.cacheDir)) {
|
|
372
|
+
if (_name !== 'supervisor-advert.json') {
|
|
373
|
+
try { fs.unlinkSync(path.join(__activeDir, _name)) } catch {}
|
|
374
|
+
}
|
|
375
|
+
continue
|
|
376
|
+
}
|
|
377
|
+
_recordsByPid.set(_pid, { ..._entry, pid: _pid, ownerLeadPid: _pid })
|
|
378
|
+
}
|
|
379
|
+
_recordsByPid.set(_supPid, _record)
|
|
380
|
+
const _supervisors = [..._recordsByPid.values()].sort((a, b) => Number(a.pid) - Number(b.pid))
|
|
381
|
+
_writeAdvert(_advertIndexFile, { updatedAt: Date.now(), supervisors: _supervisors })
|
|
382
|
+
if (_supervisors.length > 1) {
|
|
383
|
+
process.stderr.write(
|
|
384
|
+
`[server] multi-supervisor notice: active supervisors count=${_supervisors.length} ` +
|
|
385
|
+
`pids=${_supervisors.map(s => s.pid).join(',')}\n`
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
} catch (e) {
|
|
390
|
+
process.stderr.write(`[server] supervisor advert write failed: ${e && e.message}\n`)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// ── Stale status-*.json sweep ──────────────────────────────────────
|
|
394
|
+
// `status-${pid}.json` files outlive their owning supervisor when the
|
|
395
|
+
// process exits without graceful shutdown (crash, SIGKILL, host reboot).
|
|
396
|
+
// Without sweep they accumulate indefinitely and bloat list responses
|
|
397
|
+
// over the runtime dir. Invariant: a status file is stale iff its PID
|
|
398
|
+
// is no longer alive. Live-PID files belong to peer instances and are
|
|
399
|
+
// left untouched. _isPidAlive is the same check used for server.lock.
|
|
400
|
+
try {
|
|
401
|
+
const _entries = fs.readdirSync(__activeDir, { withFileTypes: true })
|
|
402
|
+
for (const _ent of _entries) {
|
|
403
|
+
if (!_ent.isFile()) continue
|
|
404
|
+
const _name = _ent.name
|
|
405
|
+
if (!_name.startsWith('status-') || !_name.endsWith('.json')) continue
|
|
406
|
+
const _pid = Number.parseInt(_name.slice(7, -5), 10)
|
|
407
|
+
if (!Number.isFinite(_pid) || _pid <= 0) continue
|
|
408
|
+
if (_isPidAlive(_pid)) continue
|
|
409
|
+
try { fs.unlinkSync(path.join(__activeDir, _name)) } catch {}
|
|
410
|
+
}
|
|
411
|
+
} catch (e) {
|
|
412
|
+
process.stderr.write(`[server] stale status sweep failed: ${e && e.message}\n`)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ── Bulk log rotation at boot ──────────────────────────────────────
|
|
416
|
+
// Worker logs (drop-trace, memory-worker, channels-worker, mcp-debug,
|
|
417
|
+
// webhook, schedule, event, statusline-trace, history/*.jsonl) outlive
|
|
418
|
+
// the supervisor process and grow without bound; over time the append
|
|
419
|
+
// fsync churn becomes the dominant background IO. Rotate each past
|
|
420
|
+
// 5 MB by renaming to `.1` (single archive; subsequent boots overwrite
|
|
421
|
+
// the archive). Invariant: rotation only at supervisor boot — worker
|
|
422
|
+
// processes have not started yet so there is no append-collision
|
|
423
|
+
// window.
|
|
424
|
+
const LOG_ROTATE_THRESHOLD = 5 * 1024 * 1024
|
|
425
|
+
const _logsToRotate = [
|
|
426
|
+
'drop-trace.log',
|
|
427
|
+
'memory-worker.log',
|
|
428
|
+
'channels-worker.log',
|
|
429
|
+
'mcp-debug.log',
|
|
430
|
+
'webhook.log',
|
|
431
|
+
'schedule.log',
|
|
432
|
+
'event.log',
|
|
433
|
+
'statusline-trace.log',
|
|
434
|
+
'history/model-profile.jsonl',
|
|
435
|
+
]
|
|
436
|
+
for (const _rel of _logsToRotate) {
|
|
437
|
+
try {
|
|
438
|
+
const _full = path.join(PLUGIN_DATA, _rel)
|
|
439
|
+
const _st = fs.statSync(_full)
|
|
440
|
+
if (_st.size > LOG_ROTATE_THRESHOLD) {
|
|
441
|
+
const _archive = _full + '.1'
|
|
442
|
+
try { fs.unlinkSync(_archive) } catch {}
|
|
443
|
+
fs.renameSync(_full, _archive)
|
|
444
|
+
}
|
|
445
|
+
} catch { /* missing or stat failure — skip */ }
|
|
446
|
+
}
|
|
447
|
+
// Also rotate scoped per-Lead-per-server mcp-debug.<lead>.<pid>.log files.
|
|
448
|
+
// server-main.mjs emits these alongside the fixed mcp-debug.log; without
|
|
449
|
+
// scoped rotation they grow unbounded across restarts of the same Lead.
|
|
450
|
+
try {
|
|
451
|
+
for (const _ent of fs.readdirSync(PLUGIN_DATA, { withFileTypes: true })) {
|
|
452
|
+
if (!_ent.isFile()) continue
|
|
453
|
+
const _name = _ent.name
|
|
454
|
+
if (!/^mcp-debug\.\d+\.\d+\.log$/.test(_name)) continue
|
|
455
|
+
const _full = path.join(PLUGIN_DATA, _name)
|
|
456
|
+
try {
|
|
457
|
+
const _st = fs.statSync(_full)
|
|
458
|
+
if (_st.size > LOG_ROTATE_THRESHOLD) {
|
|
459
|
+
const _archive = _full + '.1'
|
|
460
|
+
try { fs.unlinkSync(_archive) } catch {}
|
|
461
|
+
fs.renameSync(_full, _archive)
|
|
462
|
+
}
|
|
463
|
+
} catch {}
|
|
464
|
+
}
|
|
465
|
+
} catch { /* readdir failed — skip */ }
|
|
466
|
+
|
|
467
|
+
// ── Enter the main server ───────────────────────────────────────────
|
|
468
|
+
await import('./server-main.mjs')
|