@runfusion/fusion 0.76.0-beta.2 → 0.76.0-beta.3

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 (144) hide show
  1. package/dist/bin.js +323 -59
  2. package/dist/child-process-worker.js +274 -42
  3. package/dist/client/.vite/manifest.json +273 -273
  4. package/dist/client/assets/{AgentDetailView-gak4EWTI.js → AgentDetailView-T767kKTg.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-DP1QbZL-.js → AgentPermissionPolicyEditor--u9jyUf2.js} +1 -1
  6. package/dist/client/assets/{AgentsView-DAMRTKzI.js → AgentsView-6Ifu7qDr.js} +5 -5
  7. package/dist/client/assets/{ChatView-iE-2gf5P.js → ChatView-BV3lixQi.js} +1 -1
  8. package/dist/client/assets/{CommandCenter-B6RnkLYm.js → CommandCenter-CSRYoveB.js} +1 -1
  9. package/dist/client/assets/{DevServerView-DcFL_LzK.js → DevServerView-B9So_oYx.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-CtjqrYoO.js → DirectoryPicker-BhyDL_2m.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-BkAe2S4H.js → DocumentsView-pbERA7O-.js} +1 -1
  12. package/dist/client/assets/{EvalsView-CbGA9dMn.js → EvalsView-DJQ4ltCg.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-CMgO_S8T.js → ExperimentalAgentOnboardingModal-BwJuzkzB.js} +1 -1
  14. package/dist/client/assets/{GoalsView-_3P9J5sf.js → GoalsView-B6X5_0Ve.js} +1 -1
  15. package/dist/client/assets/InsightsView-CJgI0J7S.js +11 -0
  16. package/dist/client/assets/{InsightsView-Gn5lOBA0.css → InsightsView-DAAXuX4v.css} +1 -1
  17. package/dist/client/assets/{MemoryView-DwJas8vi.js → MemoryView-BHq1AHPu.js} +1 -1
  18. package/dist/client/assets/{PiExtensionsManager-B_LiW_e9.js → PiExtensionsManager-B8At6fO8.js} +1 -1
  19. package/dist/client/assets/{PluginManager-BkB6WLQc.js → PluginManager-k7hV7_9e.js} +1 -1
  20. package/dist/client/assets/{PullRequestView-DcH5tmYc.js → PullRequestView-B1hZFrWo.js} +1 -1
  21. package/dist/client/assets/{ReportModal-BOEbqtqG.js → ReportModal-C46V_zEa.js} +1 -1
  22. package/dist/client/assets/{ResearchView-Dp6V42pI.js → ResearchView-D8G85BYN.js} +2 -2
  23. package/dist/client/assets/{SecretsView-BCqXHB2L.js → SecretsView-CTri9LRK.js} +1 -1
  24. package/dist/client/assets/{SessionTerminal-CkdoQv4y.js → SessionTerminal-BRf6r4Vp.js} +1 -1
  25. package/dist/client/assets/SettingsModal-D0RBeZik.js +21 -0
  26. package/dist/client/assets/{SettingsModal-Dc7YG6KB.js → SettingsModal-rUQcSTJp.js} +1 -1
  27. package/dist/client/assets/{SettingsTextareaRow-WSkBoAOO.js → SettingsTextareaRow-BirlBcem.js} +1 -1
  28. package/dist/client/assets/{SetupWizardModal-VTrJAsKd.js → SetupWizardModal-xnCBM9EL.js} +2 -2
  29. package/dist/client/assets/{SkillsView-CXeXOxfF.js → SkillsView-O5U0ep50.js} +1 -1
  30. package/dist/client/assets/{WorkflowNodeEditor-DoxLGPRF.js → WorkflowNodeEditor-BX4d2qqF.js} +3 -3
  31. package/dist/client/assets/{agent-import-generation-upc0yw-M.js → agent-import-generation-DnqVkPkz.js} +1 -1
  32. package/dist/client/assets/{agentsOrgChartLayout-C3iumMrW.js → agentsOrgChartLayout-CKuzbhFf.js} +1 -1
  33. package/dist/client/assets/app-B2FwULu2.js +5 -0
  34. package/dist/client/assets/{app-BA9gw9hx.js → app-B6nJaqAl.js} +2 -2
  35. package/dist/client/assets/{app-B_lScKc1.js → app-CGXZeIRc.js} +2 -2
  36. package/dist/client/assets/{app-DG_vjDw5.js → app-CGtkeBF5.js} +3 -3
  37. package/dist/client/assets/{app-GBgoY-oh.js → app-CNzN1Plu.js} +2 -2
  38. package/dist/client/assets/{app-B0KFGP8D.js → app-DLs-f5yM.js} +3 -3
  39. package/dist/client/assets/{app-DeHIOCEP.js → app-DsvtcRUG.js} +2 -2
  40. package/dist/client/assets/{architectureDiagram-3BPJPVTR-D6jqc3fJ.js → architectureDiagram-3BPJPVTR-gi467xY0.js} +1 -1
  41. package/dist/client/assets/{blockDiagram-GPEHLZMM-DY3AKuPD.js → blockDiagram-GPEHLZMM-B_gpcYiR.js} +1 -1
  42. package/dist/client/assets/{c4Diagram-AAUBKEIU-CpD52V4g.js → c4Diagram-AAUBKEIU-DeylEYsQ.js} +1 -1
  43. package/dist/client/assets/channel-BoE8DeM4.js +1 -0
  44. package/dist/client/assets/{chunk-2J33WTMH-CPeIHyAX.js → chunk-2J33WTMH-BL2WY0HN.js} +1 -1
  45. package/dist/client/assets/{chunk-4BX2VUAB-ivAwI4xM.js → chunk-4BX2VUAB-BkLExGMe.js} +1 -1
  46. package/dist/client/assets/{chunk-55IACEB6-BtVQ5XaZ.js → chunk-55IACEB6-CSTvCF5T.js} +1 -1
  47. package/dist/client/assets/{chunk-727SXJPM-1eap1vDU.js → chunk-727SXJPM-9J8pKk4x.js} +1 -1
  48. package/dist/client/assets/{chunk-AQP2D5EJ-B2NB3BW9.js → chunk-AQP2D5EJ-DXC1vPWr.js} +1 -1
  49. package/dist/client/assets/{chunk-FMBD7UC4-Hk_smNYv.js → chunk-FMBD7UC4-CFRUb-zD.js} +1 -1
  50. package/dist/client/assets/{chunk-ND2GUHAM-BmGCAojk.js → chunk-ND2GUHAM-B87HFYS8.js} +1 -1
  51. package/dist/client/assets/{chunk-QZHKN3VN-D0aXZ8CV.js → chunk-QZHKN3VN-CTjHiGbd.js} +1 -1
  52. package/dist/client/assets/classDiagram-4FO5ZUOK-qnbLX_9_.js +1 -0
  53. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-qnbLX_9_.js +1 -0
  54. package/dist/client/assets/{cose-bilkent-S5V4N54A-DAbu_gnF.js → cose-bilkent-S5V4N54A-D3Edq3L9.js} +1 -1
  55. package/dist/client/assets/{dagre-BM42HDAG-BdmLlrDJ.js → dagre-BM42HDAG-CwwbeQfN.js} +1 -1
  56. package/dist/client/assets/{dashboard-view-CULL1yhL.js → dashboard-view-BLAoW3D4.js} +1 -1
  57. package/dist/client/assets/{dashboard-view-PRCWCSLm.js → dashboard-view-DAlJxqiJ.js} +1 -1
  58. package/dist/client/assets/{dashboard-view-BoFBSXUn.js → dashboard-view-QxQwJFLz.js} +1 -1
  59. package/dist/client/assets/{dashboard-view-CxXMDfNY.js → dashboard-view-aPzfv99T.js} +1 -1
  60. package/dist/client/assets/{diagram-2AECGRRQ-PpqLbVUA.js → diagram-2AECGRRQ-koJWWXE4.js} +1 -1
  61. package/dist/client/assets/{diagram-5GNKFQAL-9rkGmTwh.js → diagram-5GNKFQAL-Yob5cOKT.js} +1 -1
  62. package/dist/client/assets/{diagram-KO2AKTUF-BT-efOMo.js → diagram-KO2AKTUF-DelvFSkK.js} +1 -1
  63. package/dist/client/assets/{diagram-LMA3HP47-w79OWFxg.js → diagram-LMA3HP47-CMput2h2.js} +1 -1
  64. package/dist/client/assets/{diagram-OG6HWLK6-Bye9I5IO.js → diagram-OG6HWLK6-04VXDl79.js} +1 -1
  65. package/dist/client/assets/{erDiagram-TEJ5UH35-aodREgDf.js → erDiagram-TEJ5UH35-C9iP8Uyp.js} +1 -1
  66. package/dist/client/assets/{flowDiagram-I6XJVG4X-D0DKItY3.js → flowDiagram-I6XJVG4X-DENQvu_t.js} +1 -1
  67. package/dist/client/assets/{folder-open-CqvaMJvx.js → folder-open-D-554oji.js} +1 -1
  68. package/dist/client/assets/{ganttDiagram-6RSMTGT7-CZVApkvw.js → ganttDiagram-6RSMTGT7-BqOttZAT.js} +1 -1
  69. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-D_ynwJ4p.js → gitGraphDiagram-PVQCEYII-CDcydi-5.js} +1 -1
  70. package/dist/client/assets/{index-BzAldNrK.css → index-CXTd3LAB.css} +1 -1
  71. package/dist/client/assets/{index-DWRJyaa3.js → index-CtYRoouR.js} +264 -264
  72. package/dist/client/assets/{infoDiagram-5YYISTIA-pJG6zxjk.js → infoDiagram-5YYISTIA-B5LQQnxt.js} +1 -1
  73. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-nFZ5laUo.js → ishikawaDiagram-YF4QCWOH-DpFEt7D2.js} +1 -1
  74. package/dist/client/assets/{journeyDiagram-JHISSGLW-CWpCaAIe.js → journeyDiagram-JHISSGLW-MELtgu22.js} +1 -1
  75. package/dist/client/assets/{kanban-definition-UN3LZRKU-ATkPspKa.js → kanban-definition-UN3LZRKU-h33PLATd.js} +1 -1
  76. package/dist/client/assets/{mermaid.core-CDXPNs7a.js → mermaid.core-Dk2mUPOq.js} +4 -4
  77. package/dist/client/assets/{mindmap-definition-RKZ34NQL-BkqS9tpe.js → mindmap-definition-RKZ34NQL-B_T4O2-B.js} +1 -1
  78. package/dist/client/assets/{pieDiagram-4H26LBE5-DEUyVLs-.js → pieDiagram-4H26LBE5-B1YIFkRl.js} +1 -1
  79. package/dist/client/assets/{puzzle-DQ0y1-XK.js → puzzle-hvecxlpJ.js} +1 -1
  80. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-ESBJNK5c.js → quadrantDiagram-W4KKPZXB-BoGQR_hO.js} +1 -1
  81. package/dist/client/assets/{requirementDiagram-4Y6WPE33-CqPs7zcv.js → requirementDiagram-4Y6WPE33-BSDmUBIx.js} +1 -1
  82. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-CkFN0m14.js → sankeyDiagram-5OEKKPKP-Dz_GqOa5.js} +1 -1
  83. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-Dx2nDuMO.js → sequenceDiagram-3UESZ5HK-B6twjYUH.js} +1 -1
  84. package/dist/client/assets/{shield-alert-BS05s4RM.js → shield-alert-BX4WRJOv.js} +1 -1
  85. package/dist/client/assets/{standing-instructions-template-xprxYNHm.js → standing-instructions-template-BJY317CC.js} +1 -1
  86. package/dist/client/assets/{stateDiagram-AJRCARHV-EUPmRFGd.js → stateDiagram-AJRCARHV-DuwqWLx-.js} +1 -1
  87. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-DcQ2mhi5.js → stateDiagram-v2-BHNVJYJU-Bu8Sk187.js} +1 -1
  88. package/dist/client/assets/{timeline-definition-PNZ67QCA-D4YO_3V-.js → timeline-definition-PNZ67QCA-BS6VnqSI.js} +1 -1
  89. package/dist/client/assets/{upload-BjyBtxo7.js → upload-CaDoyaMW.js} +1 -1
  90. package/dist/client/assets/{users-D5ogZ7WQ.js → users-C5uCLwVM.js} +1 -1
  91. package/dist/client/assets/{vennDiagram-CIIHVFJN-BT5_XuQL.js → vennDiagram-CIIHVFJN-CUbzqT5Y.js} +1 -1
  92. package/dist/client/assets/{wardley-L42UT6IY-X_ZMW457.js → wardley-L42UT6IY-Mv-NotFS.js} +1 -1
  93. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-GK9nONHU.js → wardleyDiagram-YWT4CUSO-DuYpvLZX.js} +1 -1
  94. package/dist/client/assets/{xychartDiagram-2RQKCTM6-Dw3zXHAq.js → xychartDiagram-2RQKCTM6-24_7QXdw.js} +1 -1
  95. package/dist/client/index.html +2 -2
  96. package/dist/client/version.json +1 -1
  97. package/dist/droid-cli/package.json +1 -1
  98. package/dist/extension.js +285 -47
  99. package/dist/pi-claude-cli/package.json +1 -1
  100. package/dist/plugin-sdk/index.d.ts +23 -0
  101. package/dist/plugin-sdk/index.js +2 -0
  102. package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +2 -0
  103. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  104. package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +2 -0
  105. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  106. package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +2 -0
  107. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  108. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +2 -0
  109. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  110. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +2 -0
  111. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  112. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +2 -0
  113. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  114. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +2 -0
  115. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  116. package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +2 -0
  117. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  118. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +2 -0
  119. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  120. package/dist/plugins/fusion-plugin-linear-import/bundled.js +2 -0
  121. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  122. package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +2 -0
  123. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  124. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +2 -0
  125. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  126. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +2 -0
  127. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  128. package/dist/plugins/fusion-plugin-quality/bundled.js +2 -0
  129. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  130. package/dist/plugins/fusion-plugin-reports/bundled.js +2 -0
  131. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  132. package/dist/plugins/fusion-plugin-roadmap/bundled.js +2 -0
  133. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  134. package/dist/plugins/fusion-plugin-todos/bundled.js +2 -0
  135. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  136. package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +2 -0
  137. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  138. package/package.json +1 -1
  139. package/dist/client/assets/InsightsView-DOhO7xH5.js +0 -11
  140. package/dist/client/assets/SettingsModal-D4Fn4GlS.js +0 -21
  141. package/dist/client/assets/app-DBCGrz-f.js +0 -5
  142. package/dist/client/assets/channel-CaxOu15c.js +0 -1
  143. package/dist/client/assets/classDiagram-4FO5ZUOK-DXIkGbsl.js +0 -1
  144. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DXIkGbsl.js +0 -1
