@xopcai/xopc 0.0.178 → 0.0.180

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 (450) hide show
  1. package/dist/browser-ext/manifest.json +1 -1
  2. package/dist/extensions/telegram/xopc.extension.json +1 -1
  3. package/dist/gateway/static/root/assets/{activity-store-BzGX__8I.js → activity-store-DFKtQDgE.js} +1 -1
  4. package/dist/gateway/static/root/assets/{agent-avatar-dicebear-BzxntcaZ.js → agent-avatar-dicebear-CcTvB_4W.js} +1 -1
  5. package/dist/gateway/static/root/assets/agents-DgGj9bu6.js +2 -0
  6. package/dist/gateway/static/root/assets/{api-hi6f2Xtp.js → api-CUClAvuL.js} +1 -1
  7. package/dist/gateway/static/root/assets/{app-management-settings-panel-fTY1F7ac.js → app-management-settings-panel-Ib-cWKHM.js} +1 -1
  8. package/dist/gateway/static/root/assets/{appearance-settings-BZ-IxQfB.js → appearance-settings-CRSN8WuA.js} +1 -1
  9. package/dist/gateway/static/root/assets/apps-page-B5xahuT-.js +1 -0
  10. package/dist/gateway/static/root/assets/{archive-plugin-ZSLLG8wB.js → archive-plugin-BBF9zspN.js} +1 -1
  11. package/dist/gateway/static/root/assets/{attachment-load-D4YktqIF.js → attachment-load-D3QQfA7w.js} +1 -1
  12. package/dist/gateway/static/root/assets/automations-page-BUYrvMmD.js +6 -0
  13. package/dist/gateway/static/root/assets/{binary-plugins-BIkPxfxj.js → binary-plugins-D78XOjJ3.js} +1 -1
  14. package/dist/gateway/static/root/assets/{block-editor-Q3v_YQom.js → block-editor-D_VVXlYX.js} +46 -46
  15. package/dist/gateway/static/root/assets/browser-settings-page-Cjw07q9h.js +8 -0
  16. package/dist/gateway/static/root/assets/{browser-workflow-inputs-M5lTEW1I.js → browser-workflow-inputs-_gu08MA4.js} +1 -1
  17. package/dist/gateway/static/root/assets/{browser-workflows-page-B3T1KYk4.js → browser-workflows-page-58DBMXgG.js} +2 -2
  18. package/dist/gateway/static/root/assets/capabilities-settings-panel-D_dfVxHY.js +7 -0
  19. package/dist/gateway/static/root/assets/{capability-presets-api-DE09c0VB.js → capability-presets-api-DhCn9rPv.js} +1 -1
  20. package/dist/gateway/static/root/assets/capability-presets-settings-panel-LWWIYUT7.js +1 -0
  21. package/dist/gateway/static/root/assets/{channel-recipient-api-CFR5FBfS.js → channel-recipient-api-DYtoNV5A.js} +1 -1
  22. package/dist/gateway/static/root/assets/channels-settings-Cyj5OunL.js +1 -0
  23. package/dist/gateway/static/root/assets/channels-status-swr-TEFnV0IG.js +1 -0
  24. package/dist/gateway/static/root/assets/{connector-logo-B1mD-3pM.js → connector-logo-CxIXnifA.js} +1 -1
  25. package/dist/gateway/static/root/assets/connectors-page-0TobCpBB.js +2 -0
  26. package/dist/gateway/static/root/assets/desktop-pet-CwVSLL6N.js +1 -0
  27. package/dist/gateway/static/root/assets/desktop-pet-settings-BOIp4q0Z.js +1 -0
  28. package/dist/gateway/static/root/assets/{directory-picker-path-field-BYQJ2XUS.js → directory-picker-path-field-C8_lsW6S.js} +1 -1
  29. package/dist/gateway/static/root/assets/{dreaming-settings-BXcb8fwx.js → dreaming-settings-Da9FeWQl.js} +3 -3
  30. package/dist/gateway/static/root/assets/{extension-debug-page-O1Xu1r4Q.js → extension-debug-page-CMNnSJP6.js} +1 -1
  31. package/dist/gateway/static/root/assets/{extension-page-C2Jraq8l.js → extension-page-t0pJqLvM.js} +1 -1
  32. package/dist/gateway/static/root/assets/{extension-settings-page-DcXQDTpG.js → extension-settings-page-aiR-tKnW.js} +1 -1
  33. package/dist/gateway/static/root/assets/{gateway-config-api-DmPIZrvg.js → gateway-config-api-CkyQBD6r.js} +1 -1
  34. package/dist/gateway/static/root/assets/gateway-settings-BIZfAgfB.js +1 -0
  35. package/dist/gateway/static/root/assets/{gateway-startup-retry-BZEkOdPm.js → gateway-startup-retry-CSU7UvzG.js} +1 -1
  36. package/dist/gateway/static/root/assets/goals-settings-Dx9uTyWF.js +1 -0
  37. package/dist/gateway/static/root/assets/{heartbeat-settings-BtKu2NWT.js → heartbeat-settings-Cnr5ORXL.js} +1 -1
  38. package/dist/gateway/static/root/assets/{image-generation-api-05Gg8wsU.js → image-generation-api-BR6Kb77X.js} +1 -1
  39. package/dist/gateway/static/root/assets/index-B0Tdge_q.css +1 -0
  40. package/dist/gateway/static/root/assets/index-BA56bQ68.js +224 -0
  41. package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CfmDR9Sc.js → keyboard-shortcuts-settings-D76uYKeQ.js} +1 -1
  42. package/dist/gateway/static/root/assets/local-app-workbench-page-DSUNtLHg.js +2 -0
  43. package/dist/gateway/static/root/assets/{local-apps-page-Df8BW1A_.js → local-apps-page-BsLnojTh.js} +1 -1
  44. package/dist/gateway/static/root/assets/{locale-store-CTwSy7yW.js → locale-store-BNC8HUCI.js} +1 -1
  45. package/dist/gateway/static/root/assets/logs-page-yI8kpQtO.js +7 -0
  46. package/dist/gateway/static/root/assets/{markdown-split-B3FvKbaP.js → markdown-split-D6smTQWD.js} +1 -1
  47. package/dist/gateway/static/root/assets/{media-plugins-DsjrvvB6.js → media-plugins-D1TmMF0A.js} +1 -1
  48. package/dist/gateway/static/root/assets/messages-H_iJjnEM.js +3 -0
  49. package/dist/gateway/static/root/assets/{notes-page-tuzIwy3c.js → notes-page-Vt-diFf0.js} +1 -1
  50. package/dist/gateway/static/root/assets/notes-workbench-BD297EZt.js +4 -0
  51. package/dist/gateway/static/root/assets/outcome-detail-page-C5i_ZR9a.js +1 -0
  52. package/dist/gateway/static/root/assets/{page-tabs-ke9s4nYi.js → page-tabs-BnSzIjH0.js} +1 -1
  53. package/dist/gateway/static/root/assets/{preview-open-alternatives-Cp1eI4S9.js → preview-open-alternatives-CF12Eqi4.js} +1 -1
  54. package/dist/gateway/static/root/assets/product-delivery-BVG-eWQO.js +1 -0
  55. package/dist/gateway/static/root/assets/{product-open-page-dXQCMAtH.js → product-open-page-Du0TE_CU.js} +1 -1
  56. package/dist/gateway/static/root/assets/project-detail-page-D1HGz7Dy.js +1 -0
  57. package/dist/gateway/static/root/assets/projects-page-CqP2TnW-.js +1 -0
  58. package/dist/gateway/static/root/assets/remote-access-hub-BXdVJLS3.js +1 -0
  59. package/dist/gateway/static/root/assets/{save-bar-controls-Bl30zjHc.js → save-bar-controls-B53UNtDM.js} +1 -1
  60. package/dist/gateway/static/root/assets/{schedule-field-DqhmXQKo.js → schedule-field-C3dIMN5q.js} +1 -1
  61. package/dist/gateway/static/root/assets/{schema-form-DkUCy6vf.js → schema-form-BKvpxqof.js} +1 -1
  62. package/dist/gateway/static/root/assets/{schemas-DgQq6Z5S.js → schemas-C6onAggk.js} +2 -2
  63. package/dist/gateway/static/root/assets/sessions-page-CPAwDdl5.js +1 -0
  64. package/dist/gateway/static/root/assets/settings-advanced-gate-C-jGsoft.js +1 -0
  65. package/dist/gateway/static/root/assets/{settings-form-section-C6ZbNpdy.js → settings-form-section-kt6GIS32.js} +1 -1
  66. package/dist/gateway/static/root/assets/{settings-loading-skeleton-Bk58_cr7.js → settings-loading-skeleton-85eUPcKz.js} +1 -1
  67. package/dist/gateway/static/root/assets/{settings-page-k8iE_NdX.js → settings-page-Di5x6Xrc.js} +1 -1
  68. package/dist/gateway/static/root/assets/{settings-page-layout-DgFSya2e.js → settings-page-layout-CkkCee_x.js} +1 -1
  69. package/dist/gateway/static/root/assets/{setup-status-panel-B_KDlyZA.js → setup-status-panel-uxEE6gAB.js} +1 -1
  70. package/dist/gateway/static/root/assets/{share-preview-page-CV7nL_J9.js → share-preview-page-BtLEstaI.js} +1 -1
  71. package/dist/gateway/static/root/assets/shares-settings-2mcsMWgn.js +1 -0
  72. package/dist/gateway/static/root/assets/skill-reload-api-ocDwsrEt.js +1 -0
  73. package/dist/gateway/static/root/assets/skills-page-9_jfsFU5.js +2 -0
  74. package/dist/gateway/static/root/assets/{skills-page.utils-kahBIHg0.js → skills-page.utils-C2SHvq8V.js} +1 -1
  75. package/dist/gateway/static/root/assets/{system-settings-panel-JV3-MG5F.js → system-settings-panel-CIJ66cUy.js} +1 -1
  76. package/dist/gateway/static/root/assets/{theme-store-DFLyzoLO.js → theme-store-ClDa_Bci.js} +1 -1
  77. package/dist/gateway/static/root/assets/time-picker-D0zAsHOo.js +1 -0
  78. package/dist/gateway/static/root/assets/{typed-models-lib-60Aqo-UJ.js → typed-models-lib-GsmwmB40.js} +1 -1
  79. package/dist/gateway/static/root/assets/{use-channel-catalog-CnTkHDnw.js → use-channel-catalog-BQz3K7xj.js} +1 -1
  80. package/dist/gateway/static/root/assets/user-context-page-Bn4BLg4W.js +1 -0
  81. package/dist/gateway/static/root/assets/{voice-api-key-field-CNa8qUgS.js → voice-api-key-field-Blaxsbb3.js} +1 -1
  82. package/dist/gateway/static/root/assets/work-copy-Cwyan9Sn.js +1 -0
  83. package/dist/gateway/static/root/assets/work-discovery-page-iJ9RBSpu.js +1 -0
  84. package/dist/gateway/static/root/assets/work-item-detail-page-CqTH7PQX.js +1 -0
  85. package/dist/gateway/static/root/assets/work-page-H3m99ccS.js +3 -0
  86. package/dist/gateway/static/root/assets/{workflow-run-setup-panel-yV_x595t.js → workflow-run-setup-panel-CTG4RpRE.js} +2 -2
  87. package/dist/gateway/static/root/assets/workflows-page-BxZSKMfv.js +11 -0
  88. package/dist/gateway/static/root/index.html +9 -10
  89. package/dist/package.js +1 -1
  90. package/dist/packages/gateway-contract/src/outcomes.js +137 -0
  91. package/dist/packages/gateway-contract/src/product-delivery.js +1 -0
  92. package/dist/packages/gateway-contract/src/work-intake.js +85 -0
  93. package/dist/src/activity/service.d.ts +1 -0
  94. package/dist/src/activity/service.js +4 -1
  95. package/dist/src/agent/agent-manager.d.ts +4 -1
  96. package/dist/src/agent/agent-manager.js +21 -7
  97. package/dist/src/agent/bootstrap/filter-bootstrap-files.js +1 -1
  98. package/dist/src/agent/code-intelligence/binary.js +1 -1
  99. package/dist/src/agent/context/execution-scope.d.ts +1 -1
  100. package/dist/src/agent/context/execution-scope.js +6 -4
  101. package/dist/src/agent/embedded/model-runtime.js +1 -1
  102. package/dist/src/agent/embedded/run-for-session.d.ts +2 -0
  103. package/dist/src/agent/embedded/run-for-session.js +3 -1
  104. package/dist/src/agent/goals/post-turn.js +7 -6
  105. package/dist/src/agent/image/tool-model-config.js +1 -1
  106. package/dist/src/agent/inbound/turn-dispatcher.d.ts +6 -1
  107. package/dist/src/agent/inbound/turn-dispatcher.js +5 -2
  108. package/dist/src/agent/ipc/inbox.js +1 -1
  109. package/dist/src/agent/lifecycle/timeout-wrapper.d.ts +2 -0
  110. package/dist/src/agent/lifecycle/timeout-wrapper.js +39 -9
  111. package/dist/src/agent/mcp/bundle-mcp-materialize.js +2 -2
  112. package/dist/src/agent/mcp/bundle-mcp-runtime.js +6 -1
  113. package/dist/src/agent/mcp/bundle-mcp-types.d.ts +1 -1
  114. package/dist/src/agent/mcp/mcp-stdio-transport.js +1 -1
  115. package/dist/src/agent/mcp/mcp-transport-config.d.ts +2 -0
  116. package/dist/src/agent/mcp/mcp-transport-config.js +10 -3
  117. package/dist/src/agent/mcp/mcp-transport.d.ts +1 -0
  118. package/dist/src/agent/mcp/mcp-transport.js +5 -2
  119. package/dist/src/agent/memory/dreaming/config.js +1 -1
  120. package/dist/src/agent/memory/user-context-coordinator.d.ts +1 -0
  121. package/dist/src/agent/memory/user-context-coordinator.js +17 -1
  122. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.d.ts +1 -1
  123. package/dist/src/agent/orchestration/run-agent-turn-with-timeout.js +6 -2
  124. package/dist/src/agent/outcomes/{task-evaluation.d.ts → execution-evaluation.d.ts} +3 -3
  125. package/dist/src/agent/outcomes/{task-evaluation.js → execution-evaluation.js} +5 -5
  126. package/dist/src/agent/outcomes/execution-verifier.d.ts +29 -0
  127. package/dist/src/agent/outcomes/{task-verifier.js → execution-verifier.js} +4 -4
  128. package/dist/src/agent/service/direct-turn-helpers.d.ts +1 -0
  129. package/dist/src/agent/service/direct-turn-helpers.js +1 -0
  130. package/dist/src/agent/service/process-direct-one-shot.d.ts +3 -0
  131. package/dist/src/agent/service/process-direct-one-shot.js +4 -1
  132. package/dist/src/agent/service/process-direct-streaming.js +3 -1
  133. package/dist/src/agent/service.d.ts +1 -0
  134. package/dist/src/agent/service.js +5 -2
  135. package/dist/src/agent/session/session-inspector.js +1 -1
  136. package/dist/src/agent/skills/config.js +6 -0
  137. package/dist/src/agent/skills/filesystem-watcher.d.ts +4 -0
  138. package/dist/src/agent/skills/filesystem-watcher.js +16 -7
  139. package/dist/src/agent/skills/hub-lock.js +1 -1
  140. package/dist/src/agent/skills/index.d.ts +10 -7
  141. package/dist/src/agent/skills/index.js +56 -47
  142. package/dist/src/agent/skills/skill-manage-ops.js +1 -1
  143. package/dist/src/agent/skills/skill-manager.d.ts +5 -2
  144. package/dist/src/agent/skills/skill-manager.js +10 -5
  145. package/dist/src/agent/skills/skill-sources.d.ts +18 -0
  146. package/dist/src/agent/skills/skill-sources.js +143 -0
  147. package/dist/src/agent/skills/types.d.ts +25 -2
  148. package/dist/src/agent/tools/delegate-tool.js +2 -1
  149. package/dist/src/agent/tools/executor.d.ts +2 -0
  150. package/dist/src/agent/tools/executor.js +18 -11
  151. package/dist/src/agent/tools/factory.js +18 -1
  152. package/dist/src/agent/tools/goal-tool.js +5 -3
  153. package/dist/src/agent/tools/index.d.ts +1 -0
  154. package/dist/src/agent/tools/index.js +2 -1
  155. package/dist/src/agent/tools/managed-job-tool.d.ts +2 -0
  156. package/dist/src/agent/tools/managed-job-tool.js +160 -0
  157. package/dist/src/agent/tools/todo-tool.d.ts +5 -0
  158. package/dist/src/agent/tools/todo-tool.js +16 -4
  159. package/dist/src/agent/tools/update-plan-tool.d.ts +1 -0
  160. package/dist/src/agent/tools/update-plan-tool.js +6 -0
  161. package/dist/src/agent/workspace-runtime/registry.d.ts +3 -0
  162. package/dist/src/agent/workspace-runtime/registry.js +3 -1
  163. package/dist/src/agent-runtime/index.js +1 -1
  164. package/dist/src/agent-runtime/runtime-profile.js +1 -1
  165. package/dist/src/auth/credentials.d.ts +9 -1
  166. package/dist/src/auth/credentials.js +112 -56
  167. package/dist/src/auth/model-credential-store.js +7 -22
  168. package/dist/src/auth/oauth-provider-lock.d.ts +5 -0
  169. package/dist/src/auth/oauth-provider-lock.js +91 -0
  170. package/dist/src/auth/profiles/store.js +1 -1
  171. package/dist/src/auth/sync-provider-auth.js +2 -2
  172. package/dist/src/automations/domain/defaults.d.ts +8 -1
  173. package/dist/src/automations/domain/defaults.js +9 -2
  174. package/dist/src/automations/domain/index.js +2 -2
  175. package/dist/src/automations/domain/types.d.ts +33 -7
  176. package/dist/src/automations/domain/validation.d.ts +8 -0
  177. package/dist/src/automations/domain/validation.js +2 -0
  178. package/dist/src/automations/draft/automation-draft-validator.d.ts +2 -1
  179. package/dist/src/automations/index.js +2 -2
  180. package/dist/src/automations/service/action-executor.d.ts +2 -2
  181. package/dist/src/automations/service/action-executor.js +144 -34
  182. package/dist/src/automations/service/automation-service.d.ts +4 -0
  183. package/dist/src/automations/service/automation-service.js +197 -18
  184. package/dist/src/automations/storage/automation-run-repository.d.ts +1 -0
  185. package/dist/src/automations/storage/automation-run-repository.js +37 -7
  186. package/dist/src/automations/storage/index.js +2 -2
  187. package/dist/src/browser/providers/browser-ext-install.js +1 -1
  188. package/dist/src/browser/providers/cloakbrowser.js +2 -2
  189. package/dist/src/channels/attachments/voice-stt-webchat.d.ts +0 -3
  190. package/dist/src/channels/attachments/voice-stt-webchat.js +1 -24
  191. package/dist/src/chat-commands/builtins/goal.js +5 -4
  192. package/dist/src/cli/commands/agents.js +1 -1
  193. package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
  194. package/dist/src/cli/commands/doctor/checks/provider-auth.js +1 -1
  195. package/dist/src/cli/commands/goal.js +6 -5
  196. package/dist/src/cli/commands/models.js +1 -1
  197. package/dist/src/cli/commands/tunnel.js +3 -3
  198. package/dist/src/cli/utils/oauth-login.js +1 -7
  199. package/dist/src/config/agent-profile.js +1 -1
  200. package/dist/src/config/index.js +4 -4
  201. package/dist/src/config/loader.js +1 -1
  202. package/dist/src/config/models-json.js +1 -1
  203. package/dist/src/config/schema.d.ts +3 -0
  204. package/dist/src/config/schema.js +2 -1
  205. package/dist/src/connectors/learning-coordinator.js +7 -1
  206. package/dist/src/connectors/source-change-publisher.d.ts +17 -0
  207. package/dist/src/connectors/source-change-publisher.js +148 -0
  208. package/dist/src/discussions/analyzer.d.ts +35 -0
  209. package/dist/src/discussions/analyzer.js +127 -0
  210. package/dist/src/discussions/index.d.ts +7 -0
  211. package/dist/src/discussions/index.js +7 -0
  212. package/dist/src/discussions/live-worker.d.ts +33 -0
  213. package/dist/src/discussions/live-worker.js +148 -0
  214. package/dist/src/discussions/pipeline.d.ts +33 -0
  215. package/dist/src/discussions/pipeline.js +205 -0
  216. package/dist/src/discussions/project-inference.d.ts +13 -0
  217. package/dist/src/discussions/project-inference.js +19 -0
  218. package/dist/src/discussions/repository.d.ts +29 -0
  219. package/dist/src/discussions/repository.js +255 -0
  220. package/dist/src/discussions/service.d.ts +51 -0
  221. package/dist/src/discussions/service.js +335 -0
  222. package/dist/src/discussions/transcript.d.ts +3 -0
  223. package/dist/src/discussions/transcript.js +15 -0
  224. package/dist/src/discussions/types.d.ts +116 -0
  225. package/dist/src/discussions/types.js +10 -0
  226. package/dist/src/discussions/worker.d.ts +18 -0
  227. package/dist/src/discussions/worker.js +74 -0
  228. package/dist/src/extensions/activation-context.js +2 -1
  229. package/dist/src/extensions/lockfile.js +1 -1
  230. package/dist/src/extensions/sdk/testing.js +1 -1
  231. package/dist/src/gateway/agents-admin.js +3 -3
  232. package/dist/src/gateway/chat-stream/mapper.js +53 -3
  233. package/dist/src/gateway/chat-stream/protocol.d.ts +17 -1
  234. package/dist/src/gateway/file-path-classifier.js +1 -1
  235. package/dist/src/gateway/hono/app.js +8 -3
  236. package/dist/src/gateway/hono/lib/config-payload.js +2 -2
  237. package/dist/src/gateway/hono/middleware/auth.js +2 -2
  238. package/dist/src/gateway/hono/middleware/logger.js +1 -1
  239. package/dist/src/gateway/hono/middleware/strict-rate-limit.js +1 -1
  240. package/dist/src/gateway/hono/oauth-async.js +1 -7
  241. package/dist/src/gateway/hono/oauth.js +1 -7
  242. package/dist/src/gateway/hono/routes/agents.js +1 -1
  243. package/dist/src/gateway/hono/routes/config-patch/misc.js +2 -2
  244. package/dist/src/gateway/hono/routes/connectors.js +79 -0
  245. package/dist/src/gateway/hono/routes/discussions.d.ts +3 -0
  246. package/dist/src/gateway/hono/routes/discussions.js +177 -0
  247. package/dist/src/gateway/hono/routes/execution-receipts.d.ts +3 -0
  248. package/dist/src/gateway/hono/routes/execution-receipts.js +163 -0
  249. package/dist/src/gateway/hono/routes/exposure.js +1 -1
  250. package/dist/src/gateway/hono/routes/goals.js +3 -288
  251. package/dist/src/gateway/hono/routes/home.d.ts +2 -102
  252. package/dist/src/gateway/hono/routes/home.js +8 -424
  253. package/dist/src/gateway/hono/routes/index.js +6 -2
  254. package/dist/src/gateway/hono/routes/memory.js +7 -7
  255. package/dist/src/gateway/hono/routes/models.js +1 -1
  256. package/dist/src/gateway/hono/routes/notes.js +30 -0
  257. package/dist/src/gateway/hono/routes/projects.js +8 -152
  258. package/dist/src/gateway/hono/routes/shares.js +1 -1
  259. package/dist/src/gateway/hono/routes/status.js +2 -2
  260. package/dist/src/gateway/hono/routes/tunnel.js +3 -3
  261. package/dist/src/gateway/hono/routes/voice.js +1 -1
  262. package/dist/src/gateway/hono/routes/work.d.ts +3 -0
  263. package/dist/src/gateway/hono/routes/work.js +207 -0
  264. package/dist/src/gateway/hono/routes/you.js +13 -15
  265. package/dist/src/gateway/index.js +2 -2
  266. package/dist/src/gateway/runtime-config.js +2 -2
  267. package/dist/src/gateway/security/audit.js +2 -2
  268. package/dist/src/gateway/security/index.js +1 -1
  269. package/dist/src/gateway/service/config-coordinator.js +2 -2
  270. package/dist/src/gateway/service/run-gateway-agent.js +46 -51
  271. package/dist/src/gateway/service.d.ts +8 -1
  272. package/dist/src/gateway/service.js +142 -15
  273. package/dist/src/goals/goal-queue-store.d.ts +1 -0
  274. package/dist/src/goals/goal-queue-store.js +5 -1
  275. package/dist/src/goals/goal-queue-types.d.ts +11 -1
  276. package/dist/src/goals/goal-runner.js +10 -6
  277. package/dist/src/goals/goal-service.d.ts +2 -15
  278. package/dist/src/goals/goal-service.js +9 -42
  279. package/dist/src/goals/goal-store.d.ts +1 -18
  280. package/dist/src/goals/goal-store.js +25 -231
  281. package/dist/src/goals/goal-workflow-judge.js +1 -1
  282. package/dist/src/goals/index.d.ts +0 -4
  283. package/dist/src/goals/index.js +1 -3
  284. package/dist/src/goals/types.d.ts +4 -66
  285. package/dist/src/notes/agent-context.js +1 -1
  286. package/dist/src/notes/service.d.ts +1 -0
  287. package/dist/src/notes/service.js +10 -1
  288. package/dist/src/notes/types.d.ts +1 -0
  289. package/dist/src/proactive/events/product-event-bridge.d.ts +7 -0
  290. package/dist/src/proactive/events/product-event-bridge.js +84 -0
  291. package/dist/src/proactive/execution/context.d.ts +27 -11
  292. package/dist/src/proactive/execution/context.js +259 -25
  293. package/dist/src/proactive/execution/repository.js +18 -5
  294. package/dist/src/proactive/execution/types.d.ts +7 -1
  295. package/dist/src/proactive/execution/worker.js +7 -6
  296. package/dist/src/proactive/inbox/repository.js +8 -2
  297. package/dist/src/proactive/index.d.ts +4 -2
  298. package/dist/src/proactive/index.js +5 -3
  299. package/dist/src/proactive/temporal/worker.d.ts +16 -0
  300. package/dist/src/proactive/temporal/worker.js +110 -0
  301. package/dist/src/projects/project-loop.js +3 -3
  302. package/dist/src/providers/auth-runtime/oauth.js +12 -0
  303. package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
  304. package/dist/src/providers/index.js +2 -2
  305. package/dist/src/providers/model-registry.js +1 -1
  306. package/dist/src/providers/register-runtime-providers.js +1 -1
  307. package/dist/src/share/share-zip.js +1 -1
  308. package/dist/src/share/site-proxy.js +1 -1
  309. package/dist/src/storage/sqlite/activity-repository.d.ts +1 -0
  310. package/dist/src/storage/sqlite/activity-repository.js +4 -1
  311. package/dist/src/storage/sqlite/connector-sync-policy-repository.d.ts +20 -0
  312. package/dist/src/storage/sqlite/connector-sync-policy-repository.js +50 -0
  313. package/dist/src/storage/sqlite/execution-receipt-repository.d.ts +146 -0
  314. package/dist/src/storage/sqlite/execution-receipt-repository.js +235 -0
  315. package/dist/src/storage/sqlite/index.d.ts +4 -2
  316. package/dist/src/storage/sqlite/index.js +6 -4
  317. package/dist/src/storage/sqlite/knowledge-repository.d.ts +3 -0
  318. package/dist/src/storage/sqlite/knowledge-repository.js +9 -1
  319. package/dist/src/storage/sqlite/memory-records-repository.js +1 -1
  320. package/dist/src/storage/sqlite/migrations/075_connector_sync_policies.sql +13 -0
  321. package/dist/src/storage/sqlite/migrations/076_meeting_preparation_scenario.sql +29 -0
  322. package/dist/src/storage/sqlite/migrations/077_proactive_insight_fingerprints.sql +4 -0
  323. package/dist/src/storage/sqlite/migrations/078_discussion_captures.sql +64 -0
  324. package/dist/src/storage/sqlite/migrations/079_discussion_follow_up_scenario.sql +29 -0
  325. package/dist/src/storage/sqlite/migrations/080_task_outcome_context.sql +15 -0
  326. package/dist/src/storage/sqlite/migrations/081_project_monitoring_policies.sql +10 -0
  327. package/dist/src/storage/sqlite/migrations/082_one_click_live_discussions.sql +100 -0
  328. package/dist/src/storage/sqlite/migrations/083_session_task_plans.sql +9 -0
  329. package/dist/src/storage/sqlite/migrations/084_work_intake_lifecycle.sql +26 -0
  330. package/dist/src/storage/sqlite/migrations/085_task_outcome_projection.sql +19 -0
  331. package/dist/src/storage/sqlite/migrations/086_outcome_aggregates.sql +168 -0
  332. package/dist/src/storage/sqlite/migrations/087_context_snapshots.sql +21 -0
  333. package/dist/src/storage/sqlite/migrations/088_automation_execution_deadlines.sql +5 -0
  334. package/dist/src/storage/sqlite/migrations/089_automation_run_leases.sql +8 -0
  335. package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
  336. package/dist/src/storage/sqlite/migrations/runner.js +3 -3
  337. package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
  338. package/dist/src/storage/sqlite/notes-repository.js +9 -0
  339. package/dist/src/storage/sqlite/schema.js +1 -1
  340. package/dist/src/storage/sqlite/schema.sql +89 -44
  341. package/dist/src/storage/sqlite/session-repository.js +1 -1
  342. package/dist/src/storage/sqlite/session-task-plan-repository.d.ts +20 -0
  343. package/dist/src/storage/sqlite/session-task-plan-repository.js +77 -0
  344. package/dist/src/tui/backends/embedded-backend.d.ts +2 -0
  345. package/dist/src/tui/backends/embedded-backend.js +12 -8
  346. package/dist/src/tui/tui-backend.d.ts +2 -0
  347. package/dist/src/tui/tui-oauth-login.js +1 -7
  348. package/dist/src/tui/tui-picker-overlay.js +1 -0
  349. package/dist/src/tui/tui-picker-services.d.ts +1 -0
  350. package/dist/src/tui/tui-skills-autocomplete.d.ts +6 -2
  351. package/dist/src/tui/tui-skills-autocomplete.js +10 -6
  352. package/dist/src/tui/tui-startup-resources.d.ts +3 -1
  353. package/dist/src/tui/tui-startup-resources.js +4 -3
  354. package/dist/src/tui/tui.js +4 -1
  355. package/dist/src/tunnel/frpc-binary.js +1 -1
  356. package/dist/src/tunnel/frpc-extract.js +1 -1
  357. package/dist/src/tunnel/index.js +2 -2
  358. package/dist/src/user-context/context-compiler.d.ts +31 -0
  359. package/dist/src/user-context/context-compiler.js +51 -0
  360. package/dist/src/voice/local/model-files.js +1 -1
  361. package/dist/src/voice/stt/config.d.ts +4 -0
  362. package/dist/src/voice/stt/config.js +26 -0
  363. package/dist/src/voice/stt/index.d.ts +1 -0
  364. package/dist/src/voice/stt/index.js +2 -1
  365. package/dist/src/voice/stt/list-providers.js +1 -1
  366. package/dist/src/work/attention-governor.d.ts +20 -0
  367. package/dist/src/work/attention-governor.js +46 -0
  368. package/dist/src/work/execution-context.d.ts +16 -0
  369. package/dist/src/work/execution-context.js +66 -0
  370. package/dist/src/work/home-query-service.d.ts +147 -0
  371. package/dist/src/work/home-query-service.js +389 -0
  372. package/dist/src/work/index.d.ts +14 -0
  373. package/dist/src/work/index.js +15 -0
  374. package/dist/src/work/outcome-contract-definition.d.ts +8 -0
  375. package/dist/src/work/outcome-contract-definition.js +15 -0
  376. package/dist/src/work/outcome-execution-service.d.ts +27 -0
  377. package/dist/src/work/outcome-execution-service.js +60 -0
  378. package/dist/src/work/outcome-projection-service.d.ts +7 -0
  379. package/dist/src/work/outcome-projection-service.js +51 -0
  380. package/dist/src/work/outcome-receipt-service.d.ts +13 -0
  381. package/dist/src/work/outcome-receipt-service.js +55 -0
  382. package/dist/src/work/outcome-repository.d.ts +50 -0
  383. package/dist/src/work/outcome-repository.js +112 -0
  384. package/dist/src/work/outcome-run-coordinator.d.ts +25 -0
  385. package/dist/src/work/outcome-run-coordinator.js +117 -0
  386. package/dist/src/work/project-monitoring-service.d.ts +21 -0
  387. package/dist/src/work/project-monitoring-service.js +76 -0
  388. package/dist/src/work/project-operating-view-service.d.ts +10 -0
  389. package/dist/src/work/project-operating-view-service.js +46 -0
  390. package/dist/src/work/work-intake-repository.d.ts +49 -0
  391. package/dist/src/work/work-intake-repository.js +97 -0
  392. package/dist/src/work/work-intake-service.d.ts +26 -0
  393. package/dist/src/work/work-intake-service.js +171 -0
  394. package/dist/src/work/work-value-metrics-service.d.ts +4 -0
  395. package/dist/src/work/work-value-metrics-service.js +88 -0
  396. package/dist/src/work-items/work-item-service.d.ts +0 -2
  397. package/dist/src/work-items/work-item-service.js +0 -28
  398. package/dist/src/workflows/service/workflow-run-service.js +2 -2
  399. package/dist/src/workflows/service/workflow-run-service.types.d.ts +3 -1
  400. package/dist/src/workflows/service/workflow-session-bridge.js +2 -2
  401. package/package.json +1 -1
  402. package/dist/gateway/static/root/assets/agents-DC9F12Kw.js +0 -2
  403. package/dist/gateway/static/root/assets/apps-page-5bKr05Fa.js +0 -1
  404. package/dist/gateway/static/root/assets/automation-suggestion-card-B7mxFRtf.js +0 -1
  405. package/dist/gateway/static/root/assets/automations-page-Dg6dmt5g.js +0 -6
  406. package/dist/gateway/static/root/assets/browser-settings-page-BHoo6Rpq.js +0 -8
  407. package/dist/gateway/static/root/assets/capabilities-settings-panel-BlsqN8Fu.js +0 -7
  408. package/dist/gateway/static/root/assets/capability-presets-settings-panel-DJeT8_B4.js +0 -1
  409. package/dist/gateway/static/root/assets/channels-settings-C4VVjYEm.js +0 -1
  410. package/dist/gateway/static/root/assets/channels-status-swr-vdf8kB16.js +0 -1
  411. package/dist/gateway/static/root/assets/circle-alert-BEjgurr2.js +0 -1
  412. package/dist/gateway/static/root/assets/connectors-page-DNcrDThX.js +0 -2
  413. package/dist/gateway/static/root/assets/desktop-pet-B1urphOZ.js +0 -1
  414. package/dist/gateway/static/root/assets/desktop-pet-settings-DTL6J3EP.js +0 -1
  415. package/dist/gateway/static/root/assets/gateway-settings-DmObBx9b.js +0 -1
  416. package/dist/gateway/static/root/assets/goal-create-dialog-BRhmmBe9.js +0 -1
  417. package/dist/gateway/static/root/assets/goal-detail-page-C1k7_s_O.js +0 -4
  418. package/dist/gateway/static/root/assets/goals-config-api-Fwu1wjEP.js +0 -1
  419. package/dist/gateway/static/root/assets/goals-page-hOu9uA3q.js +0 -2
  420. package/dist/gateway/static/root/assets/goals-settings-DY-JjvHz.js +0 -1
  421. package/dist/gateway/static/root/assets/index-C0CGebQ1.js +0 -212
  422. package/dist/gateway/static/root/assets/index-eqq25gvV.css +0 -1
  423. package/dist/gateway/static/root/assets/local-app-workbench-page-EjsNT7nJ.js +0 -2
  424. package/dist/gateway/static/root/assets/logs-page-Dswjlraa.js +0 -7
  425. package/dist/gateway/static/root/assets/messages-DP6tPKk-.js +0 -3
  426. package/dist/gateway/static/root/assets/notes-workbench-DSh3-hMY.js +0 -4
  427. package/dist/gateway/static/root/assets/product-delivery-BTRT9kCS.js +0 -1
  428. package/dist/gateway/static/root/assets/project-detail-page-D0QPRHR_.js +0 -1
  429. package/dist/gateway/static/root/assets/projects-page-CI5Vz62F.js +0 -1
  430. package/dist/gateway/static/root/assets/remote-access-hub-S1jpzBS5.js +0 -1
  431. package/dist/gateway/static/root/assets/sessions-page-CTm66k7h.js +0 -1
  432. package/dist/gateway/static/root/assets/settings-advanced-gate-CU52AUVD.js +0 -1
  433. package/dist/gateway/static/root/assets/shares-settings-DK6XK95e.js +0 -1
  434. package/dist/gateway/static/root/assets/skill-reload-api-Bvv_u4ii.js +0 -1
  435. package/dist/gateway/static/root/assets/skills-page-BGHvVbay.js +0 -2
  436. package/dist/gateway/static/root/assets/time-picker-XwpxdZFr.js +0 -1
  437. package/dist/gateway/static/root/assets/user-context-page-DBCMk-Vn.js +0 -1
  438. package/dist/gateway/static/root/assets/work-discovery-page-C1UIdsCl.js +0 -1
  439. package/dist/gateway/static/root/assets/work-item-detail-page-DEAQepB8.js +0 -1
  440. package/dist/gateway/static/root/assets/work-page-D5qYmCTq.js +0 -3
  441. package/dist/gateway/static/root/assets/workflows-page-yrK17ZZ5.js +0 -11
  442. package/dist/src/agent/outcomes/task-verifier.d.ts +0 -29
  443. package/dist/src/gateway/hono/routes/task-outcomes.d.ts +0 -3
  444. package/dist/src/gateway/hono/routes/task-outcomes.js +0 -109
  445. package/dist/src/goals/goal-contract-draft.d.ts +0 -15
  446. package/dist/src/goals/goal-contract-draft.js +0 -112
  447. package/dist/src/goals/goal-evidence-review.d.ts +0 -15
  448. package/dist/src/goals/goal-evidence-review.js +0 -89
  449. package/dist/src/storage/sqlite/task-outcome-repository.d.ts +0 -90
  450. package/dist/src/storage/sqlite/task-outcome-repository.js +0 -138
