@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.
- package/dist/browser-ext/_locales/en/messages.json +34 -4
- package/dist/browser-ext/_locales/zh_CN/messages.json +34 -4
- package/dist/browser-ext/dist/assets/{auth-ClQTK565.js → auth-DB30ywek.js} +18 -53
- package/dist/browser-ext/dist/assets/pcm-capture-worklet-CGN9Mlhg.js +39 -0
- package/dist/browser-ext/dist/assets/{sidepanel-CeGqObOa.css → sidepanel-BbSQr-6P.css} +40 -6
- package/dist/browser-ext/dist/background.js +1 -1
- package/dist/browser-ext/dist/sidepanel.html +2 -2
- package/dist/browser-ext/dist/sidepanel.js +1219 -191
- package/dist/browser-ext/manifest.json +2 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-SJZ5fS17.js → activity-store-iPtG94qr.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-Cavbls0E.js → agent-avatar-dicebear-Cny5rATT.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-OyQ8OVZv.js +1 -0
- package/dist/gateway/static/root/assets/{agents-DlDTItrB.js → agents-DJeh_Ewo.js} +1 -1
- package/dist/gateway/static/root/assets/{api-CnJAVHBu.js → api-KMJzMqvZ.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-5gS0_LWl.js → app-management-settings-panel-ZqQKaZEI.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-DHzZCJon.js → appearance-settings-CfXsQkKO.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-k-QnVhKW.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-Ia98yZe3.js → archive-plugin-BrW53Hap.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-DiWW88c8.js → attachment-load-DkfFWHiS.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BuhTeGRJ.js → automations-page-e-5we6kD.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-DPnlkws1.js +6 -0
- package/dist/gateway/static/root/assets/{binary-plugins-DDD48cJv.js → binary-plugins-CWIswfXA.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-0AWVCD3F.js → block-editor-CfeF1OmA.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-DcVLoIzc.js → browser-automation-inputs-BTIufVbM.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-BKOYX5xV.js → browser-automations-page-DnsuWKRu.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-C8xyhnxx.js +8 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-CBllOLTT.js +8 -0
- package/dist/gateway/static/root/assets/channels-settings-C-A-VUE2.js +1 -0
- package/dist/gateway/static/root/assets/{channels-status-swr-BTT2sIGg.js → channels-status-swr-DMYQNs-J.js} +1 -1
- package/dist/gateway/static/root/assets/{chat-terminal-dock-CW9Byi8V.js → chat-terminal-dock-C_Fw1R3A.js} +2 -2
- package/dist/gateway/static/root/assets/connectors-page-CtsMo3H4.js +2 -0
- package/dist/gateway/static/root/assets/{date-picker-gdYmKC8W.js → date-picker-CPFxjTrN.js} +1 -1
- package/dist/gateway/static/root/assets/{dependency-picker-40BYmiMc.js → dependency-picker-C_5KqzjI.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-By1z6tKE.js → desktop-pet-DLJsC5D6.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-settings-ByjJqx9H.js → desktop-pet-settings-BdI1iSgz.js} +1 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-BnvPLhc9.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-DQO65KbM.js → extension-debug-page-BNS_TkZQ.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-Db32jip6.js → extension-page-DMSDZ7Mb.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CL8Wx8lf.js → extension-settings-page-CeGCWMt0.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-config-swr-Cpmyrr6j.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-DKOTYUma.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-CVTa5HSS.js → gateway-startup-retry-DoiPeb65.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-settings-CRXWu2q6.js → heartbeat-settings-gsdL_Yij.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-CbeAdS1T.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-DtMRG1oF.js → image-generation-api-CqjHWl2T.js} +1 -1
- package/dist/gateway/static/root/assets/index-B_Sg5BKz.css +2 -0
- package/dist/gateway/static/root/assets/index-enFXJJCU.js +74 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CjehhHPz.js → keyboard-shortcuts-settings-CCLtJ8s8.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-DzC6SrXj.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-CrIdaTrK.js → local-apps-page-3LdgP3NO.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-BkJ7g35d.js → locale-store-1urrBBCQ.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-FfSwV_VH.js → logs-page-DHClKtm6.js} +2 -2
- package/dist/gateway/static/root/assets/{management-settings-BakV5aOw.js → management-settings-BaCo9Nuo.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-DF3aJP_5.js → markdown-view-BZmcFIWa.js} +1 -1
- package/dist/gateway/static/root/assets/{media-plugins-Blv9U6Jp.js → media-plugins-BMjJppvG.js} +1 -1
- package/dist/gateway/static/root/assets/messages-DYFcjtmG.js +3 -0
- package/dist/gateway/static/root/assets/note-detail-panel-hYO2jNhL.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-BT66x22W.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-BQfVtLLC.js → page-tabs-BCszXX_F.js} +1 -1
- package/dist/gateway/static/root/assets/{preview-open-alternatives-kjHFiO81.js → preview-open-alternatives-CyM0sanh.js} +1 -1
- package/dist/gateway/static/root/assets/proactive-page-CVDGA5sB.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-BazdmFYo.js → product-open-page-lXAbj85Z.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-Bp6TAMPM.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-UhOIiDcg.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-CTq8EECj.js → registry-api-CxQstD00.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-DJIkMFT4.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-BhJlopSW.js → runtime-tools-settings-panel-BUFCgRdZ.js} +2 -2
- package/dist/gateway/static/root/assets/{schema-form-cxoO_5I1.js → schema-form-B01ksA8M.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-CQOAwPDt.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-CFuqbGI-.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-BiRTWiBi.js → settings-form-section-DvnjZBa3.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-page-D-Cb71pq.js → settings-page-DZRkpbCh.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-DVaEiAVY.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-DciHfTay.js → share-preview-page-vz0UK56q.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-XiCqMDAe.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-C3D8WanU.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-CxMWwR6A.js +2 -0
- package/dist/gateway/static/root/assets/{skills-page.utils-CU7PALQB.js → skills-page.utils-BryVNYll.js} +1 -1
- package/dist/gateway/static/root/assets/{system-settings-panel-GpjNbqvM.js → system-settings-panel-mKFDe6aZ.js} +1 -1
- package/dist/gateway/static/root/assets/task-detail-page-Cb0RZNcJ.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-mV6Cm2vj.js → theme-store-Dc5s20V3.js} +1 -1
- package/dist/gateway/static/root/assets/tunnel-api-FcI3wKy3.js +1 -0
- package/dist/gateway/static/root/assets/{use-autosave-LvCiBidm.js → use-autosave-CDgdtjTC.js} +1 -1
- package/dist/gateway/static/root/assets/user-model-page-Cnshc-Un.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-BlGysZxj.js → voice-api-key-field-Ce8cb5mN.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-D3DdcH4H.js → voice-config-api-Mk78BrpP.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-DvIQOR5x.js → work-discovery-overlay-CacFTtXe.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-JIp60bXT.js → workflow-run-setup-panel-V-5HuEUq.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-1OJimIfo.js +6 -0
- package/dist/gateway/static/root/index.html +10 -11
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/discussions.js +17 -0
- package/dist/packages/gateway-contract/src/proactive.js +4 -2
- package/dist/src/agent/agent-manager.js +4 -4
- package/dist/src/agent/background-review/run-background-review.js +1 -1
- package/dist/src/agent/commands/command-isolation.d.ts +1 -0
- package/dist/src/agent/commands/command-isolation.js +17 -1
- package/dist/src/agent/commands/commandMounts.d.ts +2 -0
- package/dist/src/agent/commands/commandMounts.js +32 -0
- package/dist/src/agent/context/project-context.d.ts +1 -0
- package/dist/src/agent/context/project-context.js +12 -4
- package/dist/src/agent/embedded/model-runtime.js +1 -1
- package/dist/src/agent/embedded/run-for-session.js +1 -1
- package/dist/src/agent/image/tool-model-config.js +1 -1
- package/dist/src/agent/mcp/oauth/mcp-oauth-store.js +1 -1
- package/dist/src/agent/sandbox/command-validator.js +0 -20
- package/dist/src/agent/sandbox/exec-policy.d.ts +1 -0
- package/dist/src/agent/sandbox/exec-policy.js +4 -4
- package/dist/src/agent/sandbox/fileAccess.d.ts +7 -0
- package/dist/src/agent/sandbox/fileAccess.js +100 -0
- package/dist/src/agent/sandbox/path-policy.d.ts +2 -0
- package/dist/src/agent/sandbox/path-policy.js +54 -17
- package/dist/src/agent/sandbox/sanitize-env-vars.js +10 -9
- package/dist/src/agent/service.js +2 -2
- package/dist/src/agent/session/session-inspector.js +1 -1
- package/dist/src/agent/skills/hub-lock.js +1 -1
- package/dist/src/agent/source-context/task-context.d.ts +3 -0
- package/dist/src/agent/source-context/task-context.js +24 -0
- package/dist/src/agent/source-context/types.d.ts +2 -2
- package/dist/src/agent/source-context/types.js +1 -1
- package/dist/src/agent/tasks/task-judge-service.js +3 -3
- package/dist/src/agent/tool-manuals/xopc-use.d.ts +1 -1
- package/dist/src/agent/tool-manuals/xopc-use.js +1 -0
- package/dist/src/agent/tools/apply-patch.js +25 -38
- package/dist/src/agent/tools/create-share-tool.js +11 -3
- package/dist/src/agent/tools/exec-command.js +2 -1
- package/dist/src/agent/tools/find.js +5 -3
- package/dist/src/agent/tools/grep.js +21 -4
- package/dist/src/agent/tools/image-generate-tool.js +3 -4
- package/dist/src/agent/tools/list-dir.js +2 -1
- package/dist/src/agent/tools/managed-job-tool.js +1 -0
- package/dist/src/agent/tools/publish-artifacts.js +4 -3
- package/dist/src/agent/tools/read.js +8 -5
- package/dist/src/agent/tools/repository-search.d.ts +2 -0
- package/dist/src/agent/tools/repository-search.js +51 -7
- package/dist/src/agent/tools/review-workspace.js +76 -10
- package/dist/src/agent/tools/send-media.js +4 -3
- package/dist/src/agent/tools/tool-paths.js +3 -1
- package/dist/src/agent/tools/write.js +4 -17
- package/dist/src/agent/tools/xopc-use-tool.js +2 -2
- package/dist/src/agent-config/schema.d.ts +24 -0
- package/dist/src/agent-config/schema.js +2 -1
- package/dist/src/ai-assist/text-assist.js +1 -1
- package/dist/src/auth/credentials.js +2 -2
- package/dist/src/auth/oauth/xopc-cloud.js +1 -1
- package/dist/src/auth/profiles/store.js +1 -1
- package/dist/src/auth/sync-provider-auth.js +2 -2
- package/dist/src/browser/providers/browser-ext-install.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/doctor/checks/provider-auth.js +21 -20
- package/dist/src/cli/commands/models.js +1 -1
- package/dist/src/config/index.js +4 -4
- package/dist/src/config/loader.js +2 -2
- package/dist/src/config/models-json.js +1 -1
- package/dist/src/config/schema.d.ts +16 -0
- package/dist/src/discussions/action-tasks.d.ts +21 -0
- package/dist/src/discussions/action-tasks.js +93 -0
- package/dist/src/discussions/analyzer.d.ts +21 -1
- package/dist/src/discussions/analyzer.js +229 -62
- package/dist/src/discussions/audio-repair.d.ts +8 -0
- package/dist/src/discussions/audio-repair.js +41 -0
- package/dist/src/discussions/edits.d.ts +22 -0
- package/dist/src/discussions/edits.js +81 -0
- package/dist/src/discussions/errors.d.ts +4 -0
- package/dist/src/discussions/errors.js +10 -0
- package/dist/src/discussions/export.d.ts +2 -0
- package/dist/src/discussions/export.js +23 -0
- package/dist/src/discussions/index.js +3 -2
- package/dist/src/discussions/live-worker.js +1 -1
- package/dist/src/discussions/organizer-worker.js +14 -5
- package/dist/src/discussions/organizer.d.ts +1 -1
- package/dist/src/discussions/organizer.js +60 -24
- package/dist/src/discussions/reconcile.d.ts +22 -0
- package/dist/src/discussions/reconcile.js +55 -0
- package/dist/src/discussions/recording.d.ts +9 -0
- package/dist/src/discussions/recording.js +219 -0
- package/dist/src/discussions/recordingJobs.d.ts +14 -0
- package/dist/src/discussions/recordingJobs.js +69 -0
- package/dist/src/discussions/repository.d.ts +4 -2
- package/dist/src/discussions/repository.js +15 -10
- package/dist/src/discussions/revisions.d.ts +12 -0
- package/dist/src/discussions/revisions.js +36 -0
- package/dist/src/discussions/sealer.d.ts +1 -0
- package/dist/src/discussions/sealer.js +61 -11
- package/dist/src/discussions/service.d.ts +16 -15
- package/dist/src/discussions/service.js +177 -91
- package/dist/src/discussions/types.d.ts +4 -139
- package/dist/src/discussions/types.js +1 -11
- package/dist/src/extensions/lockfile.js +1 -1
- package/dist/src/files/file-service.d.ts +1 -0
- package/dist/src/files/file-service.js +77 -0
- package/dist/src/gateway/agents-admin.js +1 -1
- package/dist/src/gateway/chat-stream/mapper.js +2 -2
- package/dist/src/gateway/file-path-classifier.js +1 -1
- package/dist/src/gateway/hono/app.js +3 -2
- package/dist/src/gateway/hono/lib/config-payload.d.ts +1 -0
- package/dist/src/gateway/hono/lib/config-payload.js +1 -1
- package/dist/src/gateway/hono/lib/static-ui.js +1 -0
- package/dist/src/gateway/hono/routes/context-sources.js +1 -2
- package/dist/src/gateway/hono/routes/discussions.js +126 -19
- package/dist/src/gateway/hono/routes/files.js +1 -22
- package/dist/src/gateway/hono/routes/index.js +0 -2
- package/dist/src/gateway/hono/routes/lazy-bundles.js +16 -0
- package/dist/src/gateway/hono/routes/models.js +1 -1
- package/dist/src/gateway/hono/routes/proactive-control.js +2 -36
- package/dist/src/gateway/hono/routes/project-understanding.d.ts +3 -0
- package/dist/src/gateway/hono/routes/project-understanding.js +65 -0
- package/dist/src/gateway/hono/routes/projects.js +20 -4
- package/dist/src/gateway/hono/routes/session-input-handler.d.ts +2 -2
- package/dist/src/gateway/hono/routes/session-input-handler.js +2 -2
- package/dist/src/gateway/hono/routes/shares.js +22 -1
- package/dist/src/gateway/hono/routes/tasks.js +2 -1
- package/dist/src/gateway/hono/routes/work-discovery.d.ts +0 -2
- package/dist/src/gateway/hono/routes/work-discovery.js +2 -16
- package/dist/src/gateway/hono/routes/workflows.js +2 -2
- package/dist/src/gateway/security/gateway-scopes.js +1 -2
- package/dist/src/gateway/service/agent-runner.js +2 -2
- package/dist/src/gateway/service/config-coordinator.js +1 -1
- package/dist/src/gateway/service.d.ts +3 -0
- package/dist/src/gateway/service.js +28 -13
- package/dist/src/knowledge-memory/repository.d.ts +2 -0
- package/dist/src/knowledge-memory/repository.js +3 -1
- package/dist/src/media-understanding/audio-transcription-runner.d.ts +1 -0
- package/dist/src/media-understanding/audio-transcription-runner.js +1 -0
- package/dist/src/media-understanding/runner.js +1 -0
- package/dist/src/media-understanding/types.d.ts +8 -0
- package/dist/src/notes/service.d.ts +3 -1
- package/dist/src/notes/store.d.ts +3 -1
- package/dist/src/notes/store.js +5 -3
- package/dist/src/proactive/experience.d.ts +38 -21
- package/dist/src/proactive/experience.js +113 -12
- package/dist/src/proactive/inbox/cards.js +7 -1
- package/dist/src/proactive/maintenance.js +0 -1
- package/dist/src/proactive/scenarios/control.d.ts +0 -35
- package/dist/src/proactive/scenarios/control.js +7 -53
- package/dist/src/projects/project-store.js +6 -4
- package/dist/src/providers/auth-runtime/resolve-auth.js +1 -1
- package/dist/src/providers/index.js +2 -2
- package/dist/src/providers/model-registry.js +1 -1
- package/dist/src/providers/register-runtime-providers.js +1 -1
- package/dist/src/session/client-history.d.ts +1 -1
- package/dist/src/session/client-history.js +1 -1
- package/dist/src/share/share-store.js +5 -1
- package/dist/src/storage/sqlite/migrations/166_remove_proactive_previews.sql +1 -0
- package/dist/src/storage/sqlite/migrations/167_discussion_recording_chunks.sql +7 -0
- package/dist/src/storage/sqlite/migrations/168_discussion_evidence.sql +16 -0
- package/dist/src/storage/sqlite/migrations/169_discussion_action_tasks.sql +8 -0
- package/dist/src/storage/sqlite/migrations/170_discussion_edits.sql +8 -0
- package/dist/src/storage/sqlite/migrations/171_discussion_recording_jobs.sql +14 -0
- package/dist/src/storage/sqlite/migrations/172_project_understanding.sql +3 -0
- package/dist/src/storage/sqlite/migrations/173_mobile_discussion_source.sql +5 -0
- package/dist/src/storage/sqlite/migrations/__tests__/mobile-discussion-source.test.ts +20 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tasks/home-query-service.js +2 -2
- package/dist/src/tasks/task-application-service.js +77 -3
- package/dist/src/tasks/task-context-assembler.js +1 -0
- package/dist/src/tasks/task-repository.d.ts +1 -0
- package/dist/src/tasks/task-repository.js +4 -0
- package/dist/src/tui/tui-startup-resources.js +1 -1
- package/dist/src/tui/tui.js +1 -1
- package/dist/src/voice/audio/normalize.js +28 -5
- package/dist/src/voice/stt/transcribe-core.js +1 -0
- package/dist/src/voice/stt/types.d.ts +1 -0
- package/dist/src/work-discovery/analyzer.d.ts +1 -0
- package/dist/src/work-discovery/analyzer.js +28 -22
- package/dist/src/work-discovery/index.js +3 -3
- package/dist/src/work-discovery/investigator.js +1 -1
- package/dist/src/work-discovery/project-understanding.d.ts +7 -0
- package/dist/src/work-discovery/project-understanding.js +38 -0
- package/dist/src/work-discovery/repository.d.ts +3 -1
- package/dist/src/work-discovery/repository.js +18 -5
- package/dist/src/work-discovery/service.d.ts +8 -0
- package/dist/src/work-discovery/service.js +163 -15
- package/dist/src/work-discovery/types.d.ts +2 -0
- package/dist/src/workflows/service/workflow-run-service.js +2 -2
- package/package.json +3 -3
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-DyCyuJpi.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-__yzruGu.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-DaisbmEh.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-C7zjxscU.js +0 -8
- package/dist/gateway/static/root/assets/capabilities-settings-panel-7kZMT1r6.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-BKUh6hzO.js +0 -1
- package/dist/gateway/static/root/assets/config-reload-event-Bjh2zoKp.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-m8J-F3u8.js +0 -2
- package/dist/gateway/static/root/assets/device-pairing-wizard-CfWQfz7a.js +0 -1
- package/dist/gateway/static/root/assets/gateway-config-swr-cd8owslz.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-wLly-Kc1.js +0 -1
- package/dist/gateway/static/root/assets/home-page-FqgSwpmy.js +0 -1
- package/dist/gateway/static/root/assets/index-5UchbidQ.js +0 -74
- package/dist/gateway/static/root/assets/index-CXF9-BpI.css +0 -2
- package/dist/gateway/static/root/assets/local-app-workbench-page-DtYZa4GK.js +0 -2
- package/dist/gateway/static/root/assets/messages-BGc-OJWg.js +0 -3
- package/dist/gateway/static/root/assets/note-detail-panel-JbvyGdEX.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-Dm7mE2FK.js +0 -8
- package/dist/gateway/static/root/assets/proactive-card-Ap_wfos3.js +0 -1
- package/dist/gateway/static/root/assets/proactive-page-D1lEuanT.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-h00lfcVI.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-C2Bo1FAL.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-_bZYAl49.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-F7CHsvPa.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-DtZSUX4H.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-VPfBsNVv.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-BS2pnWO0.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DX344nVV.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BhfKUsv7.js +0 -2
- package/dist/gateway/static/root/assets/task-detail-page-CElcQ9Sh.js +0 -1
- package/dist/gateway/static/root/assets/tunnel-api-Bhi_x_h4.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-BydARq2C.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-B972t6zx.js +0 -6
- package/dist/src/proactive/scenarios/preview.d.ts +0 -13
- 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 (
|
|
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
|
-
|
|
196
|
-
if (
|
|
197
|
-
const transcript =
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
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)
|
|
250
|
-
if (
|
|
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: "
|
|
256
|
-
|
|
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
|
-
}, [
|
|
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
|
|
285
|
-
if (
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
304
|
-
|
|
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
|
|
2
|
-
export type DiscussionStatus
|
|
3
|
-
|
|
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
|
-
|
|
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";
|
|
@@ -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 };
|