@xopcai/xopc 0.0.389 → 0.0.390

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 (302) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/src/plugin.js +1 -1
  3. package/dist/extensions/telegram/xopc.extension.json +1 -1
  4. package/dist/gateway/static/root/assets/{activity-store-dvik6t7Z.js → activity-store-DGMAYkw8.js} +1 -1
  5. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-bWMd5TDC.js → agent-avatar-dicebear-B4AsZzJR.js} +1 -1
  6. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-9NUY1sO3.js +1 -0
  7. package/dist/gateway/static/root/assets/agent-plugin-dialog-CNc6ntzP.js +1 -0
  8. package/dist/gateway/static/root/assets/agents-QNY9Vkrf.js +77 -0
  9. package/dist/gateway/static/root/assets/{agents-admin-api-BlNGyXpm.js → agents-admin-api-DxdaiFWn.js} +1 -1
  10. package/dist/gateway/static/root/assets/app-management-settings-panel-DHRfLkYD.js +1 -0
  11. package/dist/gateway/static/root/assets/{appearance-settings-CUAdx44v.js → appearance-settings-DtKSapgb.js} +1 -1
  12. package/dist/gateway/static/root/assets/{apps-page-D5zDB6gp.js → apps-page-B9kuRt_z.js} +1 -1
  13. package/dist/gateway/static/root/assets/{archive-plugin-Du6dJNcl.js → archive-plugin-AgySTXp_.js} +1 -1
  14. package/dist/gateway/static/root/assets/{automations-page-BO8Ig5Z4.js → automations-page-D1o7ZtUW.js} +1 -1
  15. package/dist/gateway/static/root/assets/automations-workspace-BJenz0P1.js +6 -0
  16. package/dist/gateway/static/root/assets/{binary-plugins-D7VJjwnr.js → binary-plugins-A1AXOsO6.js} +1 -1
  17. package/dist/gateway/static/root/assets/{block-editor-CaJlWTAn.js → block-editor-DUNLkLKF.js} +3 -3
  18. package/dist/gateway/static/root/assets/{browser-automation-inputs-iyMBJmQX.js → browser-automation-inputs-BvUC6OiW.js} +1 -1
  19. package/dist/gateway/static/root/assets/browser-automations-page-Dq2LlVMh.js +3 -0
  20. package/dist/gateway/static/root/assets/browser-settings-page-BOZ-1g51.js +3 -0
  21. package/dist/gateway/static/root/assets/{capabilities-MCKRMZDK.js → capabilities-BkCutK0V.js} +1 -1
  22. package/dist/gateway/static/root/assets/{capabilities-CEH1AnXc.js → capabilities-D61uSJPy.js} +1 -1
  23. package/dist/gateway/static/root/assets/capabilities-page-BDP6gzlv.js +2 -0
  24. package/dist/gateway/static/root/assets/capabilities-settings-panel-D6PL3Mfl.js +8 -0
  25. package/dist/gateway/static/root/assets/{capability-header-actions-BIA0Lwt5.js → capability-header-actions-D9uGs4Pw.js} +1 -1
  26. package/dist/gateway/static/root/assets/channels-xb7n8rwF.js +1 -0
  27. package/dist/gateway/static/root/assets/chat-terminal-dock-Dgn09YDs.js +36 -0
  28. package/dist/gateway/static/root/assets/{computer-settings-page-DvY3zgyY.js → computer-settings-page-C66P4ifT.js} +1 -1
  29. package/dist/gateway/static/root/assets/{connector-service-page-St2-Fj3y.js → connector-service-page-Dp35XdYf.js} +1 -1
  30. package/dist/gateway/static/root/assets/connectors-page-BHcRxYib.js +1 -0
  31. package/dist/gateway/static/root/assets/{date-picker-C0-D80ny.js → date-picker-CddHY982.js} +1 -1
  32. package/dist/gateway/static/root/assets/{dependency-picker-CNd7mwMU.js → dependency-picker-Ob6BZTqv.js} +1 -1
  33. package/dist/gateway/static/root/assets/{desktop-pet-C-2wDANn.js → desktop-pet-dmwCV-I4.js} +1 -1
  34. package/dist/gateway/static/root/assets/{desktop-pet-settings-DyhxLrdJ.js → desktop-pet-settings-B-TBkZAZ.js} +1 -1
  35. package/dist/gateway/static/root/assets/device-pairing-wizard-SWHwRgG3.js +1 -0
  36. package/dist/gateway/static/root/assets/{directory-picker-path-field-CNivVIPT.js → directory-picker-path-field-6VZa4fT1.js} +1 -1
  37. package/dist/gateway/static/root/assets/{extension-debug-page-CGYO3JPw.js → extension-debug-page-C0_njMva.js} +1 -1
  38. package/dist/gateway/static/root/assets/{extension-page-Dc51ALAg.js → extension-page-BeBLMFmW.js} +1 -1
  39. package/dist/gateway/static/root/assets/{extension-provider-BXPILdlw.js → extension-provider-DWI0KAKT.js} +1 -1
  40. package/dist/gateway/static/root/assets/extension-settings-page-BiLUwoKx.js +1 -0
  41. package/dist/gateway/static/root/assets/{gateway-config-swr-Bc2MytsF.js → gateway-config-swr-CmYcOqhb.js} +1 -1
  42. package/dist/gateway/static/root/assets/{gateway-realtime-BFK-vlME.js → gateway-realtime-Ci1Jst5g.js} +1 -1
  43. package/dist/gateway/static/root/assets/gateway-realtime-store-BMUiUQMk.js +1 -0
  44. package/dist/gateway/static/root/assets/gateway-settings-CzWT_v09.js +1 -0
  45. package/dist/gateway/static/root/assets/{gateway-startup-retry-BDPMttPx.js → gateway-startup-retry-C-BG5M7H.js} +1 -1
  46. package/dist/gateway/static/root/assets/{highlighted-body-KQOG7T2V-DAlRGMge.js → highlighted-body-KQOG7T2V-CsA7lCVc.js} +1 -1
  47. package/dist/gateway/static/root/assets/home-page-BeXJfxsg.js +2 -0
  48. package/dist/gateway/static/root/assets/image-generation-api-B_DqCwtV.js +1 -0
  49. package/dist/gateway/static/root/assets/imports-page-oAvAF9QS.js +1 -0
  50. package/dist/gateway/static/root/assets/index-W9ls-0Yd.css +1 -0
  51. package/dist/gateway/static/root/assets/index-xTL-vb51.js +260 -0
  52. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BYYC9Qmw.js → keyboard-shortcuts-settings--MjYe-Pb.js} +1 -1
  53. package/dist/gateway/static/root/assets/{local-app-workbench-page-BKZfn-MS.js → local-app-workbench-page-DL33dtd7.js} +1 -1
  54. package/dist/gateway/static/root/assets/{local-apps-page-BU8pUsVM.js → local-apps-page-DOa7mxjB.js} +1 -1
  55. package/dist/gateway/static/root/assets/{local-session-drafts-kFc_MAA9.js → local-session-drafts-DAtLkTPg.js} +1 -1
  56. package/dist/gateway/static/root/assets/{locale-store-Dr43FCty.js → locale-store-DMy9W6IG.js} +1 -1
  57. package/dist/gateway/static/root/assets/logs-page-C-GbTYsM.js +2 -0
  58. package/dist/gateway/static/root/assets/management-settings-C0TzuD3-.js +1 -0
  59. package/dist/gateway/static/root/assets/{markdown-view-CdKlPjJM.js → markdown-view-wyOHdXEg.js} +1 -1
  60. package/dist/gateway/static/root/assets/{media-plugins-DE9zt88i.js → media-plugins-Pb2zr_-L.js} +1 -1
  61. package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-BxyGGa4D.js +1 -0
  62. package/dist/gateway/static/root/assets/messages-O0BEUKnx.js +3 -0
  63. package/dist/gateway/static/root/assets/models-hub-cache-DdH1MODX.js +1 -0
  64. package/dist/gateway/static/root/assets/note-detail-panel-BFpEqskQ.js +5 -0
  65. package/dist/gateway/static/root/assets/{notes-api-BHouNL6-.js → notes-api-CYt2pGsm.js} +1 -1
  66. package/dist/gateway/static/root/assets/notes-page-h-qqFJ6Z.js +8 -0
  67. package/dist/gateway/static/root/assets/{page-context-capture-button-TQr8NnSc.js → page-context-capture-button-3uj5EcQC.js} +1 -1
  68. package/dist/gateway/static/root/assets/personal-page-BGyW6cdt.js +1 -0
  69. package/dist/gateway/static/root/assets/{product-open-page-BEl6syFp.js → product-open-page-hI13tPS1.js} +1 -1
  70. package/dist/gateway/static/root/assets/{product-preview-content-BxSHnbqz.js → product-preview-content-lT_in0-Q.js} +1 -1
  71. package/dist/gateway/static/root/assets/project-detail-page-Baj7fQ7N.js +1 -0
  72. package/dist/gateway/static/root/assets/projects-page-D7DyQFU3.js +1 -0
  73. package/dist/gateway/static/root/assets/{registry-api-BD5IgDOA.js → registry-api-CgQrCqL6.js} +1 -1
  74. package/dist/gateway/static/root/assets/remote-access-hub-DU_d93Tm.js +1 -0
  75. package/dist/gateway/static/root/assets/{runtime-tools-api-CjTRoQWT.js → runtime-tools-api-D12qVKeC.js} +1 -1
  76. package/dist/gateway/static/root/assets/runtime-tools-settings-panel-DOSPOfCU.js +1 -0
  77. package/dist/gateway/static/root/assets/scenes-page-swyUIF5A.js +2 -0
  78. package/dist/gateway/static/root/assets/{schema-form-ByG-R6N2.js → schema-form-CwOFgsyh.js} +1 -1
  79. package/dist/gateway/static/root/assets/{send-session-input-UAVStisp.js → send-session-input-nH8Cw0US.js} +1 -1
  80. package/dist/gateway/static/root/assets/{session-api-DvgEiNRS.js → session-api-B9HrwF-c.js} +1 -1
  81. package/dist/gateway/static/root/assets/{session-manager-BOb1tC_Z.js → session-manager-aXqXuqDX.js} +1 -1
  82. package/dist/gateway/static/root/assets/sessions-page-CI_PJmlX.js +1 -0
  83. package/dist/gateway/static/root/assets/settings-advanced-gate-DCVaeBLW.js +1 -0
  84. package/dist/gateway/static/root/assets/{settings-page-C1e9m23j.js → settings-page-BN70wE6R.js} +1 -1
  85. package/dist/gateway/static/root/assets/setup-status-panel-CHzcVRi-.js +1 -0
  86. package/dist/gateway/static/root/assets/{share-link-dialog-kE_6v5Dp.js → share-link-dialog-B1TDyhPt.js} +1 -1
  87. package/dist/gateway/static/root/assets/{share-preview-page-BT-_yR1h.js → share-preview-page-Br-eAZaP.js} +1 -1
  88. package/dist/gateway/static/root/assets/{shares-api-Dg_SqHQE.js → shares-api-Bl1ZBh5S.js} +1 -1
  89. package/dist/gateway/static/root/assets/shares-settings-jgCC_E4Y.js +1 -0
  90. package/dist/gateway/static/root/assets/{skill-api-BslUYMnX.js → skill-api-ClUw3SRC.js} +1 -1
  91. package/dist/gateway/static/root/assets/{skill-reload-api-BQ2rTOP1.js → skill-reload-api--1BfIPzb.js} +1 -1
  92. package/dist/gateway/static/root/assets/skills-page-C9oRZSuL.js +2 -0
  93. package/dist/gateway/static/root/assets/{system-settings-panel-GpbPfWwo.js → system-settings-panel-tWrqu7ai.js} +1 -1
  94. package/dist/gateway/static/root/assets/{task-detail-page-5B5ZVAu0.js → task-detail-page-BYRW7Rrr.js} +2 -2
  95. package/dist/gateway/static/root/assets/{task-expandable-content-DJrrEBB4.js → task-expandable-content-D2t4ahzR.js} +1 -1
  96. package/dist/gateway/static/root/assets/{theme-store-BKpYnggg.js → theme-store-BBLjE0fi.js} +1 -1
  97. package/dist/gateway/static/root/assets/{tracing-settings-page-D71w7dwV.js → tracing-settings-page-5ATxQH5c.js} +1 -1
  98. package/dist/gateway/static/root/assets/{tunnel-api-BV46CvS8.js → tunnel-api-CF1Qh4wU.js} +1 -1
  99. package/dist/gateway/static/root/assets/{usage-page-rMuwNpJi.js → usage-page-CuEHzXil.js} +1 -1
  100. package/dist/gateway/static/root/assets/{use-autosave-CzyqRffr.js → use-autosave-tY3keIbm.js} +1 -1
  101. package/dist/gateway/static/root/assets/{use-composer-attachments-Dviq-1f6.js → use-composer-attachments-CXsocZjt.js} +1 -1
  102. package/dist/gateway/static/root/assets/{use-file-preview-expanded-D5Kk7Hon.js → use-file-preview-expanded-CEsxnDnQ.js} +2 -2
  103. package/dist/gateway/static/root/assets/user-model-page-Dvt5TIZ6.js +2 -0
  104. package/dist/gateway/static/root/assets/{voice-api-key-field-DWtbmacd.js → voice-api-key-field-COTNLUU3.js} +1 -1
  105. package/dist/gateway/static/root/assets/{voice-config-api-DnIjZm_9.js → voice-config-api-DrxBjSja.js} +1 -1
  106. package/dist/gateway/static/root/assets/voice-transcribe-api-B47j0_xA.js +1 -0
  107. package/dist/gateway/static/root/assets/{work-discovery-overlay-ChEG71hz.js → work-discovery-overlay-PByMJrhV.js} +1 -1
  108. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-pdiRKLxP.js → workflow-run-setup-panel-CSUkMo63.js} +2 -2
  109. package/dist/gateway/static/root/assets/{workflows-page-D9LMxINt.js → workflows-page-BUDvFo3R.js} +3 -3
  110. package/dist/gateway/static/root/assets/{workspace-api-CZDvu7uA.js → workspace-api-Bmx1O449.js} +2 -2
  111. package/dist/gateway/static/root/assets/{xopc-cloud-account-card-CbUgg1SK.js → xopc-cloud-account-card-DVsAbvwP.js} +1 -1
  112. package/dist/gateway/static/root/index.html +28 -27
  113. package/dist/package.js +1 -1
  114. package/dist/packages/gateway-contract/src/discussions.js +3 -2
  115. package/dist/packages/gateway-contract/src/index.js +4 -0
  116. package/dist/packages/gateway-contract/src/provider-setup.js +6 -0
  117. package/dist/packages/gateway-contract/src/recording-outcome.js +6 -0
  118. package/dist/packages/image-providers/src/catalog.js +10 -0
  119. package/dist/packages/image-providers/src/execute.js +5 -1
  120. package/dist/packages/image-providers/src/validation.js +19 -1
  121. package/dist/src/agent/background-review/run-background-review.d.ts +0 -1
  122. package/dist/src/agent/background-review/run-background-review.js +10 -2
  123. package/dist/src/agent/embedded/run-for-session.js +26 -3
  124. package/dist/src/agent/embedded/run-turn.js +12 -0
  125. package/dist/src/agent/embedded/xopc-stream-bridge.js +3 -0
  126. package/dist/src/agent/image/generation/runtime.js +12 -14
  127. package/dist/src/agent/image/understanding/runtime.js +5 -0
  128. package/dist/src/agent/inbound/inbound-loop.d.ts +1 -15
  129. package/dist/src/agent/inbound/inbound-loop.js +5 -0
  130. package/dist/src/agent/mcp/connection-errors.d.ts +3 -0
  131. package/dist/src/agent/mcp/connection-errors.js +32 -0
  132. package/dist/src/agent/mcp/connection-setup.d.ts +2 -0
  133. package/dist/src/agent/mcp/connection-setup.js +38 -18
  134. package/dist/src/agent/memory/compaction.js +5 -1
  135. package/dist/src/agent/memory/idle-compaction-scheduler.js +3 -1
  136. package/dist/src/agent/models/manager.d.ts +1 -0
  137. package/dist/src/agent/models/manager.js +4 -2
  138. package/dist/src/agent/orchestration/agent-orchestrator.d.ts +1 -6
  139. package/dist/src/agent/orchestration/agent-orchestrator.js +5 -0
  140. package/dist/src/agent/service/process-direct-one-shot.js +5 -0
  141. package/dist/src/agent/service/process-direct-streaming.d.ts +1 -0
  142. package/dist/src/agent/service/process-direct-streaming.js +17 -1
  143. package/dist/src/agent/tools/image-generate-tool.js +5 -1
  144. package/dist/src/agent-config/resolver.js +5 -0
  145. package/dist/src/agent-config/schema.d.ts +26 -6
  146. package/dist/src/agent-config/schema.js +2 -0
  147. package/dist/src/ai-assist/text-assist.js +5 -16
  148. package/dist/src/automations/service/action-executor.js +3 -1
  149. package/dist/src/channels/index.js +1 -1
  150. package/dist/src/channels/manager.js +1 -1
  151. package/dist/src/cli/commands/channels.js +1 -1
  152. package/dist/src/cli/commands/connectors.js +1 -1
  153. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  154. package/dist/src/cli/commands/extension-marketplace.js +1 -1
  155. package/dist/src/cli/commands/models.js +1 -1
  156. package/dist/src/cli/commands/onboard.js +1 -1
  157. package/dist/src/config/agent-model-intents.js +4 -1
  158. package/dist/src/config/schema.d.ts +11 -5
  159. package/dist/src/config/schema.js +11 -3
  160. package/dist/src/discussions/analyzer.d.ts +2 -0
  161. package/dist/src/discussions/analyzer.js +38 -9
  162. package/dist/src/discussions/audio-clip.d.ts +3 -0
  163. package/dist/src/discussions/audio-clip.js +72 -0
  164. package/dist/src/discussions/live-worker.js +3 -1
  165. package/dist/src/discussions/long-review.d.ts +17 -0
  166. package/dist/src/discussions/long-review.js +278 -0
  167. package/dist/src/discussions/media-timeline.d.ts +21 -0
  168. package/dist/src/discussions/media-timeline.js +134 -0
  169. package/dist/src/discussions/organizer-worker.d.ts +4 -1
  170. package/dist/src/discussions/organizer-worker.js +17 -12
  171. package/dist/src/discussions/organizer.js +2 -0
  172. package/dist/src/discussions/processing.d.ts +43 -0
  173. package/dist/src/discussions/processing.js +125 -0
  174. package/dist/src/discussions/recording.js +202 -42
  175. package/dist/src/discussions/recordingJobs.js +2 -0
  176. package/dist/src/discussions/repository.js +4 -2
  177. package/dist/src/discussions/review.d.ts +8 -0
  178. package/dist/src/discussions/review.js +183 -0
  179. package/dist/src/discussions/sealer.d.ts +7 -1
  180. package/dist/src/discussions/sealer.js +163 -87
  181. package/dist/src/discussions/service.d.ts +7 -1
  182. package/dist/src/discussions/service.js +51 -15
  183. package/dist/src/discussions/types.d.ts +2 -1
  184. package/dist/src/extensions/sdk/index.js +1 -1
  185. package/dist/src/gateway/agent-builtin-tools.d.ts +1 -1
  186. package/dist/src/gateway/global-defaults-admin.d.ts +3 -1
  187. package/dist/src/gateway/global-defaults-admin.js +14 -2
  188. package/dist/src/gateway/hono/lib/config-payload.d.ts +11 -2
  189. package/dist/src/gateway/hono/lib/config-payload.js +2 -0
  190. package/dist/src/gateway/hono/lib/voice-settings-revisions.d.ts +5 -0
  191. package/dist/src/gateway/hono/lib/voice-settings-revisions.js +23 -0
  192. package/dist/src/gateway/hono/routes/config-patch/misc.js +8 -1
  193. package/dist/src/gateway/hono/routes/config.js +12 -0
  194. package/dist/src/gateway/hono/routes/discussions.js +63 -1
  195. package/dist/src/gateway/hono/routes/global-defaults.js +8 -1
  196. package/dist/src/gateway/hono/routes/models.js +12 -4
  197. package/dist/src/gateway/hono/routes/provider-setup.d.ts +3 -0
  198. package/dist/src/gateway/hono/routes/provider-setup.js +106 -0
  199. package/dist/src/gateway/hono/routes/status.js +2 -0
  200. package/dist/src/gateway/hono/routes/voice.js +23 -10
  201. package/dist/src/gateway/provider-model-setup.d.ts +4 -0
  202. package/dist/src/gateway/provider-model-setup.js +56 -0
  203. package/dist/src/gateway/service/config-coordinator.d.ts +2 -2
  204. package/dist/src/gateway/service.js +7 -7
  205. package/dist/src/home-intelligence/host.js +2 -2
  206. package/dist/src/media-shared/http/provider-http.d.ts +0 -6
  207. package/dist/src/media-shared/http/provider-http.js +5 -4
  208. package/dist/src/media-understanding/runner.d.ts +0 -28
  209. package/dist/src/media-understanding/runner.js +4 -0
  210. package/dist/src/platform/client.d.ts +1 -1
  211. package/dist/src/platform/contracts.d.ts +1 -1
  212. package/dist/src/providers/env-keys.js +3 -0
  213. package/dist/src/providers/extension-stream-bridge.js +3 -0
  214. package/dist/src/providers/index.js +15 -0
  215. package/dist/src/providers/model-call.d.ts +5 -2
  216. package/dist/src/providers/model-call.js +13 -2
  217. package/dist/src/providers/model-reference-auditor.js +1 -0
  218. package/dist/src/providers/model-response.js +1 -1
  219. package/dist/src/providers/model-usage.d.ts +15 -0
  220. package/dist/src/providers/model-usage.js +50 -0
  221. package/dist/src/providers/presentation.js +8 -0
  222. package/dist/src/providers/setup/engine.d.ts +37 -0
  223. package/dist/src/providers/setup/engine.js +177 -0
  224. package/dist/src/providers/setup/profiles.d.ts +4 -0
  225. package/dist/src/providers/setup/profiles.js +94 -0
  226. package/dist/src/providers/setup/voice.d.ts +23 -0
  227. package/dist/src/providers/setup/voice.js +234 -0
  228. package/dist/src/scenes/agentExecutor.d.ts +1 -0
  229. package/dist/src/scenes/agentExecutor.js +5 -0
  230. package/dist/src/storage/sqlite/migrations/253_discussion_processing_recovery.sql +20 -0
  231. package/dist/src/storage/sqlite/migrations/254_discussion_annotations.sql +17 -0
  232. package/dist/src/storage/sqlite/migrations/255_discussion_chapter_progress.sql +10 -0
  233. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  234. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  235. package/dist/src/storage/sqlite/schema.js +1 -1
  236. package/dist/src/usage/recorder.js +2 -1
  237. package/dist/src/usage/scenario-registry.js +128 -123
  238. package/dist/src/voice/dashscope/streaming-stt-session.js +3 -0
  239. package/dist/src/voice/dashscope/streaming-tts-stream.js +3 -0
  240. package/dist/src/voice/dictation.d.ts +12 -0
  241. package/dist/src/voice/dictation.js +22 -0
  242. package/dist/src/voice/metadata/builtin.js +54 -0
  243. package/dist/src/voice/platform-streams.js +3 -0
  244. package/dist/src/voice/realtime/omniEngine.js +3 -0
  245. package/dist/src/voice/realtime/omniRoute.js +3 -2
  246. package/dist/src/voice/realtime/runtime.js +4 -2
  247. package/dist/src/voice/stt/factory.js +9 -3
  248. package/dist/src/voice/stt/providers/bailian-plan-transcription.d.ts +2 -0
  249. package/dist/src/voice/stt/providers/bailian-plan-transcription.js +32 -0
  250. package/dist/src/voice/stt/providers/index.d.ts +1 -0
  251. package/dist/src/voice/stt/providers/index.js +2 -1
  252. package/dist/src/voice/token-plan.d.ts +8 -0
  253. package/dist/src/voice/token-plan.js +11 -0
  254. package/dist/src/voice/tts/factory.js +4 -1
  255. package/dist/src/voice/tts/providers/bailian-plan-speech.d.ts +2 -0
  256. package/dist/src/voice/tts/providers/bailian-plan-speech.js +84 -0
  257. package/dist/src/voice/tts/providers/index.d.ts +1 -0
  258. package/dist/src/voice/tts/providers/index.js +2 -1
  259. package/dist/src/voice/tts/speak-core.js +10 -1
  260. package/package.json +4 -4
  261. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-Ds0Ue2FB.js +0 -1
  262. package/dist/gateway/static/root/assets/agent-plugin-dialog-UunIxc1q.js +0 -1
  263. package/dist/gateway/static/root/assets/agents-BLRM4o-U.js +0 -77
  264. package/dist/gateway/static/root/assets/app-management-settings-panel-Dmy828Pe.js +0 -1
  265. package/dist/gateway/static/root/assets/automations-workspace-UcCneEs3.js +0 -6
  266. package/dist/gateway/static/root/assets/browser-automations-page-BjvQnYfO.js +0 -3
  267. package/dist/gateway/static/root/assets/browser-settings-page-D5ZghI28.js +0 -3
  268. package/dist/gateway/static/root/assets/capabilities-page-MC8qIrXo.js +0 -2
  269. package/dist/gateway/static/root/assets/capabilities-settings-panel-Blz3hEzp.js +0 -8
  270. package/dist/gateway/static/root/assets/channels-DSg_iCVC.js +0 -1
  271. package/dist/gateway/static/root/assets/chat-terminal-dock-C97SJEwd.js +0 -36
  272. package/dist/gateway/static/root/assets/connectors-page-DXk-GAsd.js +0 -1
  273. package/dist/gateway/static/root/assets/device-pairing-wizard-BeSuq__I.js +0 -1
  274. package/dist/gateway/static/root/assets/extension-settings-page-jYu6qpY2.js +0 -1
  275. package/dist/gateway/static/root/assets/fallback-models-CoyzvDo7.js +0 -1
  276. package/dist/gateway/static/root/assets/gateway-realtime-store-C3vH-Iup.js +0 -1
  277. package/dist/gateway/static/root/assets/gateway-settings-JlT1dlSj.js +0 -1
  278. package/dist/gateway/static/root/assets/home-page-DOVl8s-1.js +0 -2
  279. package/dist/gateway/static/root/assets/image-generation-api-BZBiYDmV.js +0 -1
  280. package/dist/gateway/static/root/assets/imports-page-DgdBTp_0.js +0 -1
  281. package/dist/gateway/static/root/assets/index-CmC8JkSM.js +0 -260
  282. package/dist/gateway/static/root/assets/index-Dgzc0oBu.css +0 -1
  283. package/dist/gateway/static/root/assets/logs-page-BSepiACn.js +0 -2
  284. package/dist/gateway/static/root/assets/management-settings-dE_ykPNY.js +0 -1
  285. package/dist/gateway/static/root/assets/mermaid-MCJ5UELQ-65SE6j-_.js +0 -1
  286. package/dist/gateway/static/root/assets/messages-DlTNQI92.js +0 -3
  287. package/dist/gateway/static/root/assets/models-hub-cache-C7FAsEe_.js +0 -1
  288. package/dist/gateway/static/root/assets/note-detail-panel-D9oqhdyk.js +0 -5
  289. package/dist/gateway/static/root/assets/notes-page-Bd4_7ph8.js +0 -8
  290. package/dist/gateway/static/root/assets/personal-page-BvWzq4Wp.js +0 -1
  291. package/dist/gateway/static/root/assets/project-detail-page-D-qkIXVT.js +0 -1
  292. package/dist/gateway/static/root/assets/projects-page-BQtw1Dfn.js +0 -1
  293. package/dist/gateway/static/root/assets/remote-access-hub-CYFNz0Jc.js +0 -1
  294. package/dist/gateway/static/root/assets/runtime-tools-settings-panel-Qq_NyYx_.js +0 -1
  295. package/dist/gateway/static/root/assets/scenes-page-hGr-ppYk.js +0 -2
  296. package/dist/gateway/static/root/assets/sessions-page-CpX1Rluw.js +0 -1
  297. package/dist/gateway/static/root/assets/settings-advanced-gate-D2s_MlxR.js +0 -1
  298. package/dist/gateway/static/root/assets/settings-loading-skeleton-BTqvr_Fw.js +0 -1
  299. package/dist/gateway/static/root/assets/setup-status-panel-BiHRYDHq.js +0 -1
  300. package/dist/gateway/static/root/assets/shares-settings-DfcAu08T.js +0 -1
  301. package/dist/gateway/static/root/assets/skills-page-CvKc5nBN.js +0 -2
  302. package/dist/gateway/static/root/assets/user-model-page-Dhqw3v_u.js +0 -2
