@runfusion/fusion 0.47.0 → 0.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/bin.js +153 -85
  2. package/dist/client/.vite/manifest.json +242 -242
  3. package/dist/client/assets/{AgentDetailView-D9lYcdOS.js → AgentDetailView-CF6isJ51.js} +1 -1
  4. package/dist/client/assets/{AgentPermissionPolicyEditor-Bn_PEeQW.js → AgentPermissionPolicyEditor-DFldf6aS.js} +1 -1
  5. package/dist/client/assets/{AgentsView-BRbnFmv3.js → AgentsView-DiGseTuo.js} +3 -3
  6. package/dist/client/assets/{ChatView-Batci7-w.js → ChatView-BjQBbDyo.js} +1 -1
  7. package/dist/client/assets/{CommandCenter-DAUgvVMX.js → CommandCenter-DwpiADSX.js} +1 -1
  8. package/dist/client/assets/{DevServerView-HdEXR5UB.js → DevServerView-D6RFaGPU.js} +1 -1
  9. package/dist/client/assets/{DirectoryPicker-DrCiUJlF.js → DirectoryPicker-DNBB-viz.js} +1 -1
  10. package/dist/client/assets/{DocumentsView-C0KxPWex.js → DocumentsView-DXG-sctG.js} +1 -1
  11. package/dist/client/assets/{EvalsView-wCfi9Rkj.js → EvalsView-Ol6ECKz5.js} +1 -1
  12. package/dist/client/assets/{ExperimentalAgentOnboardingModal-Crck6eJe.js → ExperimentalAgentOnboardingModal-BQc3GaE_.js} +1 -1
  13. package/dist/client/assets/{GoalsView-sFGHX3Wt.js → GoalsView-C_m90anC.js} +1 -1
  14. package/dist/client/assets/{InsightsView-jl0cX1um.js → InsightsView-BbFgPsDH.js} +1 -1
  15. package/dist/client/assets/{MemoryView-Bwv99M_s.js → MemoryView-CRS7AjWK.js} +1 -1
  16. package/dist/client/assets/{PiExtensionsManager-BsA3tCZh.js → PiExtensionsManager-BEj5xu45.js} +1 -1
  17. package/dist/client/assets/{PluginManager-Bgd8Hj0a.js → PluginManager-CzCM3XUY.js} +1 -1
  18. package/dist/client/assets/{PullRequestView-CqO16SNt.js → PullRequestView-9yNYCunb.js} +1 -1
  19. package/dist/client/assets/{ResearchView-CfRsbJWS.js → ResearchView-BKdirpiw.js} +1 -1
  20. package/dist/client/assets/{SecretsView-CHomYGnm.js → SecretsView-qGjo-0Cp.js} +1 -1
  21. package/dist/client/assets/{SessionTerminal-DcYUuwbV.js → SessionTerminal-C7YlkdGJ.js} +1 -1
  22. package/dist/client/assets/{SettingsModal-CX5GLed_.js → SettingsModal-DihNd7g8.js} +1 -1
  23. package/dist/client/assets/{SettingsModal-ybSf6HDn.js → SettingsModal-DpaptT6t.js} +3 -3
  24. package/dist/client/assets/{SetupWizardModal-D2BQL0df.js → SetupWizardModal-MovTj5pY.js} +2 -2
  25. package/dist/client/assets/{ShadcnColorPicker-B-8x0Gci.js → ShadcnColorPicker-Bnf5n5R4.js} +1 -1
  26. package/dist/client/assets/{SkillsView-4iqYHf9N.js → SkillsView-Br2LkA3x.js} +1 -1
  27. package/dist/client/assets/{TodoView-BJsknVS6.js → TodoView-BEdk6FTU.js} +1 -1
  28. package/dist/client/assets/{WorkflowNodeEditor-QdwW4bY5.js → WorkflowNodeEditor-e-eR6yzw.js} +1 -1
  29. package/dist/client/assets/{agentSkills-CwR8s-gl.js → agentSkills-D5nDZPRY.js} +1 -1
  30. package/dist/client/assets/{app-BJGkp7Tt.js → app-BM6bAO0b.js} +1 -1
  31. package/dist/client/assets/{app-CZQ_fFNf.js → app-BglOELbc.js} +1 -1
  32. package/dist/client/assets/{app-BaoNC9zg.js → app-ChkCVQpq.js} +1 -1
  33. package/dist/client/assets/{app-DgezOJub.js → app-DMn7Jt9g.js} +1 -1
  34. package/dist/client/assets/{app-DAu3AzsS.js → app-D_IT9Buz.js} +1 -1
  35. package/dist/client/assets/{app-BKLUvvm8.js → app-S8fehQv8.js} +1 -1
  36. package/dist/client/assets/{architectureDiagram-3BPJPVTR-rav0thhm.js → architectureDiagram-3BPJPVTR-BbkXWBni.js} +1 -1
  37. package/dist/client/assets/{blockDiagram-GPEHLZMM-x9Opikul.js → blockDiagram-GPEHLZMM-DhaqpHqZ.js} +1 -1
  38. package/dist/client/assets/{c4Diagram-AAUBKEIU-BGrWtNPI.js → c4Diagram-AAUBKEIU-qC0WSHMZ.js} +1 -1
  39. package/dist/client/assets/channel-CCKvUami.js +1 -0
  40. package/dist/client/assets/{chunk-2J33WTMH-BMFvL-OW.js → chunk-2J33WTMH-Dah9zMeT.js} +1 -1
  41. package/dist/client/assets/{chunk-4BX2VUAB-CWTOrH7n.js → chunk-4BX2VUAB-epHwnUb_.js} +1 -1
  42. package/dist/client/assets/{chunk-55IACEB6-l48k3a4l.js → chunk-55IACEB6-DpYBy44g.js} +1 -1
  43. package/dist/client/assets/{chunk-727SXJPM-DeynG6ym.js → chunk-727SXJPM-B9b8698e.js} +1 -1
  44. package/dist/client/assets/{chunk-AQP2D5EJ-B9ndyq3h.js → chunk-AQP2D5EJ-DrM8nnhR.js} +1 -1
  45. package/dist/client/assets/{chunk-FMBD7UC4-DV2d1-q1.js → chunk-FMBD7UC4-jyT6Scwe.js} +1 -1
  46. package/dist/client/assets/{chunk-ND2GUHAM-D0Dl37Mc.js → chunk-ND2GUHAM-EoPfF-PK.js} +1 -1
  47. package/dist/client/assets/{chunk-QZHKN3VN-NGiOWxhM.js → chunk-QZHKN3VN-qMwLVfgJ.js} +1 -1
  48. package/dist/client/assets/classDiagram-4FO5ZUOK-C_HqY7AI.js +1 -0
  49. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-C_HqY7AI.js +1 -0
  50. package/dist/client/assets/{cose-bilkent-S5V4N54A-2ojNpRS8.js → cose-bilkent-S5V4N54A-DmPFKoVD.js} +1 -1
  51. package/dist/client/assets/{dagre-BM42HDAG-BaIP71Bo.js → dagre-BM42HDAG-DeHxd-ej.js} +1 -1
  52. package/dist/client/assets/{dashboard-view-hB7ta6sK.js → dashboard-view-Cw5mMJay.js} +1 -1
  53. package/dist/client/assets/{dashboard-view-CC2QtNhx.js → dashboard-view-DXjNQ5IF.js} +1 -1
  54. package/dist/client/assets/{diagram-2AECGRRQ-DQfEScya.js → diagram-2AECGRRQ-C04A2mhr.js} +1 -1
  55. package/dist/client/assets/{diagram-5GNKFQAL-B5iw3WNT.js → diagram-5GNKFQAL-DqtOwADI.js} +1 -1
  56. package/dist/client/assets/{diagram-KO2AKTUF-C5mMn4JH.js → diagram-KO2AKTUF-CH0har-Z.js} +1 -1
  57. package/dist/client/assets/{diagram-LMA3HP47-CaMhdmPl.js → diagram-LMA3HP47-CxN-U-0W.js} +1 -1
  58. package/dist/client/assets/{diagram-OG6HWLK6-D2ed8-Fx.js → diagram-OG6HWLK6-42A7wpwF.js} +1 -1
  59. package/dist/client/assets/{erDiagram-TEJ5UH35-CZl07SyK.js → erDiagram-TEJ5UH35-CnZI5YlN.js} +1 -1
  60. package/dist/client/assets/{flowDiagram-I6XJVG4X-Dz7RLhaG.js → flowDiagram-I6XJVG4X-BKfQhMVF.js} +1 -1
  61. package/dist/client/assets/{folder-open-DCNfEqr1.js → folder-open-STXgbemw.js} +1 -1
  62. package/dist/client/assets/{ganttDiagram-6RSMTGT7-C3ny-S6Q.js → ganttDiagram-6RSMTGT7-BfA0pME8.js} +1 -1
  63. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-BjHF5C7t.js → gitGraphDiagram-PVQCEYII-BgDFB7PS.js} +1 -1
  64. package/dist/client/assets/index-BnfnLXyO.css +1 -0
  65. package/dist/client/assets/index-CHy0WNjH.js +1301 -0
  66. package/dist/client/assets/{infoDiagram-5YYISTIA-BTYecWEx.js → infoDiagram-5YYISTIA-Bb5F155s.js} +1 -1
  67. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-BBNOAxJa.js → ishikawaDiagram-YF4QCWOH-8Oati7l_.js} +1 -1
  68. package/dist/client/assets/{journeyDiagram-JHISSGLW-DrjdbyT5.js → journeyDiagram-JHISSGLW-DhHTHXTM.js} +1 -1
  69. package/dist/client/assets/{kanban-definition-UN3LZRKU-CgTNTfT7.js → kanban-definition-UN3LZRKU-BWB9EtUX.js} +1 -1
  70. package/dist/client/assets/{mermaid.core-CyFmJxTj.js → mermaid.core-BD-L2gUQ.js} +4 -4
  71. package/dist/client/assets/{mindmap-definition-RKZ34NQL-C95r0KZv.js → mindmap-definition-RKZ34NQL-BSKGd7LE.js} +1 -1
  72. package/dist/client/assets/{pieDiagram-4H26LBE5-CmYuIsZ-.js → pieDiagram-4H26LBE5-BtBFPvSW.js} +1 -1
  73. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BFLFwHMa.js → quadrantDiagram-W4KKPZXB-aWQWYPto.js} +1 -1
  74. package/dist/client/assets/{requirementDiagram-4Y6WPE33-G0s0AfQq.js → requirementDiagram-4Y6WPE33-CprlEpIa.js} +1 -1
  75. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-D8OLAFhm.js → sankeyDiagram-5OEKKPKP-Cw-AcRAV.js} +1 -1
  76. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-6fDSosvV.js → sequenceDiagram-3UESZ5HK-BMW5slyv.js} +1 -1
  77. package/dist/client/assets/{shield-alert-CBAzSycj.js → shield-alert-Bzf8yNOc.js} +1 -1
  78. package/dist/client/assets/{stateDiagram-AJRCARHV-BfNs5lJU.js → stateDiagram-AJRCARHV-CcxvIVwM.js} +1 -1
  79. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Bt0-CBqz.js → stateDiagram-v2-BHNVJYJU-awOfoFOB.js} +1 -1
  80. package/dist/client/assets/{timeline-definition-PNZ67QCA-DmKYLILn.js → timeline-definition-PNZ67QCA-DIEAc2YP.js} +1 -1
  81. package/dist/client/assets/{upload--9MWYfcA.js → upload-DuwyPTKe.js} +1 -1
  82. package/dist/client/assets/{users-vOsWas_i.js → users-D1Qhfyro.js} +1 -1
  83. package/dist/client/assets/{vennDiagram-CIIHVFJN-D3jx9PAk.js → vennDiagram-CIIHVFJN-DOkqg5Wi.js} +1 -1
  84. package/dist/client/assets/{wardley-L42UT6IY-BGRnyThw.js → wardley-L42UT6IY-DoPK1Q6d.js} +1 -1
  85. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-pwLVIgII.js → wardleyDiagram-YWT4CUSO-l1vun-m2.js} +1 -1
  86. package/dist/client/assets/{xychartDiagram-2RQKCTM6-ltxtDdDp.js → xychartDiagram-2RQKCTM6-DcUOUKvl.js} +1 -1
  87. package/dist/client/index.html +2 -2
  88. package/dist/client/version.json +1 -1
  89. package/dist/droid-cli/package.json +1 -1
  90. package/dist/extension.js +104 -77
  91. package/dist/pi-claude-cli/package.json +1 -1
  92. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  93. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  94. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  95. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  96. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  97. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  98. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  99. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  100. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  101. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  102. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  103. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  104. package/package.json +1 -1
  105. package/dist/client/assets/channel-BFVfY8Ks.js +0 -1
  106. package/dist/client/assets/classDiagram-4FO5ZUOK-pfX_P8V9.js +0 -1
  107. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-pfX_P8V9.js +0 -1
  108. package/dist/client/assets/index-CUY6xXXv.js +0 -1292
  109. package/dist/client/assets/index-CXlzJfKU.css +0 -1
