@xopcai/xopc 0.0.221 → 0.0.223

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 (232) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-NItGQmvR.js → activity-store--GBciH10.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-B8SBTGg3.js → agent-avatar-dicebear-C93T3FLD.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-Ba4XF0Im.js +68 -0
  6. package/dist/gateway/static/root/assets/{api-53ZXsuyr.js → api-HoIz_ija.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-BtmPXaqu.js → app-management-settings-panel-Bhl8Xluk.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-t7HoJyNN.js → appearance-settings-CywEN-kG.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-BZY09nuJ.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-D8o8CP9Y.js → archive-plugin-CteCVsBP.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-CsLXzas8.js → attachment-load-CbNE_EPd.js} +1 -1
  12. package/dist/gateway/static/root/assets/{automations-page-D-zgoAKz.js → automations-page-Bcqk6tOx.js} +1 -1
  13. package/dist/gateway/static/root/assets/automations-workspace-DtI6vsMf.js +6 -0
  14. package/dist/gateway/static/root/assets/{binary-plugins-B-MjP_22.js → binary-plugins-M_i3WS5M.js} +1 -1
  15. package/dist/gateway/static/root/assets/block-editor-eg9LEH2o.js +184 -0
  16. package/dist/gateway/static/root/assets/browser-settings-page-kKwbvzjj.js +8 -0
  17. package/dist/gateway/static/root/assets/{browser-workflow-inputs-Bi6RSSpD.js → browser-workflow-inputs-1UZ0vnIy.js} +1 -1
  18. package/dist/gateway/static/root/assets/{browser-workflows-page-EEFHUm1i.js → browser-workflows-page-D8ATQqZ5.js} +2 -2
  19. package/dist/gateway/static/root/assets/capabilities-settings-panel-CZRcis8B.js +7 -0
  20. package/dist/gateway/static/root/assets/{capability-presets-api-pC2fno7r.js → capability-presets-api-yNqTIuyd.js} +1 -1
  21. package/dist/gateway/static/root/assets/capability-presets-settings-panel-CgdRkwnk.js +3 -0
  22. package/dist/gateway/static/root/assets/{channel-recipient-api-B5Szgu_z.js → channel-recipient-api-krezGsKc.js} +1 -1
  23. package/dist/gateway/static/root/assets/channels-settings-DETCY0Ku.js +1 -0
  24. package/dist/gateway/static/root/assets/channels-status-swr-DMxB7slL.js +1 -0
  25. package/dist/gateway/static/root/assets/{chat-terminal-dock-DT-OFl4r.js → chat-terminal-dock-DQ5bB4Su.js} +2 -2
  26. package/dist/gateway/static/root/assets/connectors-page-D9D92u28.js +2 -0
  27. package/dist/gateway/static/root/assets/{cron-expression-MZ_AJqrm.js → cron-expression-CEwPkdhh.js} +1 -1
  28. package/dist/gateway/static/root/assets/{date-picker-Bv9hZLuD.js → date-picker-DDAxTir8.js} +1 -1
  29. package/dist/gateway/static/root/assets/{dependency-picker-D-6oO_Vk.js → dependency-picker-CYVoSZwI.js} +1 -1
  30. package/dist/gateway/static/root/assets/{desktop-pet-CiI5caKj.js → desktop-pet-CrNKfW_c.js} +1 -1
  31. package/dist/gateway/static/root/assets/{desktop-pet-settings-DhkSOy4K.js → desktop-pet-settings-C06PaB46.js} +1 -1
  32. package/dist/gateway/static/root/assets/{directory-picker-path-field-C4s4Pz8I.js → directory-picker-path-field--D7laeLF.js} +1 -1
  33. package/dist/gateway/static/root/assets/dist-DaxPZj9e.js +1 -0
  34. package/dist/gateway/static/root/assets/{dist-DY4Af0fY.js → dist-J_ySZvz0.js} +1 -1
  35. package/dist/gateway/static/root/assets/{extension-debug-page-DE7J1-6W.js → extension-debug-page-Dn4Px9KQ.js} +1 -1
  36. package/dist/gateway/static/root/assets/{extension-page-DSnACD7E.js → extension-page-DzLf3fMQ.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-settings-page-Dk7k2Pok.js → extension-settings-page-DIchwtvB.js} +1 -1
  38. package/dist/gateway/static/root/assets/floating-ui.dom-B7uQOZZW.js +1 -0
  39. package/dist/gateway/static/root/assets/{gateway-config-api-IOA1OlHx.js → gateway-config-api-SmTGLhqK.js} +1 -1
  40. package/dist/gateway/static/root/assets/{gateway-config-swr-BzTbnu4O.js → gateway-config-swr-yUxMMVjK.js} +1 -1
  41. package/dist/gateway/static/root/assets/{gateway-settings-FX7QsgSQ.js → gateway-settings-yCDkOYzA.js} +1 -1
  42. package/dist/gateway/static/root/assets/{gateway-startup-retry-B2zpkNvi.js → gateway-startup-retry-9mrsL84j.js} +1 -1
  43. package/dist/gateway/static/root/assets/heartbeat-settings-CquIULHX.js +1 -0
  44. package/dist/gateway/static/root/assets/home-page-COSGBh4c.js +1 -0
  45. package/dist/gateway/static/root/assets/{image-generation-api-BXRjz-iY.js → image-generation-api-CgFAGDi8.js} +1 -1
  46. package/dist/gateway/static/root/assets/index-5XkDWH_Q.css +2 -0
  47. package/dist/gateway/static/root/assets/index-wnDPGRTi.js +74 -0
  48. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BjYiM4xq.js → keyboard-shortcuts-settings-DI2To7yl.js} +1 -1
  49. package/dist/gateway/static/root/assets/local-app-workbench-page-CAF5SZXv.js +2 -0
  50. package/dist/gateway/static/root/assets/{local-apps-page-7DHvwSgA.js → local-apps-page-CSEJsQ1F.js} +1 -1
  51. package/dist/gateway/static/root/assets/{locale-store-iKjqemWG.js → locale-store-CPylQjH9.js} +1 -1
  52. package/dist/gateway/static/root/assets/logs-page-D1KFPHL1.js +2 -0
  53. package/dist/gateway/static/root/assets/{management-settings-BSxppo9Z.js → management-settings-CgyaIyuk.js} +1 -1
  54. package/dist/gateway/static/root/assets/{markdown-split-D4oDjZtl.js → markdown-split-paG9qap9.js} +1 -1
  55. package/dist/gateway/static/root/assets/markdown-view-CUsob4Pz.js +69 -0
  56. package/dist/gateway/static/root/assets/{media-plugins-BPNTGROA.js → media-plugins-0Q-c_QIy.js} +1 -1
  57. package/dist/gateway/static/root/assets/messages-B82nsi9k.js +3 -0
  58. package/dist/gateway/static/root/assets/notes-api-CfKZYrBC.js +1 -0
  59. package/dist/gateway/static/root/assets/{notes-page-DU5tKPTq.js → notes-page-Bvb-ycX_.js} +1 -1
  60. package/dist/gateway/static/root/assets/notes-workbench-DelxGBof.js +4 -0
  61. package/dist/gateway/static/root/assets/oauth-authorization-window-Cdolr2wE.js +1 -0
  62. package/dist/gateway/static/root/assets/{popover-select-BzdnTy3h.js → popover-select-CccGri5Y.js} +1 -1
  63. package/dist/gateway/static/root/assets/{product-open-page-CPoQvD6P.js → product-open-page-DrvBwAns.js} +1 -1
  64. package/dist/gateway/static/root/assets/project-detail-page-C2EnYXEQ.js +1 -0
  65. package/dist/gateway/static/root/assets/projects-page-BiMPU6Oa.js +1 -0
  66. package/dist/gateway/static/root/assets/{registry-api-Cx3JxXSL.js → registry-api-B_5D8epF.js} +1 -1
  67. package/dist/gateway/static/root/assets/remote-access-hub-CpbNivVs.js +1 -0
  68. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-xqnWcvIv.js → runtime-tools-settings-panel-DKjdN4Lr.js} +1 -1
  69. package/dist/gateway/static/root/assets/{schema-form-CuIDPYu6.js → schema-form-DskqaNqC.js} +1 -1
  70. package/dist/gateway/static/root/assets/sessions-page-BxKtzsKI.js +1 -0
  71. package/dist/gateway/static/root/assets/settings-advanced-gate-BfBKMMUx.js +1 -0
  72. package/dist/gateway/static/root/assets/{settings-form-section-DkAv9ncE.js → settings-form-section-CZlMvHA9.js} +1 -1
  73. package/dist/gateway/static/root/assets/{settings-loading-skeleton-BJUQiEKb.js → settings-loading-skeleton-DS6lcAg9.js} +1 -1
  74. package/dist/gateway/static/root/assets/{settings-page-KHRbLwmw.js → settings-page-oE5J9tsV.js} +1 -1
  75. package/dist/gateway/static/root/assets/{setup-status-panel-C8jd20Db.js → setup-status-panel-C7oFCIqD.js} +1 -1
  76. package/dist/gateway/static/root/assets/share-preview-page-CPFAj8NE.js +2 -0
  77. package/dist/gateway/static/root/assets/shares-settings-B46DWZK4.js +1 -0
  78. package/dist/gateway/static/root/assets/skill-reload-api-CyKolUZ-.js +1 -0
  79. package/dist/gateway/static/root/assets/{skills-page-CKhR9J81.js → skills-page-Bh4gFi8e.js} +1 -1
  80. package/dist/gateway/static/root/assets/{skills-page.utils-AYX-e5sD.js → skills-page.utils-BQaUWP9z.js} +1 -1
  81. package/dist/gateway/static/root/assets/{system-settings-panel-IIF-K9ML.js → system-settings-panel-COAZoh_O.js} +1 -1
  82. package/dist/gateway/static/root/assets/task-detail-page-CXkhprf-.js +1 -0
  83. package/dist/gateway/static/root/assets/{theme-store-BOD2D0dJ.js → theme-store-BN-32Fz5.js} +1 -1
  84. package/dist/gateway/static/root/assets/{use-autosave-DcktF1j1.js → use-autosave-BAkToU15.js} +1 -1
  85. package/dist/gateway/static/root/assets/user-context-page-Br5wgARM.js +1 -0
  86. package/dist/gateway/static/root/assets/{voice-api-key-field-BMkNTHHE.js → voice-api-key-field-CC4z9-ux.js} +1 -1
  87. package/dist/gateway/static/root/assets/{work-discovery-overlay-DVoiXekA.js → work-discovery-overlay-dLVUYSSE.js} +1 -1
  88. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-Cw4LQjuM.js → workflow-run-setup-panel-BYU5-0KF.js} +2 -2
  89. package/dist/gateway/static/root/assets/workflows-page-CJ24a1HM.js +5 -0
  90. package/dist/gateway/static/root/index.html +15 -14
  91. package/dist/package.js +1 -1
  92. package/dist/src/agent/agent-instance-gateway.d.ts +3 -0
  93. package/dist/src/agent/agent-manager.d.ts +3 -0
  94. package/dist/src/agent/agent-manager.js +34 -28
  95. package/dist/src/agent/embedded/index.d.ts +1 -1
  96. package/dist/src/agent/embedded/index.js +2 -2
  97. package/dist/src/agent/embedded/model-runtime.js +1 -1
  98. package/dist/src/agent/embedded/run-for-session.js +102 -63
  99. package/dist/src/agent/embedded/run-turn.js +56 -28
  100. package/dist/src/agent/embedded/runs.d.ts +16 -29
  101. package/dist/src/agent/embedded/runs.js +44 -65
  102. package/dist/src/agent/embedded/types.d.ts +4 -0
  103. package/dist/src/agent/image/tool-model-config.js +1 -1
  104. package/dist/src/agent/ipc/inbox.js +1 -1
  105. package/dist/src/agent/memory/create-memory-manager.js +1 -1
  106. package/dist/src/agent/orchestration/agent-run-supervisor.d.ts +28 -0
  107. package/dist/src/agent/orchestration/agent-run-supervisor.js +49 -0
  108. package/dist/src/agent/orchestration/agent-turn-policy.d.ts +18 -0
  109. package/dist/src/agent/orchestration/agent-turn-policy.js +36 -0
  110. package/dist/src/agent/orchestration/llm-turn-retry.d.ts +5 -0
  111. package/dist/src/agent/orchestration/llm-turn-retry.js +34 -4
  112. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +10 -1
  113. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +25 -4
  114. package/dist/src/agent/service/async-queue.d.ts +16 -3
  115. package/dist/src/agent/service/async-queue.js +29 -11
  116. package/dist/src/agent/service/process-direct-streaming.js +27 -4
  117. package/dist/src/agent/service.js +3 -2
  118. package/dist/src/agent/session/session-inspector.js +1 -1
  119. package/dist/src/agent/skills/hub-lock.js +1 -1
  120. package/dist/src/agent/tasks/task-judge-service.js +3 -3
  121. package/dist/src/agent/tools/exec-command.d.ts +2 -0
  122. package/dist/src/agent/tools/exec-command.js +70 -7
  123. package/dist/src/agent-runtime/index.js +1 -1
  124. package/dist/src/agent-runtime/runtime-profile.js +1 -1
  125. package/dist/src/ai-assist/text-assist.js +1 -1
  126. package/dist/src/auth/credentials.js +2 -2
  127. package/dist/src/auth/oauth/index.js +3 -3
  128. package/dist/src/auth/oauth/registry.d.ts +2 -0
  129. package/dist/src/auth/oauth/registry.js +20 -12
  130. package/dist/src/auth/oauth/xopc-cloud.d.ts +1 -0
  131. package/dist/src/auth/oauth/xopc-cloud.js +32 -16
  132. package/dist/src/auth/profiles/store.js +1 -1
  133. package/dist/src/auth/sync-provider-auth.js +2 -2
  134. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  135. package/dist/src/channels/attachments/voice-stt-webchat.js +20 -3
  136. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  137. package/dist/src/cli/commands/models.js +1 -1
  138. package/dist/src/cli/commands/tunnel.js +4 -5
  139. package/dist/src/config/index.js +4 -4
  140. package/dist/src/config/loader.js +1 -1
  141. package/dist/src/config/models-json.js +1 -1
  142. package/dist/src/config/schema.d.ts +18 -0
  143. package/dist/src/config/schema.js +19 -1
  144. package/dist/src/connectors/learning-coordinator.js +3 -19
  145. package/dist/src/extensions/lockfile.js +1 -1
  146. package/dist/src/gateway/agents-admin.js +1 -1
  147. package/dist/src/gateway/file-path-classifier.js +1 -1
  148. package/dist/src/gateway/hono/app.js +2 -1
  149. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  150. package/dist/src/gateway/hono/oauth-async.d.ts +1 -0
  151. package/dist/src/gateway/hono/oauth-async.js +11 -4
  152. package/dist/src/gateway/hono/routes/config.js +10 -0
  153. package/dist/src/gateway/hono/routes/host-fs.js +40 -1
  154. package/dist/src/gateway/hono/routes/media.js +33 -1
  155. package/dist/src/gateway/hono/routes/models.js +1 -1
  156. package/dist/src/gateway/hono/routes/notes.js +162 -7
  157. package/dist/src/gateway/hono/routes/shares.js +141 -6
  158. package/dist/src/gateway/hono/routes/tunnel.js +47 -10
  159. package/dist/src/gateway/hono/routes/voice.d.ts +2 -6
  160. package/dist/src/gateway/hono/routes/voice.js +28 -5
  161. package/dist/src/gateway/service/config-coordinator.js +1 -1
  162. package/dist/src/gateway/service.js +3 -3
  163. package/dist/src/notes/service.d.ts +6 -2
  164. package/dist/src/notes/service.js +36 -4
  165. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  166. package/dist/src/providers/index.js +3 -3
  167. package/dist/src/providers/model-registry.js +1 -1
  168. package/dist/src/providers/register-runtime-providers.js +1 -1
  169. package/dist/src/share/note-share-service.d.ts +67 -0
  170. package/dist/src/share/note-share-service.js +283 -0
  171. package/dist/src/share/share-config.js +32 -3
  172. package/dist/src/share/share-landing.d.ts +5 -1
  173. package/dist/src/share/share-landing.js +28 -1
  174. package/dist/src/share/share-store.d.ts +34 -5
  175. package/dist/src/share/share-store.js +125 -19
  176. package/dist/src/share/share-thumbnail.js +4 -2
  177. package/dist/src/share/share-types.d.ts +52 -14
  178. package/dist/src/share/share-types.js +9 -0
  179. package/dist/src/share/share-zip.d.ts +2 -2
  180. package/dist/src/tui/tui-startup-resources.js +1 -1
  181. package/dist/src/tui/tui.js +1 -1
  182. package/dist/src/tunnel/env.d.ts +12 -6
  183. package/dist/src/tunnel/env.js +34 -26
  184. package/dist/src/tunnel/gateway-lifecycle.js +2 -13
  185. package/dist/src/tunnel/index.d.ts +1 -1
  186. package/dist/src/tunnel/index.js +2 -2
  187. package/dist/src/tunnel/tunnel-audit.d.ts +1 -1
  188. package/dist/src/tunnel/tunnel-config.d.ts +1 -1
  189. package/dist/src/tunnel/tunnel-config.js +9 -4
  190. package/dist/src/tunnel/tunnel-service.d.ts +1 -1
  191. package/dist/src/tunnel/tunnel-service.js +3 -0
  192. package/dist/src/tunnel/well-known.d.ts +1 -1
  193. package/dist/src/tunnel/well-known.js +1 -1
  194. package/dist/src/tunnel/xopc-cloud-registration.d.ts +9 -0
  195. package/dist/src/tunnel/xopc-cloud-registration.js +42 -0
  196. package/dist/src/voice/audio/normalize.d.ts +7 -0
  197. package/dist/src/voice/audio/normalize.js +30 -4
  198. package/dist/src/voice/local/runtime-worker.js +26 -6
  199. package/dist/src/workflows/service/workflow-run-service.js +1 -1
  200. package/package.json +8 -14
  201. package/dist/gateway/static/root/assets/agents-Brs2-m6t.js +0 -68
  202. package/dist/gateway/static/root/assets/apps-page-DJ_Dsb1j.js +0 -1
  203. package/dist/gateway/static/root/assets/automations-workspace-DehLRXfd.js +0 -6
  204. package/dist/gateway/static/root/assets/block-editor-i4CfDqLG.js +0 -184
  205. package/dist/gateway/static/root/assets/browser-settings-page-C3QIO4Ti.js +0 -8
  206. package/dist/gateway/static/root/assets/capabilities-settings-panel-BHhUp7FW.js +0 -7
  207. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DoClNVLz.js +0 -3
  208. package/dist/gateway/static/root/assets/channels-settings-Je-9a5ZC.js +0 -1
  209. package/dist/gateway/static/root/assets/channels-status-swr-ByRbxC6z.js +0 -1
  210. package/dist/gateway/static/root/assets/connectors-page-D8z9Trw4.js +0 -2
  211. package/dist/gateway/static/root/assets/dist-C230izr8.js +0 -1
  212. package/dist/gateway/static/root/assets/heartbeat-settings-D5CGVyci.js +0 -1
  213. package/dist/gateway/static/root/assets/home-page-DzfZFZx5.js +0 -1
  214. package/dist/gateway/static/root/assets/index-Dcj5KJRg.css +0 -2
  215. package/dist/gateway/static/root/assets/index-EvA8c7fo.js +0 -74
  216. package/dist/gateway/static/root/assets/local-app-workbench-page-hPyTaVYU.js +0 -2
  217. package/dist/gateway/static/root/assets/logs-page-DW0xMKWP.js +0 -2
  218. package/dist/gateway/static/root/assets/markdown-view-DYkxMtJA.js +0 -69
  219. package/dist/gateway/static/root/assets/messages-kx5W39dO.js +0 -3
  220. package/dist/gateway/static/root/assets/notes-api-DJizatxh.js +0 -1
  221. package/dist/gateway/static/root/assets/notes-workbench-D669d98r.js +0 -4
  222. package/dist/gateway/static/root/assets/project-detail-page-CpsQ9PTA.js +0 -1
  223. package/dist/gateway/static/root/assets/projects-page-CitO1LMc.js +0 -1
  224. package/dist/gateway/static/root/assets/remote-access-hub-C9-bL4V6.js +0 -1
  225. package/dist/gateway/static/root/assets/sessions-page-CPSARi4y.js +0 -1
  226. package/dist/gateway/static/root/assets/settings-advanced-gate-BxT80Fmr.js +0 -1
  227. package/dist/gateway/static/root/assets/share-preview-page-WCVtJ3sX.js +0 -2
  228. package/dist/gateway/static/root/assets/shares-settings-DrQ_cOXh.js +0 -1
  229. package/dist/gateway/static/root/assets/skill-reload-api-C_4cG30x.js +0 -1
  230. package/dist/gateway/static/root/assets/task-detail-page-CYjsecEr.js +0 -1
  231. package/dist/gateway/static/root/assets/user-context-page-Cy1D2hkt.js +0 -1
  232. package/dist/gateway/static/root/assets/workflows-page-DKKH1_Yt.js +0 -5
