@xopcai/xopc 0.0.277 → 0.0.282

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 (315) 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/telegram/xopc.extension.json +1 -1
  11. package/dist/gateway/static/root/assets/{activity-store-SJZ5fS17.js → activity-store-iPtG94qr.js} +1 -1
  12. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cavbls0E.js → agent-avatar-dicebear-Cny5rATT.js} +1 -1
  13. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-OyQ8OVZv.js +1 -0
  14. package/dist/gateway/static/root/assets/{agents-DlDTItrB.js → agents-DJeh_Ewo.js} +1 -1
  15. package/dist/gateway/static/root/assets/{api-CnJAVHBu.js → api-KMJzMqvZ.js} +1 -1
  16. package/dist/gateway/static/root/assets/{app-management-settings-panel-5gS0_LWl.js → app-management-settings-panel-ZqQKaZEI.js} +1 -1
  17. package/dist/gateway/static/root/assets/{appearance-settings-DHzZCJon.js → appearance-settings-CfXsQkKO.js} +1 -1
  18. package/dist/gateway/static/root/assets/apps-page-k-QnVhKW.js +1 -0
  19. package/dist/gateway/static/root/assets/{archive-plugin-Ia98yZe3.js → archive-plugin-BrW53Hap.js} +1 -1
  20. package/dist/gateway/static/root/assets/{attachment-load-DiWW88c8.js → attachment-load-DkfFWHiS.js} +1 -1
  21. package/dist/gateway/static/root/assets/{automations-page-BuhTeGRJ.js → automations-page-e-5we6kD.js} +1 -1
  22. package/dist/gateway/static/root/assets/automations-workspace-DPnlkws1.js +6 -0
  23. package/dist/gateway/static/root/assets/{binary-plugins-DDD48cJv.js → binary-plugins-CWIswfXA.js} +1 -1
  24. package/dist/gateway/static/root/assets/{block-editor-0AWVCD3F.js → block-editor-CfeF1OmA.js} +3 -3
  25. package/dist/gateway/static/root/assets/{browser-automation-inputs-DcVLoIzc.js → browser-automation-inputs-BTIufVbM.js} +1 -1
  26. package/dist/gateway/static/root/assets/{browser-automations-page-BKOYX5xV.js → browser-automations-page-DnsuWKRu.js} +2 -2
  27. package/dist/gateway/static/root/assets/browser-settings-page-C8xyhnxx.js +8 -0
  28. package/dist/gateway/static/root/assets/capabilities-settings-panel-CBllOLTT.js +8 -0
  29. package/dist/gateway/static/root/assets/channels-settings-C-A-VUE2.js +1 -0
  30. package/dist/gateway/static/root/assets/{channels-status-swr-BTT2sIGg.js → channels-status-swr-DMYQNs-J.js} +1 -1
  31. package/dist/gateway/static/root/assets/{chat-terminal-dock-CW9Byi8V.js → chat-terminal-dock-C_Fw1R3A.js} +2 -2
  32. package/dist/gateway/static/root/assets/connectors-page-CtsMo3H4.js +2 -0
  33. package/dist/gateway/static/root/assets/{date-picker-gdYmKC8W.js → date-picker-CPFxjTrN.js} +1 -1
  34. package/dist/gateway/static/root/assets/{dependency-picker-40BYmiMc.js → dependency-picker-C_5KqzjI.js} +1 -1
  35. package/dist/gateway/static/root/assets/{desktop-pet-By1z6tKE.js → desktop-pet-DLJsC5D6.js} +1 -1
  36. package/dist/gateway/static/root/assets/{desktop-pet-settings-ByjJqx9H.js → desktop-pet-settings-BdI1iSgz.js} +1 -1
  37. package/dist/gateway/static/root/assets/device-pairing-wizard-BnvPLhc9.js +1 -0
  38. package/dist/gateway/static/root/assets/{extension-debug-page-DQO65KbM.js → extension-debug-page-BNS_TkZQ.js} +1 -1
  39. package/dist/gateway/static/root/assets/{extension-page-Db32jip6.js → extension-page-DMSDZ7Mb.js} +1 -1
  40. package/dist/gateway/static/root/assets/{extension-settings-page-CL8Wx8lf.js → extension-settings-page-CeGCWMt0.js} +1 -1
  41. package/dist/gateway/static/root/assets/gateway-config-swr-Cpmyrr6j.js +1 -0
  42. package/dist/gateway/static/root/assets/gateway-settings-DKOTYUma.js +1 -0
  43. package/dist/gateway/static/root/assets/{gateway-startup-retry-CVTa5HSS.js → gateway-startup-retry-DoiPeb65.js} +1 -1
  44. package/dist/gateway/static/root/assets/{heartbeat-settings-CRXWu2q6.js → heartbeat-settings-gsdL_Yij.js} +1 -1
  45. package/dist/gateway/static/root/assets/home-page-CbeAdS1T.js +1 -0
  46. package/dist/gateway/static/root/assets/{image-generation-api-DtMRG1oF.js → image-generation-api-CqjHWl2T.js} +1 -1
  47. package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +2 -0
  48. package/dist/gateway/static/root/assets/index-enFXJJCU.js +74 -0
  49. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CjehhHPz.js → keyboard-shortcuts-settings-CCLtJ8s8.js} +1 -1
  50. package/dist/gateway/static/root/assets/local-app-workbench-page-DzC6SrXj.js +2 -0
  51. package/dist/gateway/static/root/assets/{local-apps-page-CrIdaTrK.js → local-apps-page-3LdgP3NO.js} +1 -1
  52. package/dist/gateway/static/root/assets/{locale-store-BkJ7g35d.js → locale-store-1urrBBCQ.js} +1 -1
  53. package/dist/gateway/static/root/assets/{logs-page-FfSwV_VH.js → logs-page-DHClKtm6.js} +2 -2
  54. package/dist/gateway/static/root/assets/{management-settings-BakV5aOw.js → management-settings-BaCo9Nuo.js} +1 -1
  55. package/dist/gateway/static/root/assets/{markdown-view-DF3aJP_5.js → markdown-view-BZmcFIWa.js} +1 -1
  56. package/dist/gateway/static/root/assets/{media-plugins-Blv9U6Jp.js → media-plugins-BMjJppvG.js} +1 -1
  57. package/dist/gateway/static/root/assets/messages-DYFcjtmG.js +3 -0
  58. package/dist/gateway/static/root/assets/note-detail-panel-hYO2jNhL.js +5 -0
  59. package/dist/gateway/static/root/assets/notes-page-BT66x22W.js +8 -0
  60. package/dist/gateway/static/root/assets/{page-tabs-BQfVtLLC.js → page-tabs-BCszXX_F.js} +1 -1
  61. package/dist/gateway/static/root/assets/{preview-open-alternatives-kjHFiO81.js → preview-open-alternatives-CyM0sanh.js} +1 -1
  62. package/dist/gateway/static/root/assets/proactive-page-CVDGA5sB.js +1 -0
  63. package/dist/gateway/static/root/assets/{product-open-page-BazdmFYo.js → product-open-page-lXAbj85Z.js} +1 -1
  64. package/dist/gateway/static/root/assets/project-detail-page-Bp6TAMPM.js +1 -0
  65. package/dist/gateway/static/root/assets/projects-page-UhOIiDcg.js +1 -0
  66. package/dist/gateway/static/root/assets/{registry-api-CTq8EECj.js → registry-api-CxQstD00.js} +1 -1
  67. package/dist/gateway/static/root/assets/remote-access-hub-DJIkMFT4.js +1 -0
  68. package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BhJlopSW.js → runtime-tools-settings-panel-BUFCgRdZ.js} +2 -2
  69. package/dist/gateway/static/root/assets/{schema-form-cxoO_5I1.js → schema-form-B01ksA8M.js} +1 -1
  70. package/dist/gateway/static/root/assets/sessions-page-CQOAwPDt.js +1 -0
  71. package/dist/gateway/static/root/assets/settings-advanced-gate-CFuqbGI-.js +1 -0
  72. package/dist/gateway/static/root/assets/{settings-form-section-BiRTWiBi.js → settings-form-section-DvnjZBa3.js} +1 -1
  73. package/dist/gateway/static/root/assets/{settings-page-D-Cb71pq.js → settings-page-DZRkpbCh.js} +1 -1
  74. package/dist/gateway/static/root/assets/setup-status-panel-DVaEiAVY.js +1 -0
  75. package/dist/gateway/static/root/assets/{share-preview-page-DciHfTay.js → share-preview-page-vz0UK56q.js} +1 -1
  76. package/dist/gateway/static/root/assets/shares-settings-XiCqMDAe.js +1 -0
  77. package/dist/gateway/static/root/assets/skill-reload-api-C3D8WanU.js +1 -0
  78. package/dist/gateway/static/root/assets/skills-page-CxMWwR6A.js +2 -0
  79. package/dist/gateway/static/root/assets/{skills-page.utils-CU7PALQB.js → skills-page.utils-BryVNYll.js} +1 -1
  80. package/dist/gateway/static/root/assets/{system-settings-panel-GpjNbqvM.js → system-settings-panel-mKFDe6aZ.js} +1 -1
  81. package/dist/gateway/static/root/assets/task-detail-page-Cb0RZNcJ.js +1 -0
  82. package/dist/gateway/static/root/assets/{theme-store-mV6Cm2vj.js → theme-store-Dc5s20V3.js} +1 -1
  83. package/dist/gateway/static/root/assets/tunnel-api-FcI3wKy3.js +1 -0
  84. package/dist/gateway/static/root/assets/{use-autosave-LvCiBidm.js → use-autosave-CDgdtjTC.js} +1 -1
  85. package/dist/gateway/static/root/assets/user-model-page-Cnshc-Un.js +2 -0
  86. package/dist/gateway/static/root/assets/{voice-api-key-field-BlGysZxj.js → voice-api-key-field-Ce8cb5mN.js} +1 -1
  87. package/dist/gateway/static/root/assets/{voice-config-api-D3DdcH4H.js → voice-config-api-Mk78BrpP.js} +1 -1
  88. package/dist/gateway/static/root/assets/{work-discovery-overlay-DvIQOR5x.js → work-discovery-overlay-CacFTtXe.js} +1 -1
  89. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-JIp60bXT.js → workflow-run-setup-panel-V-5HuEUq.js} +2 -2
  90. package/dist/gateway/static/root/assets/workflows-page-1OJimIfo.js +6 -0
  91. package/dist/gateway/static/root/index.html +10 -11
  92. package/dist/package.js +1 -1
  93. package/dist/packages/gateway-contract/src/discussions.js +17 -0
  94. package/dist/packages/gateway-contract/src/proactive.js +4 -2
  95. package/dist/src/agent/agent-manager.js +4 -4
  96. package/dist/src/agent/background-review/run-background-review.js +1 -1
  97. package/dist/src/agent/commands/command-isolation.d.ts +1 -0
  98. package/dist/src/agent/commands/command-isolation.js +17 -1
  99. package/dist/src/agent/commands/commandMounts.d.ts +2 -0
  100. package/dist/src/agent/commands/commandMounts.js +32 -0
  101. package/dist/src/agent/context/project-context.d.ts +1 -0
  102. package/dist/src/agent/context/project-context.js +12 -4
  103. package/dist/src/agent/embedded/model-runtime.js +1 -1
  104. package/dist/src/agent/embedded/run-for-session.js +1 -1
  105. package/dist/src/agent/image/tool-model-config.js +1 -1
  106. package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
  107. package/dist/src/agent/sandbox/command-validator.js +0 -20
  108. package/dist/src/agent/sandbox/exec-policy.d.ts +1 -0
  109. package/dist/src/agent/sandbox/exec-policy.js +4 -4
  110. package/dist/src/agent/sandbox/fileAccess.d.ts +7 -0
  111. package/dist/src/agent/sandbox/fileAccess.js +100 -0
  112. package/dist/src/agent/sandbox/path-policy.d.ts +2 -0
  113. package/dist/src/agent/sandbox/path-policy.js +54 -17
  114. package/dist/src/agent/sandbox/sanitize-env-vars.js +10 -9
  115. package/dist/src/agent/service.js +2 -2
  116. package/dist/src/agent/session/session-inspector.js +1 -1
  117. package/dist/src/agent/skills/hub-lock.js +1 -1
  118. package/dist/src/agent/source-context/task-context.d.ts +3 -0
  119. package/dist/src/agent/source-context/task-context.js +24 -0
  120. package/dist/src/agent/source-context/types.d.ts +2 -2
  121. package/dist/src/agent/source-context/types.js +1 -1
  122. package/dist/src/agent/tasks/task-judge-service.js +3 -3
  123. package/dist/src/agent/tool-manuals/xopc-use.d.ts +1 -1
  124. package/dist/src/agent/tool-manuals/xopc-use.js +1 -0
  125. package/dist/src/agent/tools/apply-patch.js +25 -38
  126. package/dist/src/agent/tools/create-share-tool.js +11 -3
  127. package/dist/src/agent/tools/exec-command.js +2 -1
  128. package/dist/src/agent/tools/find.js +5 -3
  129. package/dist/src/agent/tools/grep.js +21 -4
  130. package/dist/src/agent/tools/image-generate-tool.js +3 -4
  131. package/dist/src/agent/tools/list-dir.js +2 -1
  132. package/dist/src/agent/tools/managed-job-tool.js +1 -0
  133. package/dist/src/agent/tools/publish-artifacts.js +4 -3
  134. package/dist/src/agent/tools/read.js +8 -5
  135. package/dist/src/agent/tools/repository-search.d.ts +2 -0
  136. package/dist/src/agent/tools/repository-search.js +51 -7
  137. package/dist/src/agent/tools/review-workspace.js +76 -10
  138. package/dist/src/agent/tools/send-media.js +4 -3
  139. package/dist/src/agent/tools/tool-paths.js +3 -1
  140. package/dist/src/agent/tools/write.js +4 -17
  141. package/dist/src/agent/tools/xopc-use-tool.js +2 -2
  142. package/dist/src/agent-config/schema.d.ts +24 -0
  143. package/dist/src/agent-config/schema.js +2 -1
  144. package/dist/src/ai-assist/text-assist.js +1 -1
  145. package/dist/src/auth/credentials.js +2 -2
  146. package/dist/src/auth/oauth/xopc-cloud.js +1 -1
  147. package/dist/src/auth/profiles/store.js +1 -1
  148. package/dist/src/auth/sync-provider-auth.js +2 -2
  149. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  150. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  151. package/dist/src/cli/commands/doctor/checks/provider-auth.js +21 -20
  152. package/dist/src/cli/commands/models.js +1 -1
  153. package/dist/src/config/index.js +4 -4
  154. package/dist/src/config/loader.js +2 -2
  155. package/dist/src/config/models-json.js +1 -1
  156. package/dist/src/config/schema.d.ts +16 -0
  157. package/dist/src/discussions/action-tasks.d.ts +21 -0
  158. package/dist/src/discussions/action-tasks.js +93 -0
  159. package/dist/src/discussions/analyzer.d.ts +21 -1
  160. package/dist/src/discussions/analyzer.js +229 -62
  161. package/dist/src/discussions/audio-repair.d.ts +8 -0
  162. package/dist/src/discussions/audio-repair.js +41 -0
  163. package/dist/src/discussions/edits.d.ts +22 -0
  164. package/dist/src/discussions/edits.js +81 -0
  165. package/dist/src/discussions/errors.d.ts +4 -0
  166. package/dist/src/discussions/errors.js +10 -0
  167. package/dist/src/discussions/export.d.ts +2 -0
  168. package/dist/src/discussions/export.js +23 -0
  169. package/dist/src/discussions/index.js +3 -2
  170. package/dist/src/discussions/live-worker.js +1 -1
  171. package/dist/src/discussions/organizer-worker.js +14 -5
  172. package/dist/src/discussions/organizer.d.ts +1 -1
  173. package/dist/src/discussions/organizer.js +60 -24
  174. package/dist/src/discussions/reconcile.d.ts +22 -0
  175. package/dist/src/discussions/reconcile.js +55 -0
  176. package/dist/src/discussions/recording.d.ts +9 -0
  177. package/dist/src/discussions/recording.js +219 -0
  178. package/dist/src/discussions/recordingJobs.d.ts +14 -0
  179. package/dist/src/discussions/recordingJobs.js +69 -0
  180. package/dist/src/discussions/repository.d.ts +4 -2
  181. package/dist/src/discussions/repository.js +15 -10
  182. package/dist/src/discussions/revisions.d.ts +12 -0
  183. package/dist/src/discussions/revisions.js +36 -0
  184. package/dist/src/discussions/sealer.d.ts +1 -0
  185. package/dist/src/discussions/sealer.js +61 -11
  186. package/dist/src/discussions/service.d.ts +16 -15
  187. package/dist/src/discussions/service.js +177 -91
  188. package/dist/src/discussions/types.d.ts +4 -139
  189. package/dist/src/discussions/types.js +1 -11
  190. package/dist/src/extensions/lockfile.js +1 -1
  191. package/dist/src/files/file-service.d.ts +1 -0
  192. package/dist/src/files/file-service.js +77 -0
  193. package/dist/src/gateway/agents-admin.js +1 -1
  194. package/dist/src/gateway/chat-stream/mapper.js +2 -2
  195. package/dist/src/gateway/file-path-classifier.js +1 -1
  196. package/dist/src/gateway/hono/app.js +3 -2
  197. package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -0
  198. package/dist/src/gateway/hono/lib/config-payload.js +1 -1
  199. package/dist/src/gateway/hono/lib/static-ui.js +1 -0
  200. package/dist/src/gateway/hono/routes/context-sources.js +1 -2
  201. package/dist/src/gateway/hono/routes/discussions.js +126 -19
  202. package/dist/src/gateway/hono/routes/files.js +1 -22
  203. package/dist/src/gateway/hono/routes/index.js +0 -2
  204. package/dist/src/gateway/hono/routes/lazy-bundles.js +16 -0
  205. package/dist/src/gateway/hono/routes/models.js +1 -1
  206. package/dist/src/gateway/hono/routes/proactive-control.js +2 -36
  207. package/dist/src/gateway/hono/routes/project-understanding.d.ts +3 -0
  208. package/dist/src/gateway/hono/routes/project-understanding.js +65 -0
  209. package/dist/src/gateway/hono/routes/projects.js +20 -4
  210. package/dist/src/gateway/hono/routes/session-input-handler.d.ts +2 -2
  211. package/dist/src/gateway/hono/routes/session-input-handler.js +2 -2
  212. package/dist/src/gateway/hono/routes/shares.js +22 -1
  213. package/dist/src/gateway/hono/routes/tasks.js +2 -1
  214. package/dist/src/gateway/hono/routes/work-discovery.d.ts +0 -2
  215. package/dist/src/gateway/hono/routes/work-discovery.js +2 -16
  216. package/dist/src/gateway/hono/routes/workflows.js +2 -2
  217. package/dist/src/gateway/security/gateway-scopes.js +1 -2
  218. package/dist/src/gateway/service/agent-runner.js +2 -2
  219. package/dist/src/gateway/service/config-coordinator.js +1 -1
  220. package/dist/src/gateway/service.d.ts +3 -0
  221. package/dist/src/gateway/service.js +28 -13
  222. package/dist/src/knowledge-memory/repository.d.ts +2 -0
  223. package/dist/src/knowledge-memory/repository.js +3 -1
  224. package/dist/src/media-understanding/audio-transcription-runner.d.ts +1 -0
  225. package/dist/src/media-understanding/audio-transcription-runner.js +1 -0
  226. package/dist/src/media-understanding/runner.js +1 -0
  227. package/dist/src/media-understanding/types.d.ts +8 -0
  228. package/dist/src/notes/service.d.ts +3 -1
  229. package/dist/src/notes/store.d.ts +3 -1
  230. package/dist/src/notes/store.js +5 -3
  231. package/dist/src/proactive/experience.d.ts +38 -21
  232. package/dist/src/proactive/experience.js +113 -12
  233. package/dist/src/proactive/inbox/cards.js +7 -1
  234. package/dist/src/proactive/maintenance.js +0 -1
  235. package/dist/src/proactive/scenarios/control.d.ts +0 -35
  236. package/dist/src/proactive/scenarios/control.js +7 -53
  237. package/dist/src/projects/project-store.js +6 -4
  238. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  239. package/dist/src/providers/index.js +2 -2
  240. package/dist/src/providers/model-registry.js +1 -1
  241. package/dist/src/providers/register-runtime-providers.js +1 -1
  242. package/dist/src/session/client-history.d.ts +1 -1
  243. package/dist/src/session/client-history.js +1 -1
  244. package/dist/src/share/share-store.js +5 -1
  245. package/dist/src/storage/sqlite/migrations/166_remove_proactive_previews.sql +1 -0
  246. package/dist/src/storage/sqlite/migrations/167_discussion_recording_chunks.sql +7 -0
  247. package/dist/src/storage/sqlite/migrations/168_discussion_evidence.sql +16 -0
  248. package/dist/src/storage/sqlite/migrations/169_discussion_action_tasks.sql +8 -0
  249. package/dist/src/storage/sqlite/migrations/170_discussion_edits.sql +8 -0
  250. package/dist/src/storage/sqlite/migrations/171_discussion_recording_jobs.sql +14 -0
  251. package/dist/src/storage/sqlite/migrations/172_project_understanding.sql +3 -0
  252. package/dist/src/storage/sqlite/migrations/173_mobile_discussion_source.sql +5 -0
  253. package/dist/src/storage/sqlite/migrations/__tests__/mobile-discussion-source.test.ts +20 -0
  254. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  255. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  256. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  257. package/dist/src/storage/sqlite/schema.js +1 -1
  258. package/dist/src/tasks/home-query-service.js +2 -2
  259. package/dist/src/tasks/task-application-service.js +77 -3
  260. package/dist/src/tasks/task-context-assembler.js +1 -0
  261. package/dist/src/tasks/task-repository.d.ts +1 -0
  262. package/dist/src/tasks/task-repository.js +4 -0
  263. package/dist/src/tui/tui-startup-resources.js +1 -1
  264. package/dist/src/tui/tui.js +1 -1
  265. package/dist/src/voice/audio/normalize.js +28 -5
  266. package/dist/src/voice/stt/transcribe-core.js +1 -0
  267. package/dist/src/voice/stt/types.d.ts +1 -0
  268. package/dist/src/work-discovery/analyzer.d.ts +1 -0
  269. package/dist/src/work-discovery/analyzer.js +28 -22
  270. package/dist/src/work-discovery/index.js +3 -3
  271. package/dist/src/work-discovery/investigator.js +1 -1
  272. package/dist/src/work-discovery/project-understanding.d.ts +7 -0
  273. package/dist/src/work-discovery/project-understanding.js +38 -0
  274. package/dist/src/work-discovery/repository.d.ts +3 -1
  275. package/dist/src/work-discovery/repository.js +18 -5
  276. package/dist/src/work-discovery/service.d.ts +8 -0
  277. package/dist/src/work-discovery/service.js +163 -15
  278. package/dist/src/work-discovery/types.d.ts +2 -0
  279. package/dist/src/workflows/service/workflow-run-service.js +2 -2
  280. package/package.json +3 -3
  281. package/dist/gateway/static/root/assets/agent-defaults-settings-panel-DyCyuJpi.js +0 -1
  282. package/dist/gateway/static/root/assets/apps-page-__yzruGu.js +0 -1
  283. package/dist/gateway/static/root/assets/automations-workspace-DaisbmEh.js +0 -6
  284. package/dist/gateway/static/root/assets/browser-settings-page-C7zjxscU.js +0 -8
  285. package/dist/gateway/static/root/assets/capabilities-settings-panel-7kZMT1r6.js +0 -8
  286. package/dist/gateway/static/root/assets/channels-settings-BKUh6hzO.js +0 -1
  287. package/dist/gateway/static/root/assets/config-reload-event-Bjh2zoKp.js +0 -1
  288. package/dist/gateway/static/root/assets/connectors-page-m8J-F3u8.js +0 -2
  289. package/dist/gateway/static/root/assets/device-pairing-wizard-CfWQfz7a.js +0 -1
  290. package/dist/gateway/static/root/assets/gateway-config-swr-cd8owslz.js +0 -1
  291. package/dist/gateway/static/root/assets/gateway-settings-wLly-Kc1.js +0 -1
  292. package/dist/gateway/static/root/assets/home-page-FqgSwpmy.js +0 -1
  293. package/dist/gateway/static/root/assets/index-5UchbidQ.js +0 -74
  294. package/dist/gateway/static/root/assets/index-CXF9-BpI.css +0 -2
  295. package/dist/gateway/static/root/assets/local-app-workbench-page-DtYZa4GK.js +0 -2
  296. package/dist/gateway/static/root/assets/messages-BGc-OJWg.js +0 -3
  297. package/dist/gateway/static/root/assets/note-detail-panel-JbvyGdEX.js +0 -5
  298. package/dist/gateway/static/root/assets/notes-page-Dm7mE2FK.js +0 -8
  299. package/dist/gateway/static/root/assets/proactive-card-Ap_wfos3.js +0 -1
  300. package/dist/gateway/static/root/assets/proactive-page-D1lEuanT.js +0 -1
  301. package/dist/gateway/static/root/assets/project-detail-page-h00lfcVI.js +0 -1
  302. package/dist/gateway/static/root/assets/projects-page-C2Bo1FAL.js +0 -1
  303. package/dist/gateway/static/root/assets/remote-access-hub-_bZYAl49.js +0 -1
  304. package/dist/gateway/static/root/assets/sessions-page-F7CHsvPa.js +0 -1
  305. package/dist/gateway/static/root/assets/settings-advanced-gate-DtZSUX4H.js +0 -1
  306. package/dist/gateway/static/root/assets/setup-status-panel-VPfBsNVv.js +0 -1
  307. package/dist/gateway/static/root/assets/shares-settings-BS2pnWO0.js +0 -1
  308. package/dist/gateway/static/root/assets/skill-reload-api-DX344nVV.js +0 -1
  309. package/dist/gateway/static/root/assets/skills-page-BhfKUsv7.js +0 -2
  310. package/dist/gateway/static/root/assets/task-detail-page-CElcQ9Sh.js +0 -1
  311. package/dist/gateway/static/root/assets/tunnel-api-Bhi_x_h4.js +0 -1
  312. package/dist/gateway/static/root/assets/user-model-page-BydARq2C.js +0 -2
  313. package/dist/gateway/static/root/assets/workflows-page-B972t6zx.js +0 -6
  314. package/dist/src/proactive/scenarios/preview.d.ts +0 -13
  315. package/dist/src/proactive/scenarios/preview.js +0 -81