@@ -0,0 +1,255 @@
1
+ import { getSqliteDatabase, runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
2
+ //#region src/discussions/repository.ts
3
+ function parseJson(value) {
4
+ if (!value) return void 0;
5
+ try {
6
+ return JSON.parse(value);
7
+ } catch {
8
+ return;
9
+ }
10
+ }
11
+ function discussionFromRow(row) {
12
+ return {
13
+ id: row.id,
14
+ clientRequestId: row.client_request_id,
15
+ noteId: row.note_id,
16
+ ...row.project_id ? { projectId: row.project_id } : {},
17
+ ...row.audio_attachment_id ? { audioAttachmentId: row.audio_attachment_id } : {},
18
+ source: row.source,
19
+ status: row.status,
20
+ ...row.processing_stage ? { processingStage: row.processing_stage } : {},
21
+ ...row.duration_ms != null ? { durationMs: row.duration_ms } : {},
22
+ ...row.expected_last_sequence != null ? { expectedLastSequence: row.expected_last_sequence } : {},
23
+ ...row.mime_type ? { mimeType: row.mime_type } : {},
24
+ ...row.audio_size_bytes != null ? { audioSizeBytes: row.audio_size_bytes } : {},
25
+ ...row.audio_sha256 ? { audioSha256: row.audio_sha256 } : {},
26
+ ...row.transcript_raw ? { transcriptRaw: row.transcript_raw } : {},
27
+ ...row.transcript_sha256 ? { transcriptSha256: row.transcript_sha256 } : {},
28
+ ...row.transcript_language ? { transcriptLanguage: row.transcript_language } : {},
29
+ ...row.stt_provider ? { sttProvider: row.stt_provider } : {},
30
+ ...row.analysis_json ? { analysis: parseJson(row.analysis_json) } : {},
31
+ ...row.analysis_input_hash ? { analysisInputHash: row.analysis_input_hash } : {},
32
+ ...row.analyzer_model_ref ? { analyzerModelRef: row.analyzer_model_ref } : {},
33
+ ...row.generated_title ? { generatedTitle: row.generated_title } : {},
34
+ ...row.project_inference_score != null ? { projectInferenceScore: row.project_inference_score } : {},
35
+ ...row.project_inference_source ? { projectInferenceSource: row.project_inference_source } : {},
36
+ finalizationRevision: row.finalization_revision,
37
+ attemptCount: row.attempt_count,
38
+ ...row.next_attempt_at != null ? { nextAttemptAt: row.next_attempt_at } : {},
39
+ ...row.lease_owner ? { leaseOwner: row.lease_owner } : {},
40
+ ...row.lease_expires_at != null ? { leaseExpiresAt: row.lease_expires_at } : {},
41
+ ...row.last_error_code ? { lastErrorCode: row.last_error_code } : {},
42
+ ...row.last_error_message ? { lastErrorMessage: row.last_error_message } : {},
43
+ recordingStartedAt: row.recording_started_at,
44
+ ...row.recording_finished_at != null ? { recordingFinishedAt: row.recording_finished_at } : {},
45
+ createdAt: row.created_at,
46
+ updatedAt: row.updated_at,
47
+ ...row.completed_at != null ? { completedAt: row.completed_at } : {},
48
+ ...row.audio_deleted_at != null ? { audioDeletedAt: row.audio_deleted_at } : {}
49
+ };
50
+ }
51
+ function segmentFromRow(row) {
52
+ return {
53
+ discussionId: row.discussion_id,
54
+ sequence: row.sequence,
55
+ audioSha256: row.audio_sha256,
56
+ startedAtMs: row.started_at_ms,
57
+ endedAtMs: row.ended_at_ms,
58
+ status: row.status,
59
+ ...row.transcript ? { transcript: row.transcript } : {},
60
+ ...row.provider ? { provider: row.provider } : {},
61
+ attemptCount: row.attempt_count,
62
+ ...row.next_attempt_at != null ? { nextAttemptAt: row.next_attempt_at } : {},
63
+ ...row.lease_owner ? { leaseOwner: row.lease_owner } : {},
64
+ ...row.lease_expires_at != null ? { leaseExpiresAt: row.lease_expires_at } : {},
65
+ ...row.last_error ? { lastError: row.last_error } : {},
66
+ createdAt: row.created_at,
67
+ updatedAt: row.updated_at
68
+ };
69
+ }
70
+ function readOne(column, value) {
71
+ const row = getSqliteDatabase().prepare(`SELECT * FROM discussion_captures WHERE ${column} = ?`).get(value);
72
+ return row ? discussionFromRow(row) : null;
73
+ }
74
+ function createDiscussionCapture(capture) {
75
+ runSqliteWriteTransaction((db) => db.prepare(`INSERT INTO discussion_captures (
76
+ id, client_request_id, note_id, project_id, source, status,
77
+ processing_stage, project_inference_score, project_inference_source,
78
+ finalization_revision, attempt_count, recording_started_at, created_at, updated_at
79
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(capture.id, capture.clientRequestId, capture.noteId, capture.projectId ?? null, capture.source, capture.status, capture.processingStage ?? null, capture.projectInferenceScore ?? null, capture.projectInferenceSource ?? null, capture.finalizationRevision, capture.attemptCount, capture.recordingStartedAt, capture.createdAt, capture.updatedAt));
80
+ return getDiscussionCapture(capture.id);
81
+ }
82
+ function getDiscussionCapture(id) {
83
+ return readOne("id", id);
84
+ }
85
+ function getDiscussionCaptureByClientRequestId(clientRequestId) {
86
+ return readOne("client_request_id", clientRequestId);
87
+ }
88
+ function getDiscussionCaptureByNoteId(noteId) {
89
+ const row = getSqliteDatabase().prepare("SELECT * FROM discussion_captures WHERE note_id = ?").get(noteId);
90
+ return row ? discussionFromRow(row) : null;
91
+ }
92
+ function listDiscussionCaptures(query = {}) {
93
+ const clauses = [];
94
+ const params = [];
95
+ if (query.status === "active") clauses.push(`status NOT IN ('completed', 'cancelled')`);
96
+ else if (query.status) {
97
+ clauses.push("status = ?");
98
+ params.push(query.status);
99
+ }
100
+ if (query.projectId) {
101
+ clauses.push("project_id = ?");
102
+ params.push(query.projectId);
103
+ }
104
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
105
+ const limit = Math.min(100, Math.max(1, Math.floor(query.limit ?? 30)));
106
+ const offset = Math.max(0, Math.floor(query.offset ?? 0));
107
+ const db = getSqliteDatabase();
108
+ const total = db.prepare(`SELECT COUNT(*) AS total FROM discussion_captures ${where}`).get(...params).total;
109
+ const rows = db.prepare(`SELECT * FROM discussion_captures ${where}
110
+ ORDER BY updated_at DESC, id DESC LIMIT ? OFFSET ?`).all(...params, limit, offset);
111
+ return {
112
+ items: rows.map(discussionFromRow),
113
+ total,
114
+ limit,
115
+ offset,
116
+ hasMore: offset + rows.length < total
117
+ };
118
+ }
119
+ function getDiscussionMetrics() {
120
+ const db = getSqliteDatabase();
121
+ const rows = db.prepare("SELECT status, COUNT(*) AS count FROM discussion_captures GROUP BY status").all();
122
+ const timing = db.prepare(`SELECT
123
+ AVG((SELECT MIN(s.updated_at) FROM discussion_transcript_segments s
124
+ WHERE s.discussion_id = d.id AND s.status = 'completed') - d.recording_started_at) AS first_transcript_ms,
125
+ AVG(CASE WHEN d.completed_at IS NOT NULL THEN d.completed_at - d.recording_started_at END) AS complete_ms
126
+ FROM discussion_captures d`).get();
127
+ const byStatus = Object.fromEntries([
128
+ "recording",
129
+ "finalizing",
130
+ "completed",
131
+ "failed",
132
+ "cancelled"
133
+ ].map((status) => [status, 0]));
134
+ for (const row of rows) byStatus[row.status] = row.count;
135
+ return {
136
+ total: rows.reduce((sum, row) => sum + row.count, 0),
137
+ byStatus,
138
+ averageTimeToFirstTranscriptMs: timing.first_transcript_ms == null ? null : Math.max(0, Math.round(timing.first_transcript_ms)),
139
+ averageTimeToCompleteMs: timing.complete_ms == null ? null : Math.round(timing.complete_ms)
140
+ };
141
+ }
142
+ function updateDiscussionCapture(id, patch, expectedStatuses) {
143
+ const existing = getDiscussionCapture(id);
144
+ if (!existing || expectedStatuses && !expectedStatuses.includes(existing.status)) return null;
145
+ const next = {
146
+ ...existing,
147
+ ...patch,
148
+ updatedAt: patch.updatedAt ?? Date.now()
149
+ };
150
+ const sql = `UPDATE discussion_captures SET
151
+ project_id = ?, audio_attachment_id = ?, source = ?, status = ?, processing_stage = ?,
152
+ duration_ms = ?, expected_last_sequence = ?, mime_type = ?, audio_size_bytes = ?, audio_sha256 = ?,
153
+ transcript_raw = ?, transcript_sha256 = ?, transcript_language = ?, stt_provider = ?, analysis_json = ?,
154
+ analysis_input_hash = ?, analyzer_model_ref = ?, generated_title = ?, project_inference_score = ?,
155
+ project_inference_source = ?, finalization_revision = ?, attempt_count = ?, next_attempt_at = ?,
156
+ lease_owner = ?, lease_expires_at = ?, last_error_code = ?, last_error_message = ?,
157
+ recording_started_at = ?, recording_finished_at = ?, updated_at = ?, completed_at = ?, audio_deleted_at = ?
158
+ WHERE id = ?${expectedStatuses?.length ? ` AND status IN (${expectedStatuses.map(() => "?").join(", ")})` : ""}`;
159
+ return runSqliteWriteTransaction((db) => db.prepare(sql).run(next.projectId ?? null, next.audioAttachmentId ?? null, next.source, next.status, next.processingStage ?? null, next.durationMs ?? null, next.expectedLastSequence ?? null, next.mimeType ?? null, next.audioSizeBytes ?? null, next.audioSha256 ?? null, next.transcriptRaw ?? null, next.transcriptSha256 ?? null, next.transcriptLanguage ?? null, next.sttProvider ?? null, next.analysis === void 0 ? null : JSON.stringify(next.analysis), next.analysisInputHash ?? null, next.analyzerModelRef ?? null, next.generatedTitle ?? null, next.projectInferenceScore ?? null, next.projectInferenceSource ?? null, next.finalizationRevision, next.attemptCount, next.nextAttemptAt ?? null, next.leaseOwner ?? null, next.leaseExpiresAt ?? null, next.lastErrorCode ?? null, next.lastErrorMessage ?? null, next.recordingStartedAt, next.recordingFinishedAt ?? null, next.updatedAt, next.completedAt ?? null, next.audioDeletedAt ?? null, id, ...expectedStatuses ?? []).changes) > 0 ? getDiscussionCapture(id) : null;
160
+ }
161
+ function claimNextDiscussionCapture(owner, now = Date.now(), leaseMs = 5 * 6e4) {
162
+ return runSqliteWriteTransaction((db) => {
163
+ const row = db.prepare(`SELECT * FROM discussion_captures
164
+ WHERE status = 'finalizing'
165
+ AND (next_attempt_at IS NULL OR next_attempt_at <= ?)
166
+ AND (lease_owner IS NULL OR lease_expires_at IS NULL OR lease_expires_at <= ?)
167
+ ORDER BY created_at ASC, id ASC LIMIT 1`).get(now, now);
168
+ if (!row) return null;
169
+ return db.prepare(`UPDATE discussion_captures SET attempt_count = attempt_count + 1,
170
+ next_attempt_at = NULL, lease_owner = ?, lease_expires_at = ?, updated_at = ?
171
+ WHERE id = ? AND status = 'finalizing'
172
+ AND (lease_owner IS NULL OR lease_expires_at IS NULL OR lease_expires_at <= ?)`).run(owner, now + leaseMs, now, row.id, now).changes > 0 ? getDiscussionCapture(row.id) : null;
173
+ });
174
+ }
175
+ function createDiscussionTranscriptSegment(input) {
176
+ const existing = getDiscussionTranscriptSegment(input.discussionId, input.sequence);
177
+ if (existing) return existing;
178
+ const now = input.now ?? Date.now();
179
+ runSqliteWriteTransaction((db) => db.prepare(`INSERT INTO discussion_transcript_segments (
180
+ discussion_id, sequence, audio_sha256, audio_blob, started_at_ms, ended_at_ms,
181
+ status, attempt_count, created_at, updated_at
182
+ ) VALUES (?, ?, ?, ?, ?, ?, 'uploaded', 0, ?, ?)`).run(input.discussionId, input.sequence, input.audioSha256, input.audioBuffer, input.startedAtMs, input.endedAtMs, now, now));
183
+ return getDiscussionTranscriptSegment(input.discussionId, input.sequence);
184
+ }
185
+ function getDiscussionTranscriptSegment(discussionId, sequence) {
186
+ const row = getSqliteDatabase().prepare("SELECT * FROM discussion_transcript_segments WHERE discussion_id = ? AND sequence = ?").get(discussionId, sequence);
187
+ return row ? segmentFromRow(row) : null;
188
+ }
189
+ function listDiscussionTranscriptSegments(discussionId) {
190
+ return getSqliteDatabase().prepare("SELECT * FROM discussion_transcript_segments WHERE discussion_id = ? ORDER BY sequence ASC").all(discussionId).map(segmentFromRow);
191
+ }
192
+ function claimNextDiscussionTranscriptSegment(owner, now = Date.now(), leaseMs = 2 * 6e4) {
193
+ return runSqliteWriteTransaction((db) => {
194
+ const row = db.prepare(`SELECT s.* FROM discussion_transcript_segments s
195
+ JOIN discussion_captures d ON d.id = s.discussion_id
196
+ WHERE (s.status = 'uploaded'
197
+ OR (s.status = 'transcribing' AND (s.lease_expires_at IS NULL OR s.lease_expires_at <= ?)))
198
+ AND d.status IN ('recording', 'finalizing')
199
+ AND (s.next_attempt_at IS NULL OR s.next_attempt_at <= ?)
200
+ AND NOT EXISTS (
201
+ SELECT 1 FROM discussion_transcript_segments earlier
202
+ WHERE earlier.discussion_id = s.discussion_id
203
+ AND earlier.sequence < s.sequence
204
+ AND earlier.status IN ('uploaded', 'transcribing')
205
+ )
206
+ ORDER BY s.created_at ASC, s.sequence ASC LIMIT 1`).get(now, now);
207
+ if (!row?.audio_blob) return null;
208
+ if (db.prepare(`UPDATE discussion_transcript_segments
209
+ SET status = 'transcribing', attempt_count = attempt_count + 1,
210
+ next_attempt_at = NULL, lease_owner = ?, lease_expires_at = ?, updated_at = ?
211
+ WHERE discussion_id = ? AND sequence = ?
212
+ AND (status = 'uploaded'
213
+ OR (status = 'transcribing' AND (lease_expires_at IS NULL OR lease_expires_at <= ?)))`).run(owner, now + leaseMs, now, row.discussion_id, row.sequence, now).changes === 0) return null;
214
+ const claimed = db.prepare("SELECT * FROM discussion_transcript_segments WHERE discussion_id = ? AND sequence = ?").get(row.discussion_id, row.sequence);
215
+ return {
216
+ ...segmentFromRow(claimed),
217
+ audioBuffer: Buffer.from(claimed.audio_blob)
218
+ };
219
+ });
220
+ }
221
+ function completeDiscussionTranscriptSegment(discussionId, sequence, owner, transcript, provider) {
222
+ const now = Date.now();
223
+ return runSqliteWriteTransaction((db) => db.prepare(`UPDATE discussion_transcript_segments SET
224
+ status = 'completed', transcript = ?, provider = ?, audio_blob = NULL,
225
+ lease_owner = NULL, lease_expires_at = NULL, next_attempt_at = NULL,
226
+ last_error = NULL, updated_at = ?
227
+ WHERE discussion_id = ? AND sequence = ? AND status = 'transcribing' AND lease_owner = ?`).run(transcript, provider, now, discussionId, sequence, owner).changes) > 0 ? getDiscussionTranscriptSegment(discussionId, sequence) : null;
228
+ }
229
+ function retryOrFailDiscussionTranscriptSegment(discussionId, sequence, owner, error, exhausted) {
230
+ const now = Date.now();
231
+ return runSqliteWriteTransaction((db) => db.prepare(`UPDATE discussion_transcript_segments SET
232
+ status = ?, lease_owner = NULL, lease_expires_at = NULL,
233
+ next_attempt_at = ?, last_error = ?, audio_blob = CASE WHEN ? THEN NULL ELSE audio_blob END,
234
+ updated_at = ?
235
+ WHERE discussion_id = ? AND sequence = ? AND status = 'transcribing' AND lease_owner = ?`).run(exhausted ? "failed" : "uploaded", exhausted ? null : now + 2e3, error.slice(0, 1e3), exhausted ? 1 : 0, now, discussionId, sequence, owner).changes) > 0 ? getDiscussionTranscriptSegment(discussionId, sequence) : null;
236
+ }
237
+ function deleteDiscussionSegmentAudio(discussionId) {
238
+ runSqliteWriteTransaction((db) => db.prepare("UPDATE discussion_transcript_segments SET audio_blob = NULL WHERE discussion_id = ?").run(discussionId));
239
+ }
240
+ function getDiscussionCaptureSettings() {
241
+ const row = getSqliteDatabase().prepare(`SELECT consent_policy_version, consent_acknowledged_at
242
+ FROM discussion_capture_settings WHERE workspace_id = 'default'`).get();
243
+ return {
244
+ consentPolicyVersion: row.consent_policy_version,
245
+ ...row.consent_acknowledged_at != null ? { consentAcknowledgedAt: row.consent_acknowledged_at } : {}
246
+ };
247
+ }
248
+ function acknowledgeDiscussionCaptureConsent(policyVersion) {
249
+ const now = Date.now();
250
+ runSqliteWriteTransaction((db) => db.prepare(`UPDATE discussion_capture_settings SET consent_acknowledged_at = ?, updated_at = ?
251
+ WHERE workspace_id = 'default' AND consent_policy_version = ?`).run(now, now, policyVersion));
252
+ return getDiscussionCaptureSettings();
253
+ }
254
+ //#endregion
255
+ export { acknowledgeDiscussionCaptureConsent, claimNextDiscussionCapture, claimNextDiscussionTranscriptSegment, completeDiscussionTranscriptSegment, createDiscussionCapture, createDiscussionTranscriptSegment, deleteDiscussionSegmentAudio, getDiscussionCapture, getDiscussionCaptureByClientRequestId, getDiscussionCaptureByNoteId, getDiscussionCaptureSettings, getDiscussionMetrics, getDiscussionTranscriptSegment, listDiscussionCaptures, listDiscussionTranscriptSegments, retryOrFailDiscussionTranscriptSegment, updateDiscussionCapture };
@@ -0,0 +1,51 @@
1
+ import type { NotesService } from '../notes/service.js';
2
+ import type { ProjectService } from '../projects/project-service.js';
3
+ import type { CreateDiscussionInput, DiscussionCaptureSettings, DiscussionDetail, DiscussionListResult, DiscussionMetrics, DiscussionTranscript, ListDiscussionsQuery } from './types.js';
4
+ export declare const DISCUSSION_AUDIO_MAX_BYTES: number;
5
+ export declare const DISCUSSION_SEGMENT_MAX_BYTES: number;
6
+ export declare const DISCUSSION_MAX_DURATION_MS: number;
7
+ export declare class DiscussionServiceError extends Error {
8
+ readonly code: 'invalid_input' | 'not_found' | 'conflict';
9
+ constructor(code: 'invalid_input' | 'not_found' | 'conflict', message: string);
10
+ }
11
+ export declare class DiscussionService {
12
+ private readonly notes;
13
+ private readonly projects;
14
+ private readonly emit?;
15
+ private readonly createsInFlight;
16
+ private readonly mutationTails;
17
+ private readonly objectLinks;
18
+ constructor(notes: NotesService, projects: ProjectService, emit?: (type: string, payload: unknown) => void);
19
+ settings(): DiscussionCaptureSettings;
20
+ acknowledgeConsent(policyVersion: number): DiscussionCaptureSettings;
21
+ create(input: CreateDiscussionInput): Promise<DiscussionDetail>;
22
+ private createOnce;
23
+ get(id: string): Promise<DiscussionDetail | null>;
24
+ getByNoteId(noteId: string): Promise<DiscussionDetail | null>;
25
+ list(query?: ListDiscussionsQuery): DiscussionListResult;
26
+ metrics(): DiscussionMetrics;
27
+ transcript(id: string): DiscussionTranscript | null;
28
+ uploadSegment(input: {
29
+ discussionId: string;
30
+ sequence: number;
31
+ file: {
32
+ buffer: Buffer;
33
+ mimeType: string;
34
+ };
35
+ startedAtMs: number;
36
+ endedAtMs: number;
37
+ sha256: string;
38
+ }): DiscussionTranscript;
39
+ uploadRecording(id: string, file: {
40
+ name: string;
41
+ buffer: Buffer;
42
+ mimeType: string;
43
+ }, durationMs: number): Promise<DiscussionDetail | null>;
44
+ finish(id: string, lastSequence: number, durationMs: number): Promise<DiscussionDetail | null>;
45
+ retry(id: string): Promise<DiscussionDetail | null>;
46
+ cancel(id: string): Promise<DiscussionDetail | null>;
47
+ deleteAudio(id: string): Promise<DiscussionDetail | null>;
48
+ unlinkInferredProject(id: string): Promise<DiscussionDetail | null>;
49
+ private detail;
50
+ private enqueueMutation;
51
+ }
@@ -0,0 +1,335 @@
1
+ import { createLogger } from "../utils/logger/index.js";
2
+ import { init_logger } from "../utils/logger.js";
3
+ import { runSqliteWriteTransaction } from "../storage/sqlite/transaction.js";
4
+ import { buildNoteAttachmentRef } from "../notes/attachment-ref.js";
5
+ import { ObjectLinkService } from "../activity/service.js";
6
+ import { acknowledgeDiscussionCaptureConsent, createDiscussionCapture, createDiscussionTranscriptSegment, deleteDiscussionSegmentAudio, getDiscussionCapture, getDiscussionCaptureByClientRequestId, getDiscussionCaptureByNoteId, getDiscussionCaptureSettings, getDiscussionMetrics, getDiscussionTranscriptSegment, listDiscussionCaptures, listDiscussionTranscriptSegments, updateDiscussionCapture } from "./repository.js";
7
+ import { assembleDiscussionTranscript } from "./transcript.js";
8
+ import { createHash, randomUUID } from "node:crypto";
9
+ //#region src/discussions/service.ts
10
+ init_logger();
11
+ const log = createLogger("DiscussionService");
12
+ const DISCUSSION_AUDIO_MAX_BYTES = 25 * 1024 * 1024;
13
+ const DISCUSSION_SEGMENT_MAX_BYTES = 2 * 1024 * 1024;
14
+ const DISCUSSION_MAX_DURATION_MS = 1800 * 1e3;
15
+ function isSupportedAudioMimeType(mimeType) {
16
+ const base = mimeType.toLowerCase().split(";", 1)[0]?.trim();
17
+ return base === "audio/webm" || base === "audio/mp4" || base === "audio/ogg" || base === "audio/wav" || base === "audio/x-wav" || base === "audio/mpeg";
18
+ }
19
+ function placeholderTitle(now) {
20
+ return `Discussion · ${new Date(now).toISOString().slice(0, 16).replace("T", " ")}`;
21
+ }
22
+ function escapeRegExp(value) {
23
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+ }
25
+ var DiscussionServiceError = class extends Error {
26
+ constructor(code, message) {
27
+ super(message);
28
+ this.code = code;
29
+ this.name = "DiscussionServiceError";
30
+ }
31
+ };
32
+ var DiscussionService = class {
33
+ createsInFlight = /* @__PURE__ */ new Map();
34
+ mutationTails = /* @__PURE__ */ new Map();
35
+ objectLinks = new ObjectLinkService();
36
+ constructor(notes, projects, emit) {
37
+ this.notes = notes;
38
+ this.projects = projects;
39
+ this.emit = emit;
40
+ }
41
+ settings() {
42
+ return getDiscussionCaptureSettings();
43
+ }
44
+ acknowledgeConsent(policyVersion) {
45
+ if (policyVersion !== getDiscussionCaptureSettings().consentPolicyVersion) throw new DiscussionServiceError("conflict", "Recording consent policy changed; review it again");
46
+ return acknowledgeDiscussionCaptureConsent(policyVersion);
47
+ }
48
+ async create(input) {
49
+ const clientRequestId = input.clientRequestId.trim();
50
+ if (!clientRequestId || clientRequestId.length > 200) throw new DiscussionServiceError("invalid_input", "clientRequestId must be between 1 and 200 characters");
51
+ const settings = getDiscussionCaptureSettings();
52
+ if (input.consentPolicyVersion !== settings.consentPolicyVersion || settings.consentAcknowledgedAt == null) throw new DiscussionServiceError("conflict", "Recording consent must be acknowledged first");
53
+ const existing = getDiscussionCaptureByClientRequestId(clientRequestId);
54
+ if (existing) return this.detail(existing);
55
+ const pending = this.createsInFlight.get(clientRequestId);
56
+ if (pending) return pending;
57
+ const creation = this.createOnce({
58
+ ...input,
59
+ clientRequestId
60
+ }).finally(() => this.createsInFlight.delete(clientRequestId));
61
+ this.createsInFlight.set(clientRequestId, creation);
62
+ return creation;
63
+ }
64
+ async createOnce(input) {
65
+ const project = input.contextProjectId ? this.projects.get(input.contextProjectId) : null;
66
+ if (input.contextProjectId && !project) throw new DiscussionServiceError("invalid_input", "Project not found");
67
+ const now = Date.now();
68
+ const title = placeholderTitle(now);
69
+ const note = await this.notes.createNote({
70
+ title,
71
+ markdown: `# ${title}\n\n> Recording in progress. Live transcript will appear here.`,
72
+ kind: "voice",
73
+ capturedVia: { channel: input.source }
74
+ });
75
+ const capture = {
76
+ id: randomUUID(),
77
+ clientRequestId: input.clientRequestId,
78
+ noteId: note.id,
79
+ ...project ? {
80
+ projectId: project.id,
81
+ projectInferenceScore: 1,
82
+ projectInferenceSource: "context"
83
+ } : {},
84
+ source: input.source,
85
+ status: "recording",
86
+ processingStage: "original_upload",
87
+ finalizationRevision: 0,
88
+ attemptCount: 0,
89
+ recordingStartedAt: now,
90
+ createdAt: now,
91
+ updatedAt: now
92
+ };
93
+ try {
94
+ runSqliteWriteTransaction(() => {
95
+ createDiscussionCapture(capture);
96
+ if (project) this.objectLinks.create({
97
+ id: `discussion:${capture.id}:project`,
98
+ from: {
99
+ kind: "note",
100
+ id: note.id,
101
+ title: note.title
102
+ },
103
+ to: {
104
+ kind: "project",
105
+ id: project.id,
106
+ title: project.name
107
+ },
108
+ relation: "belongs_to",
109
+ source: "user",
110
+ nowMs: now
111
+ });
112
+ });
113
+ } catch (error) {
114
+ await this.notes.deleteNote(note.id).catch(() => void 0);
115
+ throw error;
116
+ }
117
+ const discussion = getDiscussionCapture(capture.id);
118
+ log.info({
119
+ discussionId: discussion.id,
120
+ noteId: note.id,
121
+ projectId: project?.id
122
+ }, "Discussion recording started");
123
+ this.emit?.("discussion.updated", discussion);
124
+ return {
125
+ discussion,
126
+ note
127
+ };
128
+ }
129
+ async get(id) {
130
+ const capture = getDiscussionCapture(id);
131
+ return capture ? this.detail(capture) : null;
132
+ }
133
+ async getByNoteId(noteId) {
134
+ const capture = getDiscussionCaptureByNoteId(noteId);
135
+ return capture ? this.detail(capture) : null;
136
+ }
137
+ list(query = {}) {
138
+ return listDiscussionCaptures(query);
139
+ }
140
+ metrics() {
141
+ return getDiscussionMetrics();
142
+ }
143
+ transcript(id) {
144
+ if (!getDiscussionCapture(id)) return null;
145
+ const segments = listDiscussionTranscriptSegments(id);
146
+ return {
147
+ discussionId: id,
148
+ segments,
149
+ text: assembleDiscussionTranscript(segments)
150
+ };
151
+ }
152
+ uploadSegment(input) {
153
+ const capture = getDiscussionCapture(input.discussionId);
154
+ if (!capture) throw new DiscussionServiceError("not_found", "Discussion not found");
155
+ if (capture.status !== "recording") throw new DiscussionServiceError("conflict", "Discussion is no longer recording");
156
+ if (!Number.isInteger(input.sequence) || input.sequence < 0 || input.sequence > 2e3) throw new DiscussionServiceError("invalid_input", "Invalid segment sequence");
157
+ if (input.file.mimeType !== "audio/wav" && input.file.mimeType !== "audio/x-wav") throw new DiscussionServiceError("invalid_input", "Live transcript segments must be WAV audio");
158
+ if (input.file.buffer.length === 0 || input.file.buffer.length > 2097152) throw new DiscussionServiceError("invalid_input", "Live transcript segment is empty or too large");
159
+ 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");
160
+ const actualSha256 = createHash("sha256").update(input.file.buffer).digest("hex");
161
+ if (input.sha256 !== actualSha256) throw new DiscussionServiceError("invalid_input", "Segment checksum mismatch");
162
+ const existing = getDiscussionTranscriptSegment(input.discussionId, input.sequence);
163
+ if (existing && existing.audioSha256 !== actualSha256) throw new DiscussionServiceError("conflict", "Segment sequence already contains different audio");
164
+ if (!existing) {
165
+ createDiscussionTranscriptSegment({
166
+ discussionId: input.discussionId,
167
+ sequence: input.sequence,
168
+ audioSha256: actualSha256,
169
+ audioBuffer: input.file.buffer,
170
+ startedAtMs: Math.round(input.startedAtMs),
171
+ endedAtMs: Math.round(input.endedAtMs)
172
+ });
173
+ this.emit?.("discussion.transcript.updated", {
174
+ discussionId: input.discussionId,
175
+ sequence: input.sequence
176
+ });
177
+ }
178
+ return this.transcript(input.discussionId);
179
+ }
180
+ async uploadRecording(id, file, durationMs) {
181
+ return this.enqueueMutation(id, async () => {
182
+ const capture = getDiscussionCapture(id);
183
+ if (!capture) return null;
184
+ if (capture.status !== "recording") throw new DiscussionServiceError("conflict", "Discussion is no longer recording");
185
+ if (!Number.isFinite(durationMs) || durationMs < 1e3 || durationMs > 18e5) throw new DiscussionServiceError("invalid_input", "durationMs must be between 1000 and 1800000");
186
+ if (!isSupportedAudioMimeType(file.mimeType)) throw new DiscussionServiceError("invalid_input", "Unsupported audio type");
187
+ if (file.buffer.length === 0 || file.buffer.length > 26214400) throw new DiscussionServiceError("invalid_input", "Audio must be between 1 byte and 25MB");
188
+ const audioSha256 = createHash("sha256").update(file.buffer).digest("hex");
189
+ if (capture.audioAttachmentId) {
190
+ if (capture.audioSha256 === audioSha256) return this.detail(capture);
191
+ throw new DiscussionServiceError("conflict", "Discussion already has different audio");
192
+ }
193
+ const noteBefore = await this.notes.getNote(capture.noteId);
194
+ if (!noteBefore) throw new DiscussionServiceError("not_found", "Discussion note not found");
195
+ const attachment = await this.notes.addAttachment(capture.noteId, {
196
+ name: file.name.trim().slice(0, 200) || `discussion-${id}.webm`,
197
+ buffer: file.buffer,
198
+ mimeType: file.mimeType,
199
+ duration: Math.round(durationMs / 1e3)
200
+ });
201
+ if (!attachment) throw new DiscussionServiceError("not_found", "Discussion note not found");
202
+ const audioRef = buildNoteAttachmentRef(capture.noteId, attachment.id);
203
+ try {
204
+ if (!noteBefore.markdown.includes(`](${audioRef})`)) {
205
+ if (!await this.notes.updateNote(capture.noteId, { markdown: `${noteBefore.markdown.trim()}\n\n[Discussion audio](${audioRef})` })) throw new DiscussionServiceError("not_found", "Discussion note not found");
206
+ }
207
+ const updated = updateDiscussionCapture(id, {
208
+ audioAttachmentId: attachment.id,
209
+ durationMs: Math.round(durationMs),
210
+ mimeType: file.mimeType,
211
+ audioSizeBytes: file.buffer.length,
212
+ audioSha256
213
+ }, ["recording"]);
214
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while storing audio");
215
+ this.emit?.("discussion.updated", updated);
216
+ return this.detail(updated);
217
+ } catch (error) {
218
+ await this.notes.updateNote(capture.noteId, { markdown: noteBefore.markdown }).catch(() => void 0);
219
+ await this.notes.removeAttachment(capture.noteId, attachment.id).catch(() => void 0);
220
+ throw error;
221
+ }
222
+ });
223
+ }
224
+ async finish(id, lastSequence, durationMs) {
225
+ return this.enqueueMutation(id, async () => {
226
+ const capture = getDiscussionCapture(id);
227
+ if (!capture) return null;
228
+ if (capture.status === "finalizing" || capture.status === "completed") return this.detail(capture);
229
+ if (capture.status !== "recording") throw new DiscussionServiceError("conflict", "Discussion cannot be finished");
230
+ if (!capture.audioAttachmentId) throw new DiscussionServiceError("conflict", "Original recording has not been uploaded");
231
+ if (!Number.isInteger(lastSequence) || lastSequence < -1 || lastSequence > 2e3) throw new DiscussionServiceError("invalid_input", "Invalid final segment sequence");
232
+ if (!Number.isFinite(durationMs) || durationMs < 1e3 || durationMs > 18e5) throw new DiscussionServiceError("invalid_input", "Invalid recording duration");
233
+ const updated = updateDiscussionCapture(id, {
234
+ status: "finalizing",
235
+ processingStage: "final_transcription",
236
+ expectedLastSequence: lastSequence,
237
+ durationMs: Math.round(durationMs),
238
+ recordingFinishedAt: Date.now(),
239
+ attemptCount: 0,
240
+ nextAttemptAt: void 0,
241
+ lastErrorCode: void 0,
242
+ lastErrorMessage: void 0
243
+ }, ["recording"]);
244
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while finishing");
245
+ this.emit?.("discussion.updated", updated);
246
+ return this.detail(updated);
247
+ });
248
+ }
249
+ async retry(id) {
250
+ const capture = getDiscussionCapture(id);
251
+ if (!capture) return null;
252
+ if (capture.status !== "failed") throw new DiscussionServiceError("conflict", "Only failed discussions can be retried");
253
+ const updated = updateDiscussionCapture(id, {
254
+ status: "finalizing",
255
+ processingStage: capture.processingStage ?? "final_transcription",
256
+ attemptCount: 0,
257
+ nextAttemptAt: void 0,
258
+ leaseOwner: void 0,
259
+ leaseExpiresAt: void 0,
260
+ lastErrorCode: void 0,
261
+ lastErrorMessage: void 0
262
+ }, ["failed"]);
263
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while retrying");
264
+ this.emit?.("discussion.updated", updated);
265
+ return this.detail(updated);
266
+ }
267
+ async cancel(id) {
268
+ const capture = getDiscussionCapture(id);
269
+ if (!capture) return null;
270
+ if (capture.status === "cancelled") return this.detail(capture);
271
+ if (capture.status !== "recording") throw new DiscussionServiceError("conflict", "Only an active recording can be cancelled");
272
+ const updated = updateDiscussionCapture(id, {
273
+ status: "cancelled",
274
+ processingStage: void 0
275
+ }, ["recording"]);
276
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while cancelling");
277
+ deleteDiscussionSegmentAudio(id);
278
+ this.emit?.("discussion.updated", updated);
279
+ return this.detail(updated);
280
+ }
281
+ async deleteAudio(id) {
282
+ return this.enqueueMutation(id, async () => {
283
+ const capture = getDiscussionCapture(id);
284
+ if (!capture) return null;
285
+ if (capture.status === "recording" || capture.status === "finalizing") throw new DiscussionServiceError("conflict", "Audio cannot be deleted while recording or finalizing");
286
+ if (!capture.audioAttachmentId) return this.detail(capture);
287
+ const note = await this.notes.getNote(capture.noteId);
288
+ if (!note) throw new DiscussionServiceError("not_found", "Discussion note not found");
289
+ const audioRef = buildNoteAttachmentRef(capture.noteId, capture.audioAttachmentId);
290
+ const markdown = note.markdown.replace(new RegExp(`^\\[Discussion audio\\]\\(${escapeRegExp(audioRef)}\\)\\s*$`, "gm"), "").replace(/\n{3,}/g, "\n\n").trim();
291
+ if (markdown !== note.markdown) await this.notes.updateNote(capture.noteId, { markdown });
292
+ await this.notes.removeAttachment(capture.noteId, capture.audioAttachmentId);
293
+ deleteDiscussionSegmentAudio(id);
294
+ const updated = updateDiscussionCapture(id, {
295
+ audioAttachmentId: void 0,
296
+ audioDeletedAt: Date.now()
297
+ }, [capture.status]);
298
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while deleting audio");
299
+ this.emit?.("discussion.updated", updated);
300
+ return this.detail(updated);
301
+ });
302
+ }
303
+ async unlinkInferredProject(id) {
304
+ const capture = getDiscussionCapture(id);
305
+ if (!capture) return null;
306
+ if (!capture.projectId || capture.projectInferenceSource === "context") throw new DiscussionServiceError("conflict", "The project association is not AI-inferred");
307
+ this.objectLinks.delete(`discussion:${id}:project`);
308
+ const updated = updateDiscussionCapture(id, {
309
+ projectId: void 0,
310
+ projectInferenceScore: void 0,
311
+ projectInferenceSource: void 0
312
+ }, [capture.status]);
313
+ if (!updated) throw new DiscussionServiceError("conflict", "Discussion changed while unlinking project");
314
+ this.emit?.("discussion.updated", updated);
315
+ return this.detail(updated);
316
+ }
317
+ async detail(capture) {
318
+ const note = await this.notes.getNote(capture.noteId);
319
+ if (!note) throw new DiscussionServiceError("not_found", "Discussion note not found");
320
+ return {
321
+ discussion: capture,
322
+ note
323
+ };
324
+ }
325
+ enqueueMutation(id, mutation) {
326
+ const previous = this.mutationTails.get(id);
327
+ const tracked = (previous ? previous.catch(() => void 0) : Promise.resolve()).then(mutation).finally(() => {
328
+ if (this.mutationTails.get(id) === tracked) this.mutationTails.delete(id);
329
+ });
330
+ this.mutationTails.set(id, tracked);
331
+ return tracked;
332
+ }
333
+ };
334
+ //#endregion
335
+ export { DISCUSSION_AUDIO_MAX_BYTES, DISCUSSION_MAX_DURATION_MS, DISCUSSION_SEGMENT_MAX_BYTES, DiscussionService, DiscussionServiceError };
@@ -0,0 +1,3 @@
1
+ import type { DiscussionTranscriptSegment } from './types.js';
2
+ export declare function appendTranscriptWithoutOverlap(existing: string, next: string): string;
3
+ export declare function assembleDiscussionTranscript(segments: DiscussionTranscriptSegment[]): string;
@@ -0,0 +1,15 @@
1
+ //#region src/discussions/transcript.ts
2
+ function appendTranscriptWithoutOverlap(existing, next) {
3
+ const left = existing.trim();
4
+ const right = next.trim();
5
+ if (!left) return right;
6
+ if (!right) return left;
7
+ const max = Math.min(200, left.length, right.length);
8
+ for (let size = max; size >= 8; size -= 1) if (left.slice(-size).toLocaleLowerCase() === right.slice(0, size).toLocaleLowerCase()) return `${left}${right.slice(size)}`.trim();
9
+ return `${left}\n${right}`;
10
+ }
11
+ function assembleDiscussionTranscript(segments) {
12
+ return segments.reduce((text, segment) => appendTranscriptWithoutOverlap(text, segment.transcript ?? ""), "");
13
+ }
14
+ //#endregion
15
+ export { appendTranscriptWithoutOverlap, assembleDiscussionTranscript };