@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
@@ -1,34 +1,28 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
3
  import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
4
+ import { DISCUSSION_AUDIO_MAX_BYTES, DISCUSSION_MAX_DURATION_MS, DISCUSSION_SEGMENT_MAX_BYTES } from "../../packages/gateway-contract/src/discussions.js";
4
5
  import { ObjectLinkService } from "../activity/service.js";
6
+ import { DiscussionServiceError } from "./errors.js";
5
7
  import { acknowledgeDiscussionCaptureConsent, correctDiscussionTranscriptSegment, createDiscussionCapture, createDiscussionTranscriptSegment, deleteDiscussionSegmentAudio, getDiscussionCapture, getDiscussionCaptureByClientRequestId, getDiscussionCaptureByNoteId, getDiscussionCaptureSettings, getDiscussionMetrics, getDiscussionTranscriptSegment, getLatestDiscussionOrganization, listDiscussionCaptures, listDiscussionTranscriptSegments, updateDiscussionCapture } from "./repository.js";
6
8
  import { assembleDiscussionTranscript } from "./transcript.js";
9
+ import { readTranscriptRevision, refreshCanonicalTranscript, saveTranscriptRevision } from "./revisions.js";
10
+ import { editMeeting } from "./edits.js";
11
+ import { completeRecording, listRecordingChunks, removeRecordingChunks, saveRecordingChunk, validateRecordingManifest } from "./recording.js";
12
+ import { cancelRecordingJob, claimRecordingJob, finishRecordingJob, getRecordingJob, getRecordingJobInput, renewRecordingJob, submitRecordingJob } from "./recordingJobs.js";
7
13
  import { createHash, randomUUID } from "node:crypto";
8
14
  //#region src/discussions/service.ts
9
15
  init_logger();
10
16
  const log = createLogger("DiscussionService");
11
- const DISCUSSION_AUDIO_MAX_BYTES = 25 * 1024 * 1024;
12
- const DISCUSSION_SEGMENT_MAX_BYTES = 2 * 1024 * 1024;
13
- const DISCUSSION_MAX_DURATION_MS = 1800 * 1e3;
14
- function isSupportedAudioMimeType(mimeType) {
15
- const base = mimeType.toLowerCase().split(";", 1)[0]?.trim();
16
- return base === "audio/webm" || base === "audio/mp4" || base === "audio/ogg" || base === "audio/wav" || base === "audio/x-wav" || base === "audio/mpeg";
17
- }
18
17
  function placeholderTitle(now) {
19
18
  return `Discussion · ${new Date(now).toISOString().slice(0, 16).replace("T", " ")}`;
20
19
  }
