@xopcai/xopc 0.0.278 → 0.0.283

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 (468) hide show
  1. package/dist/browser-ext/_locales/en/messages.json +34 -4
  2. package/dist/browser-ext/_locales/zh_CN/messages.json +34 -4
  3. package/dist/browser-ext/dist/assets/{auth-ClQTK565.js → auth-DB30ywek.js} +18 -53
  4. package/dist/browser-ext/dist/assets/pcm-capture-worklet-CGN9Mlhg.js +39 -0
  5. package/dist/browser-ext/dist/assets/{sidepanel-CeGqObOa.css → sidepanel-BbSQr-6P.css} +40 -6
  6. package/dist/browser-ext/dist/background.js +1 -1
  7. package/dist/browser-ext/dist/sidepanel.html +2 -2
  8. package/dist/browser-ext/dist/sidepanel.js +1219 -191
  9. package/dist/browser-ext/manifest.json +2 -1
  10. package/dist/extensions/feishu/src/adapters/cli-login.js +3 -3
  11. package/dist/extensions/feishu/src/outbound/media-load.js +1 -1
  12. package/dist/extensions/telegram/src/inbound-processor.js +2 -2
  13. package/dist/extensions/telegram/src/outbound-sender.js +1 -1
  14. package/dist/extensions/telegram/src/plugin.js +2 -2
  15. package/dist/extensions/telegram/xopc.extension.json +1 -1
  16. package/dist/extensions/weixin/src/api/api.js +2 -2
  17. package/dist/extensions/weixin/src/auth/accounts.js +12 -12
  18. package/dist/extensions/weixin/src/messaging/debug-mode.js +5 -5
  19. package/dist/extensions/weixin/src/messaging/inbound.js +11 -11
  20. package/dist/gateway/static/root/assets/{activity-store-SJZ5fS17.js → activity-store-iPtG94qr.js} +1 -1
  21. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-lRVOwbrK.js → agent-avatar-dicebear-DQ74xqLi.js} +1 -1
  22. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-SElFkQd6.js +1 -0
  23. package/dist/gateway/static/root/assets/{agents-DNQNTDkU.js → agents-DYazUaZ6.js} +1 -1
  24. package/dist/gateway/static/root/assets/{api-CnJAVHBu.js → api-KMJzMqvZ.js} +1 -1
  25. package/dist/gateway/static/root/assets/{app-management-settings-panel-oHTY5PDs.js → app-management-settings-panel-B42TpqIb.js} +1 -1
  26. package/dist/gateway/static/root/assets/{appearance-settings-B0YnZ3ig.js → appearance-settings-D9Lfe7Sk.js} +1 -1
  27. package/dist/gateway/static/root/assets/apps-page-BzrodN8C.js +1 -0
  28. package/dist/gateway/static/root/assets/{archive-plugin-D4x142le.js → archive-plugin-BdmQwj4x.js} +1 -1
  29. package/dist/gateway/static/root/assets/{attachment-load-zibD2Hod.js → attachment-load-B4S0_nji.js} +1 -1
  30. package/dist/gateway/static/root/assets/{attachment-preview-renderer-CfEDFYPE.js → attachment-preview-renderer-wzIchDSv.js} +4 -4
  31. package/dist/gateway/static/root/assets/attachment-process-heavy-Db4nTY5N.js +5 -0
  32. package/dist/gateway/static/root/assets/{automations-page-Dkp7gp_S.js → automations-page-B3TmHNjQ.js} +1 -1
  33. package/dist/gateway/static/root/assets/automations-workspace--A4I0jMu.js +6 -0
  34. package/dist/gateway/static/root/assets/binary-plugins-B3cXF_DP.js +2 -0
  35. package/dist/gateway/static/root/assets/{block-editor-r5nCBBan.js → block-editor-05zyBXBe.js} +3 -3
  36. package/dist/gateway/static/root/assets/{browser-automation-inputs-DcVLoIzc.js → browser-automation-inputs-BTIufVbM.js} +1 -1
  37. package/dist/gateway/static/root/assets/{browser-automations-page-BnL4JnTM.js → browser-automations-page-DRBA9nwe.js} +2 -2
  38. package/dist/gateway/static/root/assets/browser-settings-page-DDTXUNWK.js +8 -0
  39. package/dist/gateway/static/root/assets/capabilities-settings-panel-Ll5UeXq5.js +8 -0
  40. package/dist/gateway/static/root/assets/channels-settings-BzL15bkU.js +1 -0
  41. package/dist/gateway/static/root/assets/{channels-status-swr-BTT2sIGg.js → channels-status-swr-DMYQNs-J.js} +1 -1
  42. package/dist/gateway/static/root/assets/{chat-terminal-dock-CWVnJtBp.js → chat-terminal-dock-Dx5KQyo5.js} +2 -2
  43. package/dist/gateway/static/root/assets/connectors-page-RPw1AOMb.js +2 -0
  44. package/dist/gateway/static/root/assets/{date-picker-XjPSlgq_.js → date-picker-CkDROZoe.js} +1 -1
  45. package/dist/gateway/static/root/assets/{dependency-picker-CkyfEP88.js → dependency-picker-Bl8HcvzL.js} +1 -1
  46. package/dist/gateway/static/root/assets/{desktop-pet-BAE_NVx5.js → desktop-pet-BcknAjbW.js} +1 -1
  47. package/dist/gateway/static/root/assets/{desktop-pet-settings-BUw6V2R9.js → desktop-pet-settings-BuCpXQPm.js} +1 -1
  48. package/dist/gateway/static/root/assets/device-pairing-wizard-D0_tCX49.js +1 -0
  49. package/dist/gateway/static/root/assets/{extension-debug-page-giBeIVUT.js → extension-debug-page-qRpb2FFa.js} +1 -1
  50. package/dist/gateway/static/root/assets/{extension-page-Bqo9bNtx.js → extension-page-C7gs9wu4.js} +1 -1
  51. package/dist/gateway/static/root/assets/{extension-settings-page-BX-6AA3k.js → extension-settings-page-CyJP1CMZ.js} +1 -1
  52. package/dist/gateway/static/root/assets/gateway-config-swr-Cpmyrr6j.js +1 -0
  53. package/dist/gateway/static/root/assets/gateway-settings-DOeeTdWE.js +1 -0
  54. package/dist/gateway/static/root/assets/{gateway-startup-retry-CVTa5HSS.js → gateway-startup-retry-DoiPeb65.js} +1 -1
  55. package/dist/gateway/static/root/assets/{heartbeat-settings-DWFAopk_.js → heartbeat-settings-CV6Dolcn.js} +1 -1
  56. package/dist/gateway/static/root/assets/home-page-Djw1H_Go.js +1 -0
  57. package/dist/gateway/static/root/assets/{image-generation-api-DtMRG1oF.js → image-generation-api-CqjHWl2T.js} +1 -1
  58. package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +2 -0
  59. package/dist/gateway/static/root/assets/index-CHgOG1aR.js +74 -0
  60. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-BKGPpBnV.js → keyboard-shortcuts-settings-B3XqA41D.js} +1 -1
  61. package/dist/gateway/static/root/assets/local-app-workbench-page-BPqgyYJq.js +2 -0
  62. package/dist/gateway/static/root/assets/{local-apps-page-B06ToOxB.js → local-apps-page-BcIVYp0J.js} +1 -1
  63. package/dist/gateway/static/root/assets/{locale-store-BkJ7g35d.js → locale-store-1urrBBCQ.js} +1 -1
  64. package/dist/gateway/static/root/assets/{logs-page-DibI1GsP.js → logs-page-BHax3XlS.js} +2 -2
  65. package/dist/gateway/static/root/assets/{management-settings-DJ6yMZCd.js → management-settings-DNFCdgzR.js} +1 -1
  66. package/dist/gateway/static/root/assets/{markdown-view-DF3aJP_5.js → markdown-view-BZmcFIWa.js} +1 -1
  67. package/dist/gateway/static/root/assets/{media-plugins-CdrLNAks.js → media-plugins-BDxNhoBC.js} +1 -1
  68. package/dist/gateway/static/root/assets/messages-DYFcjtmG.js +3 -0
  69. package/dist/gateway/static/root/assets/note-detail-panel-Cs46LFXO.js +5 -0
  70. package/dist/gateway/static/root/assets/notes-page-BUkcn_0t.js +8 -0
  71. package/dist/gateway/static/root/assets/{page-tabs-B7LA4z-x.js → page-tabs-agH3a2PI.js} +1 -1
  72. package/dist/gateway/static/root/assets/pdf-runtime-DU_sMPjG.js +2 -0
  73. package/dist/gateway/static/root/assets/pdf.worker.min-B4E34YdZ.js +48 -0
  74. package/dist/gateway/static/root/assets/{preview-open-alternatives-DTQRN7WR.js → preview-open-alternatives-XEZ7zDOP.js} +1 -1
  75. package/dist/gateway/static/root/assets/proactive-page-Bn484J9v.js +1 -0
  76. package/dist/gateway/static/root/assets/{product-open-page-BazdmFYo.js → product-open-page-lXAbj85Z.js} +1 -1
  77. package/dist/gateway/static/root/assets/project-detail-page-LcH21ixD.js +1 -0
  78. package/dist/gateway/static/root/assets/projects-page-DelBiwyB.js +1 -0
  79. package/dist/gateway/static/root/assets/{registry-api-CTq8EECj.js → registry-api-CxQstD00.js} +1 -1
  80. package/dist/gateway/static/root/assets/remote-access-hub-3BjFYXCf.js +1 -0
  81. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-D-DZf4I1.js → runtime-tools-settings-panel-DErKZlVJ.js} +2 -2
  82. package/dist/gateway/static/root/assets/{schema-form-B-Yj9DVW.js → schema-form-DhCmJQVJ.js} +1 -1
  83. package/dist/gateway/static/root/assets/sessions-page-CVFirgVd.js +1 -0
  84. package/dist/gateway/static/root/assets/settings-advanced-gate-DyfBpqkY.js +1 -0
  85. package/dist/gateway/static/root/assets/{settings-form-section-BxPUqwY6.js → settings-form-section-Dg6chotg.js} +1 -1
  86. package/dist/gateway/static/root/assets/{settings-page-zLn8lIrs.js → settings-page-LyuN5N3M.js} +1 -1
  87. package/dist/gateway/static/root/assets/setup-status-panel-CrXhTzHr.js +1 -0
  88. package/dist/gateway/static/root/assets/share-preview-page-CZvmf4Ob.js +2 -0
  89. package/dist/gateway/static/root/assets/shares-settings-DyM_D8hM.js +1 -0
  90. package/dist/gateway/static/root/assets/skill-reload-api-Bys58-15.js +1 -0
  91. package/dist/gateway/static/root/assets/skills-page-sWNvFo6D.js +2 -0
  92. package/dist/gateway/static/root/assets/{skills-page.utils-BR5bk2vT.js → skills-page.utils-Eu0WVHTS.js} +1 -1
  93. package/dist/gateway/static/root/assets/{system-settings-panel-C0ptJutB.js → system-settings-panel-DQKkM7B2.js} +1 -1
  94. package/dist/gateway/static/root/assets/task-detail-page-B18NcZQJ.js +1 -0
  95. package/dist/gateway/static/root/assets/{theme-store-mV6Cm2vj.js → theme-store-Dc5s20V3.js} +1 -1
  96. package/dist/gateway/static/root/assets/tunnel-api-TKQpPVd6.js +1 -0
  97. package/dist/gateway/static/root/assets/{use-autosave-Dc5aN2px.js → use-autosave-CkVlrFk1.js} +1 -1
  98. package/dist/gateway/static/root/assets/user-model-page-ul-zr58b.js +2 -0
  99. package/dist/gateway/static/root/assets/{voice-api-key-field-CY0DpJui.js → voice-api-key-field-B12fFN-L.js} +1 -1
  100. package/dist/gateway/static/root/assets/{voice-config-api-D3DdcH4H.js → voice-config-api-Mk78BrpP.js} +1 -1
  101. package/dist/gateway/static/root/assets/{work-discovery-overlay-Brg9yJBr.js → work-discovery-overlay-CT9Zy36i.js} +1 -1
  102. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-D3fq7boB.js → workflow-run-setup-panel-BwefQliE.js} +2 -2
  103. package/dist/gateway/static/root/assets/workflows-page-BY_AWs7S.js +6 -0
  104. package/dist/gateway/static/root/index.html +10 -11
  105. package/dist/package.js +1 -1
  106. package/dist/packages/gateway-contract/src/discussions.js +17 -0
  107. package/dist/packages/gateway-contract/src/proactive.js +4 -2
  108. package/dist/src/agent/agent-manager.js +17 -17
  109. package/dist/src/agent/agent-scope.js +2 -2
  110. package/dist/src/agent/background-review/run-background-review.js +4 -4
  111. package/dist/src/agent/context/coordinator.js +3 -3
  112. package/dist/src/agent/context/execution-context.js +4 -4
  113. package/dist/src/agent/context/execution-scope.js +2 -2
  114. package/dist/src/agent/context/project-context.d.ts +1 -0
  115. package/dist/src/agent/context/project-context.js +16 -8
  116. package/dist/src/agent/embedded/index.js +2 -2
  117. package/dist/src/agent/embedded/model-runtime.js +1 -1
  118. package/dist/src/agent/embedded/run-for-session.js +5 -5
  119. package/dist/src/agent/embedded/run-turn.js +2 -2
  120. package/dist/src/agent/embedded/session-tool-result-guard.js +3 -3
  121. package/dist/src/agent/external-tools/composio-provider.js +1 -1
  122. package/dist/src/agent/external-tools/gateway-tools.js +1 -1
  123. package/dist/src/agent/external-tools/mcp-provider.js +2 -2
  124. package/dist/src/agent/image/generation/openai-images-provider.js +1 -1
  125. package/dist/src/agent/image/generation/providers/dashscope.js +1 -1
  126. package/dist/src/agent/image/generation/providers/fal.js +1 -1
  127. package/dist/src/agent/image/generation/providers/google.js +1 -1
  128. package/dist/src/agent/image/generation/runtime.js +1 -1
  129. package/dist/src/agent/image/index.js +1 -1
  130. package/dist/src/agent/image/load-image-media.js +1 -1
  131. package/dist/src/agent/image/tool-model-config.js +1 -1
  132. package/dist/src/agent/inbound/attachment-pipeline.js +1 -1
  133. package/dist/src/agent/inbound/inbound-loop.js +2 -2
  134. package/dist/src/agent/index.js +1 -1
  135. package/dist/src/agent/mcp/bundle-mcp-gateway.js +1 -1
  136. package/dist/src/agent/mcp/bundle-mcp-runtime.js +2 -2
  137. package/dist/src/agent/mcp/index.js +2 -2
  138. package/dist/src/agent/mcp/mcp-transport.js +1 -1
  139. package/dist/src/agent/mcp/oauth/mcp-oauth-manager.js +1 -1
  140. package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
  141. package/dist/src/agent/memory/index.js +2 -2
  142. package/dist/src/agent/messaging/outbound-coordinator.js +1 -1
  143. package/dist/src/agent/orchestration/agent-orchestrator.js +5 -5
  144. package/dist/src/agent/prompt/service-prompt-builder.js +2 -2
  145. package/dist/src/agent/sandbox/exec-policy.js +1 -1
  146. package/dist/src/agent/service/direct-turn-helpers.js +6 -6
  147. package/dist/src/agent/service/process-direct-one-shot.js +1 -1
  148. package/dist/src/agent/service/process-direct-streaming.js +5 -5
  149. package/dist/src/agent/service/webchat-tts.js +2 -2
  150. package/dist/src/agent/service.js +19 -19
  151. package/dist/src/agent/session/session-inspector.js +1 -1
  152. package/dist/src/agent/skills/filesystem-watcher.js +1 -1
  153. package/dist/src/agent/skills/hub-lock.js +1 -1
  154. package/dist/src/agent/skills/hub-pull.js +3 -3
  155. package/dist/src/agent/skills/marketplace/adapters/store/adapter.js +1 -1
  156. package/dist/src/agent/skills/marketplace/adapters/store/store-api-client.js +1 -1
  157. package/dist/src/agent/source-context/task-context.d.ts +3 -0
  158. package/dist/src/agent/source-context/task-context.js +24 -0
  159. package/dist/src/agent/source-context/types.d.ts +2 -2
  160. package/dist/src/agent/source-context/types.js +1 -1
  161. package/dist/src/agent/tasks/task-judge-service.js +5 -5
  162. package/dist/src/agent/tool-manuals/xopc-use.d.ts +1 -1
  163. package/dist/src/agent/tool-manuals/xopc-use.js +1 -0
  164. package/dist/src/agent/tools/create-share-tool.js +3 -3
  165. package/dist/src/agent/tools/delegate-tool.js +3 -3
  166. package/dist/src/agent/tools/executor.js +1 -1
  167. package/dist/src/agent/tools/factory.js +13 -13
  168. package/dist/src/agent/tools/image-generate-tool.js +3 -3
  169. package/dist/src/agent/tools/index.js +5 -5
  170. package/dist/src/agent/tools/list-dir.js +1 -1
  171. package/dist/src/agent/tools/publish-artifacts.js +2 -2
  172. package/dist/src/agent/tools/read.js +1 -1
  173. package/dist/src/agent/tools/review-workspace.js +1 -1
  174. package/dist/src/agent/tools/send-media.js +2 -2
  175. package/dist/src/agent/tools/skills-tools.js +1 -1
  176. package/dist/src/agent/tools/tts-tool.js +1 -1
  177. package/dist/src/agent/tools/user-context-tool.js +1 -1
  178. package/dist/src/agent/tools/write.js +2 -2
  179. package/dist/src/agent/tools/xopc-use-tool.js +6 -6
  180. package/dist/src/agent/workflow/subagent-runner.js +1 -1
  181. package/dist/src/ai-assist/text-assist.js +1 -1
  182. package/dist/src/auth/credentials.js +2 -2
  183. package/dist/src/auth/oauth/xopc-cloud.js +1 -1
  184. package/dist/src/auth/profiles/store.js +1 -1
  185. package/dist/src/auth/sync-provider-auth.js +2 -2
  186. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  187. package/dist/src/capabilities/store-connector.js +1 -1
  188. package/dist/src/channels/attachments/voice-stt-webchat.js +2 -2
  189. package/dist/src/channels/index.js +2 -2
  190. package/dist/src/channels/outbound-sender.js +1 -1
  191. package/dist/src/channels/pairing/allow-from-file.js +9 -9
  192. package/dist/src/channels/pairing/pairing-store.js +6 -6
  193. package/dist/src/chat-commands/builtins/config.js +2 -2
  194. package/dist/src/chat-commands/builtins/review.js +1 -1
  195. package/dist/src/chat-commands/builtins/session.js +1 -1
  196. package/dist/src/chat-commands/builtins/thinking.js +1 -1
  197. package/dist/src/chat-commands/builtins/tts.js +1 -1
  198. package/dist/src/chat-commands/builtins/understanding.js +1 -1
  199. package/dist/src/chat-commands/builtins/workflow.js +2 -2
  200. package/dist/src/chat-commands/context.js +2 -2
  201. package/dist/src/chat-commands/index.js +3 -3
  202. package/dist/src/cli/commands/agent.js +6 -6
  203. package/dist/src/cli/commands/agents.js +1 -1
  204. package/dist/src/cli/commands/config.js +1 -1
  205. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  206. package/dist/src/cli/commands/doctor/checks/provider-auth.js +3 -3
  207. package/dist/src/cli/commands/extension-dev.js +1 -1
  208. package/dist/src/cli/commands/extension.js +1 -1
  209. package/dist/src/cli/commands/gateway/shared.js +1 -1
  210. package/dist/src/cli/commands/gateway/token.js +1 -1
  211. package/dist/src/cli/commands/image.js +1 -1
  212. package/dist/src/cli/commands/init.js +1 -1
  213. package/dist/src/cli/commands/models.js +1 -1
  214. package/dist/src/cli/commands/onboard.js +1 -1
  215. package/dist/src/cli/commands/project.js +2 -2
  216. package/dist/src/cli/commands/runtime.js +1 -1
  217. package/dist/src/cli/commands/skills.js +2 -2
  218. package/dist/src/cli/commands/tui.js +1 -1
  219. package/dist/src/cli/commands/tunnel.js +3 -3
  220. package/dist/src/cli/extension-cli-register.js +4 -4
  221. package/dist/src/cli/utils/gateway-client.js +1 -1
  222. package/dist/src/config/index.js +5 -5
  223. package/dist/src/config/loader.js +2 -2
  224. package/dist/src/config/models-json.js +1 -1
  225. package/dist/src/connectors/connected-source-understanding.js +2 -2
  226. package/dist/src/connectors/connection-recovery-service.js +3 -3
  227. package/dist/src/discussions/action-tasks.d.ts +21 -0
  228. package/dist/src/discussions/action-tasks.js +93 -0
  229. package/dist/src/discussions/analyzer.d.ts +21 -1
  230. package/dist/src/discussions/analyzer.js +229 -62
  231. package/dist/src/discussions/audio-repair.d.ts +8 -0
  232. package/dist/src/discussions/audio-repair.js +41 -0
  233. package/dist/src/discussions/edits.d.ts +22 -0
  234. package/dist/src/discussions/edits.js +81 -0
  235. package/dist/src/discussions/errors.d.ts +4 -0
  236. package/dist/src/discussions/errors.js +10 -0
  237. package/dist/src/discussions/export.d.ts +2 -0
  238. package/dist/src/discussions/export.js +23 -0
  239. package/dist/src/discussions/index.js +3 -2
  240. package/dist/src/discussions/live-worker.js +2 -2
  241. package/dist/src/discussions/organizer-worker.js +14 -5
  242. package/dist/src/discussions/organizer.d.ts +1 -1
  243. package/dist/src/discussions/organizer.js +60 -24
  244. package/dist/src/discussions/reconcile.d.ts +22 -0
  245. package/dist/src/discussions/reconcile.js +55 -0
  246. package/dist/src/discussions/recording.d.ts +9 -0
  247. package/dist/src/discussions/recording.js +219 -0
  248. package/dist/src/discussions/recordingJobs.d.ts +14 -0
  249. package/dist/src/discussions/recordingJobs.js +69 -0
  250. package/dist/src/discussions/repository.d.ts +4 -2
  251. package/dist/src/discussions/repository.js +15 -10
  252. package/dist/src/discussions/revisions.d.ts +12 -0
  253. package/dist/src/discussions/revisions.js +36 -0
  254. package/dist/src/discussions/sealer.d.ts +1 -0
  255. package/dist/src/discussions/sealer.js +62 -12
  256. package/dist/src/discussions/service.d.ts +16 -15
  257. package/dist/src/discussions/service.js +177 -91
  258. package/dist/src/discussions/types.d.ts +4 -139
  259. package/dist/src/discussions/types.js +1 -11
  260. package/dist/src/extensions/api.js +3 -3
  261. package/dist/src/extensions/bundled-extension-activation.js +1 -1
  262. package/dist/src/extensions/index.js +1 -1
  263. package/dist/src/extensions/loader.js +1 -1
  264. package/dist/src/extensions/lockfile.js +1 -1
  265. package/dist/src/extensions/sdk/index.js +1 -1
  266. package/dist/src/files/file-service.d.ts +1 -0
  267. package/dist/src/files/file-service.js +77 -0
  268. package/dist/src/gateway/agents-admin.js +4 -4
  269. package/dist/src/gateway/chat-stream/mapper.js +2 -2
  270. package/dist/src/gateway/device-routes.js +2 -2
  271. package/dist/src/gateway/file-path-classifier.js +2 -2
  272. package/dist/src/gateway/hono/app.js +6 -5
  273. package/dist/src/gateway/hono/lib/config-payload.js +3 -3
  274. package/dist/src/gateway/hono/lib/static-ui.js +1 -0
  275. package/dist/src/gateway/hono/routes/agents.js +1 -1
  276. package/dist/src/gateway/hono/routes/auth-registry-extensions.js +1 -1
  277. package/dist/src/gateway/hono/routes/browser.js +1 -1
  278. package/dist/src/gateway/hono/routes/commands-skills.js +3 -3
  279. package/dist/src/gateway/hono/routes/config-patch/gateway.js +1 -1
  280. package/dist/src/gateway/hono/routes/context-sources.js +1 -2
  281. package/dist/src/gateway/hono/routes/discussions.js +126 -19
  282. package/dist/src/gateway/hono/routes/files.js +1 -22
  283. package/dist/src/gateway/hono/routes/index.js +0 -2
  284. package/dist/src/gateway/hono/routes/lazy-bundles.js +16 -0
  285. package/dist/src/gateway/hono/routes/mcp.js +2 -2
  286. package/dist/src/gateway/hono/routes/media.js +1 -1
  287. package/dist/src/gateway/hono/routes/models.js +5 -5
  288. package/dist/src/gateway/hono/routes/notes.js +1 -1
  289. package/dist/src/gateway/hono/routes/proactive-control.js +4 -38
  290. package/dist/src/gateway/hono/routes/project-understanding.d.ts +3 -0
  291. package/dist/src/gateway/hono/routes/project-understanding.js +65 -0
  292. package/dist/src/gateway/hono/routes/projects.js +22 -6
  293. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +2 -2
  294. package/dist/src/gateway/hono/routes/session-input-handler.js +2 -2
  295. package/dist/src/gateway/hono/routes/sessions.js +1 -1
  296. package/dist/src/gateway/hono/routes/shares.js +3 -3
  297. package/dist/src/gateway/hono/routes/site-shares.js +1 -1
  298. package/dist/src/gateway/hono/routes/status.js +1 -1
  299. package/dist/src/gateway/hono/routes/tasks.js +4 -3
  300. package/dist/src/gateway/hono/routes/user-model.js +4 -4
  301. package/dist/src/gateway/hono/routes/voice.js +3 -3
  302. package/dist/src/gateway/hono/routes/work-discovery.d.ts +0 -2
  303. package/dist/src/gateway/hono/routes/work-discovery.js +2 -16
  304. package/dist/src/gateway/hono/routes/workflows.js +2 -2
  305. package/dist/src/gateway/image-generation-setup.js +1 -1
  306. package/dist/src/gateway/lock.js +10 -10
  307. package/dist/src/gateway/media-access.js +2 -2
  308. package/dist/src/gateway/ports.js +6 -6
  309. package/dist/src/gateway/security/gateway-scopes.js +1 -2
  310. package/dist/src/gateway/server.js +1 -1
  311. package/dist/src/gateway/service/agent-runner.js +2 -2
  312. package/dist/src/gateway/service/config-coordinator.js +4 -4
  313. package/dist/src/gateway/service/marketplace-service.js +3 -3
  314. package/dist/src/gateway/service/run-gateway-agent.js +3 -3
  315. package/dist/src/gateway/service.d.ts +3 -0
  316. package/dist/src/gateway/service.js +45 -30
  317. package/dist/src/gateway/session-context-summary.js +2 -2
  318. package/dist/src/gateway/workspace-editor-path.js +2 -2
  319. package/dist/src/infra/gateway-processes.js +2 -2
  320. package/dist/src/infra/path-env.js +3 -3
  321. package/dist/src/infra/update-global.js +10 -10
  322. package/dist/src/knowledge-memory/repository.d.ts +2 -0
  323. package/dist/src/knowledge-memory/repository.js +3 -1
  324. package/dist/src/local-apps/service.js +1 -1
  325. package/dist/src/media-shared/http/ssrf-guard.js +1 -1
  326. package/dist/src/media-understanding/audio-transcription-runner.d.ts +1 -0
  327. package/dist/src/media-understanding/audio-transcription-runner.js +1 -0
  328. package/dist/src/media-understanding/runner.js +1 -0
  329. package/dist/src/media-understanding/types.d.ts +8 -0
  330. package/dist/src/notes/agent-context.js +3 -3
  331. package/dist/src/notes/service.d.ts +3 -1
  332. package/dist/src/notes/store.d.ts +3 -1
  333. package/dist/src/notes/store.js +5 -3
  334. package/dist/src/proactive/actions/service.js +1 -1
  335. package/dist/src/proactive/execution/agent-executor.js +1 -1
  336. package/dist/src/proactive/execution/context.js +1 -1
  337. package/dist/src/proactive/execution/worker.js +1 -1
  338. package/dist/src/proactive/experience.d.ts +38 -21
  339. package/dist/src/proactive/experience.js +109 -13
  340. package/dist/src/proactive/follow-ups.js +1 -1
  341. package/dist/src/proactive/inbox/cards.js +9 -3
  342. package/dist/src/proactive/inbox/delivery.js +2 -2
  343. package/dist/src/proactive/inbox/digest.js +2 -2
  344. package/dist/src/proactive/inbox/notification-policy.js +1 -1
  345. package/dist/src/proactive/inbox/repository.js +1 -1
  346. package/dist/src/proactive/inbox/service.js +1 -1
  347. package/dist/src/proactive/index.js +1 -1
  348. package/dist/src/proactive/maintenance.js +0 -1
  349. package/dist/src/proactive/scenarios/control.d.ts +0 -35
  350. package/dist/src/proactive/scenarios/control.js +7 -53
  351. package/dist/src/proactive/temporal/worker.js +1 -1
  352. package/dist/src/projects/project-skill-service.js +2 -2
  353. package/dist/src/projects/project-store.js +6 -4
  354. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  355. package/dist/src/providers/index.js +2 -2
  356. package/dist/src/providers/model-reference-auditor.js +1 -1
  357. package/dist/src/providers/model-registry.js +1 -1
  358. package/dist/src/providers/register-runtime-providers.js +1 -1
  359. package/dist/src/remote-access/exposure-manager.js +1 -1
  360. package/dist/src/session/client-history.d.ts +1 -1
  361. package/dist/src/session/client-history.js +1 -1
  362. package/dist/src/session/index.js +2 -2
  363. package/dist/src/session/session-title.js +1 -1
  364. package/dist/src/session/store.js +3 -3
  365. package/dist/src/session/thinking-resolve.js +1 -1
  366. package/dist/src/share/share-store.js +1 -1
  367. package/dist/src/share/share-thumbnail.js +1 -1
  368. package/dist/src/share/site-share-store.js +1 -1
  369. package/dist/src/storage/sqlite/connection.js +9 -9
  370. package/dist/src/storage/sqlite/migrations/166_remove_proactive_previews.sql +1 -0
  371. package/dist/src/storage/sqlite/migrations/167_discussion_recording_chunks.sql +7 -0
  372. package/dist/src/storage/sqlite/migrations/168_discussion_evidence.sql +16 -0
  373. package/dist/src/storage/sqlite/migrations/169_discussion_action_tasks.sql +8 -0
  374. package/dist/src/storage/sqlite/migrations/170_discussion_edits.sql +8 -0
  375. package/dist/src/storage/sqlite/migrations/171_discussion_recording_jobs.sql +14 -0
  376. package/dist/src/storage/sqlite/migrations/172_project_understanding.sql +3 -0
  377. package/dist/src/storage/sqlite/migrations/173_mobile_discussion_source.sql +5 -0
  378. package/dist/src/storage/sqlite/migrations/__tests__/mobile-discussion-source.test.ts +20 -0
  379. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  380. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  381. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  382. package/dist/src/storage/sqlite/schema.js +1 -1
  383. package/dist/src/tasks/execution-context.js +1 -1
  384. package/dist/src/tasks/home-query-service.js +3 -3
  385. package/dist/src/tasks/index.js +3 -3
  386. package/dist/src/tasks/task-application-service.js +1 -1
  387. package/dist/src/tasks/task-context-assembler.js +1 -1
  388. package/dist/src/tasks/task-handoff-service.js +1 -1
  389. package/dist/src/tasks/task-repository.d.ts +1 -0
  390. package/dist/src/tasks/task-repository.js +4 -0
  391. package/dist/src/tasks/task-run-coordinator.js +1 -1
  392. package/dist/src/tui/backends/embedded-backend.js +9 -9
  393. package/dist/src/tui/backends/gateway-realtime-backend.js +2 -2
  394. package/dist/src/tui/extension-host/load-extensions.js +1 -1
  395. package/dist/src/tui/tui-command-formatters.js +1 -1
  396. package/dist/src/tui/tui-commands.js +5 -5
  397. package/dist/src/tui/tui-picker-overlay.js +1 -1
  398. package/dist/src/tui/tui-skills-autocomplete.js +1 -1
  399. package/dist/src/tui/tui-startup-resources.js +5 -5
  400. package/dist/src/tui/tui.js +3 -3
  401. package/dist/src/tunnel/gateway-lifecycle.js +1 -1
  402. package/dist/src/voice/audio/normalize.js +28 -5
  403. package/dist/src/voice/local/runtime-worker.js +1 -1
  404. package/dist/src/voice/realtime/memory-context.js +3 -3
  405. package/dist/src/voice/stt/index.js +2 -2
  406. package/dist/src/voice/stt/preflight.js +1 -1
  407. package/dist/src/voice/stt/providers/local-transcription.js +1 -1
  408. package/dist/src/voice/stt/transcribe-core.js +2 -1
  409. package/dist/src/voice/stt/types.d.ts +1 -0
  410. package/dist/src/voice/tts/index.js +3 -3
  411. package/dist/src/voice/tts/merge-config.js +1 -1
  412. package/dist/src/voice/tts/payload.js +1 -1
  413. package/dist/src/voice/tts/speak-core.js +1 -1
  414. package/dist/src/work-discovery/analyzer.d.ts +1 -0
  415. package/dist/src/work-discovery/analyzer.js +28 -22
  416. package/dist/src/work-discovery/index.js +3 -3
  417. package/dist/src/work-discovery/investigator.js +1 -1
  418. package/dist/src/work-discovery/project-understanding.d.ts +7 -0
  419. package/dist/src/work-discovery/project-understanding.js +38 -0
  420. package/dist/src/work-discovery/repository.d.ts +3 -1
  421. package/dist/src/work-discovery/repository.js +18 -5
  422. package/dist/src/work-discovery/service.d.ts +8 -0
  423. package/dist/src/work-discovery/service.js +165 -17
  424. package/dist/src/work-discovery/types.d.ts +2 -0
  425. package/dist/src/workflows/context/workflow-context.js +1 -1
  426. package/dist/src/workflows/service/workflow-run-service.js +6 -6
  427. package/dist/src/workflows/service/workflow-session-bridge.js +1 -1
  428. package/package.json +4 -4
  429. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-DAqn5DdL.js +0 -1
  430. package/dist/gateway/static/root/assets/apps-page-Bh0etSmL.js +0 -1
  431. package/dist/gateway/static/root/assets/attachment-process-heavy-C5CqLCsX.js +0 -5
  432. package/dist/gateway/static/root/assets/automations-workspace-w4McwueN.js +0 -6
  433. package/dist/gateway/static/root/assets/binary-plugins-CfUzu1Hc.js +0 -2
  434. package/dist/gateway/static/root/assets/browser-settings-page-DxX-t0EJ.js +0 -8
  435. package/dist/gateway/static/root/assets/capabilities-settings-panel-CvacBQAI.js +0 -8
  436. package/dist/gateway/static/root/assets/channels-settings-CJzSNo-L.js +0 -1
  437. package/dist/gateway/static/root/assets/config-reload-event-Bjh2zoKp.js +0 -1
  438. package/dist/gateway/static/root/assets/connectors-page-DNBhBT0b.js +0 -2
  439. package/dist/gateway/static/root/assets/device-pairing-wizard-DsR5NQEC.js +0 -1
  440. package/dist/gateway/static/root/assets/excel-worksheet-utils-DpdB-DjU.js +0 -1
  441. package/dist/gateway/static/root/assets/gateway-config-swr-cd8owslz.js +0 -1
  442. package/dist/gateway/static/root/assets/gateway-settings-Dil7vuyU.js +0 -1
  443. package/dist/gateway/static/root/assets/home-page-Dpeuvazg.js +0 -1
  444. package/dist/gateway/static/root/assets/index-B03HGpzt.js +0 -74
  445. package/dist/gateway/static/root/assets/index-CXF9-BpI.css +0 -2
  446. package/dist/gateway/static/root/assets/local-app-workbench-page-CBtemxFc.js +0 -2
  447. package/dist/gateway/static/root/assets/messages-BGc-OJWg.js +0 -3
  448. package/dist/gateway/static/root/assets/note-detail-panel-GOxomxOK.js +0 -5
  449. package/dist/gateway/static/root/assets/notes-page-CQBgk3_x.js +0 -8
  450. package/dist/gateway/static/root/assets/pdf.worker.min-CHFwMXne.mjs +0 -29
  451. package/dist/gateway/static/root/assets/proactive-card-BF3741px.js +0 -1
  452. package/dist/gateway/static/root/assets/proactive-page-1EM217v0.js +0 -1
  453. package/dist/gateway/static/root/assets/project-detail-page-B1Pm-fQd.js +0 -1
  454. package/dist/gateway/static/root/assets/projects-page-DUrxyNR9.js +0 -1
  455. package/dist/gateway/static/root/assets/remote-access-hub-C6VzHUwx.js +0 -1
  456. package/dist/gateway/static/root/assets/sessions-page-DtemHfkv.js +0 -1
  457. package/dist/gateway/static/root/assets/settings-advanced-gate-ByEVQe0c.js +0 -1
  458. package/dist/gateway/static/root/assets/setup-status-panel-BwIrk3Jx.js +0 -1
  459. package/dist/gateway/static/root/assets/share-preview-page-DAc_FkVc.js +0 -2
  460. package/dist/gateway/static/root/assets/shares-settings-Dwdv6QyS.js +0 -1
  461. package/dist/gateway/static/root/assets/skill-reload-api-CDzQWN60.js +0 -1
  462. package/dist/gateway/static/root/assets/skills-page-CO5XVkbY.js +0 -2
  463. package/dist/gateway/static/root/assets/task-detail-page-DOjcKOzQ.js +0 -1
  464. package/dist/gateway/static/root/assets/tunnel-api-BABuQvGD.js +0 -1
  465. package/dist/gateway/static/root/assets/user-model-page-UhsSP_pC.js +0 -2
  466. package/dist/gateway/static/root/assets/workflows-page-S7XXNBWa.js +0 -6
  467. package/dist/src/proactive/scenarios/preview.d.ts +0 -13
  468. package/dist/src/proactive/scenarios/preview.js +0 -81
