@rudderhq/server 0.4.6-canary.9 → 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 +348 -172
- 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 +31 -54
- package/dist/index.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 +13 -13
- 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 +31 -2
- package/dist/routes/orgs.js.map +1 -1
- package/dist/routes/run-intelligence.d.ts.map +1 -1
- package/dist/routes/run-intelligence.js +76 -16
- package/dist/routes/run-intelligence.js.map +1 -1
- 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 +741 -118
- 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 +95 -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.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.map +1 -1
- package/dist/services/organization-workspace-browser.js +19 -3
- 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 +16 -4
- package/dist/services/run-intelligence.d.ts.map +1 -1
- package/dist/services/run-intelligence.js +90 -15
- package/dist/services/run-intelligence.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.d.ts +23 -0
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +1 -0
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts +95 -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 +389 -219
- package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.js +594 -204
- 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 +49 -94
- 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 +49 -94
- 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-CKLAbEpH.js → abnfDiagram-VRR7QNED-Dpnx79fp.js} +1 -1
- package/ui-dist/assets/{arc-CS20WjB1.js → arc-Cpvh_rCf.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-ZJ3FMSHR-BY4lounK.js → architectureDiagram-ZJ3FMSHR-sDD_mBAI.js} +1 -1
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-BHtHl-hm.js → blockDiagram-677ZJIJ3-4O4wWti1.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-DgOJp7yn.js → c4Diagram-LMCZKHZV-BkGODE4P.js} +1 -1
- package/ui-dist/assets/channel-CM4Myg_6.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-Uin6HTPH.js → chunk-2Q5K7J3B-DsDQQpID.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-BxAI7yMx.js → chunk-32BRIVSS-Bzx_VnIw.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-Ce6AR6Sg.js → chunk-5VM5RSS4-D4LoPCni.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-DgxPO59e.js → chunk-EX3LRPZG-BO0R-mS4.js} +3 -3
- package/ui-dist/assets/{chunk-JWPE2WC7-Bn3BYmxA.js → chunk-JWPE2WC7-B4Nr8KRL.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-BlTdGU58.js → chunk-MOJQB5TN--uaDDrYM.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-C0V6evNH.js → chunk-RYQCIY6F-CyBY2w_j.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-DjvZ40fp.js → chunk-V7JOEXUC-BMid_x8S.js} +3 -3
- package/ui-dist/assets/{chunk-VR4S4FIN-CejmQVxi.js → chunk-VR4S4FIN-DVqdPKuG.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-tj-vHLHN.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-Ci7DAkTj.js → cose-bilkent-JH36ORCC-jQDZmrG3.js} +1 -1
- package/ui-dist/assets/{cynefin-VYW2F7L2-CHU2plsQ.js → cynefin-VYW2F7L2-BxBj6aea.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-pqqNoczb.js → cynefinDiagram-TSTJHNR4-BuUubF5I.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-Bk8Y_kGf.js → dagre-VKFMJZFB-B8HipJ3a.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-CiSxIS2M.js → diagram-FQU43EPY-r7gkbKSt.js} +2 -2
- package/ui-dist/assets/{diagram-G47NLZAW-qpVsOLnz.js → diagram-G47NLZAW-CxClRsxD.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-Cda5JA3K.js → diagram-NH7WQ7WH-C8HYTLlq.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-Dh_DdE2d.js → diagram-OA4YK3LP-CjwqqLKz.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-r5ouUhNZ.js → diagram-WEI45ONY-2IWfCqZb.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-C6lDAmmi.js → ebnfDiagram-CCIWWBDH-DVGZkgjy.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-BznPfnRu.js → erDiagram-Q63AITRT-C_-u3Gmw.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-bCaogpDj.js → flowDiagram-23GEKE2U-BvGg0NCq.js} +4 -4
- package/ui-dist/assets/{ganttDiagram-NO4QXBWP-Bn-e5kP6.js → ganttDiagram-NO4QXBWP-U3CZUUUR.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D7E-RLhl.js → gitGraphDiagram-IHSO6WYX-CIWuEBIo.js} +1 -1
- package/ui-dist/assets/{graph-3haZvvEy.js → graph-D3P5q-7m.js} +1 -1
- package/ui-dist/assets/index-B1rLBtZ1.js +1875 -0
- package/ui-dist/assets/{index-BUneV0z1.js → index-BAMXpiI5.js} +1 -1
- package/ui-dist/assets/{index-CheuLaX8.js → index-BAgtKYR1.js} +1 -1
- package/ui-dist/assets/{index-BDji4WI2.js → index-CBOr9F6w.js} +1 -1
- package/ui-dist/assets/{index-DlcNrNku.js → index-CbHqnWUe.js} +1 -1
- package/ui-dist/assets/{index-C1NcizE3.js → index-CjztBLJi.js} +1 -1
- package/ui-dist/assets/{index-JqztCnnf.js → index-Cu1iMhjl.js} +1 -1
- package/ui-dist/assets/{index-BFnwTV9t.js → index-D7ycf1qU.js} +1 -1
- package/ui-dist/assets/{index-JLGi6sfB.js → index-DAYlwxsg.js} +1 -1
- package/ui-dist/assets/{index-Bje1DqHh.js → index-DCFf9dps.js} +4 -4
- package/ui-dist/assets/{index-zyCQr__a.js → index-DOPIV8vd.js} +1 -1
- package/ui-dist/assets/{index-JEy5ZJbE.js → index-DRxLJD40.js} +1 -1
- package/ui-dist/assets/{index-Doc88zxN.js → index-DYMtCDKT.js} +1 -1
- package/ui-dist/assets/index-DekHKOsL.css +1 -0
- package/ui-dist/assets/{index-BN3GMGqN.js → index-DidCCw78.js} +1 -1
- package/ui-dist/assets/{index-DKRH0hjR.js → index-Ds_qma4-.js} +1 -1
- package/ui-dist/assets/{index-B2bHoMTu.js → index-HkQRx1D6.js} +1 -1
- package/ui-dist/assets/{index-DLxzQvPB.js → index-ikpL--zj.js} +1 -1
- package/ui-dist/assets/{index-mE0kmjy0.js → index-mnl55Stn.js} +1 -1
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-NSQ6saR1.js → infoDiagram-FWYZ7A6U-Cx8Fa-I4.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-XhGk3DMM.js → ishikawaDiagram-FXEZZL3T-Cmuz1QUN.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-CUyh1IKu.js → journeyDiagram-5HDEW3XC-CJgkiVXG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-P_0zllUc.js → kanban-definition-HUTT4EX6-RAWpefOB.js} +1 -1
- package/ui-dist/assets/layout-DyWQv0Rr.js +1 -0
- package/ui-dist/assets/{linear-DWu-hp7y.js → linear-D5pdgIf9.js} +1 -1
- package/ui-dist/assets/{map-B2IiHu-a.js → map-bVSzNLl2.js} +1 -1
- package/ui-dist/assets/mermaid.core-CeaXGD6B.js +312 -0
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-DpKo7DKc.js → mindmap-definition-LN4V7U3C-B8B8AX5C.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-DzDDMRRX.js → pegDiagram-2B236MQR-C4Sv-ua6.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-Dr7ZCi_a.js → pieDiagram-ENE6RG2P-CLARlOHx.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-BB_gv9s9.js → quadrantDiagram-ABIIQ3AL-DPCXQmeT.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-C2ji_jMN.js → railroadDiagram-RFXS5EU6-BpqRuKUQ.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-BJg8vYwc.js → requirementDiagram-TGXJPOKE-Dd-h62HZ.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-msvrlvO_.js → sankeyDiagram-HTMAVEWB-DylT_pqq.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-B_B68qK5.js → sequenceDiagram-DBY2YBRQ-RiCF1REj.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-D0VdKiQi.js → sizeCapture-X5ZJPWSS-DXW4JQz2.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-B4V-owEQ.js → stateDiagram-2N3HPSRC-6U3ESIGT.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-KQTrcZvp.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-B1JTwGb7.js → swimlanes-5IMT3BWC-CeNHVNCi.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-CcxS1c6r.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-C-8YlQA9.js → timeline-definition-FHXFAJF6-Vlwoe_1b.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-DP13xuPX.js → vennDiagram-L72KCM5P-DBP7D3HE.js} +6 -6
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-D6BNPJwb.js → wardleyDiagram-EHGQE667-DG-wzqpy.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-my7CwP2H.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-D-nDuwao.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-Ds2nJ2Kt.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-Ds2nJ2Kt.js +0 -1
- package/ui-dist/assets/index-BA7XbHmf.css +0 -1
- package/ui-dist/assets/index-hE0ZX3Ud.js +0 -1820
- package/ui-dist/assets/layout-C-tMX-j8.js +0 -1
- package/ui-dist/assets/mermaid.core-WA5rvr0F.js +0 -314
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-DAB9Vdyb.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-DRt36f68.js +0 -8
package/dist/routes/chats.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { addChatMessageSchema, cancelChatQueuedMessageSchema, chatAutomationCreateFromStructuredPayload, createChatConversationSchema, createChatQueuedMessageSchema, forkChatConversationSchema, steerChatQueuedMessageSchema, updateChatConversationSchema, updateChatQueuedMessageSchema } from "@rudderhq/shared";
|
|
1
|
+
import { addChatMessageSchema, cancelChatQueuedMessageSchema, chatAutomationCreateFromStructuredPayload, createChatConversationSchema, createChatQueuedMessageSchema, createSideChatSchema, forkChatConversationSchema, parseCodexInlineVisualDirectives, steerChatQueuedMessageSchema, updateChatConversationSchema, updateChatQueuedMessageSchema, } from "@rudderhq/shared";
|
|
2
2
|
import { Router } from "express";
|
|
3
3
|
import multer from "multer";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
@@ -9,16 +9,23 @@ import { validate } from "../middleware/validate.js";
|
|
|
9
9
|
import { assertTimeZone } from "../services/automations.scheduler.js";
|
|
10
10
|
import { chatAgentRunService } from "../services/chat-agent-runs.js";
|
|
11
11
|
import { CHAT_ASSISTANT_USER_ERROR_MESSAGE, chatAssistantService, ChatAssistantStreamError, userVisiblePartialBodyFromError, } from "../services/chat-assistant.js";
|
|
12
|
-
import { cancelAndReleaseActiveChatGeneration, claimChatGeneration, getActiveChatGeneration, hasActiveChatGeneration } from "../services/chat-generation-locks.js";
|
|
12
|
+
import { cancelAndReleaseActiveChatGeneration, claimChatGeneration, createChatRuntimeControlCoordinator, getActiveChatGeneration, hasActiveChatGeneration, interruptActiveChatGeneration, steerActiveChatGeneration } from "../services/chat-generation-locks.js";
|
|
13
|
+
import { hashChatGenerationBody } from "../services/chat-generation-protocol.js";
|
|
13
14
|
import { buildChatTitlePromptFromMessages, chatTitleGenerationService, } from "../services/chat-title-generation.js";
|
|
14
15
|
import { chatWorkManifestService } from "../services/chat-work-manifest.js";
|
|
15
16
|
import { validateCron } from "../services/cron.js";
|
|
16
|
-
import { accessService, agentService, automationService, chatService, goalService, heartbeatService, issueService, logActivity, operatorProfileService, organizationService, productIntelligenceService, projectService, } from "../services/index.js";
|
|
17
|
+
import { accessService, agentService, automationService, chatService, goalService, heartbeatService, issueService, logActivity, operatorProfileService, organizationService, productIntelligenceService, projectService, sideChatService, } from "../services/index.js";
|
|
17
18
|
import { runtimeResultText, sanitizeGeneratedTitle, } from "../services/title-generation.js";
|
|
18
19
|
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
|
20
|
+
import { createChatBackgroundRuntime, } from "./chat-background-runtime.js";
|
|
19
21
|
import { wakeIssueAssigneeAfterChatConversion } from "./chat-issue-assignment-wakeup.js";
|
|
20
22
|
import { registerChatStreamRoutes } from "./chats.stream-routes.js";
|
|
21
|
-
|
|
23
|
+
function chatVisibleOutputAdmissionClosed(error) {
|
|
24
|
+
return error instanceof HttpError
|
|
25
|
+
&& error.status === 409
|
|
26
|
+
&& error.message === "Chat-visible output admission is closed for this generation";
|
|
27
|
+
}
|
|
28
|
+
export function chatRoutes(db, storage, backgroundRuntime = createChatBackgroundRuntime()) {
|
|
22
29
|
const router = Router();
|
|
23
30
|
const svc = chatService(db);
|
|
24
31
|
const organizationsSvc = organizationService(db);
|
|
@@ -35,6 +42,7 @@ export function chatRoutes(db, storage) {
|
|
|
35
42
|
const heartbeat = heartbeatService(db);
|
|
36
43
|
const productIntelligence = productIntelligenceService(db);
|
|
37
44
|
const chatTitles = chatTitleGenerationService({ chats: svc, productIntelligence });
|
|
45
|
+
const sideChats = sideChatService(db);
|
|
38
46
|
const CHAT_ASSISTANT_RECOVERABLE_FAILURE_FALLBACK_MESSAGE = "The assistant reply could not be completed. Rudder saved this attempt for diagnostics; retry when ready.";
|
|
39
47
|
const upload = multer({
|
|
40
48
|
storage: multer.memoryStorage(),
|
|
@@ -91,6 +99,7 @@ export function chatRoutes(db, storage) {
|
|
|
91
99
|
if (!conversation)
|
|
92
100
|
return null;
|
|
93
101
|
assertCompanyAccess(req, conversation.orgId);
|
|
102
|
+
await sideChats.assertAccessible(conversation, req.actor.type === "board" ? (req.actor.userId ?? "local-board") : null);
|
|
94
103
|
return conversation;
|
|
95
104
|
}
|
|
96
105
|
function boardUserId(req) {
|
|
@@ -112,6 +121,12 @@ export function chatRoutes(db, storage) {
|
|
|
112
121
|
throw conflict("Fork this Feishu chat to continue in Rudder");
|
|
113
122
|
}
|
|
114
123
|
}
|
|
124
|
+
async function assertSideChatMutationAllowed(req, conversation) {
|
|
125
|
+
await sideChats.assertMutable(conversation, req.actor.type === "board" ? boardUserId(req) : null);
|
|
126
|
+
}
|
|
127
|
+
async function touchSideChat(req, conversation) {
|
|
128
|
+
await sideChats.touch(conversation, req.actor.type === "board" ? boardUserId(req) : null);
|
|
129
|
+
}
|
|
115
130
|
function isTitleOnlyChatUpdate(body) {
|
|
116
131
|
const keys = Object.keys(body);
|
|
117
132
|
return keys.length === 1 && keys[0] === "title";
|
|
@@ -219,6 +234,71 @@ export function chatRoutes(db, storage) {
|
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
}
|
|
237
|
+
function queueRequestActor(req) {
|
|
238
|
+
if (req.actor.type === "agent") {
|
|
239
|
+
return {
|
|
240
|
+
type: "agent",
|
|
241
|
+
source: req.actor.source === "agent_jwt" ? "agent_jwt" : "agent_key",
|
|
242
|
+
orgId: req.actor.orgId,
|
|
243
|
+
agentId: req.actor.agentId,
|
|
244
|
+
runId: req.actor.runId,
|
|
245
|
+
adapterType: req.actor.adapterType,
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (req.actor.type !== "board")
|
|
249
|
+
throw unauthorized();
|
|
250
|
+
return {
|
|
251
|
+
type: "board",
|
|
252
|
+
source: req.actor.source === "local_implicit"
|
|
253
|
+
? "local_implicit"
|
|
254
|
+
: req.actor.source === "board_key"
|
|
255
|
+
? "board_key"
|
|
256
|
+
: "session",
|
|
257
|
+
userId: req.actor.userId,
|
|
258
|
+
orgIds: req.actor.orgIds,
|
|
259
|
+
isInstanceAdmin: req.actor.isInstanceAdmin,
|
|
260
|
+
runId: req.actor.runId,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
function requestForQueuedActor(requestActor, orgId) {
|
|
264
|
+
if (!requestActor) {
|
|
265
|
+
throw new Error("Queued chat continuation is missing its authenticated request actor");
|
|
266
|
+
}
|
|
267
|
+
if (requestActor.type === "agent") {
|
|
268
|
+
if (!requestActor.agentId || requestActor.orgId !== orgId) {
|
|
269
|
+
throw new Error("Queued chat continuation has an invalid agent actor scope");
|
|
270
|
+
}
|
|
271
|
+
return {
|
|
272
|
+
actor: {
|
|
273
|
+
type: "agent",
|
|
274
|
+
source: requestActor.source === "agent_jwt" ? "agent_jwt" : "agent_key",
|
|
275
|
+
orgId,
|
|
276
|
+
agentId: requestActor.agentId,
|
|
277
|
+
runId: requestActor.runId,
|
|
278
|
+
adapterType: requestActor.adapterType,
|
|
279
|
+
},
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const source = requestActor.source === "local_implicit"
|
|
283
|
+
? "local_implicit"
|
|
284
|
+
: requestActor.source === "board_key"
|
|
285
|
+
? "board_key"
|
|
286
|
+
: "session";
|
|
287
|
+
const orgIds = requestActor.orgIds ?? [];
|
|
288
|
+
if (source !== "local_implicit" && !requestActor.isInstanceAdmin && !orgIds.includes(orgId)) {
|
|
289
|
+
throw new Error("Queued chat continuation has an invalid board actor scope");
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
actor: {
|
|
293
|
+
type: "board",
|
|
294
|
+
source,
|
|
295
|
+
userId: requestActor.userId,
|
|
296
|
+
orgIds,
|
|
297
|
+
isInstanceAdmin: requestActor.isInstanceAdmin,
|
|
298
|
+
runId: requestActor.runId,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
222
302
|
function turnContextFromUserMessage(userMessage) {
|
|
223
303
|
if (!userMessage.chatTurnId) {
|
|
224
304
|
throw new Error("User message missing chat turn id");
|
|
@@ -398,12 +478,19 @@ export function chatRoutes(db, storage) {
|
|
|
398
478
|
const createdMessages = [];
|
|
399
479
|
const { chatTurnId, turnVariant } = turnContext;
|
|
400
480
|
const attachGeneratedFiles = async (message, generatedAttachments) => {
|
|
401
|
-
|
|
481
|
+
const finalDirectives = parseCodexInlineVisualDirectives(assistantReply.body).directives;
|
|
482
|
+
const inlineVisuals = (assistantReply.inlineVisuals ?? []).filter((visual) => finalDirectives.some((directive) => directive.index === visual.directiveIndex && directive.file === visual.file));
|
|
483
|
+
const generatedFiles = (generatedAttachments ?? []).filter((generated) => generated.source !== "codex_inline_visual"
|
|
484
|
+
|| inlineVisuals.some((visual) => visual.status === "captured"
|
|
485
|
+
&& visual.directiveIndex === generated.directiveIndex
|
|
486
|
+
&& visual.file === generated.directiveFile));
|
|
487
|
+
if (generatedFiles.length === 0 && inlineVisuals.length === 0)
|
|
402
488
|
return message;
|
|
403
489
|
const attachments = [];
|
|
404
|
-
|
|
490
|
+
const attachmentByVisualIndex = new Map();
|
|
491
|
+
for (const generated of generatedFiles) {
|
|
405
492
|
if (generated.body.length > MAX_ATTACHMENT_BYTES) {
|
|
406
|
-
throw new ChatAssistantStreamError(`Generated attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes`, assistantReply.body,
|
|
493
|
+
throw new ChatAssistantStreamError(`Generated attachment exceeds ${MAX_ATTACHMENT_BYTES} bytes`, assistantReply.body, generatedFiles, { partialBodyUserVisible: true });
|
|
407
494
|
}
|
|
408
495
|
const stored = await storage.putFile({
|
|
409
496
|
orgId: conversation.orgId,
|
|
@@ -425,10 +512,45 @@ export function chatRoutes(db, storage) {
|
|
|
425
512
|
createdByAgentId: replyingAgentId,
|
|
426
513
|
createdByUserId: null,
|
|
427
514
|
});
|
|
428
|
-
|
|
515
|
+
const typedAttachment = attachment;
|
|
516
|
+
const publicAttachment = generated.source === "codex_inline_visual"
|
|
517
|
+
? (({ provider: _provider, objectKey: _objectKey, ...safe }) => safe)(typedAttachment)
|
|
518
|
+
: typedAttachment;
|
|
519
|
+
attachments.push(publicAttachment);
|
|
520
|
+
if (generated.source === "codex_inline_visual") {
|
|
521
|
+
attachmentByVisualIndex.set(generated.directiveIndex, publicAttachment);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
let structuredPayload = message.structuredPayload ?? null;
|
|
525
|
+
if (inlineVisuals.length > 0) {
|
|
526
|
+
const persistedMappings = inlineVisuals.map((visual) => {
|
|
527
|
+
if (visual.status === "captured") {
|
|
528
|
+
const attachment = attachmentByVisualIndex.get(visual.directiveIndex);
|
|
529
|
+
if (attachment) {
|
|
530
|
+
return {
|
|
531
|
+
directiveIndex: visual.directiveIndex,
|
|
532
|
+
file: visual.file,
|
|
533
|
+
status: "ready",
|
|
534
|
+
attachmentId: attachment.id,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
return {
|
|
539
|
+
directiveIndex: visual.directiveIndex,
|
|
540
|
+
file: visual.file,
|
|
541
|
+
status: "unavailable",
|
|
542
|
+
reason: visual.status === "unavailable" ? visual.reason : "capture_failed",
|
|
543
|
+
};
|
|
544
|
+
});
|
|
545
|
+
structuredPayload = {
|
|
546
|
+
...(structuredPayload ?? {}),
|
|
547
|
+
inlineVisuals: persistedMappings,
|
|
548
|
+
};
|
|
549
|
+
await svc.updateMessage(conversation.id, message.id, { structuredPayload });
|
|
429
550
|
}
|
|
430
551
|
return {
|
|
431
552
|
...message,
|
|
553
|
+
structuredPayload,
|
|
432
554
|
attachments: [...(message.attachments ?? []), ...attachments],
|
|
433
555
|
};
|
|
434
556
|
};
|
|
@@ -825,6 +947,425 @@ export function chatRoutes(db, storage) {
|
|
|
825
947
|
await chatRunsSvc.linkAssistantMessage(runId, conversation.id, message.id);
|
|
826
948
|
}
|
|
827
949
|
}
|
|
950
|
+
const queueWorkerId = `chat-queue:${process.pid}:${randomUUID()}`;
|
|
951
|
+
const queueLeaseMs = 30_000;
|
|
952
|
+
const queueWorkerConcurrency = 4;
|
|
953
|
+
const queueWorkerEnabled = process.env.NODE_ENV !== "test"
|
|
954
|
+
|| process.env.RUDDER_CHAT_QUEUE_WORKER_TEST === "true";
|
|
955
|
+
const runningServerQueueTasks = new Set();
|
|
956
|
+
const terminalProjectorId = `chat-terminal:${process.pid}:${randomUUID()}`;
|
|
957
|
+
let terminalProjectionRetryTimer = null;
|
|
958
|
+
let terminalProjectionRetryAt = Number.POSITIVE_INFINITY;
|
|
959
|
+
function scheduleTerminalProjectorAt(wakeAt) {
|
|
960
|
+
if (!queueWorkerEnabled || !backgroundRuntime.acceptingWork)
|
|
961
|
+
return;
|
|
962
|
+
const wakeAtMs = Math.max(Date.now(), wakeAt.getTime());
|
|
963
|
+
if (terminalProjectionRetryTimer && terminalProjectionRetryAt <= wakeAtMs)
|
|
964
|
+
return;
|
|
965
|
+
if (terminalProjectionRetryTimer)
|
|
966
|
+
backgroundRuntime.clearTimer(terminalProjectionRetryTimer);
|
|
967
|
+
terminalProjectionRetryAt = wakeAtMs;
|
|
968
|
+
terminalProjectionRetryTimer = backgroundRuntime.setTimeout(() => {
|
|
969
|
+
terminalProjectionRetryTimer = null;
|
|
970
|
+
terminalProjectionRetryAt = Number.POSITIVE_INFINITY;
|
|
971
|
+
wakeTerminalProjector();
|
|
972
|
+
}, Math.max(0, wakeAtMs - Date.now()));
|
|
973
|
+
}
|
|
974
|
+
async function drainTerminalProjections() {
|
|
975
|
+
while (backgroundRuntime.acceptingWork) {
|
|
976
|
+
const claim = await svc.generationProtocol.claimTerminalProjection({
|
|
977
|
+
workerId: terminalProjectorId,
|
|
978
|
+
leaseMs: 30_000,
|
|
979
|
+
});
|
|
980
|
+
if (!claim) {
|
|
981
|
+
const nextWakeAt = await svc.generationProtocol.getNextTerminalProjectionWakeAt();
|
|
982
|
+
if (nextWakeAt)
|
|
983
|
+
scheduleTerminalProjectorAt(nextWakeAt);
|
|
984
|
+
return;
|
|
985
|
+
}
|
|
986
|
+
// A claim requested before close may resolve after admission shuts. The
|
|
987
|
+
// terminal protocol has no release operation, so this tracked drain must
|
|
988
|
+
// finish that claim while close waits instead of leaking its lease.
|
|
989
|
+
try {
|
|
990
|
+
const finalStatus = typeof claim.payload.finalStatus === "string"
|
|
991
|
+
? claim.payload.finalStatus
|
|
992
|
+
: null;
|
|
993
|
+
const controlActionKind = typeof claim.payload.controlActionKind === "string"
|
|
994
|
+
? claim.payload.controlActionKind
|
|
995
|
+
: null;
|
|
996
|
+
const controlDisposition = finalStatus === "stopped"
|
|
997
|
+
&& controlActionKind !== "steer"
|
|
998
|
+
? "stopped"
|
|
999
|
+
: finalStatus === "interrupted_unverified"
|
|
1000
|
+
&& controlActionKind !== "steer"
|
|
1001
|
+
? "interrupted_unverified"
|
|
1002
|
+
: finalStatus === "control_lost"
|
|
1003
|
+
? "control_lost"
|
|
1004
|
+
: undefined;
|
|
1005
|
+
const completed = await svc.generationProtocol.completeTerminalProjection({
|
|
1006
|
+
outboxId: claim.id,
|
|
1007
|
+
claimToken: claim.claimToken,
|
|
1008
|
+
claimEpoch: claim.claimEpoch,
|
|
1009
|
+
controlDisposition,
|
|
1010
|
+
});
|
|
1011
|
+
if (completed)
|
|
1012
|
+
wakeServerQueue();
|
|
1013
|
+
}
|
|
1014
|
+
catch (error) {
|
|
1015
|
+
const retryAt = new Date(Date.now() + 1_000);
|
|
1016
|
+
const retry = await svc.generationProtocol.retryTerminalProjection({
|
|
1017
|
+
outboxId: claim.id,
|
|
1018
|
+
claimToken: claim.claimToken,
|
|
1019
|
+
claimEpoch: claim.claimEpoch,
|
|
1020
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1021
|
+
retryAt,
|
|
1022
|
+
maxAttempts: 5,
|
|
1023
|
+
}).catch(() => null);
|
|
1024
|
+
if (retry?.status === "retry_wait") {
|
|
1025
|
+
scheduleTerminalProjectorAt(retry.availableAt ?? retryAt);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
const terminalProjector = backgroundRuntime.createCoalescingTask(drainTerminalProjections, (error) => logger.warn({ err: error }, "chat terminal projection drain failed"));
|
|
1031
|
+
function wakeTerminalProjector() {
|
|
1032
|
+
if (!queueWorkerEnabled || !backgroundRuntime.acceptingWork)
|
|
1033
|
+
return;
|
|
1034
|
+
if (terminalProjectionRetryTimer) {
|
|
1035
|
+
backgroundRuntime.clearTimer(terminalProjectionRetryTimer);
|
|
1036
|
+
terminalProjectionRetryTimer = null;
|
|
1037
|
+
terminalProjectionRetryAt = Number.POSITIVE_INFINITY;
|
|
1038
|
+
}
|
|
1039
|
+
terminalProjector.wake();
|
|
1040
|
+
}
|
|
1041
|
+
async function runServerQueuedMessage(claim) {
|
|
1042
|
+
const conversation = await svc.getById(claim.item.conversationId);
|
|
1043
|
+
if (!conversation) {
|
|
1044
|
+
await svc.releaseServerQueuedMessageClaim({
|
|
1045
|
+
itemId: claim.item.id,
|
|
1046
|
+
generationId: claim.generationId,
|
|
1047
|
+
leaseToken: claim.leaseToken,
|
|
1048
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1049
|
+
reason: "conversation_missing",
|
|
1050
|
+
});
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
let request;
|
|
1054
|
+
try {
|
|
1055
|
+
request = requestForQueuedActor(claim.item.requestActor, conversation.orgId);
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
await svc.completeServerQueuedMessageDelivery({
|
|
1059
|
+
itemId: claim.item.id,
|
|
1060
|
+
generationId: claim.generationId,
|
|
1061
|
+
leaseToken: claim.leaseToken,
|
|
1062
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1063
|
+
status: "failed",
|
|
1064
|
+
reason: error instanceof Error ? error.message : "queued_request_actor_invalid",
|
|
1065
|
+
});
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
const actor = getActorInfo(request);
|
|
1069
|
+
const managedAbort = backgroundRuntime.manageAbortController();
|
|
1070
|
+
const abortController = managedAbort.controller;
|
|
1071
|
+
const releaseGeneration = claimChatGeneration(conversation.id, abortController, claim.generationId);
|
|
1072
|
+
if (!releaseGeneration) {
|
|
1073
|
+
managedAbort.release();
|
|
1074
|
+
await svc.releaseServerQueuedMessageClaim({
|
|
1075
|
+
itemId: claim.item.id,
|
|
1076
|
+
generationId: claim.generationId,
|
|
1077
|
+
leaseToken: claim.leaseToken,
|
|
1078
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1079
|
+
reason: "local_generation_owner_busy",
|
|
1080
|
+
});
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
let leaseRenewing = false;
|
|
1084
|
+
let leaseLost = false;
|
|
1085
|
+
const renewLease = async () => {
|
|
1086
|
+
if (leaseRenewing || leaseLost)
|
|
1087
|
+
return;
|
|
1088
|
+
leaseRenewing = true;
|
|
1089
|
+
try {
|
|
1090
|
+
const renewed = await svc.renewServerQueuedMessageClaim({
|
|
1091
|
+
itemId: claim.item.id,
|
|
1092
|
+
generationId: claim.generationId,
|
|
1093
|
+
leaseToken: claim.leaseToken,
|
|
1094
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1095
|
+
leaseMs: queueLeaseMs,
|
|
1096
|
+
});
|
|
1097
|
+
if (!renewed) {
|
|
1098
|
+
leaseLost = true;
|
|
1099
|
+
abortController.abort(new Error("Queued chat continuation lost its delivery lease"));
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
finally {
|
|
1103
|
+
leaseRenewing = false;
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
const leaseTimer = backgroundRuntime.setInterval(() => {
|
|
1107
|
+
return renewLease().catch((error) => {
|
|
1108
|
+
leaseLost = true;
|
|
1109
|
+
abortController.abort(error);
|
|
1110
|
+
});
|
|
1111
|
+
}, Math.floor(queueLeaseMs / 3));
|
|
1112
|
+
let terminalStatus = "failed";
|
|
1113
|
+
let terminalReason = null;
|
|
1114
|
+
let completionRecorded = false;
|
|
1115
|
+
let assistantConversation = conversation;
|
|
1116
|
+
let activeChatRunId = null;
|
|
1117
|
+
const transcript = [];
|
|
1118
|
+
let partialBody = "";
|
|
1119
|
+
try {
|
|
1120
|
+
const userMessage = await svc.getMessage(conversation.id, claim.userMessageId);
|
|
1121
|
+
if (!userMessage)
|
|
1122
|
+
throw new Error("Queued chat continuation user message is missing");
|
|
1123
|
+
const turnContext = turnContextFromUserMessage(userMessage);
|
|
1124
|
+
const assistantInput = await loadAssistantInput(conversation, actor);
|
|
1125
|
+
assistantConversation = assistantInput.conversation;
|
|
1126
|
+
const streamed = await assistantSvc.streamChatAssistantReply({
|
|
1127
|
+
...assistantInput,
|
|
1128
|
+
userMessageId: userMessage.id,
|
|
1129
|
+
chatTurnId: turnContext.chatTurnId,
|
|
1130
|
+
turnVariant: turnContext.turnVariant,
|
|
1131
|
+
stream: false,
|
|
1132
|
+
abortSignal: abortController.signal,
|
|
1133
|
+
controlCoordinator: createChatRuntimeControlCoordinator(conversation.id, claim.generationId, {
|
|
1134
|
+
onAttemptStarted: async ({ generationId, attemptEpoch, ownerToken, attempt }) => {
|
|
1135
|
+
await svc.beginGenerationControlAttempt({
|
|
1136
|
+
orgId: conversation.orgId,
|
|
1137
|
+
conversationId: conversation.id,
|
|
1138
|
+
generationId,
|
|
1139
|
+
attemptEpoch,
|
|
1140
|
+
ownerToken,
|
|
1141
|
+
runtimeType: attempt.runtimeType,
|
|
1142
|
+
});
|
|
1143
|
+
},
|
|
1144
|
+
onHandleRegistered: async ({ generationId, attemptEpoch, ownerToken, handle }) => {
|
|
1145
|
+
await svc.markGenerationControlReady({
|
|
1146
|
+
generationId,
|
|
1147
|
+
attemptEpoch,
|
|
1148
|
+
ownerToken,
|
|
1149
|
+
runtimeType: handle.runtimeType,
|
|
1150
|
+
providerThreadId: handle.providerThreadId ?? null,
|
|
1151
|
+
providerTurnId: handle.providerTurnId ?? null,
|
|
1152
|
+
});
|
|
1153
|
+
},
|
|
1154
|
+
onAttemptLeaseRenewed: async ({ generationId, attemptEpoch, ownerToken }) => {
|
|
1155
|
+
await svc.renewGenerationControlLease({ generationId, attemptEpoch, ownerToken });
|
|
1156
|
+
},
|
|
1157
|
+
onAttemptCompleted: async ({ generationId, attemptEpoch, ownerToken }) => {
|
|
1158
|
+
await svc.markGenerationControlAttemptCompleted({
|
|
1159
|
+
generationId,
|
|
1160
|
+
attemptEpoch,
|
|
1161
|
+
ownerToken,
|
|
1162
|
+
});
|
|
1163
|
+
},
|
|
1164
|
+
}),
|
|
1165
|
+
onRunCreated: (runId) => {
|
|
1166
|
+
activeChatRunId = runId;
|
|
1167
|
+
},
|
|
1168
|
+
onAssistantDelta: async (delta) => {
|
|
1169
|
+
if (!abortController.signal.aborted)
|
|
1170
|
+
partialBody = `${partialBody}${delta}`;
|
|
1171
|
+
},
|
|
1172
|
+
onTranscriptEntry: async (entry) => {
|
|
1173
|
+
if (!abortController.signal.aborted)
|
|
1174
|
+
transcript.push(entry);
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
partialBody = streamed.partialBody || partialBody;
|
|
1178
|
+
if (abortController.signal.aborted || streamed.outcome === "stopped") {
|
|
1179
|
+
terminalStatus = "stopped";
|
|
1180
|
+
terminalReason = "operator_stop";
|
|
1181
|
+
const stoppedMessage = await persistPartialAssistantMessage(assistantConversation, "", "stopped", turnContext, transcript, streamed.replyingAgentId, null, activeChatRunId);
|
|
1182
|
+
const stoppedMessages = stoppedMessage ? [stoppedMessage] : [];
|
|
1183
|
+
await linkChatRunMessages(assistantConversation, activeChatRunId, stoppedMessages);
|
|
1184
|
+
if (stoppedMessages.length > 0) {
|
|
1185
|
+
await logChatMessagesAdded(assistantConversation, stoppedMessages, {
|
|
1186
|
+
actorType: "system",
|
|
1187
|
+
actorId: "chat-assistant",
|
|
1188
|
+
agentId: streamed.replyingAgentId,
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
else {
|
|
1193
|
+
let completionMessageId = null;
|
|
1194
|
+
try {
|
|
1195
|
+
const attemptEpoch = Math.max(1, getActiveChatGeneration(conversation.id)?.attemptEpoch ?? 1);
|
|
1196
|
+
const completion = await svc.generationProtocol.appendVisibleEventAndProject({
|
|
1197
|
+
orgId: conversation.orgId,
|
|
1198
|
+
conversationId: conversation.id,
|
|
1199
|
+
generationId: claim.generationId,
|
|
1200
|
+
expectedAttemptEpoch: attemptEpoch,
|
|
1201
|
+
eventKind: "runtime_output",
|
|
1202
|
+
payload: {
|
|
1203
|
+
resultKind: streamed.reply.kind,
|
|
1204
|
+
body: streamed.reply.body,
|
|
1205
|
+
},
|
|
1206
|
+
bodyOffset: 0,
|
|
1207
|
+
bodyLength: streamed.reply.body.length,
|
|
1208
|
+
runId: activeChatRunId,
|
|
1209
|
+
bodyHash: hashChatGenerationBody(streamed.reply.body),
|
|
1210
|
+
body: streamed.reply.body,
|
|
1211
|
+
transcript,
|
|
1212
|
+
replyingAgentId: streamed.replyingAgentId,
|
|
1213
|
+
chatTurnId: turnContext.chatTurnId,
|
|
1214
|
+
turnVariant: turnContext.turnVariant,
|
|
1215
|
+
});
|
|
1216
|
+
completionMessageId = completion.message.id;
|
|
1217
|
+
}
|
|
1218
|
+
catch (error) {
|
|
1219
|
+
if (!chatVisibleOutputAdmissionClosed(error))
|
|
1220
|
+
throw error;
|
|
1221
|
+
terminalStatus = "stopped";
|
|
1222
|
+
terminalReason = "operator_stop";
|
|
1223
|
+
const stoppedMessage = await persistPartialAssistantMessage(assistantConversation, "", "stopped", turnContext, transcript, streamed.replyingAgentId, null, activeChatRunId);
|
|
1224
|
+
const stoppedMessages = stoppedMessage ? [stoppedMessage] : [];
|
|
1225
|
+
await linkChatRunMessages(assistantConversation, activeChatRunId, stoppedMessages);
|
|
1226
|
+
if (stoppedMessages.length > 0) {
|
|
1227
|
+
await logChatMessagesAdded(assistantConversation, stoppedMessages, {
|
|
1228
|
+
actorType: "system",
|
|
1229
|
+
actorId: "chat-assistant",
|
|
1230
|
+
agentId: streamed.replyingAgentId,
|
|
1231
|
+
});
|
|
1232
|
+
}
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
const createdMessages = await persistAssistantReply(request, assistantConversation, actor, streamed.reply, turnContext, transcript, streamed.replyingAgentId, completionMessageId, activeChatRunId);
|
|
1236
|
+
await linkChatRunMessages(assistantConversation, activeChatRunId, createdMessages);
|
|
1237
|
+
await logChatMessagesAdded(assistantConversation, createdMessages, {
|
|
1238
|
+
actorType: "system",
|
|
1239
|
+
actorId: "chat-assistant",
|
|
1240
|
+
agentId: streamed.replyingAgentId,
|
|
1241
|
+
});
|
|
1242
|
+
terminalStatus = "completed";
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
catch (error) {
|
|
1246
|
+
terminalStatus = abortController.signal.aborted ? "aborted" : "failed";
|
|
1247
|
+
terminalReason = leaseLost
|
|
1248
|
+
? "delivery_lease_lost"
|
|
1249
|
+
: error instanceof Error
|
|
1250
|
+
? error.message
|
|
1251
|
+
: "queued_continuation_failed";
|
|
1252
|
+
logger.warn({ err: error, conversationId: conversation.id, queuedMessageId: claim.item.id }, "server-owned queued chat continuation failed");
|
|
1253
|
+
if (!leaseLost) {
|
|
1254
|
+
const failurePayload = recoverableFailurePayload(error, activeChatRunId);
|
|
1255
|
+
const failureBody = userVisiblePartialBodyFromError(error)
|
|
1256
|
+
|| recoverableFailureBody(failurePayload)
|
|
1257
|
+
|| CHAT_ASSISTANT_USER_ERROR_MESSAGE;
|
|
1258
|
+
const failedMessage = await persistPartialAssistantMessage(assistantConversation, failureBody, "failed", null, transcript, chatReplyingAgentId(assistantConversation), null, activeChatRunId, failurePayload).catch(() => null);
|
|
1259
|
+
const failedMessages = failedMessage ? [failedMessage] : [];
|
|
1260
|
+
await linkChatRunMessages(assistantConversation, activeChatRunId, failedMessages).catch(() => undefined);
|
|
1261
|
+
if (failedMessages.length > 0) {
|
|
1262
|
+
await logChatMessagesAdded(assistantConversation, failedMessages, {
|
|
1263
|
+
actorType: "system",
|
|
1264
|
+
actorId: "chat-assistant",
|
|
1265
|
+
agentId: chatReplyingAgentId(assistantConversation),
|
|
1266
|
+
}).catch(() => undefined);
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
finally {
|
|
1271
|
+
backgroundRuntime.clearTimer(leaseTimer);
|
|
1272
|
+
if (!leaseLost) {
|
|
1273
|
+
const latestGeneration = await svc.getLatestGeneration(conversation.id).catch(() => null);
|
|
1274
|
+
const terminalEvidence = latestGeneration?.id === claim.generationId
|
|
1275
|
+
? await svc.generationProtocol.recordRuntimeTerminal({
|
|
1276
|
+
orgId: conversation.orgId,
|
|
1277
|
+
conversationId: conversation.id,
|
|
1278
|
+
generationId: claim.generationId,
|
|
1279
|
+
expectedAttemptEpoch: latestGeneration.attemptEpoch,
|
|
1280
|
+
expectedOwnerToken: latestGeneration.controlOwnerToken,
|
|
1281
|
+
finalStatus: terminalStatus,
|
|
1282
|
+
terminalReason: terminalReason ?? terminalStatus,
|
|
1283
|
+
}).catch((error) => {
|
|
1284
|
+
logger.warn({ err: error, generationId: claim.generationId }, "failed to record queued chat terminal evidence");
|
|
1285
|
+
return null;
|
|
1286
|
+
})
|
|
1287
|
+
: null;
|
|
1288
|
+
if (terminalEvidence) {
|
|
1289
|
+
wakeTerminalProjector();
|
|
1290
|
+
const completed = await svc.completeServerQueuedMessageDelivery({
|
|
1291
|
+
itemId: claim.item.id,
|
|
1292
|
+
generationId: claim.generationId,
|
|
1293
|
+
leaseToken: claim.leaseToken,
|
|
1294
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1295
|
+
status: terminalStatus,
|
|
1296
|
+
reason: terminalReason,
|
|
1297
|
+
}).catch((error) => {
|
|
1298
|
+
logger.warn({ err: error, queuedMessageId: claim.item.id }, "failed to complete queued chat continuation");
|
|
1299
|
+
return null;
|
|
1300
|
+
});
|
|
1301
|
+
completionRecorded = Boolean(completed);
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (!completionRecorded && !leaseLost) {
|
|
1305
|
+
await svc.releaseServerQueuedMessageClaim({
|
|
1306
|
+
itemId: claim.item.id,
|
|
1307
|
+
generationId: claim.generationId,
|
|
1308
|
+
leaseToken: claim.leaseToken,
|
|
1309
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1310
|
+
reason: "queued_continuation_completion_unconfirmed",
|
|
1311
|
+
}).catch(() => null);
|
|
1312
|
+
}
|
|
1313
|
+
releaseGeneration();
|
|
1314
|
+
managedAbort.release();
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
async function drainServerQueue() {
|
|
1318
|
+
await svc.recoverExpiredServerQueueClaims();
|
|
1319
|
+
while (backgroundRuntime.acceptingWork
|
|
1320
|
+
&& runningServerQueueTasks.size < queueWorkerConcurrency) {
|
|
1321
|
+
const claim = await svc.claimNextServerQueuedMessage({
|
|
1322
|
+
workerId: queueWorkerId,
|
|
1323
|
+
leaseMs: queueLeaseMs,
|
|
1324
|
+
});
|
|
1325
|
+
if (!claim)
|
|
1326
|
+
return;
|
|
1327
|
+
if (!backgroundRuntime.acceptingWork) {
|
|
1328
|
+
await svc.releaseServerQueuedMessageClaim({
|
|
1329
|
+
itemId: claim.item.id,
|
|
1330
|
+
generationId: claim.generationId,
|
|
1331
|
+
leaseToken: claim.leaseToken,
|
|
1332
|
+
leaseEpoch: claim.leaseEpoch,
|
|
1333
|
+
reason: "chat_background_runtime_closing",
|
|
1334
|
+
});
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
let task;
|
|
1338
|
+
task = backgroundRuntime.track(runServerQueuedMessage(claim)
|
|
1339
|
+
.catch((error) => {
|
|
1340
|
+
logger.warn({ err: error, queuedMessageId: claim.item.id }, "server-owned chat continuation crashed");
|
|
1341
|
+
})
|
|
1342
|
+
.finally(() => {
|
|
1343
|
+
runningServerQueueTasks.delete(task);
|
|
1344
|
+
wakeServerQueue();
|
|
1345
|
+
}));
|
|
1346
|
+
runningServerQueueTasks.add(task);
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
const serverQueueDrain = backgroundRuntime.createCoalescingTask(drainServerQueue, (error) => logger.warn({ err: error }, "server-owned chat queue drain failed"));
|
|
1350
|
+
function wakeServerQueue() {
|
|
1351
|
+
if (!queueWorkerEnabled || !backgroundRuntime.acceptingWork)
|
|
1352
|
+
return;
|
|
1353
|
+
serverQueueDrain.wake();
|
|
1354
|
+
}
|
|
1355
|
+
if (queueWorkerEnabled) {
|
|
1356
|
+
const recoverChatControlOwners = () => {
|
|
1357
|
+
if (!backgroundRuntime.acceptingWork)
|
|
1358
|
+
return;
|
|
1359
|
+
return svc.generationProtocol.recoverStaleControlOwners({})
|
|
1360
|
+
.then(() => {
|
|
1361
|
+
wakeTerminalProjector();
|
|
1362
|
+
wakeServerQueue();
|
|
1363
|
+
})
|
|
1364
|
+
.catch((error) => logger.warn({ err: error }, "chat control recovery failed"));
|
|
1365
|
+
};
|
|
1366
|
+
backgroundRuntime.setTimeout(recoverChatControlOwners, 0);
|
|
1367
|
+
backgroundRuntime.setInterval(recoverChatControlOwners, 10_000);
|
|
1368
|
+
}
|
|
828
1369
|
router.get("/orgs/:orgId/chats", async (req, res) => {
|
|
829
1370
|
const orgId = req.params.orgId;
|
|
830
1371
|
assertCompanyAccess(req, orgId);
|
|
@@ -833,12 +1374,21 @@ export function chatRoutes(db, storage) {
|
|
|
833
1374
|
? statusParam
|
|
834
1375
|
: "active";
|
|
835
1376
|
const q = typeof req.query.q === "string" ? req.query.q : undefined;
|
|
1377
|
+
const projectId = typeof req.query.projectId === "string"
|
|
1378
|
+
? req.query.projectId.trim() || undefined
|
|
1379
|
+
: undefined;
|
|
836
1380
|
const limit = typeof req.query.limit === "string"
|
|
837
1381
|
? positiveIntegerQuery(req.query.limit, 50, 500)
|
|
838
1382
|
: undefined;
|
|
839
1383
|
const userId = req.actor.type === "board" ? (req.actor.userId ?? "local-board") : null;
|
|
840
|
-
const conversations = await svc.list(orgId, {
|
|
841
|
-
|
|
1384
|
+
const conversations = await svc.list(orgId, {
|
|
1385
|
+
status,
|
|
1386
|
+
q,
|
|
1387
|
+
limit,
|
|
1388
|
+
...(projectId ? { projectId } : {}),
|
|
1389
|
+
}, userId);
|
|
1390
|
+
const visibleConversations = conversations.filter((conversation) => conversation.messengerVisible !== false);
|
|
1391
|
+
res.json(await assistantSvc.enrichConversations(visibleConversations));
|
|
842
1392
|
});
|
|
843
1393
|
router.post("/orgs/:orgId/chats", validate(createChatConversationSchema), async (req, res) => {
|
|
844
1394
|
const orgId = req.params.orgId;
|
|
@@ -915,6 +1465,7 @@ export function chatRoutes(db, storage) {
|
|
|
915
1465
|
if (existing.mutability === "external_bound_chat" && !isTitleOnlyChatUpdate(req.body)) {
|
|
916
1466
|
assertChatLocalMutationAllowed(existing);
|
|
917
1467
|
}
|
|
1468
|
+
await assertSideChatMutationAllowed(req, existing);
|
|
918
1469
|
if (req.body.primaryIssueId) {
|
|
919
1470
|
const issue = await issuesSvc.getById(req.body.primaryIssueId);
|
|
920
1471
|
if (!issue || issue.orgId !== existing.orgId) {
|
|
@@ -961,6 +1512,7 @@ export function chatRoutes(db, storage) {
|
|
|
961
1512
|
res.status(404).json({ error: "Chat conversation not found" });
|
|
962
1513
|
return;
|
|
963
1514
|
}
|
|
1515
|
+
await assertSideChatMutationAllowed(req, existing);
|
|
964
1516
|
const messages = await svc.listMessages(existing.id, { includeTranscript: false });
|
|
965
1517
|
const prompt = buildChatTitlePromptFromMessages(messages);
|
|
966
1518
|
if (!prompt) {
|
|
@@ -1026,6 +1578,96 @@ export function chatRoutes(db, storage) {
|
|
|
1026
1578
|
});
|
|
1027
1579
|
res.status(201).json(await assistantSvc.enrichConversation(forked));
|
|
1028
1580
|
});
|
|
1581
|
+
router.post("/chats/:id/side-chats", validate(createSideChatSchema), async (req, res) => {
|
|
1582
|
+
assertBoard(req);
|
|
1583
|
+
const existing = await assertConversationAccess(req, req.params.id);
|
|
1584
|
+
if (!existing) {
|
|
1585
|
+
res.status(404).json({ error: "Chat conversation not found" });
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
const userId = boardUserId(req);
|
|
1589
|
+
const sideChat = await sideChats.create({
|
|
1590
|
+
sourceConversationId: existing.id,
|
|
1591
|
+
sourceMessageId: req.body.sourceMessageId,
|
|
1592
|
+
clientMutationId: req.body.clientMutationId,
|
|
1593
|
+
orgId: existing.orgId,
|
|
1594
|
+
userId,
|
|
1595
|
+
});
|
|
1596
|
+
const actor = getActorInfo(req);
|
|
1597
|
+
await logActivity(db, {
|
|
1598
|
+
orgId: existing.orgId,
|
|
1599
|
+
actorType: actor.actorType,
|
|
1600
|
+
actorId: actor.actorId,
|
|
1601
|
+
agentId: actor.agentId,
|
|
1602
|
+
runId: actor.runId,
|
|
1603
|
+
action: "chat.side_chat_created",
|
|
1604
|
+
entityType: "chat",
|
|
1605
|
+
entityId: sideChat.id,
|
|
1606
|
+
details: {
|
|
1607
|
+
sourceConversationId: existing.id,
|
|
1608
|
+
sourceMessageId: req.body.sourceMessageId,
|
|
1609
|
+
},
|
|
1610
|
+
});
|
|
1611
|
+
res.status(201).json(await assistantSvc.enrichConversation(sideChat));
|
|
1612
|
+
});
|
|
1613
|
+
router.post("/chats/:id/side-chat/complete", async (req, res) => {
|
|
1614
|
+
assertBoard(req);
|
|
1615
|
+
const existing = await assertConversationAccess(req, req.params.id);
|
|
1616
|
+
if (!existing || existing.conversationKind !== "side_chat") {
|
|
1617
|
+
res.status(404).json({ error: "Side Chat not found" });
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const userId = boardUserId(req);
|
|
1621
|
+
const sideChat = await sideChats.complete({
|
|
1622
|
+
conversationId: req.params.id,
|
|
1623
|
+
userId,
|
|
1624
|
+
});
|
|
1625
|
+
const actor = getActorInfo(req);
|
|
1626
|
+
await logActivity(db, {
|
|
1627
|
+
orgId: sideChat.orgId,
|
|
1628
|
+
actorType: actor.actorType,
|
|
1629
|
+
actorId: actor.actorId,
|
|
1630
|
+
agentId: actor.agentId,
|
|
1631
|
+
runId: actor.runId,
|
|
1632
|
+
action: "chat.side_chat_completed",
|
|
1633
|
+
entityType: "chat",
|
|
1634
|
+
entityId: sideChat.id,
|
|
1635
|
+
details: {
|
|
1636
|
+
sourceConversationId: sideChat.forkedFromConversationId,
|
|
1637
|
+
sourceMessageId: sideChat.forkedFromMessageId,
|
|
1638
|
+
},
|
|
1639
|
+
});
|
|
1640
|
+
res.json(await assistantSvc.enrichConversation(sideChat));
|
|
1641
|
+
});
|
|
1642
|
+
router.post("/chats/:id/side-chat/keep", async (req, res) => {
|
|
1643
|
+
assertBoard(req);
|
|
1644
|
+
const existing = await assertConversationAccess(req, req.params.id);
|
|
1645
|
+
if (!existing || existing.conversationKind !== "side_chat") {
|
|
1646
|
+
res.status(404).json({ error: "Side Chat not found" });
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
const userId = boardUserId(req);
|
|
1650
|
+
const sideChat = await sideChats.keepInMessenger({
|
|
1651
|
+
conversationId: req.params.id,
|
|
1652
|
+
userId,
|
|
1653
|
+
});
|
|
1654
|
+
const actor = getActorInfo(req);
|
|
1655
|
+
await logActivity(db, {
|
|
1656
|
+
orgId: sideChat.orgId,
|
|
1657
|
+
actorType: actor.actorType,
|
|
1658
|
+
actorId: actor.actorId,
|
|
1659
|
+
agentId: actor.agentId,
|
|
1660
|
+
runId: actor.runId,
|
|
1661
|
+
action: "chat.side_chat_kept",
|
|
1662
|
+
entityType: "chat",
|
|
1663
|
+
entityId: sideChat.id,
|
|
1664
|
+
details: {
|
|
1665
|
+
sourceConversationId: sideChat.forkedFromConversationId,
|
|
1666
|
+
sourceMessageId: sideChat.forkedFromMessageId,
|
|
1667
|
+
},
|
|
1668
|
+
});
|
|
1669
|
+
res.json(await assistantSvc.enrichConversation(sideChat));
|
|
1670
|
+
});
|
|
1029
1671
|
router.delete("/chats/:id", async (req, res) => {
|
|
1030
1672
|
assertBoard(req);
|
|
1031
1673
|
const existing = await assertConversationAccess(req, req.params.id);
|
|
@@ -1034,6 +1676,9 @@ export function chatRoutes(db, storage) {
|
|
|
1034
1676
|
return;
|
|
1035
1677
|
}
|
|
1036
1678
|
assertChatLocalMutationAllowed(existing);
|
|
1679
|
+
if (existing.conversationKind === "side_chat") {
|
|
1680
|
+
throw conflict("Side Chat audit records cannot be deleted");
|
|
1681
|
+
}
|
|
1037
1682
|
if (hasActiveChatGeneration(existing.id)) {
|
|
1038
1683
|
if (req.query.cancelActive === "true") {
|
|
1039
1684
|
cancelAndReleaseActiveChatGeneration(existing.id);
|
|
@@ -1050,7 +1695,9 @@ export function chatRoutes(db, storage) {
|
|
|
1050
1695
|
}
|
|
1051
1696
|
for (const attachment of attachments) {
|
|
1052
1697
|
try {
|
|
1053
|
-
await
|
|
1698
|
+
if (!await svc.assetHasAttachments(attachment.assetId)) {
|
|
1699
|
+
await storage.deleteObject(attachment.orgId, attachment.objectKey);
|
|
1700
|
+
}
|
|
1054
1701
|
}
|
|
1055
1702
|
catch (err) {
|
|
1056
1703
|
logger.warn({ err, conversationId: existing.id, attachmentId: attachment.id }, "failed to delete chat attachment object during chat delete");
|
|
@@ -1088,12 +1735,15 @@ export function chatRoutes(db, storage) {
|
|
|
1088
1735
|
return;
|
|
1089
1736
|
}
|
|
1090
1737
|
assertChatLocalMutationAllowed(conversation);
|
|
1738
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1739
|
+
const requestActor = queueRequestActor(req);
|
|
1091
1740
|
const item = await svc.createQueuedMessage({
|
|
1092
1741
|
orgId: conversation.orgId,
|
|
1093
1742
|
conversationId: conversation.id,
|
|
1094
1743
|
clientMutationId: req.body.clientMutationId,
|
|
1095
1744
|
expectedGenerationId: req.body.expectedGenerationId ?? getActiveChatGeneration(conversation.id)?.generationId ?? null,
|
|
1096
1745
|
payload: req.body.payload,
|
|
1746
|
+
requestActor,
|
|
1097
1747
|
});
|
|
1098
1748
|
const actor = getActorInfo(req);
|
|
1099
1749
|
await logActivity(db, {
|
|
@@ -1110,6 +1760,7 @@ export function chatRoutes(db, storage) {
|
|
|
1110
1760
|
position: item.position,
|
|
1111
1761
|
},
|
|
1112
1762
|
});
|
|
1763
|
+
wakeServerQueue();
|
|
1113
1764
|
res.status(201).json(item);
|
|
1114
1765
|
});
|
|
1115
1766
|
router.post("/chats/:id/queue/next/claim", async (req, res) => {
|
|
@@ -1119,6 +1770,7 @@ export function chatRoutes(db, storage) {
|
|
|
1119
1770
|
return;
|
|
1120
1771
|
}
|
|
1121
1772
|
assertChatLocalMutationAllowed(conversation);
|
|
1773
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1122
1774
|
if (hasActiveChatGeneration(conversation.id)) {
|
|
1123
1775
|
throw conflict("Cannot dequeue the next message while a reply is in progress");
|
|
1124
1776
|
}
|
|
@@ -1153,6 +1805,7 @@ export function chatRoutes(db, storage) {
|
|
|
1153
1805
|
return;
|
|
1154
1806
|
}
|
|
1155
1807
|
assertChatLocalMutationAllowed(conversation);
|
|
1808
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1156
1809
|
const item = await svc.releaseQueuedMessageClaim({
|
|
1157
1810
|
conversationId: conversation.id,
|
|
1158
1811
|
itemId: req.params.itemId,
|
|
@@ -1167,6 +1820,7 @@ export function chatRoutes(db, storage) {
|
|
|
1167
1820
|
return;
|
|
1168
1821
|
}
|
|
1169
1822
|
assertChatLocalMutationAllowed(conversation);
|
|
1823
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1170
1824
|
const item = await svc.updateQueuedMessage({
|
|
1171
1825
|
conversationId: conversation.id,
|
|
1172
1826
|
itemId: req.params.itemId,
|
|
@@ -1182,6 +1836,7 @@ export function chatRoutes(db, storage) {
|
|
|
1182
1836
|
return;
|
|
1183
1837
|
}
|
|
1184
1838
|
assertChatLocalMutationAllowed(conversation);
|
|
1839
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1185
1840
|
const parsed = cancelChatQueuedMessageSchema.safeParse(req.body ?? {});
|
|
1186
1841
|
if (!parsed.success) {
|
|
1187
1842
|
res.status(400).json({ error: "Invalid queued message cancel request", details: parsed.error.issues });
|
|
@@ -1201,27 +1856,283 @@ export function chatRoutes(db, storage) {
|
|
|
1201
1856
|
return;
|
|
1202
1857
|
}
|
|
1203
1858
|
assertChatLocalMutationAllowed(conversation);
|
|
1859
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1204
1860
|
const active = getActiveChatGeneration(conversation.id);
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1861
|
+
const requestActor = queueRequestActor(req);
|
|
1862
|
+
const controlActionId = req.body.controlActionId ?? randomUUID();
|
|
1863
|
+
const expectedGenerationId = req.body.expectedActiveGenerationId
|
|
1864
|
+
?? active?.generationId
|
|
1865
|
+
?? null;
|
|
1866
|
+
if (!expectedGenerationId) {
|
|
1867
|
+
const scheduled = await svc.scheduleSteerContinuation({
|
|
1868
|
+
orgId: conversation.orgId,
|
|
1869
|
+
conversationId: conversation.id,
|
|
1870
|
+
itemId: req.params.itemId,
|
|
1871
|
+
controlActionId,
|
|
1872
|
+
requestActor,
|
|
1873
|
+
});
|
|
1874
|
+
wakeServerQueue();
|
|
1875
|
+
res.json({
|
|
1876
|
+
item: scheduled.item,
|
|
1877
|
+
result: "scheduled_next",
|
|
1878
|
+
disposition: "continuation_pending",
|
|
1879
|
+
controlActionId,
|
|
1880
|
+
activeGenerationId: scheduled.action.expectedGenerationId ?? null,
|
|
1881
|
+
queueVersion: scheduled.item.version,
|
|
1882
|
+
transcriptEventId: null,
|
|
1883
|
+
});
|
|
1884
|
+
return;
|
|
1885
|
+
}
|
|
1886
|
+
const queueSnapshot = await svc.getQueueSnapshot(conversation.id, expectedGenerationId);
|
|
1887
|
+
const expectedAttemptEpoch = req.body.expectedAttemptEpoch
|
|
1888
|
+
?? queueSnapshot.activeAttemptEpoch
|
|
1889
|
+
?? active?.attemptEpoch
|
|
1890
|
+
?? 0;
|
|
1891
|
+
const expectedControlVersion = req.body.expectedControlVersion
|
|
1892
|
+
?? queueSnapshot.activeControlVersion
|
|
1893
|
+
?? 0;
|
|
1894
|
+
const started = await svc.beginSteerControlAction({
|
|
1895
|
+
orgId: conversation.orgId,
|
|
1213
1896
|
conversationId: conversation.id,
|
|
1214
1897
|
itemId: req.params.itemId,
|
|
1215
|
-
|
|
1216
|
-
|
|
1898
|
+
controlActionId,
|
|
1899
|
+
expectedGenerationId,
|
|
1900
|
+
expectedAttemptEpoch,
|
|
1901
|
+
expectedControlVersion,
|
|
1902
|
+
requestActor,
|
|
1217
1903
|
});
|
|
1218
|
-
|
|
1904
|
+
const durableControlActionId = started.action.id;
|
|
1905
|
+
const durableGenerationId = started.action.expectedGenerationId ?? expectedGenerationId;
|
|
1906
|
+
const durableAttemptEpoch = started.action.expectedAttemptEpoch ?? expectedAttemptEpoch;
|
|
1907
|
+
const responseForDurableDisposition = (item, disposition) => ({
|
|
1219
1908
|
item,
|
|
1220
|
-
result:
|
|
1221
|
-
|
|
1909
|
+
result: disposition === "accepted_current"
|
|
1910
|
+
? "delivered_current"
|
|
1911
|
+
: disposition === "acceptance_unknown"
|
|
1912
|
+
? "acceptance_unknown"
|
|
1913
|
+
: disposition === "continuation_pending"
|
|
1914
|
+
? "scheduled_next"
|
|
1915
|
+
: disposition === "failed_actionable"
|
|
1916
|
+
? "failed_actionable"
|
|
1917
|
+
: "pending",
|
|
1918
|
+
disposition,
|
|
1919
|
+
controlActionId: durableControlActionId,
|
|
1920
|
+
activeGenerationId: durableGenerationId,
|
|
1222
1921
|
queueVersion: item.version,
|
|
1223
1922
|
transcriptEventId: null,
|
|
1224
1923
|
});
|
|
1924
|
+
if (started.idempotent && started.action.localDisposition !== "pending") {
|
|
1925
|
+
if (started.action.localDisposition === "continuation_pending")
|
|
1926
|
+
wakeServerQueue();
|
|
1927
|
+
res.json(responseForDurableDisposition(started.item, started.action.localDisposition));
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
if (started.action.localDisposition === "continuation_pending") {
|
|
1931
|
+
wakeServerQueue();
|
|
1932
|
+
res.json(responseForDurableDisposition(started.item, started.action.localDisposition));
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
const providerSendState = { denied: null };
|
|
1936
|
+
const runtimeResult = await steerActiveChatGeneration({
|
|
1937
|
+
conversationId: conversation.id,
|
|
1938
|
+
expectedGenerationId: durableGenerationId,
|
|
1939
|
+
expectedAttemptEpoch: durableAttemptEpoch,
|
|
1940
|
+
feedback: {
|
|
1941
|
+
text: started.item.payload.body,
|
|
1942
|
+
clientMessageId: started.action.providerClientMessageId ?? durableControlActionId,
|
|
1943
|
+
},
|
|
1944
|
+
claimProviderSend: async () => {
|
|
1945
|
+
const sendClaim = await svc.claimSteerProviderSend({
|
|
1946
|
+
orgId: conversation.orgId,
|
|
1947
|
+
controlActionId: durableControlActionId,
|
|
1948
|
+
});
|
|
1949
|
+
if (!sendClaim)
|
|
1950
|
+
return null;
|
|
1951
|
+
if ("sendDenied" in sendClaim) {
|
|
1952
|
+
providerSendState.denied = sendClaim;
|
|
1953
|
+
return {
|
|
1954
|
+
sendDenied: true,
|
|
1955
|
+
reason: "generation_fence_changed",
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
try {
|
|
1959
|
+
await svc.appendGenerationEvent({
|
|
1960
|
+
orgId: conversation.orgId,
|
|
1961
|
+
generationId: durableGenerationId,
|
|
1962
|
+
attemptEpoch: durableAttemptEpoch,
|
|
1963
|
+
eventKind: "steer_requested",
|
|
1964
|
+
payload: { controlActionId: durableControlActionId, queueItemId: started.item.id },
|
|
1965
|
+
controlActionId: durableControlActionId,
|
|
1966
|
+
queueItemId: started.item.id,
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
catch (error) {
|
|
1970
|
+
await svc.releaseSteerProviderSendClaim({
|
|
1971
|
+
orgId: conversation.orgId,
|
|
1972
|
+
controlActionId: durableControlActionId,
|
|
1973
|
+
reason: "steer_requested_event_failed",
|
|
1974
|
+
}).catch(() => null);
|
|
1975
|
+
throw error;
|
|
1976
|
+
}
|
|
1977
|
+
return {
|
|
1978
|
+
clientMessageId: sendClaim.providerClientMessageId ?? durableControlActionId,
|
|
1979
|
+
release: async () => {
|
|
1980
|
+
const released = await svc.releaseSteerProviderSendClaim({
|
|
1981
|
+
orgId: conversation.orgId,
|
|
1982
|
+
controlActionId: durableControlActionId,
|
|
1983
|
+
reason: "runtime_owner_changed_before_provider_send",
|
|
1984
|
+
});
|
|
1985
|
+
if (!released) {
|
|
1986
|
+
throw new Error("Steer provider send claim could not be safely released before send");
|
|
1987
|
+
}
|
|
1988
|
+
},
|
|
1989
|
+
};
|
|
1990
|
+
},
|
|
1991
|
+
});
|
|
1992
|
+
if (runtimeResult.status === "provider_send_in_flight") {
|
|
1993
|
+
res.json(responseForDurableDisposition(started.item, started.action.localDisposition));
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
const deniedProviderSend = providerSendState.denied;
|
|
1997
|
+
if (deniedProviderSend) {
|
|
1998
|
+
if (deniedProviderSend.reason === "stop_cutoff_won_before_provider_send") {
|
|
1999
|
+
await interruptActiveChatGeneration(conversation.id, "steer_fallback");
|
|
2000
|
+
}
|
|
2001
|
+
wakeServerQueue();
|
|
2002
|
+
res.json(responseForDurableDisposition(deniedProviderSend.item, deniedProviderSend.action.localDisposition));
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
2005
|
+
let resolution;
|
|
2006
|
+
if (runtimeResult.status === "delivered_current") {
|
|
2007
|
+
resolution = await svc.resolveSteerControlAction({
|
|
2008
|
+
orgId: conversation.orgId,
|
|
2009
|
+
conversationId: conversation.id,
|
|
2010
|
+
itemId: started.item.id,
|
|
2011
|
+
controlActionId: durableControlActionId,
|
|
2012
|
+
status: "accepted_current",
|
|
2013
|
+
disposition: "accepted_current",
|
|
2014
|
+
providerDisposition: "acknowledged",
|
|
2015
|
+
providerThreadId: runtimeResult.providerThreadId,
|
|
2016
|
+
providerTurnId: runtimeResult.providerTurnId,
|
|
2017
|
+
providerEvidence: {
|
|
2018
|
+
receipt: "same_turn",
|
|
2019
|
+
attemptEpoch: runtimeResult.attemptEpoch,
|
|
2020
|
+
ownerChangedAfterSend: runtimeResult.ownerChangedAfterSend === true,
|
|
2021
|
+
},
|
|
2022
|
+
});
|
|
2023
|
+
await svc.appendGenerationEvent({
|
|
2024
|
+
orgId: conversation.orgId,
|
|
2025
|
+
generationId: durableGenerationId,
|
|
2026
|
+
attemptEpoch: runtimeResult.attemptEpoch,
|
|
2027
|
+
eventKind: "steer_acknowledged",
|
|
2028
|
+
payload: {
|
|
2029
|
+
controlActionId: durableControlActionId,
|
|
2030
|
+
providerThreadId: runtimeResult.providerThreadId,
|
|
2031
|
+
providerTurnId: runtimeResult.providerTurnId,
|
|
2032
|
+
},
|
|
2033
|
+
controlActionId: durableControlActionId,
|
|
2034
|
+
queueItemId: started.item.id,
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
else if (runtimeResult.status === "acceptance_unknown") {
|
|
2038
|
+
resolution = await svc.resolveSteerControlAction({
|
|
2039
|
+
orgId: conversation.orgId,
|
|
2040
|
+
conversationId: conversation.id,
|
|
2041
|
+
itemId: started.item.id,
|
|
2042
|
+
controlActionId: durableControlActionId,
|
|
2043
|
+
status: "acceptance_unknown",
|
|
2044
|
+
disposition: "acceptance_unknown",
|
|
2045
|
+
providerDisposition: "connection_lost",
|
|
2046
|
+
reason: runtimeResult.reason,
|
|
2047
|
+
providerEvidence: {
|
|
2048
|
+
attemptEpoch: runtimeResult.attemptEpoch,
|
|
2049
|
+
ownerChangedAfterSend: runtimeResult.ownerChangedAfterSend === true,
|
|
2050
|
+
},
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
else if (runtimeResult.status === "continuation_required") {
|
|
2054
|
+
const cutoff = await svc.generationProtocol.beginSteerFallbackCutoff({
|
|
2055
|
+
orgId: conversation.orgId,
|
|
2056
|
+
conversationId: conversation.id,
|
|
2057
|
+
generationId: durableGenerationId,
|
|
2058
|
+
expectedAttemptEpoch: durableAttemptEpoch,
|
|
2059
|
+
controlActionId: durableControlActionId,
|
|
2060
|
+
queueItemId: started.item.id,
|
|
2061
|
+
requestedRenderSeq: req.body.lastCommittedRenderSeq,
|
|
2062
|
+
requestedBodyHash: req.body.renderedBodyHash,
|
|
2063
|
+
});
|
|
2064
|
+
const completionCommitted = cutoff.outcome === "completion_committed";
|
|
2065
|
+
const interrupt = completionCommitted
|
|
2066
|
+
? null
|
|
2067
|
+
: interruptActiveChatGeneration(conversation.id, "steer_fallback");
|
|
2068
|
+
const interruptDisposition = interrupt ? await interrupt : "unverified";
|
|
2069
|
+
resolution = await svc.resolveSteerControlAction({
|
|
2070
|
+
orgId: conversation.orgId,
|
|
2071
|
+
conversationId: conversation.id,
|
|
2072
|
+
itemId: started.item.id,
|
|
2073
|
+
controlActionId: durableControlActionId,
|
|
2074
|
+
status: "continuation_pending",
|
|
2075
|
+
disposition: "continuation_pending",
|
|
2076
|
+
providerDisposition: "not_sent",
|
|
2077
|
+
providerEvidence: {
|
|
2078
|
+
...(cutoff.action.providerEvidence ?? {}),
|
|
2079
|
+
...(completionCommitted
|
|
2080
|
+
? { completionDisposition: "committed" }
|
|
2081
|
+
: {
|
|
2082
|
+
interruptDisposition,
|
|
2083
|
+
acceptedThroughSeq: cutoff.action.acceptedThroughSeq,
|
|
2084
|
+
frozenBodyHash: cutoff.action.frozenBodyHash,
|
|
2085
|
+
}),
|
|
2086
|
+
},
|
|
2087
|
+
reason: completionCommitted
|
|
2088
|
+
? "target_generation_completion_committed"
|
|
2089
|
+
: interrupt
|
|
2090
|
+
? "runtime_requires_continuation"
|
|
2091
|
+
: "runtime_owner_missing_during_steer_fallback",
|
|
2092
|
+
});
|
|
2093
|
+
if (!completionCommitted && !interrupt) {
|
|
2094
|
+
await svc.generationProtocol.recordRuntimeTerminal({
|
|
2095
|
+
orgId: conversation.orgId,
|
|
2096
|
+
conversationId: conversation.id,
|
|
2097
|
+
generationId: durableGenerationId,
|
|
2098
|
+
expectedAttemptEpoch: durableAttemptEpoch,
|
|
2099
|
+
finalStatus: "interrupted_unverified",
|
|
2100
|
+
terminalReason: "steer_fallback_runtime_owner_missing",
|
|
2101
|
+
controlActionId: durableControlActionId,
|
|
2102
|
+
payload: { interruptDisposition },
|
|
2103
|
+
});
|
|
2104
|
+
wakeTerminalProjector();
|
|
2105
|
+
}
|
|
2106
|
+
wakeServerQueue();
|
|
2107
|
+
}
|
|
2108
|
+
else {
|
|
2109
|
+
resolution = await svc.resolveSteerControlAction({
|
|
2110
|
+
orgId: conversation.orgId,
|
|
2111
|
+
conversationId: conversation.id,
|
|
2112
|
+
itemId: started.item.id,
|
|
2113
|
+
controlActionId: durableControlActionId,
|
|
2114
|
+
status: "continuation_pending",
|
|
2115
|
+
disposition: "continuation_pending",
|
|
2116
|
+
providerDisposition: "not_sent",
|
|
2117
|
+
reason: "stale_generation",
|
|
2118
|
+
});
|
|
2119
|
+
await svc.appendGenerationEvent({
|
|
2120
|
+
orgId: conversation.orgId,
|
|
2121
|
+
generationId: durableGenerationId,
|
|
2122
|
+
attemptEpoch: durableAttemptEpoch,
|
|
2123
|
+
eventKind: "continuation_scheduled",
|
|
2124
|
+
payload: { controlActionId: durableControlActionId, reason: "stale_generation" },
|
|
2125
|
+
controlActionId: durableControlActionId,
|
|
2126
|
+
queueItemId: started.item.id,
|
|
2127
|
+
});
|
|
2128
|
+
wakeServerQueue();
|
|
2129
|
+
}
|
|
2130
|
+
res.json({
|
|
2131
|
+
...responseForDurableDisposition(resolution.item, resolution.action.localDisposition),
|
|
2132
|
+
activeGenerationId: durableGenerationId,
|
|
2133
|
+
queueVersion: resolution.item.version,
|
|
2134
|
+
transcriptEventId: null,
|
|
2135
|
+
});
|
|
1225
2136
|
});
|
|
1226
2137
|
router.get("/chats/:id/messages", async (req, res) => {
|
|
1227
2138
|
const conversation = await assertConversationAccess(req, req.params.id);
|
|
@@ -1261,6 +2172,7 @@ export function chatRoutes(db, storage) {
|
|
|
1261
2172
|
}
|
|
1262
2173
|
const actor = getActorInfo(req);
|
|
1263
2174
|
assertChatLocalMutationAllowed(conversation);
|
|
2175
|
+
await assertSideChatMutationAllowed(req, conversation);
|
|
1264
2176
|
if (actor.actorType === "agent") {
|
|
1265
2177
|
if (req.body.editUserMessageId) {
|
|
1266
2178
|
res.status(422).json({ error: "Agent-authored chat messages cannot edit operator messages" });
|
|
@@ -1282,6 +2194,7 @@ export function chatRoutes(db, storage) {
|
|
|
1282
2194
|
conversationId: conversation.id,
|
|
1283
2195
|
clientMutationId: `message:${randomUUID()}`,
|
|
1284
2196
|
expectedGenerationId: getActiveChatGeneration(conversation.id)?.generationId ?? null,
|
|
2197
|
+
requestActor: queueRequestActor(req),
|
|
1285
2198
|
payload: {
|
|
1286
2199
|
body: req.body.body,
|
|
1287
2200
|
attachmentIds: [],
|
|
@@ -1295,11 +2208,13 @@ export function chatRoutes(db, storage) {
|
|
|
1295
2208
|
},
|
|
1296
2209
|
},
|
|
1297
2210
|
});
|
|
2211
|
+
wakeServerQueue();
|
|
1298
2212
|
res.status(202).json({ queued: item });
|
|
1299
2213
|
return;
|
|
1300
2214
|
}
|
|
1301
2215
|
try {
|
|
1302
2216
|
const userMessage = await addUserMessage(conversation, req.body.body, actor, req.body.editUserMessageId ?? null);
|
|
2217
|
+
await touchSideChat(req, conversation);
|
|
1303
2218
|
if (!req.body.editUserMessageId) {
|
|
1304
2219
|
startChatTitleGeneration(conversation, userMessage);
|
|
1305
2220
|
}
|
|
@@ -1388,6 +2303,8 @@ export function chatRoutes(db, storage) {
|
|
|
1388
2303
|
heartbeat,
|
|
1389
2304
|
assertConversationAccess,
|
|
1390
2305
|
assertChatLocalMutationAllowed,
|
|
2306
|
+
assertSideChatMutationAllowed,
|
|
2307
|
+
touchSideChat,
|
|
1391
2308
|
boardUserId,
|
|
1392
2309
|
assertCanAssignTasks,
|
|
1393
2310
|
runSingleFileUpload,
|
|
@@ -1411,6 +2328,9 @@ export function chatRoutes(db, storage) {
|
|
|
1411
2328
|
recoverableFailurePayload,
|
|
1412
2329
|
recoverableFailureBody,
|
|
1413
2330
|
writeStreamEvent,
|
|
2331
|
+
queueRequestActor,
|
|
2332
|
+
wakeServerQueue,
|
|
2333
|
+
wakeTerminalProjector,
|
|
1414
2334
|
});
|
|
1415
2335
|
return router;
|
|
1416
2336
|
}
|