21
- var DiscussionServiceError = class extends Error {
22
- constructor(code, message) {
23
- super(message);
24
- this.code = code;
25
- this.name = "DiscussionServiceError";
26
- }
27
- };
28
20
  var DiscussionService = class {
29
21
  createsInFlight = /* @__PURE__ */ new Map();
30
22
  mutationTails = /* @__PURE__ */ new Map();
31
23
  objectLinks = new ObjectLinkService();
24
+ recordingOwner = randomUUID();
25
+ recordingControllers = /* @__PURE__ */ new Map();
32
26
  constructor(notes, projects, emit) {
33
27
  this.notes = notes;
34
28
  this.projects = projects;
@@ -42,6 +36,7 @@ var DiscussionService = class {
42
36
  return acknowledgeDiscussionCaptureConsent(policyVersion);
43
37
  }
44
38
  async create(input) {
39
+ if (input.recordedAt !== void 0 && (!Number.isSafeInteger(input.recordedAt) || input.recordedAt < 0 || input.recordedAt > Date.now() + 6e4)) throw new DiscussionServiceError("invalid_input", "Invalid recording time");
45
40
  const clientRequestId = input.clientRequestId.trim();
46
41
  if (!clientRequestId || clientRequestId.length > 200) throw new DiscussionServiceError("invalid_input", "clientRequestId must be between 1 and 200 characters");
47
42
  const settings = getDiscussionCaptureSettings();
@@ -61,12 +56,12 @@ var DiscussionService = class {
61
56
  const project = input.contextProjectId ? this.projects.get(input.contextProjectId) : null;
62
57
  if (input.contextProjectId && !project) throw new DiscussionServiceError("invalid_input", "Project not found");
63
58
  const now = Date.now();
64
- const title = placeholderTitle(now);
59
+ const title = placeholderTitle(input.recordedAt ?? now);
65
60
  const note = await this.notes.createNote({
66
61
  title,
67
62
  markdown: "",
68
63
  kind: "voice",
69
- capturedVia: { channel: input.source }
64
+ capturedVia: { channel: input.source === "mobile" ? "app" : input.source }
70
65
  });
71
66
  const capture = {
72
67
  id: randomUUID(),
@@ -80,7 +75,7 @@ var DiscussionService = class {
80
75
  source: input.source,
81
76
  status: "recording",
82
77
  transcriptRevision: 0,
83
- recordingStartedAt: now,
78
+ recordingStartedAt: input.recordedAt ?? now,
84
79
  createdAt: now,
85
80
  updatedAt: now
86
81
  };
@@ -131,16 +126,17 @@ var DiscussionService = class {
131
126
  metrics() {
132
127
  return getDiscussionMetrics();
133
128
  }
134
- transcript(id) {
129
+ transcript(id, revision) {
135
130
  const capture = getDiscussionCapture(id);
136
131
  if (!capture) return null;
137
- const segments = listDiscussionTranscriptSegments(id);
132
+ const segments = revision === void 0 ? listDiscussionTranscriptSegments(id) : readTranscriptRevision(id, revision);
133
+ if (!segments) return null;
138
134
  const count = (status) => segments.filter((segment) => segment.status === status).length;
139
135
  return {
140
136
  discussionId: id,
141
- revision: capture.transcriptRevision,
137
+ revision: revision ?? capture.transcriptRevision,
142
138
  segments,
143
- text: capture.canonicalTranscript ?? assembleDiscussionTranscript(segments),
139
+ text: revision === void 0 ? capture.canonicalTranscript ?? assembleDiscussionTranscript(segments) : assembleDiscussionTranscript(segments),
144
140
  stats: {
145
141
  ...capture.expectedLastSequence != null ? { expected: capture.expectedLastSequence + 1 } : {},
146
142
  uploaded: count("uploaded"),
@@ -159,7 +155,7 @@ var DiscussionService = class {
159
155
  if (!Number.isInteger(input.sequence) || input.sequence < 0 || input.sequence > 2e3) throw new DiscussionServiceError("invalid_input", "Invalid segment sequence");
160
156
  if (input.file.mimeType !== "audio/wav" && input.file.mimeType !== "audio/x-wav") throw new DiscussionServiceError("invalid_input", "Live transcript segments must be WAV audio");
161
157
  if (input.file.buffer.length === 0 || input.file.buffer.length > 2097152) throw new DiscussionServiceError("invalid_input", "Live transcript segment is empty or too large");
162
- if (!Number.isFinite(input.startedAtMs) || !Number.isFinite(input.endedAtMs) || input.startedAtMs < 0 || input.endedAtMs <= input.startedAtMs || input.endedAtMs - input.startedAtMs > 25e3) throw new DiscussionServiceError("invalid_input", "Invalid live transcript segment timing");
158
+ if (!Number.isFinite(input.startedAtMs) || !Number.isFinite(input.endedAtMs) || input.startedAtMs < 0 || input.endedAtMs <= input.startedAtMs || input.endedAtMs > 72e5 || input.endedAtMs - input.startedAtMs > 25e3) throw new DiscussionServiceError("invalid_input", "Invalid live transcript segment timing");
163
159
  const actualSha256 = createHash("sha256").update(input.file.buffer).digest("hex");
164
160
  if (input.sha256 !== actualSha256) throw new DiscussionServiceError("invalid_input", "Segment checksum mismatch");
165
161
  if (originalRecordingSupersedesSegment) return this.transcript(input.discussionId);
@@ -186,89 +182,167 @@ var DiscussionService = class {
186
182
  }
187
183
  return this.transcript(input.discussionId);
188
184
  }
189
- correctSegment(id, sequence, displayText, expectedRevision) {
185
+ correctSegment(id, sequence, displayText, expectedRevision, speakerLabel, applyToSpeakerGroup = false, expectedTranscriptRevision) {
190
186
  const capture = getDiscussionCapture(id);
191
187
  if (!capture) throw new DiscussionServiceError("not_found", "Discussion not found");
192
- if (capture.status !== "recording" && capture.status !== "stopping") throw new DiscussionServiceError("conflict", "The sealed transcript cannot be changed");
188
+ if (![
189
+ "recording",
190
+ "stopping",
191
+ "completed",
192
+ "needs_attention"
193
+ ].includes(capture.status)) throw new DiscussionServiceError("conflict", "Wait until processing has finished before editing");
193
194
  const text = displayText.trim();
194
195
  if (!text || text.length > 2e4) throw new DiscussionServiceError("invalid_input", "Invalid transcript text");
195
- const segment = correctDiscussionTranscriptSegment(id, sequence, text, expectedRevision);
196
- if (!segment) throw new DiscussionServiceError("conflict", "Transcript segment changed; reload and try again");
197
- const transcript = this.transcript(id);
196
+ if (speakerLabel && speakerLabel.length > 100) throw new DiscussionServiceError("invalid_input", "Speaker label is too long");
197
+ if (applyToSpeakerGroup && expectedTranscriptRevision !== capture.transcriptRevision) throw new DiscussionServiceError("conflict", "Transcript changed; reload before renaming the speaker");
198
+ const transcript = runSqliteWriteTransaction((db) => {
199
+ const originalSpeaker = getDiscussionTranscriptSegment(id, sequence)?.speakerLabel;
200
+ saveTranscriptRevision(id);
201
+ if (!correctDiscussionTranscriptSegment(id, sequence, text, expectedRevision, speakerLabel)) throw new DiscussionServiceError("conflict", "Transcript segment changed; reload and try again");
202
+ if (applyToSpeakerGroup && originalSpeaker && speakerLabel !== void 0) db.prepare("UPDATE discussion_transcript_segments SET speaker_label=?, corrected_by_user=1, corrected_at=?, revision=revision+1, updated_at=? WHERE discussion_id=? AND speaker_label=? AND sequence<>?").run(speakerLabel.trim() || null, Date.now(), Date.now(), id, originalSpeaker, sequence);
203
+ if (capture.canonicalTranscript) refreshCanonicalTranscript(id);
204
+ saveTranscriptRevision(id);
205
+ return this.transcript(id);
206
+ });
198
207
  this.emit?.("discussion.segment.updated", {
199
208
  discussionId: id,
200
209
  noteId: capture.noteId,
201
- transcriptRevision: transcript.revision,
202
- segment,
203
- text: transcript.text,
204
- stats: transcript.stats
210
+ transcriptRevision: transcript.revision
205
211
  });
206
212
  return transcript;
207
213
  }
208
- async uploadRecording(id, file, durationMs) {
209
- return this.enqueueMutation(id, async () => {
210
- const capture = getDiscussionCapture(id);
211
- if (!capture) return null;
212
- if (capture.status !== "recording" && capture.status !== "stopping") throw new DiscussionServiceError("conflict", "Discussion no longer accepts its recording");
213
- if (!Number.isFinite(durationMs) || durationMs < 1e3 || durationMs > 18e5) throw new DiscussionServiceError("invalid_input", "durationMs must be between 1000 and 1800000");
214
- if (!isSupportedAudioMimeType(file.mimeType)) throw new DiscussionServiceError("invalid_input", "Unsupported audio type");
215
- if (file.buffer.length === 0 || file.buffer.length > 26214400) throw new DiscussionServiceError("invalid_input", "Audio must be between 1 byte and 25MB");
216
- const audioSha256 = createHash("sha256").update(file.buffer).digest("hex");
217
- if (capture.audioAttachmentId) {
218
- if (capture.audioSha256 === audioSha256) return this.detail(capture);
219
- throw new DiscussionServiceError("conflict", "Discussion already has different audio");
220
- }
221
- const attachment = await this.notes.addAttachment(capture.noteId, {
222
- name: file.name.trim().slice(0, 200) || `discussion-${id}.webm`,
223
- buffer: file.buffer,
224
- mimeType: file.mimeType,
225
- duration: Math.round(durationMs / 1e3),
226
- retainWithoutReference: true
227
- });
228
- if (!attachment) throw new DiscussionServiceError("not_found", "Discussion note not found");
229
- try {
230
- const updated = updateDiscussionCapture(id, {
231
- audioAttachmentId: attachment.id,
232
- durationMs: Math.round(durationMs),
233
- mimeType: file.mimeType,
234
- audioSizeBytes: file.buffer.length,
235
- audioSha256
236
- }, ["recording", "stopping"]);
237
- if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while storing audio");
238
- this.emit?.("discussion.updated", updated);
239
- return this.detail(updated);
240
- } catch (error) {
241
- await this.notes.removeAttachment(capture.noteId, attachment.id).catch(() => void 0);
242
- throw error;
243
- }
214
+ editSummary(id, input) {
215
+ const record = editMeeting(id, input);
216
+ this.emit?.("discussion.updated", {
217
+ id,
218
+ noteId: getDiscussionCapture(id)?.noteId,
219
+ organizationRevision: record.revision
244
220
  });
221
+ return record;
245
222
  }
246
- async stop(id, lastSequence, durationMs) {
223
+ recordingChunks(id) {
224
+ if (!getDiscussionCapture(id)) throw new DiscussionServiceError("not_found", "Discussion not found");
225
+ return listRecordingChunks(id);
226
+ }
227
+ async reorganize(id, template = "general") {
228
+ if (![
229
+ "general",
230
+ "project",
231
+ "review",
232
+ "interview"
233
+ ].includes(template)) throw new DiscussionServiceError("invalid_input", "Invalid meeting template");
247
234
  return this.enqueueMutation(id, async () => {
248
235
  const capture = getDiscussionCapture(id);
249
- if (!capture) return null;
250
- if (capture.status === "stopping" || capture.status === "sealing" || capture.status === "organizing" || capture.status === "completed") return this.detail(capture);
251
- if (capture.status !== "recording") throw new DiscussionServiceError("conflict", "Discussion cannot be stopped");
252
- if (!Number.isInteger(lastSequence) || lastSequence < -1 || lastSequence > 2e3) throw new DiscussionServiceError("invalid_input", "Invalid final segment sequence");
253
- if (!Number.isFinite(durationMs) || durationMs < 1e3 || durationMs > 18e5) throw new DiscussionServiceError("invalid_input", "Invalid recording duration");
236
+ if (!capture) throw new DiscussionServiceError("not_found", "Discussion not found");
237
+ if (!["completed", "needs_attention"].includes(capture.status) || !capture.canonicalTranscript) throw new DiscussionServiceError("conflict", "Transcript is not ready");
254
238
  const updated = updateDiscussionCapture(id, {
255
- status: "stopping",
256
- expectedLastSequence: lastSequence,
257
- durationMs: Math.round(durationMs),
258
- recordingStoppedAt: Date.now(),
259
- failureStage: void 0,
239
+ status: "organizing",
240
+ template,
260
241
  failureCode: void 0,
261
242
  failureMessage: void 0
262
- }, ["recording"]);
263
- if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while finishing");
243
+ }, [capture.status]);
264
244
  this.emit?.("discussion.updated", updated);
265
245
  return this.detail(updated);
266
246
  });
267
247
  }
248
+ async uploadRecordingChunk(id, sequence, sha256, body) {
249
+ return this.enqueueMutation(id, async () => {
250
+ const capture = getDiscussionCapture(id);
251
+ if (!capture) throw new DiscussionServiceError("not_found", "Discussion not found");
252
+ if (![
253
+ "recording",
254
+ "stopping",
255
+ "needs_attention"
256
+ ].includes(capture.status) || capture.audioDeletedAt || capture.audioAttachmentId) throw new DiscussionServiceError("conflict", "Recording no longer accepts chunks");
257
+ const job = getRecordingJob(id);
258
+ if (job && ["queued", "running"].includes(job.state)) throw new DiscussionServiceError("conflict", "Recording is being finalized");
259
+ return saveRecordingChunk(capture, sequence, sha256, body);
260
+ });
261
+ }
262
+ recordingJob(id) {
263
+ if (!getDiscussionCapture(id)) throw new DiscussionServiceError("not_found", "Discussion not found");
264
+ return getRecordingJob(id);
265
+ }
266
+ async sealRecording(id, input) {
267
+ if (!Number.isInteger(input.lastSequence) || input.lastSequence < -1 || input.lastSequence > 2e3) throw new DiscussionServiceError("invalid_input", "Invalid final segment sequence");
268
+ return this.enqueueMutation(id, async () => {
269
+ const capture = getDiscussionCapture(id);
270
+ if (!capture) throw new DiscussionServiceError("not_found", "Discussion not found");
271
+ const job = getRecordingJob(id);
272
+ if (capture.audioAttachmentId && job?.state === "completed") return submitRecordingJob(id, input);
273
+ if (![
274
+ "recording",
275
+ "stopping",
276
+ "needs_attention"
277
+ ].includes(capture.status) || capture.audioDeletedAt) throw new DiscussionServiceError("conflict", "Recording cannot be completed");
278
+ if (!capture.audioAttachmentId) validateRecordingManifest(capture, input);
279
+ const submitted = runSqliteWriteTransaction(() => {
280
+ const submitted = submitRecordingJob(id, input);
281
+ if (!updateDiscussionCapture(id, {
282
+ status: "stopping",
283
+ expectedLastSequence: input.lastSequence,
284
+ recordingStoppedAt: capture.recordingStoppedAt ?? Date.now(),
285
+ failureStage: void 0,
286
+ failureCode: void 0,
287
+ failureMessage: void 0
288
+ }, [capture.status])) throw new DiscussionServiceError("conflict", "Discussion changed while submitting recording");
289
+ return submitted;
290
+ });
291
+ this.emit?.("discussion.updated", getDiscussionCapture(id));
292
+ return submitted;
293
+ });
294
+ }
295
+ /** Runs inside the existing sealer lifecycle; unfinished leases survive process restart. */
296
+ async processRecordingJob() {
297
+ const job = claimRecordingJob(this.recordingOwner);
298
+ if (!job) return;
299
+ await this.enqueueMutation(job.discussionId, async () => {
300
+ const controller = new AbortController();
301
+ this.recordingControllers.set(job.discussionId, controller);
302
+ const assertLease = () => {
303
+ controller.signal.throwIfAborted();
304
+ if (!renewRecordingJob(job.id, this.recordingOwner)) throw new Error("Recording job lease expired");
305
+ };
306
+ const renewal = setInterval(() => {
307
+ if (!renewRecordingJob(job.id, this.recordingOwner)) controller.abort();
308
+ }, 15e3);
309
+ renewal.unref();
310
+ try {
311
+ assertLease();
312
+ const capture = getDiscussionCapture(job.discussionId);
313
+ if (!capture || capture.audioDeletedAt || capture.status === "cancelled") throw new Error("Recording is no longer available");
314
+ const updated = capture.audioAttachmentId ? capture : await completeRecording(capture, job.input, this.notes, assertLease, controller.signal);
315
+ if (finishRecordingJob(job.id, this.recordingOwner)) this.emit?.("discussion.updated", updated);
316
+ } catch (error) {
317
+ const message = error instanceof Error ? error.message : String(error);
318
+ if (finishRecordingJob(job.id, this.recordingOwner, message)) {
319
+ const failed = updateDiscussionCapture(job.discussionId, {
320
+ status: "needs_attention",
321
+ failureStage: "audio_upload",
322
+ failureCode: "recording_finalize_failed",
323
+ failureMessage: message.slice(0, 1e3)
324
+ }, ["stopping"]);
325
+ if (failed) this.emit?.("discussion.updated", failed);
326
+ }
327
+ log.warn({
328
+ err: error,
329
+ discussionId: job.discussionId,
330
+ jobId: job.id
331
+ }, "Recording finalization failed");
332
+ } finally {
333
+ clearInterval(renewal);
334
+ this.recordingControllers.delete(job.discussionId);
335
+ }
336
+ });
337
+ }
268
338
  async retry(id) {
269
339
  const capture = getDiscussionCapture(id);
270
340
  if (!capture) return null;
271
341
  if (capture.status !== "needs_attention") throw new DiscussionServiceError("conflict", "Only discussions needing attention can be retried");
342
+ if (getRecordingJob(id)?.state === "failed") {
343
+ await this.sealRecording(id, getRecordingJobInput(id));
344
+ return this.get(id);
345
+ }
272
346
  const updated = updateDiscussionCapture(id, {
273
347
  status: capture.canonicalTranscript ? "organizing" : "stopping",
274
348
  ...!capture.canonicalTranscript ? { recordingStoppedAt: Date.now() } : {},
@@ -281,15 +355,24 @@ var DiscussionService = class {
281
355
  return this.detail(updated);
282
356
  }
283
357
  async cancel(id) {
284
- const capture = getDiscussionCapture(id);
285
- if (!capture) return null;
286
- if (capture.status === "cancelled") return this.detail(capture);
287
- if (capture.status !== "recording" && capture.status !== "stopping") throw new DiscussionServiceError("conflict", "Only an active recording can be cancelled");
288
- const updated = updateDiscussionCapture(id, { status: "cancelled" }, ["recording", "stopping"]);
289
- if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while cancelling");
290
- deleteDiscussionSegmentAudio(id);
291
- this.emit?.("discussion.updated", updated);
292
- return this.detail(updated);
358
+ const active = getDiscussionCapture(id);
359
+ if (active && ["recording", "stopping"].includes(active.status)) {
360
+ cancelRecordingJob(id);
361
+ this.recordingControllers.get(id)?.abort();
362
+ }
363
+ return this.enqueueMutation(id, async () => {
364
+ const capture = getDiscussionCapture(id);
365
+ if (!capture) return null;
366
+ if (capture.status === "cancelled") return this.detail(capture);
367
+ if (capture.status !== "recording" && capture.status !== "stopping") throw new DiscussionServiceError("conflict", "Only an active recording can be cancelled");
368
+ const updated = updateDiscussionCapture(id, { status: "cancelled" }, ["recording", "stopping"]);
369
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while cancelling");
370
+ cancelRecordingJob(id);
371
+ deleteDiscussionSegmentAudio(id);
372
+ await removeRecordingChunks(capture);
373
+ this.emit?.("discussion.updated", updated);
374
+ return this.detail(updated);
375
+ });
293
376
  }
294
377
  async deleteAudio(id) {
295
378
  return this.enqueueMutation(id, async () => {
@@ -300,8 +383,9 @@ var DiscussionService = class {
300
383
  "needs_attention",
301
384
  "cancelled"
302
385
  ].includes(capture.status)) throw new DiscussionServiceError("conflict", "Audio cannot be deleted while discussion processing is active");
303
- if (!capture.audioAttachmentId) return this.detail(capture);
304
- await this.notes.removeAttachment(capture.noteId, capture.audioAttachmentId);
386
+ await removeRecordingChunks(capture);
387
+ cancelRecordingJob(id);
388
+ if (capture.audioAttachmentId) await this.notes.removeAttachment(capture.noteId, capture.audioAttachmentId);
305
389
  deleteDiscussionSegmentAudio(id);
306
390
  const updated = updateDiscussionCapture(id, {
307
391
  audioAttachmentId: void 0,
@@ -329,10 +413,12 @@ var DiscussionService = class {
329
413
  async detail(capture) {
330
414
  const note = await this.notes.getNote(capture.noteId);
331
415
  if (!note) throw new DiscussionServiceError("not_found", "Discussion note not found");
416
+ const recordingJob = getRecordingJob(capture.id);
332
417
  return {
333
418
  discussion: capture,
334
419
  note,
335
420
  transcript: this.transcript(capture.id),
421
+ ...recordingJob ? { recordingJob } : {},
336
422
  ...getLatestDiscussionOrganization(capture.id) ? { organization: getLatestDiscussionOrganization(capture.id) } : {}
337
423
  };
338
424
  }
@@ -1,145 +1,10 @@
1
- export declare const DISCUSSION_STATUSES: readonly ["recording", "stopping", "sealing", "organizing", "completed", "needs_attention", "cancelled"];
2
- export type DiscussionStatus = typeof DISCUSSION_STATUSES[number];
3
- export type DiscussionSource = 'web' | 'electron';
4
- export type DiscussionProjectInferenceSource = 'context' | 'exact_name' | 'model';
5
- export type DiscussionFailureStage = 'segment_upload' | 'segment_transcription' | 'audio_upload' | 'transcript_sealing' | 'organization';
6
- export interface DiscussionActionItem {
7
- id: string;
8
- title: string;
9
- owner?: string;
10
- dueDate?: string;
11
- evidenceSegmentIds?: number[];
12
- }
13
- export interface DiscussionOrganization {
14
- title: string;
15
- summary: string;
16
- keyPoints: string[];
17
- decisions: string[];
18
- actionItems: DiscussionActionItem[];
19
- risks: string[];
20
- openQuestions: string[];
21
- projectCandidateId?: string;
22
- projectConfidence?: number;
23
- projectAlternativeConfidence?: number;
24
- }
25
- export interface DiscussionCapture {
26
- id: string;
27
- clientRequestId: string;
28
- noteId: string;
29
- projectId?: string;
30
- audioAttachmentId?: string;
31
- source: DiscussionSource;
32
- status: DiscussionStatus;
33
- durationMs?: number;
34
- expectedLastSequence?: number;
35
- mimeType?: string;
36
- audioSizeBytes?: number;
37
- audioSha256?: string;
38
- canonicalTranscript?: string;
39
- canonicalTranscriptSha256?: string;
40
- transcriptLanguage?: string;
41
- transcriptRevision: number;
42
- generatedTitle?: string;
43
- projectInferenceScore?: number;
44
- projectInferenceSource?: DiscussionProjectInferenceSource;
45
- failureStage?: DiscussionFailureStage;
46
- failureCode?: string;
47
- failureMessage?: string;
48
- recordingStartedAt: number;
49
- recordingStoppedAt?: number;
50
- createdAt: number;
51
- updatedAt: number;
52
- completedAt?: number;
53
- audioDeletedAt?: number;
54
- }
55
- export type DiscussionTranscriptSegmentStatus = 'uploaded' | 'transcribing' | 'confirmed' | 'failed';
56
- export interface DiscussionTranscriptSegment {
57
- discussionId: string;
58
- sequence: number;
59
- audioSha256: string;
60
- startedAtMs: number;
61
- endedAtMs: number;
62
- status: DiscussionTranscriptSegmentStatus;
63
- rawText?: string;
64
- displayText?: string;
65
- language?: string;
66
- provider?: string;
67
- confidence?: number;
68
- speakerLabel?: string;
69
- revision: number;
70
- correctedByUser: boolean;
71
- correctedAt?: number;
72
- attemptCount: number;
73
- nextAttemptAt?: number;
74
- leaseOwner?: string;
75
- leaseExpiresAt?: number;
76
- lastError?: string;
77
- createdAt: number;
78
- updatedAt: number;
79
- }
80
- export interface DiscussionTranscriptStats {
81
- expected?: number;
82
- uploaded: number;
83
- transcribing: number;
84
- confirmed: number;
85
- failed: number;
86
- }
87
- export interface DiscussionTranscript {
88
- discussionId: string;
89
- revision: number;
90
- segments: DiscussionTranscriptSegment[];
91
- text: string;
92
- stats: DiscussionTranscriptStats;
93
- }
94
- export interface DiscussionOrganizationRecord {
95
- id: string;
96
- discussionId: string;
97
- revision: number;
98
- inputTranscriptSha256: string;
99
- promptVersion: string;
100
- modelRef: string;
101
- organization?: DiscussionOrganization;
102
- status: 'running' | 'completed' | 'failed';
103
- errorMessage?: string;
104
- createdAt: number;
105
- completedAt?: number;
106
- }
107
- export interface DiscussionCaptureSettings {
108
- consentPolicyVersion: number;
109
- consentAcknowledgedAt?: number;
110
- }
111
- export interface CreateDiscussionInput {
112
- clientRequestId: string;
113
- contextProjectId?: string;
114
- consentPolicyVersion: number;
115
- source: DiscussionSource;
116
- }
117
- export interface ListDiscussionsQuery {
118
- status?: DiscussionStatus | 'active';
119
- projectId?: string;
120
- limit?: number;
121
- offset?: number;
122
- }
123
- export interface DiscussionListResult {
124
- items: DiscussionCapture[];
125
- total: number;
126
- limit: number;
127
- offset: number;
128
- hasMore: boolean;
129
- }
130
- export interface DiscussionMetrics {
131
- total: number;
132
- byStatus: Record<DiscussionStatus, number>;
133
- averageTimeToFirstTranscriptMs: number | null;
134
- averageTimeToCompleteMs: number | null;
135
- totalSegments: number;
136
- failedSegments: number;
137
- retriedSegments: number;
138
- averageSegmentLatencyMs: number | null;
139
- }
1
+ export { DISCUSSION_STATUSES } from '@xopcai/gateway-contract';
2
+ export type { DiscussionStatus, DiscussionSource, DiscussionProjectInferenceSource, DiscussionFailureStage, DiscussionFact, DiscussionChapter, DiscussionTemplate, DiscussionActionItem, DiscussionOrganization, DiscussionCapture, DiscussionTranscriptSegmentStatus, DiscussionTranscriptSegment, DiscussionTranscriptStats, DiscussionTranscript, DiscussionOrganizationRecord, DiscussionCaptureSettings, CreateDiscussionInput, ListDiscussionsQuery, DiscussionListResult, DiscussionMetrics, } from '@xopcai/gateway-contract';
3
+ import type { DiscussionCapture, DiscussionTranscript, DiscussionOrganizationRecord } from '@xopcai/gateway-contract';
140
4
  export interface DiscussionDetail {
141
5
  discussion: DiscussionCapture;
142
6
  note: import('../notes/types.js').Note;
143
7
  transcript: DiscussionTranscript;
144
8
  organization?: DiscussionOrganizationRecord;
9
+ recordingJob?: import('@xopcai/gateway-contract').DiscussionRecordingJob;
145
10
  }
@@ -1,12 +1,2 @@
1
- //#region src/discussions/types.ts
2
- const DISCUSSION_STATUSES = [
3
- "recording",
4
- "stopping",
5
- "sealing",
6
- "organizing",
7
- "completed",
8
- "needs_attention",
9
- "cancelled"
10
- ];
11
- //#endregion
1
+ import { DISCUSSION_STATUSES } from "../../packages/gateway-contract/src/discussions.js";
12
2
  export { DISCUSSION_STATUSES };
@@ -1,7 +1,7 @@
1
1
  import { createLogger } from "../utils/logger/index.js";
2
2
  import { init_logger } from "../utils/logger.js";
3
- import { init_paths, resolveExtensionsDir, resolveExtensionsLockPath } from "../config/paths.js";
4
3
  import { init_write_file_atomic, writeTextAtomic } from "../infra/write-file-atomic.js";
4
+ import { init_paths, resolveExtensionsDir, resolveExtensionsLockPath } from "../config/paths.js";
5
5
  import { dirname, join } from "path";
6
6
  import { existsSync } from "fs";
7
7
  import { mkdir, readFile } from "fs/promises";
@@ -42,4 +42,5 @@ export declare class FileSpaceService {
42
42
  }>;
43
43
  rootResource(spaceId: string): Promise<FileResource>;
44
44
  children(spaceId: string, path?: string): Promise<FileResource[]>;
45
+ recent(limit: number, maxFilesPerSpace?: number): Promise<FileResource[]>;
45
46
  }
@@ -167,10 +167,83 @@ const MIME_TYPES = {
167
167
  mov: "video/quicktime",
168
168
  webm: "video/webm"
169
169
  };
170
+ const RECENT_DIRECTORY_CONCURRENCY = 8;
171
+ const RECENT_RESOURCE_CONCURRENCY = 16;
170
172
  function isWithin(root, target) {
171
173
  const rel = relative(root, target);
172
174
  return rel === "" || !rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel);
173
175
  }
176
+ async function mapWithConcurrency(values, concurrency, mapper) {
177
+ const results = new Array(values.length);
178
+ let next = 0;
179
+ await Promise.all(Array.from({ length: Math.min(concurrency, values.length) }, async () => {
180
+ while (next < values.length) {
181
+ const index = next++;
182
+ results[index] = await mapper(values[index]);
183
+ }
184
+ }));
185
+ return results;
186
+ }
187
+ async function collectRecentCandidates(space, limit, maxFiles) {
188
+ const directories = [{
189
+ absolutePath: space.root,
190
+ displayPath: space.root
191
+ }];
192
+ const visited = /* @__PURE__ */ new Set();
193
+ const candidates = [];
194
+ let filesSeen = 0;
195
+ while (directories.length > 0 && filesSeen < maxFiles) {
196
+ const batch = directories.splice(0, RECENT_DIRECTORY_CONCURRENCY).filter((directory) => {
197
+ if (visited.has(directory.absolutePath)) return false;
198
+ visited.add(directory.absolutePath);
199
+ return true;
200
+ });
201
+ const scanned = await Promise.all(batch.map(async (directory) => {
202
+ const entries = [];
203
+ const handle = await opendir(directory.absolutePath).catch(() => null);
204
+ if (!handle) return entries;
205
+ for await (const entry of handle) if (!SKIPPED_NAMES.has(entry.name)) entries.push(entry);
206
+ return mapWithConcurrency(entries, RECENT_RESOURCE_CONCURRENCY, async (entry) => {
207
+ const absolutePath = resolve(directory.absolutePath, entry.name);
208
+ const displayPath = resolve(directory.displayPath, entry.name);
209
+ if (entry.isDirectory()) return { directory: {
210
+ absolutePath,
211
+ displayPath
212
+ } };
213
+ if (entry.isFile()) {
214
+ const info = await stat(absolutePath).catch(() => null);
215
+ return info?.isFile() ? { file: {
216
+ space,
217
+ absolutePath,
218
+ displayPath,
219
+ modifiedAt: Math.max(0, Math.round(info.mtimeMs))
220
+ } } : {};
221
+ }
222
+ if (!entry.isSymbolicLink()) return {};
223
+ const canonicalTarget = await realpath(absolutePath).catch(() => null);
224
+ if (!canonicalTarget || !isWithin(space.root, canonicalTarget)) return {};
225
+ const info = await stat(canonicalTarget).catch(() => null);
226
+ if (info?.isDirectory()) return { directory: {
227
+ absolutePath: canonicalTarget,
228
+ displayPath
229
+ } };
230
+ return info?.isFile() ? { file: {
231
+ space,
232
+ absolutePath: canonicalTarget,
233
+ displayPath,
234
+ modifiedAt: Math.max(0, Math.round(info.mtimeMs))
235
+ } } : {};
236
+ });
237
+ }));
238
+ for (const result of scanned.flat()) {
239
+ if (result.directory) directories.push(result.directory);
240
+ if (!result.file || filesSeen >= maxFiles) continue;
241
+ filesSeen += 1;
242
+ candidates.push(result.file);
243
+ }
244
+ }
245
+ return candidates.sort((a, b) => b.modifiedAt - a.modifiedAt).slice(0, limit);
246
+ }
174
247
  function normalizeRelativePath(input) {
175
248
  const normalized = input.replace(/\\/g, "/").replace(/^\/+/, "").replace(/\/+$/, "");
176
249
  if (normalized.includes("\0") || normalized.split("/").some((part) => part === "..")) throw new FileServiceError(400, "Invalid file path");
@@ -439,6 +512,10 @@ var FileSpaceService = class {
439
512
  }
440
513
  return entries.sort((a, b) => a.kind === b.kind ? a.name.localeCompare(b.name) : a.kind === "directory" ? -1 : 1);
441
514
  }
515
+ async recent(limit, maxFilesPerSpace = 5e3) {
516
+ const spaces = await this.list();
517
+ return (await mapWithConcurrency((await Promise.all(spaces.map((space) => collectRecentCandidates(space, limit, maxFilesPerSpace)))).flat().sort((a, b) => b.modifiedAt - a.modifiedAt).slice(0, limit), RECENT_RESOURCE_CONCURRENCY, async (candidate) => fileResourceFromPath(candidate.space, candidate.absolutePath, candidate.displayPath).catch(() => null))).filter((resource) => resource !== null);
518
+ }
442
519
  };
443
520
  //#endregion
444
521
  export { FileServiceError, FileSpaceService, fileResourceFromPath, fileResourceId, fileSpaceId, parseFileResourceId, resolveFilePath };