@@ -0,0 +1,23 @@
1
+ import { DiscussionServiceError } from "./errors.js";
2
+ //#region src/discussions/export.ts
3
+ const time = (ms) => `${String(Math.floor(ms / 36e5)).padStart(2, "0")}:${String(Math.floor(ms / 6e4) % 60).padStart(2, "0")}:${String(Math.floor(ms / 1e3) % 60).padStart(2, "0")},${String(Math.round(ms) % 1e3).padStart(3, "0")}`;
4
+ function exportDiscussion(detail, format) {
5
+ const segments = detail.transcript.segments.filter((segment) => segment.status === "confirmed");
6
+ if (format === "txt") return segments.map((segment) => `${time(segment.startedAtMs)} ${segment.speakerLabel ?? ""}\n${segment.displayText ?? segment.rawText ?? ""}`).join("\n\n");
7
+ if (format === "srt") return segments.map((segment, index) => `${index + 1}\n${time(segment.startedAtMs)} --> ${time(segment.endedAtMs)}\n${segment.speakerLabel ? `${segment.speakerLabel}: ` : ""}${segment.displayText ?? segment.rawText ?? ""}`).join("\n\n");
8
+ if (format !== "md") throw new DiscussionServiceError("invalid_input", "Unsupported export format");
9
+ const organization = detail.organization?.organization;
10
+ if (!organization) throw new DiscussionServiceError("conflict", "Meeting summary is not ready");
11
+ return [
12
+ `# ${organization.title}`,
13
+ organization.summary,
14
+ ...[
15
+ ["Decisions", organization.decisions.filter((item) => !item.ignored && !item.supersededBy).map((item) => item.text)],
16
+ ["Actions", organization.actionItems.filter((item) => !item.ignored && !item.supersededBy).map((item) => `${item.title}${item.owner ? ` · ${item.owner}` : ""}${item.dueDate ? ` · ${item.dueDate}` : ""}`)],
17
+ ["Risks", organization.risks.filter((item) => !item.ignored && !item.supersededBy).map((item) => item.text)],
18
+ ["Open questions", organization.openQuestions.filter((item) => !item.ignored && !item.supersededBy).map((item) => item.text)]
19
+ ].map(([title, items]) => `## ${title}\n\n${items.map((text) => `- ${text}`).join("\n")}`)
20
+ ].join("\n\n");
21
+ }
22
+ //#endregion
23
+ export { exportDiscussion };
@@ -1,6 +1,7 @@
1
+ import { DISCUSSION_AUDIO_MAX_BYTES, DISCUSSION_MAX_DURATION_MS, DISCUSSION_STATUSES } from "../../packages/gateway-contract/src/discussions.js";
2
+ import { DiscussionServiceError } from "./errors.js";
1
3
  import { analyzeDiscussion } from "./analyzer.js";
