mixdog 0.9.3 → 0.9.4
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/package.json +7 -3
- package/scripts/bench/lead-review-tasks-r3.json +20 -0
- package/scripts/bench/lead-review-tasks.json +20 -0
- package/scripts/bench/r4-mixed-tasks.json +20 -0
- package/scripts/bench/review-tasks.json +20 -0
- package/scripts/bench/round-codex.json +114 -0
- package/scripts/bench/round-mixdog-lead-r3.json +269 -0
- package/scripts/bench/round-mixdog-lead.json +269 -0
- package/scripts/bench/round-mixdog.json +126 -0
- package/scripts/bench/round-r10-bigsample.json +679 -0
- package/scripts/bench/round-r11-codexalign.json +257 -0
- package/scripts/bench/round-r4-codex.json +114 -0
- package/scripts/bench/round-r4-mixed.json +225 -0
- package/scripts/bench/round-r5-gpt-lead.json +259 -0
- package/scripts/bench/round-r6-codex.json +114 -0
- package/scripts/bench/round-r6-solo.json +257 -0
- package/scripts/bench/round-r7-full.json +254 -0
- package/scripts/bench/round-r8-fulldefault.json +255 -0
- package/scripts/bench-run.mjs +215 -29
- package/scripts/freevar-smoke.mjs +95 -0
- package/scripts/internal-comms-bench.mjs +1 -0
- package/scripts/internal-comms-smoke.mjs +10 -9
- package/scripts/mouse-probe.mjs +45 -0
- package/scripts/output-style-bench.mjs +13 -6
- package/scripts/output-style-smoke.mjs +4 -4
- package/scripts/provider-toolcall-test.mjs +7 -3
- package/scripts/recall-usecase-cases.json +18 -0
- package/scripts/recall-usecase-probe.json +6 -0
- package/scripts/session-bench.mjs +152 -6
- package/scripts/tool-smoke.mjs +23 -63
- package/scripts/tui-render-smoke.mjs +90 -0
- package/scripts/webhook-smoke.mjs +208 -0
- package/src/agents/debugger/AGENT.md +4 -1
- package/src/agents/heavy-worker/AGENT.md +6 -5
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +2 -1
- package/src/agents/worker/AGENT.md +8 -4
- package/src/lib/rules-builder.cjs +4 -0
- package/src/mixdog-session-runtime.mjs +632 -2042
- package/src/output-styles/default.md +34 -9
- package/src/output-styles/{oneline.md → extreme-minimal.md} +5 -4
- package/src/output-styles/minimal.md +4 -1
- package/src/output-styles/simple.md +22 -7
- package/src/rules/agent/00-common.md +2 -0
- package/src/rules/lead/lead-brief.md +12 -0
- package/src/rules/lead/lead-tool.md +0 -11
- package/src/runtime/agent/orchestrator/agent-runtime/agent-loop-policy.mjs +25 -0
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +100 -23
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +6 -15
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +362 -0
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +410 -0
- package/src/runtime/agent/orchestrator/agent-trace.mjs +16 -735
- package/src/runtime/agent/orchestrator/config.mjs +69 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-effort.mjs +62 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +209 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +489 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +81 -1281
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +607 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +32 -3
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +81 -0
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +248 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +303 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +505 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +43 -1013
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +86 -11
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +348 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +108 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-trace.mjs +58 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +368 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +760 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +40 -1143
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +732 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +193 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +297 -2123
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +130 -1002
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +227 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-events.mjs +67 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +436 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +1105 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/compact/budget.mjs +288 -0
- package/src/runtime/agent/orchestrator/session/compact/constants.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +749 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +82 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-schema.mjs +315 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +643 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +326 -0
- package/src/runtime/agent/orchestrator/session/compact.mjs +40 -2282
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/completion-guards.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +182 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +173 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +58 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +239 -0
- package/src/runtime/agent/orchestrator/session/loop.mjs +251 -397
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +471 -0
- package/src/runtime/agent/orchestrator/session/manager/context-meta.mjs +7 -4
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +406 -0
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +210 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +166 -1087
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +189 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +74 -179
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +70 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +22 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +32 -41
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +40 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +29 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +126 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +81 -92
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +161 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +108 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +28 -265
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +0 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +57 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/keyword-match.mjs +82 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +10 -122
- package/src/runtime/agent/orchestrator/tools/code-graph/text-columns.mjs +45 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/graph-binary-fetcher.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/patch/constants.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +171 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +471 -0
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +436 -0
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +342 -0
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +359 -0
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +340 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +643 -0
- package/src/runtime/agent/orchestrator/tools/patch.mjs +36 -2959
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +0 -21
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +9 -72
- package/src/runtime/agent/orchestrator/tools/shell-powershell.mjs +77 -0
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +154 -0
- package/src/runtime/channels/backends/discord-access.mjs +32 -0
- package/src/runtime/channels/backends/discord-attachments.mjs +65 -0
- package/src/runtime/channels/backends/discord-gateway.mjs +233 -0
- package/src/runtime/channels/backends/discord.mjs +12 -292
- package/src/runtime/channels/index.mjs +229 -663
- package/src/runtime/channels/lib/backend-dispatch.mjs +44 -0
- package/src/runtime/channels/lib/event-pipeline.mjs +18 -1
- package/src/runtime/channels/lib/event-queue.mjs +63 -4
- package/src/runtime/channels/lib/inbound-routing.mjs +111 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +1 -1
- package/src/runtime/channels/lib/owner-heartbeat.mjs +75 -0
- package/src/runtime/channels/lib/parent-bridge.mjs +88 -0
- package/src/runtime/channels/lib/runtime-paths.mjs +14 -4
- package/src/runtime/channels/lib/session-discovery.mjs +56 -4
- package/src/runtime/channels/lib/tool-dispatch.mjs +158 -0
- package/src/runtime/channels/lib/tool-format.mjs +1 -1
- package/src/runtime/channels/lib/transcript-discovery.mjs +4 -4
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -3
- package/src/runtime/channels/lib/voice-transcription.mjs +179 -0
- package/src/runtime/channels/lib/webhook/deliveries.mjs +312 -0
- package/src/runtime/channels/lib/webhook/log.mjs +42 -0
- package/src/runtime/channels/lib/webhook/ngrok.mjs +181 -0
- package/src/runtime/channels/lib/webhook/signature.mjs +60 -0
- package/src/runtime/channels/lib/webhook.mjs +36 -570
- package/src/runtime/channels/tool-defs.mjs +11 -130
- package/src/runtime/memory/index.mjs +201 -1948
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +58 -0
- package/src/runtime/memory/lib/cycle-scheduler.mjs +497 -0
- package/src/runtime/memory/lib/embedding-warmup.mjs +58 -0
- package/src/runtime/memory/lib/memory-config-flags.mjs +91 -0
- package/src/runtime/memory/lib/memory-cycle.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +515 -0
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +324 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +18 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +24 -842
- package/src/runtime/memory/lib/memory-embed.mjs +149 -0
- package/src/runtime/memory/lib/memory-process-lock.mjs +162 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +22 -2
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -1
- package/src/runtime/memory/lib/query-handlers.mjs +780 -0
- package/src/runtime/memory/lib/recall-format.mjs +55 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +8 -3
- package/src/runtime/memory/lib/transcript-ingest.mjs +425 -0
- package/src/runtime/memory/tool-defs.mjs +5 -13
- package/src/runtime/search/lib/http-fetch.mjs +274 -0
- package/src/runtime/search/lib/ssrf-guard.mjs +333 -0
- package/src/runtime/search/lib/web-tools.mjs +24 -602
- package/src/runtime/shared/atomic-file.mjs +26 -1
- package/src/runtime/shared/launcher-control.mjs +2 -2
- package/src/runtime/shared/tool-primitives.mjs +308 -0
- package/src/runtime/shared/tool-result-summary.mjs +515 -0
- package/src/runtime/shared/tool-surface.mjs +80 -898
- package/src/runtime/shared/transcript-writer.mjs +23 -0
- package/src/runtime/shared/update-checker.mjs +7 -4
- package/src/session-runtime/config-helpers.mjs +84 -2
- package/src/session-runtime/config-lifecycle.mjs +232 -0
- package/src/session-runtime/cwd-plugins.mjs +226 -0
- package/src/session-runtime/mcp-glue.mjs +177 -0
- package/src/session-runtime/model-recency.mjs +111 -0
- package/src/session-runtime/native-search.mjs +247 -0
- package/src/session-runtime/output-styles.mjs +11 -9
- package/src/session-runtime/prewarm.mjs +142 -0
- package/src/session-runtime/provider-models.mjs +278 -0
- package/src/session-runtime/provider-usage.mjs +120 -0
- package/src/session-runtime/quick-model-rows.mjs +170 -0
- package/src/session-runtime/quick-search-models.mjs +46 -0
- package/src/session-runtime/session-hooks.mjs +93 -0
- package/src/session-runtime/settings-api.mjs +319 -0
- package/src/session-runtime/tool-catalog.mjs +29 -29
- package/src/session-runtime/tool-defs.mjs +84 -0
- package/src/session-runtime/warmup-schedulers.mjs +201 -0
- package/src/standalone/agent-tool/helpers.mjs +237 -0
- package/src/standalone/agent-tool/notify.mjs +107 -0
- package/src/standalone/agent-tool/provider-init.mjs +143 -0
- package/src/standalone/agent-tool/render.mjs +152 -0
- package/src/standalone/agent-tool/tool-def.mjs +55 -0
- package/src/standalone/agent-tool.mjs +110 -671
- package/src/standalone/channel-worker.mjs +4 -7
- package/src/standalone/explore-tool.mjs +30 -9
- package/src/standalone/hook-bus/config.mjs +207 -0
- package/src/standalone/hook-bus/constants.mjs +90 -0
- package/src/standalone/hook-bus/handlers.mjs +481 -0
- package/src/standalone/hook-bus/payload.mjs +31 -0
- package/src/standalone/hook-bus/rules.mjs +77 -0
- package/src/standalone/hook-bus.mjs +77 -870
- package/src/standalone/memory-runtime-proxy.mjs +7 -0
- package/src/standalone/opencode-go-login.mjs +5 -1
- package/src/standalone/provider-admin.mjs +1 -16
- package/src/standalone/usage-dashboard.mjs +3 -1
- package/src/tui/App.jsx +945 -8094
- package/src/tui/app/app-format.mjs +206 -0
- package/src/tui/app/channel-pickers.mjs +510 -0
- package/src/tui/app/clipboard.mjs +67 -0
- package/src/tui/app/core-memory-picker.mjs +210 -0
- package/src/tui/app/extension-pickers.mjs +506 -0
- package/src/tui/app/input-parsers.mjs +193 -0
- package/src/tui/app/maintenance-pickers.mjs +324 -0
- package/src/tui/app/model-options.mjs +330 -0
- package/src/tui/app/model-picker.mjs +365 -0
- package/src/tui/app/onboarding-steps.mjs +400 -0
- package/src/tui/app/project-picker.mjs +247 -0
- package/src/tui/app/provider-setup-picker.mjs +580 -0
- package/src/tui/app/resume-picker.mjs +55 -0
- package/src/tui/app/route-pickers.mjs +419 -0
- package/src/tui/app/settings-picker.mjs +490 -0
- package/src/tui/app/slash-commands.mjs +101 -0
- package/src/tui/app/slash-dispatch.mjs +427 -0
- package/src/tui/app/text-layout.mjs +46 -0
- package/src/tui/app/theme-effort-pickers.mjs +154 -0
- package/src/tui/app/transcript-window.mjs +671 -0
- package/src/tui/app/use-mouse-input.mjs +460 -0
- package/src/tui/app/use-prompt-handlers.mjs +310 -0
- package/src/tui/app/use-transcript-scroll.mjs +510 -0
- package/src/tui/app/use-transcript-window.mjs +589 -0
- package/src/tui/components/ConfirmBar.jsx +1 -1
- package/src/tui/components/Picker.jsx +32 -4
- package/src/tui/components/PromptInput.jsx +23 -101
- package/src/tui/components/SlashCommandPalette.jsx +8 -1
- package/src/tui/components/StatusLine.jsx +63 -12
- package/src/tui/components/TextEntryPanel.jsx +11 -0
- package/src/tui/components/ToolExecution.jsx +52 -594
- package/src/tui/components/TranscriptItem.jsx +105 -0
- package/src/tui/components/UsagePanel.jsx +18 -4
- package/src/tui/components/prompt-input/edit-helpers.mjs +72 -0
- package/src/tui/components/prompt-input/voice-indicator.mjs +39 -0
- package/src/tui/components/tool-execution/ResultBody.jsx +56 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +405 -0
- package/src/tui/components/tool-execution/text-format.mjs +161 -0
- package/src/tui/display-width.mjs +20 -3
- package/src/tui/dist/index.mjs +19652 -18630
- package/src/tui/engine/agent-job-feed.mjs +133 -0
- package/src/tui/engine/notification-plan.mjs +76 -0
- package/src/tui/engine/render-timing.mjs +17 -0
- package/src/tui/engine/tool-approval.mjs +94 -0
- package/src/tui/engine/tool-card-results.mjs +234 -0
- package/src/tui/engine/tool-result-status.mjs +135 -0
- package/src/tui/engine.mjs +122 -562
- package/src/tui/figures.mjs +5 -0
- package/src/tui/index.jsx +105 -0
- package/src/tui/input-editing.mjs +2 -2
- package/src/tui/markdown/format-token.mjs +4 -1
- package/src/tui/statusline-ansi-bridge.mjs +11 -3
- package/src/tui/theme.mjs +6 -0
- package/src/ui/statusline-agents.mjs +213 -0
- package/src/ui/statusline-format.mjs +146 -0
- package/src/ui/statusline-segments.mjs +148 -0
- package/src/ui/statusline.mjs +67 -501
- package/src/ui/tool-card.mjs +0 -1
- package/src/vendor/statusline/bin/statusline-route.mjs +15 -2
- package/src/workflows/default/WORKFLOW.md +1 -1
- package/src/workflows/sequential/WORKFLOW.md +1 -1
- package/vendor/ink/build/display-width.js +19 -3
- package/vendor/ink/build/ink.js +103 -6
- package/vendor/ink/build/log-update.js +17 -3
- package/vendor/ink/build/wrap-text.js +125 -0
- package/scripts/_test-folder-dialog.mjs +0 -30
- package/scripts/fix-brief-fn.mjs +0 -35
- package/scripts/fix-format-tool-surface.mjs +0 -24
- package/scripts/fix-tool-exec-visible.mjs +0 -42
- package/scripts/patch-agent-brief.mjs +0 -48
- package/scripts/patch-app.mjs +0 -21
- package/scripts/patch-app2.mjs +0 -18
- package/scripts/patch-dist-brief.mjs +0 -96
- package/scripts/patch-tool-exec.mjs +0 -70
- package/src/examples/schedules/SCHEDULE.example.md +0 -32
- package/src/examples/webhooks/WEBHOOK.example.md +0 -40
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +0 -107
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.test.mjs +0 -143
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -285
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.test.mjs +0 -162
- package/src/runtime/agent/orchestrator/tools/builtin/open-config-tool.mjs +0 -26
- package/src/runtime/shared/channel-notification-routing.test.mjs +0 -45
- package/src/runtime/shared/task-notification-envelope.test.mjs +0 -107
- package/src/runtime/shared/tool-execution-contract.test.mjs +0 -183
- package/src/standalone/agent-task-status.test.mjs +0 -76
- package/src/tui/components/tool-output-format.test.mjs +0 -399
- package/src/tui/display-width.test.mjs +0 -35
- package/src/tui/engine-runtime-notification.test.mjs +0 -115
- package/src/tui/engine-tool-result-text.test.mjs +0 -75
- package/src/tui/input-editing.selection.test.mjs +0 -75
- package/src/tui/markdown/format-token.test.mjs +0 -354
- package/src/tui/markdown/render-ansi.test.mjs +0 -108
- package/src/tui/markdown/stream-fence.test.mjs +0 -26
- package/src/tui/markdown/streaming-markdown.test.mjs +0 -70
- package/src/tui/paste-fix.test.mjs +0 -119
- package/src/tui/prompt-history-store.test.mjs +0 -52
- package/src/tui/statusline-ansi-bridge.test.mjs +0 -159
- package/src/tui/transcript-tool-failures.test.mjs +0 -111
- package/src/ui/markdown.test.mjs +0 -70
- package/src/ui/statusline-context-label.test.mjs +0 -15
- package/src/vendor/statusline/bin/statusline-lib.mjs +0 -186
- package/src/vendor/statusline/bin/statusline-route.test.mjs +0 -80
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { OutputForwarder } from "./output-forwarder.mjs";
|
|
2
|
+
|
|
3
|
+
// Worker/HTTP tool-call dispatch + bridge auto-connect retry wrapper.
|
|
4
|
+
// Extracted verbatim from channels/index.mjs (behavior-preserving). The
|
|
5
|
+
// `handleToolCall` switch is entangled with ~8 runtime-lifecycle functions
|
|
6
|
+
// (claim/refresh ownership, stop owned runtime, transcript rebind, config
|
|
7
|
+
// reload) plus mutable owner state (channelBridgeActive, bridgeRuntimeConnected)
|
|
8
|
+
// and the forwarder. Those are threaded in as a `lifecycle` bag of lazy
|
|
9
|
+
// getters/functions so the module reads the live file-level references at
|
|
10
|
+
// call time — matching the original in-file closure semantics.
|
|
11
|
+
function createToolDispatch({
|
|
12
|
+
getForwarder,
|
|
13
|
+
BACKEND_TOOLS,
|
|
14
|
+
isChannelsDegraded,
|
|
15
|
+
dispatchReply,
|
|
16
|
+
dispatchFetch,
|
|
17
|
+
lifecycle,
|
|
18
|
+
}) {
|
|
19
|
+
const {
|
|
20
|
+
getBridgeRuntimeConnected,
|
|
21
|
+
getChannelBridgeActive,
|
|
22
|
+
setChannelBridgeActive,
|
|
23
|
+
writeBridgeState,
|
|
24
|
+
stopServerTyping,
|
|
25
|
+
claimBridgeOwnership,
|
|
26
|
+
refreshBridgeOwnership,
|
|
27
|
+
bindPersistedTranscriptIfAny,
|
|
28
|
+
stopOwnedRuntime,
|
|
29
|
+
reloadRuntimeConfig,
|
|
30
|
+
} = lifecycle;
|
|
31
|
+
|
|
32
|
+
async function handleToolCall(name, args, _signal) {
|
|
33
|
+
if (isChannelsDegraded()) {
|
|
34
|
+
return { content: [{ type: 'text', text: `[channels degraded] ${name} unavailable — restart MCP to recover` }], isError: true }
|
|
35
|
+
}
|
|
36
|
+
let result;
|
|
37
|
+
try {
|
|
38
|
+
switch (name) {
|
|
39
|
+
case "reply":
|
|
40
|
+
result = await dispatchReply(args);
|
|
41
|
+
break;
|
|
42
|
+
case "fetch":
|
|
43
|
+
result = await dispatchFetch(args);
|
|
44
|
+
break;
|
|
45
|
+
case "activate_channel_bridge": {
|
|
46
|
+
const active = args.active === true;
|
|
47
|
+
const wasActive = getChannelBridgeActive();
|
|
48
|
+
setChannelBridgeActive(active);
|
|
49
|
+
writeBridgeState(active);
|
|
50
|
+
if (active) {
|
|
51
|
+
// Force-takeover semantics: activation ALWAYS claims the seat
|
|
52
|
+
// (last-wins overwrite of active-instance.json) and refreshes the
|
|
53
|
+
// output-forwarder binding onto THIS session — even when the
|
|
54
|
+
// bridge was already active. `/remote` relies on this to re-occupy
|
|
55
|
+
// after another session stole ownership, and to re-pin forwarding
|
|
56
|
+
// to the current session transcript.
|
|
57
|
+
claimBridgeOwnership(wasActive ? "re-activate takeover" : "bridge activated");
|
|
58
|
+
try {
|
|
59
|
+
await refreshBridgeOwnership({ restoreBinding: true });
|
|
60
|
+
// An already-connected owner returns early from
|
|
61
|
+
// startOwnedRuntime(), so rebind explicitly to follow the
|
|
62
|
+
// current (parent-chain) session transcript.
|
|
63
|
+
if (getBridgeRuntimeConnected()) await bindPersistedTranscriptIfAny();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
process.stderr.write(`mixdog: bridge activate refresh failed (non-fatal): ${e?.message || e}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (!active && wasActive) {
|
|
69
|
+
stopServerTyping();
|
|
70
|
+
// Tear down the owner-side runtime so Discord/scheduler/webhook/
|
|
71
|
+
// event-pipeline don't keep running on a deactivated bridge.
|
|
72
|
+
try { await stopOwnedRuntime("bridge deactivated"); } catch (e) {
|
|
73
|
+
process.stderr.write(`mixdog: stopOwnedRuntime on deactivate failed: ${e?.message || e}\n`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
result = { content: [{ type: "text", text: `channel bridge ${active ? "activated" : "deactivated"}` }] };
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "reload_config": {
|
|
80
|
+
await reloadRuntimeConfig();
|
|
81
|
+
// Extend reload to the agent module so providers/presets/maintenance
|
|
82
|
+
// hot-reload on the same call (dynamic import: agent/index.mjs does not
|
|
83
|
+
// import channels, so this stays acyclic and tolerant of load order).
|
|
84
|
+
let agentReloadMsg = "";
|
|
85
|
+
if (process.env.MIXDOG_STANDALONE !== '1') {
|
|
86
|
+
try {
|
|
87
|
+
const { reloadAgentConfig } = await import("../../agent/index.mjs");
|
|
88
|
+
await reloadAgentConfig("reload_config tool");
|
|
89
|
+
agentReloadMsg = ", agent providers/presets/maintenance";
|
|
90
|
+
} catch (err) {
|
|
91
|
+
process.stderr.write(`[reload_config] agent reload failed: ${err?.message || String(err)}\n`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
result = { content: [{ type: "text", text: `config reloaded — schedules, webhooks, events${agentReloadMsg} re-registered` }] };
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
// memory — handled by memory-service.mjs MCP
|
|
98
|
+
default:
|
|
99
|
+
result = {
|
|
100
|
+
content: [{ type: "text", text: `unknown tool: ${name}` }],
|
|
101
|
+
isError: true
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
106
|
+
result = {
|
|
107
|
+
content: [{ type: "text", text: `${name} failed: ${msg}` }],
|
|
108
|
+
isError: true
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Bridge auto-connect retry + forwarder-aware tool dispatch wrapper. Used by
|
|
115
|
+
// both the HTTP MCP path (createHttpMcpServer's CallTool handler can call this)
|
|
116
|
+
// and the worker IPC handler at the bottom of index.mjs.
|
|
117
|
+
// Last timestamp a forwardNewText() call was dispatched (debounce for item 4).
|
|
118
|
+
let _lastForwardMs = 0;
|
|
119
|
+
|
|
120
|
+
async function handleToolCallWithBridgeRetry(toolName, args, signal) {
|
|
121
|
+
const forwarder = getForwarder();
|
|
122
|
+
// Debounce: only forward when ≥250 ms have elapsed since the last forward,
|
|
123
|
+
// to avoid one HTTP roundtrip per tool call on rapid-fire sequences.
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
if (now - _lastForwardMs >= 250) {
|
|
126
|
+
_lastForwardMs = now;
|
|
127
|
+
await forwarder.forwardNewText();
|
|
128
|
+
}
|
|
129
|
+
if (BACKEND_TOOLS.has(toolName) && !getBridgeRuntimeConnected()) {
|
|
130
|
+
// Remote-owner startup: ensure this owner's backend is connected.
|
|
131
|
+
for (let i = 0; i < 2 && !getBridgeRuntimeConnected(); i++) {
|
|
132
|
+
try {
|
|
133
|
+
await refreshBridgeOwnership();
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
if (!getBridgeRuntimeConnected()) await new Promise((r) => setTimeout(r, 300));
|
|
137
|
+
}
|
|
138
|
+
if (!getBridgeRuntimeConnected()) {
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: `Discord auto-connect failed after retries. Check token and network.` }],
|
|
141
|
+
isError: true
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const result = await handleToolCall(toolName, args, signal);
|
|
146
|
+
const toolLine = OutputForwarder.buildToolLine(toolName, args);
|
|
147
|
+
if (toolLine) {
|
|
148
|
+
// Distinct from the dispatch-log ok line (server-main.mjs): this forwards
|
|
149
|
+
// a human-readable tool summary to Discord for the user, not operator stdout.
|
|
150
|
+
void forwarder.forwardToolLog(toolLine, toolName, args);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { handleToolCall, handleToolCallWithBridgeRetry };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export { createToolDispatch };
|
|
@@ -42,7 +42,7 @@ function isMemoryFile(filePath) {
|
|
|
42
42
|
function isHidden(name) {
|
|
43
43
|
if (HIDDEN_TOOLS.has(name)) return true;
|
|
44
44
|
if (formatToolSurface(name, {}).label === "Memory") return false;
|
|
45
|
-
if (name === "reply" || name === "
|
|
45
|
+
if (name === "reply" || name === "fetch") return true;
|
|
46
46
|
return false;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
@@ -174,7 +174,7 @@ function compareTranscriptCandidates(left, right) {
|
|
|
174
174
|
if (right.startedAt !== left.startedAt) return right.startedAt - left.startedAt;
|
|
175
175
|
return (right.claudePid ?? 0) - (left.claudePid ?? 0);
|
|
176
176
|
}
|
|
177
|
-
function detectCurrentSessionTranscript() {
|
|
177
|
+
function detectCurrentSessionTranscript(options = {}) {
|
|
178
178
|
const candidates = [];
|
|
179
179
|
const seen = new Set();
|
|
180
180
|
const addCandidate = (candidate) => {
|
|
@@ -185,7 +185,7 @@ function detectCurrentSessionTranscript() {
|
|
|
185
185
|
seen.add(key);
|
|
186
186
|
candidates.push(candidate);
|
|
187
187
|
};
|
|
188
|
-
addCandidate(sessionTranscriptCandidate(discoverCurrentClaudeSession(), "parent-chain"));
|
|
188
|
+
addCandidate(sessionTranscriptCandidate(discoverCurrentClaudeSession(options), "parent-chain"));
|
|
189
189
|
for (const session of listInteractiveClaudeSessions()) {
|
|
190
190
|
addCandidate(sessionTranscriptCandidate(session, "sessions-dir"));
|
|
191
191
|
}
|
|
@@ -193,8 +193,8 @@ function detectCurrentSessionTranscript() {
|
|
|
193
193
|
candidates.sort(compareTranscriptCandidates);
|
|
194
194
|
return candidates[0] ?? null;
|
|
195
195
|
}
|
|
196
|
-
function discoverSessionBoundTranscript() {
|
|
197
|
-
return detectCurrentSessionTranscript();
|
|
196
|
+
function discoverSessionBoundTranscript(options = {}) {
|
|
197
|
+
return detectCurrentSessionTranscript(options);
|
|
198
198
|
}
|
|
199
199
|
|
|
200
200
|
export {
|
|
@@ -178,13 +178,16 @@ async function _withInstallLock(rootDir, lockName, fn, { pollMs = 250 } = {}) {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
async function loadManifest(dataDir) {
|
|
181
|
+
// Bundled manifest FIRST: it always matches the installed mixdog version.
|
|
182
|
+
// A stale cached manifest.json from an older install must never shadow it
|
|
183
|
+
// (caused sha256 mismatches on already-installed machines after upgrades).
|
|
184
|
+
if (existsSync(BUNDLED_MANIFEST_PATH)) {
|
|
185
|
+
return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
|
|
186
|
+
}
|
|
181
187
|
const cachedPath = join(dataDir, 'voice-runtime', 'manifest.json')
|
|
182
188
|
if (existsSync(cachedPath)) {
|
|
183
189
|
try { return JSON.parse(readFileSync(cachedPath, 'utf8')) } catch {}
|
|
184
190
|
}
|
|
185
|
-
if (existsSync(BUNDLED_MANIFEST_PATH)) {
|
|
186
|
-
return JSON.parse(readFileSync(BUNDLED_MANIFEST_PATH, 'utf8'))
|
|
187
|
-
}
|
|
188
191
|
const res = await fetch(MANIFEST_URL, { signal: AbortSignal.timeout(30_000) })
|
|
189
192
|
if (!res.ok) throw new Error(`[voice-runtime] manifest fetch failed: ${res.status} ${res.statusText}`)
|
|
190
193
|
const manifest = await res.json()
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { createRequire } from "module";
|
|
6
|
+
import { resolveVoiceRuntime } from "./voice-runtime-fetcher.mjs";
|
|
7
|
+
import { ensureReady, transcribe } from "./whisper-server.mjs";
|
|
8
|
+
import { normalizeWhisperLanguage, detectDeviceLanguage } from "./whisper-language.mjs";
|
|
9
|
+
|
|
10
|
+
const _require = createRequire(import.meta.url);
|
|
11
|
+
|
|
12
|
+
// Voice-attachment transcription pipeline. Extracted verbatim from
|
|
13
|
+
// channels/index.mjs (behavior-preserving). `config` is read live through the
|
|
14
|
+
// injected getter so runtime config reloads (reloadRuntimeConfig) keep the
|
|
15
|
+
// same semantics as the original file-level `let config` reference.
|
|
16
|
+
function isVoiceAttachment(contentType) {
|
|
17
|
+
if (typeof contentType !== 'string') return false;
|
|
18
|
+
const ct = contentType.toLowerCase();
|
|
19
|
+
return ct.startsWith("audio/") || ct.startsWith("application/ogg");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function runCmd(cmd, args, capture = false) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const proc = spawn(cmd, args, {
|
|
25
|
+
stdio: capture ? ["ignore", "pipe", "ignore"] : "ignore",
|
|
26
|
+
windowsHide: true
|
|
27
|
+
});
|
|
28
|
+
let out = "";
|
|
29
|
+
if (capture && proc.stdout) proc.stdout.on("data", (d) => {
|
|
30
|
+
out += d;
|
|
31
|
+
});
|
|
32
|
+
proc.on("close", (code) => code === 0 ? resolve(out) : reject(new Error(`${cmd} exit ${code}`)));
|
|
33
|
+
proc.on("error", reject);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Creates the voice-transcription surface bound to a live config getter and
|
|
38
|
+
// data dir. Returns { isVoiceAttachment, transcribeVoice }.
|
|
39
|
+
function createVoiceTranscription({ getConfig, dataDir }) {
|
|
40
|
+
// ── voice.transcription concurrency queue (max=1 by default, config-driven) ──
|
|
41
|
+
const _voiceTranscriptionQueue = (() => {
|
|
42
|
+
let running = 0;
|
|
43
|
+
const pending = [];
|
|
44
|
+
function drain() {
|
|
45
|
+
const limit = getConfig().voice?.transcription?.maxConcurrency ?? 1;
|
|
46
|
+
while (running < limit && pending.length > 0) {
|
|
47
|
+
const { fn, resolve, reject } = pending.shift();
|
|
48
|
+
running++;
|
|
49
|
+
fn().then(resolve, reject).finally(() => { running--; drain(); });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return function enqueue(fn) {
|
|
53
|
+
return new Promise((resolve, reject) => { pending.push({ fn, resolve, reject }); drain(); });
|
|
54
|
+
};
|
|
55
|
+
})();
|
|
56
|
+
|
|
57
|
+
// ── wav + transcript cache keyed by attachment id ──
|
|
58
|
+
const _voiceWavCache = new Map(); // attachmentId → wavPath
|
|
59
|
+
const _voiceTranscriptCache = new Map(); // attachmentId → transcript string
|
|
60
|
+
const _voiceInflight = new Map(); // attachmentId → Promise<string|null>
|
|
61
|
+
const _voiceFfmpegInflight = new Map(); // attachmentId|wavPath → Promise<void> single-flight ffmpeg
|
|
62
|
+
|
|
63
|
+
async function _probeAudioDurationSec(filePath) {
|
|
64
|
+
try {
|
|
65
|
+
const ffprobePath = (() => { try { return _require('ffprobe-static').path; } catch { return 'ffprobe'; } })();
|
|
66
|
+
return await new Promise((resolve, reject) => {
|
|
67
|
+
const args = ['-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', filePath];
|
|
68
|
+
let out = '';
|
|
69
|
+
const proc = spawn(ffprobePath, args, { windowsHide: true });
|
|
70
|
+
proc.stdout.on('data', (d) => { out += d; });
|
|
71
|
+
proc.on('close', (code) => { code === 0 ? resolve(parseFloat(out.trim()) || null) : reject(new Error(`ffprobe exit ${code}`)); });
|
|
72
|
+
proc.on('error', reject);
|
|
73
|
+
});
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function transcribeVoice(audioPath, { attachmentId } = {}) {
|
|
80
|
+
const config = getConfig();
|
|
81
|
+
// ── size gate (config: voice.transcription.maxFileSizeMB) ──
|
|
82
|
+
const maxSizeBytes = (config.voice?.transcription?.maxFileSizeMB ?? 0) * 1024 * 1024;
|
|
83
|
+
if (maxSizeBytes > 0) {
|
|
84
|
+
try {
|
|
85
|
+
const stat = await fs.promises.stat(audioPath);
|
|
86
|
+
if (stat.size > maxSizeBytes) {
|
|
87
|
+
process.stderr.write(`mixdog: voice.transcription skipped — file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB > ${config.voice.transcription.maxFileSizeMB} MB): ${audioPath}\n`);
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
} catch { /* stat failure: proceed */ }
|
|
91
|
+
}
|
|
92
|
+
// ── duration gate (config: voice.transcription.maxDurationSec) ──
|
|
93
|
+
const maxDurationSec = config.voice?.transcription?.maxDurationSec ?? 0;
|
|
94
|
+
if (maxDurationSec > 0) {
|
|
95
|
+
const dur = await _probeAudioDurationSec(audioPath);
|
|
96
|
+
if (dur !== null && dur > maxDurationSec) {
|
|
97
|
+
process.stderr.write(`mixdog: voice.transcription skipped — audio too long (${Math.floor(dur)}s > ${maxDurationSec}s): ${audioPath}\n`);
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// ── transcript cache hit ──
|
|
102
|
+
if (attachmentId && _voiceTranscriptCache.has(attachmentId)) {
|
|
103
|
+
process.stderr.write(`mixdog: voice.transcription cache hit (${attachmentId})\n`);
|
|
104
|
+
return _voiceTranscriptCache.get(attachmentId);
|
|
105
|
+
}
|
|
106
|
+
if (attachmentId && _voiceInflight.has(attachmentId)) {
|
|
107
|
+
return _voiceInflight.get(attachmentId);
|
|
108
|
+
}
|
|
109
|
+
const p = _voiceTranscriptionQueue(() => _doTranscribeVoice(audioPath, attachmentId));
|
|
110
|
+
if (attachmentId) {
|
|
111
|
+
_voiceInflight.set(attachmentId, p);
|
|
112
|
+
p.catch((err) => {
|
|
113
|
+
try { process.stderr.write(`mixdog: voice.transcription inflight rejection: ${err?.stack || err}\n`); } catch {}
|
|
114
|
+
}).finally(() => _voiceInflight.delete(attachmentId));
|
|
115
|
+
}
|
|
116
|
+
return p;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function _doTranscribeVoice(audioPath, attachmentId) {
|
|
120
|
+
const config = getConfig();
|
|
121
|
+
try {
|
|
122
|
+
const runtime = resolveVoiceRuntime(dataDir);
|
|
123
|
+
if (!runtime?.installed) {
|
|
124
|
+
const missing = [runtime?.binary ? null : 'binary', runtime?.model ? null : 'model', runtime?.ffmpeg ? null : 'ffmpeg'].filter(Boolean).join(' + ');
|
|
125
|
+
throw new Error(`voice runtime not installed (missing: ${missing}) — open the setup wizard and click "Install voice"`);
|
|
126
|
+
}
|
|
127
|
+
const whisperCmd = runtime.whisperCmd;
|
|
128
|
+
const modelPath = runtime.modelPath;
|
|
129
|
+
const ffmpegPath = runtime.ffmpegPath;
|
|
130
|
+
const lang = normalizeWhisperLanguage(config.voice?.language) ?? detectDeviceLanguage();
|
|
131
|
+
const _cpuCount = (() => { try { return os.cpus().length; } catch { return 2; } })();
|
|
132
|
+
const threadCount = config.voice?.transcription?.threadCount ?? Math.max(1, Math.ceil(_cpuCount / 4));
|
|
133
|
+
// ── wav cache keyed by attachment id ──
|
|
134
|
+
let wavPath;
|
|
135
|
+
if (attachmentId && _voiceWavCache.has(attachmentId)) {
|
|
136
|
+
wavPath = _voiceWavCache.get(attachmentId);
|
|
137
|
+
if (!fs.existsSync(wavPath)) {
|
|
138
|
+
_voiceWavCache.delete(attachmentId);
|
|
139
|
+
wavPath = undefined;
|
|
140
|
+
} else {
|
|
141
|
+
process.stderr.write(`mixdog: voice.transcription wav cache hit (${attachmentId})\n`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (!wavPath) {
|
|
145
|
+
wavPath = audioPath.replace(/\.[^.]+$/, ".wav");
|
|
146
|
+
const sampleRate = config.voice?.transcription?.sampleRate ?? 16000;
|
|
147
|
+
const channels = config.voice?.transcription?.channels ?? 1;
|
|
148
|
+
// Single-flight: parallel callers for the same key share one ffmpeg spawn.
|
|
149
|
+
const _ffmpegKey = attachmentId || wavPath;
|
|
150
|
+
if (_voiceFfmpegInflight.has(_ffmpegKey)) {
|
|
151
|
+
await _voiceFfmpegInflight.get(_ffmpegKey);
|
|
152
|
+
} else {
|
|
153
|
+
const _ffmpegPromise = runCmd(ffmpegPath, ["-i", audioPath, "-ar", String(sampleRate), "-ac", String(channels), "-threads", String(threadCount), "-y", wavPath]);
|
|
154
|
+
_voiceFfmpegInflight.set(_ffmpegKey, _ffmpegPromise);
|
|
155
|
+
try {
|
|
156
|
+
await _ffmpegPromise;
|
|
157
|
+
if (attachmentId) _voiceWavCache.set(attachmentId, wavPath);
|
|
158
|
+
} finally {
|
|
159
|
+
_voiceFfmpegInflight.delete(_ffmpegKey);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
process.stderr.write(`mixdog: voice.transcription start runtime=${runtime.kind} cmd=${path.basename(whisperCmd)}\n`);
|
|
164
|
+
await ensureReady({ serverCmd: runtime.serverCmd, modelPath, threadCount, host: '127.0.0.1' });
|
|
165
|
+
const text = await transcribe(wavPath, { language: lang });
|
|
166
|
+
const result = text.trim() || null;
|
|
167
|
+
if (attachmentId && result) _voiceTranscriptCache.set(attachmentId, result);
|
|
168
|
+
return result;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (err?.message?.startsWith('voice runtime not installed')) throw err; // propagate setup errors; caller posts user-visible failure
|
|
171
|
+
process.stderr.write(`mixdog: voice.transcription failed: ${err}\n`);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { isVoiceAttachment, transcribeVoice };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export { isVoiceAttachment, runCmd, createVoiceTranscription };
|