@evermore.work/server 2026.511.0-canary.2 → 2026.513.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/builtin-adapter-types.d.ts.map +1 -1
- package/dist/adapters/builtin-adapter-types.js +1 -0
- package/dist/adapters/builtin-adapter-types.js.map +1 -1
- package/dist/adapters/registry.d.ts.map +1 -1
- package/dist/adapters/registry.js +20 -2
- package/dist/adapters/registry.js.map +1 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -3
- package/dist/app.js.map +1 -1
- package/dist/home-paths.d.ts +2 -4
- package/dist/home-paths.d.ts.map +1 -1
- package/dist/home-paths.js +8 -35
- package/dist/home-paths.js.map +1 -1
- package/dist/routes/agents.d.ts.map +1 -1
- package/dist/routes/agents.js +8 -2
- package/dist/routes/agents.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +172 -11
- package/dist/routes/issues.js.map +1 -1
- package/dist/routes/plugins.d.ts.map +1 -1
- package/dist/routes/plugins.js +7 -21
- package/dist/routes/plugins.js.map +1 -1
- package/dist/services/environment-execution-target.d.ts.map +1 -1
- package/dist/services/environment-execution-target.js +2 -0
- package/dist/services/environment-execution-target.js.map +1 -1
- package/dist/services/environment-runtime.d.ts.map +1 -1
- package/dist/services/environment-runtime.js +30 -4
- package/dist/services/environment-runtime.js.map +1 -1
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +8 -3
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/services/index.js.map +1 -1
- package/dist/services/issue-recovery-actions.d.ts +40 -0
- package/dist/services/issue-recovery-actions.d.ts.map +1 -0
- package/dist/services/issue-recovery-actions.js +204 -0
- package/dist/services/issue-recovery-actions.js.map +1 -0
- package/dist/services/issues.d.ts +20 -0
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +160 -10
- package/dist/services/issues.js.map +1 -1
- package/dist/services/plugin-config-validator.js +1 -1
- package/dist/services/plugin-config-validator.js.map +1 -1
- package/dist/services/plugin-dev-watcher.d.ts.map +1 -1
- package/dist/services/plugin-dev-watcher.js +7 -2
- package/dist/services/plugin-dev-watcher.js.map +1 -1
- package/dist/services/plugin-environment-driver.d.ts +2 -0
- package/dist/services/plugin-environment-driver.d.ts.map +1 -1
- package/dist/services/plugin-environment-driver.js +2 -0
- package/dist/services/plugin-environment-driver.js.map +1 -1
- package/dist/services/plugin-host-services.d.ts.map +1 -1
- package/dist/services/plugin-host-services.js +31 -4
- package/dist/services/plugin-host-services.js.map +1 -1
- package/dist/services/plugin-loader.d.ts +2 -2
- package/dist/services/plugin-loader.js +3 -3
- package/dist/services/plugin-loader.js.map +1 -1
- package/dist/services/plugin-local-folders.d.ts +1 -0
- package/dist/services/plugin-local-folders.d.ts.map +1 -1
- package/dist/services/plugin-local-folders.js +45 -0
- package/dist/services/plugin-local-folders.js.map +1 -1
- package/dist/services/plugin-managed-agents.d.ts.map +1 -1
- package/dist/services/plugin-managed-agents.js +52 -9
- package/dist/services/plugin-managed-agents.js.map +1 -1
- package/dist/services/plugin-managed-skills.d.ts +14 -0
- package/dist/services/plugin-managed-skills.d.ts.map +1 -0
- package/dist/services/plugin-managed-skills.js +264 -0
- package/dist/services/plugin-managed-skills.js.map +1 -0
- package/dist/services/plugin-registry.d.ts +1 -1
- package/dist/services/plugin-registry.js +1 -1
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +225 -63
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.d.ts +2 -0
- package/dist/services/recovery/successful-run-handoff.d.ts.map +1 -1
- package/dist/services/recovery/successful-run-handoff.js +8 -1
- package/dist/services/recovery/successful-run-handoff.js.map +1 -1
- package/dist/services/recovery/successful-run-handoff.test.js +5 -2
- package/dist/services/recovery/successful-run-handoff.test.js.map +1 -1
- package/package.json +14 -13
- package/skills/evermore-converting-plans-to-tasks/SKILL.md +1 -1
- package/skills/evermore-create-plugin/SKILL.md +95 -42
- package/skills/terminal-bench-loop/SKILL.md +4 -4
- package/ui-dist/assets/{_basePickBy-CbmMN1RH.js → _basePickBy-k9wCTyNE.js} +1 -1
- package/ui-dist/assets/{_baseUniq-D5Spi0LU.js → _baseUniq-gqdJ9_M4.js} +1 -1
- package/ui-dist/assets/{arc-7n9-L5yV.js → arc-CZ5fTbi1.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-VXUJARFQ-CaCkwTrk.js → architectureDiagram-VXUJARFQ-C1PmJabO.js} +1 -1
- package/ui-dist/assets/{blockDiagram-VD42YOAC-C_gMX5sE.js → blockDiagram-VD42YOAC-C31GBmZl.js} +1 -1
- package/ui-dist/assets/{c4Diagram-YG6GDRKO-DsIPHY3x.js → c4Diagram-YG6GDRKO-CXALeUm_.js} +1 -1
- package/ui-dist/assets/channel-BscBiA2B.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-ConiONwq.js → chunk-4BX2VUAB-CI9ame_l.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-rN7J69fU.js → chunk-55IACEB6-X7CMs2bY.js} +1 -1
- package/ui-dist/assets/{chunk-B4BG7PRW-CWrv2W6l.js → chunk-B4BG7PRW-CRFGJGGj.js} +1 -1
- package/ui-dist/assets/{chunk-DI55MBZ5-DxMrfgRx.js → chunk-DI55MBZ5-pFj4tWf4.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-C3IEVrmU.js → chunk-FMBD7UC4-DwtEpJHS.js} +1 -1
- package/ui-dist/assets/{chunk-QN33PNHL-mY7dIxLL.js → chunk-QN33PNHL-O_GMkM_d.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-C57cpB6v.js → chunk-QZHKN3VN-DVQoxgmw.js} +1 -1
- package/ui-dist/assets/{chunk-TZMSLE5B-Bznk-429.js → chunk-TZMSLE5B-nYT9Oclu.js} +1 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-DRC1sToY.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-DRC1sToY.js +1 -0
- package/ui-dist/assets/clone-djKk5h1v.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-WVNEjOkH.js → cose-bilkent-S5V4N54A-DrymQvjx.js} +1 -1
- package/ui-dist/assets/{dagre-6UL2VRFP-B5zFofAq.js → dagre-6UL2VRFP-BSMS-awI.js} +1 -1
- package/ui-dist/assets/{diagram-PSM6KHXK-B5uBquxq.js → diagram-PSM6KHXK-CKA4xpzT.js} +1 -1
- package/ui-dist/assets/{diagram-QEK2KX5R--vjH56tj.js → diagram-QEK2KX5R-D224Hw3l.js} +1 -1
- package/ui-dist/assets/{diagram-S2PKOQOG-BrRUS4fb.js → diagram-S2PKOQOG-QSLRBXq3.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q2GNP2WA-DEz4OctV.js → erDiagram-Q2GNP2WA-BMb1ltru.js} +1 -1
- package/ui-dist/assets/{flowDiagram-NV44I4VS-B15VynOf.js → flowDiagram-NV44I4VS-DFO86CtG.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-JELNMOA3-DMZlAptH.js → ganttDiagram-JELNMOA3-DqGmEMSm.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-CcxPbN5-.js → gitGraphDiagram-V2S2FVAM-C58nf2i-.js} +1 -1
- package/ui-dist/assets/{graph-BZIdisjp.js → graph-DosSwXzr.js} +1 -1
- package/ui-dist/assets/{index-ZU3cQRJ_.js → index-3RqTP3Wv.js} +1 -1
- package/ui-dist/assets/{index-CMFXrEKF.js → index-4nkrIR74.js} +154 -153
- package/ui-dist/assets/index-BNTUqCLB.css +1 -0
- package/ui-dist/assets/{index-JK3bQafC.js → index-BWnvh-op.js} +1 -1
- package/ui-dist/assets/{index-C_B62ZrS.js → index-BbNMaRBx.js} +1 -1
- package/ui-dist/assets/{index-D9_Jej4N.js → index-BfcmN5ZJ.js} +1 -1
- package/ui-dist/assets/{index-CLpI7eai.js → index-BgdsvI-o.js} +1 -1
- package/ui-dist/assets/{index-DPcKcWrz.js → index-BrTEhQHs.js} +1 -1
- package/ui-dist/assets/{index-CguyMMMe.js → index-C37UJrtl.js} +1 -1
- package/ui-dist/assets/{index-B8AK13gN.js → index-Ci5TGFP3.js} +1 -1
- package/ui-dist/assets/{index-CBsefjme.js → index-CqyFdYQi.js} +1 -1
- package/ui-dist/assets/{index-jrAeI9QO.js → index-Cu1tIzzo.js} +1 -1
- package/ui-dist/assets/{index-QcuAFhJE.js → index-CvQSsdq1.js} +1 -1
- package/ui-dist/assets/{index-CtbPMroJ.js → index-D6e8ieSZ.js} +1 -1
- package/ui-dist/assets/{index-Caiv3tFo.js → index-D9fEHHhi.js} +1 -1
- package/ui-dist/assets/{index-Br9DlaVa.js → index-DU3gaUtN.js} +1 -1
- package/ui-dist/assets/{index-B9gUF7Qo.js → index-DozqwUGT.js} +1 -1
- package/ui-dist/assets/{index-B2TuAYBk.js → index-DsbA9pOP.js} +1 -1
- package/ui-dist/assets/{index-BEgNCEf4.js → index-Dz0v0MS4.js} +1 -1
- package/ui-dist/assets/{index-DChtU7Y7.js → index-Oyl53tyz.js} +1 -1
- package/ui-dist/assets/{index-BXCWvVzU.js → index-ae2EPSbU.js} +1 -1
- package/ui-dist/assets/{index-C0ActS50.js → index-cxYbeASY.js} +1 -1
- package/ui-dist/assets/{index-CuiPTihA.js → index-gB4esTR_.js} +1 -1
- package/ui-dist/assets/{index-DZAJwG4j.js → index-uX_QygSO.js} +1 -1
- package/ui-dist/assets/{infoDiagram-HS3SLOUP-C2wYobHy.js → infoDiagram-HS3SLOUP-fH1KZK8Y.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-BtfEQPBF.js → journeyDiagram-XKPGCS4Q-Bs6gBpT7.js} +1 -1
- package/ui-dist/assets/{kanban-definition-3W4ZIXB7-B4U_W3OQ.js → kanban-definition-3W4ZIXB7-CjXiZk7z.js} +1 -1
- package/ui-dist/assets/{layout-ZIWcjZTf.js → layout-BNsoNCJt.js} +1 -1
- package/ui-dist/assets/{linear-D7hp1mR1.js → linear-D4_nHGWF.js} +1 -1
- package/ui-dist/assets/{mermaid.core-npjWckTB.js → mermaid.core-QfFHliw3.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-VGOIOE7T-Cno5l-kW.js → mindmap-definition-VGOIOE7T-B_dKQ4Qp.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ADFJNKIX-Ce0R1e38.js → pieDiagram-ADFJNKIX-DflXWaiu.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-AYHSOK5B-BNZ-4uth.js → quadrantDiagram-AYHSOK5B-Bt3RB7oE.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-BIdjI2CO.js → requirementDiagram-UZGBJVZJ-DxsHOEgt.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-hYF-IP2X.js → sankeyDiagram-TZEHDZUN-DilTmstc.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-WL72ISMW-CBreF6um.js → sequenceDiagram-WL72ISMW-BYRLNxvZ.js} +1 -1
- package/ui-dist/assets/{stateDiagram-FKZM4ZOC-DOje8UoB.js → stateDiagram-FKZM4ZOC-D0jvEPoV.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-D_kHbugj.js +1 -0
- package/ui-dist/assets/{timeline-definition-IT6M3QCI-Da_eKz_x.js → timeline-definition-IT6M3QCI-KyT1QpWq.js} +1 -1
- package/ui-dist/assets/{treemap-GDKQZRPO-BAFi4Uf3.js → treemap-GDKQZRPO-BEa3HnWS.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-PRI3JC2R-Bc_vIoPh.js → xychartDiagram-PRI3JC2R-BUxqP2No.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-zaN5fJQ5.js +0 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-CZ5AdJ2X.js +0 -1
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-CZ5AdJ2X.js +0 -1
- package/ui-dist/assets/clone-CgK10jEJ.js +0 -1
- package/ui-dist/assets/index-B7sbRkCH.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-BtMV8Rt1.js +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { and, asc, desc, eq, gt, inArray, isNull, notInArray, sql } from "drizzle-orm";
|
|
2
2
|
import { DEFAULT_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, MAX_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, MIN_ISSUE_GRAPH_LIVENESS_AUTO_RECOVERY_LOOKBACK_HOURS, } from "@evermore.work/shared";
|
|
3
|
-
import { agents, agentWakeupRequests, approvals, companies, heartbeatRunEvents, heartbeatRunWatchdogDecisions, heartbeatRuns, issueApprovals, issueRelations, issueThreadInteractions, issues, } from "@evermore.work/db";
|
|
3
|
+
import { agents, agentWakeupRequests, approvals, companies, issueComments, heartbeatRunEvents, heartbeatRunWatchdogDecisions, heartbeatRuns, issueApprovals, issueRecoveryActions, issueRelations, issueThreadInteractions, issues, } from "@evermore.work/db";
|
|
4
4
|
import { parseObject, asBoolean, asNumber } from "../../adapters/utils.js";
|
|
5
5
|
import { runningProcesses } from "../../adapters/index.js";
|
|
6
6
|
import { forbidden, notFound } from "../../errors.js";
|
|
@@ -10,10 +10,11 @@ import { redactSensitiveText } from "../../redaction.js";
|
|
|
10
10
|
import { logActivity } from "../activity-log.js";
|
|
11
11
|
import { budgetService } from "../budgets.js";
|
|
12
12
|
import { instanceSettingsService } from "../instance-settings.js";
|
|
13
|
+
import { issueRecoveryActionService } from "../issue-recovery-actions.js";
|
|
13
14
|
import { issueTreeControlService } from "../issue-tree-control.js";
|
|
14
15
|
import { issueService } from "../issues.js";
|
|
15
16
|
import { getRunLogStore } from "../run-log-store.js";
|
|
16
|
-
import { DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS, FINISH_SUCCESSFUL_RUN_HANDOFF_REASON, SUCCESSFUL_RUN_MISSING_STATE_REASON, buildSuccessfulRunHandoffExhaustedNotice, } from "./successful-run-handoff.js";
|
|
17
|
+
import { DEFAULT_MAX_SUCCESSFUL_RUN_HANDOFF_ATTEMPTS, FINISH_SUCCESSFUL_RUN_HANDOFF_REASON, SUCCESSFUL_RUN_MISSING_STATE_REASON, buildSuccessfulRunHandoffExhaustedNotice, noticeMetadataReferencesRecoveryAction, } from "./successful-run-handoff.js";
|
|
17
18
|
import { RECOVERY_ORIGIN_KINDS, buildIssueGraphLivenessLeafKey, isStrandedIssueRecoveryOriginKind, parseIssueGraphLivenessIncidentKey, } from "./origins.js";
|
|
18
19
|
import { classifyIssueGraphLiveness, } from "./issue-graph-liveness.js";
|
|
19
20
|
import { recoveryAssigneeAdapterOverrides, withRecoveryModelProfileHint, } from "./model-profile-hint.js";
|
|
@@ -242,6 +243,7 @@ function buildLivenessOriginalIssueComment(finding, escalation) {
|
|
|
242
243
|
}
|
|
243
244
|
export function recoveryService(db, deps) {
|
|
244
245
|
const issuesSvc = issueService(db);
|
|
246
|
+
const recoveryActionsSvc = issueRecoveryActionService(db);
|
|
245
247
|
const treeControlSvc = issueTreeControlService(db);
|
|
246
248
|
const budgets = budgetService(db);
|
|
247
249
|
const instanceSettings = instanceSettingsService(db);
|
|
@@ -1228,6 +1230,111 @@ export function recoveryService(db, deps) {
|
|
|
1228
1230
|
});
|
|
1229
1231
|
return recovery;
|
|
1230
1232
|
}
|
|
1233
|
+
function strandedRecoveryActionKind(cause) {
|
|
1234
|
+
return cause === SUCCESSFUL_RUN_MISSING_STATE_REASON
|
|
1235
|
+
? "missing_disposition"
|
|
1236
|
+
: "stranded_assigned_issue";
|
|
1237
|
+
}
|
|
1238
|
+
function strandedRecoveryActionFingerprint(input) {
|
|
1239
|
+
return [
|
|
1240
|
+
"source_scoped_recovery",
|
|
1241
|
+
input.issue.companyId,
|
|
1242
|
+
input.issue.id,
|
|
1243
|
+
input.recoveryCause,
|
|
1244
|
+
].join(":");
|
|
1245
|
+
}
|
|
1246
|
+
function buildStrandedRecoveryActionEvidence(input) {
|
|
1247
|
+
const context = parseObject(input.latestRun?.contextSnapshot);
|
|
1248
|
+
return {
|
|
1249
|
+
sourceIssueId: input.issue.id,
|
|
1250
|
+
sourceIdentifier: input.issue.identifier,
|
|
1251
|
+
previousStatus: input.previousStatus,
|
|
1252
|
+
latestIssueStatus: input.issue.status,
|
|
1253
|
+
latestRunId: input.latestRun?.id ?? null,
|
|
1254
|
+
latestRunStatus: input.latestRun?.status ?? null,
|
|
1255
|
+
latestRunErrorCode: input.latestRun?.errorCode ?? null,
|
|
1256
|
+
retryReason: readNonEmptyString(context.retryReason) ?? null,
|
|
1257
|
+
recoveryCause: input.recoveryCause,
|
|
1258
|
+
sourceRunId: input.successfulRunHandoffEvidence?.sourceRunId ?? null,
|
|
1259
|
+
correctiveRunId: input.successfulRunHandoffEvidence?.correctiveRunId ?? null,
|
|
1260
|
+
missingDisposition: input.successfulRunHandoffEvidence?.missingDisposition ?? null,
|
|
1261
|
+
handoffAttempt: input.successfulRunHandoffEvidence?.handoffAttempt ?? null,
|
|
1262
|
+
maxHandoffAttempts: input.successfulRunHandoffEvidence?.maxHandoffAttempts ?? null,
|
|
1263
|
+
};
|
|
1264
|
+
}
|
|
1265
|
+
async function ensureSourceScopedStrandedRecoveryAction(input) {
|
|
1266
|
+
const recoveryCause = input.recoveryCause ?? "stranded_assigned_issue";
|
|
1267
|
+
const ownerAgentId = await resolveStrandedIssueRecoveryOwnerAgentId(input.issue);
|
|
1268
|
+
const now = new Date();
|
|
1269
|
+
const action = await recoveryActionsSvc.upsertSourceScoped({
|
|
1270
|
+
companyId: input.issue.companyId,
|
|
1271
|
+
sourceIssueId: input.issue.id,
|
|
1272
|
+
kind: strandedRecoveryActionKind(recoveryCause),
|
|
1273
|
+
ownerType: ownerAgentId ? "agent" : "board",
|
|
1274
|
+
ownerAgentId,
|
|
1275
|
+
previousOwnerAgentId: input.issue.assigneeAgentId,
|
|
1276
|
+
returnOwnerAgentId: input.issue.assigneeAgentId,
|
|
1277
|
+
cause: recoveryCause,
|
|
1278
|
+
fingerprint: strandedRecoveryActionFingerprint({
|
|
1279
|
+
issue: input.issue,
|
|
1280
|
+
recoveryCause,
|
|
1281
|
+
}),
|
|
1282
|
+
evidence: buildStrandedRecoveryActionEvidence({
|
|
1283
|
+
issue: input.issue,
|
|
1284
|
+
latestRun: input.latestRun,
|
|
1285
|
+
previousStatus: input.previousStatus,
|
|
1286
|
+
recoveryCause,
|
|
1287
|
+
successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
|
|
1288
|
+
}),
|
|
1289
|
+
nextAction: recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON
|
|
1290
|
+
? "Choose and record a valid issue disposition without copying transcript content."
|
|
1291
|
+
: "Restore a live execution path, fix the runtime/adapter failure, or record an intentional manual resolution.",
|
|
1292
|
+
wakePolicy: ownerAgentId
|
|
1293
|
+
? {
|
|
1294
|
+
type: "wake_owner",
|
|
1295
|
+
reason: "source_scoped_recovery_action",
|
|
1296
|
+
ownerAgentId,
|
|
1297
|
+
}
|
|
1298
|
+
: {
|
|
1299
|
+
type: "board_escalation",
|
|
1300
|
+
reason: "no_invokable_recovery_owner",
|
|
1301
|
+
},
|
|
1302
|
+
monitorPolicy: null,
|
|
1303
|
+
maxAttempts: null,
|
|
1304
|
+
lastAttemptAt: now,
|
|
1305
|
+
});
|
|
1306
|
+
return action;
|
|
1307
|
+
}
|
|
1308
|
+
async function enqueueSourceScopedStrandedRecoveryWake(input) {
|
|
1309
|
+
if (!input.action.ownerAgentId)
|
|
1310
|
+
return;
|
|
1311
|
+
await deps.enqueueWakeup(input.action.ownerAgentId, {
|
|
1312
|
+
source: "assignment",
|
|
1313
|
+
triggerDetail: "system",
|
|
1314
|
+
reason: "source_scoped_recovery_action",
|
|
1315
|
+
idempotencyKey: `source_scoped_recovery_action:${input.action.id}:${input.action.attemptCount}`,
|
|
1316
|
+
payload: withRecoveryModelProfileHint({
|
|
1317
|
+
issueId: input.issue.id,
|
|
1318
|
+
sourceIssueId: input.issue.id,
|
|
1319
|
+
recoveryActionId: input.action.id,
|
|
1320
|
+
strandedRunId: input.latestRun?.id ?? null,
|
|
1321
|
+
recoveryCause: input.recoveryCause,
|
|
1322
|
+
}),
|
|
1323
|
+
requestedByActorType: "system",
|
|
1324
|
+
requestedByActorId: null,
|
|
1325
|
+
contextSnapshot: withRecoveryModelProfileHint({
|
|
1326
|
+
issueId: input.issue.id,
|
|
1327
|
+
taskId: input.issue.id,
|
|
1328
|
+
wakeReason: "source_scoped_recovery_action",
|
|
1329
|
+
skipIssueComment: true,
|
|
1330
|
+
source: "issue_recovery_action",
|
|
1331
|
+
recoveryActionId: input.action.id,
|
|
1332
|
+
sourceIssueId: input.issue.id,
|
|
1333
|
+
strandedRunId: input.latestRun?.id ?? null,
|
|
1334
|
+
recoveryCause: input.recoveryCause,
|
|
1335
|
+
}),
|
|
1336
|
+
});
|
|
1337
|
+
}
|
|
1231
1338
|
function buildRecoveryIssueInPlaceEscalationComment(input) {
|
|
1232
1339
|
const runLink = input.latestRun
|
|
1233
1340
|
? runUiLink({ id: input.latestRun.id, agentId: input.latestRun.agentId }, input.prefix)
|
|
@@ -1298,29 +1405,31 @@ export function recoveryService(db, deps) {
|
|
|
1298
1405
|
.then((rows) => rows.map((row) => row.blockerIssueId));
|
|
1299
1406
|
}
|
|
1300
1407
|
async function escalateStrandedAssignedIssue(input) {
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
if (!nestedRecoverySuppressed) {
|
|
1304
|
-
recoveryIssue = await ensureStrandedIssueRecoveryIssue({
|
|
1408
|
+
if (isStrandedIssueRecoveryIssue(input.issue)) {
|
|
1409
|
+
return escalateStrandedRecoveryIssueInPlace({
|
|
1305
1410
|
issue: input.issue,
|
|
1306
1411
|
previousStatus: input.previousStatus,
|
|
1307
1412
|
latestRun: input.latestRun,
|
|
1308
|
-
recoveryCause: input.recoveryCause,
|
|
1309
|
-
successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
|
|
1310
1413
|
});
|
|
1311
1414
|
}
|
|
1415
|
+
const recoveryCause = input.recoveryCause ?? "stranded_assigned_issue";
|
|
1416
|
+
const recoveryAction = await ensureSourceScopedStrandedRecoveryAction({
|
|
1417
|
+
issue: input.issue,
|
|
1418
|
+
previousStatus: input.previousStatus,
|
|
1419
|
+
latestRun: input.latestRun,
|
|
1420
|
+
recoveryCause,
|
|
1421
|
+
successfulRunHandoffEvidence: input.successfulRunHandoffEvidence,
|
|
1422
|
+
});
|
|
1312
1423
|
const blockerIds = await existingUnresolvedBlockerIssueIds(input.issue.companyId, input.issue.id);
|
|
1313
|
-
const nextBlockerIds = recoveryIssue
|
|
1314
|
-
? [...new Set([...blockerIds, recoveryIssue.id])]
|
|
1315
|
-
: blockerIds;
|
|
1316
1424
|
const updated = await issuesSvc.update(input.issue.id, {
|
|
1317
1425
|
status: "blocked",
|
|
1318
|
-
blockedByIssueIds:
|
|
1426
|
+
blockedByIssueIds: blockerIds,
|
|
1427
|
+
assigneeAgentId: recoveryAction.ownerAgentId ?? input.issue.assigneeAgentId,
|
|
1319
1428
|
});
|
|
1320
1429
|
if (!updated)
|
|
1321
1430
|
return null;
|
|
1322
1431
|
const prefix = await getCompanyIssuePrefix(input.issue.companyId);
|
|
1323
|
-
const recoveryOwner =
|
|
1432
|
+
const recoveryOwner = recoveryAction.ownerAgentId ? await getAgent(recoveryAction.ownerAgentId) : null;
|
|
1324
1433
|
const sourceAssignee = input.issue.assigneeAgentId ? await getAgent(input.issue.assigneeAgentId) : null;
|
|
1325
1434
|
let notice = null;
|
|
1326
1435
|
if (input.recoveryCause === SUCCESSFUL_RUN_MISSING_STATE_REASON && input.successfulRunHandoffEvidence) {
|
|
@@ -1331,41 +1440,51 @@ export function recoveryService(db, deps) {
|
|
|
1331
1440
|
: null,
|
|
1332
1441
|
correctiveRun: input.latestRun ? { id: input.latestRun.id, status: input.latestRun.status } : null,
|
|
1333
1442
|
sourceAssignee,
|
|
1334
|
-
recoveryIssue,
|
|
1443
|
+
recoveryIssue: null,
|
|
1444
|
+
recoveryActionId: recoveryAction.id,
|
|
1335
1445
|
recoveryOwner,
|
|
1336
1446
|
latestIssueStatus: input.issue.status,
|
|
1337
1447
|
latestHandoffRunStatus: input.latestRun?.status ?? "unknown",
|
|
1338
1448
|
missingDisposition: input.successfulRunHandoffEvidence.missingDisposition,
|
|
1339
1449
|
});
|
|
1340
1450
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
recoveryLine = await buildNestedStrandedRecoveryLine(input.issue, prefix);
|
|
1344
|
-
}
|
|
1345
|
-
else if (recoveryIssue) {
|
|
1346
|
-
recoveryLine = [
|
|
1451
|
+
const recoveryLine = recoveryAction.ownerAgentId
|
|
1452
|
+
? [
|
|
1347
1453
|
"",
|
|
1348
|
-
`- Recovery
|
|
1454
|
+
`- Recovery action: \`${recoveryAction.id}\``,
|
|
1349
1455
|
`- Recovery owner: ${agentUiLink(recoveryOwner, prefix)}`,
|
|
1350
|
-
"- Next action: the recovery owner should either restore a live execution path or record the manual resolution
|
|
1351
|
-
].join("\n")
|
|
1352
|
-
|
|
1353
|
-
else {
|
|
1354
|
-
recoveryLine = [
|
|
1456
|
+
"- Next action: the recovery owner should either restore a live execution path or record the manual resolution on the source issue.",
|
|
1457
|
+
].join("\n")
|
|
1458
|
+
: [
|
|
1355
1459
|
"",
|
|
1356
|
-
|
|
1460
|
+
`- Recovery action: \`${recoveryAction.id}\``,
|
|
1461
|
+
"- Recovery owner: board escalation, because Evermore could not find an invokable manager, creator, or executive owner with budget available.",
|
|
1357
1462
|
"- Next action: a board operator should assign an invokable recovery owner, fix the agent/runtime state, or record an intentional manual resolution.",
|
|
1358
1463
|
].join("\n");
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1464
|
+
if (recoveryAction.attemptCount === 1) {
|
|
1465
|
+
const escalationCommentMarker = `Recovery action: \`${recoveryAction.id}\``;
|
|
1466
|
+
const hasEscalationComment = await db
|
|
1467
|
+
.select({ id: issueComments.id, body: issueComments.body, metadata: issueComments.metadata })
|
|
1468
|
+
.from(issueComments)
|
|
1469
|
+
.where(and(eq(issueComments.issueId, input.issue.id), eq(issueComments.authorType, "system")))
|
|
1470
|
+
.orderBy(desc(issueComments.createdAt))
|
|
1471
|
+
.limit(50)
|
|
1472
|
+
.then((rows) => rows.some((row) => (row.body ?? "").includes(escalationCommentMarker) ||
|
|
1473
|
+
noticeMetadataReferencesRecoveryAction(row.metadata, recoveryAction.id)));
|
|
1474
|
+
if (!hasEscalationComment) {
|
|
1475
|
+
if (notice) {
|
|
1476
|
+
await issuesSvc.addComment(input.issue.id, notice.body, {}, {
|
|
1477
|
+
authorType: "system",
|
|
1478
|
+
presentation: notice.presentation,
|
|
1479
|
+
metadata: notice.metadata,
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
else {
|
|
1483
|
+
await issuesSvc.addComment(input.issue.id, `${input.comment ?? ""}${recoveryLine}`, {}, {
|
|
1484
|
+
authorType: "system",
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1369
1488
|
}
|
|
1370
1489
|
await logActivity(db, {
|
|
1371
1490
|
companyId: input.issue.companyId,
|
|
@@ -1389,11 +1508,40 @@ export function recoveryService(db, deps) {
|
|
|
1389
1508
|
latestRunId: input.latestRun?.id ?? null,
|
|
1390
1509
|
latestRunStatus: input.latestRun?.status ?? null,
|
|
1391
1510
|
latestRunErrorCode: input.latestRun?.errorCode ?? null,
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1511
|
+
recoveryActionId: recoveryAction.id,
|
|
1512
|
+
recoveryOwnerAgentId: recoveryAction.ownerAgentId,
|
|
1513
|
+
previousOwnerAgentId: recoveryAction.previousOwnerAgentId,
|
|
1514
|
+
returnOwnerAgentId: recoveryAction.returnOwnerAgentId,
|
|
1515
|
+
blockerIssueIds: blockerIds,
|
|
1395
1516
|
},
|
|
1396
1517
|
});
|
|
1518
|
+
await enqueueSourceScopedStrandedRecoveryWake({
|
|
1519
|
+
action: recoveryAction,
|
|
1520
|
+
issue: input.issue,
|
|
1521
|
+
latestRun: input.latestRun,
|
|
1522
|
+
recoveryCause,
|
|
1523
|
+
});
|
|
1524
|
+
if (recoveryAction.ownerAgentId && recoveryAction.ownerAgentId === input.issue.assigneeAgentId) {
|
|
1525
|
+
const [currentIssue] = await db
|
|
1526
|
+
.select({
|
|
1527
|
+
status: issues.status,
|
|
1528
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1529
|
+
})
|
|
1530
|
+
.from(issues)
|
|
1531
|
+
.where(eq(issues.id, input.issue.id))
|
|
1532
|
+
.limit(1);
|
|
1533
|
+
if (currentIssue &&
|
|
1534
|
+
(currentIssue.status !== "blocked" ||
|
|
1535
|
+
currentIssue.assigneeAgentId !== recoveryAction.ownerAgentId)) {
|
|
1536
|
+
const reblocked = await issuesSvc.update(input.issue.id, {
|
|
1537
|
+
status: "blocked",
|
|
1538
|
+
blockedByIssueIds: blockerIds,
|
|
1539
|
+
assigneeAgentId: recoveryAction.ownerAgentId,
|
|
1540
|
+
});
|
|
1541
|
+
if (reblocked)
|
|
1542
|
+
return reblocked;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1397
1545
|
return updated;
|
|
1398
1546
|
}
|
|
1399
1547
|
async function reconcileStrandedAssignedIssues() {
|
|
@@ -1629,28 +1777,29 @@ export function recoveryService(db, deps) {
|
|
|
1629
1777
|
return result;
|
|
1630
1778
|
}
|
|
1631
1779
|
async function collectIssueGraphLivenessFindings() {
|
|
1632
|
-
const
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1780
|
+
const issueRowsPromise = Promise.resolve(db
|
|
1781
|
+
.select({
|
|
1782
|
+
id: issues.id,
|
|
1783
|
+
companyId: issues.companyId,
|
|
1784
|
+
identifier: issues.identifier,
|
|
1785
|
+
title: issues.title,
|
|
1786
|
+
status: issues.status,
|
|
1787
|
+
projectId: issues.projectId,
|
|
1788
|
+
goalId: issues.goalId,
|
|
1789
|
+
parentId: issues.parentId,
|
|
1790
|
+
assigneeAgentId: issues.assigneeAgentId,
|
|
1791
|
+
assigneeUserId: issues.assigneeUserId,
|
|
1792
|
+
createdByAgentId: issues.createdByAgentId,
|
|
1793
|
+
createdByUserId: issues.createdByUserId,
|
|
1794
|
+
executionPolicy: issues.executionPolicy,
|
|
1795
|
+
executionState: issues.executionState,
|
|
1796
|
+
monitorNextCheckAt: issues.monitorNextCheckAt,
|
|
1797
|
+
monitorAttemptCount: issues.monitorAttemptCount,
|
|
1798
|
+
})
|
|
1799
|
+
.from(issues)
|
|
1800
|
+
.where(and(isNull(issues.hiddenAt), notInArray(issues.originKind, [RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation]))));
|
|
1801
|
+
const [issueRows, relationRows, agentRows, activeRunRows, activeIssueRunRows, wakeRows, interactionRows, approvalRows, recoveryIssueRows, recoveryActionRows,] = await Promise.all([
|
|
1802
|
+
issueRowsPromise,
|
|
1654
1803
|
db
|
|
1655
1804
|
.select({
|
|
1656
1805
|
companyId: issueRelations.companyId,
|
|
@@ -1728,6 +1877,19 @@ export function recoveryService(db, deps) {
|
|
|
1728
1877
|
STRANDED_ISSUE_RECOVERY_ORIGIN_KIND,
|
|
1729
1878
|
RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation,
|
|
1730
1879
|
]), notInArray(issues.status, ["done", "cancelled"]))),
|
|
1880
|
+
issueRowsPromise.then((rows) => {
|
|
1881
|
+
const issueIdsUnderAnalysis = rows.map((row) => row.id);
|
|
1882
|
+
return issueIdsUnderAnalysis.length === 0
|
|
1883
|
+
? []
|
|
1884
|
+
: db
|
|
1885
|
+
.select({
|
|
1886
|
+
companyId: issueRecoveryActions.companyId,
|
|
1887
|
+
issueId: issueRecoveryActions.sourceIssueId,
|
|
1888
|
+
status: issueRecoveryActions.status,
|
|
1889
|
+
})
|
|
1890
|
+
.from(issueRecoveryActions)
|
|
1891
|
+
.where(and(inArray(issueRecoveryActions.status, ["active", "escalated"]), inArray(issueRecoveryActions.sourceIssueId, issueIdsUnderAnalysis)));
|
|
1892
|
+
}),
|
|
1731
1893
|
]);
|
|
1732
1894
|
const openRecoveryIssues = recoveryIssueRows.flatMap((row) => {
|
|
1733
1895
|
if (row.originKind === RECOVERY_ORIGIN_KINDS.issueGraphLivenessEscalation) {
|
|
@@ -1781,7 +1943,7 @@ export function recoveryService(db, deps) {
|
|
|
1781
1943
|
})),
|
|
1782
1944
|
pendingInteractions: interactionRows,
|
|
1783
1945
|
pendingApprovals: approvalRows,
|
|
1784
|
-
openRecoveryIssues,
|
|
1946
|
+
openRecoveryIssues: openRecoveryIssues.concat(recoveryActionRows),
|
|
1785
1947
|
now: new Date(),
|
|
1786
1948
|
});
|
|
1787
1949
|
}
|