2
- import { DISCUSSION_STATUSES } from "./types.js";
3
- import { DISCUSSION_AUDIO_MAX_BYTES, DISCUSSION_MAX_DURATION_MS, DiscussionService, DiscussionServiceError } from "./service.js";
4
+ import { DiscussionService } from "./service.js";
4
5
  import { DiscussionOrganizer } from "./organizer.js";
5
6
  import { DiscussionLiveWorker } from "./live-worker.js";
6
7
  import { DiscussionOrganizerWorker } from "./organizer-worker.js";
@@ -5,9 +5,9 @@ import { transcribe } from "../voice/stt/transcribe-core.js";
5
5
  import { isSTTAvailable } from "../voice/stt/availability.js";
6
6
  import { mergeSttConfigFromAppConfig } from "../voice/stt/config.js";
7
7
  import "../voice/stt/index.js";
8
- import { enrichLiveDiscussion } from "./analyzer.js";
9
8
  import { claimNextDiscussionTranscriptSegment, completeDiscussionTranscriptSegment, getDiscussionCapture, listDiscussionTranscriptSegments, retryOrFailDiscussionTranscriptSegment, updateDiscussionCapture } from "./repository.js";
10
9
  import { assembleDiscussionTranscript } from "./transcript.js";
10
+ import { enrichLiveDiscussion } from "./analyzer.js";
11
11
  import { acceptRankedProject, findExactProjectMention } from "./project-inference.js";
