@runfusion/fusion 0.74.0-beta.0 → 0.74.0-beta.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 (103) hide show
  1. package/dist/bin.js +9456 -8529
  2. package/dist/child-process-worker.js +223 -25
  3. package/dist/client/.vite/manifest.json +256 -256
  4. package/dist/client/assets/{AgentDetailView-CUXS0Odh.js → AgentDetailView-Bopzxiqb.js} +1 -1
  5. package/dist/client/assets/{AgentPermissionPolicyEditor-CzjHjB5r.js → AgentPermissionPolicyEditor-i5N7rUlL.js} +1 -1
  6. package/dist/client/assets/{AgentsView-B5stgI5m.js → AgentsView-M3GoJDHH.js} +3 -3
  7. package/dist/client/assets/ChatView-CqiyA9Kt.js +13 -0
  8. package/dist/client/assets/{CommandCenter-BdEQ_ZOX.js → CommandCenter-CAvMGKhl.js} +1 -1
  9. package/dist/client/assets/{DevServerView-BHgAmF3y.js → DevServerView-wqM6h3nV.js} +1 -1
  10. package/dist/client/assets/{DirectoryPicker-BQXVc5YW.js → DirectoryPicker-C8OyXCE-.js} +1 -1
  11. package/dist/client/assets/{DocumentsView-CxN0Ptrj.js → DocumentsView-CjNMZQi8.js} +1 -1
  12. package/dist/client/assets/{EvalsView-DAgJtpO5.js → EvalsView-BVlbuB5t.js} +1 -1
  13. package/dist/client/assets/{ExperimentalAgentOnboardingModal-NZcXkGrN.js → ExperimentalAgentOnboardingModal-CIwCg8sq.js} +1 -1
  14. package/dist/client/assets/{GoalsView-Dwq7NW4j.js → GoalsView-Cnw2igil.js} +1 -1
  15. package/dist/client/assets/{InsightsView-CUlL5NSC.js → InsightsView-D7GmD-Kx.js} +1 -1
  16. package/dist/client/assets/{MemoryView-WfHzJflp.js → MemoryView-BlqkfIE5.js} +1 -1
  17. package/dist/client/assets/{PiExtensionsManager-BS1dkauB.js → PiExtensionsManager-EC94zLMq.js} +1 -1
  18. package/dist/client/assets/{PluginManager-B1oLb0pn.js → PluginManager-pYyCD3uh.js} +1 -1
  19. package/dist/client/assets/{PullRequestView-Cdp662lx.js → PullRequestView-BAb0vVhu.js} +1 -1
  20. package/dist/client/assets/{ReportModal-C92wQO-k.js → ReportModal-COmReivA.js} +1 -1
  21. package/dist/client/assets/{ResearchView-4-SoxmQv.js → ResearchView-DLMo4rFw.js} +1 -1
  22. package/dist/client/assets/{SecretsView-C5UQ1Mms.js → SecretsView-w_O8NLfw.js} +1 -1
  23. package/dist/client/assets/{SessionTerminal-BTjX9p3a.js → SessionTerminal-CSPi-qWE.js} +1 -1
  24. package/dist/client/assets/{SettingsModal-B37HBcKT.css → SettingsModal-BF1KzZ0q.css} +1 -1
  25. package/dist/client/assets/SettingsModal-C9bVvYQq.js +21 -0
  26. package/dist/client/assets/{SettingsModal-BfHAE14s.js → SettingsModal-wilrGXvE.js} +1 -1
  27. package/dist/client/assets/{SettingsTextareaRow-C2JqzQdD.js → SettingsTextareaRow-Bd7_SxVv.js} +1 -1
  28. package/dist/client/assets/{SetupWizardModal-C1KmMVfU.js → SetupWizardModal-sPs6Lw3a.js} +2 -2
  29. package/dist/client/assets/{SkillsView-DyLNdfDy.js → SkillsView-B9PvYS8t.js} +1 -1
  30. package/dist/client/assets/{TodoView-DyrniX19.js → TodoView-Df0SD_JT.js} +1 -1
  31. package/dist/client/assets/{WorkflowNodeEditor-Bh-Hn1Kx.js → WorkflowNodeEditor-B3kFD-S0.js} +1 -1
  32. package/dist/client/assets/{agent-import-generation-C5pyefIj.js → agent-import-generation-OXfU1-D1.js} +1 -1
  33. package/dist/client/assets/{app-YM47G7Ks.js → app-DJIiVHdA.js} +1 -1
  34. package/dist/client/assets/{app-DNrauoYS.js → app-DMZ_RusZ.js} +3 -3
  35. package/dist/client/assets/{app-Bl764LdY.js → app-DaikOPcV.js} +3 -3
  36. package/dist/client/assets/{app-QmIotwFZ.js → app-Dn1u7zJ9.js} +3 -3
  37. package/dist/client/assets/{app-DdCnRHSe.js → app-DxTbBEMH.js} +3 -3
  38. package/dist/client/assets/{app-oUEtzFOj.js → app-hQw_x2KJ.js} +3 -3
  39. package/dist/client/assets/{architectureDiagram-3BPJPVTR-B6axGlaS.js → architectureDiagram-3BPJPVTR-B_8zk4ag.js} +1 -1
  40. package/dist/client/assets/{blockDiagram-GPEHLZMM-BENjJaZD.js → blockDiagram-GPEHLZMM-CpDKBJkO.js} +1 -1
  41. package/dist/client/assets/{c4Diagram-AAUBKEIU-Cl2qKhuO.js → c4Diagram-AAUBKEIU-D3wtfGzI.js} +1 -1
  42. package/dist/client/assets/channel-BLzjKIVz.js +1 -0
  43. package/dist/client/assets/{chunk-2J33WTMH-BWo7R8T7.js → chunk-2J33WTMH-DS9sHIMv.js} +1 -1
  44. package/dist/client/assets/{chunk-4BX2VUAB-DiRoI7pq.js → chunk-4BX2VUAB-B-xW6cfi.js} +1 -1
  45. package/dist/client/assets/{chunk-55IACEB6-DPtv1GVO.js → chunk-55IACEB6-Ddxf-sPU.js} +1 -1
  46. package/dist/client/assets/{chunk-727SXJPM-BVcm5ITs.js → chunk-727SXJPM-Bv_Z1w57.js} +1 -1
  47. package/dist/client/assets/{chunk-AQP2D5EJ-CFT4bmXK.js → chunk-AQP2D5EJ-DYHYN43E.js} +1 -1
  48. package/dist/client/assets/{chunk-FMBD7UC4-BDcEHQ0o.js → chunk-FMBD7UC4-wuuFvvBk.js} +1 -1
  49. package/dist/client/assets/{chunk-ND2GUHAM-CYlDBFdI.js → chunk-ND2GUHAM-DfVRkVZo.js} +1 -1
  50. package/dist/client/assets/{chunk-QZHKN3VN-3Wv3WxYt.js → chunk-QZHKN3VN-D0t6ryuh.js} +1 -1
  51. package/dist/client/assets/classDiagram-4FO5ZUOK-DkwPy8u9.js +1 -0
  52. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DkwPy8u9.js +1 -0
  53. package/dist/client/assets/{cose-bilkent-S5V4N54A-BuMkKBMC.js → cose-bilkent-S5V4N54A-DVlKqNFJ.js} +1 -1
  54. package/dist/client/assets/{dagre-BM42HDAG-D1P4z_OM.js → dagre-BM42HDAG-DTtiGxz-.js} +1 -1
  55. package/dist/client/assets/{dashboard-view-CcK2sH0v.js → dashboard-view-CLjYqvE9.js} +1 -1
  56. package/dist/client/assets/{dashboard-view-DaRKU5oU.js → dashboard-view-D-nDgSmP.js} +1 -1
  57. package/dist/client/assets/{dashboard-view-BgQBLynI.js → dashboard-view-DH99T6VJ.js} +1 -1
  58. package/dist/client/assets/{diagram-2AECGRRQ-hmY-reWF.js → diagram-2AECGRRQ-Tv-YxCWk.js} +1 -1
  59. package/dist/client/assets/{diagram-5GNKFQAL-CGfRqm_0.js → diagram-5GNKFQAL-DlAAPkGu.js} +1 -1
  60. package/dist/client/assets/{diagram-KO2AKTUF-BfKo4Jd4.js → diagram-KO2AKTUF-DisatTBB.js} +1 -1
  61. package/dist/client/assets/{diagram-LMA3HP47-BZqpNemd.js → diagram-LMA3HP47-CDrUxjzW.js} +1 -1
  62. package/dist/client/assets/{diagram-OG6HWLK6-CxLTV4DH.js → diagram-OG6HWLK6-BQ34a4Ej.js} +1 -1
  63. package/dist/client/assets/{erDiagram-TEJ5UH35-vw3tx2tK.js → erDiagram-TEJ5UH35-s-YHwAEU.js} +1 -1
  64. package/dist/client/assets/{flowDiagram-I6XJVG4X-OEm-Us2U.js → flowDiagram-I6XJVG4X-EpXzmG2d.js} +1 -1
  65. package/dist/client/assets/{folder-open-D_zgZHWi.js → folder-open-DN86xUly.js} +1 -1
  66. package/dist/client/assets/{ganttDiagram-6RSMTGT7-C5bc1DG1.js → ganttDiagram-6RSMTGT7-Dk2WaoX8.js} +1 -1
  67. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DEvcWRBq.js → gitGraphDiagram-PVQCEYII-CjchnKW0.js} +1 -1
  68. package/dist/client/assets/index-Dv-95s50.css +1 -0
  69. package/dist/client/assets/index-aGZ3DHQw.js +2671 -0
  70. package/dist/client/assets/{infoDiagram-5YYISTIA-kFOE9wxd.js → infoDiagram-5YYISTIA-CiU-twoj.js} +1 -1
  71. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DvLbzHmR.js → ishikawaDiagram-YF4QCWOH-BvylyUKK.js} +1 -1
  72. package/dist/client/assets/{journeyDiagram-JHISSGLW-ov6Tbcuz.js → journeyDiagram-JHISSGLW-Ca6z84KB.js} +1 -1
  73. package/dist/client/assets/{kanban-definition-UN3LZRKU-Dr0foW2M.js → kanban-definition-UN3LZRKU-DXbw2-s-.js} +1 -1
  74. package/dist/client/assets/{mermaid.core-CM55309V.js → mermaid.core-DkS33D12.js} +4 -4
  75. package/dist/client/assets/{mindmap-definition-RKZ34NQL-xNyB6Z6o.js → mindmap-definition-RKZ34NQL-BgXuWPDD.js} +1 -1
  76. package/dist/client/assets/{pieDiagram-4H26LBE5-DlS5Kpga.js → pieDiagram-4H26LBE5-CnOzTN6U.js} +1 -1
  77. package/dist/client/assets/{puzzle-BrEvi5DH.js → puzzle-Dxl1kcLF.js} +1 -1
  78. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-D4z2CHAA.js → quadrantDiagram-W4KKPZXB-DI3s1u95.js} +1 -1
  79. package/dist/client/assets/{requirementDiagram-4Y6WPE33-Bcwt6-p9.js → requirementDiagram-4Y6WPE33-Ck97mo-W.js} +1 -1
  80. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-C54N76yk.js → sankeyDiagram-5OEKKPKP-Z3w8NJac.js} +1 -1
  81. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-DtqryztE.js → sequenceDiagram-3UESZ5HK-DLGtWgfy.js} +1 -1
  82. package/dist/client/assets/{shield-alert-DhxKVKek.js → shield-alert-BCGxgBpM.js} +1 -1
  83. package/dist/client/assets/{standing-instructions-template-CdHJwmLI.js → standing-instructions-template-CUE6ItG6.js} +1 -1
  84. package/dist/client/assets/{stateDiagram-AJRCARHV-DQAvSrHw.js → stateDiagram-AJRCARHV-C6Zl2EJW.js} +1 -1
  85. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-BzIGKq7y.js → stateDiagram-v2-BHNVJYJU-BA77mDAW.js} +1 -1
  86. package/dist/client/assets/{timeline-definition-PNZ67QCA-GDj_48Um.js → timeline-definition-PNZ67QCA-D9Cp15Hm.js} +1 -1
  87. package/dist/client/assets/{upload-B7GKZiF1.js → upload-2jCicylS.js} +1 -1
  88. package/dist/client/assets/{users-CBtvFVLM.js → users-C1TTGAfX.js} +1 -1
  89. package/dist/client/assets/{vennDiagram-CIIHVFJN-PQDmLFbj.js → vennDiagram-CIIHVFJN-BwFSh6Tr.js} +1 -1
  90. package/dist/client/assets/{wardley-L42UT6IY-GmGtXctI.js → wardley-L42UT6IY-CQknAo24.js} +1 -1
  91. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-Bv6slmsW.js → wardleyDiagram-YWT4CUSO-Dwc8f8bd.js} +1 -1
  92. package/dist/client/assets/{xychartDiagram-2RQKCTM6-CK1RJHwB.js → xychartDiagram-2RQKCTM6-CPkeHv8g.js} +1 -1
  93. package/dist/client/index.html +2 -2
  94. package/dist/client/version.json +1 -1
  95. package/dist/extension.js +6436 -5628
  96. package/package.json +1 -1
  97. package/dist/client/assets/ChatView-CpoOgZ4X.js +0 -13
  98. package/dist/client/assets/SettingsModal-BcnOOo9g.js +0 -21
  99. package/dist/client/assets/channel-CktWdAhK.js +0 -1
  100. package/dist/client/assets/classDiagram-4FO5ZUOK-CrUS4gHO.js +0 -1
  101. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-CrUS4gHO.js +0 -1
  102. package/dist/client/assets/index-BinRUCFV.js +0 -2661
  103. package/dist/client/assets/index-DGmuyTtv.css +0 -1
