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,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Status HTTP server (forked child process).
|
|
3
|
+
*
|
|
4
|
+
* A tiny loopback-only HTTP server that exposes GET /bridge/status for
|
|
5
|
+
* statusline consumers. Lives in its OWN process (forked by server.mjs
|
|
6
|
+
* at boot) so bursty MCP tool activity in the parent can't starve the
|
|
7
|
+
* statusline's short 1-second curl timeout.
|
|
8
|
+
*
|
|
9
|
+
* Binds to an ephemeral port on 127.0.0.1 and advertises the port via
|
|
10
|
+
* ~/.claude/mixdog-status.json so bin/statusline.sh can discover it
|
|
11
|
+
* without hard-coded port numbers.
|
|
12
|
+
*
|
|
13
|
+
* Can be used two ways:
|
|
14
|
+
* 1. Imported: `startStatusServer({ dataDir, advertisePath, log })` —
|
|
15
|
+
* kept as a named export for tests / ad-hoc in-process hosting.
|
|
16
|
+
* 2. Spawned directly via `fork('src/status/server.mjs')` — config is
|
|
17
|
+
* read from MIXDOG_STATUS_DATA_DIR / MIXDOG_STATUS_ADVERTISE_PATH
|
|
18
|
+
* env vars. The parent kills this process at shutdown.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import http from 'http';
|
|
22
|
+
import { unlinkSync, mkdirSync, existsSync } from 'fs';
|
|
23
|
+
import { readdir as readdirAsync, readFile as readFileAsync, unlink as unlinkAsync } from 'fs/promises';
|
|
24
|
+
import { basename, dirname, join } from 'path';
|
|
25
|
+
import { pathToFileURL } from 'url';
|
|
26
|
+
import { buildBridgeStatus, buildBridgeStatusCompact, renderBridgeStatusText } from './aggregator.mjs';
|
|
27
|
+
import { writeJsonAtomicSync } from '../shared/atomic-file.mjs';
|
|
28
|
+
|
|
29
|
+
const OWNER_SESSION_ID = process.env.MIXDOG_OWNER_SESSION_ID || null;
|
|
30
|
+
const OWNER_LEAD_PID = (() => {
|
|
31
|
+
const pid = Number(process.env.MIXDOG_OWNER_LEAD_PID);
|
|
32
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
33
|
+
})();
|
|
34
|
+
const OWNER_HOST_PID = (() => {
|
|
35
|
+
const pid = Number(process.env.MIXDOG_OWNER_HOST_PID);
|
|
36
|
+
return Number.isFinite(pid) && pid > 0 ? pid : null;
|
|
37
|
+
})();
|
|
38
|
+
|
|
39
|
+
// Stale-session sweep. saveSession() never unlinks closed sessions, so the
|
|
40
|
+
// sessions/ directory grows without bound for the lifetime of the install.
|
|
41
|
+
// The aggregator scan cost is O(N) per statusline poll; once N reaches the
|
|
42
|
+
// hundreds, the scan saturates a CPU core. Closed sessions older than 30 min
|
|
43
|
+
// have no consumer (active filter uses .hb heartbeats; recent-completed
|
|
44
|
+
// window is 30 min) so removing them is safe.
|
|
45
|
+
const SESSION_SWEEP_INTERVAL_MS = 60 * 60 * 1000;
|
|
46
|
+
const SESSION_STALE_MS = 30 * 60 * 1000;
|
|
47
|
+
|
|
48
|
+
// Shared sweep helper. Both stale-session and stale-advert sweepers share
|
|
49
|
+
// the same shape: readdir the directory, fan out per-file work via
|
|
50
|
+
// Promise.all (parallel scan keeps a large dir from blocking the first
|
|
51
|
+
// status response after boot), swallow per-file errors so one corrupt
|
|
52
|
+
// file does not abort the batch, count removals, log only when >0.
|
|
53
|
+
// `perFile` returns 1 when a file was removed, 0 otherwise.
|
|
54
|
+
async function sweepDir(dir, perFile, log, message) {
|
|
55
|
+
if (!existsSync(dir)) return;
|
|
56
|
+
let names;
|
|
57
|
+
try { names = await readdirAsync(dir); } catch { return; }
|
|
58
|
+
const results = await Promise.all(names.map((f) => perFile(f)));
|
|
59
|
+
const removed = results.reduce((a, b) => a + b, 0);
|
|
60
|
+
if (removed > 0) log(`[status-server] sweep: removed ${removed} ${message}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function sweepStaleSessions(sessionsDir, log) {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
await sweepDir(sessionsDir, async (f) => {
|
|
66
|
+
if (!f.endsWith('.json')) return 0;
|
|
67
|
+
const full = join(sessionsDir, f);
|
|
68
|
+
try {
|
|
69
|
+
const j = JSON.parse(await readFileAsync(full, 'utf-8'));
|
|
70
|
+
// Anchor stale-age on the terminal updatedAt (or closedAt), not
|
|
71
|
+
// createdAt: a long-lived bridge session that closed just before a
|
|
72
|
+
// sweep would otherwise be removed inside the aggregator's 30 min
|
|
73
|
+
// recent-completed window.
|
|
74
|
+
const terminalAt = Number.isFinite(j.updatedAt) ? j.updatedAt
|
|
75
|
+
: (Number.isFinite(j.closedAt) ? j.closedAt : null);
|
|
76
|
+
if (j.closed === true && terminalAt !== null && (now - terminalAt) > SESSION_STALE_MS) {
|
|
77
|
+
await unlinkAsync(full);
|
|
78
|
+
try { await unlinkAsync(full.replace(/\.json$/, '.hb')); } catch {}
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
} catch { /* skip corrupt */ }
|
|
82
|
+
return 0;
|
|
83
|
+
}, log, 'stale closed sessions');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Advert directory sweep. Each forked status-server writes its own
|
|
87
|
+
// {session_id}.json into mixdog-status/. Crashes / SIGKILL / abrupt
|
|
88
|
+
// supervisor restarts can leave files behind whose pid is no longer
|
|
89
|
+
// alive. statusline.sh greps every advert each tick for cc_session_id
|
|
90
|
+
// pairing, so accumulation linearly slows every refresh. Invariant: a
|
|
91
|
+
// live owner is the only writer of its advert, so a pid whose process
|
|
92
|
+
// is gone proves the advert is dead. process.kill(pid, 0) returns ESRCH
|
|
93
|
+
// for non-existent pids on POSIX and Windows alike.
|
|
94
|
+
async function sweepStaleAdverts(advertisePath, log) {
|
|
95
|
+
const dir = dirname(advertisePath);
|
|
96
|
+
await sweepDir(dir, async (f) => {
|
|
97
|
+
const full = join(dir, f);
|
|
98
|
+
if (full === advertisePath) return 0;
|
|
99
|
+
// .cc-<sessionId>.path mapping files: orphan when the referenced
|
|
100
|
+
// advert is gone. Invariant: mapping body is the advert path, so a
|
|
101
|
+
// missing target proves the mapping is stale and safe to remove.
|
|
102
|
+
if (f.startsWith('.cc-') && f.endsWith('.path')) {
|
|
103
|
+
try {
|
|
104
|
+
const target = (await readFileAsync(full, 'utf-8')).trim();
|
|
105
|
+
if (target && !existsSync(target)) { await unlinkAsync(full); return 1; }
|
|
106
|
+
} catch { /* skip */ }
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
if (!f.endsWith('.json')) return 0;
|
|
110
|
+
try {
|
|
111
|
+
const j = JSON.parse(await readFileAsync(full, 'utf-8'));
|
|
112
|
+
const pid = Number(j.pid);
|
|
113
|
+
if (!Number.isFinite(pid) || pid <= 0) return 0;
|
|
114
|
+
try { process.kill(pid, 0); return 0; }
|
|
115
|
+
catch (e) {
|
|
116
|
+
if (e.code === 'ESRCH') { await unlinkAsync(full); return 1; }
|
|
117
|
+
}
|
|
118
|
+
} catch { /* skip corrupt */ }
|
|
119
|
+
return 0;
|
|
120
|
+
}, log, 'dead advert/mapping files');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function writeAdvertisement(path, record) {
|
|
124
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
125
|
+
writeJsonAtomicSync(path, record, { lock: true });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function sanitizeCcId(ccId) {
|
|
129
|
+
return String(ccId).replace(/[^a-zA-Z0-9-]/g, '-');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function perCcAdvertPath(beaconPath, ccId) {
|
|
133
|
+
const dir = dirname(beaconPath);
|
|
134
|
+
const base = basename(beaconPath, '.json');
|
|
135
|
+
return join(dir, `${base}-cc-${sanitizeCcId(ccId)}.json`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function beaconRecord(record) {
|
|
139
|
+
if (!record || typeof record !== 'object') return record;
|
|
140
|
+
const { cc_session_id: _drop, ...rest } = record;
|
|
141
|
+
return rest;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function claimAdvertPath(claim) {
|
|
145
|
+
if (typeof claim === 'string' && claim) return claim;
|
|
146
|
+
const p = claim?.path;
|
|
147
|
+
return typeof p === 'string' && p ? p : null;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function claimClientHostPid(claim) {
|
|
151
|
+
const pid = Number(claim?.clientHostPid);
|
|
152
|
+
return Number.isFinite(pid) && pid > 0 ? pid : 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const recapState = { state: 'idle', running: false, startedAt: null, lastCompletedAt: null, updatedAt: null, errorMessage: null };
|
|
156
|
+
|
|
157
|
+
function normalizeTimestamp(value) {
|
|
158
|
+
if (value === null || value === undefined || value === '') return null;
|
|
159
|
+
const n = Number(value);
|
|
160
|
+
return Number.isFinite(n) ? n : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function updateRecapState(next = {}) {
|
|
164
|
+
if (typeof next.state === 'string') recapState.state = next.state;
|
|
165
|
+
recapState.running = next.running === true;
|
|
166
|
+
recapState.startedAt = normalizeTimestamp(next.startedAt);
|
|
167
|
+
recapState.lastCompletedAt = normalizeTimestamp(next.lastCompletedAt);
|
|
168
|
+
recapState.updatedAt = normalizeTimestamp(next.updatedAt);
|
|
169
|
+
recapState.errorMessage = typeof next.errorMessage === 'string' ? next.errorMessage : null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readRecapState() {
|
|
173
|
+
return { ...recapState };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export async function startStatusServer({ dataDir, advertisePath, log = () => {}, getRecapState = readRecapState, ownerSessionId = OWNER_SESSION_ID }) {
|
|
177
|
+
// Multi-terminal claim: advertisePath is a permanent unclaimed BEACON
|
|
178
|
+
// (never cc_session_id). Each CC POSTs /register-cc-session and gets a
|
|
179
|
+
// dedicated advert file with cc_session_id set; statusline matches or
|
|
180
|
+
// claims via the beacon per bin/statusline-lib.mjs.
|
|
181
|
+
const claimedCcById = new Map(); // ccId -> { path, clientHostPid }
|
|
182
|
+
const recordRef = { current: null }; // populated after listen() so advert helper can re-emit
|
|
183
|
+
function rewriteAdvert() {
|
|
184
|
+
if (!recordRef.current) return;
|
|
185
|
+
const beacon = beaconRecord(recordRef.current);
|
|
186
|
+
try { writeAdvertisement(advertisePath, beacon); }
|
|
187
|
+
catch (e) { log(`[status-server] advert rewrite failed: ${e.message}`); }
|
|
188
|
+
for (const [ccId, claim] of claimedCcById) {
|
|
189
|
+
const perPath = claimAdvertPath(claim);
|
|
190
|
+
const clientHostPid = claimClientHostPid(claim);
|
|
191
|
+
if (!perPath) continue;
|
|
192
|
+
try {
|
|
193
|
+
writeAdvertisement(perPath, {
|
|
194
|
+
...beacon,
|
|
195
|
+
cc_session_id: ccId,
|
|
196
|
+
...(clientHostPid > 0 ? { clientHostPid } : {}),
|
|
197
|
+
});
|
|
198
|
+
} catch (e) {
|
|
199
|
+
log(`[status-server] per-cc advert rewrite failed (${ccId}): ${e.message}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const server = http.createServer(async (req, res) => {
|
|
204
|
+
// Loopback-only: reject non-localhost peers (belt-and-braces —
|
|
205
|
+
// binding to 127.0.0.1 should already exclude them).
|
|
206
|
+
const remote = req.socket?.remoteAddress || '';
|
|
207
|
+
const isLoopback = remote === '127.0.0.1' || remote === '::1' || remote === '::ffff:127.0.0.1';
|
|
208
|
+
if (!isLoopback) {
|
|
209
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
210
|
+
res.end(JSON.stringify({ ok: false, error: 'forbidden' }));
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
216
|
+
if (req.method === 'GET' && url.pathname === '/bridge/status') {
|
|
217
|
+
const fmt = url.searchParams.get('format');
|
|
218
|
+
const wantText = fmt === 'text'
|
|
219
|
+
|| (req.headers['accept'] || '').includes('text/plain');
|
|
220
|
+
const wantCompact = fmt === 'statusline-json';
|
|
221
|
+
const filterClientHostPid = Number(url.searchParams.get('clientHostPid')) || undefined;
|
|
222
|
+
const payload = wantCompact
|
|
223
|
+
? await buildBridgeStatusCompact(dataDir, { ownerSessionId, clientHostPid: filterClientHostPid, ownerHostPid: OWNER_HOST_PID })
|
|
224
|
+
: await buildBridgeStatus(dataDir, { recap: getRecapState(), ownerSessionId, clientHostPid: filterClientHostPid, ownerHostPid: OWNER_HOST_PID });
|
|
225
|
+
if (wantText) {
|
|
226
|
+
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
|
|
227
|
+
res.end(renderBridgeStatusText(payload));
|
|
228
|
+
} else {
|
|
229
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
230
|
+
res.end(JSON.stringify(payload));
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (req.method === 'POST' && url.pathname === '/register-cc-session') {
|
|
235
|
+
let body = '';
|
|
236
|
+
// Cap the accumulated body at 16 KiB. The expected payload is a
|
|
237
|
+
// tiny JSON object ({cc_session_id, client_host_pid}); anything
|
|
238
|
+
// larger is either malformed or a memory-exhaustion attempt
|
|
239
|
+
// against the loopback endpoint. Aborting closes the socket and
|
|
240
|
+
// skips the JSON.parse pass.
|
|
241
|
+
const MAX_BODY_BYTES = 16 * 1024;
|
|
242
|
+
let bodyOverflow = false;
|
|
243
|
+
req.on('data', d => {
|
|
244
|
+
if (bodyOverflow) return;
|
|
245
|
+
body += d;
|
|
246
|
+
if (body.length > MAX_BODY_BYTES) {
|
|
247
|
+
bodyOverflow = true;
|
|
248
|
+
res.writeHead(413, { 'Content-Type': 'application/json' });
|
|
249
|
+
res.end(JSON.stringify({ ok: false, error: 'payload too large' }));
|
|
250
|
+
req.destroy();
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
req.on('end', () => {
|
|
254
|
+
if (bodyOverflow) return;
|
|
255
|
+
let parsed = null;
|
|
256
|
+
try { parsed = JSON.parse(body); } catch { /* fall through */ }
|
|
257
|
+
const ccId = String(parsed?.cc_session_id ?? '').trim();
|
|
258
|
+
if (!ccId) {
|
|
259
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
260
|
+
res.end(JSON.stringify({ ok: false, error: 'cc_session_id required' }));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const rawClientHostPid = Number(parsed?.client_host_pid ?? parsed?.clientHostPid ?? 0);
|
|
264
|
+
const clientHostPid = Number.isFinite(rawClientHostPid) && rawClientHostPid > 0 ? rawClientHostPid : 0;
|
|
265
|
+
const perPath = perCcAdvertPath(advertisePath, ccId);
|
|
266
|
+
const existing = claimedCcById.get(ccId);
|
|
267
|
+
const existingPath = claimAdvertPath(existing);
|
|
268
|
+
const existingHostPid = claimClientHostPid(existing);
|
|
269
|
+
if (existingPath && existingPath !== perPath) {
|
|
270
|
+
res.writeHead(409, { 'Content-Type': 'application/json' });
|
|
271
|
+
res.end(JSON.stringify({ ok: false, claimed: ccId, ownerHostPid: OWNER_HOST_PID, clientHostPid: existingHostPid || null }));
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
claimedCcById.set(ccId, { path: perPath, clientHostPid });
|
|
275
|
+
rewriteAdvert();
|
|
276
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
277
|
+
res.end(JSON.stringify({ ok: true, claimed: ccId, ownerHostPid: OWNER_HOST_PID, clientHostPid: clientHostPid || null }));
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
282
|
+
res.end('not found');
|
|
283
|
+
} catch (e) {
|
|
284
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
285
|
+
res.end(JSON.stringify({ error: String(e?.message || e) }));
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await new Promise((resolve, reject) => {
|
|
290
|
+
server.once('error', reject);
|
|
291
|
+
server.listen(0, '127.0.0.1', () => {
|
|
292
|
+
server.removeListener('error', reject);
|
|
293
|
+
resolve();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const address = server.address();
|
|
298
|
+
const port = typeof address === 'object' && address ? address.port : 0;
|
|
299
|
+
|
|
300
|
+
const record = {
|
|
301
|
+
pid: process.pid,
|
|
302
|
+
port,
|
|
303
|
+
startedAt: Date.now(),
|
|
304
|
+
...ownerSessionId ? { ownerSessionId } : {},
|
|
305
|
+
...OWNER_LEAD_PID ? { ownerLeadPid: OWNER_LEAD_PID } : {},
|
|
306
|
+
...OWNER_HOST_PID ? { ownerHostPid: OWNER_HOST_PID } : {},
|
|
307
|
+
};
|
|
308
|
+
recordRef.current = record; // expose to /register-cc-session handler for in-place cc_session_id append
|
|
309
|
+
try { writeAdvertisement(advertisePath, beaconRecord(record)); }
|
|
310
|
+
catch (e) { log(`[status-server] advertise write failed: ${e.message}`); }
|
|
311
|
+
|
|
312
|
+
log(`[status-server] listening on 127.0.0.1:${port} (advertise=${advertisePath})`);
|
|
313
|
+
|
|
314
|
+
// Self-heal: external cleanup (antivirus, indexer, user tools) can unlink
|
|
315
|
+
// the advert file while the child stays alive. Statusline then logs
|
|
316
|
+
// NOBRIDGE despite a healthy server. Re-write the advert every 30s if
|
|
317
|
+
// missing so any external removal recovers within one tick.
|
|
318
|
+
const advertHeal = setInterval(() => {
|
|
319
|
+
if (!recordRef.current) return;
|
|
320
|
+
const beacon = beaconRecord(recordRef.current);
|
|
321
|
+
if (!existsSync(advertisePath)) {
|
|
322
|
+
try {
|
|
323
|
+
writeAdvertisement(advertisePath, beacon);
|
|
324
|
+
log(`[status-server] advert restored at ${advertisePath}`);
|
|
325
|
+
} catch (e) {
|
|
326
|
+
log(`[status-server] advert restore failed: ${e.message}`);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
for (const [ccId, claim] of claimedCcById) {
|
|
330
|
+
const perPath = claimAdvertPath(claim);
|
|
331
|
+
const clientHostPid = claimClientHostPid(claim);
|
|
332
|
+
if (!perPath) continue;
|
|
333
|
+
if (!existsSync(perPath)) {
|
|
334
|
+
try {
|
|
335
|
+
writeAdvertisement(perPath, {
|
|
336
|
+
...beacon,
|
|
337
|
+
cc_session_id: ccId,
|
|
338
|
+
...(clientHostPid > 0 ? { clientHostPid } : {}),
|
|
339
|
+
});
|
|
340
|
+
log(`[status-server] per-cc advert restored at ${perPath}`);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
log(`[status-server] per-cc advert restore failed (${ccId}): ${e.message}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}, 30000);
|
|
347
|
+
advertHeal.unref?.();
|
|
348
|
+
|
|
349
|
+
sweepStaleAdverts(advertisePath, log);
|
|
350
|
+
const advertSweep = setInterval(() => sweepStaleAdverts(advertisePath, log), SESSION_SWEEP_INTERVAL_MS);
|
|
351
|
+
advertSweep.unref?.();
|
|
352
|
+
|
|
353
|
+
const sessionsDir = join(dataDir, 'sessions');
|
|
354
|
+
sweepStaleSessions(sessionsDir, log);
|
|
355
|
+
const sessionSweep = setInterval(() => sweepStaleSessions(sessionsDir, log), SESSION_SWEEP_INTERVAL_MS);
|
|
356
|
+
sessionSweep.unref?.();
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
port,
|
|
360
|
+
rewriteAdvert,
|
|
361
|
+
close: () => new Promise((resolve) => {
|
|
362
|
+
clearInterval(advertHeal);
|
|
363
|
+
clearInterval(advertSweep);
|
|
364
|
+
clearInterval(sessionSweep);
|
|
365
|
+
try { unlinkSync(advertisePath); } catch {}
|
|
366
|
+
for (const claim of claimedCcById.values()) {
|
|
367
|
+
const perPath = claimAdvertPath(claim);
|
|
368
|
+
if (!perPath) continue;
|
|
369
|
+
try { unlinkSync(perPath); } catch {}
|
|
370
|
+
}
|
|
371
|
+
server.close(() => resolve());
|
|
372
|
+
}),
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// ── Child-process entry point ─────────────────────────────────────────
|
|
377
|
+
// When invoked directly (`node src/status/server.mjs`), read config
|
|
378
|
+
// from env and boot. Parent signals shutdown by disconnecting IPC or
|
|
379
|
+
// sending SIGTERM; either way we tear down the server and exit.
|
|
380
|
+
const isMain = import.meta.url === pathToFileURL(process.argv[1] || '').href;
|
|
381
|
+
if (isMain) {
|
|
382
|
+
const dataDir = process.env.MIXDOG_STATUS_DATA_DIR;
|
|
383
|
+
const advertisePath = process.env.MIXDOG_STATUS_ADVERTISE_PATH;
|
|
384
|
+
if (!dataDir || !advertisePath) {
|
|
385
|
+
process.stderr.write('[status-server] missing MIXDOG_STATUS_DATA_DIR or MIXDOG_STATUS_ADVERTISE_PATH\n');
|
|
386
|
+
process.exit(2);
|
|
387
|
+
}
|
|
388
|
+
const log = (m) => process.stdout.write(m + '\n');
|
|
389
|
+
let handle = null;
|
|
390
|
+
let shuttingDown = false;
|
|
391
|
+
process.on('message', (msg) => {
|
|
392
|
+
if (msg?.type === 'recap_status') {
|
|
393
|
+
updateRecapState(msg.recap);
|
|
394
|
+
try { handle?.rewriteAdvert?.(); } catch {}
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
const shutdown = async (reason) => {
|
|
398
|
+
if (shuttingDown) return;
|
|
399
|
+
shuttingDown = true;
|
|
400
|
+
log(`[status-server] shutdown (${reason})`);
|
|
401
|
+
try { if (handle) await handle.close(); } catch {}
|
|
402
|
+
process.exit(0);
|
|
403
|
+
};
|
|
404
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
405
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
406
|
+
process.on('disconnect', () => shutdown('parent-disconnect'));
|
|
407
|
+
try {
|
|
408
|
+
handle = await startStatusServer({ dataDir, advertisePath, log, ownerSessionId: OWNER_SESSION_ID });
|
|
409
|
+
} catch (e) {
|
|
410
|
+
process.stderr.write(`[status-server] failed to start: ${e && (e.stack || e.message) || e}\n`);
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
}
|