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.
Files changed (265) 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 +5 -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-codex-identity.test.mjs +32 -9
  48. package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +78 -33
  49. package/src/runtime/agent/orchestrator/providers/openai-codex-model.mjs +21 -0
  50. package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +36 -36
  51. package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +16 -6
  52. package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +50 -213
  53. package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +135 -126
  54. package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +20 -11
  55. package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +112 -36
  56. package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +220 -50
  57. package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +112 -9
  58. package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +11 -0
  59. package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +130 -60
  60. package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +2 -2
  61. package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +28 -10
  62. package/src/runtime/agent/orchestrator/providers/registry.mjs +122 -95
  63. package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +69 -3
  64. package/src/runtime/agent/orchestrator/providers/tool-stream-state.mjs +4 -22
  65. package/src/runtime/agent/orchestrator/session/agent-loop.mjs +7 -14
  66. package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +128 -0
  67. package/src/runtime/agent/orchestrator/session/context-compaction-policy.mjs +5 -0
  68. package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +27 -48
  69. package/src/runtime/agent/orchestrator/session/loop/steering.mjs +19 -14
  70. package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -43
  71. package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +5 -1
  72. package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +43 -5
  73. package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -2
  74. package/src/runtime/agent/orchestrator/session/manager/pending-messages-persistence.test.mjs +325 -0
  75. package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +599 -195
  76. package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +22 -0
  77. package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +9 -2
  78. package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +2 -0
  79. package/src/runtime/agent/orchestrator/session/manager.mjs +3 -0
  80. package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +26 -16
  81. package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +23 -11
  82. package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +118 -3
  83. package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +332 -0
  84. package/src/runtime/agent/orchestrator/session/store.mjs +38 -1
  85. package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +27 -6
  86. package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -20
  87. package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +291 -128
  88. package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +46 -9
  89. package/src/runtime/agent/orchestrator/tools/builtin/edit-encoding-eol.test.mjs +224 -0
  90. package/src/runtime/agent/orchestrator/tools/builtin/edit-sequential-occupation.test.mjs +53 -3
  91. package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +255 -25
  92. package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +87 -0
  93. package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +1 -1
  94. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +132 -9
  95. package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +10 -0
  96. package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +29 -3
  97. package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +1 -1
  98. package/src/runtime/agent/orchestrator/tools/builtin/list-tool-integrity.test.mjs +14 -0
  99. package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +180 -31
  100. package/src/runtime/agent/orchestrator/tools/builtin/local-search-telemetry.mjs +4 -1
  101. package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +32 -5
  102. package/src/runtime/agent/orchestrator/tools/builtin/native-search-health.test.mjs +5 -2
  103. package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +10 -1
  104. package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +38 -10
  105. package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +12 -0
  106. package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +31 -1
  107. package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +11 -1
  108. package/src/runtime/agent/orchestrator/tools/builtin/read-windows.mjs +22 -4
  109. package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +6 -8
  110. package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +25 -24
  111. package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +152 -133
  112. package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +114 -42
  113. package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +129 -15
  114. package/src/runtime/agent/orchestrator/tools/code-graph/loose-file-target.test.mjs +72 -0
  115. package/src/runtime/agent/orchestrator/tools/code-graph/project-root.mjs +7 -8
  116. package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +4 -4
  117. package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +201 -19
  118. package/src/runtime/agent/orchestrator/tools/code-graph/span.mjs +26 -3
  119. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-index.mjs +10 -1
  120. package/src/runtime/agent/orchestrator/tools/code-graph/symbol-search-scope.test.mjs +55 -0
  121. package/src/runtime/agent/orchestrator/tools/code-graph/text-mask.mjs +115 -25
  122. package/src/runtime/agent/orchestrator/tools/code-graph/text-span-mask.test.mjs +268 -0
  123. package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -3
  124. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +296 -46
  125. package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.test.mjs +104 -0
  126. package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +136 -0
  127. package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +127 -38
  128. package/src/runtime/agent/orchestrator/tools/patch/engine-contract-gate.test.mjs +661 -0
  129. package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +350 -9
  130. package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +418 -44
  131. package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +27 -3
  132. package/src/runtime/agent/orchestrator/tools/patch/patch-codec-eol.test.mjs +552 -0
  133. package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +65 -15
  134. package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
  135. package/src/runtime/agent/orchestrator/tools/shell-command.mjs +284 -109
  136. package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +51 -21
  137. package/src/runtime/agent/orchestrator/tools/shell-exec-policy.mjs +44 -27
  138. package/src/runtime/agent/orchestrator/tools/shell-policy.mjs +6 -1
  139. package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
  140. package/src/runtime/attachments/store.mjs +11 -3
  141. package/src/runtime/channels/data/voice-runtime-manifest.json +46 -70
  142. package/src/runtime/channels/lib/event-queue.mjs +51 -41
  143. package/src/runtime/channels/lib/scheduler.mjs +23 -28
  144. package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +6 -17
  145. package/src/runtime/channels/lib/voice-runtime-fetcher.test.mjs +17 -0
  146. package/src/runtime/channels/lib/webhook.mjs +39 -20
  147. package/src/runtime/channels/lib/whisper-server.mjs +46 -1
  148. package/src/runtime/channels/lib/worker-main.mjs +3 -1
  149. package/src/runtime/media/adapters/gemini-video.mjs +20 -4
  150. package/src/runtime/media/jobs.mjs +13 -0
  151. package/src/runtime/media/renditions.mjs +7 -1
  152. package/src/runtime/media/store.mjs +42 -22
  153. package/src/runtime/memory/index.mjs +4 -9
  154. package/src/runtime/memory/lib/agent-ipc.mjs +1 -6
  155. package/src/runtime/memory/lib/archive-security.test.mjs +21 -1
  156. package/src/runtime/memory/lib/core-memory-store.mjs +28 -50
  157. package/src/runtime/memory/lib/embedding-model-config.mjs +37 -1
  158. package/src/runtime/memory/lib/embedding-model-config.test.mjs +33 -0
  159. package/src/runtime/memory/lib/embedding-provider.mjs +34 -12
  160. package/src/runtime/memory/lib/embedding-worker.mjs +37 -5
  161. package/src/runtime/memory/lib/ko-morph.mjs +46 -15
  162. package/src/runtime/memory/lib/memory-action-handlers.mjs +18 -1
  163. package/src/runtime/memory/lib/memory-cycle1.mjs +1 -18
  164. package/src/runtime/memory/lib/memory-cycle2-gate.mjs +1 -18
  165. package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +98 -45
  166. package/src/runtime/memory/lib/memory-cycle2-shared.mjs +71 -0
  167. package/src/runtime/memory/lib/memory-cycle2-shared.test.mjs +78 -0
  168. package/src/runtime/memory/lib/memory-cycle2.mjs +37 -3
  169. package/src/runtime/memory/lib/memory-cycle3.mjs +1 -1
  170. package/src/runtime/memory/lib/memory-embedding-migration.test.mjs +50 -0
  171. package/src/runtime/memory/lib/memory-recall-store.mjs +9 -24
  172. package/src/runtime/memory/lib/memory.mjs +50 -12
  173. package/src/runtime/memory/lib/pg/process.mjs +1 -10
  174. package/src/runtime/memory/lib/pg/supervisor.mjs +1 -9
  175. package/src/runtime/memory/lib/query-handlers.mjs +2 -4
  176. package/src/runtime/memory/lib/session-ingest-runtime.mjs +2 -2
  177. package/src/runtime/shared/background-tasks.mjs +15 -0
  178. package/src/runtime/shared/background-tasks.test.mjs +78 -0
  179. package/src/runtime/shared/child-guardian.mjs +2 -10
  180. package/src/runtime/shared/pid-liveness.mjs +27 -0
  181. package/src/runtime/shared/pristine-execution-contract.json +0 -1
  182. package/src/runtime/shared/runtime-root.mjs +1 -11
  183. package/src/runtime/shared/service-discovery.mjs +1 -17
  184. package/src/runtime/shared/singleton-owner.mjs +1 -11
  185. package/src/runtime/shared/staged-update.mjs +2 -12
  186. package/src/runtime/shared/tool-status.mjs +5 -1
  187. package/src/runtime/shared/tool-surface.mjs +15 -7
  188. package/src/runtime/shared/turn-snapshot-store.mjs +168 -0
  189. package/src/runtime/shared/turn-snapshot.mjs +137 -13
  190. package/src/runtime/shared/turn-worktree-snapshot.mjs +110 -18
  191. package/src/runtime/shared/webhook-session-run.mjs +16 -2
  192. package/src/runtime/web-search/lib/http-fetch.mjs +34 -4
  193. package/src/runtime/web-search/lib/state.mjs +15 -15
  194. package/src/runtime/web-search/lib/web-tools.mjs +46 -5
  195. package/src/session-runtime/context-status.mjs +4 -12
  196. package/src/session-runtime/runtime-core.mjs +20 -1
  197. package/src/session-runtime/session-lifecycle.mjs +9 -0
  198. package/src/standalone/agent-dispatch-broker.mjs +33 -2
  199. package/src/standalone/agent-tool/job-task-reconcile.mjs +78 -0
  200. package/src/standalone/agent-tool/notify.mjs +3 -1
  201. package/src/standalone/agent-tool/spawn-flow.mjs +52 -70
  202. package/src/standalone/agent-tool/tag-registry.mjs +116 -33
  203. package/src/standalone/agent-tool/tool-def.mjs +2 -2
  204. package/src/standalone/agent-tool/worker-index.mjs +8 -11
  205. package/src/standalone/agent-tool/worker-rows.mjs +4 -1
  206. package/src/standalone/agent-tool.mjs +20 -55
  207. package/src/standalone/agent-watchdog-registry.mjs +28 -4
  208. package/src/standalone/channel-client.mjs +47 -15
  209. package/src/standalone/channel-transport.mjs +44 -25
  210. package/src/standalone/daemon.mjs +31 -2
  211. package/src/standalone/hook-bus/config.mjs +25 -9
  212. package/src/standalone/hook-bus/handlers.mjs +16 -52
  213. package/src/standalone/hook-bus.mjs +29 -23
  214. package/src/standalone/memory-runtime-proxy.mjs +1 -16
  215. package/src/standalone/session-client.mjs +86 -23
  216. package/src/standalone/session-protocol.mjs +0 -2
  217. package/src/standalone/session-runtime-host-health.test.mjs +5 -4
  218. package/src/standalone/session-runtime-host.mjs +101 -6
  219. package/src/standalone/session-runtime-record.mjs +1 -5
  220. package/src/standalone/session-runtime-worker.mjs +52 -57
  221. package/src/standalone/session-service.mjs +4 -1
  222. package/src/standalone/session-transport.mjs +56 -25
  223. package/src/tui/App.jsx +40 -49
  224. package/src/tui/app/app-view.jsx +14 -21
  225. package/src/tui/app/core-memory-picker.mjs +29 -16
  226. package/src/tui/app/create-app-pickers.mjs +15 -36
  227. package/src/tui/app/extension-pickers.mjs +60 -49
  228. package/src/tui/app/maintenance-pickers.mjs +100 -57
  229. package/src/tui/app/message-selector.mjs +6 -5
  230. package/src/tui/app/model-picker.mjs +30 -13
  231. package/src/tui/app/onboarding-steps.mjs +21 -12
  232. package/src/tui/app/panel-epoch.mjs +56 -0
  233. package/src/tui/app/panel-epoch.test.mjs +1009 -0
  234. package/src/tui/app/panel-surface.mjs +98 -0
  235. package/src/tui/app/project-picker.mjs +30 -29
  236. package/src/tui/app/prompt-submit.mjs +192 -97
  237. package/src/tui/app/prompt-submit.test.mjs +206 -0
  238. package/src/tui/app/provider-setup-picker.mjs +74 -43
  239. package/src/tui/app/resume-picker.mjs +11 -8
  240. package/src/tui/app/route-pickers.mjs +45 -28
  241. package/src/tui/app/settings-picker.mjs +69 -18
  242. package/src/tui/app/shell-layout.mjs +1 -3
  243. package/src/tui/app/slash-commands.mjs +0 -2
  244. package/src/tui/app/slash-dispatch.mjs +4 -2
  245. package/src/tui/app/text-entry-policy.mjs +25 -0
  246. package/src/tui/app/theme-effort-pickers.mjs +11 -16
  247. package/src/tui/app/usage-context-panels.mjs +21 -25
  248. package/src/tui/app/use-global-key-input.mjs +3 -2
  249. package/src/tui/app/use-prompt-draft-flow.mjs +6 -18
  250. package/src/tui/app/use-prompt-handlers.mjs +3 -2
  251. package/src/tui/app/use-welcome-prompt-hint.mjs +17 -3
  252. package/src/tui/components/Picker.jsx +1 -1
  253. package/src/tui/components/TextEntryPanel.jsx +7 -2
  254. package/src/tui/dist/index.mjs +652 -561
  255. package/src/tui/lib/voice-setup.mjs +1 -2
  256. package/src/tui/session/agent-job-feed.mjs +73 -11
  257. package/src/tui/session/session-api-ext.mjs +5 -6
  258. package/src/tui/session/session-api.mjs +1 -0
  259. package/src/tui/session/tool-approval.mjs +15 -5
  260. package/src/tui/session/tui-steering-persist.mjs +33 -19
  261. package/src/tui/session/tui-steering-persist.test.mjs +104 -0
  262. package/src/tui/session-local.mjs +0 -22
  263. package/scripts/.tmp-coverage-report.mjs +0 -96
  264. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.mjs +0 -57
  265. package/src/runtime/agent/orchestrator/tools/builtin/shell-monitor.test.mjs +0 -57