package/dist/bin.js CHANGED
@@ -2184,6 +2184,8 @@ var init_settings_schema = __esm({
2184
2184
  DEFAULT_PROJECT_SETTINGS = {
2185
2185
  // FNXC:TaskRecommendations 2026-08-08-05:02: completion follows-ups stay bounded by default; 0 disables writing them.
2186
2186
  maxRecommendationsPerTask: 3,
2187
+ // FNXC:TaskRecommendations 2026-08-13-03:56: surface completed-task proposals by default; operators can suppress the notice without suppressing capture.
2188
+ recommendationMailboxNoticeEnabled: true,
2187
2189
  globalPause: false,
2188
2190
  globalPauseReason: void 0,
2189
2191
  defaultWorkflowId: void 0,
@@ -68757,20 +68759,32 @@ async function replaceActiveTaskWorkflowContinuation(layer, input) {
68757
68759
  return upsertWorkflowWorkItem(layer, input, tx);
68758
68760
  }));
68759
68761
  }
68760
- async function seedStrandedPlanReviewContinuation(layer, input) {
68762
+ async function seedStrandedPlanReviewContinuation(layer, input, options = {}) {
68761
68763
  return layer.transactionImmediate(async (tx) => withTaskWorkflowSerialization(tx, layer.projectId, input.taskId, async () => {
68762
68764
  const active2 = await tx.select({ id: project_exports.workflowWorkItems.id }).from(project_exports.workflowWorkItems).where(and(
68763
68765
  projectScopeFor(project_exports.workflowWorkItems.projectId, layer.projectId),
68764
68766
  eq(project_exports.workflowWorkItems.taskId, input.taskId),
68765
68767
  inArray(project_exports.workflowWorkItems.state, [...ACTIVE_WORKFLOW_WORK_ITEM_STATES])
68766
68768
  ));
68767
- if (active2.length > 0) return { seeded: false, reason: "active-continuation" };
68769
+ const blocking = options.retirePredecessorId ? active2.filter((row) => row.id !== options.retirePredecessorId) : active2;
68770
+ if (blocking.length > 0) return { seeded: false, reason: "active-continuation" };
68768
68771
  const taskRows = await tx.select({ workflowStepResults: project_exports.tasks.workflowStepResults }).from(project_exports.tasks).where(and(
68769
68772
  projectScopeFor(project_exports.tasks.projectId, layer.projectId),
68770
68773
  eq(project_exports.tasks.id, input.taskId)
68771
68774
  )).limit(1);
68772
68775
  const results = taskRows[0]?.workflowStepResults;
68773
68776
  if (results?.some(isPlanReviewSatisfied)) return { seeded: false, reason: "plan-review-passed" };
68777
+ if (options.retirePredecessorId) {
68778
+ const predRows = await tx.select().from(project_exports.workflowWorkItems).where(and(
68779
+ projectScopeFor(project_exports.workflowWorkItems.projectId, layer.projectId),
68780
+ eq(project_exports.workflowWorkItems.id, options.retirePredecessorId),
68781
+ eq(project_exports.workflowWorkItems.taskId, input.taskId)
68782
+ )).limit(1);
68783
+ const pred = predRows[0];
68784
+ if (pred && !isTerminalWorkflowWorkItemState(normalizeWorkflowWorkItemState(pred.state))) {
68785
+ await transitionWorkflowWorkItem(layer, pred.id, "succeeded", { leaseOwner: null, leaseExpiresAt: null, lastError: null }, tx);
68786
+ }
68787
+ }
68774
68788
  const item = await upsertWorkflowWorkItem(layer, input, tx);
68775
68789
  return { seeded: true, workItemId: item.id };
68776
68790
  }));
@@ -97148,8 +97162,8 @@ async function upsertWorkflowWorkItemImpl(store, input, tx) {
97148
97162
  async function replaceActiveTaskWorkflowContinuationImpl(store, input) {
97149
97163
  return replaceActiveTaskWorkflowContinuation(store.asyncLayer, input);
97150
97164
  }
97151
- async function seedStrandedPlanReviewContinuationImpl(store, input) {
97152
- return seedStrandedPlanReviewContinuation(store.asyncLayer, input);
97165
+ async function seedStrandedPlanReviewContinuationImpl(store, input, options = {}) {
97166
+ return seedStrandedPlanReviewContinuation(store.asyncLayer, input, options);
97153
97167
  }
97154
97168
  async function transitionWorkflowWorkItemImpl(store, id, state, patch = {}, tx) {
97155
97169
  return transitionWorkflowWorkItem(store.asyncLayer, id, state, patch, tx);
@@ -101292,6 +101306,37 @@ async function getTaskVerificationRequestAsyncImpl(store, taskId) {
101292
101306
  const row = rows[0];
101293
101307
  return row ? { taskId: row.taskId, requestId: row.requestId, status: row.status, profile: row.profile, command: row.command, scope: row.scope, requestedBy: row.requestedBy, requestedAt: row.requestedAt, ...row.startedAt ? { startedAt: row.startedAt } : {}, ...row.completedAt ? { completedAt: row.completedAt } : {}, ...row.result ? { result: row.result } : {}, ...row.rejectionReason ? { rejectionReason: row.rejectionReason } : {} } : null;
101294
101308
  }
101309
+ async function listTaskRecommendationsImpl(store, options) {
101310
+ const layer = store.asyncLayer;
101311
+ const completeColumns = options?.completeColumns ?? await resolveProjectColumnsForRoles(store, ["complete"]);
101312
+ const rawLimit = options?.limit;
101313
+ const rawOffset = options?.offset;
101314
+ const limit2 = typeof rawLimit === "number" && Number.isFinite(rawLimit) ? Math.min(200, Math.max(1, Math.trunc(rawLimit))) : 50;
101315
+ const offset = typeof rawOffset === "number" && Number.isFinite(rawOffset) ? Math.max(0, Math.trunc(rawOffset)) : 0;
101316
+ const columns = [...completeColumns];
101317
+ if (columns.length === 0) return { items: [], rowOffset: offset, rowLimit: limit2, returnedRowCount: 0, totalRowCount: 0, hasMore: false };
101318
+ const filter2 = and(
101319
+ taskProjectScope(layer),
101320
+ isNull(project_exports.tasks.deletedAt),
101321
+ inArray(project_exports.tasks.column, columns),
101322
+ isNotNull(project_exports.tasks.recommendations),
101323
+ sql`jsonb_array_length(${project_exports.tasks.recommendations}) > 0`
101324
+ );
101325
+ const [countRows, rows] = await Promise.all([
101326
+ layer.db.select({ count: sql`count(*)` }).from(project_exports.tasks).where(filter2),
101327
+ layer.db.select({ id: project_exports.tasks.id, title: project_exports.tasks.title, column: project_exports.tasks.column, updatedAt: project_exports.tasks.updatedAt, recommendations: project_exports.tasks.recommendations }).from(project_exports.tasks).where(filter2).orderBy(desc(project_exports.tasks.updatedAt), desc(project_exports.tasks.id)).limit(limit2).offset(offset)
101328
+ ]);
101329
+ const items = [];
101330
+ for (const row of rows) {
101331
+ if (!Array.isArray(row.recommendations) || row.recommendations.length === 0) continue;
101332
+ for (const recommendation of row.recommendations) {
101333
+ items.push({ taskId: row.id, taskTitle: row.title ?? void 0, taskColumn: row.column, updatedAt: row.updatedAt, recommendation });
101334
+ }
101335
+ }
101336
+ const totalRowCount = Number(countRows[0]?.count ?? 0);
101337
+ const returnedRowCount = rows.length;
101338
+ return { items, rowOffset: offset, rowLimit: limit2, returnedRowCount, totalRowCount, hasMore: offset + returnedRowCount < totalRowCount };
101339
+ }
101295
101340
  var LEGACY_REVIEW_LANES5, LEGACY_ARCHIVE_LANES2;
101296
101341
  var init_reads = __esm({
101297
101342
  "../core/src/task-store/reads.ts"() {
@@ -101314,6 +101359,7 @@ var init_reads = __esm({
101314
101359
  init_retry_summary();
101315
101360
  init_errors3();
101316
101361
  init_project_lane_vocabulary();
101362
+ init_data_layer();
101317
101363
  init_async_persistence();
101318
101364
  init_async_search();
101319
101365
  init_async_archive_db();
@@ -103420,6 +103466,9 @@ var init_store = __esm({
103420
103466
  async clearWorkflowRunStepInstancesAsync(taskId, keepRunId) {
103421
103467
  return clearWorkflowRunStepInstancesAsyncImpl(this, taskId, keepRunId);
103422
103468
  }
103469
+ async listTaskRecommendations(options) {
103470
+ return listTaskRecommendationsImpl(this, options);
103471
+ }
103423
103472
  async listTasksForGithubTrackingReconcile(options) {
103424
103473
  return listTasksForGithubTrackingReconcileImpl(this, options);
103425
103474
  }
@@ -104146,8 +104195,8 @@ var init_store = __esm({
104146
104195
  async replaceActiveTaskWorkflowContinuation(input) {
104147
104196
  return replaceActiveTaskWorkflowContinuationImpl(this, input);
104148
104197
  }
104149
- async seedStrandedPlanReviewContinuation(input) {
104150
- return seedStrandedPlanReviewContinuationImpl(this, input);
104198
+ async seedStrandedPlanReviewContinuation(input, options = {}) {
104199
+ return seedStrandedPlanReviewContinuationImpl(this, input, options);
104151
104200
  }
104152
104201
  async transitionWorkflowWorkItem(id, state, patch = {}, tx) {
104153
104202
  return transitionWorkflowWorkItemImpl(this, id, state, patch, tx);
@@ -133285,6 +133334,77 @@ var init_task_timing = __esm({
133285
133334
  }
133286
133335
  });
133287
133336
 
133337
+ // ../core/src/task-recommendation-notice.ts
133338
+ import { createHash as createHash20 } from "node:crypto";
133339
+ function registerTaskRecommendationNoticeMailbox(store, mailbox) {
133340
+ mailboxes2.set(store, mailbox);
133341
+ return () => {
133342
+ if (mailboxes2.get(store) === mailbox) mailboxes2.delete(store);
133343
+ };
133344
+ }
133345
+ function getTaskRecommendationNoticeMailbox(store) {
133346
+ return mailboxes2.get(store);
133347
+ }
133348
+ function buildTaskRecommendationNoticeContent(task, recommendations) {
133349
+ const title2 = task.title?.trim();
133350
+ const heading = title2 ? `## Recommendations from ${task.id} \u2014 ${title2}` : `## Recommendations from ${task.id}`;
133351
+ return [
133352
+ heading,
133353
+ "",
133354
+ "These are optional, non-blocking out-of-scope proposals that were **not** executed.",
133355
+ "",
133356
+ ...recommendations.map((recommendation) => `- **${recommendation.title}** \u2014 \`${recommendation.category}\`: ${recommendation.description}`),
133357
+ "",
133358
+ `Open ${task.id}'s **Recommendations** tab to review them. **Create task** files one through normal guarded intake.`
133359
+ ].join("\n");
133360
+ }
133361
+ function buildTaskRecommendationNoticeIdempotencyKey(taskId, recommendations) {
133362
+ const ids = recommendations.map(({ id }) => id).sort().join("\n");
133363
+ const digest = createHash20("sha256").update(ids, "utf8").digest("hex").slice(0, 16);
133364
+ return `task-recommendation-notice:${taskId}:${digest}`;
133365
+ }
133366
+ async function notifyOperatorOfTaskRecommendations(store, task, recommendations, settings) {
133367
+ try {
133368
+ if (settings.recommendationMailboxNoticeEnabled === false || !recommendations?.length) return false;
133369
+ const mailbox = mailboxes2.get(store);
133370
+ if (!mailbox) return false;
133371
+ await mailbox.sendMessageOnce(
133372
+ {
133373
+ fromId: "system",
133374
+ fromType: "system",
133375
+ toId: DASHBOARD_USER_ID,
133376
+ toType: "user",
133377
+ type: "system",
133378
+ content: buildTaskRecommendationNoticeContent(task, recommendations),
133379
+ metadata: {
133380
+ kind: "task-recommendation-notice",
133381
+ taskId: task.id,
133382
+ recommendationCount: recommendations.length,
133383
+ recommendationIds: recommendations.map(({ id }) => id),
133384
+ categories: recommendations.map(({ category }) => category)
133385
+ }
133386
+ },
133387
+ buildTaskRecommendationNoticeIdempotencyKey(task.id, recommendations)
133388
+ );
133389
+ return true;
133390
+ } catch (error3) {
133391
+ noticeLog2.warn(
133392
+ `Operator recommendation notice failed for ${task.id}: ${error3 instanceof Error ? error3.message : String(error3)}`
133393
+ );
133394
+ return false;
133395
+ }
133396
+ }
133397
+ var noticeLog2, mailboxes2;
133398
+ var init_task_recommendation_notice = __esm({
133399
+ "../core/src/task-recommendation-notice.ts"() {
133400
+ "use strict";
133401
+ init_logger();
133402
+ init_types();
133403
+ noticeLog2 = createLogger("task-recommendation-notice");
133404
+ mailboxes2 = /* @__PURE__ */ new WeakMap();
133405
+ }
133406
+ });
133407
+
133288
133408
  // ../core/src/session-identity-registry.ts
133289
133409
  import { AsyncLocalStorage } from "node:async_hooks";
133290
133410
  import { resolve as resolve24 } from "node:path";
@@ -134134,6 +134254,8 @@ __export(src_exports, {
134134
134254
  buildTaskDeleteNoticeContent: () => buildTaskDeleteNoticeContent,
134135
134255
  buildTaskDeleteNoticeIdempotencyKey: () => buildTaskDeleteNoticeIdempotencyKey,
134136
134256
  buildTaskLineageTrailer: () => buildTaskLineageTrailer,
134257
+ buildTaskRecommendationNoticeContent: () => buildTaskRecommendationNoticeContent,
134258
+ buildTaskRecommendationNoticeIdempotencyKey: () => buildTaskRecommendationNoticeIdempotencyKey,
134137
134259
  buildToolOutputTruncationMarker: () => buildToolOutputTruncationMarker,
134138
134260
  buildTriageHeartbeatGuidance: () => buildTriageHeartbeatGuidance,
134139
134261
  buildTriageMemoryInstructions: () => buildTriageMemoryInstructions,
@@ -134464,6 +134586,7 @@ __export(src_exports, {
134464
134586
  getTaskMergeBlocker: () => getTaskMergeBlocker,
134465
134587
  getTaskMoveDisposer: () => getTaskMoveDisposer,
134466
134588
  getTaskPriorityRank: () => getTaskPriorityRank,
134589
+ getTaskRecommendationNoticeMailbox: () => getTaskRecommendationNoticeMailbox,
134467
134590
  getTemplatesForRole: () => getTemplatesForRole,
134468
134591
  getTotalAgentActiveMs: () => getTotalAgentActiveMs,
134469
134592
  getTrait: () => getTrait,
@@ -134714,6 +134837,7 @@ __export(src_exports, {
134714
134837
  normalizeWorkflowPromptOverrides: () => normalizeWorkflowPromptOverrides,
134715
134838
  normalizeWorkflowReviewFindings: () => normalizeWorkflowReviewFindings,
134716
134839
  notifyOperatorOfNonOperatorDelete: () => notifyOperatorOfNonOperatorDelete,
134840
+ notifyOperatorOfTaskRecommendations: () => notifyOperatorOfTaskRecommendations,
134717
134841
  overseerAdviceSeverityRank: () => overseerAdviceSeverityRank,
134718
134842
  parseAgentManifest: () => parseAgentManifest,
134719
134843
  parseCompanyArchive: () => parseCompanyArchive,
@@ -134823,6 +134947,7 @@ __export(src_exports, {
134823
134947
  registerStepParser: () => registerStepParser,
134824
134948
  registerTaskDeleteNoticeMailbox: () => registerTaskDeleteNoticeMailbox,
134825
134949
  registerTaskMoveDisposer: () => registerTaskMoveDisposer,
134950
+ registerTaskRecommendationNoticeMailbox: () => registerTaskRecommendationNoticeMailbox,
134826
134951
  registerTraitHookImpl: () => registerTraitHookImpl,
134827
134952
  rekeyFallbackProjectPartition: () => rekeyFallbackProjectPartition,
134828
134953
  releaseEmbeddedRuntimeLease: () => releaseEmbeddedRuntimeLease,
@@ -135450,6 +135575,7 @@ var init_src2 = __esm({
135450
135575
  init_task_document_concurrency();
135451
135576
  init_task_delete_attribution();
135452
135577
  init_task_delete_notice();
135578
+ init_task_recommendation_notice();
135453
135579
  init_session_identity_registry();
135454
135580
  init_task_lifecycle_event_retention();
135455
135581
  init_task_lifecycle_consumer_identity();
@@ -136287,7 +136413,7 @@ var init_logger3 = __esm({
136287
136413
  });
136288
136414
 
136289
136415
  // ../engine/src/agents/agent-logger.ts
136290
- import { createHash as createHash20 } from "node:crypto";
136416
+ import { createHash as createHash21 } from "node:crypto";
136291
136417
  function truncateToolResultDetail(value2) {
136292
136418
  if (value2.length <= TOOL_RESULT_DETAIL_LIMIT) return value2;
136293
136419
  return `${value2.slice(0, TOOL_RESULT_DETAIL_LIMIT)}${TOOL_RESULT_DETAIL_TRUNCATION_NOTICE}`;
@@ -136376,7 +136502,7 @@ function summarizeToolArgs(name2, args) {
136376
136502
  const json2 = JSON.stringify(args);
136377
136503
  if (!json2 || json2 === "{}" || json2 === "[]") return void 0;
136378
136504
  if (json2.length <= STRUCTURED_ARG_SUMMARY_MAX) return json2;
136379
- const hash3 = createHash20("sha256").update(json2).digest("hex").slice(0, STRUCTURED_ARG_HASH_HEX_LEN);
136505
+ const hash3 = createHash21("sha256").update(json2).digest("hex").slice(0, STRUCTURED_ARG_HASH_HEX_LEN);
136380
136506
  const suffix = `\u2026#${hash3}`;
136381
136507
  const keep = Math.max(0, STRUCTURED_ARG_SUMMARY_MAX - suffix.length);
136382
136508
  return `${json2.slice(0, keep)}${suffix}`;
@@ -137156,7 +137282,7 @@ var init_gating_classifications = __esm({
137156
137282
  });
137157
137283
 
137158
137284
  // ../engine/src/agents/agent-action-gate.ts
137159
- import { createHash as createHash21 } from "node:crypto";
137285
+ import { createHash as createHash22 } from "node:crypto";
137160
137286
  function getExemptTools() {
137161
137287
  if (!_exemptTools) {
137162
137288
  _exemptTools = new Set(DEFAULT_EXEMPT_TOOLS);
@@ -137208,7 +137334,7 @@ function evaluateAgentActionGate(params) {
137208
137334
  if (params.toolName === "bash") {
137209
137335
  const command2 = extractShellCommand(args);
137210
137336
  const git7 = classifyGitCommand(command2);
137211
- resourceId = command2 ? `cmd:${createHash21("sha256").update(command2).digest("hex").slice(0, 16)}` : void 0;
137337
+ resourceId = command2 ? `cmd:${createHash22("sha256").update(command2).digest("hex").slice(0, 16)}` : void 0;
137212
137338
  if (git7?.write) {
137213
137339
  category = "git_write";
137214
137340
  operation = git7.operation;
@@ -140740,11 +140866,11 @@ var init_planning_handoff_recovery = __esm({
140740
140866
  // ../engine/src/plan-review-continuation.ts
140741
140867
  async function seedPreReleasePlanReviewContinuation(store, task, ir2, options = {}) {
140742
140868
  const node = resolvePreReleasePlanReviewNode(ir2);
140743
- if (!node || node.column !== task.column) return { seeded: false };
140869
+ if (!node || node.column !== task.column) return { seeded: false, reason: "no-pre-release-plan-review" };
140744
140870
  if (isTaskBlockedOnApproval(task)) return { seeded: false, reason: "awaiting-approval" };
140745
140871
  if (task.paused === true || task.userPaused === true) return { seeded: false, reason: "paused" };
140746
140872
  const items = await store.listWorkflowWorkItemsForTask(task.id);
140747
- const active2 = items.filter((item2) => ACTIVE_WORKFLOW_WORK_ITEM_STATES.includes(item2.state));
140873
+ const active2 = items.filter((item2) => ACTIVE_WORKFLOW_WORK_ITEM_STATES.includes(item2.state) && item2.id !== options.retirePredecessorId);
140748
140874
  if (active2.length > 0) return { seeded: false, reason: "active-continuation" };
140749
140875
  const continuationSequence = items.length;
140750
140876
  const input = {
@@ -140760,6 +140886,9 @@ async function seedPreReleasePlanReviewContinuation(store, task, ir2, options =
140760
140886
  targetColumn: task.column,
140761
140887
  irHash: computeWorkflowIrPin(ir2, node.id).irHash
140762
140888
  };
140889
+ if (options.retirePredecessorId) {
140890
+ return store.seedStrandedPlanReviewContinuation(input, { retirePredecessorId: options.retirePredecessorId });
140891
+ }
140763
140892
  if (options.atomic) return store.seedStrandedPlanReviewContinuation(input);
140764
140893
  const item = await store.replaceActiveTaskWorkflowContinuation(input);
140765
140894
  return { seeded: true, workItemId: item.id };
@@ -140774,7 +140903,7 @@ async function resumeApprovedPlanReviewHandoff(store, task, ir2) {
140774
140903
  if (seeded.seeded) return { resumed: true, reason: "seeded", workItemId: seeded.workItemId };
140775
140904
  return {
140776
140905
  resumed: false,
140777
- reason: seeded.reason ?? "not-plan-in-place"
140906
+ reason: seeded.reason === void 0 || seeded.reason === "no-pre-release-plan-review" ? "not-plan-in-place" : seeded.reason
140778
140907
  };
140779
140908
  }
140780
140909
  function evaluateStrandedHoldContinuation(input) {
@@ -140812,7 +140941,7 @@ var init_plan_review_continuation = __esm({
140812
140941
  });
140813
140942
 
140814
140943
  // ../engine/src/execution/hold-release.ts
140815
- import { createHash as createHash22 } from "node:crypto";
140944
+ import { createHash as createHash23 } from "node:crypto";
140816
140945
  import { readFile as readFile31 } from "node:fs/promises";
140817
140946
  function findColumn2(ir2, columnId) {
140818
140947
  if (ir2.version !== "v2") return void 0;
@@ -140850,9 +140979,9 @@ async function checkAndRecordUnplannedExecutionBlock(store, task, ir2) {
140850
140979
  promptContent = "";
140851
140980
  }
140852
140981
  }
140853
- const promptMarker = promptContent.length > 0 ? createHash22("sha256").update(promptContent).digest("hex") : "missing";
140982
+ const promptMarker = promptContent.length > 0 ? createHash23("sha256").update(promptContent).digest("hex") : "missing";
140854
140983
  const dependencies = [...task.dependencies ?? []].sort();
140855
- const episode = createHash22("sha256").update(JSON.stringify({
140984
+ const episode = createHash23("sha256").update(JSON.stringify({
140856
140985
  planReviewNode,
140857
140986
  promptMarker,
140858
140987
  dependencies,
@@ -170972,7 +171101,7 @@ ${getErrorStderr(error3) ?? ""}`;
170972
171101
  });
170973
171102
 
170974
171103
  // ../engine/src/worktree/secrets-env-writer.ts
170975
- import { createHash as createHash23 } from "node:crypto";
171104
+ import { createHash as createHash24 } from "node:crypto";
170976
171105
  import { execFile as execFile8 } from "node:child_process";
170977
171106
  import { promises as fs6 } from "node:fs";
170978
171107
  import path2 from "node:path";
@@ -170981,7 +171110,7 @@ function isValidFilename(filename) {
170981
171110
  return !!filename && !filename.includes("/") && !filename.includes("\\") && !filename.includes("..") && !filename.includes("\0") && filename !== FINGERPRINT_FILE;
170982
171111
  }
170983
171112
  function sha2562(content) {
170984
- return createHash23("sha256").update(Buffer.from(content, "utf8")).digest("hex");
171113
+ return createHash24("sha256").update(Buffer.from(content, "utf8")).digest("hex");
170985
171114
  }
170986
171115
  function quote(value2) {
170987
171116
  return `"${value2.replaceAll("\\", "\\\\").replaceAll('"', '\\"').replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll(" ", "\\t")}"`;
@@ -179388,7 +179517,7 @@ var init_mission_feature_sync = __esm({
179388
179517
  // ../engine/src/agent-tools.ts
179389
179518
  import { appendFile as appendFile3, mkdir as mkdir28, readFile as readFile38, readdir as readdir11, realpath as realpath2, stat as stat11, writeFile as writeFile26 } from "node:fs/promises";
179390
179519
  import { existsSync as existsSync53 } from "node:fs";
179391
- import { createHash as createHash24, randomUUID as randomUUID47 } from "node:crypto";
179520
+ import { createHash as createHash25, randomUUID as randomUUID47 } from "node:crypto";
179392
179521
  import { tmpdir as tmpdir5 } from "node:os";
179393
179522
  import { extname as extname4, isAbsolute as isAbsolute14, join as join66, relative as relative14, resolve as resolve38, sep as sep10 } from "node:path";
179394
179523
  import { Type as Type2 } from "@earendil-works/pi-ai";
@@ -179432,7 +179561,7 @@ async function readAgentMemoryWorkspaceLongTerm(rootDir, agentId) {
179432
179561
  }
179433
179562
  }
179434
179563
  function qmdAgentMemoryCollectionName2(rootDir, agentId) {
179435
- const hash3 = createHash24("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
179564
+ const hash3 = createHash25("sha1").update(`${rootDir}:${agentId}`).digest("hex").slice(0, 12);
179436
179565
  return `fusion-agent-memory-${sanitizeAgentMemoryId(agentId).toLowerCase()}-${hash3}`;
179437
179566
  }
179438
179567
  function buildQmdAgentMemoryCollectionAddArgs(rootDir, agentId) {
@@ -191009,7 +191138,7 @@ var init_agent_heartbeat_error_recovery = __esm({
191009
191138
 
191010
191139
  // ../engine/src/agent-heartbeat.ts
191011
191140
  import { Type as Type3 } from "@earendil-works/pi-ai";
191012
- import { createHash as createHash25 } from "node:crypto";
191141
+ import { createHash as createHash26 } from "node:crypto";
191013
191142
  async function resolveHeartbeatReboundColumn(taskStore, taskId) {
191014
191143
  try {
191015
191144
  return resolveReboundTarget(await resolveWorkflowIrForTask(taskStore, taskId)) ?? "todo";
@@ -191164,7 +191293,7 @@ function truncatePrompt(text3, maxChars) {
191164
191293
  ... (truncated, ${text3.length} chars)`;
191165
191294
  }
191166
191295
  function shortContentHash(value2) {
191167
- return createHash25("sha256").update(value2).digest("hex").slice(0, 8);
191296
+ return createHash26("sha256").update(value2).digest("hex").slice(0, 8);
191168
191297
  }
191169
191298
  function buildIdentitySnapshot(args) {
191170
191299
  const { agent, resolvedInstructions, workspaceMemory } = args;
@@ -199053,7 +199182,7 @@ var init_verification_utils = __esm({
199053
199182
 
199054
199183
  // ../engine/src/merge/merge-dependency-sync.ts
199055
199184
  import { exec as exec25 } from "node:child_process";
199056
- import { createHash as createHash26 } from "node:crypto";
199185
+ import { createHash as createHash27 } from "node:crypto";
199057
199186
  import { existsSync as existsSync56, readFileSync as readFileSync18, writeFileSync as writeFileSync6 } from "node:fs";
199058
199187
  import { join as join69 } from "node:path";
199059
199188
  import { promisify as promisify35 } from "node:util";
@@ -199080,7 +199209,7 @@ function computeLockfileHash(rootDir) {
199080
199209
  const p2 = join69(rootDir, name2);
199081
199210
  if (existsSync56(p2)) {
199082
199211
  try {
199083
- return createHash26("sha256").update(readFileSync18(p2)).digest("hex");
199212
+ return createHash27("sha256").update(readFileSync18(p2)).digest("hex");
199084
199213
  } catch {
199085
199214
  return null;
199086
199215
  }
@@ -201622,7 +201751,7 @@ var init_mesh_lease_manager = __esm({
201622
201751
  });
201623
201752
 
201624
201753
  // ../engine/src/merge/merger-integration-worktree.ts
201625
- import { createHash as createHash27 } from "node:crypto";
201754
+ import { createHash as createHash28 } from "node:crypto";
201626
201755
  import { exec as exec31, execFile as execFile22 } from "node:child_process";
201627
201756
  import { promisify as promisify45 } from "node:util";
201628
201757
  function quoteAutostashArg(value2) {
@@ -201745,7 +201874,7 @@ async function gitDirtyFingerprintLocal(rootDir) {
201745
201874
  )
201746
201875
  ]);
201747
201876
  if (!diffOut && !statusOut) return "";
201748
- return createHash27("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
201877
+ return createHash28("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
201749
201878
  } catch {
201750
201879
  return "";
201751
201880
  }
@@ -202731,7 +202860,7 @@ import { execSync as execSync5, exec as exec33 } from "node:child_process";
202731
202860
  import * as childProcess4 from "node:child_process";
202732
202861
  import { promisify as promisify48 } from "node:util";
202733
202862
  import { existsSync as existsSync59, readFileSync as readFileSync21, writeFileSync as writeFileSync8, unlinkSync as unlinkSync3, renameSync as renameSync4 } from "node:fs";
202734
- import { createHash as createHash28 } from "node:crypto";
202863
+ import { createHash as createHash29 } from "node:crypto";
202735
202864
  import { join as join73, resolve as resolve44 } from "node:path";
202736
202865
  import { hostname as hostname2 } from "node:os";
202737
202866
  import { Type as Type4 } from "typebox";
@@ -202979,7 +203108,7 @@ async function gitDirtyFingerprint(rootDir) {
202979
203108
  )
202980
203109
  ]);
202981
203110
  if (!diffOut && !statusOut) return "";
202982
- return createHash28("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
203111
+ return createHash29("sha256").update(diffOut).update("\0").update(statusOut).digest("hex");
202983
203112
  } catch {
202984
203113
  return "";
202985
203114
  }
@@ -225402,7 +225531,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
225402
225531
  }
225403
225532
  return;
225404
225533
  }
225405
- const duplicateReport = { outcome: "parked" };
225534
+ const duplicateReport = { outcome: "parked", planningWorkItemId };
225406
225535
  if (await this.tryFinalizeExplicitDuplicateMarker(task, written, settings, {
225407
225536
  isReplan,
225408
225537
  feedback
@@ -225421,6 +225550,7 @@ You are ${assignedAgent.name}${assignedAgent.title?.trim() ? `, ${assignedAgent.
225421
225550
  isReplan,
225422
225551
  feedback
225423
225552
  });
225553
+ finalizeReport.planningWorkItemId = planningWorkItemId;
225424
225554
  this.options.onSpecifyComplete?.(task, finalizeReport);
225425
225555
  } finally {
225426
225556
  this.activeSessions.delete(task.id);
@@ -240992,6 +241122,38 @@ var init_execution_block_classifier = __esm({
240992
241122
  }
240993
241123
  });
240994
241124
 
241125
+ // ../engine/src/executor/completion-recommendation-notice.ts
241126
+ function dispatchAcceptedCompletionRecommendationNotice(args) {
241127
+ if (!args.recommendations?.length) return;
241128
+ const pending = (async () => {
241129
+ const settings = args.settings ?? await args.store.getSettings();
241130
+ const task = await args.store.getTask(args.taskId);
241131
+ if (!task || task.deletedAt) return;
241132
+ await notifyOperatorOfTaskRecommendations(
241133
+ args.store,
241134
+ { id: args.taskId, title: args.taskTitle ?? task.title },
241135
+ args.recommendations,
241136
+ settings
241137
+ );
241138
+ })();
241139
+ pendingNotices.add(pending);
241140
+ void pending.catch((error3) => {
241141
+ args.log?.warn(
241142
+ `Failed to dispatch recommendation mailbox notice for ${args.taskId}: ${error3 instanceof Error ? error3.message : String(error3)}`
241143
+ );
241144
+ }).finally(() => {
241145
+ pendingNotices.delete(pending);
241146
+ });
241147
+ }
241148
+ var pendingNotices;
241149
+ var init_completion_recommendation_notice = __esm({
241150
+ "../engine/src/executor/completion-recommendation-notice.ts"() {
241151
+ "use strict";
241152
+ init_src2();
241153
+ pendingNotices = /* @__PURE__ */ new Set();
241154
+ }
241155
+ });
241156
+
240995
241157
  // ../engine/src/executor/create-task-done-tool.ts
240996
241158
  import { Type as Type9 } from "@earendil-works/pi-ai";
240997
241159
  function createTaskDoneTool(deps, taskId, worktreePath, promptContent, codeReviewVerdicts, onDone, audit) {
@@ -241328,6 +241490,16 @@ ${rerunSuffix}`
241328
241490
  bulkCompletionRefusalAt: null
241329
241491
  });
241330
241492
  await store.logEntry(taskId, "Task marked done by agent", void 0, deps.getRunContextFor(taskId));
241493
+ if (completionRecommendations !== void 0) {
241494
+ dispatchAcceptedCompletionRecommendationNotice({
241495
+ store,
241496
+ taskId,
241497
+ taskTitle: task.title,
241498
+ recommendations: completionRecommendations,
241499
+ settings,
241500
+ log: executorLog
241501
+ });
241502
+ }
241331
241503
  const latestTask = await store.getTask(taskId);
241332
241504
  let latestColumn = latestTask.column;
241333
241505
  if (latestColumn === await resolveReboundColumnFor(store, taskId)) {
@@ -241366,6 +241538,7 @@ var init_create_task_done_tool = __esm({
241366
241538
  init_completion_predicates();
241367
241539
  init_task_done_refusal_handler();
241368
241540
  init_validate_completion_recommendations();
241541
+ init_completion_recommendation_notice();
241369
241542
  }
241370
241543
  });
241371
241544
 
@@ -252692,6 +252865,16 @@ ${rerunSuffix}` });
252692
252865
  if (latestColumn === await resolveWipTargetForTask(deps.store, task.id) && !hardPauseActive && beforeWatchdog && !beforeWatchdog.deletedAt && !(rejectIfPaused && (beforeWatchdog.paused || beforeWatchdog.userPaused))) {
252693
252866
  deps.scheduleCompletedTaskWatchdog(task.id, "fn_task_done");
252694
252867
  }
252868
+ if (recommendations !== void 0) {
252869
+ dispatchAcceptedCompletionRecommendationNotice({
252870
+ store: deps.store,
252871
+ taskId: task.id,
252872
+ taskTitle: task.title,
252873
+ recommendations,
252874
+ settings,
252875
+ log: executorLog
252876
+ });
252877
+ }
252695
252878
  return { completed: true, hardPauseActive };
252696
252879
  } catch (error3) {
252697
252880
  await restoreNoCommitsExpected();
@@ -252750,6 +252933,7 @@ var init_plan_review_no_op = __esm({
252750
252933
  init_src2();
252751
252934
  init_logger3();
252752
252935
  init_lifecycle_columns();
252936
+ init_completion_recommendation_notice();
252753
252937
  }
252754
252938
  });
252755
252939
 
@@ -256258,14 +256442,14 @@ ${stdout.trim()}`);
256258
256442
 
256259
256443
  // ../engine/src/missions/mission-execution-loop.ts
256260
256444
  import { EventEmitter as EventEmitter26 } from "node:events";
256261
- import { createHash as createHash29 } from "node:crypto";
256445
+ import { createHash as createHash30 } from "node:crypto";
256262
256446
  import { exec as exec56 } from "node:child_process";
256263
256447
  import { promisify as promisify74 } from "node:util";
256264
256448
  function quoteShellArg7(value2) {
256265
256449
  return `'${value2.replace(/'/g, `'\\''`)}'`;
256266
256450
  }
256267
256451
  function fingerprintMissionValidationInput(landedSha, provider, modelId, systemPrompt, userPrompt) {
256268
- return createHash29("sha256").update(Buffer.from(JSON.stringify(["mission-validation-input-v1", landedSha, provider, modelId, systemPrompt, userPrompt]), "utf8")).digest("hex");
256452
+ return createHash30("sha256").update(Buffer.from(JSON.stringify(["mission-validation-input-v1", landedSha, provider, modelId, systemPrompt, userPrompt]), "utf8")).digest("hex");
256269
256453
  }
256270
256454
  var execAsync53, loopLog, VALIDATION_TIMEOUT_MS, MAX_VALIDATION_RESPONSE_BYTES, MAX_VALIDATION_JSON_CANDIDATES, VALIDATION_FAILURE_BUDGET_PER_FINGERPRINT, MissionExecutionLoop;
256271
256455
  var init_mission_execution_loop = __esm({
@@ -267804,9 +267988,9 @@ var init_routine_scheduler = __esm({
267804
267988
  });
267805
267989
 
267806
267990
  // ../engine/src/healing/stuck-task-detector.ts
267807
- import { createHash as createHash30 } from "node:crypto";
267991
+ import { createHash as createHash31 } from "node:crypto";
267808
267992
  function hashToolDetail(detail) {
267809
- return createHash30("sha256").update(detail).digest("hex").slice(0, TOOL_DETAIL_HASH_HEX_LEN);
267993
+ return createHash31("sha256").update(detail).digest("hex").slice(0, TOOL_DETAIL_HASH_HEX_LEN);
267810
267994
  }
267811
267995
  function buildToolFingerprint(toolName, toolDetail) {
267812
267996
  const name2 = toolName.trim().toLowerCase() || "tool";
@@ -271384,7 +271568,7 @@ var init_node_dispatch_validation = __esm({
271384
271568
  });
271385
271569
 
271386
271570
  // ../engine/src/runtimes/in-process-runtime.ts
271387
- import { createHash as createHash31 } from "node:crypto";
271571
+ import { createHash as createHash32 } from "node:crypto";
271388
271572
  import { EventEmitter as EventEmitter27 } from "node:events";
271389
271573
  import { setImmediate as setImmediateCb4 } from "node:timers";
271390
271574
  function createDiscoveryPluginLoaderOptions(scopedStore) {
@@ -271475,11 +271659,46 @@ async function reactToSpecificationComplete(deps) {
271475
271659
  return;
271476
271660
  }
271477
271661
  deps.log(`Specified ${deps.taskId} \u2192 todo`);
271478
- const live = await deps.getTask(deps.taskId);
271479
- if (!live || live.paused || live.userPaused) return;
271480
- const ir2 = await deps.resolveIr(live.id);
271481
- await deps.seed(live, ir2);
271482
- deps.kick();
271662
+ const sleep10 = deps.sleep ?? ((ms2) => new Promise((resolve110) => setTimeout(resolve110, ms2)));
271663
+ const QUIET_PARK_REASONS = /* @__PURE__ */ new Set([
271664
+ "no-pre-release-plan-review",
271665
+ "awaiting-approval",
271666
+ "paused",
271667
+ "plan-review-passed"
271668
+ ]);
271669
+ const RETRY_DELAYS_MS2 = [1e3, 5e3];
271670
+ for (let attempt = 0; ; attempt++) {
271671
+ let failure = null;
271672
+ try {
271673
+ const live = await deps.getTask(deps.taskId);
271674
+ if (!live || live.paused || live.userPaused) return;
271675
+ if (live.status === "needs-replan") {
271676
+ deps.log(`Plan Review handoff for ${deps.taskId} not armed (needs-replan)`);
271677
+ return;
271678
+ }
271679
+ const ir2 = await deps.resolveIr(live.id);
271680
+ const result2 = await deps.seed(live, ir2);
271681
+ if (result2.seeded) {
271682
+ deps.kick();
271683
+ return;
271684
+ }
271685
+ if (result2.reason && QUIET_PARK_REASONS.has(result2.reason)) {
271686
+ deps.log(`Plan Review handoff for ${deps.taskId} not armed (${result2.reason})`);
271687
+ return;
271688
+ }
271689
+ failure = result2.reason ?? "not-seeded";
271690
+ } catch (error3) {
271691
+ failure = error3 instanceof Error ? error3.message : String(error3);
271692
+ }
271693
+ if (attempt >= RETRY_DELAYS_MS2.length) {
271694
+ deps.warn(
271695
+ `Plan Review handoff for ${deps.taskId} failed after ${attempt + 1} attempts (${failure}) \u2014 stranded-continuation self-healing will recover it`
271696
+ );
271697
+ return;
271698
+ }
271699
+ deps.warn(`Plan Review handoff for ${deps.taskId} did not seed (${failure}) \u2014 retrying`);
271700
+ await sleep10(RETRY_DELAYS_MS2[attempt]);
271701
+ }
271483
271702
  }
271484
271703
  async function drainDuePlanningContinuations(deps) {
271485
271704
  const items = await deps.listDue();
@@ -271614,7 +271833,7 @@ function stableNotificationJson(value2) {
271614
271833
  return `{${fields.join(",")}}`;
271615
271834
  }
271616
271835
  function buildCliAgentNotificationDedupeKey(input) {
271617
- const notificationFingerprint = createHash31("sha256").update(stableNotificationJson(input.info.notification ?? null)).digest("hex").slice(0, 16);
271836
+ const notificationFingerprint = createHash32("sha256").update(stableNotificationJson(input.info.notification ?? null)).digest("hex").slice(0, 16);
271618
271837
  const waitingEpoch = input.session?.updatedAt ?? "unknown-waiting-epoch";
271619
271838
  return [
271620
271839
  "cli-agent",
@@ -271769,6 +271988,8 @@ var init_in_process_runtime = __esm({
271769
271988
  messageStore;
271770
271989
  /** FNXC:TaskDeleteNotice 2026-07-26-16:10: identity-guarded teardown for the delete-notice mailbox seam. */
271771
271990
  unregisterTaskDeleteNoticeMailbox;
271991
+ /** FNXC:TaskRecommendations 2026-08-13-03:56: identity-guarded teardown for the store-scoped recommendation notice seam. */
271992
+ unregisterTaskRecommendationNoticeMailbox;
271772
271993
  chatStore;
271773
271994
  detachAgentLinkSync;
271774
271995
  /**
@@ -271827,7 +272048,8 @@ var init_in_process_runtime = __esm({
271827
272048
  createTaskStoreForBackend: createTaskStoreForBackend2,
271828
272049
  buildConsumerId: buildConsumerId2,
271829
272050
  createProjectScopedPluginMcpProvider: createProjectScopedPluginMcpProvider2,
271830
- registerTaskDeleteNoticeMailbox: registerTaskDeleteNoticeMailbox2
272051
+ registerTaskDeleteNoticeMailbox: registerTaskDeleteNoticeMailbox2,
272052
+ registerTaskRecommendationNoticeMailbox: registerTaskRecommendationNoticeMailbox2
271831
272053
  } = await Promise.resolve().then(() => (init_src2(), src_exports));
271832
272054
  if (this.config.externalTaskStore) {
271833
272055
  this.taskStore = this.config.externalTaskStore;
@@ -271886,6 +272108,10 @@ var init_in_process_runtime = __esm({
271886
272108
  this.taskStore,
271887
272109
  this.messageStore
271888
272110
  );
272111
+ this.unregisterTaskRecommendationNoticeMailbox = registerTaskRecommendationNoticeMailbox2(
272112
+ this.taskStore,
272113
+ this.messageStore
272114
+ );
271889
272115
  await yieldEventLoop4();
271890
272116
  const pluginLayer = this.taskStore.getAsyncLayer();
271891
272117
  if (!pluginLayer) {
@@ -272377,9 +272603,19 @@ var init_in_process_runtime = __esm({
272377
272603
  outcome: report2.outcome,
272378
272604
  getTask: (id) => Promise.resolve(this.taskStore.getTask(id)),
272379
272605
  resolveIr: (id) => resolveWorkflowIrForTask(this.taskStore, id),
272380
- seed: (task, ir2) => seedPreReleasePlanReviewContinuation(this.taskStore, task, ir2),
272606
+ /*
272607
+ FNXC:PlanningHandoffAtomicity 2026-08-13-03:49:
272608
+ The report carries the planning session's own durable work item id so the
272609
+ seeder can retire that exact predecessor row atomically with the successor
272610
+ install. Without it the seeder raced triage's finally-block terminal
272611
+ transition and bailed on its own caller (529 stranded cards in 18 days).
272612
+ */
272613
+ seed: (task, ir2) => seedPreReleasePlanReviewContinuation(this.taskStore, task, ir2, {
272614
+ retirePredecessorId: report2.planningWorkItemId
272615
+ }),
272381
272616
  kick: () => this.kickWorkflowContinuationProcessor(),
272382
- log: (message) => runtimeLog.log(message)
272617
+ log: (message) => runtimeLog.log(message),
272618
+ warn: (message) => runtimeLog.warn(message)
272383
272619
  }).catch((error3) => {
272384
272620
  runtimeLog.error(`Failed to start Todo plan review for ${t.id}:`, error3);
272385
272621
  });
@@ -272667,6 +272903,8 @@ var init_in_process_runtime = __esm({
272667
272903
  this.backendShutdown = void 0;
272668
272904
  this.unregisterTaskDeleteNoticeMailbox?.();
272669
272905
  this.unregisterTaskDeleteNoticeMailbox = void 0;
272906
+ this.unregisterTaskRecommendationNoticeMailbox?.();
272907
+ this.unregisterTaskRecommendationNoticeMailbox = void 0;
272670
272908
  let stopError;
272671
272909
  try {
272672
272910
  if (this.workflowContinuationTimer) {
@@ -280460,9 +280698,9 @@ import { access as access10, unlink as unlink11, writeFile as writeFile33 } from
280460
280698
  import net2 from "node:net";
280461
280699
  import { platform as platform3, tmpdir as tmpdir12 } from "node:os";
280462
280700
  import { join as join91 } from "node:path";
280463
- import { createHash as createHash32 } from "node:crypto";
280701
+ import { createHash as createHash33 } from "node:crypto";
280464
280702
  function projectHash(projectId2) {
280465
- return createHash32("sha1").update(projectId2).digest("hex").slice(0, 16);
280703
+ return createHash33("sha1").update(projectId2).digest("hex").slice(0, 16);
280466
280704
  }
280467
280705
  function computeEngineSocketPath(projectId2) {
280468
280706
  const hash3 = projectHash(projectId2);
@@ -300272,9 +300510,9 @@ __export(import_translate_service_exports, {
300272
300510
  selectEligibleItems: () => selectEligibleItems,
300273
300511
  translateImportItems: () => translateImportItems
300274
300512
  });
300275
- import { createHash as createHash33 } from "node:crypto";
300513
+ import { createHash as createHash34 } from "node:crypto";
300276
300514
  function hashSourceContent(title2, body) {
300277
- return createHash33("sha256").update(`${title2}\0${body}`).digest("hex");
300515
+ return createHash34("sha256").update(`${title2}\0${body}`).digest("hex");
300278
300516
  }
300279
300517
  function resolveTargetLocale(settingsLocale, requestLocale, globalLanguage) {
300280
300518
  if (isLocale(settingsLocale)) return settingsLocale;
@@ -307276,7 +307514,7 @@ var init_auth_middleware = __esm({
307276
307514
  });
307277
307515
 
307278
307516
  // ../dashboard/src/routes/register-task-workflow-routes.ts
307279
- import { createHash as createHash34 } from "node:crypto";
307517
+ import { createHash as createHash35 } from "node:crypto";
307280
307518
  import { createReadStream } from "node:fs";
307281
307519
  import { readFile as readFile53, stat as stat13 } from "node:fs/promises";
307282
307520
  import { join as join96 } from "node:path";
@@ -307682,7 +307920,7 @@ function buildWorkflowReviewItemId(task, result2, findingId, resolution) {
307682
307920
  findingId,
307683
307921
  resolution
307684
307922
  });
307685
- return `workflow-review-${createHash34("sha256").update(identity).digest("hex").slice(0, 24)}`;
307923
+ return `workflow-review-${createHash35("sha256").update(identity).digest("hex").slice(0, 24)}`;
307686
307924
  }
307687
307925
  async function buildWorkflowReviewItems(task, store) {
307688
307926
  const declaredTopLevelReviewResultSources = await getDeclaredTopLevelReviewResultSources(task, store);
@@ -308131,6 +308369,32 @@ function registerTaskWorkflowRoutes(ctx, deps) {
308131
308369
  rethrowAsApiError8(err);
308132
308370
  }
308133
308371
  });
308372
+ router.get("/tasks/recommendations", async (req, res) => {
308373
+ try {
308374
+ const { store: scopedStore } = await getProjectContext2(req);
308375
+ const parsePageNumber = (value2, name2) => {
308376
+ if (value2 === void 0) return void 0;
308377
+ if (typeof value2 !== "string" || value2.trim() === "" || !Number.isInteger(Number(value2)) || Number(value2) < 0) {
308378
+ throw badRequest(`${name2} must be a non-negative integer`);
308379
+ }
308380
+ return Number(value2);
308381
+ };
308382
+ const requestedLimit = parsePageNumber(req.query.limit, "limit");
308383
+ const offset = parsePageNumber(req.query.offset, "offset");
308384
+ if (requestedLimit === 0) throw badRequest("limit must be a positive integer");
308385
+ const limit2 = requestedLimit === void 0 ? void 0 : Math.min(200, requestedLimit);
308386
+ let completeColumns;
308387
+ try {
308388
+ completeColumns = await resolveProjectColumnsForRoles(scopedStore, ["complete"]);
308389
+ } catch {
308390
+ completeColumns = /* @__PURE__ */ new Set(["done"]);
308391
+ }
308392
+ res.json(await scopedStore.listTaskRecommendations({ completeColumns, limit: limit2, offset }));
308393
+ } catch (err) {
308394
+ if (err instanceof ApiError) throw err;
308395
+ rethrowAsApiError8(err);
308396
+ }
308397
+ });
308134
308398
  router.post("/tasks/duplicate-check", async (req, res) => {
308135
308399
  try {
308136
308400
  const { store: scopedStore } = await getProjectContext2(req);
@@ -357318,8 +357582,8 @@ var init_register_settings_sync_routes = __esm({
357318
357582
  exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
357319
357583
  version: 1
357320
357584
  };
357321
- const { createHash: createHash38 } = await import("node:crypto");
357322
- const checksum = createHash38("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
357585
+ const { createHash: createHash39 } = await import("node:crypto");
357586
+ const checksum = createHash39("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
357323
357587
  const localPeerInfo = await central.getLocalPeerInfo();
357324
357588
  await fetchFromRemoteNode(node, "/api/settings/sync-receive", {
357325
357589
  method: "POST",
@@ -357390,7 +357654,7 @@ var init_register_settings_sync_routes = __esm({
357390
357654
  });
357391
357655
  return;
357392
357656
  }
357393
- const { createHash: createHash38 } = await import("node:crypto");
357657
+ const { createHash: createHash39 } = await import("node:crypto");
357394
357658
  const exportedAt = (/* @__PURE__ */ new Date()).toISOString();
357395
357659
  const payloadWithoutChecksum = {
357396
357660
  global: remoteSettings.global,
@@ -357399,7 +357663,7 @@ var init_register_settings_sync_routes = __esm({
357399
357663
  exportedAt,
357400
357664
  version: 1
357401
357665
  };
357402
- const checksum = createHash38("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
357666
+ const checksum = createHash39("sha256").update(JSON.stringify(payloadWithoutChecksum)).digest("hex");
357403
357667
  const result2 = await central.applyRemoteSettings({
357404
357668
  ...payloadWithoutChecksum,
357405
357669
  checksum
@@ -384153,7 +384417,7 @@ var init_report_scrub = __esm({
384153
384417
  });
384154
384418
 
384155
384419
  // ../dashboard/src/report-pipeline.ts
384156
- import { createHash as createHash35 } from "node:crypto";
384420
+ import { createHash as createHash36 } from "node:crypto";
384157
384421
  function resolveReportMode(actionType, settings) {
384158
384422
  return settings.reportModeByAction?.[actionType] ?? settings.reportMode ?? "draft-review";
384159
384423
  }
@@ -384269,7 +384533,7 @@ async function embedScreenshot(args) {
384269
384533
  try {
384270
384534
  const extension = EXT_BY_MIME[attachment.mimeType];
384271
384535
  if (!extension) return args.report;
384272
- const safeId2 = createHash35("sha256").update(`${args.report.sessionToken ?? ""}:${attachment.artifactId}`).digest("hex").slice(0, 32);
384536
+ const safeId2 = createHash36("sha256").update(`${args.report.sessionToken ?? ""}:${attachment.artifactId}`).digest("hex").slice(0, 32);
384273
384537
  const path14 = `.fusion-reports/${safeId2}/screenshot.${extension}`;
384274
384538
  const uploaded = await args.client.uploadImageAsset({ owner: args.owner, repo: args.repo, path: path14, contentBase64: Buffer.from(attachment.bytes).toString("base64"), message: "chore: add Fusion report screenshot", mimeType: attachment.mimeType });
384275
384539
  const alt = markdownEscapeImageAlt(scrubReportText(attachment.filename, args.scrubContext) || "Report screenshot");
@@ -387383,7 +387647,7 @@ var init_types6 = __esm({
387383
387647
  });
387384
387648
 
387385
387649
  // ../dashboard/src/stt/model-manager.ts
387386
- import { createHash as createHash36, randomUUID as randomUUID68 } from "node:crypto";
387650
+ import { createHash as createHash37, randomUUID as randomUUID68 } from "node:crypto";
387387
387651
  import { mkdir as mkdir40, open as open4, readFile as readFile63, readdir as readdir16, rename as rename12, rm as rm24, stat as stat18, writeFile as writeFile39 } from "node:fs/promises";
387388
387652
  import { join as join125, relative as relative26, resolve as resolve75, sep as sep20 } from "node:path";
387389
387653
  function parseSafeTarListing(listing) {
@@ -387472,7 +387736,7 @@ function createVoiceModelManager(options = {}) {
387472
387736
  if (!response.ok || !response.body) throw Object.assign(new Error("download failed"), { voiceReason: "network" });
387473
387737
  if (!current(g2, signal)) return state;
387474
387738
  const total = Number(response.headers.get("content-length")) || void 0;
387475
- const hash3 = createHash36("sha256");
387739
+ const hash3 = createHash37("sha256");
387476
387740
  let bytes = 0;
387477
387741
  for await (const chunk of response.body) {
387478
387742
  if (!current(g2, signal)) return state;
@@ -402718,7 +402982,7 @@ var init_extension = __esm({
402718
402982
  });
402719
402983
 
402720
402984
  // src/commands/task-lifecycle.ts
402721
- import { createHash as createHash37 } from "node:crypto";
402985
+ import { createHash as createHash38 } from "node:crypto";
402722
402986
  import { exec as exec63 } from "node:child_process";
402723
402987
  import * as childProcess5 from "node:child_process";
402724
402988
  import { mkdir as mkdir43, realpath as realpath7, rm as rm25 } from "node:fs/promises";
@@ -402891,7 +403155,7 @@ async function refreshAutomatedPrHead(input) {
402891
403155
  await mkdir43(refreshWorktreesDir, { recursive: true });
402892
403156
  temporary = join135(
402893
403157
  refreshWorktreesDir,
402894
- `pr-refresh-${createHash37("sha256").update(input.headBranch).digest("hex").slice(0, 16)}`
403158
+ `pr-refresh-${createHash38("sha256").update(input.headBranch).digest("hex").slice(0, 16)}`
402895
403159
  );
402896
403160
  const reservationDir = join135(root, ".fusion", "pr-refresh-reservations");
402897
403161
  await mkdir43(reservationDir, { recursive: true });