@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
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { hasSessionCompactionThresholds } from "@rudderhq/agent-runtime-utils";
|
|
2
2
|
import { agentRuntimeState, agents, agentTaskSessions, agentWakeupRequests, heartbeatRunEvents, heartbeatRuns, issues, organizations } from "@rudderhq/db";
|
|
3
|
-
import { summarizeTokenUsage } from "@rudderhq/shared";
|
|
4
|
-
import { and, asc, desc, eq, gt, gte, inArray, lte, sql } from "drizzle-orm";
|
|
3
|
+
import { summarizeTokenUsage, toHeartbeatRun } from "@rudderhq/shared";
|
|
4
|
+
import { and, asc, desc, eq, gt, gte, inArray, lte, or, sql } from "drizzle-orm";
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
5
6
|
import { runningProcesses } from "../../agent-runtimes/index.js";
|
|
6
|
-
import { parseObject } from "../../agent-runtimes/utils.js";
|
|
7
|
+
import { killChildProcessTree, parseObject } from "../../agent-runtimes/utils.js";
|
|
7
8
|
import { notFound } from "../../errors.js";
|
|
8
9
|
import { redactCurrentUserText, redactCurrentUserValue } from "../../log-redaction.js";
|
|
9
10
|
import { logger } from "../../middleware/logger.js";
|
|
10
11
|
import { agentRunContextService } from "../agent-run-context.js";
|
|
12
|
+
import { publishAutomationRunOutputToChat } from "../automation-chat-output.js";
|
|
11
13
|
import { budgetService } from "../budgets.js";
|
|
12
14
|
import { costService } from "../costs.js";
|
|
13
15
|
import { runWorkspaceService } from "../execution-workspaces.js";
|
|
@@ -15,6 +17,7 @@ import { summarizeHeartbeatRunResultJson } from "../heartbeat-run-summary.js";
|
|
|
15
17
|
import { instanceSettingsService } from "../instance-settings.js";
|
|
16
18
|
import { issueService } from "../issues.js";
|
|
17
19
|
import { publishLiveEvent } from "../live-events.js";
|
|
20
|
+
import { appendHeartbeatRunEvent } from "../run-events.js";
|
|
18
21
|
import { getRunLogStore } from "../run-log-store.js";
|
|
19
22
|
import { workspaceOperationService } from "../workspace-operations.js";
|
|
20
23
|
export { prioritizeProjectWorkspaceCandidatesForRun } from "../agent-run-context.js";
|
|
@@ -26,9 +29,16 @@ import { createHeartbeatExecuteHandlers } from "./heartbeat.execute.js";
|
|
|
26
29
|
import { createHeartbeatMiscHandlers } from "./heartbeat.misc.js";
|
|
27
30
|
import { createHeartbeatRecoveryHandlers } from "./heartbeat.recovery.js";
|
|
28
31
|
import { createHeartbeatReleaseHandlers } from "./heartbeat.release.js";
|
|
32
|
+
import { checkpointHeartbeatRunTerminalEffect, claimExpiredHeartbeatRunExecution, claimHeartbeatRunTerminalEffects, completeHeartbeatRunTerminalEffects, failHeartbeatRunTerminalEffect, markHeartbeatRunProcessExited, reconcileHeartbeatRunEvidence, reconcileHeartbeatRunTerminalEffectsIntent, releaseHeartbeatRunTerminalEffectsClaim, renewHeartbeatRunExecutionLease, renewHeartbeatRunTerminalEffectsClaim, RUN_EXECUTION_LEASE_MS, setWakeupStatusMonotonic, terminalEffectNames, transitionHeartbeatRunToTerminal, } from "./heartbeat.terminal.js";
|
|
29
33
|
import { createHeartbeatWakeupHandlers } from "./heartbeat.wakeup.js";
|
|
30
34
|
const DEFAULT_HEARTBEAT_RUN_TIMEOUT_MS = 12 * 60 * 60 * 1000;
|
|
31
35
|
const DEFAULT_HEARTBEAT_RUN_INACTIVITY_TIMEOUT_MS = 30 * 60 * 1000;
|
|
36
|
+
const TERMINAL_EFFECT_CLAIM_RENEW_INTERVAL_MS = 60_000;
|
|
37
|
+
// heartbeatService is instantiated by routes and the scheduler. Execution
|
|
38
|
+
// ownership must therefore be shared across every service instance in this
|
|
39
|
+
// server process.
|
|
40
|
+
const activeRunExecutions = new Set();
|
|
41
|
+
const runAbortControllers = new Map();
|
|
32
42
|
function formatDurationMs(ms) {
|
|
33
43
|
const totalSeconds = Math.max(0, Math.round(ms / 1000));
|
|
34
44
|
const hours = Math.floor(totalSeconds / 3600);
|
|
@@ -40,7 +50,7 @@ function formatDurationMs(ms) {
|
|
|
40
50
|
return `${minutes}m ${seconds}s`;
|
|
41
51
|
return `${seconds}s`;
|
|
42
52
|
}
|
|
43
|
-
export function heartbeatService(db) {
|
|
53
|
+
export function heartbeatService(db, testHooks) {
|
|
44
54
|
const instanceSettings = instanceSettingsService(db);
|
|
45
55
|
const getCurrentUserRedactionOptions = async () => ({
|
|
46
56
|
enabled: (await instanceSettings.getGeneral()).censorUsernameInLogs,
|
|
@@ -50,7 +60,6 @@ export function heartbeatService(db) {
|
|
|
50
60
|
const issuesSvc = issueService(db);
|
|
51
61
|
const executionWorkspacesSvc = runWorkspaceService(db);
|
|
52
62
|
const workspaceOperationsSvc = workspaceOperationService(db);
|
|
53
|
-
const activeRunExecutions = new Set();
|
|
54
63
|
const budgetHooks = {
|
|
55
64
|
cancelWorkForScope: (scope) => cancelBudgetScopeWork(scope),
|
|
56
65
|
};
|
|
@@ -229,8 +238,7 @@ export function heartbeatService(db) {
|
|
|
229
238
|
(codec.getDisplayId ? codec.getDisplayId(parsedParams) : null) ??
|
|
230
239
|
readNonEmptyString(parsedParams?.sessionId));
|
|
231
240
|
}
|
|
232
|
-
|
|
233
|
-
return runtimeForRun?.sessionId ?? null;
|
|
241
|
+
return null;
|
|
234
242
|
}
|
|
235
243
|
async function resolveExplicitResumeSessionOverride(agent, payload, taskKey) {
|
|
236
244
|
const resumeFromRunId = readNonEmptyString(payload?.resumeFromRunId);
|
|
@@ -242,6 +250,8 @@ export function heartbeatService(db) {
|
|
|
242
250
|
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
243
251
|
sessionIdBefore: heartbeatRuns.sessionIdBefore,
|
|
244
252
|
sessionIdAfter: heartbeatRuns.sessionIdAfter,
|
|
253
|
+
sessionParamsBeforeJson: heartbeatRuns.sessionParamsBeforeJson,
|
|
254
|
+
sessionParamsAfterJson: heartbeatRuns.sessionParamsAfterJson,
|
|
245
255
|
})
|
|
246
256
|
.from(heartbeatRuns)
|
|
247
257
|
.where(and(eq(heartbeatRuns.id, resumeFromRunId), eq(heartbeatRuns.orgId, agent.orgId), eq(heartbeatRuns.agentId, agent.id)))
|
|
@@ -254,10 +264,21 @@ export function heartbeatService(db) {
|
|
|
254
264
|
? await getTaskSession(agent.orgId, agent.id, agent.agentRuntimeType, resumeTaskKey)
|
|
255
265
|
: null;
|
|
256
266
|
const sessionCodec = getAgentRuntimeSessionCodec(agent.agentRuntimeType);
|
|
267
|
+
const persistedSessionSuppression = heartbeatSessions.readSessionReuseSuppression(resumeContext);
|
|
268
|
+
const resumeRunHasSessionAfter = Boolean(readNonEmptyString(resumeRun.sessionIdAfter)) ||
|
|
269
|
+
Boolean(resumeRun.sessionParamsAfterJson && Object.keys(resumeRun.sessionParamsAfterJson).length > 0);
|
|
257
270
|
const sessionOverride = buildExplicitResumeSessionOverride({
|
|
258
271
|
resumeFromRunId,
|
|
259
272
|
resumeRunSessionIdBefore: resumeRun.sessionIdBefore,
|
|
260
273
|
resumeRunSessionIdAfter: resumeRun.sessionIdAfter,
|
|
274
|
+
resumeRunSessionParamsBefore: resumeRun.sessionParamsBeforeJson,
|
|
275
|
+
resumeRunSessionParamsAfter: resumeRun.sessionParamsAfterJson,
|
|
276
|
+
resumeRunSessionCleared: resumeRun.sessionIdAfter == null &&
|
|
277
|
+
resumeRun.sessionParamsAfterJson != null &&
|
|
278
|
+
Object.keys(resumeRun.sessionParamsAfterJson).length === 0,
|
|
279
|
+
resumeRunSessionSuppression: resumeRunHasSessionAfter ? null : persistedSessionSuppression,
|
|
280
|
+
resumeContextSessionParams: parseObject(resumeContext.resumeSessionParams),
|
|
281
|
+
resumeContextSessionDisplayId: readNonEmptyString(resumeContext.resumeSessionDisplayId),
|
|
261
282
|
taskSession: resumeTaskSession,
|
|
262
283
|
sessionCodec,
|
|
263
284
|
});
|
|
@@ -270,38 +291,64 @@ export function heartbeatService(db) {
|
|
|
270
291
|
taskId: readNonEmptyString(resumeContext.taskId) ?? readNonEmptyString(resumeContext.issueId),
|
|
271
292
|
sessionDisplayId: sessionOverride.sessionDisplayId,
|
|
272
293
|
sessionParams: sessionOverride.sessionParams,
|
|
294
|
+
sessionCleared: "sessionCleared" in sessionOverride && sessionOverride.sessionCleared === true,
|
|
295
|
+
sessionReuseSuppression: "sessionReuseSuppression" in sessionOverride
|
|
296
|
+
? sessionOverride.sessionReuseSuppression
|
|
297
|
+
: null,
|
|
273
298
|
};
|
|
274
299
|
}
|
|
275
300
|
async function upsertTaskSession(input) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
.
|
|
280
|
-
.
|
|
301
|
+
return db.transaction(async (tx) => {
|
|
302
|
+
await tx.execute(sql `select pg_advisory_xact_lock(hashtext(${`task-session:${input.orgId}:${input.agentId}:${input.agentRuntimeType}:${input.taskKey}`}))`);
|
|
303
|
+
const existing = await tx
|
|
304
|
+
.select()
|
|
305
|
+
.from(agentTaskSessions)
|
|
306
|
+
.where(and(eq(agentTaskSessions.orgId, input.orgId), eq(agentTaskSessions.agentId, input.agentId), eq(agentTaskSessions.agentRuntimeType, input.agentRuntimeType), eq(agentTaskSessions.taskKey, input.taskKey)))
|
|
307
|
+
.then((rows) => rows[0] ?? null);
|
|
308
|
+
if (existing) {
|
|
309
|
+
if (existing.lastRunId && input.lastRunId && existing.lastRunId !== input.lastRunId) {
|
|
310
|
+
const runDates = await tx
|
|
311
|
+
.select({ id: heartbeatRuns.id, createdAt: heartbeatRuns.createdAt })
|
|
312
|
+
.from(heartbeatRuns)
|
|
313
|
+
.where(inArray(heartbeatRuns.id, [existing.lastRunId, input.lastRunId]));
|
|
314
|
+
const existingDate = runDates.find((row) => row.id === existing.lastRunId)?.createdAt;
|
|
315
|
+
const incomingDate = runDates.find((row) => row.id === input.lastRunId)?.createdAt;
|
|
316
|
+
if (existingDate
|
|
317
|
+
&& incomingDate
|
|
318
|
+
&& (existingDate.getTime() > incomingDate.getTime()
|
|
319
|
+
|| (existingDate.getTime() === incomingDate.getTime()
|
|
320
|
+
&& existing.lastRunId.localeCompare(input.lastRunId) > 0))) {
|
|
321
|
+
return existing;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return tx
|
|
325
|
+
.update(agentTaskSessions)
|
|
326
|
+
.set({
|
|
327
|
+
sessionParamsJson: input.sessionParamsJson,
|
|
328
|
+
sessionDisplayId: input.sessionDisplayId,
|
|
329
|
+
lastRunId: input.lastRunId,
|
|
330
|
+
lastError: input.lastError,
|
|
331
|
+
updatedAt: new Date(),
|
|
332
|
+
})
|
|
333
|
+
.where(eq(agentTaskSessions.id, existing.id))
|
|
334
|
+
.returning()
|
|
335
|
+
.then((rows) => rows[0] ?? null);
|
|
336
|
+
}
|
|
337
|
+
return tx
|
|
338
|
+
.insert(agentTaskSessions)
|
|
339
|
+
.values({
|
|
340
|
+
orgId: input.orgId,
|
|
341
|
+
agentId: input.agentId,
|
|
342
|
+
agentRuntimeType: input.agentRuntimeType,
|
|
343
|
+
taskKey: input.taskKey,
|
|
281
344
|
sessionParamsJson: input.sessionParamsJson,
|
|
282
345
|
sessionDisplayId: input.sessionDisplayId,
|
|
283
346
|
lastRunId: input.lastRunId,
|
|
284
347
|
lastError: input.lastError,
|
|
285
|
-
updatedAt: new Date(),
|
|
286
348
|
})
|
|
287
|
-
.where(eq(agentTaskSessions.id, existing.id))
|
|
288
349
|
.returning()
|
|
289
350
|
.then((rows) => rows[0] ?? null);
|
|
290
|
-
}
|
|
291
|
-
return db
|
|
292
|
-
.insert(agentTaskSessions)
|
|
293
|
-
.values({
|
|
294
|
-
orgId: input.orgId,
|
|
295
|
-
agentId: input.agentId,
|
|
296
|
-
agentRuntimeType: input.agentRuntimeType,
|
|
297
|
-
taskKey: input.taskKey,
|
|
298
|
-
sessionParamsJson: input.sessionParamsJson,
|
|
299
|
-
sessionDisplayId: input.sessionDisplayId,
|
|
300
|
-
lastRunId: input.lastRunId,
|
|
301
|
-
lastError: input.lastError,
|
|
302
|
-
})
|
|
303
|
-
.returning()
|
|
304
|
-
.then((rows) => rows[0] ?? null);
|
|
351
|
+
});
|
|
305
352
|
}
|
|
306
353
|
async function clearTaskSessions(orgId, agentId, opts) {
|
|
307
354
|
const conditions = [
|
|
@@ -314,6 +361,27 @@ export function heartbeatService(db) {
|
|
|
314
361
|
if (opts?.agentRuntimeType) {
|
|
315
362
|
conditions.push(eq(agentTaskSessions.agentRuntimeType, opts.agentRuntimeType));
|
|
316
363
|
}
|
|
364
|
+
if (opts?.sourceRunId) {
|
|
365
|
+
const sourceRun = await getRun(opts.sourceRunId);
|
|
366
|
+
if (sourceRun) {
|
|
367
|
+
conditions.push(sql `(
|
|
368
|
+
${agentTaskSessions.lastRunId} is null
|
|
369
|
+
or ${agentTaskSessions.lastRunId} = ${opts.sourceRunId}
|
|
370
|
+
or not exists (
|
|
371
|
+
select 1
|
|
372
|
+
from heartbeat_runs previous_run
|
|
373
|
+
where previous_run.id = ${agentTaskSessions.lastRunId}
|
|
374
|
+
and (
|
|
375
|
+
previous_run.created_at > ${sourceRun.createdAt.toISOString()}::timestamptz
|
|
376
|
+
or (
|
|
377
|
+
previous_run.created_at = ${sourceRun.createdAt.toISOString()}::timestamptz
|
|
378
|
+
and previous_run.id > ${sourceRun.id}
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
)`);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
317
385
|
return db
|
|
318
386
|
.delete(agentTaskSessions)
|
|
319
387
|
.where(and(...conditions))
|
|
@@ -346,39 +414,61 @@ export function heartbeatService(db) {
|
|
|
346
414
|
.returning()
|
|
347
415
|
.then((rows) => rows[0]);
|
|
348
416
|
}
|
|
417
|
+
function publishRunStatus(updated) {
|
|
418
|
+
publishLiveEvent({
|
|
419
|
+
orgId: updated.orgId,
|
|
420
|
+
type: "heartbeat.run.status",
|
|
421
|
+
payload: {
|
|
422
|
+
runId: updated.id,
|
|
423
|
+
agentId: updated.agentId,
|
|
424
|
+
status: updated.status,
|
|
425
|
+
invocationSource: updated.invocationSource,
|
|
426
|
+
triggerDetail: updated.triggerDetail,
|
|
427
|
+
error: updated.error ?? null,
|
|
428
|
+
errorCode: updated.errorCode ?? null,
|
|
429
|
+
startedAt: updated.startedAt ? new Date(updated.startedAt).toISOString() : null,
|
|
430
|
+
finishedAt: updated.finishedAt ? new Date(updated.finishedAt).toISOString() : null,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
349
434
|
async function setRunStatus(runId, status, patch) {
|
|
435
|
+
if (["succeeded", "failed", "cancelled", "timed_out"].includes(status)) {
|
|
436
|
+
throw new Error("Terminal heartbeat run updates must use transitionRunToTerminal");
|
|
437
|
+
}
|
|
350
438
|
const updated = await db
|
|
351
439
|
.update(heartbeatRuns)
|
|
352
440
|
.set({ status, ...patch, updatedAt: new Date() })
|
|
353
441
|
.where(eq(heartbeatRuns.id, runId))
|
|
354
442
|
.returning()
|
|
355
443
|
.then((rows) => rows[0] ?? null);
|
|
356
|
-
if (updated)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
444
|
+
if (updated)
|
|
445
|
+
publishRunStatus(updated);
|
|
446
|
+
return updated;
|
|
447
|
+
}
|
|
448
|
+
async function transitionRunToTerminal(runId, status, patch, opts) {
|
|
449
|
+
const updated = await transitionHeartbeatRunToTerminal(db, {
|
|
450
|
+
runId,
|
|
451
|
+
status,
|
|
452
|
+
patch,
|
|
453
|
+
expectedStatuses: opts?.expectedStatuses,
|
|
454
|
+
activityWatermark: opts?.activityWatermark,
|
|
455
|
+
terminalEffectsPending: opts?.terminalEffectsPending,
|
|
456
|
+
terminalEffectsIntent: opts?.terminalEffectsIntent,
|
|
457
|
+
processExitedAt: opts?.processExitedAt,
|
|
458
|
+
expectedExecutionOwnerToken: opts?.expectedExecutionOwnerToken,
|
|
459
|
+
});
|
|
460
|
+
if (updated)
|
|
461
|
+
publishRunStatus(updated);
|
|
373
462
|
return updated;
|
|
374
463
|
}
|
|
464
|
+
async function reconcileRunEvidence(runId, patch) {
|
|
465
|
+
return reconcileHeartbeatRunEvidence(db, runId, patch);
|
|
466
|
+
}
|
|
467
|
+
async function reconcileTerminalEffectsIntent(runId, intent) {
|
|
468
|
+
return reconcileHeartbeatRunTerminalEffectsIntent(db, runId, intent);
|
|
469
|
+
}
|
|
375
470
|
async function setWakeupStatus(wakeupRequestId, status, patch) {
|
|
376
|
-
|
|
377
|
-
return;
|
|
378
|
-
await db
|
|
379
|
-
.update(agentWakeupRequests)
|
|
380
|
-
.set({ status, ...patch, updatedAt: new Date() })
|
|
381
|
-
.where(eq(agentWakeupRequests.id, wakeupRequestId));
|
|
471
|
+
return setWakeupStatusMonotonic(db, wakeupRequestId, status, patch);
|
|
382
472
|
}
|
|
383
473
|
async function updateWakeupRequestRecord(tx, wakeupRequestId, patch) {
|
|
384
474
|
return tx
|
|
@@ -389,13 +479,21 @@ export function heartbeatService(db) {
|
|
|
389
479
|
.then((rows) => rows[0] ?? null);
|
|
390
480
|
}
|
|
391
481
|
async function insertWakeupRequestRecord(tx, values) {
|
|
392
|
-
|
|
482
|
+
const inserted = await tx
|
|
393
483
|
.insert(agentWakeupRequests)
|
|
394
484
|
.values(values)
|
|
485
|
+
.onConflictDoNothing()
|
|
395
486
|
.returning()
|
|
396
487
|
.then((rows) => rows[0] ?? null);
|
|
488
|
+
if (inserted || !values.idempotencyKey)
|
|
489
|
+
return inserted;
|
|
490
|
+
return tx
|
|
491
|
+
.select()
|
|
492
|
+
.from(agentWakeupRequests)
|
|
493
|
+
.where(and(eq(agentWakeupRequests.orgId, values.orgId), eq(agentWakeupRequests.agentId, values.agentId), eq(agentWakeupRequests.idempotencyKey, values.idempotencyKey)))
|
|
494
|
+
.then((rows) => rows[0] ?? null);
|
|
397
495
|
}
|
|
398
|
-
async function appendRunEvent(run,
|
|
496
|
+
async function appendRunEvent(run, event) {
|
|
399
497
|
const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
|
|
400
498
|
const sanitizedMessage = event.message
|
|
401
499
|
? redactCurrentUserText(event.message, currentUserRedactionOptions)
|
|
@@ -403,17 +501,17 @@ export function heartbeatService(db) {
|
|
|
403
501
|
const sanitizedPayload = event.payload
|
|
404
502
|
? redactCurrentUserValue(event.payload, currentUserRedactionOptions)
|
|
405
503
|
: event.payload;
|
|
406
|
-
await db
|
|
504
|
+
const inserted = await appendHeartbeatRunEvent(db, {
|
|
407
505
|
orgId: run.orgId,
|
|
408
506
|
runId: run.id,
|
|
409
507
|
agentId: run.agentId,
|
|
410
|
-
seq,
|
|
411
508
|
eventType: event.eventType,
|
|
412
509
|
stream: event.stream,
|
|
413
510
|
level: event.level,
|
|
414
511
|
color: event.color,
|
|
415
512
|
message: sanitizedMessage,
|
|
416
513
|
payload: sanitizedPayload,
|
|
514
|
+
idempotencyKey: event.idempotencyKey ?? null,
|
|
417
515
|
});
|
|
418
516
|
publishLiveEvent({
|
|
419
517
|
orgId: run.orgId,
|
|
@@ -421,7 +519,7 @@ export function heartbeatService(db) {
|
|
|
421
519
|
payload: {
|
|
422
520
|
runId: run.id,
|
|
423
521
|
agentId: run.agentId,
|
|
424
|
-
seq,
|
|
522
|
+
seq: inserted.seq,
|
|
425
523
|
eventType: event.eventType,
|
|
426
524
|
stream: event.stream ?? null,
|
|
427
525
|
level: event.level ?? null,
|
|
@@ -431,13 +529,6 @@ export function heartbeatService(db) {
|
|
|
431
529
|
},
|
|
432
530
|
});
|
|
433
531
|
}
|
|
434
|
-
async function nextRunEventSeq(runId) {
|
|
435
|
-
const [row] = await db
|
|
436
|
-
.select({ maxSeq: sql `max(${heartbeatRunEvents.seq})` })
|
|
437
|
-
.from(heartbeatRunEvents)
|
|
438
|
-
.where(eq(heartbeatRunEvents.runId, runId));
|
|
439
|
-
return Number(row?.maxSeq ?? 0) + 1;
|
|
440
|
-
}
|
|
441
532
|
async function persistRunProcessMetadata(runId, meta) {
|
|
442
533
|
const startedAt = new Date(meta.startedAt);
|
|
443
534
|
const updated = await db
|
|
@@ -447,7 +538,7 @@ export function heartbeatService(db) {
|
|
|
447
538
|
processStartedAt: Number.isNaN(startedAt.getTime()) ? new Date() : startedAt,
|
|
448
539
|
updatedAt: new Date(),
|
|
449
540
|
})
|
|
450
|
-
.where(eq(heartbeatRuns.id, runId))
|
|
541
|
+
.where(and(eq(heartbeatRuns.id, runId), eq(heartbeatRuns.status, "running")))
|
|
451
542
|
.returning()
|
|
452
543
|
.then((rows) => rows[0] ?? null);
|
|
453
544
|
return updated;
|
|
@@ -465,7 +556,7 @@ export function heartbeatService(db) {
|
|
|
465
556
|
.then((rows) => rows[0] ?? null);
|
|
466
557
|
if (!updated)
|
|
467
558
|
return null;
|
|
468
|
-
await appendRunEvent(updated,
|
|
559
|
+
await appendRunEvent(updated, {
|
|
469
560
|
eventType: "lifecycle",
|
|
470
561
|
stream: "system",
|
|
471
562
|
level: "info",
|
|
@@ -473,11 +564,39 @@ export function heartbeatService(db) {
|
|
|
473
564
|
});
|
|
474
565
|
return updated;
|
|
475
566
|
}
|
|
476
|
-
async function
|
|
567
|
+
async function terminateRunProcessAndWait(run, agentRuntimeType) {
|
|
568
|
+
if (run.processExitedAt)
|
|
569
|
+
return true;
|
|
570
|
+
runAbortControllers.get(run.id)?.abort();
|
|
571
|
+
const tracked = runningProcesses.get(run.id);
|
|
572
|
+
if (tracked) {
|
|
573
|
+
const pid = tracked.child.pid;
|
|
574
|
+
killChildProcessTree(tracked.child, false);
|
|
575
|
+
if (typeof pid !== "number" || pid <= 0)
|
|
576
|
+
return true;
|
|
577
|
+
const graceMs = Math.max(1, tracked.graceSec) * 1000;
|
|
578
|
+
if (await waitForProcessExit(pid, graceMs))
|
|
579
|
+
return true;
|
|
580
|
+
killChildProcessTree(tracked.child, true);
|
|
581
|
+
return waitForProcessExit(pid, ORPHANED_PROCESS_KILL_WAIT_MS);
|
|
582
|
+
}
|
|
583
|
+
if (isTrackedLocalChildProcessAdapter(agentRuntimeType) && run.processPid && isProcessAlive(run.processPid)) {
|
|
584
|
+
return !(await terminateOrphanedProcess(run.processPid)).stillAlive;
|
|
585
|
+
}
|
|
586
|
+
return !activeRunExecutions.has(run.id);
|
|
587
|
+
}
|
|
588
|
+
async function acknowledgeRunProcessExit(runId) {
|
|
589
|
+
return markHeartbeatRunProcessExited(db, runId);
|
|
590
|
+
}
|
|
591
|
+
async function renewRunExecutionLease(runId, ownerToken) {
|
|
592
|
+
return renewHeartbeatRunExecutionLease(db, runId, ownerToken);
|
|
593
|
+
}
|
|
594
|
+
async function countRunningRunsForAgent(agentId, excludeRunId) {
|
|
595
|
+
const pendingExecutionIds = [...activeRunExecutions].filter((runId) => runId !== excludeRunId);
|
|
477
596
|
const [{ count }] = await db
|
|
478
597
|
.select({ count: sql `count(*)` })
|
|
479
598
|
.from(heartbeatRuns)
|
|
480
|
-
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "running")));
|
|
599
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), or(eq(heartbeatRuns.status, "running"), eq(heartbeatRuns.terminalEffectsPending, true), ...(pendingExecutionIds.length > 0 ? [inArray(heartbeatRuns.id, pendingExecutionIds)] : [])), ...(excludeRunId ? [sql `${heartbeatRuns.id} <> ${excludeRunId}`] : [])));
|
|
481
600
|
return Number(count ?? 0);
|
|
482
601
|
}
|
|
483
602
|
async function claimQueuedRun(run) {
|
|
@@ -499,25 +618,26 @@ export function heartbeatService(db) {
|
|
|
499
618
|
}
|
|
500
619
|
}
|
|
501
620
|
async function cancelQueuedRunDuringClaim(reason) {
|
|
502
|
-
const cancelled = await
|
|
621
|
+
const cancelled = await transitionRunToTerminal(run.id, "cancelled", {
|
|
503
622
|
finishedAt: new Date(),
|
|
504
623
|
error: reason,
|
|
505
624
|
errorCode: "cancelled",
|
|
506
|
-
});
|
|
625
|
+
}, { expectedStatuses: ["queued"], processExitedAt: new Date() });
|
|
626
|
+
if (!cancelled)
|
|
627
|
+
return null;
|
|
507
628
|
await setWakeupStatus(run.wakeupRequestId, "cancelled", {
|
|
508
629
|
finishedAt: new Date(),
|
|
509
630
|
error: reason,
|
|
510
631
|
});
|
|
511
632
|
if (cancelled) {
|
|
512
|
-
await appendRunEvent(cancelled,
|
|
633
|
+
await appendRunEvent(cancelled, {
|
|
513
634
|
eventType: "lifecycle",
|
|
514
635
|
stream: "system",
|
|
515
636
|
level: "warn",
|
|
516
637
|
message: "run cancelled",
|
|
517
638
|
});
|
|
518
|
-
await
|
|
639
|
+
await completeTerminalControlEffects(cancelled, { startNext: false });
|
|
519
640
|
}
|
|
520
|
-
await finalizeAgentStatus(run.agentId, "cancelled");
|
|
521
641
|
return null;
|
|
522
642
|
}
|
|
523
643
|
const agent = await getAgent(run.agentId);
|
|
@@ -554,16 +674,82 @@ export function heartbeatService(db) {
|
|
|
554
674
|
return await cancelQueuedRunDuringClaim(budgetBlock.reason);
|
|
555
675
|
}
|
|
556
676
|
const claimedAt = new Date();
|
|
557
|
-
const claimed = await db
|
|
558
|
-
.
|
|
559
|
-
.
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
.
|
|
566
|
-
|
|
677
|
+
const claimed = await db.transaction(async (tx) => {
|
|
678
|
+
await tx.execute(sql `select pg_advisory_xact_lock(hashtext(${`agent-run-state:${run.agentId}`}))`);
|
|
679
|
+
await tx.execute(sql `select id from agents where id = ${run.agentId} for update`);
|
|
680
|
+
const currentAgent = await tx
|
|
681
|
+
.select({ status: agents.status })
|
|
682
|
+
.from(agents)
|
|
683
|
+
.where(eq(agents.id, run.agentId))
|
|
684
|
+
.then((rows) => rows[0] ?? null);
|
|
685
|
+
if (!currentAgent || ["paused", "terminated", "pending_approval"].includes(currentAgent.status)) {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
const pendingTerminalEffects = await tx
|
|
689
|
+
.select({ id: heartbeatRuns.id })
|
|
690
|
+
.from(heartbeatRuns)
|
|
691
|
+
.where(and(eq(heartbeatRuns.agentId, run.agentId), eq(heartbeatRuns.terminalEffectsPending, true)))
|
|
692
|
+
.limit(1)
|
|
693
|
+
.then((rows) => rows[0] ?? null);
|
|
694
|
+
if (pendingTerminalEffects)
|
|
695
|
+
return null;
|
|
696
|
+
await tx.execute(sql `select id from heartbeat_runs where id = ${run.id} for update`);
|
|
697
|
+
const currentRun = await tx
|
|
698
|
+
.select()
|
|
699
|
+
.from(heartbeatRuns)
|
|
700
|
+
.where(eq(heartbeatRuns.id, run.id))
|
|
701
|
+
.then((rows) => rows[0] ?? null);
|
|
702
|
+
if (!currentRun || currentRun.status !== "queued")
|
|
703
|
+
return null;
|
|
704
|
+
const currentContext = parseObject(currentRun.contextSnapshot);
|
|
705
|
+
const currentTaskKey = deriveTaskKey(currentContext, null);
|
|
706
|
+
const resetTaskSession = shouldResetTaskSessionForWake(currentContext);
|
|
707
|
+
const sessionCodec = getAgentRuntimeSessionCodec(agent.agentRuntimeType);
|
|
708
|
+
const explicitSessionParams = normalizeSessionParams(sessionCodec.deserialize(parseObject(currentContext.resumeSessionParams)));
|
|
709
|
+
const explicitSessionDisplayId = truncateDisplayId(readNonEmptyString(currentContext.resumeSessionDisplayId) ??
|
|
710
|
+
(sessionCodec.getDisplayId ? sessionCodec.getDisplayId(explicitSessionParams) : null) ??
|
|
711
|
+
readNonEmptyString(explicitSessionParams?.sessionId));
|
|
712
|
+
const taskSession = currentTaskKey && !resetTaskSession
|
|
713
|
+
? await tx
|
|
714
|
+
.select()
|
|
715
|
+
.from(agentTaskSessions)
|
|
716
|
+
.where(and(eq(agentTaskSessions.orgId, currentRun.orgId), eq(agentTaskSessions.agentId, currentRun.agentId), eq(agentTaskSessions.agentRuntimeType, agent.agentRuntimeType), eq(agentTaskSessions.taskKey, currentTaskKey)))
|
|
717
|
+
.then((rows) => rows[0] ?? null)
|
|
718
|
+
: null;
|
|
719
|
+
const taskSessionParams = normalizeSessionParams(sessionCodec.deserialize(taskSession?.sessionParamsJson ?? null));
|
|
720
|
+
const taskSessionDisplayId = truncateDisplayId(taskSession?.sessionDisplayId ??
|
|
721
|
+
(sessionCodec.getDisplayId ? sessionCodec.getDisplayId(taskSessionParams) : null) ??
|
|
722
|
+
readNonEmptyString(taskSessionParams?.sessionId));
|
|
723
|
+
const sessionSelection = heartbeatSessions.selectRunSessionLineage({
|
|
724
|
+
forceFresh: Boolean(heartbeatSessions.readSessionReuseSuppression(currentContext)),
|
|
725
|
+
explicitSessionParams,
|
|
726
|
+
explicitSessionDisplayId,
|
|
727
|
+
taskSessionParams,
|
|
728
|
+
taskSessionDisplayId,
|
|
729
|
+
});
|
|
730
|
+
const claimedRun = await tx
|
|
731
|
+
.update(heartbeatRuns)
|
|
732
|
+
.set({
|
|
733
|
+
status: "running",
|
|
734
|
+
startedAt: currentRun.startedAt ?? claimedAt,
|
|
735
|
+
sessionIdBefore: sessionSelection.sessionDisplayId ?? readNonEmptyString(sessionSelection.sessionParams?.sessionId),
|
|
736
|
+
sessionParamsBeforeJson: sessionSelection.sessionParams,
|
|
737
|
+
sessionReuseScope: sessionSelection.reuseScope,
|
|
738
|
+
executionOwnerToken: randomUUID(),
|
|
739
|
+
executionLeaseExpiresAt: new Date(claimedAt.getTime() + RUN_EXECUTION_LEASE_MS),
|
|
740
|
+
updatedAt: claimedAt,
|
|
741
|
+
})
|
|
742
|
+
.where(and(eq(heartbeatRuns.id, run.id), eq(heartbeatRuns.status, "queued")))
|
|
743
|
+
.returning()
|
|
744
|
+
.then((rows) => rows[0] ?? null);
|
|
745
|
+
if (!claimedRun)
|
|
746
|
+
return null;
|
|
747
|
+
await tx
|
|
748
|
+
.update(agents)
|
|
749
|
+
.set({ status: "running", updatedAt: claimedAt })
|
|
750
|
+
.where(eq(agents.id, run.agentId));
|
|
751
|
+
return claimedRun;
|
|
752
|
+
});
|
|
567
753
|
if (!claimed)
|
|
568
754
|
return null;
|
|
569
755
|
publishLiveEvent({
|
|
@@ -584,14 +770,14 @@ export function heartbeatService(db) {
|
|
|
584
770
|
await setWakeupStatus(claimed.wakeupRequestId, "claimed", { claimedAt });
|
|
585
771
|
return claimed;
|
|
586
772
|
}
|
|
587
|
-
async function finalizeAgentStatus(agentId, outcome) {
|
|
773
|
+
async function finalizeAgentStatus(agentId, outcome, runId) {
|
|
588
774
|
const existing = await getAgent(agentId);
|
|
589
775
|
if (!existing)
|
|
590
776
|
return;
|
|
591
777
|
if (existing.status === "paused" || existing.status === "terminated") {
|
|
592
778
|
return;
|
|
593
779
|
}
|
|
594
|
-
const runningCount = await countRunningRunsForAgent(agentId);
|
|
780
|
+
const runningCount = await countRunningRunsForAgent(agentId, runId);
|
|
595
781
|
const nextStatus = runningCount > 0
|
|
596
782
|
? "running"
|
|
597
783
|
: outcome === "succeeded" || outcome === "cancelled"
|
|
@@ -624,7 +810,8 @@ export function heartbeatService(db) {
|
|
|
624
810
|
}
|
|
625
811
|
async function reapOrphanedRuns(opts) {
|
|
626
812
|
const staleThresholdMs = opts?.staleThresholdMs ?? 0;
|
|
627
|
-
const now = new Date();
|
|
813
|
+
const now = opts?.now ?? new Date();
|
|
814
|
+
const recoveryCutoff = opts?.recoveryCutoff ?? now;
|
|
628
815
|
// Find all runs stuck in "running" state (queued runs are legitimately waiting; resumeQueuedRuns handles them)
|
|
629
816
|
const activeRuns = await db
|
|
630
817
|
.select({
|
|
@@ -633,100 +820,107 @@ export function heartbeatService(db) {
|
|
|
633
820
|
})
|
|
634
821
|
.from(heartbeatRuns)
|
|
635
822
|
.innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
|
|
636
|
-
.where(eq(heartbeatRuns.status, "running"));
|
|
823
|
+
.where(or(eq(heartbeatRuns.status, "running"), eq(heartbeatRuns.terminalEffectsPending, true)));
|
|
637
824
|
const reaped = [];
|
|
638
825
|
for (const { run, agentRuntimeType } of activeRuns) {
|
|
639
|
-
if (
|
|
826
|
+
if (run.terminalEffectsPending) {
|
|
827
|
+
if (!run.processExitedAt) {
|
|
828
|
+
const exited = await terminateRunProcessAndWait(run, agentRuntimeType);
|
|
829
|
+
if (!exited || activeRunExecutions.has(run.id))
|
|
830
|
+
continue;
|
|
831
|
+
await acknowledgeRunProcessExit(run.id);
|
|
832
|
+
}
|
|
833
|
+
const completed = await completeTerminalControlEffects(run);
|
|
834
|
+
if (!completed)
|
|
835
|
+
continue;
|
|
836
|
+
reaped.push(run.id);
|
|
640
837
|
continue;
|
|
838
|
+
}
|
|
641
839
|
// Apply staleness threshold to avoid false positives
|
|
642
840
|
if (staleThresholdMs > 0) {
|
|
643
841
|
const refTime = run.updatedAt ? new Date(run.updatedAt).getTime() : 0;
|
|
644
842
|
if (now.getTime() - refTime < staleThresholdMs)
|
|
645
843
|
continue;
|
|
646
844
|
}
|
|
845
|
+
const recoveryClaim = await claimExpiredHeartbeatRunExecution(db, run.id, { now, recoveryCutoff });
|
|
846
|
+
if (!recoveryClaim)
|
|
847
|
+
continue;
|
|
848
|
+
const claimedRun = recoveryClaim.run;
|
|
647
849
|
const tracksLocalChild = isTrackedLocalChildProcessAdapter(agentRuntimeType);
|
|
648
850
|
let detachedTerminationMessage = null;
|
|
649
|
-
if (tracksLocalChild &&
|
|
650
|
-
const termination = await terminateOrphanedProcess(
|
|
851
|
+
if (!claimedRun.processExitedAt && tracksLocalChild && claimedRun.processPid && isProcessAlive(claimedRun.processPid)) {
|
|
852
|
+
const termination = await terminateOrphanedProcess(claimedRun.processPid);
|
|
651
853
|
if (termination.stillAlive) {
|
|
652
854
|
const detachedMessage = termination.error
|
|
653
|
-
? `Lost in-memory process handle, child pid ${
|
|
654
|
-
: `Lost in-memory process handle, but child pid ${
|
|
655
|
-
const detachedRun = await setRunStatus(
|
|
855
|
+
? `Lost in-memory process handle, child pid ${claimedRun.processPid} is still alive, and Rudder could not terminate it: ${termination.error}`
|
|
856
|
+
: `Lost in-memory process handle, but child pid ${claimedRun.processPid} is still alive`;
|
|
857
|
+
const detachedRun = await setRunStatus(claimedRun.id, "running", {
|
|
656
858
|
error: detachedMessage,
|
|
657
859
|
errorCode: DETACHED_PROCESS_ERROR_CODE,
|
|
658
860
|
});
|
|
659
861
|
if (detachedRun) {
|
|
660
|
-
await appendRunEvent(detachedRun,
|
|
862
|
+
await appendRunEvent(detachedRun, {
|
|
661
863
|
eventType: "lifecycle",
|
|
662
864
|
stream: "system",
|
|
663
865
|
level: "warn",
|
|
664
866
|
message: detachedMessage,
|
|
665
867
|
payload: {
|
|
666
|
-
processPid:
|
|
868
|
+
processPid: claimedRun.processPid,
|
|
667
869
|
},
|
|
668
870
|
});
|
|
669
871
|
}
|
|
670
872
|
continue;
|
|
671
873
|
}
|
|
672
874
|
detachedTerminationMessage = termination.terminationSignal
|
|
673
|
-
? `Terminated detached child pid ${
|
|
674
|
-
: `Detached child pid ${
|
|
875
|
+
? `Terminated detached child pid ${claimedRun.processPid} with ${termination.terminationSignal} after Rudder lost its process handle`
|
|
876
|
+
: `Detached child pid ${claimedRun.processPid} exited before Rudder could terminate it`;
|
|
675
877
|
}
|
|
676
|
-
const shouldRetry = tracksLocalChild && !!
|
|
677
|
-
const baseMessage =
|
|
678
|
-
? `Process lost -- child pid ${
|
|
878
|
+
const shouldRetry = tracksLocalChild && !!claimedRun.processPid && (claimedRun.processLossRetryCount ?? 0) < 1;
|
|
879
|
+
const baseMessage = claimedRun.processPid
|
|
880
|
+
? `Process lost -- child pid ${claimedRun.processPid} is no longer running`
|
|
679
881
|
: "Process lost -- server may have restarted";
|
|
680
|
-
let finalizedRun = await
|
|
882
|
+
let finalizedRun = await transitionRunToTerminal(claimedRun.id, "failed", {
|
|
681
883
|
error: shouldRetry ? `${baseMessage}; retrying once` : baseMessage,
|
|
682
884
|
errorCode: "process_lost",
|
|
683
885
|
finishedAt: now,
|
|
886
|
+
}, {
|
|
887
|
+
processExitedAt: now,
|
|
888
|
+
terminalEffectsIntent: shouldRetry ? { version: 1, processLossRetry: true } : { version: 1 },
|
|
889
|
+
expectedExecutionOwnerToken: recoveryClaim.ownerToken,
|
|
684
890
|
});
|
|
685
|
-
|
|
891
|
+
if (!finalizedRun)
|
|
892
|
+
continue;
|
|
893
|
+
await setWakeupStatus(claimedRun.wakeupRequestId, "failed", {
|
|
686
894
|
finishedAt: now,
|
|
687
895
|
error: shouldRetry ? `${baseMessage}; retrying once` : baseMessage,
|
|
688
896
|
});
|
|
689
|
-
if (!finalizedRun)
|
|
690
|
-
finalizedRun = await getRun(run.id);
|
|
691
|
-
if (!finalizedRun)
|
|
692
|
-
continue;
|
|
693
897
|
if (detachedTerminationMessage) {
|
|
694
|
-
await appendRunEvent(finalizedRun,
|
|
898
|
+
await appendRunEvent(finalizedRun, {
|
|
695
899
|
eventType: "lifecycle",
|
|
696
900
|
stream: "system",
|
|
697
901
|
level: "warn",
|
|
698
902
|
message: detachedTerminationMessage,
|
|
699
903
|
payload: {
|
|
700
|
-
...(
|
|
904
|
+
...(claimedRun.processPid ? { processPid: claimedRun.processPid } : {}),
|
|
701
905
|
},
|
|
702
906
|
});
|
|
703
907
|
}
|
|
704
|
-
|
|
705
|
-
if (shouldRetry) {
|
|
706
|
-
const agent = await getAgent(run.agentId);
|
|
707
|
-
if (agent) {
|
|
708
|
-
retriedRun = await enqueueProcessLossRetry(finalizedRun, agent, now);
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
else {
|
|
712
|
-
await releaseIssueExecutionAndPromote(finalizedRun);
|
|
713
|
-
}
|
|
714
|
-
await appendRunEvent(finalizedRun, await nextRunEventSeq(finalizedRun.id), {
|
|
908
|
+
await appendRunEvent(finalizedRun, {
|
|
715
909
|
eventType: "lifecycle",
|
|
716
910
|
stream: "system",
|
|
717
911
|
level: "error",
|
|
718
912
|
message: shouldRetry
|
|
719
|
-
? `${baseMessage}; queued
|
|
913
|
+
? `${baseMessage}; retry will be queued after terminal effects complete`
|
|
720
914
|
: baseMessage,
|
|
721
915
|
payload: {
|
|
722
|
-
...(
|
|
723
|
-
...(retriedRun ? { retryRunId: retriedRun.id } : {}),
|
|
916
|
+
...(claimedRun.processPid ? { processPid: claimedRun.processPid } : {}),
|
|
724
917
|
},
|
|
725
918
|
});
|
|
726
|
-
await
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
919
|
+
const completed = await completeTerminalControlEffects(finalizedRun);
|
|
920
|
+
runningProcesses.delete(claimedRun.id);
|
|
921
|
+
if (!completed)
|
|
922
|
+
continue;
|
|
923
|
+
reaped.push(claimedRun.id);
|
|
730
924
|
}
|
|
731
925
|
if (reaped.length > 0) {
|
|
732
926
|
logger.warn({ reapedCount: reaped.length, runIds: reaped }, "reaped orphaned heartbeat runs");
|
|
@@ -744,6 +938,7 @@ export function heartbeatService(db) {
|
|
|
744
938
|
run: heartbeatRuns,
|
|
745
939
|
agentRuntimeType: agents.agentRuntimeType,
|
|
746
940
|
lastEventAt: sql `max(${heartbeatRunEvents.createdAt})`,
|
|
941
|
+
eventCount: sql `count(${heartbeatRunEvents.id})::int`,
|
|
747
942
|
})
|
|
748
943
|
.from(heartbeatRuns)
|
|
749
944
|
.innerJoin(agents, eq(heartbeatRuns.agentId, agents.id))
|
|
@@ -751,7 +946,9 @@ export function heartbeatService(db) {
|
|
|
751
946
|
.where(eq(heartbeatRuns.status, "running"))
|
|
752
947
|
.groupBy(heartbeatRuns.id, agents.agentRuntimeType);
|
|
753
948
|
const timedOut = [];
|
|
754
|
-
for (const { run, agentRuntimeType, lastEventAt } of activeRuns) {
|
|
949
|
+
for (const { run, agentRuntimeType, lastEventAt, eventCount } of activeRuns) {
|
|
950
|
+
if (opts?.recoveryCutoff && new Date(run.createdAt).getTime() >= opts.recoveryCutoff.getTime())
|
|
951
|
+
continue;
|
|
755
952
|
const activityTimes = [
|
|
756
953
|
run.updatedAt,
|
|
757
954
|
lastEventAt,
|
|
@@ -768,33 +965,29 @@ export function heartbeatService(db) {
|
|
|
768
965
|
if (inactiveMs < maxInactivityMs)
|
|
769
966
|
continue;
|
|
770
967
|
const message = `Run had no recorded activity for ${formatDurationMs(maxInactivityMs)}`;
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
const graceMs = Math.max(1, running.graceSec) * 1000;
|
|
776
|
-
setTimeout(() => {
|
|
777
|
-
if (typeof pid === "number" && isProcessAlive(pid)) {
|
|
778
|
-
running.child.kill("SIGKILL");
|
|
779
|
-
}
|
|
780
|
-
}, graceMs);
|
|
781
|
-
}
|
|
782
|
-
else if (isTrackedLocalChildProcessAdapter(agentRuntimeType) && run.processPid && isProcessAlive(run.processPid)) {
|
|
783
|
-
await terminateOrphanedProcess(run.processPid);
|
|
784
|
-
}
|
|
785
|
-
const finalizedRun = await setRunStatus(run.id, "timed_out", {
|
|
968
|
+
const processExitPending = activeRunExecutions.has(run.id)
|
|
969
|
+
|| runningProcesses.has(run.id)
|
|
970
|
+
|| (!run.processExitedAt && isTrackedLocalChildProcessAdapter(agentRuntimeType) && !!run.processPid && isProcessAlive(run.processPid));
|
|
971
|
+
const finalizedRun = await transitionRunToTerminal(run.id, "timed_out", {
|
|
786
972
|
finishedAt: now,
|
|
787
973
|
error: message,
|
|
788
974
|
errorCode: "inactivity_timeout",
|
|
975
|
+
terminalEffectsPending: true,
|
|
976
|
+
}, {
|
|
977
|
+
activityWatermark: {
|
|
978
|
+
updatedAt: run.updatedAt,
|
|
979
|
+
eventCount: Number(eventCount ?? 0),
|
|
980
|
+
},
|
|
981
|
+
processExitedAt: processExitPending ? null : now,
|
|
982
|
+
expectedExecutionOwnerToken: run.executionOwnerToken,
|
|
789
983
|
});
|
|
984
|
+
if (!finalizedRun)
|
|
985
|
+
continue;
|
|
790
986
|
await setWakeupStatus(run.wakeupRequestId, "timed_out", {
|
|
791
987
|
finishedAt: now,
|
|
792
988
|
error: message,
|
|
793
989
|
});
|
|
794
|
-
|
|
795
|
-
if (!terminalRun)
|
|
796
|
-
continue;
|
|
797
|
-
await appendRunEvent(terminalRun, await nextRunEventSeq(terminalRun.id), {
|
|
990
|
+
await appendRunEvent(finalizedRun, {
|
|
798
991
|
eventType: "lifecycle",
|
|
799
992
|
stream: "system",
|
|
800
993
|
level: "error",
|
|
@@ -807,10 +1000,12 @@ export function heartbeatService(db) {
|
|
|
807
1000
|
...(run.processPid ? { processPid: run.processPid } : {}),
|
|
808
1001
|
},
|
|
809
1002
|
});
|
|
810
|
-
await
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
1003
|
+
const processExited = !processExitPending || await terminateRunProcessAndWait(finalizedRun, agentRuntimeType);
|
|
1004
|
+
if (processExited && !activeRunExecutions.has(finalizedRun.id)) {
|
|
1005
|
+
await acknowledgeRunProcessExit(finalizedRun.id);
|
|
1006
|
+
await completeTerminalControlEffects(finalizedRun);
|
|
1007
|
+
runningProcesses.delete(run.id);
|
|
1008
|
+
}
|
|
814
1009
|
timedOut.push(run.id);
|
|
815
1010
|
}
|
|
816
1011
|
if (timedOut.length > 0) {
|
|
@@ -834,6 +1029,8 @@ export function heartbeatService(db) {
|
|
|
834
1029
|
.where(eq(heartbeatRuns.status, "running"));
|
|
835
1030
|
const timedOut = [];
|
|
836
1031
|
for (const { run, agentRuntimeType } of activeRuns) {
|
|
1032
|
+
if (opts?.recoveryCutoff && new Date(run.createdAt).getTime() >= opts.recoveryCutoff.getTime())
|
|
1033
|
+
continue;
|
|
837
1034
|
const startedAt = run.startedAt ? new Date(run.startedAt).getTime() : null;
|
|
838
1035
|
if (!startedAt || !Number.isFinite(startedAt))
|
|
839
1036
|
continue;
|
|
@@ -841,33 +1038,25 @@ export function heartbeatService(db) {
|
|
|
841
1038
|
if (runtimeMs < maxRuntimeMs)
|
|
842
1039
|
continue;
|
|
843
1040
|
const message = `Run exceeded maximum duration of ${formatDurationMs(maxRuntimeMs)}`;
|
|
844
|
-
const
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
const graceMs = Math.max(1, running.graceSec) * 1000;
|
|
849
|
-
setTimeout(() => {
|
|
850
|
-
if (typeof pid === "number" && isProcessAlive(pid)) {
|
|
851
|
-
running.child.kill("SIGKILL");
|
|
852
|
-
}
|
|
853
|
-
}, graceMs);
|
|
854
|
-
}
|
|
855
|
-
else if (isTrackedLocalChildProcessAdapter(agentRuntimeType) && run.processPid && isProcessAlive(run.processPid)) {
|
|
856
|
-
await terminateOrphanedProcess(run.processPid);
|
|
857
|
-
}
|
|
858
|
-
const finalizedRun = await setRunStatus(run.id, "timed_out", {
|
|
1041
|
+
const processExitPending = activeRunExecutions.has(run.id)
|
|
1042
|
+
|| runningProcesses.has(run.id)
|
|
1043
|
+
|| (!run.processExitedAt && isTrackedLocalChildProcessAdapter(agentRuntimeType) && !!run.processPid && isProcessAlive(run.processPid));
|
|
1044
|
+
const finalizedRun = await transitionRunToTerminal(run.id, "timed_out", {
|
|
859
1045
|
finishedAt: now,
|
|
860
1046
|
error: message,
|
|
861
1047
|
errorCode: "timeout",
|
|
1048
|
+
terminalEffectsPending: true,
|
|
1049
|
+
}, {
|
|
1050
|
+
processExitedAt: processExitPending ? null : now,
|
|
1051
|
+
expectedExecutionOwnerToken: run.executionOwnerToken,
|
|
862
1052
|
});
|
|
1053
|
+
if (!finalizedRun)
|
|
1054
|
+
continue;
|
|
863
1055
|
await setWakeupStatus(run.wakeupRequestId, "timed_out", {
|
|
864
1056
|
finishedAt: now,
|
|
865
1057
|
error: message,
|
|
866
1058
|
});
|
|
867
|
-
|
|
868
|
-
if (!terminalRun)
|
|
869
|
-
continue;
|
|
870
|
-
await appendRunEvent(terminalRun, await nextRunEventSeq(terminalRun.id), {
|
|
1059
|
+
await appendRunEvent(finalizedRun, {
|
|
871
1060
|
eventType: "lifecycle",
|
|
872
1061
|
stream: "system",
|
|
873
1062
|
level: "error",
|
|
@@ -880,10 +1069,12 @@ export function heartbeatService(db) {
|
|
|
880
1069
|
...(run.processPid ? { processPid: run.processPid } : {}),
|
|
881
1070
|
},
|
|
882
1071
|
});
|
|
883
|
-
await
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1072
|
+
const processExited = !processExitPending || await terminateRunProcessAndWait(finalizedRun, agentRuntimeType);
|
|
1073
|
+
if (processExited && !activeRunExecutions.has(finalizedRun.id)) {
|
|
1074
|
+
await acknowledgeRunProcessExit(finalizedRun.id);
|
|
1075
|
+
await completeTerminalControlEffects(finalizedRun);
|
|
1076
|
+
runningProcesses.delete(run.id);
|
|
1077
|
+
}
|
|
887
1078
|
timedOut.push(run.id);
|
|
888
1079
|
}
|
|
889
1080
|
if (timedOut.length > 0) {
|
|
@@ -901,7 +1092,7 @@ export function heartbeatService(db) {
|
|
|
901
1092
|
await startNextQueuedRunForAgent(agentId);
|
|
902
1093
|
}
|
|
903
1094
|
}
|
|
904
|
-
async function updateRuntimeState(agent, run, result, session, normalizedUsage) {
|
|
1095
|
+
async function updateRuntimeState(agent, run, result, session, normalizedUsage, opts) {
|
|
905
1096
|
await ensureRuntimeState(agent);
|
|
906
1097
|
const usage = normalizedUsage ?? normalizeUsageTotals(result.usage);
|
|
907
1098
|
const rawInputTokens = usage?.inputTokens ?? 0;
|
|
@@ -919,24 +1110,37 @@ export function heartbeatService(db) {
|
|
|
919
1110
|
});
|
|
920
1111
|
const biller = resolveLedgerBiller(result);
|
|
921
1112
|
const ledgerScope = await resolveLedgerScopeForRun(db, agent.orgId, run);
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1113
|
+
if (opts?.ownsTerminal) {
|
|
1114
|
+
await db
|
|
1115
|
+
.update(agentRuntimeState)
|
|
1116
|
+
.set({
|
|
1117
|
+
agentRuntimeType: agent.agentRuntimeType,
|
|
1118
|
+
sessionId: session.legacySessionId,
|
|
1119
|
+
lastRunId: run.id,
|
|
1120
|
+
lastRunStatus: run.status,
|
|
1121
|
+
lastError: result.errorMessage ?? null,
|
|
1122
|
+
updatedAt: new Date(),
|
|
1123
|
+
})
|
|
1124
|
+
.where(and(eq(agentRuntimeState.agentId, agent.id), sql `(
|
|
1125
|
+
${agentRuntimeState.lastRunId} is null
|
|
1126
|
+
or ${agentRuntimeState.lastRunId} = ${run.id}
|
|
1127
|
+
or not exists (
|
|
1128
|
+
select 1
|
|
1129
|
+
from heartbeat_runs previous_run
|
|
1130
|
+
where previous_run.id = ${agentRuntimeState.lastRunId}
|
|
1131
|
+
and (
|
|
1132
|
+
previous_run.created_at > ${run.createdAt.toISOString()}::timestamptz
|
|
1133
|
+
or (
|
|
1134
|
+
previous_run.created_at = ${run.createdAt.toISOString()}::timestamptz
|
|
1135
|
+
and previous_run.id > ${run.id}
|
|
1136
|
+
)
|
|
1137
|
+
)
|
|
1138
|
+
)
|
|
1139
|
+
)`));
|
|
1140
|
+
}
|
|
937
1141
|
if (additionalCostCents > 0 || hasTokenUsage) {
|
|
938
1142
|
const costs = costService(db, budgetHooks);
|
|
939
|
-
await costs.
|
|
1143
|
+
await costs.createHeartbeatRunEventOnce(agent.orgId, {
|
|
940
1144
|
heartbeatRunId: run.id,
|
|
941
1145
|
agentId: agent.id,
|
|
942
1146
|
issueId: ledgerScope.issueId,
|
|
@@ -950,9 +1154,163 @@ export function heartbeatService(db) {
|
|
|
950
1154
|
outputTokens,
|
|
951
1155
|
costCents: additionalCostCents,
|
|
952
1156
|
occurredAt: new Date(),
|
|
1157
|
+
}, async (tx) => {
|
|
1158
|
+
await tx
|
|
1159
|
+
.update(agentRuntimeState)
|
|
1160
|
+
.set({
|
|
1161
|
+
totalInputTokens: sql `${agentRuntimeState.totalInputTokens} + ${tokenSummary.promptTokens}`,
|
|
1162
|
+
totalOutputTokens: sql `${agentRuntimeState.totalOutputTokens} + ${outputTokens}`,
|
|
1163
|
+
totalCachedInputTokens: sql `${agentRuntimeState.totalCachedInputTokens} + ${cachedInputTokens}`,
|
|
1164
|
+
totalCostCents: sql `${agentRuntimeState.totalCostCents} + ${additionalCostCents}`,
|
|
1165
|
+
updatedAt: new Date(),
|
|
1166
|
+
})
|
|
1167
|
+
.where(eq(agentRuntimeState.agentId, agent.id));
|
|
953
1168
|
});
|
|
954
1169
|
}
|
|
955
1170
|
}
|
|
1171
|
+
async function completeTerminalControlEffects(run, opts) {
|
|
1172
|
+
const claim = await claimHeartbeatRunTerminalEffects(db, run.id);
|
|
1173
|
+
if (!claim)
|
|
1174
|
+
return;
|
|
1175
|
+
const current = claim.run;
|
|
1176
|
+
let claimReleased = false;
|
|
1177
|
+
try {
|
|
1178
|
+
const runContext = parseObject(current.contextSnapshot);
|
|
1179
|
+
const intent = parseObject(current.terminalEffectsJson);
|
|
1180
|
+
const completedEffects = new Set(current.terminalEffectsCompletedJson ?? []);
|
|
1181
|
+
const deadLetteredEffects = new Set(current.terminalEffectsDeadLetteredJson ?? []);
|
|
1182
|
+
const runClaimedEffect = async (effectName, effect) => {
|
|
1183
|
+
if (completedEffects.has(effectName) || deadLetteredEffects.has(effectName))
|
|
1184
|
+
return undefined;
|
|
1185
|
+
const renewClaim = async () => {
|
|
1186
|
+
if (!await renewHeartbeatRunTerminalEffectsClaim(db, current.id, claim.claimToken)) {
|
|
1187
|
+
throw new Error("Heartbeat terminal effects claim was lost");
|
|
1188
|
+
}
|
|
1189
|
+
};
|
|
1190
|
+
await renewClaim();
|
|
1191
|
+
let renewalError = null;
|
|
1192
|
+
let renewal = Promise.resolve();
|
|
1193
|
+
const renewalTimer = setInterval(() => {
|
|
1194
|
+
renewal = renewal
|
|
1195
|
+
.then(renewClaim)
|
|
1196
|
+
.catch((error) => {
|
|
1197
|
+
renewalError ??= error;
|
|
1198
|
+
});
|
|
1199
|
+
}, TERMINAL_EFFECT_CLAIM_RENEW_INTERVAL_MS);
|
|
1200
|
+
renewalTimer.unref?.();
|
|
1201
|
+
try {
|
|
1202
|
+
const result = await effect();
|
|
1203
|
+
await renewal;
|
|
1204
|
+
if (renewalError)
|
|
1205
|
+
throw renewalError;
|
|
1206
|
+
await renewClaim();
|
|
1207
|
+
if (!await checkpointHeartbeatRunTerminalEffect(db, current.id, claim.claimToken, effectName)) {
|
|
1208
|
+
throw new Error(`Heartbeat terminal effect ${effectName} checkpoint was lost`);
|
|
1209
|
+
}
|
|
1210
|
+
completedEffects.add(effectName);
|
|
1211
|
+
return result;
|
|
1212
|
+
}
|
|
1213
|
+
catch (error) {
|
|
1214
|
+
const failure = await failHeartbeatRunTerminalEffect(db, current.id, claim.claimToken, effectName, error);
|
|
1215
|
+
if (!failure)
|
|
1216
|
+
throw error;
|
|
1217
|
+
if (failure.deadLettered) {
|
|
1218
|
+
deadLetteredEffects.add(effectName);
|
|
1219
|
+
return undefined;
|
|
1220
|
+
}
|
|
1221
|
+
claimReleased = true;
|
|
1222
|
+
throw error;
|
|
1223
|
+
}
|
|
1224
|
+
finally {
|
|
1225
|
+
clearInterval(renewalTimer);
|
|
1226
|
+
}
|
|
1227
|
+
};
|
|
1228
|
+
if (intent.automation) {
|
|
1229
|
+
await runClaimedEffect("automation_chat", () => publishAutomationRunOutputToChat(db, {
|
|
1230
|
+
issueId: readNonEmptyString(runContext.issueId),
|
|
1231
|
+
output: opts?.automationOutput ?? intent.automation?.output ?? current.error,
|
|
1232
|
+
status: current.status,
|
|
1233
|
+
transcript: opts?.automationTranscript ?? [],
|
|
1234
|
+
}));
|
|
1235
|
+
}
|
|
1236
|
+
const agent = await getAgent(current.agentId);
|
|
1237
|
+
const runtimeIntent = intent.runtime;
|
|
1238
|
+
if (runtimeIntent) {
|
|
1239
|
+
await runClaimedEffect("runtime_cost", () => {
|
|
1240
|
+
if (!agent)
|
|
1241
|
+
throw new Error("Agent not found while replaying runtime terminal effect");
|
|
1242
|
+
return updateRuntimeState(agent, current, {
|
|
1243
|
+
exitCode: null,
|
|
1244
|
+
signal: null,
|
|
1245
|
+
timedOut: false,
|
|
1246
|
+
errorMessage: runtimeIntent.errorMessage,
|
|
1247
|
+
provider: runtimeIntent.provider,
|
|
1248
|
+
biller: runtimeIntent.biller,
|
|
1249
|
+
model: runtimeIntent.model,
|
|
1250
|
+
billingType: runtimeIntent.billingType,
|
|
1251
|
+
costUsd: runtimeIntent.costUsd,
|
|
1252
|
+
}, { legacySessionId: runtimeIntent.legacySessionId }, runtimeIntent.normalizedUsage, { ownsTerminal: runtimeIntent.ownsTerminal !== false });
|
|
1253
|
+
});
|
|
1254
|
+
}
|
|
1255
|
+
const taskSessionIntent = intent.taskSession;
|
|
1256
|
+
if (taskSessionIntent?.operation === "clear") {
|
|
1257
|
+
await runClaimedEffect("task_session", () => clearTaskSessions(taskSessionIntent.orgId, taskSessionIntent.agentId, {
|
|
1258
|
+
taskKey: taskSessionIntent.taskKey,
|
|
1259
|
+
agentRuntimeType: taskSessionIntent.agentRuntimeType,
|
|
1260
|
+
sourceRunId: taskSessionIntent.lastRunId ?? current.id,
|
|
1261
|
+
}));
|
|
1262
|
+
}
|
|
1263
|
+
else if (taskSessionIntent?.operation === "upsert") {
|
|
1264
|
+
await runClaimedEffect("task_session", () => upsertTaskSession({
|
|
1265
|
+
orgId: taskSessionIntent.orgId,
|
|
1266
|
+
agentId: taskSessionIntent.agentId,
|
|
1267
|
+
agentRuntimeType: taskSessionIntent.agentRuntimeType,
|
|
1268
|
+
taskKey: taskSessionIntent.taskKey,
|
|
1269
|
+
sessionParamsJson: taskSessionIntent.sessionParamsJson ?? null,
|
|
1270
|
+
sessionDisplayId: taskSessionIntent.sessionDisplayId ?? null,
|
|
1271
|
+
lastRunId: taskSessionIntent.lastRunId ?? current.id,
|
|
1272
|
+
lastError: taskSessionIntent.lastError ?? null,
|
|
1273
|
+
}));
|
|
1274
|
+
}
|
|
1275
|
+
if (intent.processLossRetry) {
|
|
1276
|
+
await runClaimedEffect("process_loss_retry", () => {
|
|
1277
|
+
if (!agent)
|
|
1278
|
+
throw new Error("Agent not found while replaying process-loss retry");
|
|
1279
|
+
return enqueueProcessLossRetry(current, agent, current.finishedAt ?? new Date());
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
await runClaimedEffect("issue_release", () => releaseIssueExecutionAndPromote(current, { startNext: false }));
|
|
1283
|
+
const requiredEffects = terminalEffectNames(intent);
|
|
1284
|
+
if (!requiredEffects.every((effect) => completedEffects.has(effect) || deadLetteredEffects.has(effect))) {
|
|
1285
|
+
throw new Error("Heartbeat terminal effects did not converge");
|
|
1286
|
+
}
|
|
1287
|
+
const completed = await completeHeartbeatRunTerminalEffects(db, current.id, claim.claimToken);
|
|
1288
|
+
if (!completed)
|
|
1289
|
+
return;
|
|
1290
|
+
if (completed.agent) {
|
|
1291
|
+
publishLiveEvent({
|
|
1292
|
+
orgId: completed.agent.orgId,
|
|
1293
|
+
type: "agent.status",
|
|
1294
|
+
payload: {
|
|
1295
|
+
agentId: completed.agent.id,
|
|
1296
|
+
status: completed.agent.status,
|
|
1297
|
+
lastHeartbeatAt: completed.agent.lastHeartbeatAt
|
|
1298
|
+
? new Date(completed.agent.lastHeartbeatAt).toISOString()
|
|
1299
|
+
: null,
|
|
1300
|
+
},
|
|
1301
|
+
});
|
|
1302
|
+
}
|
|
1303
|
+
if (opts?.startNext !== false)
|
|
1304
|
+
await resumeQueuedRuns();
|
|
1305
|
+
return completed;
|
|
1306
|
+
}
|
|
1307
|
+
catch (error) {
|
|
1308
|
+
if (!claimReleased) {
|
|
1309
|
+
await releaseHeartbeatRunTerminalEffectsClaim(db, current.id, claim.claimToken, error);
|
|
1310
|
+
}
|
|
1311
|
+
throw error;
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
956
1314
|
async function startNextQueuedRunForAgent(agentId) {
|
|
957
1315
|
return withAgentStartLock(agentId, async () => {
|
|
958
1316
|
const agent = await getAgent(agentId);
|
|
@@ -961,6 +1319,14 @@ export function heartbeatService(db) {
|
|
|
961
1319
|
if (agent.status === "paused" || agent.status === "terminated" || agent.status === "pending_approval") {
|
|
962
1320
|
return [];
|
|
963
1321
|
}
|
|
1322
|
+
const pendingTerminalEffects = await db
|
|
1323
|
+
.select({ id: heartbeatRuns.id })
|
|
1324
|
+
.from(heartbeatRuns)
|
|
1325
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.terminalEffectsPending, true)))
|
|
1326
|
+
.limit(1)
|
|
1327
|
+
.then((rows) => rows[0] ?? null);
|
|
1328
|
+
if (pendingTerminalEffects)
|
|
1329
|
+
return [];
|
|
964
1330
|
const policy = parseHeartbeatPolicy(agent);
|
|
965
1331
|
const runningCount = await countRunningRunsForAgent(agentId);
|
|
966
1332
|
const availableSlots = Math.max(0, policy.maxConcurrentRuns - runningCount);
|
|
@@ -984,27 +1350,51 @@ export function heartbeatService(db) {
|
|
|
984
1350
|
return [];
|
|
985
1351
|
const claimedRuns = [];
|
|
986
1352
|
for (const queuedRun of queuedRuns) {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
1353
|
+
if (activeRunExecutions.has(queuedRun.id))
|
|
1354
|
+
continue;
|
|
1355
|
+
activeRunExecutions.add(queuedRun.id);
|
|
1356
|
+
runAbortControllers.set(queuedRun.id, new AbortController());
|
|
1357
|
+
try {
|
|
1358
|
+
const claimed = await claimQueuedRun(queuedRun);
|
|
1359
|
+
if (claimed) {
|
|
1360
|
+
claimedRuns.push(claimed);
|
|
1361
|
+
void executeRun(claimed.id, { executionReserved: true }).catch((err) => {
|
|
1362
|
+
logger.error({ err, runId: claimed.id }, "queued heartbeat execution failed");
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
runAbortControllers.delete(queuedRun.id);
|
|
1367
|
+
activeRunExecutions.delete(queuedRun.id);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
catch (error) {
|
|
1371
|
+
runAbortControllers.delete(queuedRun.id);
|
|
1372
|
+
activeRunExecutions.delete(queuedRun.id);
|
|
1373
|
+
throw error;
|
|
1374
|
+
}
|
|
990
1375
|
}
|
|
991
1376
|
if (claimedRuns.length === 0)
|
|
992
1377
|
return [];
|
|
993
|
-
for (const claimedRun of claimedRuns) {
|
|
994
|
-
void executeRun(claimedRun.id).catch((err) => {
|
|
995
|
-
logger.error({ err, runId: claimedRun.id }, "queued heartbeat execution failed");
|
|
996
|
-
});
|
|
997
|
-
}
|
|
998
1378
|
return claimedRuns;
|
|
999
1379
|
});
|
|
1000
1380
|
}
|
|
1001
1381
|
const baseContext = {
|
|
1002
|
-
db, instanceSettings, getCurrentUserRedactionOptions, runLogStore, runContextSvc, issuesSvc, executionWorkspacesSvc, workspaceOperationsSvc, activeRunExecutions, budgetHooks, budgets,
|
|
1003
|
-
getAgent, getRun, getRuntimeState, getTaskSession, getLatestRunForSession, getOldestRunForSession, resolveNormalizedUsageForSession, evaluateSessionCompaction, resolveSessionBeforeForWakeup, resolveExplicitResumeSessionOverride, upsertTaskSession, clearTaskSessions, ensureRuntimeState, setRunStatus, setWakeupStatus, updateWakeupRequestRecord, insertWakeupRequestRecord, appendRunEvent,
|
|
1382
|
+
db, instanceSettings, getCurrentUserRedactionOptions, runLogStore, runContextSvc, issuesSvc, executionWorkspacesSvc, workspaceOperationsSvc, activeRunExecutions, runAbortControllers, budgetHooks, budgets,
|
|
1383
|
+
getAgent, getRun, getRuntimeState, getTaskSession, getLatestRunForSession, getOldestRunForSession, resolveNormalizedUsageForSession, evaluateSessionCompaction, resolveSessionBeforeForWakeup, resolveExplicitResumeSessionOverride, upsertTaskSession, clearTaskSessions, ensureRuntimeState, setRunStatus, transitionRunToTerminal, reconcileRunEvidence, reconcileTerminalEffectsIntent, setWakeupStatus, updateWakeupRequestRecord, insertWakeupRequestRecord, appendRunEvent, persistRunProcessMetadata, clearDetachedRunWarning, terminateRunProcessAndWait, acknowledgeRunProcessExit, renewRunExecutionLease, countRunningRunsForAgent, claimQueuedRun, finalizeAgentStatus, completeTerminalControlEffects, reapOrphanedRuns, reapInactiveRuns, reapTimedOutRuns, resumeQueuedRuns, updateRuntimeState, startNextQueuedRunForAgent,
|
|
1004
1384
|
};
|
|
1005
1385
|
const recoveryHandlers = createHeartbeatRecoveryHandlers({ ...baseContext, startNextQueuedRunForAgent });
|
|
1006
|
-
const wakeupHandlers = createHeartbeatWakeupHandlers({
|
|
1007
|
-
|
|
1386
|
+
const wakeupHandlers = createHeartbeatWakeupHandlers({
|
|
1387
|
+
...baseContext,
|
|
1388
|
+
...recoveryHandlers,
|
|
1389
|
+
startNextQueuedRunForAgent,
|
|
1390
|
+
afterIssuePromotionCommitted: testHooks?.afterIssuePromotionCommitted,
|
|
1391
|
+
});
|
|
1392
|
+
const releaseHandlers = createHeartbeatReleaseHandlers({
|
|
1393
|
+
...baseContext,
|
|
1394
|
+
...recoveryHandlers,
|
|
1395
|
+
...wakeupHandlers,
|
|
1396
|
+
afterIssuePromotionCommitted: testHooks?.afterIssuePromotionCommitted,
|
|
1397
|
+
});
|
|
1008
1398
|
const executeHandlers = createHeartbeatExecuteHandlers({ ...baseContext, ...recoveryHandlers, ...releaseHandlers, ...wakeupHandlers });
|
|
1009
1399
|
const miscHandlers = createHeartbeatMiscHandlers({ ...baseContext, ...recoveryHandlers, ...releaseHandlers, ...wakeupHandlers, ...executeHandlers });
|
|
1010
1400
|
const { enqueueRecoveryRun, enqueueProcessLossRetry, evaluatePassiveIssueClosureForLockedIssue, parseHeartbeatPolicy } = recoveryHandlers;
|
|
@@ -1058,7 +1448,7 @@ export function heartbeatService(db) {
|
|
|
1058
1448
|
usedSkillsByRun.set(event.runId, runSkills);
|
|
1059
1449
|
}
|
|
1060
1450
|
}
|
|
1061
|
-
return rows.map((row) => ({
|
|
1451
|
+
return rows.map((row) => toHeartbeatRun({
|
|
1062
1452
|
...row,
|
|
1063
1453
|
resultJson: (() => {
|
|
1064
1454
|
const summary = summarizeHeartbeatRunResultJson(row.resultJson);
|
|
@@ -1249,7 +1639,7 @@ export function heartbeatService(db) {
|
|
|
1249
1639
|
const [run] = await db
|
|
1250
1640
|
.select()
|
|
1251
1641
|
.from(heartbeatRuns)
|
|
1252
|
-
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "running")))
|
|
1642
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), or(eq(heartbeatRuns.status, "running"), eq(heartbeatRuns.terminalEffectsPending, true))))
|
|
1253
1643
|
.orderBy(desc(heartbeatRuns.startedAt))
|
|
1254
1644
|
.limit(1);
|
|
1255
1645
|
return run ?? null;
|