mixdog 0.9.147 → 0.9.149
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 +23 -3
- package/scripts/build-voice-runtime-unix.sh +101 -0
- package/scripts/embedding-quality-probe.mjs +96 -0
- package/scripts/generate-voice-runtime-manifest.mjs +300 -0
- package/scripts/generate-voice-runtime-manifest.test.mjs +135 -0
- package/scripts/native-binary-arch.mjs +30 -13
- package/scripts/native-binary-arch.test.mjs +40 -4
- package/scripts/plan-voice-runtime-release.mjs +73 -0
- package/scripts/prepare-native-assets.mjs +50 -9
- package/scripts/prune-embedding-runtime.mjs +3 -3
- package/scripts/sync-voice-runtime-manifest.mjs +46 -0
- package/scripts/verify-release-assets.mjs +106 -6
- package/scripts/verify-voice-runtime-release.mjs +89 -0
- package/scripts/voice-runtime-config.json +52 -0
- package/src/app.mjs +42 -5
- package/src/headless-exec.mjs +58 -32
- package/src/headless-exec.test.mjs +74 -13
- package/src/rules/lead/01-general.md +2 -0
- package/src/rules/shared/10-tool-workflow.md +7 -4
- package/src/rules/shared/30-exploration.md +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +28 -9
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +6 -1
- package/src/runtime/agent/orchestrator/config.mjs +53 -18
- package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +139 -0
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +34 -5
- package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -10
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +98 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +67 -16
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +4 -82
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +0 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +62 -121
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +48 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +215 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +44 -48
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +54 -31
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +56 -2
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +34 -2
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +61 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +61 -15
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +120 -1
- package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +0 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +187 -84
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +32 -9
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +78 -33
- package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +21 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +36 -36
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +16 -6
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +50 -213
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +135 -126
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +20 -11
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +112 -36
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +220 -50
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +112 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +130 -60
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +28 -10
- package/src/runtime/agent/orchestrator/providers/registry.mjs +122 -95
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +69 -3
- package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +4 -22
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -14
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +128 -0
- package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +27 -48
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +19 -14
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -43
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +43 -5
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +325 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +599 -195
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +26 -16
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +23 -11
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +118 -3
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +332 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +38 -1
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +27 -6
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -20
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +291 -128
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -9
- package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +224 -0
- package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +53 -3
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +255 -25
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +87 -0
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +132 -9
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +29 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +180 -31
- package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +32 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +10 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +38 -10
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +31 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +11 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +22 -4
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +6 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +25 -24
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +152 -133
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +114 -42
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +129 -15
- package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +72 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +7 -8
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +201 -19
- package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +26 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +10 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +55 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +115 -25
- package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +268 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +296 -46
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +104 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +136 -0
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +127 -38
- package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +661 -0
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +350 -9
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +418 -44
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -3
- package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +552 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -15
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +284 -109
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +51 -21
- package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +44 -27
- package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/attachments/store.mjs +11 -3
- package/src/runtime/channels/data/voice-runtime-manifest.json +46 -70
- package/src/runtime/channels/lib/event-queue.mjs +51 -41
- package/src/runtime/channels/lib/scheduler.mjs +23 -28
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -17
- package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +17 -0
- package/src/runtime/channels/lib/webhook.mjs +39 -20
- package/src/runtime/channels/lib/whisper-server.mjs +46 -1
- package/src/runtime/channels/lib/worker-main.mjs +3 -1
- package/src/runtime/media/adapters/gemini-video.mjs +20 -4
- package/src/runtime/media/jobs.mjs +13 -0
- package/src/runtime/media/renditions.mjs +7 -1
- package/src/runtime/media/store.mjs +42 -22
- package/src/runtime/memory/index.mjs +4 -9
- package/src/runtime/memory/lib/agent-ipc.mjs +1 -6
- package/src/runtime/memory/lib/archive-security.test.mjs +21 -1
- package/src/runtime/memory/lib/core-memory-store.mjs +28 -50
- package/src/runtime/memory/lib/embedding-model-config.mjs +37 -1
- package/src/runtime/memory/lib/embedding-model-config.test.mjs +33 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +34 -12
- package/src/runtime/memory/lib/embedding-worker.mjs +37 -5
- package/src/runtime/memory/lib/ko-morph.mjs +46 -15
- package/src/runtime/memory/lib/memory-action-handlers.mjs +18 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -18
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -18
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +98 -45
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +71 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +78 -0
- package/src/runtime/memory/lib/memory-cycle2.mjs +37 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
- package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +50 -0
- package/src/runtime/memory/lib/memory-recall-store.mjs +9 -24
- package/src/runtime/memory/lib/memory.mjs +50 -12
- package/src/runtime/memory/lib/pg/process.mjs +1 -10
- package/src/runtime/memory/lib/pg/supervisor.mjs +1 -9
- package/src/runtime/memory/lib/query-handlers.mjs +2 -4
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +2 -2
- package/src/runtime/shared/background-tasks.mjs +15 -0
- package/src/runtime/shared/background-tasks.test.mjs +78 -0
- package/src/runtime/shared/child-guardian.mjs +2 -10
- package/src/runtime/shared/pid-liveness.mjs +27 -0
- package/src/runtime/shared/pristine-execution-contract.json +0 -1
- package/src/runtime/shared/runtime-root.mjs +1 -11
- package/src/runtime/shared/service-discovery.mjs +1 -17
- package/src/runtime/shared/singleton-owner.mjs +1 -11
- package/src/runtime/shared/staged-update.mjs +2 -12
- package/src/runtime/shared/tool-status.mjs +5 -1
- package/src/runtime/shared/tool-surface.mjs +15 -7
- package/src/runtime/shared/turn-snapshot-store.mjs +168 -0
- package/src/runtime/shared/turn-snapshot.mjs +137 -13
- package/src/runtime/shared/turn-worktree-snapshot.mjs +110 -18
- package/src/runtime/shared/webhook-session-run.mjs +16 -2
- package/src/runtime/web-search/lib/http-fetch.mjs +34 -4
- package/src/runtime/web-search/lib/state.mjs +15 -15
- package/src/runtime/web-search/lib/web-tools.mjs +46 -5
- package/src/session-runtime/context-status.mjs +4 -12
- package/src/session-runtime/runtime-core.mjs +20 -1
- package/src/session-runtime/session-lifecycle.mjs +9 -0
- package/src/standalone/agent-dispatch-broker.mjs +33 -2
- package/src/standalone/agent-tool/job-task-reconcile.mjs +78 -0
- package/src/standalone/agent-tool/notify.mjs +3 -1
- package/src/standalone/agent-tool/spawn-flow.mjs +52 -70
- package/src/standalone/agent-tool/tag-registry.mjs +116 -33
- package/src/standalone/agent-tool/tool-def.mjs +2 -2
- package/src/standalone/agent-tool/worker-index.mjs +8 -11
- package/src/standalone/agent-tool/worker-rows.mjs +4 -1
- package/src/standalone/agent-tool.mjs +20 -55
- package/src/standalone/agent-watchdog-registry.mjs +28 -4
- package/src/standalone/channel-client.mjs +47 -15
- package/src/standalone/channel-transport.mjs +44 -25
- package/src/standalone/daemon.mjs +31 -2
- package/src/standalone/hook-bus/config.mjs +25 -9
- package/src/standalone/hook-bus/handlers.mjs +16 -52
- package/src/standalone/hook-bus.mjs +29 -23
- package/src/standalone/memory-runtime-proxy.mjs +1 -16
- package/src/standalone/session-client.mjs +86 -23
- package/src/standalone/session-protocol.mjs +0 -2
- package/src/standalone/session-runtime-host-health.test.mjs +5 -4
- package/src/standalone/session-runtime-host.mjs +101 -6
- package/src/standalone/session-runtime-record.mjs +1 -5
- package/src/standalone/session-runtime-worker.mjs +52 -57
- package/src/standalone/session-service.mjs +4 -1
- package/src/standalone/session-transport.mjs +56 -25
- package/src/tui/App.jsx +40 -49
- package/src/tui/app/app-view.jsx +14 -21
- package/src/tui/app/core-memory-picker.mjs +29 -16
- package/src/tui/app/create-app-pickers.mjs +15 -36
- package/src/tui/app/extension-pickers.mjs +60 -49
- package/src/tui/app/maintenance-pickers.mjs +100 -57
- package/src/tui/app/message-selector.mjs +6 -5
- package/src/tui/app/model-picker.mjs +30 -13
- package/src/tui/app/onboarding-steps.mjs +21 -12
- package/src/tui/app/panel-epoch.mjs +56 -0
- package/src/tui/app/panel-epoch.test.mjs +1009 -0
- package/src/tui/app/panel-surface.mjs +98 -0
- package/src/tui/app/project-picker.mjs +30 -29
- package/src/tui/app/prompt-submit.mjs +192 -97
- package/src/tui/app/prompt-submit.test.mjs +206 -0
- package/src/tui/app/provider-setup-picker.mjs +74 -43
- package/src/tui/app/resume-picker.mjs +11 -8
- package/src/tui/app/route-pickers.mjs +45 -28
- package/src/tui/app/settings-picker.mjs +69 -18
- package/src/tui/app/shell-layout.mjs +1 -3
- package/src/tui/app/slash-commands.mjs +0 -2
- package/src/tui/app/slash-dispatch.mjs +4 -2
- package/src/tui/app/text-entry-policy.mjs +25 -0
- package/src/tui/app/theme-effort-pickers.mjs +11 -16
- package/src/tui/app/usage-context-panels.mjs +21 -25
- package/src/tui/app/use-global-key-input.mjs +3 -2
- package/src/tui/app/use-prompt-draft-flow.mjs +6 -18
- package/src/tui/app/use-prompt-handlers.mjs +3 -2
- package/src/tui/app/use-welcome-prompt-hint.mjs +17 -3
- package/src/tui/components/Picker.jsx +1 -1
- package/src/tui/components/TextEntryPanel.jsx +7 -2
- package/src/tui/dist/index.mjs +652 -561
- package/src/tui/lib/voice-setup.mjs +1 -2
- package/src/tui/session/agent-job-feed.mjs +73 -11
- package/src/tui/session/session-api-ext.mjs +5 -6
- package/src/tui/session/session-api.mjs +1 -0
- package/src/tui/session/tool-approval.mjs +15 -5
- package/src/tui/session/tui-steering-persist.mjs +33 -19
- package/src/tui/session/tui-steering-persist.test.mjs +104 -0
- package/src/tui/session-local.mjs +0 -22
- package/scripts/.tmp-coverage-report.mjs +0 -96
- package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.mjs +0 -57
- package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.test.mjs +0 -57
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.149",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"test:shellhardening": "npm run build:spawn:test && node --test scripts/suite-shellhardening-test.mjs scripts/suite-shell-direct-exe-test.mjs",
|
|
63
63
|
"test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-outcome-test.mjs scripts/cursor-provider-test.mjs src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs",
|
|
64
64
|
"test:embedding-runtime:warmup": "node scripts/verify-embedding-runtime.mjs --warmup",
|
|
65
|
-
"test:release-assets": "node --check scripts/verify-release-assets.mjs && node --check scripts/release-gate-test.mjs && node --check scripts/prepare-native-assets.mjs && node --test scripts/release-gate-test.mjs scripts/prepare-native-assets-test.mjs",
|
|
65
|
+
"test:release-assets": "node --check scripts/verify-release-assets.mjs && node --check scripts/release-gate-test.mjs && node --check scripts/prepare-native-assets.mjs && node --test scripts/release-gate-test.mjs scripts/prepare-native-assets-test.mjs scripts/generate-voice-runtime-manifest.test.mjs",
|
|
66
66
|
"test:native-edit-wire": "node scripts/native-edit-wire-test.mjs",
|
|
67
67
|
"test:release-critical": "npm run test:release-assets && npm run smoke:patch && npm run test:providers",
|
|
68
68
|
"test:spec-advisory": "node scripts/advisory-spec-test.mjs && npm run test:spec-advisory --prefix apps/desktop",
|
|
@@ -92,6 +92,10 @@
|
|
|
92
92
|
"audit:models": "node scripts/model-catalog-audit.mjs",
|
|
93
93
|
"patch:replay": "node scripts/patch-replay.mjs",
|
|
94
94
|
"build:tui": "node scripts/build-tui.mjs",
|
|
95
|
+
"build:local": "npm run build:fast --prefix apps/desktop",
|
|
96
|
+
"deploy:local": "npm run update:dev:fast --prefix apps/desktop",
|
|
97
|
+
"deploy:vps": "pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-remote.ps1",
|
|
98
|
+
"deploy:fast": "pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-remote.ps1 -FastDirect",
|
|
95
99
|
"release:patch": "gh workflow run deploy.yml --ref main -f bump=patch -f native=auto",
|
|
96
100
|
"release:minor": "gh workflow run deploy.yml --ref main -f bump=minor -f native=auto",
|
|
97
101
|
"release:major": "gh workflow run deploy.yml --ref main -f bump=major -f native=auto"
|
|
@@ -99,6 +103,15 @@
|
|
|
99
103
|
"engines": {
|
|
100
104
|
"node": ">=22.0.0"
|
|
101
105
|
},
|
|
106
|
+
"os": [
|
|
107
|
+
"darwin",
|
|
108
|
+
"linux",
|
|
109
|
+
"win32"
|
|
110
|
+
],
|
|
111
|
+
"cpu": [
|
|
112
|
+
"x64",
|
|
113
|
+
"arm64"
|
|
114
|
+
],
|
|
102
115
|
"dependencies": {
|
|
103
116
|
"@anthropic-ai/sdk": "^0.105.0",
|
|
104
117
|
"@google/generative-ai": "^0.24.1",
|
|
@@ -113,6 +126,7 @@
|
|
|
113
126
|
"kiwi-nlp": "^0.21.0",
|
|
114
127
|
"marked": "^14.1.4",
|
|
115
128
|
"node-cron": "^4.5.0",
|
|
129
|
+
"onnxruntime-node": "1.23.2",
|
|
116
130
|
"openai": "^6.44.0",
|
|
117
131
|
"pg": "^8.22.0",
|
|
118
132
|
"puppeteer-core": "^25.2.0",
|
|
@@ -129,6 +143,7 @@
|
|
|
129
143
|
},
|
|
130
144
|
"overrides": {
|
|
131
145
|
"onnxruntime-node": {
|
|
146
|
+
".": "1.23.2",
|
|
132
147
|
"global-agent": "^4.1.3"
|
|
133
148
|
},
|
|
134
149
|
"sharp": "^0.35.3"
|
|
@@ -140,5 +155,10 @@
|
|
|
140
155
|
},
|
|
141
156
|
"optionalDependencies": {
|
|
142
157
|
"@vscode/ripgrep": "^1.18.0"
|
|
143
|
-
}
|
|
158
|
+
},
|
|
159
|
+
"main": "index.js",
|
|
160
|
+
"directories": {
|
|
161
|
+
"doc": "docs"
|
|
162
|
+
},
|
|
163
|
+
"author": ""
|
|
144
164
|
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
CONFIG_PATH="${PROJECT_ROOT}/scripts/voice-runtime-config.json"
|
|
6
|
+
CONFIG_VERSION="$(node -e "const c=require(process.argv[1]);process.stdout.write(c.runtimeVersion)" "${CONFIG_PATH}")"
|
|
7
|
+
CONFIG_COMMIT="$(node -e "const c=require(process.argv[1]);process.stdout.write(c.whisperCommit)" "${CONFIG_PATH}")"
|
|
8
|
+
WHISPER_VERSION="${WHISPER_VERSION:-${CONFIG_VERSION}}"
|
|
9
|
+
WHISPER_COMMIT="${WHISPER_COMMIT:-${CONFIG_COMMIT}}"
|
|
10
|
+
TARGET_OS="${TARGET_OS:?TARGET_OS is required}"
|
|
11
|
+
TARGET_ARCH="${TARGET_ARCH:?TARGET_ARCH is required}"
|
|
12
|
+
BACKEND="${BACKEND:?BACKEND is required}"
|
|
13
|
+
|
|
14
|
+
if [[ "${WHISPER_VERSION}" != "${CONFIG_VERSION}" || "${WHISPER_COMMIT}" != "${CONFIG_COMMIT}" ]]; then
|
|
15
|
+
echo "voice runtime version/commit must match ${CONFIG_PATH}" >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
HOST_ARCH="$(uname -m)"
|
|
20
|
+
case "${TARGET_ARCH}:${HOST_ARCH}" in
|
|
21
|
+
x64:x86_64|x64:amd64|arm64:arm64|arm64:aarch64) ;;
|
|
22
|
+
*)
|
|
23
|
+
echo "voice runtime target ${TARGET_ARCH} does not match runner architecture ${HOST_ARCH}" >&2
|
|
24
|
+
exit 2
|
|
25
|
+
;;
|
|
26
|
+
esac
|
|
27
|
+
|
|
28
|
+
WORK_BASE="${RUNNER_TEMP:-${TMPDIR:-/tmp}}"
|
|
29
|
+
WORK_ROOT="${WORK_BASE}/mixdog-whisper-${WHISPER_COMMIT}-${TARGET_OS}-${TARGET_ARCH}-${BACKEND}"
|
|
30
|
+
SOURCE_ROOT="${WORK_ROOT}/whisper.cpp-${WHISPER_COMMIT}"
|
|
31
|
+
BUILD_ROOT="${WORK_ROOT}/build"
|
|
32
|
+
STAGE_ROOT="${WORK_ROOT}/stage"
|
|
33
|
+
DIST_ROOT="${PROJECT_ROOT}/dist"
|
|
34
|
+
ASSET_NAME="whisper-server-${TARGET_OS}-${TARGET_ARCH}-${BACKEND}.zip"
|
|
35
|
+
OUTPUT_PATH="${DIST_ROOT}/${ASSET_NAME}"
|
|
36
|
+
|
|
37
|
+
rm -rf "${WORK_ROOT}"
|
|
38
|
+
mkdir -p "${WORK_ROOT}" "${STAGE_ROOT}" "${DIST_ROOT}"
|
|
39
|
+
curl --fail --location --retry 3 \
|
|
40
|
+
"https://github.com/ggml-org/whisper.cpp/archive/${WHISPER_COMMIT}.tar.gz" \
|
|
41
|
+
--output "${WORK_ROOT}/source.tar.gz"
|
|
42
|
+
tar -xzf "${WORK_ROOT}/source.tar.gz" -C "${WORK_ROOT}"
|
|
43
|
+
|
|
44
|
+
CMAKE_ARGS=(
|
|
45
|
+
-S "${SOURCE_ROOT}"
|
|
46
|
+
-B "${BUILD_ROOT}"
|
|
47
|
+
-DCMAKE_BUILD_TYPE=Release
|
|
48
|
+
-DBUILD_SHARED_LIBS=OFF
|
|
49
|
+
-DWHISPER_BUILD_EXAMPLES=ON
|
|
50
|
+
-DWHISPER_BUILD_SERVER=ON
|
|
51
|
+
-DWHISPER_BUILD_TESTS=OFF
|
|
52
|
+
-DWHISPER_SDL2=OFF
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
case "${BACKEND}" in
|
|
56
|
+
vulkan)
|
|
57
|
+
CMAKE_ARGS+=(-DGGML_VULKAN=ON)
|
|
58
|
+
;;
|
|
59
|
+
metal)
|
|
60
|
+
CMAKE_ARGS+=(-DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON)
|
|
61
|
+
;;
|
|
62
|
+
cpu)
|
|
63
|
+
CMAKE_ARGS+=(-DGGML_METAL=OFF -DGGML_VULKAN=OFF)
|
|
64
|
+
;;
|
|
65
|
+
*)
|
|
66
|
+
echo "unsupported voice runtime backend: ${BACKEND}" >&2
|
|
67
|
+
exit 2
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
|
|
71
|
+
cmake "${CMAKE_ARGS[@]}"
|
|
72
|
+
cmake --build "${BUILD_ROOT}" --config Release --target whisper-server --parallel 2
|
|
73
|
+
|
|
74
|
+
SERVER_PATH="$(find "${BUILD_ROOT}" -type f -name whisper-server -perm -111 | head -n 1)"
|
|
75
|
+
if [[ -z "${SERVER_PATH}" ]]; then
|
|
76
|
+
echo "whisper-server was not produced" >&2
|
|
77
|
+
exit 1
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
cp "${SERVER_PATH}" "${STAGE_ROOT}/whisper-server"
|
|
81
|
+
cp "${SOURCE_ROOT}/LICENSE" "${STAGE_ROOT}/LICENSE-whisper.cpp.txt"
|
|
82
|
+
find "$(dirname "${SERVER_PATH}")" -maxdepth 1 -type f \
|
|
83
|
+
\( -name '*.so' -o -name '*.so.*' -o -name '*.dylib' \) \
|
|
84
|
+
-exec cp {} "${STAGE_ROOT}/" \;
|
|
85
|
+
chmod +x "${STAGE_ROOT}/whisper-server"
|
|
86
|
+
"${STAGE_ROOT}/whisper-server" --help >/dev/null
|
|
87
|
+
|
|
88
|
+
rm -f "${OUTPUT_PATH}"
|
|
89
|
+
(
|
|
90
|
+
cd "${STAGE_ROOT}"
|
|
91
|
+
cmake -E tar cf "${OUTPUT_PATH}" --format=zip .
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
95
|
+
HASH="$(sha256sum "${OUTPUT_PATH}" | cut -d' ' -f1)"
|
|
96
|
+
else
|
|
97
|
+
HASH="$(shasum -a 256 "${OUTPUT_PATH}" | cut -d' ' -f1)"
|
|
98
|
+
fi
|
|
99
|
+
SIZE="$(wc -c < "${OUTPUT_PATH}" | tr -d ' ')"
|
|
100
|
+
printf '%s %s\n' "${HASH}" "${ASSET_NAME}" > "${OUTPUT_PATH}.sha256"
|
|
101
|
+
echo "voice-runtime ${WHISPER_VERSION} ${TARGET_OS}-${TARGET_ARCH} ${BACKEND}: ${OUTPUT_PATH} (${SIZE} bytes, sha256=${HASH})"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
embedText,
|
|
3
|
+
embedTexts,
|
|
4
|
+
getEmbeddingDtype,
|
|
5
|
+
getEmbeddingModelId,
|
|
6
|
+
shutdownEmbeddingProvider,
|
|
7
|
+
warmupEmbeddingProvider,
|
|
8
|
+
} from '../src/runtime/memory/lib/embedding-provider.mjs'
|
|
9
|
+
|
|
10
|
+
const cases = [
|
|
11
|
+
{
|
|
12
|
+
query: '윈도우 음성 인식이 너무 많은 메모리를 쓰는 원인은?',
|
|
13
|
+
relevant: 'Windows 음성 인식 런타임은 CUDA DLL과 Whisper 모델을 함께 올려 메모리 사용량이 커졌다.',
|
|
14
|
+
distractors: [
|
|
15
|
+
'macOS 렌더러는 창 크기를 local storage에 저장한다.',
|
|
16
|
+
'세션 목록은 최근 접속 시각을 기준으로 정렬한다.',
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
query: 'How is the embedding worker released when it becomes idle?',
|
|
21
|
+
relevant: 'The embedding worker terminates after its idle timeout so native ONNX memory returns to the OS.',
|
|
22
|
+
distractors: [
|
|
23
|
+
'The release workflow uploads desktop installers to GitHub.',
|
|
24
|
+
'The renderer uses CSS grid to arrange conversation panes.',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
query: '검색 query와 저장 문서의 embedding 입력은 어떻게 구분해?',
|
|
29
|
+
relevant: '검색 query에는 retrieval instruction을 붙이고 저장 문서는 원문 그대로 embedding한다.',
|
|
30
|
+
distractors: [
|
|
31
|
+
'음성 녹음은 WAV 파일로 임시 저장한 뒤 삭제한다.',
|
|
32
|
+
'데스크톱 업데이트는 설치된 앱의 userData를 보존한다.',
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
query: 'Which function opens the memory database with the active embedding identity?',
|
|
37
|
+
relevant: 'openDatabase receives the embedding identity and resets incompatible stored vectors.',
|
|
38
|
+
distractors: [
|
|
39
|
+
'ensureReady starts the whisper server on an available TCP port.',
|
|
40
|
+
'selectVoiceModelId chooses the bundled speech recognition model.',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
function cosine(a, b) {
|
|
46
|
+
let dot = 0
|
|
47
|
+
let aa = 0
|
|
48
|
+
let bb = 0
|
|
49
|
+
for (let i = 0; i < a.length; i++) {
|
|
50
|
+
dot += a[i] * b[i]
|
|
51
|
+
aa += a[i] * a[i]
|
|
52
|
+
bb += b[i] * b[i]
|
|
53
|
+
}
|
|
54
|
+
return dot / Math.sqrt(aa * bb)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const startedAt = performance.now()
|
|
58
|
+
try {
|
|
59
|
+
await warmupEmbeddingProvider()
|
|
60
|
+
let hits = 0
|
|
61
|
+
const margins = []
|
|
62
|
+
const rows = []
|
|
63
|
+
|
|
64
|
+
for (const item of cases) {
|
|
65
|
+
const documents = [item.relevant, ...item.distractors]
|
|
66
|
+
const [queryVector, documentVectors] = await Promise.all([
|
|
67
|
+
embedText(item.query, { inputType: 'query', priority: true }),
|
|
68
|
+
embedTexts(documents, { inputType: 'document' }),
|
|
69
|
+
])
|
|
70
|
+
const scores = documentVectors.map((vector, index) => ({
|
|
71
|
+
index,
|
|
72
|
+
score: cosine(queryVector, vector),
|
|
73
|
+
})).sort((a, b) => b.score - a.score)
|
|
74
|
+
const hit = scores[0].index === 0
|
|
75
|
+
if (hit) hits++
|
|
76
|
+
margins.push(scores.find((row) => row.index === 0).score - scores.find((row) => row.index !== 0).score)
|
|
77
|
+
rows.push({
|
|
78
|
+
query: item.query,
|
|
79
|
+
hit,
|
|
80
|
+
relevant: Number(scores.find((row) => row.index === 0).score.toFixed(4)),
|
|
81
|
+
bestDistractor: Number(scores.find((row) => row.index !== 0).score.toFixed(4)),
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
console.log(JSON.stringify({
|
|
86
|
+
model: getEmbeddingModelId(),
|
|
87
|
+
dtype: getEmbeddingDtype(),
|
|
88
|
+
hitAt1: `${hits}/${cases.length}`,
|
|
89
|
+
meanMargin: Number((margins.reduce((sum, value) => sum + value, 0) / margins.length).toFixed(4)),
|
|
90
|
+
wallMs: Math.round(performance.now() - startedAt),
|
|
91
|
+
rssMiB: Number((process.memoryUsage().rss / 1024 / 1024).toFixed(1)),
|
|
92
|
+
rows,
|
|
93
|
+
}, null, 2))
|
|
94
|
+
} finally {
|
|
95
|
+
await shutdownEmbeddingProvider()
|
|
96
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
import { readFile, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { resolve } from 'node:path'
|
|
5
|
+
import { pathToFileURL } from 'node:url'
|
|
6
|
+
|
|
7
|
+
const CONFIG_URL = new URL('./voice-runtime-config.json', import.meta.url)
|
|
8
|
+
const RELEASE_HASH_INPUTS = [
|
|
9
|
+
CONFIG_URL,
|
|
10
|
+
new URL('../.github/workflows/build-voice-runtime.yml', import.meta.url),
|
|
11
|
+
new URL('./build-voice-runtime-unix.sh', import.meta.url),
|
|
12
|
+
new URL('./build-voice-runtime-windows.ps1', import.meta.url),
|
|
13
|
+
new URL('./generate-voice-runtime-manifest.mjs', import.meta.url),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
function loadVoiceRuntimeConfig() {
|
|
17
|
+
const config = JSON.parse(readFileSync(CONFIG_URL, 'utf8'))
|
|
18
|
+
if (!/^\d+\.\d+\.\d+$/.test(config.runtimeVersion || '')) {
|
|
19
|
+
throw new Error('voice runtime config has an invalid runtimeVersion')
|
|
20
|
+
}
|
|
21
|
+
if (!/^[a-f0-9]{40}$/.test(config.whisperCommit || '')) {
|
|
22
|
+
throw new Error('voice runtime config has an invalid whisperCommit')
|
|
23
|
+
}
|
|
24
|
+
if (!Array.isArray(config.platforms) || config.platforms.length === 0) {
|
|
25
|
+
throw new Error('voice runtime config has no platforms')
|
|
26
|
+
}
|
|
27
|
+
const keys = new Set()
|
|
28
|
+
const assets = new Set()
|
|
29
|
+
for (const platform of config.platforms) {
|
|
30
|
+
for (const field of ['key', 'os', 'arch', 'backend', 'runner', 'asset', 'executable']) {
|
|
31
|
+
if (!platform[field]) throw new Error(`voice runtime platform is missing ${field}`)
|
|
32
|
+
}
|
|
33
|
+
if (keys.has(platform.key)) throw new Error(`duplicate voice runtime platform ${platform.key}`)
|
|
34
|
+
if (assets.has(platform.asset)) throw new Error(`duplicate voice runtime asset ${platform.asset}`)
|
|
35
|
+
keys.add(platform.key)
|
|
36
|
+
assets.add(platform.asset)
|
|
37
|
+
Object.freeze(platform)
|
|
38
|
+
}
|
|
39
|
+
Object.freeze(config.platforms)
|
|
40
|
+
return Object.freeze(config)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const VOICE_RUNTIME_CONFIG = loadVoiceRuntimeConfig()
|
|
44
|
+
export const VOICE_RUNTIME_VERSION = VOICE_RUNTIME_CONFIG.runtimeVersion
|
|
45
|
+
export const VOICE_RUNTIME_COMMIT = VOICE_RUNTIME_CONFIG.whisperCommit
|
|
46
|
+
export const VOICE_RUNTIME_TAG = `voice-runtime-v${VOICE_RUNTIME_VERSION}`
|
|
47
|
+
|
|
48
|
+
export function releaseAssetSha256(asset) {
|
|
49
|
+
const digest = String(asset?.digest || '')
|
|
50
|
+
const match = /^sha256:([a-f0-9]{64})$/i.exec(digest)
|
|
51
|
+
if (!match) throw new Error(`release asset ${asset?.name || '(unknown)'} has no SHA256 digest`)
|
|
52
|
+
return match[1].toLowerCase()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function voiceRuntimeBuildHash() {
|
|
56
|
+
const hash = createHash('sha256')
|
|
57
|
+
for (const input of RELEASE_HASH_INPUTS) {
|
|
58
|
+
hash.update(input.pathname.split('/').pop() || '')
|
|
59
|
+
hash.update('\0')
|
|
60
|
+
hash.update(await readFile(input))
|
|
61
|
+
hash.update('\0')
|
|
62
|
+
}
|
|
63
|
+
return hash.digest('hex')
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function compareSemver(left, right) {
|
|
67
|
+
const parse = (value) => {
|
|
68
|
+
if (!/^\d+\.\d+\.\d+$/.test(String(value || ''))) {
|
|
69
|
+
throw new Error(`invalid semantic version ${value || '(missing)'}`)
|
|
70
|
+
}
|
|
71
|
+
return String(value).split('.').map(Number)
|
|
72
|
+
}
|
|
73
|
+
const a = parse(left)
|
|
74
|
+
const b = parse(right)
|
|
75
|
+
for (let index = 0; index < 3; index++) {
|
|
76
|
+
if (a[index] !== b[index]) return a[index] - b[index]
|
|
77
|
+
}
|
|
78
|
+
return 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildVoiceRuntimePlatforms(
|
|
82
|
+
releaseAssets,
|
|
83
|
+
{ repository = 'tribgames/mixdog', tag = VOICE_RUNTIME_TAG } = {},
|
|
84
|
+
) {
|
|
85
|
+
const byName = new Map((releaseAssets || []).map((asset) => [asset.name, asset]))
|
|
86
|
+
const expectedAssets = new Set(VOICE_RUNTIME_CONFIG.platforms.map((platform) => platform.asset))
|
|
87
|
+
const unexpected = (releaseAssets || []).filter(
|
|
88
|
+
(asset) => /^whisper-server-.*\.zip$/i.test(asset.name) && !expectedAssets.has(asset.name),
|
|
89
|
+
)
|
|
90
|
+
if (unexpected.length > 0) {
|
|
91
|
+
throw new Error(`release has unexpected runtime asset ${unexpected.map((asset) => asset.name).join(', ')}`)
|
|
92
|
+
}
|
|
93
|
+
const platforms = {}
|
|
94
|
+
for (const spec of VOICE_RUNTIME_CONFIG.platforms) {
|
|
95
|
+
const asset = byName.get(spec.asset)
|
|
96
|
+
if (!asset) throw new Error(`release is missing required asset ${spec.asset}`)
|
|
97
|
+
if (!Number.isSafeInteger(asset.size) || asset.size <= 0) {
|
|
98
|
+
throw new Error(`release asset ${spec.asset} has invalid size`)
|
|
99
|
+
}
|
|
100
|
+
const sourceUrl = String(asset.browser_download_url || '')
|
|
101
|
+
const url = `https://github.com/${repository}/releases/download/${tag}/${spec.asset}`
|
|
102
|
+
let validDraftUrl = false
|
|
103
|
+
try {
|
|
104
|
+
const parsed = new URL(sourceUrl)
|
|
105
|
+
const prefix = `/${repository}/releases/download/`
|
|
106
|
+
const parts = parsed.pathname.startsWith(prefix)
|
|
107
|
+
? parsed.pathname.slice(prefix.length).split('/')
|
|
108
|
+
: []
|
|
109
|
+
validDraftUrl = parsed.origin === 'https://github.com'
|
|
110
|
+
&& !parsed.search
|
|
111
|
+
&& !parsed.hash
|
|
112
|
+
&& parts.length === 2
|
|
113
|
+
&& parts[0].startsWith('untagged-')
|
|
114
|
+
&& parts[1] === spec.asset
|
|
115
|
+
} catch {
|
|
116
|
+
validDraftUrl = false
|
|
117
|
+
}
|
|
118
|
+
if (sourceUrl !== url && !validDraftUrl) {
|
|
119
|
+
throw new Error(`release asset ${spec.asset} has invalid download URL`)
|
|
120
|
+
}
|
|
121
|
+
platforms[spec.key] = {
|
|
122
|
+
variants: [{
|
|
123
|
+
id: spec.backend,
|
|
124
|
+
requires: null,
|
|
125
|
+
url,
|
|
126
|
+
sha256: releaseAssetSha256(asset),
|
|
127
|
+
size: asset.size,
|
|
128
|
+
format: 'zip',
|
|
129
|
+
executable: spec.executable,
|
|
130
|
+
}],
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return platforms
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function githubHeaders(token, accept = 'application/vnd.github+json') {
|
|
137
|
+
return {
|
|
138
|
+
Accept: accept,
|
|
139
|
+
'X-GitHub-Api-Version': '2022-11-28',
|
|
140
|
+
'User-Agent': 'mixdog-voice-runtime-manifest',
|
|
141
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function fetchGithubRelease(
|
|
146
|
+
repository,
|
|
147
|
+
tag,
|
|
148
|
+
token,
|
|
149
|
+
{ allowMissing = false } = {},
|
|
150
|
+
) {
|
|
151
|
+
let lastError
|
|
152
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
153
|
+
try {
|
|
154
|
+
const response = await fetch(`https://api.github.com/repos/${repository}/releases/tags/${tag}`, {
|
|
155
|
+
headers: githubHeaders(token),
|
|
156
|
+
signal: AbortSignal.timeout(30_000),
|
|
157
|
+
})
|
|
158
|
+
if (response.ok) return response.json()
|
|
159
|
+
if (response.status !== 404) {
|
|
160
|
+
throw new Error(`GitHub release lookup failed: HTTP ${response.status}`)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// GitHub keeps a hidden draft under an `untagged-*` URL and the
|
|
164
|
+
// releases/tags endpoint returns 404 until publication creates the real
|
|
165
|
+
// tag. Authenticated release listings still expose its requested
|
|
166
|
+
// tag_name, which lets interrupted release runs resume and verify it.
|
|
167
|
+
for (let page = 1; page <= 10; page++) {
|
|
168
|
+
const listing = await fetch(
|
|
169
|
+
`https://api.github.com/repos/${repository}/releases?per_page=100&page=${page}`,
|
|
170
|
+
{
|
|
171
|
+
headers: githubHeaders(token),
|
|
172
|
+
signal: AbortSignal.timeout(30_000),
|
|
173
|
+
},
|
|
174
|
+
)
|
|
175
|
+
if (!listing.ok) {
|
|
176
|
+
throw new Error(`GitHub release listing failed: HTTP ${listing.status}`)
|
|
177
|
+
}
|
|
178
|
+
const releases = await listing.json()
|
|
179
|
+
const draft = releases.find((release) => release?.draft && release?.tag_name === tag)
|
|
180
|
+
if (draft) return draft
|
|
181
|
+
if (releases.length < 100) break
|
|
182
|
+
}
|
|
183
|
+
if (allowMissing) return null
|
|
184
|
+
throw new Error('GitHub release lookup failed: HTTP 404')
|
|
185
|
+
} catch (error) {
|
|
186
|
+
lastError = error
|
|
187
|
+
if (attempt < 3) await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 1_000))
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
throw lastError
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function downloadReleaseAsset(asset, token) {
|
|
194
|
+
if (!asset?.url) throw new Error('release asset has no API URL')
|
|
195
|
+
const response = await fetch(asset.url, {
|
|
196
|
+
headers: githubHeaders(token, 'application/octet-stream'),
|
|
197
|
+
redirect: 'follow',
|
|
198
|
+
signal: AbortSignal.timeout(30_000),
|
|
199
|
+
})
|
|
200
|
+
if (!response.ok) throw new Error(`GitHub release asset download failed: HTTP ${response.status}`)
|
|
201
|
+
return Buffer.from(await response.arrayBuffer())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function voiceRuntimeIdentity(manifest) {
|
|
205
|
+
return {
|
|
206
|
+
version: manifest?.version,
|
|
207
|
+
source: manifest?.source,
|
|
208
|
+
release_tag: manifest?.release_tag,
|
|
209
|
+
build_hash: manifest?.build_hash,
|
|
210
|
+
platforms: manifest?.platforms,
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function sameVoiceRuntimeIdentity(left, right) {
|
|
215
|
+
return JSON.stringify(voiceRuntimeIdentity(left)) === JSON.stringify(voiceRuntimeIdentity(right))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function assertVoiceRuntimeManifestIdentity(
|
|
219
|
+
manifest,
|
|
220
|
+
{ repository = 'tribgames/mixdog' } = {},
|
|
221
|
+
) {
|
|
222
|
+
const expectedSource = `ggml-org/whisper.cpp@${VOICE_RUNTIME_COMMIT}`
|
|
223
|
+
const expectedHash = await voiceRuntimeBuildHash()
|
|
224
|
+
if (manifest?.version !== VOICE_RUNTIME_VERSION) {
|
|
225
|
+
throw new Error(`voice runtime manifest version must be ${VOICE_RUNTIME_VERSION}`)
|
|
226
|
+
}
|
|
227
|
+
if (manifest?.source !== expectedSource) {
|
|
228
|
+
throw new Error(`voice runtime manifest source must be ${expectedSource}`)
|
|
229
|
+
}
|
|
230
|
+
if (manifest?.release_tag !== VOICE_RUNTIME_TAG) {
|
|
231
|
+
throw new Error(`voice runtime manifest release_tag must be ${VOICE_RUNTIME_TAG}`)
|
|
232
|
+
}
|
|
233
|
+
if (manifest?.build_hash !== expectedHash) {
|
|
234
|
+
throw new Error('published voice runtime differs from the current build inputs; bump runtimeVersion')
|
|
235
|
+
}
|
|
236
|
+
const platformKeys = Object.keys(manifest?.platforms || {})
|
|
237
|
+
const expectedKeys = VOICE_RUNTIME_CONFIG.platforms.map((platform) => platform.key)
|
|
238
|
+
if (JSON.stringify(platformKeys) !== JSON.stringify(expectedKeys)) {
|
|
239
|
+
throw new Error(`voice runtime manifest platforms must be ${expectedKeys.join(', ')}`)
|
|
240
|
+
}
|
|
241
|
+
for (const spec of VOICE_RUNTIME_CONFIG.platforms) {
|
|
242
|
+
const variants = manifest.platforms[spec.key]?.variants
|
|
243
|
+
if (!Array.isArray(variants) || variants.length !== 1) {
|
|
244
|
+
throw new Error(`voice runtime manifest ${spec.key} must have exactly one variant`)
|
|
245
|
+
}
|
|
246
|
+
const variant = variants[0]
|
|
247
|
+
const expectedUrl = `https://github.com/${repository}/releases/download/${VOICE_RUNTIME_TAG}/${spec.asset}`
|
|
248
|
+
if (
|
|
249
|
+
variant.id !== spec.backend
|
|
250
|
+
|| variant.requires !== null
|
|
251
|
+
|| variant.url !== expectedUrl
|
|
252
|
+
|| variant.executable !== spec.executable
|
|
253
|
+
|| variant.format !== 'zip'
|
|
254
|
+
|| !/^[a-f0-9]{64}$/.test(variant.sha256 || '')
|
|
255
|
+
|| !Number.isSafeInteger(variant.size)
|
|
256
|
+
|| variant.size <= 0
|
|
257
|
+
) {
|
|
258
|
+
throw new Error(`voice runtime manifest ${spec.key} does not match the release config`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function main() {
|
|
264
|
+
const repository = String(process.env.GITHUB_REPOSITORY || 'tribgames/mixdog')
|
|
265
|
+
const tag = String(process.env.RELEASE_TAG || VOICE_RUNTIME_TAG)
|
|
266
|
+
if (tag !== VOICE_RUNTIME_TAG) {
|
|
267
|
+
throw new Error(`voice runtime tag must be ${VOICE_RUNTIME_TAG}, got ${tag}`)
|
|
268
|
+
}
|
|
269
|
+
const manifestPath = resolve(process.argv[2] || 'src/runtime/channels/data/voice-runtime-manifest.json')
|
|
270
|
+
const manifest = JSON.parse(await readFile(manifestPath, 'utf8'))
|
|
271
|
+
let platforms
|
|
272
|
+
let lastError
|
|
273
|
+
for (let attempt = 1; attempt <= 5; attempt++) {
|
|
274
|
+
try {
|
|
275
|
+
const release = await fetchGithubRelease(repository, tag, process.env.GITHUB_TOKEN)
|
|
276
|
+
platforms = buildVoiceRuntimePlatforms(release.assets, { repository, tag })
|
|
277
|
+
lastError = null
|
|
278
|
+
break
|
|
279
|
+
} catch (error) {
|
|
280
|
+
lastError = error
|
|
281
|
+
if (attempt < 5) await new Promise((resolveDelay) => setTimeout(resolveDelay, attempt * 2_000))
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (lastError) throw lastError
|
|
285
|
+
manifest.version = VOICE_RUNTIME_VERSION
|
|
286
|
+
manifest.source = `ggml-org/whisper.cpp@${VOICE_RUNTIME_COMMIT}`
|
|
287
|
+
manifest.release_tag = tag
|
|
288
|
+
manifest.build_hash = await voiceRuntimeBuildHash()
|
|
289
|
+
manifest.platforms = platforms
|
|
290
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`)
|
|
291
|
+
console.log(`updated ${manifestPath} from ${tag}`)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : ''
|
|
295
|
+
if (invokedPath === import.meta.url) {
|
|
296
|
+
main().catch((error) => {
|
|
297
|
+
console.error(error?.stack || error)
|
|
298
|
+
process.exitCode = 1
|
|
299
|
+
})
|
|
300
|
+
}
|