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,507 @@
|
|
|
1
|
+
// launch-core.mjs — importable config-UI launch core.
|
|
2
|
+
//
|
|
3
|
+
// Extracted from launch.mjs so the same alive-probe → stale-server
|
|
4
|
+
// detection → spawn → requestOpen sequence can run BOTH from the CLI
|
|
5
|
+
// shim (launch.mjs, which keeps the Windows console-hide / self-respawn
|
|
6
|
+
// mitigations for direct `bun launch.mjs` invocation) AND from inside the
|
|
7
|
+
// resident MCP server (the `open_config` tool). When the MCP server — a
|
|
8
|
+
// long-lived background process — spawns setup-server with windowsHide:true,
|
|
9
|
+
// no console window is created at all, so the conhost flash the CLI path
|
|
10
|
+
// fights with FFI/wscript shims simply never happens.
|
|
11
|
+
//
|
|
12
|
+
// Import-safe: no top-level side effects, no env mutation at module load.
|
|
13
|
+
// All paths/ports are module constants; per-call state (plugin root/data)
|
|
14
|
+
// is derived inside launchConfigUi().
|
|
15
|
+
|
|
16
|
+
import { spawn, execSync } from 'child_process';
|
|
17
|
+
import { openSync, closeSync, readFileSync, writeSync, writeFileSync, appendFileSync } from 'fs';
|
|
18
|
+
import { join, dirname } from 'path';
|
|
19
|
+
import { fileURLToPath } from 'url';
|
|
20
|
+
import http from 'http';
|
|
21
|
+
import net from 'net';
|
|
22
|
+
import { tmpdir } from 'os';
|
|
23
|
+
import { resolvePluginData } from '../src/shared/plugin-paths.mjs';
|
|
24
|
+
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
const server = join(__dirname, 'setup-server.mjs');
|
|
27
|
+
// Reuse the launcher's own runtime (bun.exe from the CLI shebang, or the
|
|
28
|
+
// bun that runs the MCP server). Spawning setup-server by absolute path lets
|
|
29
|
+
// us drop shell:true on Windows; the cmd.exe wrapper was swallowing the
|
|
30
|
+
// child's stderr and turning real bun startup errors into an empty log.
|
|
31
|
+
const CHILD_INTERPRETER = process.execPath;
|
|
32
|
+
export const PORT = 3458;
|
|
33
|
+
export const CONFIG_UI_URL = `http://localhost:${PORT}`;
|
|
34
|
+
|
|
35
|
+
function ping(timeoutMs = 1500) {
|
|
36
|
+
return new Promise(resolve => {
|
|
37
|
+
// setup-server listens on 127.0.0.1 only. agent:false disables Node's
|
|
38
|
+
// default keepAlive socket pool; Connection: close prevents any keepalive
|
|
39
|
+
// negotiation that could leave half-open sockets between probes.
|
|
40
|
+
const req = http.get({
|
|
41
|
+
host: '127.0.0.1', port: PORT, path: '/',
|
|
42
|
+
agent: false, headers: { Connection: 'close' },
|
|
43
|
+
}, res => { res.resume(); resolve(true); });
|
|
44
|
+
req.on('error', () => resolve(false));
|
|
45
|
+
req.setTimeout(timeoutMs, () => { req.destroy(); resolve(false); });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// TCP-level liveness probe used as a fallback when HTTP ping times out but
|
|
50
|
+
// the server is actually accepting connections. Distinguishes "server is
|
|
51
|
+
// listening but its handler is momentarily slow" from "server is dead" so
|
|
52
|
+
// the launcher doesn't false-trigger spawnServerWithLog and crash on the
|
|
53
|
+
// port collision.
|
|
54
|
+
function tcpOpen(timeoutMs = 300) {
|
|
55
|
+
return new Promise(resolve => {
|
|
56
|
+
const s = net.connect({ host: '127.0.0.1', port: PORT });
|
|
57
|
+
s.setTimeout(timeoutMs);
|
|
58
|
+
s.once('connect', () => { s.destroy(); resolve(true); });
|
|
59
|
+
s.once('timeout', () => { s.destroy(); resolve(false); });
|
|
60
|
+
s.once('error', () => resolve(false));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// GET /api/plugin-path. Returns the absolute plugin root the running
|
|
65
|
+
// setup-server identifies as, or null on any failure. Used to detect a
|
|
66
|
+
// stale or wrong-plugin server still bound to PORT before we hand it the
|
|
67
|
+
// /open call — otherwise a different version's setup window can pop up.
|
|
68
|
+
function fetchPluginPath(timeoutMs = 1500) {
|
|
69
|
+
return new Promise(resolve => {
|
|
70
|
+
const req = http.get(`http://127.0.0.1:${PORT}/api/plugin-path`, res => {
|
|
71
|
+
let body = '';
|
|
72
|
+
res.setEncoding('utf8');
|
|
73
|
+
res.on('data', chunk => { body += chunk; });
|
|
74
|
+
res.on('end', () => {
|
|
75
|
+
try {
|
|
76
|
+
const json = JSON.parse(body);
|
|
77
|
+
resolve(typeof json?.path === 'string' ? json.path : null);
|
|
78
|
+
} catch {
|
|
79
|
+
resolve(null);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
req.on('error', () => resolve(null));
|
|
84
|
+
req.setTimeout(timeoutMs, () => { req.destroy(); resolve(null); });
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Watchdog disabled — returning 0 keeps setup-server alive after the MCP
|
|
89
|
+
// host exits (until the user closes the chrome window). The previous
|
|
90
|
+
// execSync('powershell …') ancestor walk was a major conhost-flash source
|
|
91
|
+
// on every /mixdog:config invocation; removing it eliminated that flash.
|
|
92
|
+
function findAncestorPid() {
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function requestOpen() {
|
|
97
|
+
return new Promise(resolve => {
|
|
98
|
+
const req = http.get(`http://127.0.0.1:${PORT}/open`, res => {
|
|
99
|
+
let body = '';
|
|
100
|
+
res.setEncoding('utf8');
|
|
101
|
+
res.on('data', chunk => { body += chunk; });
|
|
102
|
+
res.on('end', () => {
|
|
103
|
+
try {
|
|
104
|
+
const json = JSON.parse(body);
|
|
105
|
+
resolve(json.ok === true);
|
|
106
|
+
} catch {
|
|
107
|
+
resolve(res.statusCode >= 200 && res.statusCode < 300);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
req.on('error', () => resolve(false));
|
|
112
|
+
// /open now holds the in-process open mutex until the spawned browser is
|
|
113
|
+
// discoverable or its wscript launcher's bounded focus loop (~6s) exits,
|
|
114
|
+
// so the response can legitimately take several seconds. Allow headroom
|
|
115
|
+
// above that bound so a slow-but-successful open is not reported as a
|
|
116
|
+
// failure (which would throw a spurious LaunchError).
|
|
117
|
+
req.setTimeout(12000, () => { req.destroy(); resolve(false); });
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function sleep(ms) {
|
|
122
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function waitForServer(timeoutMs = 4000, shouldStop = () => false) {
|
|
126
|
+
const deadline = Date.now() + timeoutMs;
|
|
127
|
+
let ready = false;
|
|
128
|
+
while (Date.now() < deadline) {
|
|
129
|
+
ready = await ping(500);
|
|
130
|
+
if (ready) break;
|
|
131
|
+
if (shouldStop()) break;
|
|
132
|
+
await sleep(250);
|
|
133
|
+
}
|
|
134
|
+
return ready && !shouldStop();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function openLaunchLog() {
|
|
138
|
+
const path = join(tmpdir(), `mixdog-setup-launch-${process.pid}.log`);
|
|
139
|
+
const fd = openSync(path, 'a');
|
|
140
|
+
writeSync(fd, [
|
|
141
|
+
'',
|
|
142
|
+
`[${new Date().toISOString()}] setup-server launch`,
|
|
143
|
+
`launcherPid=${process.pid}`,
|
|
144
|
+
`execPath=${process.execPath}`,
|
|
145
|
+
`interpreter=${CHILD_INTERPRETER}`,
|
|
146
|
+
`server=${server}`,
|
|
147
|
+
`cwd=${dirname(__dirname)}`,
|
|
148
|
+
'--- child stdout/stderr ---',
|
|
149
|
+
].join('\n') + '\n');
|
|
150
|
+
return { path, fd };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function writeLog(fd, message) {
|
|
154
|
+
try {
|
|
155
|
+
if (typeof fd === 'number') writeSync(fd, message);
|
|
156
|
+
} catch {}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function closeLog(fd) {
|
|
160
|
+
try {
|
|
161
|
+
if (typeof fd === 'number') closeSync(fd);
|
|
162
|
+
} catch {}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function readLog(path) {
|
|
166
|
+
try { return readFileSync(path, 'utf8').trim(); } catch { return ''; }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// In the importable core a launch failure THROWS (with the same diagnostic
|
|
170
|
+
// text the CLI used to print to stderr). The CLI shim catches and converts
|
|
171
|
+
// it back to a stderr write + process.exit(1); the MCP tool surfaces it as
|
|
172
|
+
// an error result.
|
|
173
|
+
class LaunchError extends Error {}
|
|
174
|
+
|
|
175
|
+
function cmdSetLine(name, value) {
|
|
176
|
+
return `set "${name}=${String(value ?? '').replace(/"/g, '')}"`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function cmdQuote(value) {
|
|
180
|
+
return `"${String(value).replace(/"/g, '""')}"`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function spawnWscriptHidden(vbsPath) {
|
|
184
|
+
return new Promise(resolve => {
|
|
185
|
+
let child;
|
|
186
|
+
try {
|
|
187
|
+
child = spawn('wscript.exe', ['//B', '//NoLogo', vbsPath], {
|
|
188
|
+
stdio: 'ignore',
|
|
189
|
+
windowsHide: true,
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
resolve({ ok: false, error: error?.message || String(error) });
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
child.once('error', error => resolve({ ok: false, error: error?.message || String(error) }));
|
|
196
|
+
child.once('exit', code => resolve({ ok: code === 0, code }));
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Spawn setup-server with a launch log, wait for readiness, and throw a
|
|
201
|
+
// LaunchError on failure. Shared by the cold-start (!alive) path and the
|
|
202
|
+
// stale-server takeover path so both use one implementation.
|
|
203
|
+
async function spawnServerWithLog(pluginRoot, pluginData, { openOnStart = true } = {}) {
|
|
204
|
+
const launchLog = openLaunchLog();
|
|
205
|
+
let spawnError = null;
|
|
206
|
+
let childExit = null;
|
|
207
|
+
let child;
|
|
208
|
+
|
|
209
|
+
if (process.platform === 'win32') {
|
|
210
|
+
closeLog(launchLog.fd);
|
|
211
|
+
const stamp = `${process.pid}-${Date.now()}`;
|
|
212
|
+
const cmdPath = join(tmpdir(), `mixdog-setup-server-${stamp}.cmd`);
|
|
213
|
+
const vbsPath = join(tmpdir(), `mixdog-setup-server-${stamp}.vbs`);
|
|
214
|
+
const batchLines = [
|
|
215
|
+
'@echo off',
|
|
216
|
+
'setlocal',
|
|
217
|
+
cmdSetLine('CLAUDE_PLUGIN_ROOT', pluginRoot),
|
|
218
|
+
cmdSetLine('CLAUDE_PLUGIN_DATA', pluginData),
|
|
219
|
+
cmdSetLine('MIXDOG_SETUP_OPEN_ON_START', openOnStart ? '1' : '0'),
|
|
220
|
+
cmdSetLine('MIXDOG_SETUP_PARENT_PID', String(findAncestorPid() || '')),
|
|
221
|
+
process.env.USERPROFILE ? cmdSetLine('USERPROFILE', process.env.USERPROFILE) : '',
|
|
222
|
+
process.env.APPDATA ? cmdSetLine('APPDATA', process.env.APPDATA) : '',
|
|
223
|
+
process.env.LOCALAPPDATA ? cmdSetLine('LOCALAPPDATA', process.env.LOCALAPPDATA) : '',
|
|
224
|
+
process.env.PATH ? cmdSetLine('PATH', process.env.PATH) : '',
|
|
225
|
+
`cd /d ${cmdQuote(pluginRoot)}`,
|
|
226
|
+
`echo [${new Date().toISOString()}] hidden setup-server cmd start>>${cmdQuote(launchLog.path)}`,
|
|
227
|
+
`${cmdQuote(CHILD_INTERPRETER)} ${cmdQuote(server)}>>${cmdQuote(launchLog.path)} 2>&1`,
|
|
228
|
+
'set "mixdogExit=%ERRORLEVEL%"',
|
|
229
|
+
'del "%~f0" >NUL 2>&1',
|
|
230
|
+
'exit /b %mixdogExit%',
|
|
231
|
+
].filter(Boolean);
|
|
232
|
+
writeFileSync(cmdPath, batchLines.join('\r\n'), 'utf8');
|
|
233
|
+
|
|
234
|
+
const escVbs = s => String(s).replace(/"/g, '""');
|
|
235
|
+
const cmdLine = `cmd.exe /d /c ""${cmdPath}""`;
|
|
236
|
+
const vbsLines = [
|
|
237
|
+
'Option Explicit',
|
|
238
|
+
'Const HIDDEN_WINDOW = 0',
|
|
239
|
+
'Dim Wmi, Startup, cmdLine, cwd, pid, rc',
|
|
240
|
+
'Set Wmi = GetObject("winmgmts:{impersonationLevel=impersonate}!\\\\.\\root\\cimv2")',
|
|
241
|
+
'Set Startup = Wmi.Get("Win32_ProcessStartup").SpawnInstance_',
|
|
242
|
+
'Startup.ShowWindow = HIDDEN_WINDOW',
|
|
243
|
+
`cmdLine = "${escVbs(cmdLine)}"`,
|
|
244
|
+
`cwd = "${escVbs(pluginRoot)}"`,
|
|
245
|
+
'rc = Wmi.Get("Win32_Process").Create(cmdLine, cwd, Startup, pid)',
|
|
246
|
+
'If rc <> 0 Then WScript.Quit rc',
|
|
247
|
+
];
|
|
248
|
+
writeFileSync(vbsPath, vbsLines.join('\r\n'), 'utf8');
|
|
249
|
+
|
|
250
|
+
const launched = await spawnWscriptHidden(vbsPath);
|
|
251
|
+
if (!launched.ok) {
|
|
252
|
+
throw new LaunchError(
|
|
253
|
+
`Failed to spawn hidden setup-server for ${CONFIG_UI_URL}\n` +
|
|
254
|
+
`Launch log: ${launchLog.path}\n` +
|
|
255
|
+
`wscript result: ${launched.error || `exit ${launched.code}`}\n`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
if (!await waitForServer(15000)) {
|
|
259
|
+
const captured = readLog(launchLog.path);
|
|
260
|
+
throw new LaunchError(
|
|
261
|
+
`setup-server did not become ready at ${CONFIG_UI_URL}/ within 15000ms (hidden WMI launch).\n` +
|
|
262
|
+
`Launch log: ${launchLog.path}\n` +
|
|
263
|
+
(captured ? `--- setup-server launch log ---\n${captured}\n--- end setup-server launch log ---\n` : 'No setup-server output was captured.\n'),
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
child = spawn(CHILD_INTERPRETER, [server], {
|
|
271
|
+
detached: true,
|
|
272
|
+
// Console-less parents (hidden wscript/PowerShell chain) would
|
|
273
|
+
// otherwise hand the detached console child a fresh visible window.
|
|
274
|
+
windowsHide: true,
|
|
275
|
+
// Use a real file descriptor, not parent pipes/stdio. This keeps the
|
|
276
|
+
// detached child independent after unref() while preserving first-start
|
|
277
|
+
// stdout/stderr for diagnostics. shell:false (default) on every
|
|
278
|
+
// platform — cmd.exe wrapping was eating the child's stderr.
|
|
279
|
+
stdio: ['ignore', launchLog.fd, launchLog.fd],
|
|
280
|
+
cwd: pluginRoot,
|
|
281
|
+
env: {
|
|
282
|
+
...process.env,
|
|
283
|
+
CLAUDE_PLUGIN_ROOT: pluginRoot,
|
|
284
|
+
CLAUDE_PLUGIN_DATA: pluginData,
|
|
285
|
+
MIXDOG_SETUP_OPEN_ON_START: openOnStart ? '1' : '0',
|
|
286
|
+
MIXDOG_SETUP_PARENT_PID: String(findAncestorPid() || ''),
|
|
287
|
+
},
|
|
288
|
+
windowsHide: true,
|
|
289
|
+
});
|
|
290
|
+
} catch (error) {
|
|
291
|
+
closeLog(launchLog.fd);
|
|
292
|
+
throw new LaunchError(
|
|
293
|
+
`Failed to spawn setup-server for ${CONFIG_UI_URL}\n` +
|
|
294
|
+
`Launch log: ${launchLog.path}\n` +
|
|
295
|
+
`${error?.stack || error?.message || String(error)}\n`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
child.once('error', error => {
|
|
300
|
+
spawnError = error;
|
|
301
|
+
writeLog(launchLog.fd, `\n[${new Date().toISOString()}] child error: ${error?.stack || error?.message || String(error)}\n`);
|
|
302
|
+
});
|
|
303
|
+
child.once('exit', (code, signal) => {
|
|
304
|
+
childExit = { code, signal };
|
|
305
|
+
writeLog(launchLog.fd, `\n[${new Date().toISOString()}] child exit: code=${code} signal=${signal}\n`);
|
|
306
|
+
});
|
|
307
|
+
child.unref();
|
|
308
|
+
|
|
309
|
+
if (!await waitForServer(15000, () => Boolean(spawnError || childExit))) {
|
|
310
|
+
closeLog(launchLog.fd);
|
|
311
|
+
const captured = readLog(launchLog.path);
|
|
312
|
+
const status = spawnError
|
|
313
|
+
? `spawn error: ${spawnError?.stack || spawnError?.message || String(spawnError)}`
|
|
314
|
+
: childExit
|
|
315
|
+
? `child exit: code=${childExit.code} signal=${childExit.signal}`
|
|
316
|
+
: 'child did not report an error or exit before readiness timeout';
|
|
317
|
+
throw new LaunchError(
|
|
318
|
+
`setup-server did not become ready at ${CONFIG_UI_URL}/ within 15000ms (${status}).\n` +
|
|
319
|
+
`Launch log: ${launchLog.path}\n` +
|
|
320
|
+
(captured ? `--- setup-server launch log ---\n${captured}\n--- end setup-server launch log ---\n` : 'No setup-server output was captured.\n'),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
closeLog(launchLog.fd);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Probe for an already-running setup-server. A live server answers the HTTP
|
|
328
|
+
// ping immediately; if its event loop is momentarily blocked inside a sync
|
|
329
|
+
// spawn (chrome /open path), the kernel still completes the TCP accept, so the
|
|
330
|
+
// tcpOpen fallback detects it. A short window therefore suffices.
|
|
331
|
+
function probeOnce() {
|
|
332
|
+
return new Promise(resolve => {
|
|
333
|
+
const req = http.get({
|
|
334
|
+
host: '127.0.0.1', port: PORT, path: '/',
|
|
335
|
+
agent: false, headers: { Connection: 'close' },
|
|
336
|
+
}, res => { res.resume(); resolve('up'); });
|
|
337
|
+
req.on('error', e => resolve(e?.code === 'ECONNREFUSED' ? 'refused' : 'timeout'));
|
|
338
|
+
req.setTimeout(800, () => { req.destroy(); resolve('timeout'); });
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function aliveProbe() {
|
|
343
|
+
const deadline = Date.now() + 1500;
|
|
344
|
+
while (Date.now() < deadline) {
|
|
345
|
+
const state = await probeOnce();
|
|
346
|
+
if (state === 'up') return true;
|
|
347
|
+
if (state === 'refused') return false;
|
|
348
|
+
await sleep(150);
|
|
349
|
+
}
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Resolve / start the resident setup-server and open (or re-open) the config
|
|
354
|
+
// UI window, returning the UI URL on success. Throws LaunchError with a
|
|
355
|
+
// diagnostic message on failure.
|
|
356
|
+
//
|
|
357
|
+
// Options:
|
|
358
|
+
// pluginRoot — override the plugin root (defaults to CLAUDE_PLUGIN_ROOT or
|
|
359
|
+
// the directory above setup/).
|
|
360
|
+
// trace — optional (msg:string)=>void hook for diagnostics; defaults
|
|
361
|
+
// to a tmp trace-log writer (matches the CLI's behaviour).
|
|
362
|
+
// prewarm — when true, only ensure the server is alive (spawned
|
|
363
|
+
// window-free, MIXDOG_SETUP_OPEN_ON_START unset). Never opens
|
|
364
|
+
// or re-focuses a window; a no-op when the server already runs.
|
|
365
|
+
export async function launchConfigUi(options = {}) {
|
|
366
|
+
const pluginRoot = options.pluginRoot || process.env.CLAUDE_PLUGIN_ROOT || dirname(__dirname);
|
|
367
|
+
const prewarm = options.prewarm === true;
|
|
368
|
+
// Slash-command shells expand ${CLAUDE_PLUGIN_ROOT} into argv but do not
|
|
369
|
+
// export it, so the spawned setup-server inherits a stripped env. Re-derive
|
|
370
|
+
// both ROOT and DATA so plugin-paths and the stricter channels-lib guard
|
|
371
|
+
// (which checks CLAUDE_PLUGIN_DATA directly) both succeed in the child.
|
|
372
|
+
process.env.CLAUDE_PLUGIN_ROOT = pluginRoot;
|
|
373
|
+
const pluginData = process.env.CLAUDE_PLUGIN_DATA || resolvePluginData();
|
|
374
|
+
process.env.CLAUDE_PLUGIN_DATA = pluginData;
|
|
375
|
+
|
|
376
|
+
const traceFn = typeof options.trace === 'function'
|
|
377
|
+
? options.trace
|
|
378
|
+
: (() => {
|
|
379
|
+
const traceLogPath = join(tmpdir(), `mixdog-launch-trace-${process.pid}.log`);
|
|
380
|
+
return msg => {
|
|
381
|
+
try { appendFileSync(traceLogPath, `[${new Date().toISOString()}] ${msg}\n`); } catch {}
|
|
382
|
+
};
|
|
383
|
+
})();
|
|
384
|
+
const __trace = msg => { try { traceFn(msg); } catch {} };
|
|
385
|
+
__trace(`launchConfigUi entry parentPid=${process.ppid} pluginRoot=${pluginRoot}`);
|
|
386
|
+
|
|
387
|
+
const alive = await aliveProbe();
|
|
388
|
+
__trace(`aliveProbe result alive=${alive}`);
|
|
389
|
+
|
|
390
|
+
// Pre-warm mode: ensure the server is booted window-free, then return.
|
|
391
|
+
// Already alive → cheap no-op (no requestOpen, no window). Not alive →
|
|
392
|
+
// spawn with openOnStart:false so setup-server boots without opening a
|
|
393
|
+
// browser window. No stale-server takeover: pre-warm is best-effort and
|
|
394
|
+
// must never kill another install's server.
|
|
395
|
+
if (prewarm) {
|
|
396
|
+
if (alive) {
|
|
397
|
+
__trace(`prewarm: server already alive → no-op`);
|
|
398
|
+
return CONFIG_UI_URL;
|
|
399
|
+
}
|
|
400
|
+
__trace(`prewarm: !alive → spawnServerWithLog (window-free)`);
|
|
401
|
+
await spawnServerWithLog(pluginRoot, pluginData, { openOnStart: false });
|
|
402
|
+
__trace(`prewarm: spawnServerWithLog completed`);
|
|
403
|
+
return CONFIG_UI_URL;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (!alive) {
|
|
407
|
+
// Boot window-free; the unconditional requestOpen below owns opening the
|
|
408
|
+
// window. Spawning with openOnStart:1 here would double-open and races a
|
|
409
|
+
// concurrent every-session prewarm that may bind the port first (in which
|
|
410
|
+
// case our own openOnStart child never gets to open anything).
|
|
411
|
+
__trace(`branch: !alive → spawnServerWithLog (window-free)`);
|
|
412
|
+
await spawnServerWithLog(pluginRoot, pluginData, { openOnStart: false });
|
|
413
|
+
__trace(`spawnServerWithLog completed`);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Confirm the live server belongs to THIS plugin install before reusing it
|
|
417
|
+
// (a stale or different-version setup-server would otherwise own the
|
|
418
|
+
// window). After a !alive spawn above this simply re-confirms our own server.
|
|
419
|
+
const remoteRoot = await fetchPluginPath();
|
|
420
|
+
__trace(`fetchPluginPath returned remoteRoot=${remoteRoot}`);
|
|
421
|
+
if (remoteRoot) {
|
|
422
|
+
// Normalize: trailing slash strip + backslash → forward slash + lowercase
|
|
423
|
+
// so `C:/x` and `C:\x` compare equal (lowercase only on Windows).
|
|
424
|
+
const normalize = p => { const s = p.replace(/[\\/]+$/, '').replace(/\\/g, '/'); return process.platform === 'win32' ? s.toLowerCase() : s; };
|
|
425
|
+
const expected = normalize(pluginRoot);
|
|
426
|
+
const actual = normalize(remoteRoot);
|
|
427
|
+
__trace(`compare expected=${expected} actual=${actual} mismatch=${expected !== actual}`);
|
|
428
|
+
if (expected !== actual) {
|
|
429
|
+
// Invariant: single live plugin install owns PORT. The occupying server
|
|
430
|
+
// belongs to a different install — resolve its PID, terminate it, poll
|
|
431
|
+
// until the port is free, then take ownership via spawnServerWithLog().
|
|
432
|
+
let stalePid = null;
|
|
433
|
+
let pidResolveError = null;
|
|
434
|
+
let killError = null;
|
|
435
|
+
try {
|
|
436
|
+
if (process.platform === 'win32') {
|
|
437
|
+
const out = execSync(
|
|
438
|
+
`powershell -NoProfile -Command "(Get-NetTCPConnection -LocalPort ${PORT} -ErrorAction SilentlyContinue | Select-Object -First 1).OwningProcess"`,
|
|
439
|
+
{ encoding: 'utf8', timeout: 3000, windowsHide: true },
|
|
440
|
+
).trim();
|
|
441
|
+
const n = parseInt(out, 10);
|
|
442
|
+
if (Number.isFinite(n) && n > 0) stalePid = n;
|
|
443
|
+
} else {
|
|
444
|
+
const out = execSync(`lsof -ti :${PORT}`, { encoding: 'utf8', timeout: 3000 }).trim();
|
|
445
|
+
const n = parseInt(out.split('\n')[0], 10);
|
|
446
|
+
if (Number.isFinite(n) && n > 0) stalePid = n;
|
|
447
|
+
}
|
|
448
|
+
} catch (e) { pidResolveError = e; }
|
|
449
|
+
if (stalePid !== null) {
|
|
450
|
+
try {
|
|
451
|
+
if (process.platform === 'win32') {
|
|
452
|
+
// /T kills the whole process tree — the setup-server spawns child
|
|
453
|
+
// browser/server descendants that survive a PID-only Stop-Process,
|
|
454
|
+
// leaking processes and holding the port.
|
|
455
|
+
execSync(`taskkill /T /F /PID ${stalePid}`, { timeout: 3000, windowsHide: true });
|
|
456
|
+
} else {
|
|
457
|
+
// setup-server is launched detached (its own process-group leader),
|
|
458
|
+
// so the port-owning PID equals the group id. Negative PID signals
|
|
459
|
+
// the whole group, reaping descendants a bare kill would orphan.
|
|
460
|
+
try { execSync(`kill -KILL -${stalePid}`, { timeout: 3000 }); }
|
|
461
|
+
catch { execSync(`kill -KILL ${stalePid}`, { timeout: 3000 }); }
|
|
462
|
+
}
|
|
463
|
+
} catch (e) { killError = e; }
|
|
464
|
+
}
|
|
465
|
+
// Poll until port is free (250ms ticks, 3000ms deadline).
|
|
466
|
+
const killDeadline = Date.now() + 3000;
|
|
467
|
+
let portFree = false;
|
|
468
|
+
while (Date.now() < killDeadline) {
|
|
469
|
+
await sleep(250);
|
|
470
|
+
portFree = !(await ping(300));
|
|
471
|
+
if (portFree) break;
|
|
472
|
+
}
|
|
473
|
+
if (!portFree) {
|
|
474
|
+
const errorDetails = [
|
|
475
|
+
pidResolveError && ` PID resolution failure: ${pidResolveError?.message || String(pidResolveError)}`,
|
|
476
|
+
killError && ` kill failure: ${killError?.message || String(killError)}`,
|
|
477
|
+
].filter(Boolean).join('\n');
|
|
478
|
+
throw new LaunchError(
|
|
479
|
+
`Port ${PORT} is in use by a different mixdog plugin instance and could not be reclaimed (kill of PID ${stalePid ?? 'unknown'} failed or timed out).\n` +
|
|
480
|
+
` expected plugin root: ${pluginRoot}\n` +
|
|
481
|
+
` actual plugin root: ${remoteRoot}\n` +
|
|
482
|
+
(errorDetails ? errorDetails + '\n' : '') +
|
|
483
|
+
`Stop the other setup-server (or change PORT) and retry.\n`,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
__trace(`takeover: killed stalePid=${stalePid}, port freed, spawning new server`);
|
|
487
|
+
await spawnServerWithLog(pluginRoot, pluginData, { openOnStart: false });
|
|
488
|
+
__trace(`takeover: spawnServerWithLog completed`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Invariant: a non-prewarm launch ALWAYS opens the window through the
|
|
492
|
+
// idempotent /open endpoint once the server is ready — regardless of which
|
|
493
|
+
// process (this launcher, a concurrent every-session prewarm, or a
|
|
494
|
+
// pre-existing server) actually booted it. Relying on the spawned child's
|
|
495
|
+
// MIXDOG_SETUP_OPEN_ON_START would silently no-op whenever a window-free
|
|
496
|
+
// prewarm server bound the port first.
|
|
497
|
+
__trace(`branch: requestOpen`);
|
|
498
|
+
const opened = await requestOpen();
|
|
499
|
+
__trace(`requestOpen returned ${opened}`);
|
|
500
|
+
if (!opened) {
|
|
501
|
+
throw new LaunchError(`Failed to open config UI window for ${CONFIG_UI_URL}\n`);
|
|
502
|
+
}
|
|
503
|
+
__trace(`launchConfigUi success`);
|
|
504
|
+
return CONFIG_UI_URL;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
export { LaunchError, tcpOpen };
|
package/setup/launch.mjs
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// launch.mjs — CLI shim for the config-UI launcher.
|
|
3
|
+
//
|
|
4
|
+
// The launch SEQUENCE (alive probe → stale-server detection → spawn →
|
|
5
|
+
// requestOpen) now lives in launch-core.mjs so the resident MCP server can
|
|
6
|
+
// import and run it window-free via the `open_config` tool. This file keeps
|
|
7
|
+
// only the bits that exist BECAUSE the CLI is invoked through a
|
|
8
|
+
// console-subsystem bun.exe: the FFI console-hide and the hidden-wscript
|
|
9
|
+
// self-respawn. Both collapse the conhost flash that the slash command's
|
|
10
|
+
// shell-out produces. Neither is needed (or wanted) inside the MCP server,
|
|
11
|
+
// which spawns setup-server with windowsHide:true and never flashes at all.
|
|
12
|
+
import { spawn } from 'child_process';
|
|
13
|
+
import { writeFileSync } from 'fs';
|
|
14
|
+
import { join, dirname } from 'path';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { tmpdir } from 'os';
|
|
17
|
+
|
|
18
|
+
// Hide our own console window as the very first thing on Windows. Claude Code
|
|
19
|
+
// runs this script via a console-subsystem bun.exe, which flashes an empty
|
|
20
|
+
// terminal for ~0.5s before the self-respawn below hands off to a hidden
|
|
21
|
+
// child. Hiding the console here collapses that flash to bun's startup time.
|
|
22
|
+
// SAFETY GUARD: only hide when this console belongs to us ALONE.
|
|
23
|
+
// GetConsoleProcessList returning a single PID equal to ours means a dedicated
|
|
24
|
+
// console (the flash case). A shared console — Claude Code's own terminal with
|
|
25
|
+
// our stdio piped into it — reports the host process too; hiding that would
|
|
26
|
+
// make the user's working terminal vanish, so we must never touch it. stdout
|
|
27
|
+
// capture is unaffected (Claude Code reads the pipe, not the window).
|
|
28
|
+
if (process.platform === 'win32') {
|
|
29
|
+
try {
|
|
30
|
+
const { dlopen, FFIType, ptr } = await import('bun:ffi');
|
|
31
|
+
const k32 = dlopen('kernel32.dll', {
|
|
32
|
+
GetConsoleWindow: { args: [], returns: FFIType.ptr },
|
|
33
|
+
GetConsoleProcessList: { args: [FFIType.ptr, FFIType.u32], returns: FFIType.u32 },
|
|
34
|
+
});
|
|
35
|
+
const u32 = dlopen('user32.dll', {
|
|
36
|
+
ShowWindow: { args: [FFIType.ptr, FFIType.i32], returns: FFIType.i32 },
|
|
37
|
+
});
|
|
38
|
+
const hwnd = k32.symbols.GetConsoleWindow();
|
|
39
|
+
if (hwnd) {
|
|
40
|
+
const list = new Uint32Array(16);
|
|
41
|
+
const count = k32.symbols.GetConsoleProcessList(ptr(list), list.length);
|
|
42
|
+
if (count === 1 && list[0] === process.pid) {
|
|
43
|
+
u32.symbols.ShowWindow(hwnd, 0); // SW_HIDE
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
} catch { /* FFI unavailable/failed — leave the console alone; at worst the brief flash remains */ }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Windows self-respawn under hidden wscript. Without this, the bun console
|
|
50
|
+
// flashes briefly between the slash command's shell-out and the detached
|
|
51
|
+
// setup-server spawn (two flashes total). Self-respawn shrinks it to one
|
|
52
|
+
// flash: parent exits immediately, hidden child re-runs this script with
|
|
53
|
+
// stdio detached so setup-server boot work happens entirely off-screen. The
|
|
54
|
+
// --hidden argv flag prevents recursion.
|
|
55
|
+
if (process.platform === 'win32' && !process.argv.includes('--hidden')) {
|
|
56
|
+
// wscript.exe is a GUI host (no console of its own), so spawning it does
|
|
57
|
+
// not flash a conhost window. Use Shell.Application.ShellExecute (NOT
|
|
58
|
+
// WScript.Shell.Run — Wsh.Run silently no-ops when launching bun.exe with
|
|
59
|
+
// an embedded-quote command line; ShellExecute keeps app and args as
|
|
60
|
+
// separate parameters) with show=0 (SW_HIDE) for the hidden child bun.
|
|
61
|
+
const __scriptPath = fileURLToPath(import.meta.url);
|
|
62
|
+
const __bunExe = process.execPath;
|
|
63
|
+
const __escVbs = s => String(s).replace(/"/g, '""');
|
|
64
|
+
// Forward --prewarm into the hidden child so the re-run boots the server
|
|
65
|
+
// window-free instead of opening the config window.
|
|
66
|
+
const __prewarmArg = process.argv.includes('--prewarm') ? ' --prewarm' : '';
|
|
67
|
+
const __argsStr = `"${__scriptPath}" --hidden${__prewarmArg}`;
|
|
68
|
+
const __vbsLines = [
|
|
69
|
+
'Set Shell = CreateObject("Shell.Application")',
|
|
70
|
+
`Shell.ShellExecute "${__escVbs(__bunExe)}", "${__escVbs(__argsStr)}", "", "open", 0`,
|
|
71
|
+
];
|
|
72
|
+
const __vbsPath = join(tmpdir(), `mixdog-launch-respawn-${process.pid}-${Date.now()}.vbs`);
|
|
73
|
+
writeFileSync(__vbsPath, __vbsLines.join('\r\n'), 'utf8');
|
|
74
|
+
const __child = spawn('wscript.exe', ['//B', '//NoLogo', __vbsPath], {
|
|
75
|
+
stdio: 'ignore', windowsHide: true, detached: true,
|
|
76
|
+
});
|
|
77
|
+
__child.unref();
|
|
78
|
+
// Brief wait so wscript finishes parsing the .vbs and dispatches
|
|
79
|
+
// ShellExecute before this parent exits. Without it, parent termination
|
|
80
|
+
// races wscript startup and the hidden child never spawns.
|
|
81
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
82
|
+
process.stdout.write(`Config UI: http://localhost:3458\n`, () => process.exit(0));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Delegate the actual launch sequence to the shared core. Reuse the trace-log
|
|
86
|
+
// behaviour the inlined implementation had by letting launchConfigUi default
|
|
87
|
+
// its own tmp trace writer. A LaunchError is converted back to the legacy
|
|
88
|
+
// stderr-write + exit(1) contract the slash command relied on.
|
|
89
|
+
const { launchConfigUi, LaunchError, CONFIG_UI_URL } = await import(
|
|
90
|
+
join(dirname(fileURLToPath(import.meta.url)), 'launch-core.mjs'),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const prewarm = process.argv.includes('--prewarm');
|
|
95
|
+
const url = await launchConfigUi({ prewarm });
|
|
96
|
+
process.stdout.write(`Config UI: ${url}\n`, () => process.exit(0));
|
|
97
|
+
} catch (err) {
|
|
98
|
+
const message = err instanceof LaunchError ? err.message : `${err?.stack || err?.message || String(err)}\n`;
|
|
99
|
+
await new Promise(resolve => process.stderr.write(message, resolve));
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|