@@ -1663,6 +1663,7 @@ var init_settings_schema = __esm({
1663
1663
  defaultProvider: void 0,
1664
1664
  defaultModelId: void 0,
1665
1665
  testMode: void 0,
1666
+ voiceInput: void 0,
1666
1667
  modelPricingOverrides: void 0,
1667
1668
  modelPricingFetchedAt: void 0,
1668
1669
  modelPricingSource: void 0,
@@ -1954,6 +1955,7 @@ var init_settings_schema = __esm({
1954
1955
  // with this on but auto-merge off, review threads are resolved but the PR is not merged.
1955
1956
  autoResolveReviewComments: true,
1956
1957
  testMode: void 0,
1958
+ voiceInput: void 0,
1957
1959
  mergeRequestContractShadowEnabled: false,
1958
1960
  mergeStrategy: "direct",
1959
1961
  directMergeCommitStrategy: "always-squash",
@@ -18387,9 +18389,13 @@ function buildRefinementSeedPrompt(title, description) {
18387
18389
  ${description}
18388
18390
  `;
18389
18391
  }
18392
+ function normalizeSeedText(content) {
18393
+ return content.replace(/\r\n/g, "\n").replace(/[ \t]+$/gm, "").trimEnd();
18394
+ }
18390
18395
  function isUnplannedSeedPrompt(content, taskId, title, description) {
18391
- if (content === buildBootstrapPrompt(taskId, title, description)) return true;
18392
- return title !== void 0 && content === buildRefinementSeedPrompt(title, description);
18396
+ const normalized = normalizeSeedText(content);
18397
+ if (normalized === normalizeSeedText(buildBootstrapPrompt(taskId, title, description))) return true;
18398
+ return title !== void 0 && normalized === normalizeSeedText(buildRefinementSeedPrompt(title, description));
18393
18399
  }
18394
18400
  var init_mesh_task_replication = __esm({
18395
18401
  "../core/src/mesh-task-replication.ts"() {
@@ -29650,9 +29656,11 @@ var init_agent_store = __esm({
29650
29656
  `Invalid state transition: ${currentState} -> ${newState}. Valid transitions: ${validTransitions.join(", ")}`
29651
29657
  );
29652
29658
  }
29659
+ const clearsPauseReasonOnResume = (currentState === "paused" || currentState === "error") && (newState === "active" || newState === "idle" || newState === "running");
29653
29660
  const updated = {
29654
29661
  ...agent,
29655
29662
  state: newState,
29663
+ ...clearsPauseReasonOnResume && { pauseReason: void 0 },
29656
29664
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29657
29665
  };
29658
29666
  await this.writeAgent(updated);
@@ -64738,11 +64746,18 @@ async function moveTaskInternalImpl(store, id, toColumn, options, internal, curr
64738
64746
  const isLegacyRecoveryRehome = options?.recoveryRehome === true && sourceIsLegacy && COLUMNS.includes(toColumn);
64739
64747
  const isWorkflowDeclaredRecoveryRehome = options?.recoveryRehome === true && !COLUMNS.includes(toColumn) && workflowHasColumn(await resolveTaskWorkflowIrForMove(store, id), toColumn);
64740
64748
  if (!isEvacuation && !isLegacyRecoveryRehome && !isWorkflowDeclaredRecoveryRehome) {
64741
- const validTargets = sourceIsLegacy ? VALID_TRANSITIONS[task.column] ?? [] : resolveAllowedColumns(await resolveTaskWorkflowIrForMove(store, id), task.column);
64742
- if (!validTargets.includes(toColumn)) {
64743
- throw new Error(
64744
- `Invalid transition: '${task.column}' \u2192 '${toColumn}'. Valid targets: ${validTargets.join(", ") || "none"}`
64749
+ const legacyTargets = sourceIsLegacy ? VALID_TRANSITIONS[task.column] ?? [] : [];
64750
+ if (!legacyTargets.includes(toColumn)) {
64751
+ const workflowTargets = resolveAllowedColumns(
64752
+ await resolveTaskWorkflowIrForMove(store, id),
64753
+ task.column
64745
64754
  );
64755
+ const validTargets = [.../* @__PURE__ */ new Set([...legacyTargets, ...workflowTargets])];
64756
+ if (!validTargets.includes(toColumn)) {
64757
+ throw new Error(
64758
+ `Invalid transition: '${task.column}' \u2192 '${toColumn}'. Valid targets: ${validTargets.join(", ") || "none"}`
64759
+ );
64760
+ }
64746
64761
  }
64747
64762
  }
64748
64763
  if (fromColumn === "in-review" && toColumn === "done" && !options?.skipMergeBlocker) {
@@ -87812,7 +87827,8 @@ async function _createTaskInternalBackendImpl(store, input, title, resolvedWorkf
87812
87827
  ownsStagingDirectory = true;
87813
87828
  if (store.isWatching) store.taskCache.set(id, { ...task });
87814
87829
  await store.writeTaskJsonFile(stagingDir, task);
87815
- const isIntakeColumn = task.column === "triage" || options?.resolvedEntryColumn !== void 0 && task.column === options.resolvedEntryColumn;
87830
+ const isUnplannedStartCreate = options?.resolvedEntryColumn !== void 0 && options.resolvedEntryColumn !== "triage" && task.column === "todo";
87831
+ const isIntakeColumn = task.column === "triage" || options?.resolvedEntryColumn !== void 0 && task.column === options.resolvedEntryColumn || isUnplannedStartCreate;
87816
87832
  const usedBootstrapPrompt = !options?.promptOverride && isIntakeColumn;
87817
87833
  const prompt = options?.promptOverride ?? (isIntakeColumn ? buildBootstrapPrompt(id, task.title, task.description) : store.generateSpecifiedPrompt(task));
87818
87834
  if (!usedBootstrapPrompt) {
@@ -88264,7 +88280,8 @@ async function _createTaskInternalImpl(store, input, title, resolvedWorkflowStep
88264
88280
  const dir = store.taskDir(id);
88265
88281
  await store.atomicCreateTaskJson(dir, task, "createTask");
88266
88282
  if (store.isWatching) store.taskCache.set(id, { ...task });
88267
- const isIntakeColumn = task.column === "triage" || options?.resolvedEntryColumn !== void 0 && task.column === options.resolvedEntryColumn;
88283
+ const isUnplannedStartCreate = options?.resolvedEntryColumn !== void 0 && options.resolvedEntryColumn !== "triage" && task.column === "todo";
88284
+ const isIntakeColumn = task.column === "triage" || options?.resolvedEntryColumn !== void 0 && task.column === options.resolvedEntryColumn || isUnplannedStartCreate;
88268
88285
  const usedBootstrapPrompt = !options?.promptOverride && isIntakeColumn;
88269
88286
  const prompt = options?.promptOverride ?? (isIntakeColumn ? buildBootstrapPrompt(id, task.title, task.description) : store.generateSpecifiedPrompt(task));
88270
88287
  if (!usedBootstrapPrompt) {
@@ -136153,15 +136170,26 @@ function countCapacitySlot(allTasks, effectiveWorkflowIdByTask, budgetColumns, w
136153
136170
  }
136154
136171
  return count2;
136155
136172
  }
136173
+ function trackHeld(taskId, reason, nowMs) {
136174
+ const existing = heldSince.get(taskId);
136175
+ if (!existing || existing.reason !== reason) {
136176
+ heldSince.set(taskId, { reason, sinceMs: nowMs });
136177
+ return 0;
136178
+ }
136179
+ return nowMs - existing.sinceMs;
136180
+ }
136156
136181
  async function runHoldReleaseSweep(store, deps) {
136157
136182
  const result = { released: [], held: [] };
136183
+ const sweepStartedMs = deps.now();
136158
136184
  const settings = await store.getSettings();
136159
136185
  const allTasks = await store.listTasks({ includeArchived: false });
136160
136186
  const irCache = /* @__PURE__ */ new Map();
136161
136187
  const effectiveWorkflowIdByTask = /* @__PURE__ */ new Map();
136188
+ const prefetchStartedMs = deps.now();
136162
136189
  for (const t of allTasks) {
136163
136190
  effectiveWorkflowIdByTask.set(t.id, await effectiveWorkflowId(store, t.id));
136164
136191
  }
136192
+ const prefetchMs = deps.now() - prefetchStartedMs;
136165
136193
  for (const task of allTasks) {
136166
136194
  if (task.paused || task.userPaused) {
136167
136195
  continue;
@@ -136176,6 +136204,7 @@ async function runHoldReleaseSweep(store, deps) {
136176
136204
  if (!column || !holdConfig) continue;
136177
136205
  const release2 = typeof holdConfig.release === "string" ? holdConfig.release : "manual";
136178
136206
  if (release2 === "manual" || release2 === "external-event") {
136207
+ trackHeld(task.id, `${release2}-only`, deps.now());
136179
136208
  result.held.push({ taskId: task.id, reason: `${release2}-only` });
136180
136209
  continue;
136181
136210
  }
@@ -136184,18 +136213,21 @@ async function runHoldReleaseSweep(store, deps) {
136184
136213
  const deadline = resolveTimerDeadline(holdConfig, task);
136185
136214
  shouldRelease = deadline !== void 0 && deps.now() >= deadline;
136186
136215
  if (!shouldRelease) {
136216
+ trackHeld(task.id, "timer-not-elapsed", deps.now());
136187
136217
  result.held.push({ taskId: task.id, reason: "timer-not-elapsed" });
136188
136218
  continue;
136189
136219
  }
136190
136220
  } else if (release2 === "dependency") {
136191
136221
  shouldRelease = await allDependenciesSatisfied(store, task, allTasks);
136192
136222
  if (!shouldRelease) {
136223
+ trackHeld(task.id, "deps-unsatisfied", deps.now());
136193
136224
  result.held.push({ taskId: task.id, reason: "deps-unsatisfied" });
136194
136225
  continue;
136195
136226
  }
136196
136227
  } else if (release2 === "capacity") {
136197
136228
  const target2 = resolveReleaseTarget(ir2, task.column, true);
136198
136229
  if (!target2) {
136230
+ trackHeld(task.id, "no-downstream-capacity-column", deps.now());
136199
136231
  result.held.push({ taskId: task.id, reason: "no-downstream-capacity-column" });
136200
136232
  continue;
136201
136233
  }
@@ -136205,6 +136237,7 @@ async function runHoldReleaseSweep(store, deps) {
136205
136237
  const budgetColumns = new Set(resolveWipBudgetColumns(ir2, target2));
136206
136238
  const occupants = countCapacitySlot(allTasks, effectiveWorkflowIdByTask, budgetColumns, workflowId, capacity.countPending);
136207
136239
  if (occupants >= capacity.limit) {
136240
+ trackHeld(task.id, "downstream-full", deps.now());
136208
136241
  result.held.push({ taskId: task.id, reason: "downstream-full" });
136209
136242
  continue;
136210
136243
  }
@@ -136214,16 +136247,40 @@ async function runHoldReleaseSweep(store, deps) {
136214
136247
  if (!shouldRelease) continue;
136215
136248
  const target = resolveReleaseTarget(ir2, task.column, release2 === "capacity");
136216
136249
  if (!target) {
136250
+ trackHeld(task.id, "no-release-target", deps.now());
136217
136251
  result.held.push({ taskId: task.id, reason: "no-release-target" });
136218
136252
  continue;
136219
136253
  }
136220
136254
  const released = await issueRelease(store, deps, task, target, ir2);
136221
136255
  if (released) {
136256
+ const waitedMs = deps.now() - (heldSince.get(task.id)?.sinceMs ?? deps.now());
136257
+ heldSince.delete(task.id);
136258
+ schedulerLog.log(
136259
+ `Hold release for ${task.id} \u2192 ${target} after ${waitedMs}ms held (release=${release2})`
136260
+ );
136222
136261
  result.released.push(task.id);
136223
136262
  } else {
136263
+ trackHeld(task.id, "move-rejected-or-no-slot", deps.now());
136224
136264
  result.held.push({ taskId: task.id, reason: "move-rejected-or-no-slot" });
136225
136265
  }
136226
136266
  }
136267
+ const sweepMs = deps.now() - sweepStartedMs;
136268
+ const longestHeldMs = result.held.reduce((max2, h) => {
136269
+ const entry = heldSince.get(h.taskId);
136270
+ return entry ? Math.max(max2, deps.now() - entry.sinceMs) : max2;
136271
+ }, 0);
136272
+ const summary = `Hold-release sweep: ${sweepMs}ms (prefetch ${prefetchMs}ms over ${allTasks.length} tasks), released=${result.released.length}, held=${result.held.length}` + (longestHeldMs > 0 ? `, longest held ${longestHeldMs}ms` : "");
136273
+ if (sweepMs >= SLOW_SWEEP_WARN_MS) {
136274
+ schedulerLog.warn(`${summary} \u2014 sweep exceeded ${SLOW_SWEEP_WARN_MS}ms and is itself the delay`);
136275
+ } else if (result.released.length > 0) {
136276
+ schedulerLog.log(summary);
136277
+ } else {
136278
+ schedulerLog.debug(summary);
136279
+ }
136280
+ const stillHeld = new Set(result.held.map((h) => h.taskId));
136281
+ for (const taskId of [...heldSince.keys()]) {
136282
+ if (!stillHeld.has(taskId)) heldSince.delete(taskId);
136283
+ }
136227
136284
  return result;
136228
136285
  }
136229
136286
  async function issueRelease(store, deps, task, target, ir2) {
@@ -136326,12 +136383,15 @@ async function releaseHeldTaskByEvent(store, taskId, eventTag, deps = {}) {
136326
136383
  );
136327
136384
  return released ? { released: true, toColumn: target } : { released: false, rejection: "capacity-exhausted-or-no-slot" };
136328
136385
  }
136386
+ var heldSince, SLOW_SWEEP_WARN_MS;
136329
136387
  var init_hold_release = __esm({
136330
136388
  "../engine/src/hold-release.ts"() {
136331
136389
  "use strict";
136332
136390
  init_src2();
136333
136391
  init_logger();
136334
136392
  init_spec_staleness();
136393
+ heldSince = /* @__PURE__ */ new Map();
136394
+ SLOW_SWEEP_WARN_MS = 2e3;
136335
136395
  }
136336
136396
  });
136337
136397
 
@@ -191897,6 +191957,15 @@ var Scheduler = class {
191897
191957
  this.schedule();
191898
191958
  }
191899
191959
  }
191960
+ if (task.status === "planning") {
191961
+ this.planningTaskIds.add(task.id);
191962
+ } else if (this.planningTaskIds.has(task.id)) {
191963
+ this.planningTaskIds.delete(task.id);
191964
+ if (this.running && !task.status && !task.paused && !task.userPaused && (task.column === "todo" || task.column === "triage")) {
191965
+ schedulerLog.log(`Task ${task.id} finished planning \u2014 triggering scheduling`);
191966
+ this.schedule();
191967
+ }
191968
+ }
191900
191969
  if (!this.options.prMonitor) return;
191901
191970
  if (task.column !== "in-review") return;
191902
191971
  if (!task.prInfo) return;
@@ -191914,6 +191983,7 @@ var Scheduler = class {
191914
191983
  this.lastAutoClaimFingerprint.delete(task.id);
191915
191984
  this.options.snapshotManager?.invalidate("task:deleted");
191916
191985
  this.pausedTaskIds.delete(task.id);
191986
+ this.planningTaskIds.delete(task.id);
191917
191987
  this.failedTaskIds.delete(task.id);
191918
191988
  this.recentEngineTodoRequeues.delete(task.id);
191919
191989
  this.wasNodeDispatchValidationBlocked.delete(task.id);
@@ -191986,6 +192056,13 @@ var Scheduler = class {
191986
192056
  activePollMs = null;
191987
192057
  /** Tracks which task IDs are currently paused, to detect unpause transitions. */
191988
192058
  pausedTaskIds = /* @__PURE__ */ new Set();
192059
+ /**
192060
+ * FNXC:CodingIdeasWorkflow 2026-07-25-13:10:
192061
+ * Tracks task IDs last seen with status "planning", so the planning -> dispatchable transition
192062
+ * can trigger an immediate scheduling pass. Plan-in-place workflows clear status without a
192063
+ * task:moved, so this is the only signal that the card just became executable.
192064
+ */
192065
+ planningTaskIds = /* @__PURE__ */ new Set();
191989
192066
  /** Tracks mission-linked tasks observed with status=failed before moveTask clears status/error. */
191990
192067
  failedTaskIds = /* @__PURE__ */ new Set();
191991
192068
  /** Tracks tasks blocked by unavailable-node policy to deduplicate block log entries. */
@@ -192405,7 +192482,7 @@ var Scheduler = class {
192405
192482
  todo.map(async (t) => {
192406
192483
  try {
192407
192484
  const content = await readFile30(getPromptPath(this.store.getTasksDir(), t.id), "utf-8");
192408
- if (content === buildBootstrapPrompt(t.id, t.title, t.description)) return null;
192485
+ if (isUnplannedSeedPrompt(content, t.id, t.title, t.description)) return null;
192409
192486
  } catch {
192410
192487
  }
192411
192488
  return t;
@@ -202218,6 +202295,44 @@ function renderRoomCoordinationPromptBlock(decision, pendingMessage) {
202218
202295
  ];
202219
202296
  }
202220
202297
 
202298
+ // ../engine/src/reports-health.ts
202299
+ var OPERATOR_ACTIONABLE_PAUSE_REASONS = /* @__PURE__ */ new Set([
202300
+ "error-unrecoverable",
202301
+ "error-retry-exhausted",
202302
+ "heartbeat-model-unavailable"
202303
+ ]);
202304
+ function classifyReportHealth(input) {
202305
+ const pauseReason = input.pauseReason?.trim();
202306
+ if (pauseReason) {
202307
+ if (OPERATOR_ACTIONABLE_PAUSE_REASONS.has(pauseReason)) {
202308
+ return {
202309
+ bucket: "operator-actionable",
202310
+ cellText: `**needs operator repair** (${pauseReason})`
202311
+ };
202312
+ }
202313
+ return {
202314
+ bucket: "paused",
202315
+ cellText: `paused (${pauseReason})`
202316
+ };
202317
+ }
202318
+ if (input.state === "error") {
202319
+ return { bucket: "operator-actionable", cellText: "**needs operator repair**" };
202320
+ }
202321
+ if (input.staleParkedAssignment) {
202322
+ return { bucket: "stale-assignment", cellText: "**stale** assignment" };
202323
+ }
202324
+ if (input.state === "running" && input.heartbeatAgeMs > input.heartbeatTimeoutMs * 2) {
202325
+ return { bucket: "stuck", cellText: "**stuck**" };
202326
+ }
202327
+ if ((input.state === "active" || input.state === "idle") && input.heartbeatAgeMs > input.staleThresholdMs) {
202328
+ return { bucket: "stale", cellText: "**stale**" };
202329
+ }
202330
+ if (input.state === "paused") {
202331
+ return { bucket: "paused", cellText: "paused" };
202332
+ }
202333
+ return { bucket: "healthy", cellText: "healthy" };
202334
+ }
202335
+
202221
202336
  // ../engine/src/agent-heartbeat.ts
202222
202337
  init_session_token_usage();
202223
202338
 
@@ -205323,26 +205438,28 @@ ${trimPromptMd(taskDetail.prompt, promptTemplate)}` : "No PROMPT.md available.",
205323
205438
  heartbeatLog.warn(`[reports-health] failed to validate task link for ${report.id}/${report.taskId}: ${error2 instanceof Error ? error2.message : String(error2)}`);
205324
205439
  }
205325
205440
  }
205326
- let health = "healthy";
205327
- if (staleParkedAssignment) {
205328
- health = "**stale** assignment";
205329
- } else if (report.state === "paused") {
205330
- health = report.pauseReason ? `paused (${report.pauseReason})` : "paused";
205331
- } else if (report.state === "error") {
205332
- health = "**stuck**";
205333
- } else if (report.state === "running") {
205334
- health = heartbeatAgeMs <= heartbeatTimeoutMs * 2 ? "healthy" : "**stuck**";
205335
- } else if ((report.state === "active" || report.state === "idle") && heartbeatAgeMs > staleThresholdMs) {
205336
- health = "**stale**";
205441
+ const classification = classifyReportHealth({
205442
+ state: report.state,
205443
+ pauseReason: report.pauseReason,
205444
+ heartbeatAgeMs,
205445
+ heartbeatTimeoutMs,
205446
+ staleThresholdMs,
205447
+ staleParkedAssignment
205448
+ });
205449
+ if (classification.bucket === "stale") {
205337
205450
  heartbeatLog.log(`[reports-health] stale report ${report.id} intervalSource=${intervalSource} staleThresholdMs=${staleThresholdMs} heartbeatAgeMs=${heartbeatAgeMs}`);
205338
205451
  }
205339
205452
  const task = renderedTask;
205340
205453
  const state = renderedState;
205341
205454
  const heartbeat = formatRelativeTime(report.lastHeartbeatAt);
205342
- return `| ${report.name} | ${state} | ${task} | ${heartbeat} | ${health} |`;
205455
+ return {
205456
+ classification,
205457
+ row: `| ${report.name} | ${state} | ${task} | ${heartbeat} | ${classification.cellText} |`
205458
+ };
205343
205459
  }));
205344
- const hasStuck = rows.some((row) => row.includes("**stuck**"));
205345
- const hasStale = rows.some((row) => row.includes("**stale**"));
205460
+ const hasStuck = rows.some(({ classification }) => classification.bucket === "stuck");
205461
+ const hasStale = rows.some(({ classification }) => classification.bucket === "stale" || classification.bucket === "stale-assignment");
205462
+ const hasOperatorActionable = rows.some(({ classification }) => classification.bucket === "operator-actionable");
205346
205463
  const actionLines = ["### Actions for Unresponsive Reports"];
205347
205464
  if (hasStuck) {
205348
205465
  actionLines.push("- For **stuck** reports: first check task-log/step progress, the active heartbeat run, and worktree/session liveness. If work is live, do not stop or reassign it; request status if needed. Reassign only after no live session and no forward progress are confirmed.");
@@ -205350,6 +205467,9 @@ ${trimPromptMd(taskDetail.prompt, promptTemplate)}` : "No PROMPT.md available.",
205350
205467
  if (hasStale) {
205351
205468
  actionLines.push("- For **stale** reports: the agent may have lost its heartbeat trigger \u2014 create a follow-up task to investigate.");
205352
205469
  }
205470
+ if (hasOperatorActionable) {
205471
+ actionLines.push("- For reports that **need operator repair**: notify the operator and create a follow-up task; do not reassign work until the parked agent's configuration or access issue is repaired.");
205472
+ }
205353
205473
  return [
205354
205474
  "## Reports Health Check",
205355
205475
  "",
@@ -205357,7 +205477,7 @@ ${trimPromptMd(taskDetail.prompt, promptTemplate)}` : "No PROMPT.md available.",
205357
205477
  "",
205358
205478
  "| Name | State | Task | Last Heartbeat | Health |",
205359
205479
  "|------|-------|------|----------------|--------|",
205360
- ...rows,
205480
+ ...rows.map(({ row }) => row),
205361
205481
  "",
205362
205482
  ...actionLines
205363
205483
  ].join("\n");
@@ -241396,12 +241516,30 @@ var TriageProcessor = class _TriageProcessor {
241396
241516
  this.activeSessions.delete(task.id);
241397
241517
  }
241398
241518
  };
