@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.
- package/dist/bin.js +13802 -9587
- package/dist/child-process-worker.js +6472 -4028
- package/dist/client/.vite/manifest.json +280 -269
- package/dist/client/assets/AgentDetailView-0G9qFd4j.js +13 -0
- package/dist/client/assets/{AgentDetailView-Cc3ATg8a.css → AgentDetailView-jzgy1BAS.css} +1 -1
- package/dist/client/assets/{AgentPermissionPolicyEditor--u9jyUf2.js → AgentPermissionPolicyEditor-BM68g7wo.js} +1 -1
- package/dist/client/assets/AgentsView-DkOUZWw4.js +24 -0
- package/dist/client/assets/ChatView-BPSm2JIH.js +13 -0
- package/dist/client/assets/{CommandCenter-CSRYoveB.js → CommandCenter-DxRSFWNc.js} +26 -26
- package/dist/client/assets/CommandCenter-ilm-DbtI.css +1 -0
- package/dist/client/assets/{DevServerView-B9So_oYx.js → DevServerView-DO-bqJFE.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-BhyDL_2m.js → DirectoryPicker-Cp2gddTE.js} +1 -1
- package/dist/client/assets/DocumentsView-D0E-3G96.js +1 -0
- package/dist/client/assets/EvalsView-BoFVq5hw.js +1 -0
- package/dist/client/assets/ExperimentalAgentOnboardingModal-CS1Sdogm.js +1 -0
- package/dist/client/assets/{GoalsView-B6X5_0Ve.js → GoalsView-sybf7JY4.js} +1 -1
- package/dist/client/assets/InsightsView-nHa7aShn.js +11 -0
- package/dist/client/assets/{MemoryView-DN_3IFmA.css → MemoryView-CdpQqPJJ.css} +1 -1
- package/dist/client/assets/MemoryView-D0IG44za.js +3 -0
- package/dist/client/assets/{PiExtensionsManager-B8At6fO8.js → PiExtensionsManager-CU76weJe.js} +2 -2
- package/dist/client/assets/PluginManager-DyT8XbcG.js +1 -0
- package/dist/client/assets/{PullRequestView-B1hZFrWo.js → PullRequestView-CnxWUSen.js} +2 -2
- package/dist/client/assets/{ReportModal-C46V_zEa.js → ReportModal-CMrmMQjD.js} +5 -5
- package/dist/client/assets/{ResearchView-D8G85BYN.js → ResearchView-BTytQSgy.js} +2 -2
- package/dist/client/assets/{SecretsView-CTri9LRK.js → SecretsView-BuSH5J87.js} +1 -1
- package/dist/client/assets/SessionTerminal-D04kYRrT.js +2 -0
- package/dist/client/assets/SettingsModal-C0FY8MPW.css +1 -0
- package/dist/client/assets/{SettingsModal-rUQcSTJp.js → SettingsModal-Dm-ngbRr.js} +1 -1
- package/dist/client/assets/{SettingsModal-DmfMotcd.css → SettingsModal-Ds0Yj47X.css} +1 -1
- package/dist/client/assets/SettingsModal-xSLhH7Tr.js +21 -0
- package/dist/client/assets/{SettingsTextareaRow-BirlBcem.js → SettingsTextareaRow-CHPgocEp.js} +1 -1
- package/dist/client/assets/{SetupWizardModal-xnCBM9EL.js → SetupWizardModal-y88aXNIQ.js} +2 -2
- package/dist/client/assets/{SkillsView-O5U0ep50.js → SkillsView-CYyQW1Hv.js} +1 -1
- package/dist/client/assets/{WorkflowNodeEditor-BX4d2qqF.js → WorkflowNodeEditor-hYeoz1X8.js} +3 -3
- package/dist/client/assets/{agent-import-generation-DnqVkPkz.js → agent-import-generation-CjiYXzCr.js} +1 -1
- package/dist/client/assets/agentsOrgChartLayout-C0UP_X3z.js +1 -0
- package/dist/client/assets/app-BgpuMvTU.js +13 -0
- package/dist/client/assets/app-BlexHhbW.js +13 -0
- package/dist/client/assets/app-BsuUOR0P.js +13 -0
- package/dist/client/assets/app-DQVOujmO.js +13 -0
- package/dist/client/assets/app-DjyTRx1N.js +5 -0
- package/dist/client/assets/app-Dniggype.js +13 -0
- package/dist/client/assets/app-EV-lyZ0T.js +5 -0
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-gi467xY0.js → architectureDiagram-3BPJPVTR-Dd6GPcqM.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-B_gpcYiR.js → blockDiagram-GPEHLZMM-CC6pLxlz.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-DeylEYsQ.js → c4Diagram-AAUBKEIU-DxMP_l6y.js} +1 -1
- package/dist/client/assets/channel-DlCEMoEf.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-BL2WY0HN.js → chunk-2J33WTMH-BKYToL43.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-BkLExGMe.js → chunk-4BX2VUAB-Bg6rjQb3.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-CSTvCF5T.js → chunk-55IACEB6-CPUwbbGa.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-9J8pKk4x.js → chunk-727SXJPM-DScQBCvv.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-DXC1vPWr.js → chunk-AQP2D5EJ-DbX7uaXK.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-CFRUb-zD.js → chunk-FMBD7UC4-DvYKea1c.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-B87HFYS8.js → chunk-ND2GUHAM-CH-Tvw7w.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-CTjHiGbd.js → chunk-QZHKN3VN-CbyEvOoM.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-Bc92O8Ks.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-Bc92O8Ks.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-D3Edq3L9.js → cose-bilkent-S5V4N54A-BPB5fV3A.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-CwwbeQfN.js → dagre-BM42HDAG-CilYJ3rR.js} +1 -1
- package/dist/client/assets/{dashboard-view-QxQwJFLz.js → dashboard-view-BnGe8cWo.js} +1 -1
- package/dist/client/assets/{dashboard-view-BLAoW3D4.js → dashboard-view-DnVf3tmI.js} +1 -1
- package/dist/client/assets/{dashboard-view-DAlJxqiJ.js → dashboard-view-DoLU6FxZ.js} +1 -1
- package/dist/client/assets/{dashboard-view-aPzfv99T.js → dashboard-view-dChSM_So.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-koJWWXE4.js → diagram-2AECGRRQ-m9u1Qhyx.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-Yob5cOKT.js → diagram-5GNKFQAL-DVr7Kf0N.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-DelvFSkK.js → diagram-KO2AKTUF-D7sgJDFx.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-CMput2h2.js → diagram-LMA3HP47-DNKNDS6r.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-04VXDl79.js → diagram-OG6HWLK6-1Bff4FWn.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-C9iP8Uyp.js → erDiagram-TEJ5UH35-DGua1UHN.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DENQvu_t.js → flowDiagram-I6XJVG4X-UDfDtwsE.js} +1 -1
- package/dist/client/assets/{folder-open-D-554oji.js → folder-open-CteOypVk.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-BqOttZAT.js → ganttDiagram-6RSMTGT7-COdmD328.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-CDcydi-5.js → gitGraphDiagram-PVQCEYII-SUSls7Kw.js} +1 -1
- package/dist/client/assets/index-0ovTXt3t.css +1 -0
- package/dist/client/assets/index-DpCHwyiD.js +2818 -0
- package/dist/client/assets/{infoDiagram-5YYISTIA-B5LQQnxt.js → infoDiagram-5YYISTIA-e_DloXj7.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-DpFEt7D2.js → ishikawaDiagram-YF4QCWOH-DVWqihXg.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-MELtgu22.js → journeyDiagram-JHISSGLW-BcPY-XP0.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-h33PLATd.js → kanban-definition-UN3LZRKU-BCFG2Jle.js} +1 -1
- package/dist/client/assets/{mermaid.core-Dk2mUPOq.js → mermaid.core-5yPje9Bi.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-B_T4O2-B.js → mindmap-definition-RKZ34NQL-DDApeT3K.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-B1YIFkRl.js → pieDiagram-4H26LBE5-N61_jyeP.js} +1 -1
- package/dist/client/assets/{puzzle-hvecxlpJ.js → puzzle-BkaCP30D.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-BoGQR_hO.js → quadrantDiagram-W4KKPZXB-C5qHiApF.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-BSDmUBIx.js → requirementDiagram-4Y6WPE33-C71okgzt.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Dz_GqOa5.js → sankeyDiagram-5OEKKPKP-B2iKyLjB.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-B6twjYUH.js → sequenceDiagram-3UESZ5HK-DfdoYKKk.js} +1 -1
- package/dist/client/assets/{shield-alert-BX4WRJOv.js → shield-alert-BNSeDB8l.js} +1 -1
- package/dist/client/assets/standing-instructions-template-pZtzo1lK.js +20 -0
- package/dist/client/assets/standing-instructions-template-w7Ouw_37.css +1 -0
- package/dist/client/assets/{stateDiagram-AJRCARHV-DuwqWLx-.js → stateDiagram-AJRCARHV-lS-sUi12.js} +1 -1
- package/dist/client/assets/{stateDiagram-v2-BHNVJYJU-Bu8Sk187.js → stateDiagram-v2-BHNVJYJU-DXJNDrV9.js} +1 -1
- package/dist/client/assets/{timeline-definition-PNZ67QCA-BS6VnqSI.js → timeline-definition-PNZ67QCA-CCwvX81K.js} +1 -1
- package/dist/client/assets/{upload-CaDoyaMW.js → upload-CDovVbu2.js} +1 -1
- package/dist/client/assets/useDiscoveredSkillsCache-BYqP-afh.js +1 -0
- package/dist/client/assets/{users-C5uCLwVM.js → users-CTqECGLo.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-CUbzqT5Y.js → vennDiagram-CIIHVFJN-DaiR5nv9.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-Mv-NotFS.js → wardley-L42UT6IY-hA5IHyPW.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-DuYpvLZX.js → wardleyDiagram-YWT4CUSO-CILUNESc.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-24_7QXdw.js → xychartDiagram-2RQKCTM6-72eNMmjz.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/droid-cli/package.json +1 -1
- package/dist/extension.js +10671 -6904
- package/dist/migrations/0059_fn_9037_tasks_source_agent_index.sql +3 -0
- package/dist/migrations/0060_fn_9059_workspace_coordination_leases.sql +10 -0
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +190 -9
- package/dist/plugin-sdk/index.js +74 -28
- package/dist/plugins/fusion-plugin-acp-runtime/bundled.js +83 -29
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-claude-runtime/bundled.js +83 -29
- package/dist/plugins/fusion-plugin-claude-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/bundled.js +1096 -73
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-grok-runtime/bundled.js +83 -29
- package/dist/plugins/fusion-plugin-grok-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/bundled.js +302 -71
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-linear-import/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-linear-import/package.json +1 -1
- package/dist/plugins/fusion-plugin-omp-runtime/bundled.js +83 -29
- package/dist/plugins/fusion-plugin-omp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-quality/bundled.js +176 -46
- package/dist/plugins/fusion-plugin-quality/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-todos/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-todos/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/bundled.js +74 -28
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +1 -1
- package/skill/fusion/references/extension-tools.md +2 -0
- package/dist/client/assets/AgentDetailView-T767kKTg.js +0 -13
- package/dist/client/assets/AgentsView-6Ifu7qDr.js +0 -24
- package/dist/client/assets/ChatView-BV3lixQi.js +0 -13
- package/dist/client/assets/CommandCenter-CE4rYjHn.css +0 -1
- package/dist/client/assets/DocumentsView-pbERA7O-.js +0 -1
- package/dist/client/assets/EvalsView-DJQ4ltCg.js +0 -1
- package/dist/client/assets/ExperimentalAgentOnboardingModal-BwJuzkzB.js +0 -1
- package/dist/client/assets/InsightsView-CJgI0J7S.js +0 -11
- package/dist/client/assets/MemoryView-BHq1AHPu.js +0 -3
- package/dist/client/assets/PluginManager-k7hV7_9e.js +0 -1
- package/dist/client/assets/SessionTerminal-BRf6r4Vp.js +0 -2
- package/dist/client/assets/SettingsModal-B30rPfYC.css +0 -1
- package/dist/client/assets/SettingsModal-D0RBeZik.js +0 -21
- package/dist/client/assets/agentsOrgChartLayout-CKuzbhFf.js +0 -1
- package/dist/client/assets/app-B2FwULu2.js +0 -5
- package/dist/client/assets/app-B6nJaqAl.js +0 -13
- package/dist/client/assets/app-CGXZeIRc.js +0 -13
- package/dist/client/assets/app-CGtkeBF5.js +0 -13
- package/dist/client/assets/app-CNzN1Plu.js +0 -13
- package/dist/client/assets/app-DLs-f5yM.js +0 -13
- package/dist/client/assets/app-DsvtcRUG.js +0 -5
- package/dist/client/assets/channel-BoE8DeM4.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-qnbLX_9_.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-qnbLX_9_.js +0 -1
- package/dist/client/assets/index-CXTd3LAB.css +0 -1
- package/dist/client/assets/index-CtYRoouR.js +0 -2818
- package/dist/client/assets/standing-instructions-template-BJY317CC.js +0 -20
- 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
|
|
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 =
|
|
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((
|
|
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
|
|
10656
|
-
if (!
|
|
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(
|
|
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
|
|
10666
|
-
const 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 '${
|
|
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 '${
|
|
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,
|
|
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 (
|
|
16309
|
+
if (context2.executorRoleOverride === true) {
|
|
16254
16310
|
return { allowed: true };
|
|
16255
16311
|
}
|
|
16256
|
-
const explicit =
|
|
16257
|
-
const roleAllowed = explicit ? canAgentTakeImplementationTaskForExplicitRouting(agent, task) : canAgentTakeImplementationTask(agent, task, { 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,
|
|
16270
|
-
const verdict = evaluateImplementationTaskBind(agent, task,
|
|
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-
|
|
18465
|
-
Memory Keeper is custom because it is not a workflow-stage principal.
|
|
18466
|
-
|
|
18467
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
19168
|
+
const path3 = join(bundleDir, file);
|
|
19107
19169
|
let current = "";
|
|
19108
19170
|
try {
|
|
19109
|
-
current = await readFile(
|
|
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(
|
|
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(
|
|
19253
|
+
async pathExists(path3) {
|
|
19192
19254
|
try {
|
|
19193
|
-
await access(
|
|
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 =
|
|
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
|
|
19530
|
-
if (status.code !== 0 || !
|
|
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(
|
|
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
|
|
19619
|
-
if (!
|
|
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 (
|
|
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(
|
|
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(
|
|
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
|
-
|
|
19693
|
-
|
|
19694
|
-
|
|
19695
|
-
|
|
19696
|
-
|
|
19697
|
-
|
|
19698
|
-
|
|
19699
|
-
|
|
19700
|
-
|
|
19701
|
-
|
|
19702
|
-
|
|
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
|
|
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
|
]
|