@@ -0,0 +1,219 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { DISCUSSION_MAX_DURATION_MS } from "../../packages/gateway-contract/src/discussions.js";
3
+ import { decodeWavToMonoFloat32 } from "../voice/local/wav.js";
4
+ import { forEachNormalizedAudioSegment } from "../voice/audio/normalize.js";
5
+ import { DiscussionServiceError } from "./errors.js";
6
+ import { getDiscussionCapture, updateDiscussionCapture } from "./repository.js";
7
+ import { resolveNoteMediaDir } from "../notes/paths.js";
8
+ import { join } from "node:path";
9
+ import { createReadStream } from "node:fs";
10
+ import { createHash, randomUUID } from "node:crypto";
11
+ import { mkdir, open, rename, rm } from "node:fs/promises";
12
+ //#region src/discussions/recording.ts
13
+ const invalid = (message) => new DiscussionServiceError("invalid_input", message);
14
+ const directory = (capture) => join(resolveNoteMediaDir(capture.noteId), ".recording");
15
+ function listRecordingChunks(id) {
16
+ return getSqliteDatabase().prepare("SELECT sequence, sha256, bytes FROM discussion_recording_chunks WHERE discussion_id = ? ORDER BY sequence").all(id);
17
+ }
18
+ /** Called under the discussion mutation queue. Receipt follows durable disk write. */
19
+ async function saveRecordingChunk(capture, sequence, sha256, body) {
20
+ if (!Number.isSafeInteger(sequence) || sequence < 0 || sequence > 1e4 || !/^[a-f0-9]{64}$/.test(sha256)) throw invalid("Invalid recording chunk metadata");
21
+ const chunks = listRecordingChunks(capture.id);
22
+ const existing = chunks.find((chunk) => chunk.sequence === sequence);
23
+ if (existing) {
24
+ await body.cancel();
25
+ if (existing.sha256 !== sha256) throw new DiscussionServiceError("conflict", "Chunk already contains different audio");
26
+ return existing;
27
+ }
28
+ const root = directory(capture);
29
+ await mkdir(root, { recursive: true });
30
+ const temporary = join(root, `${sequence}-${randomUUID()}.tmp`);
31
+ const file = await open(temporary, "wx");
32
+ const reader = body.getReader();
33
+ const hash = createHash("sha256");
34
+ let bytes = 0;
35
+ const previousBytes = chunks.reduce((total, chunk) => total + chunk.bytes, 0);
36
+ try {
37
+ while (true) {
38
+ const next = await reader.read();
39
+ if (next.done) break;
40
+ bytes += next.value.byteLength;
41
+ if (bytes > 8388608 || previousBytes + bytes > 1073741824) throw invalid("Recording exceeds upload size limit");
42
+ hash.update(next.value);
43
+ await file.writeFile(next.value);
44
+ }
45
+ if (!bytes || hash.digest("hex") !== sha256) throw invalid("Recording chunk checksum mismatch");
46
+ await file.sync();
47
+ await file.close();
48
+ const latest = getDiscussionCapture(capture.id);
49
+ if (!latest || latest.status === "cancelled" || latest.audioDeletedAt) throw new DiscussionServiceError("conflict", "Recording was deleted or cancelled");
50
+ await rename(temporary, join(root, String(sequence)));
51
+ const dir = await open(root, "r");
52
+ try {
53
+ await dir.sync();
54
+ } finally {
55
+ await dir.close();
56
+ }
57
+ getSqliteDatabase().prepare("INSERT INTO discussion_recording_chunks (discussion_id, sequence, sha256, bytes) VALUES (?, ?, ?, ?)").run(capture.id, sequence, sha256, bytes);
58
+ return {
59
+ sequence,
60
+ sha256,
61
+ bytes
62
+ };
63
+ } finally {
64
+ await reader.cancel().catch(() => void 0);
65
+ await file.close().catch(() => void 0);
66
+ await rm(temporary, { force: true });
67
+ }
68
+ }
69
+ function validateRecordingManifest(capture, input) {
70
+ if (input.containerMode !== void 0 && (input.containerMode !== "independent_wav" || input.mimeType !== "audio/wav")) throw invalid("Unsupported recording container");
71
+ const mimeType = input.mimeType.split(";")[0];
72
+ if (![
73
+ "audio/wav",
74
+ "audio/x-wav",
75
+ "audio/webm",
76
+ "audio/ogg",
77
+ "audio/mpeg",
78
+ "audio/mp4"
79
+ ].includes(mimeType)) throw invalid("Unsupported audio type");
80
+ const chunks = listRecordingChunks(capture.id);
81
+ if (!Number.isSafeInteger(input.chunkCount) || input.chunkCount < 1 || chunks.length !== input.chunkCount || chunks.some((chunk, index) => chunk.sequence !== index)) throw invalid("Recording has missing chunks");
82
+ }
83
+ async function completeRecording(capture, input, notes, assertLease, signal) {
84
+ validateRecordingManifest(capture, input);
85
+ const chunks = listRecordingChunks(capture.id);
86
+ const target = join(directory(capture), `assembled-${randomUUID()}`);
87
+ const file = await open(target, "w");
88
+ const hash = createHash("sha256");
89
+ try {
90
+ try {
91
+ if (input.containerMode === "independent_wav") {
92
+ const samples = chunks.reduce((total, chunk) => total + (chunk.bytes - 44) / 2, 0);
93
+ if (!Number.isSafeInteger(samples) || samples <= 0 || samples > 16e3 * 72e5 / 1e3) throw invalid("Invalid PCM recording duration");
94
+ const header = recordingWavHeader(samples);
95
+ hash.update(header);
96
+ await file.writeFile(header);
97
+ }
98
+ for (const chunk of chunks) {
99
+ const chunkHash = createHash("sha256");
100
+ let bytes = 0;
101
+ try {
102
+ if (input.containerMode === "independent_wav") {
103
+ signal.throwIfAborted();
104
+ const buffers = [];
105
+ for await (const buffer of createReadStream(join(directory(capture), String(chunk.sequence)))) {
106
+ signal.throwIfAborted();
107
+ bytes += buffer.length;
108
+ if (bytes > 8388608 || bytes > chunk.bytes) throw invalid("Stored WAV chunk is corrupt");
109
+ buffers.push(buffer);
110
+ }
111
+ const data = Buffer.concat(buffers, bytes);
112
+ 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");
113
+ hash.update(data.subarray(44));
114
+ await file.writeFile(data.subarray(44));
115
+ continue;
116
+ }
117
+ for await (const buffer of createReadStream(join(directory(capture), String(chunk.sequence)))) {
118
+ signal.throwIfAborted();
119
+ hash.update(buffer);
120
+ chunkHash.update(buffer);
121
+ bytes += buffer.length;
122
+ await file.writeFile(buffer);
123
+ }
124
+ if (bytes !== chunk.bytes || chunkHash.digest("hex") !== chunk.sha256) throw invalid("Stored recording chunk is corrupt");
125
+ } catch (error) {
126
+ if (!signal.aborted && (error instanceof DiscussionServiceError || error.code === "ENOENT")) {
127
+ assertLease();
128
+ getSqliteDatabase().prepare("DELETE FROM discussion_recording_chunks WHERE discussion_id=? AND sequence=?").run(capture.id, chunk.sequence);
129
+ }
130
+ throw error;
131
+ }
132
+ }
133
+ await file.sync();
134
+ } finally {
135
+ await file.close();
136
+ }
137
+ let durationMs = 0;
138
+ await forEachNormalizedAudioSegment({
139
+ filePath: target,
140
+ segmentSeconds: 30,
141
+ maxDurationSeconds: DISCUSSION_MAX_DURATION_MS / 1e3,
142
+ signal: AbortSignal.any([signal, AbortSignal.timeout(18e4)])
143
+ }, async (buffer) => {
144
+ durationMs += decodeWavToMonoFloat32(buffer).durationSeconds * 1e3;
145
+ });
146
+ if (durationMs < 1e3) throw invalid("Recording must contain at least one second of audio");
147
+ const latest = getDiscussionCapture(capture.id);
148
+ if (!latest || latest.status === "cancelled" || latest.audioDeletedAt) throw new DiscussionServiceError("conflict", "Recording was deleted or cancelled");
149
+ assertLease();
150
+ const audioSha256 = hash.digest("hex");
151
+ const attachment = await notes.addAttachment(capture.noteId, {
152
+ name: input.fileName.trim().slice(0, 200) || "meeting.webm",
153
+ buffer: { filePath: target },
154
+ mimeType: input.mimeType,
155
+ duration: Math.round(durationMs / 1e3),
156
+ retainWithoutReference: true
157
+ }, `meeting-recording:${capture.id}`);
158
+ if (!attachment) throw new DiscussionServiceError("not_found", "Discussion note not found");
159
+ const persisted = await notes.getAttachmentPath(capture.noteId, attachment.id);
160
+ if (!persisted) throw new Error("Saved recording file is missing");
161
+ const savedFile = await open(persisted.filePath, "r");
162
+ try {
163
+ await savedFile.sync();
164
+ } finally {
165
+ await savedFile.close();
166
+ }
167
+ const savedDirectory = await open(resolveNoteMediaDir(capture.noteId), "r");
168
+ try {
169
+ await savedDirectory.sync();
170
+ } finally {
171
+ await savedDirectory.close();
172
+ }
173
+ const updated = runSqliteWriteTransaction(() => {
174
+ assertLease();
175
+ return updateDiscussionCapture(capture.id, {
176
+ status: "stopping",
177
+ audioAttachmentId: attachment.id,
178
+ mimeType: input.mimeType,
179
+ audioSha256,
180
+ durationMs: Math.round(durationMs),
181
+ audioSizeBytes: chunks.reduce((sum, chunk) => sum + chunk.bytes, 0)
182
+ }, [
183
+ "recording",
184
+ "stopping",
185
+ "needs_attention"
186
+ ]);
187
+ });
188
+ if (!updated) throw new DiscussionServiceError("conflict", "Recording changed while completing");
189
+ await removeRecordingChunks(capture);
190
+ return updated;
191
+ } finally {
192
+ await rm(target, { force: true });
193
+ }
194
+ }
195
+ function recordingWavHeader(samples) {
196
+ const header = Buffer.alloc(44);
197
+ header.write("RIFF");
198
+ header.writeUInt32LE(36 + samples * 2, 4);
199
+ header.write("WAVEfmt ", 8);
200
+ header.writeUInt32LE(16, 16);
201
+ header.writeUInt16LE(1, 20);
202
+ header.writeUInt16LE(1, 22);
203
+ header.writeUInt32LE(16e3, 24);
204
+ header.writeUInt32LE(32e3, 28);
205
+ header.writeUInt16LE(2, 32);
206
+ header.writeUInt16LE(16, 34);
207
+ header.write("data", 36);
208
+ header.writeUInt32LE(samples * 2, 40);
209
+ return header;
210
+ }
211
+ async function removeRecordingChunks(capture) {
212
+ await rm(directory(capture), {
213
+ recursive: true,
214
+ force: true
215
+ });
216
+ getSqliteDatabase().prepare("DELETE FROM discussion_recording_chunks WHERE discussion_id = ?").run(capture.id);
217
+ }
218
+ //#endregion
219
+ export { completeRecording, listRecordingChunks, removeRecordingChunks, saveRecordingChunk, validateRecordingManifest };
@@ -0,0 +1,14 @@
1
+ import type { DiscussionRecordingJob, DiscussionRecordingManifest } from '@xopcai/gateway-contract';
2
+ export declare function getRecordingJob(discussionId: string): DiscussionRecordingJob | null;
3
+ export declare function getRecordingJobInput(discussionId: string): DiscussionRecordingManifest | null;
4
+ export declare function submitRecordingJob(discussionId: string, input: DiscussionRecordingManifest): DiscussionRecordingJob;
5
+ export declare function claimRecordingJob(owner: string, now?: number): {
6
+ input: DiscussionRecordingManifest;
7
+ id: string;
8
+ discussionId: string;
9
+ state: "queued" | "running" | "completed" | "failed" | "cancelled";
10
+ error?: string;
11
+ };
12
+ export declare function renewRecordingJob(id: string, owner: string, now?: number): boolean;
13
+ export declare function finishRecordingJob(id: string, owner: string, error?: string): boolean;
14
+ export declare function cancelRecordingJob(discussionId: string): void;
@@ -0,0 +1,69 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { DiscussionServiceError } from "./errors.js";
3
+ import { listRecordingChunks } from "./recording.js";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ //#region src/discussions/recordingJobs.ts
6
+ const LEASE_MS = 6e4;
7
+ function mapJob(row) {
8
+ return {
9
+ id: row.id,
10
+ discussionId: row.discussion_id,
11
+ state: row.state,
12
+ ...row.error ? { error: row.error } : {}
13
+ };
14
+ }
15
+ function getRecordingJob(discussionId) {
16
+ const row = getSqliteDatabase().prepare("SELECT * FROM discussion_recording_jobs WHERE discussion_id=?").get(discussionId);
17
+ return row ? mapJob(row) : null;
18
+ }
19
+ function getRecordingJobInput(discussionId) {
20
+ const row = getSqliteDatabase().prepare("SELECT input_json FROM discussion_recording_jobs WHERE discussion_id=?").get(discussionId);
21
+ return row ? JSON.parse(row.input_json) : null;
22
+ }
23
+ function submitRecordingJob(discussionId, input) {
24
+ const inputJson = JSON.stringify({
25
+ chunkCount: input.chunkCount,
26
+ mimeType: input.mimeType,
27
+ fileName: input.fileName,
28
+ lastSequence: input.lastSequence,
29
+ ...input.containerMode ? { containerMode: input.containerMode } : {}
30
+ });
31
+ return runSqliteWriteTransaction((db) => {
32
+ const chunks = listRecordingChunks(discussionId);
33
+ const manifestHash = createHash("sha256").update(inputJson).update(JSON.stringify(chunks)).digest("hex");
34
+ const existing = db.prepare("SELECT * FROM discussion_recording_jobs WHERE discussion_id=?").get(discussionId);
35
+ if (existing && existing.input_json !== inputJson) throw new DiscussionServiceError("conflict", "Recording manifest differs from the submitted recording");
36
+ if (existing && chunks.length && existing.manifest_hash !== manifestHash) throw new DiscussionServiceError("conflict", "Recording contents differ from the submitted manifest");
37
+ if (existing?.state === "cancelled") throw new DiscussionServiceError("conflict", "Recording job was cancelled");
38
+ if (existing?.state === "failed") db.prepare("UPDATE discussion_recording_jobs SET state='queued', error=NULL, updated_at=? WHERE id=?").run(Date.now(), existing.id);
39
+ if (!existing) db.prepare("INSERT INTO discussion_recording_jobs (id, discussion_id, input_json, manifest_hash, state, created_at, updated_at) VALUES (?, ?, ?, ?, 'queued', ?, ?)").run(randomUUID(), discussionId, inputJson, manifestHash, Date.now(), Date.now());
40
+ return getRecordingJob(discussionId);
41
+ });
42
+ }
43
+ function claimRecordingJob(owner, now = Date.now()) {
44
+ return runSqliteWriteTransaction((db) => {
45
+ const row = db.prepare(`SELECT j.* FROM discussion_recording_jobs j JOIN discussion_captures d ON d.id=j.discussion_id
46
+ WHERE (j.state='queued' OR (j.state='running' AND j.lease_until<=?))
47
+ AND (d.status IN ('recording','stopping','needs_attention') OR d.audio_attachment_id IS NOT NULL)
48
+ AND d.status<>'cancelled' AND d.audio_deleted_at IS NULL
49
+ ORDER BY j.created_at LIMIT 1`).get(now);
50
+ if (!row) return null;
51
+ db.prepare("UPDATE discussion_recording_jobs SET state='running', lease_owner=?, lease_until=?, updated_at=? WHERE id=?").run(owner, now + LEASE_MS, now, row.id);
52
+ return {
53
+ ...mapJob(row),
54
+ input: JSON.parse(row.input_json)
55
+ };
56
+ });
57
+ }
58
+ function renewRecordingJob(id, owner, now = Date.now()) {
59
+ return getSqliteDatabase().prepare("UPDATE discussion_recording_jobs SET lease_until=? WHERE id=? AND state='running' AND lease_owner=? AND lease_until>?").run(now + LEASE_MS, id, owner, now).changes === 1;
60
+ }
61
+ function finishRecordingJob(id, owner, error) {
62
+ return getSqliteDatabase().prepare(`UPDATE discussion_recording_jobs SET state=?, error=?, lease_owner=NULL, lease_until=NULL, updated_at=?
63
+ WHERE id=? AND state='running' AND lease_owner=? AND lease_until>?`).run(error === void 0 ? "completed" : "failed", error?.slice(0, 1e3) ?? null, Date.now(), id, owner, Date.now()).changes === 1;
64
+ }
65
+ function cancelRecordingJob(discussionId) {
66
+ getSqliteDatabase().prepare("UPDATE discussion_recording_jobs SET state='cancelled', lease_owner=NULL, lease_until=NULL, updated_at=? WHERE discussion_id=? AND state<>'completed'").run(Date.now(), discussionId);
67
+ }
68
+ //#endregion
69
+ export { cancelRecordingJob, claimRecordingJob, finishRecordingJob, getRecordingJob, getRecordingJobInput, renewRecordingJob, submitRecordingJob };
@@ -10,7 +10,7 @@ export declare function listDiscussionCaptures(query?: ListDiscussionsQuery): Di
10
10
  export declare function getDiscussionMetrics(): DiscussionMetrics;
