@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
package/dist/services/chats.js
CHANGED
|
@@ -1,16 +1,65 @@
|
|
|
1
|
-
import { agentIntegrationChatBindings, agentIntegrations, approvals, assets, chatAttachments, chatContextLinks, chatConversations, chatConversationUserStates, chatGenerations, chatMessages, chatQueuedMessages, messengerCustomGroupEntries, messengerCustomGroups } from "@rudderhq/db";
|
|
2
|
-
import { MESSENGER_FORK_GROUP_DEFAULT_ICON, sanitizeChatStructuredPayload } from "@rudderhq/shared";
|
|
1
|
+
import { agentIntegrationChatBindings, agentIntegrations, approvals, assets, chatAttachments, chatContextLinks, chatControlActions, chatConversations, chatConversationUserStates, chatGenerationEvents, chatGenerations, chatMessages, chatQueuedMessages, messengerCustomGroupEntries, messengerCustomGroups } from "@rudderhq/db";
|
|
2
|
+
import { chatInlineVisualMappingsFromStructuredPayload, MESSENGER_FORK_GROUP_DEFAULT_ICON, sanitizeChatStructuredPayload } from "@rudderhq/shared";
|
|
3
3
|
import { and, asc, desc, eq, gt, gte, inArray, isNull, lt, or, sql } from "drizzle-orm";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
6
6
|
import { logActivity } from "./activity-log.js";
|
|
7
7
|
import { agentService } from "./agents.js";
|
|
8
8
|
import { approvalService } from "./approvals.js";
|
|
9
|
+
import { chatGenerationProtocolService } from "./chat-generation-protocol.js";
|
|
9
10
|
import { issueApprovalService } from "./issue-approvals.js";
|
|
10
11
|
import { issueService } from "./issues.js";
|
|
11
12
|
import { normalizeLocalLibraryPathMarkdown } from "./library-path-markdown.js";
|
|
12
13
|
import { organizationService } from "./orgs.js";
|
|
13
14
|
import { isPostgresError } from "./postgres-errors.js";
|
|
15
|
+
const CHAT_TITLE_MAX_LENGTH = 200;
|
|
16
|
+
const FORK_TITLE_SUFFIX_PATTERN = / \(([1-9]\d*)\)$/;
|
|
17
|
+
const ACTIVE_CHAT_GENERATION_STATUSES = [
|
|
18
|
+
"starting",
|
|
19
|
+
"active",
|
|
20
|
+
"running",
|
|
21
|
+
"tool_busy",
|
|
22
|
+
"closing",
|
|
23
|
+
"stop_requested",
|
|
24
|
+
"stopping",
|
|
25
|
+
];
|
|
26
|
+
const NATIVE_STEER_GENERATION_STATUSES = ["starting", "active", "running", "tool_busy"];
|
|
27
|
+
const SERVER_QUEUE_RUNNING_STATUSES = ["dequeue_claimed", "running_next"];
|
|
28
|
+
const CHAT_GENERATION_CONTROL_LEASE_MS = 30_000;
|
|
29
|
+
function baseForkTitle(title, sourceIsFork, familyTitles) {
|
|
30
|
+
const trimmed = title.trim() || "Forked conversation";
|
|
31
|
+
if (!sourceIsFork)
|
|
32
|
+
return trimmed;
|
|
33
|
+
const match = trimmed.match(FORK_TITLE_SUFFIX_PATTERN);
|
|
34
|
+
const index = Number(match?.[1]);
|
|
35
|
+
if (!match || index < 2)
|
|
36
|
+
return trimmed;
|
|
37
|
+
for (const familyTitle of familyTitles) {
|
|
38
|
+
const candidateBase = familyTitle.trim();
|
|
39
|
+
if (!candidateBase || candidateBase === trimmed)
|
|
40
|
+
continue;
|
|
41
|
+
if (numberedForkTitle(candidateBase, index) !== trimmed)
|
|
42
|
+
continue;
|
|
43
|
+
if (index === 2 || familyTitles.has(numberedForkTitle(candidateBase, index - 1))) {
|
|
44
|
+
return candidateBase;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return trimmed;
|
|
48
|
+
}
|
|
49
|
+
function numberedForkTitle(baseTitle, index) {
|
|
50
|
+
const suffix = ` (${index})`;
|
|
51
|
+
const availableBaseLength = CHAT_TITLE_MAX_LENGTH - suffix.length;
|
|
52
|
+
const truncatedBase = baseTitle.slice(0, availableBaseLength).trimEnd() || "Forked conversation";
|
|
53
|
+
return `${truncatedBase}${suffix}`;
|
|
54
|
+
}
|
|
55
|
+
function nextForkTitle(source, familyTitles) {
|
|
56
|
+
const existingTitles = new Set(familyTitles);
|
|
57
|
+
const baseTitle = baseForkTitle(source.title, Boolean(source.forkedFromConversationId), existingTitles);
|
|
58
|
+
let index = 2;
|
|
59
|
+
while (existingTitles.has(numberedForkTitle(baseTitle, index)))
|
|
60
|
+
index += 1;
|
|
61
|
+
return numberedForkTitle(baseTitle, index);
|
|
62
|
+
}
|
|
14
63
|
function conversationMutability(row, sourceMetadata, sourceMetadataByConversationId) {
|
|
15
64
|
if (sourceMetadata)
|
|
16
65
|
return "external_bound_chat";
|
|
@@ -22,6 +71,7 @@ function conversationMutability(row, sourceMetadata, sourceMetadataByConversatio
|
|
|
22
71
|
}
|
|
23
72
|
import { buildSearchSnippet, CHAT_TRANSCRIPT_KEY, chatTranscriptFromPayload, chatTranscriptSummaryFromEntries, contentPath, escapeLikePattern, incomingMessagePreviewSql, issueProposalFromPayload, isVisibleIncomingChatMessage, listContextLinksForConversationIds, listPrimaryIssues, operationProposalDecisionStatusFromPayload, operationProposalFromPayload, resolveContextEntities, safeTrim, stripChatMetadataFromPayload, textContains, truncatePreview, visibleIncomingMessageSql, withOperationProposalDecisionState, withPersistedTranscript, } from "./chats.helpers.js";
|
|
24
73
|
export function chatService(db) {
|
|
74
|
+
const generationProtocol = chatGenerationProtocolService(db);
|
|
25
75
|
const QUEUED_MESSAGE_CLAIM_LEASE_MS = 2 * 60 * 1000;
|
|
26
76
|
const issuesSvc = issueService(db);
|
|
27
77
|
const approvalsSvc = approvalService(db);
|
|
@@ -388,8 +438,11 @@ export function chatService(db) {
|
|
|
388
438
|
.orderBy(chatAttachments.createdAt);
|
|
389
439
|
const map = new Map();
|
|
390
440
|
for (const row of rows) {
|
|
441
|
+
const safeRow = row.contentType === "text/html" && row.createdByAgentId
|
|
442
|
+
? (({ provider: _provider, objectKey: _objectKey, ...safe }) => safe)(row)
|
|
443
|
+
: row;
|
|
391
444
|
const attachment = {
|
|
392
|
-
...
|
|
445
|
+
...safeRow,
|
|
393
446
|
contentPath: contentPath(row.assetId),
|
|
394
447
|
};
|
|
395
448
|
const list = map.get(row.messageId);
|
|
@@ -438,12 +491,18 @@ export function chatService(db) {
|
|
|
438
491
|
};
|
|
439
492
|
}
|
|
440
493
|
async function createGeneration(orgId, conversationId) {
|
|
494
|
+
const now = new Date();
|
|
441
495
|
const [row] = await db
|
|
442
496
|
.insert(chatGenerations)
|
|
443
497
|
.values({
|
|
444
498
|
orgId,
|
|
445
499
|
conversationId,
|
|
446
500
|
status: "active",
|
|
501
|
+
controlOwnerToken: randomUUID(),
|
|
502
|
+
controlLeaseExpiresAt: new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS),
|
|
503
|
+
startedAt: now,
|
|
504
|
+
createdAt: now,
|
|
505
|
+
updatedAt: now,
|
|
447
506
|
})
|
|
448
507
|
.returning();
|
|
449
508
|
if (!row)
|
|
@@ -454,23 +513,81 @@ export function chatService(db) {
|
|
|
454
513
|
if (!generationId)
|
|
455
514
|
return null;
|
|
456
515
|
const now = new Date();
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
516
|
+
return db.transaction(async (tx) => {
|
|
517
|
+
const [row] = await tx
|
|
518
|
+
.update(chatGenerations)
|
|
519
|
+
.set({
|
|
520
|
+
status,
|
|
521
|
+
terminalReason: status,
|
|
522
|
+
controlState: "terminal",
|
|
523
|
+
runtimeTerminalAt: now,
|
|
524
|
+
completedAt: now,
|
|
525
|
+
updatedAt: now,
|
|
526
|
+
})
|
|
527
|
+
.where(and(eq(chatGenerations.id, generationId), inArray(chatGenerations.status, ACTIVE_CHAT_GENERATION_STATUSES)))
|
|
528
|
+
.returning();
|
|
529
|
+
if (!row)
|
|
530
|
+
return null;
|
|
531
|
+
const deliveredSteers = await tx
|
|
532
|
+
.update(chatQueuedMessages)
|
|
533
|
+
.set({
|
|
534
|
+
status: "delivered",
|
|
535
|
+
deliveryDisposition: "delivered",
|
|
536
|
+
reconciliationReason: null,
|
|
537
|
+
lastDeliveryReason: null,
|
|
538
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
539
|
+
updatedAt: now,
|
|
540
|
+
})
|
|
541
|
+
.where(and(eq(chatQueuedMessages.status, "accepted_current"), or(eq(chatQueuedMessages.expectedGenerationId, generationId), eq(chatQueuedMessages.activeGenerationId, generationId))))
|
|
542
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
543
|
+
const controlActionIds = deliveredSteers
|
|
544
|
+
.map((item) => item.controlActionId)
|
|
545
|
+
.filter((id) => Boolean(id));
|
|
546
|
+
if (controlActionIds.length > 0) {
|
|
547
|
+
await tx
|
|
548
|
+
.update(chatControlActions)
|
|
549
|
+
.set({
|
|
550
|
+
localDisposition: "delivered",
|
|
551
|
+
resolvedAt: now,
|
|
552
|
+
updatedAt: now,
|
|
553
|
+
})
|
|
554
|
+
.where(and(inArray(chatControlActions.id, controlActionIds), eq(chatControlActions.localDisposition, "accepted_current")));
|
|
555
|
+
}
|
|
556
|
+
const continuedSteers = await tx
|
|
557
|
+
.update(chatQueuedMessages)
|
|
558
|
+
.set({
|
|
559
|
+
status: "continuation_pending",
|
|
560
|
+
deliveryDisposition: "continuation_pending",
|
|
561
|
+
reconciliationReason: `target_generation_${status}`,
|
|
562
|
+
lastDeliveryReason: null,
|
|
563
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
564
|
+
updatedAt: now,
|
|
565
|
+
})
|
|
566
|
+
.where(and(eq(chatQueuedMessages.status, "steer_pending"), or(eq(chatQueuedMessages.expectedGenerationId, generationId), eq(chatQueuedMessages.activeGenerationId, generationId))))
|
|
567
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
568
|
+
const continuedControlActionIds = continuedSteers
|
|
569
|
+
.map((item) => item.controlActionId)
|
|
570
|
+
.filter((id) => Boolean(id));
|
|
571
|
+
if (continuedControlActionIds.length > 0) {
|
|
572
|
+
await tx
|
|
573
|
+
.update(chatControlActions)
|
|
574
|
+
.set({
|
|
575
|
+
localDisposition: "continuation_pending",
|
|
576
|
+
providerDisposition: "not_sent",
|
|
577
|
+
lastError: `target_generation_${status}`,
|
|
578
|
+
resolvedAt: now,
|
|
579
|
+
updatedAt: now,
|
|
580
|
+
})
|
|
581
|
+
.where(and(inArray(chatControlActions.id, continuedControlActionIds), eq(chatControlActions.localDisposition, "pending")));
|
|
582
|
+
}
|
|
583
|
+
return row;
|
|
584
|
+
});
|
|
468
585
|
}
|
|
469
586
|
async function getLatestActiveGeneration(conversationId) {
|
|
470
587
|
return db
|
|
471
588
|
.select()
|
|
472
589
|
.from(chatGenerations)
|
|
473
|
-
.where(and(eq(chatGenerations.conversationId, conversationId), inArray(chatGenerations.status,
|
|
590
|
+
.where(and(eq(chatGenerations.conversationId, conversationId), inArray(chatGenerations.status, ACTIVE_CHAT_GENERATION_STATUSES)))
|
|
474
591
|
.orderBy(desc(chatGenerations.startedAt), desc(chatGenerations.createdAt))
|
|
475
592
|
.limit(1)
|
|
476
593
|
.then((rows) => rows[0] ?? null);
|
|
@@ -489,7 +606,16 @@ export function chatService(db) {
|
|
|
489
606
|
const rows = await db
|
|
490
607
|
.select()
|
|
491
608
|
.from(chatQueuedMessages)
|
|
492
|
-
.where(and(eq(chatQueuedMessages.conversationId, conversationId), inArray(chatQueuedMessages.status, [
|
|
609
|
+
.where(and(eq(chatQueuedMessages.conversationId, conversationId), inArray(chatQueuedMessages.status, [
|
|
610
|
+
"queued",
|
|
611
|
+
"steer_pending",
|
|
612
|
+
"acceptance_unknown",
|
|
613
|
+
"continuation_pending",
|
|
614
|
+
"dequeue_claimed",
|
|
615
|
+
"running",
|
|
616
|
+
"running_next",
|
|
617
|
+
"failed_actionable",
|
|
618
|
+
])))
|
|
493
619
|
.orderBy(asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt));
|
|
494
620
|
return rows.map(hydrateQueuedMessage);
|
|
495
621
|
}
|
|
@@ -497,13 +623,673 @@ export function chatService(db) {
|
|
|
497
623
|
const activeGeneration = activeGenerationId === undefined
|
|
498
624
|
? await getLatestActiveGeneration(conversationId)
|
|
499
625
|
: activeGenerationId
|
|
500
|
-
?
|
|
626
|
+
? await db
|
|
627
|
+
.select()
|
|
628
|
+
.from(chatGenerations)
|
|
629
|
+
.where(and(eq(chatGenerations.id, activeGenerationId), eq(chatGenerations.conversationId, conversationId)))
|
|
630
|
+
.limit(1)
|
|
631
|
+
.then((rows) => rows[0] ?? null)
|
|
501
632
|
: null;
|
|
502
633
|
return {
|
|
503
634
|
activeGenerationId: activeGeneration?.id ?? null,
|
|
635
|
+
activeAttemptEpoch: activeGeneration?.attemptEpoch ?? null,
|
|
636
|
+
activeControlVersion: activeGeneration?.controlVersion ?? null,
|
|
637
|
+
activeGenerationStatus: activeGeneration?.status ?? null,
|
|
504
638
|
items: await listQueuedMessages(conversationId),
|
|
505
639
|
};
|
|
506
640
|
}
|
|
641
|
+
async function beginGenerationControlAttempt(input) {
|
|
642
|
+
const now = new Date();
|
|
643
|
+
const leaseExpiresAt = new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS);
|
|
644
|
+
const [row] = await db
|
|
645
|
+
.update(chatGenerations)
|
|
646
|
+
.set({
|
|
647
|
+
attemptEpoch: input.attemptEpoch,
|
|
648
|
+
controlState: "unregistered",
|
|
649
|
+
controlRuntimeType: input.runtimeType,
|
|
650
|
+
controlOwnerToken: input.ownerToken,
|
|
651
|
+
controlLeaseExpiresAt: leaseExpiresAt,
|
|
652
|
+
providerThreadId: null,
|
|
653
|
+
providerTurnId: null,
|
|
654
|
+
status: "starting",
|
|
655
|
+
updatedAt: now,
|
|
656
|
+
})
|
|
657
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId), isNull(chatGenerations.runtimeTerminalAt), inArray(chatGenerations.status, ["active", "starting", "running", "tool_busy", "closing"]), sql `${chatGenerations.attemptEpoch} <= ${input.attemptEpoch}`))
|
|
658
|
+
.returning();
|
|
659
|
+
if (!row)
|
|
660
|
+
throw conflict("Chat generation control ownership changed before runtime startup");
|
|
661
|
+
return row;
|
|
662
|
+
}
|
|
663
|
+
async function markGenerationControlReady(input) {
|
|
664
|
+
const now = new Date();
|
|
665
|
+
const [row] = await db
|
|
666
|
+
.update(chatGenerations)
|
|
667
|
+
.set({
|
|
668
|
+
controlState: "ready",
|
|
669
|
+
controlRuntimeType: input.runtimeType,
|
|
670
|
+
providerThreadId: input.providerThreadId ?? null,
|
|
671
|
+
providerTurnId: input.providerTurnId ?? null,
|
|
672
|
+
controlLeaseExpiresAt: new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS),
|
|
673
|
+
status: "running",
|
|
674
|
+
updatedAt: now,
|
|
675
|
+
})
|
|
676
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.attemptEpoch, input.attemptEpoch), eq(chatGenerations.controlOwnerToken, input.ownerToken), isNull(chatGenerations.runtimeTerminalAt), inArray(chatGenerations.status, ["active", "starting", "running", "tool_busy"])))
|
|
677
|
+
.returning();
|
|
678
|
+
if (!row)
|
|
679
|
+
throw conflict("Chat runtime control handle lost its generation lease");
|
|
680
|
+
return row;
|
|
681
|
+
}
|
|
682
|
+
async function renewGenerationControlLease(input) {
|
|
683
|
+
const now = input.now ?? new Date();
|
|
684
|
+
const [row] = await db
|
|
685
|
+
.update(chatGenerations)
|
|
686
|
+
.set({
|
|
687
|
+
controlLeaseExpiresAt: new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS),
|
|
688
|
+
updatedAt: now,
|
|
689
|
+
})
|
|
690
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.attemptEpoch, input.attemptEpoch), eq(chatGenerations.controlOwnerToken, input.ownerToken), isNull(chatGenerations.runtimeTerminalAt), inArray(chatGenerations.status, ["active", "starting", "running", "tool_busy", "closing"])))
|
|
691
|
+
.returning({ id: chatGenerations.id });
|
|
692
|
+
if (!row)
|
|
693
|
+
throw conflict("Chat generation control lease is no longer current");
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
async function markGenerationControlAttemptCompleted(input) {
|
|
697
|
+
const now = new Date();
|
|
698
|
+
return db.transaction(async (tx) => {
|
|
699
|
+
const [row] = await tx
|
|
700
|
+
.update(chatGenerations)
|
|
701
|
+
.set({
|
|
702
|
+
controlState: "unregistered",
|
|
703
|
+
status: "closing",
|
|
704
|
+
updatedAt: now,
|
|
705
|
+
})
|
|
706
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.attemptEpoch, input.attemptEpoch), eq(chatGenerations.controlOwnerToken, input.ownerToken), inArray(chatGenerations.status, ["active", "starting", "running", "tool_busy", "closing"])))
|
|
707
|
+
.returning();
|
|
708
|
+
if (!row)
|
|
709
|
+
return null;
|
|
710
|
+
const pendingSteers = await tx
|
|
711
|
+
.select({
|
|
712
|
+
id: chatQueuedMessages.id,
|
|
713
|
+
controlActionId: chatQueuedMessages.controlActionId,
|
|
714
|
+
})
|
|
715
|
+
.from(chatQueuedMessages)
|
|
716
|
+
.where(and(eq(chatQueuedMessages.status, "steer_pending"), eq(chatQueuedMessages.activeGenerationId, input.generationId), eq(chatQueuedMessages.attemptEpoch, input.attemptEpoch)));
|
|
717
|
+
const pendingItemIds = pendingSteers.map((item) => item.id).sort();
|
|
718
|
+
const controlActionIds = pendingSteers
|
|
719
|
+
.map((item) => item.controlActionId)
|
|
720
|
+
.filter((id) => Boolean(id))
|
|
721
|
+
.sort();
|
|
722
|
+
if (controlActionIds.length > 0) {
|
|
723
|
+
await tx
|
|
724
|
+
.select({ id: chatControlActions.id })
|
|
725
|
+
.from(chatControlActions)
|
|
726
|
+
.where(inArray(chatControlActions.id, controlActionIds))
|
|
727
|
+
.orderBy(asc(chatControlActions.id))
|
|
728
|
+
.for("update");
|
|
729
|
+
}
|
|
730
|
+
if (pendingItemIds.length > 0) {
|
|
731
|
+
await tx
|
|
732
|
+
.select({ id: chatQueuedMessages.id })
|
|
733
|
+
.from(chatQueuedMessages)
|
|
734
|
+
.where(inArray(chatQueuedMessages.id, pendingItemIds))
|
|
735
|
+
.orderBy(asc(chatQueuedMessages.id))
|
|
736
|
+
.for("update");
|
|
737
|
+
}
|
|
738
|
+
const uncertainSteers = pendingItemIds.length === 0
|
|
739
|
+
? []
|
|
740
|
+
: await tx
|
|
741
|
+
.update(chatQueuedMessages)
|
|
742
|
+
.set({
|
|
743
|
+
status: "acceptance_unknown",
|
|
744
|
+
deliveryDisposition: "acceptance_unknown",
|
|
745
|
+
reconciliationReason: "runtime_attempt_completed_after_steer_send",
|
|
746
|
+
lastDeliveryReason: "runtime_attempt_completed_after_steer_send",
|
|
747
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
748
|
+
updatedAt: now,
|
|
749
|
+
})
|
|
750
|
+
.where(and(inArray(chatQueuedMessages.id, pendingItemIds), eq(chatQueuedMessages.status, "steer_pending"), inArray(chatQueuedMessages.controlActionId, tx
|
|
751
|
+
.select({ id: chatControlActions.id })
|
|
752
|
+
.from(chatControlActions)
|
|
753
|
+
.where(inArray(chatControlActions.providerDisposition, [
|
|
754
|
+
"sent",
|
|
755
|
+
"timed_out",
|
|
756
|
+
"connection_lost",
|
|
757
|
+
"waiting_safe_boundary",
|
|
758
|
+
"unverified",
|
|
759
|
+
])))))
|
|
760
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
761
|
+
const uncertainControlActionIds = uncertainSteers
|
|
762
|
+
.map((item) => item.controlActionId)
|
|
763
|
+
.filter((id) => Boolean(id));
|
|
764
|
+
if (uncertainControlActionIds.length > 0) {
|
|
765
|
+
await tx
|
|
766
|
+
.update(chatControlActions)
|
|
767
|
+
.set({
|
|
768
|
+
localDisposition: "acceptance_unknown",
|
|
769
|
+
providerDisposition: "unverified",
|
|
770
|
+
lastError: "runtime_attempt_completed_after_steer_send",
|
|
771
|
+
resolvedAt: now,
|
|
772
|
+
updatedAt: now,
|
|
773
|
+
})
|
|
774
|
+
.where(and(inArray(chatControlActions.id, uncertainControlActionIds), eq(chatControlActions.localDisposition, "pending")));
|
|
775
|
+
}
|
|
776
|
+
const continuedSteers = pendingItemIds.length === 0
|
|
777
|
+
? []
|
|
778
|
+
: await tx
|
|
779
|
+
.update(chatQueuedMessages)
|
|
780
|
+
.set({
|
|
781
|
+
status: "continuation_pending",
|
|
782
|
+
deliveryDisposition: "continuation_pending",
|
|
783
|
+
reconciliationReason: "runtime_attempt_completed_without_steer_acceptance",
|
|
784
|
+
lastDeliveryReason: null,
|
|
785
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
786
|
+
updatedAt: now,
|
|
787
|
+
})
|
|
788
|
+
.where(and(inArray(chatQueuedMessages.id, pendingItemIds), eq(chatQueuedMessages.status, "steer_pending"), or(isNull(chatQueuedMessages.controlActionId), inArray(chatQueuedMessages.controlActionId, tx
|
|
789
|
+
.select({ id: chatControlActions.id })
|
|
790
|
+
.from(chatControlActions)
|
|
791
|
+
.where(or(eq(chatControlActions.providerDisposition, "not_sent"), eq(chatControlActions.providerDisposition, "rejected"), isNull(chatControlActions.providerDisposition)))))))
|
|
792
|
+
.returning({ controlActionId: chatQueuedMessages.controlActionId });
|
|
793
|
+
const continuedControlActionIds = continuedSteers
|
|
794
|
+
.map((item) => item.controlActionId)
|
|
795
|
+
.filter((id) => Boolean(id));
|
|
796
|
+
if (continuedControlActionIds.length > 0) {
|
|
797
|
+
await tx
|
|
798
|
+
.update(chatControlActions)
|
|
799
|
+
.set({
|
|
800
|
+
localDisposition: "continuation_pending",
|
|
801
|
+
providerDisposition: "not_sent",
|
|
802
|
+
lastError: "runtime_attempt_completed_without_steer_acceptance",
|
|
803
|
+
resolvedAt: now,
|
|
804
|
+
updatedAt: now,
|
|
805
|
+
})
|
|
806
|
+
.where(and(inArray(chatControlActions.id, continuedControlActionIds), eq(chatControlActions.localDisposition, "pending")));
|
|
807
|
+
}
|
|
808
|
+
return row;
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
async function appendGenerationEvent(input) {
|
|
812
|
+
return db.transaction(async (tx) => {
|
|
813
|
+
await tx.execute(sql `select id from ${chatGenerations} where ${chatGenerations.id} = ${input.generationId} and ${chatGenerations.orgId} = ${input.orgId} for update`);
|
|
814
|
+
const generation = await tx
|
|
815
|
+
.select({ id: chatGenerations.id })
|
|
816
|
+
.from(chatGenerations)
|
|
817
|
+
.where(and(eq(chatGenerations.id, input.generationId), eq(chatGenerations.orgId, input.orgId)))
|
|
818
|
+
.limit(1)
|
|
819
|
+
.then((rows) => rows[0] ?? null);
|
|
820
|
+
if (!generation)
|
|
821
|
+
throw notFound("Chat generation not found");
|
|
822
|
+
const nextSeq = await tx
|
|
823
|
+
.select({ value: sql `coalesce(max(${chatGenerationEvents.generationSeq}), 0) + 1` })
|
|
824
|
+
.from(chatGenerationEvents)
|
|
825
|
+
.where(eq(chatGenerationEvents.generationId, input.generationId))
|
|
826
|
+
.then((rows) => Number(rows[0]?.value ?? 1));
|
|
827
|
+
const [event] = await tx
|
|
828
|
+
.insert(chatGenerationEvents)
|
|
829
|
+
.values({
|
|
830
|
+
orgId: input.orgId,
|
|
831
|
+
generationId: input.generationId,
|
|
832
|
+
generationSeq: nextSeq,
|
|
833
|
+
attemptEpoch: input.attemptEpoch,
|
|
834
|
+
eventKind: input.eventKind,
|
|
835
|
+
payload: input.payload ?? {},
|
|
836
|
+
controlActionId: input.controlActionId ?? null,
|
|
837
|
+
queueItemId: input.queueItemId ?? null,
|
|
838
|
+
emittedAt: input.emittedAt ?? null,
|
|
839
|
+
})
|
|
840
|
+
.returning();
|
|
841
|
+
if (!event)
|
|
842
|
+
throw new Error("Failed to append chat generation event");
|
|
843
|
+
return event;
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
async function beginSteerControlAction(input) {
|
|
847
|
+
return db.transaction(async (tx) => {
|
|
848
|
+
const existingAction = await tx
|
|
849
|
+
.select()
|
|
850
|
+
.from(chatControlActions)
|
|
851
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId)))
|
|
852
|
+
.limit(1)
|
|
853
|
+
.then((rows) => rows[0] ?? null);
|
|
854
|
+
if (existingAction) {
|
|
855
|
+
const existingItem = await tx
|
|
856
|
+
.select()
|
|
857
|
+
.from(chatQueuedMessages)
|
|
858
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.controlActionId, existingAction.id)))
|
|
859
|
+
.limit(1)
|
|
860
|
+
.then((rows) => rows[0] ?? null);
|
|
861
|
+
const existingGeneration = existingAction.expectedGenerationId
|
|
862
|
+
? await tx
|
|
863
|
+
.select()
|
|
864
|
+
.from(chatGenerations)
|
|
865
|
+
.where(eq(chatGenerations.id, existingAction.expectedGenerationId))
|
|
866
|
+
.limit(1)
|
|
867
|
+
.then((rows) => rows[0] ?? null)
|
|
868
|
+
: null;
|
|
869
|
+
if (!existingItem || existingAction.actionKind !== "steer") {
|
|
870
|
+
throw conflict("Control action id was already used for a different operation");
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
action: existingAction,
|
|
874
|
+
item: hydrateQueuedMessage(existingItem),
|
|
875
|
+
generation: existingGeneration,
|
|
876
|
+
idempotent: true,
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
await tx.execute(sql `select id from ${chatGenerations} where ${chatGenerations.id} = ${input.expectedGenerationId} for update`);
|
|
880
|
+
await tx.execute(sql `select id from ${chatQueuedMessages} where ${chatQueuedMessages.id} = ${input.itemId} for update`);
|
|
881
|
+
const generation = await tx
|
|
882
|
+
.select()
|
|
883
|
+
.from(chatGenerations)
|
|
884
|
+
.where(and(eq(chatGenerations.id, input.expectedGenerationId), eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId)))
|
|
885
|
+
.limit(1)
|
|
886
|
+
.then((rows) => rows[0] ?? null);
|
|
887
|
+
const generationAcceptsNativeSteer = Boolean(generation && NATIVE_STEER_GENERATION_STATUSES.includes(generation.status));
|
|
888
|
+
const item = await tx
|
|
889
|
+
.select()
|
|
890
|
+
.from(chatQueuedMessages)
|
|
891
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), inArray(chatQueuedMessages.status, ["queued", "steer_pending"])))
|
|
892
|
+
.limit(1)
|
|
893
|
+
.then((rows) => rows[0] ?? null);
|
|
894
|
+
if (!item)
|
|
895
|
+
throw conflict("Queued feedback is no longer steerable");
|
|
896
|
+
if (item.status === "steer_pending") {
|
|
897
|
+
const boundAction = item.controlActionId
|
|
898
|
+
? await tx
|
|
899
|
+
.select()
|
|
900
|
+
.from(chatControlActions)
|
|
901
|
+
.where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
902
|
+
.limit(1)
|
|
903
|
+
.then((rows) => rows[0] ?? null)
|
|
904
|
+
: null;
|
|
905
|
+
if (!boundAction) {
|
|
906
|
+
throw conflict("Queued feedback has an unresolved Steer action");
|
|
907
|
+
}
|
|
908
|
+
const boundGeneration = boundAction.expectedGenerationId
|
|
909
|
+
? await tx
|
|
910
|
+
.select()
|
|
911
|
+
.from(chatGenerations)
|
|
912
|
+
.where(and(eq(chatGenerations.id, boundAction.expectedGenerationId), eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId)))
|
|
913
|
+
.limit(1)
|
|
914
|
+
.then((rows) => rows[0] ?? null)
|
|
915
|
+
: null;
|
|
916
|
+
return {
|
|
917
|
+
action: boundAction,
|
|
918
|
+
item: hydrateQueuedMessage(item),
|
|
919
|
+
generation: boundGeneration,
|
|
920
|
+
idempotent: true,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
if (generationAcceptsNativeSteer && generation && (generation.attemptEpoch !== input.expectedAttemptEpoch
|
|
924
|
+
|| generation.controlVersion !== input.expectedControlVersion)) {
|
|
925
|
+
throw conflict("The targeted chat generation control version changed");
|
|
926
|
+
}
|
|
927
|
+
const now = new Date();
|
|
928
|
+
if (!generationAcceptsNativeSteer) {
|
|
929
|
+
const [action] = await tx
|
|
930
|
+
.insert(chatControlActions)
|
|
931
|
+
.values({
|
|
932
|
+
id: input.controlActionId,
|
|
933
|
+
orgId: input.orgId,
|
|
934
|
+
expectedGenerationId: generation?.id ?? null,
|
|
935
|
+
expectedAttemptEpoch: generation?.attemptEpoch ?? null,
|
|
936
|
+
expectedControlVersion: generation?.controlVersion ?? null,
|
|
937
|
+
actionKind: "steer",
|
|
938
|
+
localDisposition: "continuation_pending",
|
|
939
|
+
providerDisposition: "not_sent",
|
|
940
|
+
providerClientMessageId: input.controlActionId,
|
|
941
|
+
resolvedAt: now,
|
|
942
|
+
})
|
|
943
|
+
.returning();
|
|
944
|
+
if (!action)
|
|
945
|
+
throw new Error("Failed to persist Steer continuation action");
|
|
946
|
+
const [updatedItem] = await tx
|
|
947
|
+
.update(chatQueuedMessages)
|
|
948
|
+
.set({
|
|
949
|
+
status: "continuation_pending",
|
|
950
|
+
deliveryIntent: "steer",
|
|
951
|
+
deliveryDisposition: "continuation_pending",
|
|
952
|
+
controlActionId: action.id,
|
|
953
|
+
requestActor: item.requestActor ?? input.requestActor ?? null,
|
|
954
|
+
activeGenerationId: generation?.id ?? item.activeGenerationId,
|
|
955
|
+
attemptEpoch: generation?.attemptEpoch ?? item.attemptEpoch,
|
|
956
|
+
providerClientMessageId: action.providerClientMessageId,
|
|
957
|
+
reconciliationReason: generation ? "target_generation_terminal" : "target_generation_missing",
|
|
958
|
+
lastDeliveryReason: null,
|
|
959
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
960
|
+
updatedAt: now,
|
|
961
|
+
})
|
|
962
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
963
|
+
.returning();
|
|
964
|
+
if (!updatedItem)
|
|
965
|
+
throw conflict("Queued feedback changed while continuation was being scheduled");
|
|
966
|
+
return {
|
|
967
|
+
action,
|
|
968
|
+
item: hydrateQueuedMessage(updatedItem),
|
|
969
|
+
generation,
|
|
970
|
+
idempotent: false,
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
if (!generation)
|
|
974
|
+
throw new Error("Expected a native Steer generation");
|
|
975
|
+
const appliedControlVersion = generation.controlVersion + 1;
|
|
976
|
+
const [action] = await tx
|
|
977
|
+
.insert(chatControlActions)
|
|
978
|
+
.values({
|
|
979
|
+
id: input.controlActionId,
|
|
980
|
+
orgId: input.orgId,
|
|
981
|
+
expectedGenerationId: generation.id,
|
|
982
|
+
expectedAttemptEpoch: generation.attemptEpoch,
|
|
983
|
+
expectedControlVersion: generation.controlVersion,
|
|
984
|
+
appliedControlVersion,
|
|
985
|
+
actionKind: "steer",
|
|
986
|
+
localDisposition: "pending",
|
|
987
|
+
providerDisposition: "not_sent",
|
|
988
|
+
controlOwnerToken: generation.controlOwnerToken,
|
|
989
|
+
providerClientMessageId: input.controlActionId,
|
|
990
|
+
})
|
|
991
|
+
.returning();
|
|
992
|
+
if (!action)
|
|
993
|
+
throw new Error("Failed to create chat Steer control action");
|
|
994
|
+
const [updatedGeneration] = await tx
|
|
995
|
+
.update(chatGenerations)
|
|
996
|
+
.set({ controlVersion: appliedControlVersion, updatedAt: now })
|
|
997
|
+
.where(and(eq(chatGenerations.id, generation.id), eq(chatGenerations.controlVersion, generation.controlVersion), eq(chatGenerations.attemptEpoch, generation.attemptEpoch)))
|
|
998
|
+
.returning();
|
|
999
|
+
if (!updatedGeneration)
|
|
1000
|
+
throw conflict("The targeted chat generation control version changed");
|
|
1001
|
+
const [updatedItem] = await tx
|
|
1002
|
+
.update(chatQueuedMessages)
|
|
1003
|
+
.set({
|
|
1004
|
+
status: "steer_pending",
|
|
1005
|
+
deliveryIntent: "steer",
|
|
1006
|
+
deliveryDisposition: "pending",
|
|
1007
|
+
controlActionId: action.id,
|
|
1008
|
+
requestActor: item.requestActor ?? input.requestActor ?? null,
|
|
1009
|
+
activeGenerationId: generation.id,
|
|
1010
|
+
attemptEpoch: generation.attemptEpoch,
|
|
1011
|
+
providerClientMessageId: action.providerClientMessageId,
|
|
1012
|
+
deliveryAttempts: sql `${chatQueuedMessages.deliveryAttempts} + 1`,
|
|
1013
|
+
lastAttemptAt: now,
|
|
1014
|
+
lastDeliveryReason: null,
|
|
1015
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1016
|
+
updatedAt: now,
|
|
1017
|
+
})
|
|
1018
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
1019
|
+
.returning();
|
|
1020
|
+
if (!updatedItem)
|
|
1021
|
+
throw conflict("Queued feedback changed while Steer was being accepted");
|
|
1022
|
+
return {
|
|
1023
|
+
action,
|
|
1024
|
+
item: hydrateQueuedMessage(updatedItem),
|
|
1025
|
+
generation: updatedGeneration,
|
|
1026
|
+
idempotent: false,
|
|
1027
|
+
};
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
async function resolveSteerControlAction(input) {
|
|
1031
|
+
const now = new Date();
|
|
1032
|
+
return db.transaction(async (tx) => {
|
|
1033
|
+
await tx.execute(sql `
|
|
1034
|
+
select ${chatControlActions.id}
|
|
1035
|
+
from ${chatControlActions}
|
|
1036
|
+
where ${chatControlActions.id} = ${input.controlActionId}
|
|
1037
|
+
and ${chatControlActions.orgId} = ${input.orgId}
|
|
1038
|
+
for update
|
|
1039
|
+
`);
|
|
1040
|
+
await tx.execute(sql `
|
|
1041
|
+
select ${chatQueuedMessages.id}
|
|
1042
|
+
from ${chatQueuedMessages}
|
|
1043
|
+
where ${chatQueuedMessages.id} = ${input.itemId}
|
|
1044
|
+
and ${chatQueuedMessages.orgId} = ${input.orgId}
|
|
1045
|
+
and ${chatQueuedMessages.conversationId} = ${input.conversationId}
|
|
1046
|
+
for update
|
|
1047
|
+
`);
|
|
1048
|
+
const existingAction = await tx
|
|
1049
|
+
.select()
|
|
1050
|
+
.from(chatControlActions)
|
|
1051
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
1052
|
+
.limit(1)
|
|
1053
|
+
.then((rows) => rows[0] ?? null);
|
|
1054
|
+
const existingItem = await tx
|
|
1055
|
+
.select()
|
|
1056
|
+
.from(chatQueuedMessages)
|
|
1057
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.controlActionId, input.controlActionId)))
|
|
1058
|
+
.limit(1)
|
|
1059
|
+
.then((rows) => rows[0] ?? null);
|
|
1060
|
+
if (!existingAction)
|
|
1061
|
+
throw notFound("Chat Steer control action not found");
|
|
1062
|
+
if (!existingItem)
|
|
1063
|
+
throw notFound("Queued Steer feedback not found");
|
|
1064
|
+
const allowedCurrentStatuses = input.status === "accepted_current"
|
|
1065
|
+
? ["steer_pending", "acceptance_unknown", "accepted_current"]
|
|
1066
|
+
: input.status === "acceptance_unknown"
|
|
1067
|
+
? ["steer_pending", "acceptance_unknown"]
|
|
1068
|
+
: input.status === "continuation_pending"
|
|
1069
|
+
? ["steer_pending", "acceptance_unknown", "continuation_pending"]
|
|
1070
|
+
: input.status === "steer_pending"
|
|
1071
|
+
? ["steer_pending"]
|
|
1072
|
+
: ["steer_pending", "acceptance_unknown", "continuation_pending", "failed_actionable"];
|
|
1073
|
+
const transitionAllowed = allowedCurrentStatuses.includes(existingItem.status);
|
|
1074
|
+
const preserveAcknowledgement = existingAction.providerDisposition === "acknowledged"
|
|
1075
|
+
&& input.providerDisposition !== "acknowledged";
|
|
1076
|
+
const providerDisposition = preserveAcknowledgement
|
|
1077
|
+
? existingAction.providerDisposition
|
|
1078
|
+
: input.providerDisposition;
|
|
1079
|
+
const [action] = await tx
|
|
1080
|
+
.update(chatControlActions)
|
|
1081
|
+
.set({
|
|
1082
|
+
localDisposition: transitionAllowed ? input.disposition : existingAction.localDisposition,
|
|
1083
|
+
providerDisposition,
|
|
1084
|
+
providerThreadId: input.providerThreadId ?? existingAction.providerThreadId,
|
|
1085
|
+
providerTurnId: input.providerTurnId ?? existingAction.providerTurnId,
|
|
1086
|
+
providerEvidence: input.providerEvidence ?? existingAction.providerEvidence,
|
|
1087
|
+
lastError: transitionAllowed ? input.reason ?? null : existingAction.lastError,
|
|
1088
|
+
providerSentAt: providerDisposition === "not_sent"
|
|
1089
|
+
? null
|
|
1090
|
+
: existingAction.providerSentAt ?? now,
|
|
1091
|
+
providerAcknowledgedAt: providerDisposition === "acknowledged"
|
|
1092
|
+
? existingAction.providerAcknowledgedAt ?? now
|
|
1093
|
+
: existingAction.providerAcknowledgedAt,
|
|
1094
|
+
resolvedAt: transitionAllowed
|
|
1095
|
+
? input.disposition === "pending" ? null : now
|
|
1096
|
+
: existingAction.resolvedAt,
|
|
1097
|
+
updatedAt: now,
|
|
1098
|
+
})
|
|
1099
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
1100
|
+
.returning();
|
|
1101
|
+
if (!action)
|
|
1102
|
+
throw notFound("Chat Steer control action not found");
|
|
1103
|
+
if (!transitionAllowed) {
|
|
1104
|
+
return { action, item: hydrateQueuedMessage(existingItem), applied: false };
|
|
1105
|
+
}
|
|
1106
|
+
const [item] = await tx
|
|
1107
|
+
.update(chatQueuedMessages)
|
|
1108
|
+
.set({
|
|
1109
|
+
status: input.status,
|
|
1110
|
+
deliveryDisposition: input.disposition,
|
|
1111
|
+
providerThreadId: input.providerThreadId ?? existingItem.providerThreadId,
|
|
1112
|
+
providerTurnId: input.providerTurnId ?? existingItem.providerTurnId,
|
|
1113
|
+
providerEvidence: input.providerEvidence ?? existingItem.providerEvidence,
|
|
1114
|
+
reconciliationReason: input.reason ?? null,
|
|
1115
|
+
lastDeliveryReason: input.reason ?? null,
|
|
1116
|
+
steeredAt: input.status === "accepted_current" ? now : existingItem.steeredAt,
|
|
1117
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1118
|
+
updatedAt: now,
|
|
1119
|
+
})
|
|
1120
|
+
.where(and(eq(chatQueuedMessages.id, existingItem.id), eq(chatQueuedMessages.status, existingItem.status), eq(chatQueuedMessages.version, existingItem.version)))
|
|
1121
|
+
.returning();
|
|
1122
|
+
if (!item)
|
|
1123
|
+
throw conflict("Queued Steer feedback changed while provider evidence was resolving");
|
|
1124
|
+
return { action, item: hydrateQueuedMessage(item), applied: true };
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
async function claimSteerProviderSend(input) {
|
|
1128
|
+
return db.transaction(async (tx) => {
|
|
1129
|
+
const existingAction = await tx
|
|
1130
|
+
.select()
|
|
1131
|
+
.from(chatControlActions)
|
|
1132
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
1133
|
+
.limit(1)
|
|
1134
|
+
.then((rows) => rows[0] ?? null);
|
|
1135
|
+
if (!existingAction
|
|
1136
|
+
|| existingAction.localDisposition !== "pending"
|
|
1137
|
+
|| existingAction.providerDisposition !== "not_sent") {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
const generation = existingAction.expectedGenerationId
|
|
1141
|
+
? await tx
|
|
1142
|
+
.select()
|
|
1143
|
+
.from(chatGenerations)
|
|
1144
|
+
.where(and(eq(chatGenerations.id, existingAction.expectedGenerationId), eq(chatGenerations.orgId, input.orgId)))
|
|
1145
|
+
.limit(1)
|
|
1146
|
+
.for("update")
|
|
1147
|
+
.then((rows) => rows[0] ?? null)
|
|
1148
|
+
: null;
|
|
1149
|
+
const leaseIsCurrent = generation
|
|
1150
|
+
? await tx
|
|
1151
|
+
.select({
|
|
1152
|
+
value: sql `${chatGenerations.controlLeaseExpiresAt} > clock_timestamp()`,
|
|
1153
|
+
})
|
|
1154
|
+
.from(chatGenerations)
|
|
1155
|
+
.where(eq(chatGenerations.id, generation.id))
|
|
1156
|
+
.limit(1)
|
|
1157
|
+
.then((rows) => rows[0]?.value === true)
|
|
1158
|
+
: false;
|
|
1159
|
+
const now = new Date();
|
|
1160
|
+
const generationFenceMatches = Boolean(generation
|
|
1161
|
+
&& NATIVE_STEER_GENERATION_STATUSES.includes(generation.status)
|
|
1162
|
+
&& generation.controlState === "ready"
|
|
1163
|
+
&& generation.runtimeTerminalAt === null
|
|
1164
|
+
&& Boolean(generation.controlOwnerToken)
|
|
1165
|
+
&& generation.attemptEpoch === existingAction.expectedAttemptEpoch
|
|
1166
|
+
&& generation.controlVersion === existingAction.appliedControlVersion
|
|
1167
|
+
&& generation.controlOwnerToken === existingAction.controlOwnerToken
|
|
1168
|
+
&& leaseIsCurrent);
|
|
1169
|
+
if (!generationFenceMatches) {
|
|
1170
|
+
await tx.execute(sql `
|
|
1171
|
+
select ${chatControlActions.id}
|
|
1172
|
+
from ${chatControlActions}
|
|
1173
|
+
where ${chatControlActions.id} = ${input.controlActionId}
|
|
1174
|
+
and ${chatControlActions.orgId} = ${input.orgId}
|
|
1175
|
+
for update
|
|
1176
|
+
`);
|
|
1177
|
+
const queueItem = await tx
|
|
1178
|
+
.select()
|
|
1179
|
+
.from(chatQueuedMessages)
|
|
1180
|
+
.where(and(eq(chatQueuedMessages.controlActionId, input.controlActionId), eq(chatQueuedMessages.orgId, input.orgId)))
|
|
1181
|
+
.limit(1)
|
|
1182
|
+
.for("update")
|
|
1183
|
+
.then((rows) => rows[0] ?? null);
|
|
1184
|
+
if (!queueItem)
|
|
1185
|
+
throw conflict("Queued Steer feedback disappeared before provider send");
|
|
1186
|
+
const reason = generation?.stopRequestedAt
|
|
1187
|
+
|| generation?.status === "stop_requested"
|
|
1188
|
+
|| generation?.status === "stopping"
|
|
1189
|
+
? "stop_cutoff_won_before_provider_send"
|
|
1190
|
+
: "generation_fence_changed_before_provider_send";
|
|
1191
|
+
const [action] = await tx
|
|
1192
|
+
.update(chatControlActions)
|
|
1193
|
+
.set({
|
|
1194
|
+
localDisposition: "continuation_pending",
|
|
1195
|
+
providerDisposition: "not_sent",
|
|
1196
|
+
providerSentAt: null,
|
|
1197
|
+
lastError: reason,
|
|
1198
|
+
resolvedAt: now,
|
|
1199
|
+
updatedAt: now,
|
|
1200
|
+
})
|
|
1201
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer"), eq(chatControlActions.localDisposition, "pending"), eq(chatControlActions.providerDisposition, "not_sent")))
|
|
1202
|
+
.returning();
|
|
1203
|
+
if (!action)
|
|
1204
|
+
return null;
|
|
1205
|
+
const [item] = await tx
|
|
1206
|
+
.update(chatQueuedMessages)
|
|
1207
|
+
.set({
|
|
1208
|
+
status: "continuation_pending",
|
|
1209
|
+
deliveryDisposition: "continuation_pending",
|
|
1210
|
+
reconciliationReason: reason,
|
|
1211
|
+
lastDeliveryReason: null,
|
|
1212
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1213
|
+
updatedAt: now,
|
|
1214
|
+
})
|
|
1215
|
+
.where(and(eq(chatQueuedMessages.id, queueItem.id), eq(chatQueuedMessages.status, "steer_pending"), eq(chatQueuedMessages.version, queueItem.version)))
|
|
1216
|
+
.returning();
|
|
1217
|
+
if (!item)
|
|
1218
|
+
throw conflict("Queued Steer feedback changed before provider send fallback");
|
|
1219
|
+
return {
|
|
1220
|
+
sendDenied: true,
|
|
1221
|
+
reason,
|
|
1222
|
+
action,
|
|
1223
|
+
item: hydrateQueuedMessage(item),
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
const [action] = await tx
|
|
1227
|
+
.update(chatControlActions)
|
|
1228
|
+
.set({
|
|
1229
|
+
providerDisposition: "sent",
|
|
1230
|
+
providerSentAt: now,
|
|
1231
|
+
updatedAt: now,
|
|
1232
|
+
})
|
|
1233
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer"), eq(chatControlActions.localDisposition, "pending"), eq(chatControlActions.providerDisposition, "not_sent"), eq(chatControlActions.expectedGenerationId, generation.id), eq(chatControlActions.expectedAttemptEpoch, generation.attemptEpoch), eq(chatControlActions.appliedControlVersion, generation.controlVersion), eq(chatControlActions.controlOwnerToken, generation.controlOwnerToken)))
|
|
1234
|
+
.returning();
|
|
1235
|
+
return action ?? null;
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
async function releaseSteerProviderSendClaim(input) {
|
|
1239
|
+
const now = new Date();
|
|
1240
|
+
return db.transaction(async (tx) => {
|
|
1241
|
+
await tx.execute(sql `
|
|
1242
|
+
select ${chatControlActions.id}
|
|
1243
|
+
from ${chatControlActions}
|
|
1244
|
+
where ${chatControlActions.id} = ${input.controlActionId}
|
|
1245
|
+
and ${chatControlActions.orgId} = ${input.orgId}
|
|
1246
|
+
for update
|
|
1247
|
+
`);
|
|
1248
|
+
const existingAction = await tx
|
|
1249
|
+
.select()
|
|
1250
|
+
.from(chatControlActions)
|
|
1251
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
1252
|
+
.limit(1)
|
|
1253
|
+
.then((rows) => rows[0] ?? null);
|
|
1254
|
+
if (!existingAction
|
|
1255
|
+
|| existingAction.providerDisposition !== "sent"
|
|
1256
|
+
|| existingAction.providerAcknowledgedAt) {
|
|
1257
|
+
return null;
|
|
1258
|
+
}
|
|
1259
|
+
await tx.execute(sql `
|
|
1260
|
+
select ${chatQueuedMessages.id}
|
|
1261
|
+
from ${chatQueuedMessages}
|
|
1262
|
+
where ${chatQueuedMessages.controlActionId} = ${input.controlActionId}
|
|
1263
|
+
for update
|
|
1264
|
+
`);
|
|
1265
|
+
const [action] = await tx
|
|
1266
|
+
.update(chatControlActions)
|
|
1267
|
+
.set({
|
|
1268
|
+
localDisposition: "pending",
|
|
1269
|
+
providerDisposition: "not_sent",
|
|
1270
|
+
providerSentAt: null,
|
|
1271
|
+
lastError: input.reason,
|
|
1272
|
+
resolvedAt: null,
|
|
1273
|
+
updatedAt: now,
|
|
1274
|
+
})
|
|
1275
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer"), eq(chatControlActions.providerDisposition, "sent"), isNull(chatControlActions.providerAcknowledgedAt)))
|
|
1276
|
+
.returning();
|
|
1277
|
+
if (!action)
|
|
1278
|
+
return null;
|
|
1279
|
+
await tx
|
|
1280
|
+
.update(chatQueuedMessages)
|
|
1281
|
+
.set({
|
|
1282
|
+
status: "steer_pending",
|
|
1283
|
+
deliveryDisposition: "pending",
|
|
1284
|
+
reconciliationReason: input.reason,
|
|
1285
|
+
lastDeliveryReason: input.reason,
|
|
1286
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1287
|
+
updatedAt: now,
|
|
1288
|
+
})
|
|
1289
|
+
.where(and(eq(chatQueuedMessages.controlActionId, input.controlActionId), inArray(chatQueuedMessages.status, ["steer_pending", "acceptance_unknown"])));
|
|
1290
|
+
return action;
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
507
1293
|
async function createQueuedMessage(input) {
|
|
508
1294
|
const payload = {
|
|
509
1295
|
...input.payload,
|
|
@@ -545,6 +1331,7 @@ export function chatService(db) {
|
|
|
545
1331
|
clientMutationId: input.clientMutationId,
|
|
546
1332
|
position: Number(positionRow?.nextPosition ?? 1),
|
|
547
1333
|
payload,
|
|
1334
|
+
requestActor: input.requestActor ?? null,
|
|
548
1335
|
expectedGenerationId: input.expectedGenerationId ?? null,
|
|
549
1336
|
})
|
|
550
1337
|
.returning();
|
|
@@ -610,6 +1397,99 @@ export function chatService(db) {
|
|
|
610
1397
|
throw conflict("Queued message was changed or is no longer cancellable");
|
|
611
1398
|
return hydrateQueuedMessage(row);
|
|
612
1399
|
}
|
|
1400
|
+
async function scheduleSteerContinuation(input) {
|
|
1401
|
+
return db.transaction(async (tx) => {
|
|
1402
|
+
const existingAction = await tx
|
|
1403
|
+
.select()
|
|
1404
|
+
.from(chatControlActions)
|
|
1405
|
+
.where(and(eq(chatControlActions.id, input.controlActionId), eq(chatControlActions.orgId, input.orgId)))
|
|
1406
|
+
.limit(1)
|
|
1407
|
+
.then((rows) => rows[0] ?? null);
|
|
1408
|
+
if (existingAction) {
|
|
1409
|
+
const existingItem = await tx
|
|
1410
|
+
.select()
|
|
1411
|
+
.from(chatQueuedMessages)
|
|
1412
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.controlActionId, existingAction.id)))
|
|
1413
|
+
.limit(1)
|
|
1414
|
+
.then((rows) => rows[0] ?? null);
|
|
1415
|
+
if (!existingItem || existingAction.actionKind !== "steer") {
|
|
1416
|
+
throw conflict("Control action id was already used for a different operation");
|
|
1417
|
+
}
|
|
1418
|
+
return {
|
|
1419
|
+
action: existingAction,
|
|
1420
|
+
item: hydrateQueuedMessage(existingItem),
|
|
1421
|
+
idempotent: true,
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
await tx.execute(sql `select id from ${chatQueuedMessages} where ${chatQueuedMessages.id} = ${input.itemId} for update`);
|
|
1425
|
+
const item = await tx
|
|
1426
|
+
.select()
|
|
1427
|
+
.from(chatQueuedMessages)
|
|
1428
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), inArray(chatQueuedMessages.status, ["queued", "steer_pending", "continuation_pending"])))
|
|
1429
|
+
.limit(1)
|
|
1430
|
+
.then((rows) => rows[0] ?? null);
|
|
1431
|
+
if (!item)
|
|
1432
|
+
throw conflict("Queued feedback is no longer schedulable");
|
|
1433
|
+
const targetGenerationId = item.expectedGenerationId ?? item.activeGenerationId;
|
|
1434
|
+
const targetGeneration = targetGenerationId
|
|
1435
|
+
? await tx
|
|
1436
|
+
.select()
|
|
1437
|
+
.from(chatGenerations)
|
|
1438
|
+
.where(and(eq(chatGenerations.id, targetGenerationId), eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId)))
|
|
1439
|
+
.limit(1)
|
|
1440
|
+
.then((rows) => rows[0] ?? null)
|
|
1441
|
+
: await tx
|
|
1442
|
+
.select()
|
|
1443
|
+
.from(chatGenerations)
|
|
1444
|
+
.where(and(eq(chatGenerations.orgId, input.orgId), eq(chatGenerations.conversationId, input.conversationId)))
|
|
1445
|
+
.orderBy(desc(chatGenerations.startedAt), desc(chatGenerations.createdAt))
|
|
1446
|
+
.limit(1)
|
|
1447
|
+
.then((rows) => rows[0] ?? null);
|
|
1448
|
+
const now = new Date();
|
|
1449
|
+
const [action] = await tx
|
|
1450
|
+
.insert(chatControlActions)
|
|
1451
|
+
.values({
|
|
1452
|
+
id: input.controlActionId,
|
|
1453
|
+
orgId: input.orgId,
|
|
1454
|
+
expectedGenerationId: targetGeneration?.id ?? null,
|
|
1455
|
+
expectedAttemptEpoch: targetGeneration?.attemptEpoch ?? null,
|
|
1456
|
+
expectedControlVersion: targetGeneration?.controlVersion ?? null,
|
|
1457
|
+
actionKind: "steer",
|
|
1458
|
+
localDisposition: "continuation_pending",
|
|
1459
|
+
providerDisposition: "not_sent",
|
|
1460
|
+
providerClientMessageId: input.controlActionId,
|
|
1461
|
+
resolvedAt: now,
|
|
1462
|
+
})
|
|
1463
|
+
.returning();
|
|
1464
|
+
if (!action)
|
|
1465
|
+
throw new Error("Failed to persist Steer continuation action");
|
|
1466
|
+
const [updatedItem] = await tx
|
|
1467
|
+
.update(chatQueuedMessages)
|
|
1468
|
+
.set({
|
|
1469
|
+
status: "continuation_pending",
|
|
1470
|
+
deliveryIntent: "steer",
|
|
1471
|
+
deliveryDisposition: "continuation_pending",
|
|
1472
|
+
controlActionId: action.id,
|
|
1473
|
+
requestActor: item.requestActor ?? input.requestActor ?? null,
|
|
1474
|
+
activeGenerationId: targetGeneration?.id ?? item.activeGenerationId,
|
|
1475
|
+
attemptEpoch: targetGeneration?.attemptEpoch ?? item.attemptEpoch,
|
|
1476
|
+
providerClientMessageId: action.providerClientMessageId,
|
|
1477
|
+
reconciliationReason: targetGeneration ? "target_generation_terminal" : "no_active_generation",
|
|
1478
|
+
lastDeliveryReason: null,
|
|
1479
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1480
|
+
updatedAt: now,
|
|
1481
|
+
})
|
|
1482
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
1483
|
+
.returning();
|
|
1484
|
+
if (!updatedItem)
|
|
1485
|
+
throw conflict("Queued feedback changed while continuation was being scheduled");
|
|
1486
|
+
return {
|
|
1487
|
+
action,
|
|
1488
|
+
item: hydrateQueuedMessage(updatedItem),
|
|
1489
|
+
idempotent: false,
|
|
1490
|
+
};
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
613
1493
|
async function markQueuedMessageSteerFallback(input) {
|
|
614
1494
|
const now = new Date();
|
|
615
1495
|
const [row] = await db
|
|
@@ -629,6 +1509,342 @@ export function chatService(db) {
|
|
|
629
1509
|
throw conflict("Queued message was changed or is no longer steerable");
|
|
630
1510
|
return hydrateQueuedMessage(row);
|
|
631
1511
|
}
|
|
1512
|
+
async function claimNextServerQueuedMessage(input) {
|
|
1513
|
+
const now = input.now ?? new Date();
|
|
1514
|
+
const leaseExpiresAt = new Date(now.getTime() + input.leaseMs);
|
|
1515
|
+
return db.transaction(async (tx) => {
|
|
1516
|
+
const batchSize = 25;
|
|
1517
|
+
let scanOffset = 0;
|
|
1518
|
+
while (true) {
|
|
1519
|
+
const candidates = await tx
|
|
1520
|
+
.select()
|
|
1521
|
+
.from(chatQueuedMessages)
|
|
1522
|
+
.where(and(inArray(chatQueuedMessages.status, ["continuation_pending", "steer_pending", "queued"]), sql `jsonb_typeof(${chatQueuedMessages.requestActor}) = 'object'`, or(isNull(chatQueuedMessages.deliveryLeaseExpiresAt), lt(chatQueuedMessages.deliveryLeaseExpiresAt, now)), sql `${chatQueuedMessages.status} <> 'steer_pending' or coalesce((
|
|
1523
|
+
select steer_action.provider_disposition
|
|
1524
|
+
from ${chatControlActions} as steer_action
|
|
1525
|
+
where steer_action.id = ${chatQueuedMessages.controlActionId}
|
|
1526
|
+
limit 1
|
|
1527
|
+
), 'not_sent') in ('not_sent', 'rejected')`))
|
|
1528
|
+
.orderBy(sql `case
|
|
1529
|
+
when ${chatQueuedMessages.status} = 'continuation_pending' then 0
|
|
1530
|
+
when ${chatQueuedMessages.status} = 'steer_pending' then 1
|
|
1531
|
+
else 2
|
|
1532
|
+
end`, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
|
|
1533
|
+
.limit(batchSize)
|
|
1534
|
+
.offset(scanOffset)
|
|
1535
|
+
.for("update", { skipLocked: true });
|
|
1536
|
+
for (const candidate of candidates) {
|
|
1537
|
+
if (!candidate.requestActor || typeof candidate.requestActor !== "object" || Array.isArray(candidate.requestActor)) {
|
|
1538
|
+
continue;
|
|
1539
|
+
}
|
|
1540
|
+
const latestGeneration = await tx
|
|
1541
|
+
.select()
|
|
1542
|
+
.from(chatGenerations)
|
|
1543
|
+
.where(and(eq(chatGenerations.orgId, candidate.orgId), eq(chatGenerations.conversationId, candidate.conversationId)))
|
|
1544
|
+
.orderBy(desc(chatGenerations.startedAt), desc(chatGenerations.createdAt))
|
|
1545
|
+
.limit(1)
|
|
1546
|
+
.then((rows) => rows[0] ?? null);
|
|
1547
|
+
const isSteer = candidate.deliveryIntent === "steer"
|
|
1548
|
+
|| candidate.status === "steer_pending"
|
|
1549
|
+
|| candidate.status === "continuation_pending";
|
|
1550
|
+
if (latestGeneration
|
|
1551
|
+
&& ACTIVE_CHAT_GENERATION_STATUSES.some((status) => status === latestGeneration.status)) {
|
|
1552
|
+
continue;
|
|
1553
|
+
}
|
|
1554
|
+
if (candidate.status === "queued"
|
|
1555
|
+
&& latestGeneration
|
|
1556
|
+
&& latestGeneration.status !== "completed") {
|
|
1557
|
+
continue;
|
|
1558
|
+
}
|
|
1559
|
+
if (isSteer
|
|
1560
|
+
&& latestGeneration
|
|
1561
|
+
&& ["failed", "control_lost", "interrupted_unverified"].includes(latestGeneration.status)) {
|
|
1562
|
+
await tx
|
|
1563
|
+
.update(chatQueuedMessages)
|
|
1564
|
+
.set({
|
|
1565
|
+
status: "failed_actionable",
|
|
1566
|
+
deliveryDisposition: "failed_actionable",
|
|
1567
|
+
reconciliationReason: `prior_generation_${latestGeneration.status}`,
|
|
1568
|
+
lastDeliveryReason: `prior_generation_${latestGeneration.status}`,
|
|
1569
|
+
updatedAt: now,
|
|
1570
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1571
|
+
})
|
|
1572
|
+
.where(eq(chatQueuedMessages.id, candidate.id));
|
|
1573
|
+
if (candidate.controlActionId) {
|
|
1574
|
+
await tx
|
|
1575
|
+
.update(chatControlActions)
|
|
1576
|
+
.set({
|
|
1577
|
+
localDisposition: "failed_actionable",
|
|
1578
|
+
lastError: `prior_generation_${latestGeneration.status}`,
|
|
1579
|
+
resolvedAt: now,
|
|
1580
|
+
updatedAt: now,
|
|
1581
|
+
})
|
|
1582
|
+
.where(eq(chatControlActions.id, candidate.controlActionId));
|
|
1583
|
+
}
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
const generationId = randomUUID();
|
|
1587
|
+
const leaseToken = randomUUID();
|
|
1588
|
+
const leaseEpoch = candidate.deliveryLeaseEpoch + 1;
|
|
1589
|
+
await tx.insert(chatGenerations).values({
|
|
1590
|
+
id: generationId,
|
|
1591
|
+
orgId: candidate.orgId,
|
|
1592
|
+
conversationId: candidate.conversationId,
|
|
1593
|
+
status: "active",
|
|
1594
|
+
attemptEpoch: 0,
|
|
1595
|
+
controlVersion: 0,
|
|
1596
|
+
controlState: "unregistered",
|
|
1597
|
+
controlOwnerToken: leaseToken,
|
|
1598
|
+
controlLeaseExpiresAt: new Date(now.getTime() + CHAT_GENERATION_CONTROL_LEASE_MS),
|
|
1599
|
+
startedAt: now,
|
|
1600
|
+
createdAt: now,
|
|
1601
|
+
updatedAt: now,
|
|
1602
|
+
});
|
|
1603
|
+
let userMessageId = candidate.continuationMessageId;
|
|
1604
|
+
if (userMessageId) {
|
|
1605
|
+
const existingMessage = await tx
|
|
1606
|
+
.select({ id: chatMessages.id })
|
|
1607
|
+
.from(chatMessages)
|
|
1608
|
+
.where(and(eq(chatMessages.id, userMessageId), eq(chatMessages.orgId, candidate.orgId), eq(chatMessages.conversationId, candidate.conversationId), eq(chatMessages.role, "user")))
|
|
1609
|
+
.limit(1)
|
|
1610
|
+
.then((rows) => rows[0] ?? null);
|
|
1611
|
+
if (!existingMessage)
|
|
1612
|
+
userMessageId = null;
|
|
1613
|
+
}
|
|
1614
|
+
if (!userMessageId) {
|
|
1615
|
+
userMessageId = randomUUID();
|
|
1616
|
+
await tx.insert(chatMessages).values({
|
|
1617
|
+
id: userMessageId,
|
|
1618
|
+
orgId: candidate.orgId,
|
|
1619
|
+
conversationId: candidate.conversationId,
|
|
1620
|
+
role: "user",
|
|
1621
|
+
kind: "message",
|
|
1622
|
+
status: "completed",
|
|
1623
|
+
body: normalizeQueuedPayload(candidate.payload).body,
|
|
1624
|
+
structuredPayload: null,
|
|
1625
|
+
chatTurnId: randomUUID(),
|
|
1626
|
+
turnVariant: 0,
|
|
1627
|
+
createdAt: now,
|
|
1628
|
+
updatedAt: now,
|
|
1629
|
+
});
|
|
1630
|
+
await tx
|
|
1631
|
+
.update(chatConversations)
|
|
1632
|
+
.set({ lastMessageAt: now, updatedAt: now })
|
|
1633
|
+
.where(and(eq(chatConversations.id, candidate.conversationId), eq(chatConversations.orgId, candidate.orgId)));
|
|
1634
|
+
}
|
|
1635
|
+
const [claimed] = await tx
|
|
1636
|
+
.update(chatQueuedMessages)
|
|
1637
|
+
.set({
|
|
1638
|
+
status: isSteer ? "running_next" : "dequeue_claimed",
|
|
1639
|
+
deliveryDisposition: isSteer ? "running_next" : null,
|
|
1640
|
+
continuationGenerationId: generationId,
|
|
1641
|
+
continuationMessageId: userMessageId,
|
|
1642
|
+
sourceMessageId: userMessageId,
|
|
1643
|
+
deliveredMessageId: userMessageId,
|
|
1644
|
+
deliveryLeaseToken: leaseToken,
|
|
1645
|
+
deliveryLeaseEpoch: leaseEpoch,
|
|
1646
|
+
deliveryLeaseOwner: input.workerId,
|
|
1647
|
+
deliveryLeaseExpiresAt: leaseExpiresAt,
|
|
1648
|
+
dequeuedAt: now,
|
|
1649
|
+
deliveryAttempts: sql `${chatQueuedMessages.deliveryAttempts} + 1`,
|
|
1650
|
+
lastAttemptAt: now,
|
|
1651
|
+
lastDeliveryReason: null,
|
|
1652
|
+
reconciliationReason: "server_claimed",
|
|
1653
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1654
|
+
updatedAt: now,
|
|
1655
|
+
})
|
|
1656
|
+
.where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.version, candidate.version)))
|
|
1657
|
+
.returning();
|
|
1658
|
+
if (!claimed)
|
|
1659
|
+
throw conflict("Queued continuation changed while being claimed");
|
|
1660
|
+
if (candidate.controlActionId) {
|
|
1661
|
+
await tx
|
|
1662
|
+
.update(chatControlActions)
|
|
1663
|
+
.set({
|
|
1664
|
+
localDisposition: "running_next",
|
|
1665
|
+
resolvedAt: null,
|
|
1666
|
+
updatedAt: now,
|
|
1667
|
+
})
|
|
1668
|
+
.where(and(eq(chatControlActions.id, candidate.controlActionId), eq(chatControlActions.orgId, candidate.orgId)));
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
item: hydrateQueuedMessage(claimed),
|
|
1672
|
+
generationId,
|
|
1673
|
+
userMessageId,
|
|
1674
|
+
leaseToken,
|
|
1675
|
+
leaseEpoch,
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
if (candidates.length < batchSize)
|
|
1679
|
+
return null;
|
|
1680
|
+
scanOffset += candidates.length;
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
async function renewServerQueuedMessageClaim(input) {
|
|
1685
|
+
const now = input.now ?? new Date();
|
|
1686
|
+
const [row] = await db
|
|
1687
|
+
.update(chatQueuedMessages)
|
|
1688
|
+
.set({
|
|
1689
|
+
deliveryLeaseExpiresAt: new Date(now.getTime() + input.leaseMs),
|
|
1690
|
+
updatedAt: now,
|
|
1691
|
+
})
|
|
1692
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.continuationGenerationId, input.generationId), eq(chatQueuedMessages.deliveryLeaseToken, input.leaseToken), eq(chatQueuedMessages.deliveryLeaseEpoch, input.leaseEpoch), inArray(chatQueuedMessages.status, SERVER_QUEUE_RUNNING_STATUSES)))
|
|
1693
|
+
.returning({ id: chatQueuedMessages.id });
|
|
1694
|
+
return Boolean(row);
|
|
1695
|
+
}
|
|
1696
|
+
async function completeServerQueuedMessageDelivery(input) {
|
|
1697
|
+
const now = new Date();
|
|
1698
|
+
return db.transaction(async (tx) => {
|
|
1699
|
+
const item = await tx
|
|
1700
|
+
.select()
|
|
1701
|
+
.from(chatQueuedMessages)
|
|
1702
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.continuationGenerationId, input.generationId), eq(chatQueuedMessages.deliveryLeaseToken, input.leaseToken), eq(chatQueuedMessages.deliveryLeaseEpoch, input.leaseEpoch), inArray(chatQueuedMessages.status, SERVER_QUEUE_RUNNING_STATUSES)))
|
|
1703
|
+
.limit(1)
|
|
1704
|
+
.then((rows) => rows[0] ?? null);
|
|
1705
|
+
if (!item)
|
|
1706
|
+
return null;
|
|
1707
|
+
const isSteer = item.deliveryIntent === "steer" || item.status === "running_next";
|
|
1708
|
+
const succeeded = input.status === "completed";
|
|
1709
|
+
const nextStatus = succeeded
|
|
1710
|
+
? (isSteer ? "delivered" : "completed")
|
|
1711
|
+
: "failed_actionable";
|
|
1712
|
+
const nextDisposition = succeeded
|
|
1713
|
+
? (isSteer ? "delivered" : null)
|
|
1714
|
+
: "failed_actionable";
|
|
1715
|
+
const [updated] = await tx
|
|
1716
|
+
.update(chatQueuedMessages)
|
|
1717
|
+
.set({
|
|
1718
|
+
status: nextStatus,
|
|
1719
|
+
deliveryDisposition: nextDisposition,
|
|
1720
|
+
deliveryLeaseToken: null,
|
|
1721
|
+
deliveryLeaseOwner: null,
|
|
1722
|
+
deliveryLeaseExpiresAt: null,
|
|
1723
|
+
lastDeliveryReason: succeeded ? null : (input.reason ?? input.status),
|
|
1724
|
+
reconciliationReason: succeeded ? null : (input.reason ?? input.status),
|
|
1725
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1726
|
+
updatedAt: now,
|
|
1727
|
+
})
|
|
1728
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
1729
|
+
.returning();
|
|
1730
|
+
if (item.controlActionId) {
|
|
1731
|
+
await tx
|
|
1732
|
+
.update(chatControlActions)
|
|
1733
|
+
.set({
|
|
1734
|
+
localDisposition: nextDisposition ?? (succeeded ? "delivered" : "failed_actionable"),
|
|
1735
|
+
lastError: succeeded ? null : (input.reason ?? input.status),
|
|
1736
|
+
resolvedAt: now,
|
|
1737
|
+
updatedAt: now,
|
|
1738
|
+
})
|
|
1739
|
+
.where(eq(chatControlActions.id, item.controlActionId));
|
|
1740
|
+
}
|
|
1741
|
+
return updated ? hydrateQueuedMessage(updated) : null;
|
|
1742
|
+
});
|
|
1743
|
+
}
|
|
1744
|
+
async function releaseServerQueuedMessageClaim(input) {
|
|
1745
|
+
const now = new Date();
|
|
1746
|
+
return db.transaction(async (tx) => {
|
|
1747
|
+
const item = await tx
|
|
1748
|
+
.select()
|
|
1749
|
+
.from(chatQueuedMessages)
|
|
1750
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.continuationGenerationId, input.generationId), eq(chatQueuedMessages.deliveryLeaseToken, input.leaseToken), eq(chatQueuedMessages.deliveryLeaseEpoch, input.leaseEpoch), inArray(chatQueuedMessages.status, SERVER_QUEUE_RUNNING_STATUSES)))
|
|
1751
|
+
.limit(1)
|
|
1752
|
+
.then((rows) => rows[0] ?? null);
|
|
1753
|
+
if (!item)
|
|
1754
|
+
return null;
|
|
1755
|
+
const generation = await tx
|
|
1756
|
+
.select()
|
|
1757
|
+
.from(chatGenerations)
|
|
1758
|
+
.where(eq(chatGenerations.id, input.generationId))
|
|
1759
|
+
.limit(1)
|
|
1760
|
+
.then((rows) => rows[0] ?? null);
|
|
1761
|
+
const providerMayHaveStarted = Boolean(generation && (generation.attemptEpoch > 0
|
|
1762
|
+
|| generation.status === "starting"
|
|
1763
|
+
|| generation.status === "running"
|
|
1764
|
+
|| generation.status === "tool_busy"
|
|
1765
|
+
|| generation.status === "closing"));
|
|
1766
|
+
const isSteer = item.deliveryIntent === "steer" || item.status === "running_next";
|
|
1767
|
+
const nextStatus = providerMayHaveStarted
|
|
1768
|
+
? (isSteer ? "acceptance_unknown" : "failed_actionable")
|
|
1769
|
+
: (isSteer ? "continuation_pending" : "queued");
|
|
1770
|
+
const nextDisposition = providerMayHaveStarted
|
|
1771
|
+
? (isSteer ? "acceptance_unknown" : "failed_actionable")
|
|
1772
|
+
: (isSteer ? "continuation_pending" : null);
|
|
1773
|
+
const [updated] = await tx
|
|
1774
|
+
.update(chatQueuedMessages)
|
|
1775
|
+
.set({
|
|
1776
|
+
status: nextStatus,
|
|
1777
|
+
deliveryDisposition: nextDisposition,
|
|
1778
|
+
continuationGenerationId: null,
|
|
1779
|
+
deliveryLeaseToken: null,
|
|
1780
|
+
deliveryLeaseOwner: null,
|
|
1781
|
+
deliveryLeaseExpiresAt: null,
|
|
1782
|
+
lastDeliveryReason: input.reason,
|
|
1783
|
+
reconciliationReason: input.reason,
|
|
1784
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1785
|
+
updatedAt: now,
|
|
1786
|
+
})
|
|
1787
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
1788
|
+
.returning();
|
|
1789
|
+
if (generation) {
|
|
1790
|
+
await tx
|
|
1791
|
+
.update(chatGenerations)
|
|
1792
|
+
.set({
|
|
1793
|
+
status: "aborted",
|
|
1794
|
+
terminalReason: input.reason,
|
|
1795
|
+
controlState: providerMayHaveStarted ? "control_lost" : "terminal",
|
|
1796
|
+
runtimeTerminalAt: now,
|
|
1797
|
+
completedAt: now,
|
|
1798
|
+
updatedAt: now,
|
|
1799
|
+
})
|
|
1800
|
+
.where(eq(chatGenerations.id, generation.id));
|
|
1801
|
+
}
|
|
1802
|
+
if (item.controlActionId) {
|
|
1803
|
+
await tx
|
|
1804
|
+
.update(chatControlActions)
|
|
1805
|
+
.set({
|
|
1806
|
+
localDisposition: nextDisposition ?? "pending",
|
|
1807
|
+
providerDisposition: providerMayHaveStarted ? "unverified" : "not_sent",
|
|
1808
|
+
lastError: input.reason,
|
|
1809
|
+
resolvedAt: providerMayHaveStarted ? now : null,
|
|
1810
|
+
updatedAt: now,
|
|
1811
|
+
})
|
|
1812
|
+
.where(eq(chatControlActions.id, item.controlActionId));
|
|
1813
|
+
}
|
|
1814
|
+
return updated ? hydrateQueuedMessage(updated) : null;
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
async function recoverExpiredServerQueueClaims(now = new Date()) {
|
|
1818
|
+
const expired = await db
|
|
1819
|
+
.select({
|
|
1820
|
+
id: chatQueuedMessages.id,
|
|
1821
|
+
generationId: chatQueuedMessages.continuationGenerationId,
|
|
1822
|
+
leaseToken: chatQueuedMessages.deliveryLeaseToken,
|
|
1823
|
+
leaseEpoch: chatQueuedMessages.deliveryLeaseEpoch,
|
|
1824
|
+
})
|
|
1825
|
+
.from(chatQueuedMessages)
|
|
1826
|
+
.where(and(inArray(chatQueuedMessages.status, SERVER_QUEUE_RUNNING_STATUSES), lt(chatQueuedMessages.deliveryLeaseExpiresAt, now)));
|
|
1827
|
+
let requeued = 0;
|
|
1828
|
+
let ambiguous = 0;
|
|
1829
|
+
for (const row of expired) {
|
|
1830
|
+
if (!row.generationId || !row.leaseToken)
|
|
1831
|
+
continue;
|
|
1832
|
+
const recovered = await releaseServerQueuedMessageClaim({
|
|
1833
|
+
itemId: row.id,
|
|
1834
|
+
generationId: row.generationId,
|
|
1835
|
+
leaseToken: row.leaseToken,
|
|
1836
|
+
leaseEpoch: row.leaseEpoch,
|
|
1837
|
+
reason: "server_continuation_lease_expired",
|
|
1838
|
+
});
|
|
1839
|
+
if (!recovered)
|
|
1840
|
+
continue;
|
|
1841
|
+
if (recovered.status === "continuation_pending" || recovered.status === "queued")
|
|
1842
|
+
requeued += 1;
|
|
1843
|
+
else
|
|
1844
|
+
ambiguous += 1;
|
|
1845
|
+
}
|
|
1846
|
+
return { inspected: expired.length, requeued, ambiguous };
|
|
1847
|
+
}
|
|
632
1848
|
async function claimNextQueuedMessage(conversationId) {
|
|
633
1849
|
await reclaimStaleQueuedMessageClaims(conversationId);
|
|
634
1850
|
const now = new Date();
|
|
@@ -770,6 +1986,16 @@ export function chatService(db) {
|
|
|
770
1986
|
if (status !== "all") {
|
|
771
1987
|
conditions.push(eq(chatConversations.status, status));
|
|
772
1988
|
}
|
|
1989
|
+
if (options?.projectId) {
|
|
1990
|
+
conditions.push(sql `EXISTS (
|
|
1991
|
+
SELECT 1
|
|
1992
|
+
FROM ${chatContextLinks}
|
|
1993
|
+
WHERE ${chatContextLinks.conversationId} = ${chatConversations.id}
|
|
1994
|
+
AND ${chatContextLinks.orgId} = ${orgId}
|
|
1995
|
+
AND ${chatContextLinks.entityType} = 'project'
|
|
1996
|
+
AND ${chatContextLinks.entityId} = ${options.projectId}
|
|
1997
|
+
)`);
|
|
1998
|
+
}
|
|
773
1999
|
if (hasSearch) {
|
|
774
2000
|
conditions.push(sql `(
|
|
775
2001
|
${chatConversations.title} ILIKE ${containsPattern} ESCAPE '\\'
|
|
@@ -990,6 +2216,28 @@ export function chatService(db) {
|
|
|
990
2216
|
}
|
|
991
2217
|
async function forkConversation(input) {
|
|
992
2218
|
const created = await db.transaction(async (tx) => {
|
|
2219
|
+
const initialSource = await tx
|
|
2220
|
+
.select()
|
|
2221
|
+
.from(chatConversations)
|
|
2222
|
+
.where(and(eq(chatConversations.id, input.sourceConversationId), eq(chatConversations.orgId, input.orgId)))
|
|
2223
|
+
.then((rows) => rows[0] ?? null);
|
|
2224
|
+
if (!initialSource)
|
|
2225
|
+
throw notFound("Chat conversation not found");
|
|
2226
|
+
const initialRootConversationId = initialSource.forkRootConversationId ?? initialSource.id;
|
|
2227
|
+
await tx.execute(sql `
|
|
2228
|
+
SELECT ${chatConversations.id}
|
|
2229
|
+
FROM ${chatConversations}
|
|
2230
|
+
WHERE ${chatConversations.id} = ${initialRootConversationId}
|
|
2231
|
+
FOR UPDATE
|
|
2232
|
+
`);
|
|
2233
|
+
if (initialSource.id !== initialRootConversationId) {
|
|
2234
|
+
await tx.execute(sql `
|
|
2235
|
+
SELECT ${chatConversations.id}
|
|
2236
|
+
FROM ${chatConversations}
|
|
2237
|
+
WHERE ${chatConversations.id} = ${initialSource.id}
|
|
2238
|
+
FOR UPDATE
|
|
2239
|
+
`);
|
|
2240
|
+
}
|
|
993
2241
|
const source = await tx
|
|
994
2242
|
.select()
|
|
995
2243
|
.from(chatConversations)
|
|
@@ -998,6 +2246,12 @@ export function chatService(db) {
|
|
|
998
2246
|
if (!source)
|
|
999
2247
|
throw notFound("Chat conversation not found");
|
|
1000
2248
|
const rootConversationId = source.forkRootConversationId ?? source.id;
|
|
2249
|
+
const familyTitles = await tx
|
|
2250
|
+
.select({ title: chatConversations.title })
|
|
2251
|
+
.from(chatConversations)
|
|
2252
|
+
.where(and(eq(chatConversations.orgId, input.orgId), or(eq(chatConversations.id, rootConversationId), eq(chatConversations.forkRootConversationId, rootConversationId))))
|
|
2253
|
+
.then((rows) => rows.map((row) => row.title));
|
|
2254
|
+
const childTitle = input.title?.trim() || nextForkTitle(source, familyTitles);
|
|
1001
2255
|
const messageConditions = [
|
|
1002
2256
|
eq(chatMessages.conversationId, source.id),
|
|
1003
2257
|
eq(chatMessages.orgId, input.orgId),
|
|
@@ -1028,7 +2282,7 @@ export function chatService(db) {
|
|
|
1028
2282
|
.values({
|
|
1029
2283
|
orgId: input.orgId,
|
|
1030
2284
|
status: "active",
|
|
1031
|
-
title:
|
|
2285
|
+
title: childTitle,
|
|
1032
2286
|
summary: source.summary,
|
|
1033
2287
|
preferredAgentId: source.preferredAgentId,
|
|
1034
2288
|
routedAgentId: source.routedAgentId,
|
|
@@ -1073,22 +2327,77 @@ export function chatService(db) {
|
|
|
1073
2327
|
? messagesToCopy.slice(0, messagesToCopy.findIndex((message) => message.id === input.sourceMessageId) + 1)
|
|
1074
2328
|
: messagesToCopy;
|
|
1075
2329
|
if (forkMessages.length > 0) {
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
2330
|
+
for (const message of forkMessages) {
|
|
2331
|
+
const copiedMessageId = randomUUID();
|
|
2332
|
+
const visualMappings = chatInlineVisualMappingsFromStructuredPayload(message.structuredPayload);
|
|
2333
|
+
const readyAttachmentIds = visualMappings
|
|
2334
|
+
.filter((mapping) => mapping.status === "ready")
|
|
2335
|
+
.map((mapping) => mapping.attachmentId);
|
|
2336
|
+
const copiedAttachmentIdBySourceId = new Map();
|
|
2337
|
+
const pendingVisualAttachments = [];
|
|
2338
|
+
if (readyAttachmentIds.length > 0) {
|
|
2339
|
+
const sourceVisualAttachments = await tx
|
|
2340
|
+
.select({
|
|
2341
|
+
id: chatAttachments.id,
|
|
2342
|
+
assetId: chatAttachments.assetId,
|
|
2343
|
+
contentType: assets.contentType,
|
|
2344
|
+
createdByAgentId: assets.createdByAgentId,
|
|
2345
|
+
createdByUserId: assets.createdByUserId,
|
|
2346
|
+
})
|
|
2347
|
+
.from(chatAttachments)
|
|
2348
|
+
.innerJoin(assets, eq(chatAttachments.assetId, assets.id))
|
|
2349
|
+
.where(and(eq(chatAttachments.conversationId, source.id), eq(chatAttachments.messageId, message.id), inArray(chatAttachments.id, readyAttachmentIds)));
|
|
2350
|
+
const safeVisualAttachments = sourceVisualAttachments.filter((attachment) => attachment.contentType === "text/html"
|
|
2351
|
+
&& Boolean(attachment.createdByAgentId)
|
|
2352
|
+
&& !attachment.createdByUserId);
|
|
2353
|
+
if (safeVisualAttachments.length > 0) {
|
|
2354
|
+
pendingVisualAttachments.push(...safeVisualAttachments.map((attachment) => {
|
|
2355
|
+
const copiedAttachmentId = randomUUID();
|
|
2356
|
+
copiedAttachmentIdBySourceId.set(attachment.id, copiedAttachmentId);
|
|
2357
|
+
return {
|
|
2358
|
+
id: copiedAttachmentId,
|
|
2359
|
+
orgId: input.orgId,
|
|
2360
|
+
conversationId: child.id,
|
|
2361
|
+
messageId: copiedMessageId,
|
|
2362
|
+
assetId: attachment.assetId,
|
|
2363
|
+
};
|
|
2364
|
+
}));
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
const copiedVisualMappings = visualMappings.map((mapping) => {
|
|
2368
|
+
if (mapping.status === "unavailable")
|
|
2369
|
+
return mapping;
|
|
2370
|
+
const copiedAttachmentId = copiedAttachmentIdBySourceId.get(mapping.attachmentId);
|
|
2371
|
+
return copiedAttachmentId
|
|
2372
|
+
? { ...mapping, attachmentId: copiedAttachmentId }
|
|
2373
|
+
: {
|
|
2374
|
+
directiveIndex: mapping.directiveIndex,
|
|
2375
|
+
file: mapping.file,
|
|
2376
|
+
status: "unavailable",
|
|
2377
|
+
reason: "fork_source_missing",
|
|
2378
|
+
};
|
|
2379
|
+
});
|
|
2380
|
+
await tx.insert(chatMessages).values({
|
|
2381
|
+
id: copiedMessageId,
|
|
2382
|
+
orgId: input.orgId,
|
|
2383
|
+
conversationId: child.id,
|
|
2384
|
+
role: message.role,
|
|
2385
|
+
kind: message.kind,
|
|
2386
|
+
status: message.status === "streaming" ? "interrupted" : message.status,
|
|
2387
|
+
body: message.body,
|
|
2388
|
+
structuredPayload: copiedVisualMappings.length > 0 ? { inlineVisuals: copiedVisualMappings } : null,
|
|
2389
|
+
approvalId: null,
|
|
2390
|
+
runId: null,
|
|
2391
|
+
replyingAgentId: message.replyingAgentId,
|
|
2392
|
+
chatTurnId: null,
|
|
2393
|
+
turnVariant: 0,
|
|
2394
|
+
createdAt: message.createdAt,
|
|
2395
|
+
updatedAt: message.updatedAt,
|
|
2396
|
+
});
|
|
2397
|
+
if (pendingVisualAttachments.length > 0) {
|
|
2398
|
+
await tx.insert(chatAttachments).values(pendingVisualAttachments);
|
|
2399
|
+
}
|
|
2400
|
+
}
|
|
1092
2401
|
}
|
|
1093
2402
|
const [systemEvent] = await tx
|
|
1094
2403
|
.insert(chatMessages)
|
|
@@ -1175,6 +2484,14 @@ export function chatService(db) {
|
|
|
1175
2484
|
.where(eq(chatAttachments.conversationId, conversationId));
|
|
1176
2485
|
return rows;
|
|
1177
2486
|
}
|
|
2487
|
+
async function assetHasAttachments(assetId) {
|
|
2488
|
+
const [row] = await db
|
|
2489
|
+
.select({ id: chatAttachments.id })
|
|
2490
|
+
.from(chatAttachments)
|
|
2491
|
+
.where(eq(chatAttachments.assetId, assetId))
|
|
2492
|
+
.limit(1);
|
|
2493
|
+
return Boolean(row);
|
|
2494
|
+
}
|
|
1178
2495
|
async function remove(id) {
|
|
1179
2496
|
return db.transaction(async (tx) => {
|
|
1180
2497
|
const attachmentRows = await tx
|
|
@@ -1189,7 +2506,10 @@ export function chatService(db) {
|
|
|
1189
2506
|
return null;
|
|
1190
2507
|
const assetIds = [...new Set(attachmentRows.map((row) => row.assetId))];
|
|
1191
2508
|
if (assetIds.length > 0) {
|
|
1192
|
-
await tx.delete(assets).where(inArray(assets.id, assetIds)
|
|
2509
|
+
await tx.delete(assets).where(and(inArray(assets.id, assetIds), sql `not exists (
|
|
2510
|
+
select 1 from ${chatAttachments}
|
|
2511
|
+
where ${chatAttachments.assetId} = ${assets.id}
|
|
2512
|
+
)`));
|
|
1193
2513
|
}
|
|
1194
2514
|
return deleted;
|
|
1195
2515
|
});
|
|
@@ -2081,6 +3401,7 @@ export function chatService(db) {
|
|
|
2081
3401
|
});
|
|
2082
3402
|
}
|
|
2083
3403
|
return {
|
|
3404
|
+
generationProtocol,
|
|
2084
3405
|
list,
|
|
2085
3406
|
listSummaries,
|
|
2086
3407
|
listPinnedSummaries,
|
|
@@ -2091,6 +3412,7 @@ export function chatService(db) {
|
|
|
2091
3412
|
updateDefaultTitle,
|
|
2092
3413
|
replaceSystemGeneratedTitle,
|
|
2093
3414
|
listAttachmentsForConversation,
|
|
3415
|
+
assetHasAttachments,
|
|
2094
3416
|
remove,
|
|
2095
3417
|
forkConversation,
|
|
2096
3418
|
resolve,
|
|
@@ -2098,6 +3420,11 @@ export function chatService(db) {
|
|
|
2098
3420
|
markUnread,
|
|
2099
3421
|
setPinned,
|
|
2100
3422
|
createGeneration,
|
|
3423
|
+
beginGenerationControlAttempt,
|
|
3424
|
+
markGenerationControlReady,
|
|
3425
|
+
renewGenerationControlLease,
|
|
3426
|
+
markGenerationControlAttemptCompleted,
|
|
3427
|
+
appendGenerationEvent,
|
|
2101
3428
|
markGenerationTerminal,
|
|
2102
3429
|
getLatestActiveGeneration,
|
|
2103
3430
|
getLatestGeneration,
|
|
@@ -2106,7 +3433,17 @@ export function chatService(db) {
|
|
|
2106
3433
|
createQueuedMessage,
|
|
2107
3434
|
updateQueuedMessage,
|
|
2108
3435
|
cancelQueuedMessage,
|
|
3436
|
+
scheduleSteerContinuation,
|
|
2109
3437
|
markQueuedMessageSteerFallback,
|
|
3438
|
+
beginSteerControlAction,
|
|
3439
|
+
claimSteerProviderSend,
|
|
3440
|
+
releaseSteerProviderSendClaim,
|
|
3441
|
+
resolveSteerControlAction,
|
|
3442
|
+
claimNextServerQueuedMessage,
|
|
3443
|
+
renewServerQueuedMessageClaim,
|
|
3444
|
+
completeServerQueuedMessageDelivery,
|
|
3445
|
+
releaseServerQueuedMessageClaim,
|
|
3446
|
+
recoverExpiredServerQueueClaims,
|
|
2110
3447
|
claimNextQueuedMessage,
|
|
2111
3448
|
releaseQueuedMessageClaim,
|
|
2112
3449
|
assertQueuedMessageClaimedForDelivery,
|