@runfusion/fusion 0.76.0-beta.3 → 0.77.0-beta.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 (175) hide show
  1. package/dist/bin.js +13802 -9587
  2. package/dist/child-process-worker.js +6472 -4028
  3. package/dist/client/.vite/manifest.json +280 -269
  4. package/dist/client/assets/AgentDetailView-0G9qFd4j.js +13 -0
  5. package/dist/client/assets/{AgentDetailView-Cc3ATg8a.css → AgentDetailView-jzgy1BAS.css} +1 -1
  6. package/dist/client/assets/{AgentPermissionPolicyEditor--u9jyUf2.js → AgentPermissionPolicyEditor-BM68g7wo.js} +1 -1
  7. package/dist/client/assets/AgentsView-DkOUZWw4.js +24 -0
  8. package/dist/client/assets/ChatView-BPSm2JIH.js +13 -0
  9. package/dist/client/assets/{CommandCenter-CSRYoveB.js → CommandCenter-DxRSFWNc.js} +26 -26
  10. package/dist/client/assets/CommandCenter-ilm-DbtI.css +1 -0
  11. package/dist/client/assets/{DevServerView-B9So_oYx.js → DevServerView-DO-bqJFE.js} +1 -1
  12. package/dist/client/assets/{DirectoryPicker-BhyDL_2m.js → DirectoryPicker-Cp2gddTE.js} +1 -1
  13. package/dist/client/assets/DocumentsView-D0E-3G96.js +1 -0
  14. package/dist/client/assets/EvalsView-BoFVq5hw.js +1 -0
  15. package/dist/client/assets/ExperimentalAgentOnboardingModal-CS1Sdogm.js +1 -0
  16. package/dist/client/assets/{GoalsView-B6X5_0Ve.js → GoalsView-sybf7JY4.js} +1 -1
  17. package/dist/client/assets/InsightsView-nHa7aShn.js +11 -0
  18. package/dist/client/assets/{MemoryView-DN_3IFmA.css → MemoryView-CdpQqPJJ.css} +1 -1
  19. package/dist/client/assets/MemoryView-D0IG44za.js +3 -0
  20. package/dist/client/assets/{PiExtensionsManager-B8At6fO8.js → PiExtensionsManager-CU76weJe.js} +2 -2
  21. package/dist/client/assets/PluginManager-DyT8XbcG.js +1 -0
  22. package/dist/client/assets/{PullRequestView-B1hZFrWo.js → PullRequestView-CnxWUSen.js} +2 -2
  23. package/dist/client/assets/{ReportModal-C46V_zEa.js → ReportModal-CMrmMQjD.js} +5 -5
  24. package/dist/client/assets/{ResearchView-D8G85BYN.js → ResearchView-BTytQSgy.js} +2 -2
  25. package/dist/client/assets/{SecretsView-CTri9LRK.js → SecretsView-BuSH5J87.js} +1 -1
  26. package/dist/client/assets/SessionTerminal-D04kYRrT.js +2 -0
  27. package/dist/client/assets/SettingsModal-C0FY8MPW.css +1 -0
  28. package/dist/client/assets/{SettingsModal-rUQcSTJp.js → SettingsModal-Dm-ngbRr.js} +1 -1
  29. package/dist/client/assets/{SettingsModal-DmfMotcd.css → SettingsModal-Ds0Yj47X.css} +1 -1
  30. package/dist/client/assets/SettingsModal-xSLhH7Tr.js +21 -0
  31. package/dist/client/assets/{SettingsTextareaRow-BirlBcem.js → SettingsTextareaRow-CHPgocEp.js} +1 -1
  32. package/dist/client/assets/{SetupWizardModal-xnCBM9EL.js → SetupWizardModal-y88aXNIQ.js} +2 -2
  33. package/dist/client/assets/{SkillsView-O5U0ep50.js → SkillsView-CYyQW1Hv.js} +1 -1
  34. package/dist/client/assets/{WorkflowNodeEditor-BX4d2qqF.js → WorkflowNodeEditor-hYeoz1X8.js} +3 -3
  35. package/dist/client/assets/{agent-import-generation-DnqVkPkz.js → agent-import-generation-CjiYXzCr.js} +1 -1
  36. package/dist/client/assets/agentsOrgChartLayout-C0UP_X3z.js +1 -0
  37. package/dist/client/assets/app-BgpuMvTU.js +13 -0
  38. package/dist/client/assets/app-BlexHhbW.js +13 -0
  39. package/dist/client/assets/app-BsuUOR0P.js +13 -0
  40. package/dist/client/assets/app-DQVOujmO.js +13 -0
  41. package/dist/client/assets/app-DjyTRx1N.js +5 -0
  42. package/dist/client/assets/app-Dniggype.js +13 -0
  43. package/dist/client/assets/app-EV-lyZ0T.js +5 -0
  44. package/dist/client/assets/{architectureDiagram-3BPJPVTR-gi467xY0.js → architectureDiagram-3BPJPVTR-Dd6GPcqM.js} +1 -1
  45. package/dist/client/assets/{blockDiagram-GPEHLZMM-B_gpcYiR.js → blockDiagram-GPEHLZMM-CC6pLxlz.js} +1 -1
  46. package/dist/client/assets/{c4Diagram-AAUBKEIU-DeylEYsQ.js → c4Diagram-AAUBKEIU-DxMP_l6y.js} +1 -1
  47. package/dist/client/assets/channel-DlCEMoEf.js +1 -0
  48. package/dist/client/assets/{chunk-2J33WTMH-BL2WY0HN.js → chunk-2J33WTMH-BKYToL43.js} +1 -1
  49. package/dist/client/assets/{chunk-4BX2VUAB-BkLExGMe.js → chunk-4BX2VUAB-Bg6rjQb3.js} +1 -1
  50. package/dist/client/assets/{chunk-55IACEB6-CSTvCF5T.js → chunk-55IACEB6-CPUwbbGa.js} +1 -1
  51. package/dist/client/assets/{chunk-727SXJPM-9J8pKk4x.js → chunk-727SXJPM-DScQBCvv.js} +1 -1
  52. package/dist/client/assets/{chunk-AQP2D5EJ-DXC1vPWr.js → chunk-AQP2D5EJ-DbX7uaXK.js} +1 -1
  53. package/dist/client/assets/{chunk-FMBD7UC4-CFRUb-zD.js → chunk-FMBD7UC4-DvYKea1c.js} +1 -1
  54. package/dist/client/assets/{chunk-ND2GUHAM-B87HFYS8.js → chunk-ND2GUHAM-CH-Tvw7w.js} +1 -1
  55. package/dist/client/assets/{chunk-QZHKN3VN-CTjHiGbd.js → chunk-QZHKN3VN-CbyEvOoM.js} +1 -1
  56. package/dist/client/assets/classDiagram-4FO5ZUOK-Bc92O8Ks.js +1 -0
  57. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Bc92O8Ks.js +1 -0
  58. package/dist/client/assets/{cose-bilkent-S5V4N54A-D3Edq3L9.js → cose-bilkent-S5V4N54A-BPB5fV3A.js} +1 -1
  59. package/dist/client/assets/{dagre-BM42HDAG-CwwbeQfN.js → dagre-BM42HDAG-CilYJ3rR.js} +1 -1
  60. package/dist/client/assets/{dashboard-view-QxQwJFLz.js → dashboard-view-BnGe8cWo.js} +1 -1
  61. package/dist/client/assets/{dashboard-view-BLAoW3D4.js → dashboard-view-DnVf3tmI.js} +1 -1
  62. package/dist/client/assets/{dashboard-view-DAlJxqiJ.js → dashboard-view-DoLU6FxZ.js} +1 -1
  63. package/dist/client/assets/{dashboard-view-aPzfv99T.js → dashboard-view-dChSM_So.js} +1 -1
  64. package/dist/client/assets/{diagram-2AECGRRQ-koJWWXE4.js → diagram-2AECGRRQ-m9u1Qhyx.js} +1 -1
  65. package/dist/client/assets/{diagram-5GNKFQAL-Yob5cOKT.js → diagram-5GNKFQAL-DVr7Kf0N.js} +1 -1
  66. package/dist/client/assets/{diagram-KO2AKTUF-DelvFSkK.js → diagram-KO2AKTUF-D7sgJDFx.js} +1 -1
  67. package/dist/client/assets/{diagram-LMA3HP47-CMput2h2.js → diagram-LMA3HP47-DNKNDS6r.js} +1 -1
  68. package/dist/client/assets/{diagram-OG6HWLK6-04VXDl79.js → diagram-OG6HWLK6-1Bff4FWn.js} +1 -1
  69. package/dist/client/assets/{erDiagram-TEJ5UH35-C9iP8Uyp.js → erDiagram-TEJ5UH35-DGua1UHN.js} +1 -1
  70. package/dist/client/assets/{flowDiagram-I6XJVG4X-DENQvu_t.js → flowDiagram-I6XJVG4X-UDfDtwsE.js} +1 -1
  71. package/dist/client/assets/{folder-open-D-554oji.js → folder-open-CteOypVk.js} +1 -1
  72. package/dist/client/assets/{ganttDiagram-6RSMTGT7-BqOttZAT.js → ganttDiagram-6RSMTGT7-COdmD328.js} +1 -1
  73. package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CDcydi-5.js → gitGraphDiagram-PVQCEYII-SUSls7Kw.js} +1 -1
  74. package/dist/client/assets/index-0ovTXt3t.css +1 -0
  75. package/dist/client/assets/index-DpCHwyiD.js +2818 -0
  76. package/dist/client/assets/{infoDiagram-5YYISTIA-B5LQQnxt.js → infoDiagram-5YYISTIA-e_DloXj7.js} +1 -1
  77. package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DpFEt7D2.js → ishikawaDiagram-YF4QCWOH-DVWqihXg.js} +1 -1
  78. package/dist/client/assets/{journeyDiagram-JHISSGLW-MELtgu22.js → journeyDiagram-JHISSGLW-BcPY-XP0.js} +1 -1
  79. package/dist/client/assets/{kanban-definition-UN3LZRKU-h33PLATd.js → kanban-definition-UN3LZRKU-BCFG2Jle.js} +1 -1
  80. package/dist/client/assets/{mermaid.core-Dk2mUPOq.js → mermaid.core-5yPje9Bi.js} +4 -4
  81. package/dist/client/assets/{mindmap-definition-RKZ34NQL-B_T4O2-B.js → mindmap-definition-RKZ34NQL-DDApeT3K.js} +1 -1
  82. package/dist/client/assets/{pieDiagram-4H26LBE5-B1YIFkRl.js → pieDiagram-4H26LBE5-N61_jyeP.js} +1 -1
  83. package/dist/client/assets/{puzzle-hvecxlpJ.js → puzzle-BkaCP30D.js} +1 -1
  84. package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BoGQR_hO.js → quadrantDiagram-W4KKPZXB-C5qHiApF.js} +1 -1
  85. package/dist/client/assets/{requirementDiagram-4Y6WPE33-BSDmUBIx.js → requirementDiagram-4Y6WPE33-C71okgzt.js} +1 -1
  86. package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Dz_GqOa5.js → sankeyDiagram-5OEKKPKP-B2iKyLjB.js} +1 -1
  87. package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B6twjYUH.js → sequenceDiagram-3UESZ5HK-DfdoYKKk.js} +1 -1
  88. package/dist/client/assets/{shield-alert-BX4WRJOv.js → shield-alert-BNSeDB8l.js} +1 -1
  89. package/dist/client/assets/standing-instructions-template-pZtzo1lK.js +20 -0
  90. package/dist/client/assets/standing-instructions-template-w7Ouw_37.css +1 -0
  91. package/dist/client/assets/{stateDiagram-AJRCARHV-DuwqWLx-.js → stateDiagram-AJRCARHV-lS-sUi12.js} +1 -1
  92. package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Bu8Sk187.js → stateDiagram-v2-BHNVJYJU-DXJNDrV9.js} +1 -1
  93. package/dist/client/assets/{timeline-definition-PNZ67QCA-BS6VnqSI.js → timeline-definition-PNZ67QCA-CCwvX81K.js} +1 -1
  94. package/dist/client/assets/{upload-CaDoyaMW.js → upload-CDovVbu2.js} +1 -1
  95. package/dist/client/assets/useDiscoveredSkillsCache-BYqP-afh.js +1 -0
  96. package/dist/client/assets/{users-C5uCLwVM.js → users-CTqECGLo.js} +1 -1
  97. package/dist/client/assets/{vennDiagram-CIIHVFJN-CUbzqT5Y.js → vennDiagram-CIIHVFJN-DaiR5nv9.js} +1 -1
  98. package/dist/client/assets/{wardley-L42UT6IY-Mv-NotFS.js → wardley-L42UT6IY-hA5IHyPW.js} +1 -1
  99. package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DuYpvLZX.js → wardleyDiagram-YWT4CUSO-CILUNESc.js} +1 -1
  100. package/dist/client/assets/{xychartDiagram-2RQKCTM6-24_7QXdw.js → xychartDiagram-2RQKCTM6-72eNMmjz.js} +1 -1
  101. package/dist/client/index.html +2 -2
  102. package/dist/client/version.json +1 -1
  103. package/dist/droid-cli/package.json +1 -1
  104. package/dist/extension.js +10671 -6904
  105. package/dist/migrations/0059_fn_9037_tasks_source_agent_index.sql +3 -0
  106. package/dist/migrations/0060_fn_9059_workspace_coordination_leases.sql +10 -0
  107. package/dist/pi-claude-cli/package.json +1 -1
  108. package/dist/plugin-sdk/index.d.ts +190 -9
  109. package/dist/plugin-sdk/index.js +74 -28
  110. package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +83 -29
  111. package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
  112. package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +83 -29
  113. package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
  114. package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +74 -28
  115. package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
  116. package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +74 -28
  117. package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
  118. package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +1096 -73
  119. package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
  120. package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +74 -28
  121. package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
  122. package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +74 -28
  123. package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
  124. package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +83 -29
  125. package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
  126. package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +302 -71
  127. package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
  128. package/dist/plugins/fusion-plugin-linear-import/bundled.js +74 -28
  129. package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
  130. package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +83 -29
  131. package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
  132. package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +74 -28
  133. package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
  134. package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +74 -28
  135. package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
  136. package/dist/plugins/fusion-plugin-quality/bundled.js +176 -46
  137. package/dist/plugins/fusion-plugin-quality/package.json +1 -1
  138. package/dist/plugins/fusion-plugin-reports/bundled.js +74 -28
  139. package/dist/plugins/fusion-plugin-reports/package.json +1 -1
  140. package/dist/plugins/fusion-plugin-roadmap/bundled.js +74 -28
  141. package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
  142. package/dist/plugins/fusion-plugin-todos/bundled.js +74 -28
  143. package/dist/plugins/fusion-plugin-todos/package.json +1 -1
  144. package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +74 -28
  145. package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
  146. package/package.json +1 -1
  147. package/skill/fusion/references/extension-tools.md +2 -0
  148. package/dist/client/assets/AgentDetailView-T767kKTg.js +0 -13
  149. package/dist/client/assets/AgentsView-6Ifu7qDr.js +0 -24
  150. package/dist/client/assets/ChatView-BV3lixQi.js +0 -13
  151. package/dist/client/assets/CommandCenter-CE4rYjHn.css +0 -1
  152. package/dist/client/assets/DocumentsView-pbERA7O-.js +0 -1
  153. package/dist/client/assets/EvalsView-DJQ4ltCg.js +0 -1
  154. package/dist/client/assets/ExperimentalAgentOnboardingModal-BwJuzkzB.js +0 -1
  155. package/dist/client/assets/InsightsView-CJgI0J7S.js +0 -11
  156. package/dist/client/assets/MemoryView-BHq1AHPu.js +0 -3
  157. package/dist/client/assets/PluginManager-k7hV7_9e.js +0 -1
  158. package/dist/client/assets/SessionTerminal-BRf6r4Vp.js +0 -2
  159. package/dist/client/assets/SettingsModal-B30rPfYC.css +0 -1
  160. package/dist/client/assets/SettingsModal-D0RBeZik.js +0 -21
  161. package/dist/client/assets/agentsOrgChartLayout-CKuzbhFf.js +0 -1
  162. package/dist/client/assets/app-B2FwULu2.js +0 -5
  163. package/dist/client/assets/app-B6nJaqAl.js +0 -13
  164. package/dist/client/assets/app-CGXZeIRc.js +0 -13
  165. package/dist/client/assets/app-CGtkeBF5.js +0 -13
  166. package/dist/client/assets/app-CNzN1Plu.js +0 -13
  167. package/dist/client/assets/app-DLs-f5yM.js +0 -13
  168. package/dist/client/assets/app-DsvtcRUG.js +0 -5
  169. package/dist/client/assets/channel-BoE8DeM4.js +0 -1
  170. package/dist/client/assets/classDiagram-4FO5ZUOK-qnbLX_9_.js +0 -1
  171. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-qnbLX_9_.js +0 -1
  172. package/dist/client/assets/index-CXTd3LAB.css +0 -1
  173. package/dist/client/assets/index-CtYRoouR.js +0 -2818
  174. package/dist/client/assets/standing-instructions-template-BJY317CC.js +0 -20
  175. package/dist/client/assets/standing-instructions-template-Ddi_4TIk.css +0 -1