11
11
  export declare function updateDiscussionCapture(id: string, patch: Partial<Omit<DiscussionCapture, 'id' | 'clientRequestId' | 'noteId' | 'createdAt'>>, expectedStatuses?: DiscussionStatus[]): DiscussionCapture | null;
12
12
  export declare function claimNextDiscussionCapture(owner: string, now?: number, leaseMs?: number): DiscussionCapture | null;
13
- export declare function releaseDiscussionWorkLease(id: string): void;
13
+ export declare function releaseDiscussionWorkLease(id: string, owner: string): void;
14
14
  export declare function createDiscussionTranscriptSegment(input: {
15
15
  discussionId: string;
16
16
  sequence: number;
@@ -24,7 +24,7 @@ export declare function getDiscussionTranscriptSegment(discussionId: string, seq
24
24
  export declare function listDiscussionTranscriptSegments(discussionId: string): DiscussionTranscriptSegment[];
25
25
  export declare function claimNextDiscussionTranscriptSegment(owner: string, now?: number, leaseMs?: number): ClaimedDiscussionTranscriptSegment | null;
26
26
  export declare function completeDiscussionTranscriptSegment(discussionId: string, sequence: number, owner: string, text: string, provider: string, language?: string, confidence?: number): DiscussionTranscriptSegment | null;
27
- export declare function correctDiscussionTranscriptSegment(discussionId: string, sequence: number, displayText: string, expectedRevision: number): DiscussionTranscriptSegment | null;
27
+ export declare function correctDiscussionTranscriptSegment(discussionId: string, sequence: number, displayText: string, expectedRevision: number, speakerLabel?: string): DiscussionTranscriptSegment | null;
28
28
  export declare function retryOrFailDiscussionTranscriptSegment(discussionId: string, sequence: number, owner: string, error: string, exhausted: boolean): DiscussionTranscriptSegment | null;
29
29
  export declare function deleteDiscussionSegmentAudio(discussionId: string): void;
30
30
  export declare function createDiscussionOrganization(input: {
@@ -32,9 +32,11 @@ export declare function createDiscussionOrganization(input: {
32
32
  inputTranscriptSha256: string;
33
33
  promptVersion: string;
34
34
  modelRef: string;
35
+ transcriptRevision: number;
35
36
  }): DiscussionOrganizationRecord;
36
37
  export declare function getLatestDiscussionOrganization(discussionId: string): DiscussionOrganizationRecord | null;
37
38
  export declare function completeDiscussionOrganization(id: string, organization: DiscussionOrganization): DiscussionOrganizationRecord | null;
38
39
  export declare function failDiscussionOrganization(id: string, error: string): void;
39
40
  export declare function getDiscussionCaptureSettings(): DiscussionCaptureSettings;
40
41
  export declare function acknowledgeDiscussionCaptureConsent(policyVersion: number): DiscussionCaptureSettings;
42
+ export declare function renewDiscussionWorkLease(id: string, owner: string): boolean;
@@ -1,5 +1,5 @@
1
1
  import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
- import { DISCUSSION_STATUSES } from "./types.js";
2
+ import { DISCUSSION_STATUSES } from "../../packages/gateway-contract/src/discussions.js";
3
3
  import { randomUUID } from "node:crypto";
4
4
  //#region src/discussions/repository.ts
5
5
  function optional(condition, value) {
@@ -17,6 +17,7 @@ function discussionFromRow(row) {
17
17
  createdAt: row.created_at,
18
18
  updatedAt: row.updated_at
19
19
  };
20
+ capture.template = row.organization_template;
20
21
  if (row.project_id) capture.projectId = row.project_id;
21
22
  if (row.audio_attachment_id) capture.audioAttachmentId = row.audio_attachment_id;
22
23
  if (row.duration_ms != null) capture.durationMs = row.duration_ms;
@@ -71,6 +72,7 @@ function organizationFromRow(row) {
71
72
  revision: row.revision,
72
73
  inputTranscriptSha256: row.input_transcript_sha256,
73
74
  promptVersion: row.prompt_version,
75
+ transcriptRevision: row.transcript_revision,
74
76
  modelRef: row.model_ref,
75
77
  ...optional(row.organization_json, { organization: JSON.parse(row.organization_json) }),
76
78
  status: row.status,
@@ -161,7 +163,7 @@ function updateDiscussionCapture(id, patch, expectedStatuses) {
161
163
  mime_type=?, audio_size_bytes=?, audio_sha256=?, canonical_transcript=?, canonical_transcript_sha256=?,
162
164
  transcript_language=?, transcript_revision=?, generated_title=?, project_inference_score=?,
163
165
  project_inference_source=?, failure_stage=?, failure_code=?, failure_message=?, recording_started_at=?,
164
- recording_stopped_at=?, updated_at=?, completed_at=?, audio_deleted_at=? WHERE id=?${statusGuard}`).run(next.projectId ?? null, next.audioAttachmentId ?? null, next.source, next.status, next.durationMs ?? null, next.expectedLastSequence ?? null, next.mimeType ?? null, next.audioSizeBytes ?? null, next.audioSha256 ?? null, next.canonicalTranscript ?? null, next.canonicalTranscriptSha256 ?? null, next.transcriptLanguage ?? null, next.transcriptRevision, next.generatedTitle ?? null, next.projectInferenceScore ?? null, next.projectInferenceSource ?? null, next.failureStage ?? null, next.failureCode ?? null, next.failureMessage ?? null, next.recordingStartedAt, next.recordingStoppedAt ?? null, next.updatedAt, next.completedAt ?? null, next.audioDeletedAt ?? null, id, ...expectedStatuses ?? []).changes) ? getDiscussionCapture(id) : null;
166
+ recording_stopped_at=?, updated_at=?, completed_at=?, audio_deleted_at=?, organization_template=? WHERE id=?${statusGuard}`).run(next.projectId ?? null, next.audioAttachmentId ?? null, next.source, next.status, next.durationMs ?? null, next.expectedLastSequence ?? null, next.mimeType ?? null, next.audioSizeBytes ?? null, next.audioSha256 ?? null, next.canonicalTranscript ?? null, next.canonicalTranscriptSha256 ?? null, next.transcriptLanguage ?? null, next.transcriptRevision, next.generatedTitle ?? null, next.projectInferenceScore ?? null, next.projectInferenceSource ?? null, next.failureStage ?? null, next.failureCode ?? null, next.failureMessage ?? null, next.recordingStartedAt, next.recordingStoppedAt ?? null, next.updatedAt, next.completedAt ?? null, next.audioDeletedAt ?? null, next.template ?? "general", id, ...expectedStatuses ?? []).changes) ? getDiscussionCapture(id) : null;
165
167
  }
166
168
  function claimNextDiscussionCapture(owner, now = Date.now(), leaseMs = 5 * 6e4) {
167
169
  return runSqliteWriteTransaction((db) => {
@@ -173,8 +175,8 @@ function claimNextDiscussionCapture(owner, now = Date.now(), leaseMs = 5 * 6e4)
173
175
  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;
174
176
  });
175
177
  }
176
- function releaseDiscussionWorkLease(id) {
177
- runSqliteWriteTransaction((db) => db.prepare("UPDATE discussion_captures SET work_lease_owner=NULL, work_lease_expires_at=NULL WHERE id=?").run(id));
178
+ function releaseDiscussionWorkLease(id, owner) {
179
+ runSqliteWriteTransaction((db) => db.prepare("UPDATE discussion_captures SET work_lease_owner=NULL, work_lease_expires_at=NULL WHERE id=? AND work_lease_owner=?").run(id, owner));
178
180
  }
179
181
  function createDiscussionTranscriptSegment(input) {
180
182
  const existing = getDiscussionTranscriptSegment(input.discussionId, input.sequence);
@@ -228,12 +230,12 @@ function completeDiscussionTranscriptSegment(discussionId, sequence, owner, text
228
230
  return changed;
229
231
  }) ? getDiscussionTranscriptSegment(discussionId, sequence) : null;
230
232
  }
231
- function correctDiscussionTranscriptSegment(discussionId, sequence, displayText, expectedRevision) {
233
+ function correctDiscussionTranscriptSegment(discussionId, sequence, displayText, expectedRevision, speakerLabel) {
232
234
  const now = Date.now();
233
235
  return runSqliteWriteTransaction((db) => {
234
- const changed = db.prepare(`UPDATE discussion_transcript_segments SET display_text=?, corrected_by_user=1,
236
+ const changed = db.prepare(`UPDATE discussion_transcript_segments SET display_text=?, speaker_label=CASE WHEN ? THEN ? ELSE speaker_label END, corrected_by_user=1,
235
237
  corrected_at=?, revision=revision+1, updated_at=? WHERE discussion_id=? AND sequence=?
236
- AND status='confirmed' AND revision=?`).run(displayText, now, now, discussionId, sequence, expectedRevision).changes;
238
+ AND status='confirmed' AND revision=?`).run(displayText, speakerLabel === void 0 ? 0 : 1, speakerLabel?.trim() || null, now, now, discussionId, sequence, expectedRevision).changes;
237
239
  if (changed) db.prepare("UPDATE discussion_captures SET transcript_revision=transcript_revision+1, updated_at=? WHERE id=?").run(now, discussionId);
238
240
  return changed;
239
241
  }) ? getDiscussionTranscriptSegment(discussionId, sequence) : null;
@@ -253,8 +255,8 @@ function createDiscussionOrganization(input) {
253
255
  const revision = (getLatestDiscussionOrganization(input.discussionId)?.revision ?? 0) + 1;
254
256
  const id = randomUUID();
255
257
  runSqliteWriteTransaction((db) => db.prepare(`INSERT INTO discussion_organizations (
256
- id, discussion_id, revision, input_transcript_sha256, prompt_version, model_ref, status, created_at
257
- ) VALUES (?, ?, ?, ?, ?, ?, 'running', ?)`).run(id, input.discussionId, revision, input.inputTranscriptSha256, input.promptVersion, input.modelRef, now));
258
+ id, discussion_id, revision, input_transcript_sha256, prompt_version, model_ref, transcript_revision, status, created_at
259
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, 'running', ?)`).run(id, input.discussionId, revision, input.inputTranscriptSha256, input.promptVersion, input.modelRef, input.transcriptRevision, now));
258
260
  return getLatestDiscussionOrganization(input.discussionId);
259
261
  }
260
262
  function getLatestDiscussionOrganization(discussionId) {
@@ -285,5 +287,8 @@ function acknowledgeDiscussionCaptureConsent(policyVersion) {
285
287
  SET consent_acknowledged_at=?, updated_at=? WHERE workspace_id='default' AND consent_policy_version=?`).run(now, now, policyVersion));
286
288
  return getDiscussionCaptureSettings();
287
289
  }
290
+ function renewDiscussionWorkLease(id, owner) {
291
+ return Number(getSqliteDatabase().prepare("UPDATE discussion_captures SET work_lease_expires_at=? WHERE id=? AND work_lease_owner=? AND status='organizing'").run(Date.now() + 5 * 6e4, id, owner).changes) === 1;
292
+ }
288
293
  //#endregion
289
- export { acknowledgeDiscussionCaptureConsent, claimNextDiscussionCapture, claimNextDiscussionTranscriptSegment, completeDiscussionOrganization, completeDiscussionTranscriptSegment, correctDiscussionTranscriptSegment, createDiscussionCapture, createDiscussionOrganization, createDiscussionTranscriptSegment, deleteDiscussionSegmentAudio, failDiscussionOrganization, getDiscussionCapture, getDiscussionCaptureByClientRequestId, getDiscussionCaptureByNoteId, getDiscussionCaptureSettings, getDiscussionMetrics, getDiscussionTranscriptSegment, getLatestDiscussionOrganization, listDiscussionCaptures, listDiscussionTranscriptSegments, releaseDiscussionWorkLease, retryOrFailDiscussionTranscriptSegment, updateDiscussionCapture };
294
+ export { acknowledgeDiscussionCaptureConsent, claimNextDiscussionCapture, claimNextDiscussionTranscriptSegment, completeDiscussionOrganization, completeDiscussionTranscriptSegment, correctDiscussionTranscriptSegment, createDiscussionCapture, createDiscussionOrganization, createDiscussionTranscriptSegment, deleteDiscussionSegmentAudio, failDiscussionOrganization, getDiscussionCapture, getDiscussionCaptureByClientRequestId, getDiscussionCaptureByNoteId, getDiscussionCaptureSettings, getDiscussionMetrics, getDiscussionTranscriptSegment, getLatestDiscussionOrganization, listDiscussionCaptures, listDiscussionTranscriptSegments, releaseDiscussionWorkLease, renewDiscussionWorkLease, retryOrFailDiscussionTranscriptSegment, updateDiscussionCapture };
@@ -0,0 +1,12 @@
1
+ import type { DiscussionTranscriptSegment } from './types.js';
2
+ export declare function saveTranscriptRevision(id: string): number;
3
+ export declare function readTranscriptRevision(id: string, revision: number): DiscussionTranscriptSegment[] | null;
4
+ export declare function replaceTranscriptSegments(id: string, segments: Array<{
5
+ text: string;
6
+ startedAtMs: number;
7
+ endedAtMs: number;
8
+ speakerLabel?: string;
9
+ correctedByUser?: boolean;
10
+ rawText?: string;
11
+ }>): void;
12
+ export declare function refreshCanonicalTranscript(id: string): void;
@@ -0,0 +1,36 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ import { getDiscussionCapture, listDiscussionTranscriptSegments, updateDiscussionCapture } from "./repository.js";
3
+ import { assembleDiscussionTranscript } from "./transcript.js";
4
+ import { createHash } from "node:crypto";
5
+ //#region src/discussions/revisions.ts
6
+ function saveTranscriptRevision(id) {
7
+ const capture = getDiscussionCapture(id);
8
+ if (!capture) throw new Error("Discussion not found");
9
+ getSqliteDatabase().prepare("INSERT OR IGNORE INTO discussion_transcript_revisions (discussion_id, revision, segments_json, created_at) VALUES (?, ?, ?, ?)").run(id, capture.transcriptRevision, JSON.stringify(listDiscussionTranscriptSegments(id)), Date.now());
10
+ return capture.transcriptRevision;
11
+ }
12
+ function readTranscriptRevision(id, revision) {
13
+ const row = getSqliteDatabase().prepare("SELECT segments_json FROM discussion_transcript_revisions WHERE discussion_id=? AND revision=?").get(id, revision);
14
+ return row ? JSON.parse(row.segments_json) : null;
15
+ }
16
+ function replaceTranscriptSegments(id, segments) {
17
+ runSqliteWriteTransaction((db) => {
18
+ saveTranscriptRevision(id);
19
+ db.prepare("DELETE FROM discussion_transcript_segments WHERE discussion_id=?").run(id);
20
+ const insert = db.prepare(`INSERT INTO discussion_transcript_segments
21
+ (discussion_id, sequence, audio_sha256, started_at_ms, ended_at_ms, status, raw_text, display_text, speaker_label, revision, corrected_by_user, attempt_count, created_at, updated_at)
22
+ VALUES (?, ?, ?, ?, ?, 'confirmed', ?, ?, ?, 1, ?, 0, ?, ?)`);
23
+ segments.forEach((segment, index) => insert.run(id, index, createHash("sha256").update(segment.text).digest("hex"), segment.startedAtMs, segment.endedAtMs, segment.rawText ?? segment.text, segment.text, segment.speakerLabel ?? null, segment.correctedByUser ? 1 : 0, Date.now(), Date.now()));
24
+ db.prepare("UPDATE discussion_captures SET transcript_revision=transcript_revision+1 WHERE id=?").run(id);
25
+ saveTranscriptRevision(id);
26
+ });
27
+ }
28
+ function refreshCanonicalTranscript(id) {
29
+ const text = assembleDiscussionTranscript(listDiscussionTranscriptSegments(id));
30
+ updateDiscussionCapture(id, {
31
+ canonicalTranscript: text,
32
+ canonicalTranscriptSha256: createHash("sha256").update(text).digest("hex")
33
+ });
34
+ }
35
+ //#endregion
36
+ export { readTranscriptRevision, refreshCanonicalTranscript, replaceTranscriptSegments, saveTranscriptRevision };
@@ -2,6 +2,7 @@ import type { Config } from '../config/schema.js';
2
2
  import type { NotesService } from '../notes/service.js';
3
3
  import type { DiscussionCapture } from './types.js';
4
4
  export interface DiscussionSealerDeps {
5
+ processRecordingJob?: () => Promise<void>;
5
6
  notes: NotesService;
6
7
  getConfig: () => Config;
7
8
  transcribeRecording?: (filePath: string, capture: DiscussionCapture, signal?: AbortSignal) => Promise<{
@@ -1,12 +1,16 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
+ import { decodeWavToMonoFloat32 } from "../voice/local/wav.js";
3
4
  import { forEachNormalizedAudioSegment } from "../voice/audio/normalize.js";
4
- import { isSTTAvailable } from "../voice/stt/availability.js";
5
5
  import { transcribe } from "../voice/stt/transcribe-core.js";
6
+ import { isSTTAvailable } from "../voice/stt/availability.js";
6
7
  import { mergeSttConfigFromAppConfig } from "../voice/stt/config.js";
7
8
  import "../voice/stt/index.js";
8
9
  import { getDiscussionCapture, listDiscussionCaptures, listDiscussionTranscriptSegments, updateDiscussionCapture } from "./repository.js";
9
- import { appendSequentialTranscript, assembleDiscussionTranscript } from "./transcript.js";
10
+ import { assembleDiscussionTranscript } from "./transcript.js";
11
+ import { replaceTranscriptSegments, saveTranscriptRevision } from "./revisions.js";
12
+ import { missingAudioRanges, slicePcmWav } from "./audio-repair.js";
13
+ import { getRecordingJob } from "./recordingJobs.js";
10
14
  import { createHash } from "node:crypto";
11
15
  //#region src/discussions/sealer.ts
12
16
  init_logger();
@@ -34,6 +38,7 @@ var DiscussionSealer = class {
34
38
  if (this.running) return;
35
39
  this.running = true;
36
40
  try {
41
+ await this.deps.processRecordingJob?.();
37
42
  const capture = [...listDiscussionCaptures({
38
43
  status: "sealing",
39
44
  limit: 1
@@ -50,6 +55,8 @@ var DiscussionSealer = class {
50
55
  async sealWhenReady(capture, now) {
51
56
  const ageMs = now - (capture.recordingStoppedAt ?? now);
52
57
  if (!capture.audioAttachmentId) {
58
+ const job = getRecordingJob(capture.id);
59
+ if (job && (job.state === "queued" || job.state === "running")) return;
53
60
  if (ageMs >= UPLOAD_GRACE_MS) this.fail(capture, "audio_upload", "recording_missing", "Original recording was not uploaded");
54
61
  return;
55
62
  }
@@ -65,6 +72,7 @@ var DiscussionSealer = class {
65
72
  const confirmed = segments.filter((segment) => segment.status === "confirmed");
66
73
  const result = expected >= 0 && missing.length === 0 && !pending && confirmed.length === expected + 1 ? { text: assembleDiscussionTranscript(confirmed) } : await this.transcribeFullRecording(sealing);
67
74
  const text = result.text.trim();
75
+ saveTranscriptRevision(capture.id);
68
76
  if (!text) throw new Error("Discussion transcription produced no text");
69
77
  const updated = updateDiscussionCapture(capture.id, {
70
78
  status: "organizing",
@@ -85,22 +93,64 @@ var DiscussionSealer = class {
85
93
  async transcribeFullRecording(capture) {
86
94
  const attachment = await this.deps.notes.getAttachmentPath(capture.noteId, capture.audioAttachmentId);
87
95
  if (!attachment) throw new Error("Discussion audio file is missing");
88
- if (this.deps.transcribeRecording) return this.deps.transcribeRecording(attachment.filePath, capture);
96
+ if (this.deps.transcribeRecording) {
97
+ const result = await this.deps.transcribeRecording(attachment.filePath, capture);
98
+ replaceTranscriptSegments(capture.id, [{
99
+ text: result.text,
100
+ startedAtMs: 0,
101
+ endedAtMs: capture.durationMs ?? 1e3
102
+ }]);
103
+ return result;
104
+ }
89
105
  const config = this.deps.getConfig();
90
106
  const sttConfig = mergeSttConfigFromAppConfig(config.tools?.media?.audio, config.tools?.media);
91
107
  if (!isSTTAvailable(sttConfig)) throw new Error("STT is not configured");
92
- let text = "";
108
+ let offset = 0;
109
+ const confirmed = listDiscussionTranscriptSegments(capture.id).filter((segment) => segment.status === "confirmed");
110
+ const segments = confirmed.map((segment) => ({
111
+ text: segment.displayText ?? segment.rawText ?? "",
112
+ rawText: segment.rawText,
113
+ startedAtMs: segment.startedAtMs,
114
+ endedAtMs: segment.endedAtMs,
115
+ speakerLabel: segment.speakerLabel,
116
+ correctedByUser: segment.correctedByUser
117
+ }));
93
118
  let language;
94
- await forEachNormalizedAudioSegment({ filePath: attachment.filePath }, async (buffer, index) => {
95
- const result = await transcribe(buffer, sttConfig, {
96
- mime: "audio/wav",
97
- fileName: `discussion-recovery-${index}.wav`
98
- });
99
- text = appendSequentialTranscript(text, result.text);
100
- language ??= result.language;
119
+ await forEachNormalizedAudioSegment({
120
+ filePath: attachment.filePath,
121
+ maxDurationSeconds: 7200
122
+ }, async (buffer, index) => {
123
+ const decoded = decodeWavToMonoFloat32(buffer);
124
+ const durationMs = decoded.durationSeconds * 1e3;
125
+ const gaps = missingAudioRanges(offset, offset + durationMs, confirmed);
126
+ for (const [gapIndex, gap] of gaps.entries()) {
127
+ const audio = gaps.length === 1 && gap.startedAtMs === offset && gap.endedAtMs === offset + durationMs ? buffer : slicePcmWav(decoded, gap.startedAtMs - offset, gap.endedAtMs - offset);
128
+ if (audio.length <= 44) continue;
129
+ const result = await transcribe(audio, sttConfig, {
130
+ mime: "audio/wav",
131
+ fileName: `discussion-recovery-${index}-${gapIndex}.wav`
132
+ });
133
+ if (result.segments?.length) for (const part of result.segments) {
134
+ if (!Number.isFinite(part.startMs) || !Number.isFinite(part.endMs) || part.startMs < 0 || part.endMs <= part.startMs || part.endMs > gap.endedAtMs - gap.startedAtMs + 100) throw new Error("Provider returned an invalid transcript time range");
135
+ segments.push({
136
+ text: part.text,
137
+ startedAtMs: gap.startedAtMs + part.startMs,
138
+ endedAtMs: Math.min(gap.endedAtMs, gap.startedAtMs + part.endMs),
139
+ ...part.speaker ? { speakerLabel: `${index + 1}:${gapIndex + 1}:${part.speaker}` } : {}
140
+ });
141
+ }
142
+ else if (result.text.trim()) segments.push({
143
+ text: result.text,
144
+ ...gap
145
+ });
146
+ language ??= result.language;
147
+ }
148
+ offset += durationMs;
101
149
  });
150
+ segments.sort((a, b) => a.startedAtMs - b.startedAtMs);
151
+ replaceTranscriptSegments(capture.id, segments);
102
152
  return {
103
- text,
153
+ text: assembleDiscussionTranscript(listDiscussionTranscriptSegments(capture.id)),
104
154
  ...language ? { language } : {}
105
155
  };
106
156
  }