@@ -1,6 +1,13 @@
1
1
  //#region src/tunnel/env.ts
2
2
  const DEV_REGISTRATION_SECRET = "dev-registration-secret";
3
- const TUNNEL_MASKED_SECRET_SENTINELS = ["***", "••••••••••••"];
3
+ const TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE = "TUNNEL_REGISTRATION_SECRET_REQUIRED";
4
+ var TunnelRegistrationSecretError = class extends Error {
5
+ code = TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE;
6
+ constructor(brokerUrl) {
7
+ super(tunnelRegistrationSecretRequiredMessage(brokerUrl));
8
+ this.name = "TunnelRegistrationSecretError";
9
+ }
10
+ };
4
11
  function brokerHostname(brokerUrl) {
5
12
  try {
6
13
  const normalized = brokerUrl.includes("://") ? brokerUrl : `https://${brokerUrl}`;
@@ -9,13 +16,12 @@ function brokerHostname(brokerUrl) {
9
16
  return null;
10
17
  }
11
18
  }
12
- /** True when the broker is the public frp.xopc.ai service (not local dev). */
13
- function isProductionTunnelBroker(brokerUrl) {
19
+ /** True only for brokers that are safe to use with the built-in development secret. */
20
+ function isLocalDevelopmentTunnelBroker(brokerUrl) {
14
21
  const host = brokerHostname(brokerUrl);
15
- if (!host) return true;
16
- if (host === "localhost" || host === "127.0.0.1" || host === "::1") return false;
17
- if (host.endsWith(".local")) return false;
18
- return host === "frp.xopc.ai";
22
+ if (!host) return false;
23
+ if (host === "localhost" || host === "127.0.0.1" || host === "::1") return true;
24
+ return host.endsWith(".local");
19
25
  }
20
26
  function maskTunnelSecretForWeb(secret) {
21
27
  const trimmed = secret.trim();
@@ -23,7 +29,6 @@ function maskTunnelSecretForWeb(secret) {
23
29
  return "•".repeat(trimmed.length);
24
30
  }
25
31
  function isMaskedTunnelSecretPatchValue(value) {
26
- if (TUNNEL_MASKED_SECRET_SENTINELS.includes(value)) return true;
27
32
  return /^•+$/.test(value);
28
33
  }
29
34
  function effectiveBrokerUrl(brokerUrl, env) {
@@ -37,37 +42,40 @@ function readTunnelRegistrationSecretFromConfigOnly(config) {
37
42
  return config?.tunnel?.registrationSecret?.trim() || null;
38
43
  }
39
44
  function getTunnelRegistrationSecretMeta(config, env = process.env, brokerUrl) {
40
- if (env.XOPC_TUNNEL_REGISTRATION_SECRET?.trim()) return {
41
- configured: true,
42
- source: "env"
43
- };
44
45
  if (config?.tunnel?.registrationSecret?.trim()) return {
45
46
  configured: true,
46
47
  source: "config"
47
48
  };
48
- if (isProductionTunnelBroker(effectiveBrokerUrl(brokerUrl ?? config?.tunnel?.brokerUrl, env))) return {
49
- configured: false,
50
- source: "missing"
51
- };
52
- return {
49
+ if (isLocalDevelopmentTunnelBroker(effectiveBrokerUrl(brokerUrl ?? config?.tunnel?.brokerUrl, env))) return {
53
50
  configured: true,
54
51
  source: "dev_default"
55
52
  };
53
+ return {
54
+ configured: false,
55
+ source: "missing"
56
+ };
57
+ }
58
+ function resolveOptionalTunnelRegistrationSecret(brokerUrl, configSecret) {
59
+ const fromConfig = configSecret?.trim();
60
+ if (fromConfig) return fromConfig;
61
+ return isLocalDevelopmentTunnelBroker(effectiveBrokerUrl(brokerUrl, process.env)) ? DEV_REGISTRATION_SECRET : void 0;
62
+ }
63
+ function tunnelRegistrationSecretRequiredMessage(brokerUrl) {
64
+ const effectiveUrl = effectiveBrokerUrl(brokerUrl, process.env);
65
+ const host = brokerHostname(effectiveUrl);
66
+ return `Tunnel registration secret is required for ${host === "frp.xopc.ai" ? "the XOPC public broker (frp.xopc.ai)" : `broker ${host ?? effectiveUrl}`}. Authorize XOPC from Remote access → Public internet, or create a Tunnel Registration Key at https://console.xopc.ai/access/client and save it there manually.`;
56
67
  }
57
68
  /**
58
69
  * Registration secret for Tunnel Broker register API.
59
- * Priority: env `XOPC_TUNNEL_REGISTRATION_SECRET` → `tunnel.registrationSecret` in config → dev default (non-production brokers only).
70
+ * Resolve `tunnel.registrationSecret` from config, with a development default for local brokers.
60
71
  */
61
- function resolveTunnelRegistrationSecret(env = process.env, brokerUrl, configSecret) {
62
- const fromEnv = env.XOPC_TUNNEL_REGISTRATION_SECRET?.trim();
63
- if (fromEnv) return fromEnv;
64
- const fromConfig = configSecret?.trim();
65
- if (fromConfig) return fromConfig;
66
- if (isProductionTunnelBroker(effectiveBrokerUrl(brokerUrl, env))) throw new Error("Tunnel registration secret is required for the production broker (frp.xopc.ai). Create a Tunnel Registration Key at https://console.xopc.ai/keys/tunnel and save it under Remote access → Public internet.");
67
- return DEV_REGISTRATION_SECRET;
72
+ function resolveTunnelRegistrationSecret(brokerUrl, configSecret) {
73
+ const secret = resolveOptionalTunnelRegistrationSecret(brokerUrl, configSecret);
74
+ if (secret) return secret;
75
+ throw new TunnelRegistrationSecretError(brokerUrl);
68
76
  }
69
77
  function resolveTunnelBrokerUrl(configBrokerUrl, env = process.env) {
70
78
  return env.XOPC_TUNNEL_BROKER_URL ?? configBrokerUrl ?? "https://frp.xopc.ai/api";
71
79
  }
72
80
  //#endregion
73
- export { TUNNEL_MASKED_SECRET_SENTINELS, getTunnelRegistrationSecretMeta, isMaskedTunnelSecretPatchValue, isProductionTunnelBroker, maskTunnelSecretForWeb, readTunnelRegistrationSecretFromConfigOnly, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret };
81
+ export { TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, TunnelRegistrationSecretError, getTunnelRegistrationSecretMeta, isLocalDevelopmentTunnelBroker, isMaskedTunnelSecretPatchValue, maskTunnelSecretForWeb, readTunnelRegistrationSecretFromConfigOnly, resolveOptionalTunnelRegistrationSecret, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret, tunnelRegistrationSecretRequiredMessage };
@@ -2,7 +2,7 @@ import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { resolveGatewayEffectiveHost } from "../config/gateway-bind.js";
4
4
  import { hasValidTunnelConsent } from "./consent.js";
5
- import { resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret } from "./env.js";
5
+ import { resolveOptionalTunnelRegistrationSecret, resolveTunnelBrokerUrl } from "./env.js";
6
6
  import { getTunnelService } from "./tunnel-service.js";
7
7
  import { resolveFrpSubdomainHost } from "./frp-subdomain-host.js";
8
8
  import { fetchTunnelWellKnown } from "./well-known.js";
@@ -25,20 +25,9 @@ function wireTunnelEventsToGateway(service) {
25
25
  tunnel.on("tunnel:progress", publish);
26
26
  }
27
27
  function applyTunnelServiceFromGatewayConfig(config, brokerUrl) {
28
- let registrationSecret;
29
- try {
30
- registrationSecret = resolveTunnelRegistrationSecret(process.env, brokerUrl, config.tunnel?.registrationSecret);
31
- } catch (err) {
32
- const em = err instanceof Error ? err.message : String(err);
33
- log.warn({
34
- phase: "tunnel_configure",
35
- errorMessage: em
36
- }, em);
37
- throw err;
38
- }
39
28
  getTunnelService().configure({
40
29
  brokerUrl,
41
- registrationSecret,
30
+ registrationSecret: resolveOptionalTunnelRegistrationSecret(brokerUrl, config.tunnel?.registrationSecret),
42
31
  autoStart: config.tunnel?.autoStart ?? false,
43
32
  gatewayHost: resolveGatewayEffectiveHost(config),
44
33
  frpSubdomainHost: resolveFrpSubdomainHost(brokerUrl)
@@ -6,7 +6,7 @@ export type { EnsureFrpcBinaryOptions, FrpcDownloadProgress } from './frpc-binar
6
6
  export { configureTunnelFromGatewayConfig, maybeAutoStartTunnelFromConfig, wireTunnelEventsToGateway, } from './gateway-lifecycle.js';
7
7
  export { fetchTunnelWellKnown, clearTunnelWellKnownCache } from './well-known.js';
8
8
  export type { TunnelWellKnownConfig, TunnelWellKnownTransport } from './well-known.js';
9
- export { getTunnelRegistrationSecretMeta, isProductionTunnelBroker, isMaskedTunnelSecretPatchValue, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret, } from './env.js';
9
+ export { getTunnelRegistrationSecretMeta, isLocalDevelopmentTunnelBroker, isMaskedTunnelSecretPatchValue, resolveTunnelBrokerUrl, resolveOptionalTunnelRegistrationSecret, resolveTunnelRegistrationSecret, TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, tunnelRegistrationSecretRequiredMessage, TunnelRegistrationSecretError, } from './env.js';
10
10
  export type { TunnelRegistrationSecretMeta, TunnelRegistrationSecretSource } from './env.js';
11
11
  export { logTunnelAudit } from './tunnel-audit.js';
12
12
  export type { TunnelAuditEvent } from './tunnel-audit.js';
@@ -1,7 +1,7 @@
1
1
  import { loadTunnelState, resolveTunnelStatePath, saveTunnelState } from "./tunnel-state.js";
2
2
  import { buildMobileConnectQrPayload, resolveLanGatewayUrl } from "./tunnel-qr.js";
3
3
  import { CURRENT_TUNNEL_CONSENT_VERSION, TUNNEL_CONSENT_REQUIRED_CODE, TUNNEL_RISK_SUMMARY_LINES, TunnelConsentError, assertTunnelMayStart, getTunnelConsentState, hasValidTunnelConsent } from "./consent.js";
4
- import { getTunnelRegistrationSecretMeta, isMaskedTunnelSecretPatchValue, isProductionTunnelBroker, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret } from "./env.js";
4
+ import { TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, TunnelRegistrationSecretError, getTunnelRegistrationSecretMeta, isLocalDevelopmentTunnelBroker, isMaskedTunnelSecretPatchValue, resolveOptionalTunnelRegistrationSecret, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret, tunnelRegistrationSecretRequiredMessage } from "./env.js";
5
5
  import { TunnelBrokerClient, resolveBrokerApiBase } from "./broker-client.js";
6
6
  import { FRPC_VERSION, clearFrpcPathForProcess, ensureFrpcBinary, publishFrpcPathForProcess } from "./frpc-binary.js";
7
7
  import { consumePairingSecret, createPairingSecret, resetPairingSessionsForTests } from "./pairing.js";
@@ -12,4 +12,4 @@ import { clearTunnelWellKnownCache, fetchTunnelWellKnown } from "./well-known.js
12
12
  import { configureTunnelFromGatewayConfig, maybeAutoStartTunnelFromConfig, wireTunnelEventsToGateway } from "./gateway-lifecycle.js";
13
13
  import { applyTunnelConsentToConfig, mergeTunnelConfigPatch, sanitizeTunnelConfig, setTunnelEnabledInConfig } from "./tunnel-config.js";
14
14
  import { consumeTunnelMutationLimit, resetTunnelMutationLimitsForTests } from "./tunnel-rate-limit.js";
15
- export { CURRENT_TUNNEL_CONSENT_VERSION, FRPC_VERSION, TUNNEL_CONSENT_REQUIRED_CODE, TUNNEL_RISK_SUMMARY_LINES, TunnelBrokerClient, TunnelConsentError, TunnelService, applyTunnelConsentToConfig, assertTunnelMayStart, buildMobileConnectQrPayload, clearFrpcPathForProcess, clearTunnelWellKnownCache, configureTunnelFromGatewayConfig, consumePairingSecret, consumeTunnelMutationLimit, createPairingSecret, ensureFrpcBinary, fetchTunnelWellKnown, getTunnelConsentState, getTunnelRegistrationSecretMeta, getTunnelService, hasValidTunnelConsent, hashGatewayToken, isMaskedTunnelSecretPatchValue, isProductionTunnelBroker, loadTunnelState, logTunnelAudit, maybeAutoStartTunnelFromConfig, mergeTunnelConfigPatch, publishFrpcPathForProcess, resetPairingSessionsForTests, resetTunnelMutationLimitsForTests, resolveBrokerApiBase, resolveFrpSubdomainHost, resolveLanGatewayUrl, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret, resolveTunnelStatePath, sanitizeTunnelConfig, saveTunnelState, setTunnelEnabledInConfig, wireTunnelEventsToGateway };
15
+ export { CURRENT_TUNNEL_CONSENT_VERSION, FRPC_VERSION, TUNNEL_CONSENT_REQUIRED_CODE, TUNNEL_REGISTRATION_SECRET_REQUIRED_CODE, TUNNEL_RISK_SUMMARY_LINES, TunnelBrokerClient, TunnelConsentError, TunnelRegistrationSecretError, TunnelService, applyTunnelConsentToConfig, assertTunnelMayStart, buildMobileConnectQrPayload, clearFrpcPathForProcess, clearTunnelWellKnownCache, configureTunnelFromGatewayConfig, consumePairingSecret, consumeTunnelMutationLimit, createPairingSecret, ensureFrpcBinary, fetchTunnelWellKnown, getTunnelConsentState, getTunnelRegistrationSecretMeta, getTunnelService, hasValidTunnelConsent, hashGatewayToken, isLocalDevelopmentTunnelBroker, isMaskedTunnelSecretPatchValue, loadTunnelState, logTunnelAudit, maybeAutoStartTunnelFromConfig, mergeTunnelConfigPatch, publishFrpcPathForProcess, resetPairingSessionsForTests, resetTunnelMutationLimitsForTests, resolveBrokerApiBase, resolveFrpSubdomainHost, resolveLanGatewayUrl, resolveOptionalTunnelRegistrationSecret, resolveTunnelBrokerUrl, resolveTunnelRegistrationSecret, resolveTunnelStatePath, sanitizeTunnelConfig, saveTunnelState, setTunnelEnabledInConfig, tunnelRegistrationSecretRequiredMessage, wireTunnelEventsToGateway };
@@ -1,2 +1,2 @@
1
- export type TunnelAuditEvent = 'tunnel.consent' | 'tunnel.start' | 'tunnel.stop' | 'tunnel.release' | 'tunnel.start_denied' | 'tunnel.pair' | 'tunnel.exchange_token' | 'tunnel.enable_lan_pairing';
1
+ export type TunnelAuditEvent = 'tunnel.consent' | 'tunnel.start' | 'tunnel.stop' | 'tunnel.release' | 'tunnel.start_denied' | 'tunnel.pair' | 'tunnel.exchange_token' | 'tunnel.registration_authorized' | 'tunnel.enable_lan_pairing';
2
2
  export declare function logTunnelAudit(event: TunnelAuditEvent, fields: Record<string, unknown>, message: string): void;
@@ -8,7 +8,7 @@ export declare function mergeTunnelConfigPatch(config: Config, patch: Record<str
8
8
  export declare function applyTunnelConsentToConfig(config: Config): void;
9
9
  export declare function setTunnelEnabledInConfig(config: Config, enabled: boolean): void;
10
10
  /**
11
- * Clear stale tunnel flags when consent is missing or outdated (Phase 2: config/runtime alignment).
11
+ * Clear stale tunnel flags when consent or broker credentials cannot support a start.
12
12
  * Returns true when `config.tunnel` was modified.
13
13
  */
14
14
  export declare function sanitizeTunnelConfig(config: Config): boolean;
@@ -1,6 +1,6 @@
1
1
  import { TunnelConfigSchema, TunnelConsentSchema, init_schema } from "../config/schema.js";
2
2
  import { assertTunnelAutoStartAllowed, buildTunnelConsentRecord, hasValidTunnelConsent } from "./consent.js";
3
- import { isMaskedTunnelSecretPatchValue } from "./env.js";
3
+ import { getTunnelRegistrationSecretMeta, isMaskedTunnelSecretPatchValue } from "./env.js";
4
4
  import { z } from "zod";
5
5
  //#region src/tunnel/tunnel-config.ts
6
6
  init_schema();
@@ -29,7 +29,11 @@ function mergeTunnelConfigPatch(config, patch) {
29
29
  ...config.tunnel ?? TunnelConfigSchema.parse({}),
30
30
  ...patchFields
31
31
  };
32
- if (parsed.data.registrationSecret === null) delete next.registrationSecret;
32
+ if (parsed.data.registrationSecret === null) {
33
+ delete next.registrationSecret;
34
+ next.enabled = false;
35
+ next.autoStart = false;
36
+ }
33
37
  if (parsed.data.autoStart === true) {
34
38
  const probeTunnel = TunnelConfigSchema.parse({
35
39
  ...next,
@@ -67,13 +71,14 @@ function setTunnelEnabledInConfig(config, enabled) {
67
71
  config.tunnel.enabled = enabled;
68
72
  }
69
73
  /**
70
- * Clear stale tunnel flags when consent is missing or outdated (Phase 2: config/runtime alignment).
74
+ * Clear stale tunnel flags when consent or broker credentials cannot support a start.
71
75
  * Returns true when `config.tunnel` was modified.
72
76
  */
73
77
  function sanitizeTunnelConfig(config) {
74
78
  const tunnel = config.tunnel;
75
79
  if (!tunnel) return false;
76
- if (hasValidTunnelConsent(config)) return false;
80
+ const brokerReady = getTunnelRegistrationSecretMeta(config).configured;
81
+ if (hasValidTunnelConsent(config) && brokerReady) return false;
77
82
  let changed = false;
78
83
  if (tunnel.enabled) {
79
84
  tunnel.enabled = false;
@@ -2,7 +2,7 @@ import { EventEmitter } from 'node:events';
2
2
  import type { TunnelQrPayload, TunnelStatus } from './tunnel-types.js';
3
3
  export type TunnelServiceConfig = {
4
4
  brokerUrl: string;
5
- registrationSecret: string;
5
+ registrationSecret?: string;
6
6
  autoStart: boolean;
7
7
  gatewayHost: string;
8
8
  frpSubdomainHost: string;
@@ -3,6 +3,7 @@ import { createLogger } from "../utils/logger/index.js";
3
3
  import { init_logger } from "../utils/logger.js";
4
4
  import { clearTunnelState, loadTunnelState, saveTunnelState, updateTunnelState } from "./tunnel-state.js";
5
5
  import { buildMobileConnectQrPayload, resolveLanGatewayUrl } from "./tunnel-qr.js";
6
+ import { TunnelRegistrationSecretError } from "./env.js";
6
7
  import { TunnelBrokerClient, resolveBrokerApiBase } from "./broker-client.js";
7
8
  import { clearFrpcPathForProcess, ensureFrpcBinary, publishFrpcPathForProcess } from "./frpc-binary.js";
8
9
  import { writeFrpcConfig } from "./frpc-config.js";
@@ -230,6 +231,7 @@ var TunnelService = class extends EventEmitter {
230
231
  phase: "tunnel_resume"
231
232
  }, "Persisted tunnel not resumable — registering again");
232
233
  }
234
+ if (!cfg.registrationSecret) throw new TunnelRegistrationSecretError(cfg.brokerUrl);
233
235
  return broker.register({
234
236
  brokerUrl: resolveBrokerApiBase(cfg.brokerUrl),
235
237
  registrationSecret: cfg.registrationSecret,
@@ -319,6 +321,7 @@ var TunnelService = class extends EventEmitter {
319
321
  const cfg = this.serviceConfig;
320
322
  if (!ctx || !cfg) return;
321
323
  try {
324
+ if (!cfg.registrationSecret) throw new TunnelRegistrationSecretError(cfg.brokerUrl);
322
325
  const registration = await broker.register({
323
326
  brokerUrl: resolveBrokerApiBase(cfg.brokerUrl),
324
327
  registrationSecret: cfg.registrationSecret,
@@ -15,7 +15,7 @@ export type TunnelWellKnownConfig = {
15
15
  };
16
16
  /**
17
17
  * Fetch `/.well-known/tunnel-config` (broker URL, frp endpoints, frpc version).
18
- * Registration secret is never published here — use `XOPC_TUNNEL_REGISTRATION_SECRET`.
18
+ * Registration secrets are never published here.
19
19
  */
20
20
  export declare function fetchTunnelWellKnown(brokerUrl: string): Promise<TunnelWellKnownConfig>;
21
21
  export declare function pingTunnelBroker(brokerUrl: string): Promise<boolean>;
@@ -10,7 +10,7 @@ function brokerOrigin(brokerUrl) {
10
10
  }
11
11
  /**
12
12
  * Fetch `/.well-known/tunnel-config` (broker URL, frp endpoints, frpc version).
13
- * Registration secret is never published here — use `XOPC_TUNNEL_REGISTRATION_SECRET`.
13
+ * Registration secrets are never published here.
14
14
  */
15
15
  async function fetchTunnelWellKnown(brokerUrl) {
16
16
  const origin = brokerOrigin(brokerUrl);
@@ -0,0 +1,9 @@
1
+ type ProvisionOptions = {
2
+ fetchImpl?: typeof fetch;
3
+ routerUrl?: string;
4
+ resolveAccessToken?: () => Promise<string | null>;
5
+ deviceName?: string;
6
+ };
7
+ /** Exchange a narrowly scoped XOPC OAuth grant for a tunnel registration key. */
8
+ export declare function provisionTunnelRegistrationKey(options?: ProvisionOptions): Promise<string>;
9
+ export {};
@@ -0,0 +1,42 @@
1
+ import { CredentialResolver, init_credentials } from "../auth/credentials.js";
2
+ import { init_xopc_cloud_config, resolveXopcModelRouterUrl } from "../providers/xopc-cloud-config.js";
3
+ import { hostname } from "node:os";
4
+ import { z } from "zod";
5
+ //#region src/tunnel/xopc-cloud-registration.ts
6
+ init_credentials();
7
+ init_xopc_cloud_config();
8
+ const registrationResponseSchema = z.object({ key: z.string().min(1) });
9
+ function responseError(body, status) {
10
+ if (body && typeof body === "object") {
11
+ const error = body.error;
12
+ if (error && typeof error === "object") {
13
+ const message = error.message;
14
+ if (typeof message === "string" && message.trim()) return message;
15
+ }
16
+ }
17
+ return `Tunnel registration failed (${status})`;
18
+ }
19
+ /** Exchange a narrowly scoped XOPC OAuth grant for a tunnel registration key. */
20
+ async function provisionTunnelRegistrationKey(options = {}) {
21
+ const accessToken = await (options.resolveAccessToken ?? (() => new CredentialResolver().resolveApiKey("xopc-tunnel")))();
22
+ if (!accessToken) throw new Error("Authorize XOPC Public Tunnel before creating a registration key");
23
+ const routerUrl = resolveXopcModelRouterUrl(options.routerUrl);
24
+ const deviceName = options.deviceName?.trim() || hostname() || "XOPC gateway";
25
+ const response = await (options.fetchImpl ?? fetch)(`${routerUrl}/tunnel/registration-key`, {
26
+ method: "POST",
27
+ headers: {
28
+ authorization: `Bearer ${accessToken}`,
29
+ accept: "application/json",
30
+ "content-type": "application/json"
31
+ },
32
+ body: JSON.stringify({ name: deviceName.slice(0, 64) }),
33
+ signal: AbortSignal.timeout(3e4)
34
+ });
35
+ const body = await response.json().catch(() => null);
36
+ if (!response.ok) throw new Error(responseError(body, response.status));
37
+ const parsed = registrationResponseSchema.safeParse(body);
38
+ if (!parsed.success) throw new Error("Tunnel registration returned an invalid response");
39
+ return parsed.data.key;
40
+ }
41
+ //#endregion
42
+ export { provisionTunnelRegistrationKey };
@@ -4,6 +4,13 @@ export declare class AudioNormalizationError extends Error {
4
4
  readonly code = "unsupported_audio_codec";
5
5
  constructor(message: string, options?: ErrorOptions);
6
6
  }
7
+ export interface AudioDecoderStatus {
8
+ available: boolean;
9
+ command: string;
10
+ error?: string;
11
+ }
12
+ /** Reports whether compressed browser/mobile recordings can be decoded on this host. */
13
+ export declare function getAudioDecoderStatus(): AudioDecoderStatus;
7
14
  export declare function detectAudioFormat(buffer: Buffer): AudioFormat;
8
15
  export declare function decodeAudioToMonoFloat32(input: {
9
16
  buffer: Buffer;
@@ -1,7 +1,7 @@
1
1
  import { UnsupportedWavEncodingError, decodeWavToMonoFloat32 } from "../local/wav.js";
2
2
  import { tmpdir } from "node:os";
3
3
  import { join } from "node:path";
4
- import { spawn } from "node:child_process";
4
+ import { spawn, spawnSync } from "node:child_process";
5
5
  import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
6
6
  //#region src/voice/audio/normalize.ts
7
7
  const TARGET_SAMPLE_RATE = 16e3;
@@ -15,6 +15,31 @@ var AudioNormalizationError = class extends Error {
15
15
  this.name = "AudioNormalizationError";
16
16
  }
17
17
  };
18
+ function ffmpegCommand() {
19
+ return process.env.XOPC_FFMPEG_PATH?.trim() || "ffmpeg";
20
+ }
21
+ /** Reports whether compressed browser/mobile recordings can be decoded on this host. */
22
+ function getAudioDecoderStatus() {
23
+ const command = ffmpegCommand();
24
+ const result = spawnSync(command, ["-version"], {
25
+ encoding: "utf8",
26
+ stdio: [
27
+ "ignore",
28
+ "ignore",
29
+ "pipe"
30
+ ],
31
+ timeout: 5e3
32
+ });
33
+ if (!result.error && result.status === 0) return {
34
+ available: true,
35
+ command
36
+ };
37
+ return {
38
+ available: false,
39
+ command,
40
+ error: `Audio decoder is unavailable (${command}): ${result.error instanceof Error ? result.error.message : result.stderr?.trim() || `decoder exited with status ${String(result.status)}`}. Install ffmpeg or set XOPC_FFMPEG_PATH.`
41
+ };
42
+ }
18
43
  function detectAudioFormat(buffer) {
19
44
  if (buffer.length >= 12 && buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WAVE") return "wav";
20
45
  if (buffer.length >= 4 && buffer.subarray(0, 4).equals(Buffer.from([
@@ -32,7 +57,8 @@ function detectAudioFormat(buffer) {
32
57
  function runFfmpeg(args, options = {}) {
33
58
  options.signal?.throwIfAborted();
34
59
  return new Promise((resolve, reject) => {
35
- const child = spawn("ffmpeg", [
60
+ const command = ffmpegCommand();
61
+ const child = spawn(command, [
36
62
  "-hide_banner",
37
63
  "-loglevel",
38
64
  "error",
@@ -75,7 +101,7 @@ function runFfmpeg(args, options = {}) {
75
101
  errorBytes += bounded.length;
76
102
  });
77
103
  child.once("error", (error) => {
78
- finish(new AudioNormalizationError(error.code === "ENOENT" ? "Audio decoder is unavailable; install ffmpeg to transcribe this format" : `Audio decoder failed to start: ${error.message}`, { cause: error }));
104
+ finish(new AudioNormalizationError(error.code === "ENOENT" ? `Audio decoder is unavailable (${command}); install ffmpeg or set XOPC_FFMPEG_PATH to transcribe this format` : `Audio decoder failed to start: ${error.message}`, { cause: error }));
79
105
  });
80
106
  child.once("close", (code) => {
81
107
  if (settled) return;
@@ -172,4 +198,4 @@ async function forEachNormalizedAudioSegment(input, consume) {
172
198
  }
173
199
  }
174
200
  //#endregion
175
- export { AudioNormalizationError, decodeAudioToMonoFloat32, detectAudioFormat, forEachNormalizedAudioSegment };
201
+ export { AudioNormalizationError, decodeAudioToMonoFloat32, detectAudioFormat, forEachNormalizedAudioSegment, getAudioDecoderStatus };
@@ -7,6 +7,18 @@ import { createInterface } from "node:readline";
7
7
  //#region src/voice/local/runtime-worker.ts
8
8
  const pipelines = /* @__PURE__ */ new Map();
9
9
  const sherpaRecognizers = /* @__PURE__ */ new Map();
10
+ async function assertLocalVoiceEngineAvailable(modelId) {
11
+ const model = getLocalVoiceModel(modelId);
12
+ if (model.engine === "sherpa-onnx") {
13
+ const imported = await import("sherpa-onnx-node").catch((cause) => {
14
+ throw new Error("The sherpa-onnx local voice engine is not installed. Install sherpa-onnx-node@1.13.4 alongside @xopcai/xopc.", { cause });
15
+ });
16
+ if (typeof (imported.default ?? imported).OfflineRecognizer !== "function") throw new Error("The sherpa-onnx local voice engine did not expose OfflineRecognizer");
17
+ } else await import("@huggingface/transformers").catch((cause) => {
18
+ throw new Error("The Transformers.js local voice engine is not installed. Install @huggingface/transformers@3.8.1 alongside @xopcai/xopc.", { cause });
19
+ });
20
+ return model.engine;
21
+ }
10
22
  const networkAgent = new RetryAgent(new EnvHttpProxyAgent(), {
11
23
  maxRetries: 4,
12
24
  minTimeout: 500,
@@ -161,12 +173,20 @@ async function transcribeWithSherpa(modelId, audio, language) {
161
173
  }
162
174
  async function handle(request) {
163
175
  const params = request.params ?? {};
164
- if (request.method === "health") return {
165
- ok: true,
166
- protocolVersion: 2,
167
- engine: "transformers.js+sherpa-onnx",
168
- engines: ["transformers.js", "sherpa-onnx"]
169
- };
176
+ if (request.method === "health") {
177
+ const modelId = typeof params.modelId === "string" && params.modelId ? params.modelId : void 0;
178
+ const selectedEngine = modelId ? await assertLocalVoiceEngineAvailable(modelId) : void 0;
179
+ return {
180
+ ok: true,
181
+ protocolVersion: 2,
182
+ engine: "transformers.js+sherpa-onnx",
183
+ engines: ["transformers.js", "sherpa-onnx"],
184
+ ...modelId ? {
185
+ modelId,
186
+ selectedEngine
187
+ } : {}
188
+ };
189
+ }
170
190
  if (request.method === "model.install") {
171
191
  const modelId = String(params.modelId ?? "");
172
192
  const modelDir = resolve(resolveLocalVoiceModelDir(modelId));
@@ -1,6 +1,6 @@
1
1
  import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
2
- import { init_providers, resolveModel } from "../../providers/index.js";
3
2
  import { extractProfileAgentId, resolveEffectiveAgentProfileForSession } from "../../config/agent-profile.js";
3
+ import { init_providers, resolveModel } from "../../providers/index.js";
4
4
  import { TaskRepository } from "../../tasks/task-repository.js";
5
5
  import { TaskConversationRepository } from "../../tasks/task-conversation-repository.js";
6
6
  import { TaskRunRepository } from "../../tasks/task-run-repository.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xopcai/xopc",
3
- "version": "0.0.221",
3
+ "version": "0.0.223",
4
4
  "description": "Local-first personal AI assistant with reviewable user understanding, durable tasks, workflows, automations, and local or cloud models.",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -108,27 +108,22 @@
108
108
  "zod": "^4.4.3"
109
109
  },
110
110
  "peerDependencies": {
111
- "@huggingface/transformers": "3.8.1",
112
111
  "@larksuiteoapi/node-sdk": "^1.66.0",
113
- "playwright-core": "^1.60.0",
114
- "sherpa-onnx-node": "1.13.4"
112
+ "playwright-core": "^1.60.0"
115
113
  },
116
114
  "peerDependenciesMeta": {
117
- "@huggingface/transformers": {
118
- "optional": true
119
- },
120
115
  "@larksuiteoapi/node-sdk": {
121
116
  "optional": true
122
117
  },
123
118
  "playwright-core": {
124
119
  "optional": true
125
- },
126
- "sherpa-onnx-node": {
127
- "optional": true
128
120
  }
129
121
  },
130
- "devDependencies": {
122
+ "optionalDependencies": {
131
123
  "@huggingface/transformers": "3.8.1",
124
+ "sherpa-onnx-node": "1.13.4"
125
+ },
126
+ "devDependencies": {
132
127
  "@larksuiteoapi/node-sdk": "^1.66.0",
133
128
  "@resvg/resvg-js": "^2.6.2",
134
129
  "@tailwindcss/vite": "^4.3.0",
@@ -152,7 +147,6 @@
152
147
  "onnxruntime-common": "1.21.0",
153
148
  "node-pty": "1.1.0",
154
149
  "playwright-core": "^1.60.0",
155
- "sherpa-onnx-node": "1.13.4",
156
150
  "svgo": "4.0.2",
157
151
  "tailwindcss": "^4.3.0",
158
152
  "tsdown": "0.21.10",
@@ -165,8 +159,8 @@
165
159
  "vue": "^3.5.35",
166
160
  "@xopcai/endpoint-tools-protocol": "0.0.0",
167
161
  "@xopcai/gateway-contract": "0.0.0",
168
- "@xopcai/realtime-protocol": "0.0.0",
169
- "@xopcai/realtime-client": "0.0.0"
162
+ "@xopcai/realtime-client": "0.0.0",
163
+ "@xopcai/realtime-protocol": "0.0.0"
170
164
  },
171
165
  "scripts": {
172
166
  "assets:brand": "node scripts/generate-brand-assets.mjs",