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,477 @@
|
|
|
1
|
+
// whisper-server.mjs
|
|
2
|
+
//
|
|
3
|
+
// State-machine manager for ONE long-lived whisper-server.exe process.
|
|
4
|
+
//
|
|
5
|
+
// Invariant model (NOT a try-a-few-things model):
|
|
6
|
+
// There is at most ONE managed whisper-server child, identified by an exact
|
|
7
|
+
// runtime contract (serverCmd / modelPath / threadCount / host). ensureReady()
|
|
8
|
+
// guarantees that — when it resolves — a child matching the CURRENT contract is
|
|
9
|
+
// bound to the fixed port and has loaded its model. Any deviation (contract
|
|
10
|
+
// change, child death, port stolen) is repaired by deterministically tearing
|
|
11
|
+
// down and recreating the SAME contract. There is NO fallback to a CLI binary,
|
|
12
|
+
// to python, to another executable, or to another model. If the contract cannot
|
|
13
|
+
// be satisfied, the operation FAILS.
|
|
14
|
+
//
|
|
15
|
+
// States: STOPPED → STARTING → READY → STOPPING → STOPPED, plus DEAD on child exit.
|
|
16
|
+
//
|
|
17
|
+
// Why a FIXED port (not `--port 0`):
|
|
18
|
+
// whisper-server 1.8.4 does not support OS-assigned ephemeral bind. Passing
|
|
19
|
+
// `--port 0` makes it print `listening at http://127.0.0.1:0` and never bind a
|
|
20
|
+
// usable port. So we bind a single deterministic port and FAIL CLOSED when it is
|
|
21
|
+
// occupied by a process we do not positively own. We NEVER scan a port range.
|
|
22
|
+
//
|
|
23
|
+
// Readiness detection:
|
|
24
|
+
// The server prints `... listening at http://<host>:<port>` AFTER the model is
|
|
25
|
+
// loaded, but that line is block-buffered when stdout is a pipe and may not flush
|
|
26
|
+
// promptly. So readiness is gated on an active TCP connect to the fixed port
|
|
27
|
+
// (the socket is bound only after model load completes). When the listening line
|
|
28
|
+
// IS observed, the parsed port is asserted to equal the fixed port (contract
|
|
29
|
+
// invariant); a mismatch is fatal.
|
|
30
|
+
//
|
|
31
|
+
// PID metadata (<dataDir>/voice/whisper-server.pid.json) exists ONLY for cleanup
|
|
32
|
+
// across process restarts. We kill a stale child ONLY when it is positively owned
|
|
33
|
+
// (recorded pid is alive AND its image is whisper-server). A foreign process on the
|
|
34
|
+
// port is never killed — we fail closed instead.
|
|
35
|
+
|
|
36
|
+
import net from 'node:net';
|
|
37
|
+
import path from 'node:path';
|
|
38
|
+
import fs from 'node:fs';
|
|
39
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
40
|
+
import { setTimeout as delay } from 'node:timers/promises';
|
|
41
|
+
|
|
42
|
+
// ── Tunables (deterministic; no ranges) ──────────────────────────────────────
|
|
43
|
+
const IS_WIN = process.platform === 'win32';
|
|
44
|
+
// Single deterministic port. Override only via env for operator control; never
|
|
45
|
+
// auto-scanned. Fail closed if occupied by a non-owned process.
|
|
46
|
+
const FIXED_PORT = (() => {
|
|
47
|
+
const raw = process.env.MIXDOG_WHISPER_SERVER_PORT;
|
|
48
|
+
const n = raw ? Number.parseInt(raw, 10) : NaN;
|
|
49
|
+
return Number.isInteger(n) && n > 0 && n < 65536 ? n : 8771;
|
|
50
|
+
})();
|
|
51
|
+
const READY_TIMEOUT_MS = 120_000; // model load + bind budget
|
|
52
|
+
const READY_POLL_MS = 250; // TCP probe cadence
|
|
53
|
+
const PROBE_CONNECT_MS = 1_000; // per TCP-connect attempt
|
|
54
|
+
const STOP_GRACE_MS = 5_000; // SIGTERM → SIGKILL escalation window
|
|
55
|
+
const INFERENCE_PATH = '/inference'; // whisper-server default --inference-path
|
|
56
|
+
|
|
57
|
+
const STATE = Object.freeze({
|
|
58
|
+
STOPPED: 'STOPPED',
|
|
59
|
+
STARTING: 'STARTING',
|
|
60
|
+
READY: 'READY',
|
|
61
|
+
STOPPING: 'STOPPING',
|
|
62
|
+
DEAD: 'DEAD',
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// ── Singleton manager state ───────────────────────────────────────────────────
|
|
66
|
+
const mgr = {
|
|
67
|
+
state: STATE.STOPPED,
|
|
68
|
+
child: null, // ChildProcess handle (non-detached)
|
|
69
|
+
port: null, // bound port (== FIXED_PORT when READY)
|
|
70
|
+
host: null,
|
|
71
|
+
runtimeKey: null, // exact contract fingerprint
|
|
72
|
+
contract: null, // { serverCmd, modelPath, threadCount, host }
|
|
73
|
+
startPromise: null, // in-flight ensureReady() start
|
|
74
|
+
inflight: new Set(), // active transcribe AbortControllers
|
|
75
|
+
logTail: '', // recent stdout/stderr for diagnostics
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function runtimeKeyOf({ serverCmd, modelPath, threadCount, host }) {
|
|
79
|
+
return JSON.stringify([serverCmd, modelPath, threadCount, host]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pidMetaPath() {
|
|
83
|
+
// Co-located with the model store; serverCmd lives under <dataDir>/voice-runtime
|
|
84
|
+
// and the model under <dataDir>/voice/models, so derive the voice dir from cwd
|
|
85
|
+
// of the server binary's data root is brittle — instead key off serverCmd's
|
|
86
|
+
// runtime root. We persist next to a stable per-user temp path derived from the
|
|
87
|
+
// serverCmd so cleanup survives a manager-process restart.
|
|
88
|
+
const base = mgr.contract?.serverCmd
|
|
89
|
+
? path.join(path.dirname(mgr.contract.serverCmd), '..', '..')
|
|
90
|
+
: process.cwd();
|
|
91
|
+
return path.join(base, 'whisper-server.pid.json');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function writePidMeta() {
|
|
95
|
+
if (!mgr.child) return;
|
|
96
|
+
const meta = {
|
|
97
|
+
pid: mgr.child.pid,
|
|
98
|
+
serverCmd: mgr.contract.serverCmd,
|
|
99
|
+
modelPath: mgr.contract.modelPath,
|
|
100
|
+
threadCount: mgr.contract.threadCount,
|
|
101
|
+
host: mgr.contract.host,
|
|
102
|
+
port: mgr.port,
|
|
103
|
+
runtimeKey: mgr.runtimeKey,
|
|
104
|
+
startedAt: Date.now(),
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
fs.mkdirSync(path.dirname(pidMetaPath()), { recursive: true });
|
|
108
|
+
fs.writeFileSync(pidMetaPath(), JSON.stringify(meta), 'utf8');
|
|
109
|
+
} catch { /* metadata is advisory cleanup state; non-fatal */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function clearPidMeta() {
|
|
113
|
+
try { fs.rmSync(pidMetaPath(), { force: true }); } catch { /* non-fatal */ }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function readPidMeta() {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(fs.readFileSync(pidMetaPath(), 'utf8'));
|
|
119
|
+
} catch { return null; }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read the live process command line for `pid`. This is the proof-of-ownership
|
|
123
|
+
// query: a recorded pid being alive with the right image is NOT sufficient (pids
|
|
124
|
+
// are reused; another whisper-server could be unrelated). We must confirm the
|
|
125
|
+
// running process was launched against OUR contract.
|
|
126
|
+
// win32: query Win32_Process.CommandLine via wmic at a DETERMINISTIC System32
|
|
127
|
+
// path (never resolved through PATH, which an attacker/shadow binary
|
|
128
|
+
// could hijack).
|
|
129
|
+
// posix: `ps -o args= -p <pid>` prints the full argument vector.
|
|
130
|
+
// Returns '' on any failure → callers MUST treat '' as "cannot prove" and fail
|
|
131
|
+
// closed (never kill, never reuse).
|
|
132
|
+
function readProcessCommandLine(pid) {
|
|
133
|
+
if (IS_WIN) {
|
|
134
|
+
const sysRoot = process.env.SystemRoot || process.env.windir || 'C\u003a\\Windows';
|
|
135
|
+
const wmic = path.join(sysRoot, 'System32', 'wbem', 'wmic.exe');
|
|
136
|
+
try {
|
|
137
|
+
const r = spawnSync(
|
|
138
|
+
wmic,
|
|
139
|
+
['process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/FORMAT:LIST'],
|
|
140
|
+
{ encoding: 'utf8', windowsHide: true },
|
|
141
|
+
);
|
|
142
|
+
return r.stdout || '';
|
|
143
|
+
} catch { return ''; }
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
const r = spawnSync('ps', ['-o', 'args=', '-p', String(pid)], { encoding: 'utf8', windowsHide: true });
|
|
147
|
+
return r.stdout || '';
|
|
148
|
+
} catch { return ''; }
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Positive ownership check: the recorded pid must be alive AND its image must be
|
|
152
|
+
// whisper-server AND its live command line must reference OUR contract — i.e. it
|
|
153
|
+
// must contain our modelPath OR our `--port <port>`. Image + alive alone is not
|
|
154
|
+
// proof (pids are reused, and an unrelated whisper-server could be running), so
|
|
155
|
+
// we additionally bind ownership to the running command line via an OS query.
|
|
156
|
+
// Any inability to prove the command-line match → return false (fail closed);
|
|
157
|
+
// only a positive match licenses killing the stale child.
|
|
158
|
+
function isOwnedWhisperServer(pid, { modelPath, port } = {}) {
|
|
159
|
+
if (!pid || !Number.isInteger(pid)) return false;
|
|
160
|
+
try { process.kill(pid, 0); } catch { return false; } // not alive / not ours
|
|
161
|
+
// 1) Image identity.
|
|
162
|
+
let imageOk = false;
|
|
163
|
+
if (IS_WIN) {
|
|
164
|
+
try {
|
|
165
|
+
const r = spawnSync(
|
|
166
|
+
'tasklist',
|
|
167
|
+
['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'],
|
|
168
|
+
{ encoding: 'utf8', windowsHide: true },
|
|
169
|
+
);
|
|
170
|
+
imageOk = /whisper-server\.exe/i.test(r.stdout || '');
|
|
171
|
+
} catch { return false; }
|
|
172
|
+
} else {
|
|
173
|
+
try {
|
|
174
|
+
const r = spawnSync('ps', ['-p', String(pid), '-o', 'comm='], { encoding: 'utf8', windowsHide: true });
|
|
175
|
+
imageOk = /whisper-server/i.test(r.stdout || '');
|
|
176
|
+
} catch { return false; }
|
|
177
|
+
}
|
|
178
|
+
if (!imageOk) return false;
|
|
179
|
+
// 2) Command-line contract proof. Fail closed if we cannot read the cmdline.
|
|
180
|
+
const cmdline = readProcessCommandLine(pid);
|
|
181
|
+
if (!cmdline) return false;
|
|
182
|
+
const modelMatch = Boolean(modelPath) && cmdline.includes(modelPath);
|
|
183
|
+
const portMatch = Number.isInteger(port)
|
|
184
|
+
&& new RegExp(`--port(?:\\s+|=)${port}(?!\\d)`).test(cmdline);
|
|
185
|
+
return modelMatch || portMatch;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function killPid(pid, force) {
|
|
189
|
+
if (!pid) return;
|
|
190
|
+
if (IS_WIN) {
|
|
191
|
+
const args = ['/PID', String(pid), '/T'];
|
|
192
|
+
if (force) args.push('/F');
|
|
193
|
+
try { spawnSync('taskkill', args, { windowsHide: true }); } catch { /* best-effort */ }
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
try { process.kill(pid, force ? 'SIGKILL' : 'SIGTERM'); } catch { /* best-effort */ }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Single TCP-connect probe. Resolves true when the socket accepts a connection
|
|
200
|
+
// (whisper-server binds only after the model is fully loaded).
|
|
201
|
+
function probePort(host, port) {
|
|
202
|
+
return new Promise((resolve) => {
|
|
203
|
+
const sock = net.connect({ host, port });
|
|
204
|
+
let done = false;
|
|
205
|
+
const finish = (ok) => {
|
|
206
|
+
if (done) return;
|
|
207
|
+
done = true;
|
|
208
|
+
try { sock.destroy(); } catch {}
|
|
209
|
+
resolve(ok);
|
|
210
|
+
};
|
|
211
|
+
sock.setTimeout(PROBE_CONNECT_MS);
|
|
212
|
+
sock.once('connect', () => finish(true));
|
|
213
|
+
sock.once('timeout', () => finish(false));
|
|
214
|
+
sock.once('error', () => finish(false));
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function appendLog(buf) {
|
|
219
|
+
mgr.logTail = (mgr.logTail + buf.toString()).slice(-4000);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Advisory: lower the child to Windows IDLE priority exactly once. Failure is
|
|
223
|
+
// non-fatal (matches os.setPriority best-effort usage elsewhere) and never blocks
|
|
224
|
+
// readiness — priority is not part of the transcription contract.
|
|
225
|
+
async function applyLowPriorityOnce(child) {
|
|
226
|
+
if (!IS_WIN || !child?.pid) return;
|
|
227
|
+
try {
|
|
228
|
+
const mod = await import('node-windows-process-info');
|
|
229
|
+
const api = mod.default ?? mod;
|
|
230
|
+
const setPriority = api.setPriority ?? api.setProcessPriority;
|
|
231
|
+
if (typeof setPriority === 'function') {
|
|
232
|
+
// IDLE_PRIORITY_CLASS === 0x40 (64); pass both the symbolic and numeric
|
|
233
|
+
// forms to tolerate the package's accepted argument shape.
|
|
234
|
+
try { setPriority(child.pid, 'idle'); }
|
|
235
|
+
catch { setPriority(child.pid, 0x40); }
|
|
236
|
+
}
|
|
237
|
+
} catch { /* package/API absent → priority stays default; non-fatal */ }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function detachChildHandlers() {
|
|
241
|
+
if (!mgr.child) return;
|
|
242
|
+
try { mgr.child.removeAllListeners(); } catch {}
|
|
243
|
+
try { mgr.child.stdout?.removeAllListeners(); } catch {}
|
|
244
|
+
try { mgr.child.stderr?.removeAllListeners(); } catch {}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Deterministic teardown of the current child. Used both for STOPPING and for a
|
|
248
|
+
// contract change. Escalates SIGTERM → SIGKILL after a grace window.
|
|
249
|
+
async function teardown(reason) {
|
|
250
|
+
const child = mgr.child;
|
|
251
|
+
const pid = child?.pid;
|
|
252
|
+
// Abort all in-flight transcribe requests first.
|
|
253
|
+
for (const ctrl of mgr.inflight) { try { ctrl.abort(new Error(`whisper-server stopping: ${reason}`)); } catch {} }
|
|
254
|
+
mgr.inflight.clear();
|
|
255
|
+
if (child && pid) {
|
|
256
|
+
detachChildHandlers();
|
|
257
|
+
const exited = new Promise((resolve) => { try { child.once('exit', resolve); } catch { resolve(); } });
|
|
258
|
+
killPid(pid, false);
|
|
259
|
+
const raced = await Promise.race([exited, delay(STOP_GRACE_MS, 'timeout')]);
|
|
260
|
+
if (raced === 'timeout') {
|
|
261
|
+
killPid(pid, true); // escalate
|
|
262
|
+
await Promise.race([exited, delay(STOP_GRACE_MS, 'timeout')]);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
mgr.child = null;
|
|
266
|
+
mgr.port = null;
|
|
267
|
+
mgr.host = null;
|
|
268
|
+
mgr.runtimeKey = null;
|
|
269
|
+
mgr.contract = null;
|
|
270
|
+
clearPidMeta();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function wireChildExit() {
|
|
274
|
+
const child = mgr.child;
|
|
275
|
+
if (!child) return;
|
|
276
|
+
child.stdout?.on('data', appendLog);
|
|
277
|
+
child.stderr?.on('data', appendLog);
|
|
278
|
+
child.once('error', () => {
|
|
279
|
+
// Spawn/runtime error → the contract is unsatisfied.
|
|
280
|
+
mgr.state = STATE.DEAD;
|
|
281
|
+
});
|
|
282
|
+
child.once('exit', () => {
|
|
283
|
+
// Child death is an invariant violation. Mark DEAD then settle to STOPPED so
|
|
284
|
+
// the next ensureReady() recreates the SAME contract (invariant repair).
|
|
285
|
+
for (const ctrl of mgr.inflight) { try { ctrl.abort(new Error('whisper-server exited')); } catch {} }
|
|
286
|
+
mgr.inflight.clear();
|
|
287
|
+
detachChildHandlers();
|
|
288
|
+
mgr.state = STATE.DEAD;
|
|
289
|
+
mgr.child = null;
|
|
290
|
+
mgr.port = null;
|
|
291
|
+
mgr.runtimeKey = null;
|
|
292
|
+
mgr.contract = null;
|
|
293
|
+
clearPidMeta();
|
|
294
|
+
mgr.state = STATE.STOPPED;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function startServer(contract) {
|
|
299
|
+
const { serverCmd, modelPath, threadCount, host } = contract;
|
|
300
|
+
mgr.state = STATE.STARTING;
|
|
301
|
+
mgr.contract = contract;
|
|
302
|
+
mgr.runtimeKey = runtimeKeyOf(contract);
|
|
303
|
+
mgr.host = host;
|
|
304
|
+
mgr.port = FIXED_PORT;
|
|
305
|
+
|
|
306
|
+
// ── Pre-spawn: reclaim a positively-owned stale child, else fail closed. ──
|
|
307
|
+
const meta = readPidMeta();
|
|
308
|
+
// Prove ownership against OUR contract (modelPath OR --port) before killing.
|
|
309
|
+
// Prefer the recorded meta's own contract fields; fall back to the contract we
|
|
310
|
+
// are about to start. A non-positive match → never kill (fail closed).
|
|
311
|
+
const ownArgs = { modelPath: meta?.modelPath ?? modelPath, port: meta?.port ?? FIXED_PORT };
|
|
312
|
+
if (meta && isOwnedWhisperServer(meta.pid, ownArgs)) {
|
|
313
|
+
killPid(meta.pid, false);
|
|
314
|
+
await delay(500);
|
|
315
|
+
if (isOwnedWhisperServer(meta.pid, ownArgs)) { killPid(meta.pid, true); await delay(500); }
|
|
316
|
+
clearPidMeta();
|
|
317
|
+
}
|
|
318
|
+
if (await probePort(host, FIXED_PORT)) {
|
|
319
|
+
// Port held by a process we do NOT positively own → never scan a range,
|
|
320
|
+
// never kill a foreign process; fail closed.
|
|
321
|
+
mgr.state = STATE.STOPPED;
|
|
322
|
+
throw new Error(
|
|
323
|
+
`whisper-server: fixed port ${FIXED_PORT} on ${host} is occupied by a non-owned process; refusing to start (fail closed)`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// ── Spawn ONCE: non-detached, cwd = dirname(serverCmd) for Windows DLL load. ──
|
|
328
|
+
const args = [
|
|
329
|
+
'--model', modelPath,
|
|
330
|
+
'--host', host,
|
|
331
|
+
'--port', String(FIXED_PORT),
|
|
332
|
+
'-t', String(threadCount),
|
|
333
|
+
];
|
|
334
|
+
const child = spawn(serverCmd, args, {
|
|
335
|
+
cwd: path.dirname(serverCmd), // resolve co-located CUDA/runtime DLLs
|
|
336
|
+
detached: false,
|
|
337
|
+
windowsHide: true,
|
|
338
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
339
|
+
});
|
|
340
|
+
mgr.child = child;
|
|
341
|
+
wireChildExit();
|
|
342
|
+
writePidMeta();
|
|
343
|
+
await applyLowPriorityOnce(child); // advisory, once
|
|
344
|
+
|
|
345
|
+
// ── Wait for READY: TCP bind + (when flushed) listening-line port assertion. ──
|
|
346
|
+
const deadline = Date.now() + READY_TIMEOUT_MS;
|
|
347
|
+
while (Date.now() < deadline) {
|
|
348
|
+
if (mgr.state === STATE.DEAD || !mgr.child) {
|
|
349
|
+
throw new Error(`whisper-server died during startup:\n${mgr.logTail.slice(-1000)}`);
|
|
350
|
+
}
|
|
351
|
+
// Assert the printed bound port matches the fixed contract port when seen.
|
|
352
|
+
const m = mgr.logTail.match(/listening at https?:\/\/[^\s:/]+:(\d+)/i);
|
|
353
|
+
if (m) {
|
|
354
|
+
const printed = Number.parseInt(m[1], 10);
|
|
355
|
+
if (printed !== FIXED_PORT) {
|
|
356
|
+
await teardown('port-contract-mismatch');
|
|
357
|
+
mgr.state = STATE.DEAD;
|
|
358
|
+
throw new Error(
|
|
359
|
+
`whisper-server bound port ${printed} but contract requires ${FIXED_PORT}`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (await probePort(host, FIXED_PORT)) {
|
|
364
|
+
mgr.port = FIXED_PORT;
|
|
365
|
+
mgr.state = STATE.READY;
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
await delay(READY_POLL_MS);
|
|
369
|
+
}
|
|
370
|
+
// Timed out: tear down deterministically and fail (no fallback).
|
|
371
|
+
await teardown('readiness-timeout');
|
|
372
|
+
mgr.state = STATE.DEAD;
|
|
373
|
+
throw new Error(`whisper-server did not become ready within ${READY_TIMEOUT_MS}ms`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Ensure a READY whisper-server matching the exact contract. Spawns once; on a
|
|
378
|
+
* contract change deterministically stops the old child and starts the new one.
|
|
379
|
+
* Resolves only when the port is bound and the model is loaded.
|
|
380
|
+
*/
|
|
381
|
+
export async function ensureReady({ serverCmd, modelPath, threadCount, host = '127.0.0.1' }) {
|
|
382
|
+
if (!serverCmd) throw new Error('ensureReady: serverCmd is required');
|
|
383
|
+
if (!modelPath) throw new Error('ensureReady: modelPath is required');
|
|
384
|
+
if (!Number.isInteger(threadCount) || threadCount < 1) {
|
|
385
|
+
throw new Error(`ensureReady: threadCount must be a positive integer (got ${threadCount})`);
|
|
386
|
+
}
|
|
387
|
+
const contract = { serverCmd, modelPath, threadCount, host };
|
|
388
|
+
const key = runtimeKeyOf(contract);
|
|
389
|
+
|
|
390
|
+
// Already READY on the same contract with a live child → done.
|
|
391
|
+
if (mgr.state === STATE.READY && mgr.runtimeKey === key && mgr.child) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
// A start is already in flight for the SAME contract → await it.
|
|
395
|
+
if (mgr.startPromise && mgr.runtimeKey === key && mgr.state === STATE.STARTING) {
|
|
396
|
+
return mgr.startPromise;
|
|
397
|
+
}
|
|
398
|
+
// Contract changed (or stale/dead) → tear down anything current first.
|
|
399
|
+
if (mgr.child || mgr.state === STATE.STARTING) {
|
|
400
|
+
mgr.state = STATE.STOPPING;
|
|
401
|
+
await teardown('contract-change');
|
|
402
|
+
mgr.state = STATE.STOPPED;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
mgr.startPromise = startServer(contract).finally(() => { mgr.startPromise = null; });
|
|
406
|
+
return mgr.startPromise;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Transcribe a WAV via POST <host:port>/inference (multipart). Returns the text.
|
|
411
|
+
* On ANY failure the request fails AND the server is marked DEAD (the next
|
|
412
|
+
* ensureReady recreates the contract). There is NO fallback.
|
|
413
|
+
*/
|
|
414
|
+
export async function transcribe(wavPath, { language } = {}) {
|
|
415
|
+
if (mgr.state !== STATE.READY || !mgr.child || !mgr.port) {
|
|
416
|
+
throw new Error(`transcribe: whisper-server not READY (state=${mgr.state})`);
|
|
417
|
+
}
|
|
418
|
+
const host = mgr.host;
|
|
419
|
+
const port = mgr.port;
|
|
420
|
+
const ctrl = new AbortController();
|
|
421
|
+
mgr.inflight.add(ctrl);
|
|
422
|
+
try {
|
|
423
|
+
const data = await fs.promises.readFile(wavPath);
|
|
424
|
+
const form = new FormData();
|
|
425
|
+
form.append('file', new Blob([data], { type: 'audio/wav' }), path.basename(wavPath));
|
|
426
|
+
form.append('response_format', 'json'); // → { "text": "..." }
|
|
427
|
+
if (language) form.append('language', String(language));
|
|
428
|
+
|
|
429
|
+
const res = await fetch(`http://${host}:${port}${INFERENCE_PATH}`, {
|
|
430
|
+
method: 'POST',
|
|
431
|
+
body: form,
|
|
432
|
+
signal: ctrl.signal,
|
|
433
|
+
});
|
|
434
|
+
if (!res.ok) {
|
|
435
|
+
const body = await res.text().catch(() => '');
|
|
436
|
+
throw new Error(`whisper-server /inference HTTP ${res.status}: ${body.slice(0, 500)}`);
|
|
437
|
+
}
|
|
438
|
+
const json = await res.json();
|
|
439
|
+
if (typeof json?.text !== 'string') {
|
|
440
|
+
throw new Error(`whisper-server /inference returned no text field: ${JSON.stringify(json).slice(0, 300)}`);
|
|
441
|
+
}
|
|
442
|
+
return json.text;
|
|
443
|
+
} catch (err) {
|
|
444
|
+
// Request failed → tear the child down and settle to ONE resting state. No fallback.
|
|
445
|
+
mgr.inflight.delete(ctrl);
|
|
446
|
+
if (mgr.state === STATE.READY || mgr.state === STATE.STARTING) {
|
|
447
|
+
// Deterministic teardown → single resting state. This is NOT a contradiction:
|
|
448
|
+
// DEAD = transient "death detected" marker. An /inference failure means
|
|
449
|
+
// the contract is no longer satisfiable on the current child, so
|
|
450
|
+
// we record the death and abort/kill it via teardown().
|
|
451
|
+
// STOPPED = the durable resting state once teardown completes. teardown()
|
|
452
|
+
// nulls mgr.child/contract/runtimeKey and clears pid metadata, so
|
|
453
|
+
// STOPPED here is "awaiting recreate", indistinguishable from a
|
|
454
|
+
// clean stop. ensureReady() treats STOPPED-with-no-child as
|
|
455
|
+
// recreate-eligible (its READY/STARTING fast-paths fail and it
|
|
456
|
+
// falls through to startServer), so the NEXT ensureReady() rebuilds
|
|
457
|
+
// the SAME contract. There is no stuck-in-DEAD path: DEAD is only
|
|
458
|
+
// ever a momentary marker that always resolves to STOPPED.
|
|
459
|
+
mgr.state = STATE.DEAD; // transient: death detected
|
|
460
|
+
try { await teardown('transcribe-failure'); } catch {}
|
|
461
|
+
mgr.state = STATE.STOPPED; // resting: awaiting recreate
|
|
462
|
+
}
|
|
463
|
+
throw err;
|
|
464
|
+
} finally {
|
|
465
|
+
mgr.inflight.delete(ctrl);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Stop the managed server: abort in-flight requests, terminate the child with
|
|
471
|
+
* deterministic SIGTERM → SIGKILL escalation, and settle to STOPPED.
|
|
472
|
+
*/
|
|
473
|
+
export async function stopVoiceWhisperServer() {
|
|
474
|
+
if (mgr.state === STATE.STOPPED && !mgr.child) return;
|
|
475
|
+
mgr.state = STATE.STOPPING;
|
|
476
|
+
try { await teardown('explicit-stop'); } finally { mgr.state = STATE.STOPPED; }
|
|
477
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export const TOOL_DEFS = [
|
|
2
|
+
{
|
|
3
|
+
name: "reply",
|
|
4
|
+
title: "Discord Reply",
|
|
5
|
+
annotations: { title: "Discord Reply", readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
|
|
6
|
+
description: "Reply on channel.",
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: "object",
|
|
9
|
+
properties: {
|
|
10
|
+
chat_id: { type: "string" },
|
|
11
|
+
text: { type: "string" },
|
|
12
|
+
reply_to: { type: "string" },
|
|
13
|
+
files: {
|
|
14
|
+
type: "array",
|
|
15
|
+
items: { type: "string" }
|
|
16
|
+
},
|
|
17
|
+
embeds: {
|
|
18
|
+
type: "array",
|
|
19
|
+
items: { type: "object", additionalProperties: true }
|
|
20
|
+
},
|
|
21
|
+
components: {
|
|
22
|
+
type: "array",
|
|
23
|
+
items: { type: "object", additionalProperties: true }
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ["chat_id", "text"]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: "react",
|
|
31
|
+
title: "Reaction",
|
|
32
|
+
annotations: { title: "Reaction", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
33
|
+
description: "React to message.",
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: "object",
|
|
36
|
+
properties: {
|
|
37
|
+
chat_id: { type: "string" },
|
|
38
|
+
message_id: { type: "string" },
|
|
39
|
+
emoji: { type: "string" }
|
|
40
|
+
},
|
|
41
|
+
required: ["chat_id", "message_id", "emoji"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: "edit_message",
|
|
46
|
+
title: "Edit Message",
|
|
47
|
+
annotations: { title: "Edit Message", readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
|
|
48
|
+
description: "Edit bot message.",
|
|
49
|
+
inputSchema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
chat_id: { type: "string" },
|
|
53
|
+
message_id: { type: "string" },
|
|
54
|
+
text: { type: "string" },
|
|
55
|
+
embeds: {
|
|
56
|
+
type: "array",
|
|
57
|
+
items: { type: "object", additionalProperties: true }
|
|
58
|
+
},
|
|
59
|
+
components: {
|
|
60
|
+
type: "array",
|
|
61
|
+
items: { type: "object", additionalProperties: true }
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["chat_id", "message_id", "text"]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "download_attachment",
|
|
69
|
+
title: "Download Attachment",
|
|
70
|
+
annotations: { title: "Download Attachment", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
71
|
+
description: "Download attachments.",
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
chat_id: { type: "string" },
|
|
76
|
+
message_id: { type: "string" }
|
|
77
|
+
},
|
|
78
|
+
required: ["chat_id", "message_id"]
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "fetch",
|
|
83
|
+
title: "Fetch",
|
|
84
|
+
annotations: { title: "Fetch", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
85
|
+
description: "Discord-only: fetch recent messages from a Discord channel. Requires channel (id); optional limit (count). NOT for URLs or web pages — use web_fetch for those.",
|
|
86
|
+
inputSchema: {
|
|
87
|
+
type: "object",
|
|
88
|
+
properties: {
|
|
89
|
+
channel: { type: "string" },
|
|
90
|
+
limit: { type: "number" }
|
|
91
|
+
},
|
|
92
|
+
required: ["channel"]
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "schedule_status",
|
|
97
|
+
title: "Schedule Status",
|
|
98
|
+
annotations: { title: "Schedule Status", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
99
|
+
description: "Show schedules.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {}
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "trigger_schedule",
|
|
107
|
+
title: "Trigger Schedule",
|
|
108
|
+
annotations: { title: "Trigger Schedule", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
109
|
+
description: "Run a scheduled task immediately by name (fire it now). Requires name.",
|
|
110
|
+
inputSchema: {
|
|
111
|
+
type: "object",
|
|
112
|
+
properties: {
|
|
113
|
+
name: { type: "string" }
|
|
114
|
+
},
|
|
115
|
+
required: ["name"]
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "schedule_control",
|
|
120
|
+
title: "Schedule Control",
|
|
121
|
+
annotations: { title: "Schedule Control", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
122
|
+
description: 'Defer or skip a schedule (not run it now): action=defer (push by `minutes`) | skip_today. Requires name + action.',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
properties: {
|
|
126
|
+
name: { type: "string" },
|
|
127
|
+
action: { type: "string", enum: ["defer", "skip_today"] },
|
|
128
|
+
minutes: { type: "number" }
|
|
129
|
+
},
|
|
130
|
+
required: ["name", "action"]
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "activate_channel_bridge",
|
|
135
|
+
title: "Activate Channel Bridge",
|
|
136
|
+
annotations: { title: "Activate Channel Bridge", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
137
|
+
description: "Set channel bridge active.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: "object",
|
|
140
|
+
properties: {
|
|
141
|
+
active: { type: "boolean" }
|
|
142
|
+
},
|
|
143
|
+
required: ["active"]
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
// memory and recall_memory tools are now provided by memory-service.mjs via MCP
|
|
147
|
+
{
|
|
148
|
+
name: "reload_config",
|
|
149
|
+
title: "Reload Config",
|
|
150
|
+
annotations: { title: "Reload Config", readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
151
|
+
description: "Reload config.",
|
|
152
|
+
inputSchema: { type: "object", properties: {} }
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "inject_command",
|
|
156
|
+
title: "Inject Claude Code Slash Command",
|
|
157
|
+
annotations: { title: "Inject Claude Code Slash Command", readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
158
|
+
description: "Inject slash command.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
command: {
|
|
163
|
+
type: "string",
|
|
164
|
+
enum: ["reload-plugins", "clear"]
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
required: ["command"]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
];
|