package/dist/bin.js CHANGED
@@ -71211,9 +71211,6 @@ function evaluateInterpreterCutoverReadiness(input) {
71211
71211
  if (!input.authoritativeFlagEnabled) {
71212
71212
  reasons.push("experimentalFeatures.workflowInterpreterAuthoritative is disabled");
71213
71213
  }
71214
- if (!input.dualObserveEnabled) {
71215
- reasons.push("experimentalFeatures.workflowInterpreterDualObserve is disabled");
71216
- }
71217
71214
  const paritySummary = input.paritySummary;
71218
71215
  if (!paritySummary) {
71219
71216
  reasons.push("workflow parity summary unavailable");
@@ -160189,7 +160186,6 @@ var init_workflow_authoritative_driver = __esm({
160189
160186
  "use strict";
160190
160187
  init_src();
160191
160188
  init_logger2();
160192
- init_workflow_parity_observer();
160193
160189
  init_workflow_graph_task_runner();
160194
160190
  AUTHORITATIVE_WORKFLOW_ID = "workflow-interpreter-authoritative";
160195
160191
  WorkflowAuthoritativeDriver = class {
@@ -160216,13 +160212,8 @@ var init_workflow_authoritative_driver = __esm({
160216
160212
  settings,
160217
160213
  WORKFLOW_INTERPRETER_AUTHORITATIVE_FLAG
160218
160214
  );
160219
- const dualObserveEnabled = isExperimentalFeatureEnabled(
160220
- settings,
160221
- WORKFLOW_INTERPRETER_DUAL_OBSERVE_FLAG
160222
- );
160223
160215
  const readiness = evaluateInterpreterCutoverReadiness({
160224
160216
  authoritativeFlagEnabled,
160225
- dualObserveEnabled,
160226
160217
  paritySummary,
160227
160218
  minimumObservedRuns: this.deps.minimumObservedRuns
160228
160219
  });
@@ -183130,8 +183121,8 @@ async function getFirstQuestionFromAgent(session, message) {
183130
183121
  }
183131
183122
  }
183132
183123
  if (!parsed) {
183133
- sessions.delete(session.id);
183134
- unpersistSession(session.id);
183124
+ const errorMessage = buildRetryableParseErrorMessage(lastError);
183125
+ setSessionError(session, errorMessage);
183135
183126
  try {
183136
183127
  await session.agent.session.dispose?.();
183137
183128
  } catch (disposeErr) {
@@ -183142,9 +183133,7 @@ async function getFirstQuestionFromAgent(session, message) {
183142
183133
  });
183143
183134
  }
183144
183135
  session.agent = void 0;
183145
- throw new Error(
183146
- `Failed to get first question from AI: ${lastError?.message || "Unknown error"}`
183147
- );
183136
+ throw new Error(`Failed to get first question from AI: ${errorMessage}`);
183148
183137
  }
183149
183138
  if (parsed.type === "complete") {
183150
183139
  const summary = parsed.data;
@@ -183515,6 +183504,10 @@ async function maybeNotifyPlanningAwaitingInput(session, question) {
183515
183504
  });
183516
183505
  }
183517
183506
  }
183507
+ function buildRetryableParseErrorMessage(error) {
183508
+ const baseMessage = (error?.message || "Failed to parse AI response").replace(/\s*Please try again\.?\s*$/i, "").trim();
183509
+ return `${baseMessage}. Retry this planning session or start a new one.`;
183510
+ }
183518
183511
  function setSessionError(session, message) {
183519
183512
  session.error = message;
183520
183513
  session.updatedAt = /* @__PURE__ */ new Date();
@@ -183647,18 +183640,12 @@ async function continueAgentConversation(session, message) {
183647
183640
  }
183648
183641
  }
183649
183642
  if (!parsed) {
183650
- const errorMsg = `${lastError?.message || "Failed to parse AI response"} You can try responding again or start a new planning session.`;
183643
+ const errorMsg = buildRetryableParseErrorMessage(lastError);
183651
183644
  diagnostics2.error(
183652
183645
  "All parse attempts exhausted for session",
183653
183646
  { sessionId: session.id, message: errorMsg, operation: "parse-exhausted" }
183654
183647
  );
183655
- session.error = errorMsg;
183656
- session.updatedAt = /* @__PURE__ */ new Date();
183657
- persistSession(session, "error", errorMsg);
183658
- planningStreamManager.broadcast(session.id, {
183659
- type: "error",
183660
- data: errorMsg
183661
- });
183648
+ setSessionError(session, errorMsg);
183662
183649
  return;
183663
183650
  }
183664
183651
  if (parsed.type === "question") {
@@ -183695,13 +183682,33 @@ async function continueAgentConversation(session, message) {
183695
183682
  setSessionError(session, errorMessage);
183696
183683
  }
183697
183684
  }
183685
+ function isPlanningResponseShape(parsed) {
183686
+ if (typeof parsed !== "object" || parsed === null || !("type" in parsed) || !("data" in parsed)) {
183687
+ return false;
183688
+ }
183689
+ const typed = parsed;
183690
+ return (typed.type === "question" || typed.type === "complete") && typed.data !== null && typed.data !== void 0;
183691
+ }
183692
+ function parseJsonCandidateForShape(candidate) {
183693
+ try {
183694
+ return JSON.parse(candidate);
183695
+ } catch {
183696
+ try {
183697
+ return JSON.parse(repairJson2(candidate));
183698
+ } catch {
183699
+ return void 0;
183700
+ }
183701
+ }
183702
+ }
183698
183703
  function extractJsonCandidate2(text) {
183699
183704
  if (!text || !text.trim()) return null;
183700
- const codeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
183701
- if (codeBlockMatch?.[1]) {
183702
- const candidate = codeBlockMatch[1].trim();
183703
- if (candidate.startsWith("{")) return candidate;
183704
- }
183705
+ const codeBlockMatches = [...text.matchAll(/```(?:json)?\s*([\s\S]*?)\s*```/g)];
183706
+ const codeBlockCandidates = codeBlockMatches.map((match) => match[1]?.trim()).filter((candidate) => Boolean(candidate?.startsWith("{")));
183707
+ const planningCodeBlock = codeBlockCandidates.find(
183708
+ (candidate) => isPlanningResponseShape(parseJsonCandidateForShape(candidate))
183709
+ );
183710
+ if (planningCodeBlock) return planningCodeBlock;
183711
+ if (codeBlockCandidates.length > 0) return codeBlockCandidates[0];
183705
183712
  const candidates = [];
183706
183713
  for (let i = 0; i < text.length; i++) {
183707
183714
  if (text[i] === "{") {
@@ -183727,19 +183734,18 @@ function extractJsonCandidate2(text) {
183727
183734
  if (ch === "}") depth--;
183728
183735
  if (depth === 0) {
183729
183736
  const candidate = text.slice(i, j2 + 1).trim();
183730
- try {
183731
- JSON.parse(candidate);
183732
- candidates.push({ start: i, end: j2, text: candidate });
183733
- } catch {
183734
- }
183737
+ candidates.push({ start: i, end: j2, text: candidate, parsed: parseJsonCandidateForShape(candidate) });
183735
183738
  break;
183736
183739
  }
183737
183740
  }
183738
183741
  }
183739
183742
  }
183740
- if (candidates.length > 0) {
183741
- candidates.sort((a, b2) => b2.text.length - a.text.length);
183742
- return candidates[0].text;
183743
+ const planningCandidate = candidates.find((candidate) => isPlanningResponseShape(candidate.parsed));
183744
+ if (planningCandidate) return planningCandidate.text;
183745
+ const validCandidates = candidates.filter((candidate) => candidate.parsed !== void 0);
183746
+ if (validCandidates.length > 0) {
183747
+ validCandidates.sort((a, b2) => b2.text.length - a.text.length || a.start - b2.start);
183748
+ return validCandidates[0].text;
183743
183749
  }
183744
183750
  const trimmed = text.trim();
183745
183751
  if (trimmed.startsWith("{")) return trimmed;
@@ -183824,11 +183830,8 @@ function parseAgentResponse2(text) {
183824
183830
  );
183825
183831
  }
183826
183832
  }
183827
- if (typeof parsed === "object" && parsed !== null && "type" in parsed && "data" in parsed) {
183828
- const typed = parsed;
183829
- if ((typed.type === "question" || typed.type === "complete") && typed.data !== null && typed.data !== void 0) {
183830
- return parsed;
183831
- }
183833
+ if (isPlanningResponseShape(parsed)) {
183834
+ return parsed;
183832
183835
  }
183833
183836
  diagnostics2.error("Invalid response structure from AI", { parsedSnippet: JSON.stringify(parsed).slice(0, 500), operation: "parse-validate" });
183834
183837
  throw new Error("AI returned an invalid response structure. Please try again.");
@@ -190655,6 +190658,13 @@ function parseIssueUrl(stdout) {
190655
190658
  url
190656
190659
  };
190657
190660
  }
190661
+ function requireNonEmptyPrBody(body) {
190662
+ const trimmed = body?.trim() ?? "";
190663
+ if (!trimmed) {
190664
+ throw new Error("PR body is required when creating a pull request");
190665
+ }
190666
+ return trimmed;
190667
+ }
190658
190668
  function normalizeCheckState(state) {
190659
190669
  switch ((state ?? "").toLowerCase()) {
190660
190670
  case "success":
@@ -191133,6 +191143,7 @@ var init_github = __esm({
191133
191143
  createPrWithGh(params) {
191134
191144
  const { owner: paramOwner, repo: paramRepo, title, body, head, base, draft, reviewers } = params;
191135
191145
  const { owner, repo } = this.resolveRepo(paramOwner, paramRepo);
191146
+ const prBody = requireNonEmptyPrBody(body);
191136
191147
  const args = [
191137
191148
  "pr",
191138
191149
  "create",
@@ -191141,11 +191152,10 @@ var init_github = __esm({
191141
191152
  "--title",
191142
191153
  title,
191143
191154
  "--head",
191144
- head
191155
+ head,
191156
+ "--body",
191157
+ prBody
191145
191158
  ];
191146
- if (body) {
191147
- args.push("--body", body);
191148
- }
191149
191159
  if (base) {
191150
191160
  args.push("--base", base);
191151
191161
  }
@@ -191175,6 +191185,7 @@ var init_github = __esm({
191175
191185
  async createPrWithApi(params) {
191176
191186
  const { owner: paramOwner, repo: paramRepo, title, body, head, base = "main", draft, reviewers } = params;
191177
191187
  const { owner, repo } = this.resolveRepo(paramOwner, paramRepo);
191188
+ const prBody = requireNonEmptyPrBody(body);
191178
191189
  const url = `${this.baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/pulls`;
191179
191190
  const headers = this.buildHeaders();
191180
191191
  const response = await fetch(url, {
@@ -191182,7 +191193,7 @@ var init_github = __esm({
191182
191193
  headers,
191183
191194
  body: JSON.stringify({
191184
191195
  title,
191185
- body: body || "",
191196
+ body: prBody,
191186
191197
  head,
191187
191198
  base,
191188
191199
  draft: draft === true
@@ -195704,7 +195715,7 @@ import { promisify as promisify54 } from "node:util";
195704
195715
  import { exec as execCb } from "node:child_process";
195705
195716
  import { access as access9, readFile as readFile27 } from "node:fs/promises";
195706
195717
  import { join as join63 } from "node:path";
195707
- function buildFallback(task) {
195718
+ function buildFallbackPrMetadata(task) {
195708
195719
  return {
195709
195720
  title: task.title ?? task.id,
195710
195721
  body: [
@@ -195808,12 +195819,13 @@ Closes ${taskId}`.trim()]
195808
195819
  return out.join("\n");
195809
195820
  }
195810
195821
  async function runCommand(command, cwd, signal) {
195811
- const { stdout } = await execAsync36(command, {
195822
+ const commandPromise = execAsync36(command, {
195812
195823
  cwd,
195813
195824
  timeout: 15e3,
195814
195825
  maxBuffer: 10 * 1024 * 1024,
195815
195826
  signal
195816
195827
  });
195828
+ const { stdout } = signal ? await raceWithAbort(commandPromise, signal) : await commandPromise;
195817
195829
  return stdout.trim();
195818
195830
  }
195819
195831
  function createAbortError3() {
@@ -195827,19 +195839,41 @@ function throwIfAborted3(signal) {
195827
195839
  }
195828
195840
  throw signal.reason instanceof Error ? signal.reason : createAbortError3();
195829
195841
  }
195830
- function waitForAbort(signal) {
195831
- return new Promise((_3, reject2) => {
195832
- if (signal.aborted) {
195833
- reject2(signal.reason instanceof Error ? signal.reason : createAbortError3());
195834
- return;
195835
- }
195836
- signal.addEventListener(
195837
- "abort",
195838
- () => reject2(signal.reason instanceof Error ? signal.reason : createAbortError3()),
195839
- { once: true }
195842
+ function getAbortReason(signal) {
195843
+ return signal.reason instanceof Error ? signal.reason : createAbortError3();
195844
+ }
195845
+ function raceWithAbort(operation, signal) {
195846
+ if (signal.aborted) {
195847
+ return Promise.reject(getAbortReason(signal));
195848
+ }
195849
+ return new Promise((resolve70, reject2) => {
195850
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
195851
+ const onAbort = () => {
195852
+ cleanup();
195853
+ reject2(getAbortReason(signal));
195854
+ };
195855
+ signal.addEventListener("abort", onAbort, { once: true });
195856
+ Promise.resolve(operation).then(
195857
+ (value) => {
195858
+ cleanup();
195859
+ resolve70(value);
195860
+ },
195861
+ (error) => {
195862
+ cleanup();
195863
+ reject2(error);
195864
+ }
195840
195865
  );
195841
195866
  });
195842
195867
  }
195868
+ function disposeSessionBestEffort(session) {
195869
+ try {
195870
+ const disposeResult = session.dispose?.();
195871
+ if (disposeResult && typeof disposeResult.catch === "function") {
195872
+ void disposeResult.catch(() => void 0);
195873
+ }
195874
+ } catch {
195875
+ }
195876
+ }
195843
195877
  function isAbortLikeError(error) {
195844
195878
  return Boolean(
195845
195879
  error && typeof error === "object" && ("name" in error ? error.name === "AbortError" : false)
@@ -195858,7 +195892,7 @@ async function resolveBaseBranch(task, repoRoot, signal) {
195858
195892
  }
195859
195893
  async function generatePrMetadata(input) {
195860
195894
  const { task, repoRoot, settings, signal, timeoutMs = PR_METADATA_TIMEOUT_MS } = input;
195861
- const fallback2 = buildFallback(task);
195895
+ const fallback2 = buildFallbackPrMetadata(task);
195862
195896
  const controller = new AbortController();
195863
195897
  const abortFromCaller = () => controller.abort(signal?.reason instanceof Error ? signal.reason : createAbortError3());
195864
195898
  const timeoutId = setTimeout(() => controller.abort(createAbortError3()), timeoutMs);
@@ -195873,24 +195907,24 @@ async function generatePrMetadata(input) {
195873
195907
  }
195874
195908
  try {
195875
195909
  throwIfAborted3(combinedSignal);
195876
- const baseBranch = await resolveBaseBranch(task, repoRoot, combinedSignal);
195877
- const [logOut, diffStatOut] = await Promise.all([
195910
+ const baseBranch = await raceWithAbort(resolveBaseBranch(task, repoRoot, combinedSignal), combinedSignal);
195911
+ const [logOut, diffStatOut] = await raceWithAbort(Promise.all([
195878
195912
  runCommand(`git log --no-merges ${baseBranch}..HEAD --format=%s%n%b`, repoRoot, combinedSignal).catch(() => ""),
195879
195913
  runCommand(`git diff --stat ${baseBranch}..HEAD`, repoRoot, combinedSignal).catch(() => "")
195880
- ]);
195914
+ ]), combinedSignal);
195881
195915
  let promptContent = "";
195882
195916
  try {
195883
195917
  const promptPath = join63(repoRoot, ".fusion", "tasks", task.id, "PROMPT.md");
195884
- promptContent = (await readFile27(promptPath, "utf8")).trim();
195918
+ promptContent = (await raceWithAbort(readFile27(promptPath, "utf8"), combinedSignal)).trim();
195885
195919
  } catch {
195886
195920
  promptContent = "";
195887
195921
  }
195888
195922
  const templatePath = join63(repoRoot, ".github", "pull_request_template.md");
195889
- const templateExists = await access9(templatePath).then(() => true).catch(() => false);
195890
- const template = templateExists ? await readFile27(templatePath, "utf8") : "";
195923
+ const templateExists = await raceWithAbort(access9(templatePath).then(() => true).catch(() => false), combinedSignal);
195924
+ const template = templateExists ? await raceWithAbort(readFile27(templatePath, "utf8"), combinedSignal) : "";
195891
195925
  const model = resolveTitleSummarizerSettingsModel(settings);
195892
195926
  let aiText = "";
195893
- const { session } = await createFnAgent2({
195927
+ const { session } = await raceWithAbort(createFnAgent2({
195894
195928
  cwd: repoRoot,
195895
195929
  tools: "readonly",
195896
195930
  defaultProvider: model.provider,
@@ -195903,7 +195937,7 @@ async function generatePrMetadata(input) {
195903
195937
  onText: (delta) => {
195904
195938
  aiText += delta;
195905
195939
  }
195906
- });
195940
+ }), combinedSignal);
195907
195941
  try {
195908
195942
  const contextPrompt = [
195909
195943
  `Task ID: ${task.id}`,
@@ -195918,15 +195952,12 @@ async function generatePrMetadata(input) {
195918
195952
  promptContent || "(none)"
195919
195953
  ].join("\n\n");
195920
195954
  throwIfAborted3(combinedSignal);
195921
- await Promise.race([
195955
+ await raceWithAbort(
195922
195956
  session.prompt(contextPrompt, { signal: combinedSignal }),
195923
- waitForAbort(combinedSignal)
195924
- ]);
195957
+ combinedSignal
195958
+ );
195925
195959
  } finally {
195926
- try {
195927
- session.dispose();
195928
- } catch {
195929
- }
195960
+ disposeSessionBestEffort(session);
195930
195961
  }
195931
195962
  const parsed = parseAiResult(aiText);
195932
195963
  if (!parsed) {
@@ -199940,13 +199971,18 @@ ${body}`;
199940
199971
  try {
199941
199972
  const { store: scopedStore } = await getProjectContext3(req);
199942
199973
  const { title, body, base } = req.body;
199943
- if (!title || typeof title !== "string") {
199944
- throw badRequest("title is required and must be a string");
199945
- }
199946
199974
  const task = await scopedStore.getTask(req.params.id);
199947
199975
  if (task.column !== "in-review") {
199948
199976
  throw badRequest("Task must be in 'in-review' column to create a PR");
199949
199977
  }
199978
+ if (!title || typeof title !== "string") {
199979
+ throw badRequest("title is required and must be a string");
199980
+ }
199981
+ if (!body || typeof body !== "string" || !body.trim()) {
199982
+ throw badRequest("body is required and must be a non-empty string");
199983
+ }
199984
+ const prTitle = title.trim();
199985
+ const prBody = body.trim();
199950
199986
  const existingPrs = getTaskPrList(task);
199951
199987
  const branchName = `fusion/${task.id.toLowerCase()}`;
199952
199988
  let owner;
@@ -199983,8 +200019,8 @@ ${body}`;
199983
200019
  prInfo = await client.createPr({
199984
200020
  owner,
199985
200021
  repo,
199986
- title,
199987
- body,
200022
+ title: prTitle,
200023
+ body: prBody,
199988
200024
  head: branchName,
199989
200025
  base
199990
200026
  });
@@ -200125,19 +200161,48 @@ ${body}`;
200125
200161
  });
200126
200162
  router.post("/tasks/:id/pr/generate-metadata", async (req, res) => {
200127
200163
  const controller = new AbortController();
200128
- const abortRequest = () => controller.abort();
200129
- req.on("close", abortRequest);
200164
+ let responseCompleted = false;
200165
+ const abortRequest = () => {
200166
+ if (!responseCompleted && !controller.signal.aborted) {
200167
+ const error = new Error("PR metadata request aborted");
200168
+ error.name = "AbortError";
200169
+ controller.abort(error);
200170
+ }
200171
+ };
200172
+ const markCompleted = () => {
200173
+ responseCompleted = true;
200174
+ };
200175
+ req.on("aborted", abortRequest);
200176
+ res.on("close", abortRequest);
200177
+ res.on("finish", markCompleted);
200130
200178
  try {
200131
200179
  const { store: scopedStore } = await getProjectContext3(req);
200132
200180
  const task = await scopedStore.getTask(req.params.id);
200133
200181
  const settings = await scopedStore.getSettings();
200134
- const metadata = await generatePrMetadata({
200182
+ let timeoutId;
200183
+ const generatorPromise = generatePrMetadata({
200135
200184
  task,
200136
200185
  repoRoot: scopedStore.getRootDir(),
200137
200186
  settings,
200138
- signal: controller.signal
200187
+ signal: controller.signal,
200188
+ timeoutMs: PR_METADATA_ROUTE_TIMEOUT_MS
200189
+ });
200190
+ void generatorPromise.catch(() => void 0);
200191
+ const timeoutFallbackPromise = new Promise((resolve70) => {
200192
+ timeoutId = setTimeout(() => {
200193
+ abortRequest();
200194
+ resolve70(buildFallbackPrMetadata(task));
200195
+ }, PR_METADATA_ROUTE_TIMEOUT_MS);
200139
200196
  });
200140
- if (req.destroyed || res.writableEnded || res.writableFinished || controller.signal.aborted) {
200197
+ let metadata;
200198
+ try {
200199
+ metadata = await Promise.race([generatorPromise, timeoutFallbackPromise]);
200200
+ } finally {
200201
+ if (timeoutId) {
200202
+ clearTimeout(timeoutId);
200203
+ }
200204
+ }
200205
+ if (res.writableEnded || res.writableFinished || res.destroyed) {
200141
200206
  return;
200142
200207
  }
200143
200208
  res.json(metadata);
@@ -200150,7 +200215,9 @@ ${body}`;
200150
200215
  }
200151
200216
  rethrowAsApiError9(err, "Failed to generate PR metadata");
200152
200217
  } finally {
200153
- req.off("close", abortRequest);
200218
+ req.removeListener("aborted", abortRequest);
200219
+ res.removeListener("close", abortRequest);
200220
+ res.removeListener("finish", markCompleted);
200154
200221
  }
200155
200222
  });
200156
200223
  router.get("/tasks/:id/pr/preflight", async (req, res) => {
@@ -200719,7 +200786,7 @@ ${body}`;
200719
200786
  }
200720
200787
  });
200721
200788
  }
200722
- var execAsync37, PR_ROUTE_MAX_BUFFER_BYTES, PR_PREFLIGHT_TIMEOUT_MS, PR_OPTIONS_TIMEOUT_MS, SAFE_GIT_REF_PATTERN, GITHUB_TRACKING_RECONCILE_INTERVAL_MS, RECENT_ISSUES_CACHE_TTL_MS, recentIssuesCache, prRouteCommandRunner, batchImportWindowMs, batchImportInstances, batchImportCleanupInterval;
200789
+ var execAsync37, PR_ROUTE_MAX_BUFFER_BYTES, PR_PREFLIGHT_TIMEOUT_MS, PR_OPTIONS_TIMEOUT_MS, PR_METADATA_ROUTE_TIMEOUT_MS, SAFE_GIT_REF_PATTERN, GITHUB_TRACKING_RECONCILE_INTERVAL_MS, RECENT_ISSUES_CACHE_TTL_MS, recentIssuesCache, prRouteCommandRunner, batchImportWindowMs, batchImportInstances, batchImportCleanupInterval;
200723
200790
  var init_register_git_github = __esm({
200724
200791
  "../dashboard/src/routes/register-git-github.ts"() {
200725
200792
  "use strict";
@@ -200743,6 +200810,7 @@ var init_register_git_github = __esm({
200743
200810
  PR_ROUTE_MAX_BUFFER_BYTES = 10 * 1024 * 1024;
200744
200811
  PR_PREFLIGHT_TIMEOUT_MS = 15e3;
200745
200812
  PR_OPTIONS_TIMEOUT_MS = 1e4;
200813
+ PR_METADATA_ROUTE_TIMEOUT_MS = 25e3;
200746
200814
  SAFE_GIT_REF_PATTERN = /^[A-Za-z0-9._/-]+$/;
200747
200815
  GITHUB_TRACKING_RECONCILE_INTERVAL_MS = 15 * 60 * 1e3;
200748
200816
  RECENT_ISSUES_CACHE_TTL_MS = 6e4;