@rudderhq/server 0.2.0-canary.9 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bootstrap/register-api-routes.d.ts.map +1 -1
- package/dist/bootstrap/register-api-routes.js +2 -0
- package/dist/bootstrap/register-api-routes.js.map +1 -1
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js +8 -1
- package/dist/bundled-plugins/plugin-linear/dist/ui/index.js.map +2 -2
- package/dist/bundled-plugins/plugin-linear/dist/worker.js +20 -5
- package/dist/bundled-plugins/plugin-linear/dist/worker.js.map +2 -2
- package/dist/home-paths.d.ts +2 -0
- package/dist/home-paths.d.ts.map +1 -1
- package/dist/home-paths.js +6 -1
- package/dist/home-paths.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -2
- package/dist/index.js.map +1 -1
- package/dist/langfuse-transcript.d.ts.map +1 -1
- package/dist/langfuse-transcript.js +16 -2
- package/dist/langfuse-transcript.js.map +1 -1
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +54 -1
- package/dist/middleware/auth.js.map +1 -1
- package/dist/onboarding-assets/ceo/HEARTBEAT.md +8 -4
- package/dist/onboarding-assets/default/HEARTBEAT.md +7 -4
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +79 -4
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/approvals.d.ts.map +1 -1
- package/dist/routes/approvals.js +47 -2
- package/dist/routes/approvals.js.map +1 -1
- package/dist/routes/chats.d.ts.map +1 -1
- package/dist/routes/chats.js +300 -92
- package/dist/routes/chats.js.map +1 -1
- package/dist/routes/costs.d.ts.map +1 -1
- package/dist/routes/costs.js +20 -0
- package/dist/routes/costs.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +236 -22
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/onboarding.d.ts +3 -0
- package/dist/routes/onboarding.d.ts.map +1 -0
- package/dist/routes/onboarding.js +545 -0
- package/dist/routes/onboarding.js.map +1 -0
- package/dist/routes/orgs.d.ts.map +1 -1
- package/dist/routes/orgs.js +22 -0
- package/dist/routes/orgs.js.map +1 -1
- package/dist/services/activity.d.ts.map +1 -1
- package/dist/services/activity.js +32 -1
- package/dist/services/activity.js.map +1 -1
- package/dist/services/agent-run-context.d.ts +1 -0
- package/dist/services/agent-run-context.d.ts.map +1 -1
- package/dist/services/agent-run-context.js +1 -0
- package/dist/services/agent-run-context.js.map +1 -1
- package/dist/services/agents.d.ts +13 -13
- package/dist/services/automations.d.ts +2 -2
- package/dist/services/calendar.d.ts +4 -4
- package/dist/services/chat-assistant.d.ts +11 -2
- package/dist/services/chat-assistant.d.ts.map +1 -1
- package/dist/services/chat-assistant.js +143 -8
- package/dist/services/chat-assistant.js.map +1 -1
- package/dist/services/chats.d.ts +112 -13
- package/dist/services/chats.d.ts.map +1 -1
- package/dist/services/chats.js +218 -38
- package/dist/services/chats.js.map +1 -1
- package/dist/services/costs.d.ts +21 -0
- package/dist/services/costs.d.ts.map +1 -1
- package/dist/services/costs.js +102 -2
- package/dist/services/costs.js.map +1 -1
- package/dist/services/finance.d.ts +2 -2
- package/dist/services/goals.d.ts +12 -12
- package/dist/services/instance-settings.d.ts.map +1 -1
- package/dist/services/instance-settings.js +27 -16
- package/dist/services/instance-settings.js.map +1 -1
- package/dist/services/issue-approvals.d.ts +16 -2
- package/dist/services/issue-approvals.d.ts.map +1 -1
- package/dist/services/issue-approvals.js +27 -4
- package/dist/services/issue-approvals.js.map +1 -1
- package/dist/services/issue-review-wakeup.d.ts +49 -1
- package/dist/services/issue-review-wakeup.d.ts.map +1 -1
- package/dist/services/issue-review-wakeup.js +39 -2
- package/dist/services/issue-review-wakeup.js.map +1 -1
- package/dist/services/issues.d.ts +2 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +126 -5
- package/dist/services/issues.js.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.d.ts +1 -0
- package/dist/services/knowledge-portability/organization-skills.d.ts.map +1 -1
- package/dist/services/knowledge-portability/organization-skills.js +3 -2
- package/dist/services/knowledge-portability/organization-skills.js.map +1 -1
- package/dist/services/messenger.d.ts +5 -0
- package/dist/services/messenger.d.ts.map +1 -1
- package/dist/services/messenger.js +154 -15
- package/dist/services/messenger.js.map +1 -1
- package/dist/services/organization-workspace-browser.d.ts.map +1 -1
- package/dist/services/organization-workspace-browser.js +64 -9
- package/dist/services/organization-workspace-browser.js.map +1 -1
- package/dist/services/orgs.d.ts +1 -1
- package/dist/services/plugin-registry.d.ts +4 -4
- package/dist/services/projects.d.ts +1 -1
- package/dist/services/runtime-kernel/heartbeat.d.ts.map +1 -1
- package/dist/services/runtime-kernel/heartbeat.js +571 -31
- package/dist/services/runtime-kernel/heartbeat.js.map +1 -1
- package/dist/services/secrets.d.ts +5 -5
- package/dist/services/workspace-backups.d.ts.map +1 -1
- package/dist/services/workspace-backups.js +6 -0
- package/dist/services/workspace-backups.js.map +1 -1
- package/dist/services/workspace-runtime.d.ts.map +1 -1
- package/dist/services/workspace-runtime.js +2 -0
- package/dist/services/workspace-runtime.js.map +1 -1
- package/package.json +13 -13
- package/resources/bundled-skills/rudder/SKILL.md +72 -7
- package/resources/bundled-skills/rudder/references/cli-reference.md +34 -9
- package/resources/bundled-skills/rudder/references/organization-skills.md +12 -7
- package/resources/bundled-skills/rudder-create-agent/references/cli-reference.md +1 -0
- package/skills/rudder/SKILL.md +72 -7
- package/skills/rudder/references/cli-reference.md +34 -9
- package/skills/rudder/references/organization-skills.md +12 -7
- package/skills/rudder-create-agent/references/cli-reference.md +1 -0
- package/ui-dist/assets/{_basePickBy-aX2f6dVl.js → _basePickBy-EvWeCTRb.js} +1 -1
- package/ui-dist/assets/{_baseUniq-BYwL7heN.js → _baseUniq-C_DXAETg.js} +1 -1
- package/ui-dist/assets/{arc-BG9f5pwY.js → arc-BWTkVM-u.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-2XIMDMQ5-BFFQoJJ1.js → architectureDiagram-2XIMDMQ5-yyX54Dgl.js} +1 -1
- package/ui-dist/assets/{blockDiagram-WCTKOSBZ-Bvx1IB1z.js → blockDiagram-WCTKOSBZ-DleWvS8P.js} +1 -1
- package/ui-dist/assets/{c4Diagram-IC4MRINW-DJbCE4sh.js → c4Diagram-IC4MRINW-CltWqWC_.js} +1 -1
- package/ui-dist/assets/channel-Gdzxe2a1.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-BOVbLFsN.js → chunk-4BX2VUAB-CA6RvGN7.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-D5pKj6S9.js → chunk-55IACEB6-D_EpF39w.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-OY5xuJeR.js → chunk-FMBD7UC4-CYMkBnLy.js} +1 -1
- package/ui-dist/assets/{chunk-JSJVCQXG-C5X67KZg.js → chunk-JSJVCQXG-CIY2Cb1T.js} +1 -1
- package/ui-dist/assets/{chunk-KX2RTZJC-C-4PZ9Q_.js → chunk-KX2RTZJC-BUyGoIKj.js} +1 -1
- package/ui-dist/assets/{chunk-NQ4KR5QH-XysPlqPj.js → chunk-NQ4KR5QH-DkntSLtY.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-B5wEbFHo.js → chunk-QZHKN3VN-DeEs3yL0.js} +1 -1
- package/ui-dist/assets/{chunk-WL4C6EOR-BanwYFa2.js → chunk-WL4C6EOR-Va8TkdTb.js} +1 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-BN6WyuN3.js +1 -0
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-BN6WyuN3.js +1 -0
- package/ui-dist/assets/clone-DL9OCUyP.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-Cd4q2swD.js → cose-bilkent-S5V4N54A-Bb6NLaVm.js} +1 -1
- package/ui-dist/assets/{dagre-KLK3FWXG-B_VyOhf3.js → dagre-KLK3FWXG-DpqLnZ3A.js} +1 -1
- package/ui-dist/assets/{diagram-E7M64L7V-BRoG4Mz6.js → diagram-E7M64L7V-D7J8NbEW.js} +1 -1
- package/ui-dist/assets/{diagram-IFDJBPK2-CRU_A9p9.js → diagram-IFDJBPK2-Ds2u81Zi.js} +1 -1
- package/ui-dist/assets/{diagram-P4PSJMXO-BYSQDbfb.js → diagram-P4PSJMXO-BwBplO7L.js} +1 -1
- package/ui-dist/assets/{erDiagram-INFDFZHY-v5j1kyWr.js → erDiagram-INFDFZHY-Ba-Ynr8U.js} +1 -1
- package/ui-dist/assets/{flowDiagram-PKNHOUZH-C06ZQgTj.js → flowDiagram-PKNHOUZH-FnOXpXb_.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-A5KZAMGK-Dw9p5nQ1.js → ganttDiagram-A5KZAMGK-B8-MpUjy.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-K3NZZRJ6-CrpXRIaP.js → gitGraphDiagram-K3NZZRJ6-DvyBGQTF.js} +1 -1
- package/ui-dist/assets/{graph-ClTUmULf.js → graph-BdpIVR-I.js} +1 -1
- package/ui-dist/assets/{index-DK13xhRv.js → index-3CPMGfu4.js} +1 -1
- package/ui-dist/assets/index-44A3IjSd.css +1 -0
- package/ui-dist/assets/{index-L6M3nVxh.js → index-B4seykMn.js} +1 -1
- package/ui-dist/assets/{index-Bpc2gRVo.js → index-B5Lq7qho.js} +1 -1
- package/ui-dist/assets/{index-DkDkjZ-D.js → index-BKWZYXO6.js} +1 -1
- package/ui-dist/assets/{index-DxzAgTWd.js → index-BO-P9C91.js} +1 -1
- package/ui-dist/assets/{index-BvGogi9q.js → index-BO9KiNr0.js} +1 -1
- package/ui-dist/assets/{index-Btwy7Cp-.js → index-Bd_GitJ7.js} +1 -1
- package/ui-dist/assets/{index-DNlWBtHa.js → index-BeyQP4jc.js} +1 -1
- package/ui-dist/assets/{index-4uxadHo5.js → index-Bp3rYm9R.js} +1 -1
- package/ui-dist/assets/{index-DWFMs9uk.js → index-CBAKsDOH.js} +1 -1
- package/ui-dist/assets/{index-T81awgzh.js → index-CWPEuLky.js} +1 -1
- package/ui-dist/assets/{index-DAhPD1Ss.js → index-Ce0xbQ5p.js} +1 -1
- package/ui-dist/assets/{index-_x9smX4T.js → index-ChyWxMPa.js} +1 -1
- package/ui-dist/assets/{index-CIr7H9OI.js → index-CkEo4bIl.js} +1 -1
- package/ui-dist/assets/{index-sLGLHxIu.js → index-CvzsgQH3.js} +1 -1
- package/ui-dist/assets/{index-D-6z8wxx.js → index-DF0X3XZi.js} +1 -1
- package/ui-dist/assets/{index-BVfM9ax8.js → index-DNFqhIup.js} +1 -1
- package/ui-dist/assets/index-Dfi8PbGx.js +1484 -0
- package/ui-dist/assets/{index-C_BTFRTX.js → index-Dys_qAzR.js} +1 -1
- package/ui-dist/assets/{index-Cr7n11UG.js → index-DzKALBsQ.js} +1 -1
- package/ui-dist/assets/{index-CqYInp-c.js → index-Qe9bMaYk.js} +1 -1
- package/ui-dist/assets/{index-CQWmziMF.js → index-baeevrWz.js} +1 -1
- package/ui-dist/assets/{index-BYC_xlrx.js → index-bs5pLhnN.js} +1 -1
- package/ui-dist/assets/{infoDiagram-LFFYTUFH-BA3VxOIU.js → infoDiagram-LFFYTUFH-51Iz4iFI.js} +1 -1
- package/ui-dist/assets/{ishikawaDiagram-PHBUUO56-DGrizi0S.js → ishikawaDiagram-PHBUUO56-XMkPw0tW.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-4ABVD52K-6ey34a7e.js → journeyDiagram-4ABVD52K-DAX0bTCG.js} +1 -1
- package/ui-dist/assets/{kanban-definition-K7BYSVSG-CwNnmsam.js → kanban-definition-K7BYSVSG-DndcgBkd.js} +1 -1
- package/ui-dist/assets/{layout-buNxvllr.js → layout-DE8DhR5g.js} +1 -1
- package/ui-dist/assets/{linear-BPWhxaRl.js → linear-B6lAW9Wb.js} +1 -1
- package/ui-dist/assets/{mermaid.core-Cajx0s-z.js → mermaid.core-BG--kYhA.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-YRQLILUH-Bf5InEp-.js → mindmap-definition-YRQLILUH-DkjV0oE3.js} +1 -1
- package/ui-dist/assets/{pieDiagram-SKSYHLDU-CZFz7NWC.js → pieDiagram-SKSYHLDU-D03TjqYu.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-337W2JSQ-XBmKVoc9.js → quadrantDiagram-337W2JSQ-C0oqv-xU.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-Z7DCOOCP-BkgdDv0H.js → requirementDiagram-Z7DCOOCP-okIS8feM.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-WA2Y5GQK-CASFR28i.js → sankeyDiagram-WA2Y5GQK-WOnxUdkO.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-2WXFIKYE-BzY7LMRv.js → sequenceDiagram-2WXFIKYE-RVCXfMRR.js} +1 -1
- package/ui-dist/assets/{stateDiagram-RAJIS63D-C9UMSk36.js → stateDiagram-RAJIS63D-CZFHvVtT.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DgYYudAJ.js +1 -0
- package/ui-dist/assets/{timeline-definition-YZTLITO2-D6m4R4xe.js → timeline-definition-YZTLITO2-S0uy5mlJ.js} +1 -1
- package/ui-dist/assets/{treemap-KZPCXAKY-7V9mnT8T.js → treemap-KZPCXAKY-Bhyg_yHs.js} +1 -1
- package/ui-dist/assets/{vennDiagram-LZ73GAT5-Ci-sfAyq.js → vennDiagram-LZ73GAT5-EnVupOQz.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-JWTSCODW-BayXhRSu.js → xychartDiagram-JWTSCODW-BYpdJxGK.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-ClX7n84B.js +0 -1
- package/ui-dist/assets/classDiagram-VBA2DB6C-DvWbsnVz.js +0 -1
- package/ui-dist/assets/classDiagram-v2-RAHNMMFH-DvWbsnVz.js +0 -1
- package/ui-dist/assets/clone-Dla3A8ZA.js +0 -1
- package/ui-dist/assets/index-CSANx6ee.css +0 -1
- package/ui-dist/assets/index-DCa9-Sy-.js +0 -1439
- package/ui-dist/assets/stateDiagram-v2-FVOUBMTO-DWVhbAdj.js +0 -1
|
@@ -20,7 +20,7 @@ import { summarizeRuntimeSkillsForTrace } from "../runtime-trace-metadata.js";
|
|
|
20
20
|
import { buildWorkspaceReadyComment, cleanupExecutionWorkspaceArtifacts, ensureRuntimeServicesForRun, persistAdapterManagedRuntimeServices, realizeExecutionWorkspace, releaseRuntimeServicesForRun, } from "../workspace-runtime.js";
|
|
21
21
|
import { issueService } from "../issues.js";
|
|
22
22
|
import { documentService } from "../documents.js";
|
|
23
|
-
import { buildIssueConvergenceReviewWakeupOptions } from "../issue-review-wakeup.js";
|
|
23
|
+
import { buildIssueConvergenceReviewWakeupOptions, buildIssueReviewCloseoutWakeupOptions, } from "../issue-review-wakeup.js";
|
|
24
24
|
import { executionWorkspaceService } from "../execution-workspaces.js";
|
|
25
25
|
import { buildObservedRunLangfuseScores } from "../run-intelligence.js";
|
|
26
26
|
import { workspaceOperationService } from "../workspace-operations.js";
|
|
@@ -48,6 +48,9 @@ const ISSUE_PASSIVE_FOLLOWUP_REASON = "issue_passive_followup";
|
|
|
48
48
|
const ISSUE_PASSIVE_FOLLOWUP_WAKE_SOURCE = "passive_issue_followup";
|
|
49
49
|
const ISSUE_PASSIVE_FOLLOWUP_FAILURE_REASON = "missing_closure";
|
|
50
50
|
const ISSUE_PASSIVE_FOLLOWUP_MAX_ATTEMPTS = 2;
|
|
51
|
+
const ISSUE_REVIEW_CLOSEOUT_REASON = "issue_review_closeout_missing";
|
|
52
|
+
const ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON = "missing_review_decision";
|
|
53
|
+
const ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS = 2;
|
|
51
54
|
const ISSUE_PASSIVE_FOLLOWUP_COOLDOWN_MS_BY_ATTEMPT = new Map([
|
|
52
55
|
[1, 2 * 60 * 1000],
|
|
53
56
|
[2, 5 * 60 * 1000],
|
|
@@ -184,15 +187,42 @@ export function buildHeartbeatAdapterInvokePayload(input) {
|
|
|
184
187
|
.map((entry) => normalizeLoadedSkill(entry))
|
|
185
188
|
.filter((entry) => Boolean(entry))
|
|
186
189
|
: [];
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
+
const promptRequestedSkills = inferUsedSkillsFromPrompt(input.meta.prompt, input.runtimeSkills);
|
|
191
|
+
const loadedSkills = Array.isArray(input.meta.loadedSkills) && input.meta.loadedSkills.length > 0
|
|
192
|
+
? input.meta.loadedSkills
|
|
193
|
+
.map((entry) => normalizeLoadedSkillForPayload(entry))
|
|
194
|
+
.filter((entry) => Boolean(entry))
|
|
195
|
+
: input.runtimeSkills
|
|
196
|
+
.map((entry) => ({
|
|
197
|
+
key: entry.key,
|
|
198
|
+
runtimeName: entry.runtimeName ?? null,
|
|
199
|
+
name: entry.name ?? null,
|
|
200
|
+
description: entry.description ?? null,
|
|
201
|
+
}));
|
|
202
|
+
const loadedSkillEvidence = loadedSkills
|
|
203
|
+
.map((entry) => normalizeLoadedSkill(entry))
|
|
204
|
+
.filter((entry) => Boolean(entry));
|
|
205
|
+
const skillEvidence = resolveSkillEvidence({
|
|
206
|
+
usedSkills: explicitUsedSkills,
|
|
207
|
+
requestedSkills: promptRequestedSkills,
|
|
208
|
+
loadedSkills: loadedSkillEvidence,
|
|
209
|
+
});
|
|
190
210
|
return {
|
|
191
211
|
...input.meta,
|
|
192
212
|
...summarizeRuntimeSkillsForTrace(input.runtimeSkills),
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
213
|
+
loadedSkillCount: loadedSkills.length,
|
|
214
|
+
loadedSkillKeys: loadedSkills.map((entry) => entry.key),
|
|
215
|
+
loadedSkills,
|
|
216
|
+
usedSkillCount: explicitUsedSkills.length,
|
|
217
|
+
usedSkillKeys: explicitUsedSkills.map((entry) => entry.key),
|
|
218
|
+
usedSkills: explicitUsedSkills,
|
|
219
|
+
promptRequestedSkillCount: promptRequestedSkills.length,
|
|
220
|
+
promptRequestedSkillKeys: promptRequestedSkills.map((entry) => entry.key),
|
|
221
|
+
promptRequestedSkills,
|
|
222
|
+
skillEvidenceType: skillEvidence.evidence,
|
|
223
|
+
skillEvidenceCount: skillEvidence.skills.length,
|
|
224
|
+
skillEvidenceKeys: skillEvidence.skills.map((entry) => entry.key),
|
|
225
|
+
skillEvidenceSkills: skillEvidence.skills,
|
|
196
226
|
};
|
|
197
227
|
}
|
|
198
228
|
function buildRecentDateKeys(windowDays, now) {
|
|
@@ -238,6 +268,151 @@ function normalizeLoadedSkill(value) {
|
|
|
238
268
|
const label = rawRuntimeName ?? rawName ?? fallbackSkillLabel(key);
|
|
239
269
|
return { key, label };
|
|
240
270
|
}
|
|
271
|
+
function normalizeLoadedSkillForPayload(value) {
|
|
272
|
+
const skill = parseObject(value);
|
|
273
|
+
const rawKey = readNonEmptyString(skill.key);
|
|
274
|
+
const rawRuntimeName = readNonEmptyString(skill.runtimeName);
|
|
275
|
+
const rawName = readNonEmptyString(skill.name);
|
|
276
|
+
const key = rawKey ?? rawRuntimeName ?? rawName;
|
|
277
|
+
if (!key)
|
|
278
|
+
return null;
|
|
279
|
+
return {
|
|
280
|
+
key,
|
|
281
|
+
runtimeName: rawRuntimeName ?? null,
|
|
282
|
+
name: rawName ?? null,
|
|
283
|
+
description: readNonEmptyString(skill.description) ?? null,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function emptySkillEvidenceCounts() {
|
|
287
|
+
return { used: 0, requested: 0, loaded: 0 };
|
|
288
|
+
}
|
|
289
|
+
function incrementSkillEvidenceCount(counts, evidence) {
|
|
290
|
+
counts[evidence] += 1;
|
|
291
|
+
}
|
|
292
|
+
function strongestSkillEvidence(left, right) {
|
|
293
|
+
const rank = {
|
|
294
|
+
used: 3,
|
|
295
|
+
requested: 2,
|
|
296
|
+
loaded: 1,
|
|
297
|
+
};
|
|
298
|
+
return rank[right] > rank[left] ? right : left;
|
|
299
|
+
}
|
|
300
|
+
function resolveSkillEvidence(input) {
|
|
301
|
+
if (input.usedSkills.length > 0)
|
|
302
|
+
return { evidence: "used", skills: input.usedSkills };
|
|
303
|
+
if (input.requestedSkills.length > 0)
|
|
304
|
+
return { evidence: "requested", skills: input.requestedSkills };
|
|
305
|
+
return { evidence: "loaded", skills: [] };
|
|
306
|
+
}
|
|
307
|
+
function readSkillEvidenceFromPayload(payload) {
|
|
308
|
+
const loadedSkills = Array.isArray(payload.loadedSkills)
|
|
309
|
+
? payload.loadedSkills
|
|
310
|
+
.map((entry) => normalizeLoadedSkill(entry))
|
|
311
|
+
.filter((entry) => Boolean(entry))
|
|
312
|
+
: [];
|
|
313
|
+
const usedSkills = Array.isArray(payload.usedSkills)
|
|
314
|
+
? payload.usedSkills
|
|
315
|
+
.map((entry) => normalizeLoadedSkill(entry))
|
|
316
|
+
.filter((entry) => Boolean(entry))
|
|
317
|
+
: [];
|
|
318
|
+
const requestedSkills = Array.isArray(payload.promptRequestedSkills)
|
|
319
|
+
? payload.promptRequestedSkills
|
|
320
|
+
.map((entry) => normalizeLoadedSkill(entry))
|
|
321
|
+
.filter((entry) => Boolean(entry))
|
|
322
|
+
: inferUsedSkillsFromPrompt(payload.prompt, loadedSkills);
|
|
323
|
+
return resolveSkillEvidence({ usedSkills, requestedSkills, loadedSkills });
|
|
324
|
+
}
|
|
325
|
+
function extractSkillSlugFromPath(value) {
|
|
326
|
+
const normalized = value.trim().replace(/\\/g, "/").replace(/[?#].*$/u, "").replace(/\/+$/u, "");
|
|
327
|
+
if (!normalized.endsWith("/SKILL.md"))
|
|
328
|
+
return null;
|
|
329
|
+
const parts = normalized.split("/").filter(Boolean);
|
|
330
|
+
if (parts.length < 2)
|
|
331
|
+
return null;
|
|
332
|
+
const slug = parts.at(-2);
|
|
333
|
+
if (!slug || slug === "." || slug === "..")
|
|
334
|
+
return null;
|
|
335
|
+
return slug;
|
|
336
|
+
}
|
|
337
|
+
function collectSkillPathsFromText(value) {
|
|
338
|
+
const paths = [];
|
|
339
|
+
const pattern = /(?:^|[\s"'`(=])((?:\.{1,2}\/|~\/|\/)?(?:[^\s"'`()<>|;&/]+\/)+SKILL\.md(?:[?#][^\s"'`()<>|;&]*)?)(?=$|[\s"'`()<>|;&])/giu;
|
|
340
|
+
for (const match of value.matchAll(pattern)) {
|
|
341
|
+
const pathValue = match[1]?.trim();
|
|
342
|
+
if (pathValue)
|
|
343
|
+
paths.push(pathValue);
|
|
344
|
+
}
|
|
345
|
+
return paths;
|
|
346
|
+
}
|
|
347
|
+
function collectStringValues(value, depth = 0) {
|
|
348
|
+
if (depth > 4)
|
|
349
|
+
return [];
|
|
350
|
+
if (typeof value === "string")
|
|
351
|
+
return [value];
|
|
352
|
+
if (Array.isArray(value))
|
|
353
|
+
return value.flatMap((entry) => collectStringValues(entry, depth + 1));
|
|
354
|
+
const record = parseObject(value);
|
|
355
|
+
return Object.values(record).flatMap((entry) => collectStringValues(entry, depth + 1));
|
|
356
|
+
}
|
|
357
|
+
function normalizeSkillUseFromPath(value) {
|
|
358
|
+
const slug = extractSkillSlugFromPath(value);
|
|
359
|
+
if (!slug)
|
|
360
|
+
return null;
|
|
361
|
+
return { key: slug, label: slug };
|
|
362
|
+
}
|
|
363
|
+
function dedupeSkillUses(skills) {
|
|
364
|
+
const seen = new Set();
|
|
365
|
+
const result = [];
|
|
366
|
+
for (const skill of skills) {
|
|
367
|
+
const normalized = normalizeLoadedSkill(skill);
|
|
368
|
+
if (!normalized || seen.has(normalized.key))
|
|
369
|
+
continue;
|
|
370
|
+
seen.add(normalized.key);
|
|
371
|
+
result.push(normalized);
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
function collectSkillUsesFromText(value) {
|
|
376
|
+
return collectSkillPathsFromText(value)
|
|
377
|
+
.map((entry) => normalizeSkillUseFromPath(entry))
|
|
378
|
+
.filter((entry) => Boolean(entry));
|
|
379
|
+
}
|
|
380
|
+
function readToolCommandInput(input) {
|
|
381
|
+
if (typeof input === "string")
|
|
382
|
+
return input;
|
|
383
|
+
const record = parseObject(input);
|
|
384
|
+
return readNonEmptyString(record.command) ?? readNonEmptyString(record.cmd);
|
|
385
|
+
}
|
|
386
|
+
function isCommandTranscriptTool(name) {
|
|
387
|
+
const normalized = name.trim().toLowerCase();
|
|
388
|
+
return ["command_execution", "shell", "shelltoolcall", "bash"].includes(normalized);
|
|
389
|
+
}
|
|
390
|
+
function isReadTranscriptTool(name) {
|
|
391
|
+
const normalized = name.trim().toLowerCase();
|
|
392
|
+
if (isCommandTranscriptTool(normalized))
|
|
393
|
+
return true;
|
|
394
|
+
if (/(?:^|[_-])(read|fetch|open|cat)(?:$|[_-])/.test(normalized))
|
|
395
|
+
return true;
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
function inferUsedSkillsFromTranscript(transcript) {
|
|
399
|
+
const skills = [];
|
|
400
|
+
for (const entry of transcript) {
|
|
401
|
+
if (entry.kind !== "tool_call")
|
|
402
|
+
continue;
|
|
403
|
+
if (!isReadTranscriptTool(entry.name))
|
|
404
|
+
continue;
|
|
405
|
+
const command = readToolCommandInput(entry.input);
|
|
406
|
+
if (command) {
|
|
407
|
+
skills.push(...collectSkillUsesFromText(command));
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
for (const value of collectStringValues(entry.input)) {
|
|
411
|
+
skills.push(...collectSkillUsesFromText(value));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return dedupeSkillUses(skills);
|
|
415
|
+
}
|
|
241
416
|
function normalizeSkillCandidate(value) {
|
|
242
417
|
return value
|
|
243
418
|
?.trim()
|
|
@@ -787,6 +962,20 @@ function normalizePassiveFollowupContext(raw) {
|
|
|
787
962
|
queuedAt: readNonEmptyString(parsed.queuedAt),
|
|
788
963
|
};
|
|
789
964
|
}
|
|
965
|
+
function normalizeReviewCloseoutContext(raw) {
|
|
966
|
+
const parsed = parseObject(raw);
|
|
967
|
+
const originRunId = readNonEmptyString(parsed.originRunId);
|
|
968
|
+
if (!originRunId)
|
|
969
|
+
return null;
|
|
970
|
+
const attempt = Math.max(0, Math.floor(asNumber(parsed.attempt, 0)));
|
|
971
|
+
return {
|
|
972
|
+
originRunId,
|
|
973
|
+
previousRunId: readNonEmptyString(parsed.previousRunId),
|
|
974
|
+
attempt,
|
|
975
|
+
maxAttempts: Math.max(1, Math.floor(asNumber(parsed.maxAttempts, ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS))),
|
|
976
|
+
reason: ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
790
979
|
function passiveFollowupCooldownMs(attempt) {
|
|
791
980
|
return ISSUE_PASSIVE_FOLLOWUP_COOLDOWN_MS_BY_ATTEMPT.get(attempt) ?? 5 * 60 * 1000;
|
|
792
981
|
}
|
|
@@ -1783,6 +1972,15 @@ export function heartbeatService(db) {
|
|
|
1783
1972
|
.then((rows) => rows[0] ?? null);
|
|
1784
1973
|
return Boolean(commentActivity);
|
|
1785
1974
|
}
|
|
1975
|
+
async function runHasIssueReviewDecision(tx, run, issueId) {
|
|
1976
|
+
const decisionActivity = await tx
|
|
1977
|
+
.select({ id: activityLog.id })
|
|
1978
|
+
.from(activityLog)
|
|
1979
|
+
.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)))
|
|
1980
|
+
.limit(1)
|
|
1981
|
+
.then((rows) => rows[0] ?? null);
|
|
1982
|
+
return Boolean(decisionActivity);
|
|
1983
|
+
}
|
|
1786
1984
|
async function issueHasDeferredWake(tx, orgId, issueId) {
|
|
1787
1985
|
const deferred = await tx
|
|
1788
1986
|
.select({ id: agentWakeupRequests.id })
|
|
@@ -1813,6 +2011,67 @@ export function heartbeatService(db) {
|
|
|
1813
2011
|
.then((rows) => rows[0] ?? null);
|
|
1814
2012
|
return Boolean(existingReview);
|
|
1815
2013
|
}
|
|
2014
|
+
async function reviewerCloseoutAlreadyRecorded(tx, runId) {
|
|
2015
|
+
const existingWake = await tx
|
|
2016
|
+
.select({ id: agentWakeupRequests.id })
|
|
2017
|
+
.from(agentWakeupRequests)
|
|
2018
|
+
.where(eq(agentWakeupRequests.idempotencyKey, `${ISSUE_REVIEW_CLOSEOUT_REASON}:${runId}`))
|
|
2019
|
+
.limit(1)
|
|
2020
|
+
.then((rows) => rows[0] ?? null);
|
|
2021
|
+
if (existingWake)
|
|
2022
|
+
return true;
|
|
2023
|
+
const existingReview = await tx
|
|
2024
|
+
.select({ id: activityLog.id })
|
|
2025
|
+
.from(activityLog)
|
|
2026
|
+
.where(and(eq(activityLog.runId, runId), eq(activityLog.action, "issue.review_closure_needs_operator_review")))
|
|
2027
|
+
.limit(1)
|
|
2028
|
+
.then((rows) => rows[0] ?? null);
|
|
2029
|
+
return Boolean(existingReview);
|
|
2030
|
+
}
|
|
2031
|
+
async function issueHasConfirmedBlockedReviewerHandoff(tx, issue, reviewerAgentId) {
|
|
2032
|
+
if (issue.status !== "blocked")
|
|
2033
|
+
return false;
|
|
2034
|
+
const existingHandoff = await tx
|
|
2035
|
+
.select({ id: activityLog.id })
|
|
2036
|
+
.from(activityLog)
|
|
2037
|
+
.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((
|
|
2038
|
+
SELECT MAX(material_activity.created_at)
|
|
2039
|
+
FROM activity_log material_activity
|
|
2040
|
+
WHERE material_activity.org_id = ${issue.orgId}
|
|
2041
|
+
AND material_activity.entity_type = 'issue'
|
|
2042
|
+
AND material_activity.entity_id = ${issue.id}::text
|
|
2043
|
+
AND (
|
|
2044
|
+
(
|
|
2045
|
+
material_activity.action = 'issue.updated'
|
|
2046
|
+
AND jsonb_typeof(material_activity.details) = 'object'
|
|
2047
|
+
AND EXISTS (
|
|
2048
|
+
SELECT 1
|
|
2049
|
+
FROM jsonb_object_keys(material_activity.details) AS detail_key(key)
|
|
2050
|
+
WHERE detail_key.key NOT IN (
|
|
2051
|
+
'identifier',
|
|
2052
|
+
'issueIdentifier',
|
|
2053
|
+
'_previous',
|
|
2054
|
+
'source',
|
|
2055
|
+
'reopened',
|
|
2056
|
+
'reopenedFrom',
|
|
2057
|
+
'normalizedFromStatus',
|
|
2058
|
+
'normalizedReason'
|
|
2059
|
+
)
|
|
2060
|
+
)
|
|
2061
|
+
)
|
|
2062
|
+
OR (
|
|
2063
|
+
material_activity.action = 'issue.comment_added'
|
|
2064
|
+
AND NOT (
|
|
2065
|
+
material_activity.actor_type = 'agent'
|
|
2066
|
+
AND material_activity.actor_id = ${reviewerAgentId}::text
|
|
2067
|
+
)
|
|
2068
|
+
)
|
|
2069
|
+
)
|
|
2070
|
+
), to_timestamp(0))`))
|
|
2071
|
+
.limit(1)
|
|
2072
|
+
.then((rows) => rows[0] ?? null);
|
|
2073
|
+
return Boolean(existingHandoff);
|
|
2074
|
+
}
|
|
1816
2075
|
async function evaluatePassiveIssueClosureForLockedIssue(input) {
|
|
1817
2076
|
const { tx, run, issue, now } = input;
|
|
1818
2077
|
const context = parseObject(run.contextSnapshot);
|
|
@@ -1821,6 +2080,49 @@ export function heartbeatService(db) {
|
|
|
1821
2080
|
return { kind: "none", reason: "run_not_issue_backed" };
|
|
1822
2081
|
if (run.status !== "succeeded")
|
|
1823
2082
|
return { kind: "none", reason: "run_not_successful" };
|
|
2083
|
+
const reviewerRun = (issue.status === "in_review" || issue.status === "blocked") &&
|
|
2084
|
+
issue.reviewerAgentId === run.agentId &&
|
|
2085
|
+
(run.invocationSource === "review" ||
|
|
2086
|
+
readNonEmptyString(context.role) === "reviewer" ||
|
|
2087
|
+
readNonEmptyString(context.wakeSource) === "review");
|
|
2088
|
+
if (reviewerRun) {
|
|
2089
|
+
if (await runHasIssueReviewDecision(tx, run, issue.id)) {
|
|
2090
|
+
return { kind: "none", reason: "review_decision_recorded" };
|
|
2091
|
+
}
|
|
2092
|
+
if (await issueHasConfirmedBlockedReviewerHandoff(tx, issue, run.agentId)) {
|
|
2093
|
+
return { kind: "none", reason: "blocked_reviewer_handoff_confirmed" };
|
|
2094
|
+
}
|
|
2095
|
+
if (await issueHasDeferredWake(tx, issue.orgId, issue.id)) {
|
|
2096
|
+
return { kind: "none", reason: "deferred_issue_wake_exists" };
|
|
2097
|
+
}
|
|
2098
|
+
if (await reviewerCloseoutAlreadyRecorded(tx, run.id)) {
|
|
2099
|
+
return { kind: "none", reason: "reviewer_closeout_already_recorded" };
|
|
2100
|
+
}
|
|
2101
|
+
const reviewCloseout = normalizeReviewCloseoutContext(context.reviewCloseout);
|
|
2102
|
+
const currentAttempt = reviewCloseout?.attempt ?? 0;
|
|
2103
|
+
const maxAttempts = reviewCloseout?.maxAttempts ?? ISSUE_REVIEW_CLOSEOUT_MAX_ATTEMPTS;
|
|
2104
|
+
const originRunId = reviewCloseout?.originRunId ?? run.id;
|
|
2105
|
+
if (currentAttempt >= maxAttempts) {
|
|
2106
|
+
return {
|
|
2107
|
+
kind: "reviewer_closeout_operator_review",
|
|
2108
|
+
issue,
|
|
2109
|
+
originRunId,
|
|
2110
|
+
previousRunId: run.id,
|
|
2111
|
+
attempts: currentAttempt,
|
|
2112
|
+
maxAttempts,
|
|
2113
|
+
reason: ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON,
|
|
2114
|
+
};
|
|
2115
|
+
}
|
|
2116
|
+
return {
|
|
2117
|
+
kind: "reviewer_closeout",
|
|
2118
|
+
issue,
|
|
2119
|
+
originRunId,
|
|
2120
|
+
previousRunId: run.id,
|
|
2121
|
+
attempts: currentAttempt + 1,
|
|
2122
|
+
maxAttempts,
|
|
2123
|
+
reason: ISSUE_REVIEW_CLOSEOUT_FAILURE_REASON,
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
1824
2126
|
if (issue.status !== "todo" && issue.status !== "in_progress") {
|
|
1825
2127
|
return { kind: "none", reason: "issue_has_closure_status" };
|
|
1826
2128
|
}
|
|
@@ -2329,6 +2631,7 @@ export function heartbeatService(db) {
|
|
|
2329
2631
|
let finalObservationSessionId = heartbeatObservationContext.sessionKey ?? null;
|
|
2330
2632
|
const runtime = await ensureRuntimeState(agent);
|
|
2331
2633
|
const context = parseObject(run.contextSnapshot);
|
|
2634
|
+
delete context.rudderGitIdentity;
|
|
2332
2635
|
const taskKey = deriveTaskKey(context, null);
|
|
2333
2636
|
const sessionCodec = getAgentRuntimeSessionCodec(agent.agentRuntimeType);
|
|
2334
2637
|
const issueId = readNonEmptyString(context.issueId);
|
|
@@ -3062,6 +3365,25 @@ export function heartbeatService(db) {
|
|
|
3062
3365
|
});
|
|
3063
3366
|
const finalizedRun = await getRun(run.id);
|
|
3064
3367
|
if (finalizedRun) {
|
|
3368
|
+
const transcriptUsedSkills = inferUsedSkillsFromTranscript(executionTranscript);
|
|
3369
|
+
if (transcriptUsedSkills.length > 0) {
|
|
3370
|
+
await appendRunEvent(finalizedRun, seq++, {
|
|
3371
|
+
eventType: "adapter.skill_usage",
|
|
3372
|
+
stream: "system",
|
|
3373
|
+
level: "info",
|
|
3374
|
+
message: "skill usage inferred from transcript",
|
|
3375
|
+
payload: {
|
|
3376
|
+
source: "transcript.skill_file_read",
|
|
3377
|
+
usedSkillCount: transcriptUsedSkills.length,
|
|
3378
|
+
usedSkillKeys: transcriptUsedSkills.map((entry) => entry.key),
|
|
3379
|
+
usedSkills: transcriptUsedSkills,
|
|
3380
|
+
skillEvidenceType: "used",
|
|
3381
|
+
skillEvidenceCount: transcriptUsedSkills.length,
|
|
3382
|
+
skillEvidenceKeys: transcriptUsedSkills.map((entry) => entry.key),
|
|
3383
|
+
skillEvidenceSkills: transcriptUsedSkills,
|
|
3384
|
+
},
|
|
3385
|
+
});
|
|
3386
|
+
}
|
|
3065
3387
|
await appendRunEvent(finalizedRun, seq++, {
|
|
3066
3388
|
eventType: "lifecycle",
|
|
3067
3389
|
stream: "system",
|
|
@@ -3528,6 +3850,96 @@ export function heartbeatService(db) {
|
|
|
3528
3850
|
});
|
|
3529
3851
|
}
|
|
3530
3852
|
}
|
|
3853
|
+
else if (passiveClosure?.kind === "reviewer_closeout") {
|
|
3854
|
+
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3855
|
+
eventType: "issue.review_closeout_missing",
|
|
3856
|
+
stream: "system",
|
|
3857
|
+
level: "warn",
|
|
3858
|
+
message: "Reviewer run finished without a structured review decision",
|
|
3859
|
+
payload: {
|
|
3860
|
+
issueId: passiveClosure.issue.id,
|
|
3861
|
+
originRunId: passiveClosure.originRunId,
|
|
3862
|
+
previousRunId: passiveClosure.previousRunId,
|
|
3863
|
+
attempts: passiveClosure.attempts,
|
|
3864
|
+
maxAttempts: passiveClosure.maxAttempts,
|
|
3865
|
+
reason: passiveClosure.reason,
|
|
3866
|
+
},
|
|
3867
|
+
});
|
|
3868
|
+
await logActivity(db, {
|
|
3869
|
+
orgId: passiveClosure.issue.orgId,
|
|
3870
|
+
actorType: "system",
|
|
3871
|
+
actorId: "issue_review_closeout_governance",
|
|
3872
|
+
action: "issue.review_closeout_missing",
|
|
3873
|
+
entityType: "issue",
|
|
3874
|
+
entityId: passiveClosure.issue.id,
|
|
3875
|
+
agentId: run.agentId,
|
|
3876
|
+
runId: run.id,
|
|
3877
|
+
details: {
|
|
3878
|
+
issueId: passiveClosure.issue.id,
|
|
3879
|
+
issueTitle: passiveClosure.issue.title,
|
|
3880
|
+
reviewerAgentId: passiveClosure.issue.reviewerAgentId,
|
|
3881
|
+
originRunId: passiveClosure.originRunId,
|
|
3882
|
+
previousRunId: passiveClosure.previousRunId,
|
|
3883
|
+
attempts: passiveClosure.attempts,
|
|
3884
|
+
maxAttempts: passiveClosure.maxAttempts,
|
|
3885
|
+
reason: passiveClosure.reason,
|
|
3886
|
+
},
|
|
3887
|
+
});
|
|
3888
|
+
if (passiveClosure.issue.reviewerAgentId) {
|
|
3889
|
+
await enqueueWakeup(passiveClosure.issue.reviewerAgentId, {
|
|
3890
|
+
...buildIssueReviewCloseoutWakeupOptions({
|
|
3891
|
+
issue: passiveClosure.issue,
|
|
3892
|
+
contextSource: "issue.review_closeout_missing",
|
|
3893
|
+
originRunId: passiveClosure.originRunId,
|
|
3894
|
+
previousRunId: passiveClosure.previousRunId,
|
|
3895
|
+
attempts: passiveClosure.attempts,
|
|
3896
|
+
maxAttempts: passiveClosure.maxAttempts,
|
|
3897
|
+
requestedByActorType: "system",
|
|
3898
|
+
requestedByActorId: "issue_review_closeout_governance",
|
|
3899
|
+
}),
|
|
3900
|
+
idempotencyKey: `${ISSUE_REVIEW_CLOSEOUT_REASON}:${run.id}`,
|
|
3901
|
+
}).catch((err) => {
|
|
3902
|
+
logger.warn({ err, issueId: passiveClosure.issue.id }, "failed to wake reviewer after missing review close-out");
|
|
3903
|
+
return null;
|
|
3904
|
+
});
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
else if (passiveClosure?.kind === "reviewer_closeout_operator_review") {
|
|
3908
|
+
await appendRunEvent(run, await nextRunEventSeq(run.id), {
|
|
3909
|
+
eventType: "issue.review_closure_needs_operator_review",
|
|
3910
|
+
stream: "system",
|
|
3911
|
+
level: "warn",
|
|
3912
|
+
message: "Reviewer close-out attempts stopped and need operator review",
|
|
3913
|
+
payload: {
|
|
3914
|
+
issueId: passiveClosure.issue.id,
|
|
3915
|
+
originRunId: passiveClosure.originRunId,
|
|
3916
|
+
previousRunId: passiveClosure.previousRunId,
|
|
3917
|
+
attempts: passiveClosure.attempts,
|
|
3918
|
+
maxAttempts: passiveClosure.maxAttempts,
|
|
3919
|
+
reason: passiveClosure.reason,
|
|
3920
|
+
},
|
|
3921
|
+
});
|
|
3922
|
+
await logActivity(db, {
|
|
3923
|
+
orgId: passiveClosure.issue.orgId,
|
|
3924
|
+
actorType: "system",
|
|
3925
|
+
actorId: "issue_review_closeout_governance",
|
|
3926
|
+
action: "issue.review_closure_needs_operator_review",
|
|
3927
|
+
entityType: "issue",
|
|
3928
|
+
entityId: passiveClosure.issue.id,
|
|
3929
|
+
agentId: run.agentId,
|
|
3930
|
+
runId: run.id,
|
|
3931
|
+
details: {
|
|
3932
|
+
issueId: passiveClosure.issue.id,
|
|
3933
|
+
issueTitle: passiveClosure.issue.title,
|
|
3934
|
+
reviewerAgentId: passiveClosure.issue.reviewerAgentId,
|
|
3935
|
+
originRunId: passiveClosure.originRunId,
|
|
3936
|
+
previousRunId: passiveClosure.previousRunId,
|
|
3937
|
+
attempts: passiveClosure.attempts,
|
|
3938
|
+
maxAttempts: passiveClosure.maxAttempts,
|
|
3939
|
+
reason: passiveClosure.reason,
|
|
3940
|
+
},
|
|
3941
|
+
});
|
|
3942
|
+
}
|
|
3531
3943
|
const promotedRun = outcome.promotedRun;
|
|
3532
3944
|
if (!promotedRun)
|
|
3533
3945
|
return;
|
|
@@ -4269,8 +4681,8 @@ export function heartbeatService(db) {
|
|
|
4269
4681
|
const run = await getRun(runId);
|
|
4270
4682
|
if (!run)
|
|
4271
4683
|
throw notFound("Heartbeat run not found");
|
|
4272
|
-
if (run.status !== "failed" && run.status !== "timed_out") {
|
|
4273
|
-
throw conflict("Only failed
|
|
4684
|
+
if (run.status !== "failed" && run.status !== "timed_out" && run.status !== "cancelled") {
|
|
4685
|
+
throw conflict("Only failed, timed out, or cancelled runs can be retried", {
|
|
4274
4686
|
status: run.status,
|
|
4275
4687
|
});
|
|
4276
4688
|
}
|
|
@@ -4333,60 +4745,186 @@ export function heartbeatService(db) {
|
|
|
4333
4745
|
const windowEnd = new Date(`${endDate}T23:59:59.999Z`);
|
|
4334
4746
|
const rows = await db
|
|
4335
4747
|
.select({
|
|
4748
|
+
runId: heartbeatRunEvents.runId,
|
|
4336
4749
|
createdAt: heartbeatRunEvents.createdAt,
|
|
4750
|
+
eventType: heartbeatRunEvents.eventType,
|
|
4337
4751
|
payload: heartbeatRunEvents.payload,
|
|
4338
4752
|
})
|
|
4339
4753
|
.from(heartbeatRunEvents)
|
|
4340
|
-
.where(and(eq(heartbeatRunEvents.orgId, scope.orgId), ...(scope.agentId ? [eq(heartbeatRunEvents.agentId, scope.agentId)] : []),
|
|
4754
|
+
.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)))
|
|
4341
4755
|
.orderBy(asc(heartbeatRunEvents.createdAt), asc(heartbeatRunEvents.id));
|
|
4342
4756
|
const days = new Map();
|
|
4343
4757
|
for (const date of dateKeys) {
|
|
4344
|
-
days.set(date, { totalCount: 0, runCount: 0, skills: new Map() });
|
|
4758
|
+
days.set(date, { totalCount: 0, runCount: 0, evidenceCounts: emptySkillEvidenceCounts(), skills: new Map() });
|
|
4345
4759
|
}
|
|
4346
4760
|
const overallSkills = new Map();
|
|
4761
|
+
const runEvidence = new Map();
|
|
4347
4762
|
let totalCount = 0;
|
|
4348
4763
|
let totalRunsWithSkills = 0;
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
if (usedSkills.length === 0)
|
|
4360
|
-
continue;
|
|
4361
|
-
const eventSkills = new Map();
|
|
4362
|
-
for (const entry of usedSkills) {
|
|
4764
|
+
const evidenceCounts = emptySkillEvidenceCounts();
|
|
4765
|
+
function addRunSkillEvidence(runId, date, evidence) {
|
|
4766
|
+
if (!days.has(date))
|
|
4767
|
+
return;
|
|
4768
|
+
if (evidence.evidence !== "used")
|
|
4769
|
+
return;
|
|
4770
|
+
if (evidence.skills.length === 0)
|
|
4771
|
+
return;
|
|
4772
|
+
const runBucket = runEvidence.get(runId) ?? { date, skills: new Map() };
|
|
4773
|
+
for (const entry of evidence.skills) {
|
|
4363
4774
|
const normalized = normalizeLoadedSkill(entry);
|
|
4364
4775
|
if (!normalized)
|
|
4365
4776
|
continue;
|
|
4366
|
-
|
|
4367
|
-
|
|
4777
|
+
const existing = runBucket.skills.get(normalized.key);
|
|
4778
|
+
if (existing) {
|
|
4779
|
+
existing.evidence = strongestSkillEvidence(existing.evidence, evidence.evidence);
|
|
4780
|
+
if (existing.label === fallbackSkillLabel(existing.key) && normalized.label !== fallbackSkillLabel(normalized.key)) {
|
|
4781
|
+
existing.label = normalized.label;
|
|
4782
|
+
}
|
|
4783
|
+
}
|
|
4784
|
+
else {
|
|
4785
|
+
runBucket.skills.set(normalized.key, {
|
|
4786
|
+
key: normalized.key,
|
|
4787
|
+
label: normalized.label,
|
|
4788
|
+
evidence: evidence.evidence,
|
|
4789
|
+
});
|
|
4790
|
+
}
|
|
4791
|
+
}
|
|
4792
|
+
if (runBucket.skills.size > 0)
|
|
4793
|
+
runEvidence.set(runId, runBucket);
|
|
4794
|
+
}
|
|
4795
|
+
async function inferUsedSkillsFromStoredRunLog(row) {
|
|
4796
|
+
if (row.logStore !== "local_file" || !row.logRef)
|
|
4797
|
+
return [];
|
|
4798
|
+
const adapter = (() => {
|
|
4799
|
+
try {
|
|
4800
|
+
return getServerAdapter(row.agentRuntimeType);
|
|
4801
|
+
}
|
|
4802
|
+
catch {
|
|
4803
|
+
return null;
|
|
4804
|
+
}
|
|
4805
|
+
})();
|
|
4806
|
+
if (!adapter)
|
|
4807
|
+
return [];
|
|
4808
|
+
const parser = adapter.parseStdoutLine ?? null;
|
|
4809
|
+
if (!parser)
|
|
4810
|
+
return [];
|
|
4811
|
+
const limitBytes = Math.min(Math.max(row.logBytes ?? 0, 256_000), 2_000_000);
|
|
4812
|
+
const read = await runLogStore
|
|
4813
|
+
.read({ store: "local_file", logRef: row.logRef }, { limitBytes })
|
|
4814
|
+
.catch(() => null);
|
|
4815
|
+
if (!read?.content)
|
|
4816
|
+
return [];
|
|
4817
|
+
const transcript = [];
|
|
4818
|
+
let stdoutBuffer = "";
|
|
4819
|
+
let stderrBuffer = "";
|
|
4820
|
+
for (const line of read.content.split("\n")) {
|
|
4821
|
+
if (!line.trim())
|
|
4822
|
+
continue;
|
|
4823
|
+
let raw;
|
|
4824
|
+
try {
|
|
4825
|
+
raw = JSON.parse(line);
|
|
4826
|
+
}
|
|
4827
|
+
catch {
|
|
4828
|
+
continue;
|
|
4829
|
+
}
|
|
4830
|
+
const parsed = parseObject(raw);
|
|
4831
|
+
const stream = parsed.stream === "stderr" ? "stderr" : parsed.stream === "stdout" ? "stdout" : null;
|
|
4832
|
+
const chunk = typeof parsed.chunk === "string" ? parsed.chunk : "";
|
|
4833
|
+
if (!stream || !chunk)
|
|
4834
|
+
continue;
|
|
4835
|
+
if (stream === "stdout") {
|
|
4836
|
+
stdoutBuffer = appendTranscriptEntriesFromChunk({
|
|
4837
|
+
buffer: stdoutBuffer,
|
|
4838
|
+
chunk,
|
|
4839
|
+
transcript,
|
|
4840
|
+
parser,
|
|
4841
|
+
kind: "stdout",
|
|
4842
|
+
});
|
|
4843
|
+
}
|
|
4844
|
+
else {
|
|
4845
|
+
stderrBuffer = appendTranscriptEntriesFromChunk({
|
|
4846
|
+
buffer: stderrBuffer,
|
|
4847
|
+
chunk,
|
|
4848
|
+
transcript,
|
|
4849
|
+
kind: "stderr",
|
|
4850
|
+
});
|
|
4368
4851
|
}
|
|
4369
4852
|
}
|
|
4370
|
-
|
|
4853
|
+
appendTranscriptEntriesFromChunk({
|
|
4854
|
+
buffer: stdoutBuffer,
|
|
4855
|
+
chunk: "",
|
|
4856
|
+
transcript,
|
|
4857
|
+
parser,
|
|
4858
|
+
kind: "stdout",
|
|
4859
|
+
finalize: true,
|
|
4860
|
+
});
|
|
4861
|
+
appendTranscriptEntriesFromChunk({
|
|
4862
|
+
buffer: stderrBuffer,
|
|
4863
|
+
chunk: "",
|
|
4864
|
+
transcript,
|
|
4865
|
+
kind: "stderr",
|
|
4866
|
+
finalize: true,
|
|
4867
|
+
});
|
|
4868
|
+
return inferUsedSkillsFromTranscript(transcript);
|
|
4869
|
+
}
|
|
4870
|
+
for (const row of rows) {
|
|
4871
|
+
const date = new Date(row.createdAt).toISOString().slice(0, 10);
|
|
4872
|
+
const payload = parseObject(row.payload);
|
|
4873
|
+
addRunSkillEvidence(row.runId, date, readSkillEvidenceFromPayload(payload));
|
|
4874
|
+
}
|
|
4875
|
+
const runRows = await db
|
|
4876
|
+
.select({
|
|
4877
|
+
id: heartbeatRuns.id,
|
|
4878
|
+
agentRuntimeType: agents.agentRuntimeType,
|
|
4879
|
+
createdAt: heartbeatRuns.createdAt,
|
|
4880
|
+
logStore: heartbeatRuns.logStore,
|
|
4881
|
+
logRef: heartbeatRuns.logRef,
|
|
4882
|
+
logBytes: heartbeatRuns.logBytes,
|
|
4883
|
+
})
|
|
4884
|
+
.from(heartbeatRuns)
|
|
4885
|
+
.innerJoin(agents, eq(agents.id, heartbeatRuns.agentId))
|
|
4886
|
+
.where(and(eq(heartbeatRuns.orgId, scope.orgId), ...(scope.agentId ? [eq(heartbeatRuns.agentId, scope.agentId)] : []), gte(heartbeatRuns.createdAt, windowStart), lte(heartbeatRuns.createdAt, windowEnd)));
|
|
4887
|
+
for (const row of runRows) {
|
|
4888
|
+
const usedSkills = await inferUsedSkillsFromStoredRunLog(row);
|
|
4889
|
+
if (usedSkills.length === 0)
|
|
4890
|
+
continue;
|
|
4891
|
+
addRunSkillEvidence(row.id, new Date(row.createdAt).toISOString().slice(0, 10), {
|
|
4892
|
+
evidence: "used",
|
|
4893
|
+
skills: usedSkills,
|
|
4894
|
+
});
|
|
4895
|
+
}
|
|
4896
|
+
for (const runBucket of runEvidence.values()) {
|
|
4897
|
+
const bucket = days.get(runBucket.date);
|
|
4898
|
+
if (!bucket || runBucket.skills.size === 0)
|
|
4371
4899
|
continue;
|
|
4372
4900
|
bucket.runCount += 1;
|
|
4373
4901
|
totalRunsWithSkills += 1;
|
|
4374
|
-
for (const
|
|
4902
|
+
for (const { key, label, evidence } of runBucket.skills.values()) {
|
|
4375
4903
|
bucket.totalCount += 1;
|
|
4376
4904
|
totalCount += 1;
|
|
4905
|
+
incrementSkillEvidenceCount(bucket.evidenceCounts, evidence);
|
|
4906
|
+
incrementSkillEvidenceCount(evidenceCounts, evidence);
|
|
4377
4907
|
const existingDaySkill = bucket.skills.get(key);
|
|
4378
4908
|
if (existingDaySkill) {
|
|
4379
4909
|
existingDaySkill.count += 1;
|
|
4910
|
+
existingDaySkill.evidence = strongestSkillEvidence(existingDaySkill.evidence, evidence);
|
|
4911
|
+
incrementSkillEvidenceCount(existingDaySkill.evidenceCounts, evidence);
|
|
4380
4912
|
}
|
|
4381
4913
|
else {
|
|
4382
|
-
|
|
4914
|
+
const skillEvidenceCounts = emptySkillEvidenceCounts();
|
|
4915
|
+
incrementSkillEvidenceCount(skillEvidenceCounts, evidence);
|
|
4916
|
+
bucket.skills.set(key, { key, label, count: 1, evidence, evidenceCounts: skillEvidenceCounts });
|
|
4383
4917
|
}
|
|
4384
4918
|
const existingOverallSkill = overallSkills.get(key);
|
|
4385
4919
|
if (existingOverallSkill) {
|
|
4386
4920
|
existingOverallSkill.count += 1;
|
|
4921
|
+
existingOverallSkill.evidence = strongestSkillEvidence(existingOverallSkill.evidence, evidence);
|
|
4922
|
+
incrementSkillEvidenceCount(existingOverallSkill.evidenceCounts, evidence);
|
|
4387
4923
|
}
|
|
4388
4924
|
else {
|
|
4389
|
-
|
|
4925
|
+
const skillEvidenceCounts = emptySkillEvidenceCounts();
|
|
4926
|
+
incrementSkillEvidenceCount(skillEvidenceCounts, evidence);
|
|
4927
|
+
overallSkills.set(key, { key, label, count: 1, evidence, evidenceCounts: skillEvidenceCounts });
|
|
4390
4928
|
}
|
|
4391
4929
|
}
|
|
4392
4930
|
}
|
|
@@ -4398,6 +4936,7 @@ export function heartbeatService(db) {
|
|
|
4398
4936
|
endDate,
|
|
4399
4937
|
totalCount,
|
|
4400
4938
|
totalRunsWithSkills,
|
|
4939
|
+
evidenceCounts,
|
|
4401
4940
|
skills: Array.from(overallSkills.values()).sort((left, right) => (right.count - left.count
|
|
4402
4941
|
|| left.label.localeCompare(right.label)
|
|
4403
4942
|
|| left.key.localeCompare(right.key))),
|
|
@@ -4407,6 +4946,7 @@ export function heartbeatService(db) {
|
|
|
4407
4946
|
date,
|
|
4408
4947
|
totalCount: bucket.totalCount,
|
|
4409
4948
|
runCount: bucket.runCount,
|
|
4949
|
+
evidenceCounts: bucket.evidenceCounts,
|
|
4410
4950
|
skills: Array.from(bucket.skills.values()).sort((left, right) => (right.count - left.count
|
|
4411
4951
|
|| left.label.localeCompare(right.label)
|
|
4412
4952
|
|| left.key.localeCompare(right.key))),
|