@rudderhq/server 0.4.6-canary.8 → 0.5.0
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/agent-runtimes/http/execute.d.ts.map +1 -1
- package/dist/agent-runtimes/http/execute.js +8 -2
- package/dist/agent-runtimes/http/execute.js.map +1 -1
- package/dist/agent-runtimes/process/execute.d.ts.map +1 -1
- package/dist/agent-runtimes/process/execute.js +3 -1
- package/dist/agent-runtimes/process/execute.js.map +1 -1
- package/dist/agent-runtimes/utils.d.ts +6 -1
- package/dist/agent-runtimes/utils.d.ts.map +1 -1
- package/dist/agent-runtimes/utils.js +1 -1
- package/dist/agent-runtimes/utils.js.map +1 -1
- package/dist/app.d.ts +3 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +17 -8
- package/dist/app.js.map +1 -1
- package/dist/bootstrap/create-http-app.d.ts +6 -1
- package/dist/bootstrap/create-http-app.d.ts.map +1 -1
- package/dist/bootstrap/create-http-app.js +122 -46
- package/dist/bootstrap/create-http-app.js.map +1 -1
- package/dist/bootstrap/plugin-host-runtime.d.ts.map +1 -1
- package/dist/bootstrap/plugin-host-runtime.js +89 -31
- package/dist/bootstrap/plugin-host-runtime.js.map +1 -1
- package/dist/bootstrap/register-api-routes.d.ts +3 -1
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +3 -3
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bootstrap/types.d.ts +1 -0
- package/dist/bootstrap/types.d.ts.map +1 -1
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +351 -174
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +3 -3
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -1
- package/dist/home-paths.d.ts.map +1 -1
- package/dist/home-paths.js +79 -7
- package/dist/home-paths.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +112 -119
- package/dist/index.js.map +1 -1
- package/dist/realtime/live-events-ws.d.ts +4 -20
- package/dist/realtime/live-events-ws.d.ts.map +1 -1
- package/dist/realtime/live-events-ws.js +68 -8
- package/dist/realtime/live-events-ws.js.map +1 -1
- package/dist/routes/access-onboarding.helpers.js +3 -3
- package/dist/routes/access-onboarding.helpers.js.map +1 -1
- package/dist/routes/access.helpers.d.ts.map +1 -1
- package/dist/routes/access.helpers.js +3 -2
- package/dist/routes/access.helpers.js.map +1 -1
- package/dist/routes/access.js +1 -1
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/activity.d.ts.map +1 -1
- package/dist/routes/activity.js +5 -1
- package/dist/routes/activity.js.map +1 -1
- package/dist/routes/agents.management-routes.d.ts.map +1 -1
- package/dist/routes/agents.management-routes.js +15 -15
- package/dist/routes/agents.management-routes.js.map +1 -1
- package/dist/routes/assets.d.ts.map +1 -1
- package/dist/routes/assets.js +9 -2
- package/dist/routes/assets.js.map +1 -1
- package/dist/routes/chat-background-runtime.d.ts +19 -0
- package/dist/routes/chat-background-runtime.d.ts.map +1 -0
- package/dist/routes/chat-background-runtime.js +129 -0
- package/dist/routes/chat-background-runtime.js.map +1 -0
- package/dist/routes/chats.d.ts +2 -1
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +944 -24
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/chats.stream-routes.d.ts.map +1 -1
- package/dist/routes/chats.stream-routes.js +552 -76
- package/dist/routes/chats.stream-routes.js.map +1 -1
- package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
- package/dist/routes/issues.comments-attachments.js +14 -31
- package/dist/routes/issues.comments-attachments.js.map +1 -1
- package/dist/routes/onboarding.js +1 -1
- package/dist/routes/onboarding.js.map +1 -1
- package/dist/routes/orgs.d.ts +2 -1
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +32 -3
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/run-intelligence.d.ts.map +1 -1
- package/dist/routes/run-intelligence.js +109 -25
- package/dist/routes/run-intelligence.js.map +1 -1
- package/dist/runtime/runtime-supervisor.d.ts +21 -0
- package/dist/runtime/runtime-supervisor.d.ts.map +1 -0
- package/dist/runtime/runtime-supervisor.js +66 -0
- package/dist/runtime/runtime-supervisor.js.map +1 -0
- package/dist/services/activity-log.d.ts +4 -1
- package/dist/services/activity-log.d.ts.map +1 -1
- package/dist/services/activity-log.js +12 -4
- package/dist/services/activity-log.js.map +1 -1
- package/dist/services/activity.d.ts +11 -394
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +24 -18
- package/dist/services/activity.js.map +1 -1
- package/dist/services/agents.d.ts +799 -137
- package/dist/services/agents.d.ts.map +1 -1
- package/dist/services/approvals.d.ts +3 -3
- package/dist/services/automation-chat-output.d.ts.map +1 -1
- package/dist/services/automation-chat-output.js +115 -94
- package/dist/services/automation-chat-output.js.map +1 -1
- package/dist/services/budgets.d.ts.map +1 -1
- package/dist/services/budgets.js +38 -36
- package/dist/services/budgets.js.map +1 -1
- package/dist/services/chat-agent-runs.d.ts +3 -1
- package/dist/services/chat-agent-runs.d.ts.map +1 -1
- package/dist/services/chat-agent-runs.js +97 -34
- package/dist/services/chat-agent-runs.js.map +1 -1
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +27 -4
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.js +66 -2
- package/dist/services/chat-assistant.helpers.js.map +1 -1
- package/dist/services/chat-assistant.inline-visuals.test.d.ts +2 -0
- package/dist/services/chat-assistant.inline-visuals.test.d.ts.map +1 -0
- package/dist/services/chat-assistant.inline-visuals.test.js +86 -0
- package/dist/services/chat-assistant.inline-visuals.test.js.map +1 -0
- package/dist/services/chat-assistant.js +115 -22
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chat-generation-locks.d.ts +85 -0
- package/dist/services/chat-generation-locks.d.ts.map +1 -1
- package/dist/services/chat-generation-locks.js +371 -9
- package/dist/services/chat-generation-locks.js.map +1 -1
- package/dist/services/chat-generation-locks.test.d.ts +2 -0
- package/dist/services/chat-generation-locks.test.d.ts.map +1 -0
- package/dist/services/chat-generation-locks.test.js +324 -0
- package/dist/services/chat-generation-locks.test.js.map +1 -0
- package/dist/services/chat-generation-protocol.d.ts +674 -0
- package/dist/services/chat-generation-protocol.d.ts.map +1 -0
- package/dist/services/chat-generation-protocol.js +1571 -0
- package/dist/services/chat-generation-protocol.js.map +1 -0
- package/dist/services/chat-generation-protocol.test.d.ts +2 -0
- package/dist/services/chat-generation-protocol.test.d.ts.map +1 -0
- package/dist/services/chat-generation-protocol.test.js +1156 -0
- package/dist/services/chat-generation-protocol.test.js.map +1 -0
- package/dist/services/chat-title-generation.d.ts +0 -3
- package/dist/services/chat-title-generation.d.ts.map +1 -1
- package/dist/services/chat-title-generation.js +2 -32
- package/dist/services/chat-title-generation.js.map +1 -1
- package/dist/services/chat-work-manifest.d.ts.map +1 -1
- package/dist/services/chat-work-manifest.js +5 -1
- package/dist/services/chat-work-manifest.js.map +1 -1
- package/dist/services/chats.d.ts +1451 -272
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.js +1372 -35
- package/dist/services/chats.js.map +1 -1
- package/dist/services/costs.d.ts +34 -7
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +129 -61
- package/dist/services/costs.js.map +1 -1
- package/dist/services/finance.d.ts +3 -3
- package/dist/services/goals.d.ts +2 -2
- package/dist/services/heartbeat-run-summary.d.ts.map +1 -1
- package/dist/services/heartbeat-run-summary.js +16 -1
- package/dist/services/heartbeat-run-summary.js.map +1 -1
- package/dist/services/heartbeat.d.ts +1 -1
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +1 -1
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/integrations/agent-integrations.d.ts +1 -1
- package/dist/services/integrations/custom-integrations.d.ts +1 -1
- package/dist/services/integrations/feishu/runtime.d.ts.map +1 -1
- package/dist/services/integrations/feishu/runtime.js +16 -0
- package/dist/services/integrations/feishu/runtime.js.map +1 -1
- package/dist/services/issues.comments-attachments.d.ts +111 -0
- package/dist/services/issues.comments-attachments.d.ts.map +1 -1
- package/dist/services/issues.comments-attachments.js +138 -66
- package/dist/services/issues.comments-attachments.js.map +1 -1
- package/dist/services/issues.d.ts +56 -0
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.helpers.d.ts.map +1 -1
- package/dist/services/issues.helpers.js +2 -2
- package/dist/services/issues.helpers.js.map +1 -1
- package/dist/services/issues.js +5 -2
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js +7 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
- package/dist/services/messenger.d.ts.map +1 -1
- package/dist/services/messenger.js +11 -9
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/organization-workspace-browser.d.ts +1 -1
- package/dist/services/organization-workspace-browser.d.ts.map +1 -1
- package/dist/services/organization-workspace-browser.js +49 -5
- package/dist/services/organization-workspace-browser.js.map +1 -1
- package/dist/services/orgs.d.ts.map +1 -1
- package/dist/services/orgs.js +36 -12
- package/dist/services/orgs.js.map +1 -1
- package/dist/services/plugin-dev-watcher.d.ts +19 -2
- package/dist/services/plugin-dev-watcher.d.ts.map +1 -1
- package/dist/services/plugin-dev-watcher.js +45 -11
- package/dist/services/plugin-dev-watcher.js.map +1 -1
- package/dist/services/plugin-registry.d.ts +28 -28
- package/dist/services/projects.d.ts +1 -1
- package/dist/services/run-events.d.ts +20 -0
- package/dist/services/run-events.d.ts.map +1 -0
- package/dist/services/run-events.js +34 -0
- package/dist/services/run-events.js.map +1 -0
- package/dist/services/run-intelligence.d.ts +66 -4
- package/dist/services/run-intelligence.d.ts.map +1 -1
- package/dist/services/run-intelligence.js +391 -42
- package/dist/services/run-intelligence.js.map +1 -1
- package/dist/services/run-log-store.d.ts.map +1 -1
- package/dist/services/run-log-store.js +26 -3
- package/dist/services/run-log-store.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.d.ts +24 -1
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +2 -1
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts +98 -37
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.d.ts +3 -1
- package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.js +394 -219
- package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.js +603 -205
- package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.js +34 -24
- package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.recovery.js +56 -6
- package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.release.d.ts +3 -1
- package/dist/services/runtime-kernel/heartbeat.release.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.release.js +276 -44
- package/dist/services/runtime-kernel/heartbeat.release.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.d.ts +42 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.sessions.js +110 -8
- package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.terminal.d.ts +575 -0
- package/dist/services/runtime-kernel/heartbeat.terminal.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.terminal.js +589 -0
- package/dist/services/runtime-kernel/heartbeat.terminal.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.js +238 -92
- package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
- package/dist/services/runtime-kernel/model-fallback.d.ts.map +1 -1
- package/dist/services/runtime-kernel/model-fallback.js +11 -0
- package/dist/services/runtime-kernel/model-fallback.js.map +1 -1
- package/dist/services/side-chats.d.ts +212 -0
- package/dist/services/side-chats.d.ts.map +1 -0
- package/dist/services/side-chats.js +379 -0
- package/dist/services/side-chats.js.map +1 -0
- package/dist/services/workspace-runtime.services.d.ts +1 -1
- package/dist/services/workspace-web-preview.d.ts +36 -0
- package/dist/services/workspace-web-preview.d.ts.map +1 -0
- package/dist/services/workspace-web-preview.js +566 -0
- package/dist/services/workspace-web-preview.js.map +1 -0
- package/package.json +13 -13
- package/resources/bundled-skills/rudder-docs/SKILL.md +161 -0
- package/resources/bundled-skills/rudder-docs/evals/trigger-evals.json +82 -0
- package/{skills/rudder → resources/bundled-skills/rudder-docs}/references/api-reference.md +17 -1
- package/{skills/rudder → resources/bundled-skills/rudder-docs}/references/cli-reference.md +52 -97
- package/resources/bundled-skills/rudder-docs/references/control-plane-practices.md +231 -0
- package/{skills/rudder → resources/bundled-skills/rudder-docs}/references/organization-skills.md +15 -1
- package/resources/bundled-skills/rudder-docs/references/source-map.md +138 -0
- package/resources/bundled-skills/visualize/SKILL.md +132 -0
- package/resources/bundled-skills/visualize/agents/openai.yaml +7 -0
- package/resources/bundled-skills/visualize/assets/example-chart.html +89 -0
- package/resources/bundled-skills/visualize/references/runtime-contract.md +114 -0
- package/skills/rudder-docs/SKILL.md +161 -0
- package/skills/rudder-docs/evals/trigger-evals.json +82 -0
- package/{resources/bundled-skills/rudder → skills/rudder-docs}/references/api-reference.md +17 -1
- package/{resources/bundled-skills/rudder → skills/rudder-docs}/references/cli-reference.md +52 -97
- package/skills/rudder-docs/references/control-plane-practices.md +231 -0
- package/{resources/bundled-skills/rudder → skills/rudder-docs}/references/organization-skills.md +15 -1
- package/skills/rudder-docs/references/source-map.md +138 -0
- package/skills/visualize/SKILL.md +132 -0
- package/skills/visualize/agents/openai.yaml +7 -0
- package/skills/visualize/assets/example-chart.html +89 -0
- package/skills/visualize/references/runtime-contract.md +114 -0
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-C4e_AuJm.js → abnfDiagram-VRR7QNED-Dpnx79fp.js} +1 -1
- package/ui-dist/assets/{arc-3OG5_qjR.js → arc-Cpvh_rCf.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-CJz3inSs.js → architectureDiagram-ZJ3FMSHR-sDD_mBAI.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-CpBIok39.js → blockDiagram-677ZJIJ3-4O4wWti1.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-BbQrD6Fx.js → c4Diagram-LMCZKHZV-BkGODE4P.js} +1 -1
- package/ui-dist/assets/channel-CM4Myg_6.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-BkoT6fZL.js → chunk-2Q5K7J3B-DsDQQpID.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-DecBtqTH.js → chunk-32BRIVSS-Bzx_VnIw.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-SFFAgh5q.js → chunk-5VM5RSS4-D4LoPCni.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-CxUndf0S.js → chunk-EX3LRPZG-BO0R-mS4.js} +3 -3
- package/ui-dist/assets/{chunk-JWPE2WC7-BbEXuDLm.js → chunk-JWPE2WC7-B4Nr8KRL.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-MOCGQpRH.js → chunk-MOJQB5TN--uaDDrYM.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-CUuwDB4L.js → chunk-RYQCIY6F-CyBY2w_j.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-D5H80ZpL.js → chunk-V7JOEXUC-BMid_x8S.js} +3 -3
- package/ui-dist/assets/{chunk-VR4S4FIN-Cg5d82yb.js → chunk-VR4S4FIN-DVqdPKuG.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-Bu7DXS_3.js → chunk-XXDRQBXY-vy03lLSS.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CtlIEzTt.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CtlIEzTt.js +1 -0
- package/ui-dist/assets/{cose-bilkent-JH36ORCC-LCobsJkA.js → cose-bilkent-JH36ORCC-jQDZmrG3.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-CxDfvhGJ.js → cynefin-VYW2F7L2-BxBj6aea.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-BbebJ96a.js → cynefinDiagram-TSTJHNR4-BuUubF5I.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-BNQ0fvpl.js → dagre-VKFMJZFB-B8HipJ3a.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-B9vCEyLD.js → diagram-FQU43EPY-r7gkbKSt.js} +2 -2
- package/ui-dist/assets/{diagram-G47NLZAW-Z7Hg_sXF.js → diagram-G47NLZAW-CxClRsxD.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-B_TZ5-gD.js → diagram-NH7WQ7WH-C8HYTLlq.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-qe-twEnv.js → diagram-OA4YK3LP-CjwqqLKz.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-D8ufoJVE.js → diagram-WEI45ONY-2IWfCqZb.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-BXXAFqU6.js → ebnfDiagram-CCIWWBDH-DVGZkgjy.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-uXmIG2c_.js → erDiagram-Q63AITRT-C_-u3Gmw.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-3eiT6gWx.js → flowDiagram-23GEKE2U-BvGg0NCq.js} +4 -4
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-CiVmqV02.js → ganttDiagram-NO4QXBWP-U3CZUUUR.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-DmIrlFHc.js → gitGraphDiagram-IHSO6WYX-CIWuEBIo.js} +1 -1
- package/ui-dist/assets/{graph-BpS6eOIF.js → graph-D3P5q-7m.js} +1 -1
- package/ui-dist/assets/index-B1rLBtZ1.js +1875 -0
- package/ui-dist/assets/{index-Dlx6uy7_.js → index-BAMXpiI5.js} +1 -1
- package/ui-dist/assets/{index-CUHGsSRd.js → index-BAgtKYR1.js} +1 -1
- package/ui-dist/assets/{index--YRaLrzl.js → index-CBOr9F6w.js} +1 -1
- package/ui-dist/assets/{index-cHC60zXl.js → index-CbHqnWUe.js} +1 -1
- package/ui-dist/assets/{index-DeWKDYpk.js → index-CjztBLJi.js} +1 -1
- package/ui-dist/assets/{index-T2PpfhXU.js → index-Cu1iMhjl.js} +1 -1
- package/ui-dist/assets/{index-Dgj1-Gzi.js → index-D7ycf1qU.js} +1 -1
- package/ui-dist/assets/{index-Dpvu2aoc.js → index-DAYlwxsg.js} +1 -1
- package/ui-dist/assets/{index-BAU4CU-l.js → index-DCFf9dps.js} +4 -4
- package/ui-dist/assets/{index-CWaZ_rS8.js → index-DOPIV8vd.js} +1 -1
- package/ui-dist/assets/{index-C-TZf854.js → index-DRxLJD40.js} +1 -1
- package/ui-dist/assets/{index-CUI9Uyvz.js → index-DYMtCDKT.js} +1 -1
- package/ui-dist/assets/index-DekHKOsL.css +1 -0
- package/ui-dist/assets/{index-BncN7e-4.js → index-DidCCw78.js} +1 -1
- package/ui-dist/assets/{index-Cl27pPic.js → index-Ds_qma4-.js} +1 -1
- package/ui-dist/assets/{index-CF3Fjm4r.js → index-HkQRx1D6.js} +1 -1
- package/ui-dist/assets/{index-Cp12taja.js → index-ikpL--zj.js} +1 -1
- package/ui-dist/assets/{index-BzDB5XYN.js → index-mnl55Stn.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-B_e34a6A.js → infoDiagram-FWYZ7A6U-Cx8Fa-I4.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-C_h8nqRS.js → ishikawaDiagram-FXEZZL3T-Cmuz1QUN.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-CrxAvEMf.js → journeyDiagram-5HDEW3XC-CJgkiVXG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-Zp6LAKss.js → kanban-definition-HUTT4EX6-RAWpefOB.js} +1 -1
- package/ui-dist/assets/layout-DyWQv0Rr.js +1 -0
- package/ui-dist/assets/{linear-_N4Odhfh.js → linear-D5pdgIf9.js} +1 -1
- package/ui-dist/assets/{map-CYGuJtD3.js → map-bVSzNLl2.js} +1 -1
- package/ui-dist/assets/mermaid.core-CeaXGD6B.js +312 -0
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-C-J9N_o_.js → mindmap-definition-LN4V7U3C-B8B8AX5C.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-B9Mlsk91.js → pegDiagram-2B236MQR-C4Sv-ua6.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-BY7DU2f8.js → pieDiagram-ENE6RG2P-CLARlOHx.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CdCl_yc0.js → quadrantDiagram-ABIIQ3AL-DPCXQmeT.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-C5w2LKAD.js → railroadDiagram-RFXS5EU6-BpqRuKUQ.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-fRb_n-yH.js → requirementDiagram-TGXJPOKE-Dd-h62HZ.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-CYjb1HUH.js → sankeyDiagram-HTMAVEWB-DylT_pqq.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CS95ySsI.js → sequenceDiagram-DBY2YBRQ-RiCF1REj.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-DPXTC0TI.js → sizeCapture-X5ZJPWSS-DXW4JQz2.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-C4VFIMBR.js → stateDiagram-2N3HPSRC-6U3ESIGT.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KQTrcZvp.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-UvxkQ8RD.js → swimlanes-5IMT3BWC-CeNHVNCi.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CcxS1c6r.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-BKotSwXW.js → timeline-definition-FHXFAJF6-Vlwoe_1b.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-TmXc-aFs.js → vennDiagram-L72KCM5P-DBP7D3HE.js} +6 -6
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-aTINQKt9.js → wardleyDiagram-EHGQE667-DG-wzqpy.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-CZ86hWNQ.js → xychartDiagram-FW5EYKEG-BAq3mbha.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/resources/bundled-skills/rudder/SKILL.md +0 -314
- package/skills/rudder/SKILL.md +0 -314
- package/ui-dist/assets/channel-roHkdL27.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-CKZLREY1.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-CKZLREY1.js +0 -1
- package/ui-dist/assets/index-BTW19O7e.js +0 -1808
- package/ui-dist/assets/index-DOlmbVZk.css +0 -1
- package/ui-dist/assets/layout-DlHAdVX4.js +0 -1
- package/ui-dist/assets/mermaid.core-B8dXcSSK.js +0 -314
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-CF5IY4p0.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BVLEg2fG.js +0 -8
|
@@ -0,0 +1,1571 @@
|
|
|
1
|
+
import { chatControlActions, chatGenerationEvents, chatGenerations, chatGenerationTerminalOutbox, chatMessages, chatQueuedMessages, } from "@rudderhq/db";
|
|
2
|
+
import { and, asc, desc, eq, inArray, isNotNull, isNull, lte, or, sql } from "drizzle-orm";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
5
|
+
import { withPersistedTranscript } from "./chats.helpers.js";
|
|
6
|
+
import { normalizeLocalLibraryPathMarkdown } from "./library-path-markdown.js";
|
|
7
|
+
const OUTPUT_ADMITTING_GENERATION_STATUSES = [
|
|
8
|
+
"starting",
|
|
9
|
+
"active",
|
|
10
|
+
"running",
|
|
11
|
+
"tool_busy",
|
|
12
|
+
"closing",
|
|
13
|
+
];
|
|
14
|
+
const CONTROL_ACTIVE_GENERATION_STATUSES = [
|
|
15
|
+
...OUTPUT_ADMITTING_GENERATION_STATUSES,
|
|
16
|
+
];
|
|
17
|
+
const RECOVERABLE_CONTROL_GENERATION_STATUSES = [
|
|
18
|
+
...CONTROL_ACTIVE_GENERATION_STATUSES,
|
|
19
|
+
"stop_requested",
|
|
20
|
+
"stopping",
|
|
21
|
+
];
|
|
22
|
+
const PROJECTABLE_TERMINAL_STATUSES = [
|
|
23
|
+
"completed",
|
|
24
|
+
"failed",
|
|
25
|
+
"stopped",
|
|
26
|
+
"aborted",
|
|
27
|
+
"interrupted_unverified",
|
|
28
|
+
"control_lost",
|
|
29
|
+
];
|
|
30
|
+
const EMPTY_BODY_SHA256 = createHash("sha256").update("").digest("hex");
|
|
31
|
+
const SHA256_PATTERN = /^[a-f0-9]{64}$/i;
|
|
32
|
+
export function hashChatGenerationBody(body) {
|
|
33
|
+
return createHash("sha256").update(body).digest("hex");
|
|
34
|
+
}
|
|
35
|
+
function normalizeBodyHash(value) {
|
|
36
|
+
if (!SHA256_PATTERN.test(value)) {
|
|
37
|
+
throw unprocessable("Chat generation body hash must be a SHA-256 hex digest");
|
|
38
|
+
}
|
|
39
|
+
return value.toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
function assertGenerationFence(generation, input) {
|
|
42
|
+
if (input.conversationId && generation.conversationId !== input.conversationId) {
|
|
43
|
+
throw notFound("Chat generation not found");
|
|
44
|
+
}
|
|
45
|
+
if (generation.attemptEpoch !== input.expectedAttemptEpoch) {
|
|
46
|
+
throw conflict("Chat generation runtime attempt changed");
|
|
47
|
+
}
|
|
48
|
+
if (input.expectedOwnerToken !== undefined
|
|
49
|
+
&& generation.controlOwnerToken !== input.expectedOwnerToken) {
|
|
50
|
+
throw conflict("Chat generation control owner changed");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function lockGeneration(tx, input) {
|
|
54
|
+
await tx.execute(sql `
|
|
55
|
+
select ${chatGenerations.id}
|
|
56
|
+
from ${chatGenerations}
|
|
57
|
+
where ${chatGenerations.id} = ${input.generationId}
|
|
58
|
+
and ${chatGenerations.orgId} = ${input.orgId}
|
|
59
|
+
for update
|
|
60
|
+
`);
|
|
61
|
+
const generation = await tx
|
|
62
|
+
.select()
|
|
63
|
+
.from(chatGenerations)
|
|
64
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.orgId, input.orgId)))
|
|
65
|
+
.limit(1)
|
|
66
|
+
.then((rows) => rows[0] ?? null);
|
|
67
|
+
if (!generation || (input.conversationId && generation.conversationId !== input.conversationId)) {
|
|
68
|
+
throw notFound("Chat generation not found");
|
|
69
|
+
}
|
|
70
|
+
return generation;
|
|
71
|
+
}
|
|
72
|
+
async function nextGenerationSeq(tx, generationId) {
|
|
73
|
+
return tx
|
|
74
|
+
.select({ value: sql `coalesce(max(${chatGenerationEvents.generationSeq}), 0) + 1` })
|
|
75
|
+
.from(chatGenerationEvents)
|
|
76
|
+
.where(eq(chatGenerationEvents.generationId, generationId))
|
|
77
|
+
.then((rows) => Number(rows[0]?.value ?? 1));
|
|
78
|
+
}
|
|
79
|
+
async function appendEventLocked(tx, input) {
|
|
80
|
+
const generationSeq = await nextGenerationSeq(tx, input.generationId);
|
|
81
|
+
const [event] = await tx
|
|
82
|
+
.insert(chatGenerationEvents)
|
|
83
|
+
.values({
|
|
84
|
+
orgId: input.orgId,
|
|
85
|
+
generationId: input.generationId,
|
|
86
|
+
generationSeq,
|
|
87
|
+
attemptEpoch: input.attemptEpoch,
|
|
88
|
+
eventKind: input.eventKind,
|
|
89
|
+
payload: input.payload ?? {},
|
|
90
|
+
bodyOffset: input.bodyOffset ?? null,
|
|
91
|
+
bodyLength: input.bodyLength ?? null,
|
|
92
|
+
assistantMessageId: input.assistantMessageId ?? null,
|
|
93
|
+
runId: input.runId ?? null,
|
|
94
|
+
controlActionId: input.controlActionId ?? null,
|
|
95
|
+
queueItemId: input.queueItemId ?? null,
|
|
96
|
+
emittedAt: input.emittedAt ?? null,
|
|
97
|
+
})
|
|
98
|
+
.returning();
|
|
99
|
+
if (!event)
|
|
100
|
+
throw new Error("Failed to append chat generation event");
|
|
101
|
+
return event;
|
|
102
|
+
}
|
|
103
|
+
async function bodyHashAtSeq(tx, generationId, generationSeq) {
|
|
104
|
+
if (generationSeq === 0)
|
|
105
|
+
return EMPTY_BODY_SHA256;
|
|
106
|
+
const event = await tx
|
|
107
|
+
.select({ payload: chatGenerationEvents.payload })
|
|
108
|
+
.from(chatGenerationEvents)
|
|
109
|
+
.where(and(eq(chatGenerationEvents.generationId, generationId), eq(chatGenerationEvents.generationSeq, generationSeq)))
|
|
110
|
+
.limit(1)
|
|
111
|
+
.then((rows) => rows[0] ?? null);
|
|
112
|
+
const value = event?.payload?.bodyHash;
|
|
113
|
+
return typeof value === "string" && SHA256_PATTERN.test(value)
|
|
114
|
+
? value.toLowerCase()
|
|
115
|
+
: null;
|
|
116
|
+
}
|
|
117
|
+
async function runtimeOutputAdmission(tx, generationId) {
|
|
118
|
+
return tx
|
|
119
|
+
.select()
|
|
120
|
+
.from(chatGenerationEvents)
|
|
121
|
+
.where(and(eq(chatGenerationEvents.generationId, generationId), eq(chatGenerationEvents.eventKind, "runtime_output")))
|
|
122
|
+
.orderBy(asc(chatGenerationEvents.generationSeq))
|
|
123
|
+
.limit(1)
|
|
124
|
+
.then((rows) => rows[0] ?? null);
|
|
125
|
+
}
|
|
126
|
+
async function latestVisibleBodyCheckpoint(tx, generationId) {
|
|
127
|
+
const events = await tx
|
|
128
|
+
.select({
|
|
129
|
+
generationSeq: chatGenerationEvents.generationSeq,
|
|
130
|
+
payload: chatGenerationEvents.payload,
|
|
131
|
+
})
|
|
132
|
+
.from(chatGenerationEvents)
|
|
133
|
+
.where(eq(chatGenerationEvents.generationId, generationId))
|
|
134
|
+
.orderBy(desc(chatGenerationEvents.generationSeq))
|
|
135
|
+
.limit(100);
|
|
136
|
+
for (const event of events) {
|
|
137
|
+
const bodyHash = event.payload?.bodyHash;
|
|
138
|
+
if (typeof bodyHash === "string" && SHA256_PATTERN.test(bodyHash)) {
|
|
139
|
+
return { generationSeq: event.generationSeq, bodyHash: bodyHash.toLowerCase() };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { generationSeq: 0, bodyHash: EMPTY_BODY_SHA256 };
|
|
143
|
+
}
|
|
144
|
+
async function visibleGenerationProjectionThrough(tx, generationId, generationSeq) {
|
|
145
|
+
const events = generationSeq <= 0
|
|
146
|
+
? []
|
|
147
|
+
: await tx
|
|
148
|
+
.select()
|
|
149
|
+
.from(chatGenerationEvents)
|
|
150
|
+
.where(and(eq(chatGenerationEvents.generationId, generationId), lte(chatGenerationEvents.generationSeq, generationSeq)))
|
|
151
|
+
.orderBy(asc(chatGenerationEvents.generationSeq));
|
|
152
|
+
let body = "";
|
|
153
|
+
const transcript = [];
|
|
154
|
+
let assistantMessageId = null;
|
|
155
|
+
let runId = null;
|
|
156
|
+
for (const event of events) {
|
|
157
|
+
if (event.assistantMessageId)
|
|
158
|
+
assistantMessageId = event.assistantMessageId;
|
|
159
|
+
if (event.runId)
|
|
160
|
+
runId = event.runId;
|
|
161
|
+
if (event.eventKind === "assistant_delta" && typeof event.payload.delta === "string") {
|
|
162
|
+
body += event.payload.delta;
|
|
163
|
+
}
|
|
164
|
+
else if (event.eventKind === "runtime_output" && typeof event.payload.body === "string") {
|
|
165
|
+
body = event.payload.body;
|
|
166
|
+
}
|
|
167
|
+
else if (event.eventKind === "transcript"
|
|
168
|
+
&& event.payload.entry
|
|
169
|
+
&& typeof event.payload.entry === "object"
|
|
170
|
+
&& !Array.isArray(event.payload.entry)) {
|
|
171
|
+
transcript.push(event.payload.entry);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { body, transcript, assistantMessageId, runId };
|
|
175
|
+
}
|
|
176
|
+
async function freezeAssistantMessageProjection(tx, generation, acceptedThroughSeq) {
|
|
177
|
+
const projection = await visibleGenerationProjectionThrough(tx, generation.id, acceptedThroughSeq);
|
|
178
|
+
if (!projection.assistantMessageId)
|
|
179
|
+
return projection;
|
|
180
|
+
const existing = await tx
|
|
181
|
+
.select()
|
|
182
|
+
.from(chatMessages)
|
|
183
|
+
.where(and(eq(chatMessages.id, projection.assistantMessageId), eq(chatMessages.orgId, generation.orgId), eq(chatMessages.conversationId, generation.conversationId), eq(chatMessages.role, "assistant")))
|
|
184
|
+
.limit(1)
|
|
185
|
+
.then((rows) => rows[0] ?? null);
|
|
186
|
+
if (!existing)
|
|
187
|
+
return projection;
|
|
188
|
+
const durableBody = await normalizeLocalLibraryPathMarkdown(projection.body, generation.orgId);
|
|
189
|
+
await tx
|
|
190
|
+
.update(chatMessages)
|
|
191
|
+
.set({
|
|
192
|
+
status: "stopped",
|
|
193
|
+
body: durableBody,
|
|
194
|
+
structuredPayload: withPersistedTranscript(existing.structuredPayload, projection.transcript),
|
|
195
|
+
...(projection.runId ? { runId: projection.runId } : {}),
|
|
196
|
+
updatedAt: new Date(),
|
|
197
|
+
})
|
|
198
|
+
.where(eq(chatMessages.id, existing.id));
|
|
199
|
+
return projection;
|
|
200
|
+
}
|
|
201
|
+
function terminalStatusFromPayload(payload) {
|
|
202
|
+
const status = payload.finalStatus;
|
|
203
|
+
return typeof status === "string"
|
|
204
|
+
&& PROJECTABLE_TERMINAL_STATUSES.includes(status)
|
|
205
|
+
? status
|
|
206
|
+
: null;
|
|
207
|
+
}
|
|
208
|
+
export function chatGenerationProtocolService(db) {
|
|
209
|
+
async function getLatestVisibleCheckpoint(input) {
|
|
210
|
+
const generation = await db
|
|
211
|
+
.select()
|
|
212
|
+
.from(chatGenerations)
|
|
213
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId)))
|
|
214
|
+
.limit(1)
|
|
215
|
+
.then((rows) => rows[0] ?? null);
|
|
216
|
+
if (!generation)
|
|
217
|
+
throw notFound("Chat generation not found");
|
|
218
|
+
const events = await db
|
|
219
|
+
.select({
|
|
220
|
+
generationSeq: chatGenerationEvents.generationSeq,
|
|
221
|
+
payload: chatGenerationEvents.payload,
|
|
222
|
+
})
|
|
223
|
+
.from(chatGenerationEvents)
|
|
224
|
+
.where(eq(chatGenerationEvents.generationId, input.generationId))
|
|
225
|
+
.orderBy(desc(chatGenerationEvents.generationSeq))
|
|
226
|
+
.limit(100);
|
|
227
|
+
for (const event of events) {
|
|
228
|
+
const bodyHash = event.payload?.bodyHash;
|
|
229
|
+
if (typeof bodyHash === "string" && SHA256_PATTERN.test(bodyHash)) {
|
|
230
|
+
return {
|
|
231
|
+
generation,
|
|
232
|
+
generationSeq: event.generationSeq,
|
|
233
|
+
bodyHash: bodyHash.toLowerCase(),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { generation, generationSeq: 0, bodyHash: EMPTY_BODY_SHA256 };
|
|
238
|
+
}
|
|
239
|
+
async function appendGenerationEvent(input) {
|
|
240
|
+
return db.transaction(async (tx) => {
|
|
241
|
+
const generation = await lockGeneration(tx, input);
|
|
242
|
+
assertGenerationFence(generation, {
|
|
243
|
+
conversationId: input.conversationId,
|
|
244
|
+
expectedAttemptEpoch: input.attemptEpoch,
|
|
245
|
+
expectedOwnerToken: input.expectedOwnerToken,
|
|
246
|
+
});
|
|
247
|
+
if (input.admission === "visible") {
|
|
248
|
+
if (await runtimeOutputAdmission(tx, generation.id)
|
|
249
|
+
|| !OUTPUT_ADMITTING_GENERATION_STATUSES.includes(generation.status)) {
|
|
250
|
+
throw conflict("Chat-visible output admission is closed for this generation");
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const payload = { ...(input.payload ?? {}) };
|
|
254
|
+
if (input.admission === "visible") {
|
|
255
|
+
if (!input.bodyHash) {
|
|
256
|
+
throw unprocessable("Visible chat generation events require the projected body hash");
|
|
257
|
+
}
|
|
258
|
+
const bodyHash = normalizeBodyHash(input.bodyHash);
|
|
259
|
+
if (typeof payload.bodyHash === "string" && payload.bodyHash.toLowerCase() !== bodyHash) {
|
|
260
|
+
throw conflict("Chat generation event body hash disagrees with its payload");
|
|
261
|
+
}
|
|
262
|
+
payload.bodyHash = bodyHash;
|
|
263
|
+
}
|
|
264
|
+
const event = await appendEventLocked(tx, { ...input, payload });
|
|
265
|
+
let updatedGeneration = generation;
|
|
266
|
+
if (input.admission === "diagnostic") {
|
|
267
|
+
const [updated] = await tx
|
|
268
|
+
.update(chatGenerations)
|
|
269
|
+
.set({
|
|
270
|
+
lateEventsDropped: sql `${chatGenerations.lateEventsDropped} + 1`,
|
|
271
|
+
lateBytes: sql `${chatGenerations.lateBytes} + ${Math.max(0, input.lateBytes ?? 0)}`,
|
|
272
|
+
updatedAt: new Date(),
|
|
273
|
+
})
|
|
274
|
+
.where(eq(chatGenerations.id, generation.id))
|
|
275
|
+
.returning();
|
|
276
|
+
updatedGeneration = updated ?? generation;
|
|
277
|
+
}
|
|
278
|
+
return { event, generation: updatedGeneration };
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
async function appendVisibleEventAndProject(input) {
|
|
282
|
+
const durableBody = await normalizeLocalLibraryPathMarkdown(input.body, input.orgId);
|
|
283
|
+
const bodyHash = normalizeBodyHash(input.bodyHash);
|
|
284
|
+
return db.transaction(async (tx) => {
|
|
285
|
+
const generation = await lockGeneration(tx, {
|
|
286
|
+
orgId: input.orgId,
|
|
287
|
+
conversationId: input.conversationId,
|
|
288
|
+
generationId: input.generationId,
|
|
289
|
+
});
|
|
290
|
+
assertGenerationFence(generation, {
|
|
291
|
+
conversationId: input.conversationId,
|
|
292
|
+
expectedAttemptEpoch: input.expectedAttemptEpoch,
|
|
293
|
+
expectedOwnerToken: input.expectedOwnerToken,
|
|
294
|
+
});
|
|
295
|
+
if (await runtimeOutputAdmission(tx, generation.id)) {
|
|
296
|
+
throw conflict("Chat-visible output admission is closed for this generation");
|
|
297
|
+
}
|
|
298
|
+
if (!OUTPUT_ADMITTING_GENERATION_STATUSES.includes(generation.status)) {
|
|
299
|
+
throw conflict("Chat-visible output admission is closed for this generation");
|
|
300
|
+
}
|
|
301
|
+
const payload = { ...(input.payload ?? {}) };
|
|
302
|
+
if (typeof payload.bodyHash === "string" && payload.bodyHash.toLowerCase() !== bodyHash) {
|
|
303
|
+
throw conflict("Chat generation event body hash disagrees with its payload");
|
|
304
|
+
}
|
|
305
|
+
payload.bodyHash = bodyHash;
|
|
306
|
+
const existing = input.messageId ? await tx
|
|
307
|
+
.select()
|
|
308
|
+
.from(chatMessages)
|
|
309
|
+
.where(and(eq(chatMessages.id, input.messageId), eq(chatMessages.orgId, input.orgId), eq(chatMessages.conversationId, input.conversationId)))
|
|
310
|
+
.limit(1)
|
|
311
|
+
.then((rows) => rows[0] ?? null) : null;
|
|
312
|
+
if (input.messageId && !existing) {
|
|
313
|
+
throw conflict("Visible generation message projection no longer exists");
|
|
314
|
+
}
|
|
315
|
+
const structuredPayload = withPersistedTranscript(existing?.structuredPayload ?? null, input.transcript);
|
|
316
|
+
const [message] = existing
|
|
317
|
+
? await tx
|
|
318
|
+
.update(chatMessages)
|
|
319
|
+
.set({
|
|
320
|
+
status: "streaming",
|
|
321
|
+
body: durableBody,
|
|
322
|
+
structuredPayload,
|
|
323
|
+
...(input.runId !== undefined ? { runId: input.runId } : {}),
|
|
324
|
+
...(input.replyingAgentId !== undefined ? { replyingAgentId: input.replyingAgentId } : {}),
|
|
325
|
+
updatedAt: new Date(),
|
|
326
|
+
})
|
|
327
|
+
.where(eq(chatMessages.id, existing.id))
|
|
328
|
+
.returning()
|
|
329
|
+
: await tx
|
|
330
|
+
.insert(chatMessages)
|
|
331
|
+
.values({
|
|
332
|
+
orgId: input.orgId,
|
|
333
|
+
conversationId: input.conversationId,
|
|
334
|
+
role: "assistant",
|
|
335
|
+
kind: "message",
|
|
336
|
+
status: "streaming",
|
|
337
|
+
body: durableBody,
|
|
338
|
+
structuredPayload,
|
|
339
|
+
runId: input.runId ?? null,
|
|
340
|
+
replyingAgentId: input.replyingAgentId ?? null,
|
|
341
|
+
chatTurnId: input.chatTurnId,
|
|
342
|
+
turnVariant: input.turnVariant,
|
|
343
|
+
})
|
|
344
|
+
.returning();
|
|
345
|
+
if (!message)
|
|
346
|
+
throw new Error("Failed to project visible chat generation event");
|
|
347
|
+
const event = await appendEventLocked(tx, {
|
|
348
|
+
...input,
|
|
349
|
+
attemptEpoch: input.expectedAttemptEpoch,
|
|
350
|
+
payload,
|
|
351
|
+
assistantMessageId: message.id,
|
|
352
|
+
});
|
|
353
|
+
return { generation, message, event };
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
async function getFrozenVisibleProjection(input) {
|
|
357
|
+
return db.transaction(async (tx) => {
|
|
358
|
+
const generation = await tx
|
|
359
|
+
.select()
|
|
360
|
+
.from(chatGenerations)
|
|
361
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId)))
|
|
362
|
+
.limit(1)
|
|
363
|
+
.then((rows) => rows[0] ?? null);
|
|
364
|
+
if (!generation)
|
|
365
|
+
throw notFound("Chat generation not found");
|
|
366
|
+
const checkpoint = generation.acceptedThroughSeq === null
|
|
367
|
+
? await latestVisibleBodyCheckpoint(tx, generation.id)
|
|
368
|
+
: { generationSeq: generation.acceptedThroughSeq, bodyHash: generation.frozenBodyHash ?? EMPTY_BODY_SHA256 };
|
|
369
|
+
const projection = await visibleGenerationProjectionThrough(tx, generation.id, checkpoint.generationSeq);
|
|
370
|
+
return { generation, checkpoint, projection };
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
async function recordClientCheckpoint(input) {
|
|
374
|
+
const renderedBodyHash = normalizeBodyHash(input.renderedBodyHash);
|
|
375
|
+
return db.transaction(async (tx) => {
|
|
376
|
+
const generation = await lockGeneration(tx, input);
|
|
377
|
+
assertGenerationFence(generation, input);
|
|
378
|
+
if (!OUTPUT_ADMITTING_GENERATION_STATUSES.includes(generation.status)) {
|
|
379
|
+
throw conflict("Chat generation checkpoint arrived after output admission closed");
|
|
380
|
+
}
|
|
381
|
+
const currentSeq = generation.lastClientCheckpointSeq ?? -1;
|
|
382
|
+
if (input.generationSeq < currentSeq) {
|
|
383
|
+
return { generation, event: null, advanced: false };
|
|
384
|
+
}
|
|
385
|
+
if (input.generationSeq === currentSeq) {
|
|
386
|
+
if (generation.lastClientCheckpointHash !== renderedBodyHash) {
|
|
387
|
+
throw conflict("Chat generation checkpoint hash changed at the same sequence");
|
|
388
|
+
}
|
|
389
|
+
return { generation, event: null, advanced: false };
|
|
390
|
+
}
|
|
391
|
+
const durableHash = await bodyHashAtSeq(tx, generation.id, input.generationSeq);
|
|
392
|
+
if (!durableHash) {
|
|
393
|
+
throw conflict("Chat generation checkpoint does not reference a durable visible event");
|
|
394
|
+
}
|
|
395
|
+
if (durableHash !== renderedBodyHash) {
|
|
396
|
+
throw conflict("Chat generation checkpoint hash does not match the durable projection");
|
|
397
|
+
}
|
|
398
|
+
const now = input.now ?? new Date();
|
|
399
|
+
const [updatedGeneration] = await tx
|
|
400
|
+
.update(chatGenerations)
|
|
401
|
+
.set({
|
|
402
|
+
lastClientCheckpointSeq: input.generationSeq,
|
|
403
|
+
lastClientCheckpointHash: renderedBodyHash,
|
|
404
|
+
updatedAt: now,
|
|
405
|
+
})
|
|
406
|
+
.where(and(eq(chatGenerations.id, generation.id), eq(chatGenerations.attemptEpoch, input.expectedAttemptEpoch)))
|
|
407
|
+
.returning();
|
|
408
|
+
if (!updatedGeneration)
|
|
409
|
+
throw conflict("Chat generation changed while checkpointing");
|
|
410
|
+
const event = await appendEventLocked(tx, {
|
|
411
|
+
orgId: input.orgId,
|
|
412
|
+
generationId: generation.id,
|
|
413
|
+
attemptEpoch: input.expectedAttemptEpoch,
|
|
414
|
+
eventKind: "client_checkpoint",
|
|
415
|
+
payload: {
|
|
416
|
+
generationSeq: input.generationSeq,
|
|
417
|
+
renderedBodyHash,
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
return { generation: updatedGeneration, event, advanced: true };
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
async function beginStopAction(input) {
|
|
424
|
+
const requestedBodyHash = normalizeBodyHash(input.requestedBodyHash);
|
|
425
|
+
return db.transaction(async (tx) => {
|
|
426
|
+
const generation = await lockGeneration(tx, {
|
|
427
|
+
orgId: input.orgId,
|
|
428
|
+
conversationId: input.conversationId,
|
|
429
|
+
generationId: input.expectedGenerationId,
|
|
430
|
+
});
|
|
431
|
+
const existingAction = await tx
|
|
432
|
+
.select()
|
|
433
|
+
.from(chatControlActions)
|
|
434
|
+
.where(eq(chatControlActions.id, input.controlActionId))
|
|
435
|
+
.limit(1)
|
|
436
|
+
.then((rows) => rows[0] ?? null);
|
|
437
|
+
if (existingAction) {
|
|
438
|
+
if (existingAction.orgId !== input.orgId
|
|
439
|
+
|| existingAction.actionKind !== "stop"
|
|
440
|
+
|| existingAction.expectedGenerationId !== input.expectedGenerationId
|
|
441
|
+
|| existingAction.expectedAttemptEpoch !== input.expectedAttemptEpoch
|
|
442
|
+
|| existingAction.requestedRenderSeq !== input.requestedRenderSeq
|
|
443
|
+
|| existingAction.requestedBodyHash !== requestedBodyHash) {
|
|
444
|
+
throw conflict("Control action id was already used for a different Stop request");
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
action: existingAction,
|
|
448
|
+
generation,
|
|
449
|
+
stopRequestedEvent: null,
|
|
450
|
+
outputCutoffEvent: null,
|
|
451
|
+
outcome: existingAction.lastError === "generation_result_already_committed"
|
|
452
|
+
? "completion_committed"
|
|
453
|
+
: "stop_applied",
|
|
454
|
+
idempotent: true,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
assertGenerationFence(generation, {
|
|
458
|
+
conversationId: input.conversationId,
|
|
459
|
+
expectedAttemptEpoch: input.expectedAttemptEpoch,
|
|
460
|
+
});
|
|
461
|
+
const completionAdmission = await runtimeOutputAdmission(tx, generation.id);
|
|
462
|
+
if (completionAdmission) {
|
|
463
|
+
const now = input.now ?? new Date();
|
|
464
|
+
const [action] = await tx
|
|
465
|
+
.insert(chatControlActions)
|
|
466
|
+
.values({
|
|
467
|
+
id: input.controlActionId,
|
|
468
|
+
orgId: input.orgId,
|
|
469
|
+
expectedGenerationId: generation.id,
|
|
470
|
+
expectedAttemptEpoch: input.expectedAttemptEpoch,
|
|
471
|
+
expectedControlVersion: input.expectedControlVersion,
|
|
472
|
+
actionKind: "stop",
|
|
473
|
+
localDisposition: "cancelled",
|
|
474
|
+
providerDisposition: "not_sent",
|
|
475
|
+
controlOwnerToken: generation.controlOwnerToken,
|
|
476
|
+
providerEvidence: {
|
|
477
|
+
completionEventId: completionAdmission.id,
|
|
478
|
+
completionGenerationSeq: completionAdmission.generationSeq,
|
|
479
|
+
},
|
|
480
|
+
requestedRenderSeq: input.requestedRenderSeq,
|
|
481
|
+
requestedBodyHash,
|
|
482
|
+
lastError: "generation_result_already_committed",
|
|
483
|
+
requestedAt: now,
|
|
484
|
+
resolvedAt: now,
|
|
485
|
+
createdAt: now,
|
|
486
|
+
updatedAt: now,
|
|
487
|
+
})
|
|
488
|
+
.returning();
|
|
489
|
+
if (!action)
|
|
490
|
+
throw new Error("Failed to persist completed-generation Stop outcome");
|
|
491
|
+
return {
|
|
492
|
+
action,
|
|
493
|
+
generation,
|
|
494
|
+
stopRequestedEvent: null,
|
|
495
|
+
outputCutoffEvent: null,
|
|
496
|
+
outcome: "completion_committed",
|
|
497
|
+
idempotent: false,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
if (generation.runtimeTerminalAt) {
|
|
501
|
+
throw conflict("Chat generation runtime is already terminal");
|
|
502
|
+
}
|
|
503
|
+
if (!CONTROL_ACTIVE_GENERATION_STATUSES.includes(generation.status)) {
|
|
504
|
+
throw conflict("Chat generation is no longer stoppable");
|
|
505
|
+
}
|
|
506
|
+
if (generation.controlVersion !== input.expectedControlVersion) {
|
|
507
|
+
throw conflict("Chat generation control version changed");
|
|
508
|
+
}
|
|
509
|
+
const durableCheckpointIsNewer = generation.lastClientCheckpointSeq !== null
|
|
510
|
+
&& generation.lastClientCheckpointHash
|
|
511
|
+
&& generation.lastClientCheckpointSeq > input.requestedRenderSeq;
|
|
512
|
+
const requestedDurableHash = durableCheckpointIsNewer
|
|
513
|
+
? null
|
|
514
|
+
: await bodyHashAtSeq(tx, generation.id, input.requestedRenderSeq);
|
|
515
|
+
let acceptedThroughSeq;
|
|
516
|
+
let frozenBodyHash;
|
|
517
|
+
if (durableCheckpointIsNewer) {
|
|
518
|
+
acceptedThroughSeq = generation.lastClientCheckpointSeq;
|
|
519
|
+
frozenBodyHash = generation.lastClientCheckpointHash;
|
|
520
|
+
}
|
|
521
|
+
else if (requestedDurableHash) {
|
|
522
|
+
if (requestedDurableHash !== requestedBodyHash) {
|
|
523
|
+
throw conflict("Stop body hash does not match the durable visible projection");
|
|
524
|
+
}
|
|
525
|
+
acceptedThroughSeq = input.requestedRenderSeq;
|
|
526
|
+
frozenBodyHash = requestedBodyHash;
|
|
527
|
+
}
|
|
528
|
+
else if (generation.lastClientCheckpointSeq !== null
|
|
529
|
+
&& generation.lastClientCheckpointHash) {
|
|
530
|
+
acceptedThroughSeq = generation.lastClientCheckpointSeq;
|
|
531
|
+
frozenBodyHash = generation.lastClientCheckpointHash;
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
throw conflict("Stop could not establish a durable rendered checkpoint");
|
|
535
|
+
}
|
|
536
|
+
const now = input.now ?? new Date();
|
|
537
|
+
const appliedControlVersion = generation.controlVersion + 1;
|
|
538
|
+
const [action] = await tx
|
|
539
|
+
.insert(chatControlActions)
|
|
540
|
+
.values({
|
|
541
|
+
id: input.controlActionId,
|
|
542
|
+
orgId: input.orgId,
|
|
543
|
+
expectedGenerationId: generation.id,
|
|
544
|
+
expectedAttemptEpoch: input.expectedAttemptEpoch,
|
|
545
|
+
expectedControlVersion: input.expectedControlVersion,
|
|
546
|
+
appliedControlVersion,
|
|
547
|
+
actionKind: "stop",
|
|
548
|
+
localDisposition: "stop_requested",
|
|
549
|
+
providerDisposition: "not_sent",
|
|
550
|
+
controlOwnerToken: generation.controlOwnerToken,
|
|
551
|
+
requestedRenderSeq: input.requestedRenderSeq,
|
|
552
|
+
requestedBodyHash,
|
|
553
|
+
acceptedThroughSeq,
|
|
554
|
+
frozenBodyHash,
|
|
555
|
+
requestedAt: now,
|
|
556
|
+
createdAt: now,
|
|
557
|
+
updatedAt: now,
|
|
558
|
+
})
|
|
559
|
+
.returning();
|
|
560
|
+
if (!action)
|
|
561
|
+
throw new Error("Failed to persist Stop control action");
|
|
562
|
+
const stopRequestedEvent = await appendEventLocked(tx, {
|
|
563
|
+
orgId: input.orgId,
|
|
564
|
+
generationId: generation.id,
|
|
565
|
+
attemptEpoch: input.expectedAttemptEpoch,
|
|
566
|
+
eventKind: "stop_requested",
|
|
567
|
+
controlActionId: action.id,
|
|
568
|
+
payload: {
|
|
569
|
+
controlActionId: action.id,
|
|
570
|
+
requestedRenderSeq: input.requestedRenderSeq,
|
|
571
|
+
requestedBodyHash,
|
|
572
|
+
acceptedThroughSeq,
|
|
573
|
+
frozenBodyHash,
|
|
574
|
+
appliedControlVersion,
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
const outputCutoffEvent = await appendEventLocked(tx, {
|
|
578
|
+
orgId: input.orgId,
|
|
579
|
+
generationId: generation.id,
|
|
580
|
+
attemptEpoch: input.expectedAttemptEpoch,
|
|
581
|
+
eventKind: "output_cutoff",
|
|
582
|
+
controlActionId: action.id,
|
|
583
|
+
payload: {
|
|
584
|
+
controlActionId: action.id,
|
|
585
|
+
acceptedThroughSeq,
|
|
586
|
+
frozenBodyHash,
|
|
587
|
+
},
|
|
588
|
+
});
|
|
589
|
+
await freezeAssistantMessageProjection(tx, generation, acceptedThroughSeq);
|
|
590
|
+
const [updatedGeneration] = await tx
|
|
591
|
+
.update(chatGenerations)
|
|
592
|
+
.set({
|
|
593
|
+
status: "stop_requested",
|
|
594
|
+
terminalReason: "operator_stop",
|
|
595
|
+
controlState: "stopping",
|
|
596
|
+
controlVersion: appliedControlVersion,
|
|
597
|
+
acceptedThroughSeq,
|
|
598
|
+
frozenBodyHash,
|
|
599
|
+
stopRequestedAt: now,
|
|
600
|
+
updatedAt: now,
|
|
601
|
+
})
|
|
602
|
+
.where(and(eq(chatGenerations.id, generation.id), eq(chatGenerations.attemptEpoch, input.expectedAttemptEpoch), eq(chatGenerations.controlVersion, input.expectedControlVersion)))
|
|
603
|
+
.returning();
|
|
604
|
+
if (!updatedGeneration)
|
|
605
|
+
throw conflict("Chat generation changed while applying Stop");
|
|
606
|
+
return {
|
|
607
|
+
action,
|
|
608
|
+
generation: updatedGeneration,
|
|
609
|
+
stopRequestedEvent,
|
|
610
|
+
outputCutoffEvent,
|
|
611
|
+
outcome: "stop_applied",
|
|
612
|
+
idempotent: false,
|
|
613
|
+
};
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
async function beginSteerFallbackCutoff(input) {
|
|
617
|
+
const requestedBodyHash = input.requestedBodyHash === undefined
|
|
618
|
+
? undefined
|
|
619
|
+
: normalizeBodyHash(input.requestedBodyHash);
|
|
620
|
+
if ((input.requestedRenderSeq === undefined) !== (requestedBodyHash === undefined)) {
|
|
621
|
+
throw unprocessable("Steer fallback render sequence and body hash must be supplied together");
|
|
622
|
+
}
|
|
623
|
+
return db.transaction(async (tx) => {
|
|
624
|
+
const generation = await lockGeneration(tx, {
|
|
625
|
+
orgId: input.orgId,
|
|
626
|
+
conversationId: input.conversationId,
|
|
627
|
+
generationId: input.generationId,
|
|
628
|
+
});
|
|
629
|
+
assertGenerationFence(generation, {
|
|
630
|
+
conversationId: input.conversationId,
|
|
631
|
+
expectedAttemptEpoch: input.expectedAttemptEpoch,
|
|
632
|
+
});
|
|
633
|
+
await tx.execute(sql `
|
|
634
|
+
select ${chatControlActions.id}
|
|
635
|
+
from ${chatControlActions}
|
|
636
|
+
where ${chatControlActions.id} = ${input.controlActionId}
|
|
637
|
+
and ${chatControlActions.orgId} = ${input.orgId}
|
|
638
|
+
for update
|
|
639
|
+
`);
|
|
640
|
+
await tx.execute(sql `
|
|
641
|
+
select ${chatQueuedMessages.id}
|
|
642
|
+
from ${chatQueuedMessages}
|
|
643
|
+
where ${chatQueuedMessages.id} = ${input.queueItemId}
|
|
644
|
+
and ${chatQueuedMessages.orgId} = ${input.orgId}
|
|
645
|
+
and ${chatQueuedMessages.conversationId} = ${input.conversationId}
|
|
646
|
+
for update
|
|
647
|
+
`);
|
|
648
|
+
const action = await tx
|
|
649
|
+
.select()
|
|
650
|
+
.from(chatControlActions)
|
|
651
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
652
|
+
.limit(1)
|
|
653
|
+
.then((rows) => rows[0] ?? null);
|
|
654
|
+
const item = await tx
|
|
655
|
+
.select()
|
|
656
|
+
.from(chatQueuedMessages)
|
|
657
|
+
.where(and(eq(chatQueuedMessages.id, input.queueItemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.controlActionId, input.controlActionId)))
|
|
658
|
+
.limit(1)
|
|
659
|
+
.then((rows) => rows[0] ?? null);
|
|
660
|
+
if (!action
|
|
661
|
+
|| action.expectedGenerationId !== generation.id
|
|
662
|
+
|| action.expectedAttemptEpoch !== generation.attemptEpoch) {
|
|
663
|
+
throw conflict("Steer fallback control action no longer targets this generation");
|
|
664
|
+
}
|
|
665
|
+
if (!item)
|
|
666
|
+
throw notFound("Queued Steer feedback not found");
|
|
667
|
+
if (action.acceptedThroughSeq !== null && action.frozenBodyHash) {
|
|
668
|
+
return {
|
|
669
|
+
action,
|
|
670
|
+
generation,
|
|
671
|
+
item,
|
|
672
|
+
outputCutoffEvent: null,
|
|
673
|
+
continuationEvent: null,
|
|
674
|
+
outcome: "cutoff_applied",
|
|
675
|
+
idempotent: true,
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
const completionAdmission = await runtimeOutputAdmission(tx, generation.id);
|
|
679
|
+
if (completionAdmission) {
|
|
680
|
+
const existingContinuationEvent = await tx
|
|
681
|
+
.select()
|
|
682
|
+
.from(chatGenerationEvents)
|
|
683
|
+
.where(and(eq(chatGenerationEvents.generationId, generation.id), eq(chatGenerationEvents.eventKind, "continuation_scheduled"), eq(chatGenerationEvents.controlActionId, action.id)))
|
|
684
|
+
.orderBy(desc(chatGenerationEvents.generationSeq))
|
|
685
|
+
.limit(1)
|
|
686
|
+
.then((rows) => rows[0] ?? null);
|
|
687
|
+
if (action.localDisposition === "continuation_pending"
|
|
688
|
+
&& existingContinuationEvent?.payload.reason === "target_generation_completion_committed") {
|
|
689
|
+
return {
|
|
690
|
+
action,
|
|
691
|
+
generation,
|
|
692
|
+
item,
|
|
693
|
+
outputCutoffEvent: null,
|
|
694
|
+
continuationEvent: existingContinuationEvent,
|
|
695
|
+
outcome: "completion_committed",
|
|
696
|
+
idempotent: true,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
const now = input.now ?? new Date();
|
|
700
|
+
const [updatedAction] = await tx
|
|
701
|
+
.update(chatControlActions)
|
|
702
|
+
.set({
|
|
703
|
+
localDisposition: "continuation_pending",
|
|
704
|
+
providerDisposition: "not_sent",
|
|
705
|
+
providerSentAt: null,
|
|
706
|
+
requestedRenderSeq: input.requestedRenderSeq ?? null,
|
|
707
|
+
requestedBodyHash: requestedBodyHash ?? null,
|
|
708
|
+
acceptedThroughSeq: null,
|
|
709
|
+
frozenBodyHash: null,
|
|
710
|
+
providerEvidence: {
|
|
711
|
+
...(action.providerEvidence ?? {}),
|
|
712
|
+
completionEventId: completionAdmission.id,
|
|
713
|
+
completionGenerationSeq: completionAdmission.generationSeq,
|
|
714
|
+
},
|
|
715
|
+
lastError: "target_generation_completion_committed",
|
|
716
|
+
resolvedAt: now,
|
|
717
|
+
updatedAt: now,
|
|
718
|
+
})
|
|
719
|
+
.where(and(eq(chatControlActions.id, action.id), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
720
|
+
.returning();
|
|
721
|
+
if (!updatedAction)
|
|
722
|
+
throw conflict("Steer continuation action changed after generation completion");
|
|
723
|
+
const [updatedItem] = await tx
|
|
724
|
+
.update(chatQueuedMessages)
|
|
725
|
+
.set({
|
|
726
|
+
status: "continuation_pending",
|
|
727
|
+
deliveryIntent: "steer",
|
|
728
|
+
deliveryDisposition: "continuation_pending",
|
|
729
|
+
reconciliationReason: "target_generation_completion_committed",
|
|
730
|
+
lastDeliveryReason: null,
|
|
731
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
732
|
+
updatedAt: now,
|
|
733
|
+
})
|
|
734
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
735
|
+
.returning();
|
|
736
|
+
if (!updatedItem)
|
|
737
|
+
throw conflict("Queued Steer feedback changed after generation completion");
|
|
738
|
+
const continuationEvent = await appendEventLocked(tx, {
|
|
739
|
+
orgId: input.orgId,
|
|
740
|
+
generationId: generation.id,
|
|
741
|
+
attemptEpoch: generation.attemptEpoch,
|
|
742
|
+
eventKind: "continuation_scheduled",
|
|
743
|
+
controlActionId: action.id,
|
|
744
|
+
queueItemId: item.id,
|
|
745
|
+
payload: {
|
|
746
|
+
controlActionId: action.id,
|
|
747
|
+
reason: "target_generation_completion_committed",
|
|
748
|
+
completionEventId: completionAdmission.id,
|
|
749
|
+
completionGenerationSeq: completionAdmission.generationSeq,
|
|
750
|
+
},
|
|
751
|
+
});
|
|
752
|
+
return {
|
|
753
|
+
action: updatedAction,
|
|
754
|
+
generation,
|
|
755
|
+
item: updatedItem,
|
|
756
|
+
outputCutoffEvent: null,
|
|
757
|
+
continuationEvent,
|
|
758
|
+
outcome: "completion_committed",
|
|
759
|
+
idempotent: false,
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (generation.runtimeTerminalAt) {
|
|
763
|
+
throw conflict("Chat generation runtime is already terminal");
|
|
764
|
+
}
|
|
765
|
+
const priorCutoffExists = generation.acceptedThroughSeq !== null
|
|
766
|
+
&& Boolean(generation.frozenBodyHash)
|
|
767
|
+
&& generation.stopRequestedAt !== null;
|
|
768
|
+
if (!priorCutoffExists
|
|
769
|
+
&& action.appliedControlVersion !== generation.controlVersion) {
|
|
770
|
+
throw conflict("Chat generation control version changed before Steer fallback cutoff");
|
|
771
|
+
}
|
|
772
|
+
let acceptedThroughSeq;
|
|
773
|
+
let frozenBodyHash;
|
|
774
|
+
if (priorCutoffExists) {
|
|
775
|
+
acceptedThroughSeq = generation.acceptedThroughSeq;
|
|
776
|
+
frozenBodyHash = generation.frozenBodyHash;
|
|
777
|
+
}
|
|
778
|
+
else {
|
|
779
|
+
const requestedDurableHash = input.requestedRenderSeq === undefined
|
|
780
|
+
? null
|
|
781
|
+
: await bodyHashAtSeq(tx, generation.id, input.requestedRenderSeq);
|
|
782
|
+
const clientCheckpointIsNewer = generation.lastClientCheckpointSeq !== null
|
|
783
|
+
&& generation.lastClientCheckpointHash
|
|
784
|
+
&& (input.requestedRenderSeq === undefined
|
|
785
|
+
|| generation.lastClientCheckpointSeq > input.requestedRenderSeq);
|
|
786
|
+
if (clientCheckpointIsNewer) {
|
|
787
|
+
acceptedThroughSeq = generation.lastClientCheckpointSeq;
|
|
788
|
+
frozenBodyHash = generation.lastClientCheckpointHash;
|
|
789
|
+
}
|
|
790
|
+
else if (requestedDurableHash && requestedBodyHash) {
|
|
791
|
+
if (requestedDurableHash !== requestedBodyHash) {
|
|
792
|
+
throw conflict("Steer fallback body hash does not match the durable visible projection");
|
|
793
|
+
}
|
|
794
|
+
acceptedThroughSeq = input.requestedRenderSeq;
|
|
795
|
+
frozenBodyHash = requestedBodyHash;
|
|
796
|
+
}
|
|
797
|
+
else if (generation.lastClientCheckpointSeq !== null && generation.lastClientCheckpointHash) {
|
|
798
|
+
acceptedThroughSeq = generation.lastClientCheckpointSeq;
|
|
799
|
+
frozenBodyHash = generation.lastClientCheckpointHash;
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
const checkpoint = await latestVisibleBodyCheckpoint(tx, generation.id);
|
|
803
|
+
acceptedThroughSeq = checkpoint.generationSeq;
|
|
804
|
+
frozenBodyHash = checkpoint.bodyHash;
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
const now = input.now ?? new Date();
|
|
808
|
+
const [updatedAction] = await tx
|
|
809
|
+
.update(chatControlActions)
|
|
810
|
+
.set({
|
|
811
|
+
localDisposition: "continuation_pending",
|
|
812
|
+
providerDisposition: "not_sent",
|
|
813
|
+
providerSentAt: null,
|
|
814
|
+
requestedRenderSeq: input.requestedRenderSeq ?? acceptedThroughSeq,
|
|
815
|
+
requestedBodyHash: requestedBodyHash ?? frozenBodyHash,
|
|
816
|
+
acceptedThroughSeq,
|
|
817
|
+
frozenBodyHash,
|
|
818
|
+
lastError: "runtime_requires_continuation",
|
|
819
|
+
resolvedAt: now,
|
|
820
|
+
updatedAt: now,
|
|
821
|
+
})
|
|
822
|
+
.where(and(eq(chatControlActions.id, action.id), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
823
|
+
.returning();
|
|
824
|
+
if (!updatedAction)
|
|
825
|
+
throw conflict("Steer fallback control action changed");
|
|
826
|
+
const [updatedItem] = await tx
|
|
827
|
+
.update(chatQueuedMessages)
|
|
828
|
+
.set({
|
|
829
|
+
status: "continuation_pending",
|
|
830
|
+
deliveryIntent: "steer",
|
|
831
|
+
deliveryDisposition: "continuation_pending",
|
|
832
|
+
reconciliationReason: "runtime_requires_continuation",
|
|
833
|
+
lastDeliveryReason: null,
|
|
834
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
835
|
+
updatedAt: now,
|
|
836
|
+
})
|
|
837
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
838
|
+
.returning();
|
|
839
|
+
if (!updatedItem)
|
|
840
|
+
throw conflict("Queued Steer feedback changed before fallback cutoff");
|
|
841
|
+
let outputCutoffEvent = null;
|
|
842
|
+
if (!priorCutoffExists) {
|
|
843
|
+
outputCutoffEvent = await appendEventLocked(tx, {
|
|
844
|
+
orgId: input.orgId,
|
|
845
|
+
generationId: generation.id,
|
|
846
|
+
attemptEpoch: generation.attemptEpoch,
|
|
847
|
+
eventKind: "output_cutoff",
|
|
848
|
+
controlActionId: action.id,
|
|
849
|
+
queueItemId: item.id,
|
|
850
|
+
payload: {
|
|
851
|
+
controlActionId: action.id,
|
|
852
|
+
reason: "steer_fallback",
|
|
853
|
+
acceptedThroughSeq,
|
|
854
|
+
frozenBodyHash,
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
const continuationEvent = await appendEventLocked(tx, {
|
|
859
|
+
orgId: input.orgId,
|
|
860
|
+
generationId: generation.id,
|
|
861
|
+
attemptEpoch: generation.attemptEpoch,
|
|
862
|
+
eventKind: "continuation_scheduled",
|
|
863
|
+
controlActionId: action.id,
|
|
864
|
+
queueItemId: item.id,
|
|
865
|
+
payload: {
|
|
866
|
+
controlActionId: action.id,
|
|
867
|
+
reason: priorCutoffExists ? "existing_output_cutoff" : "runtime_requires_continuation",
|
|
868
|
+
acceptedThroughSeq,
|
|
869
|
+
frozenBodyHash,
|
|
870
|
+
},
|
|
871
|
+
});
|
|
872
|
+
await freezeAssistantMessageProjection(tx, generation, acceptedThroughSeq);
|
|
873
|
+
let updatedGeneration = generation;
|
|
874
|
+
if (!priorCutoffExists) {
|
|
875
|
+
const [cutoffGeneration] = await tx
|
|
876
|
+
.update(chatGenerations)
|
|
877
|
+
.set({
|
|
878
|
+
status: "stop_requested",
|
|
879
|
+
terminalReason: "steer_fallback",
|
|
880
|
+
controlState: "stopping",
|
|
881
|
+
acceptedThroughSeq,
|
|
882
|
+
frozenBodyHash,
|
|
883
|
+
stopRequestedAt: now,
|
|
884
|
+
updatedAt: now,
|
|
885
|
+
})
|
|
886
|
+
.where(and(eq(chatGenerations.id, generation.id), eq(chatGenerations.controlVersion, generation.controlVersion), eq(chatGenerations.attemptEpoch, generation.attemptEpoch)))
|
|
887
|
+
.returning();
|
|
888
|
+
if (!cutoffGeneration)
|
|
889
|
+
throw conflict("Chat generation changed while applying Steer fallback cutoff");
|
|
890
|
+
updatedGeneration = cutoffGeneration;
|
|
891
|
+
}
|
|
892
|
+
return {
|
|
893
|
+
action: updatedAction,
|
|
894
|
+
generation: updatedGeneration,
|
|
895
|
+
item: updatedItem,
|
|
896
|
+
outputCutoffEvent,
|
|
897
|
+
continuationEvent,
|
|
898
|
+
outcome: "cutoff_applied",
|
|
899
|
+
idempotent: false,
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
async function recordRuntimeTerminal(input) {
|
|
904
|
+
return db.transaction(async (tx) => {
|
|
905
|
+
const generation = await lockGeneration(tx, input);
|
|
906
|
+
assertGenerationFence(generation, input);
|
|
907
|
+
const cutoffWon = generation.status === "stop_requested"
|
|
908
|
+
|| generation.status === "stopping"
|
|
909
|
+
|| generation.stopRequestedAt !== null;
|
|
910
|
+
const runtimeTerminationUnverified = input.finalStatus === "interrupted_unverified";
|
|
911
|
+
const finalStatus = cutoffWon
|
|
912
|
+
? runtimeTerminationUnverified ? "interrupted_unverified" : "stopped"
|
|
913
|
+
: input.finalStatus;
|
|
914
|
+
const cutoffReason = generation.terminalReason === "steer_fallback"
|
|
915
|
+
? "steer_fallback"
|
|
916
|
+
: "operator_stop";
|
|
917
|
+
const terminalReason = cutoffWon
|
|
918
|
+
? runtimeTerminationUnverified ? `${cutoffReason}_unverified` : cutoffReason
|
|
919
|
+
: input.terminalReason;
|
|
920
|
+
const projectionVersion = generation.controlVersion;
|
|
921
|
+
const existingOutbox = await tx
|
|
922
|
+
.select()
|
|
923
|
+
.from(chatGenerationTerminalOutbox)
|
|
924
|
+
.where(and(eq(chatGenerationTerminalOutbox.generationId, input.generationId), eq(chatGenerationTerminalOutbox.projectionVersion, projectionVersion)))
|
|
925
|
+
.limit(1)
|
|
926
|
+
.then((rows) => rows[0] ?? null);
|
|
927
|
+
if (existingOutbox) {
|
|
928
|
+
if (terminalStatusFromPayload(existingOutbox.payload) !== finalStatus
|
|
929
|
+
|| existingOutbox.payload.terminalReason !== terminalReason) {
|
|
930
|
+
throw conflict("Terminal projection version was reused with different evidence");
|
|
931
|
+
}
|
|
932
|
+
return { generation, event: null, outbox: existingOutbox, idempotent: true };
|
|
933
|
+
}
|
|
934
|
+
if (!RECOVERABLE_CONTROL_GENERATION_STATUSES.includes(generation.status)) {
|
|
935
|
+
throw conflict("Chat generation is already terminal");
|
|
936
|
+
}
|
|
937
|
+
const now = input.now ?? new Date();
|
|
938
|
+
const inferredControlAction = input.controlActionId
|
|
939
|
+
? await tx
|
|
940
|
+
.select({ id: chatControlActions.id, actionKind: chatControlActions.actionKind })
|
|
941
|
+
.from(chatControlActions)
|
|
942
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId)))
|
|
943
|
+
.limit(1)
|
|
944
|
+
.then((rows) => rows[0] ?? null)
|
|
945
|
+
: (finalStatus === "stopped" || finalStatus === "interrupted_unverified"
|
|
946
|
+
? await tx
|
|
947
|
+
.select({ id: chatControlActions.id, actionKind: chatControlActions.actionKind })
|
|
948
|
+
.from(chatControlActions)
|
|
949
|
+
.where(and(eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.expectedGenerationId, input.generationId), isNotNull(chatControlActions.acceptedThroughSeq)))
|
|
950
|
+
.orderBy(desc(chatControlActions.requestedAt))
|
|
951
|
+
.limit(1)
|
|
952
|
+
.then((rows) => rows[0] ?? null)
|
|
953
|
+
: null);
|
|
954
|
+
const inferredControlActionId = inferredControlAction?.id ?? null;
|
|
955
|
+
const payload = {
|
|
956
|
+
...(input.payload ?? {}),
|
|
957
|
+
attemptEpoch: generation.attemptEpoch,
|
|
958
|
+
finalStatus,
|
|
959
|
+
terminalReason,
|
|
960
|
+
controlActionId: inferredControlActionId,
|
|
961
|
+
controlActionKind: inferredControlAction?.actionKind ?? null,
|
|
962
|
+
runtimeTerminationVerified: finalStatus !== "interrupted_unverified",
|
|
963
|
+
};
|
|
964
|
+
const event = await appendEventLocked(tx, {
|
|
965
|
+
orgId: input.orgId,
|
|
966
|
+
generationId: input.generationId,
|
|
967
|
+
attemptEpoch: input.expectedAttemptEpoch,
|
|
968
|
+
eventKind: "runtime_terminal",
|
|
969
|
+
controlActionId: inferredControlActionId,
|
|
970
|
+
payload,
|
|
971
|
+
});
|
|
972
|
+
const [outbox] = await tx
|
|
973
|
+
.insert(chatGenerationTerminalOutbox)
|
|
974
|
+
.values({
|
|
975
|
+
orgId: input.orgId,
|
|
976
|
+
generationId: input.generationId,
|
|
977
|
+
sourceEventId: event.id,
|
|
978
|
+
projectionVersion,
|
|
979
|
+
expectedControlVersion: generation.controlVersion,
|
|
980
|
+
payload,
|
|
981
|
+
availableAt: now,
|
|
982
|
+
createdAt: now,
|
|
983
|
+
updatedAt: now,
|
|
984
|
+
})
|
|
985
|
+
.returning();
|
|
986
|
+
if (!outbox)
|
|
987
|
+
throw new Error("Failed to enqueue chat terminal projection");
|
|
988
|
+
const stagingStatus = finalStatus === "stopped"
|
|
989
|
+
|| finalStatus === "interrupted_unverified"
|
|
990
|
+
? "stopping"
|
|
991
|
+
: "closing";
|
|
992
|
+
const [updatedGeneration] = await tx
|
|
993
|
+
.update(chatGenerations)
|
|
994
|
+
.set({
|
|
995
|
+
status: stagingStatus,
|
|
996
|
+
controlState: stagingStatus === "stopping" ? "stopping" : generation.controlState,
|
|
997
|
+
terminalReason,
|
|
998
|
+
runtimeTerminalAt: now,
|
|
999
|
+
updatedAt: now,
|
|
1000
|
+
})
|
|
1001
|
+
.where(eq(chatGenerations.id, generation.id))
|
|
1002
|
+
.returning();
|
|
1003
|
+
return {
|
|
1004
|
+
generation: updatedGeneration ?? generation,
|
|
1005
|
+
event,
|
|
1006
|
+
outbox,
|
|
1007
|
+
idempotent: false,
|
|
1008
|
+
};
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
async function claimTerminalProjection(input) {
|
|
1012
|
+
const now = input.now ?? new Date();
|
|
1013
|
+
const leaseExpiresAt = new Date(now.getTime() + input.leaseMs);
|
|
1014
|
+
return db.transaction(async (tx) => {
|
|
1015
|
+
const conditions = [
|
|
1016
|
+
lte(chatGenerationTerminalOutbox.availableAt, now),
|
|
1017
|
+
or(inArray(chatGenerationTerminalOutbox.status, ["pending", "retry_wait"]), and(eq(chatGenerationTerminalOutbox.status, "claimed"), or(sql `${chatGenerationTerminalOutbox.leaseExpiresAt} is null`, lte(chatGenerationTerminalOutbox.leaseExpiresAt, now)))),
|
|
1018
|
+
];
|
|
1019
|
+
if (input.orgId)
|
|
1020
|
+
conditions.push(eq(chatGenerationTerminalOutbox.orgId, input.orgId));
|
|
1021
|
+
const candidate = await tx
|
|
1022
|
+
.select()
|
|
1023
|
+
.from(chatGenerationTerminalOutbox)
|
|
1024
|
+
.where(and(...conditions))
|
|
1025
|
+
.orderBy(asc(chatGenerationTerminalOutbox.availableAt), asc(chatGenerationTerminalOutbox.createdAt))
|
|
1026
|
+
.limit(1)
|
|
1027
|
+
.for("update", { skipLocked: true })
|
|
1028
|
+
.then((rows) => rows[0] ?? null);
|
|
1029
|
+
if (!candidate)
|
|
1030
|
+
return null;
|
|
1031
|
+
const claimToken = randomUUID();
|
|
1032
|
+
const claimEpoch = candidate.claimEpoch + 1;
|
|
1033
|
+
const [claimed] = await tx
|
|
1034
|
+
.update(chatGenerationTerminalOutbox)
|
|
1035
|
+
.set({
|
|
1036
|
+
status: "claimed",
|
|
1037
|
+
claimToken,
|
|
1038
|
+
claimEpoch,
|
|
1039
|
+
claimOwner: input.workerId,
|
|
1040
|
+
leaseExpiresAt,
|
|
1041
|
+
attemptCount: sql `${chatGenerationTerminalOutbox.attemptCount} + 1`,
|
|
1042
|
+
replayCount: candidate.status === "claimed"
|
|
1043
|
+
? sql `${chatGenerationTerminalOutbox.replayCount} + 1`
|
|
1044
|
+
: chatGenerationTerminalOutbox.replayCount,
|
|
1045
|
+
lastAttemptAt: now,
|
|
1046
|
+
updatedAt: now,
|
|
1047
|
+
})
|
|
1048
|
+
.where(eq(chatGenerationTerminalOutbox.id, candidate.id))
|
|
1049
|
+
.returning();
|
|
1050
|
+
return claimed ?? null;
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
async function getNextTerminalProjectionWakeAt(input = {}) {
|
|
1054
|
+
const conditions = [
|
|
1055
|
+
inArray(chatGenerationTerminalOutbox.status, ["pending", "retry_wait", "claimed"]),
|
|
1056
|
+
];
|
|
1057
|
+
if (input.orgId)
|
|
1058
|
+
conditions.push(eq(chatGenerationTerminalOutbox.orgId, input.orgId));
|
|
1059
|
+
const next = await db
|
|
1060
|
+
.select({
|
|
1061
|
+
status: chatGenerationTerminalOutbox.status,
|
|
1062
|
+
availableAt: chatGenerationTerminalOutbox.availableAt,
|
|
1063
|
+
leaseExpiresAt: chatGenerationTerminalOutbox.leaseExpiresAt,
|
|
1064
|
+
})
|
|
1065
|
+
.from(chatGenerationTerminalOutbox)
|
|
1066
|
+
.where(and(...conditions))
|
|
1067
|
+
.orderBy(asc(sql `
|
|
1068
|
+
case
|
|
1069
|
+
when ${chatGenerationTerminalOutbox.status} = 'claimed'
|
|
1070
|
+
and ${chatGenerationTerminalOutbox.leaseExpiresAt} is not null
|
|
1071
|
+
and ${chatGenerationTerminalOutbox.leaseExpiresAt} > ${chatGenerationTerminalOutbox.availableAt}
|
|
1072
|
+
then ${chatGenerationTerminalOutbox.leaseExpiresAt}
|
|
1073
|
+
else ${chatGenerationTerminalOutbox.availableAt}
|
|
1074
|
+
end
|
|
1075
|
+
`))
|
|
1076
|
+
.limit(1)
|
|
1077
|
+
.then((rows) => rows[0] ?? null);
|
|
1078
|
+
if (!next)
|
|
1079
|
+
return null;
|
|
1080
|
+
if (next.status === "claimed"
|
|
1081
|
+
&& next.leaseExpiresAt
|
|
1082
|
+
&& next.leaseExpiresAt > next.availableAt) {
|
|
1083
|
+
return next.leaseExpiresAt;
|
|
1084
|
+
}
|
|
1085
|
+
return next.availableAt;
|
|
1086
|
+
}
|
|
1087
|
+
async function completeTerminalProjection(input) {
|
|
1088
|
+
const now = input.now ?? new Date();
|
|
1089
|
+
return db.transaction(async (tx) => {
|
|
1090
|
+
const observedClaim = await tx
|
|
1091
|
+
.select({
|
|
1092
|
+
id: chatGenerationTerminalOutbox.id,
|
|
1093
|
+
orgId: chatGenerationTerminalOutbox.orgId,
|
|
1094
|
+
generationId: chatGenerationTerminalOutbox.generationId,
|
|
1095
|
+
})
|
|
1096
|
+
.from(chatGenerationTerminalOutbox)
|
|
1097
|
+
.where(eq(chatGenerationTerminalOutbox.id, input.outboxId))
|
|
1098
|
+
.limit(1)
|
|
1099
|
+
.then((rows) => rows[0] ?? null);
|
|
1100
|
+
if (!observedClaim)
|
|
1101
|
+
return null;
|
|
1102
|
+
const generation = await lockGeneration(tx, {
|
|
1103
|
+
orgId: observedClaim.orgId,
|
|
1104
|
+
generationId: observedClaim.generationId,
|
|
1105
|
+
});
|
|
1106
|
+
await tx.execute(sql `
|
|
1107
|
+
select ${chatGenerationTerminalOutbox.id}
|
|
1108
|
+
from ${chatGenerationTerminalOutbox}
|
|
1109
|
+
where ${chatGenerationTerminalOutbox.id} = ${input.outboxId}
|
|
1110
|
+
for update
|
|
1111
|
+
`);
|
|
1112
|
+
const claim = await tx
|
|
1113
|
+
.select()
|
|
1114
|
+
.from(chatGenerationTerminalOutbox)
|
|
1115
|
+
.where(eq(chatGenerationTerminalOutbox.id, input.outboxId))
|
|
1116
|
+
.limit(1)
|
|
1117
|
+
.then((rows) => rows[0] ?? null);
|
|
1118
|
+
if (!claim
|
|
1119
|
+
|| claim.status !== "claimed"
|
|
1120
|
+
|| claim.claimToken !== input.claimToken
|
|
1121
|
+
|| claim.claimEpoch !== input.claimEpoch
|
|
1122
|
+
|| !claim.leaseExpiresAt
|
|
1123
|
+
|| claim.leaseExpiresAt <= now) {
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
if (generation.controlVersion !== claim.expectedControlVersion) {
|
|
1127
|
+
throw conflict("Terminal projector claim has a stale control version");
|
|
1128
|
+
}
|
|
1129
|
+
const sourceAttemptEpoch = Number(claim.payload.attemptEpoch);
|
|
1130
|
+
if (!Number.isInteger(sourceAttemptEpoch) || sourceAttemptEpoch !== generation.attemptEpoch) {
|
|
1131
|
+
const [superseded] = await tx
|
|
1132
|
+
.update(chatGenerationTerminalOutbox)
|
|
1133
|
+
.set({
|
|
1134
|
+
status: "superseded",
|
|
1135
|
+
claimToken: null,
|
|
1136
|
+
claimOwner: null,
|
|
1137
|
+
leaseExpiresAt: null,
|
|
1138
|
+
projectedAt: now,
|
|
1139
|
+
lastError: "terminal_projection_attempt_superseded",
|
|
1140
|
+
updatedAt: now,
|
|
1141
|
+
})
|
|
1142
|
+
.where(and(eq(chatGenerationTerminalOutbox.id, claim.id), eq(chatGenerationTerminalOutbox.status, "claimed"), eq(chatGenerationTerminalOutbox.claimToken, input.claimToken), eq(chatGenerationTerminalOutbox.claimEpoch, input.claimEpoch)))
|
|
1143
|
+
.returning();
|
|
1144
|
+
return superseded ? {
|
|
1145
|
+
outbox: superseded,
|
|
1146
|
+
generation,
|
|
1147
|
+
event: null,
|
|
1148
|
+
superseded: true,
|
|
1149
|
+
} : null;
|
|
1150
|
+
}
|
|
1151
|
+
const finalStatus = input.finalStatus ?? terminalStatusFromPayload(claim.payload);
|
|
1152
|
+
if (!finalStatus || !PROJECTABLE_TERMINAL_STATUSES.includes(finalStatus)) {
|
|
1153
|
+
throw unprocessable("Terminal projector claim does not name a supported final status");
|
|
1154
|
+
}
|
|
1155
|
+
const terminalReason = input.terminalReason
|
|
1156
|
+
?? (typeof claim.payload.terminalReason === "string" ? claim.payload.terminalReason : finalStatus);
|
|
1157
|
+
await input.project?.(tx, claim);
|
|
1158
|
+
const deliveredSteers = await tx
|
|
1159
|
+
.update(chatQueuedMessages)
|
|
1160
|
+
.set({
|
|
1161
|
+
status: "delivered",
|
|
1162
|
+
deliveryDisposition: "delivered",
|
|
1163
|
+
reconciliationReason: null,
|
|
1164
|
+
lastDeliveryReason: null,
|
|
1165
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1166
|
+
updatedAt: now,
|
|
1167
|
+
})
|
|
1168
|
+
.where(and(or(eq(chatQueuedMessages.status, "accepted_current"), and(eq(chatQueuedMessages.status, "steer_pending"), inArray(chatQueuedMessages.controlActionId, tx
|
|
1169
|
+
.select({ id: chatControlActions.id })
|
|
1170
|
+
.from(chatControlActions)
|
|
1171
|
+
.where(eq(chatControlActions.providerDisposition, "acknowledged"))))), or(eq(chatQueuedMessages.expectedGenerationId, claim.generationId), eq(chatQueuedMessages.activeGenerationId, claim.generationId))))
|
|
1172
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
1173
|
+
const deliveredControlActionIds = deliveredSteers
|
|
1174
|
+
.map((item) => item.controlActionId)
|
|
1175
|
+
.filter((id) => Boolean(id));
|
|
1176
|
+
if (deliveredControlActionIds.length > 0) {
|
|
1177
|
+
await tx
|
|
1178
|
+
.update(chatControlActions)
|
|
1179
|
+
.set({ localDisposition: "delivered", resolvedAt: now, updatedAt: now })
|
|
1180
|
+
.where(inArray(chatControlActions.id, deliveredControlActionIds));
|
|
1181
|
+
}
|
|
1182
|
+
const uncertainSteers = await tx
|
|
1183
|
+
.update(chatQueuedMessages)
|
|
1184
|
+
.set({
|
|
1185
|
+
status: "acceptance_unknown",
|
|
1186
|
+
deliveryDisposition: "acceptance_unknown",
|
|
1187
|
+
reconciliationReason: `provider_receipt_unknown_at_${finalStatus}`,
|
|
1188
|
+
lastDeliveryReason: `provider_receipt_unknown_at_${finalStatus}`,
|
|
1189
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1190
|
+
updatedAt: now,
|
|
1191
|
+
})
|
|
1192
|
+
.where(and(eq(chatQueuedMessages.status, "steer_pending"), inArray(chatQueuedMessages.controlActionId, tx
|
|
1193
|
+
.select({ id: chatControlActions.id })
|
|
1194
|
+
.from(chatControlActions)
|
|
1195
|
+
.where(inArray(chatControlActions.providerDisposition, [
|
|
1196
|
+
"sent",
|
|
1197
|
+
"timed_out",
|
|
1198
|
+
"connection_lost",
|
|
1199
|
+
"waiting_safe_boundary",
|
|
1200
|
+
"unverified",
|
|
1201
|
+
]))), or(eq(chatQueuedMessages.expectedGenerationId, claim.generationId), eq(chatQueuedMessages.activeGenerationId, claim.generationId))))
|
|
1202
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
1203
|
+
const uncertainControlActionIds = uncertainSteers
|
|
1204
|
+
.map((item) => item.controlActionId)
|
|
1205
|
+
.filter((id) => Boolean(id));
|
|
1206
|
+
if (uncertainControlActionIds.length > 0) {
|
|
1207
|
+
await tx
|
|
1208
|
+
.update(chatControlActions)
|
|
1209
|
+
.set({
|
|
1210
|
+
localDisposition: "acceptance_unknown",
|
|
1211
|
+
lastError: `provider_receipt_unknown_at_${finalStatus}`,
|
|
1212
|
+
resolvedAt: now,
|
|
1213
|
+
updatedAt: now,
|
|
1214
|
+
})
|
|
1215
|
+
.where(inArray(chatControlActions.id, uncertainControlActionIds));
|
|
1216
|
+
}
|
|
1217
|
+
const continuationSteers = await tx
|
|
1218
|
+
.update(chatQueuedMessages)
|
|
1219
|
+
.set({
|
|
1220
|
+
status: "continuation_pending",
|
|
1221
|
+
deliveryDisposition: "continuation_pending",
|
|
1222
|
+
reconciliationReason: `target_generation_${finalStatus}`,
|
|
1223
|
+
lastDeliveryReason: null,
|
|
1224
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1225
|
+
updatedAt: now,
|
|
1226
|
+
})
|
|
1227
|
+
.where(and(eq(chatQueuedMessages.status, "steer_pending"), or(isNull(chatQueuedMessages.controlActionId), inArray(chatQueuedMessages.controlActionId, tx
|
|
1228
|
+
.select({ id: chatControlActions.id })
|
|
1229
|
+
.from(chatControlActions)
|
|
1230
|
+
.where(or(eq(chatControlActions.providerDisposition, "not_sent"), eq(chatControlActions.providerDisposition, "rejected"), isNull(chatControlActions.providerDisposition))))), or(eq(chatQueuedMessages.expectedGenerationId, claim.generationId), eq(chatQueuedMessages.activeGenerationId, claim.generationId))))
|
|
1231
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
1232
|
+
const continuationControlActionIds = continuationSteers
|
|
1233
|
+
.map((item) => item.controlActionId)
|
|
1234
|
+
.filter((id) => Boolean(id));
|
|
1235
|
+
if (continuationControlActionIds.length > 0) {
|
|
1236
|
+
await tx
|
|
1237
|
+
.update(chatControlActions)
|
|
1238
|
+
.set({
|
|
1239
|
+
localDisposition: "continuation_pending",
|
|
1240
|
+
providerDisposition: "not_sent",
|
|
1241
|
+
lastError: `target_generation_${finalStatus}`,
|
|
1242
|
+
resolvedAt: now,
|
|
1243
|
+
updatedAt: now,
|
|
1244
|
+
})
|
|
1245
|
+
.where(inArray(chatControlActions.id, continuationControlActionIds));
|
|
1246
|
+
}
|
|
1247
|
+
const terminalProjectedEvent = await appendEventLocked(tx, {
|
|
1248
|
+
orgId: claim.orgId,
|
|
1249
|
+
generationId: claim.generationId,
|
|
1250
|
+
attemptEpoch: generation.attemptEpoch,
|
|
1251
|
+
eventKind: "terminal_projected",
|
|
1252
|
+
controlActionId: typeof claim.payload.controlActionId === "string"
|
|
1253
|
+
? claim.payload.controlActionId
|
|
1254
|
+
: null,
|
|
1255
|
+
payload: {
|
|
1256
|
+
outboxId: claim.id,
|
|
1257
|
+
projectionVersion: claim.projectionVersion,
|
|
1258
|
+
finalStatus,
|
|
1259
|
+
terminalReason,
|
|
1260
|
+
},
|
|
1261
|
+
});
|
|
1262
|
+
const [updatedGeneration] = await tx
|
|
1263
|
+
.update(chatGenerations)
|
|
1264
|
+
.set({
|
|
1265
|
+
status: finalStatus,
|
|
1266
|
+
terminalReason,
|
|
1267
|
+
controlState: finalStatus === "control_lost" ? "control_lost" : "terminal",
|
|
1268
|
+
completedAt: now,
|
|
1269
|
+
updatedAt: now,
|
|
1270
|
+
})
|
|
1271
|
+
.where(eq(chatGenerations.id, claim.generationId))
|
|
1272
|
+
.returning();
|
|
1273
|
+
const controlActionId = typeof claim.payload.controlActionId === "string"
|
|
1274
|
+
? claim.payload.controlActionId
|
|
1275
|
+
: null;
|
|
1276
|
+
if (controlActionId && input.controlDisposition) {
|
|
1277
|
+
await tx
|
|
1278
|
+
.update(chatControlActions)
|
|
1279
|
+
.set({
|
|
1280
|
+
localDisposition: input.controlDisposition,
|
|
1281
|
+
resolvedAt: now,
|
|
1282
|
+
updatedAt: now,
|
|
1283
|
+
})
|
|
1284
|
+
.where(and(eq(chatControlActions.id, controlActionId), eq(chatControlActions.orgId, claim.orgId)));
|
|
1285
|
+
}
|
|
1286
|
+
const [completed] = await tx
|
|
1287
|
+
.update(chatGenerationTerminalOutbox)
|
|
1288
|
+
.set({
|
|
1289
|
+
status: "projected",
|
|
1290
|
+
claimToken: null,
|
|
1291
|
+
claimOwner: null,
|
|
1292
|
+
leaseExpiresAt: null,
|
|
1293
|
+
projectedAt: now,
|
|
1294
|
+
lastError: null,
|
|
1295
|
+
updatedAt: now,
|
|
1296
|
+
})
|
|
1297
|
+
.where(and(eq(chatGenerationTerminalOutbox.id, claim.id), eq(chatGenerationTerminalOutbox.status, "claimed"), eq(chatGenerationTerminalOutbox.claimToken, input.claimToken), eq(chatGenerationTerminalOutbox.claimEpoch, input.claimEpoch)))
|
|
1298
|
+
.returning();
|
|
1299
|
+
if (!completed || !updatedGeneration) {
|
|
1300
|
+
throw conflict("Terminal projector claim changed while completing");
|
|
1301
|
+
}
|
|
1302
|
+
return {
|
|
1303
|
+
outbox: completed,
|
|
1304
|
+
generation: updatedGeneration,
|
|
1305
|
+
event: terminalProjectedEvent,
|
|
1306
|
+
};
|
|
1307
|
+
});
|
|
1308
|
+
}
|
|
1309
|
+
async function retryTerminalProjection(input) {
|
|
1310
|
+
const now = input.now ?? new Date();
|
|
1311
|
+
return db.transaction(async (tx) => {
|
|
1312
|
+
const observedClaim = await tx
|
|
1313
|
+
.select({
|
|
1314
|
+
orgId: chatGenerationTerminalOutbox.orgId,
|
|
1315
|
+
generationId: chatGenerationTerminalOutbox.generationId,
|
|
1316
|
+
})
|
|
1317
|
+
.from(chatGenerationTerminalOutbox)
|
|
1318
|
+
.where(eq(chatGenerationTerminalOutbox.id, input.outboxId))
|
|
1319
|
+
.limit(1)
|
|
1320
|
+
.then((rows) => rows[0] ?? null);
|
|
1321
|
+
if (!observedClaim)
|
|
1322
|
+
return null;
|
|
1323
|
+
const generation = await lockGeneration(tx, observedClaim);
|
|
1324
|
+
await tx.execute(sql `
|
|
1325
|
+
select ${chatGenerationTerminalOutbox.id}
|
|
1326
|
+
from ${chatGenerationTerminalOutbox}
|
|
1327
|
+
where ${chatGenerationTerminalOutbox.id} = ${input.outboxId}
|
|
1328
|
+
for update
|
|
1329
|
+
`);
|
|
1330
|
+
const claim = await tx
|
|
1331
|
+
.select()
|
|
1332
|
+
.from(chatGenerationTerminalOutbox)
|
|
1333
|
+
.where(eq(chatGenerationTerminalOutbox.id, input.outboxId))
|
|
1334
|
+
.limit(1)
|
|
1335
|
+
.then((rows) => rows[0] ?? null);
|
|
1336
|
+
if (!claim
|
|
1337
|
+
|| claim.status !== "claimed"
|
|
1338
|
+
|| claim.claimToken !== input.claimToken
|
|
1339
|
+
|| claim.claimEpoch !== input.claimEpoch) {
|
|
1340
|
+
return null;
|
|
1341
|
+
}
|
|
1342
|
+
const failedActionable = claim.attemptCount >= input.maxAttempts;
|
|
1343
|
+
const [updated] = await tx
|
|
1344
|
+
.update(chatGenerationTerminalOutbox)
|
|
1345
|
+
.set({
|
|
1346
|
+
status: failedActionable ? "failed_actionable" : "retry_wait",
|
|
1347
|
+
claimToken: null,
|
|
1348
|
+
claimOwner: null,
|
|
1349
|
+
leaseExpiresAt: null,
|
|
1350
|
+
availableAt: failedActionable ? now : input.retryAt,
|
|
1351
|
+
lastError: input.error,
|
|
1352
|
+
updatedAt: now,
|
|
1353
|
+
})
|
|
1354
|
+
.where(and(eq(chatGenerationTerminalOutbox.id, input.outboxId), eq(chatGenerationTerminalOutbox.status, "claimed"), eq(chatGenerationTerminalOutbox.claimToken, input.claimToken), eq(chatGenerationTerminalOutbox.claimEpoch, input.claimEpoch)))
|
|
1355
|
+
.returning();
|
|
1356
|
+
if (!updated || !failedActionable)
|
|
1357
|
+
return updated ?? null;
|
|
1358
|
+
await tx
|
|
1359
|
+
.update(chatGenerations)
|
|
1360
|
+
.set({
|
|
1361
|
+
status: "failed",
|
|
1362
|
+
terminalReason: "terminal_projection_failed_actionable",
|
|
1363
|
+
controlState: "terminal",
|
|
1364
|
+
controlOwnerToken: null,
|
|
1365
|
+
controlLeaseExpiresAt: null,
|
|
1366
|
+
completedAt: now,
|
|
1367
|
+
updatedAt: now,
|
|
1368
|
+
})
|
|
1369
|
+
.where(and(eq(chatGenerations.id, generation.id), inArray(chatGenerations.status, ["closing", "stopping"])));
|
|
1370
|
+
const deliveredSteers = await tx
|
|
1371
|
+
.update(chatQueuedMessages)
|
|
1372
|
+
.set({
|
|
1373
|
+
status: "delivered",
|
|
1374
|
+
deliveryDisposition: "delivered",
|
|
1375
|
+
reconciliationReason: null,
|
|
1376
|
+
lastDeliveryReason: null,
|
|
1377
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1378
|
+
updatedAt: now,
|
|
1379
|
+
})
|
|
1380
|
+
.where(and(or(eq(chatQueuedMessages.status, "accepted_current"), and(eq(chatQueuedMessages.status, "steer_pending"), inArray(chatQueuedMessages.controlActionId, tx
|
|
1381
|
+
.select({ id: chatControlActions.id })
|
|
1382
|
+
.from(chatControlActions)
|
|
1383
|
+
.where(eq(chatControlActions.providerDisposition, "acknowledged"))))), or(eq(chatQueuedMessages.expectedGenerationId, claim.generationId), eq(chatQueuedMessages.activeGenerationId, claim.generationId))))
|
|
1384
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
1385
|
+
const deliveredControlActionIds = deliveredSteers
|
|
1386
|
+
.map((item) => item.controlActionId)
|
|
1387
|
+
.filter((id) => Boolean(id));
|
|
1388
|
+
if (deliveredControlActionIds.length > 0) {
|
|
1389
|
+
await tx
|
|
1390
|
+
.update(chatControlActions)
|
|
1391
|
+
.set({
|
|
1392
|
+
localDisposition: "delivered",
|
|
1393
|
+
lastError: null,
|
|
1394
|
+
resolvedAt: now,
|
|
1395
|
+
updatedAt: now,
|
|
1396
|
+
})
|
|
1397
|
+
.where(inArray(chatControlActions.id, deliveredControlActionIds));
|
|
1398
|
+
}
|
|
1399
|
+
const uncertainSteers = await tx
|
|
1400
|
+
.update(chatQueuedMessages)
|
|
1401
|
+
.set({
|
|
1402
|
+
status: "acceptance_unknown",
|
|
1403
|
+
deliveryDisposition: "acceptance_unknown",
|
|
1404
|
+
reconciliationReason: "terminal_projection_failed_provider_receipt_unknown",
|
|
1405
|
+
lastDeliveryReason: "terminal_projection_failed_provider_receipt_unknown",
|
|
1406
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1407
|
+
updatedAt: now,
|
|
1408
|
+
})
|
|
1409
|
+
.where(and(eq(chatQueuedMessages.status, "steer_pending"), inArray(chatQueuedMessages.controlActionId, tx
|
|
1410
|
+
.select({ id: chatControlActions.id })
|
|
1411
|
+
.from(chatControlActions)
|
|
1412
|
+
.where(inArray(chatControlActions.providerDisposition, [
|
|
1413
|
+
"sent",
|
|
1414
|
+
"timed_out",
|
|
1415
|
+
"connection_lost",
|
|
1416
|
+
"waiting_safe_boundary",
|
|
1417
|
+
"unverified",
|
|
1418
|
+
]))), or(eq(chatQueuedMessages.expectedGenerationId, claim.generationId), eq(chatQueuedMessages.activeGenerationId, claim.generationId))))
|
|
1419
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
1420
|
+
const uncertainControlActionIds = uncertainSteers
|
|
1421
|
+
.map((item) => item.controlActionId)
|
|
1422
|
+
.filter((id) => Boolean(id));
|
|
1423
|
+
if (uncertainControlActionIds.length > 0) {
|
|
1424
|
+
await tx
|
|
1425
|
+
.update(chatControlActions)
|
|
1426
|
+
.set({
|
|
1427
|
+
localDisposition: "acceptance_unknown",
|
|
1428
|
+
lastError: "terminal_projection_failed_provider_receipt_unknown",
|
|
1429
|
+
resolvedAt: now,
|
|
1430
|
+
updatedAt: now,
|
|
1431
|
+
})
|
|
1432
|
+
.where(inArray(chatControlActions.id, uncertainControlActionIds));
|
|
1433
|
+
}
|
|
1434
|
+
const failedSteers = await tx
|
|
1435
|
+
.update(chatQueuedMessages)
|
|
1436
|
+
.set({
|
|
1437
|
+
status: "failed_actionable",
|
|
1438
|
+
deliveryDisposition: "failed_actionable",
|
|
1439
|
+
reconciliationReason: "terminal_projection_failed_actionable",
|
|
1440
|
+
lastDeliveryReason: "terminal_projection_failed_actionable",
|
|
1441
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1442
|
+
updatedAt: now,
|
|
1443
|
+
})
|
|
1444
|
+
.where(and(inArray(chatQueuedMessages.status, ["steer_pending", "continuation_pending"]), or(eq(chatQueuedMessages.expectedGenerationId, claim.generationId), eq(chatQueuedMessages.activeGenerationId, claim.generationId))))
|
|
1445
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
1446
|
+
const failedControlActionIds = failedSteers
|
|
1447
|
+
.map((item) => item.controlActionId)
|
|
1448
|
+
.filter((id) => Boolean(id));
|
|
1449
|
+
if (failedControlActionIds.length > 0) {
|
|
1450
|
+
await tx
|
|
1451
|
+
.update(chatControlActions)
|
|
1452
|
+
.set({
|
|
1453
|
+
localDisposition: "failed_actionable",
|
|
1454
|
+
lastError: "terminal_projection_failed_actionable",
|
|
1455
|
+
resolvedAt: now,
|
|
1456
|
+
updatedAt: now,
|
|
1457
|
+
})
|
|
1458
|
+
.where(inArray(chatControlActions.id, failedControlActionIds));
|
|
1459
|
+
}
|
|
1460
|
+
const controlActionId = typeof claim.payload.controlActionId === "string"
|
|
1461
|
+
? claim.payload.controlActionId
|
|
1462
|
+
: null;
|
|
1463
|
+
if (controlActionId) {
|
|
1464
|
+
await tx
|
|
1465
|
+
.update(chatControlActions)
|
|
1466
|
+
.set({
|
|
1467
|
+
localDisposition: "failed_actionable",
|
|
1468
|
+
lastError: input.error,
|
|
1469
|
+
resolvedAt: now,
|
|
1470
|
+
updatedAt: now,
|
|
1471
|
+
})
|
|
1472
|
+
.where(and(eq(chatControlActions.id, controlActionId), eq(chatControlActions.orgId, claim.orgId), eq(chatControlActions.actionKind, "stop")));
|
|
1473
|
+
}
|
|
1474
|
+
return updated;
|
|
1475
|
+
});
|
|
1476
|
+
}
|
|
1477
|
+
async function recoverStaleControlOwners(input) {
|
|
1478
|
+
const now = input.now ?? new Date();
|
|
1479
|
+
const candidates = await db
|
|
1480
|
+
.select({ id: chatGenerations.id, orgId: chatGenerations.orgId })
|
|
1481
|
+
.from(chatGenerations)
|
|
1482
|
+
.where(and(inArray(chatGenerations.status, RECOVERABLE_CONTROL_GENERATION_STATUSES), isNotNull(chatGenerations.controlOwnerToken), input.assumeAllOwnersStale
|
|
1483
|
+
? sql `true`
|
|
1484
|
+
: and(isNotNull(chatGenerations.controlLeaseExpiresAt), lte(chatGenerations.controlLeaseExpiresAt, now))))
|
|
1485
|
+
.orderBy(asc(chatGenerations.updatedAt))
|
|
1486
|
+
.limit(Math.max(1, input.limit ?? 100));
|
|
1487
|
+
const recovered = [];
|
|
1488
|
+
for (const candidate of candidates) {
|
|
1489
|
+
const result = await db.transaction(async (tx) => {
|
|
1490
|
+
const generation = await lockGeneration(tx, {
|
|
1491
|
+
orgId: candidate.orgId,
|
|
1492
|
+
generationId: candidate.id,
|
|
1493
|
+
});
|
|
1494
|
+
if (!RECOVERABLE_CONTROL_GENERATION_STATUSES.includes(generation.status)
|
|
1495
|
+
|| !generation.controlOwnerToken
|
|
1496
|
+
|| (!input.assumeAllOwnersStale
|
|
1497
|
+
&& (!generation.controlLeaseExpiresAt || generation.controlLeaseExpiresAt > now))) {
|
|
1498
|
+
return null;
|
|
1499
|
+
}
|
|
1500
|
+
const nextControlVersion = generation.controlVersion + 1;
|
|
1501
|
+
const payload = {
|
|
1502
|
+
attemptEpoch: generation.attemptEpoch,
|
|
1503
|
+
finalStatus: "control_lost",
|
|
1504
|
+
terminalReason: "control_owner_stale",
|
|
1505
|
+
runtimeTerminationVerified: false,
|
|
1506
|
+
staleOwnerToken: generation.controlOwnerToken,
|
|
1507
|
+
staleAttemptEpoch: generation.attemptEpoch,
|
|
1508
|
+
};
|
|
1509
|
+
const event = await appendEventLocked(tx, {
|
|
1510
|
+
orgId: generation.orgId,
|
|
1511
|
+
generationId: generation.id,
|
|
1512
|
+
attemptEpoch: generation.attemptEpoch,
|
|
1513
|
+
eventKind: "terminal_projection_requested",
|
|
1514
|
+
payload,
|
|
1515
|
+
});
|
|
1516
|
+
const [outbox] = await tx
|
|
1517
|
+
.insert(chatGenerationTerminalOutbox)
|
|
1518
|
+
.values({
|
|
1519
|
+
orgId: generation.orgId,
|
|
1520
|
+
generationId: generation.id,
|
|
1521
|
+
sourceEventId: event.id,
|
|
1522
|
+
projectionVersion: nextControlVersion,
|
|
1523
|
+
expectedControlVersion: nextControlVersion,
|
|
1524
|
+
payload,
|
|
1525
|
+
availableAt: now,
|
|
1526
|
+
createdAt: now,
|
|
1527
|
+
updatedAt: now,
|
|
1528
|
+
})
|
|
1529
|
+
.returning();
|
|
1530
|
+
if (!outbox)
|
|
1531
|
+
throw new Error("Failed to enqueue stale chat control recovery");
|
|
1532
|
+
const [updatedGeneration] = await tx
|
|
1533
|
+
.update(chatGenerations)
|
|
1534
|
+
.set({
|
|
1535
|
+
status: "control_lost",
|
|
1536
|
+
terminalReason: "control_owner_stale",
|
|
1537
|
+
controlState: "control_lost",
|
|
1538
|
+
controlVersion: nextControlVersion,
|
|
1539
|
+
controlOwnerToken: null,
|
|
1540
|
+
controlLeaseExpiresAt: null,
|
|
1541
|
+
completedAt: now,
|
|
1542
|
+
updatedAt: now,
|
|
1543
|
+
})
|
|
1544
|
+
.where(and(eq(chatGenerations.id, generation.id), eq(chatGenerations.controlVersion, generation.controlVersion)))
|
|
1545
|
+
.returning();
|
|
1546
|
+
if (!updatedGeneration)
|
|
1547
|
+
throw conflict("Stale chat control owner changed during recovery");
|
|
1548
|
+
return { generation: updatedGeneration, event, outbox };
|
|
1549
|
+
});
|
|
1550
|
+
if (result)
|
|
1551
|
+
recovered.push(result);
|
|
1552
|
+
}
|
|
1553
|
+
return recovered;
|
|
1554
|
+
}
|
|
1555
|
+
return {
|
|
1556
|
+
getLatestVisibleCheckpoint,
|
|
1557
|
+
appendGenerationEvent,
|
|
1558
|
+
appendVisibleEventAndProject,
|
|
1559
|
+
getFrozenVisibleProjection,
|
|
1560
|
+
recordClientCheckpoint,
|
|
1561
|
+
beginStopAction,
|
|
1562
|
+
beginSteerFallbackCutoff,
|
|
1563
|
+
recordRuntimeTerminal,
|
|
1564
|
+
claimTerminalProjection,
|
|
1565
|
+
getNextTerminalProjectionWakeAt,
|
|
1566
|
+
completeTerminalProjection,
|
|
1567
|
+
retryTerminalProjection,
|
|
1568
|
+
recoverStaleControlOwners,
|
|
1569
|
+
};
|
|
1570
|
+
}
|
|
1571
|
+
//# sourceMappingURL=chat-generation-protocol.js.map
|