@rudderhq/server 0.2.5-canary.9 → 0.2.5
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/plugin-host-runtime.d.ts +39 -39
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +101 -147
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
- package/dist/bundled-plugins/plugin-linear/package.json +1 -1
- package/dist/routes/access-onboarding.helpers.d.ts +142 -0
- package/dist/routes/access-onboarding.helpers.d.ts.map +1 -0
- package/dist/routes/access-onboarding.helpers.js +762 -0
- package/dist/routes/access-onboarding.helpers.js.map +1 -0
- package/dist/routes/access.d.ts +2 -48
- package/dist/routes/access.d.ts.map +1 -1
- package/dist/routes/access.helpers.d.ts +109 -0
- package/dist/routes/access.helpers.d.ts.map +1 -0
- package/dist/routes/access.helpers.js +460 -0
- package/dist/routes/access.helpers.js.map +1 -0
- package/dist/routes/access.js +6 -1218
- package/dist/routes/access.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +55 -1057
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/agents.management-routes.d.ts +12 -0
- package/dist/routes/agents.management-routes.d.ts.map +1 -0
- package/dist/routes/agents.management-routes.js +1067 -0
- package/dist/routes/agents.management-routes.js.map +1 -0
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +42 -652
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/chats.stream-routes.d.ts +12 -0
- package/dist/routes/chats.stream-routes.d.ts.map +1 -0
- package/dist/routes/chats.stream-routes.js +666 -0
- package/dist/routes/chats.stream-routes.js.map +1 -0
- package/dist/routes/issues.comments-attachments.d.ts +12 -0
- package/dist/routes/issues.comments-attachments.d.ts.map +1 -0
- package/dist/routes/issues.comments-attachments.js +511 -0
- package/dist/routes/issues.comments-attachments.js.map +1 -0
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +43 -1128
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/issues.mutations.d.ts +12 -0
- package/dist/routes/issues.mutations.d.ts.map +1 -0
- package/dist/routes/issues.mutations.js +635 -0
- package/dist/routes/issues.mutations.js.map +1 -0
- package/dist/routes/plugins.d.ts.map +1 -1
- package/dist/routes/plugins.js +14 -694
- package/dist/routes/plugins.js.map +1 -1
- package/dist/routes/plugins.operations-routes.d.ts +28 -0
- package/dist/routes/plugins.operations-routes.d.ts.map +1 -0
- package/dist/routes/plugins.operations-routes.js +720 -0
- package/dist/routes/plugins.operations-routes.js.map +1 -0
- package/dist/services/access.d.ts +21 -21
- package/dist/services/activity.d.ts +19 -19
- package/dist/services/agents.d.ts +158 -158
- package/dist/services/approvals.d.ts +29 -29
- package/dist/services/assets.d.ts +8 -8
- package/dist/services/automations.d.ts +41 -27
- package/dist/services/automations.d.ts.map +1 -1
- package/dist/services/automations.js +287 -110
- package/dist/services/automations.js.map +1 -1
- package/dist/services/automations.scheduler.d.ts +9 -0
- package/dist/services/automations.scheduler.d.ts.map +1 -0
- package/dist/services/automations.scheduler.js +101 -0
- package/dist/services/automations.scheduler.js.map +1 -0
- package/dist/services/board-auth.d.ts +32 -32
- package/dist/services/calendar.d.ts +26 -26
- package/dist/services/chat-assistant.d.ts +3 -47
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.helpers.d.ts +156 -0
- package/dist/services/chat-assistant.helpers.d.ts.map +1 -0
- package/dist/services/chat-assistant.helpers.js +862 -0
- package/dist/services/chat-assistant.helpers.js.map +1 -0
- package/dist/services/chat-assistant.js +2 -861
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chats.d.ts +149 -247
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.helpers.d.ts +117 -0
- package/dist/services/chats.helpers.d.ts.map +1 -0
- package/dist/services/chats.helpers.js +285 -0
- package/dist/services/chats.helpers.js.map +1 -0
- package/dist/services/chats.js +6 -286
- package/dist/services/chats.js.map +1 -1
- package/dist/services/costs.d.ts +8 -8
- package/dist/services/finance.d.ts +18 -18
- package/dist/services/goals.d.ts +30 -30
- package/dist/services/heartbeat.d.ts +3 -1
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +3 -1
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/issue-approvals.d.ts +4 -4
- package/dist/services/issue-review-wakeup.d.ts +3 -3
- package/dist/services/issues.comments-attachments.d.ts +141 -0
- package/dist/services/issues.comments-attachments.d.ts.map +1 -0
- package/dist/services/issues.comments-attachments.js +313 -0
- package/dist/services/issues.comments-attachments.js.map +1 -0
- package/dist/services/issues.d.ts +205 -256
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.helpers.d.ts +87 -0
- package/dist/services/issues.helpers.d.ts.map +1 -0
- package/dist/services/issues.helpers.js +270 -0
- package/dist/services/issues.helpers.js.map +1 -0
- package/dist/services/issues.js +5 -569
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.core.d.ts +210 -0
- package/dist/services/knowledge-portability/organization-portability.core.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.core.js +997 -0
- package/dist/services/knowledge-portability/organization-portability.core.js.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.d.ts +6 -28
- package/dist/services/knowledge-portability/organization-portability.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.export.d.ts +24 -0
- package/dist/services/knowledge-portability/organization-portability.export.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.export.js +607 -0
- package/dist/services/knowledge-portability/organization-portability.export.js.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.files.d.ts +69 -0
- package/dist/services/knowledge-portability/organization-portability.files.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.files.js +597 -0
- package/dist/services/knowledge-portability/organization-portability.files.js.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.import.d.ts +31 -0
- package/dist/services/knowledge-portability/organization-portability.import.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.import.js +575 -0
- package/dist/services/knowledge-portability/organization-portability.import.js.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.js +37 -3848
- package/dist/services/knowledge-portability/organization-portability.js.map +1 -1
- package/dist/services/knowledge-portability/organization-portability.package.d.ts +72 -0
- package/dist/services/knowledge-portability/organization-portability.package.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.package.js +749 -0
- package/dist/services/knowledge-portability/organization-portability.package.js.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.preview.d.ts +18 -0
- package/dist/services/knowledge-portability/organization-portability.preview.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.preview.js +333 -0
- package/dist/services/knowledge-portability/organization-portability.preview.js.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.resolve-source.d.ts +4 -0
- package/dist/services/knowledge-portability/organization-portability.resolve-source.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-portability.resolve-source.js +86 -0
- package/dist/services/knowledge-portability/organization-portability.resolve-source.js.map +1 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts +221 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.js +999 -0
- package/dist/services/knowledge-portability/organization-skills.catalog.js.map +1 -0
- package/dist/services/knowledge-portability/organization-skills.d.ts +4 -75
- package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.js +11 -2008
- package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.scans.d.ts +16 -0
- package/dist/services/knowledge-portability/organization-skills.scans.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-skills.scans.js +300 -0
- package/dist/services/knowledge-portability/organization-skills.scans.js.map +1 -0
- package/dist/services/knowledge-portability/organization-skills.sources.d.ts +68 -0
- package/dist/services/knowledge-portability/organization-skills.sources.d.ts.map +1 -0
- package/dist/services/knowledge-portability/organization-skills.sources.js +728 -0
- package/dist/services/knowledge-portability/organization-skills.sources.js.map +1 -0
- package/dist/services/messenger.d.ts +2 -2
- package/dist/services/messenger.js +2 -2
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/organization-skills.d.ts +3 -1
- package/dist/services/organization-skills.d.ts.map +1 -1
- package/dist/services/organization-skills.js +3 -1
- package/dist/services/organization-skills.js.map +1 -1
- package/dist/services/orgs.d.ts +9 -9
- package/dist/services/plugin-loader.core.d.ts +14 -0
- package/dist/services/plugin-loader.core.d.ts.map +1 -0
- package/dist/services/plugin-loader.core.js +905 -0
- package/dist/services/plugin-loader.core.js.map +1 -0
- package/dist/services/plugin-loader.d.ts +3 -440
- package/dist/services/plugin-loader.d.ts.map +1 -1
- package/dist/services/plugin-loader.helpers.d.ts +468 -0
- package/dist/services/plugin-loader.helpers.d.ts.map +1 -0
- package/dist/services/plugin-loader.helpers.js +263 -0
- package/dist/services/plugin-loader.helpers.js.map +1 -0
- package/dist/services/plugin-loader.js +3 -1191
- package/dist/services/plugin-loader.js.map +1 -1
- package/dist/services/plugin-loader.worker-paths.d.ts +7 -0
- package/dist/services/plugin-loader.worker-paths.d.ts.map +1 -0
- package/dist/services/plugin-loader.worker-paths.js +85 -0
- package/dist/services/plugin-loader.worker-paths.js.map +1 -0
- package/dist/services/plugin-registry.d.ts +123 -123
- package/dist/services/projects.d.ts +8 -8
- package/dist/services/runtime-kernel/heartbeat.core.d.ts +725 -0
- package/dist/services/runtime-kernel/heartbeat.core.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.core.js +525 -0
- package/dist/services/runtime-kernel/heartbeat.core.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.d.ts +38 -259
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.execute.d.ts +5 -0
- package/dist/services/runtime-kernel/heartbeat.execute.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.execute.js +1052 -0
- package/dist/services/runtime-kernel/heartbeat.execute.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.js +50 -4142
- package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.misc.d.ts +30 -0
- package/dist/services/runtime-kernel/heartbeat.misc.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.misc.js +483 -0
- package/dist/services/runtime-kernel/heartbeat.misc.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts +38 -0
- package/dist/services/runtime-kernel/heartbeat.recovery.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.recovery.js +605 -0
- package/dist/services/runtime-kernel/heartbeat.recovery.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.release.d.ts +6 -0
- package/dist/services/runtime-kernel/heartbeat.release.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.release.js +398 -0
- package/dist/services/runtime-kernel/heartbeat.release.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.sessions.d.ts +229 -0
- package/dist/services/runtime-kernel/heartbeat.sessions.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.sessions.js +708 -0
- package/dist/services/runtime-kernel/heartbeat.sessions.js.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts +5 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.d.ts.map +1 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.js +552 -0
- package/dist/services/runtime-kernel/heartbeat.wakeup.js.map +1 -0
- package/dist/services/secrets.d.ts +25 -25
- package/dist/services/sidebar-badges.js +1 -1
- package/dist/services/sidebar-badges.js.map +1 -1
- package/dist/services/workspace-runtime.comments.d.ts +6 -0
- package/dist/services/workspace-runtime.comments.d.ts.map +1 -0
- package/dist/services/workspace-runtime.comments.js +17 -0
- package/dist/services/workspace-runtime.comments.js.map +1 -0
- package/dist/services/workspace-runtime.d.ts +4 -163
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.helpers.d.ts +163 -0
- package/dist/services/workspace-runtime.helpers.d.ts.map +1 -0
- package/dist/services/workspace-runtime.helpers.js +360 -0
- package/dist/services/workspace-runtime.helpers.js.map +1 -0
- package/dist/services/workspace-runtime.js +4 -1236
- package/dist/services/workspace-runtime.js.map +1 -1
- package/dist/services/workspace-runtime.lifecycle.d.ts +35 -0
- package/dist/services/workspace-runtime.lifecycle.d.ts.map +1 -0
- package/dist/services/workspace-runtime.lifecycle.js +266 -0
- package/dist/services/workspace-runtime.lifecycle.js.map +1 -0
- package/dist/services/workspace-runtime.services.d.ts +140 -0
- package/dist/services/workspace-runtime.services.d.ts.map +1 -0
- package/dist/services/workspace-runtime.services.js +606 -0
- package/dist/services/workspace-runtime.services.js.map +1 -0
- package/package.json +21 -15
- package/ui-dist/assets/{_basePickBy-B5mJzzqZ.js → _basePickBy-N8I9ml5Y.js} +1 -1
- package/ui-dist/assets/{_baseUniq-B10Ec09o.js → _baseUniq-BuSlpRSQ.js} +1 -1
- package/ui-dist/assets/{arc-Bw7wimOa.js → arc-qX-dPyA1.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-DZr0XEvv.js → architectureDiagram-2XIMDMQ5-DhjkbXsp.js} +1 -1
- package/ui-dist/assets/{blockDiagram-WCTKOSBZ-D0jl0LgB.js → blockDiagram-WCTKOSBZ-JS-tTu3J.js} +1 -1
- package/ui-dist/assets/{c4Diagram-IC4MRINW-BEFxBnEm.js → c4Diagram-IC4MRINW-4DqwCWIx.js} +1 -1
- package/ui-dist/assets/channel-CccCW5_a.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-Cbul1GoA.js → chunk-4BX2VUAB-T37SqBpp.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-DuouC3bT.js → chunk-55IACEB6-BSj9hdqK.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-bN1jF9xw.js → chunk-FMBD7UC4-Dkrlh0Wk.js} +1 -1
- package/ui-dist/assets/{chunk-JSJVCQXG-B0-Ij6ZF.js → chunk-JSJVCQXG-C0ZE3QdB.js} +1 -1
- package/ui-dist/assets/{chunk-KX2RTZJC-BjI3IEjI.js → chunk-KX2RTZJC-DOZQM9gW.js} +1 -1
- package/ui-dist/assets/{chunk-NQ4KR5QH-MUoGr46n.js → chunk-NQ4KR5QH-5Yr3U2k8.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-CQoI9Ouy.js → chunk-QZHKN3VN-CvKTufwF.js} +1 -1
- package/ui-dist/assets/{chunk-WL4C6EOR-DSJh3iDp.js → chunk-WL4C6EOR-IoEM0jyx.js} +1 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-JKk4tCW2.js +1 -0
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-JKk4tCW2.js +1 -0
- package/ui-dist/assets/clone-Onaweg8D.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-BPepglgB.js → cose-bilkent-S5V4N54A-CTvr1OFj.js} +1 -1
- package/ui-dist/assets/{dagre-KLK3FWXG-DhnHVZkt.js → dagre-KLK3FWXG-UZ-SNjVK.js} +1 -1
- package/ui-dist/assets/{diagram-E7M64L7V-DNvXtoOO.js → diagram-E7M64L7V-D7RAN0Hr.js} +1 -1
- package/ui-dist/assets/{diagram-IFDJBPK2-DhGlDTgn.js → diagram-IFDJBPK2-B4LViaFR.js} +1 -1
- package/ui-dist/assets/{diagram-P4PSJMXO-BmXEloWS.js → diagram-P4PSJMXO-CY1be7ak.js} +1 -1
- package/ui-dist/assets/{erDiagram-INFDFZHY-BTYVzaLM.js → erDiagram-INFDFZHY-Dca0KkvJ.js} +1 -1
- package/ui-dist/assets/{flowDiagram-PKNHOUZH-CqMNQUVv.js → flowDiagram-PKNHOUZH-i-qMvfwg.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-A5KZAMGK-B2le_64a.js → ganttDiagram-A5KZAMGK-Wxq2lhbh.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-BtxOBq5A.js → gitGraphDiagram-K3NZZRJ6-DwzgPlAY.js} +1 -1
- package/ui-dist/assets/{graph-C5E6qFfm.js → graph-BAqf89Tz.js} +1 -1
- package/ui-dist/assets/{index-Piq-IPXt.js → index-4eCzaLuY.js} +1 -1
- package/ui-dist/assets/{index-DT6UN2ec.js → index-8uu-nKqK.js} +1 -1
- package/ui-dist/assets/{index-T5NVZ3nR.js → index-B-1NEcI_.js} +1 -1
- package/ui-dist/assets/{index-D-MoarxG.js → index-B0b_3Eu5.js} +1 -1
- package/ui-dist/assets/{index-CZiP3FBQ.js → index-B8v0eZjP.js} +1 -1
- package/ui-dist/assets/{index-C1Ga66FM.js → index-BN7Moj3u.js} +1 -1
- package/ui-dist/assets/{index-xBUfBdQn.js → index-BSpxh3cY.js} +1 -1
- package/ui-dist/assets/{index-CQcMWp51.js → index-BY44RIi9.js} +1 -1
- package/ui-dist/assets/{index-3a93sZNI.js → index-BhyQJhdZ.js} +1 -1
- package/ui-dist/assets/{index-BsVDit5y.js → index-BkPL_iGU.js} +1 -1
- package/ui-dist/assets/{index-88lBSTsW.js → index-BsPfoHXS.js} +1 -1
- package/ui-dist/assets/{index-CyJtcUF0.js → index-BstW7nmv.js} +1 -1
- package/ui-dist/assets/{index-BvZ0Ptfl.js → index-BwB67Zyz.js} +1 -1
- package/ui-dist/assets/index-C2peSkmT.css +1 -0
- package/ui-dist/assets/{index-vkCrQLeX.js → index-C3ktOsS_.js} +1 -1
- package/ui-dist/assets/{index-D2hZpQJT.js → index-CMyABlS-.js} +1 -1
- package/ui-dist/assets/{index-C4WCPEY4.js → index-CyBJ8ujC.js} +1 -1
- package/ui-dist/assets/{index-Bf7NB_lK.js → index-DAxM2W3O.js} +1 -1
- package/ui-dist/assets/{index-Dq7H6-Lm.js → index-DVZXPmhk.js} +1 -1
- package/ui-dist/assets/{index-CskDu6A3.js → index-Dc19uAyw.js} +1 -1
- package/ui-dist/assets/index-DzHrwZu1.js +1511 -0
- package/ui-dist/assets/{index-B20JneLK.js → index-LJuf53Ye.js} +1 -1
- package/ui-dist/assets/{index-D6McTDMQ.js → index-Ugw5VWWz.js} +1 -1
- package/ui-dist/assets/{index-CcVGS6HJ.js → index-YGraEFR7.js} +1 -1
- package/ui-dist/assets/{infoDiagram-LFFYTUFH-BiCCZcIW.js → infoDiagram-LFFYTUFH-jLmDtFVR.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-PHBUUO56-BiwBemM5.js → ishikawaDiagram-PHBUUO56-6OGMyLT8.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-4ABVD52K-D8RGr2xl.js → journeyDiagram-4ABVD52K-yQjl6E0t.js} +1 -1
- package/ui-dist/assets/{kanban-definition-K7BYSVSG-C733Fj-E.js → kanban-definition-K7BYSVSG-DkdCeQlS.js} +1 -1
- package/ui-dist/assets/{layout-CM4c3NA_.js → layout-CqSYvZ_w.js} +1 -1
- package/ui-dist/assets/{linear-DzH21Xsf.js → linear-B8xGZaoi.js} +1 -1
- package/ui-dist/assets/{mermaid.core-Z2rpoVP2.js → mermaid.core-AKL_cdyk.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-YRQLILUH-DylLLj9w.js → mindmap-definition-YRQLILUH-Zr-dXC0x.js} +1 -1
- package/ui-dist/assets/{pieDiagram-SKSYHLDU-617wI_rr.js → pieDiagram-SKSYHLDU-BvDAU-Nk.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-337W2JSQ-lxoCPJIL.js → quadrantDiagram-337W2JSQ-Dn9kM62o.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-C5XydQ9-.js → requirementDiagram-Z7DCOOCP-GIsIh7Sd.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK--grmq-Q8.js → sankeyDiagram-WA2Y5GQK-CUCuBkuf.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-BS2PeYH-.js → sequenceDiagram-2WXFIKYE-MDpUY2HM.js} +1 -1
- package/ui-dist/assets/{stateDiagram-RAJIS63D-CeuZtj2z.js → stateDiagram-RAJIS63D-BymMpuUU.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-Bi2oCU6d.js +1 -0
- package/ui-dist/assets/{timeline-definition-YZTLITO2-DxHdMpRr.js → timeline-definition-YZTLITO2-B6ofPhhy.js} +1 -1
- package/ui-dist/assets/{treemap-KZPCXAKY-Bv1ZlC5h.js → treemap-KZPCXAKY-DnLO6w1l.js} +1 -1
- package/ui-dist/assets/{vennDiagram-LZ73GAT5-DvpZSXY2.js → vennDiagram-LZ73GAT5-D0MyZIDl.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-JWTSCODW-DttOu1GC.js → xychartDiagram-JWTSCODW-rADY1iUG.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-DGUh6rEi.js +0 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-1ntk2IOV.js +0 -1
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-1ntk2IOV.js +0 -1
- package/ui-dist/assets/clone-BpddY88c.js +0 -1
- package/ui-dist/assets/index-C8AD6s7S.js +0 -1510
- package/ui-dist/assets/index-Ded0dPwB.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DXq0yC5C.js +0 -1
|
@@ -1,1254 +1,36 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { createExecutionScores, observeExecutionEvent, updateExecutionObservation, updateExecutionTraceIO, updateExecutionTraceName, updateExecutionTraceSession, withExecutionObservation, } from "../../langfuse.js";
|
|
7
|
-
import { emitExecutionTranscriptTree } from "../../langfuse-transcript.js";
|
|
1
|
+
import { and, asc, desc, eq, gt, sql } from "drizzle-orm";
|
|
2
|
+
import { summarizeTokenUsage, } from "@rudderhq/shared";
|
|
3
|
+
import { agents, agentRuntimeState, agentTaskSessions, agentWakeupRequests, heartbeatRunEvents, heartbeatRuns, organizations, } from "@rudderhq/db";
|
|
4
|
+
import { notFound } from "../../errors.js";
|
|
5
|
+
import { createExecutionScores, observeExecutionEvent, } from "../../langfuse.js";
|
|
8
6
|
import { logger } from "../../middleware/logger.js";
|
|
9
7
|
import { publishLiveEvent } from "../live-events.js";
|
|
10
8
|
import { getRunLogStore } from "../run-log-store.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { parseObject, asBoolean, asNumber, appendWithCap, MAX_EXCERPT_BYTES } from "../../agent-runtimes/utils.js";
|
|
9
|
+
import { runningProcesses } from "../../agent-runtimes/index.js";
|
|
10
|
+
import { parseObject } from "../../agent-runtimes/utils.js";
|
|
14
11
|
import { costService } from "../costs.js";
|
|
15
12
|
import { budgetService } from "../budgets.js";
|
|
16
13
|
import { agentRunContextService, } from "../agent-run-context.js";
|
|
17
|
-
import { resolveDefaultAgentWorkspaceDir, } from "../../home-paths.js";
|
|
18
14
|
import { summarizeHeartbeatRunResultJson } from "../heartbeat-run-summary.js";
|
|
19
|
-
import { summarizeRuntimeSkillsForTrace } from "../runtime-trace-metadata.js";
|
|
20
|
-
import { buildWorkspaceReadyComment, cleanupExecutionWorkspaceArtifacts, ensureRuntimeServicesForRun, persistAdapterManagedRuntimeServices, realizeExecutionWorkspace, releaseRuntimeServicesForRun, } from "../workspace-runtime.js";
|
|
21
15
|
import { issueService } from "../issues.js";
|
|
22
16
|
import { documentService } from "../documents.js";
|
|
23
|
-
import { buildIssueConvergenceReviewWakeupOptions, buildIssueReviewCloseoutWakeupOptions, } from "../issue-review-wakeup.js";
|
|
24
17
|
import { executionWorkspaceService } from "../execution-workspaces.js";
|
|
25
18
|
import { buildObservedRunLangfuseScores } from "../run-intelligence.js";
|
|
26
19
|
import { workspaceOperationService } from "../workspace-operations.js";
|
|
27
|
-
import { isManagedWorkspaceConfigurationError, isWorkspacePermissionPreflightError, preflightManagedAgentWorkspace, } from "../managed-workspace-preflight.js";
|
|
28
|
-
import { buildExecutionWorkspaceAdapterConfig, issueExecutionWorkspaceModeForPersistedWorkspace, parseIssueExecutionWorkspaceSettings, parseProjectExecutionWorkspacePolicy, resolveExecutionWorkspaceMode, } from "../execution-workspace-policy.js";
|
|
29
20
|
import { instanceSettingsService } from "../instance-settings.js";
|
|
30
|
-
import { logActivity } from "../activity-log.js";
|
|
31
21
|
import { redactCurrentUserText, redactCurrentUserValue } from "../../log-redaction.js";
|
|
32
|
-
import { hasSessionCompactionThresholds,
|
|
33
|
-
import { buildIssueDocumentsPrompt } from "@rudderhq/agent-runtime-utils/server-utils";
|
|
22
|
+
import { hasSessionCompactionThresholds, } from "@rudderhq/agent-runtime-utils";
|
|
34
23
|
import { buildCreateAgentBenchmarkTags, coerceCreateAgentBenchmarkMetadata, extractCreateAgentBenchmarkMetadata, } from "@rudderhq/run-intelligence-core";
|
|
35
|
-
import { executeAdapterWithModelFallbacks } from "./model-fallback.js";
|
|
36
24
|
export { prioritizeProjectWorkspaceCandidatesForRun } from "../agent-run-context.js";
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const HEARTBEAT_MAX_CONCURRENT_RUNS_MIN =
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const startLocksByAgent = new Map();
|
|
47
|
-
const MAX_RECOVERY_CHAIN_DEPTH = 8;
|
|
48
|
-
const ISSUE_PASSIVE_FOLLOWUP_REASON = "issue_passive_followup";
|
|
49
|
-
const ISSUE_PASSIVE_FOLLOWUP_WAKE_SOURCE = "passive_issue_followup";
|
|
50
|
-
const ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON = "missing_closure";
|
|
51
|
-
const ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS = 2;
|
|
52
|
-
const ISSUE_REVIEW_CLOSEOUT_REASON = "issue_review_closeout_missing";
|
|
53
|
-
const ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON = "missing_review_decision";
|
|
54
|
-
const ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS = 2;
|
|
55
|
-
const ISSUE_PASSIVE_FOLLOWUP_COOLDOWN_MS_BY_ATTEMPT = new Map([
|
|
56
|
-
[1, 2 * 60 * 1000],
|
|
57
|
-
[2, 5 * 60 * 1000],
|
|
58
|
-
]);
|
|
59
|
-
const ISSUE_PASSIVE_FOLLOWUP_TIMER_CONTINUITY_MAX_WINDOW_MS = 15 * 60 * 1000;
|
|
60
|
-
const SESSIONED_LOCAL_ADAPTERS = new Set([
|
|
61
|
-
"claude_local",
|
|
62
|
-
"codex_local",
|
|
63
|
-
"cursor",
|
|
64
|
-
"gemini_local",
|
|
65
|
-
"opencode_local",
|
|
66
|
-
"pi_local",
|
|
67
|
-
]);
|
|
68
|
-
const heartbeatRunListColumns = {
|
|
69
|
-
id: heartbeatRuns.id,
|
|
70
|
-
orgId: heartbeatRuns.orgId,
|
|
71
|
-
agentId: heartbeatRuns.agentId,
|
|
72
|
-
invocationSource: heartbeatRuns.invocationSource,
|
|
73
|
-
triggerDetail: heartbeatRuns.triggerDetail,
|
|
74
|
-
status: heartbeatRuns.status,
|
|
75
|
-
startedAt: heartbeatRuns.startedAt,
|
|
76
|
-
finishedAt: heartbeatRuns.finishedAt,
|
|
77
|
-
error: heartbeatRuns.error,
|
|
78
|
-
wakeupRequestId: heartbeatRuns.wakeupRequestId,
|
|
79
|
-
exitCode: heartbeatRuns.exitCode,
|
|
80
|
-
signal: heartbeatRuns.signal,
|
|
81
|
-
usageJson: heartbeatRuns.usageJson,
|
|
82
|
-
resultJson: heartbeatRuns.resultJson,
|
|
83
|
-
sessionIdBefore: heartbeatRuns.sessionIdBefore,
|
|
84
|
-
sessionIdAfter: heartbeatRuns.sessionIdAfter,
|
|
85
|
-
logStore: heartbeatRuns.logStore,
|
|
86
|
-
logRef: heartbeatRuns.logRef,
|
|
87
|
-
logBytes: heartbeatRuns.logBytes,
|
|
88
|
-
logSha256: heartbeatRuns.logSha256,
|
|
89
|
-
logCompressed: heartbeatRuns.logCompressed,
|
|
90
|
-
stdoutExcerpt: sql `NULL`.as("stdoutExcerpt"),
|
|
91
|
-
stderrExcerpt: sql `NULL`.as("stderrExcerpt"),
|
|
92
|
-
errorCode: heartbeatRuns.errorCode,
|
|
93
|
-
externalRunId: heartbeatRuns.externalRunId,
|
|
94
|
-
processPid: heartbeatRuns.processPid,
|
|
95
|
-
processStartedAt: heartbeatRuns.processStartedAt,
|
|
96
|
-
retryOfRunId: heartbeatRuns.retryOfRunId,
|
|
97
|
-
processLossRetryCount: heartbeatRuns.processLossRetryCount,
|
|
98
|
-
contextSnapshot: heartbeatRuns.contextSnapshot,
|
|
99
|
-
createdAt: heartbeatRuns.createdAt,
|
|
100
|
-
updatedAt: heartbeatRuns.updatedAt,
|
|
101
|
-
};
|
|
102
|
-
function appendExcerpt(prev, chunk) {
|
|
103
|
-
return appendWithCap(prev, chunk, MAX_EXCERPT_BYTES);
|
|
104
|
-
}
|
|
105
|
-
function appendTranscriptEntriesFromChunk(input) {
|
|
106
|
-
const combined = `${input.buffer}${input.chunk}`;
|
|
107
|
-
const lines = combined.split(/\r?\n/);
|
|
108
|
-
const trailing = lines.pop() ?? "";
|
|
109
|
-
const completeLines = input.finalize && trailing ? [...lines, trailing] : lines;
|
|
110
|
-
for (const line of completeLines) {
|
|
111
|
-
if (!line.trim())
|
|
112
|
-
continue;
|
|
113
|
-
const ts = new Date().toISOString();
|
|
114
|
-
const parsed = input.parser ? input.parser(line, ts) : [];
|
|
115
|
-
if (parsed.length > 0) {
|
|
116
|
-
input.transcript.push(...parsed);
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
input.transcript.push({
|
|
120
|
-
kind: input.kind,
|
|
121
|
-
ts,
|
|
122
|
-
text: line,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return input.finalize ? "" : trailing;
|
|
126
|
-
}
|
|
127
|
-
function normalizeMaxConcurrentRuns(value) {
|
|
128
|
-
const parsed = Math.floor(asNumber(value, HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT));
|
|
129
|
-
if (!Number.isFinite(parsed))
|
|
130
|
-
return HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT;
|
|
131
|
-
return Math.max(HEARTBEAT_MAX_CONCURRENT_RUNS_MIN, Math.min(HEARTBEAT_MAX_CONCURRENT_RUNS_MAX, parsed));
|
|
132
|
-
}
|
|
133
|
-
async function withAgentStartLock(agentId, fn) {
|
|
134
|
-
const previous = startLocksByAgent.get(agentId) ?? Promise.resolve();
|
|
135
|
-
const run = previous.then(fn);
|
|
136
|
-
const marker = run.then(() => undefined, () => undefined);
|
|
137
|
-
startLocksByAgent.set(agentId, marker);
|
|
138
|
-
try {
|
|
139
|
-
return await run;
|
|
140
|
-
}
|
|
141
|
-
finally {
|
|
142
|
-
if (startLocksByAgent.get(agentId) === marker) {
|
|
143
|
-
startLocksByAgent.delete(agentId);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
function readNonEmptyString(value) {
|
|
148
|
-
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
149
|
-
}
|
|
150
|
-
export function resolveHeartbeatObservabilitySurface(contextSnapshot) {
|
|
151
|
-
return readNonEmptyString(contextSnapshot?.issueId) ? "issue_run" : "heartbeat_run";
|
|
152
|
-
}
|
|
153
|
-
function buildHeartbeatObservationName(run, agentName) {
|
|
154
|
-
const contextSnapshot = parseObject(run.contextSnapshot);
|
|
155
|
-
const issueId = readNonEmptyString(contextSnapshot.issueId);
|
|
156
|
-
return issueId ? `issue_run:${issueId}` : `heartbeat:${agentName}`;
|
|
157
|
-
}
|
|
158
|
-
function compactTraceText(value, maxLength = 120) {
|
|
159
|
-
const next = value?.replace(/\s+/g, " ").trim();
|
|
160
|
-
if (!next)
|
|
161
|
-
return null;
|
|
162
|
-
return next.length > maxLength ? `${next.slice(0, maxLength - 1)}…` : next;
|
|
163
|
-
}
|
|
164
|
-
export function buildIssueRunTraceName(input) {
|
|
165
|
-
const issueTitle = compactTraceText(input.issueTitle);
|
|
166
|
-
return issueTitle ? `issue_run:${issueTitle} [${input.issueId}]` : `issue_run:[${input.issueId}]`;
|
|
167
|
-
}
|
|
168
|
-
export function buildHeartbeatRuntimeTraceMetadata(input) {
|
|
169
|
-
const instructionsFilePath = readNonEmptyString(input.runtimeConfig.instructionsFilePath);
|
|
170
|
-
return {
|
|
171
|
-
instructionsConfigured: Boolean(instructionsFilePath),
|
|
172
|
-
instructionsFilePath,
|
|
173
|
-
...summarizeRuntimeSkillsForTrace(input.runtimeSkills),
|
|
174
|
-
...(input.adapterMeta
|
|
175
|
-
? {
|
|
176
|
-
runtimeAgentType: input.adapterMeta.agentRuntimeType,
|
|
177
|
-
runtimeCommand: input.adapterMeta.command,
|
|
178
|
-
runtimeCwd: input.adapterMeta.cwd ?? null,
|
|
179
|
-
runtimeCommandNotes: input.adapterMeta.commandNotes ?? [],
|
|
180
|
-
runtimePromptMetrics: input.adapterMeta.promptMetrics ?? null,
|
|
181
|
-
}
|
|
182
|
-
: {}),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
export function buildHeartbeatAdapterInvokePayload(input) {
|
|
186
|
-
const explicitUsedSkills = Array.isArray(input.meta.usedSkills)
|
|
187
|
-
? input.meta.usedSkills
|
|
188
|
-
.map((entry) => normalizeLoadedSkill(entry))
|
|
189
|
-
.filter((entry) => Boolean(entry))
|
|
190
|
-
: [];
|
|
191
|
-
const promptRequestedSkills = inferUsedSkillsFromPrompt(input.meta.prompt, input.runtimeSkills);
|
|
192
|
-
const loadedSkills = Array.isArray(input.meta.loadedSkills) && input.meta.loadedSkills.length > 0
|
|
193
|
-
? input.meta.loadedSkills
|
|
194
|
-
.map((entry) => normalizeLoadedSkillForPayload(entry))
|
|
195
|
-
.filter((entry) => Boolean(entry))
|
|
196
|
-
: input.runtimeSkills
|
|
197
|
-
.map((entry) => ({
|
|
198
|
-
key: entry.key,
|
|
199
|
-
runtimeName: entry.runtimeName ?? null,
|
|
200
|
-
name: entry.name ?? null,
|
|
201
|
-
description: entry.description ?? null,
|
|
202
|
-
}));
|
|
203
|
-
const loadedSkillEvidence = loadedSkills
|
|
204
|
-
.map((entry) => normalizeLoadedSkill(entry))
|
|
205
|
-
.filter((entry) => Boolean(entry));
|
|
206
|
-
const skillEvidence = resolveSkillEvidence({
|
|
207
|
-
usedSkills: explicitUsedSkills,
|
|
208
|
-
requestedSkills: promptRequestedSkills,
|
|
209
|
-
loadedSkills: loadedSkillEvidence,
|
|
210
|
-
});
|
|
211
|
-
return {
|
|
212
|
-
...input.meta,
|
|
213
|
-
...summarizeRuntimeSkillsForTrace(input.runtimeSkills),
|
|
214
|
-
loadedSkillCount: loadedSkills.length,
|
|
215
|
-
loadedSkillKeys: loadedSkills.map((entry) => entry.key),
|
|
216
|
-
loadedSkills,
|
|
217
|
-
usedSkillCount: explicitUsedSkills.length,
|
|
218
|
-
usedSkillKeys: explicitUsedSkills.map((entry) => entry.key),
|
|
219
|
-
usedSkills: explicitUsedSkills,
|
|
220
|
-
promptRequestedSkillCount: promptRequestedSkills.length,
|
|
221
|
-
promptRequestedSkillKeys: promptRequestedSkills.map((entry) => entry.key),
|
|
222
|
-
promptRequestedSkills,
|
|
223
|
-
skillEvidenceType: skillEvidence.evidence,
|
|
224
|
-
skillEvidenceCount: skillEvidence.skills.length,
|
|
225
|
-
skillEvidenceKeys: skillEvidence.skills.map((entry) => entry.key),
|
|
226
|
-
skillEvidenceSkills: skillEvidence.skills,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
function buildRecentDateKeys(windowDays, now) {
|
|
230
|
-
return Array.from({ length: windowDays }, (_, index) => {
|
|
231
|
-
const next = new Date(now);
|
|
232
|
-
next.setUTCDate(next.getUTCDate() - (windowDays - 1 - index));
|
|
233
|
-
return next.toISOString().slice(0, 10);
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
function buildDateKeysBetween(startDate, endDate) {
|
|
237
|
-
const start = new Date(`${startDate}T00:00:00.000Z`);
|
|
238
|
-
const end = new Date(`${endDate}T00:00:00.000Z`);
|
|
239
|
-
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime()) || start.getTime() > end.getTime()) {
|
|
240
|
-
return [];
|
|
241
|
-
}
|
|
242
|
-
const days = [];
|
|
243
|
-
const cursor = new Date(start);
|
|
244
|
-
while (cursor.getTime() <= end.getTime()) {
|
|
245
|
-
days.push(cursor.toISOString().slice(0, 10));
|
|
246
|
-
cursor.setUTCDate(cursor.getUTCDate() + 1);
|
|
247
|
-
}
|
|
248
|
-
return days;
|
|
249
|
-
}
|
|
250
|
-
function fallbackSkillLabel(key) {
|
|
251
|
-
const trimmed = key.trim();
|
|
252
|
-
if (!trimmed)
|
|
253
|
-
return "unknown";
|
|
254
|
-
const slashSegments = trimmed.split("/").filter(Boolean);
|
|
255
|
-
const lastSlashSegment = slashSegments.at(-1);
|
|
256
|
-
if (lastSlashSegment)
|
|
257
|
-
return lastSlashSegment;
|
|
258
|
-
const colonSegments = trimmed.split(":").filter(Boolean);
|
|
259
|
-
return colonSegments.at(-1) ?? trimmed;
|
|
260
|
-
}
|
|
261
|
-
function normalizeLoadedSkill(value) {
|
|
262
|
-
const skill = parseObject(value);
|
|
263
|
-
const rawKey = readNonEmptyString(skill.key);
|
|
264
|
-
const rawRuntimeName = readNonEmptyString(skill.runtimeName);
|
|
265
|
-
const rawName = readNonEmptyString(skill.name);
|
|
266
|
-
const key = rawKey ?? rawRuntimeName ?? rawName;
|
|
267
|
-
if (!key)
|
|
268
|
-
return null;
|
|
269
|
-
const label = rawRuntimeName ?? rawName ?? fallbackSkillLabel(key);
|
|
270
|
-
return { key, label };
|
|
271
|
-
}
|
|
272
|
-
function normalizeLoadedSkillForPayload(value) {
|
|
273
|
-
const skill = parseObject(value);
|
|
274
|
-
const rawKey = readNonEmptyString(skill.key);
|
|
275
|
-
const rawRuntimeName = readNonEmptyString(skill.runtimeName);
|
|
276
|
-
const rawName = readNonEmptyString(skill.name);
|
|
277
|
-
const key = rawKey ?? rawRuntimeName ?? rawName;
|
|
278
|
-
if (!key)
|
|
279
|
-
return null;
|
|
280
|
-
return {
|
|
281
|
-
key,
|
|
282
|
-
runtimeName: rawRuntimeName ?? null,
|
|
283
|
-
name: rawName ?? null,
|
|
284
|
-
description: readNonEmptyString(skill.description) ?? null,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
function emptySkillEvidenceCounts() {
|
|
288
|
-
return { used: 0, requested: 0, loaded: 0 };
|
|
289
|
-
}
|
|
290
|
-
function incrementSkillEvidenceCount(counts, evidence) {
|
|
291
|
-
counts[evidence] += 1;
|
|
292
|
-
}
|
|
293
|
-
function strongestSkillEvidence(left, right) {
|
|
294
|
-
const rank = {
|
|
295
|
-
used: 3,
|
|
296
|
-
requested: 2,
|
|
297
|
-
loaded: 1,
|
|
298
|
-
};
|
|
299
|
-
return rank[right] > rank[left] ? right : left;
|
|
300
|
-
}
|
|
301
|
-
function resolveSkillEvidence(input) {
|
|
302
|
-
if (input.usedSkills.length > 0)
|
|
303
|
-
return { evidence: "used", skills: input.usedSkills };
|
|
304
|
-
if (input.requestedSkills.length > 0)
|
|
305
|
-
return { evidence: "requested", skills: input.requestedSkills };
|
|
306
|
-
return { evidence: "loaded", skills: [] };
|
|
307
|
-
}
|
|
308
|
-
function readSkillEvidenceFromPayload(payload) {
|
|
309
|
-
const loadedSkills = Array.isArray(payload.loadedSkills)
|
|
310
|
-
? payload.loadedSkills
|
|
311
|
-
.map((entry) => normalizeLoadedSkill(entry))
|
|
312
|
-
.filter((entry) => Boolean(entry))
|
|
313
|
-
: [];
|
|
314
|
-
const usedSkills = Array.isArray(payload.usedSkills)
|
|
315
|
-
? payload.usedSkills
|
|
316
|
-
.map((entry) => normalizeLoadedSkill(entry))
|
|
317
|
-
.filter((entry) => Boolean(entry))
|
|
318
|
-
: [];
|
|
319
|
-
const requestedSkills = Array.isArray(payload.promptRequestedSkills)
|
|
320
|
-
? payload.promptRequestedSkills
|
|
321
|
-
.map((entry) => normalizeLoadedSkill(entry))
|
|
322
|
-
.filter((entry) => Boolean(entry))
|
|
323
|
-
: inferUsedSkillsFromPrompt(payload.prompt, loadedSkills);
|
|
324
|
-
return resolveSkillEvidence({ usedSkills, requestedSkills, loadedSkills });
|
|
325
|
-
}
|
|
326
|
-
function extractSkillSlugFromPath(value) {
|
|
327
|
-
const normalized = value.trim().replace(/\\/g, "/").replace(/[?#].*$/u, "").replace(/\/+$/u, "");
|
|
328
|
-
if (!normalized.endsWith("/SKILL.md"))
|
|
329
|
-
return null;
|
|
330
|
-
const parts = normalized.split("/").filter(Boolean);
|
|
331
|
-
if (parts.length < 2)
|
|
332
|
-
return null;
|
|
333
|
-
const slug = parts.at(-2);
|
|
334
|
-
if (!slug || slug === "." || slug === "..")
|
|
335
|
-
return null;
|
|
336
|
-
return slug;
|
|
337
|
-
}
|
|
338
|
-
function collectSkillPathsFromText(value) {
|
|
339
|
-
const paths = [];
|
|
340
|
-
const pattern = /(?:^|[\s"'`(=])((?:\.{1,2}\/|~\/|\/)?(?:[^\s"'`()<>|;&/]+\/)+SKILL\.md(?:[?#][^\s"'`()<>|;&]*)?)(?=$|[\s"'`()<>|;&])/giu;
|
|
341
|
-
for (const match of value.matchAll(pattern)) {
|
|
342
|
-
const pathValue = match[1]?.trim();
|
|
343
|
-
if (pathValue)
|
|
344
|
-
paths.push(pathValue);
|
|
345
|
-
}
|
|
346
|
-
return paths;
|
|
347
|
-
}
|
|
348
|
-
function collectStringValues(value, depth = 0) {
|
|
349
|
-
if (depth > 4)
|
|
350
|
-
return [];
|
|
351
|
-
if (typeof value === "string")
|
|
352
|
-
return [value];
|
|
353
|
-
if (Array.isArray(value))
|
|
354
|
-
return value.flatMap((entry) => collectStringValues(entry, depth + 1));
|
|
355
|
-
const record = parseObject(value);
|
|
356
|
-
return Object.values(record).flatMap((entry) => collectStringValues(entry, depth + 1));
|
|
357
|
-
}
|
|
358
|
-
function normalizeSkillUseFromPath(value) {
|
|
359
|
-
const slug = extractSkillSlugFromPath(value);
|
|
360
|
-
if (!slug)
|
|
361
|
-
return null;
|
|
362
|
-
return { key: slug, label: slug };
|
|
363
|
-
}
|
|
364
|
-
function dedupeSkillUses(skills) {
|
|
365
|
-
const seen = new Set();
|
|
366
|
-
const result = [];
|
|
367
|
-
for (const skill of skills) {
|
|
368
|
-
const normalized = normalizeLoadedSkill(skill);
|
|
369
|
-
if (!normalized || seen.has(normalized.key))
|
|
370
|
-
continue;
|
|
371
|
-
seen.add(normalized.key);
|
|
372
|
-
result.push(normalized);
|
|
373
|
-
}
|
|
374
|
-
return result;
|
|
375
|
-
}
|
|
376
|
-
function collectSkillUsesFromText(value) {
|
|
377
|
-
return collectSkillPathsFromText(value)
|
|
378
|
-
.map((entry) => normalizeSkillUseFromPath(entry))
|
|
379
|
-
.filter((entry) => Boolean(entry));
|
|
380
|
-
}
|
|
381
|
-
function readToolCommandInput(input) {
|
|
382
|
-
if (typeof input === "string")
|
|
383
|
-
return input;
|
|
384
|
-
const record = parseObject(input);
|
|
385
|
-
return readNonEmptyString(record.command) ?? readNonEmptyString(record.cmd);
|
|
386
|
-
}
|
|
387
|
-
function isCommandTranscriptTool(name) {
|
|
388
|
-
const normalized = name.trim().toLowerCase();
|
|
389
|
-
return ["command_execution", "shell", "shelltoolcall", "bash"].includes(normalized);
|
|
390
|
-
}
|
|
391
|
-
function isReadTranscriptTool(name) {
|
|
392
|
-
const normalized = name.trim().toLowerCase();
|
|
393
|
-
if (isCommandTranscriptTool(normalized))
|
|
394
|
-
return true;
|
|
395
|
-
if (/(?:^|[_-])(read|fetch|open|cat)(?:$|[_-])/.test(normalized))
|
|
396
|
-
return true;
|
|
397
|
-
return false;
|
|
398
|
-
}
|
|
399
|
-
function inferUsedSkillsFromTranscript(transcript) {
|
|
400
|
-
const skills = [];
|
|
401
|
-
for (const entry of transcript) {
|
|
402
|
-
if (entry.kind !== "tool_call")
|
|
403
|
-
continue;
|
|
404
|
-
if (!isReadTranscriptTool(entry.name))
|
|
405
|
-
continue;
|
|
406
|
-
const command = readToolCommandInput(entry.input);
|
|
407
|
-
if (command) {
|
|
408
|
-
skills.push(...collectSkillUsesFromText(command));
|
|
409
|
-
continue;
|
|
410
|
-
}
|
|
411
|
-
for (const value of collectStringValues(entry.input)) {
|
|
412
|
-
skills.push(...collectSkillUsesFromText(value));
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return dedupeSkillUses(skills);
|
|
416
|
-
}
|
|
417
|
-
function normalizeSkillCandidate(value) {
|
|
418
|
-
return value
|
|
419
|
-
?.trim()
|
|
420
|
-
.replace(/^\$/u, "")
|
|
421
|
-
.replace(/[?#].*$/u, "")
|
|
422
|
-
.replace(/\/+$/u, "")
|
|
423
|
-
.toLowerCase() || "";
|
|
424
|
-
}
|
|
425
|
-
function addSkillCandidate(candidates, value) {
|
|
426
|
-
const normalized = normalizeSkillCandidate(value);
|
|
427
|
-
if (!normalized)
|
|
428
|
-
return;
|
|
429
|
-
candidates.add(normalized);
|
|
430
|
-
const lastSegment = normalized.split(/[/:]/u).filter(Boolean).at(-1);
|
|
431
|
-
if (lastSegment)
|
|
432
|
-
candidates.add(lastSegment);
|
|
433
|
-
}
|
|
434
|
-
function readSkillReferenceSlug(href) {
|
|
435
|
-
const normalized = href.trim().replace(/[?#].*$/u, "").replace(/\/+$/u, "");
|
|
436
|
-
if (!normalized)
|
|
437
|
-
return null;
|
|
438
|
-
if (normalized.endsWith("/SKILL.md")) {
|
|
439
|
-
return normalized.slice(0, -"/SKILL.md".length).split("/").filter(Boolean).at(-1) ?? null;
|
|
440
|
-
}
|
|
441
|
-
if (normalized.toLowerCase().endsWith(".md")) {
|
|
442
|
-
const fileName = normalized.split("/").filter(Boolean).at(-1) ?? "";
|
|
443
|
-
return fileName.replace(/\.md$/iu, "") || null;
|
|
444
|
-
}
|
|
445
|
-
return null;
|
|
446
|
-
}
|
|
447
|
-
function collectSkillReferences(prompt) {
|
|
448
|
-
const references = [];
|
|
449
|
-
const pattern = /\[([^\]\n]+)\]\(([^)\n]+(?:\/SKILL\.md|\.md)(?:[?#][^)\n]*)?)\)/giu;
|
|
450
|
-
for (const match of prompt.matchAll(pattern)) {
|
|
451
|
-
const rawLabel = match[1]?.trim() ?? "";
|
|
452
|
-
const href = match[2]?.trim() ?? "";
|
|
453
|
-
if (!rawLabel || !href)
|
|
454
|
-
continue;
|
|
455
|
-
const labelWithoutPrefix = rawLabel.replace(/^\$/u, "").trim();
|
|
456
|
-
const slug = readSkillReferenceSlug(href);
|
|
457
|
-
const isExplicitSkillToken = rawLabel.startsWith("$") || href.replace(/[?#].*$/u, "").endsWith("/SKILL.md");
|
|
458
|
-
if (!isExplicitSkillToken)
|
|
459
|
-
continue;
|
|
460
|
-
const key = labelWithoutPrefix || slug;
|
|
461
|
-
if (!key)
|
|
462
|
-
continue;
|
|
463
|
-
const label = slug ?? fallbackSkillLabel(key);
|
|
464
|
-
const candidates = new Set();
|
|
465
|
-
addSkillCandidate(candidates, labelWithoutPrefix);
|
|
466
|
-
addSkillCandidate(candidates, slug);
|
|
467
|
-
addSkillCandidate(candidates, href);
|
|
468
|
-
references.push({ key, label, candidates });
|
|
469
|
-
}
|
|
470
|
-
return references;
|
|
471
|
-
}
|
|
472
|
-
function inferUsedSkillsFromPrompt(prompt, loadedSkills) {
|
|
473
|
-
const promptText = readNonEmptyString(prompt);
|
|
474
|
-
if (!promptText)
|
|
475
|
-
return [];
|
|
476
|
-
const references = collectSkillReferences(promptText);
|
|
477
|
-
if (references.length === 0)
|
|
478
|
-
return [];
|
|
479
|
-
const loaded = loadedSkills
|
|
480
|
-
.map((entry) => normalizeLoadedSkill(entry))
|
|
481
|
-
.filter((entry) => Boolean(entry))
|
|
482
|
-
.map((entry) => {
|
|
483
|
-
const candidates = new Set();
|
|
484
|
-
addSkillCandidate(candidates, entry.key);
|
|
485
|
-
addSkillCandidate(candidates, entry.label);
|
|
486
|
-
return { ...entry, candidates };
|
|
487
|
-
});
|
|
488
|
-
const used = new Map();
|
|
489
|
-
for (const reference of references) {
|
|
490
|
-
const matched = loaded.find((entry) => {
|
|
491
|
-
for (const candidate of reference.candidates) {
|
|
492
|
-
if (entry.candidates.has(candidate))
|
|
493
|
-
return true;
|
|
494
|
-
}
|
|
495
|
-
return false;
|
|
496
|
-
});
|
|
497
|
-
const normalized = matched ?? { key: reference.key, label: reference.label };
|
|
498
|
-
if (!used.has(normalized.key))
|
|
499
|
-
used.set(normalized.key, normalized);
|
|
500
|
-
}
|
|
501
|
-
return Array.from(used.values());
|
|
502
|
-
}
|
|
503
|
-
function normalizeLedgerBillingType(value) {
|
|
504
|
-
const raw = readNonEmptyString(value);
|
|
505
|
-
switch (raw) {
|
|
506
|
-
case "api":
|
|
507
|
-
case "metered_api":
|
|
508
|
-
return "metered_api";
|
|
509
|
-
case "subscription":
|
|
510
|
-
case "subscription_included":
|
|
511
|
-
return "subscription_included";
|
|
512
|
-
case "subscription_overage":
|
|
513
|
-
return "subscription_overage";
|
|
514
|
-
case "credits":
|
|
515
|
-
return "credits";
|
|
516
|
-
case "fixed":
|
|
517
|
-
return "fixed";
|
|
518
|
-
default:
|
|
519
|
-
return "unknown";
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
function resolveLedgerBiller(result) {
|
|
523
|
-
return readNonEmptyString(result.biller) ?? readNonEmptyString(result.provider) ?? "unknown";
|
|
524
|
-
}
|
|
525
|
-
function normalizeBilledCostCents(costUsd, billingType) {
|
|
526
|
-
if (billingType === "subscription_included")
|
|
527
|
-
return 0;
|
|
528
|
-
if (typeof costUsd !== "number" || !Number.isFinite(costUsd))
|
|
529
|
-
return 0;
|
|
530
|
-
return Math.max(0, Math.round(costUsd * 100));
|
|
531
|
-
}
|
|
532
|
-
async function resolveLedgerScopeForRun(db, orgId, run) {
|
|
533
|
-
const context = parseObject(run.contextSnapshot);
|
|
534
|
-
const contextIssueId = readNonEmptyString(context.issueId);
|
|
535
|
-
const contextProjectId = readNonEmptyString(context.projectId);
|
|
536
|
-
if (!contextIssueId) {
|
|
537
|
-
return {
|
|
538
|
-
issueId: null,
|
|
539
|
-
projectId: contextProjectId,
|
|
540
|
-
};
|
|
541
|
-
}
|
|
542
|
-
const issue = await db
|
|
543
|
-
.select({
|
|
544
|
-
id: issues.id,
|
|
545
|
-
projectId: issues.projectId,
|
|
546
|
-
})
|
|
547
|
-
.from(issues)
|
|
548
|
-
.where(and(eq(issues.id, contextIssueId), eq(issues.orgId, orgId)))
|
|
549
|
-
.then((rows) => rows[0] ?? null);
|
|
550
|
-
return {
|
|
551
|
-
issueId: issue?.id ?? null,
|
|
552
|
-
projectId: issue?.projectId ?? contextProjectId,
|
|
553
|
-
};
|
|
554
|
-
}
|
|
555
|
-
export function buildExplicitResumeSessionOverride(input) {
|
|
556
|
-
const desiredDisplayId = truncateDisplayId(input.resumeRunSessionIdAfter ?? input.resumeRunSessionIdBefore);
|
|
557
|
-
const taskSessionParams = normalizeSessionParams(input.sessionCodec.deserialize(input.taskSession?.sessionParamsJson ?? null));
|
|
558
|
-
const taskSessionDisplayId = truncateDisplayId(input.taskSession?.sessionDisplayId ??
|
|
559
|
-
(input.sessionCodec.getDisplayId ? input.sessionCodec.getDisplayId(taskSessionParams) : null) ??
|
|
560
|
-
readNonEmptyString(taskSessionParams?.sessionId));
|
|
561
|
-
const canReuseTaskSessionParams = input.taskSession != null &&
|
|
562
|
-
(input.taskSession.lastRunId === input.resumeFromRunId ||
|
|
563
|
-
(!!desiredDisplayId && taskSessionDisplayId === desiredDisplayId));
|
|
564
|
-
const sessionParams = canReuseTaskSessionParams
|
|
565
|
-
? taskSessionParams
|
|
566
|
-
: desiredDisplayId
|
|
567
|
-
? { sessionId: desiredDisplayId }
|
|
568
|
-
: null;
|
|
569
|
-
const sessionDisplayId = desiredDisplayId ?? (canReuseTaskSessionParams ? taskSessionDisplayId : null);
|
|
570
|
-
if (!sessionDisplayId && !sessionParams)
|
|
571
|
-
return null;
|
|
572
|
-
return {
|
|
573
|
-
sessionDisplayId,
|
|
574
|
-
sessionParams,
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
function normalizeUsageTotals(usage) {
|
|
578
|
-
if (!usage)
|
|
579
|
-
return null;
|
|
580
|
-
return {
|
|
581
|
-
inputTokens: Math.max(0, Math.floor(asNumber(usage.inputTokens, 0))),
|
|
582
|
-
cachedInputTokens: Math.max(0, Math.floor(asNumber(usage.cachedInputTokens, 0))),
|
|
583
|
-
outputTokens: Math.max(0, Math.floor(asNumber(usage.outputTokens, 0))),
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
function readRawUsageTotals(usageJson) {
|
|
587
|
-
const parsed = parseObject(usageJson);
|
|
588
|
-
if (Object.keys(parsed).length === 0)
|
|
589
|
-
return null;
|
|
590
|
-
const inputTokens = Math.max(0, Math.floor(asNumber(parsed.rawInputTokens, asNumber(parsed.inputTokens, 0))));
|
|
591
|
-
const cachedInputTokens = Math.max(0, Math.floor(asNumber(parsed.rawCachedInputTokens, asNumber(parsed.cachedInputTokens, 0))));
|
|
592
|
-
const outputTokens = Math.max(0, Math.floor(asNumber(parsed.rawOutputTokens, asNumber(parsed.outputTokens, 0))));
|
|
593
|
-
if (inputTokens <= 0 && cachedInputTokens <= 0 && outputTokens <= 0) {
|
|
594
|
-
return null;
|
|
595
|
-
}
|
|
596
|
-
return {
|
|
597
|
-
inputTokens,
|
|
598
|
-
cachedInputTokens,
|
|
599
|
-
outputTokens,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
function deriveNormalizedUsageDelta(current, previous) {
|
|
603
|
-
if (!current)
|
|
604
|
-
return null;
|
|
605
|
-
if (!previous)
|
|
606
|
-
return { ...current };
|
|
607
|
-
const inputTokens = current.inputTokens >= previous.inputTokens
|
|
608
|
-
? current.inputTokens - previous.inputTokens
|
|
609
|
-
: current.inputTokens;
|
|
610
|
-
const cachedInputTokens = current.cachedInputTokens >= previous.cachedInputTokens
|
|
611
|
-
? current.cachedInputTokens - previous.cachedInputTokens
|
|
612
|
-
: current.cachedInputTokens;
|
|
613
|
-
const outputTokens = current.outputTokens >= previous.outputTokens
|
|
614
|
-
? current.outputTokens - previous.outputTokens
|
|
615
|
-
: current.outputTokens;
|
|
616
|
-
return {
|
|
617
|
-
inputTokens: Math.max(0, inputTokens),
|
|
618
|
-
cachedInputTokens: Math.max(0, cachedInputTokens),
|
|
619
|
-
outputTokens: Math.max(0, outputTokens),
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
function formatCount(value) {
|
|
623
|
-
if (typeof value !== "number" || !Number.isFinite(value))
|
|
624
|
-
return "0";
|
|
625
|
-
return value.toLocaleString("en-US");
|
|
626
|
-
}
|
|
627
|
-
export function parseSessionCompactionPolicy(agent) {
|
|
628
|
-
return resolveSessionCompactionPolicy(agent.agentRuntimeType, agent.runtimeConfig).policy;
|
|
629
|
-
}
|
|
630
|
-
export function resolveRuntimeSessionParamsForWorkspace(input) {
|
|
631
|
-
const { orgId, agent, previousSessionParams, resolvedWorkspace } = input;
|
|
632
|
-
const previousSessionId = readNonEmptyString(previousSessionParams?.sessionId);
|
|
633
|
-
if (!previousSessionId) {
|
|
634
|
-
return {
|
|
635
|
-
sessionParams: previousSessionParams,
|
|
636
|
-
warning: null,
|
|
637
|
-
};
|
|
638
|
-
}
|
|
639
|
-
const canonicalAgentCwd = readNonEmptyString(resolvedWorkspace.cwd) ?? resolveDefaultAgentWorkspaceDir(orgId, agent);
|
|
640
|
-
if (!canonicalAgentCwd) {
|
|
641
|
-
return {
|
|
642
|
-
sessionParams: previousSessionParams,
|
|
643
|
-
warning: null,
|
|
644
|
-
};
|
|
645
|
-
}
|
|
646
|
-
const previousCwd = readNonEmptyString(previousSessionParams?.cwd);
|
|
647
|
-
if (previousCwd && path.resolve(previousCwd) === path.resolve(canonicalAgentCwd)) {
|
|
648
|
-
return {
|
|
649
|
-
sessionParams: previousSessionParams,
|
|
650
|
-
warning: null,
|
|
651
|
-
};
|
|
652
|
-
}
|
|
653
|
-
const previousWorkspaceId = readNonEmptyString(previousSessionParams?.workspaceId);
|
|
654
|
-
const migratedSessionParams = {
|
|
655
|
-
...(previousSessionParams ?? {}),
|
|
656
|
-
cwd: canonicalAgentCwd,
|
|
657
|
-
};
|
|
658
|
-
if (!previousWorkspaceId ||
|
|
659
|
-
!resolvedWorkspace.workspaceId ||
|
|
660
|
-
previousWorkspaceId === resolvedWorkspace.workspaceId) {
|
|
661
|
-
if (resolvedWorkspace.workspaceId)
|
|
662
|
-
migratedSessionParams.workspaceId = resolvedWorkspace.workspaceId;
|
|
663
|
-
if (resolvedWorkspace.repoUrl)
|
|
664
|
-
migratedSessionParams.repoUrl = resolvedWorkspace.repoUrl;
|
|
665
|
-
if (resolvedWorkspace.repoRef)
|
|
666
|
-
migratedSessionParams.repoRef = resolvedWorkspace.repoRef;
|
|
667
|
-
}
|
|
668
|
-
return {
|
|
669
|
-
sessionParams: migratedSessionParams,
|
|
670
|
-
warning: previousCwd
|
|
671
|
-
? `Agent workspace "${canonicalAgentCwd}" is now the canonical run workspace. ` +
|
|
672
|
-
`Attempting to resume session "${previousSessionId}" that was previously saved in "${previousCwd}".`
|
|
673
|
-
: `Agent workspace "${canonicalAgentCwd}" is now the canonical run workspace. ` +
|
|
674
|
-
`Attempting to resume session "${previousSessionId}" with the canonical agent workspace attached.`,
|
|
675
|
-
};
|
|
676
|
-
}
|
|
677
|
-
function parseIssueAssigneeAgentRuntimeOverrides(raw) {
|
|
678
|
-
const parsed = parseObject(raw);
|
|
679
|
-
const parsedAdapterConfig = parseObject(parsed.agentRuntimeConfig);
|
|
680
|
-
const agentRuntimeConfig = Object.keys(parsedAdapterConfig).length > 0 ? parsedAdapterConfig : null;
|
|
681
|
-
const useProjectWorkspace = typeof parsed.useProjectWorkspace === "boolean"
|
|
682
|
-
? parsed.useProjectWorkspace
|
|
683
|
-
: null;
|
|
684
|
-
if (!agentRuntimeConfig && useProjectWorkspace === null)
|
|
685
|
-
return null;
|
|
686
|
-
return {
|
|
687
|
-
agentRuntimeConfig,
|
|
688
|
-
useProjectWorkspace,
|
|
689
|
-
};
|
|
690
|
-
}
|
|
691
|
-
function deriveTaskKey(contextSnapshot, payload) {
|
|
692
|
-
return (readNonEmptyString(contextSnapshot?.taskKey) ??
|
|
693
|
-
readNonEmptyString(contextSnapshot?.taskId) ??
|
|
694
|
-
readNonEmptyString(contextSnapshot?.issueId) ??
|
|
695
|
-
readNonEmptyString(payload?.taskKey) ??
|
|
696
|
-
readNonEmptyString(payload?.taskId) ??
|
|
697
|
-
readNonEmptyString(payload?.issueId) ??
|
|
698
|
-
null);
|
|
699
|
-
}
|
|
700
|
-
export function shouldResetTaskSessionForWake(contextSnapshot) {
|
|
701
|
-
if (contextSnapshot?.forceFreshSession === true)
|
|
702
|
-
return true;
|
|
703
|
-
const wakeReason = readNonEmptyString(contextSnapshot?.wakeReason);
|
|
704
|
-
if (wakeReason === "issue_assigned")
|
|
705
|
-
return true;
|
|
706
|
-
return false;
|
|
707
|
-
}
|
|
708
|
-
export function formatRuntimeWorkspaceWarningLog(warning) {
|
|
709
|
-
return {
|
|
710
|
-
stream: "stdout",
|
|
711
|
-
chunk: `[rudder] ${warning}\n`,
|
|
712
|
-
};
|
|
713
|
-
}
|
|
714
|
-
function describeSessionResetReason(contextSnapshot) {
|
|
715
|
-
if (contextSnapshot?.forceFreshSession === true)
|
|
716
|
-
return "forceFreshSession was requested";
|
|
717
|
-
const wakeReason = readNonEmptyString(contextSnapshot?.wakeReason);
|
|
718
|
-
if (wakeReason === "issue_assigned")
|
|
719
|
-
return "wake reason is issue_assigned";
|
|
720
|
-
return null;
|
|
721
|
-
}
|
|
722
|
-
function deriveCommentId(contextSnapshot, payload) {
|
|
723
|
-
return (readNonEmptyString(contextSnapshot?.wakeCommentId) ??
|
|
724
|
-
readNonEmptyString(contextSnapshot?.commentId) ??
|
|
725
|
-
readNonEmptyString(payload?.commentId) ??
|
|
726
|
-
null);
|
|
727
|
-
}
|
|
728
|
-
function enrichWakeContextSnapshot(input) {
|
|
729
|
-
const { contextSnapshot, reason, source, triggerDetail, payload } = input;
|
|
730
|
-
const issueIdFromPayload = readNonEmptyString(payload?.["issueId"]);
|
|
731
|
-
const commentIdFromPayload = readNonEmptyString(payload?.["commentId"]);
|
|
732
|
-
const taskKey = deriveTaskKey(contextSnapshot, payload);
|
|
733
|
-
const wakeCommentId = deriveCommentId(contextSnapshot, payload);
|
|
734
|
-
if (!readNonEmptyString(contextSnapshot["wakeReason"]) && reason) {
|
|
735
|
-
contextSnapshot.wakeReason = reason;
|
|
736
|
-
}
|
|
737
|
-
if (!readNonEmptyString(contextSnapshot["issueId"]) && issueIdFromPayload) {
|
|
738
|
-
contextSnapshot.issueId = issueIdFromPayload;
|
|
739
|
-
}
|
|
740
|
-
if (!readNonEmptyString(contextSnapshot["taskId"]) && issueIdFromPayload) {
|
|
741
|
-
contextSnapshot.taskId = issueIdFromPayload;
|
|
742
|
-
}
|
|
743
|
-
if (!readNonEmptyString(contextSnapshot["taskKey"]) && taskKey) {
|
|
744
|
-
contextSnapshot.taskKey = taskKey;
|
|
745
|
-
}
|
|
746
|
-
if (!readNonEmptyString(contextSnapshot["commentId"]) && commentIdFromPayload) {
|
|
747
|
-
contextSnapshot.commentId = commentIdFromPayload;
|
|
748
|
-
}
|
|
749
|
-
if (!readNonEmptyString(contextSnapshot["wakeCommentId"]) && wakeCommentId) {
|
|
750
|
-
contextSnapshot.wakeCommentId = wakeCommentId;
|
|
751
|
-
}
|
|
752
|
-
if (!readNonEmptyString(contextSnapshot["wakeSource"]) && source) {
|
|
753
|
-
contextSnapshot.wakeSource = source;
|
|
754
|
-
}
|
|
755
|
-
if (!readNonEmptyString(contextSnapshot["wakeTriggerDetail"]) && triggerDetail) {
|
|
756
|
-
contextSnapshot.wakeTriggerDetail = triggerDetail;
|
|
757
|
-
}
|
|
758
|
-
return {
|
|
759
|
-
contextSnapshot,
|
|
760
|
-
issueIdFromPayload,
|
|
761
|
-
commentIdFromPayload,
|
|
762
|
-
taskKey,
|
|
763
|
-
wakeCommentId,
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
function mergeCoalescedContextSnapshot(existingRaw, incoming) {
|
|
767
|
-
const existing = parseObject(existingRaw);
|
|
768
|
-
const merged = {
|
|
769
|
-
...existing,
|
|
770
|
-
...incoming,
|
|
771
|
-
};
|
|
772
|
-
const commentId = deriveCommentId(incoming, null);
|
|
773
|
-
if (commentId) {
|
|
774
|
-
merged.commentId = commentId;
|
|
775
|
-
merged.wakeCommentId = commentId;
|
|
776
|
-
}
|
|
777
|
-
return merged;
|
|
778
|
-
}
|
|
779
|
-
function issueCommentAuthorKind(comment) {
|
|
780
|
-
if (comment.authorAgentId)
|
|
781
|
-
return "agent";
|
|
782
|
-
if (comment.authorUserId)
|
|
783
|
-
return "user";
|
|
784
|
-
return "system";
|
|
785
|
-
}
|
|
786
|
-
function issueCommentAuthorLabel(comment) {
|
|
787
|
-
if (comment.authorAgentId) {
|
|
788
|
-
return comment.authorAgentName?.trim() || `Agent ${comment.authorAgentId.slice(0, 8)}`;
|
|
789
|
-
}
|
|
790
|
-
if (comment.authorUserId) {
|
|
791
|
-
return comment.authorUserName?.trim() || `User ${comment.authorUserId.slice(0, 8)}`;
|
|
792
|
-
}
|
|
793
|
-
return "System";
|
|
794
|
-
}
|
|
795
|
-
function buildDeferredWakePayload(payload, contextSnapshot, issueId) {
|
|
796
|
-
const deferredPayload = { ...(payload ?? {}) };
|
|
797
|
-
if (issueId && !readNonEmptyString(deferredPayload.issueId)) {
|
|
798
|
-
deferredPayload.issueId = issueId;
|
|
799
|
-
}
|
|
800
|
-
deferredPayload[DEFERRED_WAKE_CONTEXT_KEY] = contextSnapshot;
|
|
801
|
-
return deferredPayload;
|
|
802
|
-
}
|
|
803
|
-
function readDeferredWakeContext(payloadRaw) {
|
|
804
|
-
const payload = parseObject(payloadRaw);
|
|
805
|
-
return parseObject(payload[DEFERRED_WAKE_CONTEXT_KEY]);
|
|
806
|
-
}
|
|
807
|
-
function readDeferredWakePayload(payloadRaw) {
|
|
808
|
-
const payload = parseObject(payloadRaw);
|
|
809
|
-
delete payload[DEFERRED_WAKE_CONTEXT_KEY];
|
|
810
|
-
return payload;
|
|
811
|
-
}
|
|
812
|
-
function deriveDeferredWakeTaskKey(payloadRaw) {
|
|
813
|
-
const payload = readDeferredWakePayload(payloadRaw);
|
|
814
|
-
const contextSnapshot = readDeferredWakeContext(payloadRaw);
|
|
815
|
-
return deriveTaskKey(contextSnapshot, payload);
|
|
816
|
-
}
|
|
817
|
-
async function hydrateWakeContextSnapshot(db, orgId, contextSnapshot) {
|
|
818
|
-
const issueId = readNonEmptyString(contextSnapshot.issueId);
|
|
819
|
-
const commentId = deriveCommentId(contextSnapshot, null);
|
|
820
|
-
const issueContext = parseObject(contextSnapshot.issue);
|
|
821
|
-
const commentContext = parseObject(contextSnapshot.comment);
|
|
822
|
-
const needsIssueContext = !!issueId &&
|
|
823
|
-
(!readNonEmptyString(issueContext.id) ||
|
|
824
|
-
!readNonEmptyString(issueContext.title) ||
|
|
825
|
-
!readNonEmptyString(issueContext.status) ||
|
|
826
|
-
!("priority" in issueContext) ||
|
|
827
|
-
!("description" in issueContext));
|
|
828
|
-
const needsProjectId = !!issueId && !readNonEmptyString(contextSnapshot.projectId);
|
|
829
|
-
const needsCommentContext = !!commentId &&
|
|
830
|
-
(!readNonEmptyString(commentContext.id) ||
|
|
831
|
-
!readNonEmptyString(commentContext.body) ||
|
|
832
|
-
!readNonEmptyString(commentContext.authorKind) ||
|
|
833
|
-
!readNonEmptyString(commentContext.authorLabel) ||
|
|
834
|
-
!readNonEmptyString(commentContext.createdAt));
|
|
835
|
-
if (!needsIssueContext && !needsProjectId && !needsCommentContext)
|
|
836
|
-
return;
|
|
837
|
-
if (issueId && (needsIssueContext || needsProjectId)) {
|
|
838
|
-
const issueRow = await db
|
|
839
|
-
.select({
|
|
840
|
-
id: issues.id,
|
|
841
|
-
title: issues.title,
|
|
842
|
-
description: issues.description,
|
|
843
|
-
status: issues.status,
|
|
844
|
-
priority: issues.priority,
|
|
845
|
-
projectId: issues.projectId,
|
|
846
|
-
})
|
|
847
|
-
.from(issues)
|
|
848
|
-
.where(and(eq(issues.id, issueId), eq(issues.orgId, orgId)))
|
|
849
|
-
.then((rows) => rows[0] ?? null);
|
|
850
|
-
if (issueRow) {
|
|
851
|
-
contextSnapshot.issue = {
|
|
852
|
-
...issueContext,
|
|
853
|
-
id: readNonEmptyString(issueContext.id) ?? issueRow.id,
|
|
854
|
-
title: readNonEmptyString(issueContext.title) ?? issueRow.title,
|
|
855
|
-
description: "description" in issueContext ? issueContext.description : issueRow.description,
|
|
856
|
-
status: readNonEmptyString(issueContext.status) ?? issueRow.status,
|
|
857
|
-
priority: "priority" in issueContext ? issueContext.priority : issueRow.priority,
|
|
858
|
-
};
|
|
859
|
-
if (!readNonEmptyString(contextSnapshot.projectId) && issueRow.projectId) {
|
|
860
|
-
contextSnapshot.projectId = issueRow.projectId;
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
if (commentId && needsCommentContext) {
|
|
865
|
-
const commentConditions = [eq(issueComments.id, commentId), eq(issueComments.orgId, orgId)];
|
|
866
|
-
if (issueId) {
|
|
867
|
-
commentConditions.push(eq(issueComments.issueId, issueId));
|
|
868
|
-
}
|
|
869
|
-
const commentRow = await db
|
|
870
|
-
.select({
|
|
871
|
-
id: issueComments.id,
|
|
872
|
-
body: issueComments.body,
|
|
873
|
-
authorAgentId: issueComments.authorAgentId,
|
|
874
|
-
authorUserId: issueComments.authorUserId,
|
|
875
|
-
authorAgentName: agents.name,
|
|
876
|
-
authorUserName: authUsers.name,
|
|
877
|
-
createdAt: issueComments.createdAt,
|
|
878
|
-
})
|
|
879
|
-
.from(issueComments)
|
|
880
|
-
.leftJoin(agents, eq(issueComments.authorAgentId, agents.id))
|
|
881
|
-
.leftJoin(authUsers, eq(issueComments.authorUserId, authUsers.id))
|
|
882
|
-
.where(and(...commentConditions))
|
|
883
|
-
.then((rows) => rows[0] ?? null);
|
|
884
|
-
if (commentRow) {
|
|
885
|
-
contextSnapshot.comment = {
|
|
886
|
-
...commentContext,
|
|
887
|
-
id: readNonEmptyString(commentContext.id) ?? commentRow.id,
|
|
888
|
-
body: readNonEmptyString(commentContext.body) ?? commentRow.body,
|
|
889
|
-
authorAgentId: "authorAgentId" in commentContext ? commentContext.authorAgentId : commentRow.authorAgentId,
|
|
890
|
-
authorUserId: "authorUserId" in commentContext ? commentContext.authorUserId : commentRow.authorUserId,
|
|
891
|
-
authorKind: readNonEmptyString(commentContext.authorKind) ?? issueCommentAuthorKind(commentRow),
|
|
892
|
-
authorLabel: readNonEmptyString(commentContext.authorLabel) ?? issueCommentAuthorLabel(commentRow),
|
|
893
|
-
createdAt: readNonEmptyString(commentContext.createdAt) ?? commentRow.createdAt.toISOString(),
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
function firstNonEmptyLine(value) {
|
|
899
|
-
if (typeof value !== "string")
|
|
900
|
-
return null;
|
|
901
|
-
const line = value
|
|
902
|
-
.split("\n")
|
|
903
|
-
.map((chunk) => chunk.trim())
|
|
904
|
-
.find(Boolean);
|
|
905
|
-
return line ?? null;
|
|
906
|
-
}
|
|
907
|
-
function deriveRecoveryFailureKind(run) {
|
|
908
|
-
return (readNonEmptyString(run.errorCode) ??
|
|
909
|
-
(run.status === "timed_out" ? "timed_out" : null) ??
|
|
910
|
-
run.status);
|
|
911
|
-
}
|
|
912
|
-
function deriveRecoveryFailureSummary(run) {
|
|
913
|
-
return (firstNonEmptyLine(run.error) ??
|
|
914
|
-
firstNonEmptyLine(run.stderrExcerpt) ??
|
|
915
|
-
firstNonEmptyLine(run.stdoutExcerpt) ??
|
|
916
|
-
(run.status === "timed_out" ? "The run timed out before it completed." : null) ??
|
|
917
|
-
"The previous run failed before it completed.");
|
|
918
|
-
}
|
|
919
|
-
function mergeMissingRecoveryContextFields(target, source) {
|
|
920
|
-
const keysToBackfill = [
|
|
921
|
-
"issueId",
|
|
922
|
-
"taskId",
|
|
923
|
-
"taskKey",
|
|
924
|
-
"projectId",
|
|
925
|
-
"projectWorkspaceId",
|
|
926
|
-
"commentId",
|
|
927
|
-
"wakeCommentId",
|
|
928
|
-
"issue",
|
|
929
|
-
"comment",
|
|
930
|
-
"source",
|
|
931
|
-
"wakeSource",
|
|
932
|
-
"wakeTriggerDetail",
|
|
933
|
-
];
|
|
934
|
-
for (const key of keysToBackfill) {
|
|
935
|
-
if (!(key in target) || target[key] === null || target[key] === undefined || target[key] === "") {
|
|
936
|
-
const value = source[key];
|
|
937
|
-
if (value !== null && value !== undefined && value !== "") {
|
|
938
|
-
target[key] = value;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
async function hydrateRecoveryBaseContextSnapshot(run, getRunById) {
|
|
944
|
-
const mergedContext = { ...parseObject(run.contextSnapshot) };
|
|
945
|
-
let ancestorRunId = readNonEmptyString(run.retryOfRunId);
|
|
946
|
-
let depth = 0;
|
|
947
|
-
while (ancestorRunId && depth < MAX_RECOVERY_CHAIN_DEPTH) {
|
|
948
|
-
const ancestorRun = await getRunById(ancestorRunId);
|
|
949
|
-
if (!ancestorRun)
|
|
950
|
-
break;
|
|
951
|
-
mergeMissingRecoveryContextFields(mergedContext, parseObject(ancestorRun.contextSnapshot));
|
|
952
|
-
ancestorRunId = readNonEmptyString(ancestorRun.retryOfRunId);
|
|
953
|
-
depth += 1;
|
|
954
|
-
}
|
|
955
|
-
return mergedContext;
|
|
956
|
-
}
|
|
957
|
-
function buildRecoveryContextSnapshot(input) {
|
|
958
|
-
const { baseContextSnapshot, run, recoveryTrigger, wakeReason, wakeSource, triggerDetail } = input;
|
|
959
|
-
const failureKind = deriveRecoveryFailureKind(run);
|
|
960
|
-
const failureSummary = deriveRecoveryFailureSummary(run);
|
|
961
|
-
const recovery = {
|
|
962
|
-
originalRunId: run.id,
|
|
963
|
-
failureKind,
|
|
964
|
-
failureSummary,
|
|
965
|
-
recoveryTrigger,
|
|
966
|
-
recoveryMode: "continue_preferred",
|
|
967
|
-
};
|
|
968
|
-
return {
|
|
969
|
-
...baseContextSnapshot,
|
|
970
|
-
wakeReason,
|
|
971
|
-
wakeSource,
|
|
972
|
-
wakeTriggerDetail: triggerDetail,
|
|
973
|
-
retryOfRunId: run.id,
|
|
974
|
-
retryReason: failureKind,
|
|
975
|
-
recovery,
|
|
976
|
-
};
|
|
977
|
-
}
|
|
978
|
-
function normalizePassiveFollowupContext(raw) {
|
|
979
|
-
const parsed = parseObject(raw);
|
|
980
|
-
const originRunId = readNonEmptyString(parsed.originRunId);
|
|
981
|
-
if (!originRunId)
|
|
982
|
-
return null;
|
|
983
|
-
const attempt = Math.max(0, Math.floor(asNumber(parsed.attempt, 0)));
|
|
984
|
-
return {
|
|
985
|
-
originRunId,
|
|
986
|
-
previousRunId: readNonEmptyString(parsed.previousRunId),
|
|
987
|
-
attempt,
|
|
988
|
-
maxAttempts: Math.max(1, Math.floor(asNumber(parsed.maxAttempts, ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS))),
|
|
989
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
990
|
-
queuedAt: readNonEmptyString(parsed.queuedAt),
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
function normalizeReviewCloseoutContext(raw) {
|
|
994
|
-
const parsed = parseObject(raw);
|
|
995
|
-
const originRunId = readNonEmptyString(parsed.originRunId);
|
|
996
|
-
if (!originRunId)
|
|
997
|
-
return null;
|
|
998
|
-
const attempt = Math.max(0, Math.floor(asNumber(parsed.attempt, 0)));
|
|
999
|
-
return {
|
|
1000
|
-
originRunId,
|
|
1001
|
-
previousRunId: readNonEmptyString(parsed.previousRunId),
|
|
1002
|
-
attempt,
|
|
1003
|
-
maxAttempts: Math.max(1, Math.floor(asNumber(parsed.maxAttempts, ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS))),
|
|
1004
|
-
reason: ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON,
|
|
1005
|
-
};
|
|
1006
|
-
}
|
|
1007
|
-
function passiveFollowupCooldownMs(attempt) {
|
|
1008
|
-
return ISSUE_PASSIVE_FOLLOWUP_COOLDOWN_MS_BY_ATTEMPT.get(attempt) ?? 5 * 60 * 1000;
|
|
1009
|
-
}
|
|
1010
|
-
function issueHasReviewer(issue) {
|
|
1011
|
-
return Boolean(issue.reviewerAgentId || issue.reviewerUserId);
|
|
1012
|
-
}
|
|
1013
|
-
function isAgentEligibleForTimerContinuation(agent) {
|
|
1014
|
-
return (agent.status !== "paused" &&
|
|
1015
|
-
agent.status !== "terminated" &&
|
|
1016
|
-
agent.status !== "pending_approval");
|
|
1017
|
-
}
|
|
1018
|
-
function hasCredibleTimerContinuation(input) {
|
|
1019
|
-
if (!input.policy.enabled || input.policy.intervalSec <= 0)
|
|
1020
|
-
return false;
|
|
1021
|
-
if (!isAgentEligibleForTimerContinuation(input.agent))
|
|
1022
|
-
return false;
|
|
1023
|
-
const intervalMs = input.policy.intervalSec * 1000;
|
|
1024
|
-
const nearTermWindowMs = Math.min(intervalMs * 2, ISSUE_PASSIVE_FOLLOWUP_TIMER_CONTINUITY_MAX_WINDOW_MS);
|
|
1025
|
-
const lastHeartbeatMs = input.agent.lastHeartbeatAt
|
|
1026
|
-
? new Date(input.agent.lastHeartbeatAt).getTime()
|
|
1027
|
-
: new Date(input.agent.createdAt).getTime();
|
|
1028
|
-
const runFinishedMs = input.run.finishedAt
|
|
1029
|
-
? new Date(input.run.finishedAt).getTime()
|
|
1030
|
-
: input.now.getTime();
|
|
1031
|
-
const baselineMs = Math.max(lastHeartbeatMs, runFinishedMs);
|
|
1032
|
-
const nextTimerMs = baselineMs + intervalMs;
|
|
1033
|
-
return Math.max(0, nextTimerMs - input.now.getTime()) <= nearTermWindowMs;
|
|
1034
|
-
}
|
|
1035
|
-
function buildPassiveFollowupContextSnapshot(input) {
|
|
1036
|
-
const baseContext = { ...parseObject(input.run.contextSnapshot) };
|
|
1037
|
-
delete baseContext.recovery;
|
|
1038
|
-
delete baseContext.retryOfRunId;
|
|
1039
|
-
delete baseContext.retryReason;
|
|
1040
|
-
const taskKey = deriveTaskKey(baseContext, { issueId: input.issue.id }) ?? input.issue.id;
|
|
1041
|
-
return {
|
|
1042
|
-
...baseContext,
|
|
1043
|
-
issueId: input.issue.id,
|
|
1044
|
-
taskId: input.issue.id,
|
|
1045
|
-
taskKey,
|
|
1046
|
-
projectId: readNonEmptyString(baseContext.projectId) ?? input.issue.projectId ?? undefined,
|
|
1047
|
-
wakeReason: ISSUE_PASSIVE_FOLLOWUP_REASON,
|
|
1048
|
-
wakeSource: ISSUE_PASSIVE_FOLLOWUP_WAKE_SOURCE,
|
|
1049
|
-
wakeTriggerDetail: "system",
|
|
1050
|
-
issue: {
|
|
1051
|
-
id: input.issue.id,
|
|
1052
|
-
title: input.issue.title,
|
|
1053
|
-
description: input.issue.description,
|
|
1054
|
-
status: input.issue.status,
|
|
1055
|
-
priority: input.issue.priority,
|
|
1056
|
-
},
|
|
1057
|
-
passiveFollowup: {
|
|
1058
|
-
originRunId: input.originRunId,
|
|
1059
|
-
previousRunId: input.run.id,
|
|
1060
|
-
attempt: input.attempt,
|
|
1061
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
1062
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
1063
|
-
queuedAt: input.now.toISOString(),
|
|
1064
|
-
},
|
|
1065
|
-
...(issueHasReviewer(input.issue)
|
|
1066
|
-
? {
|
|
1067
|
-
reviewGate: {
|
|
1068
|
-
reviewerAgentId: input.issue.reviewerAgentId,
|
|
1069
|
-
reviewerUserId: input.issue.reviewerUserId,
|
|
1070
|
-
closeOutRequirement: "Move the issue to in_review when work is ready, or to blocked/cancelled if it cannot proceed.",
|
|
1071
|
-
},
|
|
1072
|
-
}
|
|
1073
|
-
: {}),
|
|
1074
|
-
};
|
|
1075
|
-
}
|
|
1076
|
-
function runTaskKey(run) {
|
|
1077
|
-
return deriveTaskKey(run.contextSnapshot, null);
|
|
1078
|
-
}
|
|
1079
|
-
function isSameTaskScope(left, right) {
|
|
1080
|
-
return (left ?? null) === (right ?? null);
|
|
1081
|
-
}
|
|
1082
|
-
function isTrackedLocalChildProcessAdapter(agentRuntimeType) {
|
|
1083
|
-
return SESSIONED_LOCAL_ADAPTERS.has(agentRuntimeType);
|
|
1084
|
-
}
|
|
1085
|
-
// A positive liveness check means some process currently owns the PID.
|
|
1086
|
-
// On Linux, PIDs can be recycled, so this is a best-effort signal rather
|
|
1087
|
-
// than proof that the original child is still alive.
|
|
1088
|
-
function isProcessAlive(pid) {
|
|
1089
|
-
if (typeof pid !== "number" || !Number.isInteger(pid) || pid <= 0)
|
|
1090
|
-
return false;
|
|
1091
|
-
try {
|
|
1092
|
-
process.kill(pid, 0);
|
|
1093
|
-
return true;
|
|
1094
|
-
}
|
|
1095
|
-
catch (error) {
|
|
1096
|
-
const code = error?.code;
|
|
1097
|
-
if (code === "EPERM")
|
|
1098
|
-
return true;
|
|
1099
|
-
if (code === "ESRCH")
|
|
1100
|
-
return false;
|
|
1101
|
-
return false;
|
|
1102
|
-
}
|
|
1103
|
-
}
|
|
1104
|
-
async function waitForProcessExit(pid, timeoutMs) {
|
|
1105
|
-
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
1106
|
-
while (Date.now() < deadline) {
|
|
1107
|
-
if (!isProcessAlive(pid))
|
|
1108
|
-
return true;
|
|
1109
|
-
await new Promise((resolve) => setTimeout(resolve, ORPHANED_PROCESS_POLL_INTERVAL_MS));
|
|
1110
|
-
}
|
|
1111
|
-
return !isProcessAlive(pid);
|
|
1112
|
-
}
|
|
1113
|
-
async function terminateOrphanedProcess(pid) {
|
|
1114
|
-
if (!isProcessAlive(pid)) {
|
|
1115
|
-
return {
|
|
1116
|
-
stillAlive: false,
|
|
1117
|
-
terminationSignal: null,
|
|
1118
|
-
error: null,
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
let terminationSignal = null;
|
|
1122
|
-
try {
|
|
1123
|
-
process.kill(pid, "SIGTERM");
|
|
1124
|
-
terminationSignal = "SIGTERM";
|
|
1125
|
-
}
|
|
1126
|
-
catch (error) {
|
|
1127
|
-
const code = error?.code;
|
|
1128
|
-
if (code === "ESRCH") {
|
|
1129
|
-
return {
|
|
1130
|
-
stillAlive: false,
|
|
1131
|
-
terminationSignal: null,
|
|
1132
|
-
error: null,
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
return {
|
|
1136
|
-
stillAlive: isProcessAlive(pid),
|
|
1137
|
-
terminationSignal,
|
|
1138
|
-
error: `SIGTERM failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1139
|
-
};
|
|
1140
|
-
}
|
|
1141
|
-
if (await waitForProcessExit(pid, ORPHANED_PROCESS_TERMINATION_GRACE_MS)) {
|
|
1142
|
-
return {
|
|
1143
|
-
stillAlive: false,
|
|
1144
|
-
terminationSignal,
|
|
1145
|
-
error: null,
|
|
1146
|
-
};
|
|
1147
|
-
}
|
|
1148
|
-
try {
|
|
1149
|
-
process.kill(pid, "SIGKILL");
|
|
1150
|
-
terminationSignal = "SIGKILL";
|
|
1151
|
-
}
|
|
1152
|
-
catch (error) {
|
|
1153
|
-
const code = error?.code;
|
|
1154
|
-
if (code === "ESRCH") {
|
|
1155
|
-
return {
|
|
1156
|
-
stillAlive: false,
|
|
1157
|
-
terminationSignal,
|
|
1158
|
-
error: null,
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
return {
|
|
1162
|
-
stillAlive: isProcessAlive(pid),
|
|
1163
|
-
terminationSignal,
|
|
1164
|
-
error: `SIGKILL failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1165
|
-
};
|
|
1166
|
-
}
|
|
1167
|
-
const exitedAfterKill = await waitForProcessExit(pid, ORPHANED_PROCESS_KILL_WAIT_MS);
|
|
1168
|
-
return {
|
|
1169
|
-
stillAlive: !exitedAfterKill,
|
|
1170
|
-
terminationSignal,
|
|
1171
|
-
error: exitedAfterKill ? null : `Timed out waiting for child pid ${pid} to exit after ${terminationSignal}`,
|
|
1172
|
-
};
|
|
1173
|
-
}
|
|
1174
|
-
function truncateDisplayId(value, max = 128) {
|
|
1175
|
-
if (!value)
|
|
1176
|
-
return null;
|
|
1177
|
-
return value.length > max ? value.slice(0, max) : value;
|
|
1178
|
-
}
|
|
1179
|
-
function normalizeAgentNameKey(value) {
|
|
1180
|
-
if (typeof value !== "string")
|
|
1181
|
-
return null;
|
|
1182
|
-
const normalized = value.trim().toLowerCase();
|
|
1183
|
-
return normalized.length > 0 ? normalized : null;
|
|
1184
|
-
}
|
|
1185
|
-
const defaultSessionCodec = {
|
|
1186
|
-
deserialize(raw) {
|
|
1187
|
-
const asObj = parseObject(raw);
|
|
1188
|
-
if (Object.keys(asObj).length > 0)
|
|
1189
|
-
return asObj;
|
|
1190
|
-
const sessionId = readNonEmptyString(raw?.sessionId);
|
|
1191
|
-
if (sessionId)
|
|
1192
|
-
return { sessionId };
|
|
1193
|
-
return null;
|
|
1194
|
-
},
|
|
1195
|
-
serialize(params) {
|
|
1196
|
-
if (!params || Object.keys(params).length === 0)
|
|
1197
|
-
return null;
|
|
1198
|
-
return params;
|
|
1199
|
-
},
|
|
1200
|
-
getDisplayId(params) {
|
|
1201
|
-
return readNonEmptyString(params?.sessionId);
|
|
1202
|
-
},
|
|
1203
|
-
};
|
|
1204
|
-
function getAgentRuntimeSessionCodec(agentRuntimeType) {
|
|
1205
|
-
const adapter = getServerAdapter(agentRuntimeType);
|
|
1206
|
-
return adapter.sessionCodec ?? defaultSessionCodec;
|
|
1207
|
-
}
|
|
1208
|
-
function normalizeSessionParams(params) {
|
|
1209
|
-
if (!params)
|
|
1210
|
-
return null;
|
|
1211
|
-
return Object.keys(params).length > 0 ? params : null;
|
|
1212
|
-
}
|
|
1213
|
-
function resolveNextSessionState(input) {
|
|
1214
|
-
const { codec, adapterResult, previousParams, previousDisplayId, previousLegacySessionId } = input;
|
|
1215
|
-
if (adapterResult.clearSession) {
|
|
1216
|
-
return {
|
|
1217
|
-
params: null,
|
|
1218
|
-
displayId: null,
|
|
1219
|
-
legacySessionId: null,
|
|
1220
|
-
};
|
|
1221
|
-
}
|
|
1222
|
-
const explicitParams = adapterResult.sessionParams;
|
|
1223
|
-
const hasExplicitParams = adapterResult.sessionParams !== undefined;
|
|
1224
|
-
const hasExplicitSessionId = adapterResult.sessionId !== undefined;
|
|
1225
|
-
const explicitSessionId = readNonEmptyString(adapterResult.sessionId);
|
|
1226
|
-
const hasExplicitDisplay = adapterResult.sessionDisplayId !== undefined;
|
|
1227
|
-
const explicitDisplayId = readNonEmptyString(adapterResult.sessionDisplayId);
|
|
1228
|
-
const shouldUsePrevious = !hasExplicitParams && !hasExplicitSessionId && !hasExplicitDisplay;
|
|
1229
|
-
const candidateParams = hasExplicitParams
|
|
1230
|
-
? explicitParams
|
|
1231
|
-
: hasExplicitSessionId
|
|
1232
|
-
? (explicitSessionId ? { sessionId: explicitSessionId } : null)
|
|
1233
|
-
: previousParams;
|
|
1234
|
-
const serialized = normalizeSessionParams(codec.serialize(normalizeSessionParams(candidateParams) ?? null));
|
|
1235
|
-
const deserialized = normalizeSessionParams(codec.deserialize(serialized));
|
|
1236
|
-
const displayId = truncateDisplayId(explicitDisplayId ??
|
|
1237
|
-
(codec.getDisplayId ? codec.getDisplayId(deserialized) : null) ??
|
|
1238
|
-
readNonEmptyString(deserialized?.sessionId) ??
|
|
1239
|
-
(shouldUsePrevious ? previousDisplayId : null) ??
|
|
1240
|
-
explicitSessionId ??
|
|
1241
|
-
(shouldUsePrevious ? previousLegacySessionId : null));
|
|
1242
|
-
const legacySessionId = explicitSessionId ??
|
|
1243
|
-
readNonEmptyString(deserialized?.sessionId) ??
|
|
1244
|
-
displayId ??
|
|
1245
|
-
(shouldUsePrevious ? previousLegacySessionId : null);
|
|
1246
|
-
return {
|
|
1247
|
-
params: serialized,
|
|
1248
|
-
displayId,
|
|
1249
|
-
legacySessionId,
|
|
1250
|
-
};
|
|
1251
|
-
}
|
|
25
|
+
import * as heartbeatCore from "./heartbeat.core.js";
|
|
26
|
+
import * as heartbeatSessions from "./heartbeat.sessions.js";
|
|
27
|
+
const { MAX_LIVE_LOG_CHUNK_BYTES, HEARTBEAT_MAX_CONCURRENT_RUNS_DEFAULT, HEARTBEAT_MAX_CONCURRENT_RUNS_MIN, HEARTBEAT_MAX_CONCURRENT_RUNS_MAX, DEFERRED_WAKE_CONTEXT_KEY, DETACHED_PROCESS_ERROR_CODE, ORPHANED_PROCESS_TERMINATION_GRACE_MS, ORPHANED_PROCESS_KILL_WAIT_MS, ORPHANED_PROCESS_POLL_INTERVAL_MS, startLocksByAgent, MAX_RECOVERY_CHAIN_DEPTH, ISSUE_PASSIVE_FOLLOWUP_REASON, ISSUE_PASSIVE_FOLLOWUP_WAKE_SOURCE, ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON, ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS, ISSUE_REVIEW_CLOSEOUT_REASON, ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON, ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS, ISSUE_PASSIVE_FOLLOWUP_COOLDOWN_MS_BY_ATTEMPT, ISSUE_PASSIVE_FOLLOWUP_TIMER_CONTINUITY_MAX_WINDOW_MS, SESSIONED_LOCAL_ADAPTERS, heartbeatRunListColumns, appendExcerpt, appendTranscriptEntriesFromChunk, normalizeMaxConcurrentRuns, withAgentStartLock, readNonEmptyString, resolveHeartbeatObservabilitySurface, buildHeartbeatObservationName, compactTraceText, buildIssueRunTraceName, buildHeartbeatRuntimeTraceMetadata, buildHeartbeatAdapterInvokePayload, buildRecentDateKeys, buildDateKeysBetween, fallbackSkillLabel, normalizeLoadedSkill, normalizeLoadedSkillForPayload, emptySkillEvidenceCounts, incrementSkillEvidenceCount, strongestSkillEvidence, resolveSkillEvidence, readSkillEvidenceFromPayload, extractSkillSlugFromPath, collectSkillPathsFromText, collectStringValues, normalizeSkillUseFromPath, dedupeSkillUses, collectSkillUsesFromText, readToolCommandInput, isCommandTranscriptTool, isReadTranscriptTool, inferUsedSkillsFromTranscript, normalizeSkillCandidate, addSkillCandidate, readSkillReferenceSlug, collectSkillReferences, inferUsedSkillsFromPrompt, normalizeLedgerBillingType, resolveLedgerBiller, normalizeBilledCostCents, resolveLedgerScopeForRun } = heartbeatCore;
|
|
28
|
+
const { buildExplicitResumeSessionOverride, normalizeUsageTotals, readRawUsageTotals, deriveNormalizedUsageDelta, formatCount, parseSessionCompactionPolicy, resolveRuntimeSessionParamsForWorkspace, parseIssueAssigneeAgentRuntimeOverrides, deriveTaskKey, shouldResetTaskSessionForWake, formatRuntimeWorkspaceWarningLog, describeSessionResetReason, deriveCommentId, enrichWakeContextSnapshot, mergeCoalescedContextSnapshot, issueCommentAuthorKind, issueCommentAuthorLabel, buildDeferredWakePayload, readDeferredWakeContext, readDeferredWakePayload, deriveDeferredWakeTaskKey, hydrateWakeContextSnapshot, firstNonEmptyLine, deriveRecoveryFailureKind, deriveRecoveryFailureSummary, mergeMissingRecoveryContextFields, hydrateRecoveryBaseContextSnapshot, buildRecoveryContextSnapshot, normalizePassiveFollowupContext, normalizeReviewCloseoutContext, passiveFollowupCooldownMs, issueHasReviewer, isAgentEligibleForTimerContinuation, hasCredibleTimerContinuation, buildPassiveFollowupContextSnapshot, runTaskKey, isSameTaskScope, isTrackedLocalChildProcessAdapter, isProcessAlive, waitForProcessExit, terminateOrphanedProcess, truncateDisplayId, normalizeAgentNameKey, defaultSessionCodec, getAgentRuntimeSessionCodec, normalizeSessionParams, resolveNextSessionState } = heartbeatSessions;
|
|
29
|
+
import { createHeartbeatExecuteHandlers } from "./heartbeat.execute.js";
|
|
30
|
+
import { createHeartbeatMiscHandlers } from "./heartbeat.misc.js";
|
|
31
|
+
import { createHeartbeatRecoveryHandlers } from "./heartbeat.recovery.js";
|
|
32
|
+
import { createHeartbeatReleaseHandlers } from "./heartbeat.release.js";
|
|
33
|
+
import { createHeartbeatWakeupHandlers } from "./heartbeat.wakeup.js";
|
|
1252
34
|
export function heartbeatService(db) {
|
|
1253
35
|
const instanceSettings = instanceSettingsService(db);
|
|
1254
36
|
const getCurrentUserRedactionOptions = async () => ({
|
|
@@ -1262,7 +44,7 @@ export function heartbeatService(db) {
|
|
|
1262
44
|
const workspaceOperationsSvc = workspaceOperationService(db);
|
|
1263
45
|
const activeRunExecutions = new Set();
|
|
1264
46
|
const budgetHooks = {
|
|
1265
|
-
cancelWorkForScope: cancelBudgetScopeWork,
|
|
47
|
+
cancelWorkForScope: (scope) => cancelBudgetScopeWork(scope),
|
|
1266
48
|
};
|
|
1267
49
|
const budgets = budgetService(db, budgetHooks);
|
|
1268
50
|
async function getAgent(agentId) {
|
|
@@ -1794,508 +576,24 @@ export function heartbeatService(db) {
|
|
|
1794
576
|
});
|
|
1795
577
|
return updated;
|
|
1796
578
|
}
|
|
1797
|
-
async function
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
baseContextSnapshot,
|
|
1816
|
-
run,
|
|
1817
|
-
recoveryTrigger: opts.recoveryTrigger,
|
|
1818
|
-
wakeReason: opts.wakeReason,
|
|
1819
|
-
wakeSource: `recovery.${opts.recoveryTrigger}`,
|
|
1820
|
-
triggerDetail: opts.triggerDetail,
|
|
1821
|
-
});
|
|
1822
|
-
const issueId = readNonEmptyString(recoveryContextSnapshot.issueId);
|
|
1823
|
-
const taskKey = deriveTaskKey(recoveryContextSnapshot, null);
|
|
1824
|
-
const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey);
|
|
1825
|
-
const recovery = recoveryContextSnapshot.recovery;
|
|
1826
|
-
const requestPayload = {
|
|
1827
|
-
originalRunId: run.id,
|
|
1828
|
-
failureKind: recovery.failureKind,
|
|
1829
|
-
recoveryTrigger: recovery.recoveryTrigger,
|
|
1830
|
-
...(issueId ? { issueId } : {}),
|
|
1831
|
-
};
|
|
1832
|
-
const outcome = await db.transaction(async (tx) => {
|
|
1833
|
-
let issueRow = null;
|
|
1834
|
-
if (issueId) {
|
|
1835
|
-
await tx.execute(sql `select id from issues where id = ${issueId} and org_id = ${run.orgId} for update`);
|
|
1836
|
-
issueRow = await tx
|
|
1837
|
-
.select({
|
|
1838
|
-
id: issues.id,
|
|
1839
|
-
orgId: issues.orgId,
|
|
1840
|
-
executionRunId: issues.executionRunId,
|
|
1841
|
-
executionAgentNameKey: issues.executionAgentNameKey,
|
|
1842
|
-
})
|
|
1843
|
-
.from(issues)
|
|
1844
|
-
.where(and(eq(issues.id, issueId), eq(issues.orgId, run.orgId)))
|
|
1845
|
-
.then((rows) => rows[0] ?? null);
|
|
1846
|
-
}
|
|
1847
|
-
if (issueRow?.executionRunId) {
|
|
1848
|
-
const activeExecutionRun = await tx
|
|
1849
|
-
.select()
|
|
1850
|
-
.from(heartbeatRuns)
|
|
1851
|
-
.where(eq(heartbeatRuns.id, issueRow.executionRunId))
|
|
1852
|
-
.then((rows) => rows[0] ?? null);
|
|
1853
|
-
const isActiveExecutionRun = activeExecutionRun &&
|
|
1854
|
-
(activeExecutionRun.status === "queued" || activeExecutionRun.status === "running");
|
|
1855
|
-
if (!isActiveExecutionRun) {
|
|
1856
|
-
await tx
|
|
1857
|
-
.update(issues)
|
|
1858
|
-
.set({
|
|
1859
|
-
executionRunId: null,
|
|
1860
|
-
executionAgentNameKey: null,
|
|
1861
|
-
executionLockedAt: null,
|
|
1862
|
-
updatedAt: opts.now,
|
|
1863
|
-
})
|
|
1864
|
-
.where(eq(issues.id, issueRow.id));
|
|
1865
|
-
issueRow = {
|
|
1866
|
-
...issueRow,
|
|
1867
|
-
executionRunId: null,
|
|
1868
|
-
executionAgentNameKey: null,
|
|
1869
|
-
};
|
|
1870
|
-
}
|
|
1871
|
-
else if (activeExecutionRun) {
|
|
1872
|
-
const activeContext = parseObject(activeExecutionRun.contextSnapshot);
|
|
1873
|
-
const activeRecovery = parseObject(activeContext.recovery);
|
|
1874
|
-
if (activeExecutionRun.agentId === run.agentId &&
|
|
1875
|
-
(activeExecutionRun.retryOfRunId === run.id ||
|
|
1876
|
-
readNonEmptyString(activeRecovery.originalRunId) === run.id)) {
|
|
1877
|
-
return { kind: "existing", run: activeExecutionRun };
|
|
1878
|
-
}
|
|
1879
|
-
throw conflict("Issue already has an active execution run", {
|
|
1880
|
-
issueId: issueRow.id,
|
|
1881
|
-
executionRunId: activeExecutionRun.id,
|
|
1882
|
-
});
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
const wakeupRequest = await tx
|
|
1886
|
-
.insert(agentWakeupRequests)
|
|
1887
|
-
.values({
|
|
1888
|
-
orgId: run.orgId,
|
|
1889
|
-
agentId: run.agentId,
|
|
1890
|
-
source: opts.source,
|
|
1891
|
-
triggerDetail: opts.triggerDetail,
|
|
1892
|
-
reason: opts.wakeReason,
|
|
1893
|
-
payload: requestPayload,
|
|
1894
|
-
status: "queued",
|
|
1895
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
1896
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
1897
|
-
updatedAt: opts.now,
|
|
1898
|
-
})
|
|
1899
|
-
.returning()
|
|
1900
|
-
.then((rows) => rows[0]);
|
|
1901
|
-
const recoveryRun = await tx
|
|
1902
|
-
.insert(heartbeatRuns)
|
|
1903
|
-
.values({
|
|
1904
|
-
orgId: run.orgId,
|
|
1905
|
-
agentId: run.agentId,
|
|
1906
|
-
invocationSource: opts.source,
|
|
1907
|
-
triggerDetail: opts.triggerDetail,
|
|
1908
|
-
status: "queued",
|
|
1909
|
-
wakeupRequestId: wakeupRequest.id,
|
|
1910
|
-
contextSnapshot: recoveryContextSnapshot,
|
|
1911
|
-
sessionIdBefore: sessionBefore,
|
|
1912
|
-
retryOfRunId: run.id,
|
|
1913
|
-
processLossRetryCount: opts.recoveryTrigger === "automatic"
|
|
1914
|
-
? (run.processLossRetryCount ?? 0) + 1
|
|
1915
|
-
: (run.processLossRetryCount ?? 0),
|
|
1916
|
-
updatedAt: opts.now,
|
|
1917
|
-
})
|
|
1918
|
-
.returning()
|
|
1919
|
-
.then((rows) => rows[0]);
|
|
1920
|
-
await tx
|
|
1921
|
-
.update(agentWakeupRequests)
|
|
1922
|
-
.set({
|
|
1923
|
-
runId: recoveryRun.id,
|
|
1924
|
-
updatedAt: opts.now,
|
|
1925
|
-
})
|
|
1926
|
-
.where(eq(agentWakeupRequests.id, wakeupRequest.id));
|
|
1927
|
-
if (issueRow) {
|
|
1928
|
-
await tx
|
|
1929
|
-
.update(issues)
|
|
1930
|
-
.set({
|
|
1931
|
-
executionRunId: recoveryRun.id,
|
|
1932
|
-
executionAgentNameKey: normalizeAgentNameKey(agent.name),
|
|
1933
|
-
executionLockedAt: opts.now,
|
|
1934
|
-
updatedAt: opts.now,
|
|
1935
|
-
})
|
|
1936
|
-
.where(eq(issues.id, issueRow.id));
|
|
1937
|
-
}
|
|
1938
|
-
return { kind: "queued", run: recoveryRun };
|
|
1939
|
-
});
|
|
1940
|
-
if (outcome.kind === "existing")
|
|
1941
|
-
return outcome.run;
|
|
1942
|
-
const recoveryRun = outcome.run;
|
|
1943
|
-
await appendRunEvent(recoveryRun, await nextRunEventSeq(recoveryRun.id), {
|
|
1944
|
-
eventType: "lifecycle",
|
|
1945
|
-
stream: "system",
|
|
1946
|
-
level: opts.recoveryTrigger === "automatic" ? "warn" : "info",
|
|
1947
|
-
message: `Recovery queued from run ${run.id}`,
|
|
1948
|
-
payload: {
|
|
1949
|
-
originalRunId: run.id,
|
|
1950
|
-
failureKind: recovery.failureKind,
|
|
1951
|
-
failureSummary: recovery.failureSummary,
|
|
1952
|
-
recoveryTrigger: recovery.recoveryTrigger,
|
|
1953
|
-
recoveryMode: recovery.recoveryMode,
|
|
1954
|
-
},
|
|
1955
|
-
});
|
|
1956
|
-
publishLiveEvent({
|
|
1957
|
-
orgId: recoveryRun.orgId,
|
|
1958
|
-
type: "heartbeat.run.queued",
|
|
1959
|
-
payload: {
|
|
1960
|
-
runId: recoveryRun.id,
|
|
1961
|
-
agentId: recoveryRun.agentId,
|
|
1962
|
-
invocationSource: recoveryRun.invocationSource,
|
|
1963
|
-
triggerDetail: recoveryRun.triggerDetail,
|
|
1964
|
-
wakeupRequestId: recoveryRun.wakeupRequestId,
|
|
1965
|
-
},
|
|
1966
|
-
});
|
|
1967
|
-
if (opts.startImmediately !== false) {
|
|
1968
|
-
await startNextQueuedRunForAgent(agent.id);
|
|
1969
|
-
}
|
|
1970
|
-
return recoveryRun;
|
|
1971
|
-
}
|
|
1972
|
-
async function enqueueProcessLossRetry(run, agent, now) {
|
|
1973
|
-
return enqueueRecoveryRun(run, agent, {
|
|
1974
|
-
recoveryTrigger: "automatic",
|
|
1975
|
-
source: "automation",
|
|
1976
|
-
triggerDetail: "system",
|
|
1977
|
-
wakeReason: "process_lost_retry",
|
|
1978
|
-
requestedByActorType: "system",
|
|
1979
|
-
requestedByActorId: null,
|
|
1980
|
-
startImmediately: false,
|
|
1981
|
-
now,
|
|
1982
|
-
});
|
|
1983
|
-
}
|
|
1984
|
-
function parseHeartbeatPolicy(agent) {
|
|
1985
|
-
const runtimeConfig = parseObject(agent.runtimeConfig);
|
|
1986
|
-
const heartbeat = parseObject(runtimeConfig.heartbeat);
|
|
1987
|
-
return {
|
|
1988
|
-
enabled: asBoolean(heartbeat.enabled, true),
|
|
1989
|
-
intervalSec: Math.max(0, asNumber(heartbeat.intervalSec, 0)),
|
|
1990
|
-
wakeOnDemand: asBoolean(heartbeat.wakeOnDemand ?? heartbeat.wakeOnAssignment ?? heartbeat.wakeOnOnDemand ?? heartbeat.wakeOnAutomation, true),
|
|
1991
|
-
maxConcurrentRuns: normalizeMaxConcurrentRuns(heartbeat.maxConcurrentRuns),
|
|
1992
|
-
};
|
|
1993
|
-
}
|
|
1994
|
-
async function runHasIssueClosureComment(tx, run, issueId) {
|
|
1995
|
-
const commentActivity = await tx
|
|
1996
|
-
.select({ id: activityLog.id })
|
|
1997
|
-
.from(activityLog)
|
|
1998
|
-
.where(and(eq(activityLog.orgId, run.orgId), eq(activityLog.action, "issue.comment_added"), eq(activityLog.entityType, "issue"), eq(activityLog.entityId, issueId), eq(activityLog.runId, run.id)))
|
|
1999
|
-
.limit(1)
|
|
2000
|
-
.then((rows) => rows[0] ?? null);
|
|
2001
|
-
return Boolean(commentActivity);
|
|
2002
|
-
}
|
|
2003
|
-
async function runHasIssueReviewDecision(tx, run, issueId) {
|
|
2004
|
-
const decisionActivity = await tx
|
|
2005
|
-
.select({ id: activityLog.id })
|
|
2006
|
-
.from(activityLog)
|
|
2007
|
-
.where(and(eq(activityLog.orgId, run.orgId), eq(activityLog.action, "issue.review_decision_recorded"), eq(activityLog.entityType, "issue"), eq(activityLog.entityId, issueId), eq(activityLog.runId, run.id)))
|
|
2008
|
-
.limit(1)
|
|
2009
|
-
.then((rows) => rows[0] ?? null);
|
|
2010
|
-
return Boolean(decisionActivity);
|
|
2011
|
-
}
|
|
2012
|
-
async function issueHasDeferredWake(tx, orgId, issueId) {
|
|
2013
|
-
const deferred = await tx
|
|
2014
|
-
.select({ id: agentWakeupRequests.id })
|
|
2015
|
-
.from(agentWakeupRequests)
|
|
2016
|
-
.where(and(eq(agentWakeupRequests.orgId, orgId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issueId}`))
|
|
2017
|
-
.limit(1)
|
|
2018
|
-
.then((rows) => rows[0] ?? null);
|
|
2019
|
-
return Boolean(deferred);
|
|
2020
|
-
}
|
|
2021
|
-
async function passiveFollowupAlreadyRecorded(tx, runId) {
|
|
2022
|
-
const idempotencyKey = `${ISSUE_PASSIVE_FOLLOWUP_REASON}:${runId}`;
|
|
2023
|
-
const existingWake = await tx
|
|
2024
|
-
.select({ id: agentWakeupRequests.id })
|
|
2025
|
-
.from(agentWakeupRequests)
|
|
2026
|
-
.where(eq(agentWakeupRequests.idempotencyKey, idempotencyKey))
|
|
2027
|
-
.limit(1)
|
|
2028
|
-
.then((rows) => rows[0] ?? null);
|
|
2029
|
-
if (existingWake)
|
|
2030
|
-
return true;
|
|
2031
|
-
const existingReview = await tx
|
|
2032
|
-
.select({ id: activityLog.id })
|
|
2033
|
-
.from(activityLog)
|
|
2034
|
-
.where(and(eq(activityLog.runId, runId), inArray(activityLog.action, [
|
|
2035
|
-
"issue.closure_needs_operator_review",
|
|
2036
|
-
"issue.convergence_review_requested",
|
|
2037
|
-
])))
|
|
2038
|
-
.limit(1)
|
|
2039
|
-
.then((rows) => rows[0] ?? null);
|
|
2040
|
-
return Boolean(existingReview);
|
|
2041
|
-
}
|
|
2042
|
-
async function reviewerCloseoutAlreadyRecorded(tx, runId) {
|
|
2043
|
-
const existingWake = await tx
|
|
2044
|
-
.select({ id: agentWakeupRequests.id })
|
|
2045
|
-
.from(agentWakeupRequests)
|
|
2046
|
-
.where(eq(agentWakeupRequests.idempotencyKey, `${ISSUE_REVIEW_CLOSEOUT_REASON}:${runId}`))
|
|
2047
|
-
.limit(1)
|
|
2048
|
-
.then((rows) => rows[0] ?? null);
|
|
2049
|
-
if (existingWake)
|
|
2050
|
-
return true;
|
|
2051
|
-
const existingReview = await tx
|
|
2052
|
-
.select({ id: activityLog.id })
|
|
2053
|
-
.from(activityLog)
|
|
2054
|
-
.where(and(eq(activityLog.runId, runId), eq(activityLog.action, "issue.review_closure_needs_operator_review")))
|
|
2055
|
-
.limit(1)
|
|
2056
|
-
.then((rows) => rows[0] ?? null);
|
|
2057
|
-
return Boolean(existingReview);
|
|
2058
|
-
}
|
|
2059
|
-
async function issueHasConfirmedBlockedReviewerHandoff(tx, issue, reviewerAgentId) {
|
|
2060
|
-
if (issue.status !== "blocked")
|
|
2061
|
-
return false;
|
|
2062
|
-
const existingHandoff = await tx
|
|
2063
|
-
.select({ id: activityLog.id })
|
|
2064
|
-
.from(activityLog)
|
|
2065
|
-
.where(and(eq(activityLog.orgId, issue.orgId), eq(activityLog.action, "issue.review_decision_recorded"), eq(activityLog.entityType, "issue"), sql `${activityLog.entityId} = ${issue.id}::text`, eq(activityLog.actorType, "agent"), sql `${activityLog.actorId} = ${reviewerAgentId}::text`, sql `${activityLog.details} ->> 'decision' = 'blocked'`, sql `${activityLog.createdAt} >= COALESCE((
|
|
2066
|
-
SELECT MAX(material_activity.created_at)
|
|
2067
|
-
FROM activity_log material_activity
|
|
2068
|
-
WHERE material_activity.org_id = ${issue.orgId}
|
|
2069
|
-
AND material_activity.entity_type = 'issue'
|
|
2070
|
-
AND material_activity.entity_id = ${issue.id}::text
|
|
2071
|
-
AND (
|
|
2072
|
-
(
|
|
2073
|
-
material_activity.action = 'issue.updated'
|
|
2074
|
-
AND jsonb_typeof(material_activity.details) = 'object'
|
|
2075
|
-
AND EXISTS (
|
|
2076
|
-
SELECT 1
|
|
2077
|
-
FROM jsonb_object_keys(material_activity.details) AS detail_key(key)
|
|
2078
|
-
WHERE detail_key.key NOT IN (
|
|
2079
|
-
'identifier',
|
|
2080
|
-
'issueIdentifier',
|
|
2081
|
-
'_previous',
|
|
2082
|
-
'source',
|
|
2083
|
-
'reopened',
|
|
2084
|
-
'reopenedFrom',
|
|
2085
|
-
'normalizedFromStatus',
|
|
2086
|
-
'normalizedReason'
|
|
2087
|
-
)
|
|
2088
|
-
)
|
|
2089
|
-
)
|
|
2090
|
-
OR (
|
|
2091
|
-
material_activity.action = 'issue.comment_added'
|
|
2092
|
-
AND NOT (
|
|
2093
|
-
material_activity.actor_type = 'agent'
|
|
2094
|
-
AND material_activity.actor_id = ${reviewerAgentId}::text
|
|
2095
|
-
)
|
|
2096
|
-
)
|
|
2097
|
-
)
|
|
2098
|
-
), to_timestamp(0))`))
|
|
2099
|
-
.limit(1)
|
|
2100
|
-
.then((rows) => rows[0] ?? null);
|
|
2101
|
-
return Boolean(existingHandoff);
|
|
2102
|
-
}
|
|
2103
|
-
async function evaluatePassiveIssueClosureForLockedIssue(input) {
|
|
2104
|
-
const { tx, run, issue, now } = input;
|
|
2105
|
-
const context = parseObject(run.contextSnapshot);
|
|
2106
|
-
const runIssueId = readNonEmptyString(context.issueId);
|
|
2107
|
-
if (!runIssueId || runIssueId !== issue.id)
|
|
2108
|
-
return { kind: "none", reason: "run_not_issue_backed" };
|
|
2109
|
-
if (run.status !== "succeeded")
|
|
2110
|
-
return { kind: "none", reason: "run_not_successful" };
|
|
2111
|
-
const reviewerRun = (issue.status === "in_review" || issue.status === "blocked") &&
|
|
2112
|
-
issue.reviewerAgentId === run.agentId &&
|
|
2113
|
-
(run.invocationSource === "review" ||
|
|
2114
|
-
readNonEmptyString(context.role) === "reviewer" ||
|
|
2115
|
-
readNonEmptyString(context.wakeSource) === "review");
|
|
2116
|
-
if (reviewerRun) {
|
|
2117
|
-
if (await runHasIssueReviewDecision(tx, run, issue.id)) {
|
|
2118
|
-
return { kind: "none", reason: "review_decision_recorded" };
|
|
2119
|
-
}
|
|
2120
|
-
if (await issueHasConfirmedBlockedReviewerHandoff(tx, issue, run.agentId)) {
|
|
2121
|
-
return { kind: "none", reason: "blocked_reviewer_handoff_confirmed" };
|
|
2122
|
-
}
|
|
2123
|
-
if (await issueHasDeferredWake(tx, issue.orgId, issue.id)) {
|
|
2124
|
-
return { kind: "none", reason: "deferred_issue_wake_exists" };
|
|
2125
|
-
}
|
|
2126
|
-
if (await reviewerCloseoutAlreadyRecorded(tx, run.id)) {
|
|
2127
|
-
return { kind: "none", reason: "reviewer_closeout_already_recorded" };
|
|
2128
|
-
}
|
|
2129
|
-
const reviewCloseout = normalizeReviewCloseoutContext(context.reviewCloseout);
|
|
2130
|
-
const currentAttempt = reviewCloseout?.attempt ?? 0;
|
|
2131
|
-
const maxAttempts = reviewCloseout?.maxAttempts ?? ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS;
|
|
2132
|
-
const originRunId = reviewCloseout?.originRunId ?? run.id;
|
|
2133
|
-
if (currentAttempt >= maxAttempts) {
|
|
2134
|
-
return {
|
|
2135
|
-
kind: "reviewer_closeout_operator_review",
|
|
2136
|
-
issue,
|
|
2137
|
-
originRunId,
|
|
2138
|
-
previousRunId: run.id,
|
|
2139
|
-
attempts: currentAttempt,
|
|
2140
|
-
maxAttempts,
|
|
2141
|
-
reason: ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON,
|
|
2142
|
-
};
|
|
2143
|
-
}
|
|
2144
|
-
return {
|
|
2145
|
-
kind: "reviewer_closeout",
|
|
2146
|
-
issue,
|
|
2147
|
-
originRunId,
|
|
2148
|
-
previousRunId: run.id,
|
|
2149
|
-
attempts: currentAttempt + 1,
|
|
2150
|
-
maxAttempts,
|
|
2151
|
-
reason: ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON,
|
|
2152
|
-
};
|
|
2153
|
-
}
|
|
2154
|
-
if (issue.status !== "todo" && issue.status !== "in_progress") {
|
|
2155
|
-
return { kind: "none", reason: "issue_has_closure_status" };
|
|
2156
|
-
}
|
|
2157
|
-
if (issue.assigneeAgentId !== run.agentId) {
|
|
2158
|
-
return { kind: "none", reason: "issue_no_longer_assigned_to_run_agent" };
|
|
2159
|
-
}
|
|
2160
|
-
if (!issueHasReviewer(issue) && await runHasIssueClosureComment(tx, run, issue.id)) {
|
|
2161
|
-
return { kind: "none", reason: "run_authored_issue_comment" };
|
|
2162
|
-
}
|
|
2163
|
-
if (await issueHasDeferredWake(tx, issue.orgId, issue.id)) {
|
|
2164
|
-
return { kind: "none", reason: "deferred_issue_wake_exists" };
|
|
2165
|
-
}
|
|
2166
|
-
if (await passiveFollowupAlreadyRecorded(tx, run.id)) {
|
|
2167
|
-
return { kind: "none", reason: "passive_followup_already_recorded" };
|
|
2168
|
-
}
|
|
2169
|
-
const agent = await tx
|
|
2170
|
-
.select()
|
|
2171
|
-
.from(agents)
|
|
2172
|
-
.where(eq(agents.id, run.agentId))
|
|
2173
|
-
.then((rows) => rows[0] ?? null);
|
|
2174
|
-
if (!agent || agent.orgId !== run.orgId) {
|
|
2175
|
-
return { kind: "none", reason: "agent_not_found" };
|
|
2176
|
-
}
|
|
2177
|
-
const policy = parseHeartbeatPolicy(agent);
|
|
2178
|
-
if (hasCredibleTimerContinuation({ agent, policy, run, now })) {
|
|
2179
|
-
return { kind: "none", reason: "timer_continuity_expected" };
|
|
2180
|
-
}
|
|
2181
|
-
const passiveContext = normalizePassiveFollowupContext(context.passiveFollowup);
|
|
2182
|
-
const currentAttempt = passiveContext?.attempt ?? 0;
|
|
2183
|
-
const originRunId = passiveContext?.originRunId ?? run.id;
|
|
2184
|
-
if (currentAttempt >= ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS) {
|
|
2185
|
-
if (issueHasReviewer(issue)) {
|
|
2186
|
-
return {
|
|
2187
|
-
kind: "reviewer_convergence",
|
|
2188
|
-
issue,
|
|
2189
|
-
originRunId,
|
|
2190
|
-
previousRunId: run.id,
|
|
2191
|
-
attempts: currentAttempt,
|
|
2192
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
2193
|
-
};
|
|
2194
|
-
}
|
|
2195
|
-
return {
|
|
2196
|
-
kind: "operator_review",
|
|
2197
|
-
issue,
|
|
2198
|
-
originRunId,
|
|
2199
|
-
previousRunId: run.id,
|
|
2200
|
-
attempts: currentAttempt,
|
|
2201
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
2202
|
-
};
|
|
2203
|
-
}
|
|
2204
|
-
const nextAttempt = currentAttempt + 1;
|
|
2205
|
-
const requestedAt = new Date(now.getTime() + passiveFollowupCooldownMs(nextAttempt));
|
|
2206
|
-
const contextSnapshot = buildPassiveFollowupContextSnapshot({
|
|
2207
|
-
run,
|
|
2208
|
-
issue,
|
|
2209
|
-
originRunId,
|
|
2210
|
-
attempt: nextAttempt,
|
|
2211
|
-
now,
|
|
2212
|
-
});
|
|
2213
|
-
const taskKey = deriveTaskKey(contextSnapshot, { issueId: issue.id });
|
|
2214
|
-
const sessionBefore = await resolveSessionBeforeForWakeup(agent, taskKey);
|
|
2215
|
-
const requestPayload = {
|
|
2216
|
-
issueId: issue.id,
|
|
2217
|
-
originRunId,
|
|
2218
|
-
previousRunId: run.id,
|
|
2219
|
-
attempt: nextAttempt,
|
|
2220
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
2221
|
-
};
|
|
2222
|
-
const wakeupRequest = await tx
|
|
2223
|
-
.insert(agentWakeupRequests)
|
|
2224
|
-
.values({
|
|
2225
|
-
orgId: run.orgId,
|
|
2226
|
-
agentId: run.agentId,
|
|
2227
|
-
source: "automation",
|
|
2228
|
-
triggerDetail: "system",
|
|
2229
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_REASON,
|
|
2230
|
-
payload: requestPayload,
|
|
2231
|
-
status: "queued",
|
|
2232
|
-
requestedByActorType: "system",
|
|
2233
|
-
requestedByActorId: "issue_closure_governance",
|
|
2234
|
-
idempotencyKey: `${ISSUE_PASSIVE_FOLLOWUP_REASON}:${run.id}`,
|
|
2235
|
-
requestedAt,
|
|
2236
|
-
updatedAt: now,
|
|
2237
|
-
})
|
|
2238
|
-
.returning()
|
|
2239
|
-
.then((rows) => rows[0]);
|
|
2240
|
-
const followupRun = await tx
|
|
2241
|
-
.insert(heartbeatRuns)
|
|
2242
|
-
.values({
|
|
2243
|
-
orgId: run.orgId,
|
|
2244
|
-
agentId: run.agentId,
|
|
2245
|
-
invocationSource: "automation",
|
|
2246
|
-
triggerDetail: "system",
|
|
2247
|
-
status: "queued",
|
|
2248
|
-
wakeupRequestId: wakeupRequest.id,
|
|
2249
|
-
contextSnapshot,
|
|
2250
|
-
sessionIdBefore: sessionBefore,
|
|
2251
|
-
updatedAt: now,
|
|
2252
|
-
})
|
|
2253
|
-
.returning()
|
|
2254
|
-
.then((rows) => rows[0]);
|
|
2255
|
-
await tx
|
|
2256
|
-
.update(agentWakeupRequests)
|
|
2257
|
-
.set({
|
|
2258
|
-
runId: followupRun.id,
|
|
2259
|
-
updatedAt: now,
|
|
2260
|
-
})
|
|
2261
|
-
.where(eq(agentWakeupRequests.id, wakeupRequest.id));
|
|
2262
|
-
await tx
|
|
2263
|
-
.update(issues)
|
|
2264
|
-
.set({
|
|
2265
|
-
executionRunId: followupRun.id,
|
|
2266
|
-
executionAgentNameKey: normalizeAgentNameKey(agent.name),
|
|
2267
|
-
executionLockedAt: now,
|
|
2268
|
-
updatedAt: now,
|
|
2269
|
-
})
|
|
2270
|
-
.where(eq(issues.id, issue.id));
|
|
2271
|
-
return {
|
|
2272
|
-
kind: "queued",
|
|
2273
|
-
run: followupRun,
|
|
2274
|
-
issue,
|
|
2275
|
-
originRunId,
|
|
2276
|
-
previousRunId: run.id,
|
|
2277
|
-
attempt: nextAttempt,
|
|
2278
|
-
requestedAt,
|
|
2279
|
-
};
|
|
2280
|
-
}
|
|
2281
|
-
async function countRunningRunsForAgent(agentId) {
|
|
2282
|
-
const [{ count }] = await db
|
|
2283
|
-
.select({ count: sql `count(*)` })
|
|
2284
|
-
.from(heartbeatRuns)
|
|
2285
|
-
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "running")));
|
|
2286
|
-
return Number(count ?? 0);
|
|
2287
|
-
}
|
|
2288
|
-
async function claimQueuedRun(run) {
|
|
2289
|
-
if (run.status !== "queued")
|
|
2290
|
-
return run;
|
|
2291
|
-
if (run.wakeupRequestId) {
|
|
2292
|
-
const wakeup = await db
|
|
2293
|
-
.select({ requestedAt: agentWakeupRequests.requestedAt })
|
|
2294
|
-
.from(agentWakeupRequests)
|
|
2295
|
-
.where(eq(agentWakeupRequests.id, run.wakeupRequestId))
|
|
2296
|
-
.then((rows) => rows[0] ?? null);
|
|
2297
|
-
if (wakeup && new Date(wakeup.requestedAt).getTime() > Date.now()) {
|
|
2298
|
-
return null;
|
|
579
|
+
async function countRunningRunsForAgent(agentId) {
|
|
580
|
+
const [{ count }] = await db
|
|
581
|
+
.select({ count: sql `count(*)` })
|
|
582
|
+
.from(heartbeatRuns)
|
|
583
|
+
.where(and(eq(heartbeatRuns.agentId, agentId), eq(heartbeatRuns.status, "running")));
|
|
584
|
+
return Number(count ?? 0);
|
|
585
|
+
}
|
|
586
|
+
async function claimQueuedRun(run) {
|
|
587
|
+
if (run.status !== "queued")
|
|
588
|
+
return run;
|
|
589
|
+
if (run.wakeupRequestId) {
|
|
590
|
+
const wakeup = await db
|
|
591
|
+
.select({ requestedAt: agentWakeupRequests.requestedAt })
|
|
592
|
+
.from(agentWakeupRequests)
|
|
593
|
+
.where(eq(agentWakeupRequests.id, run.wakeupRequestId))
|
|
594
|
+
.then((rows) => rows[0] ?? null);
|
|
595
|
+
if (wakeup && new Date(wakeup.requestedAt).getTime() > Date.now()) {
|
|
596
|
+
return null;
|
|
2299
597
|
}
|
|
2300
598
|
}
|
|
2301
599
|
const agent = await getAgent(run.agentId);
|
|
@@ -2603,2410 +901,20 @@ export function heartbeatService(db) {
|
|
|
2603
901
|
return claimedRuns;
|
|
2604
902
|
});
|
|
2605
903
|
}
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
activeRunExecutions.add(run.id);
|
|
2621
|
-
try {
|
|
2622
|
-
const agent = await getAgent(run.agentId);
|
|
2623
|
-
if (!agent) {
|
|
2624
|
-
await setRunStatus(runId, "failed", {
|
|
2625
|
-
error: "Agent not found",
|
|
2626
|
-
errorCode: "agent_not_found",
|
|
2627
|
-
finishedAt: new Date(),
|
|
2628
|
-
});
|
|
2629
|
-
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
2630
|
-
finishedAt: new Date(),
|
|
2631
|
-
error: "Agent not found",
|
|
2632
|
-
});
|
|
2633
|
-
const failedRun = await getRun(runId);
|
|
2634
|
-
if (failedRun)
|
|
2635
|
-
await releaseIssueExecutionAndPromote(failedRun);
|
|
2636
|
-
return;
|
|
2637
|
-
}
|
|
2638
|
-
const heartbeatObservationContext = buildHeartbeatObservabilityContext(run, {
|
|
2639
|
-
runtime: agent.agentRuntimeType,
|
|
2640
|
-
metadata: {
|
|
2641
|
-
agentName: agent.name,
|
|
2642
|
-
invocationSource: run.invocationSource,
|
|
2643
|
-
triggerDetail: run.triggerDetail,
|
|
2644
|
-
},
|
|
2645
|
-
});
|
|
2646
|
-
await withExecutionObservation(heartbeatObservationContext, {
|
|
2647
|
-
name: buildHeartbeatObservationName(run, agent.name),
|
|
2648
|
-
asType: "agent",
|
|
2649
|
-
input: {
|
|
2650
|
-
agentId: agent.id,
|
|
2651
|
-
agentName: agent.name,
|
|
2652
|
-
invocationSource: run.invocationSource,
|
|
2653
|
-
triggerDetail: run.triggerDetail,
|
|
2654
|
-
issueId: readNonEmptyString(parseObject(run.contextSnapshot).issueId),
|
|
2655
|
-
},
|
|
2656
|
-
}, async (observation) => {
|
|
2657
|
-
const executionTranscript = [];
|
|
2658
|
-
let stdoutTranscriptBuffer = "";
|
|
2659
|
-
let stderrTranscriptBuffer = "";
|
|
2660
|
-
let stdoutTranscriptParser = null;
|
|
2661
|
-
let transcriptFallbackResult = null;
|
|
2662
|
-
let modelTurnInput;
|
|
2663
|
-
let finalObservationOutput = null;
|
|
2664
|
-
let finalObservationStatus = run.status;
|
|
2665
|
-
let finalObservationSessionId = heartbeatObservationContext.sessionKey ?? null;
|
|
2666
|
-
const runtime = await ensureRuntimeState(agent);
|
|
2667
|
-
const context = parseObject(run.contextSnapshot);
|
|
2668
|
-
delete context.rudderGitIdentity;
|
|
2669
|
-
const taskKey = deriveTaskKey(context, null);
|
|
2670
|
-
const sessionCodec = getAgentRuntimeSessionCodec(agent.agentRuntimeType);
|
|
2671
|
-
const issueId = readNonEmptyString(context.issueId);
|
|
2672
|
-
const issueContext = issueId
|
|
2673
|
-
? await db
|
|
2674
|
-
.select({
|
|
2675
|
-
id: issues.id,
|
|
2676
|
-
identifier: issues.identifier,
|
|
2677
|
-
title: issues.title,
|
|
2678
|
-
description: issues.description,
|
|
2679
|
-
projectId: issues.projectId,
|
|
2680
|
-
projectWorkspaceId: issues.projectWorkspaceId,
|
|
2681
|
-
executionWorkspaceId: issues.executionWorkspaceId,
|
|
2682
|
-
executionWorkspacePreference: issues.executionWorkspacePreference,
|
|
2683
|
-
assigneeAgentId: issues.assigneeAgentId,
|
|
2684
|
-
assigneeAgentRuntimeOverrides: issues.assigneeAgentRuntimeOverrides,
|
|
2685
|
-
executionWorkspaceSettings: issues.executionWorkspaceSettings,
|
|
2686
|
-
})
|
|
2687
|
-
.from(issues)
|
|
2688
|
-
.where(and(eq(issues.id, issueId), eq(issues.orgId, agent.orgId)))
|
|
2689
|
-
.then((rows) => rows[0] ?? null)
|
|
2690
|
-
: null;
|
|
2691
|
-
const issueAssigneeOverrides = issueContext && issueContext.assigneeAgentId === agent.id
|
|
2692
|
-
? parseIssueAssigneeAgentRuntimeOverrides(issueContext.assigneeAgentRuntimeOverrides)
|
|
2693
|
-
: null;
|
|
2694
|
-
const issueExecutionWorkspaceSettings = parseIssueExecutionWorkspaceSettings(issueContext?.executionWorkspaceSettings);
|
|
2695
|
-
const contextProjectId = readNonEmptyString(context.projectId);
|
|
2696
|
-
const executionProjectId = issueContext?.projectId ?? contextProjectId;
|
|
2697
|
-
const projectExecutionWorkspacePolicy = executionProjectId
|
|
2698
|
-
? await db
|
|
2699
|
-
.select({ executionWorkspacePolicy: projects.executionWorkspacePolicy })
|
|
2700
|
-
.from(projects)
|
|
2701
|
-
.where(and(eq(projects.id, executionProjectId), eq(projects.orgId, agent.orgId)))
|
|
2702
|
-
.then((rows) => parseProjectExecutionWorkspacePolicy(rows[0]?.executionWorkspacePolicy))
|
|
2703
|
-
: null;
|
|
2704
|
-
const taskSession = taskKey
|
|
2705
|
-
? await getTaskSession(agent.orgId, agent.id, agent.agentRuntimeType, taskKey)
|
|
2706
|
-
: null;
|
|
2707
|
-
const resetTaskSession = shouldResetTaskSessionForWake(context);
|
|
2708
|
-
const sessionResetReason = describeSessionResetReason(context);
|
|
2709
|
-
const taskSessionForRun = resetTaskSession ? null : taskSession;
|
|
2710
|
-
const explicitResumeSessionParams = normalizeSessionParams(sessionCodec.deserialize(parseObject(context.resumeSessionParams)));
|
|
2711
|
-
const explicitResumeSessionDisplayId = truncateDisplayId(readNonEmptyString(context.resumeSessionDisplayId) ??
|
|
2712
|
-
(sessionCodec.getDisplayId ? sessionCodec.getDisplayId(explicitResumeSessionParams) : null) ??
|
|
2713
|
-
readNonEmptyString(explicitResumeSessionParams?.sessionId));
|
|
2714
|
-
const previousSessionParams = explicitResumeSessionParams ??
|
|
2715
|
-
(explicitResumeSessionDisplayId ? { sessionId: explicitResumeSessionDisplayId } : null) ??
|
|
2716
|
-
normalizeSessionParams(sessionCodec.deserialize(taskSessionForRun?.sessionParamsJson ?? null));
|
|
2717
|
-
const config = parseObject(agent.agentRuntimeConfig);
|
|
2718
|
-
const executionWorkspaceMode = resolveExecutionWorkspaceMode({
|
|
2719
|
-
projectPolicy: projectExecutionWorkspacePolicy,
|
|
2720
|
-
issueSettings: issueExecutionWorkspaceSettings,
|
|
2721
|
-
legacyUseProjectWorkspace: issueAssigneeOverrides?.useProjectWorkspace ?? null,
|
|
2722
|
-
});
|
|
2723
|
-
const resolvedWorkspace = await runContextSvc.resolveWorkspaceForRun(agent, context, previousSessionParams, { useProjectWorkspace: executionWorkspaceMode !== "agent_default" });
|
|
2724
|
-
const workspaceManagedConfig = buildExecutionWorkspaceAdapterConfig({
|
|
2725
|
-
agentConfig: config,
|
|
2726
|
-
projectPolicy: projectExecutionWorkspacePolicy,
|
|
2727
|
-
issueSettings: issueExecutionWorkspaceSettings,
|
|
2728
|
-
mode: executionWorkspaceMode,
|
|
2729
|
-
legacyUseProjectWorkspace: issueAssigneeOverrides?.useProjectWorkspace ?? null,
|
|
2730
|
-
});
|
|
2731
|
-
const mergedConfig = issueAssigneeOverrides?.agentRuntimeConfig
|
|
2732
|
-
? { ...workspaceManagedConfig, ...issueAssigneeOverrides.agentRuntimeConfig }
|
|
2733
|
-
: workspaceManagedConfig;
|
|
2734
|
-
const { resolvedConfig, runtimeConfig, runtimeSkillEntries, secretKeys } = await runContextSvc.prepareRuntimeConfig({
|
|
2735
|
-
scene: "heartbeat",
|
|
2736
|
-
agent,
|
|
2737
|
-
baseConfig: mergedConfig,
|
|
2738
|
-
});
|
|
2739
|
-
heartbeatObservationContext.metadata = {
|
|
2740
|
-
...(heartbeatObservationContext.metadata ?? {}),
|
|
2741
|
-
...buildHeartbeatRuntimeTraceMetadata({
|
|
2742
|
-
runtimeConfig,
|
|
2743
|
-
runtimeSkills: runtimeSkillEntries,
|
|
2744
|
-
}),
|
|
2745
|
-
};
|
|
2746
|
-
const issueRef = issueContext
|
|
2747
|
-
? {
|
|
2748
|
-
id: issueContext.id,
|
|
2749
|
-
identifier: issueContext.identifier,
|
|
2750
|
-
title: issueContext.title,
|
|
2751
|
-
projectId: issueContext.projectId,
|
|
2752
|
-
projectWorkspaceId: issueContext.projectWorkspaceId,
|
|
2753
|
-
executionWorkspaceId: issueContext.executionWorkspaceId,
|
|
2754
|
-
executionWorkspacePreference: issueContext.executionWorkspacePreference,
|
|
2755
|
-
}
|
|
2756
|
-
: null;
|
|
2757
|
-
const rootObservationInput = {
|
|
2758
|
-
agentId: agent.id,
|
|
2759
|
-
agentName: agent.name,
|
|
2760
|
-
invocationSource: run.invocationSource,
|
|
2761
|
-
triggerDetail: run.triggerDetail,
|
|
2762
|
-
issue: issueRef
|
|
2763
|
-
? {
|
|
2764
|
-
id: issueRef.id,
|
|
2765
|
-
identifier: issueRef.identifier ?? null,
|
|
2766
|
-
title: issueRef.title ?? null,
|
|
2767
|
-
}
|
|
2768
|
-
: null,
|
|
2769
|
-
};
|
|
2770
|
-
updateExecutionObservation(observation, heartbeatObservationContext, {
|
|
2771
|
-
input: rootObservationInput,
|
|
2772
|
-
});
|
|
2773
|
-
updateExecutionTraceIO(observation, { input: rootObservationInput });
|
|
2774
|
-
if (issueRef) {
|
|
2775
|
-
updateExecutionTraceName(observation, buildIssueRunTraceName({
|
|
2776
|
-
issueTitle: issueRef.title,
|
|
2777
|
-
issueId: issueRef.id,
|
|
2778
|
-
}));
|
|
2779
|
-
}
|
|
2780
|
-
const existingExecutionWorkspace = issueRef?.executionWorkspaceId ? await executionWorkspacesSvc.getById(issueRef.executionWorkspaceId) : null;
|
|
2781
|
-
const workspaceOperationRecorder = workspaceOperationsSvc.createRecorder({
|
|
2782
|
-
orgId: agent.orgId,
|
|
2783
|
-
heartbeatRunId: run.id,
|
|
2784
|
-
executionWorkspaceId: existingExecutionWorkspace?.id ?? null,
|
|
2785
|
-
});
|
|
2786
|
-
const executionWorkspace = await realizeExecutionWorkspace({
|
|
2787
|
-
base: {
|
|
2788
|
-
baseCwd: resolvedWorkspace.cwd,
|
|
2789
|
-
source: resolvedWorkspace.source,
|
|
2790
|
-
projectId: resolvedWorkspace.projectId,
|
|
2791
|
-
workspaceId: resolvedWorkspace.workspaceId,
|
|
2792
|
-
repoUrl: resolvedWorkspace.repoUrl,
|
|
2793
|
-
repoRef: resolvedWorkspace.repoRef,
|
|
2794
|
-
},
|
|
2795
|
-
config: runtimeConfig,
|
|
2796
|
-
issue: issueRef,
|
|
2797
|
-
agent: {
|
|
2798
|
-
id: agent.id,
|
|
2799
|
-
name: agent.name,
|
|
2800
|
-
orgId: agent.orgId,
|
|
2801
|
-
},
|
|
2802
|
-
recorder: workspaceOperationRecorder,
|
|
2803
|
-
});
|
|
2804
|
-
const resolvedProjectId = executionWorkspace.projectId ?? issueRef?.projectId ?? executionProjectId ?? null;
|
|
2805
|
-
const resolvedProjectWorkspaceId = issueRef?.projectWorkspaceId ?? resolvedWorkspace.workspaceId ?? null;
|
|
2806
|
-
const shouldReuseExisting = issueRef?.executionWorkspacePreference === "reuse_existing" &&
|
|
2807
|
-
existingExecutionWorkspace &&
|
|
2808
|
-
existingExecutionWorkspace.status !== "archived";
|
|
2809
|
-
let persistedExecutionWorkspace = null;
|
|
2810
|
-
try {
|
|
2811
|
-
persistedExecutionWorkspace = shouldReuseExisting && existingExecutionWorkspace
|
|
2812
|
-
? await executionWorkspacesSvc.update(existingExecutionWorkspace.id, {
|
|
2813
|
-
cwd: executionWorkspace.cwd,
|
|
2814
|
-
repoUrl: executionWorkspace.repoUrl,
|
|
2815
|
-
baseRef: executionWorkspace.repoRef,
|
|
2816
|
-
branchName: executionWorkspace.branchName,
|
|
2817
|
-
providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs",
|
|
2818
|
-
providerRef: executionWorkspace.worktreePath,
|
|
2819
|
-
status: "active",
|
|
2820
|
-
lastUsedAt: new Date(),
|
|
2821
|
-
metadata: {
|
|
2822
|
-
...(existingExecutionWorkspace.metadata ?? {}),
|
|
2823
|
-
source: executionWorkspace.source,
|
|
2824
|
-
createdByRuntime: executionWorkspace.created,
|
|
2825
|
-
},
|
|
2826
|
-
})
|
|
2827
|
-
: resolvedProjectId
|
|
2828
|
-
? await executionWorkspacesSvc.create({
|
|
2829
|
-
orgId: agent.orgId,
|
|
2830
|
-
projectId: resolvedProjectId,
|
|
2831
|
-
projectWorkspaceId: resolvedProjectWorkspaceId,
|
|
2832
|
-
sourceIssueId: issueRef?.id ?? null,
|
|
2833
|
-
mode: executionWorkspaceMode === "isolated_workspace"
|
|
2834
|
-
? "isolated_workspace"
|
|
2835
|
-
: executionWorkspaceMode === "operator_branch"
|
|
2836
|
-
? "operator_branch"
|
|
2837
|
-
: executionWorkspaceMode === "agent_default"
|
|
2838
|
-
? "adapter_managed"
|
|
2839
|
-
: "shared_workspace",
|
|
2840
|
-
strategyType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "project_primary",
|
|
2841
|
-
name: executionWorkspace.branchName ?? issueRef?.identifier ?? `workspace-${agent.id.slice(0, 8)}`,
|
|
2842
|
-
status: "active",
|
|
2843
|
-
cwd: executionWorkspace.cwd,
|
|
2844
|
-
repoUrl: executionWorkspace.repoUrl,
|
|
2845
|
-
baseRef: executionWorkspace.repoRef,
|
|
2846
|
-
branchName: executionWorkspace.branchName,
|
|
2847
|
-
providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs",
|
|
2848
|
-
providerRef: executionWorkspace.worktreePath,
|
|
2849
|
-
lastUsedAt: new Date(),
|
|
2850
|
-
openedAt: new Date(),
|
|
2851
|
-
metadata: {
|
|
2852
|
-
source: executionWorkspace.source,
|
|
2853
|
-
createdByRuntime: executionWorkspace.created,
|
|
2854
|
-
},
|
|
2855
|
-
})
|
|
2856
|
-
: null;
|
|
2857
|
-
}
|
|
2858
|
-
catch (error) {
|
|
2859
|
-
if (executionWorkspace.created) {
|
|
2860
|
-
try {
|
|
2861
|
-
await cleanupExecutionWorkspaceArtifacts({
|
|
2862
|
-
workspace: {
|
|
2863
|
-
id: existingExecutionWorkspace?.id ?? `transient-${run.id}`,
|
|
2864
|
-
cwd: executionWorkspace.cwd,
|
|
2865
|
-
providerType: executionWorkspace.strategy === "git_worktree" ? "git_worktree" : "local_fs",
|
|
2866
|
-
providerRef: executionWorkspace.worktreePath,
|
|
2867
|
-
branchName: executionWorkspace.branchName,
|
|
2868
|
-
repoUrl: executionWorkspace.repoUrl,
|
|
2869
|
-
baseRef: executionWorkspace.repoRef,
|
|
2870
|
-
projectId: resolvedProjectId,
|
|
2871
|
-
projectWorkspaceId: resolvedProjectWorkspaceId,
|
|
2872
|
-
sourceIssueId: issueRef?.id ?? null,
|
|
2873
|
-
metadata: {
|
|
2874
|
-
createdByRuntime: true,
|
|
2875
|
-
source: executionWorkspace.source,
|
|
2876
|
-
},
|
|
2877
|
-
},
|
|
2878
|
-
projectWorkspace: {
|
|
2879
|
-
cwd: resolvedWorkspace.cwd,
|
|
2880
|
-
cleanupCommand: null,
|
|
2881
|
-
},
|
|
2882
|
-
teardownCommand: projectExecutionWorkspacePolicy?.workspaceStrategy?.teardownCommand ?? null,
|
|
2883
|
-
recorder: workspaceOperationRecorder,
|
|
2884
|
-
});
|
|
2885
|
-
}
|
|
2886
|
-
catch (cleanupError) {
|
|
2887
|
-
logger.warn({
|
|
2888
|
-
runId: run.id,
|
|
2889
|
-
issueId,
|
|
2890
|
-
executionWorkspaceCwd: executionWorkspace.cwd,
|
|
2891
|
-
cleanupError: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
2892
|
-
}, "Failed to cleanup realized execution workspace after persistence failure");
|
|
2893
|
-
}
|
|
2894
|
-
}
|
|
2895
|
-
throw error;
|
|
2896
|
-
}
|
|
2897
|
-
await workspaceOperationRecorder.attachExecutionWorkspaceId(persistedExecutionWorkspace?.id ?? null);
|
|
2898
|
-
if (existingExecutionWorkspace &&
|
|
2899
|
-
persistedExecutionWorkspace &&
|
|
2900
|
-
existingExecutionWorkspace.id !== persistedExecutionWorkspace.id &&
|
|
2901
|
-
existingExecutionWorkspace.status === "active") {
|
|
2902
|
-
await executionWorkspacesSvc.update(existingExecutionWorkspace.id, {
|
|
2903
|
-
status: "idle",
|
|
2904
|
-
cleanupReason: null,
|
|
2905
|
-
});
|
|
2906
|
-
}
|
|
2907
|
-
if (issueId && persistedExecutionWorkspace) {
|
|
2908
|
-
const nextIssueWorkspaceMode = issueExecutionWorkspaceModeForPersistedWorkspace(persistedExecutionWorkspace.mode);
|
|
2909
|
-
const shouldSwitchIssueToExistingWorkspace = issueRef?.executionWorkspacePreference === "reuse_existing" ||
|
|
2910
|
-
executionWorkspaceMode === "isolated_workspace" ||
|
|
2911
|
-
executionWorkspaceMode === "operator_branch";
|
|
2912
|
-
const nextIssuePatch = {};
|
|
2913
|
-
if (issueRef?.executionWorkspaceId !== persistedExecutionWorkspace.id) {
|
|
2914
|
-
nextIssuePatch.executionWorkspaceId = persistedExecutionWorkspace.id;
|
|
2915
|
-
}
|
|
2916
|
-
if (resolvedProjectWorkspaceId && issueRef?.projectWorkspaceId !== resolvedProjectWorkspaceId) {
|
|
2917
|
-
nextIssuePatch.projectWorkspaceId = resolvedProjectWorkspaceId;
|
|
2918
|
-
}
|
|
2919
|
-
if (shouldSwitchIssueToExistingWorkspace) {
|
|
2920
|
-
nextIssuePatch.executionWorkspacePreference = "reuse_existing";
|
|
2921
|
-
nextIssuePatch.executionWorkspaceSettings = {
|
|
2922
|
-
...(issueExecutionWorkspaceSettings ?? {}),
|
|
2923
|
-
mode: nextIssueWorkspaceMode,
|
|
2924
|
-
};
|
|
2925
|
-
}
|
|
2926
|
-
if (Object.keys(nextIssuePatch).length > 0) {
|
|
2927
|
-
await issuesSvc.update(issueId, nextIssuePatch);
|
|
2928
|
-
}
|
|
2929
|
-
}
|
|
2930
|
-
if (persistedExecutionWorkspace) {
|
|
2931
|
-
context.executionWorkspaceId = persistedExecutionWorkspace.id;
|
|
2932
|
-
await db
|
|
2933
|
-
.update(heartbeatRuns)
|
|
2934
|
-
.set({
|
|
2935
|
-
contextSnapshot: context,
|
|
2936
|
-
updatedAt: new Date(),
|
|
2937
|
-
})
|
|
2938
|
-
.where(eq(heartbeatRuns.id, run.id));
|
|
2939
|
-
}
|
|
2940
|
-
const runtimeSessionResolution = resolveRuntimeSessionParamsForWorkspace({
|
|
2941
|
-
orgId: agent.orgId,
|
|
2942
|
-
agent,
|
|
2943
|
-
previousSessionParams,
|
|
2944
|
-
resolvedWorkspace: {
|
|
2945
|
-
...resolvedWorkspace,
|
|
2946
|
-
cwd: resolveDefaultAgentWorkspaceDir(agent.orgId, agent),
|
|
2947
|
-
source: "agent_home",
|
|
2948
|
-
},
|
|
2949
|
-
});
|
|
2950
|
-
const runtimeSessionParams = runtimeSessionResolution.sessionParams;
|
|
2951
|
-
const runtimeWorkspaceWarnings = [
|
|
2952
|
-
...resolvedWorkspace.warnings,
|
|
2953
|
-
...executionWorkspace.warnings,
|
|
2954
|
-
...(runtimeSessionResolution.warning ? [runtimeSessionResolution.warning] : []),
|
|
2955
|
-
...(resetTaskSession && sessionResetReason
|
|
2956
|
-
? [
|
|
2957
|
-
taskKey
|
|
2958
|
-
? `Skipping saved session resume for task "${taskKey}" because ${sessionResetReason}.`
|
|
2959
|
-
: `Skipping saved session resume because ${sessionResetReason}.`,
|
|
2960
|
-
]
|
|
2961
|
-
: []),
|
|
2962
|
-
];
|
|
2963
|
-
const runtimeSceneContext = await runContextSvc.buildSceneContext({
|
|
2964
|
-
scene: "heartbeat",
|
|
2965
|
-
agent,
|
|
2966
|
-
resolvedWorkspace,
|
|
2967
|
-
runtimeConfig,
|
|
2968
|
-
executionWorkspaceMode,
|
|
2969
|
-
executionWorkspace: {
|
|
2970
|
-
cwd: executionWorkspace.cwd,
|
|
2971
|
-
source: executionWorkspace.source,
|
|
2972
|
-
strategy: executionWorkspace.strategy,
|
|
2973
|
-
projectId: executionWorkspace.projectId,
|
|
2974
|
-
workspaceId: executionWorkspace.workspaceId,
|
|
2975
|
-
repoUrl: executionWorkspace.repoUrl,
|
|
2976
|
-
repoRef: executionWorkspace.repoRef,
|
|
2977
|
-
branchName: executionWorkspace.branchName,
|
|
2978
|
-
worktreePath: executionWorkspace.worktreePath,
|
|
2979
|
-
},
|
|
2980
|
-
});
|
|
2981
|
-
context.rudderScene = runtimeSceneContext.rudderScene;
|
|
2982
|
-
context.rudderWorkspace = runtimeSceneContext.rudderWorkspace;
|
|
2983
|
-
context.rudderWorkspaces = runtimeSceneContext.rudderWorkspaces;
|
|
2984
|
-
if (runtimeSceneContext.rudderRuntimeServiceIntents) {
|
|
2985
|
-
context.rudderRuntimeServiceIntents = runtimeSceneContext.rudderRuntimeServiceIntents;
|
|
2986
|
-
}
|
|
2987
|
-
else {
|
|
2988
|
-
delete context.rudderRuntimeServiceIntents;
|
|
2989
|
-
}
|
|
2990
|
-
if (executionWorkspace.projectId && !readNonEmptyString(context.projectId)) {
|
|
2991
|
-
context.projectId = executionWorkspace.projectId;
|
|
2992
|
-
}
|
|
2993
|
-
if (issueContext) {
|
|
2994
|
-
const issueDocumentPayload = await documentsSvc.getIssueDocumentPayload({
|
|
2995
|
-
id: issueContext.id,
|
|
2996
|
-
description: issueContext.description,
|
|
2997
|
-
});
|
|
2998
|
-
const issueDocumentsPrompt = buildIssueDocumentsPrompt(issueDocumentPayload);
|
|
2999
|
-
if (issueDocumentsPrompt) {
|
|
3000
|
-
context.issueDocumentsPrompt = issueDocumentsPrompt;
|
|
3001
|
-
}
|
|
3002
|
-
else {
|
|
3003
|
-
delete context.issueDocumentsPrompt;
|
|
3004
|
-
}
|
|
3005
|
-
}
|
|
3006
|
-
const runtimeSessionFallback = taskKey || resetTaskSession ? null : runtime.sessionId;
|
|
3007
|
-
let previousSessionDisplayId = truncateDisplayId(explicitResumeSessionDisplayId ??
|
|
3008
|
-
taskSessionForRun?.sessionDisplayId ??
|
|
3009
|
-
(sessionCodec.getDisplayId ? sessionCodec.getDisplayId(runtimeSessionParams) : null) ??
|
|
3010
|
-
readNonEmptyString(runtimeSessionParams?.sessionId) ??
|
|
3011
|
-
runtimeSessionFallback);
|
|
3012
|
-
let runtimeSessionIdForAdapter = readNonEmptyString(runtimeSessionParams?.sessionId) ?? runtimeSessionFallback;
|
|
3013
|
-
let runtimeSessionParamsForAdapter = runtimeSessionParams;
|
|
3014
|
-
const sessionCompaction = await evaluateSessionCompaction({
|
|
3015
|
-
agent,
|
|
3016
|
-
sessionId: previousSessionDisplayId ?? runtimeSessionIdForAdapter,
|
|
3017
|
-
issueId,
|
|
3018
|
-
});
|
|
3019
|
-
if (sessionCompaction.rotate) {
|
|
3020
|
-
context.rudderSessionHandoffMarkdown = sessionCompaction.handoffMarkdown;
|
|
3021
|
-
context.rudderSessionRotationReason = sessionCompaction.reason;
|
|
3022
|
-
context.rudderPreviousSessionId = previousSessionDisplayId ?? runtimeSessionIdForAdapter;
|
|
3023
|
-
runtimeSessionIdForAdapter = null;
|
|
3024
|
-
runtimeSessionParamsForAdapter = null;
|
|
3025
|
-
previousSessionDisplayId = null;
|
|
3026
|
-
if (sessionCompaction.reason) {
|
|
3027
|
-
runtimeWorkspaceWarnings.push(`Starting a fresh session because ${sessionCompaction.reason}.`);
|
|
3028
|
-
}
|
|
3029
|
-
}
|
|
3030
|
-
else {
|
|
3031
|
-
delete context.rudderSessionHandoffMarkdown;
|
|
3032
|
-
delete context.rudderSessionRotationReason;
|
|
3033
|
-
delete context.rudderPreviousSessionId;
|
|
3034
|
-
}
|
|
3035
|
-
const runtimeForAdapter = {
|
|
3036
|
-
sessionId: runtimeSessionIdForAdapter,
|
|
3037
|
-
sessionParams: runtimeSessionParamsForAdapter,
|
|
3038
|
-
sessionDisplayId: previousSessionDisplayId,
|
|
3039
|
-
taskKey,
|
|
3040
|
-
};
|
|
3041
|
-
let seq = 1;
|
|
3042
|
-
let handle = null;
|
|
3043
|
-
let stdoutExcerpt = "";
|
|
3044
|
-
let stderrExcerpt = "";
|
|
3045
|
-
try {
|
|
3046
|
-
await preflightManagedAgentWorkspace({
|
|
3047
|
-
agentHome: readNonEmptyString(runtimeSceneContext.rudderWorkspace.agentHome) ?? "",
|
|
3048
|
-
instructionsDir: readNonEmptyString(runtimeSceneContext.rudderWorkspace.instructionsDir) ?? "",
|
|
3049
|
-
memoryDir: readNonEmptyString(runtimeSceneContext.rudderWorkspace.memoryDir) ?? "",
|
|
3050
|
-
lifeDir: readNonEmptyString(runtimeSceneContext.rudderWorkspace.lifeDir) ?? "",
|
|
3051
|
-
skillsDir: readNonEmptyString(runtimeSceneContext.rudderWorkspace.agentSkillsDir) ?? "",
|
|
3052
|
-
});
|
|
3053
|
-
const startedAt = run.startedAt ?? new Date();
|
|
3054
|
-
const runningWithSession = await db
|
|
3055
|
-
.update(heartbeatRuns)
|
|
3056
|
-
.set({
|
|
3057
|
-
startedAt,
|
|
3058
|
-
sessionIdBefore: runtimeForAdapter.sessionDisplayId ?? runtimeForAdapter.sessionId,
|
|
3059
|
-
contextSnapshot: context,
|
|
3060
|
-
updatedAt: new Date(),
|
|
3061
|
-
})
|
|
3062
|
-
.where(eq(heartbeatRuns.id, run.id))
|
|
3063
|
-
.returning()
|
|
3064
|
-
.then((rows) => rows[0] ?? null);
|
|
3065
|
-
if (runningWithSession)
|
|
3066
|
-
run = runningWithSession;
|
|
3067
|
-
const runningAgent = await db
|
|
3068
|
-
.update(agents)
|
|
3069
|
-
.set({ status: "running", updatedAt: new Date() })
|
|
3070
|
-
.where(eq(agents.id, agent.id))
|
|
3071
|
-
.returning()
|
|
3072
|
-
.then((rows) => rows[0] ?? null);
|
|
3073
|
-
if (runningAgent) {
|
|
3074
|
-
publishLiveEvent({
|
|
3075
|
-
orgId: runningAgent.orgId,
|
|
3076
|
-
type: "agent.status",
|
|
3077
|
-
payload: {
|
|
3078
|
-
agentId: runningAgent.id,
|
|
3079
|
-
status: runningAgent.status,
|
|
3080
|
-
outcome: "running",
|
|
3081
|
-
},
|
|
3082
|
-
});
|
|
3083
|
-
}
|
|
3084
|
-
const currentRun = run;
|
|
3085
|
-
await appendRunEvent(currentRun, seq++, {
|
|
3086
|
-
eventType: "lifecycle",
|
|
3087
|
-
stream: "system",
|
|
3088
|
-
level: "info",
|
|
3089
|
-
message: "run started",
|
|
3090
|
-
});
|
|
3091
|
-
handle = await runLogStore.begin({
|
|
3092
|
-
orgId: run.orgId,
|
|
3093
|
-
agentId: run.agentId,
|
|
3094
|
-
runId,
|
|
3095
|
-
});
|
|
3096
|
-
await db
|
|
3097
|
-
.update(heartbeatRuns)
|
|
3098
|
-
.set({
|
|
3099
|
-
logStore: handle.store,
|
|
3100
|
-
logRef: handle.logRef,
|
|
3101
|
-
updatedAt: new Date(),
|
|
3102
|
-
})
|
|
3103
|
-
.where(eq(heartbeatRuns.id, runId));
|
|
3104
|
-
const adapter = getServerAdapter(agent.agentRuntimeType);
|
|
3105
|
-
stdoutTranscriptParser = adapter.parseStdoutLine ?? null;
|
|
3106
|
-
const currentUserRedactionOptions = await getCurrentUserRedactionOptions();
|
|
3107
|
-
const onLog = async (stream, chunk) => {
|
|
3108
|
-
const sanitizedChunk = redactCurrentUserText(chunk, currentUserRedactionOptions);
|
|
3109
|
-
if (stream === "stdout")
|
|
3110
|
-
stdoutExcerpt = appendExcerpt(stdoutExcerpt, sanitizedChunk);
|
|
3111
|
-
if (stream === "stderr")
|
|
3112
|
-
stderrExcerpt = appendExcerpt(stderrExcerpt, sanitizedChunk);
|
|
3113
|
-
const ts = new Date().toISOString();
|
|
3114
|
-
if (handle) {
|
|
3115
|
-
await runLogStore.append(handle, {
|
|
3116
|
-
stream,
|
|
3117
|
-
chunk: sanitizedChunk,
|
|
3118
|
-
ts,
|
|
3119
|
-
});
|
|
3120
|
-
}
|
|
3121
|
-
const payloadChunk = sanitizedChunk.length > MAX_LIVE_LOG_CHUNK_BYTES
|
|
3122
|
-
? sanitizedChunk.slice(sanitizedChunk.length - MAX_LIVE_LOG_CHUNK_BYTES)
|
|
3123
|
-
: sanitizedChunk;
|
|
3124
|
-
publishLiveEvent({
|
|
3125
|
-
orgId: run.orgId,
|
|
3126
|
-
type: "heartbeat.run.log",
|
|
3127
|
-
payload: {
|
|
3128
|
-
runId: run.id,
|
|
3129
|
-
agentId: run.agentId,
|
|
3130
|
-
ts,
|
|
3131
|
-
stream,
|
|
3132
|
-
chunk: payloadChunk,
|
|
3133
|
-
truncated: payloadChunk.length !== sanitizedChunk.length,
|
|
3134
|
-
},
|
|
3135
|
-
});
|
|
3136
|
-
if (stream === "stdout") {
|
|
3137
|
-
stdoutTranscriptBuffer = appendTranscriptEntriesFromChunk({
|
|
3138
|
-
buffer: stdoutTranscriptBuffer,
|
|
3139
|
-
chunk: sanitizedChunk,
|
|
3140
|
-
transcript: executionTranscript,
|
|
3141
|
-
parser: stdoutTranscriptParser,
|
|
3142
|
-
kind: "stdout",
|
|
3143
|
-
});
|
|
3144
|
-
return;
|
|
3145
|
-
}
|
|
3146
|
-
stderrTranscriptBuffer = appendTranscriptEntriesFromChunk({
|
|
3147
|
-
buffer: stderrTranscriptBuffer,
|
|
3148
|
-
chunk: sanitizedChunk,
|
|
3149
|
-
transcript: executionTranscript,
|
|
3150
|
-
kind: "stderr",
|
|
3151
|
-
});
|
|
3152
|
-
};
|
|
3153
|
-
for (const warning of runtimeWorkspaceWarnings) {
|
|
3154
|
-
const logEntry = formatRuntimeWorkspaceWarningLog(warning);
|
|
3155
|
-
await onLog(logEntry.stream, logEntry.chunk);
|
|
3156
|
-
}
|
|
3157
|
-
const adapterEnv = Object.fromEntries(Object.entries(parseObject(resolvedConfig.env)).filter((entry) => typeof entry[0] === "string" && typeof entry[1] === "string"));
|
|
3158
|
-
const runtimeServices = await ensureRuntimeServicesForRun({
|
|
3159
|
-
db,
|
|
3160
|
-
runId: run.id,
|
|
3161
|
-
agent: {
|
|
3162
|
-
id: agent.id,
|
|
3163
|
-
name: agent.name,
|
|
3164
|
-
orgId: agent.orgId,
|
|
3165
|
-
},
|
|
3166
|
-
issue: issueRef,
|
|
3167
|
-
workspace: executionWorkspace,
|
|
3168
|
-
executionWorkspaceId: persistedExecutionWorkspace?.id ?? issueRef?.executionWorkspaceId ?? null,
|
|
3169
|
-
config: resolvedConfig,
|
|
3170
|
-
adapterEnv,
|
|
3171
|
-
onLog,
|
|
3172
|
-
});
|
|
3173
|
-
if (runtimeServices.length > 0) {
|
|
3174
|
-
context.rudderRuntimeServices = runtimeServices;
|
|
3175
|
-
context.rudderRuntimePrimaryUrl =
|
|
3176
|
-
runtimeServices.find((service) => readNonEmptyString(service.url))?.url ?? null;
|
|
3177
|
-
await db
|
|
3178
|
-
.update(heartbeatRuns)
|
|
3179
|
-
.set({
|
|
3180
|
-
contextSnapshot: context,
|
|
3181
|
-
updatedAt: new Date(),
|
|
3182
|
-
})
|
|
3183
|
-
.where(eq(heartbeatRuns.id, run.id));
|
|
3184
|
-
}
|
|
3185
|
-
if (issueId && (executionWorkspace.created || runtimeServices.some((service) => !service.reused))) {
|
|
3186
|
-
try {
|
|
3187
|
-
await issuesSvc.addComment(issueId, buildWorkspaceReadyComment({
|
|
3188
|
-
workspace: executionWorkspace,
|
|
3189
|
-
runtimeServices,
|
|
3190
|
-
}), { agentId: agent.id });
|
|
3191
|
-
}
|
|
3192
|
-
catch (err) {
|
|
3193
|
-
await onLog("stderr", `[rudder] Failed to post workspace-ready comment: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
3194
|
-
}
|
|
3195
|
-
}
|
|
3196
|
-
const onAdapterMeta = async (meta) => {
|
|
3197
|
-
if (meta.env && secretKeys.size > 0) {
|
|
3198
|
-
for (const key of secretKeys) {
|
|
3199
|
-
if (key in meta.env)
|
|
3200
|
-
meta.env[key] = "***REDACTED***";
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
modelTurnInput = meta.prompt;
|
|
3204
|
-
heartbeatObservationContext.metadata = {
|
|
3205
|
-
...(heartbeatObservationContext.metadata ?? {}),
|
|
3206
|
-
...buildHeartbeatRuntimeTraceMetadata({
|
|
3207
|
-
runtimeConfig,
|
|
3208
|
-
runtimeSkills: runtimeSkillEntries,
|
|
3209
|
-
adapterMeta: meta,
|
|
3210
|
-
}),
|
|
3211
|
-
};
|
|
3212
|
-
updateExecutionObservation(observation, heartbeatObservationContext, {
|
|
3213
|
-
input: rootObservationInput,
|
|
3214
|
-
});
|
|
3215
|
-
await appendRunEvent(currentRun, seq++, {
|
|
3216
|
-
eventType: "adapter.invoke",
|
|
3217
|
-
stream: "system",
|
|
3218
|
-
level: "info",
|
|
3219
|
-
message: "adapter invocation",
|
|
3220
|
-
payload: buildHeartbeatAdapterInvokePayload({
|
|
3221
|
-
meta,
|
|
3222
|
-
runtimeSkills: runtimeSkillEntries,
|
|
3223
|
-
}),
|
|
3224
|
-
});
|
|
3225
|
-
};
|
|
3226
|
-
const authToken = adapter.supportsLocalAgentJwt
|
|
3227
|
-
? createLocalAgentJwt(agent.id, agent.orgId, agent.agentRuntimeType, run.id)
|
|
3228
|
-
: null;
|
|
3229
|
-
if (adapter.supportsLocalAgentJwt && !authToken) {
|
|
3230
|
-
logger.warn({
|
|
3231
|
-
orgId: agent.orgId,
|
|
3232
|
-
agentId: agent.id,
|
|
3233
|
-
runId: run.id,
|
|
3234
|
-
agentRuntimeType: agent.agentRuntimeType,
|
|
3235
|
-
}, "local agent jwt secret missing or invalid; running without injected RUDDER_API_KEY");
|
|
3236
|
-
}
|
|
3237
|
-
const adapterResult = await executeAdapterWithModelFallbacks(adapter, {
|
|
3238
|
-
runId: run.id,
|
|
3239
|
-
agent,
|
|
3240
|
-
runtime: runtimeForAdapter,
|
|
3241
|
-
config: runtimeConfig,
|
|
3242
|
-
context,
|
|
3243
|
-
onLog,
|
|
3244
|
-
onMeta: onAdapterMeta,
|
|
3245
|
-
onSpawn: async (meta) => {
|
|
3246
|
-
await persistRunProcessMetadata(run.id, meta);
|
|
3247
|
-
},
|
|
3248
|
-
authToken: authToken ?? undefined,
|
|
3249
|
-
}, {
|
|
3250
|
-
resolveAdapter: findServerAdapter,
|
|
3251
|
-
createAuthToken: (agentRuntimeType) => createLocalAgentJwt(agent.id, agent.orgId, agentRuntimeType, run.id) ?? undefined,
|
|
3252
|
-
onAttemptStart: (_attempt, attemptAdapter) => {
|
|
3253
|
-
stdoutTranscriptParser = attemptAdapter.parseStdoutLine ?? null;
|
|
3254
|
-
},
|
|
3255
|
-
});
|
|
3256
|
-
const adapterManagedRuntimeServices = adapterResult.runtimeServices
|
|
3257
|
-
? await persistAdapterManagedRuntimeServices({
|
|
3258
|
-
db,
|
|
3259
|
-
agentRuntimeType: agent.agentRuntimeType,
|
|
3260
|
-
runId: run.id,
|
|
3261
|
-
agent: {
|
|
3262
|
-
id: agent.id,
|
|
3263
|
-
name: agent.name,
|
|
3264
|
-
orgId: agent.orgId,
|
|
3265
|
-
},
|
|
3266
|
-
issue: issueRef,
|
|
3267
|
-
workspace: executionWorkspace,
|
|
3268
|
-
reports: adapterResult.runtimeServices,
|
|
3269
|
-
})
|
|
3270
|
-
: [];
|
|
3271
|
-
if (adapterManagedRuntimeServices.length > 0) {
|
|
3272
|
-
const combinedRuntimeServices = [
|
|
3273
|
-
...runtimeServices,
|
|
3274
|
-
...adapterManagedRuntimeServices,
|
|
3275
|
-
];
|
|
3276
|
-
context.rudderRuntimeServices = combinedRuntimeServices;
|
|
3277
|
-
context.rudderRuntimePrimaryUrl =
|
|
3278
|
-
combinedRuntimeServices.find((service) => readNonEmptyString(service.url))?.url ?? null;
|
|
3279
|
-
await db
|
|
3280
|
-
.update(heartbeatRuns)
|
|
3281
|
-
.set({
|
|
3282
|
-
contextSnapshot: context,
|
|
3283
|
-
updatedAt: new Date(),
|
|
3284
|
-
})
|
|
3285
|
-
.where(eq(heartbeatRuns.id, run.id));
|
|
3286
|
-
if (issueId) {
|
|
3287
|
-
try {
|
|
3288
|
-
await issuesSvc.addComment(issueId, buildWorkspaceReadyComment({
|
|
3289
|
-
workspace: executionWorkspace,
|
|
3290
|
-
runtimeServices: adapterManagedRuntimeServices,
|
|
3291
|
-
}), { agentId: agent.id });
|
|
3292
|
-
}
|
|
3293
|
-
catch (err) {
|
|
3294
|
-
await onLog("stderr", `[rudder] Failed to post adapter-managed runtime comment: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
3295
|
-
}
|
|
3296
|
-
}
|
|
3297
|
-
}
|
|
3298
|
-
const nextSessionState = resolveNextSessionState({
|
|
3299
|
-
codec: sessionCodec,
|
|
3300
|
-
adapterResult,
|
|
3301
|
-
previousParams: previousSessionParams,
|
|
3302
|
-
previousDisplayId: runtimeForAdapter.sessionDisplayId,
|
|
3303
|
-
previousLegacySessionId: runtimeForAdapter.sessionId,
|
|
3304
|
-
});
|
|
3305
|
-
const rawUsage = normalizeUsageTotals(adapterResult.usage);
|
|
3306
|
-
const sessionUsageResolution = await resolveNormalizedUsageForSession({
|
|
3307
|
-
agentId: agent.id,
|
|
3308
|
-
runId: run.id,
|
|
3309
|
-
sessionId: nextSessionState.displayId ?? nextSessionState.legacySessionId,
|
|
3310
|
-
rawUsage,
|
|
3311
|
-
});
|
|
3312
|
-
const normalizedUsage = sessionUsageResolution.normalizedUsage;
|
|
3313
|
-
let outcome;
|
|
3314
|
-
const latestRun = await getRun(run.id);
|
|
3315
|
-
if (latestRun?.status === "cancelled") {
|
|
3316
|
-
outcome = "cancelled";
|
|
3317
|
-
}
|
|
3318
|
-
else if (adapterResult.timedOut) {
|
|
3319
|
-
outcome = "timed_out";
|
|
3320
|
-
}
|
|
3321
|
-
else if ((adapterResult.exitCode ?? 0) === 0 && !adapterResult.errorMessage) {
|
|
3322
|
-
outcome = "succeeded";
|
|
3323
|
-
}
|
|
3324
|
-
else {
|
|
3325
|
-
outcome = "failed";
|
|
3326
|
-
}
|
|
3327
|
-
let logSummary = null;
|
|
3328
|
-
if (handle) {
|
|
3329
|
-
logSummary = await runLogStore.finalize(handle);
|
|
3330
|
-
}
|
|
3331
|
-
const status = outcome === "succeeded"
|
|
3332
|
-
? "succeeded"
|
|
3333
|
-
: outcome === "cancelled"
|
|
3334
|
-
? "cancelled"
|
|
3335
|
-
: outcome === "timed_out"
|
|
3336
|
-
? "timed_out"
|
|
3337
|
-
: "failed";
|
|
3338
|
-
heartbeatObservationContext.status = status;
|
|
3339
|
-
finalObservationStatus = status;
|
|
3340
|
-
finalObservationSessionId = nextSessionState.displayId ?? nextSessionState.legacySessionId ?? finalObservationSessionId;
|
|
3341
|
-
const adapterResultSummary = summarizeHeartbeatRunResultJson(adapterResult.resultJson);
|
|
3342
|
-
transcriptFallbackResult = {
|
|
3343
|
-
ts: new Date().toISOString(),
|
|
3344
|
-
model: readNonEmptyString(adapterResult.model),
|
|
3345
|
-
output: readNonEmptyString(adapterResult.summary)
|
|
3346
|
-
?? readNonEmptyString(adapterResultSummary?.result)
|
|
3347
|
-
?? readNonEmptyString(adapterResultSummary?.summary)
|
|
3348
|
-
?? readNonEmptyString(adapterResultSummary?.message)
|
|
3349
|
-
?? null,
|
|
3350
|
-
usage: adapterResult.usage ?? null,
|
|
3351
|
-
costUsd: typeof adapterResult.costUsd === "number" ? adapterResult.costUsd : null,
|
|
3352
|
-
subtype: status,
|
|
3353
|
-
isError: outcome !== "succeeded",
|
|
3354
|
-
errors: adapterResult.errorMessage ? [adapterResult.errorMessage] : [],
|
|
3355
|
-
};
|
|
3356
|
-
const usageJson = normalizedUsage || adapterResult.costUsd != null
|
|
3357
|
-
? {
|
|
3358
|
-
...(normalizedUsage ?? {}),
|
|
3359
|
-
...(rawUsage ? {
|
|
3360
|
-
rawInputTokens: rawUsage.inputTokens,
|
|
3361
|
-
rawCachedInputTokens: rawUsage.cachedInputTokens,
|
|
3362
|
-
rawOutputTokens: rawUsage.outputTokens,
|
|
3363
|
-
} : {}),
|
|
3364
|
-
...(sessionUsageResolution.derivedFromSessionTotals ? { usageSource: "session_delta" } : {}),
|
|
3365
|
-
...((nextSessionState.displayId ?? nextSessionState.legacySessionId)
|
|
3366
|
-
? { persistedSessionId: nextSessionState.displayId ?? nextSessionState.legacySessionId }
|
|
3367
|
-
: {}),
|
|
3368
|
-
sessionReused: runtimeForAdapter.sessionId != null || runtimeForAdapter.sessionDisplayId != null,
|
|
3369
|
-
taskSessionReused: taskSessionForRun != null,
|
|
3370
|
-
freshSession: runtimeForAdapter.sessionId == null && runtimeForAdapter.sessionDisplayId == null,
|
|
3371
|
-
sessionRotated: sessionCompaction.rotate,
|
|
3372
|
-
sessionRotationReason: sessionCompaction.reason,
|
|
3373
|
-
provider: readNonEmptyString(adapterResult.provider) ?? "unknown",
|
|
3374
|
-
biller: resolveLedgerBiller(adapterResult),
|
|
3375
|
-
model: readNonEmptyString(adapterResult.model) ?? "unknown",
|
|
3376
|
-
...(adapterResult.costUsd != null ? { costUsd: adapterResult.costUsd } : {}),
|
|
3377
|
-
billingType: normalizeLedgerBillingType(adapterResult.billingType),
|
|
3378
|
-
}
|
|
3379
|
-
: null;
|
|
3380
|
-
await setRunStatus(run.id, status, {
|
|
3381
|
-
finishedAt: new Date(),
|
|
3382
|
-
error: outcome === "succeeded"
|
|
3383
|
-
? null
|
|
3384
|
-
: redactCurrentUserText(adapterResult.errorMessage ?? (outcome === "timed_out" ? "Timed out" : "Adapter failed"), currentUserRedactionOptions),
|
|
3385
|
-
errorCode: outcome === "timed_out"
|
|
3386
|
-
? "timeout"
|
|
3387
|
-
: outcome === "cancelled"
|
|
3388
|
-
? "cancelled"
|
|
3389
|
-
: outcome === "failed"
|
|
3390
|
-
? (adapterResult.errorCode ?? "adapter_failed")
|
|
3391
|
-
: null,
|
|
3392
|
-
exitCode: adapterResult.exitCode,
|
|
3393
|
-
signal: adapterResult.signal,
|
|
3394
|
-
usageJson,
|
|
3395
|
-
resultJson: adapterResult.resultJson ?? null,
|
|
3396
|
-
sessionIdAfter: nextSessionState.displayId ?? nextSessionState.legacySessionId,
|
|
3397
|
-
stdoutExcerpt,
|
|
3398
|
-
stderrExcerpt,
|
|
3399
|
-
logBytes: logSummary?.bytes,
|
|
3400
|
-
logSha256: logSummary?.sha256,
|
|
3401
|
-
logCompressed: logSummary?.compressed ?? false,
|
|
3402
|
-
});
|
|
3403
|
-
await setWakeupStatus(run.wakeupRequestId, outcome === "succeeded" ? "completed" : status, {
|
|
3404
|
-
finishedAt: new Date(),
|
|
3405
|
-
error: adapterResult.errorMessage ?? null,
|
|
3406
|
-
});
|
|
3407
|
-
const finalizedRun = await getRun(run.id);
|
|
3408
|
-
if (finalizedRun) {
|
|
3409
|
-
const transcriptUsedSkills = inferUsedSkillsFromTranscript(executionTranscript);
|
|
3410
|
-
if (transcriptUsedSkills.length > 0) {
|
|
3411
|
-
await appendRunEvent(finalizedRun, seq++, {
|
|
3412
|
-
eventType: "adapter.skill_usage",
|
|
3413
|
-
stream: "system",
|
|
3414
|
-
level: "info",
|
|
3415
|
-
message: "skill usage inferred from transcript",
|
|
3416
|
-
payload: {
|
|
3417
|
-
source: "transcript.skill_file_read",
|
|
3418
|
-
usedSkillCount: transcriptUsedSkills.length,
|
|
3419
|
-
usedSkillKeys: transcriptUsedSkills.map((entry) => entry.key),
|
|
3420
|
-
usedSkills: transcriptUsedSkills,
|
|
3421
|
-
skillEvidenceType: "used",
|
|
3422
|
-
skillEvidenceCount: transcriptUsedSkills.length,
|
|
3423
|
-
skillEvidenceKeys: transcriptUsedSkills.map((entry) => entry.key),
|
|
3424
|
-
skillEvidenceSkills: transcriptUsedSkills,
|
|
3425
|
-
},
|
|
3426
|
-
});
|
|
3427
|
-
}
|
|
3428
|
-
await appendRunEvent(finalizedRun, seq++, {
|
|
3429
|
-
eventType: "lifecycle",
|
|
3430
|
-
stream: "system",
|
|
3431
|
-
level: outcome === "succeeded" ? "info" : "error",
|
|
3432
|
-
message: `run ${outcome}`,
|
|
3433
|
-
payload: {
|
|
3434
|
-
status,
|
|
3435
|
-
exitCode: adapterResult.exitCode,
|
|
3436
|
-
},
|
|
3437
|
-
});
|
|
3438
|
-
await releaseIssueExecutionAndPromote(finalizedRun);
|
|
3439
|
-
}
|
|
3440
|
-
if (finalizedRun) {
|
|
3441
|
-
await updateRuntimeState(agent, finalizedRun, adapterResult, {
|
|
3442
|
-
legacySessionId: nextSessionState.legacySessionId,
|
|
3443
|
-
}, normalizedUsage);
|
|
3444
|
-
if (taskKey) {
|
|
3445
|
-
if (adapterResult.clearSession || (!nextSessionState.params && !nextSessionState.displayId)) {
|
|
3446
|
-
await clearTaskSessions(agent.orgId, agent.id, {
|
|
3447
|
-
taskKey,
|
|
3448
|
-
agentRuntimeType: agent.agentRuntimeType,
|
|
3449
|
-
});
|
|
3450
|
-
}
|
|
3451
|
-
else {
|
|
3452
|
-
await upsertTaskSession({
|
|
3453
|
-
orgId: agent.orgId,
|
|
3454
|
-
agentId: agent.id,
|
|
3455
|
-
agentRuntimeType: agent.agentRuntimeType,
|
|
3456
|
-
taskKey,
|
|
3457
|
-
sessionParamsJson: nextSessionState.params,
|
|
3458
|
-
sessionDisplayId: nextSessionState.displayId,
|
|
3459
|
-
lastRunId: finalizedRun.id,
|
|
3460
|
-
lastError: outcome === "succeeded" ? null : (adapterResult.errorMessage ?? "run_failed"),
|
|
3461
|
-
});
|
|
3462
|
-
}
|
|
3463
|
-
}
|
|
3464
|
-
await emitHeartbeatLiveEval(finalizedRun.id);
|
|
3465
|
-
}
|
|
3466
|
-
await finalizeAgentStatus(agent.id, outcome);
|
|
3467
|
-
}
|
|
3468
|
-
catch (err) {
|
|
3469
|
-
const isWorkspacePreflightFailure = isWorkspacePermissionPreflightError(err) ||
|
|
3470
|
-
isManagedWorkspaceConfigurationError(err);
|
|
3471
|
-
const message = redactCurrentUserText(err instanceof Error ? err.message : "Unknown adapter failure", await getCurrentUserRedactionOptions());
|
|
3472
|
-
heartbeatObservationContext.status = "failed";
|
|
3473
|
-
finalObservationStatus = "failed";
|
|
3474
|
-
transcriptFallbackResult = {
|
|
3475
|
-
ts: new Date().toISOString(),
|
|
3476
|
-
output: message,
|
|
3477
|
-
subtype: "failed",
|
|
3478
|
-
isError: true,
|
|
3479
|
-
errors: [message],
|
|
3480
|
-
};
|
|
3481
|
-
logger.error({ err, runId }, "heartbeat execution failed");
|
|
3482
|
-
let logSummary = null;
|
|
3483
|
-
if (handle) {
|
|
3484
|
-
try {
|
|
3485
|
-
logSummary = await runLogStore.finalize(handle);
|
|
3486
|
-
}
|
|
3487
|
-
catch (finalizeErr) {
|
|
3488
|
-
logger.warn({ err: finalizeErr, runId }, "failed to finalize run log after error");
|
|
3489
|
-
}
|
|
3490
|
-
}
|
|
3491
|
-
const failedRun = await setRunStatus(run.id, "failed", {
|
|
3492
|
-
error: message,
|
|
3493
|
-
errorCode: isWorkspacePreflightFailure ? err.errorCode : "adapter_failed",
|
|
3494
|
-
finishedAt: new Date(),
|
|
3495
|
-
stdoutExcerpt,
|
|
3496
|
-
stderrExcerpt,
|
|
3497
|
-
logBytes: logSummary?.bytes,
|
|
3498
|
-
logSha256: logSummary?.sha256,
|
|
3499
|
-
logCompressed: logSummary?.compressed ?? false,
|
|
3500
|
-
});
|
|
3501
|
-
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
3502
|
-
finishedAt: new Date(),
|
|
3503
|
-
error: message,
|
|
3504
|
-
});
|
|
3505
|
-
if (failedRun) {
|
|
3506
|
-
await appendRunEvent(failedRun, seq++, {
|
|
3507
|
-
eventType: isWorkspacePreflightFailure ? "runtime.workspace_preflight_failed" : "error",
|
|
3508
|
-
stream: "system",
|
|
3509
|
-
level: "error",
|
|
3510
|
-
message,
|
|
3511
|
-
...(isWorkspacePreflightFailure
|
|
3512
|
-
? {
|
|
3513
|
-
payload: {
|
|
3514
|
-
errorCode: err.errorCode,
|
|
3515
|
-
failure: err.failure,
|
|
3516
|
-
},
|
|
3517
|
-
}
|
|
3518
|
-
: {}),
|
|
3519
|
-
});
|
|
3520
|
-
await releaseIssueExecutionAndPromote(failedRun);
|
|
3521
|
-
if (!isWorkspacePreflightFailure) {
|
|
3522
|
-
await updateRuntimeState(agent, failedRun, {
|
|
3523
|
-
exitCode: null,
|
|
3524
|
-
signal: null,
|
|
3525
|
-
timedOut: false,
|
|
3526
|
-
errorMessage: message,
|
|
3527
|
-
}, {
|
|
3528
|
-
legacySessionId: runtimeForAdapter.sessionId,
|
|
3529
|
-
});
|
|
3530
|
-
if (taskKey && (previousSessionParams || previousSessionDisplayId || taskSession)) {
|
|
3531
|
-
await upsertTaskSession({
|
|
3532
|
-
orgId: agent.orgId,
|
|
3533
|
-
agentId: agent.id,
|
|
3534
|
-
agentRuntimeType: agent.agentRuntimeType,
|
|
3535
|
-
taskKey,
|
|
3536
|
-
sessionParamsJson: previousSessionParams,
|
|
3537
|
-
sessionDisplayId: previousSessionDisplayId,
|
|
3538
|
-
lastRunId: failedRun.id,
|
|
3539
|
-
lastError: message,
|
|
3540
|
-
});
|
|
3541
|
-
}
|
|
3542
|
-
}
|
|
3543
|
-
await emitHeartbeatLiveEval(failedRun.id);
|
|
3544
|
-
}
|
|
3545
|
-
await finalizeAgentStatus(agent.id, "failed");
|
|
3546
|
-
}
|
|
3547
|
-
finally {
|
|
3548
|
-
stdoutTranscriptBuffer = appendTranscriptEntriesFromChunk({
|
|
3549
|
-
buffer: stdoutTranscriptBuffer,
|
|
3550
|
-
chunk: "",
|
|
3551
|
-
transcript: executionTranscript,
|
|
3552
|
-
parser: stdoutTranscriptParser,
|
|
3553
|
-
finalize: true,
|
|
3554
|
-
kind: "stdout",
|
|
3555
|
-
});
|
|
3556
|
-
stderrTranscriptBuffer = appendTranscriptEntriesFromChunk({
|
|
3557
|
-
buffer: stderrTranscriptBuffer,
|
|
3558
|
-
chunk: "",
|
|
3559
|
-
transcript: executionTranscript,
|
|
3560
|
-
finalize: true,
|
|
3561
|
-
kind: "stderr",
|
|
3562
|
-
});
|
|
3563
|
-
try {
|
|
3564
|
-
const transcriptStats = emitExecutionTranscriptTree({
|
|
3565
|
-
context: heartbeatObservationContext,
|
|
3566
|
-
parentObservation: observation,
|
|
3567
|
-
transcript: executionTranscript,
|
|
3568
|
-
initialTurnInput: modelTurnInput,
|
|
3569
|
-
fallbackResult: transcriptFallbackResult,
|
|
3570
|
-
});
|
|
3571
|
-
finalObservationOutput = transcriptStats.finalOutput ?? transcriptFallbackResult?.output ?? null;
|
|
3572
|
-
finalObservationSessionId = transcriptStats.finalSessionId ?? finalObservationSessionId;
|
|
3573
|
-
}
|
|
3574
|
-
catch (error) {
|
|
3575
|
-
logger.warn({
|
|
3576
|
-
runId: run.id,
|
|
3577
|
-
err: error instanceof Error ? error.message : String(error),
|
|
3578
|
-
}, "Failed to export heartbeat transcript tree to Langfuse");
|
|
3579
|
-
}
|
|
3580
|
-
updateExecutionObservation(observation, heartbeatObservationContext, {
|
|
3581
|
-
input: rootObservationInput,
|
|
3582
|
-
output: finalObservationOutput,
|
|
3583
|
-
level: finalObservationStatus === "failed" || finalObservationStatus === "timed_out" ? "ERROR" : "DEFAULT",
|
|
3584
|
-
statusMessage: finalObservationStatus ?? undefined,
|
|
3585
|
-
});
|
|
3586
|
-
updateExecutionTraceIO(observation, {
|
|
3587
|
-
input: rootObservationInput,
|
|
3588
|
-
output: finalObservationOutput,
|
|
3589
|
-
});
|
|
3590
|
-
updateExecutionTraceSession(observation, finalObservationSessionId);
|
|
3591
|
-
}
|
|
3592
|
-
});
|
|
3593
|
-
}
|
|
3594
|
-
catch (outerErr) {
|
|
3595
|
-
// Setup code before adapter.execute threw (e.g. ensureRuntimeState, resolveWorkspaceForRun).
|
|
3596
|
-
// The inner catch did not fire, so we must record the failure here.
|
|
3597
|
-
const message = outerErr instanceof Error ? outerErr.message : "Unknown setup failure";
|
|
3598
|
-
logger.error({ err: outerErr, runId }, "heartbeat execution setup failed");
|
|
3599
|
-
await setRunStatus(runId, "failed", {
|
|
3600
|
-
error: message,
|
|
3601
|
-
errorCode: "adapter_failed",
|
|
3602
|
-
finishedAt: new Date(),
|
|
3603
|
-
}).catch(() => undefined);
|
|
3604
|
-
await setWakeupStatus(run.wakeupRequestId, "failed", {
|
|
3605
|
-
finishedAt: new Date(),
|
|
3606
|
-
error: message,
|
|
3607
|
-
}).catch(() => undefined);
|
|
3608
|
-
const failedRun = await getRun(runId).catch(() => null);
|
|
3609
|
-
if (failedRun) {
|
|
3610
|
-
// Emit a run-log event so the failure is visible in the run timeline,
|
|
3611
|
-
// consistent with what the inner catch block does for adapter failures.
|
|
3612
|
-
await appendRunEvent(failedRun, 1, {
|
|
3613
|
-
eventType: "error",
|
|
3614
|
-
stream: "system",
|
|
3615
|
-
level: "error",
|
|
3616
|
-
message,
|
|
3617
|
-
}).catch(() => undefined);
|
|
3618
|
-
await emitHeartbeatLiveEval(failedRun.id).catch(() => undefined);
|
|
3619
|
-
await releaseIssueExecutionAndPromote(failedRun).catch(() => undefined);
|
|
3620
|
-
}
|
|
3621
|
-
// Ensure the agent is not left stuck in "running" if the inner catch handler's
|
|
3622
|
-
// DB calls threw (e.g. a transient DB error in finalizeAgentStatus).
|
|
3623
|
-
await finalizeAgentStatus(run.agentId, "failed").catch(() => undefined);
|
|
3624
|
-
}
|
|
3625
|
-
finally {
|
|
3626
|
-
await releaseRuntimeServicesForRun(run.id).catch(() => undefined);
|
|
3627
|
-
activeRunExecutions.delete(run.id);
|
|
3628
|
-
await startNextQueuedRunForAgent(run.agentId);
|
|
3629
|
-
}
|
|
3630
|
-
}
|
|
3631
|
-
async function releaseIssueExecutionAndPromote(run) {
|
|
3632
|
-
const outcome = await db.transaction(async (tx) => {
|
|
3633
|
-
await tx.execute(sql `select id from issues where org_id = ${run.orgId} and execution_run_id = ${run.id} for update`);
|
|
3634
|
-
const issue = await tx
|
|
3635
|
-
.select({
|
|
3636
|
-
id: issues.id,
|
|
3637
|
-
orgId: issues.orgId,
|
|
3638
|
-
identifier: issues.identifier,
|
|
3639
|
-
title: issues.title,
|
|
3640
|
-
description: issues.description,
|
|
3641
|
-
status: issues.status,
|
|
3642
|
-
priority: issues.priority,
|
|
3643
|
-
projectId: issues.projectId,
|
|
3644
|
-
assigneeAgentId: issues.assigneeAgentId,
|
|
3645
|
-
reviewerAgentId: issues.reviewerAgentId,
|
|
3646
|
-
reviewerUserId: issues.reviewerUserId,
|
|
3647
|
-
})
|
|
3648
|
-
.from(issues)
|
|
3649
|
-
.where(and(eq(issues.orgId, run.orgId), eq(issues.executionRunId, run.id)))
|
|
3650
|
-
.then((rows) => rows[0] ?? null);
|
|
3651
|
-
if (!issue)
|
|
3652
|
-
return { promotedRun: null, passiveClosure: null };
|
|
3653
|
-
const now = new Date();
|
|
3654
|
-
const passiveClosure = await evaluatePassiveIssueClosureForLockedIssue({
|
|
3655
|
-
tx,
|
|
3656
|
-
run,
|
|
3657
|
-
issue,
|
|
3658
|
-
now,
|
|
3659
|
-
});
|
|
3660
|
-
if (passiveClosure.kind === "queued") {
|
|
3661
|
-
return { promotedRun: passiveClosure.run, passiveClosure };
|
|
3662
|
-
}
|
|
3663
|
-
await tx
|
|
3664
|
-
.update(issues)
|
|
3665
|
-
.set({
|
|
3666
|
-
executionRunId: null,
|
|
3667
|
-
executionAgentNameKey: null,
|
|
3668
|
-
executionLockedAt: null,
|
|
3669
|
-
updatedAt: now,
|
|
3670
|
-
})
|
|
3671
|
-
.where(eq(issues.id, issue.id));
|
|
3672
|
-
while (true) {
|
|
3673
|
-
const deferred = await tx
|
|
3674
|
-
.select()
|
|
3675
|
-
.from(agentWakeupRequests)
|
|
3676
|
-
.where(and(eq(agentWakeupRequests.orgId, issue.orgId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issue.id}`))
|
|
3677
|
-
.orderBy(asc(agentWakeupRequests.requestedAt))
|
|
3678
|
-
.limit(1)
|
|
3679
|
-
.then((rows) => rows[0] ?? null);
|
|
3680
|
-
if (!deferred)
|
|
3681
|
-
return { promotedRun: null, passiveClosure };
|
|
3682
|
-
const deferredAgent = await tx
|
|
3683
|
-
.select()
|
|
3684
|
-
.from(agents)
|
|
3685
|
-
.where(eq(agents.id, deferred.agentId))
|
|
3686
|
-
.then((rows) => rows[0] ?? null);
|
|
3687
|
-
if (!deferredAgent ||
|
|
3688
|
-
deferredAgent.orgId !== issue.orgId ||
|
|
3689
|
-
deferredAgent.status === "paused" ||
|
|
3690
|
-
deferredAgent.status === "terminated" ||
|
|
3691
|
-
deferredAgent.status === "pending_approval") {
|
|
3692
|
-
await tx
|
|
3693
|
-
.update(agentWakeupRequests)
|
|
3694
|
-
.set({
|
|
3695
|
-
status: "failed",
|
|
3696
|
-
finishedAt: new Date(),
|
|
3697
|
-
error: "Deferred wake could not be promoted: agent is not invokable",
|
|
3698
|
-
updatedAt: new Date(),
|
|
3699
|
-
})
|
|
3700
|
-
.where(eq(agentWakeupRequests.id, deferred.id));
|
|
3701
|
-
continue;
|
|
3702
|
-
}
|
|
3703
|
-
const deferredPayload = parseObject(deferred.payload);
|
|
3704
|
-
const deferredContextSeed = parseObject(deferredPayload[DEFERRED_WAKE_CONTEXT_KEY]);
|
|
3705
|
-
const promotedContextSeed = { ...deferredContextSeed };
|
|
3706
|
-
const promotedReason = readNonEmptyString(deferred.reason) ?? "issue_execution_promoted";
|
|
3707
|
-
const promotedSource = readNonEmptyString(deferred.source) ?? "automation";
|
|
3708
|
-
const promotedTriggerDetail = readNonEmptyString(deferred.triggerDetail) ?? null;
|
|
3709
|
-
const promotedPayload = deferredPayload;
|
|
3710
|
-
delete promotedPayload[DEFERRED_WAKE_CONTEXT_KEY];
|
|
3711
|
-
const { contextSnapshot: promotedContextSnapshot, taskKey: promotedTaskKey, } = enrichWakeContextSnapshot({
|
|
3712
|
-
contextSnapshot: promotedContextSeed,
|
|
3713
|
-
reason: promotedReason,
|
|
3714
|
-
source: promotedSource,
|
|
3715
|
-
triggerDetail: promotedTriggerDetail,
|
|
3716
|
-
payload: promotedPayload,
|
|
3717
|
-
});
|
|
3718
|
-
const sessionBefore = readNonEmptyString(promotedContextSnapshot.resumeSessionDisplayId) ??
|
|
3719
|
-
await resolveSessionBeforeForWakeup(deferredAgent, promotedTaskKey);
|
|
3720
|
-
const now = new Date();
|
|
3721
|
-
const newRun = await tx
|
|
3722
|
-
.insert(heartbeatRuns)
|
|
3723
|
-
.values({
|
|
3724
|
-
orgId: deferredAgent.orgId,
|
|
3725
|
-
agentId: deferredAgent.id,
|
|
3726
|
-
invocationSource: promotedSource,
|
|
3727
|
-
triggerDetail: promotedTriggerDetail,
|
|
3728
|
-
status: "queued",
|
|
3729
|
-
wakeupRequestId: deferred.id,
|
|
3730
|
-
contextSnapshot: promotedContextSnapshot,
|
|
3731
|
-
sessionIdBefore: sessionBefore,
|
|
3732
|
-
})
|
|
3733
|
-
.returning()
|
|
3734
|
-
.then((rows) => rows[0]);
|
|
3735
|
-
await tx
|
|
3736
|
-
.update(agentWakeupRequests)
|
|
3737
|
-
.set({
|
|
3738
|
-
status: "queued",
|
|
3739
|
-
reason: "issue_execution_promoted",
|
|
3740
|
-
runId: newRun.id,
|
|
3741
|
-
claimedAt: null,
|
|
3742
|
-
finishedAt: null,
|
|
3743
|
-
error: null,
|
|
3744
|
-
updatedAt: now,
|
|
3745
|
-
})
|
|
3746
|
-
.where(eq(agentWakeupRequests.id, deferred.id));
|
|
3747
|
-
await tx
|
|
3748
|
-
.update(issues)
|
|
3749
|
-
.set({
|
|
3750
|
-
executionRunId: newRun.id,
|
|
3751
|
-
executionAgentNameKey: normalizeAgentNameKey(deferredAgent.name),
|
|
3752
|
-
executionLockedAt: now,
|
|
3753
|
-
updatedAt: now,
|
|
3754
|
-
})
|
|
3755
|
-
.where(eq(issues.id, issue.id));
|
|
3756
|
-
return { promotedRun: newRun, passiveClosure };
|
|
3757
|
-
}
|
|
3758
|
-
});
|
|
3759
|
-
const passiveClosure = outcome.passiveClosure;
|
|
3760
|
-
if (passiveClosure?.kind === "queued") {
|
|
3761
|
-
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3762
|
-
eventType: "issue.passive_followup_queued",
|
|
3763
|
-
stream: "system",
|
|
3764
|
-
level: "warn",
|
|
3765
|
-
message: `Queued passive issue follow-up ${passiveClosure.run.id}`,
|
|
3766
|
-
payload: {
|
|
3767
|
-
issueId: passiveClosure.issue.id,
|
|
3768
|
-
followupRunId: passiveClosure.run.id,
|
|
3769
|
-
originRunId: passiveClosure.originRunId,
|
|
3770
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3771
|
-
attempt: passiveClosure.attempt,
|
|
3772
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3773
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
3774
|
-
requestedAt: passiveClosure.requestedAt.toISOString(),
|
|
3775
|
-
},
|
|
3776
|
-
});
|
|
3777
|
-
await appendRunEvent(passiveClosure.run, await nextRunEventSeq(passiveClosure.run.id), {
|
|
3778
|
-
eventType: "issue.passive_followup_queued",
|
|
3779
|
-
stream: "system",
|
|
3780
|
-
level: "warn",
|
|
3781
|
-
message: `Passive follow-up queued because run ${run.id} ended without issue close-out`,
|
|
3782
|
-
payload: {
|
|
3783
|
-
issueId: passiveClosure.issue.id,
|
|
3784
|
-
originRunId: passiveClosure.originRunId,
|
|
3785
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3786
|
-
attempt: passiveClosure.attempt,
|
|
3787
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3788
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
3789
|
-
requestedAt: passiveClosure.requestedAt.toISOString(),
|
|
3790
|
-
},
|
|
3791
|
-
});
|
|
3792
|
-
await logActivity(db, {
|
|
3793
|
-
orgId: passiveClosure.issue.orgId,
|
|
3794
|
-
actorType: "system",
|
|
3795
|
-
actorId: "issue_closure_governance",
|
|
3796
|
-
action: "issue.passive_followup_queued",
|
|
3797
|
-
entityType: "issue",
|
|
3798
|
-
entityId: passiveClosure.issue.id,
|
|
3799
|
-
agentId: run.agentId,
|
|
3800
|
-
runId: run.id,
|
|
3801
|
-
details: {
|
|
3802
|
-
issueId: passiveClosure.issue.id,
|
|
3803
|
-
issueTitle: passiveClosure.issue.title,
|
|
3804
|
-
followupRunId: passiveClosure.run.id,
|
|
3805
|
-
originRunId: passiveClosure.originRunId,
|
|
3806
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3807
|
-
attempt: passiveClosure.attempt,
|
|
3808
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3809
|
-
reason: ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON,
|
|
3810
|
-
requestedAt: passiveClosure.requestedAt.toISOString(),
|
|
3811
|
-
},
|
|
3812
|
-
});
|
|
3813
|
-
}
|
|
3814
|
-
else if (passiveClosure?.kind === "operator_review") {
|
|
3815
|
-
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3816
|
-
eventType: "issue.closure_needs_operator_review",
|
|
3817
|
-
stream: "system",
|
|
3818
|
-
level: "warn",
|
|
3819
|
-
message: "Passive issue follow-up stopped and needs operator review",
|
|
3820
|
-
payload: {
|
|
3821
|
-
issueId: passiveClosure.issue.id,
|
|
3822
|
-
originRunId: passiveClosure.originRunId,
|
|
3823
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3824
|
-
attempts: passiveClosure.attempts,
|
|
3825
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3826
|
-
reason: passiveClosure.reason,
|
|
3827
|
-
},
|
|
3828
|
-
});
|
|
3829
|
-
await logActivity(db, {
|
|
3830
|
-
orgId: passiveClosure.issue.orgId,
|
|
3831
|
-
actorType: "system",
|
|
3832
|
-
actorId: "issue_closure_governance",
|
|
3833
|
-
action: "issue.closure_needs_operator_review",
|
|
3834
|
-
entityType: "issue",
|
|
3835
|
-
entityId: passiveClosure.issue.id,
|
|
3836
|
-
agentId: run.agentId,
|
|
3837
|
-
runId: run.id,
|
|
3838
|
-
details: {
|
|
3839
|
-
issueId: passiveClosure.issue.id,
|
|
3840
|
-
issueTitle: passiveClosure.issue.title,
|
|
3841
|
-
originRunId: passiveClosure.originRunId,
|
|
3842
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3843
|
-
attempts: passiveClosure.attempts,
|
|
3844
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3845
|
-
reason: passiveClosure.reason,
|
|
3846
|
-
},
|
|
3847
|
-
});
|
|
3848
|
-
}
|
|
3849
|
-
else if (passiveClosure?.kind === "reviewer_convergence") {
|
|
3850
|
-
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3851
|
-
eventType: "issue.convergence_review_requested",
|
|
3852
|
-
stream: "system",
|
|
3853
|
-
level: "warn",
|
|
3854
|
-
message: "Passive issue follow-up stopped and needs reviewer convergence",
|
|
3855
|
-
payload: {
|
|
3856
|
-
issueId: passiveClosure.issue.id,
|
|
3857
|
-
reviewerAgentId: passiveClosure.issue.reviewerAgentId,
|
|
3858
|
-
reviewerUserId: passiveClosure.issue.reviewerUserId,
|
|
3859
|
-
originRunId: passiveClosure.originRunId,
|
|
3860
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3861
|
-
attempts: passiveClosure.attempts,
|
|
3862
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3863
|
-
reason: passiveClosure.reason,
|
|
3864
|
-
},
|
|
3865
|
-
});
|
|
3866
|
-
await logActivity(db, {
|
|
3867
|
-
orgId: passiveClosure.issue.orgId,
|
|
3868
|
-
actorType: "system",
|
|
3869
|
-
actorId: "issue_closure_governance",
|
|
3870
|
-
action: "issue.convergence_review_requested",
|
|
3871
|
-
entityType: "issue",
|
|
3872
|
-
entityId: passiveClosure.issue.id,
|
|
3873
|
-
agentId: run.agentId,
|
|
3874
|
-
runId: run.id,
|
|
3875
|
-
details: {
|
|
3876
|
-
issueId: passiveClosure.issue.id,
|
|
3877
|
-
issueTitle: passiveClosure.issue.title,
|
|
3878
|
-
reviewerAgentId: passiveClosure.issue.reviewerAgentId,
|
|
3879
|
-
reviewerUserId: passiveClosure.issue.reviewerUserId,
|
|
3880
|
-
originRunId: passiveClosure.originRunId,
|
|
3881
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3882
|
-
attempts: passiveClosure.attempts,
|
|
3883
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3884
|
-
reason: passiveClosure.reason,
|
|
3885
|
-
},
|
|
3886
|
-
});
|
|
3887
|
-
if (passiveClosure.issue.reviewerAgentId) {
|
|
3888
|
-
await enqueueWakeup(passiveClosure.issue.reviewerAgentId, {
|
|
3889
|
-
...buildIssueConvergenceReviewWakeupOptions({
|
|
3890
|
-
issue: passiveClosure.issue,
|
|
3891
|
-
contextSource: "issue.passive_followup_exhausted",
|
|
3892
|
-
originRunId: passiveClosure.originRunId,
|
|
3893
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3894
|
-
attempts: passiveClosure.attempts,
|
|
3895
|
-
maxAttempts: ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS,
|
|
3896
|
-
requestedByActorType: "system",
|
|
3897
|
-
requestedByActorId: "issue_closure_governance",
|
|
3898
|
-
}),
|
|
3899
|
-
idempotencyKey: `issue_convergence_review_requested:${passiveClosure.originRunId}`,
|
|
3900
|
-
}).catch((err) => {
|
|
3901
|
-
logger.warn({ err, issueId: passiveClosure.issue.id }, "failed to wake reviewer after passive issue close-out exhaustion");
|
|
3902
|
-
return null;
|
|
3903
|
-
});
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
else if (passiveClosure?.kind === "reviewer_closeout") {
|
|
3907
|
-
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3908
|
-
eventType: "issue.review_closeout_missing",
|
|
3909
|
-
stream: "system",
|
|
3910
|
-
level: "warn",
|
|
3911
|
-
message: "Reviewer run finished without a structured review decision",
|
|
3912
|
-
payload: {
|
|
3913
|
-
issueId: passiveClosure.issue.id,
|
|
3914
|
-
originRunId: passiveClosure.originRunId,
|
|
3915
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3916
|
-
attempts: passiveClosure.attempts,
|
|
3917
|
-
maxAttempts: passiveClosure.maxAttempts,
|
|
3918
|
-
reason: passiveClosure.reason,
|
|
3919
|
-
},
|
|
3920
|
-
});
|
|
3921
|
-
await logActivity(db, {
|
|
3922
|
-
orgId: passiveClosure.issue.orgId,
|
|
3923
|
-
actorType: "system",
|
|
3924
|
-
actorId: "issue_review_closeout_governance",
|
|
3925
|
-
action: "issue.review_closeout_missing",
|
|
3926
|
-
entityType: "issue",
|
|
3927
|
-
entityId: passiveClosure.issue.id,
|
|
3928
|
-
agentId: run.agentId,
|
|
3929
|
-
runId: run.id,
|
|
3930
|
-
details: {
|
|
3931
|
-
issueId: passiveClosure.issue.id,
|
|
3932
|
-
issueTitle: passiveClosure.issue.title,
|
|
3933
|
-
reviewerAgentId: passiveClosure.issue.reviewerAgentId,
|
|
3934
|
-
originRunId: passiveClosure.originRunId,
|
|
3935
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3936
|
-
attempts: passiveClosure.attempts,
|
|
3937
|
-
maxAttempts: passiveClosure.maxAttempts,
|
|
3938
|
-
reason: passiveClosure.reason,
|
|
3939
|
-
},
|
|
3940
|
-
});
|
|
3941
|
-
if (passiveClosure.issue.reviewerAgentId) {
|
|
3942
|
-
await enqueueWakeup(passiveClosure.issue.reviewerAgentId, {
|
|
3943
|
-
...buildIssueReviewCloseoutWakeupOptions({
|
|
3944
|
-
issue: passiveClosure.issue,
|
|
3945
|
-
contextSource: "issue.review_closeout_missing",
|
|
3946
|
-
originRunId: passiveClosure.originRunId,
|
|
3947
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3948
|
-
attempts: passiveClosure.attempts,
|
|
3949
|
-
maxAttempts: passiveClosure.maxAttempts,
|
|
3950
|
-
requestedByActorType: "system",
|
|
3951
|
-
requestedByActorId: "issue_review_closeout_governance",
|
|
3952
|
-
}),
|
|
3953
|
-
idempotencyKey: `${ISSUE_REVIEW_CLOSEOUT_REASON}:${run.id}`,
|
|
3954
|
-
}).catch((err) => {
|
|
3955
|
-
logger.warn({ err, issueId: passiveClosure.issue.id }, "failed to wake reviewer after missing review close-out");
|
|
3956
|
-
return null;
|
|
3957
|
-
});
|
|
3958
|
-
}
|
|
3959
|
-
}
|
|
3960
|
-
else if (passiveClosure?.kind === "reviewer_closeout_operator_review") {
|
|
3961
|
-
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3962
|
-
eventType: "issue.review_closure_needs_operator_review",
|
|
3963
|
-
stream: "system",
|
|
3964
|
-
level: "warn",
|
|
3965
|
-
message: "Reviewer close-out attempts stopped and need operator review",
|
|
3966
|
-
payload: {
|
|
3967
|
-
issueId: passiveClosure.issue.id,
|
|
3968
|
-
originRunId: passiveClosure.originRunId,
|
|
3969
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3970
|
-
attempts: passiveClosure.attempts,
|
|
3971
|
-
maxAttempts: passiveClosure.maxAttempts,
|
|
3972
|
-
reason: passiveClosure.reason,
|
|
3973
|
-
},
|
|
3974
|
-
});
|
|
3975
|
-
await logActivity(db, {
|
|
3976
|
-
orgId: passiveClosure.issue.orgId,
|
|
3977
|
-
actorType: "system",
|
|
3978
|
-
actorId: "issue_review_closeout_governance",
|
|
3979
|
-
action: "issue.review_closure_needs_operator_review",
|
|
3980
|
-
entityType: "issue",
|
|
3981
|
-
entityId: passiveClosure.issue.id,
|
|
3982
|
-
agentId: run.agentId,
|
|
3983
|
-
runId: run.id,
|
|
3984
|
-
details: {
|
|
3985
|
-
issueId: passiveClosure.issue.id,
|
|
3986
|
-
issueTitle: passiveClosure.issue.title,
|
|
3987
|
-
reviewerAgentId: passiveClosure.issue.reviewerAgentId,
|
|
3988
|
-
originRunId: passiveClosure.originRunId,
|
|
3989
|
-
previousRunId: passiveClosure.previousRunId,
|
|
3990
|
-
attempts: passiveClosure.attempts,
|
|
3991
|
-
maxAttempts: passiveClosure.maxAttempts,
|
|
3992
|
-
reason: passiveClosure.reason,
|
|
3993
|
-
},
|
|
3994
|
-
});
|
|
3995
|
-
}
|
|
3996
|
-
const promotedRun = outcome.promotedRun;
|
|
3997
|
-
if (!promotedRun)
|
|
3998
|
-
return;
|
|
3999
|
-
publishLiveEvent({
|
|
4000
|
-
orgId: promotedRun.orgId,
|
|
4001
|
-
type: "heartbeat.run.queued",
|
|
4002
|
-
payload: {
|
|
4003
|
-
runId: promotedRun.id,
|
|
4004
|
-
agentId: promotedRun.agentId,
|
|
4005
|
-
invocationSource: promotedRun.invocationSource,
|
|
4006
|
-
triggerDetail: promotedRun.triggerDetail,
|
|
4007
|
-
wakeupRequestId: promotedRun.wakeupRequestId,
|
|
4008
|
-
},
|
|
4009
|
-
});
|
|
4010
|
-
await startNextQueuedRunForAgent(promotedRun.agentId);
|
|
4011
|
-
}
|
|
4012
|
-
async function enqueueWakeup(agentId, opts = {}) {
|
|
4013
|
-
const source = opts.source ?? "on_demand";
|
|
4014
|
-
const triggerDetail = opts.triggerDetail ?? null;
|
|
4015
|
-
const contextSnapshot = { ...(opts.contextSnapshot ?? {}) };
|
|
4016
|
-
const reason = opts.reason ?? null;
|
|
4017
|
-
const payload = opts.payload ?? null;
|
|
4018
|
-
const existingWakeupRequestId = readNonEmptyString(opts.existingWakeupRequestId);
|
|
4019
|
-
const { contextSnapshot: enrichedContextSnapshot, issueIdFromPayload, taskKey, wakeCommentId, } = enrichWakeContextSnapshot({
|
|
4020
|
-
contextSnapshot,
|
|
4021
|
-
reason,
|
|
4022
|
-
source,
|
|
4023
|
-
triggerDetail,
|
|
4024
|
-
payload,
|
|
4025
|
-
});
|
|
4026
|
-
let issueId = readNonEmptyString(enrichedContextSnapshot.issueId) ?? issueIdFromPayload;
|
|
4027
|
-
const agent = await getAgent(agentId);
|
|
4028
|
-
if (!agent)
|
|
4029
|
-
throw notFound("Agent not found");
|
|
4030
|
-
const explicitResumeSession = await resolveExplicitResumeSessionOverride(agent, payload, taskKey);
|
|
4031
|
-
if (explicitResumeSession) {
|
|
4032
|
-
enrichedContextSnapshot.resumeFromRunId = explicitResumeSession.resumeFromRunId;
|
|
4033
|
-
enrichedContextSnapshot.resumeSessionDisplayId = explicitResumeSession.sessionDisplayId;
|
|
4034
|
-
enrichedContextSnapshot.resumeSessionParams = explicitResumeSession.sessionParams;
|
|
4035
|
-
if (!readNonEmptyString(enrichedContextSnapshot.issueId) && explicitResumeSession.issueId) {
|
|
4036
|
-
enrichedContextSnapshot.issueId = explicitResumeSession.issueId;
|
|
4037
|
-
}
|
|
4038
|
-
if (!readNonEmptyString(enrichedContextSnapshot.taskId) && explicitResumeSession.taskId) {
|
|
4039
|
-
enrichedContextSnapshot.taskId = explicitResumeSession.taskId;
|
|
4040
|
-
}
|
|
4041
|
-
if (!readNonEmptyString(enrichedContextSnapshot.taskKey) && explicitResumeSession.taskKey) {
|
|
4042
|
-
enrichedContextSnapshot.taskKey = explicitResumeSession.taskKey;
|
|
4043
|
-
}
|
|
4044
|
-
issueId = readNonEmptyString(enrichedContextSnapshot.issueId) ?? issueId;
|
|
4045
|
-
}
|
|
4046
|
-
await hydrateWakeContextSnapshot(db, agent.orgId, enrichedContextSnapshot);
|
|
4047
|
-
const effectiveTaskKey = readNonEmptyString(enrichedContextSnapshot.taskKey) ?? taskKey;
|
|
4048
|
-
const sessionBefore = explicitResumeSession?.sessionDisplayId ??
|
|
4049
|
-
await resolveSessionBeforeForWakeup(agent, effectiveTaskKey);
|
|
4050
|
-
const writeSkippedRequest = async (skipReason) => {
|
|
4051
|
-
if (existingWakeupRequestId) {
|
|
4052
|
-
await setWakeupStatus(existingWakeupRequestId, "skipped", {
|
|
4053
|
-
reason: skipReason,
|
|
4054
|
-
finishedAt: new Date(),
|
|
4055
|
-
runId: null,
|
|
4056
|
-
claimedAt: null,
|
|
4057
|
-
error: null,
|
|
4058
|
-
});
|
|
4059
|
-
return;
|
|
4060
|
-
}
|
|
4061
|
-
await db.insert(agentWakeupRequests).values({
|
|
4062
|
-
orgId: agent.orgId,
|
|
4063
|
-
agentId,
|
|
4064
|
-
source,
|
|
4065
|
-
triggerDetail,
|
|
4066
|
-
reason: skipReason,
|
|
4067
|
-
payload,
|
|
4068
|
-
status: "skipped",
|
|
4069
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4070
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4071
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4072
|
-
finishedAt: new Date(),
|
|
4073
|
-
});
|
|
4074
|
-
};
|
|
4075
|
-
let projectId = readNonEmptyString(enrichedContextSnapshot.projectId);
|
|
4076
|
-
if (!projectId && issueId) {
|
|
4077
|
-
projectId = await db
|
|
4078
|
-
.select({ projectId: issues.projectId })
|
|
4079
|
-
.from(issues)
|
|
4080
|
-
.where(and(eq(issues.id, issueId), eq(issues.orgId, agent.orgId)))
|
|
4081
|
-
.then((rows) => rows[0]?.projectId ?? null);
|
|
4082
|
-
}
|
|
4083
|
-
const budgetBlock = await budgets.getInvocationBlock(agent.orgId, agentId, {
|
|
4084
|
-
issueId,
|
|
4085
|
-
projectId,
|
|
4086
|
-
});
|
|
4087
|
-
if (budgetBlock) {
|
|
4088
|
-
await writeSkippedRequest("budget.blocked");
|
|
4089
|
-
throw conflict(budgetBlock.reason, {
|
|
4090
|
-
scopeType: budgetBlock.scopeType,
|
|
4091
|
-
scopeId: budgetBlock.scopeId,
|
|
4092
|
-
});
|
|
4093
|
-
}
|
|
4094
|
-
if (agent.status === "paused") {
|
|
4095
|
-
const deferredPayload = buildDeferredWakePayload(payload, enrichedContextSnapshot, issueId);
|
|
4096
|
-
if (existingWakeupRequestId) {
|
|
4097
|
-
await setWakeupStatus(existingWakeupRequestId, "deferred_agent_paused", {
|
|
4098
|
-
reason,
|
|
4099
|
-
payload: deferredPayload,
|
|
4100
|
-
runId: null,
|
|
4101
|
-
claimedAt: null,
|
|
4102
|
-
finishedAt: null,
|
|
4103
|
-
error: null,
|
|
4104
|
-
});
|
|
4105
|
-
return null;
|
|
4106
|
-
}
|
|
4107
|
-
await db.transaction(async (tx) => {
|
|
4108
|
-
const deferredRows = await tx
|
|
4109
|
-
.select()
|
|
4110
|
-
.from(agentWakeupRequests)
|
|
4111
|
-
.where(and(eq(agentWakeupRequests.orgId, agent.orgId), eq(agentWakeupRequests.agentId, agentId), eq(agentWakeupRequests.status, "deferred_agent_paused"), sql `${agentWakeupRequests.runId} is null`))
|
|
4112
|
-
.orderBy(asc(agentWakeupRequests.requestedAt));
|
|
4113
|
-
const existingDeferred = deferredRows.find((candidate) => isSameTaskScope(deriveDeferredWakeTaskKey(candidate.payload), effectiveTaskKey));
|
|
4114
|
-
if (existingDeferred) {
|
|
4115
|
-
const mergedDeferredContext = mergeCoalescedContextSnapshot(readDeferredWakeContext(existingDeferred.payload), enrichedContextSnapshot);
|
|
4116
|
-
await updateWakeupRequestRecord(tx, existingDeferred.id, {
|
|
4117
|
-
payload: buildDeferredWakePayload({
|
|
4118
|
-
...readDeferredWakePayload(existingDeferred.payload),
|
|
4119
|
-
...(payload ?? {}),
|
|
4120
|
-
}, mergedDeferredContext, issueId),
|
|
4121
|
-
coalescedCount: (existingDeferred.coalescedCount ?? 0) + 1,
|
|
4122
|
-
error: null,
|
|
4123
|
-
finishedAt: null,
|
|
4124
|
-
claimedAt: null,
|
|
4125
|
-
runId: null,
|
|
4126
|
-
});
|
|
4127
|
-
return;
|
|
4128
|
-
}
|
|
4129
|
-
await insertWakeupRequestRecord(tx, {
|
|
4130
|
-
orgId: agent.orgId,
|
|
4131
|
-
agentId,
|
|
4132
|
-
source,
|
|
4133
|
-
triggerDetail,
|
|
4134
|
-
reason,
|
|
4135
|
-
payload: deferredPayload,
|
|
4136
|
-
status: "deferred_agent_paused",
|
|
4137
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4138
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4139
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4140
|
-
});
|
|
4141
|
-
});
|
|
4142
|
-
return null;
|
|
4143
|
-
}
|
|
4144
|
-
if (agent.status === "terminated" || agent.status === "pending_approval") {
|
|
4145
|
-
throw conflict("Agent is not invokable in its current state", { status: agent.status });
|
|
4146
|
-
}
|
|
4147
|
-
const policy = parseHeartbeatPolicy(agent);
|
|
4148
|
-
if (source === "timer" && !policy.enabled) {
|
|
4149
|
-
await writeSkippedRequest("heartbeat.disabled");
|
|
4150
|
-
return null;
|
|
4151
|
-
}
|
|
4152
|
-
if (source !== "timer" && !policy.wakeOnDemand) {
|
|
4153
|
-
await writeSkippedRequest("heartbeat.wakeOnDemand.disabled");
|
|
4154
|
-
return null;
|
|
4155
|
-
}
|
|
4156
|
-
const bypassIssueExecutionLock = reason === "issue_comment_mentioned" ||
|
|
4157
|
-
readNonEmptyString(enrichedContextSnapshot.wakeReason) === "issue_comment_mentioned";
|
|
4158
|
-
if (issueId && !bypassIssueExecutionLock) {
|
|
4159
|
-
const agentNameKey = normalizeAgentNameKey(agent.name);
|
|
4160
|
-
const outcome = await db.transaction(async (tx) => {
|
|
4161
|
-
await tx.execute(sql `select id from issues where id = ${issueId} and org_id = ${agent.orgId} for update`);
|
|
4162
|
-
const issue = await tx
|
|
4163
|
-
.select({
|
|
4164
|
-
id: issues.id,
|
|
4165
|
-
orgId: issues.orgId,
|
|
4166
|
-
executionRunId: issues.executionRunId,
|
|
4167
|
-
executionAgentNameKey: issues.executionAgentNameKey,
|
|
4168
|
-
})
|
|
4169
|
-
.from(issues)
|
|
4170
|
-
.where(and(eq(issues.id, issueId), eq(issues.orgId, agent.orgId)))
|
|
4171
|
-
.then((rows) => rows[0] ?? null);
|
|
4172
|
-
if (!issue) {
|
|
4173
|
-
if (existingWakeupRequestId) {
|
|
4174
|
-
await updateWakeupRequestRecord(tx, existingWakeupRequestId, {
|
|
4175
|
-
status: "skipped",
|
|
4176
|
-
reason: "issue_execution_issue_not_found",
|
|
4177
|
-
runId: null,
|
|
4178
|
-
claimedAt: null,
|
|
4179
|
-
finishedAt: new Date(),
|
|
4180
|
-
error: null,
|
|
4181
|
-
});
|
|
4182
|
-
}
|
|
4183
|
-
else {
|
|
4184
|
-
await insertWakeupRequestRecord(tx, {
|
|
4185
|
-
orgId: agent.orgId,
|
|
4186
|
-
agentId,
|
|
4187
|
-
source,
|
|
4188
|
-
triggerDetail,
|
|
4189
|
-
reason: "issue_execution_issue_not_found",
|
|
4190
|
-
payload,
|
|
4191
|
-
status: "skipped",
|
|
4192
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4193
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4194
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4195
|
-
finishedAt: new Date(),
|
|
4196
|
-
});
|
|
4197
|
-
}
|
|
4198
|
-
return { kind: "skipped" };
|
|
4199
|
-
}
|
|
4200
|
-
let activeExecutionRun = issue.executionRunId
|
|
4201
|
-
? await tx
|
|
4202
|
-
.select()
|
|
4203
|
-
.from(heartbeatRuns)
|
|
4204
|
-
.where(eq(heartbeatRuns.id, issue.executionRunId))
|
|
4205
|
-
.then((rows) => rows[0] ?? null)
|
|
4206
|
-
: null;
|
|
4207
|
-
if (activeExecutionRun && activeExecutionRun.status !== "queued" && activeExecutionRun.status !== "running") {
|
|
4208
|
-
activeExecutionRun = null;
|
|
4209
|
-
}
|
|
4210
|
-
if (!activeExecutionRun && issue.executionRunId) {
|
|
4211
|
-
await tx
|
|
4212
|
-
.update(issues)
|
|
4213
|
-
.set({
|
|
4214
|
-
executionRunId: null,
|
|
4215
|
-
executionAgentNameKey: null,
|
|
4216
|
-
executionLockedAt: null,
|
|
4217
|
-
updatedAt: new Date(),
|
|
4218
|
-
})
|
|
4219
|
-
.where(eq(issues.id, issue.id));
|
|
4220
|
-
}
|
|
4221
|
-
if (!activeExecutionRun) {
|
|
4222
|
-
const legacyRun = await tx
|
|
4223
|
-
.select()
|
|
4224
|
-
.from(heartbeatRuns)
|
|
4225
|
-
.where(and(eq(heartbeatRuns.orgId, issue.orgId), inArray(heartbeatRuns.status, ["queued", "running"]), sql `${heartbeatRuns.contextSnapshot} ->> 'issueId' = ${issue.id}`))
|
|
4226
|
-
.orderBy(sql `case when ${heartbeatRuns.status} = 'running' then 0 else 1 end`, asc(heartbeatRuns.createdAt))
|
|
4227
|
-
.limit(1)
|
|
4228
|
-
.then((rows) => rows[0] ?? null);
|
|
4229
|
-
if (legacyRun) {
|
|
4230
|
-
activeExecutionRun = legacyRun;
|
|
4231
|
-
const legacyAgent = await tx
|
|
4232
|
-
.select({ name: agents.name })
|
|
4233
|
-
.from(agents)
|
|
4234
|
-
.where(eq(agents.id, legacyRun.agentId))
|
|
4235
|
-
.then((rows) => rows[0] ?? null);
|
|
4236
|
-
await tx
|
|
4237
|
-
.update(issues)
|
|
4238
|
-
.set({
|
|
4239
|
-
executionRunId: legacyRun.id,
|
|
4240
|
-
executionAgentNameKey: normalizeAgentNameKey(legacyAgent?.name),
|
|
4241
|
-
executionLockedAt: new Date(),
|
|
4242
|
-
updatedAt: new Date(),
|
|
4243
|
-
})
|
|
4244
|
-
.where(eq(issues.id, issue.id));
|
|
4245
|
-
}
|
|
4246
|
-
}
|
|
4247
|
-
if (activeExecutionRun) {
|
|
4248
|
-
const executionAgent = await tx
|
|
4249
|
-
.select({ name: agents.name })
|
|
4250
|
-
.from(agents)
|
|
4251
|
-
.where(eq(agents.id, activeExecutionRun.agentId))
|
|
4252
|
-
.then((rows) => rows[0] ?? null);
|
|
4253
|
-
const executionAgentNameKey = normalizeAgentNameKey(issue.executionAgentNameKey) ??
|
|
4254
|
-
normalizeAgentNameKey(executionAgent?.name);
|
|
4255
|
-
const isSameExecutionAgent = Boolean(executionAgentNameKey) && executionAgentNameKey === agentNameKey;
|
|
4256
|
-
const shouldQueueFollowupForCommentWake = Boolean(wakeCommentId) &&
|
|
4257
|
-
activeExecutionRun.status === "running" &&
|
|
4258
|
-
isSameExecutionAgent;
|
|
4259
|
-
if (isSameExecutionAgent && !shouldQueueFollowupForCommentWake) {
|
|
4260
|
-
const mergedContextSnapshot = mergeCoalescedContextSnapshot(activeExecutionRun.contextSnapshot, enrichedContextSnapshot);
|
|
4261
|
-
const mergedRun = await tx
|
|
4262
|
-
.update(heartbeatRuns)
|
|
4263
|
-
.set({
|
|
4264
|
-
contextSnapshot: mergedContextSnapshot,
|
|
4265
|
-
updatedAt: new Date(),
|
|
4266
|
-
})
|
|
4267
|
-
.where(eq(heartbeatRuns.id, activeExecutionRun.id))
|
|
4268
|
-
.returning()
|
|
4269
|
-
.then((rows) => rows[0] ?? activeExecutionRun);
|
|
4270
|
-
if (existingWakeupRequestId) {
|
|
4271
|
-
await updateWakeupRequestRecord(tx, existingWakeupRequestId, {
|
|
4272
|
-
status: "coalesced",
|
|
4273
|
-
reason: "issue_execution_same_name",
|
|
4274
|
-
runId: mergedRun.id,
|
|
4275
|
-
claimedAt: null,
|
|
4276
|
-
finishedAt: new Date(),
|
|
4277
|
-
error: null,
|
|
4278
|
-
});
|
|
4279
|
-
}
|
|
4280
|
-
else {
|
|
4281
|
-
await insertWakeupRequestRecord(tx, {
|
|
4282
|
-
orgId: agent.orgId,
|
|
4283
|
-
agentId,
|
|
4284
|
-
source,
|
|
4285
|
-
triggerDetail,
|
|
4286
|
-
reason: "issue_execution_same_name",
|
|
4287
|
-
payload,
|
|
4288
|
-
status: "coalesced",
|
|
4289
|
-
coalescedCount: 1,
|
|
4290
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4291
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4292
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4293
|
-
runId: mergedRun.id,
|
|
4294
|
-
finishedAt: new Date(),
|
|
4295
|
-
});
|
|
4296
|
-
}
|
|
4297
|
-
return { kind: "coalesced", run: mergedRun };
|
|
4298
|
-
}
|
|
4299
|
-
const deferredPayload = buildDeferredWakePayload(payload, enrichedContextSnapshot, issueId);
|
|
4300
|
-
const existingDeferred = await tx
|
|
4301
|
-
.select()
|
|
4302
|
-
.from(agentWakeupRequests)
|
|
4303
|
-
.where(and(eq(agentWakeupRequests.orgId, agent.orgId), eq(agentWakeupRequests.agentId, agentId), eq(agentWakeupRequests.status, "deferred_issue_execution"), sql `${agentWakeupRequests.payload} ->> 'issueId' = ${issue.id}`))
|
|
4304
|
-
.orderBy(asc(agentWakeupRequests.requestedAt))
|
|
4305
|
-
.limit(1)
|
|
4306
|
-
.then((rows) => rows[0] ?? null);
|
|
4307
|
-
if (existingDeferred) {
|
|
4308
|
-
const mergedDeferredContext = mergeCoalescedContextSnapshot(readDeferredWakeContext(existingDeferred.payload), enrichedContextSnapshot);
|
|
4309
|
-
const mergedDeferredPayload = buildDeferredWakePayload({
|
|
4310
|
-
...readDeferredWakePayload(existingDeferred.payload),
|
|
4311
|
-
...(payload ?? {}),
|
|
4312
|
-
}, mergedDeferredContext, issueId);
|
|
4313
|
-
if (existingWakeupRequestId && existingDeferred.id !== existingWakeupRequestId) {
|
|
4314
|
-
await updateWakeupRequestRecord(tx, existingDeferred.id, {
|
|
4315
|
-
payload: mergedDeferredPayload,
|
|
4316
|
-
coalescedCount: (existingDeferred.coalescedCount ?? 0) + 1,
|
|
4317
|
-
});
|
|
4318
|
-
await updateWakeupRequestRecord(tx, existingWakeupRequestId, {
|
|
4319
|
-
status: "coalesced",
|
|
4320
|
-
reason: "issue_execution_deferred",
|
|
4321
|
-
runId: null,
|
|
4322
|
-
claimedAt: null,
|
|
4323
|
-
finishedAt: new Date(),
|
|
4324
|
-
error: null,
|
|
4325
|
-
});
|
|
4326
|
-
}
|
|
4327
|
-
else {
|
|
4328
|
-
await updateWakeupRequestRecord(tx, existingDeferred.id, {
|
|
4329
|
-
payload: mergedDeferredPayload,
|
|
4330
|
-
coalescedCount: (existingDeferred.coalescedCount ?? 0) + 1,
|
|
4331
|
-
status: "deferred_issue_execution",
|
|
4332
|
-
reason: "issue_execution_deferred",
|
|
4333
|
-
runId: null,
|
|
4334
|
-
claimedAt: null,
|
|
4335
|
-
finishedAt: null,
|
|
4336
|
-
error: null,
|
|
4337
|
-
});
|
|
4338
|
-
}
|
|
4339
|
-
return { kind: "deferred" };
|
|
4340
|
-
}
|
|
4341
|
-
if (existingWakeupRequestId) {
|
|
4342
|
-
await updateWakeupRequestRecord(tx, existingWakeupRequestId, {
|
|
4343
|
-
status: "deferred_issue_execution",
|
|
4344
|
-
reason: "issue_execution_deferred",
|
|
4345
|
-
payload: deferredPayload,
|
|
4346
|
-
runId: null,
|
|
4347
|
-
claimedAt: null,
|
|
4348
|
-
finishedAt: null,
|
|
4349
|
-
error: null,
|
|
4350
|
-
});
|
|
4351
|
-
}
|
|
4352
|
-
else {
|
|
4353
|
-
await insertWakeupRequestRecord(tx, {
|
|
4354
|
-
orgId: agent.orgId,
|
|
4355
|
-
agentId,
|
|
4356
|
-
source,
|
|
4357
|
-
triggerDetail,
|
|
4358
|
-
reason: "issue_execution_deferred",
|
|
4359
|
-
payload: deferredPayload,
|
|
4360
|
-
status: "deferred_issue_execution",
|
|
4361
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4362
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4363
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4364
|
-
});
|
|
4365
|
-
}
|
|
4366
|
-
return { kind: "deferred" };
|
|
4367
|
-
}
|
|
4368
|
-
const wakeupRequest = existingWakeupRequestId
|
|
4369
|
-
? await updateWakeupRequestRecord(tx, existingWakeupRequestId, {
|
|
4370
|
-
status: "queued",
|
|
4371
|
-
runId: null,
|
|
4372
|
-
claimedAt: null,
|
|
4373
|
-
finishedAt: null,
|
|
4374
|
-
error: null,
|
|
4375
|
-
})
|
|
4376
|
-
: await insertWakeupRequestRecord(tx, {
|
|
4377
|
-
orgId: agent.orgId,
|
|
4378
|
-
agentId,
|
|
4379
|
-
source,
|
|
4380
|
-
triggerDetail,
|
|
4381
|
-
reason,
|
|
4382
|
-
payload,
|
|
4383
|
-
status: "queued",
|
|
4384
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4385
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4386
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4387
|
-
});
|
|
4388
|
-
const newRun = await tx
|
|
4389
|
-
.insert(heartbeatRuns)
|
|
4390
|
-
.values({
|
|
4391
|
-
orgId: agent.orgId,
|
|
4392
|
-
agentId,
|
|
4393
|
-
invocationSource: source,
|
|
4394
|
-
triggerDetail,
|
|
4395
|
-
status: "queued",
|
|
4396
|
-
wakeupRequestId: wakeupRequest.id,
|
|
4397
|
-
contextSnapshot: enrichedContextSnapshot,
|
|
4398
|
-
sessionIdBefore: sessionBefore,
|
|
4399
|
-
})
|
|
4400
|
-
.returning()
|
|
4401
|
-
.then((rows) => rows[0]);
|
|
4402
|
-
await updateWakeupRequestRecord(tx, wakeupRequest.id, {
|
|
4403
|
-
runId: newRun.id,
|
|
4404
|
-
status: "queued",
|
|
4405
|
-
claimedAt: null,
|
|
4406
|
-
finishedAt: null,
|
|
4407
|
-
error: null,
|
|
4408
|
-
});
|
|
4409
|
-
await tx
|
|
4410
|
-
.update(issues)
|
|
4411
|
-
.set({
|
|
4412
|
-
executionRunId: newRun.id,
|
|
4413
|
-
executionAgentNameKey: agentNameKey,
|
|
4414
|
-
executionLockedAt: new Date(),
|
|
4415
|
-
updatedAt: new Date(),
|
|
4416
|
-
})
|
|
4417
|
-
.where(eq(issues.id, issue.id));
|
|
4418
|
-
return { kind: "queued", run: newRun };
|
|
4419
|
-
});
|
|
4420
|
-
if (outcome.kind === "deferred" || outcome.kind === "skipped")
|
|
4421
|
-
return null;
|
|
4422
|
-
if (outcome.kind === "coalesced")
|
|
4423
|
-
return outcome.run;
|
|
4424
|
-
const newRun = outcome.run;
|
|
4425
|
-
publishLiveEvent({
|
|
4426
|
-
orgId: newRun.orgId,
|
|
4427
|
-
type: "heartbeat.run.queued",
|
|
4428
|
-
payload: {
|
|
4429
|
-
runId: newRun.id,
|
|
4430
|
-
agentId: newRun.agentId,
|
|
4431
|
-
invocationSource: newRun.invocationSource,
|
|
4432
|
-
triggerDetail: newRun.triggerDetail,
|
|
4433
|
-
wakeupRequestId: newRun.wakeupRequestId,
|
|
4434
|
-
},
|
|
4435
|
-
});
|
|
4436
|
-
await startNextQueuedRunForAgent(agent.id);
|
|
4437
|
-
return newRun;
|
|
4438
|
-
}
|
|
4439
|
-
const activeRuns = await db
|
|
4440
|
-
.select()
|
|
4441
|
-
.from(heartbeatRuns)
|
|
4442
|
-
.where(and(eq(heartbeatRuns.agentId, agentId), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
4443
|
-
.orderBy(desc(heartbeatRuns.createdAt));
|
|
4444
|
-
const sameScopeQueuedRun = activeRuns.find((candidate) => candidate.status === "queued" && isSameTaskScope(runTaskKey(candidate), taskKey));
|
|
4445
|
-
const sameScopeRunningRun = activeRuns.find((candidate) => candidate.status === "running" && isSameTaskScope(runTaskKey(candidate), taskKey));
|
|
4446
|
-
const shouldQueueFollowupForCommentWake = Boolean(wakeCommentId) && Boolean(sameScopeRunningRun) && !sameScopeQueuedRun;
|
|
4447
|
-
const coalescedTargetRun = sameScopeQueuedRun ??
|
|
4448
|
-
(shouldQueueFollowupForCommentWake ? null : sameScopeRunningRun ?? null);
|
|
4449
|
-
if (coalescedTargetRun) {
|
|
4450
|
-
const mergedContextSnapshot = mergeCoalescedContextSnapshot(coalescedTargetRun.contextSnapshot, contextSnapshot);
|
|
4451
|
-
const mergedRun = await db
|
|
4452
|
-
.update(heartbeatRuns)
|
|
4453
|
-
.set({
|
|
4454
|
-
contextSnapshot: mergedContextSnapshot,
|
|
4455
|
-
updatedAt: new Date(),
|
|
4456
|
-
})
|
|
4457
|
-
.where(eq(heartbeatRuns.id, coalescedTargetRun.id))
|
|
4458
|
-
.returning()
|
|
4459
|
-
.then((rows) => rows[0] ?? coalescedTargetRun);
|
|
4460
|
-
if (existingWakeupRequestId) {
|
|
4461
|
-
await setWakeupStatus(existingWakeupRequestId, "coalesced", {
|
|
4462
|
-
runId: mergedRun.id,
|
|
4463
|
-
claimedAt: null,
|
|
4464
|
-
finishedAt: new Date(),
|
|
4465
|
-
error: null,
|
|
4466
|
-
});
|
|
4467
|
-
}
|
|
4468
|
-
else {
|
|
4469
|
-
await db.insert(agentWakeupRequests).values({
|
|
4470
|
-
orgId: agent.orgId,
|
|
4471
|
-
agentId,
|
|
4472
|
-
source,
|
|
4473
|
-
triggerDetail,
|
|
4474
|
-
reason,
|
|
4475
|
-
payload,
|
|
4476
|
-
status: "coalesced",
|
|
4477
|
-
coalescedCount: 1,
|
|
4478
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4479
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4480
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4481
|
-
runId: mergedRun.id,
|
|
4482
|
-
finishedAt: new Date(),
|
|
4483
|
-
});
|
|
4484
|
-
}
|
|
4485
|
-
return mergedRun;
|
|
4486
|
-
}
|
|
4487
|
-
const wakeupRequest = existingWakeupRequestId
|
|
4488
|
-
? await updateWakeupRequestRecord(db, existingWakeupRequestId, {
|
|
4489
|
-
status: "queued",
|
|
4490
|
-
runId: null,
|
|
4491
|
-
claimedAt: null,
|
|
4492
|
-
finishedAt: null,
|
|
4493
|
-
error: null,
|
|
4494
|
-
})
|
|
4495
|
-
: await insertWakeupRequestRecord(db, {
|
|
4496
|
-
orgId: agent.orgId,
|
|
4497
|
-
agentId,
|
|
4498
|
-
source,
|
|
4499
|
-
triggerDetail,
|
|
4500
|
-
reason,
|
|
4501
|
-
payload,
|
|
4502
|
-
status: "queued",
|
|
4503
|
-
requestedByActorType: opts.requestedByActorType ?? null,
|
|
4504
|
-
requestedByActorId: opts.requestedByActorId ?? null,
|
|
4505
|
-
idempotencyKey: opts.idempotencyKey ?? null,
|
|
4506
|
-
});
|
|
4507
|
-
const newRun = await db
|
|
4508
|
-
.insert(heartbeatRuns)
|
|
4509
|
-
.values({
|
|
4510
|
-
orgId: agent.orgId,
|
|
4511
|
-
agentId,
|
|
4512
|
-
invocationSource: source,
|
|
4513
|
-
triggerDetail,
|
|
4514
|
-
status: "queued",
|
|
4515
|
-
wakeupRequestId: wakeupRequest.id,
|
|
4516
|
-
contextSnapshot: enrichedContextSnapshot,
|
|
4517
|
-
sessionIdBefore: sessionBefore,
|
|
4518
|
-
})
|
|
4519
|
-
.returning()
|
|
4520
|
-
.then((rows) => rows[0]);
|
|
4521
|
-
await updateWakeupRequestRecord(db, wakeupRequest.id, {
|
|
4522
|
-
status: "queued",
|
|
4523
|
-
runId: newRun.id,
|
|
4524
|
-
claimedAt: null,
|
|
4525
|
-
finishedAt: null,
|
|
4526
|
-
error: null,
|
|
4527
|
-
});
|
|
4528
|
-
publishLiveEvent({
|
|
4529
|
-
orgId: newRun.orgId,
|
|
4530
|
-
type: "heartbeat.run.queued",
|
|
4531
|
-
payload: {
|
|
4532
|
-
runId: newRun.id,
|
|
4533
|
-
agentId: newRun.agentId,
|
|
4534
|
-
invocationSource: newRun.invocationSource,
|
|
4535
|
-
triggerDetail: newRun.triggerDetail,
|
|
4536
|
-
wakeupRequestId: newRun.wakeupRequestId,
|
|
4537
|
-
},
|
|
4538
|
-
});
|
|
4539
|
-
await startNextQueuedRunForAgent(agent.id);
|
|
4540
|
-
return newRun;
|
|
4541
|
-
}
|
|
4542
|
-
async function resumeDeferredWakeupsForAgent(agentId) {
|
|
4543
|
-
const agent = await getAgent(agentId);
|
|
4544
|
-
if (!agent)
|
|
4545
|
-
throw notFound("Agent not found");
|
|
4546
|
-
const replayedRequestIds = [];
|
|
4547
|
-
while (true) {
|
|
4548
|
-
const deferred = await db
|
|
4549
|
-
.select()
|
|
4550
|
-
.from(agentWakeupRequests)
|
|
4551
|
-
.where(and(eq(agentWakeupRequests.orgId, agent.orgId), eq(agentWakeupRequests.agentId, agentId), eq(agentWakeupRequests.status, "deferred_agent_paused"), sql `${agentWakeupRequests.runId} is null`))
|
|
4552
|
-
.orderBy(asc(agentWakeupRequests.requestedAt))
|
|
4553
|
-
.limit(1)
|
|
4554
|
-
.then((rows) => rows[0] ?? null);
|
|
4555
|
-
if (!deferred)
|
|
4556
|
-
break;
|
|
4557
|
-
const replayPayload = readDeferredWakePayload(deferred.payload);
|
|
4558
|
-
const replayContextSnapshot = readDeferredWakeContext(deferred.payload);
|
|
4559
|
-
try {
|
|
4560
|
-
await enqueueWakeup(agentId, {
|
|
4561
|
-
source: readNonEmptyString(deferred.source) ?? "on_demand",
|
|
4562
|
-
triggerDetail: readNonEmptyString(deferred.triggerDetail) ?? undefined,
|
|
4563
|
-
reason: readNonEmptyString(deferred.reason) ?? null,
|
|
4564
|
-
payload: replayPayload,
|
|
4565
|
-
idempotencyKey: deferred.idempotencyKey,
|
|
4566
|
-
requestedByActorType: deferred.requestedByActorType ?? undefined,
|
|
4567
|
-
requestedByActorId: deferred.requestedByActorId,
|
|
4568
|
-
contextSnapshot: replayContextSnapshot,
|
|
4569
|
-
existingWakeupRequestId: deferred.id,
|
|
4570
|
-
});
|
|
4571
|
-
}
|
|
4572
|
-
catch (error) {
|
|
4573
|
-
const current = await db
|
|
4574
|
-
.select({ status: agentWakeupRequests.status })
|
|
4575
|
-
.from(agentWakeupRequests)
|
|
4576
|
-
.where(eq(agentWakeupRequests.id, deferred.id))
|
|
4577
|
-
.then((rows) => rows[0] ?? null);
|
|
4578
|
-
if (current?.status === "deferred_agent_paused") {
|
|
4579
|
-
await setWakeupStatus(deferred.id, "failed", {
|
|
4580
|
-
finishedAt: new Date(),
|
|
4581
|
-
error: error instanceof Error ? error.message : String(error),
|
|
4582
|
-
});
|
|
4583
|
-
}
|
|
4584
|
-
}
|
|
4585
|
-
replayedRequestIds.push(deferred.id);
|
|
4586
|
-
const current = await db
|
|
4587
|
-
.select({ status: agentWakeupRequests.status })
|
|
4588
|
-
.from(agentWakeupRequests)
|
|
4589
|
-
.where(eq(agentWakeupRequests.id, deferred.id))
|
|
4590
|
-
.then((rows) => rows[0] ?? null);
|
|
4591
|
-
if (current?.status === "deferred_agent_paused")
|
|
4592
|
-
break;
|
|
4593
|
-
}
|
|
4594
|
-
return {
|
|
4595
|
-
replayed: replayedRequestIds.length,
|
|
4596
|
-
wakeupRequestIds: replayedRequestIds,
|
|
4597
|
-
};
|
|
4598
|
-
}
|
|
4599
|
-
async function listProjectScopedRunIds(orgId, projectId) {
|
|
4600
|
-
const runIssueId = sql `${heartbeatRuns.contextSnapshot} ->> 'issueId'`;
|
|
4601
|
-
const effectiveProjectId = sql `coalesce(${heartbeatRuns.contextSnapshot} ->> 'projectId', ${issues.projectId}::text)`;
|
|
4602
|
-
const rows = await db
|
|
4603
|
-
.selectDistinctOn([heartbeatRuns.id], { id: heartbeatRuns.id })
|
|
4604
|
-
.from(heartbeatRuns)
|
|
4605
|
-
.leftJoin(issues, and(eq(issues.orgId, orgId), sql `${issues.id}::text = ${runIssueId}`))
|
|
4606
|
-
.where(and(eq(heartbeatRuns.orgId, orgId), inArray(heartbeatRuns.status, ["queued", "running"]), sql `${effectiveProjectId} = ${projectId}`));
|
|
4607
|
-
return rows.map((row) => row.id);
|
|
4608
|
-
}
|
|
4609
|
-
async function listProjectScopedWakeupIds(orgId, projectId) {
|
|
4610
|
-
const wakeIssueId = sql `${agentWakeupRequests.payload} ->> 'issueId'`;
|
|
4611
|
-
const effectiveProjectId = sql `coalesce(${agentWakeupRequests.payload} ->> 'projectId', ${issues.projectId}::text)`;
|
|
4612
|
-
const rows = await db
|
|
4613
|
-
.selectDistinctOn([agentWakeupRequests.id], { id: agentWakeupRequests.id })
|
|
4614
|
-
.from(agentWakeupRequests)
|
|
4615
|
-
.leftJoin(issues, and(eq(issues.orgId, orgId), sql `${issues.id}::text = ${wakeIssueId}`))
|
|
4616
|
-
.where(and(eq(agentWakeupRequests.orgId, orgId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"]), sql `${agentWakeupRequests.runId} is null`, sql `${effectiveProjectId} = ${projectId}`));
|
|
4617
|
-
return rows.map((row) => row.id);
|
|
4618
|
-
}
|
|
4619
|
-
async function cancelPendingWakeupsForBudgetScope(scope) {
|
|
4620
|
-
const now = new Date();
|
|
4621
|
-
let wakeupIds = [];
|
|
4622
|
-
if (scope.scopeType === "organization") {
|
|
4623
|
-
wakeupIds = await db
|
|
4624
|
-
.select({ id: agentWakeupRequests.id })
|
|
4625
|
-
.from(agentWakeupRequests)
|
|
4626
|
-
.where(and(eq(agentWakeupRequests.orgId, scope.orgId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"]), sql `${agentWakeupRequests.runId} is null`))
|
|
4627
|
-
.then((rows) => rows.map((row) => row.id));
|
|
4628
|
-
}
|
|
4629
|
-
else if (scope.scopeType === "agent") {
|
|
4630
|
-
wakeupIds = await db
|
|
4631
|
-
.select({ id: agentWakeupRequests.id })
|
|
4632
|
-
.from(agentWakeupRequests)
|
|
4633
|
-
.where(and(eq(agentWakeupRequests.orgId, scope.orgId), eq(agentWakeupRequests.agentId, scope.scopeId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"]), sql `${agentWakeupRequests.runId} is null`))
|
|
4634
|
-
.then((rows) => rows.map((row) => row.id));
|
|
4635
|
-
}
|
|
4636
|
-
else {
|
|
4637
|
-
wakeupIds = await listProjectScopedWakeupIds(scope.orgId, scope.scopeId);
|
|
4638
|
-
}
|
|
4639
|
-
if (wakeupIds.length === 0)
|
|
4640
|
-
return 0;
|
|
4641
|
-
await db
|
|
4642
|
-
.update(agentWakeupRequests)
|
|
4643
|
-
.set({
|
|
4644
|
-
status: "cancelled",
|
|
4645
|
-
finishedAt: now,
|
|
4646
|
-
error: "Cancelled due to budget pause",
|
|
4647
|
-
updatedAt: now,
|
|
4648
|
-
})
|
|
4649
|
-
.where(inArray(agentWakeupRequests.id, wakeupIds));
|
|
4650
|
-
return wakeupIds.length;
|
|
4651
|
-
}
|
|
4652
|
-
async function cancelRunInternal(runId, reason = "Cancelled by control plane") {
|
|
4653
|
-
const run = await getRun(runId);
|
|
4654
|
-
if (!run)
|
|
4655
|
-
throw notFound("Heartbeat run not found");
|
|
4656
|
-
if (run.status !== "running" && run.status !== "queued")
|
|
4657
|
-
return run;
|
|
4658
|
-
const running = runningProcesses.get(run.id);
|
|
4659
|
-
if (running) {
|
|
4660
|
-
running.child.kill("SIGTERM");
|
|
4661
|
-
const graceMs = Math.max(1, running.graceSec) * 1000;
|
|
4662
|
-
setTimeout(() => {
|
|
4663
|
-
if (!running.child.killed) {
|
|
4664
|
-
running.child.kill("SIGKILL");
|
|
4665
|
-
}
|
|
4666
|
-
}, graceMs);
|
|
4667
|
-
}
|
|
4668
|
-
const cancelled = await setRunStatus(run.id, "cancelled", {
|
|
4669
|
-
finishedAt: new Date(),
|
|
4670
|
-
error: reason,
|
|
4671
|
-
errorCode: "cancelled",
|
|
4672
|
-
});
|
|
4673
|
-
await setWakeupStatus(run.wakeupRequestId, "cancelled", {
|
|
4674
|
-
finishedAt: new Date(),
|
|
4675
|
-
error: reason,
|
|
4676
|
-
});
|
|
4677
|
-
if (cancelled) {
|
|
4678
|
-
await appendRunEvent(cancelled, 1, {
|
|
4679
|
-
eventType: "lifecycle",
|
|
4680
|
-
stream: "system",
|
|
4681
|
-
level: "warn",
|
|
4682
|
-
message: "run cancelled",
|
|
4683
|
-
});
|
|
4684
|
-
await releaseIssueExecutionAndPromote(cancelled);
|
|
4685
|
-
}
|
|
4686
|
-
runningProcesses.delete(run.id);
|
|
4687
|
-
await finalizeAgentStatus(run.agentId, "cancelled");
|
|
4688
|
-
await startNextQueuedRunForAgent(run.agentId);
|
|
4689
|
-
return cancelled;
|
|
4690
|
-
}
|
|
4691
|
-
async function cancelActiveForAgentInternal(agentId, reason = "Cancelled due to agent pause") {
|
|
4692
|
-
const runs = await db
|
|
4693
|
-
.select()
|
|
4694
|
-
.from(heartbeatRuns)
|
|
4695
|
-
.where(and(eq(heartbeatRuns.agentId, agentId), inArray(heartbeatRuns.status, ["queued", "running"])));
|
|
4696
|
-
for (const run of runs) {
|
|
4697
|
-
await setRunStatus(run.id, "cancelled", {
|
|
4698
|
-
finishedAt: new Date(),
|
|
4699
|
-
error: reason,
|
|
4700
|
-
errorCode: "cancelled",
|
|
4701
|
-
});
|
|
4702
|
-
await setWakeupStatus(run.wakeupRequestId, "cancelled", {
|
|
4703
|
-
finishedAt: new Date(),
|
|
4704
|
-
error: reason,
|
|
4705
|
-
});
|
|
4706
|
-
const running = runningProcesses.get(run.id);
|
|
4707
|
-
if (running) {
|
|
4708
|
-
running.child.kill("SIGTERM");
|
|
4709
|
-
runningProcesses.delete(run.id);
|
|
4710
|
-
}
|
|
4711
|
-
await releaseIssueExecutionAndPromote(run);
|
|
4712
|
-
}
|
|
4713
|
-
return runs.length;
|
|
4714
|
-
}
|
|
4715
|
-
async function cancelBudgetScopeWork(scope) {
|
|
4716
|
-
if (scope.scopeType === "agent") {
|
|
4717
|
-
await cancelActiveForAgentInternal(scope.scopeId, "Cancelled due to budget pause");
|
|
4718
|
-
await cancelPendingWakeupsForBudgetScope(scope);
|
|
4719
|
-
return;
|
|
4720
|
-
}
|
|
4721
|
-
const runIds = scope.scopeType === "organization"
|
|
4722
|
-
? await db
|
|
4723
|
-
.select({ id: heartbeatRuns.id })
|
|
4724
|
-
.from(heartbeatRuns)
|
|
4725
|
-
.where(and(eq(heartbeatRuns.orgId, scope.orgId), inArray(heartbeatRuns.status, ["queued", "running"])))
|
|
4726
|
-
.then((rows) => rows.map((row) => row.id))
|
|
4727
|
-
: await listProjectScopedRunIds(scope.orgId, scope.scopeId);
|
|
4728
|
-
for (const runId of runIds) {
|
|
4729
|
-
await cancelRunInternal(runId, "Cancelled due to budget pause");
|
|
4730
|
-
}
|
|
4731
|
-
await cancelPendingWakeupsForBudgetScope(scope);
|
|
4732
|
-
}
|
|
4733
|
-
async function retryRunInternal(runId, opts) {
|
|
4734
|
-
const run = await getRun(runId);
|
|
4735
|
-
if (!run)
|
|
4736
|
-
throw notFound("Heartbeat run not found");
|
|
4737
|
-
if (run.status !== "failed" && run.status !== "timed_out" && run.status !== "cancelled") {
|
|
4738
|
-
throw conflict("Only failed, timed out, or cancelled runs can be retried", {
|
|
4739
|
-
status: run.status,
|
|
4740
|
-
});
|
|
4741
|
-
}
|
|
4742
|
-
const agent = await getAgent(run.agentId);
|
|
4743
|
-
if (!agent)
|
|
4744
|
-
throw notFound("Agent not found");
|
|
4745
|
-
if (agent.status === "paused" ||
|
|
4746
|
-
agent.status === "terminated" ||
|
|
4747
|
-
agent.status === "pending_approval") {
|
|
4748
|
-
throw conflict("Agent is not invokable in its current state", { status: agent.status });
|
|
4749
|
-
}
|
|
4750
|
-
const policy = parseHeartbeatPolicy(agent);
|
|
4751
|
-
if (!policy.wakeOnDemand) {
|
|
4752
|
-
throw conflict("Agent is not configured for on-demand wakeups");
|
|
4753
|
-
}
|
|
4754
|
-
const context = parseObject(run.contextSnapshot);
|
|
4755
|
-
const issueId = readNonEmptyString(context.issueId);
|
|
4756
|
-
let projectId = readNonEmptyString(context.projectId);
|
|
4757
|
-
if (!projectId && issueId) {
|
|
4758
|
-
projectId = await db
|
|
4759
|
-
.select({ projectId: issues.projectId })
|
|
4760
|
-
.from(issues)
|
|
4761
|
-
.where(and(eq(issues.id, issueId), eq(issues.orgId, agent.orgId)))
|
|
4762
|
-
.then((rows) => rows[0]?.projectId ?? null);
|
|
4763
|
-
}
|
|
4764
|
-
const budgetBlock = await budgets.getInvocationBlock(agent.orgId, agent.id, {
|
|
4765
|
-
issueId,
|
|
4766
|
-
projectId,
|
|
4767
|
-
});
|
|
4768
|
-
if (budgetBlock) {
|
|
4769
|
-
throw conflict(budgetBlock.reason, {
|
|
4770
|
-
scopeType: budgetBlock.scopeType,
|
|
4771
|
-
scopeId: budgetBlock.scopeId,
|
|
4772
|
-
});
|
|
4773
|
-
}
|
|
4774
|
-
return enqueueRecoveryRun(run, agent, {
|
|
4775
|
-
recoveryTrigger: "manual",
|
|
4776
|
-
source: "on_demand",
|
|
4777
|
-
triggerDetail: "manual",
|
|
4778
|
-
wakeReason: "retry_failed_run",
|
|
4779
|
-
requestedByActorType: opts?.requestedByActorType ?? "user",
|
|
4780
|
-
requestedByActorId: opts?.requestedByActorId ?? null,
|
|
4781
|
-
now: opts?.now ?? new Date(),
|
|
4782
|
-
});
|
|
4783
|
-
}
|
|
4784
|
-
async function buildSkillAnalytics(scope, opts) {
|
|
4785
|
-
const now = opts?.now ?? new Date();
|
|
4786
|
-
const customDateKeys = opts?.startDate && opts?.endDate
|
|
4787
|
-
? buildDateKeysBetween(opts.startDate, opts.endDate).slice(0, 120)
|
|
4788
|
-
: [];
|
|
4789
|
-
const windowDays = customDateKeys.length > 0
|
|
4790
|
-
? customDateKeys.length
|
|
4791
|
-
: Math.max(1, Math.min(opts?.windowDays ?? 30, 90));
|
|
4792
|
-
const dateKeys = customDateKeys.length > 0
|
|
4793
|
-
? customDateKeys
|
|
4794
|
-
: buildRecentDateKeys(windowDays, now);
|
|
4795
|
-
const startDate = dateKeys[0];
|
|
4796
|
-
const endDate = dateKeys.at(-1);
|
|
4797
|
-
const windowStart = new Date(`${startDate}T00:00:00.000Z`);
|
|
4798
|
-
const windowEnd = new Date(`${endDate}T23:59:59.999Z`);
|
|
4799
|
-
const rows = await db
|
|
4800
|
-
.select({
|
|
4801
|
-
runId: heartbeatRunEvents.runId,
|
|
4802
|
-
createdAt: heartbeatRunEvents.createdAt,
|
|
4803
|
-
eventType: heartbeatRunEvents.eventType,
|
|
4804
|
-
payload: heartbeatRunEvents.payload,
|
|
4805
|
-
})
|
|
4806
|
-
.from(heartbeatRunEvents)
|
|
4807
|
-
.where(and(eq(heartbeatRunEvents.orgId, scope.orgId), ...(scope.agentId ? [eq(heartbeatRunEvents.agentId, scope.agentId)] : []), inArray(heartbeatRunEvents.eventType, ["adapter.invoke", "adapter.skill_usage"]), gte(heartbeatRunEvents.createdAt, windowStart), lte(heartbeatRunEvents.createdAt, windowEnd)))
|
|
4808
|
-
.orderBy(asc(heartbeatRunEvents.createdAt), asc(heartbeatRunEvents.id));
|
|
4809
|
-
const days = new Map();
|
|
4810
|
-
for (const date of dateKeys) {
|
|
4811
|
-
days.set(date, { totalCount: 0, runCount: 0, evidenceCounts: emptySkillEvidenceCounts(), skills: new Map() });
|
|
4812
|
-
}
|
|
4813
|
-
const overallSkills = new Map();
|
|
4814
|
-
const runEvidence = new Map();
|
|
4815
|
-
let totalCount = 0;
|
|
4816
|
-
let totalRunsWithSkills = 0;
|
|
4817
|
-
const evidenceCounts = emptySkillEvidenceCounts();
|
|
4818
|
-
function addRunSkillEvidence(runId, date, evidence) {
|
|
4819
|
-
if (!days.has(date))
|
|
4820
|
-
return;
|
|
4821
|
-
if (evidence.evidence !== "used")
|
|
4822
|
-
return;
|
|
4823
|
-
if (evidence.skills.length === 0)
|
|
4824
|
-
return;
|
|
4825
|
-
const runBucket = runEvidence.get(runId) ?? { date, skills: new Map() };
|
|
4826
|
-
for (const entry of evidence.skills) {
|
|
4827
|
-
const normalized = normalizeLoadedSkill(entry);
|
|
4828
|
-
if (!normalized)
|
|
4829
|
-
continue;
|
|
4830
|
-
const existing = runBucket.skills.get(normalized.key);
|
|
4831
|
-
if (existing) {
|
|
4832
|
-
existing.evidence = strongestSkillEvidence(existing.evidence, evidence.evidence);
|
|
4833
|
-
if (existing.label === fallbackSkillLabel(existing.key) && normalized.label !== fallbackSkillLabel(normalized.key)) {
|
|
4834
|
-
existing.label = normalized.label;
|
|
4835
|
-
}
|
|
4836
|
-
}
|
|
4837
|
-
else {
|
|
4838
|
-
runBucket.skills.set(normalized.key, {
|
|
4839
|
-
key: normalized.key,
|
|
4840
|
-
label: normalized.label,
|
|
4841
|
-
evidence: evidence.evidence,
|
|
4842
|
-
});
|
|
4843
|
-
}
|
|
4844
|
-
}
|
|
4845
|
-
if (runBucket.skills.size > 0)
|
|
4846
|
-
runEvidence.set(runId, runBucket);
|
|
4847
|
-
}
|
|
4848
|
-
async function inferUsedSkillsFromStoredRunLog(row) {
|
|
4849
|
-
if (row.logStore !== "local_file" || !row.logRef)
|
|
4850
|
-
return [];
|
|
4851
|
-
const adapter = (() => {
|
|
4852
|
-
try {
|
|
4853
|
-
return getServerAdapter(row.agentRuntimeType);
|
|
4854
|
-
}
|
|
4855
|
-
catch {
|
|
4856
|
-
return null;
|
|
4857
|
-
}
|
|
4858
|
-
})();
|
|
4859
|
-
if (!adapter)
|
|
4860
|
-
return [];
|
|
4861
|
-
const parser = adapter.parseStdoutLine ?? null;
|
|
4862
|
-
if (!parser)
|
|
4863
|
-
return [];
|
|
4864
|
-
const limitBytes = Math.min(Math.max(row.logBytes ?? 0, 256_000), 2_000_000);
|
|
4865
|
-
const read = await runLogStore
|
|
4866
|
-
.read({ store: "local_file", logRef: row.logRef }, { limitBytes })
|
|
4867
|
-
.catch(() => null);
|
|
4868
|
-
if (!read?.content)
|
|
4869
|
-
return [];
|
|
4870
|
-
const transcript = [];
|
|
4871
|
-
let stdoutBuffer = "";
|
|
4872
|
-
let stderrBuffer = "";
|
|
4873
|
-
for (const line of read.content.split("\n")) {
|
|
4874
|
-
if (!line.trim())
|
|
4875
|
-
continue;
|
|
4876
|
-
let raw;
|
|
4877
|
-
try {
|
|
4878
|
-
raw = JSON.parse(line);
|
|
4879
|
-
}
|
|
4880
|
-
catch {
|
|
4881
|
-
continue;
|
|
4882
|
-
}
|
|
4883
|
-
const parsed = parseObject(raw);
|
|
4884
|
-
const stream = parsed.stream === "stderr" ? "stderr" : parsed.stream === "stdout" ? "stdout" : null;
|
|
4885
|
-
const chunk = typeof parsed.chunk === "string" ? parsed.chunk : "";
|
|
4886
|
-
if (!stream || !chunk)
|
|
4887
|
-
continue;
|
|
4888
|
-
if (stream === "stdout") {
|
|
4889
|
-
stdoutBuffer = appendTranscriptEntriesFromChunk({
|
|
4890
|
-
buffer: stdoutBuffer,
|
|
4891
|
-
chunk,
|
|
4892
|
-
transcript,
|
|
4893
|
-
parser,
|
|
4894
|
-
kind: "stdout",
|
|
4895
|
-
});
|
|
4896
|
-
}
|
|
4897
|
-
else {
|
|
4898
|
-
stderrBuffer = appendTranscriptEntriesFromChunk({
|
|
4899
|
-
buffer: stderrBuffer,
|
|
4900
|
-
chunk,
|
|
4901
|
-
transcript,
|
|
4902
|
-
kind: "stderr",
|
|
4903
|
-
});
|
|
4904
|
-
}
|
|
4905
|
-
}
|
|
4906
|
-
appendTranscriptEntriesFromChunk({
|
|
4907
|
-
buffer: stdoutBuffer,
|
|
4908
|
-
chunk: "",
|
|
4909
|
-
transcript,
|
|
4910
|
-
parser,
|
|
4911
|
-
kind: "stdout",
|
|
4912
|
-
finalize: true,
|
|
4913
|
-
});
|
|
4914
|
-
appendTranscriptEntriesFromChunk({
|
|
4915
|
-
buffer: stderrBuffer,
|
|
4916
|
-
chunk: "",
|
|
4917
|
-
transcript,
|
|
4918
|
-
kind: "stderr",
|
|
4919
|
-
finalize: true,
|
|
4920
|
-
});
|
|
4921
|
-
return inferUsedSkillsFromTranscript(transcript);
|
|
4922
|
-
}
|
|
4923
|
-
for (const row of rows) {
|
|
4924
|
-
const date = new Date(row.createdAt).toISOString().slice(0, 10);
|
|
4925
|
-
const payload = parseObject(row.payload);
|
|
4926
|
-
addRunSkillEvidence(row.runId, date, readSkillEvidenceFromPayload(payload));
|
|
4927
|
-
}
|
|
4928
|
-
const runRows = await db
|
|
4929
|
-
.select({
|
|
4930
|
-
id: heartbeatRuns.id,
|
|
4931
|
-
agentRuntimeType: agents.agentRuntimeType,
|
|
4932
|
-
createdAt: heartbeatRuns.createdAt,
|
|
4933
|
-
logStore: heartbeatRuns.logStore,
|
|
4934
|
-
logRef: heartbeatRuns.logRef,
|
|
4935
|
-
logBytes: heartbeatRuns.logBytes,
|
|
4936
|
-
})
|
|
4937
|
-
.from(heartbeatRuns)
|
|
4938
|
-
.innerJoin(agents, eq(agents.id, heartbeatRuns.agentId))
|
|
4939
|
-
.where(and(eq(heartbeatRuns.orgId, scope.orgId), ...(scope.agentId ? [eq(heartbeatRuns.agentId, scope.agentId)] : []), gte(heartbeatRuns.createdAt, windowStart), lte(heartbeatRuns.createdAt, windowEnd)));
|
|
4940
|
-
for (const row of runRows) {
|
|
4941
|
-
const usedSkills = await inferUsedSkillsFromStoredRunLog(row);
|
|
4942
|
-
if (usedSkills.length === 0)
|
|
4943
|
-
continue;
|
|
4944
|
-
addRunSkillEvidence(row.id, new Date(row.createdAt).toISOString().slice(0, 10), {
|
|
4945
|
-
evidence: "used",
|
|
4946
|
-
skills: usedSkills,
|
|
4947
|
-
});
|
|
4948
|
-
}
|
|
4949
|
-
for (const runBucket of runEvidence.values()) {
|
|
4950
|
-
const bucket = days.get(runBucket.date);
|
|
4951
|
-
if (!bucket || runBucket.skills.size === 0)
|
|
4952
|
-
continue;
|
|
4953
|
-
bucket.runCount += 1;
|
|
4954
|
-
totalRunsWithSkills += 1;
|
|
4955
|
-
for (const { key, label, evidence } of runBucket.skills.values()) {
|
|
4956
|
-
bucket.totalCount += 1;
|
|
4957
|
-
totalCount += 1;
|
|
4958
|
-
incrementSkillEvidenceCount(bucket.evidenceCounts, evidence);
|
|
4959
|
-
incrementSkillEvidenceCount(evidenceCounts, evidence);
|
|
4960
|
-
const existingDaySkill = bucket.skills.get(key);
|
|
4961
|
-
if (existingDaySkill) {
|
|
4962
|
-
existingDaySkill.count += 1;
|
|
4963
|
-
existingDaySkill.evidence = strongestSkillEvidence(existingDaySkill.evidence, evidence);
|
|
4964
|
-
incrementSkillEvidenceCount(existingDaySkill.evidenceCounts, evidence);
|
|
4965
|
-
}
|
|
4966
|
-
else {
|
|
4967
|
-
const skillEvidenceCounts = emptySkillEvidenceCounts();
|
|
4968
|
-
incrementSkillEvidenceCount(skillEvidenceCounts, evidence);
|
|
4969
|
-
bucket.skills.set(key, { key, label, count: 1, evidence, evidenceCounts: skillEvidenceCounts });
|
|
4970
|
-
}
|
|
4971
|
-
const existingOverallSkill = overallSkills.get(key);
|
|
4972
|
-
if (existingOverallSkill) {
|
|
4973
|
-
existingOverallSkill.count += 1;
|
|
4974
|
-
existingOverallSkill.evidence = strongestSkillEvidence(existingOverallSkill.evidence, evidence);
|
|
4975
|
-
incrementSkillEvidenceCount(existingOverallSkill.evidenceCounts, evidence);
|
|
4976
|
-
}
|
|
4977
|
-
else {
|
|
4978
|
-
const skillEvidenceCounts = emptySkillEvidenceCounts();
|
|
4979
|
-
incrementSkillEvidenceCount(skillEvidenceCounts, evidence);
|
|
4980
|
-
overallSkills.set(key, { key, label, count: 1, evidence, evidenceCounts: skillEvidenceCounts });
|
|
4981
|
-
}
|
|
4982
|
-
}
|
|
4983
|
-
}
|
|
4984
|
-
return {
|
|
4985
|
-
agentId: scope.agentId ?? "__all__",
|
|
4986
|
-
orgId: scope.orgId,
|
|
4987
|
-
windowDays,
|
|
4988
|
-
startDate,
|
|
4989
|
-
endDate,
|
|
4990
|
-
totalCount,
|
|
4991
|
-
totalRunsWithSkills,
|
|
4992
|
-
evidenceCounts,
|
|
4993
|
-
skills: Array.from(overallSkills.values()).sort((left, right) => (right.count - left.count
|
|
4994
|
-
|| left.label.localeCompare(right.label)
|
|
4995
|
-
|| left.key.localeCompare(right.key))),
|
|
4996
|
-
days: dateKeys.map((date) => {
|
|
4997
|
-
const bucket = days.get(date);
|
|
4998
|
-
return {
|
|
4999
|
-
date,
|
|
5000
|
-
totalCount: bucket.totalCount,
|
|
5001
|
-
runCount: bucket.runCount,
|
|
5002
|
-
evidenceCounts: bucket.evidenceCounts,
|
|
5003
|
-
skills: Array.from(bucket.skills.values()).sort((left, right) => (right.count - left.count
|
|
5004
|
-
|| left.label.localeCompare(right.label)
|
|
5005
|
-
|| left.key.localeCompare(right.key))),
|
|
5006
|
-
};
|
|
5007
|
-
}),
|
|
5008
|
-
};
|
|
5009
|
-
}
|
|
904
|
+
const baseContext = {
|
|
905
|
+
db, instanceSettings, getCurrentUserRedactionOptions, runLogStore, runContextSvc, issuesSvc, documentsSvc, executionWorkspacesSvc, workspaceOperationsSvc, activeRunExecutions, budgetHooks, budgets,
|
|
906
|
+
getAgent, getRun, getRuntimeState, getTaskSession, getLatestRunForSession, getOldestRunForSession, resolveNormalizedUsageForSession, evaluateSessionCompaction, resolveSessionBeforeForWakeup, resolveExplicitResumeSessionOverride, upsertTaskSession, clearTaskSessions, ensureRuntimeState, buildHeartbeatObservabilityContext, emitHeartbeatObservationEvent, emitHeartbeatLiveEval, setRunStatus, setWakeupStatus, updateWakeupRequestRecord, insertWakeupRequestRecord, appendRunEvent, nextRunEventSeq, persistRunProcessMetadata, clearDetachedRunWarning, countRunningRunsForAgent, claimQueuedRun, finalizeAgentStatus, reapOrphanedRuns, resumeQueuedRuns, updateRuntimeState, startNextQueuedRunForAgent,
|
|
907
|
+
};
|
|
908
|
+
const recoveryHandlers = createHeartbeatRecoveryHandlers({ ...baseContext, startNextQueuedRunForAgent });
|
|
909
|
+
const wakeupHandlers = createHeartbeatWakeupHandlers({ ...baseContext, ...recoveryHandlers, startNextQueuedRunForAgent });
|
|
910
|
+
const releaseHandlers = createHeartbeatReleaseHandlers({ ...baseContext, ...recoveryHandlers, ...wakeupHandlers });
|
|
911
|
+
const executeHandlers = createHeartbeatExecuteHandlers({ ...baseContext, ...recoveryHandlers, ...releaseHandlers, ...wakeupHandlers });
|
|
912
|
+
const miscHandlers = createHeartbeatMiscHandlers({ ...baseContext, ...recoveryHandlers, ...releaseHandlers, ...wakeupHandlers, ...executeHandlers });
|
|
913
|
+
const { enqueueRecoveryRun, enqueueProcessLossRetry, evaluatePassiveIssueClosureForLockedIssue, parseHeartbeatPolicy } = recoveryHandlers;
|
|
914
|
+
const { enqueueWakeup } = wakeupHandlers;
|
|
915
|
+
const { releaseIssueExecutionAndPromote } = releaseHandlers;
|
|
916
|
+
const { executeRun } = executeHandlers;
|
|
917
|
+
const { resumeDeferredWakeupsForAgent, listProjectScopedRunIds, listProjectScopedWakeupIds, cancelPendingWakeupsForBudgetScope, cancelRunInternal, cancelActiveForAgentInternal, cancelBudgetScopeWork, retryRunInternal, buildSkillAnalytics } = miscHandlers;
|
|
5010
918
|
return {
|
|
5011
919
|
list: async (orgId, agentId, limit) => {
|
|
5012
920
|
const query = db
|