@@ -5,6 +5,9 @@ var __export = (target, all) => {
5
5
  __defProp(target, name, { get: all[name], enumerable: true });
6
6
  };
7
7
 
8
+ // src/plugin-sdk-core-runtime-shim.mjs
9
+ import { spawn } from "node:child_process";
10
+
8
11
  // ../../node_modules/.pnpm/drizzle-orm@0.45.2_@opentelemetry+api@1.9.0_better-sqlite3@12.9.0_pg@8.22.0_postgres@3.4.9/node_modules/drizzle-orm/entity.js
9
12
  var entityKind = Symbol.for("drizzle:entityKind");
10
13
  var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
@@ -1074,7 +1077,7 @@ function sql(strings, ...params) {
1074
1077
  return new SQL([new StringChunk(str)]);
1075
1078
  }
1076
1079
  sql2.raw = raw;
1077
- function join2(chunks, separator) {
1080
+ function join5(chunks, separator) {
1078
1081
  const result = [];
1079
1082
  for (const [i, chunk] of chunks.entries()) {
1080
1083
  if (i > 0 && separator !== void 0) {
@@ -1084,7 +1087,7 @@ function sql(strings, ...params) {
1084
1087
  }
1085
1088
  return new SQL(result);
1086
1089
  }
1087
- sql2.join = join2;
1090
+ sql2.join = join5;
1088
1091
  function identifier(value) {
1089
1092
  return new Name(value);
1090
1093
  }
@@ -4535,7 +4538,7 @@ var PgSelectQueryBuilderBase = class extends TypedQueryBuilder {
4535
4538
  const baseTableName = this.tableName;
4536
4539
  const tableName = getTableLikeName(table);
4537
4540
  for (const item of extractUsedTable(table)) this.usedTables.add(item);
4538
- if (typeof tableName === "string" && this.config.joins?.some((join2) => join2.alias === tableName)) {
4541
+ if (typeof tableName === "string" && this.config.joins?.some((join5) => join5.alias === tableName)) {
4539
4542
  throw new Error(`Alias "${tableName}" is already used in this query`);
4540
4543
  }
4541
4544
  if (!this.isPartialSelect) {
@@ -5776,7 +5779,9 @@ __export(project_exports, {
5776
5779
  workflowSettings: () => workflowSettings,
5777
5780
  workflowSteps: () => workflowSteps,
5778
5781
  workflowWorkItems: () => workflowWorkItems,
5779
- workflows: () => workflows
5782
+ workflows: () => workflows,
5783
+ workspaceCoordinationLeases: () => workspaceCoordinationLeases,
5784
+ workspaceLandIntents: () => workspaceLandIntents
5780
5785
  });
5781
5786
  var projectSchema = pgSchema(PROJECT_SCHEMA);
5782
5787
  var tasks = projectSchema.table("tasks", {
@@ -6043,6 +6048,9 @@ var tasks = projectSchema.table("tasks", {
6043
6048
  the gate is a full tasks-table scan. Sparse: most rows have NULL parent.
6044
6049
  */
6045
6050
  index("idxTasksSourceParentTaskId").on(t.sourceParentTaskId),
6051
+ /* FNXC:TaskRecommendations 2026-08-13-22:23: duplicate intake filters source
6052
+ * lineage on recommendation/agent creates; this sparse index avoids a task-table scan. */
6053
+ index("idxTasksProjectSourceAgentId").on(t.projectId, t.sourceAgentId).where(sql`${t.sourceAgentId} IS NOT NULL`),
6046
6054
  // FNXC:EphemeralAgentTaskCreation 2026-07-30-12:00: proposal retries share one stable key, so the database—not a read-before-create race—enforces at-most-once materialization.
6047
6055
  uniqueIndex("uqTasksProjectProposalClaimId").on(t.projectId, t.proposalClaimId).where(sql`${t.proposalClaimId} IS NOT NULL`),
6048
6056
  /*
@@ -6277,6 +6285,52 @@ var distributedTaskIdReservations = projectSchema.table("distributed_task_id_res
6277
6285
  index("idxDistributedTaskIdReservationsPrefixStatus").on(t.prefix, t.status),
6278
6286
  index("idxDistributedTaskIdReservationsExpiry").on(t.status, t.expiresAt)
6279
6287
  ]);
6288
+ var workspaceCoordinationLeases = projectSchema.table("workspace_coordination_leases", {
6289
+ projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6290
+ leaseKey: text("lease_key").notNull(),
6291
+ kind: text("kind").notNull(),
6292
+ ownerTaskId: text("owner_task_id").notNull(),
6293
+ ownerNodeId: text("owner_node_id").notNull(),
6294
+ ownerIncarnationId: text("owner_incarnation_id").notNull(),
6295
+ ownerRunId: text("owner_run_id"),
6296
+ fenceToken: bigint("fence_token", { mode: "bigint" }).notNull().default(sql`0`),
6297
+ fenceRefName: text("fence_ref_name"),
6298
+ fenceRefSha: text("fence_ref_sha"),
6299
+ status: text("status").notNull(),
6300
+ acquiredAt: text("acquired_at").notNull(),
6301
+ renewedAt: text("renewed_at").notNull(),
6302
+ expiresAt: text("expires_at").notNull(),
6303
+ createdAt: text("created_at").notNull(),
6304
+ updatedAt: text("updated_at").notNull()
6305
+ }, (t) => [
6306
+ primaryKey({ columns: [t.projectId, t.leaseKey] }),
6307
+ check("workspace_coordination_leases_kind_check", sql`${t.kind} IN ('acquire', 'land', 'merge-dispatch')`),
6308
+ check("workspace_coordination_leases_status_check", sql`${t.status} IN ('held', 'released', 'expired')`),
6309
+ index("idxWorkspaceCoordinationLeasesOwnerTask").on(t.projectId, t.ownerTaskId),
6310
+ index("idxWorkspaceCoordinationLeasesOwnerNode").on(t.projectId, t.ownerNodeId),
6311
+ index("idxWorkspaceCoordinationLeasesExpiry").on(t.status, t.expiresAt)
6312
+ ]);
6313
+ var workspaceLandIntents = projectSchema.table("workspace_land_intents", {
6314
+ projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6315
+ taskId: text("task_id").notNull(),
6316
+ repoRelPath: text("repo_rel_path").notNull(),
6317
+ remoteUrl: text("remote_url").notNull(),
6318
+ integrationRef: text("integration_ref").notNull(),
6319
+ intendedSha: text("intended_sha").notNull(),
6320
+ expectedTip: text("expected_tip").notNull(),
6321
+ fenceRefName: text("fence_ref_name").notNull(),
6322
+ fenceRefSha: text("fence_ref_sha").notNull(),
6323
+ ownerTaskId: text("owner_task_id").notNull(),
6324
+ ownerNodeId: text("owner_node_id").notNull(),
6325
+ ownerIncarnationId: text("owner_incarnation_id").notNull(),
6326
+ fenceToken: bigint("fence_token", { mode: "bigint" }).notNull(),
6327
+ status: text("status").notNull(),
6328
+ resolvedSha: text("resolved_sha"),
6329
+ resolution: text("resolution"),
6330
+ createdAt: text("created_at").notNull(),
6331
+ updatedAt: text("updated_at").notNull(),
6332
+ resolvedAt: text("resolved_at")
6333
+ }, (t) => [primaryKey({ columns: [t.projectId, t.taskId, t.repoRelPath] }), check("workspace_land_intents_status_check", sql`${t.status} IN ('pending', 'recorded', 'abandoned')`), check("workspace_land_intents_resolution_check", sql`${t.resolution} IS NULL OR ${t.resolution} IN ('landed', 'not-landed')`), index("idxWorkspaceLandIntentsStatus").on(t.projectId, t.status)]);
6280
6334
  var symbolLocks = projectSchema.table("symbol_locks", {
6281
6335
  projectId: text("project_id").notNull().default(sql`current_setting('fusion.project_id', true)`),
6282
6336
  symbolKey: text("symbol_key").notNull(),
@@ -8851,6 +8905,11 @@ var DEFAULT_GLOBAL_SETTINGS = {
8851
8905
  Critical-action confirmation dialogs stay enabled by default. This global-only preference may opt an operator into primary/default auto-approval, but project settings cannot enable it for collaborators.
8852
8906
  */
8853
8907
  skipConfirmationDialogs: false,
8908
+ /*
8909
+ FNXC:QuickEntry 2026-08-16-03:15:
8910
+ Quick Add keeps its historical Enter-to-submit behavior by default; only an operator's global preference may opt into newline-first entry.
8911
+ */
8912
+ quickAddSubmitOnEnter: true,
8854
8913
  language: void 0,
8855
8914
  defaultProvider: void 0,
8856
8915
  defaultCredentialInstanceId: void 0,
@@ -9313,9 +9372,6 @@ var DEFAULT_PROJECT_SETTINGS = {
9313
9372
  * New and unconfigured projects default AI merge to sync a dirty checked-out integration branch, restoring the legacy stash → fast-forward → restore landing behavior. Explicit persisted merger.allowDirtyLocalCheckoutSync values still win, and no existing-project migration stamps this default into storage.
9314
9373
  */
9315
9374
  merger: { mode: "ai", maxReviewPasses: 3, allowDirtyLocalCheckoutSync: true },
9316
- mergeDiffVolumeMinLines: void 0,
9317
- mergeDiffVolumeThreshold: void 0,
9318
- mergeDiffVolumeAllowlist: void 0,
9319
9375
  requiredChecks: void 0,
9320
9376
  mergeStrategyOverlapBehavior: "flip-to-prefer-branch",
9321
9377
  postMergeAuditMode: "warn",
@@ -10652,26 +10708,26 @@ function validateParallelism(nodes2, outgoing, nodesById) {
10652
10708
  }
10653
10709
  const joinsReached = /* @__PURE__ */ new Set();
10654
10710
  for (const edge of branchEdges) {
10655
- const join3 = walkBranchToJoin(edge.to, split.id, outgoing, nodesById);
10656
- if (!join3) {
10711
+ const join6 = walkBranchToJoin(edge.to, split.id, outgoing, nodesById);
10712
+ if (!join6) {
10657
10713
  throw new WorkflowIrError(`split '${split.id}' has a branch with no reachable matching join`);
10658
10714
  }
10659
- joinsReached.add(join3);
10715
+ joinsReached.add(join6);
10660
10716
  }
10661
10717
  if (joinsReached.size !== 1) {
10662
10718
  throw new WorkflowIrError(`split '${split.id}' branches converge on more than one join`);
10663
10719
  }
10664
10720
  const joinId = [...joinsReached][0];
10665
- const join2 = nodesById.get(joinId);
10666
- const mode = join2.config?.mode;
10721
+ const join5 = nodesById.get(joinId);
10722
+ const mode = join5.config?.mode;
10667
10723
  if (mode && typeof mode === "object" && "quorum" in mode) {
10668
10724
  const n = mode.quorum;
10669
10725
  if (typeof n !== "number" || !Number.isInteger(n) || n < 1) {
10670
- throw new WorkflowIrError(`join '${join2.id}' quorum must be a positive integer`);
10726
+ throw new WorkflowIrError(`join '${join5.id}' quorum must be a positive integer`);
10671
10727
  }
10672
10728
  if (n > branchEdges.length) {
10673
10729
  throw new WorkflowIrError(
10674
- `join '${join2.id}' quorum(${n}) exceeds the split's ${branchEdges.length} branches`
10730
+ `join '${join5.id}' quorum(${n}) exceeds the split's ${branchEdges.length} branches`
10675
10731
  );
10676
10732
  }
10677
10733
  }
@@ -16243,18 +16299,18 @@ function canAgentTakeImplementationTaskForBacklogPickup(agent, task, options = {
16243
16299
  function canAgentTakeImplementationTask(agent, task, options) {
16244
16300
  return canAgentTakeImplementationTaskForBacklogPickup(agent, task, options);
16245
16301
  }
16246
- function evaluateImplementationTaskBind(agent, task, context = {}) {
16302
+ function evaluateImplementationTaskBind(agent, task, context2 = {}) {
16247
16303
  if (!isImplementationTask(task)) {
16248
16304
  return { allowed: true };
16249
16305
  }
16250
16306
  if (!canAgentReceiveImplementationTasks(agent)) {
16251
16307
  return { allowed: false, reason: formatRoleMismatchReason(agent, task) };
16252
16308
  }
16253
- if (context.executorRoleOverride === true) {
16309
+ if (context2.executorRoleOverride === true) {
16254
16310
  return { allowed: true };
16255
16311
  }
16256
- const explicit = context.explicitRouting === true;
16257
- const roleAllowed = explicit ? canAgentTakeImplementationTaskForExplicitRouting(agent, task) : canAgentTakeImplementationTask(agent, task, { allowEngineer: context.allowEngineer });
16312
+ const explicit = context2.explicitRouting === true;
16313
+ const roleAllowed = explicit ? canAgentTakeImplementationTaskForExplicitRouting(agent, task) : canAgentTakeImplementationTask(agent, task, { allowEngineer: context2.allowEngineer });
16258
16314
  return roleAllowed ? { allowed: true } : { allowed: false, reason: formatRoleMismatchReason(agent, task) };
16259
16315
  }
16260
16316
  var AgentTaskRoutingPolicyError = class extends Error {
@@ -16266,8 +16322,8 @@ var AgentTaskRoutingPolicyError = class extends Error {
16266
16322
  }
16267
16323
  code = "agent-task-routing-policy";
16268
16324
  };
16269
- function assertImplementationTaskBindAllowed(agent, task, context = {}) {
16270
- const verdict = evaluateImplementationTaskBind(agent, task, context);
16325
+ function assertImplementationTaskBindAllowed(agent, task, context2 = {}) {
16326
+ const verdict = evaluateImplementationTaskBind(agent, task, context2);
16271
16327
  if (!verdict.allowed) {
16272
16328
  throw new AgentTaskRoutingPolicyError(agent.id, task.id, verdict.reason);
16273
16329
  }
@@ -17966,10 +18022,6 @@ var AgentStore = class _AgentStore extends EventEmitter {
17966
18022
  };
17967
18023
  await this.writeAgent(updated);
17968
18024
  this.emit("agent:stateChanged", agentId, currentState, newState);
17969
- if (this.asyncLayer) try {
17970
- await appendAgentActivityEvent(this.asyncLayer, { type: "agent:state-changed", attributionClaim: resolveAgentActivityAttribution([{ id: agentId, provenance: "roster" }], "executor"), taskId: updated.taskId, occurredAt: updated.updatedAt, discriminator: updated.updatedAt, metadata: { fromState: currentState, toState: newState, source: "update" } });
17971
- } catch {
17972
- }
17973
18025
  this.emit("agent:updated", updated, currentState);
17974
18026
  return updated;
17975
18027
  });
@@ -18461,10 +18513,10 @@ var AgentStore = class _AgentStore extends EventEmitter {
18461
18513
  return agents2;
18462
18514
  }
18463
18515
  /*
18464
- FNXC:MemoryAgent 2026-08-11-09:41:
18465
- Memory Keeper is custom because it is not a workflow-stage principal. Unlike the four routed
18466
- owners its heartbeat is enabled, while auto-claim remains off because it only maintains memory.
18467
- This runs during init, where createAgent name collisions would abort startup; preflight probes,
18516
+ FNXC:MemoryAgent 2026-08-16-02:31:
18517
+ Memory Keeper is custom because it is not a workflow-stage principal. Its heartbeat defaults off
18518
+ until an operator opts in, while auto-claim remains off because it only maintains memory. This
18519
+ runs during init, where createAgent name collisions would abort startup; preflight probes,
18468
18520
  a fallback name, a null degraded result, and the init-side catch keep an operator's same-named
18469
18521
  agent untouched and the project runnable.
18470
18522
  */
@@ -18490,7 +18542,13 @@ var AgentStore = class _AgentStore extends EventEmitter {
18490
18542
  roles: [...BUILTIN_MEMORY_AGENT_DEFAULT.roles],
18491
18543
  title: BUILTIN_MEMORY_AGENT_DEFAULT.title,
18492
18544
  metadata: { [BUILTIN_MEMORY_AGENT_PROVENANCE_KEY]: true },
18493
- runtimeConfig: { enabled: true, autoClaimRelevantTasks: false, heartbeatIntervalMs: 36e5 },
18545
+ /*
18546
+ FNXC:MemoryAgent 2026-08-16-02:31:
18547
+ Memory consolidation is opt-in: newly provisioned Memory Keepers must not begin
18548
+ hourly autonomous heartbeats until an operator enables the existing heartbeat toggle.
18549
+ Keep auto-claim off and the one-hour interval ready for that later opt-in.
18550
+ */
18551
+ runtimeConfig: { enabled: false, autoClaimRelevantTasks: false, heartbeatIntervalMs: 36e5 },
18494
18552
  instructionsText: BUILTIN_MEMORY_AGENT_DEFAULT.instructionsText,
18495
18553
  soul: BUILTIN_MEMORY_AGENT_DEFAULT.soul,
18496
18554
  bundleConfig: { ...BUILTIN_MEMORY_AGENT_DEFAULT.bundleConfig, files: [...BUILTIN_MEMORY_AGENT_DEFAULT.bundleConfig.files] }
@@ -18505,7 +18563,9 @@ var AgentStore = class _AgentStore extends EventEmitter {
18505
18563
  return owner;
18506
18564
  }
18507
18565
  const metadata = { ...owner.metadata ?? {}, [BUILTIN_MEMORY_AGENT_PROVENANCE_KEY]: true };
18508
- const runtimeConfig = { ...owner.runtimeConfig ?? {}, enabled: true, autoClaimRelevantTasks: false, heartbeatIntervalMs: 36e5 };
18566
+ const currentRuntimeConfig = owner.runtimeConfig ?? {};
18567
+ const enabled = typeof currentRuntimeConfig.enabled === "boolean" ? currentRuntimeConfig.enabled : false;
18568
+ const runtimeConfig = currentRuntimeConfig.enabled === enabled && currentRuntimeConfig.autoClaimRelevantTasks === false && currentRuntimeConfig.heartbeatIntervalMs === DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS ? currentRuntimeConfig : { ...currentRuntimeConfig, enabled, autoClaimRelevantTasks: false, heartbeatIntervalMs: DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS };
18509
18569
  const updates = {
18510
18570
  roles: [...BUILTIN_MEMORY_AGENT_DEFAULT.roles],
18511
18571
  role: "custom",
@@ -18515,7 +18575,9 @@ var AgentStore = class _AgentStore extends EventEmitter {
18515
18575
  instructionsText: owner.instructionsText?.trim() ? owner.instructionsText : BUILTIN_MEMORY_AGENT_DEFAULT.instructionsText,
18516
18576
  soul: owner.soul?.trim() ? owner.soul : BUILTIN_MEMORY_AGENT_DEFAULT.soul
18517
18577
  };
18518
- owner = { ...owner, ...updates, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
18578
+ const nextOwner = { ...owner, ...updates };
18579
+ if (JSON.stringify(nextOwner) === JSON.stringify(owner)) return owner;
18580
+ owner = { ...nextOwner, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
18519
18581
  await this.writeAgent(owner, executor);
18520
18582
  return owner;
18521
18583
  };
@@ -19103,14 +19165,14 @@ var AgentStore = class _AgentStore extends EventEmitter {
19103
19165
  const bundleDir = await this.resolveCompatibleBundleDir(agent.id, true);
19104
19166
  await mkdir(bundleDir, { recursive: true });
19105
19167
  for (const [file, content] of [["AGENTS.md", definition.instructionsText], ["soul.md", definition.soul]]) {
19106
- const path = join(bundleDir, file);
19168
+ const path3 = join(bundleDir, file);
19107
19169
  let current = "";
19108
19170
  try {
19109
- current = await readFile(path, "utf-8");
19171
+ current = await readFile(path3, "utf-8");
19110
19172
  } catch (error) {
19111
19173
  if (error.code !== "ENOENT") throw error;
19112
19174
  }
19113
- if (!current.trim()) await writeFile(path, content, "utf-8");
19175
+ if (!current.trim()) await writeFile(path3, content, "utf-8");
19114
19176
  }
19115
19177
  });
19116
19178
  }
@@ -19188,9 +19250,9 @@ var AgentStore = class _AgentStore extends EventEmitter {
19188
19250
  }
19189
19251
  return canonicalPath;
19190
19252
  }
19191
- async pathExists(path) {
19253
+ async pathExists(path3) {
19192
19254
  try {
19193
- await access(path, fsConstants.F_OK);
19255
+ await access(path3, fsConstants.F_OK);
19194
19256
  return true;
19195
19257
  } catch {
19196
19258
  return false;
@@ -19387,19 +19449,6 @@ var AgentStore = class _AgentStore extends EventEmitter {
19387
19449
  this.agentSnapshotCache.set(agent.id, this.watchSnapshotOf(agent));
19388
19450
  if (stateChanged) {
19389
19451
  this.emit("agent:stateChanged", agent.id, previousState, agent.state);
19390
- if (this.asyncLayer) {
19391
- try {
19392
- await appendAgentActivityEvent(this.asyncLayer, {
19393
- type: "agent:state-changed",
19394
- attributionClaim: resolveAgentActivityAttribution([{ id: agent.id, provenance: "roster" }], "executor"),
19395
- taskId: agent.taskId,
19396
- occurredAt: agent.updatedAt,
19397
- discriminator: agent.updatedAt,
19398
- metadata: { fromState: previousState, toState: agent.state, source: "reconciliation" }
19399
- });
19400
- } catch {
19401
- }
19402
- }
19403
19452
  this.emit("agent:updated", agent, previousState);
19404
19453
  } else {
19405
19454
  this.emit("agent:updated", agent);
@@ -19458,13 +19507,80 @@ var AgentStore = class _AgentStore extends EventEmitter {
19458
19507
  }
19459
19508
  };
19460
19509
 
19510
+ // src/plugin-sdk-core-runtime-shim.mjs
19511
+ function superviseSpawn(command, args = [], options = {}) {
19512
+ const killGraceMs = options.killGraceMs ?? 2e3;
19513
+ const maxLifetimeMs = options.maxLifetimeMs;
19514
+ const spawnOptions = { ...options };
19515
+ delete spawnOptions.killGraceMs;
19516
+ delete spawnOptions.maxLifetimeMs;
19517
+ const processGroup = globalThis.process.platform !== "win32";
19518
+ const child = spawn(command, [...args], { ...spawnOptions, detached: processGroup });
19519
+ const pgid = processGroup && typeof child.pid === "number" ? child.pid : null;
19520
+ let settled = false;
19521
+ let resolveExit;
19522
+ const waitExit = new Promise((resolve2) => {
19523
+ resolveExit = resolve2;
19524
+ });
19525
+ const killProcess = (signal = "SIGTERM") => {
19526
+ if (typeof child.pid !== "number") return;
19527
+ try {
19528
+ if (pgid != null) globalThis.process.kill(-pgid, signal);
19529
+ else child.kill(signal);
19530
+ } catch {
19531
+ try {
19532
+ child.kill(signal);
19533
+ } catch {
19534
+ }
19535
+ }
19536
+ };
19537
+ let lifetimeTimer = null;
19538
+ if (typeof maxLifetimeMs === "number" && Number.isFinite(maxLifetimeMs) && maxLifetimeMs > 0) {
19539
+ lifetimeTimer = globalThis.setTimeout(() => {
19540
+ if (settled) return;
19541
+ killProcess("SIGTERM");
19542
+ const escalationTimer = globalThis.setTimeout(() => {
19543
+ if (!settled) killProcess("SIGKILL");
19544
+ }, killGraceMs);
19545
+ escalationTimer.unref?.();
19546
+ }, maxLifetimeMs);
19547
+ lifetimeTimer.unref?.();
19548
+ }
19549
+ child.once("close", (code, signal) => {
19550
+ settled = true;
19551
+ if (lifetimeTimer) globalThis.clearTimeout(lifetimeTimer);
19552
+ resolveExit?.({ code, signal });
19553
+ });
19554
+ child.on("error", () => {
19555
+ });
19556
+ return {
19557
+ pid: child.pid,
19558
+ pgid,
19559
+ child,
19560
+ kill(signal = "SIGTERM") {
19561
+ if (settled) return;
19562
+ killProcess(signal);
19563
+ if (signal === "SIGTERM") {
19564
+ const escalationTimer = globalThis.setTimeout(() => {
19565
+ if (!settled) killProcess("SIGKILL");
19566
+ }, killGraceMs);
19567
+ escalationTimer.unref?.();
19568
+ }
19569
+ },
19570
+ waitExit() {
19571
+ return waitExit;
19572
+ }
19573
+ };
19574
+ }
19575
+
19461
19576
  // ../plugin-sdk/src/index.ts
19462
19577
  function definePlugin(plugin2) {
19463
19578
  return plugin2;
19464
19579
  }
19465
19580
 
19466
19581
  // ../../plugins/fusion-plugin-cursor-runtime/src/cli-spawn.ts
19467
- import { spawn } from "node:child_process";
19582
+ import { spawn as spawn2, spawnSync } from "node:child_process";
19583
+ import path, { sep as PATH_SEP } from "node:path";
19468
19584
  function formatSpawnError(error) {
19469
19585
  const code = typeof error.code === "string" ? `${error.code}: ` : "";
19470
19586
  return `spawn error: ${code}${error.message}`.trim();
@@ -19481,7 +19597,7 @@ async function runCursorCommand(binary, args, timeoutMs) {
19481
19597
  if (timer) clearTimeout(timer);
19482
19598
  resolve2(result);
19483
19599
  };
19484
- const child = spawn(binary, args, {
19600
+ const child = spawn2(binary, args, {
19485
19601
  stdio: ["ignore", "pipe", "pipe"],
19486
19602
  shell: process.platform === "win32"
19487
19603
  });
@@ -19509,6 +19625,56 @@ ${diagnostic}` : diagnostic;
19509
19625
  });
19510
19626
  });
19511
19627
  }
19628
+ var resolvedSpawnBinaries = /* @__PURE__ */ new Map();
19629
+ function resolveCursorBinaryForSpawn(binary) {
19630
+ if (process.platform !== "win32") return binary;
19631
+ if (binary.includes(PATH_SEP) || binary.includes("/") || /\.[^.\\/]+$/i.test(binary)) return binary;
19632
+ const cached = resolvedSpawnBinaries.get(binary);
19633
+ if (cached) return cached;
19634
+ let result;
19635
+ try {
19636
+ result = spawnSync("where.exe", [binary], { encoding: "utf-8", windowsHide: true, shell: false, timeout: 2e3 });
19637
+ } catch {
19638
+ return binary;
19639
+ }
19640
+ const lines = String(result.stdout ?? "").split(/\r?\n/).map((line2) => line2.trim()).filter(Boolean);
19641
+ if (!lines.length) return binary;
19642
+ const first = lines[0];
19643
+ const firstDirectory = path.dirname(first).toLowerCase();
19644
+ const extensions = (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD").split(";").map((extension) => extension.toLowerCase());
19645
+ const candidate = extensions.map((extension) => lines.find((line2) => path.dirname(line2).toLowerCase() === firstDirectory && path.extname(line2).toLowerCase() === extension)).find(Boolean) ?? first;
19646
+ resolvedSpawnBinaries.set(binary, candidate);
19647
+ return candidate;
19648
+ }
19649
+ function classifyWindowsLaunchTarget(target) {
19650
+ const extension = path.extname(target).toLowerCase();
19651
+ if (!extension || extension === ".exe" || extension === ".com") return "direct";
19652
+ if (extension === ".cmd" || extension === ".bat") return "cmd-shim";
19653
+ if (extension === ".ps1") return "powershell-shim";
19654
+ return "unsupported";
19655
+ }
19656
+ var CMD_UNSAFE = /["%!^&|<>()\r\n\0]/;
19657
+ function assertCmdBoundarySafe(token) {
19658
+ const offending = token.match(CMD_UNSAFE)?.[0];
19659
+ if (offending) throw new Error(`Cursor cmd boundary rejected token ${JSON.stringify(token)} containing ${JSON.stringify(offending)}; relocate the worktree or use a direct cursor-agent executable.`);
19660
+ }
19661
+ function quoteCmdArgument(value) {
19662
+ assertCmdBoundarySafe(value);
19663
+ return `"${value}"`;
19664
+ }
19665
+ function resolvePowerShellExecutable() {
19666
+ try {
19667
+ const result = spawnSync("where.exe", ["pwsh.exe"], {
19668
+ encoding: "utf-8",
19669
+ windowsHide: true,
19670
+ shell: false,
19671
+ timeout: 2e3
19672
+ });
19673
+ if (result.status === 0 && String(result.stdout ?? "").split(/\r?\n/).some((line2) => line2.trim())) return "pwsh.exe";
19674
+ } catch {
19675
+ }
19676
+ return "powershell.exe";
19677
+ }
19512
19678
 
19513
19679
  // ../../plugins/fusion-plugin-cursor-runtime/src/probe.ts
19514
19680
  var CANDIDATES = ["cursor-agent", "cursor"];
@@ -19526,12 +19692,12 @@ function summarizeFailure(binary, stdout, stderr) {
19526
19692
  }
19527
19693
  async function probeCursorAuthStatus(binary, timeoutMs) {
19528
19694
  const status = await runCursorCommand(binary, ["status", "--format", "json"], timeoutMs);
19529
- const output = (status.stdout || "").trim();
19530
- if (status.code !== 0 || !output) {
19695
+ const output2 = (status.stdout || "").trim();
19696
+ if (status.code !== 0 || !output2) {
19531
19697
  return { authenticated: false, reason: "cursor-agent status --format json did not return output" };
19532
19698
  }
19533
19699
  try {
19534
- const parsed = JSON.parse(output);
19700
+ const parsed = JSON.parse(output2);
19535
19701
  if (typeof parsed?.isAuthenticated === "boolean") {
19536
19702
  return {
19537
19703
  authenticated: parsed.isAuthenticated,
@@ -19615,15 +19781,15 @@ async function discoverCursorModels(binary, timeoutMs = 5e3) {
19615
19781
  if (res.code !== 0) {
19616
19782
  return { models: [], source: "none", fallbackUsed: true, reason: "model discovery command unavailable" };
19617
19783
  }
19618
- const output = (res.stdout || "").trim();
19619
- if (!output) {
19784
+ const output2 = (res.stdout || "").trim();
19785
+ if (!output2) {
19620
19786
  return { models: [], source: "none", fallbackUsed: true, reason: "model discovery command returned no output" };
19621
19787
  }
19622
- if (output.toLowerCase() === EMPTY_ACCOUNT_MESSAGE) {
19788
+ if (output2.toLowerCase() === EMPTY_ACCOUNT_MESSAGE) {
19623
19789
  return { models: [], source: "models-text", fallbackUsed: false, reason: "no models available for this account" };
19624
19790
  }
19625
19791
  try {
19626
- const parsed = JSON.parse(output);
19792
+ const parsed = JSON.parse(output2);
19627
19793
  if (Array.isArray(parsed)) {
19628
19794
  const ids2 = [];
19629
19795
  const modelMeta = {};
@@ -19646,7 +19812,7 @@ async function discoverCursorModels(binary, timeoutMs = 5e3) {
19646
19812
  }
19647
19813
  } catch {
19648
19814
  }
19649
- const ids = parseModelLines(output);
19815
+ const ids = parseModelLines(output2);
19650
19816
  if (ids.length > 0) {
19651
19817
  return { models: ids, source: "models-text", fallbackUsed: false };
19652
19818
  }
@@ -19685,24 +19851,881 @@ async function discoverCursorProviderModels(options) {
19685
19851
  }
19686
19852
 
19687
19853
  // ../../plugins/fusion-plugin-cursor-runtime/src/runtime-adapter.ts
19854
+ import { randomUUID as randomUUID4 } from "node:crypto";
19855
+
19856
+ // ../../plugins/fusion-plugin-cursor-runtime/src/prompt-transport.ts
19857
+ import { existsSync } from "node:fs";
19858
+ import { spawn as spawn3 } from "node:child_process";
19859
+ import readline from "node:readline";
19860
+ import path2 from "node:path";
19861
+
19862
+ // ../../plugins/fusion-plugin-cursor-runtime/src/stream-parser.ts
19863
+ function parseCursorStreamLine(line2) {
19864
+ if (!line2.trim()) return { kind: "unknown" };
19865
+ let value;
19866
+ try {
19867
+ const parsed = JSON.parse(line2);
19868
+ if (!parsed || typeof parsed !== "object") return { kind: "unknown" };
19869
+ value = parsed;
19870
+ } catch {
19871
+ return { kind: "unknown" };
19872
+ }
19873
+ const type = value.type;
19874
+ const sessionId = typeof value.session_id === "string" ? value.session_id : void 0;
19875
+ if (type === "system" && value.subtype === "init") return { kind: "system-init", sessionId, cwd: typeof value.cwd === "string" ? value.cwd : void 0, model: typeof value.model === "string" ? value.model : void 0 };
19876
+ if (type === "thinking" && value.subtype === "delta" && typeof value.text === "string") return { kind: "thinking-delta", text: value.text };
19877
+ if (type === "assistant") {
19878
+ const message = value.message;
19879
+ const content = Array.isArray(message?.content) ? message.content : [];
19880
+ const text3 = content.filter((item) => Boolean(item) && typeof item === "object" && typeof item.text === "string").map((item) => item.text).join("");
19881
+ return text3 ? { kind: "assistant-text", text: text3 } : { kind: "unknown" };
19882
+ }
19883
+ if (type === "tool_call" && (value.subtype === "started" || value.subtype === "completed")) {
19884
+ const call = value.tool_call;
19885
+ const key = call && Object.keys(call).find((name2) => name2.endsWith("ToolCall"));
19886
+ if (!key) return { kind: "unknown" };
19887
+ const details = call[key];
19888
+ const name = key.slice(0, -"ToolCall".length) || "unknown";
19889
+ return { kind: value.subtype === "started" ? "tool-call-started" : "tool-call-completed", callId: typeof value.call_id === "string" ? value.call_id : void 0, name, args: details?.args && typeof details.args === "object" ? details.args : void 0, result: details?.result };
19890
+ }
19891
+ if (type === "result") return { kind: "result", sessionId, text: typeof value.result === "string" ? value.result : void 0, isError: value.is_error === true, usage: value.usage };
19892
+ return { kind: "unknown" };
19893
+ }
19894
+
19895
+ // ../../plugins/fusion-plugin-cursor-runtime/src/prompt-transport.ts
19896
+ var FIRST_LINE_DEFAULT_MS = 3e4;
19897
+ var INACTIVITY_DEFAULT_MS = 12e4;
19898
+ var STDERR_MAX = 16384;
19899
+ function timeout(name, fallback) {
19900
+ const value = Number(process.env[name]);
19901
+ return Number.isFinite(value) && value > 0 ? value : fallback;
19902
+ }
19903
+ async function launchCursorPrompt(input, deps = {}) {
19904
+ if (!input.cwd || !existsSync(input.cwd)) throw new Error(`Cursor CLI requires an existing session cwd: ${input.cwd || "(missing)"}`);
19905
+ const platform = deps.platform ?? process.platform;
19906
+ const configuredBinary = input.binary?.trim();
19907
+ const target = resolveCursorBinaryForSpawn(configuredBinary || "cursor-agent");
19908
+ const fallbackBinary = configuredBinary ? void 0 : target === "cursor-agent" ? "cursor" : void 0;
19909
+ const model = (input.model || "auto").replace(/^cursor-cli\//, "");
19910
+ const args = ["--print", "--output-format", "stream-json", "--model", model, "--trust"];
19911
+ if (input.workspaceFlagRequired) args.push("--workspace", input.cwd);
19912
+ if (input.tools === "coding") args.push("--force");
19913
+ else args.push("--mode", "plan");
19914
+ if (input.approveMcps) args.push("--approve-mcps");
19915
+ if (input.resumeId) args.push("--resume", input.resumeId);
19916
+ const options = { shell: false, windowsHide: true, stdio: ["pipe", "pipe", "pipe"], cwd: input.cwd, maxLifetimeMs: Number.POSITIVE_INFINITY };
19917
+ const supervise = deps.supervise ?? superviseSpawn;
19918
+ let command = target;
19919
+ let launchArgs = args;
19920
+ let launchOptions = options;
19921
+ if (platform === "win32") {
19922
+ const classification = classifyWindowsLaunchTarget(target);
19923
+ if (classification === "unsupported") throw new Error(`Cursor CLI resolved target ${target} has unsupported ${path2.extname(target)} extension; point cursorCliBinaryPath at the cursor-agent executable.`);
19924
+ if (classification === "cmd-shim") {
19925
+ [target, ...args].forEach(assertCmdBoundarySafe);
19926
+ const commandLine = [quoteCmdArgument(target), ...args.map(quoteCmdArgument)].join(" ");
19927
+ const configured = process.env.ComSpec;
19928
+ const comspec = configured && path2.isAbsolute(configured) && path2.basename(configured).toLowerCase() === "cmd.exe" && !configured.includes('"') ? configured : "cmd.exe";
19929
+ if (comspec.length + commandLine.length > 8e3) throw new Error(`Cursor cmd command line length ${comspec.length + commandLine.length} exceeds 8000 characters for ${target}.`);
19930
+ command = comspec;
19931
+ launchArgs = ["/d", "/s", "/c", `"${commandLine}"`];
19932
+ launchOptions = { ...options, windowsVerbatimArguments: true };
19933
+ } else if (classification === "powershell-shim") {
19934
+ command = (deps.resolvePowerShell ?? resolvePowerShellExecutable)();
19935
+ launchArgs = ["-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", target, ...args];
19936
+ }
19937
+ }
19938
+ let supervised;
19939
+ try {
19940
+ supervised = supervise(command, launchArgs, launchOptions);
19941
+ } catch (error) {
19942
+ if (fallbackBinary && error.code === "ENOENT") {
19943
+ return launchCursorPrompt({ ...input, binary: fallbackBinary }, deps);
19944
+ }
19945
+ throw new Error(`Cursor CLI spawn failed for ${target} (${platform === "win32" ? classifyWindowsLaunchTarget(target) : "direct"}): ${error instanceof Error ? error.message : String(error)}`);
19946
+ }
19947
+ const child = supervised.child;
19948
+ let settled = false, sawResult = false, output2 = "", sessionId, usage, stderr = "", teardownReason;
19949
+ let firstTimer;
19950
+ let inactivityTimer;
19951
+ const teardown = (reason) => {
19952
+ if (teardownReason || child.exitCode !== null || child.signalCode !== null) return;
19953
+ teardownReason = reason;
19954
+ if (platform === "win32" && typeof supervised.pid === "number") {
19955
+ try {
19956
+ (deps.taskkill ?? spawn3)("taskkill", ["/pid", String(supervised.pid), "/T", "/F"], { stdio: "ignore", windowsHide: true }).on("error", () => void 0);
19957
+ } catch {
19958
+ }
19959
+ supervised.kill();
19960
+ } else supervised.kill("SIGKILL");
19961
+ };
19962
+ return new Promise((resolve2, reject) => {
19963
+ const finish = (error) => {
19964
+ if (settled) return;
19965
+ settled = true;
19966
+ if (firstTimer) clearTimeout(firstTimer);
19967
+ if (inactivityTimer) clearTimeout(inactivityTimer);
19968
+ if (input.signal) input.signal.removeEventListener("abort", abort);
19969
+ if (error) reject(error);
19970
+ else resolve2({ sessionId, text: output2, usage });
19971
+ };
19972
+ const retryWithFallback = (error) => {
19973
+ if (!fallbackBinary || error.code !== "ENOENT" || settled) return false;
19974
+ settled = true;
19975
+ if (firstTimer) clearTimeout(firstTimer);
19976
+ if (inactivityTimer) clearTimeout(inactivityTimer);
19977
+ if (input.signal) input.signal.removeEventListener("abort", abort);
19978
+ void launchCursorPrompt({ ...input, binary: fallbackBinary }, deps).then(resolve2, reject);
19979
+ return true;
19980
+ };
19981
+ const resetInactivity = () => {
19982
+ if (inactivityTimer) clearTimeout(inactivityTimer);
19983
+ inactivityTimer = setTimeout(() => {
19984
+ teardown("Cursor CLI inactivity timeout");
19985
+ finish(new Error("Cursor CLI inactivity timeout"));
19986
+ }, timeout("PI_CURSOR_CLI_TIMEOUT_MS", INACTIVITY_DEFAULT_MS));
19987
+ };
19988
+ const abort = () => {
19989
+ teardown("Cursor CLI aborted");
19990
+ finish(new Error("Cursor CLI aborted"));
19991
+ };
19992
+ firstTimer = setTimeout(() => {
19993
+ teardown("Cursor CLI first-line timeout");
19994
+ finish(new Error("Cursor CLI first-line timeout"));
19995
+ }, timeout("PI_CURSOR_CLI_FIRST_LINE_TIMEOUT_MS", FIRST_LINE_DEFAULT_MS));
19996
+ input.signal?.addEventListener("abort", abort, { once: true });
19997
+ child.stderr?.on("data", (chunk) => {
19998
+ stderr = (stderr + String(chunk)).slice(-STDERR_MAX);
19999
+ });
20000
+ child.stdin?.on("error", (error) => {
20001
+ if (error.code !== "EPIPE" && error.code !== "ERR_STREAM_DESTROYED") finish(error);
20002
+ });
20003
+ const lines = readline.createInterface({ input: child.stdout });
20004
+ lines.on("line", (line2) => {
20005
+ if (firstTimer) {
20006
+ clearTimeout(firstTimer);
20007
+ firstTimer = void 0;
20008
+ }
20009
+ resetInactivity();
20010
+ const event = parseCursorStreamLine(line2);
20011
+ if (event.kind === "system-init") sessionId = event.sessionId ?? sessionId;
20012
+ if (event.kind === "thinking-delta") input.onThinking?.(event.text);
20013
+ if (event.kind === "assistant-text") {
20014
+ output2 += event.text;
20015
+ input.onText?.(event.text);
20016
+ }
20017
+ if (event.kind === "tool-call-started") input.onToolStart?.(event.name, event.args);
20018
+ if (event.kind === "tool-call-completed") input.onToolEnd?.(event.name, false, event.result);
20019
+ if (event.kind === "result") {
20020
+ sawResult = true;
20021
+ sessionId = event.sessionId ?? sessionId;
20022
+ usage = event.usage;
20023
+ if (event.isError) finish(new Error(`Cursor CLI reported an error: ${event.text ?? "unknown error"}`));
20024
+ }
20025
+ });
20026
+ child.once("error", (error) => {
20027
+ if (!retryWithFallback(error)) finish(new Error(`Cursor CLI spawn failed for ${target}: ${error.message}`));
20028
+ });
20029
+ child.once("close", (code) => {
20030
+ if (teardownReason) return finish(new Error(teardownReason));
20031
+ if (code !== 0) return finish(new Error(`Cursor CLI exited ${code}: ${stderr}`));
20032
+ if (!sawResult) return finish(new Error("Cursor CLI stream ended without a result event."));
20033
+ finish();
20034
+ });
20035
+ try {
20036
+ child.stdin?.end(input.prompt);
20037
+ } catch (error) {
20038
+ finish(error instanceof Error ? error : new Error(String(error)));
20039
+ }
20040
+ });
20041
+ }
20042
+
20043
+ // ../../plugins/fusion-plugin-cursor-runtime/src/worktree-hygiene.ts
20044
+ import { existsSync as existsSync2, mkdirSync, readFileSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
20045
+ import { hostname } from "node:os";
20046
+ import { dirname as dirname2, isAbsolute, join as join2, relative, sep } from "node:path";
20047
+ import { spawnSync as spawnSync2 } from "node:child_process";
20048
+ var START = "# >>> fusion cursor-runtime >>>";
20049
+ var END = "# <<< fusion cursor-runtime <<<";
20050
+ var git = (cwd, ...args) => spawnSync2("git", args, { cwd, encoding: "utf8" });
20051
+ var output = (cwd, ...args) => {
20052
+ const result = git(cwd, ...args);
20053
+ return result.status === 0 ? result.stdout.trim() : void 0;
20054
+ };
20055
+ var atomic = (file, raw) => {
20056
+ mkdirSync(dirname2(file), { recursive: true });
20057
+ const tmp = `${file}.${process.pid}.tmp`;
20058
+ writeFileSync(tmp, raw, { mode: 384 });
20059
+ renameSync(tmp, file);
20060
+ };
20061
+ function resolveGitShape(worktreePath) {
20062
+ const topLevel = output(worktreePath, "rev-parse", "--show-toplevel");
20063
+ const excludePath = output(worktreePath, "rev-parse", "--git-path", "info/exclude");
20064
+ const gitDir = output(worktreePath, "rev-parse", "--git-path", ".");
20065
+ if (!topLevel || !excludePath || !gitDir) return void 0;
20066
+ const rel = relative(topLevel, worktreePath).split(sep).filter(Boolean).join("/");
20067
+ const fromGitPath = (value) => isAbsolute(value) ? value : join2(worktreePath, value);
20068
+ return { topLevel, excludePath: fromGitPath(excludePath), gitDir: fromGitPath(gitDir), relativeWorktree: rel };
20069
+ }
20070
+ function classifyCursorConfigTracking(worktreePath) {
20071
+ const shape = resolveGitShape(worktreePath);
20072
+ if (!shape) return "non-git";
20073
+ const path3 = `${shape.relativeWorktree ? `${shape.relativeWorktree}/` : ""}.cursor/mcp.json`;
20074
+ return git(shape.topLevel, "ls-files", "--error-unmatch", "--", path3).status === 0 ? "tracked" : "untracked";
20075
+ }
20076
+ var block = (shape) => {
20077
+ const prefix = shape.relativeWorktree ? `/${shape.relativeWorktree}/` : "/";
20078
+ return `${START}
20079
+ ${prefix}.cursor/mcp.json
20080
+ ${prefix}.cursor/.fusion-mcp-state.json
20081
+ ${prefix}.cursor/.fusion-mcp.lock/
20082
+ ${END}
20083
+ `;
20084
+ };
20085
+ var withoutBlock = (raw) => raw.replace(new RegExp(`${START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n[\\s\\S]*?${END.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n?`, "g"), "");
20086
+ var BOOTSTRAP_LOCK_TTL_MS = 1e4;
20087
+ var BOOTSTRAP_LOCK_ATTEMPTS = 5;
20088
+ var BOOTSTRAP_LOCK_RETRY_MS = 10;
20089
+ var bootstrapLock = (shape) => join2(shape.gitDir, "fusion-cursor-exclude.lock");
20090
+ var sleepSync = (milliseconds) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
20091
+ var lockAgeMs = (lock) => {
20092
+ try {
20093
+ return Date.now() - statSync(lock).mtimeMs;
20094
+ } catch {
20095
+ return Number.POSITIVE_INFINITY;
20096
+ }
20097
+ };
20098
+ function withBootstrapLock(shape, fn) {
20099
+ const lock = bootstrapLock(shape);
20100
+ const mine = { pid: process.pid, hostname: hostname(), acquiredAt: Date.now() };
20101
+ let acquired = false;
20102
+ for (let attempt = 0; attempt < BOOTSTRAP_LOCK_ATTEMPTS; attempt++) {
20103
+ try {
20104
+ mkdirSync(lock);
20105
+ writeFileSync(join2(lock, "owner.json"), JSON.stringify(mine), { mode: 384 });
20106
+ acquired = true;
20107
+ break;
20108
+ } catch {
20109
+ let owner;
20110
+ try {
20111
+ owner = JSON.parse(readFileSync(join2(lock, "owner.json"), "utf8"));
20112
+ } catch {
20113
+ }
20114
+ const deadLocalOwner = owner?.hostname === mine.hostname && typeof owner.pid === "number" && (() => {
20115
+ try {
20116
+ process.kill(owner.pid, 0);
20117
+ return false;
20118
+ } catch {
20119
+ return true;
20120
+ }
20121
+ })();
20122
+ const stale2 = lockAgeMs(lock) > BOOTSTRAP_LOCK_TTL_MS || deadLocalOwner || owner !== void 0 && Date.now() - (owner.acquiredAt ?? 0) > BOOTSTRAP_LOCK_TTL_MS;
20123
+ if (stale2) {
20124
+ try {
20125
+ rmSync(lock, { recursive: true, force: true });
20126
+ } catch {
20127
+ }
20128
+ }
20129
+ if (attempt < BOOTSTRAP_LOCK_ATTEMPTS - 1) sleepSync(BOOTSTRAP_LOCK_RETRY_MS);
20130
+ }
20131
+ }
20132
+ if (!acquired) throw Object.assign(new Error("Cursor MCP exclude lock unavailable"), { code: "bridge-start-failed" });
20133
+ try {
20134
+ return fn();
20135
+ } finally {
20136
+ try {
20137
+ const owner = JSON.parse(readFileSync(join2(lock, "owner.json"), "utf8"));
20138
+ if (owner.pid === mine.pid && owner.hostname === mine.hostname) rmSync(lock, { recursive: true, force: true });
20139
+ } catch {
20140
+ }
20141
+ }
20142
+ }
20143
+ function ensureCursorConfigExcluded(worktreePath, options = {}) {
20144
+ const shape = resolveGitShape(worktreePath);
20145
+ if (!shape) return { present: false, fusionAuthored: false, created: false };
20146
+ const ensure = () => {
20147
+ const raw = existsSync2(shape.excludePath) ? readFileSync(shape.excludePath, "utf8") : "";
20148
+ if (raw.includes(START) && raw.includes(END)) return { present: true, fusionAuthored: true, created: false };
20149
+ atomic(shape.excludePath, `${raw}${raw && !raw.endsWith("\n") ? "\n" : ""}${block(shape)}`);
20150
+ return { present: true, fusionAuthored: true, created: true };
20151
+ };
20152
+ return options.lockHeld ? ensure() : withBootstrapLock(shape, ensure);
20153
+ }
20154
+ function removeCursorConfigExclusion(worktreePath) {
20155
+ const shape = resolveGitShape(worktreePath);
20156
+ if (!shape || !existsSync2(shape.excludePath)) return;
20157
+ const raw = readFileSync(shape.excludePath, "utf8");
20158
+ if (!raw.includes(START) || !raw.includes(END)) return;
20159
+ const next = withoutBlock(raw);
20160
+ if (next) atomic(shape.excludePath, next);
20161
+ else unlinkSync(shape.excludePath);
20162
+ }
20163
+
20164
+ // ../../plugins/fusion-plugin-cursor-runtime/src/cursor-mcp-config.ts
20165
+ import { closeSync, existsSync as existsSync3, fsyncSync, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync2, renameSync as renameSync2, rmSync as rmSync2, rmdirSync, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync2 } from "node:fs";
20166
+ import { hostname as hostname2 } from "node:os";
20167
+ import { dirname as dirname3, join as join3 } from "node:path";
20168
+ import { randomUUID as randomUUID2 } from "node:crypto";
20169
+ var TTL = 15 * 6e4;
20170
+ var LOCK_TTL_MS = 3e4;
20171
+ var LOCK_ATTEMPTS = 5;
20172
+ var held = /* @__PURE__ */ new Map();
20173
+ var paths = (worktreePath) => {
20174
+ const dir = join3(worktreePath, ".cursor");
20175
+ return { dir, config: join3(dir, "mcp.json"), state: join3(dir, ".fusion-mcp-state.json"), lock: join3(dir, ".fusion-mcp.lock") };
20176
+ };
20177
+ var rawFile = (file) => existsSync3(file) ? readFileSync2(file, "utf8") : null;
20178
+ var atomic2 = (file, raw, durable = false) => {
20179
+ const temp = `${file}.${process.pid}.${randomUUID2()}.tmp`;
20180
+ writeFileSync2(temp, raw, { mode: 384 });
20181
+ if (durable) {
20182
+ const fileDescriptor = openSync(temp, "r");
20183
+ try {
20184
+ fsyncSync(fileDescriptor);
20185
+ } finally {
20186
+ closeSync(fileDescriptor);
20187
+ }
20188
+ }
20189
+ renameSync2(temp, file);
20190
+ if (durable) {
20191
+ try {
20192
+ const directoryDescriptor = openSync(dirname3(file), "r");
20193
+ try {
20194
+ fsyncSync(directoryDescriptor);
20195
+ } finally {
20196
+ closeSync(directoryDescriptor);
20197
+ }
20198
+ } catch {
20199
+ }
20200
+ }
20201
+ };
20202
+ var writeState = (file, state, durable = false) => atomic2(file, JSON.stringify(state), durable);
20203
+ var same = (raw, expected) => raw === expected;
20204
+ var own = (key) => /^fusion-custom-tools-/.test(key);
20205
+ var parseCursorMcpConfig = (raw) => JSON.parse(raw);
20206
+ var detectFusionKeysInRaw = (raw) => {
20207
+ try {
20208
+ return Object.keys(parseCursorMcpConfig(raw ?? "{}").mcpServers ?? {}).filter(own);
20209
+ } catch {
20210
+ return [];
20211
+ }
20212
+ };
20213
+ function stripFusionKeys(parsed) {
20214
+ const copy = { ...parsed, mcpServers: { ...parsed.mcpServers ?? {} } };
20215
+ for (const key of Object.keys(copy.mcpServers)) if (own(key)) delete copy.mcpServers[key];
20216
+ return copy;
20217
+ }
20218
+ function composeMcpConfig({ currentRaw, leases }) {
20219
+ let parsed;
20220
+ try {
20221
+ parsed = currentRaw ? parseCursorMcpConfig(currentRaw) : {};
20222
+ } catch {
20223
+ parsed = {};
20224
+ }
20225
+ const next = stripFusionKeys(parsed);
20226
+ next.mcpServers ??= {};
20227
+ for (const [key, lease] of Object.entries(leases)) next.mcpServers[key] = lease.serverEntry;
20228
+ return `${JSON.stringify(next, null, 2)}
20229
+ `;
20230
+ }
20231
+ function detectOperatorEdit({ currentRaw, lastWrittenRaw, pending, baseline, stateExists }) {
20232
+ if (!stateExists) return false;
20233
+ return !same(currentRaw, lastWrittenRaw ?? baseline.raw) && !same(currentRaw, pending?.raw ?? "__no_pending__");
20234
+ }
20235
+ function resolvePendingJournal({ currentRaw, lastWrittenRaw, pending, baseline }) {
20236
+ if (!pending) return "discard";
20237
+ if (pending.kind === "quarantine" || same(currentRaw, pending.raw)) return "promote";
20238
+ return same(currentRaw, lastWrittenRaw ?? baseline.raw) ? "discard" : "discard-edited";
20239
+ }
20240
+ function evaluateQuarantine({ currentRaw, quarantine }) {
20241
+ if (!quarantine) return "unknown";
20242
+ if (currentRaw === null) return "clear";
20243
+ if (currentRaw === quarantine.raw) return "hold";
20244
+ try {
20245
+ return Object.keys(parseCursorMcpConfig(currentRaw).mcpServers ?? {}).some(own) ? "hold" : "clear";
20246
+ } catch {
20247
+ return "repin";
20248
+ }
20249
+ }
20250
+ var lockOwnerPath = (lock) => join3(lock, "owner.json");
20251
+ var currentHost = () => hostname2();
20252
+ var pidAlive = (pid) => {
20253
+ try {
20254
+ process.kill(pid, 0);
20255
+ return true;
20256
+ } catch {
20257
+ return false;
20258
+ }
20259
+ };
20260
+ var readLockOwner = (lock) => {
20261
+ try {
20262
+ return JSON.parse(readFileSync2(lockOwnerPath(lock), "utf8"));
20263
+ } catch {
20264
+ return void 0;
20265
+ }
20266
+ };
20267
+ var lockAgeMs2 = (lock) => {
20268
+ try {
20269
+ return Date.now() - statSync2(lock).mtimeMs;
20270
+ } catch {
20271
+ return Number.POSITIVE_INFINITY;
20272
+ }
20273
+ };
20274
+ var lockIsStale = (lock, owner) => lockAgeMs2(lock) > LOCK_TTL_MS || owner !== void 0 && (Date.now() - owner.acquiredAt > LOCK_TTL_MS || owner.hostname === currentHost() && !pidAlive(owner.pid));
20275
+ var pause = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
20276
+ async function withLock(worktreePath, create, fn) {
20277
+ const p = paths(worktreePath);
20278
+ if (!existsSync3(p.dir)) {
20279
+ if (!create) return void 0;
20280
+ mkdirSync2(p.dir, { recursive: true });
20281
+ }
20282
+ const mine = { pid: process.pid, hostname: currentHost(), acquiredAt: Date.now() };
20283
+ for (let attempt = 0; attempt < LOCK_ATTEMPTS; attempt++) {
20284
+ try {
20285
+ mkdirSync2(p.lock);
20286
+ writeFileSync2(lockOwnerPath(p.lock), JSON.stringify(mine), { mode: 384 });
20287
+ break;
20288
+ } catch {
20289
+ if (existsSync3(p.lock) && lockIsStale(p.lock, readLockOwner(p.lock))) {
20290
+ try {
20291
+ rmSync2(p.lock, { recursive: true, force: true });
20292
+ } catch {
20293
+ }
20294
+ }
20295
+ if (attempt === LOCK_ATTEMPTS - 1) throw Object.assign(new Error("Cursor MCP config lock unavailable"), { code: "bridge-start-failed" });
20296
+ await pause(8 + Math.floor(Math.random() * 12));
20297
+ }
20298
+ }
20299
+ try {
20300
+ return await fn();
20301
+ } finally {
20302
+ const owner = readLockOwner(p.lock);
20303
+ if (owner?.pid === mine.pid && owner.hostname === mine.hostname) {
20304
+ try {
20305
+ rmSync2(p.lock, { recursive: true, force: true });
20306
+ } catch {
20307
+ }
20308
+ }
20309
+ }
20310
+ }
20311
+ function withLockSync(worktreePath, fn) {
20312
+ const p = paths(worktreePath);
20313
+ if (!existsSync3(p.dir)) return void 0;
20314
+ const mine = { pid: process.pid, hostname: currentHost(), acquiredAt: Date.now() };
20315
+ try {
20316
+ mkdirSync2(p.lock);
20317
+ writeFileSync2(lockOwnerPath(p.lock), JSON.stringify(mine), { mode: 384 });
20318
+ } catch {
20319
+ return void 0;
20320
+ }
20321
+ try {
20322
+ return fn();
20323
+ } catch {
20324
+ return void 0;
20325
+ } finally {
20326
+ const owner = readLockOwner(p.lock);
20327
+ if (owner?.pid === mine.pid && owner.hostname === mine.hostname) {
20328
+ try {
20329
+ rmSync2(p.lock, { recursive: true, force: true });
20330
+ } catch {
20331
+ }
20332
+ }
20333
+ }
20334
+ }
20335
+ function readState(p) {
20336
+ try {
20337
+ const state = JSON.parse(readFileSync2(p.state, "utf8"));
20338
+ return state.version === 1 && state.baseline ? state : void 0;
20339
+ } catch {
20340
+ return void 0;
20341
+ }
20342
+ }
20343
+ function recoverJournal(p, state, current) {
20344
+ if (!state.pending) return;
20345
+ const resolution = resolvePendingJournal({ currentRaw: current, lastWrittenRaw: state.lastWrittenRaw, pending: state.pending, baseline: state.baseline });
20346
+ if (resolution === "promote") state.lastWrittenRaw = state.pending.raw;
20347
+ if (resolution === "discard-edited") {
20348
+ state.operatorEditObserved = true;
20349
+ try {
20350
+ if (current !== null) parseCursorMcpConfig(current);
20351
+ } catch {
20352
+ state.configUnwritable = true;
20353
+ }
20354
+ }
20355
+ state.pending = null;
20356
+ writeState(p.state, state);
20357
+ }
20358
+ function journalWrite(p, state, kind, raw) {
20359
+ state.pending = { kind, raw, seq: (state.pending?.seq ?? 0) + 1 };
20360
+ writeState(p.state, state, true);
20361
+ if (kind === "delete") {
20362
+ if (existsSync3(p.config)) unlinkSync2(p.config);
20363
+ } else if (kind !== "quarantine") atomic2(p.config, raw ?? "");
20364
+ state.lastWrittenRaw = raw;
20365
+ state.pending = null;
20366
+ writeState(p.state, state);
20367
+ }
20368
+ function stale(state) {
20369
+ for (const [key, lease] of Object.entries(state.leases)) {
20370
+ if (Date.now() - lease.heartbeatAt > TTL || lease.hostname === currentHost() && !pidAlive(lease.pid)) delete state.leases[key];
20371
+ }
20372
+ }
20373
+ function terminal(worktreePath, p, state) {
20374
+ const current = rawFile(p.config);
20375
+ if (state.configUnwritable) {
20376
+ state.leases = {};
20377
+ state.quarantine = { raw: current, fusionKeys: detectFusionKeysInRaw(current), observedAt: (/* @__PURE__ */ new Date()).toISOString() };
20378
+ state.pending = { kind: "quarantine", raw: current, seq: 1 };
20379
+ writeState(p.state, state, true);
20380
+ state.pending = null;
20381
+ state.lastWrittenRaw = null;
20382
+ writeState(p.state, state, true);
20383
+ return false;
20384
+ }
20385
+ if (!state.operatorEditObserved) journalWrite(p, state, state.baseline.existed ? "restore" : "delete", state.baseline.raw);
20386
+ else if (current !== null) journalWrite(p, state, "write", composeMcpConfig({ currentRaw: current, leases: {} }));
20387
+ unlinkSync2(p.state);
20388
+ if (state.exclusionOwnedByFusion) removeCursorConfigExclusion(worktreePath);
20389
+ return state.baseline.createdDir;
20390
+ }
20391
+ function removeCursorDirectoryAfterLock(dir, shouldRemove) {
20392
+ if (!shouldRemove) return;
20393
+ try {
20394
+ rmdirSync(dir);
20395
+ } catch {
20396
+ }
20397
+ }
20398
+ function detectAndLatch(p, state, current) {
20399
+ recoverJournal(p, state, current);
20400
+ const observed = rawFile(p.config);
20401
+ if (detectOperatorEdit({ currentRaw: observed, lastWrittenRaw: state.lastWrittenRaw, pending: state.pending, baseline: state.baseline, stateExists: true })) {
20402
+ state.operatorEditObserved = true;
20403
+ try {
20404
+ if (observed) parseCursorMcpConfig(observed);
20405
+ } catch {
20406
+ state.configUnwritable = true;
20407
+ }
20408
+ writeState(p.state, state);
20409
+ }
20410
+ }
20411
+ function bootstrapCursorWorktree(worktreePath) {
20412
+ if (classifyCursorConfigTracking(worktreePath) === "tracked") throw Object.assign(new Error("Cursor MCP config is git-tracked"), { code: "bridge-start-failed" });
20413
+ const exclusion = ensureCursorConfigExcluded(worktreePath);
20414
+ const p = paths(worktreePath);
20415
+ const existed = existsSync3(p.dir);
20416
+ mkdirSync2(p.dir, { recursive: true });
20417
+ return { exclusion, createdDirObserved: !existed };
20418
+ }
20419
+ async function stageCursorMcpLease({ worktreePath, serverKey, serverEntry }) {
20420
+ const boot = bootstrapCursorWorktree(worktreePath);
20421
+ const p = paths(worktreePath);
20422
+ let clearedQuarantine = false;
20423
+ await withLock(worktreePath, true, () => {
20424
+ const current = rawFile(p.config);
20425
+ let state = readState(p);
20426
+ if (state?.quarantine) {
20427
+ const verdict = evaluateQuarantine({ currentRaw: current, quarantine: state.quarantine });
20428
+ if (verdict !== "clear") {
20429
+ if (verdict === "repin") {
20430
+ state.quarantine = { raw: current, fusionKeys: detectFusionKeysInRaw(current), observedAt: (/* @__PURE__ */ new Date()).toISOString() };
20431
+ writeState(p.state, state, true);
20432
+ }
20433
+ throw Object.assign(new Error("Cursor MCP worktree is quarantined"), { code: "bridge-start-failed" });
20434
+ }
20435
+ unlinkSync2(p.state);
20436
+ if (state.exclusionOwnedByFusion) removeCursorConfigExclusion(worktreePath);
20437
+ clearedQuarantine = true;
20438
+ return;
20439
+ }
20440
+ if (!state) {
20441
+ let parsable = true;
20442
+ try {
20443
+ if (current) parseCursorMcpConfig(current);
20444
+ } catch {
20445
+ parsable = false;
20446
+ }
20447
+ state = { version: 1, baseline: { existed: current !== null, raw: current, mode: current === null ? null : statSync2(p.config).mode, parsable, createdDir: boot.createdDirObserved }, exclusionOwnedByFusion: boot.exclusion.fusionAuthored, lastWrittenRaw: null, pending: null, operatorEditObserved: false, configUnwritable: !parsable, quarantine: parsable ? null : { raw: current, fusionKeys: detectFusionKeysInRaw(current), observedAt: (/* @__PURE__ */ new Date()).toISOString() }, leases: {} };
20448
+ writeState(p.state, state, true);
20449
+ if (!parsable) throw Object.assign(new Error("Cursor MCP config is not safely writable"), { code: "bridge-start-failed" });
20450
+ }
20451
+ detectAndLatch(p, state, current);
20452
+ if (state.configUnwritable) throw Object.assign(new Error("Cursor MCP config is not safely writable"), { code: "bridge-start-failed" });
20453
+ const exclusion = ensureCursorConfigExcluded(worktreePath, { lockHeld: true });
20454
+ state.exclusionOwnedByFusion ||= exclusion.fusionAuthored;
20455
+ stale(state);
20456
+ state.leases[serverKey] = { pid: process.pid, hostname: currentHost(), heartbeatAt: Date.now(), serverEntry };
20457
+ const composed = composeMcpConfig({ currentRaw: rawFile(p.config), leases: state.leases });
20458
+ journalWrite(p, state, "write", composed);
20459
+ const worktreeLeases = held.get(worktreePath) ?? /* @__PURE__ */ new Set();
20460
+ worktreeLeases.add(serverKey);
20461
+ held.set(worktreePath, worktreeLeases);
20462
+ });
20463
+ if (clearedQuarantine) return stageCursorMcpLease({ worktreePath, serverKey, serverEntry });
20464
+ let released = false;
20465
+ return { heartbeat: async () => await withLock(worktreePath, false, () => {
20466
+ const state = readState(p);
20467
+ if (state?.leases[serverKey]) {
20468
+ detectAndLatch(p, state, rawFile(p.config));
20469
+ if (!state.configUnwritable) {
20470
+ state.leases[serverKey].heartbeatAt = Date.now();
20471
+ writeState(p.state, state);
20472
+ }
20473
+ }
20474
+ }), dispose: async () => {
20475
+ if (released) return;
20476
+ released = true;
20477
+ let removeDir = false;
20478
+ await withLock(worktreePath, false, () => {
20479
+ const state = readState(p);
20480
+ if (!state) return;
20481
+ detectAndLatch(p, state, rawFile(p.config));
20482
+ delete state.leases[serverKey];
20483
+ stale(state);
20484
+ if (Object.keys(state.leases).length) {
20485
+ if (!state.configUnwritable) journalWrite(p, state, "write", composeMcpConfig({ currentRaw: rawFile(p.config), leases: state.leases }));
20486
+ else writeState(p.state, state);
20487
+ } else removeDir = terminal(worktreePath, p, state);
20488
+ held.get(worktreePath)?.delete(serverKey);
20489
+ });
20490
+ removeCursorDirectoryAfterLock(p.dir, removeDir);
20491
+ } };
20492
+ }
20493
+ async function reconcileCursorWorktree(worktreePath) {
20494
+ const p = paths(worktreePath);
20495
+ if (!existsSync3(p.dir)) return;
20496
+ let removeDir = false;
20497
+ try {
20498
+ await withLock(worktreePath, false, () => {
20499
+ const state = readState(p);
20500
+ if (!state) return;
20501
+ const current = rawFile(p.config);
20502
+ if (state.quarantine) {
20503
+ const verdict = evaluateQuarantine({ currentRaw: current, quarantine: state.quarantine });
20504
+ if (verdict === "clear") {
20505
+ unlinkSync2(p.state);
20506
+ if (state.exclusionOwnedByFusion) removeCursorConfigExclusion(worktreePath);
20507
+ removeDir = state.baseline.createdDir;
20508
+ } else if (verdict === "repin") {
20509
+ state.quarantine = { raw: current, fusionKeys: detectFusionKeysInRaw(current), observedAt: (/* @__PURE__ */ new Date()).toISOString() };
20510
+ writeState(p.state, state, true);
20511
+ }
20512
+ return;
20513
+ }
20514
+ detectAndLatch(p, state, current);
20515
+ const leaseCount = Object.keys(state.leases).length;
20516
+ stale(state);
20517
+ if (!Object.keys(state.leases).length) removeDir = terminal(worktreePath, p, state);
20518
+ else if (!state.configUnwritable && Object.keys(state.leases).length !== leaseCount) {
20519
+ journalWrite(p, state, "write", composeMcpConfig({ currentRaw: rawFile(p.config), leases: state.leases }));
20520
+ } else if (Object.keys(state.leases).length !== leaseCount) writeState(p.state, state);
20521
+ });
20522
+ } catch {
20523
+ }
20524
+ removeCursorDirectoryAfterLock(p.dir, removeDir);
20525
+ }
20526
+ function releaseHeldLeasesSync(worktreePath) {
20527
+ for (const [path3, keys] of held) {
20528
+ if (worktreePath && path3 !== worktreePath) continue;
20529
+ for (const key of [...keys]) {
20530
+ const p = paths(path3);
20531
+ let removeDir = false;
20532
+ withLockSync(path3, () => {
20533
+ const state = readState(p);
20534
+ if (!state || state.quarantine) return;
20535
+ detectAndLatch(p, state, rawFile(p.config));
20536
+ delete state.leases[key];
20537
+ if (!Object.keys(state.leases).length) removeDir = terminal(path3, p, state);
20538
+ else if (!state.configUnwritable) journalWrite(p, state, "write", composeMcpConfig({ currentRaw: rawFile(p.config), leases: state.leases }));
20539
+ else writeState(p.state, state);
20540
+ });
20541
+ removeCursorDirectoryAfterLock(p.dir, removeDir);
20542
+ }
20543
+ }
20544
+ }
20545
+
20546
+ // ../../plugins/fusion-plugin-cursor-runtime/src/tool-mapping.ts
20547
+ function fromCursorToolName(observedName, serverKey) {
20548
+ const exact = `${serverKey}-`;
20549
+ if (observedName.startsWith(exact)) return observedName.slice(exact.length);
20550
+ return observedName.replace(new RegExp(`^mcp[_-]*${serverKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[_-]*`), "");
20551
+ }
20552
+
20553
+ // ../../plugins/fusion-plugin-cursor-runtime/src/tool-bridge.ts
20554
+ import { timingSafeEqual, randomUUID as randomUUID3 } from "node:crypto";
20555
+ import { createServer } from "node:http";
20556
+ import { existsSync as existsSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3, chmodSync } from "node:fs";
20557
+ import { tmpdir } from "node:os";
20558
+ import { dirname as dirname4, join as join4 } from "node:path";
20559
+ import { fileURLToPath } from "node:url";
20560
+ var toolsToMcpToolDefs = (tools) => (tools ?? []).filter((tool) => tool?.name.startsWith("fn_") && typeof tool.execute === "function").map((tool) => ({ name: tool.name, description: tool.description ?? "", inputSchema: tool.parameters ?? { type: "object", properties: {} } }));
20561
+ var cursorMcpSchemaServerPath = () => join4(dirname4(fileURLToPath(import.meta.url)), "mcp-schema-server.cjs");
20562
+ var text2 = (value) => {
20563
+ if (value == null) return "";
20564
+ if (typeof value === "string") return value;
20565
+ if (typeof value === "object" && value && "text" in value && typeof value.text === "string") return value.text;
20566
+ try {
20567
+ return JSON.stringify(value);
20568
+ } catch {
20569
+ return String(value);
20570
+ }
20571
+ };
20572
+ var loopback = (address) => address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
20573
+ async function startCursorToolBridge(tools) {
20574
+ const defs = toolsToMcpToolDefs(tools);
20575
+ if (!defs.length) return null;
20576
+ const asset = cursorMcpSchemaServerPath();
20577
+ if (!existsSync4(asset)) {
20578
+ const error = Object.assign(new Error(`Fusion MCP schema server is missing: ${asset}`), { code: "mcp-schema-server-missing" });
20579
+ throw error;
20580
+ }
20581
+ const token = randomUUID3();
20582
+ const publishedNames = new Set(defs.map((tool) => tool.name));
20583
+ const byName = new Map((tools ?? []).filter((tool) => publishedNames.has(tool.name) && typeof tool.execute === "function").map((tool) => [tool.name, tool]));
20584
+ const schema = join4(tmpdir(), `fusion-cursor-mcp-schemas-${process.pid}-${randomUUID3()}.json`);
20585
+ writeFileSync3(schema, JSON.stringify(defs), { mode: 384 });
20586
+ chmodSync(schema, 384);
20587
+ const server = createServer(async (req, res) => {
20588
+ const host = req.headers.host;
20589
+ const localHost = `127.0.0.1:${server.address()?.port ?? ""}`;
20590
+ const auth = req.headers.authorization;
20591
+ const provided = auth?.startsWith("Bearer ") ? auth.slice(7) : "";
20592
+ const matches = provided.length === token.length && timingSafeEqual(Buffer.from(provided), Buffer.from(token));
20593
+ if (!loopback(req.socket.remoteAddress) || host !== localHost || !matches) {
20594
+ res.statusCode = 401;
20595
+ res.end();
20596
+ return;
20597
+ }
20598
+ if (req.method !== "POST" || req.url !== "/tool-call") {
20599
+ res.statusCode = 404;
20600
+ res.end();
20601
+ return;
20602
+ }
20603
+ let raw = "";
20604
+ for await (const chunk of req) raw += chunk;
20605
+ let body;
20606
+ try {
20607
+ body = JSON.parse(raw || "{}");
20608
+ } catch {
20609
+ res.statusCode = 400;
20610
+ res.end(JSON.stringify({ isError: true, text: "invalid JSON body" }));
20611
+ return;
20612
+ }
20613
+ const tool = typeof body.name === "string" ? byName.get(body.name) : void 0;
20614
+ res.setHeader("content-type", "application/json");
20615
+ if (!tool?.execute) {
20616
+ res.end(JSON.stringify({ isError: true, content: [{ type: "text", text: "Unknown Fusion tool" }] }));
20617
+ return;
20618
+ }
20619
+ try {
20620
+ const result = await tool.execute(`cursor-mcp-${randomUUID3()}`, body.arguments ?? {});
20621
+ res.end(JSON.stringify({ isError: false, content: [{ type: "text", text: text2(result) }] }));
20622
+ } catch (error) {
20623
+ res.end(JSON.stringify({ isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }] }));
20624
+ }
20625
+ });
20626
+ const port = await new Promise((resolve2, reject) => {
20627
+ server.once("error", reject);
20628
+ server.listen(0, "127.0.0.1", () => resolve2(server.address().port));
20629
+ });
20630
+ return { token, toolCount: defs.length, serverEntry: { command: process.execPath, args: [asset, schema], env: { FUSION_CURSOR_TOOL_BRIDGE_URL: `http://127.0.0.1:${port}`, FUSION_CURSOR_TOOL_BRIDGE_TOKEN: token } }, dispose: async () => {
20631
+ await new Promise((resolve2) => server.close(() => resolve2()));
20632
+ try {
20633
+ unlinkSync3(schema);
20634
+ } catch {
20635
+ }
20636
+ } };
20637
+ }
20638
+
20639
+ // ../../plugins/fusion-plugin-cursor-runtime/src/runtime-adapter.ts
20640
+ var MCP_HEARTBEAT_INTERVAL_MS = 5 * 6e4;
20641
+ var exitHookInstalled = false;
20642
+ function installExitHook() {
20643
+ if (!exitHookInstalled) {
20644
+ exitHookInstalled = true;
20645
+ process.once("exit", () => {
20646
+ try {
20647
+ releaseHeldLeasesSync();
20648
+ } catch {
20649
+ }
20650
+ });
20651
+ }
20652
+ }
20653
+ function context(options, names = []) {
20654
+ const skills = Array.isArray(options.skills) ? options.skills.filter((value) => typeof value === "string" && value.trim()) : [];
20655
+ return ["Fusion runtime context:", `- Tool mode: ${options.tools ?? "readonly"}`, skills.length ? `- Requested skills: ${skills.join(", ")}` : "", names.length ? `- Fusion MCP tools: ${names.join(", ")}` : ""].filter(Boolean).join("\n");
20656
+ }
19688
20657
  var CursorRuntimeAdapter = class {
20658
+ constructor(settings) {
20659
+ this.settings = settings;
20660
+ }
19689
20661
  id = "cursor";
19690
20662
  name = "Cursor Runtime";
19691
20663
  async createSession(options) {
19692
- return {
19693
- session: {
19694
- model: options.defaultModelId ?? "cursor/default",
19695
- systemPrompt: options.systemPrompt,
19696
- messages: []
19697
- },
19698
- sessionFile: void 0
19699
- };
19700
- }
19701
- async promptWithFallback() {
19702
- return;
20664
+ await reconcileCursorWorktree(options.cwd);
20665
+ installExitHook();
20666
+ const messages2 = [];
20667
+ const session = { model: options.defaultModelId?.replace(/^cursor-cli\//, "") ?? "auto", systemPrompt: options.systemPrompt, messages: messages2, state: { messages: messages2 }, sessionId: "", cwd: options.cwd, tools: options.tools, callbacks: { onText: options.onText, onThinking: options.onThinking, onToolStart: options.onToolStart, onToolEnd: options.onToolEnd }, fusedSystemPrompt: [options.systemPrompt?.trim(), context(options)].filter(Boolean).join("\n\n"), disposed: false, dispose: async () => {
20668
+ if (session.disposed) return;
20669
+ session.disposed = true;
20670
+ session.activeAbortController?.abort();
20671
+ if (session.mcpHeartbeatTimer) clearInterval(session.mcpHeartbeatTimer);
20672
+ await session.mcpLease?.dispose().catch(() => void 0);
20673
+ await session.toolBridge?.dispose().catch(() => void 0);
20674
+ } };
20675
+ let startingBridge = null;
20676
+ if (options.fusionTools?.length && classifyCursorConfigTracking(options.cwd) !== "tracked") {
20677
+ try {
20678
+ startingBridge = await startCursorToolBridge(options.fusionTools);
20679
+ if (startingBridge) {
20680
+ const serverKey = `fusion-custom-tools-${randomUUID4()}`;
20681
+ const lease = await stageCursorMcpLease({ worktreePath: options.cwd, serverKey, serverEntry: startingBridge.serverEntry });
20682
+ session.toolBridge = startingBridge;
20683
+ session.mcpLease = lease;
20684
+ session.mcpServerKey = serverKey;
20685
+ session.mcpHeartbeatTimer = setInterval(() => {
20686
+ void lease.heartbeat().catch(() => void 0);
20687
+ }, MCP_HEARTBEAT_INTERVAL_MS);
20688
+ session.mcpHeartbeatTimer.unref?.();
20689
+ session.fusedSystemPrompt = [options.systemPrompt?.trim(), context(options, options.fusionTools.map((tool) => `${serverKey}-${tool.name}`))].filter(Boolean).join("\n\n");
20690
+ startingBridge = null;
20691
+ }
20692
+ } catch (error) {
20693
+ await startingBridge?.dispose().catch(() => void 0);
20694
+ session.fusionToolBridgeError = { reasonCode: error.code === "mcp-schema-server-missing" ? "mcp-schema-server-missing" : "bridge-start-failed" };
20695
+ }
20696
+ } else if (options.fusionTools?.length) session.fusionToolBridgeError = { reasonCode: "bridge-start-failed" };
20697
+ return { session, sessionFile: void 0 };
20698
+ }
20699
+ async promptWithFallback(session, prompt, _options) {
20700
+ if (session.disposed) throw new Error("Cursor session is disposed.");
20701
+ const priorId = session.sessionId;
20702
+ const first = !priorId;
20703
+ const sent = first ? `${session.fusedSystemPrompt}
20704
+
20705
+ User request:
20706
+ ${prompt}` : prompt;
20707
+ const emitted = /* @__PURE__ */ new Set();
20708
+ const controller = new AbortController();
20709
+ session.activeAbortController = controller;
20710
+ const map2 = (name) => session.mcpServerKey ? fromCursorToolName(name, session.mcpServerKey) : name;
20711
+ try {
20712
+ const outcome = await launchCursorPrompt({ binary: typeof this.settings?.cursorCliBinaryPath === "string" ? this.settings.cursorCliBinaryPath : void 0, model: session.model, cwd: session.cwd, tools: session.tools, prompt: sent, resumeId: priorId || void 0, signal: controller.signal, approveMcps: Boolean(session.mcpLease), onThinking: session.callbacks.onThinking, onToolStart: (name, args) => session.callbacks.onToolStart?.(map2(name), args), onToolEnd: (name, isError, result) => session.callbacks.onToolEnd?.(map2(name), isError, result), onText: (text3) => {
20713
+ if (!emitted.has(text3)) {
20714
+ emitted.add(text3);
20715
+ session.callbacks.onText?.(text3);
20716
+ }
20717
+ } });
20718
+ session.sessionId = outcome.sessionId ?? session.sessionId;
20719
+ session.messages.push({ role: "user", content: prompt }, { role: "assistant", content: outcome.text });
20720
+ } catch (error) {
20721
+ session.sessionId = priorId;
20722
+ throw error;
20723
+ } finally {
20724
+ if (session.activeAbortController === controller) session.activeAbortController = void 0;
20725
+ }
19703
20726
  }
19704
20727
  describeModel(session) {
19705
- return `cursor/${session.model ?? "default"}`;
20728
+ return `cursor-cli/${(session.model || "auto").replace(/^cursor-cli\//, "")}`;
19706
20729
  }
19707
20730
  };
19708
20731
 
@@ -19727,7 +20750,7 @@ var plugin = definePlugin({
19727
20750
  name: "Cursor Runtime",
19728
20751
  version: "0.1.0"
19729
20752
  },
19730
- factory: async () => new CursorRuntimeAdapter()
20753
+ factory: async (ctx) => new CursorRuntimeAdapter(ctx.settings)
19731
20754
  },
19732
20755
  cliProviders: [
19733
20756
  {
@@ -19756,7 +20779,7 @@ var plugin = definePlugin({
19756
20779
  discoverModels: discoverCursorProviderModels,
19757
20780
  runtime: {
19758
20781
  runtimeId: "cursor",
19759
- createAdapter: async () => new CursorRuntimeAdapter()
20782
+ createAdapter: async (ctx) => new CursorRuntimeAdapter(ctx.settings)
19760
20783
  }
19761
20784
  }
19762
20785
  ]