@rudderhq/server 0.5.2-canary.2 → 0.6.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/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +15 -0
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bootstrap/types.d.ts +11 -0
- package/dist/bootstrap/types.d.ts.map +1 -1
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +741 -7
- 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 +3 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -1
- package/dist/index.js.map +1 -1
- package/dist/local-runtime.d.ts +7 -0
- package/dist/local-runtime.d.ts.map +1 -1
- package/dist/local-runtime.js +15 -1
- package/dist/local-runtime.js.map +1 -1
- package/dist/middleware/auth.js +2 -2
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/error-handler.d.ts.map +1 -1
- package/dist/middleware/error-handler.js +3 -2
- package/dist/middleware/error-handler.js.map +1 -1
- package/dist/middleware/logger.d.ts +4 -0
- package/dist/middleware/logger.d.ts.map +1 -1
- package/dist/middleware/logger.js +82 -7
- package/dist/middleware/logger.js.map +1 -1
- package/dist/middleware/logger.test.js +94 -1
- package/dist/middleware/logger.test.js.map +1 -1
- package/dist/routes/chats.annotation-routes.d.ts +47 -0
- package/dist/routes/chats.annotation-routes.d.ts.map +1 -0
- package/dist/routes/chats.annotation-routes.js +164 -0
- package/dist/routes/chats.annotation-routes.js.map +1 -0
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +467 -165
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/chats.stream-routes.d.ts +1 -10
- package/dist/routes/chats.stream-routes.d.ts.map +1 -1
- package/dist/routes/chats.stream-routes.js +168 -110
- package/dist/routes/chats.stream-routes.js.map +1 -1
- package/dist/routes/chats.stream-support.d.ts +79 -0
- package/dist/routes/chats.stream-support.d.ts.map +1 -0
- package/dist/routes/chats.stream-support.js +121 -0
- package/dist/routes/chats.stream-support.js.map +1 -0
- package/dist/routes/issues.comments-attachments.d.ts.map +1 -1
- package/dist/routes/issues.comments-attachments.js +16 -2
- package/dist/routes/issues.comments-attachments.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +21 -4
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/issues.mutations.d.ts.map +1 -1
- package/dist/routes/issues.mutations.js +20 -33
- package/dist/routes/issues.mutations.js.map +1 -1
- package/dist/routes/managed-mcp-agent-bindings.d.ts +15 -0
- package/dist/routes/managed-mcp-agent-bindings.d.ts.map +1 -0
- package/dist/routes/managed-mcp-agent-bindings.js +66 -0
- package/dist/routes/managed-mcp-agent-bindings.js.map +1 -0
- package/dist/routes/managed-mcp-connections.d.ts +8 -0
- package/dist/routes/managed-mcp-connections.d.ts.map +1 -0
- package/dist/routes/managed-mcp-connections.js +189 -0
- package/dist/routes/managed-mcp-connections.js.map +1 -0
- package/dist/routes/managed-mcp-runtime.d.ts +5 -0
- package/dist/routes/managed-mcp-runtime.d.ts.map +1 -0
- package/dist/routes/managed-mcp-runtime.js +148 -0
- package/dist/routes/managed-mcp-runtime.js.map +1 -0
- package/dist/routes/messenger.d.ts.map +1 -1
- package/dist/routes/messenger.js +1 -1
- package/dist/routes/messenger.js.map +1 -1
- package/dist/services/access.d.ts.map +1 -1
- package/dist/services/access.js +91 -12
- package/dist/services/access.js.map +1 -1
- package/dist/services/agent-run-context.d.ts +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +5 -1
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/board-auth.d.ts +2 -2
- package/dist/services/chat-annotation-copy-lineage.d.ts +32 -0
- package/dist/services/chat-annotation-copy-lineage.d.ts.map +1 -0
- package/dist/services/chat-annotation-copy-lineage.js +110 -0
- package/dist/services/chat-annotation-copy-lineage.js.map +1 -0
- package/dist/services/chat-assistant.annotations.d.ts +16 -0
- package/dist/services/chat-assistant.annotations.d.ts.map +1 -0
- package/dist/services/chat-assistant.annotations.js +108 -0
- package/dist/services/chat-assistant.annotations.js.map +1 -0
- package/dist/services/chat-assistant.annotations.test.d.ts +2 -0
- package/dist/services/chat-assistant.annotations.test.d.ts.map +1 -0
- package/dist/services/chat-assistant.annotations.test.js +257 -0
- package/dist/services/chat-assistant.annotations.test.js.map +1 -0
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +2 -3
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.js +37 -53
- package/dist/services/chat-assistant.helpers.js.map +1 -1
- package/dist/services/chat-assistant.js +61 -5
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chat-generation-protocol.d.ts +8 -15
- package/dist/services/chat-generation-protocol.d.ts.map +1 -1
- package/dist/services/chat-generation-protocol.js +19 -59
- package/dist/services/chat-generation-protocol.js.map +1 -1
- package/dist/services/chat-generation-protocol.test.js +168 -6
- package/dist/services/chat-generation-protocol.test.js.map +1 -1
- package/dist/services/chat-generation-provenance.d.ts +14 -0
- package/dist/services/chat-generation-provenance.d.ts.map +1 -0
- package/dist/services/chat-generation-provenance.js +71 -0
- package/dist/services/chat-generation-provenance.js.map +1 -0
- package/dist/services/chat-inline-annotation-rendering.d.ts +2 -0
- package/dist/services/chat-inline-annotation-rendering.d.ts.map +1 -0
- package/dist/services/chat-inline-annotation-rendering.js +265 -0
- package/dist/services/chat-inline-annotation-rendering.js.map +1 -0
- package/dist/services/chat-inline-annotation-resolved-labels.d.ts +11 -0
- package/dist/services/chat-inline-annotation-resolved-labels.d.ts.map +1 -0
- package/dist/services/chat-inline-annotation-resolved-labels.js +490 -0
- package/dist/services/chat-inline-annotation-resolved-labels.js.map +1 -0
- package/dist/services/chat-inline-annotation-validation.d.ts +15 -0
- package/dist/services/chat-inline-annotation-validation.d.ts.map +1 -0
- package/dist/services/chat-inline-annotation-validation.js +430 -0
- package/dist/services/chat-inline-annotation-validation.js.map +1 -0
- package/dist/services/chat-inline-annotations.d.ts +18 -0
- package/dist/services/chat-inline-annotations.d.ts.map +1 -0
- package/dist/services/chat-inline-annotations.js +52 -0
- package/dist/services/chat-inline-annotations.js.map +1 -0
- package/dist/services/chat-inline-annotations.test.d.ts +2 -0
- package/dist/services/chat-inline-annotations.test.d.ts.map +1 -0
- package/dist/services/chat-inline-annotations.test.js +1306 -0
- package/dist/services/chat-inline-annotations.test.js.map +1 -0
- package/dist/services/chat-queued-message-materialization.d.ts +126 -0
- package/dist/services/chat-queued-message-materialization.d.ts.map +1 -0
- package/dist/services/chat-queued-message-materialization.js +327 -0
- package/dist/services/chat-queued-message-materialization.js.map +1 -0
- package/dist/services/chat-steer-messages.d.ts +3 -3
- package/dist/services/chat-steer-messages.d.ts.map +1 -1
- package/dist/services/chat-steer-messages.js +50 -80
- package/dist/services/chat-steer-messages.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.annotation-persistence.d.ts +20 -0
- package/dist/services/chats.annotation-persistence.d.ts.map +1 -0
- package/dist/services/chats.annotation-persistence.js +206 -0
- package/dist/services/chats.annotation-persistence.js.map +1 -0
- package/dist/services/chats.d.ts +129 -96
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.helpers.d.ts +2 -2
- package/dist/services/chats.inline-visual-persistence.d.ts +3 -3
- package/dist/services/chats.inline-visual-persistence.d.ts.map +1 -1
- package/dist/services/chats.inline-visual-persistence.js +114 -54
- package/dist/services/chats.inline-visual-persistence.js.map +1 -1
- package/dist/services/chats.js +743 -252
- package/dist/services/chats.js.map +1 -1
- package/dist/services/finance.d.ts +1 -1
- package/dist/services/index.d.ts +8 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/integrations/agent-integrations.d.ts +2 -2
- package/dist/services/integrations/custom-integrations.d.ts +3 -1
- package/dist/services/integrations/custom-integrations.d.ts.map +1 -1
- package/dist/services/integrations/custom-integrations.js +13 -2
- package/dist/services/integrations/custom-integrations.js.map +1 -1
- package/dist/services/issues.comments-attachments.d.ts +7 -1
- package/dist/services/issues.comments-attachments.d.ts.map +1 -1
- package/dist/services/issues.comments-attachments.js +13 -1
- package/dist/services/issues.comments-attachments.js.map +1 -1
- package/dist/services/issues.d.ts +6 -0
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +22 -3
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.d.ts +4 -1
- package/dist/services/knowledge-portability/organization-portability.core.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.js +17 -3
- package/dist/services/knowledge-portability/organization-portability.core.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.export.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.export.js +2 -2
- package/dist/services/knowledge-portability/organization-portability.export.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.import.js +13 -3
- package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts +3 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.catalog.js +54 -23
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.js +627 -124
- package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.sources.d.ts +1 -0
- package/dist/services/knowledge-portability/organization-skills.sources.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.sources.js +53 -10
- package/dist/services/knowledge-portability/organization-skills.sources.js.map +1 -1
- package/dist/services/mcp/authorizer-lock.d.ts +9 -0
- package/dist/services/mcp/authorizer-lock.d.ts.map +1 -0
- package/dist/services/mcp/authorizer-lock.js +15 -0
- package/dist/services/mcp/authorizer-lock.js.map +1 -0
- package/dist/services/mcp/managed-bindings.d.ts +17 -0
- package/dist/services/mcp/managed-bindings.d.ts.map +1 -0
- package/dist/services/mcp/managed-bindings.js +332 -0
- package/dist/services/mcp/managed-bindings.js.map +1 -0
- package/dist/services/mcp/managed-client.d.ts +64 -0
- package/dist/services/mcp/managed-client.d.ts.map +1 -0
- package/dist/services/mcp/managed-client.js +238 -0
- package/dist/services/mcp/managed-client.js.map +1 -0
- package/dist/services/mcp/managed-client.test.d.ts +2 -0
- package/dist/services/mcp/managed-client.test.d.ts.map +1 -0
- package/dist/services/mcp/managed-client.test.js +398 -0
- package/dist/services/mcp/managed-client.test.js.map +1 -0
- package/dist/services/mcp/managed-connections.d.ts +81 -0
- package/dist/services/mcp/managed-connections.d.ts.map +1 -0
- package/dist/services/mcp/managed-connections.js +920 -0
- package/dist/services/mcp/managed-connections.js.map +1 -0
- package/dist/services/mcp/managed-runtime.d.ts +134 -0
- package/dist/services/mcp/managed-runtime.d.ts.map +1 -0
- package/dist/services/mcp/managed-runtime.js +282 -0
- package/dist/services/mcp/managed-runtime.js.map +1 -0
- package/dist/services/mcp/oauth-provider.d.ts +48 -0
- package/dist/services/mcp/oauth-provider.d.ts.map +1 -0
- package/dist/services/mcp/oauth-provider.js +120 -0
- package/dist/services/mcp/oauth-provider.js.map +1 -0
- package/dist/services/mcp/oauth-scope.test.d.ts +2 -0
- package/dist/services/mcp/oauth-scope.test.d.ts.map +1 -0
- package/dist/services/mcp/oauth-scope.test.js +69 -0
- package/dist/services/mcp/oauth-scope.test.js.map +1 -0
- package/dist/services/mcp/oauth-session-gc.d.ts +14 -0
- package/dist/services/mcp/oauth-session-gc.d.ts.map +1 -0
- package/dist/services/mcp/oauth-session-gc.js +34 -0
- package/dist/services/mcp/oauth-session-gc.js.map +1 -0
- package/dist/services/mcp/oauth-session-gc.test.d.ts +2 -0
- package/dist/services/mcp/oauth-session-gc.test.d.ts.map +1 -0
- package/dist/services/mcp/oauth-session-gc.test.js +66 -0
- package/dist/services/mcp/oauth-session-gc.test.js.map +1 -0
- package/dist/services/mcp/oauth.d.ts +61 -0
- package/dist/services/mcp/oauth.d.ts.map +1 -0
- package/dist/services/mcp/oauth.js +1690 -0
- package/dist/services/mcp/oauth.js.map +1 -0
- package/dist/services/mcp/pinned-fetch.d.ts +15 -0
- package/dist/services/mcp/pinned-fetch.d.ts.map +1 -0
- package/dist/services/mcp/pinned-fetch.js +400 -0
- package/dist/services/mcp/pinned-fetch.js.map +1 -0
- package/dist/services/mcp/pinned-fetch.test.d.ts +2 -0
- package/dist/services/mcp/pinned-fetch.test.d.ts.map +1 -0
- package/dist/services/mcp/pinned-fetch.test.js +190 -0
- package/dist/services/mcp/pinned-fetch.test.js.map +1 -0
- package/dist/services/mcp/provider-registry.d.ts +77 -0
- package/dist/services/mcp/provider-registry.d.ts.map +1 -0
- package/dist/services/mcp/provider-registry.js +80 -0
- package/dist/services/mcp/provider-registry.js.map +1 -0
- package/dist/services/mcp/provider-registry.test.d.ts +2 -0
- package/dist/services/mcp/provider-registry.test.d.ts.map +1 -0
- package/dist/services/mcp/provider-registry.test.js +80 -0
- package/dist/services/mcp/provider-registry.test.js.map +1 -0
- package/dist/services/mcp/security-policy.d.ts +48 -0
- package/dist/services/mcp/security-policy.d.ts.map +1 -0
- package/dist/services/mcp/security-policy.js +344 -0
- package/dist/services/mcp/security-policy.js.map +1 -0
- package/dist/services/mcp/security-policy.test.d.ts +2 -0
- package/dist/services/mcp/security-policy.test.d.ts.map +1 -0
- package/dist/services/mcp/security-policy.test.js +226 -0
- package/dist/services/mcp/security-policy.test.js.map +1 -0
- package/dist/services/mcp/tool-discovery.d.ts +49 -0
- package/dist/services/mcp/tool-discovery.d.ts.map +1 -0
- package/dist/services/mcp/tool-discovery.js +198 -0
- package/dist/services/mcp/tool-discovery.js.map +1 -0
- package/dist/services/mcp/tool-discovery.test.d.ts +2 -0
- package/dist/services/mcp/tool-discovery.test.d.ts.map +1 -0
- package/dist/services/mcp/tool-discovery.test.js +168 -0
- package/dist/services/mcp/tool-discovery.test.js.map +1 -0
- package/dist/services/messenger.d.ts +1 -1
- package/dist/services/messenger.d.ts.map +1 -1
- package/dist/services/messenger.js +21 -3
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/plugin-registry.d.ts +3 -3
- package/dist/services/runtime-kernel/heartbeat.core.d.ts +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.core.js +30 -4
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.wakeup.js +38 -23
- package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -1
- package/dist/services/secrets.d.ts +64 -6
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +38 -15
- package/dist/services/secrets.js.map +1 -1
- package/dist/services/side-chats.d.ts +1 -1
- package/dist/services/side-chats.d.ts.map +1 -1
- package/dist/services/side-chats.js +56 -14
- package/dist/services/side-chats.js.map +1 -1
- package/dist/services/workspace-runtime.services.d.ts +2 -2
- package/dist/services/workspace-web-preview.js +1 -1
- package/package.json +16 -13
- package/resources/bundled-skills/rudder-docs/references/operating-practices.md +12 -5
- package/skills/rudder-docs/references/operating-practices.md +12 -5
- package/ui-dist/assets/LibraryLiveSurface-CKOthw54.js +1 -0
- package/ui-dist/assets/{abnfDiagram-VRR7QNED-KGtP7zoC.js → abnfDiagram-VRR7QNED-C6a_wMho.js} +1 -1
- package/ui-dist/assets/{arc-JjnKyiOb.js → arc-CP0En6KA.js} +1 -1
- package/ui-dist/assets/architectureDiagram-ZJ3FMSHR-AHfOdcaZ.js +36 -0
- package/ui-dist/assets/{blockDiagram-677ZJIJ3-D7YzMYrz.js → blockDiagram-677ZJIJ3-Bkl9lp63.js} +1 -1
- package/ui-dist/assets/{c4Diagram-LMCZKHZV-Dpf7UQxl.js → c4Diagram-LMCZKHZV-BpZareVt.js} +1 -1
- package/ui-dist/assets/channel-DqxL_Zcy.js +1 -0
- package/ui-dist/assets/{chunk-2Q5K7J3B-UJ3gsGlQ.js → chunk-2Q5K7J3B-BQAwVuXc.js} +1 -1
- package/ui-dist/assets/{chunk-32BRIVSS-BeHl2j2l.js → chunk-32BRIVSS-YkmfA08F.js} +1 -1
- package/ui-dist/assets/{chunk-5VM5RSS4-STE9fJd0.js → chunk-5VM5RSS4-DvvjwTnG.js} +1 -1
- package/ui-dist/assets/{chunk-EX3LRPZG-Bj8g8j4B.js → chunk-EX3LRPZG-TXD_Yhg5.js} +1 -1
- package/ui-dist/assets/{chunk-JWPE2WC7-0V4Pwta-.js → chunk-JWPE2WC7-CgMlj6XI.js} +1 -1
- package/ui-dist/assets/{chunk-MOJQB5TN-DNPn50gm.js → chunk-MOJQB5TN-BnJjU8Rc.js} +1 -1
- package/ui-dist/assets/{chunk-RYQCIY6F-Befmnv8r.js → chunk-RYQCIY6F-D9B64JKM.js} +1 -1
- package/ui-dist/assets/{chunk-V7JOEXUC-Boh8TjHj.js → chunk-V7JOEXUC-PS09QVpl.js} +1 -1
- package/ui-dist/assets/{chunk-VR4S4FIN-B_yEQSo3.js → chunk-VR4S4FIN-DQXb7pRS.js} +1 -1
- package/ui-dist/assets/{chunk-XXDRQBXY-Cf1GnUFx.js → chunk-XXDRQBXY-B1UdLiF2.js} +1 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-DMwjzuJE.js +1 -0
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-DMwjzuJE.js +1 -0
- package/ui-dist/assets/cose-bilkent-JH36ORCC-lqqmMB3m.js +1 -0
- package/ui-dist/assets/{cynefin-VYW2F7L2-Dv1yELYC.js → cynefin-VYW2F7L2-C9hu0XqZ.js} +1 -1
- package/ui-dist/assets/{cynefinDiagram-TSTJHNR4-CeFwjFoZ.js → cynefinDiagram-TSTJHNR4-Uuhz4c-1.js} +1 -1
- package/ui-dist/assets/{dagre-VKFMJZFB-8idd_4sX.js → dagre-VKFMJZFB-ChDrQYse.js} +1 -1
- package/ui-dist/assets/{diagram-FQU43EPY-BBVua6hM.js → diagram-FQU43EPY-DLtjkVSN.js} +1 -1
- package/ui-dist/assets/{diagram-G47NLZAW-D1zLQZlp.js → diagram-G47NLZAW-DCvEm4D0.js} +1 -1
- package/ui-dist/assets/{diagram-NH7WQ7WH-DE_FTSNv.js → diagram-NH7WQ7WH-Dz5jlsPG.js} +1 -1
- package/ui-dist/assets/{diagram-OA4YK3LP-61VSKBEn.js → diagram-OA4YK3LP-BXmQLO-Q.js} +1 -1
- package/ui-dist/assets/{diagram-WEI45ONY-C-tED6BJ.js → diagram-WEI45ONY-JnMU3xrx.js} +1 -1
- package/ui-dist/assets/{ebnfDiagram-CCIWWBDH-BEB8WGt0.js → ebnfDiagram-CCIWWBDH-Kryuqj4r.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q63AITRT-AN_jm1TZ.js → erDiagram-Q63AITRT-Bn7g2mSW.js} +1 -1
- package/ui-dist/assets/{flowDiagram-23GEKE2U-BwLC6tI7.js → flowDiagram-23GEKE2U-DcjqTK91.js} +4 -4
- package/ui-dist/assets/ganttDiagram-NO4QXBWP-BRkw9-GE.js +292 -0
- package/ui-dist/assets/{gitGraphDiagram-IHSO6WYX-D9HCL9rY.js → gitGraphDiagram-IHSO6WYX-Cexcp6kR.js} +1 -1
- package/ui-dist/assets/graph-Cu7OV4Oi.js +1 -0
- package/ui-dist/assets/{index-BQXnpwke.js → index-ASZMPBYm.js} +5 -5
- package/ui-dist/assets/{index-5OM32_rf.js → index-B17BqCTj.js} +1 -1
- package/ui-dist/assets/{index-Cf0gLy6a.js → index-B9MccU1g.js} +1 -1
- package/ui-dist/assets/{index-BoSJ4Rx1.js → index-BPqbtW_X.js} +1 -1
- package/ui-dist/assets/{index-CpIdZ4-7.js → index-C2HjDa98.js} +1 -1
- package/ui-dist/assets/index-CMPtpj0A.js +1 -0
- package/ui-dist/assets/{index-p0ZGU1_7.js → index-CZXgOYtk.js} +1 -1
- package/ui-dist/assets/{index-74cx3ESJ.js → index-CknAauOP.js} +1 -1
- package/ui-dist/assets/index-Cu1GuH0a.js +1 -0
- package/ui-dist/assets/index-D8PdEImm.css +1 -0
- package/ui-dist/assets/{index-DrVDiJhe.js → index-DB1YX9-p.js} +1 -1
- package/ui-dist/assets/{index-vHMnAQSb.js → index-DBxLZXK7.js} +624 -610
- package/ui-dist/assets/{index-CpEfk5zb.js → index-DOCVOD97.js} +1 -1
- package/ui-dist/assets/{index-DMuWG0YN.js → index-DUbakRAC.js} +1 -1
- package/ui-dist/assets/{index-AT8juaIi.js → index-Dbg1ZwO2.js} +1 -1
- package/ui-dist/assets/index-DwbLsoLt.js +1 -0
- package/ui-dist/assets/index-hCLSotmS.js +1 -0
- package/ui-dist/assets/index-jrhEFc-j.js +1 -0
- package/ui-dist/assets/index-kz8ueXgo.js +1 -0
- package/ui-dist/assets/{infoDiagram-FWYZ7A6U-CyO3CAA1.js → infoDiagram-FWYZ7A6U-DugVGI3K.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-FXEZZL3T-BLLZuF1_.js → ishikawaDiagram-FXEZZL3T-CHy06Oev.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-5HDEW3XC-CWzQocvE.js → journeyDiagram-5HDEW3XC-BI0w53rb.js} +1 -1
- package/ui-dist/assets/{kanban-definition-HUTT4EX6-Dz4SFU-I.js → kanban-definition-HUTT4EX6-v_bLwx7B.js} +1 -1
- package/ui-dist/assets/layout-Dwp1s_bo.js +1 -0
- package/ui-dist/assets/{linear-BlzDlAaL.js → linear-DAV4STLZ.js} +1 -1
- package/ui-dist/assets/map-Bs6QpaaQ.js +1 -0
- package/ui-dist/assets/{mermaid.core-BtCydd-w.js → mermaid.core-fVmaL47V.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-LN4V7U3C-BeIkKRBx.js → mindmap-definition-LN4V7U3C-5dWD4dxm.js} +1 -1
- package/ui-dist/assets/{pegDiagram-2B236MQR-BZgpT4TN.js → pegDiagram-2B236MQR-DB2XsDPN.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ENE6RG2P-BRFggcg2.js → pieDiagram-ENE6RG2P-Bc5teLCS.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-ABIIQ3AL-CibUo3gT.js → quadrantDiagram-ABIIQ3AL-BlfNdETt.js} +1 -1
- package/ui-dist/assets/{railroadDiagram-RFXS5EU6-DINJqhgn.js → railroadDiagram-RFXS5EU6-BbXywkq3.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-TGXJPOKE-NegluQIe.js → requirementDiagram-TGXJPOKE-DU-CapdF.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-HTMAVEWB-Kq41yHAd.js → sankeyDiagram-HTMAVEWB-Ftvh3MqA.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-DBY2YBRQ-CngfEI4f.js → sequenceDiagram-DBY2YBRQ-CCtHt9V4.js} +1 -1
- package/ui-dist/assets/{sizeCapture-X5ZJPWSS-DUWRNlme.js → sizeCapture-X5ZJPWSS-x0jnIKHx.js} +1 -1
- package/ui-dist/assets/{stateDiagram-2N3HPSRC-BwHC_Fz4.js → stateDiagram-2N3HPSRC-2qCrydE7.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-CvGRUgRf.js +1 -0
- package/ui-dist/assets/{swimlanes-5IMT3BWC-Cqp2rT1K.js → swimlanes-5IMT3BWC-6kqJq5Xf.js} +2 -2
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-EBa7-qSc.js +8 -0
- package/ui-dist/assets/{timeline-definition-FHXFAJF6-BG3GYPCu.js → timeline-definition-FHXFAJF6-BuRz8KHZ.js} +1 -1
- package/ui-dist/assets/{vennDiagram-L72KCM5P-D1IFU27N.js → vennDiagram-L72KCM5P-BKzMZ_Rv.js} +1 -1
- package/ui-dist/assets/{wardleyDiagram-EHGQE667-DboO2kYu.js → wardleyDiagram-EHGQE667-8uXlFtBV.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-FW5EYKEG-vI2BGrXP.js → xychartDiagram-FW5EYKEG-CWKGlpF9.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/architectureDiagram-ZJ3FMSHR-DHyfFgK0.js +0 -36
- package/ui-dist/assets/channel-TMGI9Qt9.js +0 -1
- package/ui-dist/assets/classDiagram-OUVF2IWQ-r5zIpY6G.js +0 -1
- package/ui-dist/assets/classDiagram-v2-EOCWNBFH-r5zIpY6G.js +0 -1
- package/ui-dist/assets/cose-bilkent-JH36ORCC-fSNs9447.js +0 -1
- package/ui-dist/assets/ganttDiagram-NO4QXBWP-CJYhBF1C.js +0 -292
- package/ui-dist/assets/graph-DdBT9W3J.js +0 -1
- package/ui-dist/assets/index-6UIO-gg7.js +0 -1
- package/ui-dist/assets/index-BVkQVMFn.js +0 -1
- package/ui-dist/assets/index-BaAPxkqh.css +0 -1
- package/ui-dist/assets/index-CmCERTmS.js +0 -1
- package/ui-dist/assets/index-Cr4lUIaJ.js +0 -1
- package/ui-dist/assets/index-D0fAqlES.js +0 -1
- package/ui-dist/assets/index-DfNcAyZq.js +0 -1
- package/ui-dist/assets/layout-D3tY9q4Q.js +0 -1
- package/ui-dist/assets/map-Ctg1-a9N.js +0 -1
- package/ui-dist/assets/stateDiagram-v2-6OUMAXLB-C6P3NY96.js +0 -1
- package/ui-dist/assets/swimlanesDiagram-G3AALYLV-BNdDt62J.js +0 -8
package/dist/services/chats.js
CHANGED
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
import { agentIntegrationChatBindings, agentIntegrations, approvals, assets, chatAttachments, chatContextLinks, chatControlActions, chatConversations, chatConversationUserStates, chatGenerationEvents, chatGenerations, chatMessages, chatQueuedMessages } from "@rudderhq/db";
|
|
2
2
|
import { sanitizeChatStructuredPayload } from "@rudderhq/shared";
|
|
3
|
-
import {
|
|
3
|
+
import { withChatTranscriptGenerationProvenance } from "@rudderhq/shared/chat-transcript-provenance";
|
|
4
|
+
import { and, asc, desc, eq, gt, inArray, isNotNull, isNull, lt, lte, or, sql } from "drizzle-orm";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
5
|
-
import { conflict, notFound, unprocessable } from "../errors.js";
|
|
6
|
+
import { conflict, HttpError, notFound, unprocessable } from "../errors.js";
|
|
6
7
|
import { logActivity } from "./activity-log.js";
|
|
7
8
|
import { agentService } from "./agents.js";
|
|
8
9
|
import { approvalService } from "./approvals.js";
|
|
9
10
|
import { ensureChatFamilyGroup } from "./chat-family-groups.js";
|
|
10
11
|
import { chatGenerationProtocolService } from "./chat-generation-protocol.js";
|
|
12
|
+
import { validateCanonicalChatInlineAnnotations } from "./chat-inline-annotation-validation.js";
|
|
13
|
+
import { hydrateQueuedMessage, materializeQueuedUserMessage, normalizeQueuedMessagePayload, QUEUED_ANNOTATION_ASSETS_KEY, queuedAnnotationAssetState, queuedMessageMutationFingerprint, withQueuedAnnotationAssetState, } from "./chat-queued-message-materialization.js";
|
|
14
|
+
import { createChatAnnotationMessagePersistence } from "./chats.annotation-persistence.js";
|
|
11
15
|
import { ACTIVE_CHAT_GENERATION_STATUSES, CHAT_GENERATION_CONTROL_LEASE_MS, NATIVE_STEER_GENERATION_STATUSES, SERVER_QUEUE_RUNNING_STATUSES, } from "./chats.constants.js";
|
|
12
16
|
import { createChatConversation, createChatWithInitialMessage } from "./chats.create.js";
|
|
13
17
|
import { conversationMutability, nextForkTitle } from "./chats.fork-helpers.js";
|
|
14
18
|
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";
|
|
15
|
-
import {
|
|
19
|
+
import { copyForkChatMessages, listRecentUserChatMessages, updateTrustedInlineVisualMappings, } from "./chats.inline-visual-persistence.js";
|
|
16
20
|
import { issueApprovalService } from "./issue-approvals.js";
|
|
17
21
|
import { issueService } from "./issues.js";
|
|
18
22
|
import { normalizeLocalLibraryPathMarkdown } from "./library-path-markdown.js";
|
|
19
23
|
import { organizationService } from "./orgs.js";
|
|
20
24
|
import { isPostgresError } from "./postgres-errors.js";
|
|
21
25
|
const CHAT_TITLE_MAX_LENGTH = 200;
|
|
26
|
+
class InvalidQueueDeliveryActionLinkError extends Error {
|
|
27
|
+
}
|
|
22
28
|
export function chatService(db) {
|
|
23
29
|
const generationProtocol = chatGenerationProtocolService(db);
|
|
24
30
|
const QUEUED_MESSAGE_CLAIM_LEASE_MS = 2 * 60 * 1000;
|
|
@@ -27,6 +33,7 @@ export function chatService(db) {
|
|
|
27
33
|
const issueApprovalsSvc = issueApprovalService(db);
|
|
28
34
|
const organizationsSvc = organizationService(db);
|
|
29
35
|
const agentsSvc = agentService(db);
|
|
36
|
+
const addUserChatMessage = createChatAnnotationMessagePersistence(db, getMessage);
|
|
30
37
|
async function ensureConversationUserStates(rows, userId) {
|
|
31
38
|
if (rows.length === 0)
|
|
32
39
|
return;
|
|
@@ -415,30 +422,6 @@ export function chatService(db) {
|
|
|
415
422
|
function isQueuePositionConflict(error) {
|
|
416
423
|
return isPostgresError(error, "23505");
|
|
417
424
|
}
|
|
418
|
-
function normalizeQueuedPayload(payload) {
|
|
419
|
-
return {
|
|
420
|
-
body: String(payload.body ?? ""),
|
|
421
|
-
attachmentIds: Array.isArray(payload.attachmentIds)
|
|
422
|
-
? payload.attachmentIds.filter((id) => typeof id === "string")
|
|
423
|
-
: [],
|
|
424
|
-
projectId: typeof payload.projectId === "string" ? payload.projectId : null,
|
|
425
|
-
skillRefs: Array.isArray(payload.skillRefs)
|
|
426
|
-
? payload.skillRefs.filter((ref) => typeof ref === "string")
|
|
427
|
-
: [],
|
|
428
|
-
accessMode: typeof payload.accessMode === "string" ? payload.accessMode : null,
|
|
429
|
-
model: typeof payload.model === "string" ? payload.model : null,
|
|
430
|
-
effort: typeof payload.effort === "string" ? payload.effort : null,
|
|
431
|
-
metadata: payload.metadata && typeof payload.metadata === "object" && !Array.isArray(payload.metadata)
|
|
432
|
-
? payload.metadata
|
|
433
|
-
: null,
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function hydrateQueuedMessage(row) {
|
|
437
|
-
return {
|
|
438
|
-
...row,
|
|
439
|
-
payload: normalizeQueuedPayload(row.payload),
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
425
|
async function createGeneration(orgId, conversationId) {
|
|
443
426
|
const now = new Date();
|
|
444
427
|
const [row] = await db
|
|
@@ -447,8 +430,8 @@ export function chatService(db) {
|
|
|
447
430
|
orgId,
|
|
448
431
|
conversationId,
|
|
449
432
|
status: "active",
|
|
450
|
-
controlOwnerToken:
|
|
451
|
-
controlLeaseExpiresAt:
|
|
433
|
+
controlOwnerToken: null,
|
|
434
|
+
controlLeaseExpiresAt: null,
|
|
452
435
|
startedAt: now,
|
|
453
436
|
createdAt: now,
|
|
454
437
|
updatedAt: now,
|
|
@@ -552,6 +535,7 @@ export function chatService(db) {
|
|
|
552
535
|
}
|
|
553
536
|
async function listQueuedMessages(conversationId) {
|
|
554
537
|
await reclaimStaleQueuedMessageClaims(conversationId);
|
|
538
|
+
await reconcileDeliveredQueuedMessages({ conversationId });
|
|
555
539
|
const rows = await db
|
|
556
540
|
.select()
|
|
557
541
|
.from(chatQueuedMessages)
|
|
@@ -793,6 +777,10 @@ export function chatService(db) {
|
|
|
793
777
|
});
|
|
794
778
|
}
|
|
795
779
|
async function beginSteerControlAction(input) {
|
|
780
|
+
await reconcileDeliveredQueuedMessages({
|
|
781
|
+
orgId: input.orgId,
|
|
782
|
+
conversationId: input.conversationId,
|
|
783
|
+
});
|
|
796
784
|
return db.transaction(async (tx) => {
|
|
797
785
|
const existingAction = await tx
|
|
798
786
|
.select()
|
|
@@ -837,11 +825,29 @@ export function chatService(db) {
|
|
|
837
825
|
const item = await tx
|
|
838
826
|
.select()
|
|
839
827
|
.from(chatQueuedMessages)
|
|
840
|
-
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), inArray(chatQueuedMessages.status, ["queued", "steer_pending"])))
|
|
828
|
+
.where(and(eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), inArray(chatQueuedMessages.status, ["queued", "steer_pending", "failed_actionable"])))
|
|
841
829
|
.limit(1)
|
|
842
830
|
.then((rows) => rows[0] ?? null);
|
|
843
831
|
if (!item)
|
|
844
832
|
throw conflict("Queued feedback is no longer steerable");
|
|
833
|
+
if (item.status === "failed_actionable") {
|
|
834
|
+
const failedAction = item.controlActionId
|
|
835
|
+
? await tx
|
|
836
|
+
.select()
|
|
837
|
+
.from(chatControlActions)
|
|
838
|
+
.where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, input.orgId), eq(chatControlActions.actionKind, "steer")))
|
|
839
|
+
.limit(1)
|
|
840
|
+
.then((rows) => rows[0] ?? null)
|
|
841
|
+
: null;
|
|
842
|
+
const confirmedPreDeliveryFailure = Boolean(failedAction
|
|
843
|
+
&& ["not_sent", "rejected"].includes(failedAction.providerDisposition ?? "not_sent")
|
|
844
|
+
&& !item.sourceMessageId
|
|
845
|
+
&& !item.deliveredMessageId
|
|
846
|
+
&& !item.continuationMessageId);
|
|
847
|
+
if (!confirmedPreDeliveryFailure) {
|
|
848
|
+
throw conflict("Queued feedback delivery is not safely retryable");
|
|
849
|
+
}
|
|
850
|
+
}
|
|
845
851
|
if (item.status === "steer_pending") {
|
|
846
852
|
const boundAction = item.controlActionId
|
|
847
853
|
? await tx
|
|
@@ -1261,39 +1267,85 @@ export function chatService(db) {
|
|
|
1261
1267
|
return action;
|
|
1262
1268
|
});
|
|
1263
1269
|
}
|
|
1264
|
-
async function
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1270
|
+
async function createQueuedMessageWithStagedAttachments(input) {
|
|
1271
|
+
if ((input.payload.attachmentIds?.length ?? 0) > 0) {
|
|
1272
|
+
throw unprocessable("Queued messages cannot reference client-provided attachment ids");
|
|
1273
|
+
}
|
|
1274
|
+
const payload = normalizeQueuedMessagePayload(input.payload);
|
|
1275
|
+
if (!payload.body.trim() && (payload.inlineAnnotations?.length ?? 0) === 0) {
|
|
1276
|
+
throw unprocessable("Queued message body or at least one inline annotation is required");
|
|
1277
|
+
}
|
|
1278
|
+
const fingerprint = queuedMessageMutationFingerprint({
|
|
1279
|
+
payload,
|
|
1280
|
+
stagedAttachments: input.stagedAttachments,
|
|
1281
|
+
attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
|
|
1282
|
+
});
|
|
1276
1283
|
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
1277
1284
|
try {
|
|
1278
1285
|
return await db.transaction(async (tx) => {
|
|
1279
1286
|
const existing = await tx
|
|
1280
1287
|
.select()
|
|
1281
1288
|
.from(chatQueuedMessages)
|
|
1282
|
-
.where(and(eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.clientMutationId, input.clientMutationId)))
|
|
1289
|
+
.where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.clientMutationId, input.clientMutationId)))
|
|
1283
1290
|
.limit(1)
|
|
1284
1291
|
.then((rows) => rows[0] ?? null);
|
|
1285
1292
|
if (existing) {
|
|
1286
|
-
|
|
1293
|
+
const existingPrivateState = queuedAnnotationAssetState(existing.payload);
|
|
1294
|
+
const existingFingerprint = existingPrivateState?.fingerprint
|
|
1295
|
+
?? queuedMessageMutationFingerprint({
|
|
1296
|
+
payload: normalizeQueuedMessagePayload(existing.payload),
|
|
1297
|
+
stagedAttachments: [],
|
|
1298
|
+
attachmentFileIndexesByAnnotationId: new Map(),
|
|
1299
|
+
});
|
|
1300
|
+
if (existingFingerprint !== fingerprint) {
|
|
1287
1301
|
throw conflict("Queued message idempotency key reused with a different payload");
|
|
1288
1302
|
}
|
|
1289
|
-
return
|
|
1303
|
+
return {
|
|
1304
|
+
item: hydrateQueuedMessage(existing),
|
|
1305
|
+
accepted: false,
|
|
1306
|
+
cleanupAttachments: [...input.stagedAttachments],
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
await validateCanonicalChatInlineAnnotations(tx, {
|
|
1310
|
+
orgId: input.orgId,
|
|
1311
|
+
conversationId: input.conversationId,
|
|
1312
|
+
annotations: payload.inlineAnnotations ?? [],
|
|
1313
|
+
uploadedFileCount: input.stagedAttachments.length,
|
|
1314
|
+
attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
|
|
1315
|
+
});
|
|
1316
|
+
const assetIds = [];
|
|
1317
|
+
for (const attachment of input.stagedAttachments) {
|
|
1318
|
+
const [asset] = await tx
|
|
1319
|
+
.insert(assets)
|
|
1320
|
+
.values({
|
|
1321
|
+
orgId: input.orgId,
|
|
1322
|
+
provider: attachment.provider,
|
|
1323
|
+
objectKey: attachment.objectKey,
|
|
1324
|
+
contentType: attachment.contentType,
|
|
1325
|
+
byteSize: attachment.byteSize,
|
|
1326
|
+
sha256: attachment.sha256,
|
|
1327
|
+
originalFilename: attachment.originalFilename,
|
|
1328
|
+
createdByAgentId: attachment.createdByAgentId,
|
|
1329
|
+
createdByUserId: attachment.createdByUserId,
|
|
1330
|
+
})
|
|
1331
|
+
.returning({ id: assets.id });
|
|
1332
|
+
if (!asset)
|
|
1333
|
+
throw new Error("Failed to stage queued annotation asset");
|
|
1334
|
+
assetIds.push(asset.id);
|
|
1290
1335
|
}
|
|
1336
|
+
const persistedPayload = withQueuedAnnotationAssetState({
|
|
1337
|
+
payload,
|
|
1338
|
+
fingerprint,
|
|
1339
|
+
assetIds,
|
|
1340
|
+
stagedAttachments: input.stagedAttachments,
|
|
1341
|
+
attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
|
|
1342
|
+
});
|
|
1291
1343
|
const [positionRow] = await tx
|
|
1292
1344
|
.select({
|
|
1293
1345
|
nextPosition: sql `coalesce(max(${chatQueuedMessages.position}), 0) + 1`,
|
|
1294
1346
|
})
|
|
1295
1347
|
.from(chatQueuedMessages)
|
|
1296
|
-
.where(eq(chatQueuedMessages.conversationId, input.conversationId));
|
|
1348
|
+
.where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId)));
|
|
1297
1349
|
const [row] = await tx
|
|
1298
1350
|
.insert(chatQueuedMessages)
|
|
1299
1351
|
.values({
|
|
@@ -1301,14 +1353,18 @@ export function chatService(db) {
|
|
|
1301
1353
|
conversationId: input.conversationId,
|
|
1302
1354
|
clientMutationId: input.clientMutationId,
|
|
1303
1355
|
position: Number(positionRow?.nextPosition ?? 1),
|
|
1304
|
-
payload,
|
|
1356
|
+
payload: persistedPayload,
|
|
1305
1357
|
requestActor: input.requestActor ?? null,
|
|
1306
1358
|
expectedGenerationId: input.expectedGenerationId ?? null,
|
|
1307
1359
|
})
|
|
1308
1360
|
.returning();
|
|
1309
1361
|
if (!row)
|
|
1310
1362
|
throw new Error("Failed to create queued chat message");
|
|
1311
|
-
return
|
|
1363
|
+
return {
|
|
1364
|
+
item: hydrateQueuedMessage(row),
|
|
1365
|
+
accepted: true,
|
|
1366
|
+
cleanupAttachments: [],
|
|
1367
|
+
};
|
|
1312
1368
|
});
|
|
1313
1369
|
}
|
|
1314
1370
|
catch (error) {
|
|
@@ -1319,54 +1375,235 @@ export function chatService(db) {
|
|
|
1319
1375
|
}
|
|
1320
1376
|
throw new Error("Failed to create queued chat message");
|
|
1321
1377
|
}
|
|
1322
|
-
async function
|
|
1323
|
-
const
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1378
|
+
async function createQueuedMessage(input) {
|
|
1379
|
+
const result = await createQueuedMessageWithStagedAttachments({
|
|
1380
|
+
...input,
|
|
1381
|
+
stagedAttachments: [],
|
|
1382
|
+
attachmentFileIndexesByAnnotationId: new Map(),
|
|
1383
|
+
});
|
|
1384
|
+
return result.item;
|
|
1385
|
+
}
|
|
1386
|
+
async function queuedAssetCleanupRows(tx, orgId, payload) {
|
|
1387
|
+
const state = queuedAnnotationAssetState(payload);
|
|
1388
|
+
if (!state || state.attachments.length === 0)
|
|
1389
|
+
return [];
|
|
1390
|
+
const rows = await tx
|
|
1391
|
+
.select()
|
|
1392
|
+
.from(assets)
|
|
1393
|
+
.where(and(eq(assets.orgId, orgId), inArray(assets.id, state.attachments.map((attachment) => attachment.assetId))))
|
|
1394
|
+
.for("share");
|
|
1395
|
+
if (rows.length !== state.attachments.length) {
|
|
1396
|
+
throw unprocessable("Queued annotation assets are incomplete");
|
|
1397
|
+
}
|
|
1398
|
+
return rows.map((asset) => ({
|
|
1399
|
+
assetId: asset.id,
|
|
1400
|
+
provider: asset.provider,
|
|
1401
|
+
objectKey: asset.objectKey,
|
|
1402
|
+
contentType: asset.contentType,
|
|
1403
|
+
byteSize: asset.byteSize,
|
|
1404
|
+
sha256: asset.sha256,
|
|
1405
|
+
originalFilename: asset.originalFilename,
|
|
1406
|
+
createdByAgentId: asset.createdByAgentId,
|
|
1407
|
+
createdByUserId: asset.createdByUserId,
|
|
1408
|
+
}));
|
|
1409
|
+
}
|
|
1410
|
+
async function updateQueuedMessageWithStagedAttachments(input) {
|
|
1411
|
+
if ((input.payload.attachmentIds?.length ?? 0) > 0) {
|
|
1412
|
+
throw unprocessable("Queued messages cannot reference client-provided attachment ids");
|
|
1413
|
+
}
|
|
1414
|
+
if (input.stagedAttachments.length > 0 && input.payload.inlineAnnotations === undefined) {
|
|
1415
|
+
throw unprocessable("Queued annotation files require an explicit annotation replacement");
|
|
1416
|
+
}
|
|
1417
|
+
return db.transaction(async (tx) => {
|
|
1418
|
+
const current = await tx
|
|
1419
|
+
.select()
|
|
1420
|
+
.from(chatQueuedMessages)
|
|
1421
|
+
.where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.version, input.version), eq(chatQueuedMessages.status, "queued"), isNull(chatQueuedMessages.cancelledAt)))
|
|
1422
|
+
.limit(1)
|
|
1423
|
+
.for("update")
|
|
1424
|
+
.then((rows) => rows[0] ?? null);
|
|
1425
|
+
if (!current)
|
|
1426
|
+
throw conflict("Queued message was changed or is no longer editable");
|
|
1427
|
+
const currentPayload = normalizeQueuedMessagePayload(current.payload);
|
|
1428
|
+
const payload = normalizeQueuedMessagePayload({
|
|
1328
1429
|
...input.payload,
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1430
|
+
inlineAnnotations: input.payload.inlineAnnotations === undefined
|
|
1431
|
+
? currentPayload.inlineAnnotations
|
|
1432
|
+
: input.payload.inlineAnnotations,
|
|
1433
|
+
});
|
|
1434
|
+
if (!payload.body.trim() && (payload.inlineAnnotations?.length ?? 0) === 0) {
|
|
1435
|
+
throw unprocessable("Queued message body or at least one inline annotation is required");
|
|
1436
|
+
}
|
|
1437
|
+
const replacingAnnotations = input.payload.inlineAnnotations !== undefined;
|
|
1438
|
+
if (replacingAnnotations && current.sourceMessageId) {
|
|
1439
|
+
throw unprocessable("Sent annotation snapshots are immutable across queued message edits");
|
|
1440
|
+
}
|
|
1441
|
+
const currentPrivateState = queuedAnnotationAssetState(current.payload);
|
|
1442
|
+
const currentAttachmentFileIndexesByAnnotationId = new Map();
|
|
1443
|
+
for (const attachment of currentPrivateState?.attachments ?? []) {
|
|
1444
|
+
const indexes = currentAttachmentFileIndexesByAnnotationId.get(attachment.annotationId);
|
|
1445
|
+
if (indexes)
|
|
1446
|
+
indexes.push(attachment.fileIndex);
|
|
1447
|
+
else
|
|
1448
|
+
currentAttachmentFileIndexesByAnnotationId.set(attachment.annotationId, [attachment.fileIndex]);
|
|
1449
|
+
}
|
|
1450
|
+
await validateCanonicalChatInlineAnnotations(tx, {
|
|
1451
|
+
orgId: input.orgId,
|
|
1452
|
+
conversationId: input.conversationId,
|
|
1453
|
+
annotations: payload.inlineAnnotations ?? [],
|
|
1454
|
+
uploadedFileCount: replacingAnnotations
|
|
1455
|
+
? input.stagedAttachments.length
|
|
1456
|
+
: currentPrivateState?.attachments.length ?? 0,
|
|
1457
|
+
attachmentFileIndexesByAnnotationId: replacingAnnotations
|
|
1458
|
+
? input.attachmentFileIndexesByAnnotationId
|
|
1459
|
+
: currentAttachmentFileIndexesByAnnotationId,
|
|
1460
|
+
editUserMessageId: replacingAnnotations ? null : current.sourceMessageId,
|
|
1461
|
+
});
|
|
1462
|
+
const cleanupAttachments = replacingAnnotations
|
|
1463
|
+
? await queuedAssetCleanupRows(tx, input.orgId, current.payload)
|
|
1464
|
+
: [];
|
|
1465
|
+
let persistedPayload = payload;
|
|
1466
|
+
if (replacingAnnotations) {
|
|
1467
|
+
const fingerprint = queuedMessageMutationFingerprint({
|
|
1468
|
+
payload,
|
|
1469
|
+
stagedAttachments: input.stagedAttachments,
|
|
1470
|
+
attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
|
|
1471
|
+
});
|
|
1472
|
+
const assetIds = [];
|
|
1473
|
+
for (const attachment of input.stagedAttachments) {
|
|
1474
|
+
const [asset] = await tx
|
|
1475
|
+
.insert(assets)
|
|
1476
|
+
.values({
|
|
1477
|
+
orgId: input.orgId,
|
|
1478
|
+
provider: attachment.provider,
|
|
1479
|
+
objectKey: attachment.objectKey,
|
|
1480
|
+
contentType: attachment.contentType,
|
|
1481
|
+
byteSize: attachment.byteSize,
|
|
1482
|
+
sha256: attachment.sha256,
|
|
1483
|
+
originalFilename: attachment.originalFilename,
|
|
1484
|
+
createdByAgentId: attachment.createdByAgentId,
|
|
1485
|
+
createdByUserId: attachment.createdByUserId,
|
|
1486
|
+
})
|
|
1487
|
+
.returning({ id: assets.id });
|
|
1488
|
+
if (!asset)
|
|
1489
|
+
throw new Error("Failed to stage replacement queued annotation asset");
|
|
1490
|
+
assetIds.push(asset.id);
|
|
1491
|
+
}
|
|
1492
|
+
persistedPayload = withQueuedAnnotationAssetState({
|
|
1493
|
+
payload,
|
|
1494
|
+
fingerprint,
|
|
1495
|
+
assetIds,
|
|
1496
|
+
stagedAttachments: input.stagedAttachments,
|
|
1497
|
+
attachmentFileIndexesByAnnotationId: input.attachmentFileIndexesByAnnotationId,
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
else if (currentPrivateState) {
|
|
1501
|
+
persistedPayload = {
|
|
1502
|
+
...payload,
|
|
1503
|
+
[QUEUED_ANNOTATION_ASSETS_KEY]: current.payload[QUEUED_ANNOTATION_ASSETS_KEY],
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
const now = new Date();
|
|
1507
|
+
const [row] = await tx
|
|
1508
|
+
.update(chatQueuedMessages)
|
|
1509
|
+
.set({
|
|
1510
|
+
payload: persistedPayload,
|
|
1511
|
+
version: input.version + 1,
|
|
1512
|
+
lastDeliveryReason: current.sourceMessageId
|
|
1513
|
+
? current.lastDeliveryReason
|
|
1514
|
+
: null,
|
|
1515
|
+
updatedAt: now,
|
|
1516
|
+
})
|
|
1517
|
+
.where(and(eq(chatQueuedMessages.orgId, input.orgId), eq(chatQueuedMessages.conversationId, input.conversationId), eq(chatQueuedMessages.id, input.itemId), eq(chatQueuedMessages.version, input.version), eq(chatQueuedMessages.status, "queued"), isNull(chatQueuedMessages.cancelledAt)))
|
|
1518
|
+
.returning();
|
|
1519
|
+
if (!row)
|
|
1520
|
+
throw conflict("Queued message was changed or is no longer editable");
|
|
1521
|
+
if (current.sourceMessageId) {
|
|
1522
|
+
const [updatedMessage] = await tx
|
|
1523
|
+
.update(chatMessages)
|
|
1524
|
+
.set({ body: payload.body, updatedAt: now })
|
|
1525
|
+
.where(and(eq(chatMessages.id, current.sourceMessageId), eq(chatMessages.orgId, input.orgId), eq(chatMessages.conversationId, input.conversationId), eq(chatMessages.role, "user"), isNull(chatMessages.supersededAt)))
|
|
1526
|
+
.returning({ id: chatMessages.id });
|
|
1527
|
+
if (!updatedMessage) {
|
|
1528
|
+
throw conflict("Materialized queued message changed while its prose was edited");
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return { item: hydrateQueuedMessage(row), cleanupAttachments };
|
|
1532
|
+
});
|
|
1533
|
+
}
|
|
1534
|
+
async function updateQueuedMessage(input) {
|
|
1535
|
+
const result = await updateQueuedMessageWithStagedAttachments({
|
|
1536
|
+
...input,
|
|
1537
|
+
stagedAttachments: [],
|
|
1538
|
+
attachmentFileIndexesByAnnotationId: new Map(),
|
|
1539
|
+
});
|
|
1540
|
+
return result.item;
|
|
1541
|
+
}
|
|
1542
|
+
async function cancelQueuedMessageWithStagedAttachments(input) {
|
|
1543
|
+
return db.transaction(async (tx) => {
|
|
1544
|
+
const conditions = [
|
|
1545
|
+
eq(chatQueuedMessages.orgId, input.orgId),
|
|
1546
|
+
eq(chatQueuedMessages.conversationId, input.conversationId),
|
|
1547
|
+
eq(chatQueuedMessages.id, input.itemId),
|
|
1548
|
+
eq(chatQueuedMessages.status, "queued"),
|
|
1549
|
+
isNull(chatQueuedMessages.cancelledAt),
|
|
1550
|
+
];
|
|
1551
|
+
if (input.version)
|
|
1552
|
+
conditions.push(eq(chatQueuedMessages.version, input.version));
|
|
1553
|
+
const current = await tx
|
|
1554
|
+
.select()
|
|
1555
|
+
.from(chatQueuedMessages)
|
|
1556
|
+
.where(and(...conditions))
|
|
1557
|
+
.limit(1)
|
|
1558
|
+
.for("update")
|
|
1559
|
+
.then((rows) => rows[0] ?? null);
|
|
1560
|
+
if (!current)
|
|
1561
|
+
throw conflict("Queued message was changed or is no longer cancellable");
|
|
1562
|
+
const cleanupAttachments = await queuedAssetCleanupRows(tx, input.orgId, current.payload);
|
|
1563
|
+
const now = new Date();
|
|
1564
|
+
const [row] = await tx
|
|
1565
|
+
.update(chatQueuedMessages)
|
|
1566
|
+
.set({
|
|
1567
|
+
payload: normalizeQueuedMessagePayload(current.payload),
|
|
1568
|
+
status: "cancelled",
|
|
1569
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1570
|
+
cancelledAt: now,
|
|
1571
|
+
updatedAt: now,
|
|
1572
|
+
})
|
|
1573
|
+
.where(and(...conditions, eq(chatQueuedMessages.version, current.version)))
|
|
1574
|
+
.returning();
|
|
1575
|
+
if (!row)
|
|
1576
|
+
throw conflict("Queued message was changed or is no longer cancellable");
|
|
1577
|
+
return { item: hydrateQueuedMessage(row), cleanupAttachments };
|
|
1578
|
+
});
|
|
1347
1579
|
}
|
|
1348
1580
|
async function cancelQueuedMessage(input) {
|
|
1349
|
-
const
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
.
|
|
1366
|
-
.
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1581
|
+
const result = await cancelQueuedMessageWithStagedAttachments(input);
|
|
1582
|
+
return result.item;
|
|
1583
|
+
}
|
|
1584
|
+
async function finalizeQueuedAnnotationAssetCleanup(input) {
|
|
1585
|
+
if (input.assetIds.length === 0)
|
|
1586
|
+
return [];
|
|
1587
|
+
return db.transaction(async (tx) => {
|
|
1588
|
+
const linkedRows = await tx
|
|
1589
|
+
.select({ assetId: chatAttachments.assetId })
|
|
1590
|
+
.from(chatAttachments)
|
|
1591
|
+
.where(and(eq(chatAttachments.orgId, input.orgId), inArray(chatAttachments.assetId, [...input.assetIds])));
|
|
1592
|
+
const linkedIds = new Set(linkedRows.map((row) => row.assetId));
|
|
1593
|
+
const queueRows = await tx
|
|
1594
|
+
.select({ payload: chatQueuedMessages.payload })
|
|
1595
|
+
.from(chatQueuedMessages)
|
|
1596
|
+
.where(eq(chatQueuedMessages.orgId, input.orgId));
|
|
1597
|
+
const referencedIds = new Set(queueRows.flatMap((row) => queuedAnnotationAssetState(row.payload)?.attachments.map((attachment) => attachment.assetId) ?? []));
|
|
1598
|
+
const safeIds = [...new Set(input.assetIds)].filter((assetId) => !linkedIds.has(assetId) && !referencedIds.has(assetId));
|
|
1599
|
+
if (safeIds.length === 0)
|
|
1600
|
+
return [];
|
|
1601
|
+
const deleted = await tx
|
|
1602
|
+
.delete(assets)
|
|
1603
|
+
.where(and(eq(assets.orgId, input.orgId), inArray(assets.id, safeIds)))
|
|
1604
|
+
.returning({ id: assets.id });
|
|
1605
|
+
return deleted.map((row) => row.id);
|
|
1606
|
+
});
|
|
1370
1607
|
}
|
|
1371
1608
|
async function scheduleSteerContinuation(input) {
|
|
1372
1609
|
return db.transaction(async (tx) => {
|
|
@@ -1481,31 +1718,46 @@ export function chatService(db) {
|
|
|
1481
1718
|
return hydrateQueuedMessage(row);
|
|
1482
1719
|
}
|
|
1483
1720
|
async function claimNextServerQueuedMessage(input) {
|
|
1721
|
+
await reconcileDeliveredQueuedMessages({ limit: 25 });
|
|
1484
1722
|
const now = input.now ?? new Date();
|
|
1485
1723
|
const leaseExpiresAt = new Date(now.getTime() + input.leaseMs);
|
|
1486
1724
|
return db.transaction(async (tx) => {
|
|
1487
1725
|
const batchSize = 25;
|
|
1488
1726
|
let scanOffset = 0;
|
|
1727
|
+
const claimableQueuePredicate = and(inArray(chatQueuedMessages.status, ["continuation_pending", "steer_pending", "queued"]), isNull(chatQueuedMessages.cancelledAt), sql `jsonb_typeof(${chatQueuedMessages.requestActor}) = 'object'`, or(isNull(chatQueuedMessages.deliveryLeaseExpiresAt), lt(chatQueuedMessages.deliveryLeaseExpiresAt, now)), sql `(${chatQueuedMessages.status} <> 'steer_pending' or coalesce((
|
|
1728
|
+
select steer_action.provider_disposition
|
|
1729
|
+
from ${chatControlActions} as steer_action
|
|
1730
|
+
where steer_action.id = ${chatQueuedMessages.controlActionId}
|
|
1731
|
+
limit 1
|
|
1732
|
+
), 'not_sent') in ('not_sent', 'rejected'))`);
|
|
1733
|
+
const claimableQueuePriority = sql `case
|
|
1734
|
+
when ${chatQueuedMessages.status} = 'continuation_pending' then 0
|
|
1735
|
+
when ${chatQueuedMessages.status} = 'steer_pending' then 1
|
|
1736
|
+
else 2
|
|
1737
|
+
end`;
|
|
1489
1738
|
while (true) {
|
|
1490
1739
|
const candidates = await tx
|
|
1491
1740
|
.select()
|
|
1492
1741
|
.from(chatQueuedMessages)
|
|
1493
|
-
.where(
|
|
1494
|
-
|
|
1495
|
-
from ${chatControlActions} as steer_action
|
|
1496
|
-
where steer_action.id = ${chatQueuedMessages.controlActionId}
|
|
1497
|
-
limit 1
|
|
1498
|
-
), 'not_sent') in ('not_sent', 'rejected'))`))
|
|
1499
|
-
.orderBy(sql `case
|
|
1500
|
-
when ${chatQueuedMessages.status} = 'continuation_pending' then 0
|
|
1501
|
-
when ${chatQueuedMessages.status} = 'steer_pending' then 1
|
|
1502
|
-
else 2
|
|
1503
|
-
end`, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
|
|
1742
|
+
.where(claimableQueuePredicate)
|
|
1743
|
+
.orderBy(claimableQueuePriority, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
|
|
1504
1744
|
.limit(batchSize)
|
|
1505
1745
|
.offset(scanOffset)
|
|
1506
1746
|
.for("update", { skipLocked: true });
|
|
1747
|
+
let retainedCandidateCount = 0;
|
|
1507
1748
|
for (const candidate of candidates) {
|
|
1508
1749
|
if (!candidate.requestActor || typeof candidate.requestActor !== "object" || Array.isArray(candidate.requestActor)) {
|
|
1750
|
+
retainedCandidateCount += 1;
|
|
1751
|
+
continue;
|
|
1752
|
+
}
|
|
1753
|
+
const [conversationHead] = await tx
|
|
1754
|
+
.select({ id: chatQueuedMessages.id })
|
|
1755
|
+
.from(chatQueuedMessages)
|
|
1756
|
+
.where(and(claimableQueuePredicate, eq(chatQueuedMessages.orgId, candidate.orgId), eq(chatQueuedMessages.conversationId, candidate.conversationId)))
|
|
1757
|
+
.orderBy(claimableQueuePriority, asc(chatQueuedMessages.position), asc(chatQueuedMessages.createdAt))
|
|
1758
|
+
.limit(1);
|
|
1759
|
+
if (!conversationHead || conversationHead.id !== candidate.id) {
|
|
1760
|
+
retainedCandidateCount += 1;
|
|
1509
1761
|
continue;
|
|
1510
1762
|
}
|
|
1511
1763
|
const latestGeneration = await tx
|
|
@@ -1520,11 +1772,18 @@ export function chatService(db) {
|
|
|
1520
1772
|
|| candidate.status === "continuation_pending";
|
|
1521
1773
|
if (latestGeneration
|
|
1522
1774
|
&& ACTIVE_CHAT_GENERATION_STATUSES.some((status) => status === latestGeneration.status)) {
|
|
1775
|
+
retainedCandidateCount += 1;
|
|
1523
1776
|
continue;
|
|
1524
1777
|
}
|
|
1525
1778
|
if (candidate.status === "queued"
|
|
1526
1779
|
&& latestGeneration
|
|
1527
|
-
&& latestGeneration.status !== "completed"
|
|
1780
|
+
&& latestGeneration.status !== "completed"
|
|
1781
|
+
&& !((latestGeneration.status === "stopped"
|
|
1782
|
+
&& latestGeneration.terminalReason === "operator_stop")
|
|
1783
|
+
|| (candidate.continuationMessageId
|
|
1784
|
+
&& latestGeneration.status === "aborted"
|
|
1785
|
+
&& latestGeneration.terminalReason === candidate.lastDeliveryReason))) {
|
|
1786
|
+
retainedCandidateCount += 1;
|
|
1528
1787
|
continue;
|
|
1529
1788
|
}
|
|
1530
1789
|
if (isSteer
|
|
@@ -1554,9 +1813,58 @@ export function chatService(db) {
|
|
|
1554
1813
|
}
|
|
1555
1814
|
continue;
|
|
1556
1815
|
}
|
|
1816
|
+
let materialized;
|
|
1817
|
+
try {
|
|
1818
|
+
materialized = await materializeQueuedUserMessage(tx, {
|
|
1819
|
+
orgId: candidate.orgId,
|
|
1820
|
+
conversationId: candidate.conversationId,
|
|
1821
|
+
item: candidate,
|
|
1822
|
+
now,
|
|
1823
|
+
expectedStatuses: [candidate.status],
|
|
1824
|
+
structuredPayload: isSteer
|
|
1825
|
+
? {
|
|
1826
|
+
source: "steer",
|
|
1827
|
+
targetGenerationId: candidate.activeGenerationId ?? candidate.expectedGenerationId,
|
|
1828
|
+
queueItemId: candidate.id,
|
|
1829
|
+
controlActionId: candidate.controlActionId,
|
|
1830
|
+
deliveryDisposition: candidate.deliveryDisposition,
|
|
1831
|
+
}
|
|
1832
|
+
: { source: "queue", queueItemId: candidate.id },
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
catch (error) {
|
|
1836
|
+
if (!(error instanceof HttpError) || error.status !== 422)
|
|
1837
|
+
throw error;
|
|
1838
|
+
const validationFailureReason = "queued_message_validation_failed";
|
|
1839
|
+
await tx
|
|
1840
|
+
.update(chatQueuedMessages)
|
|
1841
|
+
.set({
|
|
1842
|
+
status: "failed_actionable",
|
|
1843
|
+
deliveryDisposition: "failed_actionable",
|
|
1844
|
+
reconciliationReason: validationFailureReason,
|
|
1845
|
+
lastDeliveryReason: validationFailureReason,
|
|
1846
|
+
updatedAt: now,
|
|
1847
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1848
|
+
})
|
|
1849
|
+
.where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.orgId, candidate.orgId), eq(chatQueuedMessages.conversationId, candidate.conversationId), eq(chatQueuedMessages.status, candidate.status), eq(chatQueuedMessages.version, candidate.version), isNull(chatQueuedMessages.cancelledAt)));
|
|
1850
|
+
if (candidate.controlActionId) {
|
|
1851
|
+
await tx
|
|
1852
|
+
.update(chatControlActions)
|
|
1853
|
+
.set({
|
|
1854
|
+
localDisposition: "failed_actionable",
|
|
1855
|
+
lastError: validationFailureReason,
|
|
1856
|
+
resolvedAt: now,
|
|
1857
|
+
updatedAt: now,
|
|
1858
|
+
})
|
|
1859
|
+
.where(and(eq(chatControlActions.id, candidate.controlActionId), eq(chatControlActions.orgId, candidate.orgId)));
|
|
1860
|
+
}
|
|
1861
|
+
continue;
|
|
1862
|
+
}
|
|
1863
|
+
const materializedItem = materialized.item;
|
|
1864
|
+
const userMessageId = materialized.message.id;
|
|
1557
1865
|
const generationId = randomUUID();
|
|
1558
1866
|
const leaseToken = randomUUID();
|
|
1559
|
-
const leaseEpoch =
|
|
1867
|
+
const leaseEpoch = materializedItem.deliveryLeaseEpoch + 1;
|
|
1560
1868
|
await tx.insert(chatGenerations).values({
|
|
1561
1869
|
id: generationId,
|
|
1562
1870
|
orgId: candidate.orgId,
|
|
@@ -1565,44 +1873,12 @@ export function chatService(db) {
|
|
|
1565
1873
|
attemptEpoch: 0,
|
|
1566
1874
|
controlVersion: 0,
|
|
1567
1875
|
controlState: "unregistered",
|
|
1568
|
-
controlOwnerToken:
|
|
1569
|
-
controlLeaseExpiresAt:
|
|
1876
|
+
controlOwnerToken: null,
|
|
1877
|
+
controlLeaseExpiresAt: null,
|
|
1570
1878
|
startedAt: now,
|
|
1571
1879
|
createdAt: now,
|
|
1572
1880
|
updatedAt: now,
|
|
1573
1881
|
});
|
|
1574
|
-
let userMessageId = candidate.continuationMessageId;
|
|
1575
|
-
if (userMessageId) {
|
|
1576
|
-
const existingMessage = await tx
|
|
1577
|
-
.select({ id: chatMessages.id })
|
|
1578
|
-
.from(chatMessages)
|
|
1579
|
-
.where(and(eq(chatMessages.id, userMessageId), eq(chatMessages.orgId, candidate.orgId), eq(chatMessages.conversationId, candidate.conversationId), eq(chatMessages.role, "user")))
|
|
1580
|
-
.limit(1)
|
|
1581
|
-
.then((rows) => rows[0] ?? null);
|
|
1582
|
-
if (!existingMessage)
|
|
1583
|
-
userMessageId = null;
|
|
1584
|
-
}
|
|
1585
|
-
if (!userMessageId) {
|
|
1586
|
-
userMessageId = randomUUID();
|
|
1587
|
-
await tx.insert(chatMessages).values({
|
|
1588
|
-
id: userMessageId,
|
|
1589
|
-
orgId: candidate.orgId,
|
|
1590
|
-
conversationId: candidate.conversationId,
|
|
1591
|
-
role: "user",
|
|
1592
|
-
kind: "message",
|
|
1593
|
-
status: "completed",
|
|
1594
|
-
body: normalizeQueuedPayload(candidate.payload).body,
|
|
1595
|
-
structuredPayload: null,
|
|
1596
|
-
chatTurnId: randomUUID(),
|
|
1597
|
-
turnVariant: 0,
|
|
1598
|
-
createdAt: now,
|
|
1599
|
-
updatedAt: now,
|
|
1600
|
-
});
|
|
1601
|
-
await tx
|
|
1602
|
-
.update(chatConversations)
|
|
1603
|
-
.set({ lastMessageAt: now, updatedAt: now })
|
|
1604
|
-
.where(and(eq(chatConversations.id, candidate.conversationId), eq(chatConversations.orgId, candidate.orgId)));
|
|
1605
|
-
}
|
|
1606
1882
|
const [claimed] = await tx
|
|
1607
1883
|
.update(chatQueuedMessages)
|
|
1608
1884
|
.set({
|
|
@@ -1624,7 +1900,7 @@ export function chatService(db) {
|
|
|
1624
1900
|
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1625
1901
|
updatedAt: now,
|
|
1626
1902
|
})
|
|
1627
|
-
.where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.
|
|
1903
|
+
.where(and(eq(chatQueuedMessages.id, candidate.id), eq(chatQueuedMessages.orgId, candidate.orgId), eq(chatQueuedMessages.conversationId, candidate.conversationId), eq(chatQueuedMessages.status, candidate.status), eq(chatQueuedMessages.version, materializedItem.version), isNull(chatQueuedMessages.cancelledAt)))
|
|
1628
1904
|
.returning();
|
|
1629
1905
|
if (!claimed)
|
|
1630
1906
|
throw conflict("Queued continuation changed while being claimed");
|
|
@@ -1648,7 +1924,7 @@ export function chatService(db) {
|
|
|
1648
1924
|
}
|
|
1649
1925
|
if (candidates.length < batchSize)
|
|
1650
1926
|
return null;
|
|
1651
|
-
scanOffset +=
|
|
1927
|
+
scanOffset += retainedCandidateCount;
|
|
1652
1928
|
}
|
|
1653
1929
|
});
|
|
1654
1930
|
}
|
|
@@ -1664,6 +1940,63 @@ export function chatService(db) {
|
|
|
1664
1940
|
.returning({ id: chatQueuedMessages.id });
|
|
1665
1941
|
return Boolean(row);
|
|
1666
1942
|
}
|
|
1943
|
+
async function acknowledgeServerQueuedMessageDelivery(input) {
|
|
1944
|
+
const now = new Date();
|
|
1945
|
+
return db.transaction(async (tx) => {
|
|
1946
|
+
const item = await tx
|
|
1947
|
+
.select()
|
|
1948
|
+
.from(chatQueuedMessages)
|
|
1949
|
+
.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), isNull(chatQueuedMessages.cancelledAt)))
|
|
1950
|
+
.limit(1)
|
|
1951
|
+
.then((rows) => rows[0] ?? null);
|
|
1952
|
+
if (!item || !item.deliveredMessageId)
|
|
1953
|
+
return null;
|
|
1954
|
+
const deliveredMessage = await tx
|
|
1955
|
+
.select({ id: chatMessages.id })
|
|
1956
|
+
.from(chatMessages)
|
|
1957
|
+
.where(and(eq(chatMessages.id, item.deliveredMessageId), eq(chatMessages.orgId, item.orgId), eq(chatMessages.conversationId, item.conversationId), eq(chatMessages.role, "user")))
|
|
1958
|
+
.limit(1)
|
|
1959
|
+
.then((rows) => rows[0] ?? null);
|
|
1960
|
+
if (!deliveredMessage)
|
|
1961
|
+
return null;
|
|
1962
|
+
const isSteer = item.deliveryIntent === "steer" || item.status === "running_next";
|
|
1963
|
+
const nextStatus = isSteer ? "delivered" : "completed";
|
|
1964
|
+
const nextDisposition = isSteer ? "delivered" : null;
|
|
1965
|
+
const [updated] = await tx
|
|
1966
|
+
.update(chatQueuedMessages)
|
|
1967
|
+
.set({
|
|
1968
|
+
status: nextStatus,
|
|
1969
|
+
deliveryDisposition: nextDisposition,
|
|
1970
|
+
deliveryLeaseToken: null,
|
|
1971
|
+
deliveryLeaseOwner: null,
|
|
1972
|
+
deliveryLeaseExpiresAt: null,
|
|
1973
|
+
lastDeliveryReason: null,
|
|
1974
|
+
reconciliationReason: null,
|
|
1975
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1976
|
+
updatedAt: now,
|
|
1977
|
+
})
|
|
1978
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version)))
|
|
1979
|
+
.returning();
|
|
1980
|
+
if (!updated)
|
|
1981
|
+
return null;
|
|
1982
|
+
if (item.controlActionId) {
|
|
1983
|
+
const [updatedAction] = await tx
|
|
1984
|
+
.update(chatControlActions)
|
|
1985
|
+
.set({
|
|
1986
|
+
localDisposition: "delivered",
|
|
1987
|
+
lastError: null,
|
|
1988
|
+
resolvedAt: now,
|
|
1989
|
+
updatedAt: now,
|
|
1990
|
+
})
|
|
1991
|
+
.where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, item.orgId)))
|
|
1992
|
+
.returning({ id: chatControlActions.id });
|
|
1993
|
+
if (!updatedAction) {
|
|
1994
|
+
throw new InvalidQueueDeliveryActionLinkError();
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
return hydrateQueuedMessage(updated);
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
1667
2000
|
async function completeServerQueuedMessageDelivery(input) {
|
|
1668
2001
|
const now = new Date();
|
|
1669
2002
|
return db.transaction(async (tx) => {
|
|
@@ -1802,6 +2135,7 @@ export function chatService(db) {
|
|
|
1802
2135
|
});
|
|
1803
2136
|
}
|
|
1804
2137
|
async function recoverExpiredServerQueueClaims(now = new Date()) {
|
|
2138
|
+
await reconcileDeliveredQueuedMessages({ limit: 25 });
|
|
1805
2139
|
const expired = await db
|
|
1806
2140
|
.select({
|
|
1807
2141
|
id: chatQueuedMessages.id,
|
|
@@ -1920,7 +2254,7 @@ export function chatService(db) {
|
|
|
1920
2254
|
if (!row || row.status !== "dequeue_claimed") {
|
|
1921
2255
|
throw conflict("Queued message is not claimed for delivery");
|
|
1922
2256
|
}
|
|
1923
|
-
const payload =
|
|
2257
|
+
const payload = normalizeQueuedMessagePayload(row.payload);
|
|
1924
2258
|
if (payload.body.trim() !== input.body.trim()) {
|
|
1925
2259
|
throw conflict("Queued message body no longer matches claimed payload");
|
|
1926
2260
|
}
|
|
@@ -1931,9 +2265,11 @@ export function chatService(db) {
|
|
|
1931
2265
|
const [row] = await db
|
|
1932
2266
|
.update(chatQueuedMessages)
|
|
1933
2267
|
.set({
|
|
1934
|
-
status: "
|
|
2268
|
+
status: "completed",
|
|
1935
2269
|
sourceMessageId: input.sourceMessageId,
|
|
1936
2270
|
deliveredMessageId: input.sourceMessageId,
|
|
2271
|
+
lastDeliveryReason: null,
|
|
2272
|
+
reconciliationReason: null,
|
|
1937
2273
|
version: sql `${chatQueuedMessages.version} + 1`,
|
|
1938
2274
|
updatedAt: now,
|
|
1939
2275
|
})
|
|
@@ -1943,6 +2279,123 @@ export function chatService(db) {
|
|
|
1943
2279
|
throw conflict("Queued message is no longer deliverable");
|
|
1944
2280
|
return hydrateQueuedMessage(row);
|
|
1945
2281
|
}
|
|
2282
|
+
async function reconcileDeliveredQueuedMessages(input = {}) {
|
|
2283
|
+
const candidates = await db
|
|
2284
|
+
.select()
|
|
2285
|
+
.from(chatQueuedMessages)
|
|
2286
|
+
.where(and(inArray(chatQueuedMessages.status, [
|
|
2287
|
+
"dequeue_claimed",
|
|
2288
|
+
"running",
|
|
2289
|
+
"running_next",
|
|
2290
|
+
"failed",
|
|
2291
|
+
"failed_actionable",
|
|
2292
|
+
]), isNull(chatQueuedMessages.cancelledAt), ...(input.orgId ? [eq(chatQueuedMessages.orgId, input.orgId)] : []), ...(input.conversationId ? [eq(chatQueuedMessages.conversationId, input.conversationId)] : [])))
|
|
2293
|
+
.orderBy(asc(chatQueuedMessages.updatedAt), asc(chatQueuedMessages.id))
|
|
2294
|
+
.limit(input.limit ?? 100);
|
|
2295
|
+
const generationIds = [...new Set(candidates
|
|
2296
|
+
.map((item) => item.continuationGenerationId)
|
|
2297
|
+
.filter((id) => Boolean(id)))];
|
|
2298
|
+
const generations = generationIds.length === 0
|
|
2299
|
+
? []
|
|
2300
|
+
: await db
|
|
2301
|
+
.select({ id: chatGenerations.id, status: chatGenerations.status })
|
|
2302
|
+
.from(chatGenerations)
|
|
2303
|
+
.where(inArray(chatGenerations.id, generationIds));
|
|
2304
|
+
const terminalGenerationIds = new Set(generations
|
|
2305
|
+
.filter((generation) => !ACTIVE_CHAT_GENERATION_STATUSES.includes(generation.status))
|
|
2306
|
+
.map((generation) => generation.id));
|
|
2307
|
+
const repairableCandidates = candidates.filter((item) => {
|
|
2308
|
+
const hasNoServerClaimIdentity = !item.deliveryLeaseToken
|
|
2309
|
+
&& !item.continuationGenerationId
|
|
2310
|
+
&& item.deliveryLeaseEpoch === 0;
|
|
2311
|
+
const terminalServerClaimReleased = !item.deliveryLeaseToken
|
|
2312
|
+
&& !item.deliveryLeaseOwner
|
|
2313
|
+
&& !item.deliveryLeaseExpiresAt
|
|
2314
|
+
&& Boolean(item.continuationGenerationId && terminalGenerationIds.has(item.continuationGenerationId));
|
|
2315
|
+
return hasNoServerClaimIdentity || Boolean(terminalServerClaimReleased);
|
|
2316
|
+
});
|
|
2317
|
+
const evidenceMessageIds = [...new Set(repairableCandidates.flatMap((item) => [
|
|
2318
|
+
item.deliveredMessageId,
|
|
2319
|
+
item.sourceMessageId,
|
|
2320
|
+
item.continuationMessageId,
|
|
2321
|
+
]).filter((id) => Boolean(id)))];
|
|
2322
|
+
if (evidenceMessageIds.length === 0)
|
|
2323
|
+
return 0;
|
|
2324
|
+
const messages = await db
|
|
2325
|
+
.select({
|
|
2326
|
+
id: chatMessages.id,
|
|
2327
|
+
orgId: chatMessages.orgId,
|
|
2328
|
+
conversationId: chatMessages.conversationId,
|
|
2329
|
+
role: chatMessages.role,
|
|
2330
|
+
})
|
|
2331
|
+
.from(chatMessages)
|
|
2332
|
+
.where(inArray(chatMessages.id, evidenceMessageIds));
|
|
2333
|
+
const messageById = new Map(messages.map((message) => [message.id, message]));
|
|
2334
|
+
const now = new Date();
|
|
2335
|
+
let reconciled = 0;
|
|
2336
|
+
for (const item of repairableCandidates) {
|
|
2337
|
+
const evidence = [item.deliveredMessageId, item.sourceMessageId, item.continuationMessageId]
|
|
2338
|
+
.map((id) => id ? messageById.get(id) : null)
|
|
2339
|
+
.find((message) => Boolean(message
|
|
2340
|
+
&& message.role === "user"
|
|
2341
|
+
&& message.orgId === item.orgId
|
|
2342
|
+
&& message.conversationId === item.conversationId));
|
|
2343
|
+
if (!evidence)
|
|
2344
|
+
continue;
|
|
2345
|
+
const isSteer = item.deliveryIntent === "steer" || item.status === "running_next";
|
|
2346
|
+
const nextStatus = isSteer ? "delivered" : "completed";
|
|
2347
|
+
const nextDisposition = isSteer ? "delivered" : null;
|
|
2348
|
+
let reconciledItem;
|
|
2349
|
+
try {
|
|
2350
|
+
reconciledItem = await db.transaction(async (tx) => {
|
|
2351
|
+
const [updated] = await tx
|
|
2352
|
+
.update(chatQueuedMessages)
|
|
2353
|
+
.set({
|
|
2354
|
+
status: nextStatus,
|
|
2355
|
+
deliveryDisposition: nextDisposition,
|
|
2356
|
+
sourceMessageId: evidence.id,
|
|
2357
|
+
deliveredMessageId: evidence.id,
|
|
2358
|
+
continuationMessageId: evidence.id,
|
|
2359
|
+
deliveryLeaseToken: null,
|
|
2360
|
+
deliveryLeaseOwner: null,
|
|
2361
|
+
deliveryLeaseExpiresAt: null,
|
|
2362
|
+
lastDeliveryReason: null,
|
|
2363
|
+
reconciliationReason: "durable_delivery_evidence",
|
|
2364
|
+
version: sql `${chatQueuedMessages.version} + 1`,
|
|
2365
|
+
updatedAt: now,
|
|
2366
|
+
})
|
|
2367
|
+
.where(and(eq(chatQueuedMessages.id, item.id), eq(chatQueuedMessages.version, item.version), isNull(chatQueuedMessages.cancelledAt)))
|
|
2368
|
+
.returning({ id: chatQueuedMessages.id });
|
|
2369
|
+
if (!updated)
|
|
2370
|
+
return false;
|
|
2371
|
+
if (item.controlActionId) {
|
|
2372
|
+
const [updatedAction] = await tx
|
|
2373
|
+
.update(chatControlActions)
|
|
2374
|
+
.set({
|
|
2375
|
+
localDisposition: "delivered",
|
|
2376
|
+
lastError: null,
|
|
2377
|
+
resolvedAt: now,
|
|
2378
|
+
updatedAt: now,
|
|
2379
|
+
})
|
|
2380
|
+
.where(and(eq(chatControlActions.id, item.controlActionId), eq(chatControlActions.orgId, item.orgId)))
|
|
2381
|
+
.returning({ id: chatControlActions.id });
|
|
2382
|
+
if (!updatedAction) {
|
|
2383
|
+
throw new InvalidQueueDeliveryActionLinkError();
|
|
2384
|
+
}
|
|
2385
|
+
}
|
|
2386
|
+
return true;
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2389
|
+
catch (error) {
|
|
2390
|
+
if (error instanceof InvalidQueueDeliveryActionLinkError)
|
|
2391
|
+
continue;
|
|
2392
|
+
throw error;
|
|
2393
|
+
}
|
|
2394
|
+
if (reconciledItem)
|
|
2395
|
+
reconciled += 1;
|
|
2396
|
+
}
|
|
2397
|
+
return reconciled;
|
|
2398
|
+
}
|
|
1946
2399
|
async function markQueuedMessageDeliveryTerminal(input) {
|
|
1947
2400
|
const now = new Date();
|
|
1948
2401
|
const nextStatus = input.status === "completed" ? "completed" : "failed";
|
|
@@ -1974,10 +2427,13 @@ export function chatService(db) {
|
|
|
1974
2427
|
.select({
|
|
1975
2428
|
assistantMessageId: chatGenerationEvents.assistantMessageId,
|
|
1976
2429
|
generationId: chatGenerationEvents.generationId,
|
|
2430
|
+
generationCreatedAt: chatGenerations.createdAt,
|
|
2431
|
+
eventRecordedAt: chatGenerationEvents.recordedAt,
|
|
1977
2432
|
})
|
|
1978
2433
|
.from(chatGenerationEvents)
|
|
1979
|
-
.
|
|
1980
|
-
.
|
|
2434
|
+
.innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
|
|
2435
|
+
.where(and(inArray(chatGenerationEvents.assistantMessageId, assistantMessageIds), inArray(chatGenerationEvents.orgId, [...new Set(rows.map((row) => row.orgId))])))
|
|
2436
|
+
.orderBy(desc(chatGenerations.createdAt), desc(chatGenerationEvents.recordedAt), desc(chatGenerationEvents.generationSeq))
|
|
1981
2437
|
: Promise.resolve([]),
|
|
1982
2438
|
nativeSteerMessageIds.length > 0
|
|
1983
2439
|
? db
|
|
@@ -1992,13 +2448,32 @@ export function chatService(db) {
|
|
|
1992
2448
|
.where(or(inArray(chatQueuedMessages.sourceMessageId, nativeSteerMessageIds), inArray(chatQueuedMessages.continuationMessageId, nativeSteerMessageIds)))
|
|
1993
2449
|
: Promise.resolve([]),
|
|
1994
2450
|
]);
|
|
2451
|
+
const generationIds = [...new Set(generationMessageRows.map((row) => row.generationId))];
|
|
2452
|
+
const generationsById = generationIds.length === 0
|
|
2453
|
+
? new Map()
|
|
2454
|
+
: new Map((await db
|
|
2455
|
+
.select({
|
|
2456
|
+
id: chatGenerations.id,
|
|
2457
|
+
orgId: chatGenerations.orgId,
|
|
2458
|
+
conversationId: chatGenerations.conversationId,
|
|
2459
|
+
terminalReason: chatGenerations.terminalReason,
|
|
2460
|
+
})
|
|
2461
|
+
.from(chatGenerations)
|
|
2462
|
+
.where(and(inArray(chatGenerations.id, generationIds), inArray(chatGenerations.orgId, [...new Set(rows.map((row) => row.orgId))]))))
|
|
2463
|
+
.map((generation) => [generation.id, generation]));
|
|
2464
|
+
const messageById = new Map(rows.map((row) => [row.id, row]));
|
|
1995
2465
|
const generationIdByAssistantMessageId = new Map();
|
|
1996
2466
|
for (const generationMessageRow of generationMessageRows) {
|
|
1997
|
-
|
|
2467
|
+
const assistantMessageId = generationMessageRow.assistantMessageId;
|
|
2468
|
+
if (!assistantMessageId || generationIdByAssistantMessageId.has(assistantMessageId))
|
|
2469
|
+
continue;
|
|
2470
|
+
const message = messageById.get(assistantMessageId);
|
|
2471
|
+
const generation = generationsById.get(generationMessageRow.generationId);
|
|
2472
|
+
if (!message || !generation)
|
|
1998
2473
|
continue;
|
|
1999
|
-
if (
|
|
2474
|
+
if (generation.orgId !== message.orgId || generation.conversationId !== message.conversationId)
|
|
2000
2475
|
continue;
|
|
2001
|
-
generationIdByAssistantMessageId.set(
|
|
2476
|
+
generationIdByAssistantMessageId.set(assistantMessageId, generationMessageRow.generationId);
|
|
2002
2477
|
}
|
|
2003
2478
|
const steerDispositionByMessageId = new Map();
|
|
2004
2479
|
for (const queueRow of linkedSteerQueueRows) {
|
|
@@ -2046,6 +2521,72 @@ export function chatService(db) {
|
|
|
2046
2521
|
const generationId = generationIdByAssistantMessageId.get(messageId);
|
|
2047
2522
|
return Boolean(generationId && nativeSteerTargetGenerationIds.has(generationId));
|
|
2048
2523
|
});
|
|
2524
|
+
const assistantGenerationIds = [
|
|
2525
|
+
...new Set(generationIdByAssistantMessageId.values()),
|
|
2526
|
+
];
|
|
2527
|
+
const assistantGenerationPairs = [...generationIdByAssistantMessageId].map(([assistantMessageId, generationId]) => ({ assistantMessageId, generationId }));
|
|
2528
|
+
const selectedGenerationEventCondition = assistantGenerationPairs.length > 0
|
|
2529
|
+
? or(...assistantGenerationPairs.map(({ assistantMessageId, generationId }) => and(eq(chatGenerationEvents.assistantMessageId, assistantMessageId), eq(chatGenerationEvents.generationId, generationId))))
|
|
2530
|
+
: undefined;
|
|
2531
|
+
const generationIdsNeedingFullTranscript = includeTranscript
|
|
2532
|
+
? assistantGenerationIds
|
|
2533
|
+
: [...nativeSteerTargetGenerationIds].filter((generationId) => assistantGenerationIds.includes(generationId));
|
|
2534
|
+
const assistantMessageIdsNeedingFullTranscript = assistantGenerationPairs
|
|
2535
|
+
.filter(({ generationId }) => generationIdsNeedingFullTranscript.includes(generationId))
|
|
2536
|
+
.map(({ assistantMessageId }) => assistantMessageId);
|
|
2537
|
+
const transcriptByAssistantMessageId = new Map();
|
|
2538
|
+
if (generationIdsNeedingFullTranscript.length > 0) {
|
|
2539
|
+
const transcriptEventRows = await db
|
|
2540
|
+
.select({
|
|
2541
|
+
assistantMessageId: chatGenerationEvents.assistantMessageId,
|
|
2542
|
+
generationId: chatGenerationEvents.generationId,
|
|
2543
|
+
generationSeq: chatGenerationEvents.generationSeq,
|
|
2544
|
+
payload: chatGenerationEvents.payload,
|
|
2545
|
+
})
|
|
2546
|
+
.from(chatGenerationEvents)
|
|
2547
|
+
.innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
|
|
2548
|
+
.where(and(selectedGenerationEventCondition, inArray(chatGenerationEvents.assistantMessageId, assistantMessageIdsNeedingFullTranscript), eq(chatGenerationEvents.eventKind, "transcript"), or(isNull(chatGenerations.acceptedThroughSeq), lte(chatGenerationEvents.generationSeq, chatGenerations.acceptedThroughSeq))))
|
|
2549
|
+
.orderBy(asc(chatGenerationEvents.generationId), asc(chatGenerationEvents.generationSeq));
|
|
2550
|
+
for (const eventRow of transcriptEventRows) {
|
|
2551
|
+
if (!eventRow.assistantMessageId)
|
|
2552
|
+
continue;
|
|
2553
|
+
const entry = eventRow.payload.entry;
|
|
2554
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
2555
|
+
continue;
|
|
2556
|
+
const provenancedEntry = withChatTranscriptGenerationProvenance(entry, {
|
|
2557
|
+
generationId: eventRow.generationId,
|
|
2558
|
+
generationSeq: eventRow.generationSeq,
|
|
2559
|
+
});
|
|
2560
|
+
const transcript = transcriptByAssistantMessageId.get(eventRow.assistantMessageId);
|
|
2561
|
+
if (transcript)
|
|
2562
|
+
transcript.push(provenancedEntry);
|
|
2563
|
+
else
|
|
2564
|
+
transcriptByAssistantMessageId.set(eventRow.assistantMessageId, [provenancedEntry]);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
const transcriptSummaryByAssistantMessageId = new Map();
|
|
2568
|
+
if (!includeTranscript && assistantGenerationPairs.length > 0) {
|
|
2569
|
+
const summaryRows = await db
|
|
2570
|
+
.select({
|
|
2571
|
+
assistantMessageId: chatGenerationEvents.assistantMessageId,
|
|
2572
|
+
entryCount: sql `count(*)`,
|
|
2573
|
+
startedAt: sql `min(${chatGenerationEvents.payload}->'entry'->>'ts')`,
|
|
2574
|
+
endedAt: sql `max(${chatGenerationEvents.payload}->'entry'->>'ts')`,
|
|
2575
|
+
})
|
|
2576
|
+
.from(chatGenerationEvents)
|
|
2577
|
+
.innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
|
|
2578
|
+
.where(and(selectedGenerationEventCondition, eq(chatGenerationEvents.eventKind, "transcript"), or(isNull(chatGenerations.acceptedThroughSeq), lte(chatGenerationEvents.generationSeq, chatGenerations.acceptedThroughSeq))))
|
|
2579
|
+
.groupBy(chatGenerationEvents.assistantMessageId);
|
|
2580
|
+
for (const summaryRow of summaryRows) {
|
|
2581
|
+
if (!summaryRow.assistantMessageId)
|
|
2582
|
+
continue;
|
|
2583
|
+
transcriptSummaryByAssistantMessageId.set(summaryRow.assistantMessageId, {
|
|
2584
|
+
entryCount: Number(summaryRow.entryCount),
|
|
2585
|
+
startedAt: summaryRow.startedAt,
|
|
2586
|
+
endedAt: summaryRow.endedAt,
|
|
2587
|
+
});
|
|
2588
|
+
}
|
|
2589
|
+
}
|
|
2049
2590
|
const nativeSteerTranscriptPayloadByMessageId = new Map();
|
|
2050
2591
|
if (nativeSteerAssistantMessageIds.length > 0) {
|
|
2051
2592
|
const transcriptPayloadRows = await db
|
|
@@ -2064,14 +2605,20 @@ export function chatService(db) {
|
|
|
2064
2605
|
const includeRowTranscript = includeTranscript
|
|
2065
2606
|
|| Boolean(generationId && nativeSteerTargetGenerationIds.has(generationId));
|
|
2066
2607
|
const transcriptPayload = nativeSteerTranscriptPayloadByMessageId.get(row.id) ?? row.structuredPayload;
|
|
2067
|
-
const
|
|
2608
|
+
const ledgerTranscript = transcriptByAssistantMessageId.get(row.id);
|
|
2609
|
+
const transcript = includeRowTranscript
|
|
2610
|
+
? ledgerTranscript?.length
|
|
2611
|
+
? ledgerTranscript
|
|
2612
|
+
: chatTranscriptFromPayload(transcriptPayload)
|
|
2613
|
+
: [];
|
|
2068
2614
|
const transcriptSummary = includeRowTranscript
|
|
2069
2615
|
? chatTranscriptSummaryFromEntries(transcript)
|
|
2070
|
-
: row.transcriptSummary ?? null;
|
|
2616
|
+
: transcriptSummaryByAssistantMessageId.get(row.id) ?? row.transcriptSummary ?? null;
|
|
2071
2617
|
const structuredPayload = effectiveStructuredPayload(row);
|
|
2072
2618
|
return {
|
|
2073
2619
|
...row,
|
|
2074
2620
|
generationId,
|
|
2621
|
+
generationTerminalReason: generationId ? (generationsById.get(generationId)?.terminalReason ?? null) : null,
|
|
2075
2622
|
structuredPayload: stripChatMetadataFromPayload(structuredPayload),
|
|
2076
2623
|
transcript: includeRowTranscript ? transcript : undefined,
|
|
2077
2624
|
transcriptSummary,
|
|
@@ -2347,10 +2894,10 @@ export function chatService(db) {
|
|
|
2347
2894
|
const forkMessages = input.sourceMessageId
|
|
2348
2895
|
? messagesToCopy.slice(0, messagesToCopy.findIndex((message) => message.id === input.sourceMessageId) + 1)
|
|
2349
2896
|
: messagesToCopy;
|
|
2350
|
-
await
|
|
2897
|
+
await copyForkChatMessages({
|
|
2351
2898
|
tx,
|
|
2352
2899
|
messages: forkMessages,
|
|
2353
|
-
|
|
2900
|
+
sourceConversation: source,
|
|
2354
2901
|
targetConversationId: child.id,
|
|
2355
2902
|
orgId: input.orgId,
|
|
2356
2903
|
});
|
|
@@ -2640,9 +3187,44 @@ export function chatService(db) {
|
|
|
2640
3187
|
.then((rows) => rows[0] ?? null);
|
|
2641
3188
|
if (!row)
|
|
2642
3189
|
return null;
|
|
3190
|
+
const generation = await db
|
|
3191
|
+
.select({
|
|
3192
|
+
generationId: chatGenerationEvents.generationId,
|
|
3193
|
+
acceptedThroughSeq: chatGenerations.acceptedThroughSeq,
|
|
3194
|
+
})
|
|
3195
|
+
.from(chatGenerationEvents)
|
|
3196
|
+
.innerJoin(chatGenerations, eq(chatGenerations.id, chatGenerationEvents.generationId))
|
|
3197
|
+
.where(eq(chatGenerationEvents.assistantMessageId, messageId))
|
|
3198
|
+
.orderBy(desc(chatGenerations.createdAt), desc(chatGenerationEvents.recordedAt), desc(chatGenerationEvents.generationSeq))
|
|
3199
|
+
.limit(1)
|
|
3200
|
+
.then((rows) => rows[0] ?? null);
|
|
3201
|
+
const transcript = generation
|
|
3202
|
+
? await db
|
|
3203
|
+
.select({
|
|
3204
|
+
generationId: chatGenerationEvents.generationId,
|
|
3205
|
+
generationSeq: chatGenerationEvents.generationSeq,
|
|
3206
|
+
payload: chatGenerationEvents.payload,
|
|
3207
|
+
})
|
|
3208
|
+
.from(chatGenerationEvents)
|
|
3209
|
+
.where(and(eq(chatGenerationEvents.generationId, generation.generationId), eq(chatGenerationEvents.assistantMessageId, messageId), eq(chatGenerationEvents.eventKind, "transcript"), generation.acceptedThroughSeq === null
|
|
3210
|
+
? undefined
|
|
3211
|
+
: lte(chatGenerationEvents.generationSeq, generation.acceptedThroughSeq)))
|
|
3212
|
+
.orderBy(asc(chatGenerationEvents.generationSeq))
|
|
3213
|
+
.then((events) => events.flatMap((event) => {
|
|
3214
|
+
const entry = event.payload.entry;
|
|
3215
|
+
return entry && typeof entry === "object" && !Array.isArray(entry)
|
|
3216
|
+
? [withChatTranscriptGenerationProvenance(entry, {
|
|
3217
|
+
generationId: event.generationId,
|
|
3218
|
+
generationSeq: event.generationSeq,
|
|
3219
|
+
})]
|
|
3220
|
+
: [];
|
|
3221
|
+
}))
|
|
3222
|
+
: [];
|
|
2643
3223
|
return {
|
|
2644
3224
|
messageId: row.id,
|
|
2645
|
-
transcript:
|
|
3225
|
+
transcript: transcript.length > 0
|
|
3226
|
+
? transcript
|
|
3227
|
+
: chatTranscriptFromPayload(row.structuredPayload),
|
|
2646
3228
|
};
|
|
2647
3229
|
}
|
|
2648
3230
|
async function getMessage(conversationId, messageId) {
|
|
@@ -2654,104 +3236,7 @@ export function chatService(db) {
|
|
|
2654
3236
|
if (!row)
|
|
2655
3237
|
return null;
|
|
2656
3238
|
const [hydrated] = await hydrateMessages([row]);
|
|
2657
|
-
return hydrated
|
|
2658
|
-
}
|
|
2659
|
-
async function assignLegacyTurnChainForUserMessage(target) {
|
|
2660
|
-
const turnId = randomUUID();
|
|
2661
|
-
const now = new Date();
|
|
2662
|
-
await db
|
|
2663
|
-
.update(chatMessages)
|
|
2664
|
-
.set({ chatTurnId: turnId, turnVariant: 0, updatedAt: now })
|
|
2665
|
-
.where(eq(chatMessages.id, target.id));
|
|
2666
|
-
const following = await db
|
|
2667
|
-
.select()
|
|
2668
|
-
.from(chatMessages)
|
|
2669
|
-
.where(and(eq(chatMessages.conversationId, target.conversationId), gt(chatMessages.createdAt, target.createdAt)))
|
|
2670
|
-
.orderBy(chatMessages.createdAt);
|
|
2671
|
-
for (const row of following) {
|
|
2672
|
-
if (row.role === "user")
|
|
2673
|
-
break;
|
|
2674
|
-
await db
|
|
2675
|
-
.update(chatMessages)
|
|
2676
|
-
.set({ chatTurnId: turnId, turnVariant: 0, updatedAt: now })
|
|
2677
|
-
.where(eq(chatMessages.id, row.id));
|
|
2678
|
-
}
|
|
2679
|
-
}
|
|
2680
|
-
async function supersedeActiveMessagesFrom(conversationId, fromCreatedAt) {
|
|
2681
|
-
const now = new Date();
|
|
2682
|
-
await db
|
|
2683
|
-
.update(chatMessages)
|
|
2684
|
-
.set({ supersededAt: now, updatedAt: now })
|
|
2685
|
-
.where(and(eq(chatMessages.conversationId, conversationId), isNull(chatMessages.supersededAt), gte(chatMessages.createdAt, fromCreatedAt)));
|
|
2686
|
-
}
|
|
2687
|
-
async function copyMessageAttachments(sourceMessageId, targetMessageId) {
|
|
2688
|
-
const sourceAttachments = await db
|
|
2689
|
-
.select()
|
|
2690
|
-
.from(chatAttachments)
|
|
2691
|
-
.where(eq(chatAttachments.messageId, sourceMessageId))
|
|
2692
|
-
.orderBy(chatAttachments.createdAt);
|
|
2693
|
-
if (sourceAttachments.length === 0)
|
|
2694
|
-
return;
|
|
2695
|
-
await db
|
|
2696
|
-
.insert(chatAttachments)
|
|
2697
|
-
.values(sourceAttachments.map((attachment) => ({
|
|
2698
|
-
orgId: attachment.orgId,
|
|
2699
|
-
conversationId: attachment.conversationId,
|
|
2700
|
-
messageId: targetMessageId,
|
|
2701
|
-
assetId: attachment.assetId,
|
|
2702
|
-
})));
|
|
2703
|
-
}
|
|
2704
|
-
async function addUserChatMessage(conversationId, orgId, body, editUserMessageId, options = {}) {
|
|
2705
|
-
if (editUserMessageId) {
|
|
2706
|
-
let [target] = await db
|
|
2707
|
-
.select()
|
|
2708
|
-
.from(chatMessages)
|
|
2709
|
-
.where(and(eq(chatMessages.id, editUserMessageId), eq(chatMessages.conversationId, conversationId)))
|
|
2710
|
-
.limit(1);
|
|
2711
|
-
if (!target) {
|
|
2712
|
-
throw notFound("Chat message not found");
|
|
2713
|
-
}
|
|
2714
|
-
if (target.role !== "user" || target.kind !== "message") {
|
|
2715
|
-
throw unprocessable("Only plain user messages can be edited");
|
|
2716
|
-
}
|
|
2717
|
-
if (target.supersededAt) {
|
|
2718
|
-
throw unprocessable("Cannot edit a superseded message");
|
|
2719
|
-
}
|
|
2720
|
-
if (!target.chatTurnId) {
|
|
2721
|
-
await assignLegacyTurnChainForUserMessage(target);
|
|
2722
|
-
[target] = await db
|
|
2723
|
-
.select()
|
|
2724
|
-
.from(chatMessages)
|
|
2725
|
-
.where(eq(chatMessages.id, editUserMessageId))
|
|
2726
|
-
.limit(1);
|
|
2727
|
-
if (!target?.chatTurnId) {
|
|
2728
|
-
throw new Error("Failed to assign chat turn metadata");
|
|
2729
|
-
}
|
|
2730
|
-
}
|
|
2731
|
-
await supersedeActiveMessagesFrom(conversationId, target.createdAt);
|
|
2732
|
-
const turnId = target.chatTurnId;
|
|
2733
|
-
const nextVariant = target.turnVariant + 1;
|
|
2734
|
-
const editedMessage = await addMessage(conversationId, {
|
|
2735
|
-
orgId,
|
|
2736
|
-
role: "user",
|
|
2737
|
-
kind: "message",
|
|
2738
|
-
body,
|
|
2739
|
-
chatTurnId: turnId,
|
|
2740
|
-
turnVariant: nextVariant,
|
|
2741
|
-
});
|
|
2742
|
-
await copyMessageAttachments(target.id, editedMessage.id);
|
|
2743
|
-
return (await getMessage(conversationId, editedMessage.id)) ?? editedMessage;
|
|
2744
|
-
}
|
|
2745
|
-
const turnId = randomUUID();
|
|
2746
|
-
return addMessage(conversationId, {
|
|
2747
|
-
orgId,
|
|
2748
|
-
role: "user",
|
|
2749
|
-
kind: "message",
|
|
2750
|
-
body,
|
|
2751
|
-
structuredPayload: options.structuredPayload ?? null,
|
|
2752
|
-
chatTurnId: turnId,
|
|
2753
|
-
turnVariant: 0,
|
|
2754
|
-
});
|
|
3239
|
+
return hydrated ? hydrated : null;
|
|
2755
3240
|
}
|
|
2756
3241
|
async function addMessage(conversationId, input) {
|
|
2757
3242
|
const durableBody = input.role === "user"
|
|
@@ -3444,8 +3929,12 @@ export function chatService(db) {
|
|
|
3444
3929
|
getQueueSnapshot,
|
|
3445
3930
|
listQueuedMessages,
|
|
3446
3931
|
createQueuedMessage,
|
|
3932
|
+
createQueuedMessageWithStagedAttachments,
|
|
3447
3933
|
updateQueuedMessage,
|
|
3934
|
+
updateQueuedMessageWithStagedAttachments,
|
|
3448
3935
|
cancelQueuedMessage,
|
|
3936
|
+
cancelQueuedMessageWithStagedAttachments,
|
|
3937
|
+
finalizeQueuedAnnotationAssetCleanup,
|
|
3449
3938
|
scheduleSteerContinuation,
|
|
3450
3939
|
markQueuedMessageSteerFallback,
|
|
3451
3940
|
beginSteerControlAction,
|
|
@@ -3454,6 +3943,7 @@ export function chatService(db) {
|
|
|
3454
3943
|
resolveSteerControlAction,
|
|
3455
3944
|
claimNextServerQueuedMessage,
|
|
3456
3945
|
renewServerQueuedMessageClaim,
|
|
3946
|
+
acknowledgeServerQueuedMessageDelivery,
|
|
3457
3947
|
completeServerQueuedMessageDelivery,
|
|
3458
3948
|
releaseServerQueuedMessageClaim,
|
|
3459
3949
|
recoverExpiredServerQueueClaims,
|
|
@@ -3466,6 +3956,7 @@ export function chatService(db) {
|
|
|
3466
3956
|
getMessageTranscript,
|
|
3467
3957
|
addMessage,
|
|
3468
3958
|
updateMessage,
|
|
3959
|
+
updateMessageStructuredPayload,
|
|
3469
3960
|
updateMessageInternalInlineVisuals,
|
|
3470
3961
|
markInterruptedStreamingMessages,
|
|
3471
3962
|
addUserChatMessage,
|