@@ -0,0 +1,135 @@
1
+ import assert from 'node:assert/strict'
2
+ import test from 'node:test'
3
+
4
+ import {
5
+ VOICE_RUNTIME_CONFIG,
6
+ VOICE_RUNTIME_TAG,
7
+ buildVoiceRuntimePlatforms,
8
+ fetchGithubRelease,
9
+ voiceRuntimeBuildHash,
10
+ } from './generate-voice-runtime-manifest.mjs'
11
+ import { mergeVoiceRuntimeManifest } from './sync-voice-runtime-manifest.mjs'
12
+
13
+ const names = VOICE_RUNTIME_CONFIG.platforms.map((platform) => platform.asset)
14
+
15
+ test('voice runtime release assets produce a complete fail-closed manifest', () => {
16
+ assert.equal(VOICE_RUNTIME_CONFIG.vulkanSdkVersion, '1.4.309.0')
17
+ const assets = names.map((name, index) => ({
18
+ name,
19
+ size: index + 1,
20
+ digest: `sha256:${String(index + 1).padStart(64, '0')}`,
21
+ browser_download_url: `https://github.com/tribgames/mixdog/releases/download/${VOICE_RUNTIME_TAG}/${name}`,
22
+ }))
23
+ const platforms = buildVoiceRuntimePlatforms(assets)
24
+ assert.equal(platforms['win32-x64'].variants[0].id, 'vulkan')
25
+ assert.equal(platforms['linux-x64'].variants[0].id, 'vulkan')
26
+ assert.equal(platforms['linux-arm64'].variants[0].id, 'vulkan')
27
+ assert.equal(platforms['darwin-arm64'].variants[0].id, 'metal')
28
+ assert.equal(platforms['darwin-x64'].variants[0].id, 'cpu')
29
+ assert.equal(platforms['win32-x64'].variants[0].executable, 'whisper-server.exe')
30
+ assert.deepEqual(
31
+ VOICE_RUNTIME_CONFIG.platforms
32
+ .filter((platform) => platform.os === 'linux')
33
+ .map((platform) => platform.runner),
34
+ ['ubuntu-24.04', 'ubuntu-24.04-arm'],
35
+ )
36
+ })
37
+
38
+ test('voice runtime manifest generation rejects a partial release', () => {
39
+ assert.throws(() => buildVoiceRuntimePlatforms([]), /missing required asset/)
40
+ })
41
+
42
+ test('voice runtime manifest generation rejects undeclared archives', () => {
43
+ assert.throws(
44
+ () => buildVoiceRuntimePlatforms([{
45
+ name: 'whisper-server-linux-riscv64-cpu.zip',
46
+ size: 1,
47
+ digest: `sha256:${'1'.padStart(64, '0')}`,
48
+ browser_download_url:
49
+ `https://github.com/tribgames/mixdog/releases/download/${VOICE_RUNTIME_TAG}/whisper-server-linux-riscv64-cpu.zip`,
50
+ }]),
51
+ /unexpected runtime asset/,
52
+ )
53
+ })
54
+
55
+ test('voice runtime manifest normalizes hidden draft asset URLs to the published tag', () => {
56
+ const assets = names.map((name, index) => ({
57
+ name,
58
+ size: index + 1,
59
+ digest: `sha256:${String(index + 1).padStart(64, '0')}`,
60
+ browser_download_url:
61
+ `https://github.com/tribgames/mixdog/releases/download/untagged-fixture/${name}`,
62
+ }))
63
+ const platforms = buildVoiceRuntimePlatforms(assets)
64
+ assert.equal(
65
+ platforms['win32-x64'].variants[0].url,
66
+ `https://github.com/tribgames/mixdog/releases/download/${VOICE_RUNTIME_TAG}/whisper-server-win32-x64-vulkan.zip`,
67
+ )
68
+ })
69
+
70
+ test('voice runtime release lookup finds an authenticated untagged draft', async (t) => {
71
+ const originalFetch = globalThis.fetch
72
+ t.after(() => {
73
+ globalThis.fetch = originalFetch
74
+ })
75
+ const requests = []
76
+ globalThis.fetch = async (url) => {
77
+ requests.push(String(url))
78
+ if (String(url).includes('/releases/tags/')) {
79
+ return new Response(null, { status: 404 })
80
+ }
81
+ return Response.json([{
82
+ tag_name: VOICE_RUNTIME_TAG,
83
+ draft: true,
84
+ assets: [],
85
+ }])
86
+ }
87
+
88
+ const release = await fetchGithubRelease(
89
+ 'tribgames/mixdog',
90
+ VOICE_RUNTIME_TAG,
91
+ 'fixture-token',
92
+ )
93
+ assert.equal(release.draft, true)
94
+ assert.equal(release.tag_name, VOICE_RUNTIME_TAG)
95
+ assert.equal(requests.length, 2)
96
+ assert.match(requests[1], /\/releases\?per_page=100&page=1$/)
97
+ })
98
+
99
+ test('voice runtime build identity is stable and merge preserves model and ffmpeg metadata', async () => {
100
+ const buildHash = await voiceRuntimeBuildHash()
101
+ assert.match(buildHash, /^[a-f0-9]{64}$/)
102
+ const platforms = Object.fromEntries(VOICE_RUNTIME_CONFIG.platforms.map((platform, index) => [
103
+ platform.key,
104
+ {
105
+ variants: [{
106
+ id: platform.backend,
107
+ requires: null,
108
+ url: `https://github.com/tribgames/mixdog/releases/download/${VOICE_RUNTIME_TAG}/${platform.asset}`,
109
+ sha256: String(index + 1).padStart(64, '0'),
110
+ size: index + 1,
111
+ format: 'zip',
112
+ executable: platform.executable,
113
+ }],
114
+ },
115
+ ]))
116
+ const current = {
117
+ version: '1.8.4',
118
+ source: 'old',
119
+ model: { id: 'standard' },
120
+ ffmpeg: { version: 'b6.1.1' },
121
+ platforms: {},
122
+ }
123
+ const released = {
124
+ ...current,
125
+ version: VOICE_RUNTIME_CONFIG.runtimeVersion,
126
+ source: `ggml-org/whisper.cpp@${VOICE_RUNTIME_CONFIG.whisperCommit}`,
127
+ release_tag: VOICE_RUNTIME_TAG,
128
+ build_hash: buildHash,
129
+ platforms,
130
+ }
131
+ const merged = await mergeVoiceRuntimeManifest(current, released)
132
+ assert.strictEqual(merged.model, current.model)
133
+ assert.strictEqual(merged.ffmpeg, current.ffmpeg)
134
+ assert.deepEqual(merged.platforms, platforms)
135
+ })
@@ -23,9 +23,18 @@ const PE_MACHINE = new Map([[0x8664, 'x64'], [0xaa64, 'arm64'], [0x14c, 'ia32']]
23
23
  function machOArch(buffer) {
24
24
  if (buffer.length < 8) return null
25
25
  const magic = buffer.readUInt32BE(0)
26
- // Thin Mach-O: 0xfeedfacf (64-bit) / 0xfeedface (32-bit), either endianness.
27
- if (magic === 0xfeedfacf || magic === 0xfeedface) return MACHO_CPU.get(buffer.readUInt32BE(4)) || null
28
- if (magic === 0xcffaedfe || magic === 0xcefaedfe) return MACHO_CPU.get(buffer.readUInt32LE(4)) || null
26
+ // mach_header_64 is 32 bytes; mach_header is 28. Do not trust cputype until
27
+ // the full header extent is present.
28
+ if (magic === 0xfeedfacf || magic === 0xcffaedfe) {
29
+ if (buffer.length < 32) return null
30
+ const cpu = magic === 0xfeedfacf ? buffer.readUInt32BE(4) : buffer.readUInt32LE(4)
31
+ return MACHO_CPU.get(cpu) || null
32
+ }
33
+ if (magic === 0xfeedface || magic === 0xcefaedfe) {
34
+ if (buffer.length < 28) return null
35
+ const cpu = magic === 0xfeedface ? buffer.readUInt32BE(4) : buffer.readUInt32LE(4)
36
+ return MACHO_CPU.get(cpu) || null
37
+ }
29
38
  return null
30
39
  }
31
40
 
@@ -40,10 +49,10 @@ function machOFatArches(buffer) {
40
49
  const count = buffer.readUInt32BE(4)
41
50
  if (!count || count > 16) return null
42
51
  const entrySize = wide ? 32 : 20
52
+ if (buffer.length < 8 + count * entrySize) return null
43
53
  const arches = []
44
54
  for (let index = 0; index < count; index += 1) {
45
55
  const offset = 8 + index * entrySize
46
- if (offset + 4 > buffer.length) return null
47
56
  const arch = MACHO_CPU.get(buffer.readUInt32BE(offset))
48
57
  if (arch) arches.push(arch)
49
58
  }
@@ -51,17 +60,26 @@ function machOFatArches(buffer) {
51
60
  }
52
61
 
53
62
  function elfArch(buffer) {
54
- if (buffer.length < 20) return null
55
- if (buffer.readUInt32BE(0) !== 0x7f454c46) return null
56
- const littleEndian = buffer[5] === 1
63
+ if (buffer.length < 16 || buffer.readUInt32BE(0) !== 0x7f454c46) return null
64
+ const eiClass = buffer[4]
65
+ const eiData = buffer[5]
66
+ if (eiData !== 1 && eiData !== 2) return null
67
+ const headerSize = eiClass === 1 ? 52 : eiClass === 2 ? 64 : 0
68
+ if (!headerSize || buffer.length < headerSize) return null
69
+ const littleEndian = eiData === 1
57
70
  const machine = littleEndian ? buffer.readUInt16LE(18) : buffer.readUInt16BE(18)
58
- return ELF_MACHINE.get(machine) || null
71
+ const arch = ELF_MACHINE.get(machine)
72
+ if (!arch) return null
73
+ const expectedClass = arch === 'ia32' ? 1 : 2
74
+ if (eiClass !== expectedClass) return null
75
+ return arch
59
76
  }
60
77
 
61
78
  function peArch(buffer) {
62
79
  if (buffer.length < 64 || buffer.readUInt16LE(0) !== 0x5a4d) return null
63
80
  const headerOffset = buffer.readUInt32LE(60)
64
- if (headerOffset + 6 > buffer.length) return null
81
+ // IMAGE_NT_HEADERS starts at e_lfanew: 4-byte PE signature + 20-byte COFF header.
82
+ if (headerOffset < 64 || headerOffset + 24 > buffer.length) return null
65
83
  if (buffer.readUInt32LE(headerOffset) !== 0x00004550) return null
66
84
  return PE_MACHINE.get(buffer.readUInt16LE(headerOffset + 4)) || null
67
85
  }
@@ -83,11 +101,10 @@ export function nativeBinaryArch(buffer) {
83
101
  }
84
102
 
85
103
  /**
86
- * True when the artifact can run on `arch`. An unrecognized header answers
87
- * true: the caller is verifying compiled objects, and refusing every file it
88
- * cannot parse would fail builds over unrelated payloads.
104
+ * True when the artifact can run on `arch`. Unknown, truncated, or unsupported
105
+ * headers answer false so a wrong-arch or unreadable object cannot publish.
89
106
  */
90
107
  export function nativeBinaryRunsOn(buffer, arch) {
91
108
  const arches = nativeBinaryArches(buffer)
92
- return arches === null || arches.includes(arch)
109
+ return Array.isArray(arches) && arches.includes(arch)
93
110
  }
@@ -28,9 +28,10 @@ function machOFat(cpus) {
28
28
  return buffer
29
29
  }
30
30
 
31
- function elf(machine) {
32
- const buffer = Buffer.alloc(32)
31
+ function elf(machine, { eiClass = 2 } = {}) {
32
+ const buffer = Buffer.alloc(eiClass === 1 ? 52 : 64)
33
33
  buffer.writeUInt32BE(0x7f454c46, 0)
34
+ buffer[4] = eiClass
34
35
  buffer[5] = 1
35
36
  buffer.writeUInt16LE(machine, 18)
36
37
  return buffer
@@ -60,16 +61,51 @@ test('universal binaries report every slice they carry', () => {
60
61
  test('ELF and PE headers are read too', () => {
61
62
  assert.equal(nativeBinaryArch(elf(0x3e)), 'x64')
62
63
  assert.equal(nativeBinaryArch(elf(0xb7)), 'arm64')
64
+ assert.equal(nativeBinaryArch(elf(0x03, { eiClass: 1 })), 'ia32')
63
65
  assert.equal(nativeBinaryArch(pe(0x8664)), 'x64')
64
66
  assert.equal(nativeBinaryArch(pe(0xaa64)), 'arm64')
65
67
  })
66
68
 
69
+ test('ELF class must match the named machine', () => {
70
+ assert.equal(nativeBinaryArch(elf(0x3e, { eiClass: 1 })), null)
71
+ assert.equal(nativeBinaryRunsOn(elf(0x3e, { eiClass: 1 }), 'x64'), false)
72
+ assert.equal(nativeBinaryArch(elf(0xb7, { eiClass: 1 })), null)
73
+ assert.equal(nativeBinaryArch(elf(0x03)), null)
74
+ })
75
+
67
76
  test('a foreign architecture is rejected for the target', () => {
68
77
  assert.equal(nativeBinaryRunsOn(machO({ cpu: 0x0100000c }), 'x64'), false)
69
78
  assert.equal(nativeBinaryRunsOn(elf(0x3e), 'arm64'), false)
70
79
  })
71
80
 
72
- test('bytes without a recognizable object header do not fail a build', () => {
81
+ test('unknown, truncated, or unsupported headers are not architecture-compatible', () => {
73
82
  assert.equal(nativeBinaryArches(Buffer.from('#!/bin/sh\nexec node "$0"\n')), null)
74
- assert.equal(nativeBinaryRunsOn(Buffer.alloc(4), 'x64'), true)
83
+ assert.equal(nativeBinaryRunsOn(Buffer.alloc(4), 'x64'), false)
84
+ assert.equal(nativeBinaryRunsOn(Buffer.from('#!/bin/sh\n'), 'arm64'), false)
85
+ assert.equal(nativeBinaryRunsOn(elf(0xf3), 'x64'), false)
86
+
87
+ const truncatedMachO = Buffer.alloc(8)
88
+ truncatedMachO.writeUInt32BE(0xcffaedfe, 0)
89
+ truncatedMachO.writeUInt32LE(0x01000007, 4)
90
+ assert.equal(nativeBinaryRunsOn(truncatedMachO, 'x64'), false)
91
+
92
+ const truncatedElf = Buffer.alloc(20)
93
+ truncatedElf.writeUInt32BE(0x7f454c46, 0)
94
+ truncatedElf[4] = 1
95
+ truncatedElf[5] = 1
96
+ truncatedElf.writeUInt16LE(0x3e, 18)
97
+ assert.equal(nativeBinaryRunsOn(truncatedElf, 'x64'), false)
98
+
99
+ const truncatedPe = Buffer.alloc(70)
100
+ truncatedPe.writeUInt16LE(0x5a4d, 0)
101
+ truncatedPe.writeUInt32LE(64, 60)
102
+ truncatedPe.writeUInt32LE(0x00004550, 64)
103
+ truncatedPe.writeUInt16LE(0x8664, 68)
104
+ assert.equal(nativeBinaryRunsOn(truncatedPe, 'x64'), false)
105
+
106
+ const truncatedFat = Buffer.alloc(12)
107
+ truncatedFat.writeUInt32BE(0xcafebabe, 0)
108
+ truncatedFat.writeUInt32BE(1, 4)
109
+ truncatedFat.writeUInt32BE(0x01000007, 8)
110
+ assert.equal(nativeBinaryRunsOn(truncatedFat, 'x64'), false)
75
111
  })
@@ -0,0 +1,73 @@
1
+ import { appendFileSync } from 'node:fs'
2
+ import { readFile } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+
5
+ import {
6
+ VOICE_RUNTIME_CONFIG,
7
+ VOICE_RUNTIME_TAG,
8
+ assertVoiceRuntimeManifestIdentity,
9
+ compareSemver,
10
+ downloadReleaseAsset,
11
+ fetchGithubRelease,
12
+ sameVoiceRuntimeIdentity,
13
+ } from './generate-voice-runtime-manifest.mjs'
14
+
15
+ async function main() {
16
+ const repository = String(process.env.GITHUB_REPOSITORY || 'tribgames/mixdog')
17
+ const requestedTag = String(process.env.INPUT_TAG || '').trim()
18
+ if (requestedTag && requestedTag !== VOICE_RUNTIME_TAG) {
19
+ throw new Error(`voice runtime tag must be ${VOICE_RUNTIME_TAG}, got ${requestedTag}`)
20
+ }
21
+
22
+ const bundledPath = resolve('src/runtime/channels/data/voice-runtime-manifest.json')
23
+ const bundled = JSON.parse(await readFile(bundledPath, 'utf8'))
24
+ if (compareSemver(bundled.version, VOICE_RUNTIME_CONFIG.runtimeVersion) > 0) {
25
+ throw new Error(
26
+ `refusing voice runtime downgrade from ${bundled.version} to ${VOICE_RUNTIME_CONFIG.runtimeVersion}`,
27
+ )
28
+ }
29
+
30
+ const release = await fetchGithubRelease(repository, VOICE_RUNTIME_TAG, process.env.GITHUB_TOKEN, {
31
+ allowMissing: true,
32
+ })
33
+ let buildNeeded = true
34
+ let syncNeeded = true
35
+ let releaseState = 'missing'
36
+
37
+ if (release?.draft) {
38
+ releaseState = 'draft'
39
+ } else if (release) {
40
+ releaseState = 'published'
41
+ if (release.prerelease) throw new Error(`${VOICE_RUNTIME_TAG} must not be a prerelease`)
42
+ const manifestAsset = release.assets?.find((asset) => asset.name === 'voice-runtime-manifest.json')
43
+ if (!manifestAsset) {
44
+ throw new Error(`published ${VOICE_RUNTIME_TAG} has no voice-runtime-manifest.json`)
45
+ }
46
+ const released = JSON.parse(
47
+ (await downloadReleaseAsset(manifestAsset, process.env.GITHUB_TOKEN)).toString('utf8'),
48
+ )
49
+ await assertVoiceRuntimeManifestIdentity(released, { repository })
50
+ buildNeeded = false
51
+ syncNeeded = !sameVoiceRuntimeIdentity(bundled, released)
52
+ }
53
+
54
+ const values = {
55
+ tag: VOICE_RUNTIME_TAG,
56
+ version: VOICE_RUNTIME_CONFIG.runtimeVersion,
57
+ commit: VOICE_RUNTIME_CONFIG.whisperCommit,
58
+ vulkan_sdk_version: VOICE_RUNTIME_CONFIG.vulkanSdkVersion,
59
+ matrix: JSON.stringify(VOICE_RUNTIME_CONFIG.platforms),
60
+ build_needed: String(buildNeeded),
61
+ sync_needed: String(syncNeeded),
62
+ }
63
+ const output = process.env.GITHUB_OUTPUT
64
+ if (output) {
65
+ for (const [key, value] of Object.entries(values)) appendFileSync(output, `${key}=${value}\n`)
66
+ }
67
+ console.log(JSON.stringify({ release_state: releaseState, ...values }, null, 2))
68
+ }
69
+
70
+ main().catch((error) => {
71
+ console.error(error?.stack || error)
72
+ process.exitCode = 1
73
+ })
@@ -22,17 +22,40 @@ import { ensureTokenAddon } from '../src/runtime/agent/orchestrator/tools/token-
22
22
 
23
23
  const DEFAULT_PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
24
24
 
25
+ export const NATIVE_ASSET_PLATFORMS = Object.freeze([
26
+ 'darwin-arm64',
27
+ 'darwin-x64',
28
+ 'linux-arm64',
29
+ 'linux-x64',
30
+ 'win32-x64',
31
+ ]);
32
+
25
33
  export const REQUIRED_NATIVE_INSTALLERS = Object.freeze({
26
34
  graph: ensureGraphBinary,
27
35
  patch: ensurePatchBinary,
28
36
  spawn: ensureSpawnBinary,
37
+ });
38
+
39
+ export const OPTIONAL_NATIVE_INSTALLERS = Object.freeze({
29
40
  token: ensureTokenAddon,
30
41
  });
31
42
 
43
+ export function nativeAssetPlatformKey(platform = process.platform, arch = process.arch) {
44
+ return `${platform}-${arch}`;
45
+ }
46
+
32
47
  export async function prepareRequiredNativeAssets({
33
48
  packageRoot = DEFAULT_PACKAGE_ROOT,
34
- installers = REQUIRED_NATIVE_INSTALLERS,
49
+ installers = { ...REQUIRED_NATIVE_INSTALLERS, ...OPTIONAL_NATIVE_INSTALLERS },
50
+ platform = process.platform,
51
+ arch = process.arch,
35
52
  } = {}) {
53
+ const platformKey = nativeAssetPlatformKey(platform, arch);
54
+ if (!NATIVE_ASSET_PLATFORMS.includes(platformKey)) {
55
+ throw new Error(
56
+ `native assets are not published for ${platformKey}; supported: ${NATIVE_ASSET_PLATFORMS.join(', ')}`,
57
+ );
58
+ }
36
59
  const root = resolve(String(packageRoot || ''));
37
60
  const target = packageNativeToolsDir(root);
38
61
  const stagingRoot = await mkdtemp(join(root, '.native-tools-install-'));
@@ -40,24 +63,42 @@ export async function prepareRequiredNativeAssets({
40
63
  const stagedToolsDir = join(stagingRoot, 'native-tools');
41
64
  try {
42
65
  await mkdir(stagedToolsDir, { recursive: true });
43
- const entries = await Promise.all(
44
- Object.entries(NATIVE_TOOL_FILENAMES).map(async ([name, fileName]) => {
45
- const install = installers[name];
66
+ const installOne = async (name, fileName, { optional }) => {
67
+ const install = installers[name];
46
68
  if (typeof install !== 'function') {
69
+ if (optional) {
70
+ console.warn(`[native-assets] optional ${name} skipped: installer is invalid`);
71
+ return null;
72
+ }
47
73
  throw new Error(`native asset installer is invalid: ${name}`);
48
74
  }
75
+ try {
49
76
  const installedPath = await install(downloadDataDir);
50
- if (typeof installedPath !== 'string' || installedPath.length === 0) {
51
- throw new Error(`native asset installer returned no path: ${name}`);
52
- }
77
+ if (typeof installedPath !== 'string' || installedPath.length === 0) {
78
+ throw new Error(`native asset installer returned no path: ${name}`);
79
+ }
53
80
  const destination = join(stagedToolsDir, fileName);
54
81
  await copyFile(installedPath, destination);
55
82
  if (process.platform !== 'win32' && name !== 'token') {
56
83
  await chmod(destination, 0o755);
57
84
  }
58
85
  return [name, join(target, fileName)];
59
- }),
86
+ } catch (error) {
87
+ if (!optional) throw error;
88
+ console.warn(`[native-assets] optional ${name} skipped: ${error?.message || error}`);
89
+ return null;
90
+ }
91
+ };
92
+ const requiredEntries = await Promise.all(
93
+ Object.keys(REQUIRED_NATIVE_INSTALLERS).map((name) => (
94
+ installOne(name, NATIVE_TOOL_FILENAMES[name], { optional: false })
95
+ )),
60
96
  );
97
+ const optionalEntries = [];
98
+ for (const name of Object.keys(OPTIONAL_NATIVE_INSTALLERS)) {
99
+ const entry = await installOne(name, NATIVE_TOOL_FILENAMES[name], { optional: true });
100
+ if (entry) optionalEntries.push(entry);
101
+ }
61
102
  await rm(target, {
62
103
  recursive: true,
63
104
  force: true,
@@ -65,7 +106,7 @@ export async function prepareRequiredNativeAssets({
65
106
  retryDelay: 100,
66
107
  });
67
108
  await rename(stagedToolsDir, target);
68
- return Object.fromEntries(entries);
109
+ return Object.fromEntries([...requiredEntries, ...optionalEntries]);
69
110
  } finally {
70
111
  // Antivirus and process scanners can briefly retain freshly copied .exe
71
112
  // handles on Windows. Cleanup must not replace the installer's real error
@@ -99,7 +99,7 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
99
99
  )))).find(Boolean)
100
100
  if (!ortRoot) throw new Error('Embedding runtime is incomplete: onnxruntime-node is unavailable')
101
101
 
102
- const targetBinaryDir = join(ortRoot, 'bin', 'napi-v3', target.platform, target.arch)
102
+ const targetBinaryDir = join(ortRoot, 'bin', 'napi-v6', target.platform, target.arch)
103
103
  if (!(await exists(join(targetBinaryDir, 'onnxruntime_binding.node')))) {
104
104
  throw new Error(`Embedding runtime is incomplete: missing ${target.key} ONNX binding`)
105
105
  }
@@ -118,9 +118,9 @@ export async function pruneEmbeddingRuntime(packageRoot, options = {}) {
118
118
  ]))
119
119
 
120
120
  // onnxruntime-node resolves exactly:
121
- // bin/napi-v3/${process.platform}/${process.arch}/onnxruntime_binding.node.
121
+ // bin/napi-v6/${process.platform}/${process.arch}/onnxruntime_binding.node.
122
122
  // Keep one native payload and remove every foreign OS/architecture.
123
- const napiRoot = join(ortRoot, 'bin', 'napi-v3')
123
+ const napiRoot = join(ortRoot, 'bin', 'napi-v6')
124
124
  await removeChildrenExcept(napiRoot, new Set([target.platform]))
125
125
  await removeChildrenExcept(join(napiRoot, target.platform), new Set([target.arch]))
126
126
  await removeChildrenExcept(ortRoot, new Set([
@@ -0,0 +1,46 @@
1
+ import { readFile, writeFile } from 'node:fs/promises'
2
+ import { resolve } from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+
5
+ import {
6
+ assertVoiceRuntimeManifestIdentity,
7
+ compareSemver,
8
+ } from './generate-voice-runtime-manifest.mjs'
9
+
10
+ export async function mergeVoiceRuntimeManifest(current, released, options = {}) {
11
+ await assertVoiceRuntimeManifestIdentity(released, options)
12
+ if (compareSemver(current.version, released.version) > 0) {
13
+ throw new Error(`refusing voice runtime manifest downgrade from ${current.version} to ${released.version}`)
14
+ }
15
+ return {
16
+ ...current,
17
+ version: released.version,
18
+ source: released.source,
19
+ release_tag: released.release_tag,
20
+ build_hash: released.build_hash,
21
+ platforms: released.platforms,
22
+ }
23
+ }
24
+
25
+ async function main() {
26
+ const releasedPath = resolve(process.argv[2] || 'dist/voice-runtime-manifest.json')
27
+ const bundledPath = resolve(
28
+ process.argv[3] || 'src/runtime/channels/data/voice-runtime-manifest.json',
29
+ )
30
+ const [released, current] = await Promise.all([
31
+ readFile(releasedPath, 'utf8').then(JSON.parse),
32
+ readFile(bundledPath, 'utf8').then(JSON.parse),
33
+ ])
34
+ const repository = String(process.env.GITHUB_REPOSITORY || 'tribgames/mixdog')
35
+ const merged = await mergeVoiceRuntimeManifest(current, released, { repository })
36
+ await writeFile(bundledPath, `${JSON.stringify(merged, null, 2)}\n`)
37
+ console.log(`synchronized ${bundledPath} from ${releasedPath}`)
38
+ }
39
+
40
+ const invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : ''
41
+ if (invokedPath === import.meta.url) {
42
+ main().catch((error) => {
43
+ console.error(error?.stack || error)
44
+ process.exitCode = 1
45
+ })
46
+ }