@xopcai/xopc 0.0.309 → 0.0.311
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/manifest.json +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/gateway/static/root/assets/{activity-store-XKNYlFUI.js → activity-store-DVUTs1fk.js} +1 -1
- package/dist/gateway/static/root/assets/{agent-avatar-dicebear-1S2TFECE.js → agent-avatar-dicebear-zKINl7UH.js} +1 -1
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-D2ePZKdY.js +1 -0
- package/dist/gateway/static/root/assets/{agents-CPqUMtCL.js → agents-BiD9STyv.js} +1 -1
- package/dist/gateway/static/root/assets/{api-BEUEE4f2.js → api-_aHQA8bd.js} +1 -1
- package/dist/gateway/static/root/assets/{app-link-BiBXVEvp.js → app-link-wamYbNUl.js} +1 -1
- package/dist/gateway/static/root/assets/{app-management-settings-panel-CgVBSqzu.js → app-management-settings-panel-C94LAxyt.js} +1 -1
- package/dist/gateway/static/root/assets/{appearance-settings-CoWdCokb.js → appearance-settings-CP01zHvC.js} +1 -1
- package/dist/gateway/static/root/assets/apps-page-B7DmSJQj.js +1 -0
- package/dist/gateway/static/root/assets/{archive-plugin-C3ZA6LRS.js → archive-plugin-SwWFt5MT.js} +1 -1
- package/dist/gateway/static/root/assets/{attachment-load-CdgS6_zy.js → attachment-load-DdTFml9l.js} +1 -1
- package/dist/gateway/static/root/assets/{automations-page-BtHL2hpg.js → automations-page-Kmr5WVFp.js} +1 -1
- package/dist/gateway/static/root/assets/automations-workspace-bkijiu-Y.js +7 -0
- package/dist/gateway/static/root/assets/{binary-plugins-CiDhtq_H.js → binary-plugins-47UTtDNq.js} +1 -1
- package/dist/gateway/static/root/assets/{block-editor-Br00T83w.js → block-editor-DvzZkMG8.js} +3 -3
- package/dist/gateway/static/root/assets/{browser-automation-inputs-D2tILykA.js → browser-automation-inputs-C611KRBx.js} +1 -1
- package/dist/gateway/static/root/assets/{browser-automations-page-BVuEmryB.js → browser-automations-page-DrUKNL9X.js} +2 -2
- package/dist/gateway/static/root/assets/browser-settings-page-Dhf_MWCf.js +3 -0
- package/dist/gateway/static/root/assets/capabilities-settings-panel-BCubrDf8.js +8 -0
- package/dist/gateway/static/root/assets/channels-settings-e4T1DEfN.js +1 -0
- package/dist/gateway/static/root/assets/{chat-terminal-dock-JsMtzDao.js → chat-terminal-dock-Bg_m7HUE.js} +2 -2
- package/dist/gateway/static/root/assets/{chunk-KS7C4IRE-Cq1tE_3b.js → chunk-KS7C4IRE-BMYIfy9x.js} +2 -2
- package/dist/gateway/static/root/assets/computer-settings-page-CosCmVsT.js +1 -0
- package/dist/gateway/static/root/assets/{connector-service-page-DZFNUTiN.js → connector-service-page-BL-hSR7_.js} +1 -1
- package/dist/gateway/static/root/assets/connectors-page-C0WWxnxJ.js +2 -0
- package/dist/gateway/static/root/assets/date-picker-DqYFVasF.js +1 -0
- package/dist/gateway/static/root/assets/{dependency-picker-B-BcuG1y.js → dependency-picker-DCyyFLKp.js} +1 -1
- package/dist/gateway/static/root/assets/{desktop-pet-CdBBhhdY.js → desktop-pet-CFnnpRA_.js} +1 -1
- package/dist/gateway/static/root/assets/desktop-pet-settings-U5wWRH4c.js +1 -0
- package/dist/gateway/static/root/assets/device-pairing-wizard-BhHxvFO1.js +1 -0
- package/dist/gateway/static/root/assets/{extension-debug-page-BFp5eKtY.js → extension-debug-page-DA8WJ3-E.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-DL3vyyH6.js → extension-page-C2BIB88m.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-BXtvdAsY.js → extension-settings-page-kpYo979N.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-config-swr-xAOGtenO.js → gateway-config-swr-DUDX42qY.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-realtime-BuaTsMSi.js → gateway-realtime-CGOW0-b4.js} +1 -1
- package/dist/gateway/static/root/assets/gateway-realtime-store-D4um_sV7.js +1 -0
- package/dist/gateway/static/root/assets/gateway-settings-CE9t1yTa.js +1 -0
- package/dist/gateway/static/root/assets/{gateway-startup-retry-BwDxKHd4.js → gateway-startup-retry-Cf8OxlvY.js} +1 -1
- package/dist/gateway/static/root/assets/home-page-C7Ezx8Du.js +1 -0
- package/dist/gateway/static/root/assets/{image-generation-api-wGMeI_yW.js → image-generation-api-D2YorS--.js} +1 -1
- package/dist/gateway/static/root/assets/{imports-page-zexFT5Rq.js → imports-page-NPk698vI.js} +1 -1
- package/dist/gateway/static/root/assets/index-1wIKA-kM.css +1 -0
- package/dist/gateway/static/root/assets/index-DxYitLp8.js +79 -0
- package/dist/gateway/static/root/assets/{keyboard-shortcuts-settings-CutG9zLh.js → keyboard-shortcuts-settings-BhreF0_O.js} +1 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-CPmynb3x.js +2 -0
- package/dist/gateway/static/root/assets/{local-apps-page-CJy6I7Dt.js → local-apps-page-DOZZBdqj.js} +1 -1
- package/dist/gateway/static/root/assets/{locale-store-DPZ79W1-.js → locale-store-DA0Z5jM_.js} +1 -1
- package/dist/gateway/static/root/assets/{logs-page-egGcPHNi.js → logs-page-M8kz26yo.js} +2 -2
- package/dist/gateway/static/root/assets/{management-settings-BQhIOELc.js → management-settings-JLqHYQvY.js} +1 -1
- package/dist/gateway/static/root/assets/{markdown-view-p2P5hP1U.js → markdown-view-BDpcjR_Z.js} +1 -1
- package/dist/gateway/static/root/assets/markdown-view-DSjfHfaW.css +1 -0
- package/dist/gateway/static/root/assets/{media-plugins-CsBylqlN.js → media-plugins-B75SWUJd.js} +1 -1
- package/dist/gateway/static/root/assets/messages-4BtQKYlM.js +3 -0
- package/dist/gateway/static/root/assets/note-detail-panel-B_Ft0M32.js +5 -0
- package/dist/gateway/static/root/assets/notes-page-C32mxSD-.js +8 -0
- package/dist/gateway/static/root/assets/{page-tabs-ChQ4jJFs.js → page-tabs-CMlQUzT6.js} +1 -1
- package/dist/gateway/static/root/assets/{popover-select-9i-ZtHXL.js → popover-select-B-GDnZO9.js} +1 -1
- package/dist/gateway/static/root/assets/preview-open-alternatives-cZ2hszQT.js +1 -0
- package/dist/gateway/static/root/assets/{product-open-page-BADxWEdP.js → product-open-page-CPoSbglF.js} +1 -1
- package/dist/gateway/static/root/assets/project-detail-page-DYttWua0.js +1 -0
- package/dist/gateway/static/root/assets/projects-page-D46drWbl.js +1 -0
- package/dist/gateway/static/root/assets/{registry-api-B_e_nZSN.js → registry-api-C0DCx3wv.js} +1 -1
- package/dist/gateway/static/root/assets/remote-access-hub-C9n9Nuhi.js +1 -0
- package/dist/gateway/static/root/assets/{runtime-tools-settings-panel-D-pwiIyp.js → runtime-tools-settings-panel-bMLWRApp.js} +2 -2
- package/dist/gateway/static/root/assets/scenes-page-Dw3hmHAr.js +1 -0
- package/dist/gateway/static/root/assets/{schema-form-IWmb05DX.js → schema-form-UVJV_7AS.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-C8P6ff91.js +1 -0
- package/dist/gateway/static/root/assets/settings-advanced-gate-whpUsAy3.js +1 -0
- package/dist/gateway/static/root/assets/{settings-form-section-B0veRBZs.js → settings-form-section-BqzOqonM.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-DQ9RJ8-b.js +1 -0
- package/dist/gateway/static/root/assets/settings-page-layout-D3Spald3.js +1 -0
- package/dist/gateway/static/root/assets/{settings-shell-layer-context-DCp6ykjQ.js → settings-shell-layer-context-BTQUDoJ2.js} +1 -1
- package/dist/gateway/static/root/assets/setup-status-panel-C8u601m8.js +1 -0
- package/dist/gateway/static/root/assets/{share-preview-page-CirpgkfD.js → share-preview-page-BcgYzA7y.js} +1 -1
- package/dist/gateway/static/root/assets/shares-settings-Bv-2rZ8e.js +1 -0
- package/dist/gateway/static/root/assets/skill-reload-api-LM-cgWUS.js +1 -0
- package/dist/gateway/static/root/assets/skills-page-ggmb7rCW.js +2 -0
- package/dist/gateway/static/root/assets/{skills-page.utils-B42mpWKV.js → skills-page.utils-ADABzrtk.js} +1 -1
- package/dist/gateway/static/root/assets/system-settings-panel-DfkcW_Za.js +1 -0
- package/dist/gateway/static/root/assets/task-detail-page-DC4yqDFd.js +1 -0
- package/dist/gateway/static/root/assets/{theme-store-BFbj_2Tu.js → theme-store-CegIlB8d.js} +1 -1
- package/dist/gateway/static/root/assets/tunnel-api-CTuwTdMm.js +1 -0
- package/dist/gateway/static/root/assets/use-autosave-5ZDd9XKh.js +1 -0
- package/dist/gateway/static/root/assets/user-model-page-BFKFhdZ0.js +2 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-BZbKQ8HT.js → voice-api-key-field-D3urfd-n.js} +1 -1
- package/dist/gateway/static/root/assets/{voice-config-api-SKCeQLrH.js → voice-config-api-DTyMdGOR.js} +1 -1
- package/dist/gateway/static/root/assets/{work-discovery-overlay-BRVTTXNG.js → work-discovery-overlay-JzM3Ylbs.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-run-setup-panel-D2kuwv5e.js → workflow-run-setup-panel-Bn2CtRdB.js} +2 -2
- package/dist/gateway/static/root/assets/workflows-page-BiCmt98A.js +6 -0
- package/dist/gateway/static/root/connector-icons/SOURCES.md +0 -2
- package/dist/gateway/static/root/index.html +17 -20
- package/dist/gateway/static/root/notification-sw.js +1 -1
- package/dist/package.js +1 -1
- package/dist/packages/gateway-contract/src/notifications.js +12 -11
- package/dist/packages/gateway-contract/src/proactive.js +6 -6
- package/dist/packages/gateway-contract/src/project-monitoring.js +4 -4
- package/dist/packages/gateway-contract/src/project-operating.js +1 -4
- package/dist/src/agent/agent-manager.d.ts +2 -6
- package/dist/src/agent/agent-manager.js +2 -1
- package/dist/src/agent/background-review/run-background-review.js +1 -1
- package/dist/src/agent/commands/command-registry.js +1 -1
- package/dist/src/agent/context/execution-scope.js +1 -1
- package/dist/src/agent/external-tools/cliProvider.d.ts +47 -0
- package/dist/src/agent/external-tools/cliProvider.js +317 -0
- package/dist/src/agent/external-tools/composio-provider.js +3 -3
- package/dist/src/agent/external-tools/gateway-tools.js +8 -2
- package/dist/src/agent/external-tools/index.js +6 -0
- package/dist/src/agent/external-tools/service.js +1 -1
- package/dist/src/agent/external-tools/types.d.ts +1 -1
- package/dist/src/agent/external-tools/types.js +1 -0
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
- package/dist/src/agent/mcp/oauth/mcp-oauth-manager.js +1 -1
- package/dist/src/agent/memory/turn-provenance.js +2 -1
- package/dist/src/agent/service/parse-outbound-session-key.js +1 -1
- package/dist/src/agent/service.js +1 -0
- package/dist/src/agent/service.types.d.ts +2 -0
- package/dist/src/agent/tools/factory.d.ts +2 -0
- package/dist/src/agent/tools/factory.js +3 -2
- package/dist/src/agent/tools/search/providers/xopc-cloud.d.ts +20 -0
- package/dist/src/agent/tools/search/providers/xopc-cloud.js +56 -0
- package/dist/src/agent/tools/search/registry.d.ts +4 -2
- package/dist/src/agent/tools/search/registry.js +5 -3
- package/dist/src/agent/tools/xopc-use-tool.d.ts +4 -2
- package/dist/src/agent/tools/xopc-use-tool.js +46 -43
- package/dist/src/automations/service/automation-service.d.ts +0 -4
- package/dist/src/automations/service/automation-service.js +0 -31
- package/dist/src/capabilities/store-connector.js +46 -0
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/cli/commands/project.js +1 -1
- package/dist/src/config/schema.d.ts +4 -4
- package/dist/src/config/schema.js +2 -12
- package/dist/src/config/web-patch.js +0 -5
- package/dist/src/connectors/account-selection.d.ts +14 -0
- package/dist/src/connectors/account-selection.js +26 -0
- package/dist/src/connectors/china-catalog.js +93 -226
- package/dist/src/connectors/cli/adapterRegistry.d.ts +4 -0
- package/dist/src/connectors/cli/adapterRegistry.js +26 -0
- package/dist/src/connectors/cli/adapters/lark.d.ts +2 -0
- package/dist/src/connectors/cli/adapters/lark.js +143 -0
- package/dist/src/connectors/cli/adapters/larkShortcuts.d.ts +3 -0
- package/dist/src/connectors/cli/adapters/larkShortcuts.js +64 -0
- package/dist/src/connectors/cli/adapters/wecom.d.ts +2 -0
- package/dist/src/connectors/cli/adapters/wecom.js +97 -0
- package/dist/src/connectors/cli/adapters/wps365/bundle.js +575 -0
- package/dist/src/connectors/cli/adapters/wps365.d.ts +2 -0
- package/dist/src/connectors/cli/adapters/wps365.js +115 -0
- package/dist/src/connectors/cli/assets.d.ts +3 -0
- package/dist/src/connectors/cli/assets.js +44 -0
- package/dist/src/connectors/cli/authorization.d.ts +16 -0
- package/dist/src/connectors/cli/authorization.js +184 -0
- package/dist/src/connectors/cli/distributions.d.ts +4 -0
- package/dist/src/connectors/cli/distributions.js +69 -0
- package/dist/src/connectors/cli/installer.d.ts +5 -0
- package/dist/src/connectors/cli/installer.js +102 -0
- package/dist/src/connectors/cli/lifecycle.d.ts +1 -0
- package/dist/src/connectors/cli/lifecycle.js +19 -0
- package/dist/src/connectors/cli/process.d.ts +18 -0
- package/dist/src/connectors/cli/process.js +100 -0
- package/dist/src/connectors/cli/protocol.d.ts +10 -0
- package/dist/src/connectors/cli/protocol.js +63 -0
- package/dist/src/connectors/cli/runtime.d.ts +10 -0
- package/dist/src/connectors/cli/runtime.js +93 -0
- package/dist/src/connectors/cli/store.d.ts +35 -0
- package/dist/src/connectors/cli/store.js +121 -0
- package/dist/src/connectors/cli/types.d.ts +73 -0
- package/dist/src/connectors/composio-backends.js +1 -1
- package/dist/src/connectors/composio-sessions.d.ts +1 -0
- package/dist/src/connectors/composio-sessions.js +8 -3
- package/dist/src/connectors/composio.js +1 -1
- package/dist/src/connectors/connection-candidates.js +22 -3
- package/dist/src/connectors/connection-recovery-service.d.ts +1 -0
- package/dist/src/connectors/connection-recovery-service.js +53 -8
- package/dist/src/connectors/health.js +52 -0
- package/dist/src/connectors/instances.js +5 -1
- package/dist/src/connectors/lifecycle.js +2 -0
- package/dist/src/connectors/principal.js +2 -1
- package/dist/src/connectors/runtime-adapter-registry.js +31 -0
- package/dist/src/connectors/types.d.ts +13 -1
- package/dist/src/connectors/usage.js +1 -1
- package/dist/src/connectors/workflow-preflight.js +8 -4
- package/dist/src/extensions/install.js +1 -1
- package/dist/src/gateway/hono/app.d.ts +2 -0
- package/dist/src/gateway/hono/app.js +4 -2
- package/dist/src/gateway/hono/lib/config-payload.d.ts +0 -15
- package/dist/src/gateway/hono/lib/config-payload.js +0 -11
- package/dist/src/gateway/hono/routes/cli-connectors.d.ts +3 -0
- package/dist/src/gateway/hono/routes/cli-connectors.js +219 -0
- package/dist/src/gateway/hono/routes/config-patch/gateway.d.ts +2 -2
- package/dist/src/gateway/hono/routes/config-patch/gateway.js +0 -59
- package/dist/src/gateway/hono/routes/config.js +0 -16
- package/dist/src/gateway/hono/routes/connectors.js +21 -22
- package/dist/src/gateway/hono/routes/deps.d.ts +2 -0
- package/dist/src/gateway/hono/routes/lazy-bundles.js +4 -17
- package/dist/src/gateway/hono/routes/projects.js +1 -1
- package/dist/src/gateway/hono/routes/public-gateway.js +0 -1
- package/dist/src/gateway/hono/routes/scenes.d.ts +3 -0
- package/dist/src/gateway/hono/routes/scenes.js +142 -0
- package/dist/src/gateway/hono/routes/tasks.js +0 -46
- package/dist/src/gateway/hono/routes/user-model.js +54 -1
- package/dist/src/gateway/scenes/browserNotifications.d.ts +4 -0
- package/dist/src/gateway/scenes/browserNotifications.js +54 -0
- package/dist/src/gateway/scenes/host.d.ts +34 -0
- package/dist/src/gateway/scenes/host.js +129 -0
- package/dist/src/gateway/scenes/mailContext.d.ts +32 -0
- package/dist/src/gateway/scenes/mailContext.js +235 -0
- package/dist/src/gateway/security/gateway-scopes.js +0 -4
- package/dist/src/gateway/security/index.js +1 -1
- package/dist/src/gateway/service/config-coordinator.d.ts +1 -10
- package/dist/src/gateway/service/config-coordinator.js +0 -16
- package/dist/src/gateway/service/run-gateway-agent.js +1 -1
- package/dist/src/gateway/service.d.ts +4 -25
- package/dist/src/gateway/service.js +44 -99
- package/dist/src/gateway/session-context-summary.js +2 -1
- package/dist/src/heartbeat/index.js +1 -1
- package/dist/src/infra/unhandled-rejections.js +1 -1
- package/dist/src/migrations/runner.js +1 -4
- package/dist/src/notifications/browser-endpoint.d.ts +2 -0
- package/dist/src/notifications/browser-endpoint.js +13 -0
- package/dist/src/notifications/browserSubscriptions.d.ts +20 -0
- package/dist/src/notifications/browserSubscriptions.js +59 -0
- package/dist/src/notifications/browserTransport.d.ts +9 -0
- package/dist/src/notifications/browserTransport.js +64 -0
- package/dist/src/notifications/dispatch.d.ts +46 -0
- package/dist/src/notifications/dispatch.js +109 -0
- package/dist/src/notifications/domain-delivery.d.ts +21 -0
- package/dist/src/notifications/planner.js +0 -36
- package/dist/src/notifications/quietHours.d.ts +6 -0
- package/dist/src/notifications/quietHours.js +23 -0
- package/dist/src/notifications/resultOutbox.d.ts +23 -0
- package/dist/src/notifications/resultOutbox.js +49 -0
- package/dist/src/notifications/service.d.ts +18 -4
- package/dist/src/notifications/service.js +42 -61
- package/dist/src/notifications/store.d.ts +2 -1
- package/dist/src/notifications/store.js +7 -8
- package/dist/src/projects/project-service.d.ts +1 -4
- package/dist/src/projects/project-service.js +2 -30
- package/dist/src/projects/workspace.js +1 -1
- package/dist/src/providers/model-catalog-persistence.js +19 -1
- package/dist/src/providers/model-catalog-store.js +1 -0
- package/dist/src/providers/model-catalog-types.d.ts +17 -0
- package/dist/src/providers/xopc-cloud-model-source.js +27 -0
- package/dist/src/scenes/agentExecutor.d.ts +8 -0
- package/dist/src/scenes/agentExecutor.js +63 -0
- package/dist/src/scenes/contracts.d.ts +118 -0
- package/dist/src/scenes/contracts.js +145 -0
- package/dist/src/scenes/execution.d.ts +56 -0
- package/dist/src/scenes/execution.js +168 -0
- package/dist/src/scenes/httpServices.d.ts +34 -0
- package/dist/src/scenes/inbox.d.ts +16 -0
- package/dist/src/scenes/inbox.js +68 -0
- package/dist/src/scenes/mailContext.d.ts +20 -0
- package/dist/src/scenes/mailContext.js +106 -0
- package/dist/src/scenes/mailObservations.d.ts +20 -0
- package/dist/src/scenes/mailObservations.js +107 -0
- package/dist/src/scenes/maintenance.d.ts +3 -0
- package/dist/src/scenes/maintenance.js +24 -0
- package/dist/src/scenes/metrics.d.ts +35 -0
- package/dist/src/scenes/metrics.js +94 -0
- package/dist/src/scenes/notificationEligibility.d.ts +17 -0
- package/dist/src/scenes/notificationEligibility.js +72 -0
- package/dist/src/scenes/notificationPublication.d.ts +7 -0
- package/dist/src/scenes/notificationPublication.js +43 -0
- package/dist/src/scenes/preferences.d.ts +40 -0
- package/dist/src/scenes/preferences.js +108 -0
- package/dist/src/scenes/readiness.d.ts +7 -0
- package/dist/src/scenes/readiness.js +20 -0
- package/dist/src/scenes/repository.d.ts +174 -0
- package/dist/src/scenes/repository.js +740 -0
- package/dist/src/scenes/resultNotifications.d.ts +17 -0
- package/dist/src/scenes/resultNotifications.js +204 -0
- package/dist/src/scenes/runtime.d.ts +25 -0
- package/dist/src/scenes/runtime.js +101 -0
- package/dist/src/scenes/schedule.d.ts +9 -0
- package/dist/src/scenes/schedule.js +45 -0
- package/dist/src/scenes/service.d.ts +29 -0
- package/dist/src/scenes/service.js +116 -0
- package/dist/src/scenes/templates.d.ts +64 -0
- package/dist/src/scenes/templates.js +76 -0
- package/dist/src/scenes/userNotes.d.ts +10 -0
- package/dist/src/scenes/userNotes.js +34 -0
- package/dist/src/session/resolve-session.js +2 -1
- package/dist/src/session/store.js +2 -1
- package/dist/src/share/share-config.js +0 -5
- package/dist/src/share/site-share-config.js +0 -5
- package/dist/src/storage/sqlite/connection-wait-repository.js +20 -7
- package/dist/src/storage/sqlite/connection.js +1 -1
- package/dist/src/storage/sqlite/connector-account-repository.js +1 -0
- package/dist/src/storage/sqlite/connector-sync-policy-repository.d.ts +0 -4
- package/dist/src/storage/sqlite/connector-sync-policy-repository.js +3 -17
- package/dist/src/storage/sqlite/conversation-repository.js +2 -1
- package/dist/src/storage/sqlite/index.js +2 -1
- package/dist/src/storage/sqlite/migrations/187_cli_connectors.sql +34 -0
- package/dist/src/storage/sqlite/migrations/188_scene_storage.sql +2 -0
- package/dist/src/storage/sqlite/migrations/189_scene_source_health.sql +8 -0
- package/dist/src/storage/sqlite/migrations/conversation-backup.d.ts +2 -0
- package/dist/src/storage/sqlite/migrations/conversation-backup.js +7 -3
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +20 -44
- package/dist/src/storage/sqlite/migrations/scene-reset.d.ts +5 -0
- package/dist/src/storage/sqlite/migrations/scene-reset.js +40 -0
- package/dist/src/storage/sqlite/scenes-schema.d.ts +8 -0
- package/dist/src/storage/sqlite/scenes-schema.js +61 -0
- package/dist/src/storage/sqlite/schema.js +4 -10
- package/dist/src/storage/sqlite/schemas/notifications.sql +65 -0
- package/dist/src/storage/sqlite/schemas/scenes.sql +212 -0
- package/dist/src/storage/sqlite/session-read-repository.d.ts +8 -0
- package/dist/src/storage/sqlite/session-read-repository.js +35 -0
- package/dist/src/storage/sqlite/session-repository.d.ts +1 -1
- package/dist/src/storage/sqlite/session-repository.js +2 -25
- package/dist/src/storage/sqlite/sql-assets.d.ts +2 -0
- package/dist/src/storage/sqlite/sql-assets.js +18 -0
- package/dist/src/storage/sqlite/transaction.d.ts +2 -0
- package/dist/src/storage/sqlite/transaction.js +20 -1
- package/dist/src/tasks/home-query-service.d.ts +0 -2
- package/dist/src/tasks/home-query-service.js +13 -43
- package/dist/src/tasks/index.d.ts +0 -1
- package/dist/src/tasks/index.js +4 -5
- package/dist/src/tasks/project-operating-view-service.js +1 -4
- package/dist/src/tui/backends/embedded-backend.js +1 -1
- package/dist/src/tui/tui-commands.js +1 -1
- package/dist/src/user-model/goals.d.ts +10 -0
- package/dist/src/user-model/goals.js +29 -1
- package/dist/src/user-model/index.js +2 -2
- package/dist/src/voice/realtime/memory-context.js +2 -1
- package/dist/src/workflows/service/workflow-run-service.js +1 -1
- package/package.json +4 -2
- package/dist/gateway/static/root/assets/agent-defaults-settings-panel-f0-YFMUz.js +0 -1
- package/dist/gateway/static/root/assets/api-CN4id43t.js +0 -1
- package/dist/gateway/static/root/assets/apps-page-CexHOcWg.js +0 -1
- package/dist/gateway/static/root/assets/automations-workspace-onJPfmO6.js +0 -6
- package/dist/gateway/static/root/assets/browser-settings-page-BgVTyPFY.js +0 -3
- package/dist/gateway/static/root/assets/capabilities-settings-panel-Dra8wj1S.js +0 -8
- package/dist/gateway/static/root/assets/channels-settings-CkxiMDJ4.js +0 -1
- package/dist/gateway/static/root/assets/channels-status-swr-Dtder4G_.js +0 -1
- package/dist/gateway/static/root/assets/computer-settings-page-DHerePwo.js +0 -1
- package/dist/gateway/static/root/assets/connectors-page-cl9ZrV4D.js +0 -2
- package/dist/gateway/static/root/assets/cron-expression-DAhdv_Xv.js +0 -2
- package/dist/gateway/static/root/assets/date-picker-PacO1m3a.js +0 -1
- package/dist/gateway/static/root/assets/desktop-pet-settings-Gy03NgMI.js +0 -1
- package/dist/gateway/static/root/assets/device-pairing-wizard-aV25nKag.js +0 -1
- package/dist/gateway/static/root/assets/external-link-DjqjBmc3.js +0 -1
- package/dist/gateway/static/root/assets/gateway-realtime-store-BG95uGVW.js +0 -1
- package/dist/gateway/static/root/assets/gateway-settings-Dcr2Ikeh.js +0 -1
- package/dist/gateway/static/root/assets/globe-DSSGFWFn.js +0 -1
- package/dist/gateway/static/root/assets/heartbeat-settings-DOy3alvT.js +0 -1
- package/dist/gateway/static/root/assets/home-page-CDm3dGVy.js +0 -1
- package/dist/gateway/static/root/assets/index-D-vczw_0.css +0 -1
- package/dist/gateway/static/root/assets/index-DJo4DimT.js +0 -79
- package/dist/gateway/static/root/assets/loader-circle-CifKLtgG.js +0 -1
- package/dist/gateway/static/root/assets/local-app-workbench-page-CwLvTwjn.js +0 -2
- package/dist/gateway/static/root/assets/markdown-view-Y68dCxuc.css +0 -1
- package/dist/gateway/static/root/assets/messages-BiYTyLJr.js +0 -3
- package/dist/gateway/static/root/assets/note-detail-panel-c2k5Uz6X.js +0 -5
- package/dist/gateway/static/root/assets/notes-page-BLQWJMhf.js +0 -8
- package/dist/gateway/static/root/assets/preview-open-alternatives-9B5W7nVE.js +0 -1
- package/dist/gateway/static/root/assets/proactive-page-iOjI0d_e.js +0 -1
- package/dist/gateway/static/root/assets/project-detail-page-BcxbQn2M.js +0 -1
- package/dist/gateway/static/root/assets/projects-page-BuSvld0O.js +0 -1
- package/dist/gateway/static/root/assets/remote-access-hub-CFS8LQKW.js +0 -1
- package/dist/gateway/static/root/assets/sessions-page-DZjc__ok.js +0 -1
- package/dist/gateway/static/root/assets/settings-advanced-gate-C1SM5B93.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-CxRWyWYe.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-layout-X1x-2Cp9.js +0 -1
- package/dist/gateway/static/root/assets/setup-status-panel-D9Rl7Tec.js +0 -1
- package/dist/gateway/static/root/assets/shares-settings-CwCThBs0.js +0 -1
- package/dist/gateway/static/root/assets/skill-reload-api-DG6VGbOX.js +0 -1
- package/dist/gateway/static/root/assets/skills-page-BdRJ2vnL.js +0 -2
- package/dist/gateway/static/root/assets/system-settings-panel-DXIZpAvU.js +0 -1
- package/dist/gateway/static/root/assets/task-detail-page-QXo9LJly.js +0 -1
- package/dist/gateway/static/root/assets/tunnel-api-Db2guRdW.js +0 -1
- package/dist/gateway/static/root/assets/use-autosave-DDY9qoXZ.js +0 -1
- package/dist/gateway/static/root/assets/user-model-page-CzPmMkaO.js +0 -2
- package/dist/gateway/static/root/assets/workflows-page-CQXtiYtq.js +0 -6
- package/dist/gateway/static/root/connector-icons/wps-calendar.svg +0 -14
- package/dist/gateway/static/root/connector-icons/wps-mail.svg +0 -7
- package/dist/src/connectors/source-change-publisher.d.ts +0 -17
- package/dist/src/connectors/source-change-publisher.js +0 -151
- package/dist/src/gateway/heartbeat/check-store.d.ts +0 -18
- package/dist/src/gateway/heartbeat/check-store.js +0 -74
- package/dist/src/gateway/heartbeat/index.d.ts +0 -1
- package/dist/src/gateway/heartbeat/index.js +0 -2
- package/dist/src/gateway/heartbeat/service.d.ts +0 -55
- package/dist/src/gateway/heartbeat/service.js +0 -228
- package/dist/src/gateway/hono/routes/proactive-control.d.ts +0 -3
- package/dist/src/gateway/hono/routes/proactive-control.js +0 -135
- package/dist/src/gateway/hono/routes/proactive.d.ts +0 -4
- package/dist/src/gateway/hono/routes/proactive.js +0 -164
- package/dist/src/gateway/hono/routes/workspace.d.ts +0 -3
- package/dist/src/gateway/hono/routes/workspace.js +0 -62
- package/dist/src/gateway/workspace-heartbeat-path.d.ts +0 -3
- package/dist/src/gateway/workspace-heartbeat-path.js +0 -14
- package/dist/src/migrations/conversation-routing-config.d.ts +0 -2
- package/dist/src/migrations/conversation-routing-config.js +0 -42
- package/dist/src/notifications/proactive-channel.d.ts +0 -8
- package/dist/src/notifications/proactive-channel.js +0 -60
- package/dist/src/notifications/proactive-policy.d.ts +0 -3
- package/dist/src/notifications/proactive-policy.js +0 -35
- package/dist/src/notifications/web-push.d.ts +0 -19
- package/dist/src/notifications/web-push.js +0 -185
- package/dist/src/proactive/actions/service.d.ts +0 -1
- package/dist/src/proactive/actions/service.js +0 -82
- package/dist/src/proactive/events/envelope.d.ts +0 -2
- package/dist/src/proactive/events/envelope.js +0 -54
- package/dist/src/proactive/events/product-event-bridge.d.ts +0 -7
- package/dist/src/proactive/events/product-event-bridge.js +0 -87
- package/dist/src/proactive/events/publisher.d.ts +0 -4
- package/dist/src/proactive/events/repository.d.ts +0 -9
- package/dist/src/proactive/events/repository.js +0 -63
- package/dist/src/proactive/events/types.d.ts +0 -44
- package/dist/src/proactive/events/types.js +0 -15
- package/dist/src/proactive/execution/agent-executor.d.ts +0 -21
- package/dist/src/proactive/execution/agent-executor.js +0 -125
- package/dist/src/proactive/execution/authorization.d.ts +0 -7
- package/dist/src/proactive/execution/authorization.js +0 -71
- package/dist/src/proactive/execution/context.d.ts +0 -49
- package/dist/src/proactive/execution/context.js +0 -408
- package/dist/src/proactive/execution/insight.d.ts +0 -14
- package/dist/src/proactive/execution/insight.js +0 -132
- package/dist/src/proactive/execution/repository.d.ts +0 -28
- package/dist/src/proactive/execution/repository.js +0 -211
- package/dist/src/proactive/execution/types.d.ts +0 -80
- package/dist/src/proactive/execution/worker.d.ts +0 -15
- package/dist/src/proactive/execution/worker.js +0 -146
- package/dist/src/proactive/experience.d.ts +0 -89
- package/dist/src/proactive/experience.js +0 -195
- package/dist/src/proactive/follow-ups.d.ts +0 -128
- package/dist/src/proactive/follow-ups.js +0 -182
- package/dist/src/proactive/inbox/cards.d.ts +0 -21
- package/dist/src/proactive/inbox/cards.js +0 -276
- package/dist/src/proactive/inbox/delivery.d.ts +0 -6
- package/dist/src/proactive/inbox/delivery.js +0 -54
- package/dist/src/proactive/inbox/digest.d.ts +0 -8
- package/dist/src/proactive/inbox/digest.js +0 -123
- package/dist/src/proactive/inbox/lifecycle.d.ts +0 -4
- package/dist/src/proactive/inbox/lifecycle.js +0 -62
- package/dist/src/proactive/inbox/notification-policy.d.ts +0 -1
- package/dist/src/proactive/inbox/notification-policy.js +0 -25
- package/dist/src/proactive/inbox/repository.d.ts +0 -25
- package/dist/src/proactive/inbox/repository.js +0 -244
- package/dist/src/proactive/inbox/service.d.ts +0 -23
- package/dist/src/proactive/inbox/service.js +0 -46
- package/dist/src/proactive/inbox/types.d.ts +0 -64
- package/dist/src/proactive/inbox/worker.d.ts +0 -13
- package/dist/src/proactive/inbox/worker.js +0 -71
- package/dist/src/proactive/index.d.ts +0 -20
- package/dist/src/proactive/index.js +0 -14
- package/dist/src/proactive/mail-thread.d.ts +0 -13
- package/dist/src/proactive/mail-thread.js +0 -55
- package/dist/src/proactive/maintenance.d.ts +0 -4
- package/dist/src/proactive/maintenance.js +0 -28
- package/dist/src/proactive/metrics.d.ts +0 -10
- package/dist/src/proactive/metrics.js +0 -28
- package/dist/src/proactive/policy/presence.d.ts +0 -2
- package/dist/src/proactive/policy/presence.js +0 -30
- package/dist/src/proactive/policy/service.d.ts +0 -54
- package/dist/src/proactive/policy/service.js +0 -123
- package/dist/src/proactive/routing/batch-repository.d.ts +0 -27
- package/dist/src/proactive/routing/batch-repository.js +0 -57
- package/dist/src/proactive/routing/matcher.d.ts +0 -5
- package/dist/src/proactive/routing/matcher.js +0 -57
- package/dist/src/proactive/routing/types.d.ts +0 -44
- package/dist/src/proactive/routing/types.js +0 -1
- package/dist/src/proactive/scenarios/control.d.ts +0 -52
- package/dist/src/proactive/scenarios/control.js +0 -75
- package/dist/src/proactive/scenarios/prompt-composer.d.ts +0 -14
- package/dist/src/proactive/scenarios/prompt-composer.js +0 -26
- package/dist/src/proactive/scenarios/repository.d.ts +0 -23
- package/dist/src/proactive/scenarios/repository.js +0 -174
- package/dist/src/proactive/scenarios/service.d.ts +0 -15
- package/dist/src/proactive/scenarios/service.js +0 -26
- package/dist/src/proactive/scenarios/types.d.ts +0 -43
- package/dist/src/proactive/scenarios/types.js +0 -1
- package/dist/src/proactive/service.d.ts +0 -25
- package/dist/src/proactive/service.js +0 -78
- package/dist/src/proactive/source-freshness.d.ts +0 -8
- package/dist/src/proactive/source-freshness.js +0 -28
- package/dist/src/proactive/status.d.ts +0 -2
- package/dist/src/proactive/status.js +0 -22
- package/dist/src/proactive/temporal/schedule.d.ts +0 -3
- package/dist/src/proactive/temporal/schedule.js +0 -68
- package/dist/src/proactive/temporal/worker.d.ts +0 -17
- package/dist/src/proactive/temporal/worker.js +0 -129
- package/dist/src/tasks/project-monitoring-service.d.ts +0 -32
- package/dist/src/tasks/project-monitoring-service.js +0 -118
- /package/dist/src/{proactive/execution → connectors/cli}/types.js +0 -0
- /package/dist/src/{proactive/events/publisher.js → notifications/domain-delivery.js} +0 -0
- /package/dist/src/{proactive/inbox/types.js → scenes/httpServices.js} +0 -0
|
@@ -0,0 +1,740 @@
|
|
|
1
|
+
import { activationInputSchema, canTransitionActivation, sceneContentHash, sceneNotesSchema, sceneTemplateSchema } from "./contracts.js";
|
|
2
|
+
import { nextSceneScheduleAt, sceneScheduleSchema } from "./schedule.js";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
//#region src/scenes/repository.ts
|
|
5
|
+
const checksAllowed = `NOT EXISTS (SELECT 1 FROM scene_preferences policy
|
|
6
|
+
WHERE policy.owner_id = a.owner_id AND policy.workspace_id = a.workspace_id
|
|
7
|
+
AND (json_extract(policy.preferences_json, '$.checksPaused') = 1
|
|
8
|
+
OR julianday(json_extract(policy.preferences_json, '$.checksPausedUntil')) > julianday(?, 'unixepoch')))`;
|
|
9
|
+
var SceneConflictError = class extends Error {};
|
|
10
|
+
var SceneNotFoundError = class extends Error {};
|
|
11
|
+
var SceneInputError = class extends Error {};
|
|
12
|
+
/** The caller supplies the database; storage is initialized by the normal database upgrade. */
|
|
13
|
+
var SceneRepository = class {
|
|
14
|
+
constructor(db) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
}
|
|
17
|
+
transaction(operation) {
|
|
18
|
+
const savepoint = `scene_${randomUUID().replaceAll("-", "")}`;
|
|
19
|
+
this.db.exec(`SAVEPOINT ${savepoint}`);
|
|
20
|
+
try {
|
|
21
|
+
const result = operation();
|
|
22
|
+
this.db.exec(`RELEASE ${savepoint}`);
|
|
23
|
+
return result;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
this.db.exec(`ROLLBACK TO ${savepoint}`);
|
|
26
|
+
this.db.exec(`RELEASE ${savepoint}`);
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
installTemplate(value) {
|
|
31
|
+
const template = sceneTemplateSchema.parse(value);
|
|
32
|
+
const hash = sceneContentHash(template);
|
|
33
|
+
return this.transaction(() => {
|
|
34
|
+
const previous = this.db.prepare("SELECT content_hash FROM scene_template_versions WHERE template_key = ? AND version = ?").get(template.key, template.version);
|
|
35
|
+
if (previous && previous.content_hash !== hash) throw new SceneConflictError("Template versions are immutable");
|
|
36
|
+
this.db.prepare("INSERT OR IGNORE INTO scene_template_versions VALUES (?, ?, ?, ?)").run(template.key, template.version, hash, JSON.stringify(template));
|
|
37
|
+
return template;
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
getTemplate(key, version) {
|
|
41
|
+
const row = this.db.prepare("SELECT manifest_json FROM scene_template_versions WHERE template_key = ? AND version = ?").get(key, version);
|
|
42
|
+
if (!row) throw new SceneNotFoundError("Scene template not found");
|
|
43
|
+
return sceneTemplateSchema.parse(JSON.parse(String(row.manifest_json)));
|
|
44
|
+
}
|
|
45
|
+
listTemplates() {
|
|
46
|
+
return this.db.prepare("SELECT manifest_json FROM scene_template_versions ORDER BY template_key, version").all().map((row) => sceneTemplateSchema.parse(JSON.parse(String(row.manifest_json))));
|
|
47
|
+
}
|
|
48
|
+
listRuns(principal, activationId, limit = 50, afterId = "") {
|
|
49
|
+
this.getActivation(principal, activationId);
|
|
50
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("Invalid scene list limit");
|
|
51
|
+
const cursor = afterId ? this.db.prepare("SELECT created_at FROM scene_runs WHERE id = ? AND activation_id = ?").get(afterId, activationId) : void 0;
|
|
52
|
+
if (afterId && !cursor) throw new SceneNotFoundError("Scene run cursor not found");
|
|
53
|
+
return this.db.prepare(`SELECT id, activation_id AS activationId, status, attempt, reason, created_at AS createdAt, retry_at AS retryAt
|
|
54
|
+
FROM scene_runs WHERE activation_id = ? AND (? IS NULL OR created_at < ? OR (created_at = ? AND id < ?))
|
|
55
|
+
ORDER BY created_at DESC, id DESC LIMIT ?`).all(activationId, cursor?.created_at ?? null, cursor?.created_at ?? null, cursor?.created_at ?? null, afterId, limit);
|
|
56
|
+
}
|
|
57
|
+
createActivation(principal, value, requestId) {
|
|
58
|
+
this.assertPrincipal(principal);
|
|
59
|
+
const input = activationInputSchema.parse(value);
|
|
60
|
+
if (requestId !== void 0 && (!requestId.trim() || requestId.length > 200)) throw new Error("Invalid scene request identity");
|
|
61
|
+
return this.transaction(() => {
|
|
62
|
+
const hash = sceneContentHash(input);
|
|
63
|
+
if (requestId !== void 0) {
|
|
64
|
+
const previous = this.db.prepare("SELECT * FROM scene_activation_requests WHERE owner_id = ? AND workspace_id = ? AND request_id = ?").get(principal.ownerId, principal.workspaceId, requestId);
|
|
65
|
+
if (previous) {
|
|
66
|
+
if (previous.content_hash !== hash) throw new SceneConflictError("Scene request identity was reused");
|
|
67
|
+
return this.getActivation(principal, String(previous.activation_id));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.getTemplate(input.templateKey, input.templateVersion);
|
|
71
|
+
const activation = {
|
|
72
|
+
...input,
|
|
73
|
+
...principal,
|
|
74
|
+
id: randomUUID(),
|
|
75
|
+
status: "needs_setup",
|
|
76
|
+
revision: 1
|
|
77
|
+
};
|
|
78
|
+
this.db.prepare(`INSERT INTO scene_activations VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(activation.id, principal.ownerId, principal.workspaceId, input.templateKey, input.templateVersion, input.goal, JSON.stringify(input.scope), JSON.stringify(input.permissions), activation.status, activation.revision);
|
|
79
|
+
if (requestId !== void 0) this.db.prepare("INSERT INTO scene_activation_requests VALUES (?, ?, ?, ?, ?)").run(principal.ownerId, principal.workspaceId, requestId, hash, activation.id);
|
|
80
|
+
return activation;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
listActivations(principal, limit = 50, afterId = "") {
|
|
84
|
+
this.assertPrincipal(principal);
|
|
85
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("Invalid scene list limit");
|
|
86
|
+
return this.db.prepare("SELECT id FROM scene_activations WHERE owner_id = ? AND workspace_id = ? AND id > ? ORDER BY id LIMIT ?").all(principal.ownerId, principal.workspaceId, afterId, limit).map((row) => this.getActivation(principal, String(row.id)));
|
|
87
|
+
}
|
|
88
|
+
getActivation(principal, id) {
|
|
89
|
+
this.assertPrincipal(principal);
|
|
90
|
+
const row = this.db.prepare("SELECT * FROM scene_activations WHERE id = ? AND owner_id = ? AND workspace_id = ?").get(id, principal.ownerId, principal.workspaceId);
|
|
91
|
+
if (!row) throw new SceneNotFoundError("Scene activation not found");
|
|
92
|
+
const providers = this.getTemplate(String(row.template_key), String(row.template_version)).contextProviders;
|
|
93
|
+
const setupMissing = [];
|
|
94
|
+
if (providers.includes("user_notes")) {
|
|
95
|
+
const notes = this.db.prepare("SELECT content, valid_until FROM scene_notes WHERE activation_id = ?").get(id);
|
|
96
|
+
if (!notes || !String(notes.content).trim() || notes.valid_until !== null && Number(notes.valid_until) <= Date.now()) setupMissing.push("notes");
|
|
97
|
+
if (!this.db.prepare("SELECT 1 FROM scene_schedule_cursors WHERE activation_id = ?").get(id)) setupMissing.push("schedule");
|
|
98
|
+
}
|
|
99
|
+
if (providers.includes("mail") && !this.db.prepare("SELECT 1 FROM scene_work_items WHERE activation_id = ? AND status = 'watching'").get(id)) setupMissing.push("deadline");
|
|
100
|
+
return {
|
|
101
|
+
setupMissing,
|
|
102
|
+
id: String(row.id),
|
|
103
|
+
...principal,
|
|
104
|
+
templateKey: String(row.template_key),
|
|
105
|
+
templateVersion: String(row.template_version),
|
|
106
|
+
goal: String(row.goal),
|
|
107
|
+
scope: JSON.parse(String(row.scope_json)),
|
|
108
|
+
permissions: JSON.parse(String(row.permissions_json)),
|
|
109
|
+
status: row.status,
|
|
110
|
+
revision: Number(row.revision)
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
recordSourceHealth(activation, reason, now, retryAt = null) {
|
|
114
|
+
if (!this.db.prepare("SELECT 1 FROM scene_activations WHERE id = ? AND revision = ? AND status = 'active'").get(activation.id, activation.revision)) return;
|
|
115
|
+
this.db.prepare(`INSERT INTO scene_source_health VALUES (?, ?, ?, ?, ?, ?)
|
|
116
|
+
ON CONFLICT(activation_id) DO UPDATE SET last_attempt_at = excluded.last_attempt_at,
|
|
117
|
+
last_success_at = COALESCE(excluded.last_success_at, scene_source_health.last_success_at),
|
|
118
|
+
reason = excluded.reason, consecutive_failures = CASE WHEN excluded.reason IS NULL THEN 0 ELSE scene_source_health.consecutive_failures + 1 END,
|
|
119
|
+
retry_at = excluded.retry_at`).run(activation.id, now, reason === null ? now : null, reason, reason === null ? 0 : 1, retryAt);
|
|
120
|
+
}
|
|
121
|
+
getSourceHealth(activationId) {
|
|
122
|
+
return this.db.prepare(`SELECT last_attempt_at AS lastAttemptAt, last_success_at AS lastSuccessAt,
|
|
123
|
+
reason, consecutive_failures AS consecutiveFailures, retry_at AS retryAt FROM scene_source_health WHERE activation_id = ?`).get(activationId) ?? null;
|
|
124
|
+
}
|
|
125
|
+
reuseUnchangedResult(claim, hash, now, inspectOnly = false) {
|
|
126
|
+
return this.transaction(() => {
|
|
127
|
+
if (!this.isCurrentClaim(claim, now)) return false;
|
|
128
|
+
if (!this.db.prepare(`SELECT p.id FROM scene_presentations p
|
|
129
|
+
JOIN scene_outcomes o ON o.id = p.outcome_id JOIN scene_runs r ON r.id = o.run_id
|
|
130
|
+
JOIN scene_context_snapshots s ON s.run_id = r.id AND s.lease_epoch = r.lease_epoch
|
|
131
|
+
JOIN scene_trigger_intents i ON i.id = r.intent_id JOIN scene_events e ON e.id = i.event_id
|
|
132
|
+
JOIN scene_trigger_intents ci ON ci.id = ? JOIN scene_events ce ON ce.id = ci.event_id
|
|
133
|
+
WHERE r.activation_id = ? AND r.activation_revision = ? AND s.content_hash = ?
|
|
134
|
+
AND ce.event_type IN ('manual.check', 'scene.manual.check') AND e.subject_id = ce.subject_id AND e.account_id IS ce.account_id
|
|
135
|
+
AND p.status <> 'withdrawn' AND p.withdrawn_at IS NULL AND (p.expires_at IS NULL OR p.expires_at > ?)
|
|
136
|
+
LIMIT 1`).get(claim.intentId, claim.activationId, claim.activationRevision, hash, now)) return false;
|
|
137
|
+
if (inspectOnly) return true;
|
|
138
|
+
this.db.prepare("UPDATE scene_runs SET status = 'skipped', reason = 'unchanged_result', lease_owner = NULL, lease_until = NULL WHERE id = ?").run(claim.id);
|
|
139
|
+
this.db.prepare("UPDATE scene_trigger_intents SET status = 'resolved' WHERE id = ?").run(claim.intentId);
|
|
140
|
+
return true;
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** Configuration edits require a fresh preflight before any execution resumes. */
|
|
144
|
+
configureActivation(principal, id, revision, value) {
|
|
145
|
+
return this.transaction(() => {
|
|
146
|
+
const activation = this.getActivation(principal, id);
|
|
147
|
+
if (activation.revision !== revision || ![
|
|
148
|
+
"active",
|
|
149
|
+
"paused",
|
|
150
|
+
"needs_setup"
|
|
151
|
+
].includes(activation.status)) throw new SceneConflictError("Scene activation changed or cannot be configured");
|
|
152
|
+
const input = activationInputSchema.pick({
|
|
153
|
+
goal: true,
|
|
154
|
+
scope: true,
|
|
155
|
+
permissions: true
|
|
156
|
+
}).parse(value);
|
|
157
|
+
this.db.prepare(`UPDATE scene_activations SET goal = ?, scope_json = ?, permissions_json = ?,
|
|
158
|
+
status = 'needs_setup', revision = revision + 1 WHERE id = ?`).run(input.goal, JSON.stringify(input.scope), JSON.stringify(input.permissions), id);
|
|
159
|
+
this.db.prepare("UPDATE scene_trigger_intents SET status = 'cancelled' WHERE activation_id = ? AND status IN ('pending', 'claimed')").run(id);
|
|
160
|
+
this.db.prepare("UPDATE scene_runs SET status = 'cancelled', reason = 'activation_changed', lease_owner = NULL, lease_until = NULL WHERE activation_id = ? AND status IN ('running', 'retry_wait')").run(id);
|
|
161
|
+
this.db.prepare(`UPDATE scene_work_items SET status = 'paused', revision = revision + 1, last_check_reason = 'activation_changed'
|
|
162
|
+
WHERE activation_id = ? AND status <> 'completed'`).run(id);
|
|
163
|
+
this.db.prepare(`UPDATE scene_presentations SET status = 'withdrawn' WHERE outcome_id IN
|
|
164
|
+
(SELECT o.id FROM scene_outcomes o JOIN scene_runs r ON r.id = o.run_id WHERE r.activation_id = ?)`).run(id);
|
|
165
|
+
return this.getActivation(principal, id);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
writeNotes(principal, id, value, now) {
|
|
169
|
+
const input = sceneNotesSchema.parse(value);
|
|
170
|
+
if (!Number.isSafeInteger(now) || now < 0 || input.validUntil !== null && input.validUntil <= now) throw new Error("Invalid notes validity time");
|
|
171
|
+
return this.transaction(() => {
|
|
172
|
+
const activation = this.getActivation(principal, id);
|
|
173
|
+
if (activation.scope.kind !== "personal" || ![
|
|
174
|
+
"active",
|
|
175
|
+
"paused",
|
|
176
|
+
"needs_setup"
|
|
177
|
+
].includes(activation.status) || !this.getTemplate(activation.templateKey, activation.templateVersion).contextProviders.includes("user_notes")) throw new SceneConflictError("Scene does not accept personal notes");
|
|
178
|
+
const row = this.db.prepare("SELECT revision FROM scene_notes WHERE activation_id = ?").get(id);
|
|
179
|
+
if (Number(row?.revision ?? 0) !== input.expectedRevision) throw new SceneConflictError("Scene notes changed");
|
|
180
|
+
const revision = input.expectedRevision + 1;
|
|
181
|
+
this.db.prepare(`INSERT INTO scene_notes VALUES (?, ?, ?, ?, ?)
|
|
182
|
+
ON CONFLICT(activation_id) DO UPDATE SET content = excluded.content, revision = excluded.revision,
|
|
183
|
+
valid_until = excluded.valid_until, updated_at = excluded.updated_at`).run(id, input.content, revision, input.validUntil, now);
|
|
184
|
+
this.db.prepare(`UPDATE scene_trigger_intents SET status = 'cancelled' WHERE id IN
|
|
185
|
+
(SELECT intent_id FROM scene_runs WHERE activation_id = ? AND status IN ('running', 'retry_wait'))`).run(id);
|
|
186
|
+
this.db.prepare(`UPDATE scene_runs SET status = 'cancelled', reason = 'source_changed', lease_owner = NULL, lease_until = NULL
|
|
187
|
+
WHERE activation_id = ? AND status IN ('running', 'retry_wait')`).run(id);
|
|
188
|
+
this.db.prepare(`UPDATE scene_presentations SET status = 'withdrawn' WHERE outcome_id IN
|
|
189
|
+
(SELECT o.id FROM scene_outcomes o JOIN scene_runs r ON r.id = o.run_id WHERE r.activation_id = ?)`).run(id);
|
|
190
|
+
return revision;
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
readNotes(principal, id) {
|
|
194
|
+
this.getActivation(principal, id);
|
|
195
|
+
return this.db.prepare("SELECT content, revision, valid_until AS validUntil FROM scene_notes WHERE activation_id = ?").get(id) ?? {
|
|
196
|
+
content: "",
|
|
197
|
+
revision: 0,
|
|
198
|
+
validUntil: null
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
listWorkItems(principal, activationId, limit = 50, afterId = "") {
|
|
202
|
+
this.getActivation(principal, activationId);
|
|
203
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("Invalid scene list limit");
|
|
204
|
+
return this.db.prepare(`SELECT id, subject_id AS subjectId, account_id AS accountId, due_at AS dueAt, status, revision,
|
|
205
|
+
last_check_reason AS lastCheckReason FROM scene_work_items WHERE activation_id = ? AND id > ? ORDER BY id LIMIT ?`).all(activationId, afterId, limit);
|
|
206
|
+
}
|
|
207
|
+
listSchedules(principal, activationId) {
|
|
208
|
+
this.getActivation(principal, activationId);
|
|
209
|
+
return this.db.prepare("SELECT * FROM scene_schedule_cursors WHERE activation_id = ? ORDER BY trigger_key").all(activationId).map((row) => ({
|
|
210
|
+
triggerKey: String(row.trigger_key),
|
|
211
|
+
schedule: sceneScheduleSchema.parse(JSON.parse(String(row.schedule_json))),
|
|
212
|
+
revision: Number(row.revision),
|
|
213
|
+
nextDueAt: Number(row.next_due_at)
|
|
214
|
+
}));
|
|
215
|
+
}
|
|
216
|
+
createWorkItem(principal, activationId, input, now) {
|
|
217
|
+
return this.transaction(() => {
|
|
218
|
+
const activation = this.getActivation(principal, activationId);
|
|
219
|
+
if (activation.status !== "active") throw new SceneConflictError("Scene activation is not active");
|
|
220
|
+
if (!input.subjectId.trim() || !activation.permissions.accountIds.includes(input.accountId)) throw new SceneInputError("Work item account is not authorized");
|
|
221
|
+
if (activation.scope.kind !== "objects" || !activation.scope.ids.includes(input.subjectId)) throw new SceneInputError("Mail follow-up requires an explicit object scope");
|
|
222
|
+
if (!this.getTemplate(activation.templateKey, activation.templateVersion).triggers.some((item) => item.type === "schedule")) throw new SceneInputError("Work item template needs a schedule trigger");
|
|
223
|
+
if (!Number.isSafeInteger(input.dueAt) || input.dueAt <= now) throw new SceneInputError("Work item deadline must be in the future");
|
|
224
|
+
if (this.db.prepare("SELECT 1 FROM scene_work_items WHERE activation_id = ? AND account_id = ? AND subject_id = ?").get(activationId, input.accountId, input.subjectId)) throw new SceneConflictError("Scene work item already exists");
|
|
225
|
+
const id = randomUUID();
|
|
226
|
+
this.db.prepare(`INSERT INTO scene_work_items (id, activation_id, subject_id, account_id, due_at, revision, status)
|
|
227
|
+
VALUES (?, ?, ?, ?, ?, 1, 'watching')`).run(id, activationId, input.subjectId, input.accountId, input.dueAt);
|
|
228
|
+
return {
|
|
229
|
+
id,
|
|
230
|
+
activationId,
|
|
231
|
+
...input,
|
|
232
|
+
revision: 1,
|
|
233
|
+
status: "watching"
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/** Internal worker scan; identity comes from persisted ownership, never an event payload. */
|
|
238
|
+
recordModelUsage(claim, usage, now) {
|
|
239
|
+
if (!this.isCurrentClaim(claim, now)) return;
|
|
240
|
+
this.db.prepare(`INSERT OR IGNORE INTO scene_model_usage VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(claim.id, claim.leaseEpoch, usage.provider, usage.model, usage.inputTokens, usage.outputTokens, usage.totalTokens, usage.estimatedCost, now);
|
|
241
|
+
}
|
|
242
|
+
listMailObservationItems(afterId = "", now = Date.now()) {
|
|
243
|
+
return this.db.prepare(`SELECT w.*, a.owner_id, a.workspace_id FROM scene_work_items w
|
|
244
|
+
JOIN scene_activations a ON a.id = w.activation_id
|
|
245
|
+
WHERE a.status = 'active' AND w.status = 'watching' AND w.id > ? AND ${checksAllowed}
|
|
246
|
+
ORDER BY w.id LIMIT 100`).all(afterId, now / 1e3).map((row) => ({
|
|
247
|
+
principal: {
|
|
248
|
+
ownerId: String(row.owner_id),
|
|
249
|
+
workspaceId: String(row.workspace_id)
|
|
250
|
+
},
|
|
251
|
+
item: {
|
|
252
|
+
id: String(row.id),
|
|
253
|
+
activationId: String(row.activation_id),
|
|
254
|
+
subjectId: String(row.subject_id),
|
|
255
|
+
accountId: String(row.account_id),
|
|
256
|
+
dueAt: Number(row.due_at),
|
|
257
|
+
revision: Number(row.revision),
|
|
258
|
+
status: "watching"
|
|
259
|
+
}
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
isMailObservationCurrent(activation, item, now = Date.now()) {
|
|
263
|
+
return Boolean(this.db.prepare(`SELECT 1 FROM scene_work_items w JOIN scene_activations a ON a.id = w.activation_id
|
|
264
|
+
WHERE w.id = ? AND w.activation_id = ? AND w.revision = ? AND w.status = 'watching'
|
|
265
|
+
AND a.owner_id = ? AND a.workspace_id = ? AND a.revision = ? AND a.status = 'active' AND ${checksAllowed}`).get(item.id, activation.id, item.revision, activation.ownerId, activation.workspaceId, activation.revision, now / 1e3));
|
|
266
|
+
}
|
|
267
|
+
observeMailWorkItem(principal, item, fingerprint, now) {
|
|
268
|
+
if (!/^[a-f0-9]{64}$/.test(fingerprint)) throw new Error("Invalid scene observation fingerprint");
|
|
269
|
+
return this.transaction(() => {
|
|
270
|
+
const activation = this.getActivation(principal, item.activationId);
|
|
271
|
+
if (!this.isMailObservationCurrent(activation, item, now)) return false;
|
|
272
|
+
const row = this.db.prepare("SELECT * FROM scene_work_items WHERE id = ? AND activation_id = ?").get(item.id, item.activationId);
|
|
273
|
+
if (!row || row.revision !== item.revision || row.status !== "watching" || activation.status !== "active") return false;
|
|
274
|
+
if (row.subject_id !== item.subjectId || row.account_id !== item.accountId) throw new SceneConflictError("Work item identity changed");
|
|
275
|
+
if (row.observed_fingerprint === fingerprint) return false;
|
|
276
|
+
const trigger = this.getTemplate(activation.templateKey, activation.templateVersion).triggers.find((entry) => entry.type === "event" && entry.eventType === "mail.thread.changed");
|
|
277
|
+
if (!trigger) return false;
|
|
278
|
+
const sequence = Number(row.observation_sequence) + 1;
|
|
279
|
+
this.db.prepare("UPDATE scene_work_items SET observed_fingerprint = ?, observation_sequence = ? WHERE id = ?").run(fingerprint, sequence, item.id);
|
|
280
|
+
if (row.observed_fingerprint === null) return false;
|
|
281
|
+
const occurrenceKey = `mail:${item.id}:${item.revision}:${sequence}`;
|
|
282
|
+
this.acceptTrigger(principal, item.activationId, {
|
|
283
|
+
triggerKey: trigger.id,
|
|
284
|
+
occurrenceKey,
|
|
285
|
+
dueAt: now,
|
|
286
|
+
event: {
|
|
287
|
+
source: `mail:${item.accountId}`,
|
|
288
|
+
sourceEventId: occurrenceKey,
|
|
289
|
+
subjectId: item.subjectId,
|
|
290
|
+
accountId: item.accountId,
|
|
291
|
+
eventType: "mail.thread.changed",
|
|
292
|
+
occurredAt: now
|
|
293
|
+
}
|
|
294
|
+
}, now);
|
|
295
|
+
this.db.prepare(`UPDATE scene_presentations SET status = 'withdrawn' WHERE outcome_id IN (
|
|
296
|
+
SELECT o.id FROM scene_outcomes o JOIN scene_runs r ON r.id = o.run_id
|
|
297
|
+
JOIN scene_trigger_intents i ON i.id = r.intent_id JOIN scene_events e ON e.id = i.event_id
|
|
298
|
+
WHERE i.activation_id = ? AND e.subject_id = ? AND (e.account_id = ? OR e.account_id IS NULL))`).run(item.activationId, item.subjectId, item.accountId);
|
|
299
|
+
return true;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/** Revision changes invalidate only this item's pending work and visible results. */
|
|
303
|
+
updateWorkItem(principal, id, input, now) {
|
|
304
|
+
return this.transaction(() => {
|
|
305
|
+
this.assertPrincipal(principal);
|
|
306
|
+
const row = this.db.prepare(`SELECT w.* FROM scene_work_items w JOIN scene_activations a ON a.id = w.activation_id
|
|
307
|
+
WHERE w.id = ? AND a.owner_id = ? AND a.workspace_id = ?`).get(id, principal.ownerId, principal.workspaceId);
|
|
308
|
+
if (!row) throw new SceneNotFoundError("Scene work item not found");
|
|
309
|
+
if (row.revision !== input.expectedRevision || row.status === "completed") throw new SceneConflictError("Work item changed or completed");
|
|
310
|
+
const status = input.status ?? row.status;
|
|
311
|
+
if (![
|
|
312
|
+
"watching",
|
|
313
|
+
"paused",
|
|
314
|
+
"completed"
|
|
315
|
+
].includes(String(status))) throw new Error("Invalid work item status");
|
|
316
|
+
if (status === "watching") {
|
|
317
|
+
const activation = this.getActivation(principal, String(row.activation_id));
|
|
318
|
+
if (activation.status !== "active" || activation.scope.kind !== "objects" || !activation.scope.ids.includes(String(row.subject_id)) || !activation.permissions.accountIds.includes(String(row.account_id))) throw new SceneConflictError("Work item is outside the active scene scope");
|
|
319
|
+
}
|
|
320
|
+
if (input.dueAt !== void 0 && (!Number.isSafeInteger(input.dueAt) || input.dueAt <= now)) throw new SceneInputError("Work item deadline must be in the future");
|
|
321
|
+
if (input.status === void 0 && input.dueAt === void 0) throw new SceneInputError("Work item update is empty");
|
|
322
|
+
this.db.prepare("UPDATE scene_work_items SET due_at = ?, status = ?, revision = revision + 1, last_check_reason = NULL WHERE id = ?").run(input.dueAt ?? row.due_at, status, id);
|
|
323
|
+
const matchingIntents = `SELECT i.id FROM scene_trigger_intents i JOIN scene_events e ON e.id = i.event_id
|
|
324
|
+
WHERE i.activation_id = ? AND e.subject_id = ? AND (e.account_id = ? OR e.account_id IS NULL)`;
|
|
325
|
+
this.db.prepare(`UPDATE scene_runs SET status = 'cancelled', reason = 'work_item_changed', lease_owner = NULL, lease_until = NULL
|
|
326
|
+
WHERE intent_id IN (${matchingIntents}) AND status IN ('running', 'retry_wait')`).run(row.activation_id, row.subject_id, row.account_id);
|
|
327
|
+
this.db.prepare(`UPDATE scene_trigger_intents SET status = 'cancelled' WHERE id IN (${matchingIntents}) AND status IN ('pending', 'claimed')`).run(row.activation_id, row.subject_id, row.account_id);
|
|
328
|
+
this.db.prepare(`UPDATE scene_presentations SET status = 'withdrawn' WHERE outcome_id IN (
|
|
329
|
+
SELECT o.id FROM scene_outcomes o JOIN scene_runs r ON r.id = o.run_id WHERE r.intent_id IN (${matchingIntents}))`).run(row.activation_id, row.subject_id, row.account_id);
|
|
330
|
+
return {
|
|
331
|
+
id,
|
|
332
|
+
activationId: String(row.activation_id),
|
|
333
|
+
accountId: String(row.account_id),
|
|
334
|
+
subjectId: String(row.subject_id),
|
|
335
|
+
dueAt: input.dueAt ?? Number(row.due_at),
|
|
336
|
+
revision: Number(row.revision) + 1,
|
|
337
|
+
status
|
|
338
|
+
};
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
/** Due dates are durable; restarting cannot generate a second occurrence. */
|
|
342
|
+
enqueueDueWorkItems(now) {
|
|
343
|
+
return this.transaction(() => {
|
|
344
|
+
const rows = this.db.prepare(`SELECT w.*, a.owner_id, a.workspace_id FROM scene_work_items w
|
|
345
|
+
JOIN scene_activations a ON a.id = w.activation_id WHERE w.status = 'watching' AND a.status = 'active'
|
|
346
|
+
AND w.due_at <= ? AND (w.last_triggered_revision IS NULL OR w.last_triggered_revision <> w.revision) AND ${checksAllowed}
|
|
347
|
+
ORDER BY w.due_at, w.id LIMIT 100`).all(now, now / 1e3);
|
|
348
|
+
let accepted = 0;
|
|
349
|
+
for (const row of rows) {
|
|
350
|
+
const activationId = String(row.activation_id);
|
|
351
|
+
const principal = {
|
|
352
|
+
ownerId: String(row.owner_id),
|
|
353
|
+
workspaceId: String(row.workspace_id)
|
|
354
|
+
};
|
|
355
|
+
const activation = this.getActivation(principal, activationId);
|
|
356
|
+
if (now - Number(row.due_at) > 864e5) {
|
|
357
|
+
this.db.prepare("UPDATE scene_work_items SET status = 'paused', last_check_reason = 'deadline_expired' WHERE id = ?").run(row.id);
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const trigger = this.getTemplate(activation.templateKey, activation.templateVersion).triggers.find((item) => item.type === "schedule");
|
|
361
|
+
if (!trigger) throw new Error("Work item template needs a schedule trigger");
|
|
362
|
+
const occurrenceKey = `work-item:${row.id}:${row.revision}`;
|
|
363
|
+
this.acceptTrigger(principal, activationId, {
|
|
364
|
+
triggerKey: trigger.id,
|
|
365
|
+
occurrenceKey,
|
|
366
|
+
dueAt: Number(row.due_at),
|
|
367
|
+
event: {
|
|
368
|
+
source: "schedule",
|
|
369
|
+
sourceEventId: occurrenceKey,
|
|
370
|
+
eventType: "scene.work_item.due",
|
|
371
|
+
subjectId: String(row.subject_id),
|
|
372
|
+
accountId: String(row.account_id),
|
|
373
|
+
occurredAt: Number(row.due_at)
|
|
374
|
+
}
|
|
375
|
+
}, now);
|
|
376
|
+
this.db.prepare("UPDATE scene_work_items SET last_triggered_revision = revision, last_check_reason = 'due' WHERE id = ?").run(row.id);
|
|
377
|
+
accepted += 1;
|
|
378
|
+
}
|
|
379
|
+
return accepted;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
transitionActivation(principal, id, revision, status, now = Date.now()) {
|
|
383
|
+
return this.transaction(() => {
|
|
384
|
+
const activation = this.getActivation(principal, id);
|
|
385
|
+
if (activation.revision !== revision || !canTransitionActivation(activation.status, status)) throw new SceneConflictError("Scene activation changed or transition is invalid");
|
|
386
|
+
this.db.prepare("UPDATE scene_activations SET status = ?, revision = revision + 1 WHERE id = ?").run(status, id);
|
|
387
|
+
this.db.prepare("UPDATE scene_trigger_intents SET status = 'cancelled' WHERE activation_id = ? AND status IN ('pending', 'claimed')").run(id);
|
|
388
|
+
this.db.prepare("UPDATE scene_runs SET status = 'cancelled', reason = 'activation_changed', lease_owner = NULL, lease_until = NULL WHERE activation_id = ? AND status IN ('running', 'retry_wait')").run(id);
|
|
389
|
+
if (status === "active") {
|
|
390
|
+
this.db.prepare("UPDATE scene_work_items SET revision = revision + 1, last_check_reason = NULL WHERE activation_id = ? AND status = 'watching'").run(id);
|
|
391
|
+
const schedules = this.db.prepare("SELECT trigger_key, schedule_json FROM scene_schedule_cursors WHERE activation_id = ?").all(id);
|
|
392
|
+
for (const schedule of schedules) this.db.prepare("UPDATE scene_schedule_cursors SET next_due_at = ?, revision = revision + 1 WHERE activation_id = ? AND trigger_key = ?").run(nextSceneScheduleAt(JSON.parse(String(schedule.schedule_json)), now), id, schedule.trigger_key);
|
|
393
|
+
}
|
|
394
|
+
if (status === "needs_setup" || status === "archived") this.db.prepare(`UPDATE scene_presentations SET status = 'withdrawn' WHERE outcome_id IN
|
|
395
|
+
(SELECT o.id FROM scene_outcomes o JOIN scene_runs r ON r.id = o.run_id WHERE r.activation_id = ?)`).run(id);
|
|
396
|
+
return this.getActivation(principal, id);
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
setSchedule(principal, activationId, triggerKey, expectedRevision, value, now) {
|
|
400
|
+
const schedule = sceneScheduleSchema.parse(value);
|
|
401
|
+
const nextDueAt = nextSceneScheduleAt(schedule, now);
|
|
402
|
+
if (!Number.isSafeInteger(expectedRevision) || expectedRevision < 0) throw new Error("Invalid schedule revision");
|
|
403
|
+
return this.transaction(() => {
|
|
404
|
+
const activation = this.getActivation(principal, activationId);
|
|
405
|
+
if (activation.status !== "active") throw new SceneConflictError("Scene activation is not active");
|
|
406
|
+
if (!this.getTemplate(activation.templateKey, activation.templateVersion).triggers.some((trigger) => trigger.id === triggerKey && trigger.type === "schedule")) throw new SceneInputError("Unknown schedule trigger");
|
|
407
|
+
const previous = this.db.prepare("SELECT revision FROM scene_schedule_cursors WHERE activation_id = ? AND trigger_key = ?").get(activationId, triggerKey);
|
|
408
|
+
if (Number(previous?.revision ?? 0) !== expectedRevision) throw new SceneConflictError("Scene schedule changed");
|
|
409
|
+
const revision = expectedRevision + 1;
|
|
410
|
+
this.db.prepare(`INSERT INTO scene_schedule_cursors VALUES (?, ?, ?, ?, ?)
|
|
411
|
+
ON CONFLICT(activation_id, trigger_key) DO UPDATE SET schedule_json = excluded.schedule_json,
|
|
412
|
+
revision = excluded.revision, next_due_at = excluded.next_due_at`).run(activationId, triggerKey, JSON.stringify(schedule), revision, nextDueAt);
|
|
413
|
+
this.db.prepare(`UPDATE scene_runs SET status = 'cancelled', reason = 'schedule_changed', lease_owner = NULL, lease_until = NULL
|
|
414
|
+
WHERE status IN ('running', 'retry_wait') AND intent_id IN
|
|
415
|
+
(SELECT id FROM scene_trigger_intents WHERE activation_id = ? AND trigger_key = ?)`).run(activationId, triggerKey);
|
|
416
|
+
this.db.prepare("UPDATE scene_trigger_intents SET status = 'cancelled' WHERE activation_id = ? AND trigger_key = ? AND status IN ('pending', 'claimed')").run(activationId, triggerKey);
|
|
417
|
+
return revision;
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
/** Coalesces missed occurrences into at most one bounded catch-up per schedule. */
|
|
421
|
+
enqueueDueSchedules(now) {
|
|
422
|
+
if (!Number.isSafeInteger(now) || now < 0) throw new Error("Invalid scene schedule time");
|
|
423
|
+
return this.transaction(() => {
|
|
424
|
+
const rows = this.db.prepare(`SELECT s.*, a.owner_id, a.workspace_id FROM scene_schedule_cursors s
|
|
425
|
+
JOIN scene_activations a ON a.id = s.activation_id WHERE a.status = 'active' AND s.next_due_at <= ? AND ${checksAllowed}
|
|
426
|
+
ORDER BY s.next_due_at, s.activation_id, s.trigger_key LIMIT 100`).all(now, now / 1e3);
|
|
427
|
+
let published = 0;
|
|
428
|
+
for (const row of rows) {
|
|
429
|
+
const principal = {
|
|
430
|
+
ownerId: String(row.owner_id),
|
|
431
|
+
workspaceId: String(row.workspace_id)
|
|
432
|
+
};
|
|
433
|
+
const activation = this.getActivation(principal, String(row.activation_id));
|
|
434
|
+
if (now - Number(row.next_due_at) <= 864e5) {
|
|
435
|
+
const occurrenceKey = sceneContentHash({
|
|
436
|
+
activationId: activation.id,
|
|
437
|
+
activationRevision: activation.revision,
|
|
438
|
+
triggerKey: row.trigger_key,
|
|
439
|
+
scheduleRevision: row.revision,
|
|
440
|
+
dueAt: row.next_due_at
|
|
441
|
+
});
|
|
442
|
+
this.acceptTrigger(principal, activation.id, {
|
|
443
|
+
triggerKey: String(row.trigger_key),
|
|
444
|
+
occurrenceKey,
|
|
445
|
+
dueAt: now,
|
|
446
|
+
event: {
|
|
447
|
+
source: "scene_schedule",
|
|
448
|
+
sourceEventId: occurrenceKey,
|
|
449
|
+
eventType: "scene.schedule.due",
|
|
450
|
+
subjectId: activation.scope.kind === "objects" ? activation.scope.ids[0] : activation.id,
|
|
451
|
+
occurredAt: Number(row.next_due_at),
|
|
452
|
+
...activation.permissions.accountIds.length === 1 ? { accountId: activation.permissions.accountIds[0] } : {}
|
|
453
|
+
}
|
|
454
|
+
}, now);
|
|
455
|
+
published += 1;
|
|
456
|
+
}
|
|
457
|
+
this.db.prepare("UPDATE scene_schedule_cursors SET next_due_at = ? WHERE activation_id = ? AND trigger_key = ?").run(nextSceneScheduleAt(JSON.parse(String(row.schedule_json)), now), activation.id, row.trigger_key);
|
|
458
|
+
}
|
|
459
|
+
return published;
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
/** Called by an authorized producer inside its domain transaction when applicable. */
|
|
463
|
+
acceptTrigger(principal, activationId, input, now) {
|
|
464
|
+
return this.transaction(() => {
|
|
465
|
+
const activation = this.getActivation(principal, activationId);
|
|
466
|
+
if (activation.status !== "active") throw new SceneConflictError("Scene activation is not active");
|
|
467
|
+
const trigger = this.getTemplate(activation.templateKey, activation.templateVersion).triggers.find((item) => item.id === input.triggerKey);
|
|
468
|
+
if (!trigger || trigger.type === "event" && trigger.eventType !== input.event.eventType) throw new SceneConflictError("Scene trigger does not match");
|
|
469
|
+
if (!input.occurrenceKey || !input.event.source || !input.event.sourceEventId || !input.event.subjectId || !input.event.eventType) throw new Error("Missing scene event identity");
|
|
470
|
+
if (input.event.accountId !== void 0 && !activation.permissions.accountIds.includes(input.event.accountId)) throw new Error("Scene event account is not authorized");
|
|
471
|
+
for (const time of [
|
|
472
|
+
now,
|
|
473
|
+
input.dueAt,
|
|
474
|
+
input.event.occurredAt
|
|
475
|
+
]) if (!Number.isSafeInteger(time) || time < 0) throw new Error("Invalid scene event time");
|
|
476
|
+
this.db.prepare(`INSERT OR IGNORE INTO scene_events VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(randomUUID(), principal.ownerId, principal.workspaceId, input.event.source, input.event.sourceEventId, input.event.eventType, input.event.subjectId, input.event.occurredAt, now, input.event.accountId ?? null);
|
|
477
|
+
const event = this.db.prepare("SELECT * FROM scene_events WHERE owner_id = ? AND workspace_id = ? AND source = ? AND source_event_id = ?").get(principal.ownerId, principal.workspaceId, input.event.source, input.event.sourceEventId);
|
|
478
|
+
if (event.event_type !== input.event.eventType || event.subject_id !== input.event.subjectId || event.account_id !== (input.event.accountId ?? null) || Number(event.occurred_at) !== input.event.occurredAt) throw new SceneConflictError("Event identity was reused with different content");
|
|
479
|
+
const previous = this.db.prepare("SELECT * FROM scene_trigger_intents WHERE activation_id = ? AND trigger_key = ? AND occurrence_key = ?").get(activationId, input.triggerKey, input.occurrenceKey);
|
|
480
|
+
if (previous) {
|
|
481
|
+
if (previous.event_id !== event.id) throw new SceneConflictError("Occurrence identity was reused");
|
|
482
|
+
return String(previous.id);
|
|
483
|
+
}
|
|
484
|
+
const id = randomUUID();
|
|
485
|
+
this.db.prepare(`INSERT INTO scene_trigger_intents VALUES (?, ?, ?, ?, ?, ?, ?, 'pending')`).run(id, activationId, activation.revision, input.triggerKey, input.occurrenceKey, event.id, input.dueAt);
|
|
486
|
+
this.db.prepare("INSERT INTO scene_intent_events VALUES (?, ?, ?)").run(id, event.id, now);
|
|
487
|
+
return id;
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
acceptManualCheck(principal, activationId, triggerKey, requestId, now) {
|
|
491
|
+
return this.transaction(() => {
|
|
492
|
+
const activation = this.getActivation(principal, activationId);
|
|
493
|
+
if (activation.status !== "active") throw new SceneConflictError("Scene activation is not active");
|
|
494
|
+
const occurrenceKey = sceneContentHash({
|
|
495
|
+
activationId,
|
|
496
|
+
revision: activation.revision,
|
|
497
|
+
requestId
|
|
498
|
+
});
|
|
499
|
+
const previous = this.db.prepare("SELECT id FROM scene_trigger_intents WHERE activation_id = ? AND trigger_key = ? AND occurrence_key = ?").get(activationId, triggerKey, occurrenceKey);
|
|
500
|
+
if (previous) return String(previous.id);
|
|
501
|
+
return this.acceptTrigger(principal, activationId, {
|
|
502
|
+
triggerKey,
|
|
503
|
+
occurrenceKey,
|
|
504
|
+
dueAt: now,
|
|
505
|
+
event: {
|
|
506
|
+
source: "manual",
|
|
507
|
+
sourceEventId: occurrenceKey,
|
|
508
|
+
eventType: "scene.manual.check",
|
|
509
|
+
subjectId: activation.scope.kind === "objects" ? activation.scope.ids[0] : activation.id,
|
|
510
|
+
occurredAt: now,
|
|
511
|
+
...activation.permissions.accountIds.length === 1 ? { accountId: activation.permissions.accountIds[0] } : {}
|
|
512
|
+
}
|
|
513
|
+
}, now);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
claimNext(worker, now, leaseMs = 12e4) {
|
|
517
|
+
if (!worker.trim() || !Number.isSafeInteger(now) || now < 0 || !Number.isSafeInteger(leaseMs) || leaseMs < 1) throw new Error("Invalid scene lease");
|
|
518
|
+
return this.transaction(() => {
|
|
519
|
+
this.db.prepare(`UPDATE scene_runs SET status = 'failed', reason = 'attempt_limit', lease_owner = NULL, lease_until = NULL
|
|
520
|
+
WHERE status = 'running' AND lease_until <= ? AND attempt >= 3`).run(now);
|
|
521
|
+
this.db.prepare(`UPDATE scene_trigger_intents SET status = 'resolved' WHERE status = 'claimed'
|
|
522
|
+
AND id IN (SELECT intent_id FROM scene_runs WHERE status = 'failed')`).run();
|
|
523
|
+
const retry = this.db.prepare(`SELECT r.* FROM scene_runs r JOIN scene_activations a ON a.id = r.activation_id
|
|
524
|
+
WHERE a.status = 'active' AND a.revision = r.activation_revision AND ${checksAllowed}
|
|
525
|
+
AND (r.attempt < 3 OR (r.status = 'retry_wait' AND r.reason IN ('source_not_ready', 'daily_budget')))
|
|
526
|
+
AND ((r.status = 'running' AND r.lease_until <= ?) OR (r.status = 'retry_wait' AND r.retry_at <= ?))
|
|
527
|
+
ORDER BY r.created_at, r.id LIMIT 1`).get(now / 1e3, now, now);
|
|
528
|
+
if (retry) {
|
|
529
|
+
this.db.prepare(`UPDATE scene_runs SET status = 'running', attempt = attempt + CASE WHEN status = 'retry_wait' AND reason IN ('source_not_ready', 'daily_budget') THEN 0 ELSE 1 END, lease_epoch = lease_epoch + 1,
|
|
530
|
+
lease_owner = ?, lease_until = ?, retry_at = NULL WHERE id = ?`).run(worker, now + leaseMs, retry.id);
|
|
531
|
+
return this.readClaim(String(retry.id));
|
|
532
|
+
}
|
|
533
|
+
const intent = this.db.prepare(`SELECT i.* FROM scene_trigger_intents i JOIN scene_activations a ON a.id = i.activation_id
|
|
534
|
+
WHERE i.status = 'pending' AND i.due_at <= ? AND a.status = 'active' AND a.revision = i.activation_revision AND ${checksAllowed}
|
|
535
|
+
AND NOT EXISTS (SELECT 1 FROM scene_runs r WHERE r.activation_id = a.id AND r.status IN ('running', 'retry_wait'))
|
|
536
|
+
ORDER BY i.due_at, i.id LIMIT 1`).get(now, now / 1e3);
|
|
537
|
+
if (!intent) return null;
|
|
538
|
+
const id = randomUUID();
|
|
539
|
+
this.db.prepare(`INSERT INTO scene_runs (id, intent_id, activation_id, activation_revision, status, attempt, lease_epoch, lease_owner, lease_until, created_at)
|
|
540
|
+
VALUES (?, ?, ?, ?, 'running', 1, 1, ?, ?, ?)`).run(id, intent.id, intent.activation_id, intent.activation_revision, worker, now + leaseMs, now);
|
|
541
|
+
this.db.prepare("UPDATE scene_trigger_intents SET status = 'claimed' WHERE id = ?").run(intent.id);
|
|
542
|
+
return this.readClaim(id);
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
renewLease(claim, now, leaseMs) {
|
|
546
|
+
if (!Number.isSafeInteger(leaseMs) || leaseMs < 1) throw new Error("Invalid scene lease");
|
|
547
|
+
return this.db.prepare(`UPDATE scene_runs SET lease_until = ? WHERE id = ? AND status = 'running'
|
|
548
|
+
AND lease_owner = ? AND lease_epoch = ? AND lease_until > ?`).run(now + leaseMs, claim.id, claim.leaseOwner, claim.leaseEpoch, now).changes === 1;
|
|
549
|
+
}
|
|
550
|
+
reserveModelCall(claim, now, limits = {
|
|
551
|
+
activation: 30,
|
|
552
|
+
owner: 100
|
|
553
|
+
}) {
|
|
554
|
+
if (![limits.activation, limits.owner].every((limit) => Number.isInteger(limit) && limit > 0)) throw new Error("Invalid model budget");
|
|
555
|
+
return this.transaction(() => {
|
|
556
|
+
if (!this.isCurrentClaim(claim, now)) return false;
|
|
557
|
+
if (this.db.prepare("SELECT 1 FROM scene_model_reservations WHERE run_id = ? AND lease_epoch = ?").get(claim.id, claim.leaseEpoch)) return false;
|
|
558
|
+
const row = this.db.prepare("SELECT owner_id FROM scene_activations WHERE id = ?").get(claim.activationId);
|
|
559
|
+
const start = Math.floor(now / 864e5) * 864e5;
|
|
560
|
+
const count = this.db.prepare(`SELECT count(*) AS owner_count, COALESCE(sum(activation_id = ?), 0) AS activation_count
|
|
561
|
+
FROM scene_model_reservations WHERE owner_id = ? AND reserved_at >= ? AND reserved_at < ?`).get(claim.activationId, row.owner_id, start, start + 864e5);
|
|
562
|
+
if (Number(count.owner_count) >= limits.owner || Number(count.activation_count) >= limits.activation) return false;
|
|
563
|
+
this.db.prepare("INSERT INTO scene_model_reservations VALUES (?, ?, ?, ?, ?)").run(claim.id, claim.leaseEpoch, row.owner_id, claim.activationId, now);
|
|
564
|
+
return true;
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
deferRun(claim, now, reason, retryAt) {
|
|
568
|
+
if (!Number.isSafeInteger(retryAt) || retryAt <= now) throw new Error("Invalid scene retry time");
|
|
569
|
+
return this.transaction(() => {
|
|
570
|
+
if (!this.isCurrentClaim(claim, now)) return "discarded";
|
|
571
|
+
const row = this.db.prepare("SELECT created_at FROM scene_runs WHERE id = ?").get(claim.id);
|
|
572
|
+
if (reason === "source_not_ready" && now - Number(row.created_at) >= 864e5) return this.failRun(claim, now, "source_wait_expired") ? "failed" : "discarded";
|
|
573
|
+
this.db.prepare("UPDATE scene_runs SET status = 'retry_wait', reason = ?, retry_at = ?, lease_owner = NULL, lease_until = NULL WHERE id = ?").run(reason, retryAt, claim.id);
|
|
574
|
+
return "deferred";
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
getRunInput(claim, now) {
|
|
578
|
+
if (!this.isCurrentClaim(claim, now)) return null;
|
|
579
|
+
const row = this.db.prepare(`SELECT a.owner_id, a.workspace_id, e.subject_id, e.account_id, e.event_type, e.occurred_at FROM scene_activations a
|
|
580
|
+
JOIN scene_trigger_intents i ON i.activation_id = a.id JOIN scene_events e ON e.id = i.event_id
|
|
581
|
+
WHERE i.id = ? AND a.id = ?`).get(claim.intentId, claim.activationId);
|
|
582
|
+
return {
|
|
583
|
+
activation: this.getActivation({
|
|
584
|
+
ownerId: String(row.owner_id),
|
|
585
|
+
workspaceId: String(row.workspace_id)
|
|
586
|
+
}, claim.activationId),
|
|
587
|
+
subjectId: String(row.subject_id),
|
|
588
|
+
...row.account_id !== null ? { accountId: String(row.account_id) } : {},
|
|
589
|
+
...["scene.work_item.due", "scene.schedule.due"].includes(String(row.event_type)) ? { notBefore: Number(row.occurred_at) } : {}
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
saveSnapshot(claim, hash, evidenceIds, now) {
|
|
593
|
+
return this.transaction(() => {
|
|
594
|
+
if (!this.isCurrentClaim(claim, now)) return false;
|
|
595
|
+
this.db.prepare("INSERT INTO scene_context_snapshots VALUES (?, ?, ?, ?, ?)").run(claim.id, claim.leaseEpoch, hash, JSON.stringify(evidenceIds), now);
|
|
596
|
+
return true;
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
finishReadOnlyRun(claim, result, now) {
|
|
600
|
+
return this.transaction(() => {
|
|
601
|
+
if (!this.isCurrentClaim(claim, now)) return false;
|
|
602
|
+
const row = this.db.prepare(`SELECT t.manifest_json, a.owner_id, a.workspace_id FROM scene_activations a JOIN scene_template_versions t
|
|
603
|
+
ON a.template_key = t.template_key AND a.template_version = t.version WHERE a.id = ?`).get(claim.activationId);
|
|
604
|
+
const template = sceneTemplateSchema.parse(JSON.parse(String(row.manifest_json)));
|
|
605
|
+
if (![
|
|
606
|
+
"no_change",
|
|
607
|
+
"observation",
|
|
608
|
+
"artifact",
|
|
609
|
+
"decision"
|
|
610
|
+
].includes(result.kind) || !template.allowedOutcomeKinds.includes(result.kind)) throw new Error("Scene result kind is not allowed");
|
|
611
|
+
if (result.kind !== "no_change" && (!result.summary.trim() || result.evidenceIds.length === 0)) throw new Error("Scene result needs summary and evidence");
|
|
612
|
+
const snapshot = this.db.prepare("SELECT content_hash, evidence_ids_json FROM scene_context_snapshots WHERE run_id = ? AND lease_epoch = ?").get(claim.id, claim.leaseEpoch);
|
|
613
|
+
if (snapshot) this.db.prepare(`UPDATE scene_work_items SET observed_fingerprint = ? WHERE activation_id = ? AND status = 'watching'
|
|
614
|
+
AND EXISTS (SELECT 1 FROM scene_trigger_intents i JOIN scene_events e ON e.id = i.event_id
|
|
615
|
+
WHERE i.id = ? AND e.subject_id = scene_work_items.subject_id AND e.account_id = scene_work_items.account_id)`).run(snapshot.content_hash, claim.activationId, claim.intentId);
|
|
616
|
+
const outcomeId = randomUUID();
|
|
617
|
+
this.db.prepare(`UPDATE scene_presentations SET status = 'withdrawn' WHERE outcome_id IN (
|
|
618
|
+
SELECT o.id FROM scene_outcomes o JOIN scene_runs r ON r.id = o.run_id
|
|
619
|
+
JOIN scene_trigger_intents i ON i.id = r.intent_id JOIN scene_events e ON e.id = i.event_id
|
|
620
|
+
JOIN scene_trigger_intents current_intent ON current_intent.id = ?
|
|
621
|
+
JOIN scene_events current_event ON current_event.id = current_intent.event_id
|
|
622
|
+
WHERE r.activation_id = ? AND e.subject_id = current_event.subject_id AND e.account_id IS current_event.account_id
|
|
623
|
+
)`).run(claim.intentId, claim.activationId);
|
|
624
|
+
this.db.prepare("INSERT INTO scene_outcomes VALUES (?, ?, ?, ?, ?)").run(outcomeId, claim.id, result.kind, JSON.stringify(result), now);
|
|
625
|
+
if (result.kind !== "no_change") {
|
|
626
|
+
const presentationId = randomUUID();
|
|
627
|
+
this.db.prepare("INSERT INTO scene_presentations(id, outcome_id, destination, status, created_at) VALUES (?, ?, 'inbox', 'unread', ?)").run(presentationId, outcomeId, now);
|
|
628
|
+
this.db.prepare(`INSERT INTO notification_result_outbox
|
|
629
|
+
(id, owner_id, workspace_id, subject_id, status, attempt, next_attempt_at, created_at, updated_at)
|
|
630
|
+
VALUES (?, ?, ?, ?, 'pending', 0, ?, ?, ?)`).run(presentationId, row.owner_id, row.workspace_id, presentationId, now, now, now);
|
|
631
|
+
}
|
|
632
|
+
const reason = result.kind === "no_change" ? snapshot && JSON.parse(String(snapshot.evidence_ids_json)).length === 0 ? "empty_input" : "no_relevant_change" : null;
|
|
633
|
+
this.db.prepare("UPDATE scene_runs SET status = ?, reason = ?, lease_owner = NULL, lease_until = NULL WHERE id = ?").run(result.kind === "no_change" ? "skipped" : "succeeded", reason, claim.id);
|
|
634
|
+
this.db.prepare("UPDATE scene_trigger_intents SET status = 'resolved' WHERE id = ?").run(claim.intentId);
|
|
635
|
+
return true;
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
listInbox(principal, limit = 50, afterId = "", activationId = null) {
|
|
639
|
+
this.assertPrincipal(principal);
|
|
640
|
+
const now = Date.now();
|
|
641
|
+
if (activationId !== null) this.getActivation(principal, activationId);
|
|
642
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("Invalid scene inbox limit");
|
|
643
|
+
const cursor = afterId ? this.db.prepare(`SELECT p.created_at FROM scene_presentations p
|
|
644
|
+
JOIN scene_outcomes o ON o.id = p.outcome_id JOIN scene_runs r ON r.id = o.run_id
|
|
645
|
+
JOIN scene_activations a ON a.id = r.activation_id WHERE p.id = ? AND a.owner_id = ? AND a.workspace_id = ?
|
|
646
|
+
AND (? IS NULL OR a.id = ?)`).get(afterId, principal.ownerId, principal.workspaceId, activationId, activationId) : void 0;
|
|
647
|
+
if (afterId && !cursor) throw new SceneNotFoundError("Scene inbox cursor not found");
|
|
648
|
+
return this.db.prepare(`SELECT p.id, p.status, o.id AS outcome_id, o.content_json, a.id AS activation_id
|
|
649
|
+
FROM scene_presentations p JOIN scene_outcomes o ON o.id = p.outcome_id JOIN scene_runs r ON r.id = o.run_id
|
|
650
|
+
JOIN scene_activations a ON a.id = r.activation_id WHERE a.owner_id = ? AND a.workspace_id = ?
|
|
651
|
+
AND (? IS NULL OR a.id = ?)
|
|
652
|
+
AND (p.status IN ('unread', 'read') OR (p.status = 'snoozed' AND p.snoozed_until <= ?))
|
|
653
|
+
AND (p.expires_at IS NULL OR p.expires_at > ?) AND p.withdrawn_at IS NULL
|
|
654
|
+
AND (? IS NULL OR p.created_at < ? OR (p.created_at = ? AND p.id < ?))
|
|
655
|
+
ORDER BY p.created_at DESC, p.id DESC LIMIT ?`).all(principal.ownerId, principal.workspaceId, activationId, activationId, now, now, cursor?.created_at ?? null, cursor?.created_at ?? null, cursor?.created_at ?? null, afterId, limit).map((row) => this.getPresentation(principal, String(row.id)));
|
|
656
|
+
}
|
|
657
|
+
getPresentation(principal, id) {
|
|
658
|
+
this.assertPrincipal(principal);
|
|
659
|
+
const row = this.db.prepare(`SELECT p.*, o.id AS outcome_id, o.content_json, r.activation_id, r.intent_id
|
|
660
|
+
FROM scene_presentations p JOIN scene_outcomes o ON o.id = p.outcome_id
|
|
661
|
+
JOIN scene_runs r ON r.id = o.run_id JOIN scene_activations a ON a.id = r.activation_id
|
|
662
|
+
WHERE p.id = ? AND a.owner_id = ? AND a.workspace_id = ? AND p.withdrawn_at IS NULL AND p.status <> 'withdrawn'`).get(id, principal.ownerId, principal.workspaceId);
|
|
663
|
+
if (!row) throw new SceneNotFoundError("Scene presentation not found");
|
|
664
|
+
const now = Date.now();
|
|
665
|
+
const actionable = (row.status === "unread" || row.status === "read" || row.status === "snoozed" && row.snoozed_until !== null && Number(row.snoozed_until) <= now) && (row.expires_at === null || Number(row.expires_at) > now);
|
|
666
|
+
const source = this.db.prepare(`SELECT m.subject, m.sender, m.thread_id FROM scene_trigger_intents i
|
|
667
|
+
JOIN scene_events e ON e.id = i.event_id JOIN scene_mail_sources m ON m.id = e.subject_id
|
|
668
|
+
AND m.owner_id = e.owner_id AND m.workspace_id = e.workspace_id AND m.account_id = e.account_id WHERE i.id = ?`).get(row.intent_id);
|
|
669
|
+
const providers = this.getActivation(principal, String(row.activation_id)).permissions.contextProviders;
|
|
670
|
+
const sources = source ? [{
|
|
671
|
+
kind: "mail",
|
|
672
|
+
title: String(source.subject),
|
|
673
|
+
sender: String(source.sender),
|
|
674
|
+
href: `https://mail.google.com/mail/#all/${encodeURIComponent(String(source.thread_id))}`
|
|
675
|
+
}] : providers.includes("user_notes") ? [{
|
|
676
|
+
kind: "notes",
|
|
677
|
+
title: "",
|
|
678
|
+
sender: "",
|
|
679
|
+
href: `#/scenes/${row.activation_id}`
|
|
680
|
+
}] : [];
|
|
681
|
+
return {
|
|
682
|
+
createdAt: Number(row.created_at),
|
|
683
|
+
sources,
|
|
684
|
+
sourceHealth: this.getSourceHealth(String(row.activation_id)),
|
|
685
|
+
id: String(row.id),
|
|
686
|
+
activationId: String(row.activation_id),
|
|
687
|
+
outcomeId: String(row.outcome_id),
|
|
688
|
+
content: JSON.parse(String(row.content_json)),
|
|
689
|
+
status: String(row.status),
|
|
690
|
+
readOnly: !actionable
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
listDigestResults(principal, digestId, limit = 20, afterId = "") {
|
|
694
|
+
this.assertPrincipal(principal);
|
|
695
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new SceneInputError("Invalid digest page");
|
|
696
|
+
if (!this.db.prepare("SELECT 1 FROM notification_digests WHERE id = ? AND owner_id = ? AND workspace_id = ?").get(digestId, principal.ownerId, principal.workspaceId)) throw new SceneNotFoundError("Scene digest not found");
|
|
697
|
+
const rows = this.db.prepare(`SELECT p.id FROM notification_digest_members m
|
|
698
|
+
JOIN scene_presentations p ON p.id = m.subject_id JOIN scene_outcomes o ON o.id = p.outcome_id
|
|
699
|
+
JOIN scene_runs r ON r.id = o.run_id JOIN scene_activations a ON a.id = r.activation_id
|
|
700
|
+
WHERE m.digest_id = ? AND a.owner_id = ? AND a.workspace_id = ?
|
|
701
|
+
AND p.withdrawn_at IS NULL AND p.status <> 'withdrawn' AND p.id > ? ORDER BY p.id LIMIT ?`).all(digestId, principal.ownerId, principal.workspaceId, afterId, limit + 1);
|
|
702
|
+
return {
|
|
703
|
+
outcomes: rows.slice(0, limit).map((row) => this.getPresentation(principal, String(row.id))),
|
|
704
|
+
nextCursor: rows.length > limit ? String(rows[limit - 1].id) : null
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
failRun(claim, now, reason, retryAt) {
|
|
708
|
+
return this.transaction(() => {
|
|
709
|
+
if (!this.isCurrentClaim(claim, now)) return false;
|
|
710
|
+
const retry = retryAt !== void 0 && claim.attempt < 3;
|
|
711
|
+
if (retry && (!Number.isSafeInteger(retryAt) || retryAt <= now)) throw new Error("Invalid scene retry time");
|
|
712
|
+
this.db.prepare("UPDATE scene_runs SET status = ?, reason = ?, retry_at = ?, lease_owner = NULL, lease_until = NULL WHERE id = ?").run(retry ? "retry_wait" : "failed", reason, retry ? retryAt : null, claim.id);
|
|
713
|
+
if (!retry) this.db.prepare("UPDATE scene_trigger_intents SET status = 'resolved' WHERE id = ?").run(claim.intentId);
|
|
714
|
+
return true;
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
isCurrentClaim(claim, now) {
|
|
718
|
+
return !!this.db.prepare(`SELECT 1 FROM scene_runs r JOIN scene_activations a ON a.id = r.activation_id
|
|
719
|
+
WHERE r.id = ? AND r.intent_id = ? AND r.activation_id = ? AND r.activation_revision = ?
|
|
720
|
+
AND r.status = 'running' AND r.lease_owner = ? AND r.lease_epoch = ? AND r.lease_until > ?
|
|
721
|
+
AND a.status = 'active' AND a.revision = r.activation_revision AND ${checksAllowed}`).get(claim.id, claim.intentId, claim.activationId, claim.activationRevision, claim.leaseOwner, claim.leaseEpoch, now, now / 1e3);
|
|
722
|
+
}
|
|
723
|
+
readClaim(id) {
|
|
724
|
+
const row = this.db.prepare("SELECT * FROM scene_runs WHERE id = ?").get(id);
|
|
725
|
+
return {
|
|
726
|
+
id,
|
|
727
|
+
intentId: String(row.intent_id),
|
|
728
|
+
activationId: String(row.activation_id),
|
|
729
|
+
activationRevision: Number(row.activation_revision),
|
|
730
|
+
leaseOwner: String(row.lease_owner),
|
|
731
|
+
leaseEpoch: Number(row.lease_epoch),
|
|
732
|
+
attempt: Number(row.attempt)
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
assertPrincipal(principal) {
|
|
736
|
+
if (!principal.ownerId?.trim() || !principal.workspaceId?.trim()) throw new Error("Scene principal is required");
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
//#endregion
|
|
740
|
+
export { SceneConflictError, SceneInputError, SceneNotFoundError, SceneRepository };
|