@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
package/dist/routes/issues.js
CHANGED
|
@@ -2,14 +2,14 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { Router } from "express";
|
|
3
3
|
import multer from "multer";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { and, desc, eq, inArray } from "drizzle-orm";
|
|
6
|
-
import { activityLog, executionWorkspaces, issueExecutionDecisions, projectWorkspaces } from "@evermore.work/db";
|
|
7
|
-
import { addIssueCommentSchema, acceptIssueThreadInteractionSchema, cancelIssueThreadInteractionSchema, companySearchQuerySchema, createIssueAttachmentMetadataSchema, createIssueThreadInteractionSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createChildIssueSchema, createIssueSchema, resolveCreateIssueStatusDefault, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY, rejectIssueThreadInteractionSchema, restoreIssueDocumentRevisionSchema, respondIssueThreadInteractionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, getClosedIsolatedExecutionWorkspaceMessage, isClosedIsolatedExecutionWorkspace, normalizeIssueIdentifier as normalizeIssueReferenceIdentifier, } from "@evermore.work/shared";
|
|
5
|
+
import { and, desc, eq, inArray, notInArray } from "drizzle-orm";
|
|
6
|
+
import { activityLog, executionWorkspaces, issueExecutionDecisions, issueRelations, issues as issueRows, projectWorkspaces, } from "@evermore.work/db";
|
|
7
|
+
import { addIssueCommentSchema, acceptIssueThreadInteractionSchema, cancelIssueThreadInteractionSchema, companySearchQuerySchema, createIssueAttachmentMetadataSchema, createIssueThreadInteractionSchema, createIssueWorkProductSchema, createIssueLabelSchema, checkoutIssueSchema, createChildIssueSchema, createIssueSchema, resolveCreateIssueStatusDefault, resolveIssueRecoveryActionSchema, feedbackTargetTypeSchema, feedbackTraceStatusSchema, feedbackVoteValueSchema, upsertIssueFeedbackVoteSchema, linkIssueApprovalSchema, issueDocumentKeySchema, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY, rejectIssueThreadInteractionSchema, restoreIssueDocumentRevisionSchema, respondIssueThreadInteractionSchema, updateIssueWorkProductSchema, upsertIssueDocumentSchema, updateIssueSchema, getClosedIsolatedExecutionWorkspaceMessage, isClosedIsolatedExecutionWorkspace, normalizeIssueIdentifier as normalizeIssueReferenceIdentifier, } from "@evermore.work/shared";
|
|
8
8
|
import { trackAgentTaskCompleted } from "@evermore.work/shared/telemetry";
|
|
9
9
|
import { getTelemetryClient } from "../telemetry.js";
|
|
10
10
|
import { validate } from "../middleware/validate.js";
|
|
11
11
|
import * as serviceIndex from "../services/index.js";
|
|
12
|
-
import { accessService, agentService, companyService, companySearchService, goalService, heartbeatService, issueApprovalService, issueThreadInteractionService, ISSUE_LIST_DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT, issueReferenceService, issueService, clampIssueListLimit, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
|
|
12
|
+
import { accessService, agentService, companyService, companySearchService, goalService, heartbeatService, issueApprovalService, issueRecoveryActionService, issueThreadInteractionService, ISSUE_LIST_DEFAULT_LIMIT, ISSUE_LIST_MAX_LIMIT, issueReferenceService, issueService, clampIssueListLimit, documentService, logActivity, projectService, routineService, workProductService, } from "../services/index.js";
|
|
13
13
|
import { logger } from "../middleware/logger.js";
|
|
14
14
|
import { conflict, forbidden, HttpError, notFound, unauthorized, unprocessable } from "../errors.js";
|
|
15
15
|
import { assertBoard, assertCompanyAccess, getActorInfo } from "./authz.js";
|
|
@@ -235,6 +235,34 @@ async function listSuccessfulRunHandoffStates(db, companyId, issueIds) {
|
|
|
235
235
|
}
|
|
236
236
|
return states;
|
|
237
237
|
}
|
|
238
|
+
async function relationRecoveryActionMap(recoveryActionsSvc, companyId, relations) {
|
|
239
|
+
const candidates = [];
|
|
240
|
+
const visit = (summary) => {
|
|
241
|
+
candidates.push(summary);
|
|
242
|
+
for (const terminal of summary.terminalBlockers ?? []) {
|
|
243
|
+
visit(terminal);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
for (const blocker of relations.blockedBy)
|
|
247
|
+
visit(blocker);
|
|
248
|
+
for (const blocking of relations.blocks)
|
|
249
|
+
visit(blocking);
|
|
250
|
+
if (candidates.length === 0)
|
|
251
|
+
return new Map();
|
|
252
|
+
const ids = [...new Set(candidates.map((summary) => summary.id))];
|
|
253
|
+
return recoveryActionsSvc.listActiveForIssues(companyId, ids);
|
|
254
|
+
}
|
|
255
|
+
function withRecoveryActionsOnRelationSummaries(relations, recoveryActionByIssueId) {
|
|
256
|
+
const augment = (summary) => ({
|
|
257
|
+
...summary,
|
|
258
|
+
activeRecoveryAction: recoveryActionByIssueId.get(summary.id) ?? summary.activeRecoveryAction ?? null,
|
|
259
|
+
terminalBlockers: summary.terminalBlockers?.map(augment),
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
blockedBy: relations.blockedBy.map(augment),
|
|
263
|
+
blocks: relations.blocks.map(augment),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
238
266
|
const ACTIVE_REVIEW_APPROVAL_STATUSES = new Set(["pending", "revision_requested"]);
|
|
239
267
|
const INVALID_AGENT_IN_REVIEW_DISPOSITION_MESSAGE = "invalid_issue_disposition: Agent-authored updates that move an issue to in_review must include a real review path. " +
|
|
240
268
|
"This request would leave the issue in_review without anyone or anything owning the next action. " +
|
|
@@ -512,6 +540,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
512
540
|
const projectsSvc = projectService(db);
|
|
513
541
|
const goalsSvc = goalService(db);
|
|
514
542
|
const issueApprovalsSvc = issueApprovalService(db);
|
|
543
|
+
const recoveryActionsSvc = issueRecoveryActionService(db);
|
|
515
544
|
const executionWorkspacesSvc = executionWorkspaceServiceDirect(db);
|
|
516
545
|
const workProductsSvc = workProductService(db);
|
|
517
546
|
const documentsSvc = documentService(db);
|
|
@@ -1082,10 +1111,15 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1082
1111
|
limit,
|
|
1083
1112
|
offset,
|
|
1084
1113
|
});
|
|
1085
|
-
const
|
|
1114
|
+
const issueIds = result.map((issue) => issue.id);
|
|
1115
|
+
const [handoffStates, recoveryActionByIssue] = await Promise.all([
|
|
1116
|
+
listSuccessfulRunHandoffStates(db, companyId, issueIds),
|
|
1117
|
+
recoveryActionsSvc.listActiveForIssues(companyId, issueIds),
|
|
1118
|
+
]);
|
|
1086
1119
|
res.json(result.map((issue) => ({
|
|
1087
1120
|
...issue,
|
|
1088
1121
|
successfulRunHandoff: handoffStates.get(issue.id) ?? null,
|
|
1122
|
+
activeRecoveryAction: recoveryActionByIssue.get(issue.id) ?? null,
|
|
1089
1123
|
})));
|
|
1090
1124
|
});
|
|
1091
1125
|
router.get("/companies/:companyId/labels", async (req, res) => {
|
|
@@ -1153,7 +1187,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1153
1187
|
const currentExecutionWorkspacePromise = issue.executionWorkspaceId
|
|
1154
1188
|
? executionWorkspacesSvc.getById(issue.executionWorkspaceId)
|
|
1155
1189
|
: Promise.resolve(null);
|
|
1156
|
-
const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, blockerAttention, productivityReview, scheduledRetry, attachments, continuationSummary, currentExecutionWorkspace,] = await Promise.all([
|
|
1190
|
+
const [{ project, goal }, ancestors, commentCursor, wakeComment, relations, blockerAttention, productivityReview, scheduledRetry, attachments, continuationSummary, currentExecutionWorkspace, activeRecoveryAction,] = await Promise.all([
|
|
1157
1191
|
resolveIssueProjectAndGoal(issue),
|
|
1158
1192
|
svc.getAncestors(issue.id),
|
|
1159
1193
|
svc.getCommentCursor(issue.id),
|
|
@@ -1165,7 +1199,10 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1165
1199
|
svc.listAttachments(issue.id),
|
|
1166
1200
|
documentsSvc.getIssueDocumentByKey(issue.id, ISSUE_CONTINUATION_SUMMARY_DOCUMENT_KEY),
|
|
1167
1201
|
currentExecutionWorkspacePromise,
|
|
1202
|
+
recoveryActionsSvc.getActiveForIssue(issue.companyId, issue.id),
|
|
1168
1203
|
]);
|
|
1204
|
+
const recoveryActionsByRelationIssue = await relationRecoveryActionMap(recoveryActionsSvc, issue.companyId, relations);
|
|
1205
|
+
const relationsWithRecoveryActions = withRecoveryActionsOnRelationSummaries(relations, recoveryActionsByRelationIssue);
|
|
1169
1206
|
res.json({
|
|
1170
1207
|
issue: {
|
|
1171
1208
|
id: issue.id,
|
|
@@ -1177,12 +1214,13 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1177
1214
|
...(blockerAttention ? { blockerAttention } : {}),
|
|
1178
1215
|
productivityReview,
|
|
1179
1216
|
scheduledRetry,
|
|
1217
|
+
activeRecoveryAction,
|
|
1180
1218
|
priority: issue.priority,
|
|
1181
1219
|
projectId: issue.projectId,
|
|
1182
1220
|
goalId: goal?.id ?? issue.goalId,
|
|
1183
1221
|
parentId: issue.parentId,
|
|
1184
|
-
blockedBy:
|
|
1185
|
-
blocks:
|
|
1222
|
+
blockedBy: relationsWithRecoveryActions.blockedBy,
|
|
1223
|
+
blocks: relationsWithRecoveryActions.blocks,
|
|
1186
1224
|
assigneeAgentId: issue.assigneeAgentId,
|
|
1187
1225
|
assigneeUserId: issue.assigneeUserId,
|
|
1188
1226
|
originKind: issue.originKind,
|
|
@@ -1246,7 +1284,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1246
1284
|
return;
|
|
1247
1285
|
}
|
|
1248
1286
|
assertCompanyAccess(req, issue.companyId);
|
|
1249
|
-
const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations, blockerAttention, productivityReview, referenceSummary, successfulRunHandoffStates, scheduledRetry,] = await Promise.all([
|
|
1287
|
+
const [{ project, goal }, ancestors, mentionedProjectIds, documentPayload, relations, blockerAttention, productivityReview, referenceSummary, successfulRunHandoffStates, scheduledRetry, activeRecoveryAction,] = await Promise.all([
|
|
1250
1288
|
resolveIssueProjectAndGoal(issue),
|
|
1251
1289
|
svc.getAncestors(issue.id),
|
|
1252
1290
|
svc.findMentionedProjectIds(issue.id, { includeCommentBodies: false }),
|
|
@@ -1257,7 +1295,10 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1257
1295
|
issueReferencesSvc.listIssueReferenceSummary(issue.id),
|
|
1258
1296
|
listSuccessfulRunHandoffStates(db, issue.companyId, [issue.id]),
|
|
1259
1297
|
svc.getCurrentScheduledRetry(issue.id),
|
|
1298
|
+
recoveryActionsSvc.getActiveForIssue(issue.companyId, issue.id),
|
|
1260
1299
|
]);
|
|
1300
|
+
const recoveryActionsByRelationIssue = await relationRecoveryActionMap(recoveryActionsSvc, issue.companyId, relations);
|
|
1301
|
+
const relationsWithRecoveryActions = withRecoveryActionsOnRelationSummaries(relations, recoveryActionsByRelationIssue);
|
|
1261
1302
|
const mentionedProjects = mentionedProjectIds.length > 0
|
|
1262
1303
|
? await projectsSvc.listByIds(issue.companyId, mentionedProjectIds)
|
|
1263
1304
|
: [];
|
|
@@ -1273,8 +1314,9 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1273
1314
|
productivityReview,
|
|
1274
1315
|
successfulRunHandoff: successfulRunHandoffStates.get(issue.id) ?? null,
|
|
1275
1316
|
scheduledRetry,
|
|
1276
|
-
|
|
1277
|
-
|
|
1317
|
+
activeRecoveryAction,
|
|
1318
|
+
blockedBy: relationsWithRecoveryActions.blockedBy,
|
|
1319
|
+
blocks: relationsWithRecoveryActions.blocks,
|
|
1278
1320
|
relatedWork: referenceSummary,
|
|
1279
1321
|
referencedIssueIdentifiers: referenceSummary.outbound.map((item) => item.issue.identifier ?? item.issue.id),
|
|
1280
1322
|
...documentPayload,
|
|
@@ -1285,6 +1327,125 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1285
1327
|
workProducts,
|
|
1286
1328
|
});
|
|
1287
1329
|
});
|
|
1330
|
+
router.get("/issues/:id/recovery-actions", async (req, res) => {
|
|
1331
|
+
const id = req.params.id;
|
|
1332
|
+
const issue = await svc.getById(id);
|
|
1333
|
+
if (!issue) {
|
|
1334
|
+
res.status(404).json({ error: "Issue not found" });
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
assertCompanyAccess(req, issue.companyId);
|
|
1338
|
+
const active = await recoveryActionsSvc.getActiveForIssue(issue.companyId, issue.id);
|
|
1339
|
+
res.json({
|
|
1340
|
+
active,
|
|
1341
|
+
actions: active ? [active] : [],
|
|
1342
|
+
});
|
|
1343
|
+
});
|
|
1344
|
+
router.post("/issues/:id/recovery-actions/resolve", validate(resolveIssueRecoveryActionSchema), async (req, res) => {
|
|
1345
|
+
const id = req.params.id;
|
|
1346
|
+
const existing = await svc.getById(id);
|
|
1347
|
+
if (!existing) {
|
|
1348
|
+
res.status(404).json({ error: "Issue not found" });
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
assertCompanyAccess(req, existing.companyId);
|
|
1352
|
+
if (!(await assertAgentIssueMutationAllowed(req, res, existing)))
|
|
1353
|
+
return;
|
|
1354
|
+
const { actionId, outcome, sourceIssueStatus, resolutionNote } = req.body;
|
|
1355
|
+
if (outcome === "false_positive" || outcome === "cancelled") {
|
|
1356
|
+
assertBoard(req);
|
|
1357
|
+
}
|
|
1358
|
+
const actor = getActorInfo(req);
|
|
1359
|
+
const updateFields = sourceIssueStatus ? { status: sourceIssueStatus } : {};
|
|
1360
|
+
await assertAgentInReviewReviewPath({
|
|
1361
|
+
existing,
|
|
1362
|
+
updateFields,
|
|
1363
|
+
actorType: req.actor.type,
|
|
1364
|
+
});
|
|
1365
|
+
const actionStatus = outcome === "cancelled" ? "cancelled" : "resolved";
|
|
1366
|
+
const result = await db.transaction(async (tx) => {
|
|
1367
|
+
let issue = existing;
|
|
1368
|
+
if (outcome === "blocked") {
|
|
1369
|
+
const unresolvedBlockers = await tx
|
|
1370
|
+
.select({ id: issueRows.id })
|
|
1371
|
+
.from(issueRelations)
|
|
1372
|
+
.innerJoin(issueRows, eq(issueRelations.issueId, issueRows.id))
|
|
1373
|
+
.where(and(eq(issueRelations.companyId, existing.companyId), eq(issueRelations.relatedIssueId, existing.id), eq(issueRelations.type, "blocks"), notInArray(issueRows.status, ["done", "cancelled"])))
|
|
1374
|
+
.limit(1);
|
|
1375
|
+
if (unresolvedBlockers.length === 0) {
|
|
1376
|
+
throw unprocessable("Blocked recovery resolution requires an unresolved first-class blocker on the source issue");
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
if (sourceIssueStatus) {
|
|
1380
|
+
const updatedIssue = await svc.update(id, {
|
|
1381
|
+
status: sourceIssueStatus,
|
|
1382
|
+
actorAgentId: actor.agentId ?? null,
|
|
1383
|
+
actorUserId: actor.actorType === "user" ? actor.actorId : null,
|
|
1384
|
+
}, tx);
|
|
1385
|
+
if (!updatedIssue)
|
|
1386
|
+
throw notFound("Issue not found");
|
|
1387
|
+
issue = updatedIssue;
|
|
1388
|
+
}
|
|
1389
|
+
const recoveryAction = await recoveryActionsSvc.resolveActiveForIssue({
|
|
1390
|
+
companyId: existing.companyId,
|
|
1391
|
+
sourceIssueId: existing.id,
|
|
1392
|
+
actionId: actionId ?? null,
|
|
1393
|
+
status: actionStatus,
|
|
1394
|
+
outcome,
|
|
1395
|
+
resolutionNote: resolutionNote ?? null,
|
|
1396
|
+
}, tx);
|
|
1397
|
+
if (!recoveryAction)
|
|
1398
|
+
throw notFound("Active recovery action not found");
|
|
1399
|
+
return { issue, recoveryAction };
|
|
1400
|
+
});
|
|
1401
|
+
await routinesSvc.syncRunStatusForIssue(result.issue.id);
|
|
1402
|
+
if (sourceIssueStatus && existing.status !== result.issue.status) {
|
|
1403
|
+
await logActivity(db, {
|
|
1404
|
+
companyId: result.issue.companyId,
|
|
1405
|
+
actorType: actor.actorType,
|
|
1406
|
+
actorId: actor.actorId,
|
|
1407
|
+
agentId: actor.agentId,
|
|
1408
|
+
runId: actor.runId,
|
|
1409
|
+
action: "issue.updated",
|
|
1410
|
+
entityType: "issue",
|
|
1411
|
+
entityId: result.issue.id,
|
|
1412
|
+
details: {
|
|
1413
|
+
identifier: result.issue.identifier,
|
|
1414
|
+
status: result.issue.status,
|
|
1415
|
+
source: "recovery_action_resolution",
|
|
1416
|
+
recoveryActionId: result.recoveryAction.id,
|
|
1417
|
+
_previous: {
|
|
1418
|
+
status: existing.status,
|
|
1419
|
+
},
|
|
1420
|
+
},
|
|
1421
|
+
});
|
|
1422
|
+
}
|
|
1423
|
+
await logActivity(db, {
|
|
1424
|
+
companyId: result.issue.companyId,
|
|
1425
|
+
actorType: actor.actorType,
|
|
1426
|
+
actorId: actor.actorId,
|
|
1427
|
+
agentId: actor.agentId,
|
|
1428
|
+
runId: actor.runId,
|
|
1429
|
+
action: "issue.recovery_action_resolved",
|
|
1430
|
+
entityType: "issue",
|
|
1431
|
+
entityId: result.issue.id,
|
|
1432
|
+
details: {
|
|
1433
|
+
identifier: result.issue.identifier,
|
|
1434
|
+
recoveryActionId: result.recoveryAction.id,
|
|
1435
|
+
recoveryActionStatus: result.recoveryAction.status,
|
|
1436
|
+
outcome: result.recoveryAction.outcome,
|
|
1437
|
+
sourceIssueStatus: sourceIssueStatus ?? null,
|
|
1438
|
+
resolutionNote: result.recoveryAction.resolutionNote,
|
|
1439
|
+
},
|
|
1440
|
+
});
|
|
1441
|
+
res.json({
|
|
1442
|
+
issue: {
|
|
1443
|
+
...result.issue,
|
|
1444
|
+
activeRecoveryAction: null,
|
|
1445
|
+
},
|
|
1446
|
+
recoveryAction: result.recoveryAction,
|
|
1447
|
+
});
|
|
1448
|
+
});
|
|
1288
1449
|
router.get("/issues/:id/work-products", async (req, res) => {
|
|
1289
1450
|
const id = req.params.id;
|
|
1290
1451
|
const issue = await svc.getById(id);
|