241519
+ this.taskColumnWakeHandler = (task) => {
241520
+ if (!task?.id) return;
241521
+ if (task.column !== "todo" && task.column !== "triage") return;
241522
+ if (task.paused === true || task.userPaused === true) return;
241523
+ if (this.processing.has(task.id) || this.hasLivePlanningWork(task.id)) return;
241524
+ this.requestImmediatePoll();
241525
+ };
241399
241526
  }
241400
241527
  running = false;
241401
241528
  polling = false;
241402
241529
  pollInterval = null;
241403
241530
  /** The interval (ms) of the currently active `setInterval` timer. */
241404
241531
  activePollMs = null;
241532
+ /*
241533
+ FNXC:CodingIdeasWorkflow 2026-07-25-11:20:
241534
+ Event-wake state for requestImmediatePoll(). Planning discovery is timer-driven, so pressing
241535
+ Start on an Ideas card (which only writes a column change) used to wait out the remainder of the
241536
+ poll interval — up to pollIntervalMs, 15s by default — before anything even looked at the card.
241537
+ `nudgeTimer` debounces a burst of moves into one poll; `nudgeDuringPoll` remembers a nudge that
241538
+ arrived while a poll was already in flight, since that poll may have snapshotted the task list
241539
+ before the move landed and would otherwise drop the wake entirely.
241540
+ */
241541
+ nudgeTimer = null;
241542
+ nudgeDuringPoll = false;
241405
241543
  processing = /* @__PURE__ */ new Set();