12
12
  import { randomUUID } from "node:crypto";
13
13
  //#region src/discussions/live-worker.ts
@@ -1,6 +1,6 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
- import { claimNextDiscussionCapture, getDiscussionCapture, releaseDiscussionWorkLease, updateDiscussionCapture } from "./repository.js";
3
+ import { claimNextDiscussionCapture, getDiscussionCapture, releaseDiscussionWorkLease, renewDiscussionWorkLease, updateDiscussionCapture } from "./repository.js";
4
4
  import { randomUUID } from "node:crypto";
5
5
  //#region src/discussions/organizer-worker.ts
6
6
  init_logger();
@@ -31,13 +31,20 @@ var DiscussionOrganizerWorker = class {
31
31
  try {
32
32
  const capture = claimNextDiscussionCapture(this.owner);
33
33
  if (!capture) return;
34
+ const controller = new AbortController();
35
+ const renewal = setInterval(() => {
36
+ if (!renewDiscussionWorkLease(capture.id, this.owner)) controller.abort();
37
+ }, 3e4);
38
+ renewal.unref();
34
39
  try {
35
- await this.processor.process(capture, this.owner);
36
- releaseDiscussionWorkLease(capture.id);
40
+ await this.processor.process(capture, this.owner, controller.signal);
41
+ releaseDiscussionWorkLease(capture.id, this.owner);
37
42
  } catch (error) {
38
43
  const message = error instanceof Error ? error.message : String(error);
39
- releaseDiscussionWorkLease(capture.id);
40
- const updated = getDiscussionCapture(capture.id) && updateDiscussionCapture(capture.id, {
44
+ const ownsLease = renewDiscussionWorkLease(capture.id, this.owner);
45
+ releaseDiscussionWorkLease(capture.id, this.owner);
46
+ const current = getDiscussionCapture(capture.id);
47
+ const updated = ownsLease && current && updateDiscussionCapture(capture.id, {
41
48
  status: "needs_attention",
42
49
  failureStage: "organization",
43
50
  failureCode: "organization_failed",
@@ -48,6 +55,8 @@ var DiscussionOrganizerWorker = class {
48
55
  err: error,
49
56
  discussionId: capture.id
50
57
  }, `Discussion organization failed: ${message}`);
58
+ } finally {
59
+ clearInterval(renewal);
51
60
  }
52
61
  } finally {
53
62
  this.running = false;
@@ -17,6 +17,6 @@ export declare class DiscussionOrganizer {
17
17
  private readonly deps;
18
18
  private readonly objectLinks;
19
19
  constructor(deps: DiscussionOrganizerDeps);
20
- process(capture: DiscussionCapture, _owner: string, signal?: AbortSignal): Promise<DiscussionCapture>;
20
+ process(capture: DiscussionCapture, owner: string, signal?: AbortSignal): Promise<DiscussionCapture>;
21
21
  private linkProject;
22
22
  }
@@ -1,18 +1,22 @@
1
+ import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
1
2
  import { ObjectLinkService } from "../activity/service.js";
3
+ import { completeDiscussionOrganization, createDiscussionOrganization, getDiscussionCapture, getLatestDiscussionOrganization, listDiscussionTranscriptSegments, renewDiscussionWorkLease, updateDiscussionCapture } from "./repository.js";
4
+ import { saveTranscriptRevision } from "./revisions.js";
2
5
  import { analyzeDiscussion } from "./analyzer.js";
3
- import { completeDiscussionOrganization, createDiscussionOrganization, updateDiscussionCapture } from "./repository.js";
6
+ import { applyMeetingEdits } from "./edits.js";
4
7
  import { acceptRankedProject, findExactProjectMention } from "./project-inference.js";
5
8
  //#region src/discussions/organizer.ts
6
- const PROMPT_VERSION = "discussion-organizer-v1";
9
+ const PROMPT_VERSION = "discussion-evidence-v2";
7
10
  var DiscussionOrganizer = class {
8
11
  objectLinks = new ObjectLinkService();
9
12
  constructor(deps) {
10
13
  this.deps = deps;
11
14
  }
12
- async process(capture, _owner, signal) {
15
+ async process(capture, owner, signal) {
13
16
  const transcript = capture.canonicalTranscript?.trim();
14
17
  const inputHash = capture.canonicalTranscriptSha256;
15
18
  if (!transcript || !inputHash) throw new Error("Canonical discussion transcript is missing");
19
+ const transcriptRevision = saveTranscriptRevision(capture.id);
16
20
  const projects = this.deps.projects.list({
17
21
  status: "active",
18
22
  limit: 100
@@ -20,18 +24,35 @@ var DiscussionOrganizer = class {
20
24
  const result = this.deps.organizeTranscript ? await this.deps.organizeTranscript(transcript, capture, signal) : await analyzeDiscussion({
21
25
  config: this.deps.getConfig(),
22
26
  transcript,
27
+ segments: listDiscussionTranscriptSegments(capture.id),
28
+ discussionId: capture.id,
29
+ template: capture.template,
23
30
  projects: projects.map(({ id, name }) => ({
24
31
  id,
25
32
  name
26
33
  })),
27
34
  signal
28
35
  });
29
- completeDiscussionOrganization(createDiscussionOrganization({
30
- discussionId: capture.id,
31
- inputTranscriptSha256: inputHash,
32
- promptVersion: PROMPT_VERSION,
33
- modelRef: result.modelRef
34
- }).id, result.organization);
36
+ signal?.throwIfAborted();
37
+ if (!renewDiscussionWorkLease(capture.id, owner)) throw new Error("Meeting processing lease lost");
38
+ if (getDiscussionCapture(capture.id)?.canonicalTranscriptSha256 !== inputHash) throw new Error("Transcript changed during organization");
39
+ const previous = getLatestDiscussionOrganization(capture.id)?.organization;
40
+ if (previous) {
41
+ const signature = (refs) => JSON.stringify([...refs].sort((a, b) => a - b));
42
+ for (const kind of [
43
+ "decisions",
44
+ "actionItems",
45
+ "risks",
46
+ "openQuestions"
47
+ ]) for (const item of result.organization[kind]) {
48
+ const candidates = previous[kind].filter((old) => signature(old.evidenceSegmentIds) === signature(item.evidenceSegmentIds));
49
+ const label = (value) => "title" in value ? value.title : value.text;
50
+ const exact = previous[kind].filter((old) => label(old) === label(item));
51
+ const matches = exact.length ? exact : candidates;
52
+ const currentMatches = result.organization[kind].filter((other) => signature(other.evidenceSegmentIds) === signature(item.evidenceSegmentIds));
53
+ if (matches.length === 1 && (label(matches[0]) === label(item) && result.organization[kind].filter((other) => label(other) === label(item)).length === 1 || currentMatches.length === 1)) item.id = matches[0].id;
54
+ }
55
+ }
35
56
  let inferredProject;
36
57
  if (!capture.projectId) {
37
58
  const exact = findExactProjectMention(transcript, projects);
@@ -50,23 +71,38 @@ var DiscussionOrganizer = class {
50
71
  if (!note) throw new Error("Discussion note is missing");
51
72
  const title = result.organization.title.trim().slice(0, 200);
52
73
  if (note.title?.startsWith("Discussion ·") && title) await this.deps.notes.updateNote(capture.noteId, { title }, "ai_edit");
53
- const updated = updateDiscussionCapture(capture.id, {
54
- status: "completed",
55
- generatedTitle: title,
56
- ...inferredProject ? {
57
- projectId: inferredProject.id,
58
- projectInferenceScore: inferredProject.score,
59
- projectInferenceSource: inferredProject.source
60
- } : {},
61
- failureStage: void 0,
62
- failureCode: void 0,
63
- failureMessage: void 0,
64
- completedAt: Date.now()
65
- }, ["organizing"]);
66
- if (!updated) throw new Error("Discussion changed while applying organization");
74
+ const updated = runSqliteWriteTransaction(() => {
75
+ signal?.throwIfAborted();
76
+ if (!renewDiscussionWorkLease(capture.id, owner)) throw new Error("Meeting processing lease lost");
77
+ if (getDiscussionCapture(capture.id)?.canonicalTranscriptSha256 !== inputHash) throw new Error("Transcript changed during organization");
78
+ const record = createDiscussionOrganization({
79
+ discussionId: capture.id,
80
+ transcriptRevision,
81
+ inputTranscriptSha256: inputHash,
82
+ promptVersion: PROMPT_VERSION,
83
+ modelRef: result.modelRef
84
+ });
85
+ result.organization = applyMeetingEdits(capture.id, result.organization);
86
+ completeDiscussionOrganization(record.id, result.organization);
87
+ const published = updateDiscussionCapture(capture.id, {
88
+ status: "completed",
89
+ generatedTitle: title,
90
+ ...inferredProject ? {
91
+ projectId: inferredProject.id,
92
+ projectInferenceScore: inferredProject.score,
93
+ projectInferenceSource: inferredProject.source
94
+ } : {},
95
+ failureStage: void 0,
96
+ failureCode: void 0,
97
+ failureMessage: void 0,
98
+ completedAt: capture.completedAt ?? Date.now()
99
+ }, ["organizing"]);
100
+ if (!published) throw new Error("Discussion changed while applying organization");
101
+ return published;
102
+ });
67
103
  if (inferredProject) this.linkProject(updated, inferredProject.id);
68
104
  this.deps.onUpdated?.(updated);
69
- this.deps.onCompleted?.(updated, result.organization);
105
+ if (!capture.completedAt) this.deps.onCompleted?.(updated, result.organization);
70
106
  return updated;
71
107
  }
72
108
  linkProject(capture, projectId) {
@@ -0,0 +1,22 @@
1
+ import { z } from 'zod';
2
+ import type { DiscussionOrganization } from './types.js';
3
+ type Candidate = {
4
+ id: string;
5
+ kind: string;
6
+ text: string;
7
+ evidenceSegmentIds: number[];
8
+ };
9
+ export declare const MeetingChangesSchema: z.ZodObject<{
10
+ changes: z.ZodArray<z.ZodObject<{
11
+ fromId: z.ZodString;
12
+ toId: z.ZodString;
13
+ relation: z.ZodEnum<{
14
+ supersedes: "supersedes";
15
+ contradicts: "contradicts";
16
+ }>;
17
+ }, z.core.$strip>>;
18
+ }, z.core.$strip>;
19
+ export type MeetingChange = z.infer<typeof MeetingChangesSchema>['changes'][number];
20
+ /** Relations can only point from an earlier fact to an actual later fact. */
21
+ export declare function reconcileMeetingChapters(chapters: DiscussionOrganization[], compare: (earlier: Candidate[], later: Candidate[]) => Promise<MeetingChange[]>): Promise<MeetingChange[]>;
22
+ export {};
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ //#region src/discussions/reconcile.ts
3
+ const MeetingChangesSchema = z.object({ changes: z.array(z.object({
4
+ fromId: z.string(),
5
+ toId: z.string(),
6
+ relation: z.enum(["supersedes", "contradicts"])
7
+ })).max(200) });
8
+ function groups(items) {
9
+ const result = [];
10
+ let current = [];
11
+ let size = 0;
12
+ for (const item of items) {
13
+ const length = JSON.stringify(item).length;
14
+ if (length > 12e3) throw new Error("Meeting fact exceeds reconciliation input budget");
15
+ if (current.length && size + length > 12e3) {
16
+ result.push(current);
17
+ current = [];
18
+ size = 0;
19
+ }
20
+ current.push(item);
21
+ size += length;
22
+ }
23
+ if (current.length) result.push(current);
24
+ return result;
25
+ }
26
+ /** Relations can only point from an earlier fact to an actual later fact. */
27
+ async function reconcileMeetingChapters(chapters, compare) {
28
+ const earlier = [];
29
+ const changes = /* @__PURE__ */ new Map();
30
+ for (const chapter of chapters) {
31
+ const later = [];
32
+ for (const kind of [
33
+ "decisions",
34
+ "actionItems",
35
+ "risks",
36
+ "openQuestions"
37
+ ]) for (const item of chapter[kind]) later.push({
38
+ id: item.id,
39
+ kind,
40
+ text: "title" in item ? item.title : item.text,
41
+ evidenceSegmentIds: item.evidenceSegmentIds
42
+ });
43
+ for (const left of groups(earlier)) for (const right of groups(later)) {
44
+ const output = await compare(left, right);
45
+ for (const change of output) {
46
+ if (change.fromId === change.toId || !left.some((item) => item.id === change.fromId) || !right.some((item) => item.id === change.toId)) throw new Error("Meeting change refers to an invalid or unordered fact");
47
+ changes.set(`${change.fromId}:${change.toId}`, change);
48
+ }
49
+ }
50
+ earlier.push(...later.filter((item) => item.kind === "decisions" || item.kind === "actionItems"));
51
+ }
52
+ return [...changes.values()];
53
+ }
54
+ //#endregion
55
+ export { MeetingChangesSchema, reconcileMeetingChapters };
@@ -0,0 +1,9 @@
1
+ import { type DiscussionRecordingChunk, type DiscussionRecordingManifest } from '@xopcai/gateway-contract';
2
+ import type { NotesService } from '../notes/service.js';
3
+ import type { DiscussionCapture } from './types.js';
4
+ export declare function listRecordingChunks(id: string): DiscussionRecordingChunk[];
5
+ /** Called under the discussion mutation queue. Receipt follows durable disk write. */
6
+ export declare function saveRecordingChunk(capture: DiscussionCapture, sequence: number, sha256: string, body: ReadableStream<Uint8Array>): Promise<DiscussionRecordingChunk>;
7
+ export declare function validateRecordingManifest(capture: DiscussionCapture, input: DiscussionRecordingManifest): void;
8
+ export declare function completeRecording(capture: DiscussionCapture, input: DiscussionRecordingManifest, notes: NotesService, assertLease: () => void, signal: AbortSignal): Promise<DiscussionCapture>;
9
+ export declare function removeRecordingChunks(capture: DiscussionCapture): Promise<void>;
@@ -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;