@evermore.work/server 2026.518.0-canary.0 → 2026.518.0-canary.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/redaction.d.ts.map +1 -1
- package/dist/redaction.js +31 -4
- package/dist/redaction.js.map +1 -1
- package/dist/routes/issues.d.ts.map +1 -1
- package/dist/routes/issues.js +312 -6
- package/dist/routes/issues.js.map +1 -1
- package/dist/services/heartbeat.d.ts +3 -0
- package/dist/services/heartbeat.d.ts.map +1 -1
- package/dist/services/heartbeat.js +89 -6
- package/dist/services/heartbeat.js.map +1 -1
- package/dist/services/issues.d.ts.map +1 -1
- package/dist/services/issues.js +11 -1
- package/dist/services/issues.js.map +1 -1
- package/dist/services/plugin-local-folders.d.ts.map +1 -1
- package/dist/services/plugin-local-folders.js +6 -2
- package/dist/services/plugin-local-folders.js.map +1 -1
- package/dist/services/plugin-managed-routines.d.ts +1 -0
- package/dist/services/plugin-managed-routines.d.ts.map +1 -1
- package/dist/services/projects.d.ts +1 -1
- package/dist/services/recovery/service.d.ts +1 -0
- package/dist/services/recovery/service.d.ts.map +1 -1
- package/dist/services/recovery/service.js +333 -5
- package/dist/services/recovery/service.js.map +1 -1
- package/dist/services/routines.d.ts +4 -0
- package/dist/services/routines.d.ts.map +1 -1
- package/dist/services/routines.js +52 -5
- package/dist/services/routines.js.map +1 -1
- package/dist/services/secrets.d.ts +6 -2
- package/dist/services/secrets.d.ts.map +1 -1
- package/dist/services/secrets.js +60 -17
- package/dist/services/secrets.js.map +1 -1
- package/package.json +15 -15
- package/ui-dist/assets/{_basePickBy-CyZigXxL.js → _basePickBy-CqWMcapU.js} +1 -1
- package/ui-dist/assets/{_baseUniq-DgESSGjb.js → _baseUniq-CtXUWMY8.js} +1 -1
- package/ui-dist/assets/{arc-fdje2cl0.js → arc-C1XjRaNf.js} +1 -1
- package/ui-dist/assets/{architectureDiagram-VXUJARFQ-BKh2ybZw.js → architectureDiagram-VXUJARFQ-CCeWA90x.js} +1 -1
- package/ui-dist/assets/{blockDiagram-VD42YOAC-hZzVAtlS.js → blockDiagram-VD42YOAC-DfCY6om4.js} +1 -1
- package/ui-dist/assets/{c4Diagram-YG6GDRKO-DqGOUNLx.js → c4Diagram-YG6GDRKO-BGrul0up.js} +1 -1
- package/ui-dist/assets/channel-ChYSBygj.js +1 -0
- package/ui-dist/assets/{chunk-4BX2VUAB-DkFfF4Vw.js → chunk-4BX2VUAB-d9TTQhTl.js} +1 -1
- package/ui-dist/assets/{chunk-55IACEB6-D1I9MYTq.js → chunk-55IACEB6-C7jwF-m3.js} +1 -1
- package/ui-dist/assets/{chunk-B4BG7PRW-3DDKi6PV.js → chunk-B4BG7PRW-lsfcegaU.js} +1 -1
- package/ui-dist/assets/{chunk-DI55MBZ5-DFC0ufTR.js → chunk-DI55MBZ5-B6Sn2B-l.js} +1 -1
- package/ui-dist/assets/{chunk-FMBD7UC4-Bcq3FqYg.js → chunk-FMBD7UC4-d-DUqf0y.js} +1 -1
- package/ui-dist/assets/{chunk-QN33PNHL-AxoPJ4dk.js → chunk-QN33PNHL-CZF3jISb.js} +1 -1
- package/ui-dist/assets/{chunk-QZHKN3VN-C6usVPcB.js → chunk-QZHKN3VN-DsaPyy1F.js} +1 -1
- package/ui-dist/assets/{chunk-TZMSLE5B-BXd6So6Q.js → chunk-TZMSLE5B-BmiOAEkf.js} +1 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-C7XPxf1M.js +1 -0
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-C7XPxf1M.js +1 -0
- package/ui-dist/assets/clone-BjVNMY9x.js +1 -0
- package/ui-dist/assets/{cose-bilkent-S5V4N54A-QUlh4iz_.js → cose-bilkent-S5V4N54A-Z7JQMhqR.js} +1 -1
- package/ui-dist/assets/{dagre-6UL2VRFP-DeWl3Bnh.js → dagre-6UL2VRFP-BOezmgTi.js} +1 -1
- package/ui-dist/assets/{diagram-PSM6KHXK-BOgm0Y0v.js → diagram-PSM6KHXK-Bi3xQylZ.js} +1 -1
- package/ui-dist/assets/{diagram-QEK2KX5R--sjcGOSR.js → diagram-QEK2KX5R-D5xUfRKy.js} +1 -1
- package/ui-dist/assets/{diagram-S2PKOQOG-swytmQ59.js → diagram-S2PKOQOG-Cs742jKm.js} +1 -1
- package/ui-dist/assets/{erDiagram-Q2GNP2WA-AG9oeKf_.js → erDiagram-Q2GNP2WA-D4voueCw.js} +1 -1
- package/ui-dist/assets/{flowDiagram-NV44I4VS-DjfsYR72.js → flowDiagram-NV44I4VS-CU8ncxLE.js} +1 -1
- package/ui-dist/assets/{ganttDiagram-JELNMOA3-CzkYgrob.js → ganttDiagram-JELNMOA3-D_ppL0-H.js} +1 -1
- package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-CYdF_fRA.js → gitGraphDiagram-V2S2FVAM-DbcI4Yg7.js} +1 -1
- package/ui-dist/assets/{graph-C9CpmUHW.js → graph-C6Xbwedt.js} +1 -1
- package/ui-dist/assets/{index-Be5cmSx7.js → index-2xIzoXjY.js} +1 -1
- package/ui-dist/assets/{index-BwheAg6e.js → index-7jgl9bxD.js} +1 -1
- package/ui-dist/assets/{index-CBTbN9sK.js → index-BMoirXsY.js} +1 -1
- package/ui-dist/assets/{index-CN8ht-3V.js → index-Byew81VK.js} +1 -1
- package/ui-dist/assets/{index-wwJmI27u.js → index-C6ZkE1Ht.js} +1 -1
- package/ui-dist/assets/{index-CPMdaECG.js → index-C9271z7J.js} +1 -1
- package/ui-dist/assets/{index-CXBisfFe.js → index-CKA2c7F8.js} +1 -1
- package/ui-dist/assets/{index-NFt_e-il.js → index-CRlZz7hL.js} +1 -1
- package/ui-dist/assets/{index-BNyx3LoE.js → index-CbwP7jIY.js} +1 -1
- package/ui-dist/assets/{index-DslHQrSc.js → index-CqTES4fn.js} +1 -1
- package/ui-dist/assets/{index-n4B5qB20.js → index-D9xQ3bUB.js} +1 -1
- package/ui-dist/assets/{index-C3ztpdsf.js → index-DSAXt971.js} +1 -1
- package/ui-dist/assets/{index-DkTllr0Y.js → index-DWSXKoZc.js} +1 -1
- package/ui-dist/assets/{index-RIwOURie.js → index-Dbt26HjH.js} +1 -1
- package/ui-dist/assets/{index-BU-ccYxt.js → index-Dftcrj43.js} +1 -1
- package/ui-dist/assets/{index-Cp6wXhn6.js → index-DgQjhgdA.js} +1 -1
- package/ui-dist/assets/{index-BdzhusxB.js → index-DlWHuNOg.js} +1 -1
- package/ui-dist/assets/{index-0J3lkVvW.js → index-DnZdiDv2.js} +1 -1
- package/ui-dist/assets/{index-86eXFEVV.js → index-DqHXftYp.js} +1 -1
- package/ui-dist/assets/{index-DIZJVs_j.js → index-DvA5N6JH.js} +1 -1
- package/ui-dist/assets/{index-Beg6-tI6.js → index-Dz05lp4r.js} +1 -1
- package/ui-dist/assets/{index-P78vSPeP.js → index-STXLWsTQ.js} +156 -156
- package/ui-dist/assets/{index-DUzr_Pqd.js → index-U9vA4wEM.js} +1 -1
- package/ui-dist/assets/index-y1iMp6V0.css +1 -0
- package/ui-dist/assets/{infoDiagram-HS3SLOUP-DbTu1ikC.js → infoDiagram-HS3SLOUP-DKJbdqb7.js} +1 -1
- package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-Dr_3cCaU.js → journeyDiagram-XKPGCS4Q-CWmIi8cd.js} +1 -1
- package/ui-dist/assets/{kanban-definition-3W4ZIXB7-D2VqYXi6.js → kanban-definition-3W4ZIXB7-B3q1bO5h.js} +1 -1
- package/ui-dist/assets/{layout-u3iqUgQ_.js → layout-D5BmO8lB.js} +1 -1
- package/ui-dist/assets/{linear-BZiN27c7.js → linear-DyH8N0l1.js} +1 -1
- package/ui-dist/assets/{mermaid.core-Cdqrn86d.js → mermaid.core-BbPNdvQf.js} +4 -4
- package/ui-dist/assets/{mindmap-definition-VGOIOE7T-DGcs3SZu.js → mindmap-definition-VGOIOE7T-CoaXwazv.js} +1 -1
- package/ui-dist/assets/{pieDiagram-ADFJNKIX-CUokE42u.js → pieDiagram-ADFJNKIX-BGfmYX-N.js} +1 -1
- package/ui-dist/assets/{quadrantDiagram-AYHSOK5B--ZywPGaP.js → quadrantDiagram-AYHSOK5B-DAyRfIdk.js} +1 -1
- package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-Dfij5FW1.js → requirementDiagram-UZGBJVZJ-DaLL5SEP.js} +1 -1
- package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-BJA1WqbQ.js → sankeyDiagram-TZEHDZUN-D8Dh0Tnc.js} +1 -1
- package/ui-dist/assets/{sequenceDiagram-WL72ISMW-_qX4RaDf.js → sequenceDiagram-WL72ISMW-Cy6bb3Jm.js} +1 -1
- package/ui-dist/assets/{stateDiagram-FKZM4ZOC-Cgt2GXml.js → stateDiagram-FKZM4ZOC-iatgt-rZ.js} +1 -1
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-B-gV_soZ.js +1 -0
- package/ui-dist/assets/{timeline-definition-IT6M3QCI-DuRPO8CY.js → timeline-definition-IT6M3QCI-oMHUv8mk.js} +1 -1
- package/ui-dist/assets/{treemap-GDKQZRPO-D9I4Kr4d.js → treemap-GDKQZRPO-BKN_7nXQ.js} +1 -1
- package/ui-dist/assets/{xychartDiagram-PRI3JC2R-COU-zxX4.js → xychartDiagram-PRI3JC2R-D4bKshmY.js} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/channel-D11NtE_b.js +0 -1
- package/ui-dist/assets/classDiagram-2ON5EDUG-Ca0nuZ4I.js +0 -1
- package/ui-dist/assets/classDiagram-v2-WZHVMYZB-Ca0nuZ4I.js +0 -1
- package/ui-dist/assets/clone-pBvhn5z6.js +0 -1
- package/ui-dist/assets/index-BiweVGJL.css +0 -1
- package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-CvGN1v50.js +0 -1
package/dist/redaction.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAuCA,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAgDrD,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA8BvF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAI1G;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQzD"}
|
package/dist/redaction.js
CHANGED
|
@@ -1,12 +1,37 @@
|
|
|
1
1
|
import { redactCommandText } from "@evermore.work/adapter-utils";
|
|
2
|
-
const
|
|
2
|
+
const SECRET_FIELD_NAME_PATTERN = String.raw `[A-Za-z0-9_-]*(?:api[-_]?key|access[-_]?token|auth(?:_?token)?|token|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)[A-Za-z0-9_-]*`;
|
|
3
|
+
const SECRET_PAYLOAD_KEY_RE = new RegExp(SECRET_FIELD_NAME_PATTERN, "i");
|
|
3
4
|
const COMMAND_PAYLOAD_KEY_RE = /(^command$|^cmd$|command[-_]?line|resolved[-_]?command|EVERMORE_RESOLVED_COMMAND)/i;
|
|
4
5
|
const COMMAND_ARGS_PAYLOAD_KEY_RE = /^(commandArgs|command_?args|argv)$/i;
|
|
5
6
|
const JWT_VALUE_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)?$/;
|
|
6
|
-
const CLI_SECRET_FLAG_RE =
|
|
7
|
-
const JSON_SECRET_FIELD_TEXT_RE =
|
|
8
|
-
const ESCAPED_JSON_SECRET_FIELD_TEXT_RE =
|
|
7
|
+
const CLI_SECRET_FLAG_RE = new RegExp(String.raw `^-{1,2}${SECRET_FIELD_NAME_PATTERN}$`, "i");
|
|
8
|
+
const JSON_SECRET_FIELD_TEXT_RE = new RegExp(String.raw `((?:"|')?${SECRET_FIELD_NAME_PATTERN}(?:"|')?\s*:\s*(?:"|'))[^"'` + "`" + String.raw `\r\n]+((?:"|'))`, "gi");
|
|
9
|
+
const ESCAPED_JSON_SECRET_FIELD_TEXT_RE = new RegExp(String.raw `((?:\\")?${SECRET_FIELD_NAME_PATTERN}(?:\\")?\s*:\s*(?:\\"))[^\\\r\n]+((?:\\"))`, "gi");
|
|
10
|
+
const SECRET_TEXT_HINTS = [
|
|
11
|
+
"api",
|
|
12
|
+
"key",
|
|
13
|
+
"token",
|
|
14
|
+
"auth",
|
|
15
|
+
"bearer",
|
|
16
|
+
"secret",
|
|
17
|
+
"pass",
|
|
18
|
+
"credential",
|
|
19
|
+
"jwt",
|
|
20
|
+
"private",
|
|
21
|
+
"cookie",
|
|
22
|
+
"connectionstring",
|
|
23
|
+
"sk-",
|
|
24
|
+
"ghp_",
|
|
25
|
+
"gho_",
|
|
26
|
+
"ghu_",
|
|
27
|
+
"ghs_",
|
|
28
|
+
"ghr_",
|
|
29
|
+
];
|
|
9
30
|
export const REDACTED_EVENT_VALUE = "***REDACTED***";
|
|
31
|
+
function maybeContainsSecretText(input) {
|
|
32
|
+
const lower = input.toLowerCase();
|
|
33
|
+
return SECRET_TEXT_HINTS.some((hint) => lower.includes(hint)) || input.includes(".");
|
|
34
|
+
}
|
|
10
35
|
function isPlainObject(value) {
|
|
11
36
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
12
37
|
return false;
|
|
@@ -91,6 +116,8 @@ export function redactEventPayload(payload) {
|
|
|
91
116
|
return sanitizeRecord(payload);
|
|
92
117
|
}
|
|
93
118
|
export function redactSensitiveText(input) {
|
|
119
|
+
if (!maybeContainsSecretText(input))
|
|
120
|
+
return input;
|
|
94
121
|
return redactCommandText(input
|
|
95
122
|
.replace(JSON_SECRET_FIELD_TEXT_RE, `$1${REDACTED_EVENT_VALUE}$2`)
|
|
96
123
|
.replace(ESCAPED_JSON_SECRET_FIELD_TEXT_RE, `$1${REDACTED_EVENT_VALUE}$2`), REDACTED_EVENT_VALUE);
|
package/dist/redaction.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAEjE,MAAM,qBAAqB,
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/routes/issues.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAS5C,OAAO,EA8BL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAI3B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA8C1D,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,0CAA0C,CAAC;AASlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"issues.d.ts","sourceRoot":"","sources":["../../src/routes/issues.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAS5C,OAAO,EA8BL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAI3B,MAAM,uBAAuB,CAAC;AAG/B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA8C1D,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,0CAA0C,CAAC;AASlD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sCAAsC,CAAC;AAgBhF,KAAK,oBAAoB,GAAG;IAC1B,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtF,CAAC;AAirBF,wBAAgB,WAAW,CACzB,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,cAAc,EACvB,IAAI,GAAE;IACJ,qBAAqB,CAAC,EAAE;QACtB,0BAA0B,CAAC,KAAK,CAAC,EAAE;YACjC,SAAS,CAAC,EAAE,MAAM,CAAC;YACnB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,GAAG,CAAC,EAAE,IAAI,CAAC;SACZ,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;KACtB,CAAC;IACF,aAAa,CAAC,EAAE,oBAAoB,CAAC;IACrC,iBAAiB,CAAC,EAAE,wBAAwB,CAAC;IAC7C,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CACtC,8CAs+IP"}
|
package/dist/routes/issues.js
CHANGED
|
@@ -389,6 +389,8 @@ function queueResolvedInteractionContinuationWakeup(input) {
|
|
|
389
389
|
return;
|
|
390
390
|
if (!input.issue.assigneeAgentId || isClosedIssueStatus(input.issue.status))
|
|
391
391
|
return;
|
|
392
|
+
const forceFreshSession = input.forceFreshSession === true;
|
|
393
|
+
const workspaceRefreshReason = readNonEmptyString(input.workspaceRefreshReason);
|
|
392
394
|
void input.heartbeat.wakeup(input.issue.assigneeAgentId, {
|
|
393
395
|
source: "automation",
|
|
394
396
|
triggerDetail: "system",
|
|
@@ -414,6 +416,8 @@ function queueResolvedInteractionContinuationWakeup(input) {
|
|
|
414
416
|
sourceRunId: input.interaction.sourceRunId ?? null,
|
|
415
417
|
wakeReason: "issue_commented",
|
|
416
418
|
source: input.source,
|
|
419
|
+
...(forceFreshSession ? { forceFreshSession: true } : {}),
|
|
420
|
+
...(workspaceRefreshReason ? { workspaceRefreshReason } : {}),
|
|
417
421
|
},
|
|
418
422
|
}).catch((err) => logger.warn({
|
|
419
423
|
err,
|
|
@@ -545,6 +549,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
545
549
|
const workProductsSvc = workProductService(db);
|
|
546
550
|
const documentsSvc = documentService(db);
|
|
547
551
|
const issueReferencesSvc = issueReferenceService(db);
|
|
552
|
+
const issueThreadInteractionsSvc = issueThreadInteractionService(db);
|
|
548
553
|
const routinesSvc = routineService(db, {
|
|
549
554
|
pluginWorkerManager: opts.pluginWorkerManager,
|
|
550
555
|
});
|
|
@@ -556,6 +561,128 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
556
561
|
};
|
|
557
562
|
const feedbackExportService = opts?.feedbackExportService;
|
|
558
563
|
const environmentsSvc = environmentService(db);
|
|
564
|
+
async function classifySourceRecoveryRevalidation(input) {
|
|
565
|
+
const { issue } = input;
|
|
566
|
+
if (issue.status === "done" || issue.status === "cancelled") {
|
|
567
|
+
return `Recovery action became stale because the source issue reached ${issue.status}.`;
|
|
568
|
+
}
|
|
569
|
+
if (input.blockedToTodoRecovery === true) {
|
|
570
|
+
return "Recovery action became stale because the source issue was manually moved from blocked to todo.";
|
|
571
|
+
}
|
|
572
|
+
if (input.trigger === "read_projection")
|
|
573
|
+
return null;
|
|
574
|
+
if (input.trigger === "comment" &&
|
|
575
|
+
input.resumeRequested !== true &&
|
|
576
|
+
input.reopened !== true &&
|
|
577
|
+
input.statusChanged !== true) {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
const durableSourceChange = input.statusChanged === true ||
|
|
581
|
+
input.assigneeChanged === true ||
|
|
582
|
+
input.blockersChanged === true ||
|
|
583
|
+
input.executionPolicyChanged === true ||
|
|
584
|
+
input.monitorChanged === true ||
|
|
585
|
+
input.documentChanged === true ||
|
|
586
|
+
input.workProductChanged === true ||
|
|
587
|
+
input.resumeRequested === true ||
|
|
588
|
+
input.reopened === true;
|
|
589
|
+
if (!durableSourceChange)
|
|
590
|
+
return null;
|
|
591
|
+
if (issue.status === "blocked") {
|
|
592
|
+
const readiness = await svc.getDependencyReadiness(issue.id);
|
|
593
|
+
if (readiness.unresolvedBlockerCount > 0) {
|
|
594
|
+
return "Recovery action became stale because the source issue now has unresolved first-class blockers.";
|
|
595
|
+
}
|
|
596
|
+
return null;
|
|
597
|
+
}
|
|
598
|
+
if (issue.assigneeUserId && issue.status !== "done" && issue.status !== "cancelled") {
|
|
599
|
+
return "Recovery action became stale because the source issue now has a human owner.";
|
|
600
|
+
}
|
|
601
|
+
if ((issue.status === "todo" || issue.status === "in_progress") && issue.assigneeAgentId) {
|
|
602
|
+
return `Recovery action became stale because the source issue is ${issue.status} with an agent owner.`;
|
|
603
|
+
}
|
|
604
|
+
if (issue.status === "in_review") {
|
|
605
|
+
const executionState = parseIssueExecutionState(issue.executionState);
|
|
606
|
+
const participant = executionState?.status === "pending" ? executionState.currentParticipant : null;
|
|
607
|
+
if ((participant?.type === "agent" && readNonEmptyString(participant.agentId)) ||
|
|
608
|
+
(participant?.type === "user" && readNonEmptyString(participant.userId))) {
|
|
609
|
+
return "Recovery action became stale because the source issue now has a typed review participant.";
|
|
610
|
+
}
|
|
611
|
+
const interactions = await issueThreadInteractionsSvc.listForIssue(issue.id);
|
|
612
|
+
if (interactions.some((interaction) => interaction.status === "pending")) {
|
|
613
|
+
return "Recovery action became stale because the source issue now has a pending issue interaction.";
|
|
614
|
+
}
|
|
615
|
+
const approvals = await issueApprovalsSvc.listApprovalsForIssue(issue.id);
|
|
616
|
+
if (approvals.some((approval) => approval.status === "pending" || approval.status === "revision_requested")) {
|
|
617
|
+
return "Recovery action became stale because the source issue now has a pending approval.";
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const monitor = summarizeIssueMonitor(issue, normalizeIssueExecutionPolicy(issue.executionPolicy ?? null));
|
|
621
|
+
if (monitor.nextCheckAt && Date.parse(monitor.nextCheckAt) > Date.now()) {
|
|
622
|
+
return "Recovery action became stale because the source issue now has a scheduled monitor.";
|
|
623
|
+
}
|
|
624
|
+
return null;
|
|
625
|
+
}
|
|
626
|
+
async function revalidateActiveSourceRecovery(input) {
|
|
627
|
+
const activeRecoveryAction = input.activeRecoveryAction === undefined
|
|
628
|
+
? await recoveryActionsSvc.getActiveForIssue(input.issue.companyId, input.issue.id)
|
|
629
|
+
: input.activeRecoveryAction;
|
|
630
|
+
if (!activeRecoveryAction)
|
|
631
|
+
return null;
|
|
632
|
+
const resolutionNote = await classifySourceRecoveryRevalidation(input);
|
|
633
|
+
if (!resolutionNote)
|
|
634
|
+
return activeRecoveryAction;
|
|
635
|
+
const resolved = await recoveryActionsSvc.resolveActiveForIssue({
|
|
636
|
+
companyId: input.issue.companyId,
|
|
637
|
+
sourceIssueId: input.issue.id,
|
|
638
|
+
actionId: activeRecoveryAction.id,
|
|
639
|
+
status: "cancelled",
|
|
640
|
+
outcome: "cancelled",
|
|
641
|
+
resolutionNote,
|
|
642
|
+
});
|
|
643
|
+
if (!resolved)
|
|
644
|
+
return activeRecoveryAction;
|
|
645
|
+
const actor = input.actor;
|
|
646
|
+
await logActivity(db, {
|
|
647
|
+
companyId: input.issue.companyId,
|
|
648
|
+
actorType: actor?.actorType ?? "system",
|
|
649
|
+
actorId: actor?.actorId ?? "system",
|
|
650
|
+
agentId: actor?.agentId ?? null,
|
|
651
|
+
runId: actor?.runId ?? null,
|
|
652
|
+
action: "issue.recovery_action_resolved",
|
|
653
|
+
entityType: "issue",
|
|
654
|
+
entityId: input.issue.id,
|
|
655
|
+
details: {
|
|
656
|
+
identifier: input.issue.identifier,
|
|
657
|
+
recoveryActionId: resolved.id,
|
|
658
|
+
recoveryActionStatus: resolved.status,
|
|
659
|
+
outcome: resolved.outcome,
|
|
660
|
+
sourceIssueStatus: input.issue.status,
|
|
661
|
+
resolutionNote: resolved.resolutionNote,
|
|
662
|
+
source: "source_revalidation",
|
|
663
|
+
trigger: input.trigger,
|
|
664
|
+
},
|
|
665
|
+
});
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
async function revalidateActiveSourceRecoveryForRead(input) {
|
|
669
|
+
try {
|
|
670
|
+
return await revalidateActiveSourceRecovery(input);
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
logger.warn({ err, issueId: input.issue.id, trigger: input.trigger }, "failed to revalidate recovery action during read projection");
|
|
674
|
+
return input.activeRecoveryAction ?? null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
async function revalidateActiveSourceRecoveryAfterCommittedWrite(input) {
|
|
678
|
+
try {
|
|
679
|
+
return await revalidateActiveSourceRecovery(input);
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
logger.warn({ err, issueId: input.issue.id, trigger: input.trigger }, "failed to revalidate recovery action after committed issue write");
|
|
683
|
+
return input.activeRecoveryAction ?? null;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
559
686
|
function withContentPath(attachment) {
|
|
560
687
|
return {
|
|
561
688
|
...attachment,
|
|
@@ -899,6 +1026,42 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
899
1026
|
});
|
|
900
1027
|
return false;
|
|
901
1028
|
}
|
|
1029
|
+
async function assertRecoveryActionAuthority(req, res, issue, activeRecoveryAction, input) {
|
|
1030
|
+
if (req.actor.type !== "agent")
|
|
1031
|
+
return true;
|
|
1032
|
+
if (!activeRecoveryAction)
|
|
1033
|
+
return true;
|
|
1034
|
+
const actorAgentId = req.actor.agentId;
|
|
1035
|
+
if (!actorAgentId) {
|
|
1036
|
+
res.status(403).json({ error: "Agent authentication required" });
|
|
1037
|
+
return false;
|
|
1038
|
+
}
|
|
1039
|
+
if (issue.assigneeAgentId === actorAgentId)
|
|
1040
|
+
return true;
|
|
1041
|
+
if (issue.assigneeAgentId &&
|
|
1042
|
+
await hasActiveCheckoutManagementOverride(actorAgentId, issue.companyId, issue.assigneeAgentId)) {
|
|
1043
|
+
return true;
|
|
1044
|
+
}
|
|
1045
|
+
if (activeRecoveryAction.ownerAgentId === actorAgentId)
|
|
1046
|
+
return true;
|
|
1047
|
+
if (activeRecoveryAction.ownerAgentId &&
|
|
1048
|
+
await hasActiveCheckoutManagementOverride(actorAgentId, issue.companyId, activeRecoveryAction.ownerAgentId)) {
|
|
1049
|
+
return true;
|
|
1050
|
+
}
|
|
1051
|
+
res.status(403).json({
|
|
1052
|
+
error: "Agent cannot resolve another owner's recovery action",
|
|
1053
|
+
details: {
|
|
1054
|
+
issueId: issue.id,
|
|
1055
|
+
recoveryActionId: activeRecoveryAction.id,
|
|
1056
|
+
actorAgentId,
|
|
1057
|
+
assigneeAgentId: issue.assigneeAgentId,
|
|
1058
|
+
recoveryOwnerAgentId: activeRecoveryAction.ownerAgentId,
|
|
1059
|
+
source: input.source,
|
|
1060
|
+
securityPrinciples: ["Least Privilege", "Complete Mediation", "Secure Defaults"],
|
|
1061
|
+
},
|
|
1062
|
+
});
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
902
1065
|
async function resolveActiveIssueRun(issue) {
|
|
903
1066
|
let runToInterrupt = issue.executionRunId ? await heartbeat.getRun(issue.executionRunId) : null;
|
|
904
1067
|
if ((!runToInterrupt || runToInterrupt.status !== "running") && issue.assigneeAgentId) {
|
|
@@ -1123,6 +1286,22 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1123
1286
|
listSuccessfulRunHandoffStates(db, companyId, issueIds),
|
|
1124
1287
|
recoveryActionsSvc.listActiveForIssues(companyId, issueIds),
|
|
1125
1288
|
]);
|
|
1289
|
+
const actor = getActorInfo(req);
|
|
1290
|
+
await Promise.all(result.map(async (issue) => {
|
|
1291
|
+
const activeRecoveryAction = recoveryActionByIssue.get(issue.id) ?? null;
|
|
1292
|
+
if (!activeRecoveryAction)
|
|
1293
|
+
return;
|
|
1294
|
+
const revalidated = await revalidateActiveSourceRecoveryForRead({
|
|
1295
|
+
issue,
|
|
1296
|
+
trigger: "read_projection",
|
|
1297
|
+
actor,
|
|
1298
|
+
activeRecoveryAction,
|
|
1299
|
+
});
|
|
1300
|
+
if (revalidated)
|
|
1301
|
+
recoveryActionByIssue.set(issue.id, revalidated);
|
|
1302
|
+
else
|
|
1303
|
+
recoveryActionByIssue.delete(issue.id);
|
|
1304
|
+
}));
|
|
1126
1305
|
res.json(result.map((issue) => ({
|
|
1127
1306
|
...issue,
|
|
1128
1307
|
successfulRunHandoff: handoffStates.get(issue.id) ?? null,
|
|
@@ -1246,6 +1425,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1246
1425
|
]);
|
|
1247
1426
|
const recoveryActionsByRelationIssue = await relationRecoveryActionMap(recoveryActionsSvc, issue.companyId, relations);
|
|
1248
1427
|
const relationsWithRecoveryActions = withRecoveryActionsOnRelationSummaries(relations, recoveryActionsByRelationIssue);
|
|
1428
|
+
const revalidatedActiveRecoveryAction = await revalidateActiveSourceRecoveryForRead({
|
|
1429
|
+
issue,
|
|
1430
|
+
trigger: "read_projection",
|
|
1431
|
+
actor: getActorInfo(req),
|
|
1432
|
+
activeRecoveryAction,
|
|
1433
|
+
});
|
|
1249
1434
|
res.json({
|
|
1250
1435
|
issue: {
|
|
1251
1436
|
id: issue.id,
|
|
@@ -1257,7 +1442,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1257
1442
|
...(blockerAttention ? { blockerAttention } : {}),
|
|
1258
1443
|
productivityReview,
|
|
1259
1444
|
scheduledRetry,
|
|
1260
|
-
activeRecoveryAction,
|
|
1445
|
+
activeRecoveryAction: revalidatedActiveRecoveryAction,
|
|
1261
1446
|
priority: issue.priority,
|
|
1262
1447
|
projectId: issue.projectId,
|
|
1263
1448
|
goalId: goal?.id ?? issue.goalId,
|
|
@@ -1342,6 +1527,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1342
1527
|
]);
|
|
1343
1528
|
const recoveryActionsByRelationIssue = await relationRecoveryActionMap(recoveryActionsSvc, issue.companyId, relations);
|
|
1344
1529
|
const relationsWithRecoveryActions = withRecoveryActionsOnRelationSummaries(relations, recoveryActionsByRelationIssue);
|
|
1530
|
+
const revalidatedActiveRecoveryAction = await revalidateActiveSourceRecoveryForRead({
|
|
1531
|
+
issue,
|
|
1532
|
+
trigger: "read_projection",
|
|
1533
|
+
actor: getActorInfo(req),
|
|
1534
|
+
activeRecoveryAction,
|
|
1535
|
+
});
|
|
1345
1536
|
const mentionedProjects = mentionedProjectIds.length > 0
|
|
1346
1537
|
? await projectsSvc.listByIds(issue.companyId, mentionedProjectIds)
|
|
1347
1538
|
: [];
|
|
@@ -1357,7 +1548,7 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1357
1548
|
productivityReview,
|
|
1358
1549
|
successfulRunHandoff: successfulRunHandoffStates.get(issue.id) ?? null,
|
|
1359
1550
|
scheduledRetry,
|
|
1360
|
-
activeRecoveryAction,
|
|
1551
|
+
activeRecoveryAction: revalidatedActiveRecoveryAction,
|
|
1361
1552
|
blockedBy: relationsWithRecoveryActions.blockedBy,
|
|
1362
1553
|
blocks: relationsWithRecoveryActions.blocks,
|
|
1363
1554
|
relatedWork: referenceSummary,
|
|
@@ -1378,7 +1569,11 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1378
1569
|
return;
|
|
1379
1570
|
}
|
|
1380
1571
|
assertCompanyAccess(req, issue.companyId);
|
|
1381
|
-
const active = await
|
|
1572
|
+
const active = await revalidateActiveSourceRecoveryForRead({
|
|
1573
|
+
issue,
|
|
1574
|
+
trigger: "read_projection",
|
|
1575
|
+
actor: getActorInfo(req),
|
|
1576
|
+
});
|
|
1382
1577
|
res.json({
|
|
1383
1578
|
active,
|
|
1384
1579
|
actions: active ? [active] : [],
|
|
@@ -1394,6 +1589,10 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1394
1589
|
assertCompanyAccess(req, existing.companyId);
|
|
1395
1590
|
if (!(await assertAgentIssueMutationAllowed(req, res, existing)))
|
|
1396
1591
|
return;
|
|
1592
|
+
const activeRecoveryAction = await recoveryActionsSvc.getActiveForIssue(existing.companyId, existing.id);
|
|
1593
|
+
if (!(await assertRecoveryActionAuthority(req, res, existing, activeRecoveryAction, { source: "recovery_action_resolution" }))) {
|
|
1594
|
+
return;
|
|
1595
|
+
}
|
|
1397
1596
|
const { actionId, outcome, sourceIssueStatus, resolutionNote } = req.body;
|
|
1398
1597
|
if (outcome === "false_positive" || outcome === "cancelled") {
|
|
1399
1598
|
assertBoard(req);
|
|
@@ -1481,6 +1680,29 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1481
1680
|
resolutionNote: result.recoveryAction.resolutionNote,
|
|
1482
1681
|
},
|
|
1483
1682
|
});
|
|
1683
|
+
if (sourceIssueStatus === "todo" &&
|
|
1684
|
+
existing.status !== result.issue.status &&
|
|
1685
|
+
result.issue.assigneeAgentId) {
|
|
1686
|
+
void heartbeat.wakeup(result.issue.assigneeAgentId, {
|
|
1687
|
+
source: "automation",
|
|
1688
|
+
triggerDetail: "system",
|
|
1689
|
+
reason: "issue_recovery_action_restored",
|
|
1690
|
+
payload: {
|
|
1691
|
+
issueId: result.issue.id,
|
|
1692
|
+
recoveryActionId: result.recoveryAction.id,
|
|
1693
|
+
mutation: "recovery_action_resolution",
|
|
1694
|
+
},
|
|
1695
|
+
requestedByActorType: actor.actorType,
|
|
1696
|
+
requestedByActorId: actor.actorId,
|
|
1697
|
+
contextSnapshot: {
|
|
1698
|
+
issueId: result.issue.id,
|
|
1699
|
+
taskId: result.issue.id,
|
|
1700
|
+
wakeReason: "issue_recovery_action_restored",
|
|
1701
|
+
source: "issue.recovery_action_resolution",
|
|
1702
|
+
recoveryActionId: result.recoveryAction.id,
|
|
1703
|
+
},
|
|
1704
|
+
}).catch((err) => logger.warn({ err, issueId: result.issue.id, agentId: result.issue.assigneeAgentId }, "failed to wake agent after recovery action restored issue"));
|
|
1705
|
+
}
|
|
1484
1706
|
res.json({
|
|
1485
1707
|
issue: {
|
|
1486
1708
|
...result.issue,
|
|
@@ -1608,6 +1830,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1608
1830
|
source: "issue.document_updated",
|
|
1609
1831
|
});
|
|
1610
1832
|
}
|
|
1833
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
1834
|
+
issue,
|
|
1835
|
+
trigger: "document",
|
|
1836
|
+
actor,
|
|
1837
|
+
documentChanged: true,
|
|
1838
|
+
});
|
|
1611
1839
|
res.status(result.created ? 201 : 200).json(doc);
|
|
1612
1840
|
});
|
|
1613
1841
|
router.post("/issues/:id/documents/:key/lock", async (req, res) => {
|
|
@@ -1775,6 +2003,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1775
2003
|
actor,
|
|
1776
2004
|
source: "issue.document_restored",
|
|
1777
2005
|
});
|
|
2006
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
2007
|
+
issue,
|
|
2008
|
+
trigger: "document",
|
|
2009
|
+
actor,
|
|
2010
|
+
documentChanged: true,
|
|
2011
|
+
});
|
|
1778
2012
|
res.json(result.document);
|
|
1779
2013
|
});
|
|
1780
2014
|
router.delete("/issues/:id/documents/:key", async (req, res) => {
|
|
@@ -1839,6 +2073,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1839
2073
|
actor,
|
|
1840
2074
|
source: "issue.document_deleted",
|
|
1841
2075
|
});
|
|
2076
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
2077
|
+
issue,
|
|
2078
|
+
trigger: "document",
|
|
2079
|
+
actor,
|
|
2080
|
+
documentChanged: true,
|
|
2081
|
+
});
|
|
1842
2082
|
res.json({ ok: true });
|
|
1843
2083
|
});
|
|
1844
2084
|
router.post("/issues/:id/work-products", validate(createIssueWorkProductSchema), async (req, res) => {
|
|
@@ -1871,6 +2111,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1871
2111
|
entityId: issue.id,
|
|
1872
2112
|
details: { workProductId: product.id, type: product.type, provider: product.provider },
|
|
1873
2113
|
});
|
|
2114
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
2115
|
+
issue,
|
|
2116
|
+
trigger: "work_product",
|
|
2117
|
+
actor,
|
|
2118
|
+
workProductChanged: true,
|
|
2119
|
+
});
|
|
1874
2120
|
res.status(201).json(product);
|
|
1875
2121
|
});
|
|
1876
2122
|
router.patch("/work-products/:id", validate(updateIssueWorkProductSchema), async (req, res) => {
|
|
@@ -1905,6 +2151,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1905
2151
|
entityId: existing.issueId,
|
|
1906
2152
|
details: { workProductId: product.id, changedKeys: Object.keys(req.body).sort() },
|
|
1907
2153
|
});
|
|
2154
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
2155
|
+
issue,
|
|
2156
|
+
trigger: "work_product",
|
|
2157
|
+
actor,
|
|
2158
|
+
workProductChanged: true,
|
|
2159
|
+
});
|
|
1908
2160
|
res.json(product);
|
|
1909
2161
|
});
|
|
1910
2162
|
router.delete("/work-products/:id", async (req, res) => {
|
|
@@ -1939,6 +2191,12 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
1939
2191
|
entityId: existing.issueId,
|
|
1940
2192
|
details: { workProductId: removed.id, type: removed.type },
|
|
1941
2193
|
});
|
|
2194
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
2195
|
+
issue,
|
|
2196
|
+
trigger: "work_product",
|
|
2197
|
+
actor,
|
|
2198
|
+
workProductChanged: true,
|
|
2199
|
+
});
|
|
1942
2200
|
res.json(removed);
|
|
1943
2201
|
});
|
|
1944
2202
|
router.post("/issues/:id/read", async (req, res) => {
|
|
@@ -2376,6 +2634,19 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
2376
2634
|
await assertIssueEnvironmentSelection(existing.companyId, updateFields.executionWorkspaceSettings?.environmentId);
|
|
2377
2635
|
const requestedAssigneeAgentId = normalizedAssigneeAgentId === undefined ? existing.assigneeAgentId : normalizedAssigneeAgentId;
|
|
2378
2636
|
const explicitMoveToTodoRequested = reopenRequested || resumeRequested === true;
|
|
2637
|
+
const recoveryRelevantSourceMutationRequested = req.body.status !== undefined ||
|
|
2638
|
+
normalizedAssigneeAgentId !== undefined ||
|
|
2639
|
+
req.body.assigneeUserId !== undefined ||
|
|
2640
|
+
Array.isArray(req.body.blockedByIssueIds) ||
|
|
2641
|
+
req.body.executionPolicy !== undefined ||
|
|
2642
|
+
explicitMoveToTodoRequested;
|
|
2643
|
+
const activeRecoveryActionBeforeUpdate = recoveryRelevantSourceMutationRequested
|
|
2644
|
+
? await recoveryActionsSvc.getActiveForIssue(existing.companyId, existing.id)
|
|
2645
|
+
: null;
|
|
2646
|
+
if (recoveryRelevantSourceMutationRequested &&
|
|
2647
|
+
!(await assertRecoveryActionAuthority(req, res, existing, activeRecoveryActionBeforeUpdate, { source: "issue_update" }))) {
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2379
2650
|
const effectiveMoveToTodoRequested = explicitMoveToTodoRequested ||
|
|
2380
2651
|
(!!commentBody &&
|
|
2381
2652
|
shouldImplicitlyMoveCommentedIssueToTodo({
|
|
@@ -2663,6 +2934,30 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
2663
2934
|
previous.status !== undefined &&
|
|
2664
2935
|
issue.status === "todo";
|
|
2665
2936
|
const reopenFromStatus = reopened ? existing.status : null;
|
|
2937
|
+
const statusChangedFromBlockedToTodo = existing.status === "blocked" &&
|
|
2938
|
+
issue.status === "todo" &&
|
|
2939
|
+
(req.body.status !== undefined || reopened);
|
|
2940
|
+
const revalidatedRecoveryAction = await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
2941
|
+
issue,
|
|
2942
|
+
trigger: "issue_update",
|
|
2943
|
+
actor,
|
|
2944
|
+
activeRecoveryAction: activeRecoveryActionBeforeUpdate ?? undefined,
|
|
2945
|
+
statusChanged: existing.status !== issue.status,
|
|
2946
|
+
assigneeChanged: existing.assigneeAgentId !== issue.assigneeAgentId ||
|
|
2947
|
+
existing.assigneeUserId !== issue.assigneeUserId,
|
|
2948
|
+
blockersChanged: Array.isArray(req.body.blockedByIssueIds),
|
|
2949
|
+
executionPolicyChanged: req.body.executionPolicy !== undefined,
|
|
2950
|
+
monitorChanged,
|
|
2951
|
+
resumeRequested: resumeRequested === true,
|
|
2952
|
+
reopened,
|
|
2953
|
+
blockedToTodoRecovery: statusChangedFromBlockedToTodo,
|
|
2954
|
+
});
|
|
2955
|
+
if (activeRecoveryActionBeforeUpdate && !revalidatedRecoveryAction) {
|
|
2956
|
+
issueResponse = {
|
|
2957
|
+
...issueResponse,
|
|
2958
|
+
activeRecoveryAction: null,
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2666
2961
|
await logActivity(db, {
|
|
2667
2962
|
companyId: issue.companyId,
|
|
2668
2963
|
actorType: actor.actorType,
|
|
@@ -2913,9 +3208,6 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
2913
3208
|
const statusChangedFromBacklog = existing.status === "backlog" &&
|
|
2914
3209
|
issue.status !== "backlog" &&
|
|
2915
3210
|
req.body.status !== undefined;
|
|
2916
|
-
const statusChangedFromBlockedToTodo = existing.status === "blocked" &&
|
|
2917
|
-
issue.status === "todo" &&
|
|
2918
|
-
(req.body.status !== undefined || reopened);
|
|
2919
3211
|
const statusChangedFromClosedToTodo = isClosedIssueStatus(existing.status) &&
|
|
2920
3212
|
issue.status === "todo" &&
|
|
2921
3213
|
req.body.status !== undefined;
|
|
@@ -3450,12 +3742,17 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
3450
3742
|
requestedByActorId: actor.actorId,
|
|
3451
3743
|
});
|
|
3452
3744
|
}
|
|
3745
|
+
const acceptedPlanConfirmation = interaction.kind === "request_confirmation" &&
|
|
3746
|
+
interaction.status === "accepted" &&
|
|
3747
|
+
issue.workMode === "planning";
|
|
3453
3748
|
queueResolvedInteractionContinuationWakeup({
|
|
3454
3749
|
heartbeat,
|
|
3455
3750
|
issue: continuationWakeIssue,
|
|
3456
3751
|
interaction,
|
|
3457
3752
|
actor,
|
|
3458
3753
|
source: "issue.interaction.accept",
|
|
3754
|
+
forceFreshSession: acceptedPlanConfirmation,
|
|
3755
|
+
workspaceRefreshReason: acceptedPlanConfirmation ? "accepted_plan_confirmation" : null,
|
|
3459
3756
|
});
|
|
3460
3757
|
res.json(interaction);
|
|
3461
3758
|
});
|
|
@@ -3889,6 +4186,15 @@ export function issueRoutes(db, storage, opts = {}) {
|
|
|
3889
4186
|
actor,
|
|
3890
4187
|
source: "issue.comment",
|
|
3891
4188
|
});
|
|
4189
|
+
await revalidateActiveSourceRecoveryAfterCommittedWrite({
|
|
4190
|
+
issue: currentIssue,
|
|
4191
|
+
trigger: "comment",
|
|
4192
|
+
actor,
|
|
4193
|
+
statusChanged: reopened,
|
|
4194
|
+
resumeRequested: resumeRequested === true,
|
|
4195
|
+
reopened,
|
|
4196
|
+
blockedToTodoRecovery: reopened && reopenFromStatus === "blocked" && currentIssue.status === "todo",
|
|
4197
|
+
});
|
|
3892
4198
|
// Merge all wakeups from this comment into one enqueue per agent to avoid duplicate runs.
|
|
3893
4199
|
void (async () => {
|
|
3894
4200
|
const wakeups = new Map();
|