241406
241544
  /** Synchronous ownership fence shared with advanced-triage self-healing. */
241407
241545
  advancedRecoveryReservations = /* @__PURE__ */ new Set();
@@ -241441,6 +241579,8 @@ var TriageProcessor = class _TriageProcessor {
241441
241579
  stuckAborted = /* @__PURE__ */ new Set();
241442
241580
  taskDeletedHandler;
241443
241581
  taskPausedHandler;
241582
+ /** FNXC:CodingIdeasWorkflow 2026-07-25-11:20: store-event wake for planning-eligible columns. */
241583
+ taskColumnWakeHandler;
241444
241584
  _approvalRequestStore;
241445
241585
  /**
241446
241586
  * @param store — Task store instance (also used to listen for `settings:updated` events)
@@ -241545,6 +241685,10 @@ var TriageProcessor = class _TriageProcessor {
241545
241685
  if (this.taskPausedHandler && typeof this.store.on === "function") {
241546
241686
  this.store.on("task:updated", this.taskPausedHandler);
241547
241687
  }
241688
+ if (this.taskColumnWakeHandler && typeof this.store.on === "function") {
241689
+ this.store.on("task:updated", this.taskColumnWakeHandler);
241690
+ this.store.on("task:created", this.taskColumnWakeHandler);
241691
+ }
241548
241692
  this.clearStaleSpecifyingStatuses().catch((err) => {
241549
241693
  planLog.error("Failed to clear stale planning statuses:", err);
241550
241694
  });
@@ -241577,12 +241721,21 @@ var TriageProcessor = class _TriageProcessor {
241577
241721
  this.pollInterval = null;
241578
241722
  this.activePollMs = null;
241579
241723
  }
241724
+ if (this.nudgeTimer) {
241725
+ clearTimeout(this.nudgeTimer);
241726
+ this.nudgeTimer = null;
241727
+ }
241728
+ this.nudgeDuringPoll = false;
241580
241729
  if (this.taskDeletedHandler && typeof this.store.off === "function") {
241581
241730
  this.store.off("task:deleted", this.taskDeletedHandler);
241582
241731
  }
241583
241732
  if (this.taskPausedHandler && typeof this.store.off === "function") {
241584
241733
  this.store.off("task:updated", this.taskPausedHandler);
241585
241734
  }
241735
+ if (this.taskColumnWakeHandler && typeof this.store.off === "function") {
241736
+ this.store.off("task:updated", this.taskColumnWakeHandler);
241737
+ this.store.off("task:created", this.taskColumnWakeHandler);
241738
+ }
241586
241739
  this.abortAndDisposeActiveSessions("engine stop");
241587
241740
  planLog.log("Processor stopped");
241588
241741
  }
@@ -241990,7 +242143,17 @@ var TriageProcessor = class _TriageProcessor {
241990
242143
  if (isUnplannedSeedPrompt(content, todoTask.id, todoTask.title, todoTask.description)) {
241991
242144
  eligibleTodoTasks.push(todoTask);
241992
242145
  }
241993
- } catch {
242146
+ } catch (err) {
242147
+ if (err?.code === "ENOENT") {
242148
+ planLog.warn(
242149
+ `${todoTask.id}: PROMPT.md is missing \u2014 treating as unplanned and admitting it for planning`
242150
+ );
242151
+ eligibleTodoTasks.push(todoTask);
242152
+ } else {
242153
+ planLog.warn(
242154
+ `${todoTask.id}: PROMPT.md unreadable (${err?.code ?? "unknown"}) \u2014 skipping planning discovery for this poll`
242155
+ );
242156
+ }
241994
242157
  }
241995
242158
  }
241996
242159
  return [...eligibleTriageTasks, ...eligibleTodoTasks].sort((a, b3) => {
@@ -242004,10 +242167,41 @@ var TriageProcessor = class _TriageProcessor {
242004
242167
  return numeric2 !== 0 ? numeric2 : a.id.localeCompare(b3.id);
242005
242168
  });
242006
242169
  }
242170
+ /**
242171
+ * Run a planning-discovery poll now instead of waiting for the next timer tick.
242172
+ *
242173
+ * FNXC:CodingIdeasWorkflow 2026-07-25-11:20:
242174
+ * Requirement: starting a task must begin planning immediately, not "within 15 seconds".
242175
+ * The Start affordance on an intake (Ideas) card performs a bare column move — there is no
242176
+ * dispatch call in that path — so planning only began when the next `setInterval` tick happened
242177
+ * to fire. The store-event wake (taskColumnWakeHandler) is the primary caller.
242178
+ *
242179
+ * Contract: advisory and idempotent. It never plans a task by itself, it only advances WHEN the
242180
+ * existing poll runs — every pause, seed-prompt, dependency, and concurrency gate still applies,
242181
+ * so a nudge on a capacity-blocked card is a no-op rather than an admission bypass. Returns false
242182
+ * when the processor is not running.
242183
+ */
242184
+ requestImmediatePoll() {
242185
+ if (!this.running) return false;
242186
+ if (this.polling) {
242187
+ this.nudgeDuringPoll = true;
242188
+ return true;
242189
+ }
242190
+ if (this.nudgeTimer) return true;
242191
+ this.nudgeTimer = setTimeout(() => {
242192
+ this.nudgeTimer = null;
242193
+ void this.poll();
242194
+ }, _TriageProcessor.NUDGE_DEBOUNCE_MS);
242195
+ this.nudgeTimer.unref?.();
242196
+ return true;
242197
+ }
242198
+ /** Coalescing window for requestImmediatePoll, so a multi-card drag causes one poll, not N. */
242199
+ static NUDGE_DEBOUNCE_MS = 150;
242007
242200
  async poll() {
242008
242201
  if (!this.running) return;
242009
242202
  if (this.polling) return;
242010
242203
  this.polling = true;
242204
+ this.nudgeDuringPoll = false;
242011
242205
  try {
242012
242206
  const settings = await this.store.getSettings();
242013
242207
  this.refreshPollInterval(settings.pollIntervalMs);
@@ -242113,6 +242307,10 @@ var TriageProcessor = class _TriageProcessor {
242113
242307
  planLog.error("Poll error:", err);
242114
242308
  } finally {
242115
242309
  this.polling = false;
242310
+ if (this.nudgeDuringPoll && this.running) {
242311
+ this.nudgeDuringPoll = false;
242312
+ this.requestImmediatePoll();
242313
+ }
242116
242314
  }
242117
242315
  }
242118
242316
  async backfillBlankTitleAfterTerminalTriageFailure(task) {