@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.
Files changed (108) hide show
  1. package/dist/redaction.d.ts.map +1 -1
  2. package/dist/redaction.js +31 -4
  3. package/dist/redaction.js.map +1 -1
  4. package/dist/routes/issues.d.ts.map +1 -1
  5. package/dist/routes/issues.js +312 -6
  6. package/dist/routes/issues.js.map +1 -1
  7. package/dist/services/heartbeat.d.ts +3 -0
  8. package/dist/services/heartbeat.d.ts.map +1 -1
  9. package/dist/services/heartbeat.js +89 -6
  10. package/dist/services/heartbeat.js.map +1 -1
  11. package/dist/services/issues.d.ts.map +1 -1
  12. package/dist/services/issues.js +11 -1
  13. package/dist/services/issues.js.map +1 -1
  14. package/dist/services/plugin-local-folders.d.ts.map +1 -1
  15. package/dist/services/plugin-local-folders.js +6 -2
  16. package/dist/services/plugin-local-folders.js.map +1 -1
  17. package/dist/services/plugin-managed-routines.d.ts +1 -0
  18. package/dist/services/plugin-managed-routines.d.ts.map +1 -1
  19. package/dist/services/projects.d.ts +1 -1
  20. package/dist/services/recovery/service.d.ts +1 -0
  21. package/dist/services/recovery/service.d.ts.map +1 -1
  22. package/dist/services/recovery/service.js +333 -5
  23. package/dist/services/recovery/service.js.map +1 -1
  24. package/dist/services/routines.d.ts +4 -0
  25. package/dist/services/routines.d.ts.map +1 -1
  26. package/dist/services/routines.js +52 -5
  27. package/dist/services/routines.js.map +1 -1
  28. package/dist/services/secrets.d.ts +6 -2
  29. package/dist/services/secrets.d.ts.map +1 -1
  30. package/dist/services/secrets.js +60 -17
  31. package/dist/services/secrets.js.map +1 -1
  32. package/package.json +15 -15
  33. package/ui-dist/assets/{_basePickBy-CyZigXxL.js → _basePickBy-CqWMcapU.js} +1 -1
  34. package/ui-dist/assets/{_baseUniq-DgESSGjb.js → _baseUniq-CtXUWMY8.js} +1 -1
  35. package/ui-dist/assets/{arc-fdje2cl0.js → arc-C1XjRaNf.js} +1 -1
  36. package/ui-dist/assets/{architectureDiagram-VXUJARFQ-BKh2ybZw.js → architectureDiagram-VXUJARFQ-CCeWA90x.js} +1 -1
  37. package/ui-dist/assets/{blockDiagram-VD42YOAC-hZzVAtlS.js → blockDiagram-VD42YOAC-DfCY6om4.js} +1 -1
  38. package/ui-dist/assets/{c4Diagram-YG6GDRKO-DqGOUNLx.js → c4Diagram-YG6GDRKO-BGrul0up.js} +1 -1
  39. package/ui-dist/assets/channel-ChYSBygj.js +1 -0
  40. package/ui-dist/assets/{chunk-4BX2VUAB-DkFfF4Vw.js → chunk-4BX2VUAB-d9TTQhTl.js} +1 -1
  41. package/ui-dist/assets/{chunk-55IACEB6-D1I9MYTq.js → chunk-55IACEB6-C7jwF-m3.js} +1 -1
  42. package/ui-dist/assets/{chunk-B4BG7PRW-3DDKi6PV.js → chunk-B4BG7PRW-lsfcegaU.js} +1 -1
  43. package/ui-dist/assets/{chunk-DI55MBZ5-DFC0ufTR.js → chunk-DI55MBZ5-B6Sn2B-l.js} +1 -1
  44. package/ui-dist/assets/{chunk-FMBD7UC4-Bcq3FqYg.js → chunk-FMBD7UC4-d-DUqf0y.js} +1 -1
  45. package/ui-dist/assets/{chunk-QN33PNHL-AxoPJ4dk.js → chunk-QN33PNHL-CZF3jISb.js} +1 -1
  46. package/ui-dist/assets/{chunk-QZHKN3VN-C6usVPcB.js → chunk-QZHKN3VN-DsaPyy1F.js} +1 -1
  47. package/ui-dist/assets/{chunk-TZMSLE5B-BXd6So6Q.js → chunk-TZMSLE5B-BmiOAEkf.js} +1 -1
  48. package/ui-dist/assets/classDiagram-2ON5EDUG-C7XPxf1M.js +1 -0
  49. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-C7XPxf1M.js +1 -0
  50. package/ui-dist/assets/clone-BjVNMY9x.js +1 -0
  51. package/ui-dist/assets/{cose-bilkent-S5V4N54A-QUlh4iz_.js → cose-bilkent-S5V4N54A-Z7JQMhqR.js} +1 -1
  52. package/ui-dist/assets/{dagre-6UL2VRFP-DeWl3Bnh.js → dagre-6UL2VRFP-BOezmgTi.js} +1 -1
  53. package/ui-dist/assets/{diagram-PSM6KHXK-BOgm0Y0v.js → diagram-PSM6KHXK-Bi3xQylZ.js} +1 -1
  54. package/ui-dist/assets/{diagram-QEK2KX5R--sjcGOSR.js → diagram-QEK2KX5R-D5xUfRKy.js} +1 -1
  55. package/ui-dist/assets/{diagram-S2PKOQOG-swytmQ59.js → diagram-S2PKOQOG-Cs742jKm.js} +1 -1
  56. package/ui-dist/assets/{erDiagram-Q2GNP2WA-AG9oeKf_.js → erDiagram-Q2GNP2WA-D4voueCw.js} +1 -1
  57. package/ui-dist/assets/{flowDiagram-NV44I4VS-DjfsYR72.js → flowDiagram-NV44I4VS-CU8ncxLE.js} +1 -1
  58. package/ui-dist/assets/{ganttDiagram-JELNMOA3-CzkYgrob.js → ganttDiagram-JELNMOA3-D_ppL0-H.js} +1 -1
  59. package/ui-dist/assets/{gitGraphDiagram-V2S2FVAM-CYdF_fRA.js → gitGraphDiagram-V2S2FVAM-DbcI4Yg7.js} +1 -1
  60. package/ui-dist/assets/{graph-C9CpmUHW.js → graph-C6Xbwedt.js} +1 -1
  61. package/ui-dist/assets/{index-Be5cmSx7.js → index-2xIzoXjY.js} +1 -1
  62. package/ui-dist/assets/{index-BwheAg6e.js → index-7jgl9bxD.js} +1 -1
  63. package/ui-dist/assets/{index-CBTbN9sK.js → index-BMoirXsY.js} +1 -1
  64. package/ui-dist/assets/{index-CN8ht-3V.js → index-Byew81VK.js} +1 -1
  65. package/ui-dist/assets/{index-wwJmI27u.js → index-C6ZkE1Ht.js} +1 -1
  66. package/ui-dist/assets/{index-CPMdaECG.js → index-C9271z7J.js} +1 -1
  67. package/ui-dist/assets/{index-CXBisfFe.js → index-CKA2c7F8.js} +1 -1
  68. package/ui-dist/assets/{index-NFt_e-il.js → index-CRlZz7hL.js} +1 -1
  69. package/ui-dist/assets/{index-BNyx3LoE.js → index-CbwP7jIY.js} +1 -1
  70. package/ui-dist/assets/{index-DslHQrSc.js → index-CqTES4fn.js} +1 -1
  71. package/ui-dist/assets/{index-n4B5qB20.js → index-D9xQ3bUB.js} +1 -1
  72. package/ui-dist/assets/{index-C3ztpdsf.js → index-DSAXt971.js} +1 -1
  73. package/ui-dist/assets/{index-DkTllr0Y.js → index-DWSXKoZc.js} +1 -1
  74. package/ui-dist/assets/{index-RIwOURie.js → index-Dbt26HjH.js} +1 -1
  75. package/ui-dist/assets/{index-BU-ccYxt.js → index-Dftcrj43.js} +1 -1
  76. package/ui-dist/assets/{index-Cp6wXhn6.js → index-DgQjhgdA.js} +1 -1
  77. package/ui-dist/assets/{index-BdzhusxB.js → index-DlWHuNOg.js} +1 -1
  78. package/ui-dist/assets/{index-0J3lkVvW.js → index-DnZdiDv2.js} +1 -1
  79. package/ui-dist/assets/{index-86eXFEVV.js → index-DqHXftYp.js} +1 -1
  80. package/ui-dist/assets/{index-DIZJVs_j.js → index-DvA5N6JH.js} +1 -1
  81. package/ui-dist/assets/{index-Beg6-tI6.js → index-Dz05lp4r.js} +1 -1
  82. package/ui-dist/assets/{index-P78vSPeP.js → index-STXLWsTQ.js} +156 -156
  83. package/ui-dist/assets/{index-DUzr_Pqd.js → index-U9vA4wEM.js} +1 -1
  84. package/ui-dist/assets/index-y1iMp6V0.css +1 -0
  85. package/ui-dist/assets/{infoDiagram-HS3SLOUP-DbTu1ikC.js → infoDiagram-HS3SLOUP-DKJbdqb7.js} +1 -1
  86. package/ui-dist/assets/{journeyDiagram-XKPGCS4Q-Dr_3cCaU.js → journeyDiagram-XKPGCS4Q-CWmIi8cd.js} +1 -1
  87. package/ui-dist/assets/{kanban-definition-3W4ZIXB7-D2VqYXi6.js → kanban-definition-3W4ZIXB7-B3q1bO5h.js} +1 -1
  88. package/ui-dist/assets/{layout-u3iqUgQ_.js → layout-D5BmO8lB.js} +1 -1
  89. package/ui-dist/assets/{linear-BZiN27c7.js → linear-DyH8N0l1.js} +1 -1
  90. package/ui-dist/assets/{mermaid.core-Cdqrn86d.js → mermaid.core-BbPNdvQf.js} +4 -4
  91. package/ui-dist/assets/{mindmap-definition-VGOIOE7T-DGcs3SZu.js → mindmap-definition-VGOIOE7T-CoaXwazv.js} +1 -1
  92. package/ui-dist/assets/{pieDiagram-ADFJNKIX-CUokE42u.js → pieDiagram-ADFJNKIX-BGfmYX-N.js} +1 -1
  93. package/ui-dist/assets/{quadrantDiagram-AYHSOK5B--ZywPGaP.js → quadrantDiagram-AYHSOK5B-DAyRfIdk.js} +1 -1
  94. package/ui-dist/assets/{requirementDiagram-UZGBJVZJ-Dfij5FW1.js → requirementDiagram-UZGBJVZJ-DaLL5SEP.js} +1 -1
  95. package/ui-dist/assets/{sankeyDiagram-TZEHDZUN-BJA1WqbQ.js → sankeyDiagram-TZEHDZUN-D8Dh0Tnc.js} +1 -1
  96. package/ui-dist/assets/{sequenceDiagram-WL72ISMW-_qX4RaDf.js → sequenceDiagram-WL72ISMW-Cy6bb3Jm.js} +1 -1
  97. package/ui-dist/assets/{stateDiagram-FKZM4ZOC-Cgt2GXml.js → stateDiagram-FKZM4ZOC-iatgt-rZ.js} +1 -1
  98. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-B-gV_soZ.js +1 -0
  99. package/ui-dist/assets/{timeline-definition-IT6M3QCI-DuRPO8CY.js → timeline-definition-IT6M3QCI-oMHUv8mk.js} +1 -1
  100. package/ui-dist/assets/{treemap-GDKQZRPO-D9I4Kr4d.js → treemap-GDKQZRPO-BKN_7nXQ.js} +1 -1
  101. package/ui-dist/assets/{xychartDiagram-PRI3JC2R-COU-zxX4.js → xychartDiagram-PRI3JC2R-D4bKshmY.js} +1 -1
  102. package/ui-dist/index.html +2 -2
  103. package/ui-dist/assets/channel-D11NtE_b.js +0 -1
  104. package/ui-dist/assets/classDiagram-2ON5EDUG-Ca0nuZ4I.js +0 -1
  105. package/ui-dist/assets/classDiagram-v2-WZHVMYZB-Ca0nuZ4I.js +0 -1
  106. package/ui-dist/assets/clone-pBvhn5z6.js +0 -1
  107. package/ui-dist/assets/index-BiweVGJL.css +0 -1
  108. package/ui-dist/assets/stateDiagram-v2-4FDKWEC3-CvGN1v50.js +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AA2CrD,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,CAOzD"}
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 SECRET_PAYLOAD_KEY_RE = /(api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)/i;
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 = /^-{1,2}(?:api[-_]?key|(?:access[-_]?|auth[-_]?)?token|token|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)$/i;
7
- const JSON_SECRET_FIELD_TEXT_RE = /((?:"|')?(?:api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)(?:"|')?\s*:\s*(?:"|'))[^"'`\r\n]+((?:"|'))/gi;
8
- const ESCAPED_JSON_SECRET_FIELD_TEXT_RE = /((?:\\")?(?:api[-_]?key|access[-_]?token|auth(?:_?token)?|authorization|bearer|secret|passwd|password|credential|jwt|private[-_]?key|cookie|connectionstring)(?:\\")?\s*:\s*(?:\\"))[^\\\r\n]+((?:\\"))/gi;
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);
@@ -1 +1 @@
1
- {"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
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;AAShF,KAAK,oBAAoB,GAAG;IAC1B,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CAAC;CACtF,CAAC;AA2qBF,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,8CA8lIP"}
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"}
@@ -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 recoveryActionsSvc.getActiveForIssue(issue.companyId, issue.id);
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();