@runfusion/fusion 0.41.0 → 0.43.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 +45728 -42714
- package/dist/client/.vite/manifest.json +76 -76
- package/dist/client/assets/AgentDetailView-DiWELtb1.css +1 -0
- package/dist/client/assets/AgentDetailView-FUKwQxBP.js +18 -0
- package/dist/client/assets/{AgentPermissionPolicyEditor-VDXL4XQc.js → AgentPermissionPolicyEditor-DMDUhF1n.js} +2 -2
- package/dist/client/assets/{AgentsView-ChXccELt.js → AgentsView-BrQLd-yI.js} +3 -3
- package/dist/client/assets/ChatView-Wz_0goif.js +6 -0
- package/dist/client/assets/{DevServerView-DGOahw1o.js → DevServerView-DRqQpHsc.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-DgqHlxyh.js → DirectoryPicker-BFot4CVM.js} +1 -1
- package/dist/client/assets/{DocumentsView-CbbZwzbH.js → DocumentsView-hamhUcvL.js} +1 -1
- package/dist/client/assets/{EvalsView-BL_ue7fJ.js → EvalsView-DAXqDqn2.js} +1 -1
- package/dist/client/assets/{ExperimentalAgentOnboardingModal-8H4XAu4O.js → ExperimentalAgentOnboardingModal-B4aZ1y0n.js} +1 -1
- package/dist/client/assets/{GoalsView-CO4FVL3E.js → GoalsView-2QOp9DA0.js} +1 -1
- package/dist/client/assets/{InsightsView-C5vLvWVE.js → InsightsView-D5fzFy-F.js} +1 -1
- package/dist/client/assets/{MemoryView-BL3W3yOv.js → MemoryView-B082KoR-.js} +2 -2
- package/dist/client/assets/NodesView-BA3pKMZu.js +14 -0
- package/dist/client/assets/{PiExtensionsManager-HvSiVeDN.js → PiExtensionsManager-C4I11iKm.js} +2 -2
- package/dist/client/assets/{PluginManager-DIjrlhnU.js → PluginManager-BzTzTMXZ.js} +1 -1
- package/dist/client/assets/{PullRequestView-C2jQX8xZ.js → PullRequestView-11xJ565q.js} +2 -2
- package/dist/client/assets/{ReliabilityView-DbqEwoRs.js → ReliabilityView-CpELpGqw.js} +1 -1
- package/dist/client/assets/{ResearchView-C1QghmLI.js → ResearchView-BHiXfjzE.js} +1 -1
- package/dist/client/assets/{SecretsView-Y2MQ-9n5.js → SecretsView-CC5OYoCn.js} +1 -1
- package/dist/client/assets/SessionTerminal-G70Jwkzs.js +2 -0
- package/dist/client/assets/{SettingsModal-DabcZZ6S.js → SettingsModal-BKEbtdqR.js} +1 -1
- package/dist/client/assets/SettingsModal-C0M1CqOu.js +31 -0
- package/dist/client/assets/SettingsModal-DpAxDq6M.css +1 -0
- package/dist/client/assets/{SetupWizardModal-Db-Rxinm.js → SetupWizardModal-CBfItlP6.js} +1 -1
- package/dist/client/assets/{SkillsView-CIEY1-DE.js → SkillsView-DbcW87-E.js} +1 -1
- package/dist/client/assets/{StashRecoveryView-B6iDu5tI.js → StashRecoveryView-CH9RPtzX.js} +1 -1
- package/dist/client/assets/{TodoView-B8IK_BPM.js → TodoView-ToviJ0SV.js} +2 -2
- package/dist/client/assets/WorkflowNodeEditor-BTu0D9c5.js +8 -0
- package/dist/client/assets/WorkflowNodeEditor-BokhW3de.css +1 -0
- package/dist/client/assets/app-B2ZxMfJT.js +13 -0
- package/dist/client/assets/app-BffGU_HQ.js +13 -0
- package/dist/client/assets/app-C8AHhs5N.js +13 -0
- package/dist/client/assets/app-CUu1sm3x.js +13 -0
- package/dist/client/assets/app-Ri9Te2Dv.js +13 -0
- package/dist/client/assets/app-sEkTyT-w.js +13 -0
- package/dist/client/assets/{dashboard-view-O5j3PSGY.js → dashboard-view-C3SsjXo1.js} +1621 -1621
- package/dist/client/assets/{dashboard-view-Dn4CztR6.js → dashboard-view-DtgLZ_LM.js} +1 -1
- package/dist/client/assets/dashboard-view-Du53JbmQ.css +1 -0
- package/dist/client/assets/{folder-open-C9XAaduE.js → folder-open-BlPyIWyf.js} +1 -1
- package/dist/client/assets/index-DqhCiU35.css +1 -0
- package/dist/client/assets/index-bZCpHtGq.js +793 -0
- package/dist/client/assets/{upload-BRAEugYV.js → upload-B-qmm1NH.js} +1 -1
- package/dist/client/assets/{users-BlflbMZ3.js → users-D6C5TXf2.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 +44655 -41906
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/plugin-sdk/index.d.ts +6258 -86
- package/dist/plugins/fusion-plugin-acp-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-cli-printing-press/package.json +1 -1
- package/dist/plugins/fusion-plugin-compound-engineering/bundled.js +236 -26
- package/dist/plugins/fusion-plugin-compound-engineering/package.json +1 -1
- package/dist/plugins/fusion-plugin-cursor-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-dependency-graph/package.json +1 -1
- package/dist/plugins/fusion-plugin-droid-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-hermes-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-openclaw-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-paperclip-runtime/package.json +1 -1
- package/dist/plugins/fusion-plugin-reports/package.json +1 -1
- package/dist/plugins/fusion-plugin-roadmap/package.json +1 -1
- package/dist/plugins/fusion-plugin-whatsapp-chat/package.json +1 -1
- package/package.json +2 -9
- package/skill/fusion/references/extension-tools.md +3 -3
- package/skill/fusion/references/fusion-capabilities.md +2 -2
- package/dist/client/assets/AgentDetailView-CY-O6VkV.css +0 -1
- package/dist/client/assets/AgentDetailView-krC0B2O0.js +0 -18
- package/dist/client/assets/ChatView-Dz_xBKit.js +0 -6
- package/dist/client/assets/NodesView-DfBNNm9c.js +0 -14
- package/dist/client/assets/SessionTerminal-DaTXNT3A.js +0 -2
- package/dist/client/assets/SettingsModal-BAzm2kYd.js +0 -31
- package/dist/client/assets/SettingsModal-bJvc9aHl.css +0 -1
- package/dist/client/assets/WorkflowNodeEditor-DaPyvD_R.js +0 -8
- package/dist/client/assets/WorkflowNodeEditor-Po5uv_dN.css +0 -1
- package/dist/client/assets/app-CTRT_hQh.js +0 -13
- package/dist/client/assets/app-CVg6W-HU.js +0 -13
- package/dist/client/assets/app-CY3pQXOr.js +0 -13
- package/dist/client/assets/app-DNh04p5I.js +0 -13
- package/dist/client/assets/app-DhHgtB9j.js +0 -13
- package/dist/client/assets/app-NL_hPGDS.js +0 -13
- package/dist/client/assets/dashboard-view-BgiLoG1G.css +0 -1
- package/dist/client/assets/index-BHGM5W8a.css +0 -1
- package/dist/client/assets/index-CgfrecpF.js +0 -793
|
@@ -65,6 +65,30 @@ var COMPOUND_ENGINEERING_SKILLS = [
|
|
|
65
65
|
skillFiles: ["skills/ce-compound/SKILL.md"],
|
|
66
66
|
enabled: true,
|
|
67
67
|
triggerPatterns: ["compound this", "document this learning", "capture this solution"]
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
skillId: "ce-commit",
|
|
71
|
+
name: "ce-commit",
|
|
72
|
+
description: "Create a git commit with a clear, value-communicating message following repo conventions.",
|
|
73
|
+
skillFiles: ["skills/ce-commit/SKILL.md"],
|
|
74
|
+
enabled: true,
|
|
75
|
+
triggerPatterns: ["commit", "commit this", "save my changes", "create a commit"]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
skillId: "ce-commit-push-pr",
|
|
79
|
+
name: "ce-commit-push-pr",
|
|
80
|
+
description: "Commit, push, and open a PR with an adaptive, value-first description that scales with the change.",
|
|
81
|
+
skillFiles: ["skills/ce-commit-push-pr/SKILL.md"],
|
|
82
|
+
enabled: true,
|
|
83
|
+
triggerPatterns: ["commit and PR", "ship this", "create a PR", "open a pull request"]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
skillId: "ce-resolve-pr-feedback",
|
|
87
|
+
name: "ce-resolve-pr-feedback",
|
|
88
|
+
description: "Resolve PR review feedback by evaluating validity and fixing issues in parallel.",
|
|
89
|
+
skillFiles: ["skills/ce-resolve-pr-feedback/SKILL.md"],
|
|
90
|
+
enabled: true,
|
|
91
|
+
triggerPatterns: ["resolve PR feedback", "address review comments", "fix review feedback"]
|
|
68
92
|
}
|
|
69
93
|
];
|
|
70
94
|
|
|
@@ -138,6 +162,72 @@ function isPluginLocalPath(p) {
|
|
|
138
162
|
return isAbsolute(p) && !GLOBAL_SKILL_DIR_PATTERN.test(resolve(p) + sep);
|
|
139
163
|
}
|
|
140
164
|
|
|
165
|
+
// ../../plugins/fusion-plugin-compound-engineering/src/agent-installation.ts
|
|
166
|
+
import { cpSync as cpSync2, existsSync as existsSync2, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync2 } from "node:fs";
|
|
167
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, resolve as resolve2, sep as sep2 } from "node:path";
|
|
168
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
169
|
+
function resolveBundledAgentsRoot() {
|
|
170
|
+
const here = fileURLToPath2(import.meta.url);
|
|
171
|
+
const dir = dirname2(here);
|
|
172
|
+
const local = resolve2(dir, "agents");
|
|
173
|
+
if (existsSync2(local)) return local;
|
|
174
|
+
return resolve2(dir, "..", "src", "agents");
|
|
175
|
+
}
|
|
176
|
+
function resolveDefaultAgentsInstallTargetRoot() {
|
|
177
|
+
const here = fileURLToPath2(import.meta.url);
|
|
178
|
+
return resolve2(dirname2(here), "..", ".fusion-ce-agents");
|
|
179
|
+
}
|
|
180
|
+
var GLOBAL_AGENT_DIR_PATTERN = /[\\/]\.(claude|codex|gemini)[\\/]agents([\\/]|$)/;
|
|
181
|
+
function assertPluginLocalAgentsTarget(targetRoot) {
|
|
182
|
+
const normalized = resolve2(targetRoot);
|
|
183
|
+
if (GLOBAL_AGENT_DIR_PATTERN.test(normalized + sep2)) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Refusing to install Compound Engineering agents into a global client agents directory: ${normalized}. Install target MUST be plugin-local (never <home>/.claude|.codex|.gemini/agents).`
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function assertValidAgentSource(agentId, sourceFile) {
|
|
190
|
+
if (!existsSync2(sourceFile)) {
|
|
191
|
+
throw new Error(`Bundled agent def missing for '${agentId}': ${sourceFile}`);
|
|
192
|
+
}
|
|
193
|
+
const content = readFileSync2(sourceFile, "utf-8");
|
|
194
|
+
if (!/^---[\s\S]*?\bname\s*:\s*\S/m.test(content)) {
|
|
195
|
+
throw new Error(`Bundled agent def '${agentId}' at ${sourceFile} is missing a frontmatter 'name:' field`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function installBundledCeAgents(options = {}) {
|
|
199
|
+
const targetRoot = options.targetRoot ? resolve2(options.targetRoot) : resolveDefaultAgentsInstallTargetRoot();
|
|
200
|
+
assertPluginLocalAgentsTarget(targetRoot);
|
|
201
|
+
const sourceRoot = options.sourceRoot ? resolve2(options.sourceRoot) : resolveBundledAgentsRoot();
|
|
202
|
+
const sourceFiles = existsSync2(sourceRoot) ? readdirSync(sourceRoot).filter((f) => f.endsWith(".md")) : [];
|
|
203
|
+
const results = sourceFiles.map((file) => {
|
|
204
|
+
const agentId = file.replace(/\.md$/, "");
|
|
205
|
+
const sourceFile = join2(sourceRoot, file);
|
|
206
|
+
const targetFile = join2(targetRoot, file);
|
|
207
|
+
try {
|
|
208
|
+
assertValidAgentSource(agentId, sourceFile);
|
|
209
|
+
if (existsSync2(targetFile)) {
|
|
210
|
+
return { agentId, sourceFile, targetFile, outcome: "skipped", reason: "existing install preserved" };
|
|
211
|
+
}
|
|
212
|
+
mkdirSync2(targetRoot, { recursive: true });
|
|
213
|
+
cpSync2(sourceFile, targetFile);
|
|
214
|
+
return { agentId, sourceFile, targetFile, outcome: "installed" };
|
|
215
|
+
} catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
agentId,
|
|
218
|
+
sourceFile,
|
|
219
|
+
targetFile,
|
|
220
|
+
outcome: "error",
|
|
221
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
return { targetRoot, results };
|
|
226
|
+
}
|
|
227
|
+
function isPluginLocalAgentsPath(p) {
|
|
228
|
+
return isAbsolute2(p) && !GLOBAL_AGENT_DIR_PATTERN.test(resolve2(p) + sep2);
|
|
229
|
+
}
|
|
230
|
+
|
|
141
231
|
// ../../plugins/fusion-plugin-compound-engineering/src/schema.ts
|
|
142
232
|
function ensureCeSchema(db) {
|
|
143
233
|
db.exec(`
|
|
@@ -216,8 +306,8 @@ function ensureCeSchema(db) {
|
|
|
216
306
|
}
|
|
217
307
|
|
|
218
308
|
// ../../plugins/fusion-plugin-compound-engineering/src/session/orchestrator.ts
|
|
219
|
-
import { mkdirSync as
|
|
220
|
-
import { dirname as
|
|
309
|
+
import { mkdirSync as mkdirSync3, writeFileSync } from "node:fs";
|
|
310
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3 } from "node:path";
|
|
221
311
|
|
|
222
312
|
// ../../plugins/fusion-plugin-compound-engineering/src/sync/pipeline-store.ts
|
|
223
313
|
import { randomUUID } from "node:crypto";
|
|
@@ -755,6 +845,7 @@ var MAX_ACTIVITY_TURNS = 200;
|
|
|
755
845
|
var MAX_ACTIVITY_TURN_CHARS = 16e3;
|
|
756
846
|
var MAX_PERSISTED_ACTIVITY_TURNS = 50;
|
|
757
847
|
var MAX_PERSISTED_ACTIVITY_TURN_CHARS = 4e3;
|
|
848
|
+
var INTERACTIVE_AI_UNAVAILABLE_MESSAGE = "Session cannot be continued in this process: interactive AI sessions are unavailable (no factory on this context). Resume from a route context with the engine loaded.";
|
|
758
849
|
var CE_EVENTS = {
|
|
759
850
|
turn: "compound-engineering:session-turn",
|
|
760
851
|
question: "compound-engineering:session-question",
|
|
@@ -1000,8 +1091,36 @@ var CeOrchestrator = class {
|
|
|
1000
1091
|
);
|
|
1001
1092
|
}
|
|
1002
1093
|
const live = this.live.get(sessionId);
|
|
1094
|
+
if (!live && !this.factory) {
|
|
1095
|
+
throw new Error(INTERACTIVE_AI_UNAVAILABLE_MESSAGE);
|
|
1096
|
+
}
|
|
1097
|
+
const turn = this.runAnswerTurn(session, questionId, response);
|
|
1098
|
+
if (opts.detach) {
|
|
1099
|
+
if (!live) {
|
|
1100
|
+
this.store.update(sessionId, { status: "active", error: null });
|
|
1101
|
+
}
|
|
1102
|
+
void turn;
|
|
1103
|
+
return { session: this.requireSession(sessionId) };
|
|
1104
|
+
}
|
|
1105
|
+
return turn;
|
|
1106
|
+
}
|
|
1107
|
+
async runAnswerTurn(session, questionId, response) {
|
|
1108
|
+
const sessionId = session.id;
|
|
1109
|
+
let live = this.live.get(sessionId);
|
|
1003
1110
|
if (!live) {
|
|
1004
|
-
|
|
1111
|
+
try {
|
|
1112
|
+
await this.rehydrate(session);
|
|
1113
|
+
live = this.live.get(sessionId);
|
|
1114
|
+
if (!live) {
|
|
1115
|
+
throw new Error(`Session ${sessionId} could not be rehydrated with a live handle.`);
|
|
1116
|
+
}
|
|
1117
|
+
} catch (err) {
|
|
1118
|
+
const interrupted = this.interruptSession(sessionId, err);
|
|
1119
|
+
return {
|
|
1120
|
+
session: interrupted,
|
|
1121
|
+
event: { type: "error", data: { message: interrupted.error ?? "interrupted", cause: err } }
|
|
1122
|
+
};
|
|
1123
|
+
}
|
|
1005
1124
|
}
|
|
1006
1125
|
this.store.appendHistory(sessionId, {
|
|
1007
1126
|
role: "user",
|
|
@@ -1009,12 +1128,7 @@ var CeOrchestrator = class {
|
|
|
1009
1128
|
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1010
1129
|
});
|
|
1011
1130
|
this.store.update(sessionId, { status: "active", currentQuestion: null });
|
|
1012
|
-
|
|
1013
|
-
if (opts.detach) {
|
|
1014
|
-
void turn;
|
|
1015
|
-
return { session: this.requireSession(sessionId) };
|
|
1016
|
-
}
|
|
1017
|
-
return turn;
|
|
1131
|
+
return this.runTurn(sessionId, () => live.answer(questionId, response), live);
|
|
1018
1132
|
}
|
|
1019
1133
|
/**
|
|
1020
1134
|
* Resume an `awaiting_input`, `interrupted`, or `error` session — and, crucially,
|
|
@@ -1051,7 +1165,7 @@ var CeOrchestrator = class {
|
|
|
1051
1165
|
if (!this.factory) {
|
|
1052
1166
|
const next = this.store.update(sessionId, {
|
|
1053
1167
|
status: "interrupted",
|
|
1054
|
-
error:
|
|
1168
|
+
error: INTERACTIVE_AI_UNAVAILABLE_MESSAGE
|
|
1055
1169
|
}) ?? session;
|
|
1056
1170
|
return { session: next };
|
|
1057
1171
|
}
|
|
@@ -1128,6 +1242,22 @@ var CeOrchestrator = class {
|
|
|
1128
1242
|
getState(sessionId) {
|
|
1129
1243
|
return this.store.get(sessionId);
|
|
1130
1244
|
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Cancel a session: stop any live in-process handle but keep the persisted row
|
|
1247
|
+
* for inspection/resume by marking it `interrupted`. Unlike discard(), cancel
|
|
1248
|
+
* preserves the conversation and progress; discard stops the handle AND deletes
|
|
1249
|
+
* the row. Terminal sessions are idempotent no-ops.
|
|
1250
|
+
*/
|
|
1251
|
+
cancel(sessionId) {
|
|
1252
|
+
const session = this.store.get(sessionId);
|
|
1253
|
+
if (!session) return void 0;
|
|
1254
|
+
if (session.status === "completed" || session.status === "error" || session.status === "interrupted") {
|
|
1255
|
+
return session;
|
|
1256
|
+
}
|
|
1257
|
+
const interrupted = this.interruptSession(sessionId, new Error("Cancelled by user"));
|
|
1258
|
+
this.disposeLive(sessionId);
|
|
1259
|
+
return interrupted;
|
|
1260
|
+
}
|
|
1131
1261
|
/**
|
|
1132
1262
|
* Discard a session: dispose any live in-process handle (so an in-flight
|
|
1133
1263
|
* agent doesn't keep running unobserved) and delete the persisted row.
|
|
@@ -1288,9 +1418,9 @@ var CeOrchestrator = class {
|
|
|
1288
1418
|
const stage = getStage(session.stage);
|
|
1289
1419
|
const location = stage?.artifactLocation ?? `docs/ce/${session.stage}/`;
|
|
1290
1420
|
const content = this.extractArtifactContent(data);
|
|
1291
|
-
const target = location.endsWith("/") ?
|
|
1292
|
-
const abs =
|
|
1293
|
-
|
|
1421
|
+
const target = location.endsWith("/") ? join3(location, `${session.stage}-${session.id}.md`) : location;
|
|
1422
|
+
const abs = isAbsolute3(target) ? target : join3(this.projectRoot, target);
|
|
1423
|
+
mkdirSync3(dirname3(abs), { recursive: true });
|
|
1294
1424
|
writeFileSync(abs, content, "utf-8");
|
|
1295
1425
|
return abs;
|
|
1296
1426
|
}
|
|
@@ -1322,6 +1452,35 @@ var CeOrchestrator = class {
|
|
|
1322
1452
|
}
|
|
1323
1453
|
};
|
|
1324
1454
|
|
|
1455
|
+
// ../../plugins/fusion-plugin-compound-engineering/src/session/session-recovery.ts
|
|
1456
|
+
var DEFAULT_RECOVERY_SCAN_TTL_MS = 12e4;
|
|
1457
|
+
var lastRecoveryScanAt = /* @__PURE__ */ new WeakMap();
|
|
1458
|
+
function recoverStaleSessionsForContext(ctx, options) {
|
|
1459
|
+
const key = ctx.taskStore;
|
|
1460
|
+
const now = options.now ?? Date.now();
|
|
1461
|
+
const ttlMs = options.ttlMs ?? DEFAULT_RECOVERY_SCAN_TTL_MS;
|
|
1462
|
+
if (!options.force) {
|
|
1463
|
+
const last = lastRecoveryScanAt.get(key) ?? 0;
|
|
1464
|
+
if (now - last < ttlMs) return [];
|
|
1465
|
+
}
|
|
1466
|
+
lastRecoveryScanAt.set(key, now);
|
|
1467
|
+
try {
|
|
1468
|
+
const recovered = getCeSessionStore(ctx).recoverStaleSessions(now);
|
|
1469
|
+
if (recovered.length > 0) {
|
|
1470
|
+
ctx.logger.info(`Compound Engineering recovered stale session(s) during ${options.reason}: ${recovered.join(", ")}`);
|
|
1471
|
+
if (options.emitEvent) {
|
|
1472
|
+
ctx.emitEvent("compound-engineering:sessions-recovered", { sessionIds: recovered, reason: options.reason });
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return recovered;
|
|
1476
|
+
} catch (err) {
|
|
1477
|
+
ctx.logger.warn(
|
|
1478
|
+
`Compound Engineering stale-session recovery skipped during ${options.reason}: ${err instanceof Error ? err.message : String(err)}`
|
|
1479
|
+
);
|
|
1480
|
+
return [];
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1325
1484
|
// ../../plugins/fusion-plugin-compound-engineering/src/routes/route-helpers.ts
|
|
1326
1485
|
function asString2(v) {
|
|
1327
1486
|
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
@@ -1401,12 +1560,24 @@ function createSessionRoutes() {
|
|
|
1401
1560
|
}
|
|
1402
1561
|
}
|
|
1403
1562
|
},
|
|
1563
|
+
{
|
|
1564
|
+
method: "POST",
|
|
1565
|
+
path: "/sessions/:id/cancel",
|
|
1566
|
+
description: "Cancel an in-flight CE session (stops the agent, keeps the row as interrupted).",
|
|
1567
|
+
handler: async (req, ctx) => {
|
|
1568
|
+
const id = req.params.id;
|
|
1569
|
+
const session = getOrchestrator(ctx).cancel(id);
|
|
1570
|
+
if (!session) return { status: 404, body: { error: `Session ${id} not found` } };
|
|
1571
|
+
return { status: 200, body: { session } };
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
1404
1574
|
{
|
|
1405
1575
|
method: "GET",
|
|
1406
1576
|
path: "/sessions/:id",
|
|
1407
1577
|
description: "Get current session state, including in-flight working output (liveActivity).",
|
|
1408
1578
|
handler: async (req, ctx) => {
|
|
1409
1579
|
const id = req.params.id;
|
|
1580
|
+
recoverStaleSessionsForContext(ctx, { reason: "route" });
|
|
1410
1581
|
const session = getCeSessionStore(ctx).get(id);
|
|
1411
1582
|
if (!session) return { status: 404, body: { error: `Session ${id} not found` } };
|
|
1412
1583
|
const liveActivity = getOrchestrator(ctx).getLiveActivity(id);
|
|
@@ -1421,6 +1592,7 @@ function createSessionRoutes() {
|
|
|
1421
1592
|
path: "/sessions",
|
|
1422
1593
|
description: "List CE sessions (optionally filtered by status/stage).",
|
|
1423
1594
|
handler: async (req, ctx) => {
|
|
1595
|
+
recoverStaleSessionsForContext(ctx, { reason: "route" });
|
|
1424
1596
|
const query = req.query ?? {};
|
|
1425
1597
|
const status = asCeSessionStatus(typeof query.status === "string" ? query.status : void 0);
|
|
1426
1598
|
const stage = typeof query.stage === "string" ? query.stage : void 0;
|
|
@@ -1456,8 +1628,8 @@ function createSessionRoutes() {
|
|
|
1456
1628
|
}
|
|
1457
1629
|
|
|
1458
1630
|
// ../../plugins/fusion-plugin-compound-engineering/src/artifacts/discovery.ts
|
|
1459
|
-
import { accessSync, constants, readdirSync, readFileSync as
|
|
1460
|
-
import { isAbsolute as
|
|
1631
|
+
import { accessSync, constants, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
1632
|
+
import { isAbsolute as isAbsolute4, join as join4, relative, sep as sep3 } from "node:path";
|
|
1461
1633
|
var CONVENTIONAL_LOCATIONS = [
|
|
1462
1634
|
{ stage: "strategy", label: "Strategy", path: "STRATEGY.md", kind: "file" },
|
|
1463
1635
|
{ stage: "ideation", label: "Ideation", path: "docs/ideation", kind: "directory" },
|
|
@@ -1468,13 +1640,13 @@ var CONVENTIONAL_LOCATIONS = [
|
|
|
1468
1640
|
];
|
|
1469
1641
|
var MAX_ARTIFACT_BYTES = 2e6;
|
|
1470
1642
|
function toPosix(p) {
|
|
1471
|
-
return p.split(
|
|
1643
|
+
return p.split(sep3).join("/");
|
|
1472
1644
|
}
|
|
1473
1645
|
function isWithin(root, locationAbs, candidate) {
|
|
1474
1646
|
const relToLocation = relative(locationAbs, candidate);
|
|
1475
|
-
if (relToLocation.startsWith("..") ||
|
|
1647
|
+
if (relToLocation.startsWith("..") || isAbsolute4(relToLocation)) return false;
|
|
1476
1648
|
const relToRoot = relative(root, candidate);
|
|
1477
|
-
if (relToRoot.startsWith("..") ||
|
|
1649
|
+
if (relToRoot.startsWith("..") || isAbsolute4(relToRoot)) return false;
|
|
1478
1650
|
return true;
|
|
1479
1651
|
}
|
|
1480
1652
|
function makeId(stage, relPath) {
|
|
@@ -1522,7 +1694,7 @@ function sortEntries(entries) {
|
|
|
1522
1694
|
});
|
|
1523
1695
|
}
|
|
1524
1696
|
function discoverLocation(root, loc) {
|
|
1525
|
-
const locationAbs =
|
|
1697
|
+
const locationAbs = join4(root, loc.path);
|
|
1526
1698
|
const entries = [];
|
|
1527
1699
|
let present = false;
|
|
1528
1700
|
let st;
|
|
@@ -1558,7 +1730,7 @@ function discoverLocation(root, loc) {
|
|
|
1558
1730
|
);
|
|
1559
1731
|
return { stage: loc.stage, label: loc.label, present, entries: sortEntries(entries) };
|
|
1560
1732
|
}
|
|
1561
|
-
names =
|
|
1733
|
+
names = readdirSync2(locationAbs);
|
|
1562
1734
|
} catch (err) {
|
|
1563
1735
|
entries.push(makeError(loc.stage, toPosix(loc.path), err instanceof Error ? err.message : String(err)));
|
|
1564
1736
|
return { stage: loc.stage, label: loc.label, present, entries: sortEntries(entries) };
|
|
@@ -1566,8 +1738,8 @@ function discoverLocation(root, loc) {
|
|
|
1566
1738
|
for (const childName of names) {
|
|
1567
1739
|
if (childName.startsWith(".")) continue;
|
|
1568
1740
|
if (!childName.toLowerCase().endsWith(".md")) continue;
|
|
1569
|
-
const abs =
|
|
1570
|
-
const relPath = toPosix(
|
|
1741
|
+
const abs = join4(locationAbs, childName);
|
|
1742
|
+
const relPath = toPosix(join4(loc.path, childName));
|
|
1571
1743
|
let childStat;
|
|
1572
1744
|
try {
|
|
1573
1745
|
childStat = statSync(abs);
|
|
@@ -1600,8 +1772,8 @@ function readArtifactById(projectRoot, id) {
|
|
|
1600
1772
|
const relPath = id.slice(sepIdx + 1);
|
|
1601
1773
|
const loc = CONVENTIONAL_LOCATIONS.find((l) => l.stage === stage);
|
|
1602
1774
|
if (!loc) return void 0;
|
|
1603
|
-
const locationAbs =
|
|
1604
|
-
const abs =
|
|
1775
|
+
const locationAbs = join4(projectRoot, loc.path);
|
|
1776
|
+
const abs = join4(projectRoot, relPath);
|
|
1605
1777
|
if (loc.kind === "file") {
|
|
1606
1778
|
if (toPosix(relPath) !== toPosix(loc.path)) return void 0;
|
|
1607
1779
|
} else if (!isWithin(projectRoot, locationAbs, abs)) {
|
|
@@ -1609,7 +1781,7 @@ function readArtifactById(projectRoot, id) {
|
|
|
1609
1781
|
}
|
|
1610
1782
|
if (loc.kind === "directory") {
|
|
1611
1783
|
const rel = relative(locationAbs, abs);
|
|
1612
|
-
if (rel.includes(
|
|
1784
|
+
if (rel.includes(sep3) || rel.startsWith("..") || !rel.toLowerCase().endsWith(".md")) {
|
|
1613
1785
|
return void 0;
|
|
1614
1786
|
}
|
|
1615
1787
|
}
|
|
@@ -1627,7 +1799,7 @@ function readArtifactById(projectRoot, id) {
|
|
|
1627
1799
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
1628
1800
|
}
|
|
1629
1801
|
try {
|
|
1630
|
-
content =
|
|
1802
|
+
content = readFileSync3(abs, "utf8");
|
|
1631
1803
|
} catch (err) {
|
|
1632
1804
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
1633
1805
|
}
|
|
@@ -1939,8 +2111,42 @@ var plugin = definePlugin({
|
|
|
1939
2111
|
const message = error instanceof Error ? error.message : String(error);
|
|
1940
2112
|
ctx.logger.error(`Compound Engineering skill install failed: ${message}`);
|
|
1941
2113
|
}
|
|
2114
|
+
try {
|
|
2115
|
+
const { targetRoot, results } = installBundledCeAgents();
|
|
2116
|
+
const installed = results.filter((r) => r.outcome === "installed").length;
|
|
2117
|
+
const errored = results.filter((r) => r.outcome === "error");
|
|
2118
|
+
if (errored.length > 0) {
|
|
2119
|
+
ctx.logger.warn(
|
|
2120
|
+
`Compound Engineering: ${errored.length} agent def(s) failed to install: ${errored.map((e) => `${e.agentId} (${e.reason})`).join(", ")}`
|
|
2121
|
+
);
|
|
2122
|
+
}
|
|
2123
|
+
ctx.logger.info(
|
|
2124
|
+
`Compound Engineering agent personas ready \u2014 installed=${installed} target=${targetRoot}`
|
|
2125
|
+
);
|
|
2126
|
+
ctx.emitEvent("compound-engineering:agents-installed", { targetRoot, results });
|
|
2127
|
+
} catch (error) {
|
|
2128
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2129
|
+
ctx.logger.error(`Compound Engineering agent install failed: ${message}`);
|
|
2130
|
+
}
|
|
2131
|
+
recoverStaleSessionsForContext(ctx, { reason: "load", force: true, emitEvent: true });
|
|
1942
2132
|
}
|
|
1943
2133
|
},
|
|
2134
|
+
// Expose the plugin-local ce-* persona-definition directory to executor /
|
|
2135
|
+
// workflow-step sessions via FUSION_CE_AGENTS_DIR. The CE skills read a persona
|
|
2136
|
+
// def from here and pass its body to fn_spawn_agent's systemPromptOverride —
|
|
2137
|
+
// the lightweight subagent path (no plugin agent-contribution channel exists).
|
|
2138
|
+
// Defs are installed in onLoad.
|
|
2139
|
+
executorRuntimeEnv: () => ({
|
|
2140
|
+
env: {
|
|
2141
|
+
FUSION_CE_AGENTS_DIR: resolveDefaultAgentsInstallTargetRoot(),
|
|
2142
|
+
// Step sessions run with cwd=projectRoot, so a CE skill can't reach its
|
|
2143
|
+
// bundled scripts via a relative `scripts/...` path. Expose the installed
|
|
2144
|
+
// skills root so skill docs can invoke scripts by absolute path
|
|
2145
|
+
// (`"$FUSION_CE_SKILLS_DIR/<skill-id>/scripts/<name>"`).
|
|
2146
|
+
FUSION_CE_SKILLS_DIR: resolveDefaultInstallTargetRoot()
|
|
2147
|
+
},
|
|
2148
|
+
description: "compound-engineering ce-* persona definitions and bundled skills directories"
|
|
2149
|
+
}),
|
|
1944
2150
|
routes: [...createSessionRoutes(), ...createArtifactRoutes()],
|
|
1945
2151
|
dashboardViews: [
|
|
1946
2152
|
{
|
|
@@ -1973,12 +2179,16 @@ export {
|
|
|
1973
2179
|
getReconcileIntervalMinutes,
|
|
1974
2180
|
getReconcileOnHooks,
|
|
1975
2181
|
getStage,
|
|
2182
|
+
installBundledCeAgents,
|
|
1976
2183
|
installBundledCeSkills,
|
|
2184
|
+
isPluginLocalAgentsPath,
|
|
1977
2185
|
isPluginLocalPath,
|
|
1978
2186
|
listStages,
|
|
1979
2187
|
reconcileCePipelines,
|
|
1980
2188
|
registerStage,
|
|
2189
|
+
resolveBundledAgentsRoot,
|
|
1981
2190
|
resolveBundledSkillsRoot,
|
|
2191
|
+
resolveDefaultAgentsInstallTargetRoot,
|
|
1982
2192
|
resolveDefaultInstallTargetRoot,
|
|
1983
2193
|
settingsSchema
|
|
1984
2194
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runfusion/fusion",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Fusion CLI: HTTP API server, daemon, dashboard launcher, and task tooling for the Fusion AI coding agent.",
|
|
6
6
|
"homepage": "https://github.com/Runfusion/Fusion#readme",
|
|
@@ -57,17 +57,9 @@
|
|
|
57
57
|
"ws": "^8.18.0"
|
|
58
58
|
},
|
|
59
59
|
"peerDependencies": {
|
|
60
|
-
"@earendil-works/pi-ai": "*",
|
|
61
|
-
"@earendil-works/pi-coding-agent": "*",
|
|
62
60
|
"typebox": "*"
|
|
63
61
|
},
|
|
64
62
|
"peerDependenciesMeta": {
|
|
65
|
-
"@earendil-works/pi-ai": {
|
|
66
|
-
"optional": true
|
|
67
|
-
},
|
|
68
|
-
"@earendil-works/pi-coding-agent": {
|
|
69
|
-
"optional": true
|
|
70
|
-
},
|
|
71
63
|
"typebox": {
|
|
72
64
|
"optional": true
|
|
73
65
|
}
|
|
@@ -108,6 +100,7 @@
|
|
|
108
100
|
"typecheck": "tsc --noEmit",
|
|
109
101
|
"test": "vitest run --silent=passed-only --reporter=dot",
|
|
110
102
|
"test:ci-shape": "vitest run src/__tests__/ci-workflow.test.ts --silent=passed-only --reporter=dot",
|
|
103
|
+
"test:docs-index": "vitest run src/__tests__/docs-readme-index.test.ts --silent=passed-only --reporter=dot",
|
|
111
104
|
"test:slow-cli": "cross-env FUSION_TEST_SLOW_CLI=1 vitest run src/commands/__tests__/agent-export.test.ts --silent=passed-only --reporter=dot",
|
|
112
105
|
"test:extension-integration": "cross-env FUSION_TEST_EXTENSION_INTEGRATION=1 vitest run src/__tests__/extension-integration.test.ts --silent=passed-only --reporter=dot",
|
|
113
106
|
"test:build-exe": "cross-env FUSION_TEST_BUILD_EXE=1 vitest run --config vitest.build-exe.config.ts --silent=passed-only --reporter=dot",
|
|
@@ -104,15 +104,15 @@ Request a refinement of a completed or in-review task. Creates a new follow-up t
|
|
|
104
104
|
|
|
105
105
|
### fn_task_archive
|
|
106
106
|
|
|
107
|
-
Archive a
|
|
107
|
+
Archive a task from any live column (move to archived). Archived tasks are preserved for historical reference but moved out of the main board view.
|
|
108
108
|
|
|
109
109
|
| Parameter | Type | Required | Description |
|
|
110
110
|
|-----------|------|----------|-------------|
|
|
111
|
-
| `id` | string | ✓ | Task ID to archive (e.g. FN-001).
|
|
111
|
+
| `id` | string | ✓ | Task ID to archive from any live column (e.g. FN-001). |
|
|
112
112
|
|
|
113
113
|
### fn_task_unarchive
|
|
114
114
|
|
|
115
|
-
Unarchive an archived task (move from archived →
|
|
115
|
+
Unarchive an archived task (move from archived → its restore column). Restores to the pre-archive column when available, with active execution columns downgraded to todo.
|
|
116
116
|
|
|
117
117
|
| Parameter | Type | Required | Description |
|
|
118
118
|
|-----------|------|----------|-------------|
|
|
@@ -22,8 +22,8 @@ All skill/extension tool invocations in this catalog use the public `fn_*` names
|
|
|
22
22
|
| `fn_task_retry` | Retry a failed task — clears the error state. Non-review failures move to todo; in-review execution failures move to todo preserving progress; in-review merge failures stay in-place for auto-merge retry. |
|
|
23
23
|
| `fn_task_duplicate` | Duplicate an existing task, creating a fresh copy in planning. Copies the title and description but resets all execution state. The AI planning agent will replan the new task. |
|
|
24
24
|
| `fn_task_refine` | Request a refinement of a completed or in-review task. Creates a new follow-up task in planning that references the original task as a dependency. Use this when a done or in-review task needs additional work, improvements, or follow-up changes. |
|
|
25
|
-
| `fn_task_archive` | Archive a
|
|
26
|
-
| `fn_task_unarchive` | Unarchive an archived task (move from archived →
|
|
25
|
+
| `fn_task_archive` | Archive a task from any live column (move to archived). Archived tasks are preserved for historical reference but moved out of the main board view. |
|
|
26
|
+
| `fn_task_unarchive` | Unarchive an archived task (move from archived → its restore column). Restores to the pre-archive column when available, with active execution columns downgraded to todo. |
|
|
27
27
|
| `fn_task_delete` | Soft-delete a task from active Fusion board views. The task row and artifacts are preserved; optional allowResurrection marks the ID for intentional recreation. |
|
|
28
28
|
| `fn_task_import_github` | Import GitHub issues as Fusion tasks. Fetches open issues from a repository and creates tasks in the planning column. Each task includes the issue title and body with a link to the source issue. |
|
|
29
29
|
| `fn_task_import_github_issue` | Import a specific GitHub issue as a Fusion task. Fetches the issue by number and creates a single task in the planning column with the issue title and body. |
|