@@ -0,0 +1,125 @@
1
+ import { getSqliteDatabase, init_transaction, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { getDiscussionCapture, updateDiscussionCapture } from "./repository.js";
3
+ import { randomUUID } from "node:crypto";
4
+ //#region src/discussions/processing.ts
5
+ init_transaction();
6
+ const LEASE_MS = 6e4;
7
+ const PROCESSING_MAX_ATTEMPTS = 3;
8
+ function getProcessingRun(id) {
9
+ return getSqliteDatabase().prepare("SELECT * FROM discussion_processing WHERE discussion_id=?").get(id);
10
+ }
11
+ /** A new original-audio generation leaves published transcript/summary revisions untouched. */
12
+ function createProcessingRun(capture, forceAudio = false) {
13
+ return runSqliteWriteTransaction((db) => {
14
+ db.prepare("DELETE FROM discussion_processing WHERE discussion_id=?").run(capture.id);
15
+ db.prepare(`INSERT INTO discussion_processing (discussion_id, run_id, audio_sha256, base_transcript_revision, force_audio)
16
+ VALUES (?, ?, ?, ?, ?)`).run(capture.id, randomUUID(), capture.audioSha256 ?? null, capture.transcriptRevision, forceAudio ? 1 : 0);
17
+ return getProcessingRun(capture.id);
18
+ });
19
+ }
20
+ function ensureProcessingRun(capture, alignSource = false) {
21
+ const run = getProcessingRun(capture.id);
22
+ return !run || alignSource && (run.audio_sha256 !== (capture.audioSha256 ?? null) || run.base_transcript_revision !== capture.transcriptRevision) ? createProcessingRun(capture, run?.force_audio === 1) : run;
23
+ }
24
+ function resetProcessingRetry(id) {
25
+ getSqliteDatabase().prepare("UPDATE discussion_processing SET retry_stage=NULL, attempt_count=0, next_attempt_at=NULL WHERE discussion_id=?").run(id);
26
+ }
27
+ function processingProgress(id) {
28
+ const run = getProcessingRun(id);
29
+ if (!run) return void 0;
30
+ const row = getSqliteDatabase().prepare(`SELECT COUNT(*) count, COALESCE(SUM(ended_at_ms-started_at_ms),0) duration
31
+ FROM discussion_transcription_checkpoints WHERE run_id=?`).get(run.run_id);
32
+ return {
33
+ fromAudio: run.force_audio === 1,
34
+ completedChunks: row.count,
35
+ transcribedMs: Math.round(row.duration),
36
+ attemptCount: run.attempt_count,
37
+ maxAttempts: 3,
38
+ ...run.next_attempt_at !== null ? { nextAttemptAt: run.next_attempt_at } : {}
39
+ };
40
+ }
41
+ function claimSealingCapture(owner, now) {
42
+ return runSqliteWriteTransaction((db) => {
43
+ const row = db.prepare(`SELECT d.id FROM discussion_captures d LEFT JOIN discussion_processing p ON p.discussion_id=d.id
44
+ WHERE d.status IN ('stopping','sealing') AND (p.next_attempt_at IS NULL OR p.next_attempt_at<=?)
45
+ AND (p.lease_until IS NULL OR p.lease_until<=?) ORDER BY d.updated_at, d.id LIMIT 1`).get(now, Date.now());
46
+ if (!row) return null;
47
+ const capture = getDiscussionCapture(row.id);
48
+ ensureProcessingRun(capture, true);
49
+ db.prepare("UPDATE discussion_processing SET lease_owner=?, lease_until=? WHERE discussion_id=?").run(owner, Date.now() + LEASE_MS, row.id);
50
+ return capture;
51
+ });
52
+ }
53
+ function renewSealingLease(id, owner) {
54
+ return getSqliteDatabase().prepare(`UPDATE discussion_processing SET lease_until=? WHERE discussion_id=? AND lease_owner=? AND lease_until>?`).run(Date.now() + LEASE_MS, id, owner, Date.now()).changes === 1;
55
+ }
56
+ function releaseSealingLease(id, owner) {
57
+ getSqliteDatabase().prepare("UPDATE discussion_processing SET lease_owner=NULL,lease_until=NULL WHERE discussion_id=? AND lease_owner=?").run(id, owner);
58
+ }
59
+ function assertProcessingRun(run, owner, signal) {
60
+ signal.throwIfAborted();
61
+ const current = getDiscussionCapture(run.discussion_id);
62
+ if (!current || current.status !== "sealing" || current.audioDeletedAt || current.audioSha256 !== (run.audio_sha256 ?? void 0) || current.transcriptRevision !== run.base_transcript_revision || getProcessingRun(current.id)?.run_id !== run.run_id || !renewSealingLease(current.id, owner)) throw new Error("Transcription processing lease or source revision changed");
63
+ }
64
+ function readCheckpoint(runId, start, end) {
65
+ const row = getSqliteDatabase().prepare("SELECT result_json FROM discussion_transcription_checkpoints WHERE run_id=? AND started_at_ms=? AND ended_at_ms=?").get(runId, start, end);
66
+ return row ? JSON.parse(row.result_json) : void 0;
67
+ }
68
+ function saveCheckpoint(runId, start, end, result) {
69
+ getSqliteDatabase().prepare(`INSERT INTO discussion_transcription_checkpoints (run_id,started_at_ms,ended_at_ms,result_json,created_at) VALUES (?,?,?,?,?)`).run(runId, start, end, JSON.stringify(result), Date.now());
70
+ }
71
+ function isTransientProcessingError(error, depth = 0) {
72
+ if (depth > 3) return false;
73
+ const item = error;
74
+ const status = item?.status ?? item?.statusCode;
75
+ if (status !== void 0) return [
76
+ 408,
77
+ 425,
78
+ 429
79
+ ].includes(status) || status >= 500;
80
+ const text = error instanceof Error ? `${error.name} ${error.message}` : String(error);
81
+ if (/not configured|no model configured|no api key|invalid.*key|unauthorized|forbidden|authentication|unsupported|invalid.*(audio|time range)|no text|audio.*missing|checksum mismatch|transcript.*missing|ENOENT|lease|revision changed/i.test(text)) return false;
82
+ if (item?.reasonCode === "not_configured" || item?.reasonCode === "unsupported_format") return false;
83
+ if (item?.reasonCode === "timeout" || item?.reasonCode === "provider_error") return true;
84
+ if (/timeout|timed out|rate.?limit|temporar|unavailable|fetch failed|network|connection error|ECONN|ETIMEDOUT|EAI_AGAIN|socket|overload|internal server|server error|try again|invalid discussion organization|invalid evidence|truncated|\b(408|429|5\d\d)\b|SyntaxError/i.test(text)) return true;
85
+ return item?.cause !== void 0 && item.cause !== error ? isTransientProcessingError(item.cause, depth + 1) : false;
86
+ }
87
+ /** Persist bounded retries instead of sleeping inside a worker and blocking other recordings. */
88
+ function failOrRetryProcessing(capture, stage, error) {
89
+ return runSqliteWriteTransaction((db) => {
90
+ const current = getDiscussionCapture(capture.id);
91
+ if (!current || current.status !== (stage === "organization" ? "organizing" : "sealing")) return null;
92
+ const run = ensureProcessingRun(current);
93
+ const attempts = (run.retry_stage === stage ? run.attempt_count : 0) + 1;
94
+ const retry = attempts < 3 && isTransientProcessingError(error);
95
+ const next = retry ? Date.now() + 5e3 * 4 ** (attempts - 1) : null;
96
+ db.prepare("UPDATE discussion_processing SET retry_stage=?,attempt_count=?,next_attempt_at=? WHERE discussion_id=?").run(stage, attempts, next, capture.id);
97
+ return updateDiscussionCapture(capture.id, {
98
+ status: retry ? current.status : "needs_attention",
99
+ failureStage: stage,
100
+ failureCode: retry ? "processing_retry_scheduled" : `${stage}_failed`,
101
+ failureMessage: (error instanceof Error ? error.message : String(error)).slice(0, 1e3)
102
+ }, [current.status]);
103
+ });
104
+ }
105
+ /** Race the signal as well as passing it downstream, so an uncooperative provider cannot hold the worker. */
106
+ async function withProcessingDeadline(work, parent, timeoutMs) {
107
+ const timeout = new AbortController();
108
+ const timer = setTimeout(() => timeout.abort(/* @__PURE__ */ new Error("Processing request timed out")), timeoutMs);
109
+ timer.unref();
110
+ const signal = AbortSignal.any([parent, timeout.signal]);
111
+ let abort;
112
+ try {
113
+ signal.throwIfAborted();
114
+ const aborted = new Promise((_resolve, reject) => {
115
+ abort = () => reject(signal.reason);
116
+ signal.addEventListener("abort", abort, { once: true });
117
+ });
118
+ return await Promise.race([work(signal), aborted]);
119
+ } finally {
120
+ clearTimeout(timer);
121
+ if (abort) signal.removeEventListener("abort", abort);
122
+ }
123
+ }
124
+ //#endregion
125
+ export { PROCESSING_MAX_ATTEMPTS, assertProcessingRun, claimSealingCapture, createProcessingRun, ensureProcessingRun, failOrRetryProcessing, getProcessingRun, isTransientProcessingError, processingProgress, readCheckpoint, releaseSealingLease, renewSealingLease, resetProcessingRetry, saveCheckpoint, withProcessingDeadline };
@@ -1,9 +1,10 @@
1
1
  import { getSqliteDatabase, init_transaction, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
- import { DISCUSSION_MAX_DURATION_MS } from "../../packages/gateway-contract/src/discussions.js";
2
+ import { DISCUSSION_MAX_SAVED_DURATION_MS } from "../../packages/gateway-contract/src/discussions.js";
3
3
  import { init_src } from "../../packages/gateway-contract/src/index.js";
4
4
  import { resolveNoteMediaDir } from "../notes/paths.js";
5
5
  import { DiscussionServiceError } from "./errors.js";
6
6
  import { getDiscussionCapture, updateDiscussionCapture } from "./repository.js";
7
+ import { pcmEpoch, probeMediaEpoch } from "./media-timeline.js";
7
8
  import { decodeWavToMonoFloat32 } from "../voice/audio/wav.js";
8
9
  import { forEachNormalizedAudioSegment } from "../voice/audio/normalize.js";
9
10
  import { join } from "node:path";
@@ -71,6 +72,20 @@ async function saveRecordingChunk(capture, sequence, sha256, body) {
71
72
  }
72
73
  function validateRecordingManifest(capture, input) {
73
74
  if (input.containerMode !== void 0 && (input.containerMode !== "independent_wav" || input.mimeType !== "audio/wav")) throw invalid("Unsupported recording container");
75
+ if (input.events !== void 0 && (!Array.isArray(input.events) || input.events.length > 1e3 || input.events.some((event) => !event || !Number.isSafeInteger(event.recordedAt) || event.recordedAt < 0 || !Number.isSafeInteger(event.savedMs) || event.savedMs < 0 || event.savedMs > 7205e3 || ![
76
+ "start",
77
+ "resume",
78
+ "interrupted",
79
+ "seal"
80
+ ].includes(event.kind)))) throw invalid("Invalid recording events");
81
+ if (input.epochs !== void 0) {
82
+ if (input.containerMode || !Array.isArray(input.epochs) || input.epochs.length < 2 || input.epochs.length > 100 || input.epochs.some((epoch, index) => !epoch || !Number.isSafeInteger(epoch.firstChunk) || (index === 0 ? epoch.firstChunk !== 0 : epoch.firstChunk <= input.epochs[index - 1].firstChunk) || epoch.firstChunk >= input.chunkCount || !Number.isSafeInteger(epoch.startedAt) || epoch.startedAt < 0 || epoch.previousEndedAt !== void 0 && (!Number.isSafeInteger(epoch.previousEndedAt) || epoch.previousEndedAt < 0) || typeof epoch.mimeType !== "string" || ![
83
+ "audio/webm",
84
+ "audio/mp4",
85
+ "audio/ogg",
86
+ "audio/wav"
87
+ ].includes(epoch.mimeType.split(";")[0]))) throw invalid("Invalid recording epochs");
88
+ }
74
89
  const mimeType = input.mimeType.split(";")[0];
75
90
  if (![
76
91
  "audio/wav",
@@ -89,48 +104,52 @@ async function completeRecording(capture, input, notes, assertLease, signal) {
89
104
  const target = join(directory(capture), `assembled-${randomUUID()}`);
90
105
  const file = await open(target, "w");
91
106
  const hash = createHash("sha256");
107
+ let epochs;
92
108
  try {
93
109
  try {
94
- if (input.containerMode === "independent_wav") {
95
- const samples = chunks.reduce((total, chunk) => total + (chunk.bytes - 44) / 2, 0);
96
- if (!Number.isSafeInteger(samples) || samples <= 0 || samples > 16e3 * 72e5 / 1e3) throw invalid("Invalid PCM recording duration");
97
- const header = recordingWavHeader(samples);
98
- hash.update(header);
99
- await file.writeFile(header);
100
- }
101
- for (const chunk of chunks) {
102
- const chunkHash = createHash("sha256");
103
- let bytes = 0;
104
- try {
105
- if (input.containerMode === "independent_wav") {
106
- signal.throwIfAborted();
107
- const buffers = [];
110
+ if (input.epochs) epochs = await assembleEpochs(capture, input, notes, file, signal, assertLease);
111
+ else {
112
+ if (input.containerMode === "independent_wav") {
113
+ const samples = chunks.reduce((total, chunk) => total + (chunk.bytes - 44) / 2, 0);
114
+ if (!Number.isSafeInteger(samples) || samples <= 0 || samples > 16e3 * 7205e3 / 1e3) throw invalid("Invalid PCM recording duration");
115
+ const header = recordingWavHeader(samples);
116
+ hash.update(header);
117
+ await file.writeFile(header);
118
+ }
119
+ for (const chunk of chunks) {
120
+ const chunkHash = createHash("sha256");
121
+ let bytes = 0;
122
+ try {
123
+ if (input.containerMode === "independent_wav") {
124
+ signal.throwIfAborted();
125
+ const buffers = [];
126
+ for await (const buffer of createReadStream(join(directory(capture), String(chunk.sequence)))) {
127
+ signal.throwIfAborted();
128
+ bytes += buffer.length;
129
+ if (bytes > 8388608 || bytes > chunk.bytes) throw invalid("Stored WAV chunk is corrupt");
130
+ buffers.push(buffer);
131
+ }
132
+ const data = Buffer.concat(buffers, bytes);
133
+ if (data.length !== chunk.bytes || data.length <= 44 || data.length > 8388608 || data.length % 2 !== 0 || !data.subarray(0, 44).equals(recordingWavHeader((data.length - 44) / 2)) || createHash("sha256").update(data).digest("hex") !== chunk.sha256) throw invalid("Stored WAV chunk is corrupt");
134
+ hash.update(data.subarray(44));
135
+ await file.writeFile(data.subarray(44));
136
+ continue;
137
+ }
108
138
  for await (const buffer of createReadStream(join(directory(capture), String(chunk.sequence)))) {
109
139
  signal.throwIfAborted();
140
+ hash.update(buffer);
141
+ chunkHash.update(buffer);
110
142
  bytes += buffer.length;
111
- if (bytes > 8388608 || bytes > chunk.bytes) throw invalid("Stored WAV chunk is corrupt");
112
- buffers.push(buffer);
143
+ await file.writeFile(buffer);
113
144
  }
114
- const data = Buffer.concat(buffers, bytes);
115
- if (data.length !== chunk.bytes || data.length <= 44 || data.length > 8388608 || data.length % 2 !== 0 || !data.subarray(0, 44).equals(recordingWavHeader((data.length - 44) / 2)) || createHash("sha256").update(data).digest("hex") !== chunk.sha256) throw invalid("Stored WAV chunk is corrupt");
116
- hash.update(data.subarray(44));
117
- await file.writeFile(data.subarray(44));
118
- continue;
119
- }
120
- for await (const buffer of createReadStream(join(directory(capture), String(chunk.sequence)))) {
121
- signal.throwIfAborted();
122
- hash.update(buffer);
123
- chunkHash.update(buffer);
124
- bytes += buffer.length;
125
- await file.writeFile(buffer);
126
- }
127
- if (bytes !== chunk.bytes || chunkHash.digest("hex") !== chunk.sha256) throw invalid("Stored recording chunk is corrupt");
128
- } catch (error) {
129
- if (!signal.aborted && (error instanceof DiscussionServiceError || error.code === "ENOENT")) {
130
- assertLease();
131
- getSqliteDatabase().prepare("DELETE FROM discussion_recording_chunks WHERE discussion_id=? AND sequence=?").run(capture.id, chunk.sequence);
145
+ if (bytes !== chunk.bytes || chunkHash.digest("hex") !== chunk.sha256) throw invalid("Stored recording chunk is corrupt");
146
+ } catch (error) {
147
+ if (!signal.aborted && (error instanceof DiscussionServiceError || error.code === "ENOENT")) {
148
+ assertLease();
149
+ getSqliteDatabase().prepare("DELETE FROM discussion_recording_chunks WHERE discussion_id=? AND sequence=?").run(capture.id, chunk.sequence);
150
+ }
151
+ throw error;
132
152
  }
133
- throw error;
134
153
  }
135
154
  }
136
155
  await file.sync();
@@ -141,7 +160,7 @@ async function completeRecording(capture, input, notes, assertLease, signal) {
141
160
  await forEachNormalizedAudioSegment({
142
161
  filePath: target,
143
162
  segmentSeconds: 30,
144
- maxDurationSeconds: DISCUSSION_MAX_DURATION_MS / 1e3,
163
+ maxDurationSeconds: DISCUSSION_MAX_SAVED_DURATION_MS / 1e3,
145
164
  signal: AbortSignal.any([signal, AbortSignal.timeout(18e4)])
146
165
  }, async (buffer) => {
147
166
  durationMs += decodeWavToMonoFloat32(buffer).durationSeconds * 1e3;
@@ -150,11 +169,37 @@ async function completeRecording(capture, input, notes, assertLease, signal) {
150
169
  const latest = getDiscussionCapture(capture.id);
151
170
  if (!latest || latest.status === "cancelled" || latest.audioDeletedAt) throw new DiscussionServiceError("conflict", "Recording was deleted or cancelled");
152
171
  assertLease();
153
- const audioSha256 = hash.digest("hex");
172
+ const audioSha256 = input.epochs ? await hashFile(target, signal) : hash.digest("hex");
173
+ const outputMime = input.epochs ? "audio/wav" : input.mimeType;
174
+ const timeline = {
175
+ primary: input.epochs || input.containerMode === "independent_wav" || outputMime.includes("wav") ? "pcm" : "encoded",
176
+ epochs: epochs ?? [outputMime.includes("wav") ? pcmEpoch(durationMs) : await probeMediaEpoch(target, durationMs, 0, signal)]
177
+ };
178
+ if (input.events?.length) {
179
+ const events = await notes.addAttachment(capture.noteId, {
180
+ name: `recording-events-${capture.id}.json`,
181
+ buffer: Buffer.from(JSON.stringify({
182
+ version: 1,
183
+ discussionId: capture.id,
184
+ events: input.events
185
+ })),
186
+ mimeType: "application/json",
187
+ retainWithoutReference: true
188
+ }, `meeting-events:${capture.id}`);
189
+ if (!events) throw new Error("Discussion note is missing");
190
+ const saved = await notes.getAttachmentPath(capture.noteId, events.id);
191
+ if (!saved) throw new Error("Saved recording events are missing");
192
+ const durable = await open(saved.filePath, "r");
193
+ try {
194
+ await durable.sync();
195
+ } finally {
196
+ await durable.close();
197
+ }
198
+ }
154
199
  const attachment = await notes.addAttachment(capture.noteId, {
155
- name: input.fileName.trim().slice(0, 200) || "meeting.webm",
200
+ name: input.epochs ? "meeting-resumed.wav" : input.fileName.trim().slice(0, 200) || "meeting.webm",
156
201
  buffer: { filePath: target },
157
- mimeType: input.mimeType,
202
+ mimeType: outputMime,
158
203
  duration: Math.round(durationMs / 1e3),
159
204
  retainWithoutReference: true
160
205
  }, `meeting-recording:${capture.id}`);
@@ -175,13 +220,14 @@ async function completeRecording(capture, input, notes, assertLease, signal) {
175
220
  }
176
221
  const updated = runSqliteWriteTransaction(() => {
177
222
  assertLease();
223
+ getSqliteDatabase().prepare("INSERT OR REPLACE INTO discussion_media_timeline (discussion_id,audio_sha256,timeline_json) VALUES (?,?,?)").run(capture.id, audioSha256, JSON.stringify(timeline));
178
224
  return updateDiscussionCapture(capture.id, {
179
225
  status: "stopping",
180
226
  audioAttachmentId: attachment.id,
181
- mimeType: input.mimeType,
227
+ mimeType: outputMime,
182
228
  audioSha256,
183
229
  durationMs: Math.round(durationMs),
184
- audioSizeBytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0)
230
+ audioSizeBytes: attachment.size
185
231
  }, [
186
232
  "recording",
187
233
  "stopping",
@@ -195,6 +241,120 @@ async function completeRecording(capture, input, notes, assertLease, signal) {
195
241
  await rm(target, { force: true });
196
242
  }
197
243
  }
244
+ async function hashFile(path, signal) {
245
+ const hash = createHash("sha256");
246
+ for await (const buffer of createReadStream(path)) {
247
+ signal.throwIfAborted();
248
+ hash.update(buffer);
249
+ }
250
+ return hash.digest("hex");
251
+ }
252
+ /** Independent recorder containers stay immutable; only decoded PCM is joined for playback/STT. */
253
+ async function assembleEpochs(capture, input, notes, output, signal, assertLease) {
254
+ const epochs = [];
255
+ const chunks = listRecordingChunks(capture.id);
256
+ const sources = [];
257
+ let samples = 0;
258
+ await output.writeFile(recordingWavHeader(0));
259
+ for (const [index, epoch] of input.epochs.entries()) {
260
+ const path = join(directory(capture), `epoch-${randomUUID()}`);
261
+ const source = await open(path, "wx");
262
+ try {
263
+ try {
264
+ const end = input.epochs[index + 1]?.firstChunk ?? chunks.length;
265
+ for (const chunk of chunks.slice(epoch.firstChunk, end)) try {
266
+ let bytes = 0;
267
+ const hash = createHash("sha256");
268
+ for await (const buffer of createReadStream(join(directory(capture), String(chunk.sequence)))) {
269
+ signal.throwIfAborted();
270
+ bytes += buffer.length;
271
+ hash.update(buffer);
272
+ await source.writeFile(buffer);
273
+ }
274
+ if (bytes !== chunk.bytes || hash.digest("hex") !== chunk.sha256) throw invalid("Stored recording chunk is corrupt");
275
+ } catch (error) {
276
+ if (!signal.aborted && (error instanceof DiscussionServiceError || error.code === "ENOENT")) {
277
+ assertLease();
278
+ getSqliteDatabase().prepare("DELETE FROM discussion_recording_chunks WHERE discussion_id=? AND sequence=?").run(capture.id, chunk.sequence);
279
+ }
280
+ throw error;
281
+ }
282
+ await source.sync();
283
+ } finally {
284
+ await source.close();
285
+ }
286
+ let epochSamples = 0;
287
+ await forEachNormalizedAudioSegment({
288
+ filePath: path,
289
+ segmentSeconds: 30,
290
+ maxDurationSeconds: DISCUSSION_MAX_SAVED_DURATION_MS / 1e3,
291
+ signal: AbortSignal.any([signal, AbortSignal.timeout(18e4)])
292
+ }, async (buffer) => {
293
+ const audio = decodeWavToMonoFloat32(buffer);
294
+ const pcm = Buffer.alloc(audio.samples.length * 2);
295
+ for (let i = 0; i < audio.samples.length; i++) pcm.writeInt16LE(Math.max(-32768, Math.min(32767, Math.round(audio.samples[i] * 32768))), i * 2);
296
+ samples += audio.samples.length;
297
+ epochSamples += audio.samples.length;
298
+ if (samples > 16e3 * 7205e3 / 1e3) throw invalid("Recording exceeds duration limit");
299
+ await output.writeFile(pcm);
300
+ });
301
+ if (!epochSamples) throw invalid("Recording epoch has no decodable audio");
302
+ const epochDuration = epochSamples / 16;
303
+ epochs.push(epoch.mimeType.includes("wav") ? pcmEpoch(epochDuration, (samples - epochSamples) / 16) : await probeMediaEpoch(path, epochDuration, (samples - epochSamples) / 16, signal));
304
+ assertLease();
305
+ signal.throwIfAborted();
306
+ const sha256 = await hashFile(path, signal);
307
+ const extension = epoch.mimeType.includes("mp4") ? "m4a" : epoch.mimeType.includes("ogg") ? "ogg" : epoch.mimeType.includes("wav") ? "wav" : "webm";
308
+ const attachment = await notes.addAttachment(capture.noteId, {
309
+ name: `recording-part-${capture.id}-${index + 1}.${extension}`,
310
+ buffer: { filePath: path },
311
+ mimeType: epoch.mimeType,
312
+ retainWithoutReference: true
313
+ }, `meeting-source:${capture.id}:${index}:${sha256}`);
314
+ if (!attachment) throw new Error("Discussion note is missing");
315
+ const stored = await notes.getAttachmentPath(capture.noteId, attachment.id);
316
+ if (!stored) throw new Error("Saved recording source is missing");
317
+ const durable = await open(stored.filePath, "r");
318
+ try {
319
+ await durable.sync();
320
+ } finally {
321
+ await durable.close();
322
+ }
323
+ sources.push({
324
+ ...epoch,
325
+ attachmentId: attachment.id,
326
+ sha256,
327
+ durationMs: epochSamples / 16
328
+ });
329
+ } finally {
330
+ await rm(path, { force: true });
331
+ }
332
+ }
333
+ await output.write(recordingWavHeader(samples), 0, 44, 0);
334
+ assertLease();
335
+ signal.throwIfAborted();
336
+ const manifestAttachment = await notes.addAttachment(capture.noteId, {
337
+ name: `recording-sources-${capture.id}.json`,
338
+ buffer: Buffer.from(JSON.stringify({
339
+ version: 1,
340
+ discussionId: capture.id,
341
+ noteId: capture.noteId,
342
+ sources
343
+ })),
344
+ mimeType: "application/json",
345
+ retainWithoutReference: true
346
+ }, `meeting-sources:${capture.id}`);
347
+ if (!manifestAttachment) throw new Error("Discussion note is missing");
348
+ const manifestFile = await notes.getAttachmentPath(capture.noteId, manifestAttachment.id);
349
+ if (!manifestFile) throw new Error("Saved recording manifest is missing");
350
+ const durable = await open(manifestFile.filePath, "r");
351
+ try {
352
+ await durable.sync();
353
+ } finally {
354
+ await durable.close();
355
+ }
356
+ return epochs;
357
+ }
198
358
  function recordingWavHeader(samples) {
199
359
  const header = Buffer.alloc(44);
200
360
  header.write("RIFF");
@@ -27,6 +27,8 @@ function submitRecordingJob(discussionId, input) {
27
27
  mimeType: input.mimeType,
28
28
  fileName: input.fileName,
29
29
  lastSequence: input.lastSequence,
30
+ ...input.events ? { events: input.events } : {},
31
+ ...input.epochs ? { epochs: input.epochs } : {},
30
32
  ...input.containerMode ? { containerMode: input.containerMode } : {}
31
33
  });
32
34
  return runSqliteWriteTransaction((db) => {
@@ -170,7 +170,8 @@ function claimNextDiscussionCapture(owner, now = Date.now(), leaseMs = 5 * 6e4)
170
170
  return runSqliteWriteTransaction((db) => {
171
171
  const row = db.prepare(`SELECT * FROM discussion_captures
172
172
  WHERE status='organizing' AND (work_lease_owner IS NULL OR work_lease_expires_at <= ?)
173
- ORDER BY updated_at ASC LIMIT 1`).get(now);
173
+ AND NOT EXISTS (SELECT 1 FROM discussion_processing p WHERE p.discussion_id=discussion_captures.id AND p.next_attempt_at>?)
174
+ ORDER BY updated_at ASC LIMIT 1`).get(now, now);
174
175
  if (!row) return null;
175
176
  return db.prepare(`UPDATE discussion_captures SET work_lease_owner=?, work_lease_expires_at=?, updated_at=?
176
177
  WHERE id=? AND status='organizing' AND (work_lease_owner IS NULL OR work_lease_expires_at <= ?)`).run(owner, now + leaseMs, now, row.id, now).changes ? getDiscussionCapture(row.id) : null;
@@ -226,7 +227,8 @@ function completeDiscussionTranscriptSegment(discussionId, sequence, owner, text
226
227
  raw_text=?, display_text=?, provider=?, language=?, confidence=?, audio_blob=NULL,
227
228
  lease_owner=NULL, lease_expires_at=NULL, next_attempt_at=NULL, last_error=NULL,
228
229
  revision=revision+1, updated_at=? WHERE discussion_id=? AND sequence=?
229
- AND status='transcribing' AND lease_owner=?`).run(text, text, provider, language ?? null, confidence ?? null, now, discussionId, sequence, owner).changes;
230
+ AND status='transcribing' AND lease_owner=?
231
+ AND EXISTS (SELECT 1 FROM discussion_captures d WHERE d.id=discussion_id AND d.status IN ('recording','stopping'))`).run(text, text, provider, language ?? null, confidence ?? null, now, discussionId, sequence, owner).changes;
230
232
  if (changed) db.prepare("UPDATE discussion_captures SET transcript_revision=transcript_revision+1, updated_at=? WHERE id=?").run(now, discussionId);
231
233
  return changed;
232
234
  }) ? getDiscussionTranscriptSegment(discussionId, sequence) : null;
@@ -0,0 +1,8 @@
1
+ import type { DiscussionAnnotation, DiscussionAnnotationInput, DiscussionEvidence, DiscussionResolvedAnnotation } from '@xopcai/gateway-contract';
2
+ import type { NotesService } from '../notes/service.js';
3
+ import { type RecordingTimeline } from './media-timeline.js';
4
+ export declare function listAnnotations(id: string): DiscussionAnnotation[];
5
+ export declare function putAnnotation(id: string, value: DiscussionAnnotationInput): DiscussionAnnotation;
6
+ export declare function recordingTimeline(id: string, notes: NotesService): Promise<RecordingTimeline | undefined>;
7
+ export declare function evidenceFor(id: string, revision: number, ids: number[], notes: NotesService): Promise<DiscussionEvidence>;
8
+ export declare function resolvedAnnotations(id: string, notes: NotesService): Promise<DiscussionResolvedAnnotation[]>;
@@ -0,0 +1,183 @@
1
+ import { getSqliteDatabase, init_transaction, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { DiscussionServiceError } from "./errors.js";
3
+ import { getDiscussionCapture, listDiscussionTranscriptSegments } from "./repository.js";
4
+ import { readTranscriptRevision, saveTranscriptRevision } from "./revisions.js";
5
+ import { pcmEpoch, playbackRanges, pointToPcm, probeMediaEpoch } from "./media-timeline.js";
6
+ import { z } from "zod";
7
+ //#region src/discussions/review.ts
8
+ init_transaction();
9
+ const anchorSchema = z.object({
10
+ type: z.enum(["time", "transcript"]),
11
+ capturedAt: z.number().int().nonnegative(),
12
+ epoch: z.number().int().min(0).max(100).optional(),
13
+ coordinate: z.enum(["encoded", "pcm"]).optional(),
14
+ offsetMs: z.number().min(0).max(8e6).optional(),
15
+ positionMs: z.number().min(0).max(8e6).optional(),
16
+ transcriptRevision: z.number().int().nonnegative().optional(),
17
+ segmentIds: z.array(z.number().int().nonnegative()).min(1).max(100).optional()
18
+ }).strict().refine((anchor) => anchor.type === "transcript" ? anchor.transcriptRevision !== void 0 && !!anchor.segmentIds?.length && anchor.offsetMs === void 0 && anchor.positionMs === void 0 : anchor.transcriptRevision === void 0 && anchor.segmentIds === void 0 && !(anchor.offsetMs !== void 0 && anchor.positionMs !== void 0) && (anchor.offsetMs === void 0 || anchor.epoch !== void 0));
19
+ const inputSchema = z.object({
20
+ id: z.string().regex(/^[a-zA-Z0-9_-]{1,100}$/),
21
+ kind: z.enum([
22
+ "highlight",
23
+ "question",
24
+ "note"
25
+ ]),
26
+ text: z.string().max(4e3),
27
+ anchor: anchorSchema,
28
+ expectedRevision: z.number().int().nonnegative(),
29
+ deleted: z.boolean().optional()
30
+ });
31
+ const map = (row) => ({
32
+ id: row.id,
33
+ kind: row.kind,
34
+ text: row.text,
35
+ anchor: JSON.parse(row.anchor_json),
36
+ revision: row.revision,
37
+ deleted: !!row.deleted,
38
+ createdAt: row.created_at,
39
+ updatedAt: row.updated_at
40
+ });
41
+ const requireCapture = (id) => {
42
+ const capture = getDiscussionCapture(id);
43
+ if (!capture) throw new DiscussionServiceError("not_found", "Discussion not found");
44
+ return capture;
45
+ };
46
+ function listAnnotations(id) {
47
+ requireCapture(id);
48
+ return getSqliteDatabase().prepare("SELECT * FROM discussion_annotations WHERE discussion_id=? ORDER BY created_at,id").all(id).map(map).sort((a, b) => a.anchor.capturedAt - b.anchor.capturedAt || a.id.localeCompare(b.id));
49
+ }
50
+ function putAnnotation(id, value) {
51
+ const parsed = inputSchema.safeParse(value);
52
+ if (!parsed.success) throw new DiscussionServiceError("invalid_input", "Invalid annotation");
53
+ const input = parsed.data;
54
+ return runSqliteWriteTransaction((db) => {
55
+ const capture = requireCapture(id);
56
+ if (input.anchor.type === "transcript") {
57
+ if (input.anchor.transcriptRevision === capture.transcriptRevision) saveTranscriptRevision(id);
58
+ const segments = readTranscriptRevision(id, input.anchor.transcriptRevision);
59
+ if (!segments || input.anchor.segmentIds.some((sequence) => !segments.some((segment) => segment.sequence === sequence && segment.status === "confirmed"))) throw new DiscussionServiceError("conflict", "Transcript evidence is no longer available");
60
+ }
61
+ const row = db.prepare("SELECT * FROM discussion_annotations WHERE discussion_id=? AND id=?").get(id, input.id);
62
+ const current = row ? map(row) : void 0;
63
+ const anchor = JSON.stringify(input.anchor);
64
+ if (current && current.kind === input.kind && current.text === input.text && current.deleted === !!input.deleted && JSON.stringify(current.anchor) === anchor) return current;
65
+ if ((current?.revision ?? 0) !== input.expectedRevision) throw new DiscussionServiceError("conflict", "Annotation changed; reload before saving");
66
+ if (current && JSON.stringify(current.anchor) !== anchor) {
67
+ const previous = current.anchor;
68
+ if (!(previous.type === "time" && previous.offsetMs === void 0 && previous.positionMs === void 0 && input.anchor.type === "time" && previous.capturedAt === input.anchor.capturedAt && previous.epoch === input.anchor.epoch && previous.coordinate === input.anchor.coordinate)) throw new DiscussionServiceError("conflict", "Original annotation anchor is immutable");
69
+ }
70
+ if (!current && Number(db.prepare("SELECT COUNT(*) n FROM discussion_annotations WHERE discussion_id=?").get(id).n) >= 1e3) throw new DiscussionServiceError("invalid_input", "Annotation limit reached");
71
+ const now = Date.now();
72
+ const revision = (current?.revision ?? 0) + 1;
73
+ db.prepare(`INSERT INTO discussion_annotations(discussion_id,id,kind,text,anchor_json,revision,deleted,created_at,updated_at)
74
+ VALUES (?,?,?,?,?,?,?,?,?) ON CONFLICT(discussion_id,id) DO UPDATE SET kind=excluded.kind,text=excluded.text,anchor_json=excluded.anchor_json,revision=excluded.revision,deleted=excluded.deleted,updated_at=excluded.updated_at`).run(id, input.id, input.kind, input.text, anchor, revision, input.deleted ? 1 : 0, current?.createdAt ?? now, now);
75
+ return {
76
+ id: input.id,
77
+ kind: input.kind,
78
+ text: input.text,
79
+ anchor: input.anchor,
80
+ revision,
81
+ deleted: !!input.deleted,
82
+ createdAt: current?.createdAt ?? now,
83
+ updatedAt: now
84
+ };
85
+ });
86
+ }
87
+ const timingAttempts = /* @__PURE__ */ new Map();
88
+ const timingInFlight = /* @__PURE__ */ new Map();
89
+ async function recordingTimeline(id, notes) {
90
+ const capture = requireCapture(id);
91
+ if (!capture.audioAttachmentId || capture.audioDeletedAt || !capture.durationMs) return;
92
+ const key = `${id}:${capture.audioSha256}`;
93
+ if (timingInFlight.has(key)) return timingInFlight.get(key);
94
+ const cached = getSqliteDatabase().prepare("SELECT timeline_json FROM discussion_media_timeline WHERE discussion_id=? AND audio_sha256=?").get(id, capture.audioSha256 ?? "");
95
+ let previous;
96
+ try {
97
+ previous = cached ? JSON.parse(cached.timeline_json) : void 0;
98
+ } catch {}
99
+ if (previous && Array.isArray(previous.epochs) && (previous.primary === "pcm" || previous.epochs.every((epoch) => epoch.verified))) return previous;
100
+ if (capture.mimeType?.includes("wav")) return {
101
+ primary: "pcm",
102
+ epochs: [pcmEpoch(capture.durationMs)]
103
+ };
104
+ if (previous && Date.now() - (timingAttempts.get(key) ?? 0) < 3e4) return previous;
105
+ timingAttempts.set(key, Date.now());
106
+ if (timingAttempts.size > 256) timingAttempts.delete(timingAttempts.keys().next().value);
107
+ const pending = (async () => {
108
+ const attachment = await notes.getAttachmentPath(capture.noteId, capture.audioAttachmentId);
109
+ if (!attachment) return;
110
+ const timeline = {
111
+ primary: "encoded",
112
+ epochs: [await probeMediaEpoch(attachment.filePath, capture.durationMs, 0, AbortSignal.timeout(35e3))]
113
+ };
114
+ if (getDiscussionCapture(id)?.audioSha256 === capture.audioSha256) getSqliteDatabase().prepare("INSERT OR REPLACE INTO discussion_media_timeline(discussion_id,audio_sha256,timeline_json) VALUES(?,?,?)").run(id, capture.audioSha256 ?? "", JSON.stringify(timeline));
115
+ return timeline;
116
+ })().finally(() => timingInFlight.delete(key));
117
+ timingInFlight.set(key, pending);
118
+ return pending;
119
+ }
120
+ async function evidenceFor(id, revision, ids, notes) {
121
+ const capture = requireCapture(id);
122
+ if (!Number.isSafeInteger(revision) || revision < 0 || !ids.length || ids.length > 100 || ids.some((id) => !Number.isSafeInteger(id) || id < 0)) throw new DiscussionServiceError("invalid_input", "Invalid evidence reference");
123
+ if (capture.transcriptRevision === revision) saveTranscriptRevision(id);
124
+ const source = readTranscriptRevision(id, revision);
125
+ if (!source || ids.some((id) => !source.some((segment) => segment.sequence === id && segment.status === "confirmed"))) throw new DiscussionServiceError("not_found", "Referenced transcript revision or segment is unavailable");
126
+ const attachment = capture.audioAttachmentId && !capture.audioDeletedAt ? await notes.getAttachmentPath(capture.noteId, capture.audioAttachmentId) : null;
127
+ const timeline = attachment ? await recordingTimeline(id, notes) : void 0;
128
+ return {
129
+ transcriptRevision: revision,
130
+ stale: revision !== getDiscussionCapture(id)?.transcriptRevision,
131
+ audioAvailable: !!attachment,
132
+ timingVerified: !!timeline && (timeline.primary === "pcm" || timeline.epochs.every((epoch) => epoch.verified)),
133
+ segments: source.filter((segment) => ids.includes(segment.sequence)).map((segment) => ({
134
+ sequence: segment.sequence,
135
+ startedAtMs: segment.startedAtMs,
136
+ endedAtMs: segment.endedAtMs,
137
+ text: segment.displayText ?? segment.rawText ?? "",
138
+ speakerLabel: segment.speakerLabel,
139
+ playback: timeline ? playbackRanges(timeline, segment.startedAtMs, segment.endedAtMs) : []
140
+ }))
141
+ };
142
+ }
143
+ async function resolvedAnnotations(id, notes) {
144
+ const capture = requireCapture(id);
145
+ const annotations = listAnnotations(id);
146
+ const timeline = await recordingTimeline(id, notes);
147
+ const current = listDiscussionTranscriptSegments(id).filter((segment) => segment.status === "confirmed");
148
+ return Promise.all(annotations.map(async (annotation) => {
149
+ const anchor = annotation.anchor;
150
+ if (annotation.deleted) return {
151
+ annotation,
152
+ state: "unavailable",
153
+ playback: []
154
+ };
155
+ if (anchor.type === "transcript") {
156
+ const evidence = await evidenceFor(id, anchor.transcriptRevision, anchor.segmentIds, notes).catch(() => void 0);
157
+ return {
158
+ annotation,
159
+ state: evidence ? "located" : "unavailable",
160
+ evidence,
161
+ playback: evidence?.segments.flatMap((segment) => segment.playback) ?? []
162
+ };
163
+ }
164
+ const epoch = timeline?.epochs[anchor.epoch ?? 0];
165
+ const position = anchor.positionMs ?? (epoch && anchor.offsetMs !== void 0 ? anchor.coordinate === "pcm" ? anchor.offsetMs < epoch.durationMs ? epoch.baseMs + anchor.offsetMs : void 0 : pointToPcm(epoch, anchor.offsetMs) : void 0);
166
+ if (!(!capture.audioDeletedAt && !!capture.audioAttachmentId && position !== void 0 && position >= 0 && position < (capture.durationMs ?? 0))) return {
167
+ annotation,
168
+ state: !capture.audioAttachmentId && !capture.audioDeletedAt ? "pending" : "unavailable",
169
+ playback: []
170
+ };
171
+ const refs = current.filter((segment) => segment.startedAtMs <= position && segment.endedAtMs > position).map((segment) => segment.sequence);
172
+ const evidence = refs.length ? await evidenceFor(id, capture.transcriptRevision, refs, notes) : void 0;
173
+ return {
174
+ annotation,
175
+ state: "located",
176
+ positionMs: position,
177
+ playback: timeline ? playbackRanges(timeline, Math.max(0, position - 2e3), Math.min(capture.durationMs, position + 8e3)) : [],
178
+ evidence
179
+ };
180
+ }));
181
+ }
182
+ //#endregion
183
+ export { evidenceFor, listAnnotations, putAnnotation, recordingTimeline, resolvedAnnotations };