mixdog 0.9.147 → 0.9.148

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.
Files changed (255) hide show
  1. package/package.json +23 -3
  2. package/scripts/build-voice-runtime-unix.sh +101 -0
  3. package/scripts/embedding-quality-probe.mjs +96 -0
  4. package/scripts/generate-voice-runtime-manifest.mjs +300 -0
  5. package/scripts/generate-voice-runtime-manifest.test.mjs +135 -0
  6. package/scripts/native-binary-arch.mjs +30 -13
  7. package/scripts/native-binary-arch.test.mjs +40 -4
  8. package/scripts/plan-voice-runtime-release.mjs +73 -0
  9. package/scripts/prepare-native-assets.mjs +50 -9
  10. package/scripts/prune-embedding-runtime.mjs +3 -3
  11. package/scripts/sync-voice-runtime-manifest.mjs +46 -0
  12. package/scripts/verify-release-assets.mjs +106 -6
  13. package/scripts/verify-voice-runtime-release.mjs +89 -0
  14. package/scripts/voice-runtime-config.json +52 -0
  15. package/src/app.mjs +42 -5
  16. package/src/headless-exec.mjs +58 -32
  17. package/src/headless-exec.test.mjs +74 -13
  18. package/src/rules/lead/01-general.md +2 -0
  19. package/src/rules/shared/10-tool-workflow.md +7 -4
  20. package/src/rules/shared/30-exploration.md +2 -1
  21. package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +28 -9
  22. package/src/runtime/agent/orchestrator/agent-trace-format.mjs +6 -1
  23. package/src/runtime/agent/orchestrator/config.mjs +53 -18
  24. package/src/runtime/agent/orchestrator/mcp/cancellation.test.mjs +139 -0
  25. package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +34 -5
  26. package/src/runtime/agent/orchestrator/mcp/client.mjs +94 -10
  27. package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +5 -0
  28. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.mjs +98 -0
  29. package/src/runtime/agent/orchestrator/providers/anthropic-fast-mode.test.mjs +90 -0
  30. package/src/runtime/agent/orchestrator/providers/anthropic-leaked-toolcall.mjs +67 -16
  31. package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +4 -82
  32. package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +0 -5
  33. package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +62 -121
  34. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.mjs +48 -0
  35. package/src/runtime/agent/orchestrator/providers/anthropic-server-fallback.test.mjs +215 -0
  36. package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +44 -48
  37. package/src/runtime/agent/orchestrator/providers/anthropic.mjs +54 -31
  38. package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +56 -2
  39. package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +34 -2
  40. package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +61 -1
  41. package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +6 -1
  42. package/src/runtime/agent/orchestrator/providers/gemini.mjs +61 -15
  43. package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +120 -1
  44. package/src/runtime/agent/orchestrator/providers/lib/env-utils.mjs +9 -0
  45. package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +0 -3
  46. package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +187 -84
  47. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +36 -36
  48. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +16 -6
  49. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +50 -213
  50. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +135 -126
  51. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +17 -11
  52. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +20 -7
  53. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -0
  54. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +132 -42
  55. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
  56. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +28 -10
  57. package/src/runtime/agent/orchestrator/providers/registry.mjs +122 -95
  58. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +58 -3
  59. package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +4 -22
  60. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -14
  61. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +128 -0
  62. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +5 -0
  63. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +27 -48
  64. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +19 -14
  65. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -43
  66. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +5 -1
  67. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -2
  68. package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +325 -0
  69. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +599 -195
  70. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +22 -0
  71. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +9 -2
  72. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  73. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +26 -16
  74. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +23 -11
  75. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +118 -3
  76. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +332 -0
  77. package/src/runtime/agent/orchestrator/session/store.mjs +38 -1
  78. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +27 -6
  79. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -20
  80. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +291 -128
  81. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +45 -8
  82. package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +224 -0
  83. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +53 -3
  84. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +255 -25
  85. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +87 -0
  86. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +1 -1
  87. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +132 -9
  88. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -0
  89. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +29 -3
  90. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +1 -1
  91. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +178 -29
  92. package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -1
  93. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +32 -5
  94. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +5 -2
  95. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +10 -1
  96. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +38 -10
  97. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +12 -0
  98. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +31 -1
  99. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +11 -1
  100. package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +22 -4
  101. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +6 -8
  102. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +25 -24
  103. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +152 -133
  104. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +114 -42
  105. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +129 -15
  106. package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +72 -0
  107. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +7 -8
  108. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +4 -4
  109. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +201 -19
  110. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +26 -3
  111. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +10 -1
  112. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +55 -0
  113. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +115 -25
  114. package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +268 -0
  115. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
  116. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +296 -46
  117. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +104 -0
  118. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +136 -0
  119. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +127 -38
  120. package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +661 -0
  121. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +350 -9
  122. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +418 -44
  123. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -3
  124. package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +552 -0
  125. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -15
  126. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  127. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +284 -109
  128. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +51 -21
  129. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +44 -27
  130. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -1
  131. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  132. package/src/runtime/attachments/store.mjs +11 -3
  133. package/src/runtime/channels/data/voice-runtime-manifest.json +46 -70
  134. package/src/runtime/channels/lib/event-queue.mjs +51 -41
  135. package/src/runtime/channels/lib/scheduler.mjs +23 -28
  136. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -17
  137. package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +17 -0
  138. package/src/runtime/channels/lib/webhook.mjs +39 -20
  139. package/src/runtime/channels/lib/whisper-server.mjs +46 -1
  140. package/src/runtime/channels/lib/worker-main.mjs +3 -1
  141. package/src/runtime/media/adapters/gemini-video.mjs +20 -4
  142. package/src/runtime/media/jobs.mjs +13 -0
  143. package/src/runtime/media/renditions.mjs +7 -1
  144. package/src/runtime/media/store.mjs +42 -22
  145. package/src/runtime/memory/index.mjs +4 -9
  146. package/src/runtime/memory/lib/agent-ipc.mjs +1 -6
  147. package/src/runtime/memory/lib/archive-security.test.mjs +21 -1
  148. package/src/runtime/memory/lib/core-memory-store.mjs +28 -50
  149. package/src/runtime/memory/lib/embedding-model-config.mjs +37 -1
  150. package/src/runtime/memory/lib/embedding-model-config.test.mjs +33 -0
  151. package/src/runtime/memory/lib/embedding-provider.mjs +34 -12
  152. package/src/runtime/memory/lib/embedding-worker.mjs +37 -5
  153. package/src/runtime/memory/lib/ko-morph.mjs +46 -15
  154. package/src/runtime/memory/lib/memory-action-handlers.mjs +18 -1
  155. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -18
  156. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -18
  157. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +98 -45
  158. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +71 -0
  159. package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +78 -0
  160. package/src/runtime/memory/lib/memory-cycle2.mjs +37 -3
  161. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  162. package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +50 -0
  163. package/src/runtime/memory/lib/memory-recall-store.mjs +9 -24
  164. package/src/runtime/memory/lib/memory.mjs +50 -12
  165. package/src/runtime/memory/lib/pg/process.mjs +1 -10
  166. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -9
  167. package/src/runtime/memory/lib/query-handlers.mjs +2 -4
  168. package/src/runtime/memory/lib/session-ingest-runtime.mjs +2 -2
  169. package/src/runtime/shared/background-tasks.mjs +15 -0
  170. package/src/runtime/shared/background-tasks.test.mjs +78 -0
  171. package/src/runtime/shared/child-guardian.mjs +2 -10
  172. package/src/runtime/shared/pid-liveness.mjs +27 -0
  173. package/src/runtime/shared/runtime-root.mjs +1 -11
  174. package/src/runtime/shared/service-discovery.mjs +1 -17
  175. package/src/runtime/shared/singleton-owner.mjs +1 -11
  176. package/src/runtime/shared/staged-update.mjs +2 -12
  177. package/src/runtime/shared/tool-status.mjs +5 -1
  178. package/src/runtime/shared/tool-surface.mjs +15 -7
  179. package/src/runtime/shared/turn-snapshot-store.mjs +168 -0
  180. package/src/runtime/shared/turn-snapshot.mjs +137 -13
  181. package/src/runtime/shared/turn-worktree-snapshot.mjs +110 -18
  182. package/src/runtime/shared/webhook-session-run.mjs +16 -2
  183. package/src/runtime/web-search/lib/http-fetch.mjs +34 -4
  184. package/src/runtime/web-search/lib/state.mjs +15 -15
  185. package/src/runtime/web-search/lib/web-tools.mjs +46 -5
  186. package/src/session-runtime/context-status.mjs +4 -12
  187. package/src/session-runtime/runtime-core.mjs +20 -1
  188. package/src/standalone/agent-dispatch-broker.mjs +33 -2
  189. package/src/standalone/agent-tool/job-task-reconcile.mjs +78 -0
  190. package/src/standalone/agent-tool/notify.mjs +3 -1
  191. package/src/standalone/agent-tool/spawn-flow.mjs +48 -66
  192. package/src/standalone/agent-tool/tag-registry.mjs +116 -33
  193. package/src/standalone/agent-tool/tool-def.mjs +2 -2
  194. package/src/standalone/agent-tool/worker-index.mjs +8 -11
  195. package/src/standalone/agent-tool/worker-rows.mjs +4 -1
  196. package/src/standalone/agent-tool.mjs +20 -55
  197. package/src/standalone/agent-watchdog-registry.mjs +28 -4
  198. package/src/standalone/channel-client.mjs +47 -15
  199. package/src/standalone/channel-transport.mjs +44 -25
  200. package/src/standalone/daemon.mjs +31 -2
  201. package/src/standalone/hook-bus/config.mjs +25 -9
  202. package/src/standalone/hook-bus/handlers.mjs +16 -52
  203. package/src/standalone/hook-bus.mjs +29 -23
  204. package/src/standalone/memory-runtime-proxy.mjs +1 -16
  205. package/src/standalone/session-client.mjs +86 -23
  206. package/src/standalone/session-protocol.mjs +0 -2
  207. package/src/standalone/session-runtime-host-health.test.mjs +5 -4
  208. package/src/standalone/session-runtime-host.mjs +101 -6
  209. package/src/standalone/session-runtime-record.mjs +1 -5
  210. package/src/standalone/session-runtime-worker.mjs +52 -57
  211. package/src/standalone/session-service.mjs +4 -1
  212. package/src/standalone/session-transport.mjs +56 -25
  213. package/src/tui/App.jsx +40 -49
  214. package/src/tui/app/app-view.jsx +14 -21
  215. package/src/tui/app/core-memory-picker.mjs +29 -16
  216. package/src/tui/app/create-app-pickers.mjs +15 -36
  217. package/src/tui/app/extension-pickers.mjs +60 -49
  218. package/src/tui/app/maintenance-pickers.mjs +100 -57
  219. package/src/tui/app/message-selector.mjs +6 -5
  220. package/src/tui/app/model-picker.mjs +30 -13
  221. package/src/tui/app/onboarding-steps.mjs +21 -12
  222. package/src/tui/app/panel-epoch.mjs +56 -0
  223. package/src/tui/app/panel-epoch.test.mjs +1009 -0
  224. package/src/tui/app/panel-surface.mjs +98 -0
  225. package/src/tui/app/project-picker.mjs +30 -29
  226. package/src/tui/app/prompt-submit.mjs +192 -97
  227. package/src/tui/app/prompt-submit.test.mjs +206 -0
  228. package/src/tui/app/provider-setup-picker.mjs +74 -43
  229. package/src/tui/app/resume-picker.mjs +11 -8
  230. package/src/tui/app/route-pickers.mjs +45 -28
  231. package/src/tui/app/settings-picker.mjs +69 -18
  232. package/src/tui/app/shell-layout.mjs +1 -3
  233. package/src/tui/app/slash-commands.mjs +0 -2
  234. package/src/tui/app/slash-dispatch.mjs +4 -2
  235. package/src/tui/app/text-entry-policy.mjs +25 -0
  236. package/src/tui/app/theme-effort-pickers.mjs +11 -16
  237. package/src/tui/app/usage-context-panels.mjs +21 -25
  238. package/src/tui/app/use-global-key-input.mjs +3 -2
  239. package/src/tui/app/use-prompt-draft-flow.mjs +6 -18
  240. package/src/tui/app/use-prompt-handlers.mjs +3 -2
  241. package/src/tui/app/use-welcome-prompt-hint.mjs +17 -3
  242. package/src/tui/components/Picker.jsx +1 -1
  243. package/src/tui/components/TextEntryPanel.jsx +7 -2
  244. package/src/tui/dist/index.mjs +652 -561
  245. package/src/tui/lib/voice-setup.mjs +1 -2
  246. package/src/tui/session/agent-job-feed.mjs +73 -11
  247. package/src/tui/session/session-api-ext.mjs +5 -6
  248. package/src/tui/session/session-api.mjs +1 -0
  249. package/src/tui/session/tool-approval.mjs +15 -5
  250. package/src/tui/session/tui-steering-persist.mjs +33 -19
  251. package/src/tui/session/tui-steering-persist.test.mjs +104 -0
  252. package/src/tui/session-local.mjs +0 -22
  253. package/scripts/.tmp-coverage-report.mjs +0 -96
  254. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.mjs +0 -57
  255. 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.147",
3
+ "version": "0.9.148",
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
+ }