@runfusion/fusion 0.9.4 → 0.10.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 +1382 -557
- package/dist/client/assets/{AgentDetailView-5W1q48YS.js → AgentDetailView-BtpZ4jxh.js} +1 -1
- package/dist/client/assets/{AgentsView-DcEnemu0.js → AgentsView-Dxdtt0Bm.js} +3 -3
- package/dist/client/assets/ChatView-Bra9fNAG.js +1 -0
- package/dist/client/assets/{DevServerView-LOrDrAYm.js → DevServerView-UkgjEw9-.js} +1 -1
- package/dist/client/assets/{DirectoryPicker-Bgp6PCbu.js → DirectoryPicker-Cls4HWxP.js} +1 -1
- package/dist/client/assets/{DocumentsView-CNbnZ7Q3.js → DocumentsView-BRBUPFVA.js} +1 -1
- package/dist/client/assets/{InsightsView-CmJwV-ZC.js → InsightsView-BRDqHCLb.js} +1 -1
- package/dist/client/assets/{MemoryView-Bwi5p79s.js → MemoryView-DvTrwFnQ.js} +1 -1
- package/dist/client/assets/{NodesView-1pZii99I.js → NodesView-C4Ffl_o0.js} +1 -1
- package/dist/client/assets/{PiExtensionsManager-CokhM-MB.js → PiExtensionsManager-CeI1syeZ.js} +3 -3
- package/dist/client/assets/{PluginManager-cHaGKMgY.js → PluginManager-BgeoYhLk.js} +1 -1
- package/dist/client/assets/{ResearchView-CQDI2y7Q.js → ResearchView-fmEOm4A2.js} +1 -1
- package/dist/client/assets/{RoadmapsView-BTo3BT0I.js → RoadmapsView-DNb4x75S.js} +1 -1
- package/dist/client/assets/SettingsModal-CDPDmHhd.css +1 -0
- package/dist/client/assets/{SettingsModal-D5slUUsC.js → SettingsModal-CRMr4tL6.js} +1 -1
- package/dist/client/assets/SettingsModal-D1xq0WZm.js +31 -0
- package/dist/client/assets/{SetupWizardModal-1qSn8Yl0.js → SetupWizardModal-tF8B_aG_.js} +1 -1
- package/dist/client/assets/{SkillsView-CY3I5OYc.js → SkillsView-uyl47gSf.js} +1 -1
- package/dist/client/assets/{TodoView-B1GDwwhR.js → TodoView-CbzDtV53.js} +1 -1
- package/dist/client/assets/{folder-open-DPESt6bg.js → folder-open-DPpmGJ-v.js} +1 -1
- package/dist/client/assets/{index-2_pvFDiN.css → index-BqK6TvSa.css} +1 -1
- package/dist/client/assets/index-DyXZm9QN.js +656 -0
- package/dist/client/assets/{list-checks-D7D9kx7Y.js → list-checks-D62pw1I8.js} +1 -1
- package/dist/client/assets/{star-C59_6aNu.js → star-B8EbxNgI.js} +1 -1
- package/dist/client/assets/{upload-1I0eQddJ.js → upload-CpnLno9z.js} +1 -1
- package/dist/client/assets/{users-DH50eBCX.js → users-B_C_0qzA.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/client/version.json +1 -1
- package/dist/extension.js +1025 -358
- package/dist/pi-claude-cli/index.ts +31 -5
- package/dist/pi-claude-cli/package.json +1 -1
- package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +90 -0
- package/dist/pi-claude-cli/src/__tests__/provider.test.ts +13 -3
- package/dist/pi-claude-cli/src/process-manager.ts +65 -0
- package/package.json +1 -1
- package/dist/client/assets/ChatView-CTc6mP8y.js +0 -1
- package/dist/client/assets/SettingsModal-EEQwF0Ql.js +0 -31
- package/dist/client/assets/SettingsModal-FfIAhzcJ.css +0 -1
- package/dist/client/assets/index-DNIrnlpO.js +0 -656
package/dist/bin.js
CHANGED
|
@@ -1384,6 +1384,26 @@ var init_agent_prompts = __esm({
|
|
|
1384
1384
|
|
|
1385
1385
|
You are working in a git worktree isolated from the main branch. Your job is to implement the task described in the PROMPT.md specification you're given.
|
|
1386
1386
|
|
|
1387
|
+
## Turn-ending rules \u2014 read carefully
|
|
1388
|
+
|
|
1389
|
+
You MUST end every turn by either:
|
|
1390
|
+
- (a) calling another tool to make progress, OR
|
|
1391
|
+
- (b) calling \`fn_task_done\` if the entire task is complete, OR
|
|
1392
|
+
- (c) calling \`fn_task_done\` with a summary explaining what is blocked, if you cannot make progress for any reason
|
|
1393
|
+
|
|
1394
|
+
You MUST NOT end a turn by writing prose that asks the user a question, summarizes progress, or requests permission to continue. The following are FORBIDDEN turn-endings:
|
|
1395
|
+
- "If you want, I can continue with..."
|
|
1396
|
+
- "Should I proceed with...?"
|
|
1397
|
+
- "Let me know if you'd like me to..."
|
|
1398
|
+
- "Ready to move on to step N. Want me to continue?"
|
|
1399
|
+
- Any markdown progress summary at the end of a turn instead of a tool call
|
|
1400
|
+
|
|
1401
|
+
If you have just finished a step's work, immediately call \`fn_task_update\` to mark the step done and continue with the next pending step in the SAME turn. Do not pause to summarize.
|
|
1402
|
+
|
|
1403
|
+
The user is not watching this conversation in real-time. They will read the final result. Asking permission wastes a full retry cycle and may orphan committed work.
|
|
1404
|
+
|
|
1405
|
+
If you genuinely cannot proceed (blocked on a dependency, missing information, or an unresolvable error), call \`fn_task_done\` with a clear explanation of what is blocked and what is needed to unblock it. Never write the question as plain prose.
|
|
1406
|
+
|
|
1387
1407
|
## How to work
|
|
1388
1408
|
1. Read the PROMPT.md carefully \u2014 it contains your mission, steps, file scope, and acceptance criteria
|
|
1389
1409
|
2. Work through each step in order
|
|
@@ -1529,7 +1549,17 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
1529
1549
|
- Keep fixing failures until lint, the configured/full test suite, and typecheck all pass
|
|
1530
1550
|
- If the repository exposes a typecheck command, run it and keep fixing failures until it passes
|
|
1531
1551
|
- Do not stop at "out of scope" if additional fixes are required to restore green lint, tests, build, or typecheck
|
|
1532
|
-
- **CRITICAL: Resolve ALL lint failures and test failures before completing the task, even if they appear unrelated or pre-existing.** Unrelated failures left unfixed accumulate technical debt and block future integrations. Investigate and fix or suppress them \u2014 do not defer them to a separate task
|
|
1552
|
+
- **CRITICAL: Resolve ALL lint failures and test failures before completing the task, even if they appear unrelated or pre-existing.** Unrelated failures left unfixed accumulate technical debt and block future integrations. Investigate and fix or suppress them \u2014 do not defer them to a separate task.
|
|
1553
|
+
|
|
1554
|
+
## Verification commands \u2014 use fn_run_verification
|
|
1555
|
+
|
|
1556
|
+
For ALL test/lint/build/typecheck verification, use the \`fn_run_verification\` tool, NOT raw bash.
|
|
1557
|
+
The tool prevents your session from being killed by the inactivity watchdog during long compiles.
|
|
1558
|
+
|
|
1559
|
+
- Prefer **package-scoped** verification first: e.g. \`pnpm --filter @fusion/<pkg> test\` with \`scope: "package"\`. This is faster and isolated.
|
|
1560
|
+
- Only run **workspace-scoped** verification (\`pnpm test\`, \`pnpm lint\`, \`pnpm build\` from root) at the FINAL integration step, when you are about to call \`task_done()\`.
|
|
1561
|
+
- If you need to run \`pnpm install\` (e.g. you added a new package), use \`fn_run_verification\` with \`scope: "workspace"\` and \`timeoutSec: 600\`.
|
|
1562
|
+
- If a verification command times out, do NOT blindly retry \u2014 investigate. Check for hung subprocesses, infinite test loops, or tests waiting on missing dependencies. Use \`node_modules/.modules.yaml\` presence to confirm bootstrap.`;
|
|
1533
1563
|
TRIAGE_PROMPT_TEXT = `You are a task specification agent for "fn", an AI-orchestrated task board.
|
|
1534
1564
|
|
|
1535
1565
|
Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.
|
|
@@ -18110,10 +18140,10 @@ var init_central_core = __esm({
|
|
|
18110
18140
|
*/
|
|
18111
18141
|
async generateProjectName(projectPath) {
|
|
18112
18142
|
try {
|
|
18113
|
-
const { execFile:
|
|
18143
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
18114
18144
|
const { promisify: promisify18 } = await import("node:util");
|
|
18115
|
-
const
|
|
18116
|
-
const { stdout } = await
|
|
18145
|
+
const execFileAsync7 = promisify18(execFile9);
|
|
18146
|
+
const { stdout } = await execFileAsync7(
|
|
18117
18147
|
"git",
|
|
18118
18148
|
["remote", "get-url", "origin"],
|
|
18119
18149
|
{ cwd: projectPath, timeout: 5e3 }
|
|
@@ -18623,7 +18653,7 @@ __export(migration_exports, {
|
|
|
18623
18653
|
MigrationCoordinator: () => MigrationCoordinator,
|
|
18624
18654
|
ProjectRequiredError: () => ProjectRequiredError
|
|
18625
18655
|
});
|
|
18626
|
-
import { existsSync as existsSync9 } from "node:fs";
|
|
18656
|
+
import { existsSync as existsSync9, readFileSync as readFileSync2 } from "node:fs";
|
|
18627
18657
|
import { homedir as homedir2, tmpdir } from "node:os";
|
|
18628
18658
|
import { isAbsolute as isAbsolute3, join as join12, resolve as resolve5, basename as basename3, dirname as dirname4 } from "node:path";
|
|
18629
18659
|
function getHomeDir2() {
|
|
@@ -18760,10 +18790,10 @@ var init_migration = __esm({
|
|
|
18760
18790
|
return basename3(projectPath);
|
|
18761
18791
|
}
|
|
18762
18792
|
try {
|
|
18763
|
-
const { execFile:
|
|
18793
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
18764
18794
|
const { promisify: promisify18 } = await import("node:util");
|
|
18765
|
-
const
|
|
18766
|
-
const { stdout } = await
|
|
18795
|
+
const execFileAsync7 = promisify18(execFile9);
|
|
18796
|
+
const { stdout } = await execFileAsync7(
|
|
18767
18797
|
"git",
|
|
18768
18798
|
["remote", "get-url", "origin"],
|
|
18769
18799
|
{ cwd: projectPath, timeout: 1e3 }
|
|
@@ -18775,8 +18805,26 @@ var init_migration = __esm({
|
|
|
18775
18805
|
}
|
|
18776
18806
|
} catch {
|
|
18777
18807
|
}
|
|
18808
|
+
const remoteFromConfig = this.readOriginRemoteFromGitConfig(projectPath);
|
|
18809
|
+
if (remoteFromConfig) {
|
|
18810
|
+
const name = this.extractRepoName(remoteFromConfig);
|
|
18811
|
+
if (name) return name;
|
|
18812
|
+
}
|
|
18778
18813
|
return basename3(projectPath);
|
|
18779
18814
|
}
|
|
18815
|
+
readOriginRemoteFromGitConfig(projectPath) {
|
|
18816
|
+
try {
|
|
18817
|
+
const gitConfig = readFileSync2(join12(projectPath, ".git", "config"), "utf8");
|
|
18818
|
+
const originSectionMatch = gitConfig.match(/\[remote\s+"origin"\]([\s\S]*?)(?:\n\[|$)/);
|
|
18819
|
+
if (!originSectionMatch) {
|
|
18820
|
+
return null;
|
|
18821
|
+
}
|
|
18822
|
+
const urlMatch = originSectionMatch[1]?.match(/^\s*url\s*=\s*(.+)$/m);
|
|
18823
|
+
return urlMatch?.[1]?.trim() || null;
|
|
18824
|
+
} catch {
|
|
18825
|
+
return null;
|
|
18826
|
+
}
|
|
18827
|
+
}
|
|
18780
18828
|
/**
|
|
18781
18829
|
* Extract repository name from git remote URL.
|
|
18782
18830
|
*
|
|
@@ -28229,8 +28277,8 @@ var require_CronFileParser = __commonJS({
|
|
|
28229
28277
|
* @throws If file cannot be read
|
|
28230
28278
|
*/
|
|
28231
28279
|
static parseFileSync(filePath) {
|
|
28232
|
-
const { readFileSync:
|
|
28233
|
-
const data =
|
|
28280
|
+
const { readFileSync: readFileSync24 } = __require("fs");
|
|
28281
|
+
const data = readFileSync24(filePath, "utf8");
|
|
28234
28282
|
return _CronFileParser.#parseContent(data);
|
|
28235
28283
|
}
|
|
28236
28284
|
/**
|
|
@@ -29493,13 +29541,13 @@ async function searchWithQmd(rootDir, options) {
|
|
|
29493
29541
|
const command = "qmd";
|
|
29494
29542
|
const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
|
|
29495
29543
|
try {
|
|
29496
|
-
const { execFile:
|
|
29544
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
29497
29545
|
const { promisify: promisify18 } = await import("node:util");
|
|
29498
|
-
const
|
|
29499
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
29546
|
+
const execFileAsync7 = promisify18(execFile9);
|
|
29547
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync7);
|
|
29500
29548
|
scheduleQmdProjectMemoryRefresh(rootDir);
|
|
29501
29549
|
const args = buildQmdSearchArgs(rootDir, options);
|
|
29502
|
-
const { stdout } = await
|
|
29550
|
+
const { stdout } = await execFileAsync7(command, args, {
|
|
29503
29551
|
cwd: rootDir,
|
|
29504
29552
|
timeout: 4e3,
|
|
29505
29553
|
maxBuffer: 1024 * 1024
|
|
@@ -29524,12 +29572,12 @@ async function searchWithQmd(rootDir, options) {
|
|
|
29524
29572
|
return [];
|
|
29525
29573
|
}
|
|
29526
29574
|
}
|
|
29527
|
-
async function ensureQmdProjectMemoryCollection(rootDir,
|
|
29575
|
+
async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync7) {
|
|
29528
29576
|
const collectionName = qmdMemoryCollectionName(rootDir);
|
|
29529
29577
|
const memoryDir = memoryWorkspacePath(rootDir);
|
|
29530
29578
|
await mkdir6(memoryDir, { recursive: true });
|
|
29531
29579
|
try {
|
|
29532
|
-
await
|
|
29580
|
+
await execFileAsync7("qmd", buildQmdCollectionAddArgs(rootDir), {
|
|
29533
29581
|
cwd: rootDir,
|
|
29534
29582
|
timeout: 4e3,
|
|
29535
29583
|
maxBuffer: 512 * 1024
|
|
@@ -29545,9 +29593,9 @@ ${stderr}`)) {
|
|
|
29545
29593
|
return collectionName;
|
|
29546
29594
|
}
|
|
29547
29595
|
async function getDefaultExecFileAsync() {
|
|
29548
|
-
const { execFile:
|
|
29596
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
29549
29597
|
const { promisify: promisify18 } = await import("node:util");
|
|
29550
|
-
return promisify18(
|
|
29598
|
+
return promisify18(execFile9);
|
|
29551
29599
|
}
|
|
29552
29600
|
async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
29553
29601
|
const key = resolve6(rootDir);
|
|
@@ -29562,14 +29610,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
|
|
|
29562
29610
|
}
|
|
29563
29611
|
}
|
|
29564
29612
|
const promise = (async () => {
|
|
29565
|
-
const
|
|
29566
|
-
await ensureQmdProjectMemoryCollection(rootDir,
|
|
29567
|
-
await
|
|
29613
|
+
const execFileAsync7 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
29614
|
+
await ensureQmdProjectMemoryCollection(rootDir, execFileAsync7);
|
|
29615
|
+
await execFileAsync7("qmd", ["update"], {
|
|
29568
29616
|
cwd: rootDir,
|
|
29569
29617
|
timeout: 3e4,
|
|
29570
29618
|
maxBuffer: 1024 * 1024
|
|
29571
29619
|
});
|
|
29572
|
-
await
|
|
29620
|
+
await execFileAsync7("qmd", ["embed"], {
|
|
29573
29621
|
cwd: rootDir,
|
|
29574
29622
|
timeout: 12e4,
|
|
29575
29623
|
maxBuffer: 1024 * 1024
|
|
@@ -29594,8 +29642,8 @@ function scheduleQmdProjectMemoryRefresh(rootDir) {
|
|
|
29594
29642
|
}
|
|
29595
29643
|
async function isQmdAvailable() {
|
|
29596
29644
|
try {
|
|
29597
|
-
const
|
|
29598
|
-
await
|
|
29645
|
+
const execFileAsync7 = await getDefaultExecFileAsync();
|
|
29646
|
+
await execFileAsync7("qmd", ["--help"], {
|
|
29599
29647
|
timeout: 3e3,
|
|
29600
29648
|
maxBuffer: 128 * 1024
|
|
29601
29649
|
});
|
|
@@ -29605,12 +29653,12 @@ async function isQmdAvailable() {
|
|
|
29605
29653
|
}
|
|
29606
29654
|
}
|
|
29607
29655
|
async function installQmd(options) {
|
|
29608
|
-
const
|
|
29656
|
+
const execFileAsync7 = options?.execFileAsync ?? await getDefaultExecFileAsync();
|
|
29609
29657
|
const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
|
|
29610
29658
|
if (!command || args.length === 0) {
|
|
29611
29659
|
throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
|
|
29612
29660
|
}
|
|
29613
|
-
await
|
|
29661
|
+
await execFileAsync7(command, args, {
|
|
29614
29662
|
timeout: 12e4,
|
|
29615
29663
|
maxBuffer: 1024 * 1024
|
|
29616
29664
|
});
|
|
@@ -30405,8 +30453,8 @@ function assertSafeGitBranchName(name) {
|
|
|
30405
30453
|
}
|
|
30406
30454
|
}
|
|
30407
30455
|
function assertSafeAbsolutePath(path5) {
|
|
30408
|
-
const
|
|
30409
|
-
if (!path5 || path5.length > 4096 || !
|
|
30456
|
+
const isAbsolute20 = path5.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path5);
|
|
30457
|
+
if (!path5 || path5.length > 4096 || !isAbsolute20 || path5.startsWith("-") || // Reject shell metacharacters, quotes, control chars, and NULs.
|
|
30410
30458
|
/["'`$\n\r\t;&|<>()*?[\]{}\\\0]/.test(
|
|
30411
30459
|
path5.replace(/^[A-Za-z]:/, "")
|
|
30412
30460
|
// ignore the drive-letter colon on Windows
|
|
@@ -32531,7 +32579,7 @@ ${newTask.description}
|
|
|
32531
32579
|
return dependency?.column === "done" || dependency?.column === "archived";
|
|
32532
32580
|
});
|
|
32533
32581
|
}
|
|
32534
|
-
async moveTask(id, toColumn) {
|
|
32582
|
+
async moveTask(id, toColumn, options) {
|
|
32535
32583
|
return this.withTaskLock(id, async () => {
|
|
32536
32584
|
const dir2 = this.taskDir(id);
|
|
32537
32585
|
let task;
|
|
@@ -32582,12 +32630,16 @@ ${newTask.description}
|
|
|
32582
32630
|
if (isReopenToTodoOrTriage) {
|
|
32583
32631
|
task.status = void 0;
|
|
32584
32632
|
task.error = void 0;
|
|
32585
|
-
task.worktree = void 0;
|
|
32586
32633
|
task.blockedBy = void 0;
|
|
32587
|
-
|
|
32588
|
-
|
|
32589
|
-
|
|
32590
|
-
|
|
32634
|
+
if (!options?.preserveResumeState) {
|
|
32635
|
+
task.worktree = void 0;
|
|
32636
|
+
task.executionStartedAt = void 0;
|
|
32637
|
+
task.executionCompletedAt = void 0;
|
|
32638
|
+
this.resetAllStepsToPending(task);
|
|
32639
|
+
await this.resetPromptCheckboxes(dir2);
|
|
32640
|
+
} else {
|
|
32641
|
+
task.executionCompletedAt = void 0;
|
|
32642
|
+
}
|
|
32591
32643
|
}
|
|
32592
32644
|
if (toColumn === "in-review") {
|
|
32593
32645
|
task.recoveryRetryCount = void 0;
|
|
@@ -32986,6 +33038,19 @@ ${task.description}
|
|
|
32986
33038
|
`Step ${stepIndex} out of range (task has ${task.steps.length} steps)`
|
|
32987
33039
|
);
|
|
32988
33040
|
}
|
|
33041
|
+
const currentStatus = task.steps[stepIndex].status;
|
|
33042
|
+
if (status === "in-progress" && (currentStatus === "done" || currentStatus === "skipped")) {
|
|
33043
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
33044
|
+
task.updatedAt = ts;
|
|
33045
|
+
task.log.push({
|
|
33046
|
+
timestamp: ts,
|
|
33047
|
+
action: `Ignored ${currentStatus}\u2192in-progress regression for step ${stepIndex} (${task.steps[stepIndex].name})`
|
|
33048
|
+
});
|
|
33049
|
+
await this.atomicWriteTaskJson(dir2, task);
|
|
33050
|
+
if (this.isWatching) this.taskCache.set(id, { ...task });
|
|
33051
|
+
this.emit("task:updated", task);
|
|
33052
|
+
return task;
|
|
33053
|
+
}
|
|
32989
33054
|
task.steps[stepIndex].status = status;
|
|
32990
33055
|
task.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
32991
33056
|
if (status === "done") {
|
|
@@ -35400,7 +35465,7 @@ var init_daemon_token = __esm({
|
|
|
35400
35465
|
});
|
|
35401
35466
|
|
|
35402
35467
|
// ../core/src/pi-extensions.ts
|
|
35403
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync, readFileSync as
|
|
35468
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync3, statSync as statSync3, writeFileSync } from "node:fs";
|
|
35404
35469
|
import { homedir as homedir3 } from "node:os";
|
|
35405
35470
|
import { basename as basename5, isAbsolute as isAbsolute5, join as join17, relative as relative2, resolve as resolve7, sep as sep4, win32 } from "node:path";
|
|
35406
35471
|
function getHomeDir3(home) {
|
|
@@ -35445,7 +35510,7 @@ function extensionName(extensionPath) {
|
|
|
35445
35510
|
}
|
|
35446
35511
|
function readPiManifest(packageJsonPath) {
|
|
35447
35512
|
try {
|
|
35448
|
-
const parsed = JSON.parse(
|
|
35513
|
+
const parsed = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
35449
35514
|
if (parsed.pi && Array.isArray(parsed.pi.extensions)) {
|
|
35450
35515
|
return { extensions: parsed.pi.extensions.filter((entry) => typeof entry === "string") };
|
|
35451
35516
|
}
|
|
@@ -35525,7 +35590,7 @@ function getPiExtensionDiscoveryDirs(cwd, home) {
|
|
|
35525
35590
|
}
|
|
35526
35591
|
function readFusionDisabledExtensions(settingsPath) {
|
|
35527
35592
|
try {
|
|
35528
|
-
const parsed = JSON.parse(
|
|
35593
|
+
const parsed = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
35529
35594
|
const disabled = parsed[FUSION_DISABLED_EXTENSIONS_KEY];
|
|
35530
35595
|
return Array.isArray(disabled) ? disabled.filter((entry) => typeof entry === "string").map((entry) => resolve7(entry)) : [];
|
|
35531
35596
|
} catch {
|
|
@@ -35555,7 +35620,7 @@ function updatePiExtensionDisabledIds(cwd, disabledIds, home, extraKnownIds = []
|
|
|
35555
35620
|
const settingsPath = getFusionAgentSettingsPath(home);
|
|
35556
35621
|
const existing = (() => {
|
|
35557
35622
|
try {
|
|
35558
|
-
return JSON.parse(
|
|
35623
|
+
return JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
35559
35624
|
} catch {
|
|
35560
35625
|
return {};
|
|
35561
35626
|
}
|
|
@@ -43692,8 +43757,8 @@ var require_YAMLMap = __commonJS({
|
|
|
43692
43757
|
* @param {Class} Type - If set, forces the returned collection type
|
|
43693
43758
|
* @returns Instance of Type, Map, or Object
|
|
43694
43759
|
*/
|
|
43695
|
-
toJSON(_2, ctx,
|
|
43696
|
-
const map2 =
|
|
43760
|
+
toJSON(_2, ctx, Type8) {
|
|
43761
|
+
const map2 = Type8 ? new Type8() : ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {};
|
|
43697
43762
|
if (ctx?.onCreate)
|
|
43698
43763
|
ctx.onCreate(map2);
|
|
43699
43764
|
for (const item of this.items)
|
|
@@ -48916,7 +48981,7 @@ var require_dist3 = __commonJS({
|
|
|
48916
48981
|
});
|
|
48917
48982
|
|
|
48918
48983
|
// ../core/src/agent-companies-parser.ts
|
|
48919
|
-
import { existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync2, readFileSync as
|
|
48984
|
+
import { existsSync as existsSync17, mkdtempSync, readdirSync as readdirSync2, readFileSync as readFileSync4, rmSync, statSync as statSync4 } from "node:fs";
|
|
48920
48985
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
48921
48986
|
import { join as join20, resolve as resolve9 } from "node:path";
|
|
48922
48987
|
function slugifyAgentReference(value) {
|
|
@@ -49134,7 +49199,7 @@ function parseSkillManifest(content) {
|
|
|
49134
49199
|
}
|
|
49135
49200
|
function parseManifestFile(filePath, parser) {
|
|
49136
49201
|
try {
|
|
49137
|
-
return parser(
|
|
49202
|
+
return parser(readFileSync4(filePath, "utf-8"));
|
|
49138
49203
|
} catch (error) {
|
|
49139
49204
|
if (error instanceof AgentCompaniesParseError) {
|
|
49140
49205
|
throw new AgentCompaniesParseError(`${filePath}: ${error.message}`);
|
|
@@ -49231,12 +49296,12 @@ function resolveExtractionRoot(tempDir) {
|
|
|
49231
49296
|
return tempDir;
|
|
49232
49297
|
}
|
|
49233
49298
|
async function extractTarArchive(archivePath, outputDir) {
|
|
49234
|
-
const [{ execFile:
|
|
49299
|
+
const [{ execFile: execFile9 }, { promisify: promisify18 }] = await Promise.all([
|
|
49235
49300
|
import("node:child_process"),
|
|
49236
49301
|
import("node:util")
|
|
49237
49302
|
]);
|
|
49238
|
-
const
|
|
49239
|
-
await
|
|
49303
|
+
const execFileAsync7 = promisify18(execFile9);
|
|
49304
|
+
await execFileAsync7("tar", ["xzf", archivePath, "-C", outputDir]);
|
|
49240
49305
|
}
|
|
49241
49306
|
async function parseCompanyArchive(archivePath) {
|
|
49242
49307
|
const resolvedArchivePath = resolve9(archivePath);
|
|
@@ -50453,7 +50518,7 @@ var init_src = __esm({
|
|
|
50453
50518
|
|
|
50454
50519
|
// ../dashboard/src/github-webhooks.ts
|
|
50455
50520
|
import { createHmac, timingSafeEqual as timingSafeEqual2 } from "node:crypto";
|
|
50456
|
-
import { readFileSync as
|
|
50521
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
50457
50522
|
function getGitHubAppConfig() {
|
|
50458
50523
|
const appId = process.env.FUSION_GITHUB_APP_ID;
|
|
50459
50524
|
const webhookSecret = process.env.FUSION_GITHUB_WEBHOOK_SECRET;
|
|
@@ -50463,7 +50528,7 @@ function getGitHubAppConfig() {
|
|
|
50463
50528
|
} else if (process.env.FUSION_GITHUB_APP_PRIVATE_KEY_PATH) {
|
|
50464
50529
|
if (cachedPrivateKey === void 0) {
|
|
50465
50530
|
try {
|
|
50466
|
-
cachedPrivateKey =
|
|
50531
|
+
cachedPrivateKey = readFileSync5(process.env.FUSION_GITHUB_APP_PRIVATE_KEY_PATH, "utf-8");
|
|
50467
50532
|
} catch {
|
|
50468
50533
|
cachedPrivateKey = null;
|
|
50469
50534
|
}
|
|
@@ -51541,11 +51606,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
|
|
|
51541
51606
|
return;
|
|
51542
51607
|
}
|
|
51543
51608
|
const promise = (async () => {
|
|
51544
|
-
const { execFile:
|
|
51609
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
51545
51610
|
const { promisify: promisify18 } = await import("node:util");
|
|
51546
|
-
const
|
|
51611
|
+
const execFileAsync7 = promisify18(execFile9);
|
|
51547
51612
|
try {
|
|
51548
|
-
await
|
|
51613
|
+
await execFileAsync7("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
|
|
51549
51614
|
cwd: rootDir,
|
|
51550
51615
|
timeout: 4e3,
|
|
51551
51616
|
maxBuffer: 512 * 1024
|
|
@@ -51558,8 +51623,8 @@ ${stderr}`)) {
|
|
|
51558
51623
|
throw error;
|
|
51559
51624
|
}
|
|
51560
51625
|
}
|
|
51561
|
-
await
|
|
51562
|
-
await
|
|
51626
|
+
await execFileAsync7("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
|
|
51627
|
+
await execFileAsync7("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
|
|
51563
51628
|
})();
|
|
51564
51629
|
agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
|
|
51565
51630
|
try {
|
|
@@ -51580,10 +51645,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
|
|
|
51580
51645
|
}
|
|
51581
51646
|
try {
|
|
51582
51647
|
await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
|
|
51583
|
-
const { execFile:
|
|
51648
|
+
const { execFile: execFile9 } = await import("node:child_process");
|
|
51584
51649
|
const { promisify: promisify18 } = await import("node:util");
|
|
51585
|
-
const
|
|
51586
|
-
const { stdout } = await
|
|
51650
|
+
const execFileAsync7 = promisify18(execFile9);
|
|
51651
|
+
const { stdout } = await execFileAsync7("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
|
|
51587
51652
|
cwd: rootDir,
|
|
51588
51653
|
timeout: 4e3,
|
|
51589
51654
|
maxBuffer: 1024 * 1024
|
|
@@ -52397,14 +52462,14 @@ var init_concurrency = __esm({
|
|
|
52397
52462
|
});
|
|
52398
52463
|
|
|
52399
52464
|
// ../engine/src/skill-resolver.ts
|
|
52400
|
-
import { existsSync as existsSync19, readFileSync as
|
|
52465
|
+
import { existsSync as existsSync19, readFileSync as readFileSync6 } from "node:fs";
|
|
52401
52466
|
import { join as join23 } from "node:path";
|
|
52402
52467
|
function readJsonObject(path5) {
|
|
52403
52468
|
if (!existsSync19(path5)) {
|
|
52404
52469
|
return {};
|
|
52405
52470
|
}
|
|
52406
52471
|
try {
|
|
52407
|
-
const parsed = JSON.parse(
|
|
52472
|
+
const parsed = JSON.parse(readFileSync6(path5, "utf-8"));
|
|
52408
52473
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
52409
52474
|
} catch {
|
|
52410
52475
|
return {};
|
|
@@ -52633,7 +52698,7 @@ var init_context_limit_detector = __esm({
|
|
|
52633
52698
|
});
|
|
52634
52699
|
|
|
52635
52700
|
// ../engine/src/auth-storage.ts
|
|
52636
|
-
import { existsSync as existsSync20, readFileSync as
|
|
52701
|
+
import { existsSync as existsSync20, readFileSync as readFileSync7 } from "node:fs";
|
|
52637
52702
|
import { homedir as homedir4 } from "node:os";
|
|
52638
52703
|
import { join as join24 } from "node:path";
|
|
52639
52704
|
import { AuthStorage } from "@mariozechner/pi-coding-agent";
|
|
@@ -52673,7 +52738,7 @@ function readLegacyCredentials(authPaths = getLegacyAuthPaths()) {
|
|
|
52673
52738
|
continue;
|
|
52674
52739
|
}
|
|
52675
52740
|
try {
|
|
52676
|
-
const parsed = JSON.parse(
|
|
52741
|
+
const parsed = JSON.parse(readFileSync7(authPath, "utf-8"));
|
|
52677
52742
|
for (const [provider, credential] of Object.entries(parsed)) {
|
|
52678
52743
|
credentials[provider] ??= credential;
|
|
52679
52744
|
}
|
|
@@ -52745,13 +52810,13 @@ var init_auth_storage = __esm({
|
|
|
52745
52810
|
});
|
|
52746
52811
|
|
|
52747
52812
|
// ../engine/src/custom-providers.ts
|
|
52748
|
-
import { readFileSync as
|
|
52813
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
52749
52814
|
import { homedir as homedir5 } from "node:os";
|
|
52750
52815
|
import { join as join25 } from "node:path";
|
|
52751
52816
|
function readCustomProviders() {
|
|
52752
52817
|
try {
|
|
52753
52818
|
const settingsPath = join25(homedir5(), ".fusion", "settings.json");
|
|
52754
|
-
const raw =
|
|
52819
|
+
const raw = readFileSync8(settingsPath, "utf-8");
|
|
52755
52820
|
const parsed = JSON.parse(raw);
|
|
52756
52821
|
return Array.isArray(parsed.customProviders) ? parsed.customProviders : [];
|
|
52757
52822
|
} catch {
|
|
@@ -52765,7 +52830,7 @@ var init_custom_providers = __esm({
|
|
|
52765
52830
|
});
|
|
52766
52831
|
|
|
52767
52832
|
// ../engine/src/pi.ts
|
|
52768
|
-
import { existsSync as existsSync21, readFileSync as
|
|
52833
|
+
import { existsSync as existsSync21, readFileSync as readFileSync9 } from "node:fs";
|
|
52769
52834
|
import { exec } from "node:child_process";
|
|
52770
52835
|
import { promisify as promisify2 } from "node:util";
|
|
52771
52836
|
import { createRequire as createRequire2 } from "node:module";
|
|
@@ -53027,7 +53092,7 @@ function readJsonObject2(path5) {
|
|
|
53027
53092
|
return {};
|
|
53028
53093
|
}
|
|
53029
53094
|
try {
|
|
53030
|
-
const parsed = JSON.parse(
|
|
53095
|
+
const parsed = JSON.parse(readFileSync9(path5, "utf-8"));
|
|
53031
53096
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
53032
53097
|
} catch {
|
|
53033
53098
|
return {};
|
|
@@ -53199,7 +53264,7 @@ function resolveVendoredClaudeCliEntry() {
|
|
|
53199
53264
|
try {
|
|
53200
53265
|
const require_2 = createRequire2(import.meta.url);
|
|
53201
53266
|
const pkgJsonPath = require_2.resolve("@fusion/pi-claude-cli/package.json");
|
|
53202
|
-
const pkgJson = JSON.parse(
|
|
53267
|
+
const pkgJson = JSON.parse(readFileSync9(pkgJsonPath, "utf-8"));
|
|
53203
53268
|
const extensions = pkgJson.pi?.extensions;
|
|
53204
53269
|
if (!Array.isArray(extensions) || extensions.length === 0) return null;
|
|
53205
53270
|
const entry = extensions[0];
|
|
@@ -55022,9 +55087,9 @@ async function readAttachmentContents(rootDir, taskId, attachments) {
|
|
|
55022
55087
|
return { attachmentContents, imageContents };
|
|
55023
55088
|
}
|
|
55024
55089
|
const { readFile: readFile24 } = await import("node:fs/promises");
|
|
55025
|
-
const { join:
|
|
55090
|
+
const { join: join69 } = await import("node:path");
|
|
55026
55091
|
for (const att of attachments) {
|
|
55027
|
-
const filePath =
|
|
55092
|
+
const filePath = join69(
|
|
55028
55093
|
rootDir,
|
|
55029
55094
|
".fusion",
|
|
55030
55095
|
"tasks",
|
|
@@ -56562,9 +56627,9 @@ Remove or replace these ids and call fn_task_create again.`
|
|
|
56562
56627
|
}
|
|
56563
56628
|
try {
|
|
56564
56629
|
const { readFile: readFile24 } = await import("node:fs/promises");
|
|
56565
|
-
const { join:
|
|
56630
|
+
const { join: join69 } = await import("node:path");
|
|
56566
56631
|
const promptContent = await readFile24(
|
|
56567
|
-
|
|
56632
|
+
join69(rootDir, promptPath),
|
|
56568
56633
|
"utf-8"
|
|
56569
56634
|
).catch((err) => {
|
|
56570
56635
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -60774,6 +60839,14 @@ import { exec as exec3 } from "node:child_process";
|
|
|
60774
60839
|
import { promisify as promisify4 } from "node:util";
|
|
60775
60840
|
import { existsSync as existsSync24, lstatSync, readdirSync as readdirSync4, rmSync as rmSync2 } from "node:fs";
|
|
60776
60841
|
import { join as join30, relative as relative5, resolve as resolve13, isAbsolute as isAbsolute9 } from "node:path";
|
|
60842
|
+
function getExecStdout(result) {
|
|
60843
|
+
if (typeof result === "string") return result;
|
|
60844
|
+
if (result && typeof result === "object" && "stdout" in result) {
|
|
60845
|
+
const stdout = result.stdout;
|
|
60846
|
+
return typeof stdout === "string" ? stdout : String(stdout ?? "");
|
|
60847
|
+
}
|
|
60848
|
+
return "";
|
|
60849
|
+
}
|
|
60777
60850
|
async function isGitRepository(dir2) {
|
|
60778
60851
|
try {
|
|
60779
60852
|
await execAsync3("git rev-parse --git-dir", {
|
|
@@ -60789,10 +60862,11 @@ async function isGitRepository(dir2) {
|
|
|
60789
60862
|
}
|
|
60790
60863
|
async function getRegisteredWorktreePaths(rootDir) {
|
|
60791
60864
|
try {
|
|
60792
|
-
const
|
|
60865
|
+
const result = await execAsync3("git worktree list --porcelain", {
|
|
60793
60866
|
cwd: rootDir,
|
|
60794
60867
|
encoding: "utf-8"
|
|
60795
60868
|
});
|
|
60869
|
+
const stdout = getExecStdout(result);
|
|
60796
60870
|
const paths = /* @__PURE__ */ new Set();
|
|
60797
60871
|
for (const line of stdout.split("\n")) {
|
|
60798
60872
|
if (line.startsWith("worktree ")) {
|
|
@@ -60944,10 +61018,11 @@ async function reapOrphanWorktrees(projectRoot) {
|
|
|
60944
61018
|
async function scanOrphanedBranches(rootDir, store) {
|
|
60945
61019
|
let allBranches;
|
|
60946
61020
|
try {
|
|
60947
|
-
const
|
|
61021
|
+
const result = await execAsync3("git branch --list 'fusion/*'", {
|
|
60948
61022
|
cwd: rootDir,
|
|
60949
61023
|
encoding: "utf-8"
|
|
60950
61024
|
});
|
|
61025
|
+
const stdout = getExecStdout(result);
|
|
60951
61026
|
allBranches = stdout.split("\n").map((line) => line.trim().replace(/^\*?\s*/, "")).filter((line) => line.startsWith("fusion/"));
|
|
60952
61027
|
} catch (err) {
|
|
60953
61028
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -62083,13 +62158,304 @@ var init_task_completion = __esm({
|
|
|
62083
62158
|
}
|
|
62084
62159
|
});
|
|
62085
62160
|
|
|
62161
|
+
// ../engine/src/run-verification-tool.ts
|
|
62162
|
+
import { spawn as spawn3 } from "node:child_process";
|
|
62163
|
+
import { existsSync as existsSync26 } from "node:fs";
|
|
62164
|
+
import { isAbsolute as isAbsolute10, join as join33 } from "node:path";
|
|
62165
|
+
import { Type as Type4 } from "@mariozechner/pi-ai";
|
|
62166
|
+
function appendToBuffer(buf, chunk) {
|
|
62167
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf8");
|
|
62168
|
+
buf.totalBytes += chunkBytes;
|
|
62169
|
+
if (buf.totalBytes <= MAX_OUTPUT_BYTES) {
|
|
62170
|
+
buf.head += chunk;
|
|
62171
|
+
return;
|
|
62172
|
+
}
|
|
62173
|
+
const tailCap = MAX_OUTPUT_BYTES / 2;
|
|
62174
|
+
buf.tail += chunk;
|
|
62175
|
+
if (Buffer.byteLength(buf.tail, "utf8") > tailCap) {
|
|
62176
|
+
const bytes = Buffer.from(buf.tail, "utf8");
|
|
62177
|
+
buf.tail = bytes.subarray(bytes.length - tailCap).toString("utf8");
|
|
62178
|
+
}
|
|
62179
|
+
}
|
|
62180
|
+
function flattenBuffer(buf) {
|
|
62181
|
+
if (buf.tail.length === 0) return buf.head;
|
|
62182
|
+
return buf.head + `
|
|
62183
|
+
|
|
62184
|
+
[... output truncated \u2014 ${buf.totalBytes} bytes total, showing head + tail ...]
|
|
62185
|
+
|
|
62186
|
+
` + buf.tail;
|
|
62187
|
+
}
|
|
62188
|
+
async function runVerificationCommand2(opts) {
|
|
62189
|
+
const { command, cwd, timeoutMs, expectFailure = false, onHeartbeat, onLine } = opts;
|
|
62190
|
+
const startMs = Date.now();
|
|
62191
|
+
const warnings = [];
|
|
62192
|
+
const stdoutBuf = { head: "", tail: "", totalBytes: 0 };
|
|
62193
|
+
const stderrBuf = { head: "", tail: "", totalBytes: 0 };
|
|
62194
|
+
return new Promise((resolve39) => {
|
|
62195
|
+
const child = spawn3(command, {
|
|
62196
|
+
cwd,
|
|
62197
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
62198
|
+
env: { ...process.env },
|
|
62199
|
+
shell: true
|
|
62200
|
+
});
|
|
62201
|
+
let timedOut = false;
|
|
62202
|
+
let killed = false;
|
|
62203
|
+
let settled = false;
|
|
62204
|
+
let lastLineMs = Date.now();
|
|
62205
|
+
const quietTimer = setInterval(() => {
|
|
62206
|
+
const silenceMs = Date.now() - lastLineMs;
|
|
62207
|
+
if (silenceMs >= QUIET_HEARTBEAT_INTERVAL_MS) {
|
|
62208
|
+
executorLog.log(
|
|
62209
|
+
`[fn_run_verification] command quiet for ${Math.round(silenceMs / 1e3)}s, still running... (${command})`
|
|
62210
|
+
);
|
|
62211
|
+
onHeartbeat();
|
|
62212
|
+
}
|
|
62213
|
+
}, QUIET_HEARTBEAT_INTERVAL_MS);
|
|
62214
|
+
const hardTimer = setTimeout(() => {
|
|
62215
|
+
if (settled) return;
|
|
62216
|
+
timedOut = true;
|
|
62217
|
+
executorLog.warn(
|
|
62218
|
+
`[fn_run_verification] hard timeout (${timeoutMs / 1e3}s) \u2014 sending SIGTERM to: ${command}`
|
|
62219
|
+
);
|
|
62220
|
+
child.kill("SIGTERM");
|
|
62221
|
+
setTimeout(() => {
|
|
62222
|
+
if (!settled) {
|
|
62223
|
+
executorLog.warn(
|
|
62224
|
+
`[fn_run_verification] SIGTERM ignored \u2014 sending SIGKILL to: ${command}`
|
|
62225
|
+
);
|
|
62226
|
+
child.kill("SIGKILL");
|
|
62227
|
+
killed = true;
|
|
62228
|
+
}
|
|
62229
|
+
}, SIGKILL_GRACE_MS);
|
|
62230
|
+
}, timeoutMs);
|
|
62231
|
+
let stdoutRemainder = "";
|
|
62232
|
+
child.stdout.on("data", (chunk) => {
|
|
62233
|
+
const text = stdoutRemainder + chunk.toString("utf8");
|
|
62234
|
+
const lines = text.split("\n");
|
|
62235
|
+
stdoutRemainder = lines.pop() ?? "";
|
|
62236
|
+
for (const line of lines) {
|
|
62237
|
+
const lineWithNewline = line + "\n";
|
|
62238
|
+
appendToBuffer(stdoutBuf, lineWithNewline);
|
|
62239
|
+
lastLineMs = Date.now();
|
|
62240
|
+
onHeartbeat();
|
|
62241
|
+
onLine?.(lineWithNewline);
|
|
62242
|
+
}
|
|
62243
|
+
});
|
|
62244
|
+
let stderrRemainder = "";
|
|
62245
|
+
child.stderr.on("data", (chunk) => {
|
|
62246
|
+
const text = stderrRemainder + chunk.toString("utf8");
|
|
62247
|
+
const lines = text.split("\n");
|
|
62248
|
+
stderrRemainder = lines.pop() ?? "";
|
|
62249
|
+
for (const line of lines) {
|
|
62250
|
+
const lineWithNewline = line + "\n";
|
|
62251
|
+
appendToBuffer(stderrBuf, lineWithNewline);
|
|
62252
|
+
lastLineMs = Date.now();
|
|
62253
|
+
onHeartbeat();
|
|
62254
|
+
onLine?.(lineWithNewline);
|
|
62255
|
+
}
|
|
62256
|
+
});
|
|
62257
|
+
child.on("close", (code, signal) => {
|
|
62258
|
+
if (settled) return;
|
|
62259
|
+
settled = true;
|
|
62260
|
+
clearInterval(quietTimer);
|
|
62261
|
+
clearTimeout(hardTimer);
|
|
62262
|
+
if (stdoutRemainder) appendToBuffer(stdoutBuf, stdoutRemainder);
|
|
62263
|
+
if (stderrRemainder) appendToBuffer(stderrBuf, stderrRemainder);
|
|
62264
|
+
const exitCode = code ?? null;
|
|
62265
|
+
const durationMs = Date.now() - startMs;
|
|
62266
|
+
const zeroExit = exitCode === 0;
|
|
62267
|
+
const success = expectFailure ? true : zeroExit;
|
|
62268
|
+
if (!success && !timedOut) {
|
|
62269
|
+
executorLog.warn(
|
|
62270
|
+
`[fn_run_verification] command failed (exit=${exitCode}, signal=${signal ?? "none"}): ${command}`
|
|
62271
|
+
);
|
|
62272
|
+
}
|
|
62273
|
+
resolve39({
|
|
62274
|
+
success,
|
|
62275
|
+
exitCode,
|
|
62276
|
+
durationMs,
|
|
62277
|
+
stdout: flattenBuffer(stdoutBuf),
|
|
62278
|
+
stderr: flattenBuffer(stderrBuf),
|
|
62279
|
+
timedOut,
|
|
62280
|
+
killed,
|
|
62281
|
+
command,
|
|
62282
|
+
cwd,
|
|
62283
|
+
warnings
|
|
62284
|
+
});
|
|
62285
|
+
});
|
|
62286
|
+
child.on("error", (err) => {
|
|
62287
|
+
if (settled) return;
|
|
62288
|
+
settled = true;
|
|
62289
|
+
clearInterval(quietTimer);
|
|
62290
|
+
clearTimeout(hardTimer);
|
|
62291
|
+
const durationMs = Date.now() - startMs;
|
|
62292
|
+
warnings.push(`Spawn error: ${err.message}`);
|
|
62293
|
+
resolve39({
|
|
62294
|
+
success: false,
|
|
62295
|
+
exitCode: null,
|
|
62296
|
+
durationMs,
|
|
62297
|
+
stdout: flattenBuffer(stdoutBuf),
|
|
62298
|
+
stderr: flattenBuffer(stderrBuf) + `
|
|
62299
|
+
Spawn error: ${err.message}`,
|
|
62300
|
+
timedOut: false,
|
|
62301
|
+
killed: false,
|
|
62302
|
+
command,
|
|
62303
|
+
cwd,
|
|
62304
|
+
warnings
|
|
62305
|
+
});
|
|
62306
|
+
});
|
|
62307
|
+
});
|
|
62308
|
+
}
|
|
62309
|
+
function createRunVerificationTool(opts) {
|
|
62310
|
+
const { worktreePath, rootDir, taskId, recordActivity, log: log18 } = opts;
|
|
62311
|
+
return {
|
|
62312
|
+
name: "fn_run_verification",
|
|
62313
|
+
label: "Run Verification",
|
|
62314
|
+
description: "Run a verification command (tests, lint, build, typecheck) with timeout and progress heartbeat protection. Use this instead of bash for any pnpm/npm test/lint/build commands. Prevents the inactivity watchdog from killing your session during long compiles.",
|
|
62315
|
+
parameters: runVerificationParams,
|
|
62316
|
+
execute: async (_toolCallId, params) => {
|
|
62317
|
+
const { command, scope, expectFailure = false } = params;
|
|
62318
|
+
const warnings = [];
|
|
62319
|
+
if (scope === "workspace" && command.trimStart().startsWith("pnpm --filter")) {
|
|
62320
|
+
const msg = 'scope is "workspace" but command starts with "pnpm --filter" \u2014 consider using scope="package" for scoped commands.';
|
|
62321
|
+
warnings.push(msg);
|
|
62322
|
+
log18.warn(`[fn_run_verification] ${taskId}: ${msg}`);
|
|
62323
|
+
}
|
|
62324
|
+
let resolvedCwd;
|
|
62325
|
+
if (params.cwd && isAbsolute10(params.cwd)) {
|
|
62326
|
+
resolvedCwd = params.cwd;
|
|
62327
|
+
} else if (params.cwd) {
|
|
62328
|
+
resolvedCwd = join33(worktreePath, params.cwd);
|
|
62329
|
+
} else {
|
|
62330
|
+
resolvedCwd = worktreePath;
|
|
62331
|
+
}
|
|
62332
|
+
const defaultTimeoutSec = scope === "package" ? DEFAULT_TIMEOUT_PACKAGE_SEC : DEFAULT_TIMEOUT_WORKSPACE_SEC;
|
|
62333
|
+
const rawTimeoutSec = params.timeoutSec ?? defaultTimeoutSec;
|
|
62334
|
+
const timeoutSec = Math.min(rawTimeoutSec, MAX_TIMEOUT_SEC);
|
|
62335
|
+
const timeoutMs = timeoutSec * 1e3;
|
|
62336
|
+
if (rawTimeoutSec > MAX_TIMEOUT_SEC) {
|
|
62337
|
+
const msg = `timeoutSec ${rawTimeoutSec} exceeds hard cap of ${MAX_TIMEOUT_SEC}s \u2014 clamped.`;
|
|
62338
|
+
warnings.push(msg);
|
|
62339
|
+
log18.warn(`[fn_run_verification] ${taskId}: ${msg}`);
|
|
62340
|
+
}
|
|
62341
|
+
let effectiveCommand = command;
|
|
62342
|
+
if (command.trimStart().startsWith("pnpm --filter")) {
|
|
62343
|
+
const modulesYaml = join33(rootDir, "node_modules", ".modules.yaml");
|
|
62344
|
+
if (!existsSync26(modulesYaml)) {
|
|
62345
|
+
const installCmd = "pnpm install --prefer-offline";
|
|
62346
|
+
const msg = `node_modules/.modules.yaml not found in workspace root \u2014 auto-prepending \`${installCmd}\` before running the command.`;
|
|
62347
|
+
warnings.push(msg);
|
|
62348
|
+
log18.warn(`[fn_run_verification] ${taskId}: ${msg}`);
|
|
62349
|
+
effectiveCommand = `${installCmd} && ${command}`;
|
|
62350
|
+
}
|
|
62351
|
+
}
|
|
62352
|
+
log18.info(
|
|
62353
|
+
`[fn_run_verification] ${taskId}: scope=${scope} timeout=${timeoutSec}s cwd=${resolvedCwd} cmd=${effectiveCommand}`
|
|
62354
|
+
);
|
|
62355
|
+
const result = await runVerificationCommand2({
|
|
62356
|
+
command: effectiveCommand,
|
|
62357
|
+
cwd: resolvedCwd,
|
|
62358
|
+
timeoutMs,
|
|
62359
|
+
expectFailure,
|
|
62360
|
+
onHeartbeat: recordActivity
|
|
62361
|
+
});
|
|
62362
|
+
const allWarnings = [...warnings, ...result.warnings];
|
|
62363
|
+
const lines = [];
|
|
62364
|
+
if (allWarnings.length > 0) {
|
|
62365
|
+
lines.push(`Warnings:
|
|
62366
|
+
${allWarnings.map((w) => ` - ${w}`).join("\n")}
|
|
62367
|
+
`);
|
|
62368
|
+
}
|
|
62369
|
+
if (result.timedOut) {
|
|
62370
|
+
lines.push(
|
|
62371
|
+
`Command timed out after ${timeoutSec}s and was ${result.killed ? "killed (SIGKILL)" : "terminated (SIGTERM)"}.
|
|
62372
|
+
`
|
|
62373
|
+
);
|
|
62374
|
+
}
|
|
62375
|
+
lines.push(`Exit code: ${result.exitCode ?? "null (signal)"}`);
|
|
62376
|
+
lines.push(`Duration: ${(result.durationMs / 1e3).toFixed(1)}s`);
|
|
62377
|
+
lines.push(`Success: ${result.success}`);
|
|
62378
|
+
if (result.stdout.length > 0) {
|
|
62379
|
+
lines.push(`
|
|
62380
|
+
--- stdout ---
|
|
62381
|
+
${result.stdout}`);
|
|
62382
|
+
}
|
|
62383
|
+
if (result.stderr.length > 0) {
|
|
62384
|
+
lines.push(`
|
|
62385
|
+
--- stderr ---
|
|
62386
|
+
${result.stderr}`);
|
|
62387
|
+
}
|
|
62388
|
+
if (result.timedOut) {
|
|
62389
|
+
lines.push(
|
|
62390
|
+
"\nDo NOT blindly retry \u2014 investigate whether subprocesses are hung, test loops are infinite, or dependencies are missing."
|
|
62391
|
+
);
|
|
62392
|
+
}
|
|
62393
|
+
const text = lines.join("\n");
|
|
62394
|
+
log18.info(
|
|
62395
|
+
`[fn_run_verification] ${taskId}: done exit=${result.exitCode} duration=${result.durationMs}ms success=${result.success}`
|
|
62396
|
+
);
|
|
62397
|
+
return {
|
|
62398
|
+
content: [{ type: "text", text }],
|
|
62399
|
+
details: {
|
|
62400
|
+
success: result.success,
|
|
62401
|
+
exitCode: result.exitCode,
|
|
62402
|
+
durationMs: result.durationMs,
|
|
62403
|
+
timedOut: result.timedOut,
|
|
62404
|
+
killed: result.killed,
|
|
62405
|
+
command: result.command,
|
|
62406
|
+
cwd: result.cwd
|
|
62407
|
+
}
|
|
62408
|
+
};
|
|
62409
|
+
}
|
|
62410
|
+
};
|
|
62411
|
+
}
|
|
62412
|
+
var MAX_OUTPUT_BYTES, QUIET_HEARTBEAT_INTERVAL_MS, SIGKILL_GRACE_MS, DEFAULT_TIMEOUT_PACKAGE_SEC, DEFAULT_TIMEOUT_WORKSPACE_SEC, MAX_TIMEOUT_SEC, runVerificationParams;
|
|
62413
|
+
var init_run_verification_tool = __esm({
|
|
62414
|
+
"../engine/src/run-verification-tool.ts"() {
|
|
62415
|
+
"use strict";
|
|
62416
|
+
init_logger2();
|
|
62417
|
+
MAX_OUTPUT_BYTES = 200 * 1024;
|
|
62418
|
+
QUIET_HEARTBEAT_INTERVAL_MS = 6e4;
|
|
62419
|
+
SIGKILL_GRACE_MS = 1e4;
|
|
62420
|
+
DEFAULT_TIMEOUT_PACKAGE_SEC = 300;
|
|
62421
|
+
DEFAULT_TIMEOUT_WORKSPACE_SEC = 900;
|
|
62422
|
+
MAX_TIMEOUT_SEC = 1800;
|
|
62423
|
+
runVerificationParams = Type4.Object({
|
|
62424
|
+
command: Type4.String({
|
|
62425
|
+
description: 'The shell command to run, e.g. "pnpm --filter @fusion/droid-cli test", "pnpm lint", "pnpm build"'
|
|
62426
|
+
}),
|
|
62427
|
+
cwd: Type4.Optional(
|
|
62428
|
+
Type4.String({
|
|
62429
|
+
description: "Working directory for the command. Defaults to the task worktree root if omitted or relative."
|
|
62430
|
+
})
|
|
62431
|
+
),
|
|
62432
|
+
scope: Type4.Union(
|
|
62433
|
+
[Type4.Literal("package"), Type4.Literal("workspace")],
|
|
62434
|
+
{
|
|
62435
|
+
description: '"package" for scoped commands like `pnpm --filter <pkg>`, "workspace" for root-level commands like `pnpm test`.'
|
|
62436
|
+
}
|
|
62437
|
+
),
|
|
62438
|
+
timeoutSec: Type4.Optional(
|
|
62439
|
+
Type4.Number({
|
|
62440
|
+
description: "Override the default timeout in seconds. Default: 300 for package scope, 900 for workspace scope. Hard cap: 1800."
|
|
62441
|
+
})
|
|
62442
|
+
),
|
|
62443
|
+
expectFailure: Type4.Optional(
|
|
62444
|
+
Type4.Boolean({
|
|
62445
|
+
description: "If true, a non-zero exit code is reported but not flagged as an error. Default: false."
|
|
62446
|
+
})
|
|
62447
|
+
)
|
|
62448
|
+
});
|
|
62449
|
+
}
|
|
62450
|
+
});
|
|
62451
|
+
|
|
62086
62452
|
// ../engine/src/executor.ts
|
|
62087
62453
|
import { exec as exec5 } from "node:child_process";
|
|
62088
62454
|
import { promisify as promisify6 } from "node:util";
|
|
62089
|
-
import { isAbsolute as
|
|
62090
|
-
import { existsSync as
|
|
62455
|
+
import { isAbsolute as isAbsolute11, join as join34, relative as relative6, resolve as resolvePath } from "node:path";
|
|
62456
|
+
import { existsSync as existsSync27 } from "node:fs";
|
|
62091
62457
|
import { readFile as readFile14, writeFile as writeFile12 } from "node:fs/promises";
|
|
62092
|
-
import { Type as
|
|
62458
|
+
import { Type as Type5 } from "@mariozechner/pi-ai";
|
|
62093
62459
|
import { ModelRegistry as ModelRegistry2, SessionManager as SessionManager2 } from "@mariozechner/pi-coding-agent";
|
|
62094
62460
|
function truncateWorkflowScriptOutput2(output) {
|
|
62095
62461
|
if (output.length <= WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2) return output;
|
|
@@ -62322,6 +62688,46 @@ ${comment.text}
|
|
|
62322
62688
|
|
|
62323
62689
|
Please adjust your approach based on this feedback.`;
|
|
62324
62690
|
}
|
|
62691
|
+
function detectPseudoPause(lastText) {
|
|
62692
|
+
if (!lastText || lastText.trim().length === 0) {
|
|
62693
|
+
return { kind: "none" };
|
|
62694
|
+
}
|
|
62695
|
+
const regexPatterns = [
|
|
62696
|
+
/\bif you (?:want|wish|need|like|prefer|'?d like)\b/i,
|
|
62697
|
+
/\bshould I (?:continue|proceed|go ahead|move on|start|begin)\b/i,
|
|
62698
|
+
/\blet me know\b/i,
|
|
62699
|
+
/\b(?:want|would you like) me to (?:continue|proceed|finish|complete|do)\b/i,
|
|
62700
|
+
/\bready to (?:proceed|continue|move on|begin)\b/i,
|
|
62701
|
+
/\bshall I\b/i,
|
|
62702
|
+
/\b(?:awaiting|waiting for) (?:your )?(?:approval|confirmation|go-ahead|response)\b/i
|
|
62703
|
+
];
|
|
62704
|
+
for (const pattern of regexPatterns) {
|
|
62705
|
+
const match = pattern.exec(lastText);
|
|
62706
|
+
if (match) {
|
|
62707
|
+
const start = Math.max(0, match.index - 40);
|
|
62708
|
+
const end = Math.min(lastText.length, match.index + match[0].length + 80);
|
|
62709
|
+
const snippet = lastText.slice(start, end).replace(/\n+/g, " ").trim();
|
|
62710
|
+
return { kind: "regex", matched: snippet };
|
|
62711
|
+
}
|
|
62712
|
+
}
|
|
62713
|
+
const trimmed = lastText.trimEnd();
|
|
62714
|
+
if (trimmed.length > 200) {
|
|
62715
|
+
if (trimmed.endsWith("?")) {
|
|
62716
|
+
const lastLine = trimmed.split("\n").at(-1) ?? trimmed;
|
|
62717
|
+
return { kind: "structural", matched: lastLine.trim() };
|
|
62718
|
+
}
|
|
62719
|
+
const nextStepsPattern = /(?:^|\n)#+\s*(?:notes?|next steps?|summary|what'?s? next)\s*:?\s*$/i;
|
|
62720
|
+
if (nextStepsPattern.test(trimmed)) {
|
|
62721
|
+
const lastLine = trimmed.split("\n").at(-1) ?? trimmed;
|
|
62722
|
+
return { kind: "structural", matched: lastLine.trim() };
|
|
62723
|
+
}
|
|
62724
|
+
if (/next steps?\s*:?\s*$/i.test(trimmed)) {
|
|
62725
|
+
const lastLine = trimmed.split("\n").at(-1) ?? trimmed;
|
|
62726
|
+
return { kind: "structural", matched: lastLine.trim() };
|
|
62727
|
+
}
|
|
62728
|
+
}
|
|
62729
|
+
return { kind: "none" };
|
|
62730
|
+
}
|
|
62325
62731
|
function detectReviewHandoffIntent(commentText) {
|
|
62326
62732
|
const text = commentText.toLowerCase();
|
|
62327
62733
|
const handoffPhrases = [
|
|
@@ -62364,6 +62770,7 @@ var init_executor = __esm({
|
|
|
62364
62770
|
init_agent_tools();
|
|
62365
62771
|
init_task_completion();
|
|
62366
62772
|
init_auth_storage();
|
|
62773
|
+
init_run_verification_tool();
|
|
62367
62774
|
init_agent_logger();
|
|
62368
62775
|
init_agent_tools();
|
|
62369
62776
|
execAsync5 = promisify6(exec5);
|
|
@@ -62376,38 +62783,38 @@ var init_executor = __esm({
|
|
|
62376
62783
|
WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
|
|
62377
62784
|
NonRetryableWorktreeError = class extends Error {
|
|
62378
62785
|
};
|
|
62379
|
-
taskUpdateParams =
|
|
62380
|
-
step:
|
|
62381
|
-
status:
|
|
62382
|
-
STEP_STATUSES.map((s) =>
|
|
62786
|
+
taskUpdateParams = Type5.Object({
|
|
62787
|
+
step: Type5.Number({ description: "Step number (0-indexed)" }),
|
|
62788
|
+
status: Type5.Union(
|
|
62789
|
+
STEP_STATUSES.map((s) => Type5.Literal(s)),
|
|
62383
62790
|
{ description: "New status: pending, in-progress, done, or skipped" }
|
|
62384
62791
|
)
|
|
62385
62792
|
});
|
|
62386
|
-
taskAddDepParams =
|
|
62387
|
-
task_id:
|
|
62388
|
-
confirm:
|
|
62793
|
+
taskAddDepParams = Type5.Object({
|
|
62794
|
+
task_id: Type5.String({ description: 'The ID of the task to depend on (e.g. "KB-001")' }),
|
|
62795
|
+
confirm: Type5.Optional(Type5.Boolean({ description: "Set to true to confirm adding the dependency. Required because adding a dep to an in-progress task will stop execution and discard current work." }))
|
|
62389
62796
|
});
|
|
62390
|
-
spawnAgentParams =
|
|
62391
|
-
name:
|
|
62392
|
-
role:
|
|
62393
|
-
|
|
62394
|
-
|
|
62395
|
-
|
|
62396
|
-
|
|
62397
|
-
|
|
62398
|
-
|
|
62797
|
+
spawnAgentParams = Type5.Object({
|
|
62798
|
+
name: Type5.String({ description: "Name for the child agent" }),
|
|
62799
|
+
role: Type5.Union([
|
|
62800
|
+
Type5.Literal("triage"),
|
|
62801
|
+
Type5.Literal("executor"),
|
|
62802
|
+
Type5.Literal("reviewer"),
|
|
62803
|
+
Type5.Literal("merger"),
|
|
62804
|
+
Type5.Literal("engineer"),
|
|
62805
|
+
Type5.Literal("custom")
|
|
62399
62806
|
], { description: "Role for the child agent" }),
|
|
62400
|
-
task:
|
|
62807
|
+
task: Type5.String({ description: "Task description for the child agent to execute" })
|
|
62401
62808
|
});
|
|
62402
|
-
reviewStepParams =
|
|
62403
|
-
step:
|
|
62404
|
-
type:
|
|
62405
|
-
[
|
|
62809
|
+
reviewStepParams = Type5.Object({
|
|
62810
|
+
step: Type5.Number({ description: "Step number to review" }),
|
|
62811
|
+
type: Type5.Union(
|
|
62812
|
+
[Type5.Literal("plan"), Type5.Literal("code")],
|
|
62406
62813
|
{ description: 'Review type: "plan" or "code"' }
|
|
62407
62814
|
),
|
|
62408
|
-
step_name:
|
|
62409
|
-
baseline:
|
|
62410
|
-
|
|
62815
|
+
step_name: Type5.String({ description: "Name of the step being reviewed" }),
|
|
62816
|
+
baseline: Type5.Optional(
|
|
62817
|
+
Type5.String({
|
|
62411
62818
|
description: "Git commit SHA for code review diff baseline. Capture HEAD before starting a step and pass it here."
|
|
62412
62819
|
})
|
|
62413
62820
|
)
|
|
@@ -62420,6 +62827,26 @@ You are working in a git worktree isolated from the main branch. Your job is to
|
|
|
62420
62827
|
You are the primary implementation agent in Fusion.
|
|
62421
62828
|
You execute task specs in isolated worktrees, produce production-quality changes, and hand off work that can pass independent review and merge.
|
|
62422
62829
|
|
|
62830
|
+
## Turn-ending rules \u2014 read carefully
|
|
62831
|
+
|
|
62832
|
+
You MUST end every turn by either:
|
|
62833
|
+
- (a) calling another tool to make progress, OR
|
|
62834
|
+
- (b) calling \`fn_task_done\` if the entire task is complete, OR
|
|
62835
|
+
- (c) calling \`fn_task_done\` with a summary explaining what is blocked, if you cannot make progress for any reason
|
|
62836
|
+
|
|
62837
|
+
You MUST NOT end a turn by writing prose that asks the user a question, summarizes progress, or requests permission to continue. The following are FORBIDDEN turn-endings:
|
|
62838
|
+
- "If you want, I can continue with..."
|
|
62839
|
+
- "Should I proceed with...?"
|
|
62840
|
+
- "Let me know if you'd like me to..."
|
|
62841
|
+
- "Ready to move on to step N. Want me to continue?"
|
|
62842
|
+
- Any markdown progress summary at the end of a turn instead of a tool call
|
|
62843
|
+
|
|
62844
|
+
If you have just finished a step's work, immediately call \`fn_task_update\` to mark the step done and continue with the next pending step in the SAME turn. Do not pause to summarize.
|
|
62845
|
+
|
|
62846
|
+
The user is not watching this conversation in real-time. They will read the final result. Asking permission wastes a full retry cycle and may orphan committed work.
|
|
62847
|
+
|
|
62848
|
+
If you genuinely cannot proceed (blocked on a dependency, missing information, or an unresolvable error), call \`fn_task_done\` with a clear explanation of what is blocked and what is needed to unblock it. Never write the question as plain prose.
|
|
62849
|
+
|
|
62423
62850
|
## How to work
|
|
62424
62851
|
1. Read the PROMPT.md carefully \u2014 it contains your mission, steps, file scope, acceptance criteria, and Do NOT constraints
|
|
62425
62852
|
2. Before touching code, read all files listed in "Context to Read First" and understand the full step outcome
|
|
@@ -62585,6 +63012,16 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
62585
63012
|
- Update tests when intended behavior changed; fix implementation when behavior regressed unintentionally
|
|
62586
63013
|
- **CRITICAL: Resolve ALL lint failures and test failures before completing the task, even if they appear unrelated or pre-existing.** Unrelated failures left unfixed accumulate technical debt and block future integrations. Investigate and fix or suppress them \u2014 do not defer them to a separate task.
|
|
62587
63014
|
|
|
63015
|
+
## Verification commands \u2014 use fn_run_verification
|
|
63016
|
+
|
|
63017
|
+
For ALL test/lint/build/typecheck verification, use the \`fn_run_verification\` tool, NOT raw bash.
|
|
63018
|
+
The tool prevents your session from being killed by the inactivity watchdog during long compiles.
|
|
63019
|
+
|
|
63020
|
+
- Prefer **package-scoped** verification first: e.g. \`pnpm --filter @fusion/<pkg> test\` with \`scope: "package"\`. This is faster and isolated.
|
|
63021
|
+
- Only run **workspace-scoped** verification (\`pnpm test\`, \`pnpm lint\`, \`pnpm build\` from root) at the FINAL integration step, when you are about to call \`fn_task_done\`.
|
|
63022
|
+
- If you need to run \`pnpm install\` (e.g. you added a new package), use \`fn_run_verification\` with \`scope: "workspace"\` and \`timeoutSec: 600\`.
|
|
63023
|
+
- If a verification command times out, do NOT blindly retry \u2014 investigate. Check for hung subprocesses, infinite test loops, or tests waiting on missing dependencies. Use \`node_modules/.modules.yaml\` presence to confirm bootstrap.
|
|
63024
|
+
|
|
62588
63025
|
## Common Pitfalls
|
|
62589
63026
|
- Editing files outside the assigned worktree (except allowed memory/attachment paths)
|
|
62590
63027
|
- Skipping or partially running required quality gates
|
|
@@ -63041,7 +63478,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63041
63478
|
* this as a successful retry, since the original bounce may itself be
|
|
63042
63479
|
* stuck.
|
|
63043
63480
|
*/
|
|
63044
|
-
async performWorkflowRerunBounce(taskId, worktreePath) {
|
|
63481
|
+
async performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState = true) {
|
|
63045
63482
|
if (this.workflowRerunPending.has(taskId)) {
|
|
63046
63483
|
executorLog.warn(`${taskId}: workflow rerun bounce already in flight \u2014 skipping re-entry`);
|
|
63047
63484
|
return "skipped-pending";
|
|
@@ -63054,7 +63491,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63054
63491
|
}
|
|
63055
63492
|
if (latestTask.column === "in-progress") {
|
|
63056
63493
|
const originalExecutionStartedAt = latestTask.executionStartedAt;
|
|
63057
|
-
|
|
63494
|
+
if (preserveResumeState) {
|
|
63495
|
+
await this.store.moveTask(taskId, "todo", { preserveResumeState: true });
|
|
63496
|
+
} else {
|
|
63497
|
+
await this.store.moveTask(taskId, "todo");
|
|
63498
|
+
}
|
|
63058
63499
|
await this.store.updateTask(taskId, {
|
|
63059
63500
|
worktree: worktreePath,
|
|
63060
63501
|
executionStartedAt: originalExecutionStartedAt ?? null
|
|
@@ -63072,11 +63513,11 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63072
63513
|
this.workflowRerunPending.delete(taskId);
|
|
63073
63514
|
}
|
|
63074
63515
|
}
|
|
63075
|
-
scheduleWorkflowRerun(taskId, worktreePath, successMessage) {
|
|
63516
|
+
scheduleWorkflowRerun(taskId, worktreePath, successMessage, preserveResumeState = true) {
|
|
63076
63517
|
this.clearWorkflowRerunWatchdog(taskId);
|
|
63077
63518
|
setTimeout(async () => {
|
|
63078
63519
|
try {
|
|
63079
|
-
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath);
|
|
63520
|
+
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
|
|
63080
63521
|
if (outcome === "bounced") {
|
|
63081
63522
|
executorLog.log(successMessage);
|
|
63082
63523
|
} else {
|
|
@@ -63108,7 +63549,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63108
63549
|
`Watchdog: workflow rerun handoff stalled for ${WORKFLOW_RERUN_WATCHDOG_MS / 1e3}s (still ${currentTask.column}) \u2014 retrying once`
|
|
63109
63550
|
).catch(() => void 0);
|
|
63110
63551
|
try {
|
|
63111
|
-
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath);
|
|
63552
|
+
const outcome = await this.performWorkflowRerunBounce(taskId, worktreePath, preserveResumeState);
|
|
63112
63553
|
if (outcome === "bounced") {
|
|
63113
63554
|
executorLog.warn(`${taskId}: workflow rerun watchdog retry succeeded`);
|
|
63114
63555
|
} else {
|
|
@@ -63246,7 +63687,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63246
63687
|
return false;
|
|
63247
63688
|
}
|
|
63248
63689
|
const settings = await this.store.getSettings();
|
|
63249
|
-
if (task.worktree &&
|
|
63690
|
+
if (task.worktree && existsSync27(task.worktree)) {
|
|
63250
63691
|
const modifiedFiles = await this.captureModifiedFiles(task.worktree, task.baseCommitSha);
|
|
63251
63692
|
if (modifiedFiles.length > 0) {
|
|
63252
63693
|
await this.store.updateTask(task.id, { modifiedFiles });
|
|
@@ -63255,7 +63696,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63255
63696
|
if (task.executionMode !== "fast") {
|
|
63256
63697
|
const workflowResult = await this.runWorkflowSteps(task, task.worktree, settings);
|
|
63257
63698
|
if (!workflowResult.allPassed) {
|
|
63258
|
-
await this.sendTaskBackForFix(task, task.worktree, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed during recovery");
|
|
63699
|
+
await this.sendTaskBackForFix(task, task.worktree, workflowResult.feedback, workflowResult.stepName || "Unknown", "Workflow step failed during recovery", false);
|
|
63259
63700
|
return true;
|
|
63260
63701
|
}
|
|
63261
63702
|
} else {
|
|
@@ -63407,7 +63848,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63407
63848
|
if (task.dependencies.length === 0) return null;
|
|
63408
63849
|
for (const depId of task.dependencies) {
|
|
63409
63850
|
const dep = allTasks.find((t) => t.id === depId);
|
|
63410
|
-
if (dep && dep.worktree && (dep.column === "done" || dep.column === "in-review") &&
|
|
63851
|
+
if (dep && dep.worktree && (dep.column === "done" || dep.column === "in-review") && existsSync27(dep.worktree)) {
|
|
63411
63852
|
return dep.worktree;
|
|
63412
63853
|
}
|
|
63413
63854
|
}
|
|
@@ -63458,7 +63899,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63458
63899
|
const activeMergeStatuses = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
63459
63900
|
const isActiveTask = activeColumns.has(task.column) || activeMergeStatuses.has(task.status ?? "");
|
|
63460
63901
|
if (!isActiveTask) {
|
|
63461
|
-
const tasksDir =
|
|
63902
|
+
const tasksDir = join34(this.store.getFusionDir(), "tasks");
|
|
63462
63903
|
const promptPath = getPromptPath(tasksDir, task.id);
|
|
63463
63904
|
const staleness = await evaluateSpecStaleness({ settings, promptPath });
|
|
63464
63905
|
if (staleness.isStale) {
|
|
@@ -63505,7 +63946,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63505
63946
|
worktreeName = generateWorktreeName(this.rootDir);
|
|
63506
63947
|
break;
|
|
63507
63948
|
}
|
|
63508
|
-
worktreePath =
|
|
63949
|
+
worktreePath = join34(this.rootDir, ".worktrees", worktreeName);
|
|
63509
63950
|
}
|
|
63510
63951
|
let stuckRequeue = null;
|
|
63511
63952
|
let taskDone = false;
|
|
@@ -63528,8 +63969,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63528
63969
|
"Project directory is not a Git repository. Fusion requires a Git repository for worktree creation. Initialize with 'git init' or run from a Git project directory."
|
|
63529
63970
|
);
|
|
63530
63971
|
}
|
|
63531
|
-
const branchName = `fusion/${task.id.toLowerCase()}`;
|
|
63532
|
-
let isResume =
|
|
63972
|
+
const branchName = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
63973
|
+
let isResume = existsSync27(worktreePath);
|
|
63533
63974
|
let acquiredFromPool = false;
|
|
63534
63975
|
const baseBranch = task.baseBranch || null;
|
|
63535
63976
|
if (task.worktree && isResume && !await isUsableTaskWorktree(this.rootDir, worktreePath)) {
|
|
@@ -63542,8 +63983,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63542
63983
|
this.currentRunContext
|
|
63543
63984
|
);
|
|
63544
63985
|
await this.store.updateTask(task.id, { worktree: null, branch: null });
|
|
63545
|
-
worktreePath =
|
|
63546
|
-
isResume =
|
|
63986
|
+
worktreePath = join34(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
63987
|
+
isResume = existsSync27(worktreePath);
|
|
63547
63988
|
}
|
|
63548
63989
|
if (!isResume) {
|
|
63549
63990
|
if (this.options.pool && settings.recycleWorktrees) {
|
|
@@ -63665,6 +64106,9 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63665
64106
|
await this.store.updateStep(task.id, 0, "pending");
|
|
63666
64107
|
}
|
|
63667
64108
|
}
|
|
64109
|
+
if (isResume && task.branch && detail.steps.length > 0) {
|
|
64110
|
+
await this.reconcileStepsFromGitHistory(task.id, detail, worktreePath);
|
|
64111
|
+
}
|
|
63668
64112
|
const skillContext = await buildSessionSkillContext({
|
|
63669
64113
|
agentStore: this.options.agentStore,
|
|
63670
64114
|
task: detail,
|
|
@@ -63735,7 +64179,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63735
64179
|
if (this.pausedAborted.has(task.id)) {
|
|
63736
64180
|
this.pausedAborted.delete(task.id);
|
|
63737
64181
|
await this.store.logEntry(task.id, "Execution paused \u2014 step sessions terminated, moved to todo", void 0, this.currentRunContext);
|
|
63738
|
-
await this.store.moveTask(task.id, "todo");
|
|
64182
|
+
await this.store.moveTask(task.id, "todo", { preserveResumeState: true });
|
|
63739
64183
|
return;
|
|
63740
64184
|
}
|
|
63741
64185
|
if (this.stuckAborted.has(task.id)) {
|
|
@@ -63819,7 +64263,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63819
64263
|
} else if (this.pausedAborted.has(task.id)) {
|
|
63820
64264
|
this.pausedAborted.delete(task.id);
|
|
63821
64265
|
await this.store.logEntry(task.id, "Execution paused during step-session", void 0, this.currentRunContext);
|
|
63822
|
-
await this.store.moveTask(task.id, "todo");
|
|
64266
|
+
await this.store.moveTask(task.id, "todo", { preserveResumeState: true });
|
|
63823
64267
|
} else if (this.stuckAborted.has(task.id)) {
|
|
63824
64268
|
stuckRequeue = this.stuckAborted.get(task.id) ?? true;
|
|
63825
64269
|
this.stuckAborted.delete(task.id);
|
|
@@ -63837,7 +64281,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63837
64281
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay2}: ${errorMessage}`);
|
|
63838
64282
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay2}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
63839
64283
|
}
|
|
63840
|
-
if (worktreePath &&
|
|
64284
|
+
if (worktreePath && existsSync27(worktreePath)) {
|
|
63841
64285
|
try {
|
|
63842
64286
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
63843
64287
|
await audit.git({ type: "worktree:remove", target: worktreePath });
|
|
@@ -63896,7 +64340,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63896
64340
|
try {
|
|
63897
64341
|
const latestTask = await this.store.getTask(task.id);
|
|
63898
64342
|
await this.resetStepsIfWorkLost(latestTask);
|
|
63899
|
-
if (worktreePath &&
|
|
64343
|
+
if (worktreePath && existsSync27(worktreePath)) {
|
|
63900
64344
|
try {
|
|
63901
64345
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
63902
64346
|
} catch (wtErr) {
|
|
@@ -63938,6 +64382,17 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63938
64382
|
this.createTaskDoneTool(task.id, () => {
|
|
63939
64383
|
taskDone = true;
|
|
63940
64384
|
}),
|
|
64385
|
+
createRunVerificationTool({
|
|
64386
|
+
worktreePath,
|
|
64387
|
+
rootDir: this.rootDir,
|
|
64388
|
+
taskId: task.id,
|
|
64389
|
+
recordActivity: () => stuckDetector?.recordActivity(task.id),
|
|
64390
|
+
log: {
|
|
64391
|
+
info: (s) => executorLog.log(s),
|
|
64392
|
+
warn: (s) => executorLog.warn(s),
|
|
64393
|
+
error: (s) => executorLog.warn(s)
|
|
64394
|
+
}
|
|
64395
|
+
}),
|
|
63941
64396
|
// Skip fn_review_step tool in fast mode — fast mode bypasses automated review gates
|
|
63942
64397
|
...executionMode !== "fast" ? [
|
|
63943
64398
|
this.createReviewStepTool(task.id, worktreePath, detail.prompt, codeReviewVerdicts, sessionRef, stepCheckpoints, detail, stuckDetector)
|
|
@@ -63967,11 +64422,13 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63967
64422
|
// Add plugin tools from PluginRunner
|
|
63968
64423
|
...this.options.pluginRunner?.getPluginTools() ?? []
|
|
63969
64424
|
];
|
|
64425
|
+
let lastAssistantText = "";
|
|
63970
64426
|
const agentLogger = new AgentLogger({
|
|
63971
64427
|
store: this.store,
|
|
63972
64428
|
taskId: task.id,
|
|
63973
64429
|
agent: "executor",
|
|
63974
64430
|
onAgentText: (taskId, delta) => {
|
|
64431
|
+
lastAssistantText += delta;
|
|
63975
64432
|
stuckDetector?.recordActivity(taskId);
|
|
63976
64433
|
this.options.onAgentText?.(taskId, delta);
|
|
63977
64434
|
},
|
|
@@ -63989,7 +64446,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
63989
64446
|
const executorFallbackProvider = settings.fallbackProvider;
|
|
63990
64447
|
const executorFallbackModelId = settings.fallbackModelId;
|
|
63991
64448
|
const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
|
|
63992
|
-
const isResuming = !!task.sessionFile &&
|
|
64449
|
+
const isResuming = !!task.sessionFile && existsSync27(task.sessionFile);
|
|
63993
64450
|
const sessionManager = isResuming ? SessionManager2.open(task.sessionFile) : SessionManager2.create(worktreePath);
|
|
63994
64451
|
executorLog.log(`${task.id}: creating agent session (provider=${executorProvider ?? "default"}, model=${executorModelId ?? "default"}, resuming=${isResuming})`);
|
|
63995
64452
|
const executorInstructions = await this.resolveInstructionsForRole("executor");
|
|
@@ -64124,7 +64581,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64124
64581
|
} else {
|
|
64125
64582
|
executorLog.log(`${task.id} paused (graceful session exit) \u2014 moving to todo`);
|
|
64126
64583
|
await this.store.logEntry(task.id, "Execution paused \u2014 session preserved for resume, moved to todo");
|
|
64127
|
-
await this.store.moveTask(task.id, "todo");
|
|
64584
|
+
await this.store.moveTask(task.id, "todo", { preserveResumeState: true });
|
|
64128
64585
|
}
|
|
64129
64586
|
return;
|
|
64130
64587
|
}
|
|
@@ -64188,6 +64645,19 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64188
64645
|
void 0,
|
|
64189
64646
|
this.currentRunContext
|
|
64190
64647
|
);
|
|
64648
|
+
const previousSessionText = lastAssistantText;
|
|
64649
|
+
const pseudoPause = detectPseudoPause(previousSessionText);
|
|
64650
|
+
if (pseudoPause.kind !== "none") {
|
|
64651
|
+
const shortMatch = (pseudoPause.matched ?? "").slice(0, 120);
|
|
64652
|
+
await this.store.logEntry(
|
|
64653
|
+
task.id,
|
|
64654
|
+
`Pseudo-pause detected (kind=${pseudoPause.kind}, matched='${shortMatch}')`,
|
|
64655
|
+
void 0,
|
|
64656
|
+
this.currentRunContext
|
|
64657
|
+
);
|
|
64658
|
+
executorLog.log(`${task.id} pseudo-pause detected (kind=${pseudoPause.kind}): ${shortMatch}`);
|
|
64659
|
+
}
|
|
64660
|
+
lastAssistantText = "";
|
|
64191
64661
|
this.activeSessions.delete(task.id);
|
|
64192
64662
|
this.tokenUsageBaselines.delete(task.id);
|
|
64193
64663
|
session.dispose();
|
|
@@ -64227,15 +64697,38 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64227
64697
|
lastModelId: detail.modelId
|
|
64228
64698
|
});
|
|
64229
64699
|
stuckDetector?.trackTask(task.id, retrySession2);
|
|
64230
|
-
|
|
64231
|
-
|
|
64232
|
-
|
|
64233
|
-
|
|
64234
|
-
|
|
64235
|
-
|
|
64236
|
-
|
|
64237
|
-
|
|
64238
|
-
|
|
64700
|
+
let retryPrompt;
|
|
64701
|
+
if (pseudoPause.kind !== "none") {
|
|
64702
|
+
const shortMatch = (pseudoPause.matched ?? "").slice(0, 120);
|
|
64703
|
+
retryPrompt = [
|
|
64704
|
+
`Your previous turn ended with a pseudo-pause: "${shortMatch}". This is forbidden.`,
|
|
64705
|
+
"",
|
|
64706
|
+
"Turn-ending rules you violated:",
|
|
64707
|
+
"- You MUST NOT end a turn by asking the user a question, summarizing progress, or requesting permission to continue.",
|
|
64708
|
+
"- Phrases like 'If you want, I can continue', 'Should I proceed?', 'Let me know if...' are FORBIDDEN turn-endings.",
|
|
64709
|
+
"- The user is not watching this conversation. Questions written as prose are ignored.",
|
|
64710
|
+
"- If you genuinely cannot proceed, call fn_task_done with a clear explanation \u2014 never write the blocker as plain prose.",
|
|
64711
|
+
"",
|
|
64712
|
+
"What you must do now:",
|
|
64713
|
+
"1. Review the PROMPT.md steps and identify the next pending step.",
|
|
64714
|
+
"2. Do the work for that step immediately \u2014 call fn_task_update, write code, run tests.",
|
|
64715
|
+
"3. Continue until all steps are done, then call fn_task_done.",
|
|
64716
|
+
"Do NOT ask for permission. Do NOT write a summary. Just call a tool and keep working.",
|
|
64717
|
+
"",
|
|
64718
|
+
"Original task:",
|
|
64719
|
+
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
|
|
64720
|
+
].join("\n");
|
|
64721
|
+
} else {
|
|
64722
|
+
retryPrompt = [
|
|
64723
|
+
"Your previous session ended without calling the fn_task_done tool.",
|
|
64724
|
+
"The task may already be complete \u2014 review the current state of the worktree and either:",
|
|
64725
|
+
"1. If the work is done, call fn_task_done with a summary of what was accomplished.",
|
|
64726
|
+
"2. If there is remaining work, finish it and then call fn_task_done.",
|
|
64727
|
+
"",
|
|
64728
|
+
"Original task:",
|
|
64729
|
+
buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
|
|
64730
|
+
].join("\n");
|
|
64731
|
+
}
|
|
64239
64732
|
stuckDetector?.recordActivity(task.id);
|
|
64240
64733
|
await promptWithFallback(retrySession2, retryPrompt);
|
|
64241
64734
|
checkSessionError(retrySession2);
|
|
@@ -64372,7 +64865,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64372
64865
|
this.options.onComplete?.(task);
|
|
64373
64866
|
} else {
|
|
64374
64867
|
executorLog.log(`${task.id} paused \u2014 moving to todo`);
|
|
64375
|
-
if (worktreePath &&
|
|
64868
|
+
if (worktreePath && existsSync27(worktreePath)) {
|
|
64376
64869
|
try {
|
|
64377
64870
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
64378
64871
|
executorLog.log(`Removed old worktree for paused task: ${worktreePath}`);
|
|
@@ -64442,9 +64935,10 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64442
64935
|
await this.store.logEntry(task.id, `Context-overflow fresh-session requeue (${attempt}/${MAX_RECOVERY_RETRIES} in ${delay2}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
64443
64936
|
await this.store.updateTask(task.id, {
|
|
64444
64937
|
recoveryRetryCount: decision.nextState.recoveryRetryCount,
|
|
64445
|
-
nextRecoveryAt: decision.nextState.nextRecoveryAt
|
|
64938
|
+
nextRecoveryAt: decision.nextState.nextRecoveryAt,
|
|
64939
|
+
sessionFile: null
|
|
64446
64940
|
});
|
|
64447
|
-
await this.store.moveTask(task.id, "todo");
|
|
64941
|
+
await this.store.moveTask(task.id, "todo", { preserveResumeState: true });
|
|
64448
64942
|
return;
|
|
64449
64943
|
}
|
|
64450
64944
|
executorLog.error(`\u2717 ${task.id} context-overflow requeue budget exhausted (${MAX_RECOVERY_RETRIES} attempts): ${errorMessage}`);
|
|
@@ -64467,7 +64961,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64467
64961
|
executorLog.warn(`\u26A1 ${task.id} transient error \u2014 retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay2}: ${errorMessage}`);
|
|
64468
64962
|
await this.store.logEntry(task.id, `Transient error (retry ${attempt}/${MAX_RECOVERY_RETRIES} in ${delay2}): ${errorMessage}`, void 0, this.currentRunContext);
|
|
64469
64963
|
}
|
|
64470
|
-
if (worktreePath &&
|
|
64964
|
+
if (worktreePath && existsSync27(worktreePath)) {
|
|
64471
64965
|
try {
|
|
64472
64966
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
64473
64967
|
executorLog.log(`Removed old worktree for transient retry: ${worktreePath}`);
|
|
@@ -64522,7 +65016,7 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64522
65016
|
try {
|
|
64523
65017
|
const latestTask = await this.store.getTask(task.id);
|
|
64524
65018
|
await this.resetStepsIfWorkLost(latestTask);
|
|
64525
|
-
if (worktreePath &&
|
|
65019
|
+
if (worktreePath && existsSync27(worktreePath)) {
|
|
64526
65020
|
try {
|
|
64527
65021
|
await execAsync5(`git worktree remove "${worktreePath}" --force`, { cwd: this.rootDir });
|
|
64528
65022
|
executorLog.log(`Removed old worktree for stuck-killed retry: ${worktreePath}`);
|
|
@@ -64569,19 +65063,39 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64569
65063
|
details: {}
|
|
64570
65064
|
};
|
|
64571
65065
|
}
|
|
64572
|
-
|
|
65066
|
+
const stepIndex = step - 1;
|
|
65067
|
+
if (!Number.isInteger(stepIndex) || stepIndex < 0) {
|
|
65068
|
+
return {
|
|
65069
|
+
content: [{
|
|
65070
|
+
type: "text",
|
|
65071
|
+
text: `Invalid step number: ${step}. Steps are 1-indexed.`
|
|
65072
|
+
}],
|
|
65073
|
+
details: {}
|
|
65074
|
+
};
|
|
65075
|
+
}
|
|
65076
|
+
const task = await store.updateStep(taskId, stepIndex, status);
|
|
65077
|
+
const stepInfo = task.steps[stepIndex];
|
|
65078
|
+
const persistedStatus = stepInfo.status;
|
|
65079
|
+
const progress = task.steps.filter((s) => s.status === "done").length;
|
|
65080
|
+
if (status === "in-progress" && persistedStatus === "in-progress" && sessionRef.current) {
|
|
64573
65081
|
const leafId = sessionRef.current.sessionManager.getLeafId();
|
|
64574
65082
|
if (leafId) {
|
|
64575
65083
|
stepCheckpoints.set(step, leafId);
|
|
64576
65084
|
}
|
|
64577
65085
|
}
|
|
64578
|
-
|
|
64579
|
-
|
|
64580
|
-
|
|
65086
|
+
if (persistedStatus !== status) {
|
|
65087
|
+
return {
|
|
65088
|
+
content: [{
|
|
65089
|
+
type: "text",
|
|
65090
|
+
text: `Step ${step} (${stepInfo.name}) is already ${persistedStatus} \u2014 ${status} request ignored to preserve completed work. Progress: ${progress}/${task.steps.length} done.`
|
|
65091
|
+
}],
|
|
65092
|
+
details: {}
|
|
65093
|
+
};
|
|
65094
|
+
}
|
|
64581
65095
|
return {
|
|
64582
65096
|
content: [{
|
|
64583
65097
|
type: "text",
|
|
64584
|
-
text: `Step ${step} (${stepInfo.name}) \u2192 ${
|
|
65098
|
+
text: `Step ${step} (${stepInfo.name}) \u2192 ${persistedStatus}. Progress: ${progress}/${task.steps.length} done.`
|
|
64585
65099
|
}],
|
|
64586
65100
|
details: {}
|
|
64587
65101
|
};
|
|
@@ -64676,8 +65190,8 @@ Lint, tests, and typecheck are also hard quality gates:
|
|
|
64676
65190
|
name: "fn_task_done",
|
|
64677
65191
|
label: "Mark Task Done",
|
|
64678
65192
|
description: "Signal that all steps are complete, tests pass, and documentation is updated. Call this as the final action after finishing all work. Automatically marks all remaining steps as done. Optionally provide a summary of what was changed/fixed.",
|
|
64679
|
-
parameters:
|
|
64680
|
-
summary:
|
|
65193
|
+
parameters: Type5.Object({
|
|
65194
|
+
summary: Type5.Optional(Type5.String({
|
|
64681
65195
|
description: "Optional summary of what was changed/fixed and what was verified (2-4 sentences)"
|
|
64682
65196
|
}))
|
|
64683
65197
|
}),
|
|
@@ -64983,7 +65497,7 @@ Take a different approach. Do NOT repeat the rejected strategy. Re-read the step
|
|
|
64983
65497
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
64984
65498
|
*/
|
|
64985
65499
|
async injectWorkflowRevisionInstructions(task, feedback) {
|
|
64986
|
-
const promptPath =
|
|
65500
|
+
const promptPath = join34(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
64987
65501
|
let content;
|
|
64988
65502
|
try {
|
|
64989
65503
|
content = await readFile14(promptPath, "utf-8");
|
|
@@ -65069,7 +65583,7 @@ ${feedback}
|
|
|
65069
65583
|
* Injects failure feedback into PROMPT.md, resets steps, clears session,
|
|
65070
65584
|
* and schedules a move to todo → in-progress after the executing guard clears.
|
|
65071
65585
|
*/
|
|
65072
|
-
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason) {
|
|
65586
|
+
async sendTaskBackForFix(task, worktreePath, failureFeedback, stepName, reason, preserveResumeState = true) {
|
|
65073
65587
|
const taskId = task.id;
|
|
65074
65588
|
this.clearCompletedTaskWatchdog(taskId);
|
|
65075
65589
|
await this.store.addTaskComment(
|
|
@@ -65096,7 +65610,8 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
65096
65610
|
this.scheduleWorkflowRerun(
|
|
65097
65611
|
taskId,
|
|
65098
65612
|
worktreePath,
|
|
65099
|
-
`${taskId}: sent back to in-progress for remediation
|
|
65613
|
+
`${taskId}: sent back to in-progress for remediation`,
|
|
65614
|
+
preserveResumeState
|
|
65100
65615
|
);
|
|
65101
65616
|
}
|
|
65102
65617
|
/**
|
|
@@ -65105,7 +65620,7 @@ Please fix the issues so the verification can pass on the next attempt.`,
|
|
|
65105
65620
|
* The section is replaced entirely to avoid accumulation of old feedback.
|
|
65106
65621
|
*/
|
|
65107
65622
|
async injectWorkflowStepFailureInstructions(task, failureFeedback, stepName, retryCount) {
|
|
65108
|
-
const promptPath =
|
|
65623
|
+
const promptPath = join34(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
|
|
65109
65624
|
let content;
|
|
65110
65625
|
try {
|
|
65111
65626
|
content = await readFile14(promptPath, "utf-8");
|
|
@@ -65889,7 +66404,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
65889
66404
|
* rather than fail the task permanently.
|
|
65890
66405
|
*/
|
|
65891
66406
|
async resolveWorktreeStartPoint(startPoint, taskId) {
|
|
65892
|
-
const command =
|
|
66407
|
+
const command = isAbsolute11(startPoint) && existsSync27(startPoint) ? `git -C "${startPoint}" rev-parse --verify HEAD^{commit}` : `git rev-parse --verify "${startPoint}^{commit}"`;
|
|
65893
66408
|
try {
|
|
65894
66409
|
const { stdout } = await execAsync5(command, { cwd: this.rootDir });
|
|
65895
66410
|
return stdout.trim() || startPoint;
|
|
@@ -65909,7 +66424,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
65909
66424
|
*/
|
|
65910
66425
|
async tryCreateWorktree(branch, path5, taskId, startPoint, attemptNumber = 0, recoveryDepth = 0) {
|
|
65911
66426
|
await this.assertWorktreePathNotNested(path5, taskId);
|
|
65912
|
-
if (
|
|
66427
|
+
if (existsSync27(path5)) {
|
|
65913
66428
|
const isRegistered = await this.isRegisteredWorktree(path5);
|
|
65914
66429
|
if (!isRegistered) {
|
|
65915
66430
|
await this.store.logEntry(
|
|
@@ -66059,7 +66574,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
66059
66574
|
taskId
|
|
66060
66575
|
);
|
|
66061
66576
|
if (shouldGenerateNewName) {
|
|
66062
|
-
const newPath =
|
|
66577
|
+
const newPath = join34(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
|
|
66063
66578
|
for (let suffix = 2; suffix <= 6; suffix++) {
|
|
66064
66579
|
const suffixedBranch = `${branch}-${suffix}`;
|
|
66065
66580
|
try {
|
|
@@ -66107,7 +66622,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
66107
66622
|
if (wt === rootResolved) continue;
|
|
66108
66623
|
if (wt === target) continue;
|
|
66109
66624
|
const rel = relative6(wt, target);
|
|
66110
|
-
if (rel && !rel.startsWith("..") && !
|
|
66625
|
+
if (rel && !rel.startsWith("..") && !isAbsolute11(rel)) {
|
|
66111
66626
|
await this.store.logEntry(
|
|
66112
66627
|
taskId,
|
|
66113
66628
|
`Refusing to create nested worktree`,
|
|
@@ -66351,6 +66866,69 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
66351
66866
|
executorLog.log(`${taskId}: recovered ${recovered} approved step(s) on resume`);
|
|
66352
66867
|
}
|
|
66353
66868
|
}
|
|
66869
|
+
/**
|
|
66870
|
+
* On resume (task already has a branch from a prior run), walk git history
|
|
66871
|
+
* and mark steps as done when a commit matching the step-completion convention
|
|
66872
|
+
* is found. This prevents the agent from redoing already-committed work after
|
|
66873
|
+
* an auto-requeue.
|
|
66874
|
+
*
|
|
66875
|
+
* Commit message convention (case-insensitive):
|
|
66876
|
+
* feat|chore|fix(FN-XXXX): complete Step N
|
|
66877
|
+
*
|
|
66878
|
+
* Called after the worktree is acquired and before the agent session starts.
|
|
66879
|
+
*/
|
|
66880
|
+
async reconcileStepsFromGitHistory(taskId, detail, worktreePath) {
|
|
66881
|
+
const baseCommitSha = detail.baseCommitSha;
|
|
66882
|
+
if (!baseCommitSha) return;
|
|
66883
|
+
const pendingOrInProgressSteps = detail.steps.filter(
|
|
66884
|
+
(s, i) => (s.status === "pending" || s.status === "in-progress") && i > 0
|
|
66885
|
+
);
|
|
66886
|
+
if (pendingOrInProgressSteps.length === 0) return;
|
|
66887
|
+
let logOutput;
|
|
66888
|
+
try {
|
|
66889
|
+
const { stdout } = await execAsync5(
|
|
66890
|
+
`git log "${baseCommitSha}..HEAD" --oneline`,
|
|
66891
|
+
{ cwd: worktreePath }
|
|
66892
|
+
);
|
|
66893
|
+
logOutput = stdout;
|
|
66894
|
+
} catch (err) {
|
|
66895
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
66896
|
+
executorLog.warn(`${taskId}: reconcileStepsFromGitHistory \u2014 git log failed: ${msg}`);
|
|
66897
|
+
return;
|
|
66898
|
+
}
|
|
66899
|
+
if (!logOutput.trim()) return;
|
|
66900
|
+
const stepCommitRegex = /^(?:feat|chore|fix)\([Ff][Nn]-\d+\)(?:!)?:\s*complete\s+step\s+(\d+)/i;
|
|
66901
|
+
const reconciledStepIndices = /* @__PURE__ */ new Set();
|
|
66902
|
+
for (const line of logOutput.split("\n")) {
|
|
66903
|
+
const message = line.replace(/^[0-9a-f]+ /, "").trim();
|
|
66904
|
+
const match = message.match(stepCommitRegex);
|
|
66905
|
+
if (!match) continue;
|
|
66906
|
+
const stepIndex = parseInt(match[1], 10);
|
|
66907
|
+
if (Number.isNaN(stepIndex) || stepIndex < 0 || stepIndex >= detail.steps.length) continue;
|
|
66908
|
+
const step = detail.steps[stepIndex];
|
|
66909
|
+
if (step.status === "pending" || step.status === "in-progress") {
|
|
66910
|
+
reconciledStepIndices.add(stepIndex);
|
|
66911
|
+
}
|
|
66912
|
+
}
|
|
66913
|
+
for (const stepIndex of reconciledStepIndices) {
|
|
66914
|
+
await this.store.updateStep(taskId, stepIndex, "done");
|
|
66915
|
+
await this.store.logEntry(
|
|
66916
|
+
taskId,
|
|
66917
|
+
`Reconciled Step ${stepIndex} as done from git history (resume)`,
|
|
66918
|
+
void 0,
|
|
66919
|
+
this.currentRunContext
|
|
66920
|
+
);
|
|
66921
|
+
executorLog.log(`${taskId}: reconciled Step ${stepIndex} as done from git history`);
|
|
66922
|
+
}
|
|
66923
|
+
if (reconciledStepIndices.size > 0) {
|
|
66924
|
+
const updated = await this.store.getTask(taskId);
|
|
66925
|
+
const lowestPending = updated.steps.findIndex((s) => s.status === "pending" || s.status === "in-progress");
|
|
66926
|
+
if (lowestPending >= 0 && lowestPending !== updated.currentStep) {
|
|
66927
|
+
await this.store.updateTask(taskId, { currentStep: lowestPending });
|
|
66928
|
+
executorLog.log(`${taskId}: set currentStep to ${lowestPending} after step reconciliation`);
|
|
66929
|
+
}
|
|
66930
|
+
}
|
|
66931
|
+
}
|
|
66354
66932
|
/**
|
|
66355
66933
|
* Check whether the task's branch has any unique commits compared to main.
|
|
66356
66934
|
* If the branch has no unique commits and the task has steps marked done,
|
|
@@ -66596,7 +67174,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
|
|
|
66596
67174
|
metadata: { type: "spawned", parentTaskId: taskId }
|
|
66597
67175
|
});
|
|
66598
67176
|
const childWorktreeName = generateWorktreeName(this.rootDir);
|
|
66599
|
-
const childWorktreePath =
|
|
67177
|
+
const childWorktreePath = join34(this.rootDir, ".worktrees", childWorktreeName);
|
|
66600
67178
|
const childBranch = `fusion/spawn-${agent.id}`;
|
|
66601
67179
|
await this.createWorktree(childBranch, childWorktreePath, taskId, worktreePath);
|
|
66602
67180
|
await this.options.agentStore.updateAgentState(agent.id, "active");
|
|
@@ -66784,9 +67362,9 @@ var init_node_routing_policy = __esm({
|
|
|
66784
67362
|
});
|
|
66785
67363
|
|
|
66786
67364
|
// ../engine/src/scheduler.ts
|
|
66787
|
-
import { existsSync as
|
|
67365
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
66788
67366
|
import { readFile as readFile15 } from "node:fs/promises";
|
|
66789
|
-
import { basename as basename8, join as
|
|
67367
|
+
import { basename as basename8, join as join35 } from "node:path";
|
|
66790
67368
|
function pathsOverlap2(a, b) {
|
|
66791
67369
|
for (const pa of a) {
|
|
66792
67370
|
const prefixA = pa.endsWith("/*") ? pa.slice(0, -1) : null;
|
|
@@ -66956,12 +67534,12 @@ var init_scheduler = __esm({
|
|
|
66956
67534
|
* @returns Object with `valid: true` if checks pass, or `valid: false` with a `reason` string if they fail
|
|
66957
67535
|
*/
|
|
66958
67536
|
async validateTaskFilesystem(id) {
|
|
66959
|
-
const taskDir =
|
|
66960
|
-
if (!
|
|
67537
|
+
const taskDir = join35(this.store.getTasksDir(), id);
|
|
67538
|
+
if (!existsSync28(taskDir)) {
|
|
66961
67539
|
return { valid: false, reason: "missing directory" };
|
|
66962
67540
|
}
|
|
66963
|
-
const promptPath =
|
|
66964
|
-
if (!
|
|
67541
|
+
const promptPath = join35(taskDir, "PROMPT.md");
|
|
67542
|
+
if (!existsSync28(promptPath)) {
|
|
66965
67543
|
return { valid: false, reason: "missing or empty PROMPT.md" };
|
|
66966
67544
|
}
|
|
66967
67545
|
try {
|
|
@@ -67083,7 +67661,7 @@ var init_scheduler = __esm({
|
|
|
67083
67661
|
break;
|
|
67084
67662
|
}
|
|
67085
67663
|
reservedNames.add(worktreeName);
|
|
67086
|
-
return
|
|
67664
|
+
return join35(this.store.getRootDir(), ".worktrees", worktreeName);
|
|
67087
67665
|
}
|
|
67088
67666
|
/**
|
|
67089
67667
|
* Run one scheduling pass.
|
|
@@ -69038,7 +69616,7 @@ __export(agent_reflection_exports, {
|
|
|
69038
69616
|
AgentReflectionService: () => AgentReflectionService
|
|
69039
69617
|
});
|
|
69040
69618
|
import { readFile as readFile16 } from "node:fs/promises";
|
|
69041
|
-
import { isAbsolute as
|
|
69619
|
+
import { isAbsolute as isAbsolute12, resolve as resolve14 } from "node:path";
|
|
69042
69620
|
var reflectionLog, REFLECTION_SYSTEM_PROMPT, DEFAULT_OUTCOME_LIMIT, AgentReflectionService;
|
|
69043
69621
|
var init_agent_reflection = __esm({
|
|
69044
69622
|
"../engine/src/agent-reflection.ts"() {
|
|
@@ -69349,7 +69927,7 @@ Rules:
|
|
|
69349
69927
|
pieces.push(agent.instructionsText.trim());
|
|
69350
69928
|
}
|
|
69351
69929
|
if (agent.instructionsPath?.trim()) {
|
|
69352
|
-
const resolvedPath =
|
|
69930
|
+
const resolvedPath = isAbsolute12(agent.instructionsPath) ? agent.instructionsPath : resolve14(this.rootDir, agent.instructionsPath);
|
|
69353
69931
|
try {
|
|
69354
69932
|
const content = await readFile16(resolvedPath, "utf-8");
|
|
69355
69933
|
if (content.trim()) {
|
|
@@ -69388,7 +69966,7 @@ Rules:
|
|
|
69388
69966
|
});
|
|
69389
69967
|
|
|
69390
69968
|
// ../engine/src/agent-heartbeat.ts
|
|
69391
|
-
import { Type as
|
|
69969
|
+
import { Type as Type6 } from "@mariozechner/pi-ai";
|
|
69392
69970
|
function isBlockedStateDuplicate(current, previous) {
|
|
69393
69971
|
return current.blockedBy === previous.blockedBy && current.contextHash === previous.contextHash;
|
|
69394
69972
|
}
|
|
@@ -69592,8 +70170,8 @@ When sending messages:
|
|
|
69592
70170
|
Critical: a heartbeat without observable progress (a log, a document write, a
|
|
69593
70171
|
status change, a comment, a delegation, or an explicit "no-op with reason") is
|
|
69594
70172
|
a bug. Do not loop on the same plan across heartbeats without recording why.`;
|
|
69595
|
-
heartbeatDoneParams =
|
|
69596
|
-
summary:
|
|
70173
|
+
heartbeatDoneParams = Type6.Object({
|
|
70174
|
+
summary: Type6.Optional(Type6.String({ description: "Summary of what was accomplished this heartbeat" }))
|
|
69597
70175
|
});
|
|
69598
70176
|
HeartbeatMonitor = class {
|
|
69599
70177
|
store;
|
|
@@ -72258,7 +72836,7 @@ ${source.content ?? ""}`;
|
|
|
72258
72836
|
|
|
72259
72837
|
// ../engine/src/research/providers/local-docs-provider.ts
|
|
72260
72838
|
import { promises as fs } from "node:fs";
|
|
72261
|
-
import { extname as extname2, join as
|
|
72839
|
+
import { extname as extname2, join as join36, relative as relative7, resolve as resolve15 } from "node:path";
|
|
72262
72840
|
function buildExcerpt(content, terms) {
|
|
72263
72841
|
const lower = content.toLowerCase();
|
|
72264
72842
|
const first = terms.find((term) => lower.includes(term));
|
|
@@ -72387,7 +72965,7 @@ var init_local_docs_provider = __esm({
|
|
|
72387
72965
|
const rootEntries = await fs.readdir(this.projectRoot, { withFileTypes: true });
|
|
72388
72966
|
for (const entry of rootEntries) {
|
|
72389
72967
|
if (entry.isFile() && entry.name.toLowerCase().endsWith(".md")) {
|
|
72390
|
-
files.push(
|
|
72968
|
+
files.push(join36(this.projectRoot, entry.name));
|
|
72391
72969
|
}
|
|
72392
72970
|
}
|
|
72393
72971
|
return [...new Set(files)];
|
|
@@ -72397,7 +72975,7 @@ var init_local_docs_provider = __esm({
|
|
|
72397
72975
|
const entries = await fs.readdir(dir2, { withFileTypes: true });
|
|
72398
72976
|
for (const entry of entries) {
|
|
72399
72977
|
this.throwIfAborted(signal);
|
|
72400
|
-
const fullPath =
|
|
72978
|
+
const fullPath = join36(dir2, entry.name);
|
|
72401
72979
|
const relPath = relative7(this.projectRoot, fullPath).replace(/\\/g, "/");
|
|
72402
72980
|
if (matchesGitignore(relPath, ignorePatterns)) continue;
|
|
72403
72981
|
if (entry.isDirectory()) {
|
|
@@ -72422,7 +73000,7 @@ var init_local_docs_provider = __esm({
|
|
|
72422
73000
|
}
|
|
72423
73001
|
async readGitignore() {
|
|
72424
73002
|
try {
|
|
72425
|
-
const content = await fs.readFile(
|
|
73003
|
+
const content = await fs.readFile(join36(this.projectRoot, ".gitignore"), "utf-8");
|
|
72426
73004
|
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#"));
|
|
72427
73005
|
} catch {
|
|
72428
73006
|
return [];
|
|
@@ -73938,7 +74516,22 @@ var init_shell_utils = __esm({
|
|
|
73938
74516
|
|
|
73939
74517
|
// ../engine/src/cron-runner.ts
|
|
73940
74518
|
import { exec as exec6 } from "node:child_process";
|
|
73941
|
-
|
|
74519
|
+
function execCommand(command, options) {
|
|
74520
|
+
return new Promise((resolve39, reject2) => {
|
|
74521
|
+
exec6(command, options, (error, stdout, stderr) => {
|
|
74522
|
+
const stdoutText = typeof stdout === "string" ? stdout : String(stdout ?? "");
|
|
74523
|
+
const stderrText = typeof stderr === "string" ? stderr : String(stderr ?? "");
|
|
74524
|
+
if (error) {
|
|
74525
|
+
const errWithOutput = error;
|
|
74526
|
+
errWithOutput.stdout = stdoutText;
|
|
74527
|
+
errWithOutput.stderr = stderrText;
|
|
74528
|
+
reject2(errWithOutput);
|
|
74529
|
+
return;
|
|
74530
|
+
}
|
|
74531
|
+
resolve39({ stdout: stdoutText, stderr: stderrText });
|
|
74532
|
+
});
|
|
74533
|
+
});
|
|
74534
|
+
}
|
|
73942
74535
|
async function createAiPromptExecutor(cwd) {
|
|
73943
74536
|
const disposeLog = createLogger2("cron-runner");
|
|
73944
74537
|
return async (prompt, modelProvider, modelId) => {
|
|
@@ -73966,17 +74559,19 @@ async function createAiPromptExecutor(cwd) {
|
|
|
73966
74559
|
};
|
|
73967
74560
|
}
|
|
73968
74561
|
function truncateOutput(stdout, stderr) {
|
|
73969
|
-
|
|
73970
|
-
|
|
73971
|
-
|
|
73972
|
-
|
|
74562
|
+
const out = stdout ?? "";
|
|
74563
|
+
const err = stderr ?? "";
|
|
74564
|
+
let combined = out;
|
|
74565
|
+
if (err) {
|
|
74566
|
+
combined += out ? "\n--- stderr ---\n" : "";
|
|
74567
|
+
combined += err;
|
|
73973
74568
|
}
|
|
73974
74569
|
if (combined.length > MAX_OUTPUT_LENGTH) {
|
|
73975
74570
|
combined = combined.slice(0, MAX_OUTPUT_LENGTH) + "\n[output truncated]";
|
|
73976
74571
|
}
|
|
73977
74572
|
return combined;
|
|
73978
74573
|
}
|
|
73979
|
-
var
|
|
74574
|
+
var log14, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
|
|
73980
74575
|
var init_cron_runner = __esm({
|
|
73981
74576
|
"../engine/src/cron-runner.ts"() {
|
|
73982
74577
|
"use strict";
|
|
@@ -73984,7 +74579,6 @@ var init_cron_runner = __esm({
|
|
|
73984
74579
|
init_logger2();
|
|
73985
74580
|
init_shell_utils();
|
|
73986
74581
|
init_pi();
|
|
73987
|
-
execAsync6 = promisify7(exec6);
|
|
73988
74582
|
log14 = createLogger2("cron-runner");
|
|
73989
74583
|
DEFAULT_TIMEOUT_MS6 = 5 * 60 * 1e3;
|
|
73990
74584
|
MAX_BUFFER = 1024 * 1024;
|
|
@@ -74128,7 +74722,7 @@ var init_cron_runner = __esm({
|
|
|
74128
74722
|
log14.log(`Executing ${schedule.name} (${schedule.id}): ${schedule.command}`);
|
|
74129
74723
|
try {
|
|
74130
74724
|
const timeoutMs = schedule.timeoutMs ?? DEFAULT_TIMEOUT_MS6;
|
|
74131
|
-
const { stdout, stderr } = await
|
|
74725
|
+
const { stdout, stderr } = await execCommand(schedule.command, {
|
|
74132
74726
|
timeout: timeoutMs,
|
|
74133
74727
|
maxBuffer: MAX_BUFFER,
|
|
74134
74728
|
shell: defaultShell
|
|
@@ -74245,7 +74839,7 @@ var init_cron_runner = __esm({
|
|
|
74245
74839
|
};
|
|
74246
74840
|
}
|
|
74247
74841
|
try {
|
|
74248
|
-
const { stdout, stderr } = await
|
|
74842
|
+
const { stdout, stderr } = await execCommand(step.command, {
|
|
74249
74843
|
timeout: timeoutMs,
|
|
74250
74844
|
maxBuffer: MAX_BUFFER,
|
|
74251
74845
|
shell: defaultShell
|
|
@@ -74318,8 +74912,9 @@ var init_cron_runner = __esm({
|
|
|
74318
74912
|
setTimeout(() => reject2(new Error(`AI prompt step timed out after ${timeoutMs / 1e3}s`)), timeoutMs);
|
|
74319
74913
|
});
|
|
74320
74914
|
const response = await Promise.race([resultPromise, timeoutPromise]);
|
|
74321
|
-
const
|
|
74322
|
-
|
|
74915
|
+
const responseText = String(response ?? "");
|
|
74916
|
+
const output = responseText.length > MAX_OUTPUT_LENGTH ? responseText.slice(0, MAX_OUTPUT_LENGTH) + "\n[output truncated]" : responseText;
|
|
74917
|
+
log14.log(` \u2713 AI prompt step "${step.name}" completed (${responseText.length} chars)`);
|
|
74323
74918
|
return {
|
|
74324
74919
|
stepId: step.id,
|
|
74325
74920
|
stepName: step.name,
|
|
@@ -74416,7 +75011,7 @@ var init_cron_runner = __esm({
|
|
|
74416
75011
|
|
|
74417
75012
|
// ../engine/src/routine-runner.ts
|
|
74418
75013
|
import { exec as exec7 } from "node:child_process";
|
|
74419
|
-
import { promisify as
|
|
75014
|
+
import { promisify as promisify7 } from "node:util";
|
|
74420
75015
|
function truncateOutput2(stdout, stderr) {
|
|
74421
75016
|
let output = stdout;
|
|
74422
75017
|
if (stderr) {
|
|
@@ -74429,7 +75024,7 @@ function truncateOutput2(stdout, stderr) {
|
|
|
74429
75024
|
}
|
|
74430
75025
|
return output;
|
|
74431
75026
|
}
|
|
74432
|
-
var import_cron_parser4, log15,
|
|
75027
|
+
var import_cron_parser4, log15, execAsync6, DEFAULT_TIMEOUT_MS7, MAX_BUFFER2, MAX_OUTPUT_LENGTH2, MAX_CATCH_UP_INTERVALS, RoutineRunner;
|
|
74433
75028
|
var init_routine_runner = __esm({
|
|
74434
75029
|
"../engine/src/routine-runner.ts"() {
|
|
74435
75030
|
"use strict";
|
|
@@ -74437,7 +75032,7 @@ var init_routine_runner = __esm({
|
|
|
74437
75032
|
init_logger2();
|
|
74438
75033
|
init_shell_utils();
|
|
74439
75034
|
log15 = createLogger2("routine-runner");
|
|
74440
|
-
|
|
75035
|
+
execAsync6 = promisify7(exec7);
|
|
74441
75036
|
DEFAULT_TIMEOUT_MS7 = 5 * 60 * 1e3;
|
|
74442
75037
|
MAX_BUFFER2 = 1024 * 1024;
|
|
74443
75038
|
MAX_OUTPUT_LENGTH2 = 10 * 1024;
|
|
@@ -74592,7 +75187,7 @@ var init_routine_runner = __esm({
|
|
|
74592
75187
|
}
|
|
74593
75188
|
async executeCommand(command, timeoutMs, startedAt) {
|
|
74594
75189
|
try {
|
|
74595
|
-
const { stdout, stderr } = await
|
|
75190
|
+
const { stdout, stderr } = await execAsync6(command, {
|
|
74596
75191
|
timeout: timeoutMs ?? DEFAULT_TIMEOUT_MS7,
|
|
74597
75192
|
maxBuffer: MAX_BUFFER2,
|
|
74598
75193
|
shell: defaultShell
|
|
@@ -75291,9 +75886,9 @@ var init_stuck_task_detector = __esm({
|
|
|
75291
75886
|
|
|
75292
75887
|
// ../engine/src/self-healing.ts
|
|
75293
75888
|
import { exec as exec8 } from "node:child_process";
|
|
75294
|
-
import { promisify as
|
|
75295
|
-
import { existsSync as
|
|
75296
|
-
import { isAbsolute as
|
|
75889
|
+
import { promisify as promisify8 } from "node:util";
|
|
75890
|
+
import { existsSync as existsSync29, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
|
|
75891
|
+
import { isAbsolute as isAbsolute13, join as join37, relative as relative8, resolve as resolve16 } from "node:path";
|
|
75297
75892
|
function shellQuote(value) {
|
|
75298
75893
|
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
75299
75894
|
}
|
|
@@ -75327,7 +75922,7 @@ function isNoTaskDoneFailure(task) {
|
|
|
75327
75922
|
function hasStepProgress(task) {
|
|
75328
75923
|
return task.steps.some((step) => step.status !== "pending");
|
|
75329
75924
|
}
|
|
75330
|
-
var log16,
|
|
75925
|
+
var log16, execAsync7, APPROVED_TRIAGE_RECOVERY_GRACE_MS, ORPHANED_EXECUTION_RECOVERY_GRACE_MS, ACTIVE_MERGE_STATUSES, NON_TERMINAL_STEP_STATUSES2, GHOST_REVIEW_PRESERVED_STATUSES, ORPHANED_WITH_WORKTREE_GRACE_MS, MAX_TASK_DONE_RETRIES, SelfHealingManager;
|
|
75331
75926
|
var init_self_healing = __esm({
|
|
75332
75927
|
"../engine/src/self-healing.ts"() {
|
|
75333
75928
|
"use strict";
|
|
@@ -75335,7 +75930,7 @@ var init_self_healing = __esm({
|
|
|
75335
75930
|
init_logger2();
|
|
75336
75931
|
init_worktree_pool();
|
|
75337
75932
|
log16 = createLogger2("self-healing");
|
|
75338
|
-
|
|
75933
|
+
execAsync7 = promisify8(exec8);
|
|
75339
75934
|
APPROVED_TRIAGE_RECOVERY_GRACE_MS = 6e4;
|
|
75340
75935
|
ORPHANED_EXECUTION_RECOVERY_GRACE_MS = 6e4;
|
|
75341
75936
|
ACTIVE_MERGE_STATUSES = /* @__PURE__ */ new Set(["merging", "merging-pr"]);
|
|
@@ -75540,12 +76135,12 @@ var init_self_healing = __esm({
|
|
|
75540
76135
|
if (completedSteps.length === 0) return;
|
|
75541
76136
|
const branchName = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
75542
76137
|
try {
|
|
75543
|
-
const { stdout: mergeBaseOut } = await
|
|
76138
|
+
const { stdout: mergeBaseOut } = await execAsync7(
|
|
75544
76139
|
`git merge-base "${branchName}" HEAD`,
|
|
75545
76140
|
{ cwd: this.options.rootDir, encoding: "utf-8", timeout: 3e4 }
|
|
75546
76141
|
);
|
|
75547
76142
|
const mergeBase = mergeBaseOut.trim();
|
|
75548
|
-
const { stdout: branchHeadOut } = await
|
|
76143
|
+
const { stdout: branchHeadOut } = await execAsync7(
|
|
75549
76144
|
`git rev-parse "${branchName}"`,
|
|
75550
76145
|
{ cwd: this.options.rootDir, encoding: "utf-8", timeout: 3e4 }
|
|
75551
76146
|
);
|
|
@@ -75593,11 +76188,11 @@ var init_self_healing = __esm({
|
|
|
75593
76188
|
const storedSha = task.mergeDetails?.commitSha;
|
|
75594
76189
|
if (storedSha) {
|
|
75595
76190
|
try {
|
|
75596
|
-
await
|
|
76191
|
+
await execAsync7(
|
|
75597
76192
|
`git merge-base --is-ancestor ${shellQuote(storedSha)} HEAD`,
|
|
75598
76193
|
{ cwd: this.options.rootDir }
|
|
75599
76194
|
);
|
|
75600
|
-
const { stdout: stdout2 } = await
|
|
76195
|
+
const { stdout: stdout2 } = await execAsync7(
|
|
75601
76196
|
`git log -1 --format=%H%x1f%s ${shellQuote(storedSha)}`,
|
|
75602
76197
|
{ cwd: this.options.rootDir, maxBuffer: 1024 * 1024 }
|
|
75603
76198
|
);
|
|
@@ -75605,7 +76200,7 @@ var init_self_healing = __esm({
|
|
|
75605
76200
|
if (sha2) {
|
|
75606
76201
|
const commit2 = { sha: sha2, subject: subject2 };
|
|
75607
76202
|
try {
|
|
75608
|
-
const stats = await
|
|
76203
|
+
const stats = await execAsync7(`git show --shortstat --format= ${shellQuote(sha2)}`, {
|
|
75609
76204
|
cwd: this.options.rootDir,
|
|
75610
76205
|
maxBuffer: 1024 * 1024
|
|
75611
76206
|
});
|
|
@@ -75626,7 +76221,7 @@ var init_self_healing = __esm({
|
|
|
75626
76221
|
`--grep=${grepArg}`,
|
|
75627
76222
|
shellQuote(range2)
|
|
75628
76223
|
].join(" ");
|
|
75629
|
-
return
|
|
76224
|
+
return execAsync7(command, {
|
|
75630
76225
|
cwd: this.options.rootDir,
|
|
75631
76226
|
maxBuffer: 1024 * 1024
|
|
75632
76227
|
});
|
|
@@ -75666,7 +76261,7 @@ var init_self_healing = __esm({
|
|
|
75666
76261
|
if (!sha) return null;
|
|
75667
76262
|
const commit = { sha, subject };
|
|
75668
76263
|
try {
|
|
75669
|
-
const stats = await
|
|
76264
|
+
const stats = await execAsync7(`git show --shortstat --format= ${shellQuote(sha)}`, {
|
|
75670
76265
|
cwd: this.options.rootDir,
|
|
75671
76266
|
maxBuffer: 1024 * 1024
|
|
75672
76267
|
});
|
|
@@ -75680,9 +76275,9 @@ var init_self_healing = __esm({
|
|
|
75680
76275
|
return commit;
|
|
75681
76276
|
}
|
|
75682
76277
|
async cleanupInterruptedMergeArtifacts(task) {
|
|
75683
|
-
if (task.worktree &&
|
|
76278
|
+
if (task.worktree && existsSync29(task.worktree)) {
|
|
75684
76279
|
try {
|
|
75685
|
-
await
|
|
76280
|
+
await execAsync7(`git worktree remove ${shellQuote(task.worktree)} --force`, {
|
|
75686
76281
|
cwd: this.options.rootDir,
|
|
75687
76282
|
timeout: 12e4
|
|
75688
76283
|
});
|
|
@@ -75695,7 +76290,7 @@ var init_self_healing = __esm({
|
|
|
75695
76290
|
}
|
|
75696
76291
|
const branch = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
75697
76292
|
try {
|
|
75698
|
-
await
|
|
76293
|
+
await execAsync7(`git branch -D ${shellQuote(branch)}`, {
|
|
75699
76294
|
cwd: this.options.rootDir,
|
|
75700
76295
|
timeout: 12e4
|
|
75701
76296
|
});
|
|
@@ -76290,7 +76885,7 @@ var init_self_healing = __esm({
|
|
|
76290
76885
|
return false;
|
|
76291
76886
|
}
|
|
76292
76887
|
const staleness = now - new Date(t.updatedAt).getTime();
|
|
76293
|
-
const hasWorktree = t.worktree &&
|
|
76888
|
+
const hasWorktree = t.worktree && existsSync29(t.worktree);
|
|
76294
76889
|
const graceMs = hasWorktree ? ORPHANED_WITH_WORKTREE_GRACE_MS : ORPHANED_EXECUTION_RECOVERY_GRACE_MS;
|
|
76295
76890
|
return staleness >= graceMs;
|
|
76296
76891
|
});
|
|
@@ -76299,7 +76894,7 @@ var init_self_healing = __esm({
|
|
|
76299
76894
|
let recovered = 0;
|
|
76300
76895
|
for (const task of orphaned) {
|
|
76301
76896
|
try {
|
|
76302
|
-
const hadWorktree = task.worktree &&
|
|
76897
|
+
const hadWorktree = task.worktree && existsSync29(task.worktree);
|
|
76303
76898
|
const reason = hadWorktree ? "worktree exists but no active session" : "missing worktree/session";
|
|
76304
76899
|
await this.resetStepsIfWorkLost(task);
|
|
76305
76900
|
await this.store.updateTask(task.id, {
|
|
@@ -76445,9 +77040,9 @@ var init_self_healing = __esm({
|
|
|
76445
77040
|
}
|
|
76446
77041
|
}
|
|
76447
77042
|
async hasRecoverableGitWork(task) {
|
|
76448
|
-
if (task.worktree &&
|
|
77043
|
+
if (task.worktree && existsSync29(task.worktree)) {
|
|
76449
77044
|
try {
|
|
76450
|
-
const { stdout: status } = await
|
|
77045
|
+
const { stdout: status } = await execAsync7("git status --porcelain", {
|
|
76451
77046
|
cwd: task.worktree,
|
|
76452
77047
|
timeout: 3e4
|
|
76453
77048
|
});
|
|
@@ -76462,7 +77057,7 @@ var init_self_healing = __esm({
|
|
|
76462
77057
|
}
|
|
76463
77058
|
const branchName = task.branch || `fusion/${task.id.toLowerCase()}`;
|
|
76464
77059
|
try {
|
|
76465
|
-
await
|
|
77060
|
+
await execAsync7(`git rev-parse --verify "${branchName}"`, {
|
|
76466
77061
|
cwd: this.options.rootDir,
|
|
76467
77062
|
timeout: 3e4
|
|
76468
77063
|
});
|
|
@@ -76470,7 +77065,7 @@ var init_self_healing = __esm({
|
|
|
76470
77065
|
return false;
|
|
76471
77066
|
}
|
|
76472
77067
|
try {
|
|
76473
|
-
const { stdout: uniqueCommits } = await
|
|
77068
|
+
const { stdout: uniqueCommits } = await execAsync7(
|
|
76474
77069
|
`git rev-list --count HEAD.."${branchName}"`,
|
|
76475
77070
|
{ cwd: this.options.rootDir, timeout: 3e4 }
|
|
76476
77071
|
);
|
|
@@ -76570,7 +77165,7 @@ var init_self_healing = __esm({
|
|
|
76570
77165
|
/** Run `git worktree prune` to clean stale metadata. */
|
|
76571
77166
|
async pruneWorktrees() {
|
|
76572
77167
|
try {
|
|
76573
|
-
await
|
|
77168
|
+
await execAsync7("git worktree prune", {
|
|
76574
77169
|
cwd: this.options.rootDir,
|
|
76575
77170
|
timeout: 3e4
|
|
76576
77171
|
});
|
|
@@ -76603,7 +77198,7 @@ var init_self_healing = __esm({
|
|
|
76603
77198
|
let cleaned = 0;
|
|
76604
77199
|
for (const worktreePath of orphaned) {
|
|
76605
77200
|
try {
|
|
76606
|
-
await
|
|
77201
|
+
await execAsync7(`git worktree remove "${worktreePath}" --force`, {
|
|
76607
77202
|
cwd: this.options.rootDir,
|
|
76608
77203
|
timeout: 3e4
|
|
76609
77204
|
});
|
|
@@ -76630,11 +77225,11 @@ var init_self_healing = __esm({
|
|
|
76630
77225
|
* tracks registered idle worktrees, never these orphans.
|
|
76631
77226
|
*/
|
|
76632
77227
|
async reapUnregisteredOrphans() {
|
|
76633
|
-
const worktreesDir =
|
|
76634
|
-
if (!
|
|
77228
|
+
const worktreesDir = join37(this.options.rootDir, ".worktrees");
|
|
77229
|
+
if (!existsSync29(worktreesDir)) return 0;
|
|
76635
77230
|
let dirs;
|
|
76636
77231
|
try {
|
|
76637
|
-
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) =>
|
|
77232
|
+
dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join37(worktreesDir, e.name));
|
|
76638
77233
|
} catch (err) {
|
|
76639
77234
|
log16.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
|
|
76640
77235
|
return 0;
|
|
@@ -76645,7 +77240,7 @@ var init_self_healing = __esm({
|
|
|
76645
77240
|
let cleaned = 0;
|
|
76646
77241
|
for (const path5 of unregistered) {
|
|
76647
77242
|
const rel = relative8(worktreesDir, path5);
|
|
76648
|
-
if (rel === "" || rel.startsWith("..") ||
|
|
77243
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute13(rel)) {
|
|
76649
77244
|
log16.warn(`Refusing to remove path outside .worktrees: ${path5}`);
|
|
76650
77245
|
continue;
|
|
76651
77246
|
}
|
|
@@ -76683,7 +77278,7 @@ var init_self_healing = __esm({
|
|
|
76683
77278
|
const deletedBranches = [];
|
|
76684
77279
|
for (const branch of orphaned) {
|
|
76685
77280
|
try {
|
|
76686
|
-
await
|
|
77281
|
+
await execAsync7(`git branch -d "${branch}"`, {
|
|
76687
77282
|
cwd: this.options.rootDir,
|
|
76688
77283
|
timeout: 3e4
|
|
76689
77284
|
});
|
|
@@ -76696,7 +77291,7 @@ var init_self_healing = __esm({
|
|
|
76696
77291
|
`Safe delete failed for orphaned branch ${branch}: ${errorMessage} \u2014 attempting force delete`
|
|
76697
77292
|
);
|
|
76698
77293
|
try {
|
|
76699
|
-
await
|
|
77294
|
+
await execAsync7(`git branch -D "${branch}"`, {
|
|
76700
77295
|
cwd: this.options.rootDir,
|
|
76701
77296
|
timeout: 3e4
|
|
76702
77297
|
});
|
|
@@ -76739,8 +77334,8 @@ var init_self_healing = __esm({
|
|
|
76739
77334
|
}
|
|
76740
77335
|
/** Remove oldest idle worktrees if total count exceeds 2× maxWorktrees. */
|
|
76741
77336
|
async enforceWorktreeCap() {
|
|
76742
|
-
const worktreesDir =
|
|
76743
|
-
if (!
|
|
77337
|
+
const worktreesDir = join37(this.options.rootDir, ".worktrees");
|
|
77338
|
+
if (!existsSync29(worktreesDir)) return;
|
|
76744
77339
|
try {
|
|
76745
77340
|
const settings = await this.store.getSettings();
|
|
76746
77341
|
const cap = (settings.maxWorktrees ?? 4) * 2;
|
|
@@ -76764,7 +77359,7 @@ var init_self_healing = __esm({
|
|
|
76764
77359
|
for (const { path: worktreePath } of withMtime) {
|
|
76765
77360
|
if (removed >= excess) break;
|
|
76766
77361
|
try {
|
|
76767
|
-
await
|
|
77362
|
+
await execAsync7(`git worktree remove "${worktreePath}" --force`, {
|
|
76768
77363
|
cwd: this.options.rootDir,
|
|
76769
77364
|
timeout: 3e4
|
|
76770
77365
|
});
|
|
@@ -76787,7 +77382,7 @@ var init_self_healing = __esm({
|
|
|
76787
77382
|
});
|
|
76788
77383
|
|
|
76789
77384
|
// ../engine/src/plugin-runner.ts
|
|
76790
|
-
import { Type as
|
|
77385
|
+
import { Type as Type7 } from "@mariozechner/pi-ai";
|
|
76791
77386
|
var DEFAULT_HOOK_TIMEOUT_MS, PluginRunner;
|
|
76792
77387
|
var init_plugin_runner = __esm({
|
|
76793
77388
|
"../engine/src/plugin-runner.ts"() {
|
|
@@ -77102,7 +77697,7 @@ var init_plugin_runner = __esm({
|
|
|
77102
77697
|
};
|
|
77103
77698
|
}
|
|
77104
77699
|
};
|
|
77105
|
-
const anySchema =
|
|
77700
|
+
const anySchema = Type7.Any();
|
|
77106
77701
|
return {
|
|
77107
77702
|
name: `plugin_${pluginTool.name}`,
|
|
77108
77703
|
label: pluginTool.name,
|
|
@@ -78401,7 +78996,7 @@ var init_ipc_host = __esm({
|
|
|
78401
78996
|
import { EventEmitter as EventEmitter21 } from "node:events";
|
|
78402
78997
|
import { fork } from "node:child_process";
|
|
78403
78998
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
78404
|
-
import { dirname as dirname10, join as
|
|
78999
|
+
import { dirname as dirname10, join as join38 } from "node:path";
|
|
78405
79000
|
var HealthMonitor, ChildProcessRuntime;
|
|
78406
79001
|
var init_child_process_runtime = __esm({
|
|
78407
79002
|
"../engine/src/runtimes/child-process-runtime.ts"() {
|
|
@@ -78563,7 +79158,7 @@ var init_child_process_runtime = __esm({
|
|
|
78563
79158
|
const isCompiled = !import.meta.url.endsWith(".ts");
|
|
78564
79159
|
const currentDir = dirname10(fileURLToPath3(import.meta.url));
|
|
78565
79160
|
const workerFile = isCompiled ? "child-process-worker.js" : "child-process-worker.ts";
|
|
78566
|
-
return
|
|
79161
|
+
return join38(currentDir, workerFile);
|
|
78567
79162
|
}
|
|
78568
79163
|
/**
|
|
78569
79164
|
* Set up event forwarding from IPC host to runtime listeners.
|
|
@@ -80034,7 +80629,8 @@ var init_provider_adapters = __esm({
|
|
|
80034
80629
|
|
|
80035
80630
|
// ../engine/src/remote-access/tunnel-process-manager.ts
|
|
80036
80631
|
import { EventEmitter as EventEmitter24 } from "node:events";
|
|
80037
|
-
import { spawn as
|
|
80632
|
+
import { exec as exec9, execFile as execFile3, spawn as spawn4 } from "node:child_process";
|
|
80633
|
+
import { promisify as promisify9 } from "node:util";
|
|
80038
80634
|
function nowIso() {
|
|
80039
80635
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
80040
80636
|
}
|
|
@@ -80074,7 +80670,7 @@ function toStateError(code, err) {
|
|
|
80074
80670
|
at: nowIso()
|
|
80075
80671
|
};
|
|
80076
80672
|
}
|
|
80077
|
-
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, LineBuffer, TunnelProcessManager;
|
|
80673
|
+
var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, execFileAsync, execAsync8, LineBuffer, TunnelProcessManager;
|
|
80078
80674
|
var init_tunnel_process_manager = __esm({
|
|
80079
80675
|
"../engine/src/remote-access/tunnel-process-manager.ts"() {
|
|
80080
80676
|
"use strict";
|
|
@@ -80082,6 +80678,8 @@ var init_tunnel_process_manager = __esm({
|
|
|
80082
80678
|
init_provider_adapters();
|
|
80083
80679
|
DEFAULT_MAX_LOG_ENTRIES = 400;
|
|
80084
80680
|
DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
|
|
80681
|
+
execFileAsync = promisify9(execFile3);
|
|
80682
|
+
execAsync8 = promisify9(exec9);
|
|
80085
80683
|
LineBuffer = class {
|
|
80086
80684
|
pending = "";
|
|
80087
80685
|
push(chunk) {
|
|
@@ -80122,7 +80720,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
80122
80720
|
super();
|
|
80123
80721
|
this.maxLogEntries = options.maxLogEntries ?? DEFAULT_MAX_LOG_ENTRIES;
|
|
80124
80722
|
this.defaultStopTimeoutMs = options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS2;
|
|
80125
|
-
this.spawnImpl = options.spawnImpl ??
|
|
80723
|
+
this.spawnImpl = options.spawnImpl ?? spawn4;
|
|
80126
80724
|
}
|
|
80127
80725
|
getStatus() {
|
|
80128
80726
|
return { ...this.status, lastError: this.status.lastError ? { ...this.status.lastError } : null };
|
|
@@ -80153,6 +80751,51 @@ var init_tunnel_process_manager = __esm({
|
|
|
80153
80751
|
await this.stopInternal();
|
|
80154
80752
|
});
|
|
80155
80753
|
}
|
|
80754
|
+
async detectExternalFunnel() {
|
|
80755
|
+
if (this.processHandle || this.status.state === "starting" || this.status.state === "running") {
|
|
80756
|
+
return null;
|
|
80757
|
+
}
|
|
80758
|
+
try {
|
|
80759
|
+
const { stdout } = await execFileAsync("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
80760
|
+
const data = JSON.parse(String(stdout));
|
|
80761
|
+
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
80762
|
+
if (!dnsName) {
|
|
80763
|
+
return null;
|
|
80764
|
+
}
|
|
80765
|
+
return {
|
|
80766
|
+
provider: "tailscale",
|
|
80767
|
+
url: `https://${dnsName}/`,
|
|
80768
|
+
pid: null
|
|
80769
|
+
};
|
|
80770
|
+
} catch {
|
|
80771
|
+
return null;
|
|
80772
|
+
}
|
|
80773
|
+
}
|
|
80774
|
+
async killExternalFunnel() {
|
|
80775
|
+
const resetCommands = [
|
|
80776
|
+
{ command: "tailscale", args: ["serve", "reset"] },
|
|
80777
|
+
{ command: "tailscale", args: ["funnel", "reset"] },
|
|
80778
|
+
{ command: "tailscale", args: ["funnel", "off"] }
|
|
80779
|
+
];
|
|
80780
|
+
for (const resetCommand of resetCommands) {
|
|
80781
|
+
try {
|
|
80782
|
+
await execFileAsync(resetCommand.command, resetCommand.args, { timeout: 5e3 });
|
|
80783
|
+
return;
|
|
80784
|
+
} catch {
|
|
80785
|
+
}
|
|
80786
|
+
}
|
|
80787
|
+
try {
|
|
80788
|
+
const { stdout } = await execAsync8('pgrep -f "tailscale funnel"', { timeout: 5e3 });
|
|
80789
|
+
const pids = stdout.split(/\s+/).map((value) => Number(value.trim())).filter((value) => Number.isInteger(value) && value > 0);
|
|
80790
|
+
await Promise.all(pids.map(async (pid) => {
|
|
80791
|
+
try {
|
|
80792
|
+
process.kill(pid, "SIGTERM");
|
|
80793
|
+
} catch {
|
|
80794
|
+
}
|
|
80795
|
+
}));
|
|
80796
|
+
} catch {
|
|
80797
|
+
}
|
|
80798
|
+
}
|
|
80156
80799
|
async switchProvider(target, config) {
|
|
80157
80800
|
return this.runExclusive(async () => {
|
|
80158
80801
|
const previousProvider = this.status.provider;
|
|
@@ -80435,7 +81078,7 @@ var init_tunnel_process_manager = __esm({
|
|
|
80435
81078
|
});
|
|
80436
81079
|
|
|
80437
81080
|
// ../engine/src/project-engine.ts
|
|
80438
|
-
import { execFile as
|
|
81081
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
80439
81082
|
import { promisify as promisify10 } from "node:util";
|
|
80440
81083
|
function formatErrorDetails(error) {
|
|
80441
81084
|
if (error instanceof Error) {
|
|
@@ -80447,7 +81090,7 @@ function formatErrorDetails(error) {
|
|
|
80447
81090
|
const detail = String(error);
|
|
80448
81091
|
return { message: detail, detail };
|
|
80449
81092
|
}
|
|
80450
|
-
var
|
|
81093
|
+
var execFileAsync2, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
|
|
80451
81094
|
var init_project_engine = __esm({
|
|
80452
81095
|
"../engine/src/project-engine.ts"() {
|
|
80453
81096
|
"use strict";
|
|
@@ -80464,7 +81107,7 @@ var init_project_engine = __esm({
|
|
|
80464
81107
|
init_research_orchestrator();
|
|
80465
81108
|
init_research_step_runner();
|
|
80466
81109
|
init_tunnel_process_manager();
|
|
80467
|
-
|
|
81110
|
+
execFileAsync2 = promisify10(execFile4);
|
|
80468
81111
|
MERGE_HANDOFF_GRACE_MS = 300;
|
|
80469
81112
|
isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
|
|
80470
81113
|
ProjectEngine = class _ProjectEngine {
|
|
@@ -80868,6 +81511,30 @@ ${detail}`
|
|
|
80868
81511
|
}
|
|
80869
81512
|
return manager.getStatus();
|
|
80870
81513
|
}
|
|
81514
|
+
async detectExternalTunnel() {
|
|
81515
|
+
const manager = this.remoteTunnelManager;
|
|
81516
|
+
if (!manager) {
|
|
81517
|
+
return null;
|
|
81518
|
+
}
|
|
81519
|
+
const settings = await this.runtime.getTaskStore().getSettings();
|
|
81520
|
+
const provider = settings.remoteAccess?.activeProvider ?? null;
|
|
81521
|
+
if (provider !== "tailscale") {
|
|
81522
|
+
return null;
|
|
81523
|
+
}
|
|
81524
|
+
return manager.detectExternalFunnel();
|
|
81525
|
+
}
|
|
81526
|
+
async killExternalTunnel() {
|
|
81527
|
+
const manager = this.remoteTunnelManager;
|
|
81528
|
+
if (!manager) {
|
|
81529
|
+
return;
|
|
81530
|
+
}
|
|
81531
|
+
const settings = await this.runtime.getTaskStore().getSettings();
|
|
81532
|
+
const provider = settings.remoteAccess?.activeProvider ?? null;
|
|
81533
|
+
if (provider !== "tailscale") {
|
|
81534
|
+
return;
|
|
81535
|
+
}
|
|
81536
|
+
await manager.killExternalFunnel();
|
|
81537
|
+
}
|
|
80871
81538
|
/** Get the RoutineRunner (if initialized). */
|
|
80872
81539
|
getRoutineRunner() {
|
|
80873
81540
|
return this.runtime.getRoutineRunner();
|
|
@@ -81077,7 +81744,7 @@ ${detail}`
|
|
|
81077
81744
|
async checkExecutableAvailable(command) {
|
|
81078
81745
|
const checker = process.platform === "win32" ? "where" : "which";
|
|
81079
81746
|
try {
|
|
81080
|
-
await
|
|
81747
|
+
await execFileAsync2(checker, [command]);
|
|
81081
81748
|
return { available: true };
|
|
81082
81749
|
} catch {
|
|
81083
81750
|
return {
|
|
@@ -84723,6 +85390,7 @@ __export(mission_interview_exports, {
|
|
|
84723
85390
|
RateLimitError: () => RateLimitError3,
|
|
84724
85391
|
SessionNotFoundError: () => SessionNotFoundError3,
|
|
84725
85392
|
__getMissionInterviewDiagnostics: () => __getMissionInterviewDiagnostics,
|
|
85393
|
+
__registerMissionInterviewSessionForTest: () => __registerMissionInterviewSessionForTest,
|
|
84726
85394
|
__resetMissionInterviewState: () => __resetMissionInterviewState,
|
|
84727
85395
|
__setMissionInterviewDiagnostics: () => __setMissionInterviewDiagnostics,
|
|
84728
85396
|
cancelMissionInterviewSession: () => cancelMissionInterviewSession,
|
|
@@ -85483,6 +86151,19 @@ function cleanupMissionInterviewSession(sessionId) {
|
|
|
85483
86151
|
cleanupInMemoryMissionSession(sessionId);
|
|
85484
86152
|
unpersistMissionSession(sessionId);
|
|
85485
86153
|
}
|
|
86154
|
+
function __registerMissionInterviewSessionForTest(sessionId, missionTitle = "Test Mission") {
|
|
86155
|
+
sessions3.set(sessionId, {
|
|
86156
|
+
id: sessionId,
|
|
86157
|
+
ip: "127.0.0.1",
|
|
86158
|
+
missionId: "",
|
|
86159
|
+
missionTitle,
|
|
86160
|
+
history: [],
|
|
86161
|
+
thinkingOutput: "",
|
|
86162
|
+
lastGeneratedThinking: "",
|
|
86163
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
86164
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
86165
|
+
});
|
|
86166
|
+
}
|
|
85486
86167
|
function __resetMissionInterviewState() {
|
|
85487
86168
|
for (const [id] of sessions3) {
|
|
85488
86169
|
cleanupInMemoryMissionSession(id);
|
|
@@ -86746,7 +87427,7 @@ var init_api_error = __esm({
|
|
|
86746
87427
|
// ../dashboard/src/plugin-routes.ts
|
|
86747
87428
|
import { Router } from "express";
|
|
86748
87429
|
import { access as access5, stat as stat6, readFile as readFile17 } from "node:fs/promises";
|
|
86749
|
-
import { join as
|
|
87430
|
+
import { join as join39, isAbsolute as isAbsolute14, dirname as dirname11, basename as basename9 } from "node:path";
|
|
86750
87431
|
async function resolvePluginManifest(sourcePath) {
|
|
86751
87432
|
try {
|
|
86752
87433
|
await access5(sourcePath);
|
|
@@ -86762,7 +87443,7 @@ async function resolvePluginManifest(sourcePath) {
|
|
|
86762
87443
|
if (!sourceStat.isDirectory()) {
|
|
86763
87444
|
throw badRequest(`Path is not a directory: ${sourcePath}`);
|
|
86764
87445
|
}
|
|
86765
|
-
const directManifestPath =
|
|
87446
|
+
const directManifestPath = join39(sourcePath, "manifest.json");
|
|
86766
87447
|
try {
|
|
86767
87448
|
await access5(directManifestPath);
|
|
86768
87449
|
const manifest = await readAndValidateManifest(directManifestPath);
|
|
@@ -86773,7 +87454,7 @@ async function resolvePluginManifest(sourcePath) {
|
|
|
86773
87454
|
const dirName = basename9(sourcePath).toLowerCase();
|
|
86774
87455
|
if (DIST_DIR_NAMES.has(dirName)) {
|
|
86775
87456
|
const parentDir = dirname11(sourcePath);
|
|
86776
|
-
const parentManifestPath =
|
|
87457
|
+
const parentManifestPath = join39(parentDir, "manifest.json");
|
|
86777
87458
|
try {
|
|
86778
87459
|
await access5(parentManifestPath);
|
|
86779
87460
|
const manifest = await readAndValidateManifest(parentManifestPath);
|
|
@@ -86827,7 +87508,7 @@ var init_src3 = __esm({
|
|
|
86827
87508
|
});
|
|
86828
87509
|
|
|
86829
87510
|
// ../../plugins/fusion-plugin-hermes-runtime/dist/cli-spawn.js
|
|
86830
|
-
import { spawn as
|
|
87511
|
+
import { spawn as spawn5, spawnSync } from "node:child_process";
|
|
86831
87512
|
import os2 from "node:os";
|
|
86832
87513
|
import path, { sep as PATH_SEP } from "node:path";
|
|
86833
87514
|
function resolveBinaryForSpawn(binary) {
|
|
@@ -86891,7 +87572,7 @@ async function listHermesProfiles(opts) {
|
|
|
86891
87572
|
const timeoutMs = opts?.timeoutMs ?? 5e3;
|
|
86892
87573
|
return new Promise((resolve39, reject2) => {
|
|
86893
87574
|
let settled = false;
|
|
86894
|
-
const child =
|
|
87575
|
+
const child = spawn5(binary, ["profile", "list"], {
|
|
86895
87576
|
stdio: ["ignore", "pipe", "pipe"],
|
|
86896
87577
|
env: { ...process.env }
|
|
86897
87578
|
});
|
|
@@ -87015,7 +87696,7 @@ async function invokeHermesCli(prompt, settings, resumeSessionId, signal) {
|
|
|
87015
87696
|
if (settings.profile) {
|
|
87016
87697
|
spawnEnv.HERMES_HOME = hermesProfileHome(settings.profile);
|
|
87017
87698
|
}
|
|
87018
|
-
const child =
|
|
87699
|
+
const child = spawn5(binary, args, {
|
|
87019
87700
|
stdio: ["ignore", "pipe", "pipe"],
|
|
87020
87701
|
env: spawnEnv
|
|
87021
87702
|
});
|
|
@@ -87170,7 +87851,7 @@ var init_runtime_adapter = __esm({
|
|
|
87170
87851
|
});
|
|
87171
87852
|
|
|
87172
87853
|
// ../../plugins/fusion-plugin-hermes-runtime/dist/probe.js
|
|
87173
|
-
import { spawn as
|
|
87854
|
+
import { spawn as spawn6 } from "node:child_process";
|
|
87174
87855
|
async function probeHermesBinary(opts) {
|
|
87175
87856
|
const startedAt = Date.now();
|
|
87176
87857
|
const binary = typeof opts?.binaryPath === "string" && opts.binaryPath.trim().length > 0 ? opts.binaryPath.trim() : "hermes";
|
|
@@ -87181,7 +87862,7 @@ async function probeHermesBinary(opts) {
|
|
|
87181
87862
|
resolvePromise({ ...result, probeDurationMs: Date.now() - startedAt });
|
|
87182
87863
|
};
|
|
87183
87864
|
let settled = false;
|
|
87184
|
-
const child =
|
|
87865
|
+
const child = spawn6(resolvedPath ?? binary, ["--version"], {
|
|
87185
87866
|
stdio: ["ignore", "pipe", "pipe"]
|
|
87186
87867
|
});
|
|
87187
87868
|
const timer = setTimeout(() => {
|
|
@@ -87242,7 +87923,7 @@ async function probeHermesBinary(opts) {
|
|
|
87242
87923
|
async function tryResolveBinaryPath(binary) {
|
|
87243
87924
|
return new Promise((resolvePromise) => {
|
|
87244
87925
|
const which = process.platform === "win32" ? "where" : "which";
|
|
87245
|
-
const child =
|
|
87926
|
+
const child = spawn6(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
|
|
87246
87927
|
let out = "";
|
|
87247
87928
|
child.stdout?.on("data", (chunk) => {
|
|
87248
87929
|
out += chunk.toString("utf-8");
|
|
@@ -87320,7 +88001,7 @@ var init_dist = __esm({
|
|
|
87320
88001
|
});
|
|
87321
88002
|
|
|
87322
88003
|
// ../../plugins/fusion-plugin-openclaw-runtime/dist/pi-module.js
|
|
87323
|
-
import { spawn as
|
|
88004
|
+
import { spawn as spawn7 } from "node:child_process";
|
|
87324
88005
|
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
87325
88006
|
function asString(v) {
|
|
87326
88007
|
return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
@@ -87397,7 +88078,7 @@ async function promptCli(session, message, config, callbacks, signal) {
|
|
|
87397
88078
|
cb.onToolStart?.("openclaw.agent", { sessionId: session.sessionId });
|
|
87398
88079
|
return new Promise((resolve39, reject2) => {
|
|
87399
88080
|
let settled = false;
|
|
87400
|
-
const child =
|
|
88081
|
+
const child = spawn7(config.binaryPath, args, {
|
|
87401
88082
|
stdio: ["ignore", "pipe", "pipe"]
|
|
87402
88083
|
});
|
|
87403
88084
|
const hardKill = setTimeout(() => {
|
|
@@ -87548,7 +88229,7 @@ var init_runtime_adapter2 = __esm({
|
|
|
87548
88229
|
});
|
|
87549
88230
|
|
|
87550
88231
|
// ../../plugins/fusion-plugin-openclaw-runtime/dist/probe.js
|
|
87551
|
-
import { spawn as
|
|
88232
|
+
import { spawn as spawn8 } from "node:child_process";
|
|
87552
88233
|
async function probeOpenClawBinary(opts = {}) {
|
|
87553
88234
|
const startedAt = Date.now();
|
|
87554
88235
|
const binary = opts.binaryPath ?? "openclaw";
|
|
@@ -87559,7 +88240,7 @@ async function probeOpenClawBinary(opts = {}) {
|
|
|
87559
88240
|
resolvePromise({ ...partial, probeDurationMs: Date.now() - startedAt });
|
|
87560
88241
|
};
|
|
87561
88242
|
let settled = false;
|
|
87562
|
-
const child =
|
|
88243
|
+
const child = spawn8(resolvedPath ?? binary, ["--version"], {
|
|
87563
88244
|
stdio: ["ignore", "pipe", "pipe"]
|
|
87564
88245
|
});
|
|
87565
88246
|
const timer = setTimeout(() => {
|
|
@@ -87620,7 +88301,7 @@ async function probeOpenClawBinary(opts = {}) {
|
|
|
87620
88301
|
async function tryResolveBinaryPath2(binary) {
|
|
87621
88302
|
return new Promise((resolvePromise) => {
|
|
87622
88303
|
const which = process.platform === "win32" ? "where" : "which";
|
|
87623
|
-
const child =
|
|
88304
|
+
const child = spawn8(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
|
|
87624
88305
|
let out = "";
|
|
87625
88306
|
child.stdout?.on("data", (chunk) => {
|
|
87626
88307
|
out += chunk.toString("utf-8");
|
|
@@ -88234,8 +88915,8 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88234
88915
|
});
|
|
88235
88916
|
if (retrySpecification) {
|
|
88236
88917
|
const { rm: rm6 } = await import("node:fs/promises");
|
|
88237
|
-
const { join:
|
|
88238
|
-
const promptPath =
|
|
88918
|
+
const { join: join69 } = await import("node:path");
|
|
88919
|
+
const promptPath = join69(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
|
88239
88920
|
await rm6(promptPath, { force: true });
|
|
88240
88921
|
await scopedStore.logEntry(req.params.id, "Retry requested from dashboard (planning retry budget reset)");
|
|
88241
88922
|
const updated2 = await scopedStore.getTask(req.params.id);
|
|
@@ -88275,6 +88956,49 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88275
88956
|
rethrowAsApiError8(err);
|
|
88276
88957
|
}
|
|
88277
88958
|
});
|
|
88959
|
+
router.post("/tasks/:id/reset", async (req, res) => {
|
|
88960
|
+
try {
|
|
88961
|
+
const { store: scopedStore } = await getProjectContext3(req);
|
|
88962
|
+
const { confirm: confirmed } = req.body ?? {};
|
|
88963
|
+
if (!confirmed) {
|
|
88964
|
+
throw badRequest(
|
|
88965
|
+
'This operation is destructive and will erase all task progress. Pass { "confirm": true } in the request body to proceed.'
|
|
88966
|
+
);
|
|
88967
|
+
}
|
|
88968
|
+
const task = await scopedStore.getTask(req.params.id);
|
|
88969
|
+
for (let i = 0; i < task.steps.length; i++) {
|
|
88970
|
+
if (task.steps[i].status !== "pending") {
|
|
88971
|
+
await scopedStore.updateStep(req.params.id, i, "pending");
|
|
88972
|
+
}
|
|
88973
|
+
}
|
|
88974
|
+
await scopedStore.updateTask(req.params.id, {
|
|
88975
|
+
worktree: null,
|
|
88976
|
+
branch: null,
|
|
88977
|
+
currentStep: 0,
|
|
88978
|
+
status: null,
|
|
88979
|
+
error: null,
|
|
88980
|
+
stuckKillCount: 0,
|
|
88981
|
+
taskDoneRetryCount: null,
|
|
88982
|
+
workflowStepRetries: void 0,
|
|
88983
|
+
recoveryRetryCount: null,
|
|
88984
|
+
nextRecoveryAt: null,
|
|
88985
|
+
postReviewFixCount: 0,
|
|
88986
|
+
verificationFailureCount: 0,
|
|
88987
|
+
mergeConflictBounceCount: 0
|
|
88988
|
+
});
|
|
88989
|
+
await scopedStore.logEntry(
|
|
88990
|
+
req.params.id,
|
|
88991
|
+
"Task reset by user \u2014 all progress cleared, fresh worktree and branch will be allocated"
|
|
88992
|
+
);
|
|
88993
|
+
const updated = await scopedStore.moveTask(req.params.id, "todo");
|
|
88994
|
+
res.json(updated);
|
|
88995
|
+
} catch (err) {
|
|
88996
|
+
if (err instanceof ApiError) {
|
|
88997
|
+
throw err;
|
|
88998
|
+
}
|
|
88999
|
+
rethrowAsApiError8(err);
|
|
89000
|
+
}
|
|
89001
|
+
});
|
|
88278
89002
|
router.post("/tasks/:id/duplicate", async (req, res) => {
|
|
88279
89003
|
try {
|
|
88280
89004
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
@@ -88657,8 +89381,8 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88657
89381
|
await scopedStore.logEntry(task.id, "Plan rejected by user", "Specification will be regenerated");
|
|
88658
89382
|
await scopedStore.updateTask(task.id, { status: void 0 });
|
|
88659
89383
|
const { rm: rm6 } = await import("node:fs/promises");
|
|
88660
|
-
const { join:
|
|
88661
|
-
const promptPath =
|
|
89384
|
+
const { join: join69 } = await import("node:path");
|
|
89385
|
+
const promptPath = join69(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
|
88662
89386
|
await rm6(promptPath, { force: true });
|
|
88663
89387
|
const updated = await scopedStore.getTask(task.id);
|
|
88664
89388
|
res.json(updated);
|
|
@@ -88928,8 +89652,8 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88928
89652
|
if (task.column === "triage") {
|
|
88929
89653
|
await scopedStore.logEntry(task.id, "AI spec revision requested", feedback);
|
|
88930
89654
|
const { rm: rm7 } = await import("node:fs/promises");
|
|
88931
|
-
const { join:
|
|
88932
|
-
const promptPath2 =
|
|
89655
|
+
const { join: join70 } = await import("node:path");
|
|
89656
|
+
const promptPath2 = join70(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
|
88933
89657
|
await rm7(promptPath2, { force: true });
|
|
88934
89658
|
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
|
88935
89659
|
const updated2 = await scopedStore.getTask(task.id);
|
|
@@ -88945,8 +89669,8 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88945
89669
|
await scopedStore.logEntry(task.id, "AI spec revision requested", feedback);
|
|
88946
89670
|
const updated = await scopedStore.moveTask(task.id, "triage");
|
|
88947
89671
|
const { rm: rm6 } = await import("node:fs/promises");
|
|
88948
|
-
const { join:
|
|
88949
|
-
const promptPath =
|
|
89672
|
+
const { join: join69 } = await import("node:path");
|
|
89673
|
+
const promptPath = join69(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
|
88950
89674
|
await rm6(promptPath, { force: true });
|
|
88951
89675
|
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
|
88952
89676
|
res.json(updated);
|
|
@@ -88966,8 +89690,8 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88966
89690
|
if (task.column === "triage") {
|
|
88967
89691
|
await scopedStore.logEntry(task.id, "Specification rebuild requested by user");
|
|
88968
89692
|
const { rm: rm7 } = await import("node:fs/promises");
|
|
88969
|
-
const { join:
|
|
88970
|
-
const promptPath2 =
|
|
89693
|
+
const { join: join70 } = await import("node:path");
|
|
89694
|
+
const promptPath2 = join70(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
|
88971
89695
|
await rm7(promptPath2, { force: true });
|
|
88972
89696
|
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
|
88973
89697
|
const updated2 = await scopedStore.getTask(task.id);
|
|
@@ -88981,8 +89705,8 @@ function registerTaskWorkflowRoutes(ctx, deps) {
|
|
|
88981
89705
|
await scopedStore.logEntry(task.id, "Specification rebuild requested by user");
|
|
88982
89706
|
const updated = await scopedStore.moveTask(task.id, "triage");
|
|
88983
89707
|
const { rm: rm6 } = await import("node:fs/promises");
|
|
88984
|
-
const { join:
|
|
88985
|
-
const promptPath =
|
|
89708
|
+
const { join: join69 } = await import("node:path");
|
|
89709
|
+
const promptPath = join69(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
|
|
88986
89710
|
await rm6(promptPath, { force: true });
|
|
88987
89711
|
await scopedStore.updateTask(task.id, { status: "needs-replan" });
|
|
88988
89712
|
res.json(updated);
|
|
@@ -90247,7 +90971,7 @@ __export(chat_exports, {
|
|
|
90247
90971
|
resolveFileReferences: () => resolveFileReferences
|
|
90248
90972
|
});
|
|
90249
90973
|
import { EventEmitter as EventEmitter29 } from "node:events";
|
|
90250
|
-
import { join as
|
|
90974
|
+
import { join as join40, resolve as resolve18, relative as relative9 } from "node:path";
|
|
90251
90975
|
function __getChatDiagnostics() {
|
|
90252
90976
|
return _diagnostics;
|
|
90253
90977
|
}
|
|
@@ -90280,7 +91004,7 @@ function validateFilePath(basePath, filePath) {
|
|
|
90280
91004
|
throw new Error(`Access denied: Absolute paths not allowed`);
|
|
90281
91005
|
}
|
|
90282
91006
|
const resolvedBase = resolve18(basePath);
|
|
90283
|
-
const resolvedPath = resolve18(
|
|
91007
|
+
const resolvedPath = resolve18(join40(resolvedBase, decodedPath));
|
|
90284
91008
|
const relativePath = relative9(resolvedBase, resolvedPath);
|
|
90285
91009
|
if (relativePath.startsWith("..") || relativePath.startsWith("../") || relativePath === "..") {
|
|
90286
91010
|
throw new Error(`Access denied: Path traversal detected`);
|
|
@@ -90861,7 +91585,7 @@ ${mentionContext}`;
|
|
|
90861
91585
|
import { randomUUID as randomUUID16 } from "node:crypto";
|
|
90862
91586
|
import { createReadStream as createReadStream2 } from "node:fs";
|
|
90863
91587
|
import { mkdir as mkdir13, rm as rm2, writeFile as writeFile13 } from "node:fs/promises";
|
|
90864
|
-
import { basename as basename10, join as
|
|
91588
|
+
import { basename as basename10, join as join41, resolve as resolve19 } from "node:path";
|
|
90865
91589
|
function resolveAttachmentPath(rootDir, sessionId, filename) {
|
|
90866
91590
|
const sessionDir = resolve19(rootDir, ".fusion", "chat-attachments", sessionId);
|
|
90867
91591
|
const safeName = basename10(filename);
|
|
@@ -91114,7 +91838,7 @@ function registerChatRoutes(ctx, deps) {
|
|
|
91114
91838
|
await mkdir13(sessionDir, { recursive: true });
|
|
91115
91839
|
const sanitizedFilename = (file.originalname || "attachment").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
91116
91840
|
const filename = `${Date.now()}-${sanitizedFilename}`;
|
|
91117
|
-
const filePath =
|
|
91841
|
+
const filePath = join41(sessionDir, filename);
|
|
91118
91842
|
await writeFile13(filePath, file.buffer);
|
|
91119
91843
|
const attachment = {
|
|
91120
91844
|
id: `att-${randomUUID16().slice(0, 8)}`,
|
|
@@ -96035,15 +96759,16 @@ var init_remote_auth = __esm({
|
|
|
96035
96759
|
});
|
|
96036
96760
|
|
|
96037
96761
|
// ../dashboard/src/routes/register-settings-memory-routes.ts
|
|
96038
|
-
import { execFile as
|
|
96762
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
96763
|
+
import { homedir as homedir6 } from "node:os";
|
|
96039
96764
|
import { promisify as promisify11 } from "node:util";
|
|
96040
96765
|
function registerSettingsMemoryRoutes(ctx, deps) {
|
|
96041
96766
|
const { router, options, store, runtimeLogger, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError8 } = ctx;
|
|
96042
96767
|
const { githubToken, validateModelPresets: validateModelPresets2, sanitizeOverlapIgnorePaths: sanitizeOverlapIgnorePaths2, discoverDashboardPiExtensions: discoverDashboardPiExtensions2 } = deps;
|
|
96043
|
-
const
|
|
96768
|
+
const execFileAsync7 = promisify11(execFile5);
|
|
96044
96769
|
async function queryTailscaleFunnelUrl(targetPort) {
|
|
96045
96770
|
try {
|
|
96046
|
-
const { stdout } = await
|
|
96771
|
+
const { stdout } = await execFileAsync7("tailscale", ["status", "--json"], { timeout: 3e3 });
|
|
96047
96772
|
const data = JSON.parse(stdout);
|
|
96048
96773
|
const dnsName = data.Self?.DNSName?.replace(/\.$/, "");
|
|
96049
96774
|
if (!dnsName) return null;
|
|
@@ -96056,12 +96781,38 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
96056
96781
|
async function isCloudflaredAvailable() {
|
|
96057
96782
|
const command = process.platform === "win32" ? "where" : "which";
|
|
96058
96783
|
try {
|
|
96059
|
-
await
|
|
96784
|
+
await execFileAsync7(command, ["cloudflared"]);
|
|
96060
96785
|
return true;
|
|
96061
96786
|
} catch {
|
|
96062
96787
|
return false;
|
|
96063
96788
|
}
|
|
96064
96789
|
}
|
|
96790
|
+
function resolveCloudflaredBinaryName() {
|
|
96791
|
+
if (process.platform === "linux") {
|
|
96792
|
+
if (process.arch === "arm") {
|
|
96793
|
+
return "cloudflared-linux-armhf";
|
|
96794
|
+
}
|
|
96795
|
+
if (process.arch === "arm64") {
|
|
96796
|
+
return "cloudflared-linux-arm64";
|
|
96797
|
+
}
|
|
96798
|
+
if (process.arch === "x64") {
|
|
96799
|
+
return "cloudflared-linux-amd64";
|
|
96800
|
+
}
|
|
96801
|
+
console.warn(`[remote-access] Unsupported Linux architecture '${process.arch}' for cloudflared; falling back to amd64`);
|
|
96802
|
+
return "cloudflared-linux-amd64";
|
|
96803
|
+
}
|
|
96804
|
+
if (process.platform === "darwin") {
|
|
96805
|
+
if (process.arch === "arm64") {
|
|
96806
|
+
return "cloudflared-darwin-arm64";
|
|
96807
|
+
}
|
|
96808
|
+
if (process.arch === "x64") {
|
|
96809
|
+
return "cloudflared-darwin-amd64";
|
|
96810
|
+
}
|
|
96811
|
+
console.warn(`[remote-access] Unsupported macOS architecture '${process.arch}' for cloudflared; falling back to amd64`);
|
|
96812
|
+
return "cloudflared-darwin-amd64";
|
|
96813
|
+
}
|
|
96814
|
+
return "cloudflared-linux-amd64";
|
|
96815
|
+
}
|
|
96065
96816
|
function resolveCloudflaredInstallCommand() {
|
|
96066
96817
|
if (process.platform === "darwin") {
|
|
96067
96818
|
return "brew install cloudflared";
|
|
@@ -96069,21 +96820,77 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
96069
96820
|
if (process.platform === "win32") {
|
|
96070
96821
|
return "winget install Cloudflare.cloudflared";
|
|
96071
96822
|
}
|
|
96072
|
-
|
|
96823
|
+
const binaryName = resolveCloudflaredBinaryName();
|
|
96824
|
+
const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/${binaryName}`;
|
|
96825
|
+
return `curl -L --output /tmp/cloudflared ${downloadUrl} && chmod +x /tmp/cloudflared && mv /tmp/cloudflared /usr/local/bin/cloudflared`;
|
|
96826
|
+
}
|
|
96827
|
+
function formatExecError(error) {
|
|
96828
|
+
if (!(error instanceof Error)) {
|
|
96829
|
+
return String(error);
|
|
96830
|
+
}
|
|
96831
|
+
const stdout = error.stdout?.trim();
|
|
96832
|
+
const stderr = error.stderr?.trim();
|
|
96833
|
+
return [error.message, stderr, stdout].filter(Boolean).join(" | ");
|
|
96073
96834
|
}
|
|
96074
96835
|
async function installCloudflared() {
|
|
96075
|
-
|
|
96076
|
-
|
|
96077
|
-
|
|
96836
|
+
if (process.platform === "win32") {
|
|
96837
|
+
const command = resolveCloudflaredInstallCommand();
|
|
96838
|
+
try {
|
|
96839
|
+
await execFileAsync7("winget", ["install", "Cloudflare.cloudflared"], { timeout: 12e4 });
|
|
96840
|
+
return { success: true, command };
|
|
96841
|
+
} catch (error) {
|
|
96842
|
+
return { success: false, command, error: formatExecError(error) };
|
|
96843
|
+
}
|
|
96844
|
+
}
|
|
96845
|
+
const attemptedCommands = [];
|
|
96846
|
+
const downloadBinaryName = process.platform === "darwin" || process.platform === "linux" ? resolveCloudflaredBinaryName() : "cloudflared-linux-amd64";
|
|
96847
|
+
const downloadUrl = `https://github.com/cloudflare/cloudflared/releases/latest/download/${downloadBinaryName}`;
|
|
96848
|
+
const tempPath = "/tmp/cloudflared";
|
|
96849
|
+
const installFromDirectDownload = async () => {
|
|
96850
|
+
attemptedCommands.push(`curl -L --output ${tempPath} ${downloadUrl}`);
|
|
96851
|
+
await execFileAsync7("curl", ["-L", "--output", tempPath, downloadUrl], { timeout: 12e4 });
|
|
96852
|
+
attemptedCommands.push(`chmod +x ${tempPath}`);
|
|
96853
|
+
await execFileAsync7("chmod", ["+x", tempPath], { timeout: 3e4 });
|
|
96854
|
+
const globalInstallPath = "/usr/local/bin/cloudflared";
|
|
96855
|
+
attemptedCommands.push(`mv ${tempPath} ${globalInstallPath}`);
|
|
96856
|
+
try {
|
|
96857
|
+
await execFileAsync7("mv", [tempPath, globalInstallPath], { timeout: 3e4 });
|
|
96858
|
+
} catch (error) {
|
|
96859
|
+
const localBinDir = `${homedir6()}/.local/bin`;
|
|
96860
|
+
const localInstallPath = `${localBinDir}/cloudflared`;
|
|
96861
|
+
attemptedCommands.push(`mkdir -p ${localBinDir}`);
|
|
96862
|
+
attemptedCommands.push(`mv ${tempPath} ${localInstallPath}`);
|
|
96863
|
+
await execFileAsync7("mkdir", ["-p", localBinDir], { timeout: 3e4 });
|
|
96864
|
+
try {
|
|
96865
|
+
await execFileAsync7("mv", [tempPath, localInstallPath], { timeout: 3e4 });
|
|
96866
|
+
} catch (fallbackError) {
|
|
96867
|
+
throw new Error(
|
|
96868
|
+
`Failed to install cloudflared to /usr/local/bin and ~/.local/bin (${formatExecError(error)}; fallback: ${formatExecError(fallbackError)})`
|
|
96869
|
+
);
|
|
96870
|
+
}
|
|
96871
|
+
}
|
|
96872
|
+
};
|
|
96873
|
+
if (process.platform === "darwin") {
|
|
96874
|
+
attemptedCommands.push("which brew");
|
|
96875
|
+
try {
|
|
96876
|
+
await execFileAsync7("which", ["brew"], { timeout: 15e3 });
|
|
96877
|
+
attemptedCommands.push("brew install cloudflared");
|
|
96878
|
+
await execFileAsync7("brew", ["install", "cloudflared"], { timeout: 12e4 });
|
|
96879
|
+
return { success: true, command: attemptedCommands.join(" && ") };
|
|
96880
|
+
} catch {
|
|
96881
|
+
try {
|
|
96882
|
+
await installFromDirectDownload();
|
|
96883
|
+
return { success: true, command: attemptedCommands.join(" && ") };
|
|
96884
|
+
} catch (error) {
|
|
96885
|
+
return { success: false, command: attemptedCommands.join(" && "), error: formatExecError(error) };
|
|
96886
|
+
}
|
|
96887
|
+
}
|
|
96888
|
+
}
|
|
96078
96889
|
try {
|
|
96079
|
-
await
|
|
96080
|
-
return { success: true, command };
|
|
96890
|
+
await installFromDirectDownload();
|
|
96891
|
+
return { success: true, command: attemptedCommands.join(" && ") };
|
|
96081
96892
|
} catch (error) {
|
|
96082
|
-
return {
|
|
96083
|
-
success: false,
|
|
96084
|
-
command,
|
|
96085
|
-
error: error instanceof Error ? error.message : String(error)
|
|
96086
|
-
};
|
|
96893
|
+
return { success: false, command: attemptedCommands.join(" && "), error: formatExecError(error) };
|
|
96087
96894
|
}
|
|
96088
96895
|
}
|
|
96089
96896
|
async function resolveRemoteBaseUrl(remoteAccess, tunnelUrl) {
|
|
@@ -96370,17 +97177,23 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
96370
97177
|
const tunnelStatus = manager?.getStatus();
|
|
96371
97178
|
const restore = engine?.getRemoteTunnelRestoreDiagnostics();
|
|
96372
97179
|
const activeProvider = tunnelStatus?.provider ?? settings.remoteAccess?.activeProvider ?? null;
|
|
97180
|
+
const tunnelState = tunnelStatus?.state ?? "stopped";
|
|
96373
97181
|
let cloudflaredAvailable = null;
|
|
96374
97182
|
if (activeProvider === "cloudflare") {
|
|
96375
97183
|
cloudflaredAvailable = await isCloudflaredAvailable();
|
|
96376
97184
|
}
|
|
97185
|
+
const externalTunnel = tunnelState === "stopped" ? await engine?.detectExternalTunnel() : null;
|
|
96377
97186
|
res.json({
|
|
96378
97187
|
provider: activeProvider,
|
|
96379
|
-
state:
|
|
97188
|
+
state: tunnelState,
|
|
96380
97189
|
url: tunnelStatus?.url ?? null,
|
|
96381
97190
|
lastError: tunnelStatus?.lastError?.message ?? null,
|
|
96382
97191
|
lastErrorCode: tunnelStatus?.lastError?.code ?? null,
|
|
96383
97192
|
cloudflaredAvailable,
|
|
97193
|
+
externalTunnel: externalTunnel ? {
|
|
97194
|
+
provider: externalTunnel.provider,
|
|
97195
|
+
url: externalTunnel.url
|
|
97196
|
+
} : null,
|
|
96384
97197
|
restore: restore ?? {
|
|
96385
97198
|
outcome: "skipped",
|
|
96386
97199
|
reason: "not_attempted",
|
|
@@ -96493,6 +97306,18 @@ function registerSettingsMemoryRoutes(ctx, deps) {
|
|
|
96493
97306
|
rethrowAsApiError8(err, "Failed to stop remote tunnel");
|
|
96494
97307
|
}
|
|
96495
97308
|
});
|
|
97309
|
+
router.post("/remote/tunnel/kill-external", async (req, res) => {
|
|
97310
|
+
try {
|
|
97311
|
+
const { engine } = await getProjectContext3(req);
|
|
97312
|
+
if (engine) {
|
|
97313
|
+
await engine.killExternalTunnel();
|
|
97314
|
+
}
|
|
97315
|
+
res.json({ ok: true });
|
|
97316
|
+
} catch (err) {
|
|
97317
|
+
if (err instanceof ApiError) throw err;
|
|
97318
|
+
rethrowAsApiError8(err, "Failed to kill external remote tunnel");
|
|
97319
|
+
}
|
|
97320
|
+
});
|
|
96496
97321
|
router.post("/remote/token/persistent/regenerate", async (req, res) => {
|
|
96497
97322
|
try {
|
|
96498
97323
|
const { store: scopedStore } = await getProjectContext3(req);
|
|
@@ -97381,7 +98206,7 @@ import * as os3 from "os";
|
|
|
97381
98206
|
import * as path2 from "path";
|
|
97382
98207
|
import * as fs2 from "node:fs";
|
|
97383
98208
|
import { createRequire as createRequire3 } from "node:module";
|
|
97384
|
-
import { join as
|
|
98209
|
+
import { join as join42, dirname as dirname12 } from "node:path";
|
|
97385
98210
|
function getNativePrebuildName() {
|
|
97386
98211
|
const platform3 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
|
|
97387
98212
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
@@ -97391,12 +98216,12 @@ function findInstalledNodePtyNativeDir() {
|
|
|
97391
98216
|
try {
|
|
97392
98217
|
const packageJsonPath = require2.resolve("node-pty/package.json");
|
|
97393
98218
|
const pkgRoot = dirname12(packageJsonPath);
|
|
97394
|
-
const releaseDir =
|
|
97395
|
-
if (fs2.existsSync(
|
|
98219
|
+
const releaseDir = join42(pkgRoot, "build", "Release");
|
|
98220
|
+
if (fs2.existsSync(join42(releaseDir, "pty.node"))) {
|
|
97396
98221
|
return releaseDir;
|
|
97397
98222
|
}
|
|
97398
|
-
const prebuildDir =
|
|
97399
|
-
if (fs2.existsSync(
|
|
98223
|
+
const prebuildDir = join42(pkgRoot, "prebuilds", getNativePrebuildName());
|
|
98224
|
+
if (fs2.existsSync(join42(prebuildDir, "pty.node"))) {
|
|
97400
98225
|
return prebuildDir;
|
|
97401
98226
|
}
|
|
97402
98227
|
return null;
|
|
@@ -97422,8 +98247,8 @@ function ensureNodePtyNativePermissions() {
|
|
|
97422
98247
|
candidateDirs.add(installedNativeDir);
|
|
97423
98248
|
}
|
|
97424
98249
|
for (const nativeDir of candidateDirs) {
|
|
97425
|
-
const helperPath =
|
|
97426
|
-
const nativeModulePath =
|
|
98250
|
+
const helperPath = join42(nativeDir, "spawn-helper");
|
|
98251
|
+
const nativeModulePath = join42(nativeDir, "pty.node");
|
|
97427
98252
|
try {
|
|
97428
98253
|
fs2.chmodSync(helperPath, 493);
|
|
97429
98254
|
} catch {
|
|
@@ -97441,14 +98266,14 @@ function ensureNodePtyNativePermissions() {
|
|
|
97441
98266
|
function findStagedNativeDir() {
|
|
97442
98267
|
const prebuildName = getNativePrebuildName();
|
|
97443
98268
|
if (process.env.FUSION_RUNTIME_DIR) {
|
|
97444
|
-
const envPath =
|
|
97445
|
-
if (fs2.existsSync(
|
|
98269
|
+
const envPath = join42(process.env.FUSION_RUNTIME_DIR, prebuildName);
|
|
98270
|
+
if (fs2.existsSync(join42(envPath, "pty.node"))) {
|
|
97446
98271
|
return envPath;
|
|
97447
98272
|
}
|
|
97448
98273
|
}
|
|
97449
98274
|
const execDir = dirname12(process.execPath);
|
|
97450
|
-
const nextToBinary =
|
|
97451
|
-
if (fs2.existsSync(
|
|
98275
|
+
const nextToBinary = join42(execDir, "runtime", prebuildName);
|
|
98276
|
+
if (fs2.existsSync(join42(nextToBinary, "pty.node"))) {
|
|
97452
98277
|
return nextToBinary;
|
|
97453
98278
|
}
|
|
97454
98279
|
return null;
|
|
@@ -97468,7 +98293,7 @@ async function loadPtyModule() {
|
|
|
97468
98293
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
97469
98294
|
}
|
|
97470
98295
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
97471
|
-
const nativePath =
|
|
98296
|
+
const nativePath = join42(nativeDir, "pty.node");
|
|
97472
98297
|
if (fs2.existsSync(nativePath)) {
|
|
97473
98298
|
try {
|
|
97474
98299
|
const nativeModule = { exports: {} };
|
|
@@ -100353,10 +101178,10 @@ var init_github_poll = __esm({
|
|
|
100353
101178
|
});
|
|
100354
101179
|
|
|
100355
101180
|
// ../dashboard/src/routes/resolve-diff-base.ts
|
|
100356
|
-
import { execFile as
|
|
101181
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
100357
101182
|
import { promisify as promisify12 } from "node:util";
|
|
100358
101183
|
async function runGitCommand(args, cwd, timeout2 = 1e4) {
|
|
100359
|
-
const result = await
|
|
101184
|
+
const result = await execFileAsync3("git", args, {
|
|
100360
101185
|
cwd,
|
|
100361
101186
|
timeout: timeout2,
|
|
100362
101187
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -100430,16 +101255,16 @@ async function resolveDiffBase(task, cwd, headRef = "HEAD", runGit = runGitComma
|
|
|
100430
101255
|
return void 0;
|
|
100431
101256
|
}
|
|
100432
101257
|
}
|
|
100433
|
-
var
|
|
101258
|
+
var execFileAsync3;
|
|
100434
101259
|
var init_resolve_diff_base = __esm({
|
|
100435
101260
|
"../dashboard/src/routes/resolve-diff-base.ts"() {
|
|
100436
101261
|
"use strict";
|
|
100437
|
-
|
|
101262
|
+
execFileAsync3 = promisify12(execFile6);
|
|
100438
101263
|
}
|
|
100439
101264
|
});
|
|
100440
101265
|
|
|
100441
101266
|
// ../dashboard/src/routes/register-git-github.ts
|
|
100442
|
-
import { isAbsolute as
|
|
101267
|
+
import { isAbsolute as isAbsolute16 } from "node:path";
|
|
100443
101268
|
function getCommandErrorMessage2(error) {
|
|
100444
101269
|
if (error instanceof Error) {
|
|
100445
101270
|
const anyError = error;
|
|
@@ -101040,7 +101865,7 @@ async function getGitWorkingDiff(cwd) {
|
|
|
101040
101865
|
function isValidGitFilePath(filePath) {
|
|
101041
101866
|
if (!filePath || !filePath.trim()) return false;
|
|
101042
101867
|
if (filePath.startsWith("-")) return false;
|
|
101043
|
-
if (
|
|
101868
|
+
if (isAbsolute16(filePath)) return false;
|
|
101044
101869
|
if (filePath.includes("\0")) return false;
|
|
101045
101870
|
if (filePath.includes("..")) return false;
|
|
101046
101871
|
if (/[;&|`$(){}[\]\r\n]/.test(filePath)) return false;
|
|
@@ -102840,7 +103665,7 @@ var init_register_git_github = __esm({
|
|
|
102840
103665
|
});
|
|
102841
103666
|
|
|
102842
103667
|
// ../dashboard/src/terminal.ts
|
|
102843
|
-
import { spawn as
|
|
103668
|
+
import { spawn as spawn9 } from "node:child_process";
|
|
102844
103669
|
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
102845
103670
|
import { EventEmitter as EventEmitter32 } from "node:events";
|
|
102846
103671
|
function extractBaseCommand(command) {
|
|
@@ -103002,7 +103827,7 @@ var init_terminal = __esm({
|
|
|
103002
103827
|
return { sessionId: "", error: validation.error };
|
|
103003
103828
|
}
|
|
103004
103829
|
const sessionId = randomUUID17();
|
|
103005
|
-
const childProcess =
|
|
103830
|
+
const childProcess = spawn9(command, [], {
|
|
103006
103831
|
cwd,
|
|
103007
103832
|
shell: true,
|
|
103008
103833
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -103126,7 +103951,7 @@ var init_terminal = __esm({
|
|
|
103126
103951
|
});
|
|
103127
103952
|
|
|
103128
103953
|
// ../dashboard/src/file-service.ts
|
|
103129
|
-
import { join as
|
|
103954
|
+
import { join as join43, resolve as resolve21, relative as relative11, dirname as dirname13, basename as basename12 } from "node:path";
|
|
103130
103955
|
import { readdir as readdir8, readFile as fsReadFile, writeFile as fsWriteFile, stat as stat7, copyFile as fsCopyFile, rename as fsRename, rm as fsRm, mkdir as mkdir14, access as access6 } from "node:fs/promises";
|
|
103131
103956
|
async function getTaskBasePath(store, taskId) {
|
|
103132
103957
|
try {
|
|
@@ -103139,7 +103964,7 @@ async function getTaskBasePath(store, taskId) {
|
|
|
103139
103964
|
}
|
|
103140
103965
|
}
|
|
103141
103966
|
const rootDir = store.getRootDir();
|
|
103142
|
-
return resolve21(
|
|
103967
|
+
return resolve21(join43(rootDir, ".fusion", "tasks", taskId));
|
|
103143
103968
|
} catch (err) {
|
|
103144
103969
|
const error = err;
|
|
103145
103970
|
if (error.code === "ENOENT" || error.message && error.message.includes("not found")) {
|
|
@@ -103166,7 +103991,7 @@ function validatePath(basePath, filePath) {
|
|
|
103166
103991
|
throw new FileServiceError(`Access denied: Absolute paths not allowed`, "EINVAL");
|
|
103167
103992
|
}
|
|
103168
103993
|
const resolvedBase = resolve21(basePath);
|
|
103169
|
-
const resolvedPath = resolve21(
|
|
103994
|
+
const resolvedPath = resolve21(join43(resolvedBase, decodedPath));
|
|
103170
103995
|
const relativePath = relative11(resolvedBase, resolvedPath);
|
|
103171
103996
|
if (relativePath.startsWith("..") || relativePath.startsWith("../") || relativePath === "..") {
|
|
103172
103997
|
throw new FileServiceError(`Access denied: Path traversal detected`, "EINVAL");
|
|
@@ -103195,7 +104020,7 @@ async function listFilesForBasePath(basePath, subPath) {
|
|
|
103195
104020
|
const entries = await readdir8(targetPath, { withFileTypes: true });
|
|
103196
104021
|
const fileNodes = [];
|
|
103197
104022
|
for (const entry of entries) {
|
|
103198
|
-
const entryPath =
|
|
104023
|
+
const entryPath = join43(targetPath, entry.name);
|
|
103199
104024
|
const entryStats = await stat7(entryPath);
|
|
103200
104025
|
fileNodes.push({
|
|
103201
104026
|
name: entry.name,
|
|
@@ -103522,7 +104347,7 @@ async function renameWorkspaceFile(store, workspace, filePath, newName) {
|
|
|
103522
104347
|
}
|
|
103523
104348
|
throw err;
|
|
103524
104349
|
}
|
|
103525
|
-
const destPath =
|
|
104350
|
+
const destPath = join43(dirname13(resolvedPath), newName);
|
|
103526
104351
|
const destRelative = relative11(resolve21(workspaceBase), destPath);
|
|
103527
104352
|
if (destRelative.startsWith("..") || destRelative.startsWith("../") || destRelative === "..") {
|
|
103528
104353
|
throw new FileServiceError("Destination would be outside workspace", "EINVAL");
|
|
@@ -103615,7 +104440,7 @@ function isHiddenPathSegment(name) {
|
|
|
103615
104440
|
return name.startsWith(".");
|
|
103616
104441
|
}
|
|
103617
104442
|
async function walkDirForMarkdown(basePath, currentRelative, results, options) {
|
|
103618
|
-
const currentPath = currentRelative ?
|
|
104443
|
+
const currentPath = currentRelative ? join43(basePath, currentRelative) : basePath;
|
|
103619
104444
|
let entries;
|
|
103620
104445
|
try {
|
|
103621
104446
|
entries = await readdir8(currentPath, { withFileTypes: true });
|
|
@@ -103623,7 +104448,7 @@ async function walkDirForMarkdown(basePath, currentRelative, results, options) {
|
|
|
103623
104448
|
return;
|
|
103624
104449
|
}
|
|
103625
104450
|
for (const entry of entries) {
|
|
103626
|
-
const entryRelativePath = currentRelative ?
|
|
104451
|
+
const entryRelativePath = currentRelative ? join43(currentRelative, entry.name) : entry.name;
|
|
103627
104452
|
if (entry.isDirectory()) {
|
|
103628
104453
|
if (MARKDOWN_SCAN_EXCLUDED_DIRS.has(entry.name)) {
|
|
103629
104454
|
continue;
|
|
@@ -103640,7 +104465,7 @@ async function walkDirForMarkdown(basePath, currentRelative, results, options) {
|
|
|
103640
104465
|
if (!options.showHidden && isHiddenPathSegment(entry.name)) {
|
|
103641
104466
|
continue;
|
|
103642
104467
|
}
|
|
103643
|
-
const fullPath =
|
|
104468
|
+
const fullPath = join43(basePath, entryRelativePath);
|
|
103644
104469
|
let fileStats;
|
|
103645
104470
|
try {
|
|
103646
104471
|
fileStats = await stat7(fullPath);
|
|
@@ -103690,7 +104515,7 @@ async function scanMarkdownFiles(store, options) {
|
|
|
103690
104515
|
return;
|
|
103691
104516
|
}
|
|
103692
104517
|
for (const entry of entries) {
|
|
103693
|
-
const entryRelativePath = relativeDir ?
|
|
104518
|
+
const entryRelativePath = relativeDir ? join43(relativeDir, entry.name) : entry.name;
|
|
103694
104519
|
let shouldRecurse = entry.isDirectory();
|
|
103695
104520
|
if (!shouldRecurse && typeof entry.isSymbolicLink === "function" && entry.isSymbolicLink()) {
|
|
103696
104521
|
let symlinkPath;
|
|
@@ -103780,8 +104605,8 @@ async function searchWorkspaceFiles(store, workspace, query) {
|
|
|
103780
104605
|
if (entry.isDirectory() && EXCLUDED_DIRS.has(entry.name)) {
|
|
103781
104606
|
continue;
|
|
103782
104607
|
}
|
|
103783
|
-
const fullPath =
|
|
103784
|
-
const relPath =
|
|
104608
|
+
const fullPath = join43(dir2, entry.name);
|
|
104609
|
+
const relPath = join43(relativeDir, entry.name);
|
|
103785
104610
|
if (entry.isFile()) {
|
|
103786
104611
|
if (entry.name.toLowerCase().includes(lowerQuery)) {
|
|
103787
104612
|
results.push({
|
|
@@ -103802,8 +104627,8 @@ async function copyDirectoryRecursive(source, destination) {
|
|
|
103802
104627
|
await mkdir14(destination, { recursive: true });
|
|
103803
104628
|
const entries = await readdir8(source, { withFileTypes: true });
|
|
103804
104629
|
for (const entry of entries) {
|
|
103805
|
-
const sourcePath =
|
|
103806
|
-
const destPath =
|
|
104630
|
+
const sourcePath = join43(source, entry.name);
|
|
104631
|
+
const destPath = join43(destination, entry.name);
|
|
103807
104632
|
if (entry.isDirectory()) {
|
|
103808
104633
|
await copyDirectoryRecursive(sourcePath, destPath);
|
|
103809
104634
|
} else {
|
|
@@ -107872,7 +108697,7 @@ var require_BufferList = __commonJS({
|
|
|
107872
108697
|
this.head = this.tail = null;
|
|
107873
108698
|
this.length = 0;
|
|
107874
108699
|
};
|
|
107875
|
-
BufferList.prototype.join = function
|
|
108700
|
+
BufferList.prototype.join = function join69(s) {
|
|
107876
108701
|
if (this.length === 0) return "";
|
|
107877
108702
|
var p = this.head;
|
|
107878
108703
|
var ret = "" + p.data;
|
|
@@ -130369,19 +131194,19 @@ var init_register_agents_projects_nodes = __esm({
|
|
|
130369
131194
|
});
|
|
130370
131195
|
|
|
130371
131196
|
// ../dashboard/src/exec-file.ts
|
|
130372
|
-
import { execFile as
|
|
131197
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
130373
131198
|
import { promisify as promisify13 } from "node:util";
|
|
130374
|
-
var
|
|
131199
|
+
var execFileAsync4;
|
|
130375
131200
|
var init_exec_file = __esm({
|
|
130376
131201
|
"../dashboard/src/exec-file.ts"() {
|
|
130377
131202
|
"use strict";
|
|
130378
|
-
|
|
131203
|
+
execFileAsync4 = promisify13(execFile7);
|
|
130379
131204
|
}
|
|
130380
131205
|
});
|
|
130381
131206
|
|
|
130382
131207
|
// ../dashboard/src/routes/register-project-routes.ts
|
|
130383
131208
|
import * as fsPromises from "node:fs/promises";
|
|
130384
|
-
import { dirname as dirname14, isAbsolute as
|
|
131209
|
+
import { dirname as dirname14, isAbsolute as isAbsolute17, join as join44 } from "node:path";
|
|
130385
131210
|
var access9, stat8, mkdir15, readdir9, rm3, registerProjectRoutes;
|
|
130386
131211
|
var init_register_project_routes = __esm({
|
|
130387
131212
|
"../dashboard/src/routes/register-project-routes.ts"() {
|
|
@@ -130529,7 +131354,7 @@ var init_register_project_routes = __esm({
|
|
|
130529
131354
|
if (normalizedPath.includes("\0")) {
|
|
130530
131355
|
throw badRequest("path cannot contain null bytes");
|
|
130531
131356
|
}
|
|
130532
|
-
if (!
|
|
131357
|
+
if (!isAbsolute17(normalizedPath)) {
|
|
130533
131358
|
throw badRequest("path must be an absolute path");
|
|
130534
131359
|
}
|
|
130535
131360
|
if (cloneUrl !== void 0) {
|
|
@@ -130586,7 +131411,7 @@ var init_register_project_routes = __esm({
|
|
|
130586
131411
|
throw badRequest("cloneUrl must be a non-empty string when provided");
|
|
130587
131412
|
}
|
|
130588
131413
|
try {
|
|
130589
|
-
await
|
|
131414
|
+
await execFileAsync4("git", ["clone", cloneSource, normalizedPath], {
|
|
130590
131415
|
timeout: 9e4,
|
|
130591
131416
|
maxBuffer: 10 * 1024 * 1024,
|
|
130592
131417
|
encoding: "utf-8"
|
|
@@ -130604,7 +131429,7 @@ var init_register_project_routes = __esm({
|
|
|
130604
131429
|
}
|
|
130605
131430
|
}
|
|
130606
131431
|
let hasFusionDir = false;
|
|
130607
|
-
const fusionDirPath =
|
|
131432
|
+
const fusionDirPath = join44(normalizedPath, ".fusion");
|
|
130608
131433
|
try {
|
|
130609
131434
|
await access9(fusionDirPath);
|
|
130610
131435
|
hasFusionDir = true;
|
|
@@ -130668,8 +131493,8 @@ var init_register_project_routes = __esm({
|
|
|
130668
131493
|
const entries = await readdir9(searchPath, { withFileTypes: true });
|
|
130669
131494
|
for (const entry of entries) {
|
|
130670
131495
|
if (!entry.isDirectory()) continue;
|
|
130671
|
-
const dirPath =
|
|
130672
|
-
if (isValidSqliteDatabaseFile(
|
|
131496
|
+
const dirPath = join44(searchPath, entry.name);
|
|
131497
|
+
if (isValidSqliteDatabaseFile(join44(dirPath, ".fusion", "fusion.db"))) {
|
|
130673
131498
|
detected.push({
|
|
130674
131499
|
path: dirPath,
|
|
130675
131500
|
suggestedName: entry.name,
|
|
@@ -131104,14 +131929,14 @@ var init_register_node_routes = __esm({
|
|
|
131104
131929
|
|
|
131105
131930
|
// ../dashboard/src/auth-paths.ts
|
|
131106
131931
|
import path3 from "node:path";
|
|
131107
|
-
import { homedir as
|
|
131108
|
-
function getFusionAgentDir2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
131932
|
+
import { homedir as homedir7 } from "node:os";
|
|
131933
|
+
function getFusionAgentDir2(home = process.env.HOME || process.env.USERPROFILE || homedir7()) {
|
|
131109
131934
|
return path3.join(home, ".fusion", "agent");
|
|
131110
131935
|
}
|
|
131111
|
-
function getFusionAuthPath2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
131936
|
+
function getFusionAuthPath2(home = process.env.HOME || process.env.USERPROFILE || homedir7()) {
|
|
131112
131937
|
return path3.join(getFusionAgentDir2(home), "auth.json");
|
|
131113
131938
|
}
|
|
131114
|
-
function getAuthFileCandidates(cwd = process.cwd(), home = process.env.HOME || process.env.USERPROFILE ||
|
|
131939
|
+
function getAuthFileCandidates(cwd = process.cwd(), home = process.env.HOME || process.env.USERPROFILE || homedir7()) {
|
|
131115
131940
|
return [
|
|
131116
131941
|
path3.join(home, ".fusion", "agent", "auth.json"),
|
|
131117
131942
|
path3.join(home, ".fusion", "auth.json"),
|
|
@@ -134096,7 +134921,7 @@ You MUST respond with ONLY valid JSON (no markdown, no explanation):
|
|
|
134096
134921
|
import { createWriteStream } from "node:fs";
|
|
134097
134922
|
import * as fsPromises2 from "node:fs/promises";
|
|
134098
134923
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
134099
|
-
import { join as
|
|
134924
|
+
import { join as join45, resolve as resolve22 } from "node:path";
|
|
134100
134925
|
import { Readable } from "node:stream";
|
|
134101
134926
|
import { pipeline as streamPipeline } from "node:stream/promises";
|
|
134102
134927
|
function registerAgentImportExportRoutes(ctx) {
|
|
@@ -134137,7 +134962,7 @@ function registerAgentImportExportRoutes(ctx) {
|
|
|
134137
134962
|
} else if (typeof outputDir === "string") {
|
|
134138
134963
|
throw badRequest("outputDir cannot be empty");
|
|
134139
134964
|
} else {
|
|
134140
|
-
resolvedOutputDir = await mkdtemp(
|
|
134965
|
+
resolvedOutputDir = await mkdtemp(join45(tmpdir3(), "fusion-agent-export-"));
|
|
134141
134966
|
}
|
|
134142
134967
|
const result = await exportAgentsToDirectory2(agentsToExport, resolvedOutputDir, {
|
|
134143
134968
|
companyName: typeof companyName === "string" ? companyName : void 0,
|
|
@@ -134264,7 +135089,7 @@ ${body}`;
|
|
|
134264
135089
|
return result;
|
|
134265
135090
|
}
|
|
134266
135091
|
const safeCompanySlug = companySlug ? slugifyPathSegment2(companySlug, "unknown-company") : "unknown-company";
|
|
134267
|
-
const skillsBaseDir =
|
|
135092
|
+
const skillsBaseDir = join45(projectRoot, "skills", "imported", safeCompanySlug);
|
|
134268
135093
|
const usedSlugs = /* @__PURE__ */ new Set();
|
|
134269
135094
|
for (const skill of skills) {
|
|
134270
135095
|
const name = typeof skill.name === "string" && skill.name.trim().length > 0 ? skill.name.trim() : null;
|
|
@@ -134281,8 +135106,8 @@ ${body}`;
|
|
|
134281
135106
|
skillSlug = `${skillSlug}-${counter}`;
|
|
134282
135107
|
}
|
|
134283
135108
|
usedSlugs.add(skillSlug);
|
|
134284
|
-
const skillDir =
|
|
134285
|
-
const skillPath =
|
|
135109
|
+
const skillDir = join45(skillsBaseDir, skillSlug);
|
|
135110
|
+
const skillPath = join45(skillDir, "SKILL.md");
|
|
134286
135111
|
try {
|
|
134287
135112
|
await access10(skillPath);
|
|
134288
135113
|
result.skipped.push(name);
|
|
@@ -134452,8 +135277,8 @@ ${body}`;
|
|
|
134452
135277
|
const archiveUrl = `https://github.com/${repoOwner}/${repoName}/archive/refs/heads/main.tar.gz`;
|
|
134453
135278
|
let tempDir = null;
|
|
134454
135279
|
try {
|
|
134455
|
-
tempDir = await mkdtemp(
|
|
134456
|
-
const archivePath =
|
|
135280
|
+
tempDir = await mkdtemp(join45(tmpdir3(), `fn-agent-import-${importCompanySlug}-`));
|
|
135281
|
+
const archivePath = join45(tempDir, "archive.tar.gz");
|
|
134457
135282
|
const downloadController = new AbortController();
|
|
134458
135283
|
const downloadTimeout = setTimeout(() => downloadController.abort(), 3e4);
|
|
134459
135284
|
let archiveResponse;
|
|
@@ -135582,7 +136407,7 @@ import * as child_process from "node:child_process";
|
|
|
135582
136407
|
function getHomeDir5() {
|
|
135583
136408
|
return process.env.HOME || process.env.USERPROFILE || os4.homedir();
|
|
135584
136409
|
}
|
|
135585
|
-
function
|
|
136410
|
+
function execFileAsync5(file, args, options) {
|
|
135586
136411
|
return new Promise((resolve39, reject2) => {
|
|
135587
136412
|
child_process.execFile(file, args, options, (error, stdout, stderr) => {
|
|
135588
136413
|
if (error) {
|
|
@@ -135731,7 +136556,7 @@ async function readConfiguredApiKey(provider, authStorage) {
|
|
|
135731
136556
|
}
|
|
135732
136557
|
async function readClaudeKeychainCredentials() {
|
|
135733
136558
|
try {
|
|
135734
|
-
const { stdout } = await
|
|
136559
|
+
const { stdout } = await execFileAsync5(
|
|
135735
136560
|
"security",
|
|
135736
136561
|
["find-generic-password", "-s", "Claude Code-credentials", "-w"],
|
|
135737
136562
|
{ encoding: "utf-8", timeout: 5e3 }
|
|
@@ -136604,13 +137429,13 @@ async function fetchGitHubCopilotUsage() {
|
|
|
136604
137429
|
windows: []
|
|
136605
137430
|
};
|
|
136606
137431
|
try {
|
|
136607
|
-
await
|
|
137432
|
+
await execFileAsync5("gh", ["auth", "status"], { encoding: "utf-8", timeout: 5e3 });
|
|
136608
137433
|
} catch {
|
|
136609
137434
|
usage.error = "GitHub CLI not authenticated \u2014 run 'gh auth login'";
|
|
136610
137435
|
return usage;
|
|
136611
137436
|
}
|
|
136612
137437
|
try {
|
|
136613
|
-
const { stdout } = await
|
|
137438
|
+
const { stdout } = await execFileAsync5("gh", ["api", "/user/copilot", "--jq", "."], {
|
|
136614
137439
|
encoding: "utf-8",
|
|
136615
137440
|
timeout: 1e4
|
|
136616
137441
|
});
|
|
@@ -136763,7 +137588,7 @@ var init_register_usage_routes = __esm({
|
|
|
136763
137588
|
});
|
|
136764
137589
|
|
|
136765
137590
|
// ../dashboard/src/claude-cli-probe.ts
|
|
136766
|
-
import { spawn as
|
|
137591
|
+
import { spawn as spawn10 } from "node:child_process";
|
|
136767
137592
|
async function probeClaudeCli(options = {}) {
|
|
136768
137593
|
const startedAt = Date.now();
|
|
136769
137594
|
const timeoutMs = options.timeoutMs ?? PROBE_TIMEOUT_MS;
|
|
@@ -136773,7 +137598,7 @@ async function probeClaudeCli(options = {}) {
|
|
|
136773
137598
|
resolvePromise({ ...result, probeDurationMs: Date.now() - startedAt });
|
|
136774
137599
|
};
|
|
136775
137600
|
let settled = false;
|
|
136776
|
-
const child =
|
|
137601
|
+
const child = spawn10(binaryPath ?? "claude", ["--version"], {
|
|
136777
137602
|
stdio: ["ignore", "pipe", "pipe"]
|
|
136778
137603
|
});
|
|
136779
137604
|
const timer = setTimeout(() => {
|
|
@@ -136831,7 +137656,7 @@ async function probeClaudeCli(options = {}) {
|
|
|
136831
137656
|
async function tryResolveBinaryPath3(binary) {
|
|
136832
137657
|
return new Promise((resolvePromise) => {
|
|
136833
137658
|
const which = process.platform === "win32" ? "where" : "which";
|
|
136834
|
-
const child =
|
|
137659
|
+
const child = spawn10(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
|
|
136835
137660
|
let out = "";
|
|
136836
137661
|
child.stdout?.on("data", (chunk) => {
|
|
136837
137662
|
out += chunk.toString("utf-8");
|
|
@@ -137555,7 +138380,7 @@ function stripAnsi2(str) {
|
|
|
137555
138380
|
return str.replace(/\x1B\[[0-9;]*[mGKJHFABCDSTsu]/g, "");
|
|
137556
138381
|
}
|
|
137557
138382
|
async function mintAgentApiKeyViaCli(opts) {
|
|
137558
|
-
const { spawn:
|
|
138383
|
+
const { spawn: spawn16 } = await import("node:child_process");
|
|
137559
138384
|
const bin = opts.cliBinaryPath ?? "paperclipai";
|
|
137560
138385
|
const args = [
|
|
137561
138386
|
"agent",
|
|
@@ -137579,7 +138404,7 @@ async function mintAgentApiKeyViaCli(opts) {
|
|
|
137579
138404
|
return new Promise((resolve39, reject2) => {
|
|
137580
138405
|
let child;
|
|
137581
138406
|
try {
|
|
137582
|
-
child =
|
|
138407
|
+
child = spawn16(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
137583
138408
|
} catch (err) {
|
|
137584
138409
|
const code = err.code;
|
|
137585
138410
|
if (code === "ENOENT") {
|
|
@@ -137662,7 +138487,7 @@ function remapSpawnError(err, bin) {
|
|
|
137662
138487
|
return err instanceof Error ? err : new Error(String(err));
|
|
137663
138488
|
}
|
|
137664
138489
|
async function spawnPaperclipCliJson(args, opts) {
|
|
137665
|
-
const { spawn:
|
|
138490
|
+
const { spawn: spawn16 } = await import("node:child_process");
|
|
137666
138491
|
const bin = opts.cliBinaryPath ?? "paperclipai";
|
|
137667
138492
|
const fullArgs = [...args, "--json"];
|
|
137668
138493
|
if (opts.cliConfigPath) {
|
|
@@ -137673,7 +138498,7 @@ async function spawnPaperclipCliJson(args, opts) {
|
|
|
137673
138498
|
return new Promise((resolve39, reject2) => {
|
|
137674
138499
|
let child;
|
|
137675
138500
|
try {
|
|
137676
|
-
child =
|
|
138501
|
+
child = spawn16(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
137677
138502
|
} catch (err) {
|
|
137678
138503
|
reject2(remapSpawnError(err, bin));
|
|
137679
138504
|
return;
|
|
@@ -138487,9 +139312,9 @@ var init_register_runtime_provider_routes = __esm({
|
|
|
138487
139312
|
});
|
|
138488
139313
|
|
|
138489
139314
|
// ../dashboard/src/update-check.ts
|
|
138490
|
-
import { readFileSync as
|
|
139315
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
138491
139316
|
import { mkdir as mkdir17, rm as rm5, writeFile as writeFile15 } from "node:fs/promises";
|
|
138492
|
-
import { join as
|
|
139317
|
+
import { join as join47 } from "node:path";
|
|
138493
139318
|
function ttlForFrequency(frequency) {
|
|
138494
139319
|
switch (frequency) {
|
|
138495
139320
|
case "manual":
|
|
@@ -138503,7 +139328,7 @@ function ttlForFrequency(frequency) {
|
|
|
138503
139328
|
}
|
|
138504
139329
|
}
|
|
138505
139330
|
function getCachePath(fusionDir) {
|
|
138506
|
-
return
|
|
139331
|
+
return join47(fusionDir, CACHE_FILENAME);
|
|
138507
139332
|
}
|
|
138508
139333
|
function parseVersion(version) {
|
|
138509
139334
|
return version.split(".").slice(0, 3).map((part) => Number.parseInt(part, 10)).map((value) => Number.isFinite(value) ? value : 0);
|
|
@@ -138527,7 +139352,7 @@ function isValidResult(value) {
|
|
|
138527
139352
|
}
|
|
138528
139353
|
function readCachedUpdateCheck(fusionDir) {
|
|
138529
139354
|
try {
|
|
138530
|
-
const raw =
|
|
139355
|
+
const raw = readFileSync10(getCachePath(fusionDir), "utf-8");
|
|
138531
139356
|
const parsed = JSON.parse(raw);
|
|
138532
139357
|
return isValidResult(parsed) ? parsed : null;
|
|
138533
139358
|
} catch {
|
|
@@ -138595,15 +139420,15 @@ var init_update_check = __esm({
|
|
|
138595
139420
|
});
|
|
138596
139421
|
|
|
138597
139422
|
// ../dashboard/src/cli-package-version.ts
|
|
138598
|
-
import { existsSync as
|
|
139423
|
+
import { existsSync as existsSync31, readFileSync as readFileSync11 } from "node:fs";
|
|
138599
139424
|
import { dirname as dirname15, resolve as resolve23 } from "node:path";
|
|
138600
139425
|
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
138601
139426
|
function readCliPackageVersion(pkgPath) {
|
|
138602
|
-
if (!
|
|
139427
|
+
if (!existsSync31(pkgPath)) {
|
|
138603
139428
|
return null;
|
|
138604
139429
|
}
|
|
138605
139430
|
try {
|
|
138606
|
-
const parsed = JSON.parse(
|
|
139431
|
+
const parsed = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
138607
139432
|
if (parsed.name === CLI_PACKAGE_NAME && typeof parsed.version === "string" && parsed.version.length > 0) {
|
|
138608
139433
|
return {
|
|
138609
139434
|
packageJsonPath: pkgPath,
|
|
@@ -142972,7 +143797,7 @@ var init_todo_routes = __esm({
|
|
|
142972
143797
|
|
|
142973
143798
|
// ../dashboard/src/dev-server-detect.ts
|
|
142974
143799
|
import { glob, readFile as readFile20 } from "node:fs/promises";
|
|
142975
|
-
import { dirname as dirname16, join as
|
|
143800
|
+
import { dirname as dirname16, join as join48, relative as relative12, resolve as resolve24 } from "node:path";
|
|
142976
143801
|
async function readPackageJson(filePath) {
|
|
142977
143802
|
try {
|
|
142978
143803
|
const raw = await readFile20(filePath, "utf-8");
|
|
@@ -143015,7 +143840,7 @@ function scoreCandidate(scriptName, pkg) {
|
|
|
143015
143840
|
async function collectWorkspacePackageJsons(projectRoot) {
|
|
143016
143841
|
const discovered = /* @__PURE__ */ new Set();
|
|
143017
143842
|
try {
|
|
143018
|
-
await readFile20(
|
|
143843
|
+
await readFile20(join48(projectRoot, "pnpm-workspace.yaml"), "utf-8");
|
|
143019
143844
|
} catch {
|
|
143020
143845
|
}
|
|
143021
143846
|
for (const pattern of ["packages/*/package.json", "apps/*/package.json"]) {
|
|
@@ -143049,7 +143874,7 @@ function toSource(projectRoot, packageJsonPath) {
|
|
|
143049
143874
|
async function detectDevServerScripts(projectRoot) {
|
|
143050
143875
|
const root = resolve24(projectRoot);
|
|
143051
143876
|
const candidates = [];
|
|
143052
|
-
const rootPackagePath =
|
|
143877
|
+
const rootPackagePath = join48(root, "package.json");
|
|
143053
143878
|
const rootPackage = await readPackageJson(rootPackagePath);
|
|
143054
143879
|
if (rootPackage) {
|
|
143055
143880
|
for (const script of extractScripts(rootPackage)) {
|
|
@@ -143116,9 +143941,9 @@ var init_dev_server_detect = __esm({
|
|
|
143116
143941
|
|
|
143117
143942
|
// ../dashboard/src/dev-server-store.ts
|
|
143118
143943
|
import { mkdir as mkdir18, readFile as readFile21, writeFile as writeFile16 } from "node:fs/promises";
|
|
143119
|
-
import { dirname as dirname17, join as
|
|
143944
|
+
import { dirname as dirname17, join as join49, resolve as resolve25 } from "node:path";
|
|
143120
143945
|
function devServerFilePath(projectDir) {
|
|
143121
|
-
return
|
|
143946
|
+
return join49(resolve25(projectDir), ".fusion", "dev-server.json");
|
|
143122
143947
|
}
|
|
143123
143948
|
function normalizeState(candidate) {
|
|
143124
143949
|
const defaults = DEV_SERVER_DEFAULT_STATE();
|
|
@@ -143486,7 +144311,7 @@ var init_dev_server_port_detect = __esm({
|
|
|
143486
144311
|
|
|
143487
144312
|
// ../dashboard/src/dev-server-process.ts
|
|
143488
144313
|
import { EventEmitter as EventEmitter33 } from "node:events";
|
|
143489
|
-
import { spawn as
|
|
144314
|
+
import { spawn as spawn11 } from "node:child_process";
|
|
143490
144315
|
function killManagedProcess2(child, signal) {
|
|
143491
144316
|
if (typeof child.pid !== "number") {
|
|
143492
144317
|
return;
|
|
@@ -143565,7 +144390,7 @@ var init_dev_server_process = __esm({
|
|
|
143565
144390
|
detectedUrl: void 0,
|
|
143566
144391
|
detectedPort: void 0
|
|
143567
144392
|
});
|
|
143568
|
-
const child =
|
|
144393
|
+
const child = spawn11(safeCommand, [], {
|
|
143569
144394
|
cwd: safeCwd,
|
|
143570
144395
|
detached: process.platform !== "win32",
|
|
143571
144396
|
shell: true,
|
|
@@ -144372,7 +145197,7 @@ Your job is to refine task descriptions based on the user's selected refinement
|
|
|
144372
145197
|
|
|
144373
145198
|
// ../dashboard/src/routes.ts
|
|
144374
145199
|
import multer from "multer";
|
|
144375
|
-
import { resolve as resolve26, sep as sep7, join as
|
|
145200
|
+
import { resolve as resolve26, sep as sep7, join as join50, isAbsolute as isAbsolute18 } from "node:path";
|
|
144376
145201
|
import * as nodeFs from "node:fs";
|
|
144377
145202
|
import os5 from "node:os";
|
|
144378
145203
|
import v8 from "node:v8";
|
|
@@ -144393,8 +145218,8 @@ function hasPackageManagerSettings2(settings) {
|
|
|
144393
145218
|
function getPiPackageManagerAgentDir() {
|
|
144394
145219
|
const fusionAgentDir = getFusionAgentDir();
|
|
144395
145220
|
const legacyAgentDir = getLegacyPiAgentDir();
|
|
144396
|
-
const fusionSettings = readJsonObject3(
|
|
144397
|
-
const legacySettings = readJsonObject3(
|
|
145221
|
+
const fusionSettings = readJsonObject3(join50(fusionAgentDir, "settings.json"));
|
|
145222
|
+
const legacySettings = readJsonObject3(join50(legacyAgentDir, "settings.json"));
|
|
144398
145223
|
if (hasPackageManagerSettings2(fusionSettings) || !nodeFs.existsSync(legacyAgentDir)) {
|
|
144399
145224
|
return fusionAgentDir;
|
|
144400
145225
|
}
|
|
@@ -144417,10 +145242,10 @@ async function discoverDashboardPiExtensions(cwd) {
|
|
|
144417
145242
|
try {
|
|
144418
145243
|
const { DefaultPackageManager: DefaultPackageManager5 } = await import("@mariozechner/pi-coding-agent");
|
|
144419
145244
|
const agentDir = getPiPackageManagerAgentDir();
|
|
144420
|
-
const legacyGlobalSettings = readJsonObject3(
|
|
144421
|
-
const fusionGlobalSettings = readJsonObject3(
|
|
145245
|
+
const legacyGlobalSettings = readJsonObject3(join50(getLegacyPiAgentDir(), "settings.json"));
|
|
145246
|
+
const fusionGlobalSettings = readJsonObject3(join50(getFusionAgentDir(), "settings.json"));
|
|
144422
145247
|
const globalSettings = { ...legacyGlobalSettings, ...fusionGlobalSettings };
|
|
144423
|
-
const projectSettings = readJsonObject3(
|
|
145248
|
+
const projectSettings = readJsonObject3(join50(cwd, ".fusion", "settings.json"));
|
|
144424
145249
|
const mergedSettings = { ...globalSettings, ...projectSettings };
|
|
144425
145250
|
const packageManager = new DefaultPackageManager5({
|
|
144426
145251
|
cwd,
|
|
@@ -144556,7 +145381,7 @@ function sanitizeOverlapIgnorePaths(value) {
|
|
|
144556
145381
|
if (!trimmed) {
|
|
144557
145382
|
throw badRequest(`overlapIgnorePaths[${index2}] cannot be empty`);
|
|
144558
145383
|
}
|
|
144559
|
-
if (
|
|
145384
|
+
if (isAbsolute18(trimmed) || /^[a-zA-Z]:\//.test(trimmed)) {
|
|
144560
145385
|
throw badRequest(`overlapIgnorePaths[${index2}] must be a project-relative path`);
|
|
144561
145386
|
}
|
|
144562
145387
|
if (/^\.{1,2}(\/|$)/.test(trimmed) || /(^|\/)\.\.(\/|$)/.test(trimmed)) {
|
|
@@ -146761,7 +147586,7 @@ Description: ${step.description}`
|
|
|
146761
147586
|
return;
|
|
146762
147587
|
}
|
|
146763
147588
|
}
|
|
146764
|
-
const { resolve: resolve39, dirname: dirname29, join:
|
|
147589
|
+
const { resolve: resolve39, dirname: dirname29, join: join69 } = await import("node:path");
|
|
146765
147590
|
const { readdir: readdir12, stat: stat12 } = await import("node:fs/promises");
|
|
146766
147591
|
const rawPath = req.query.path || process.env.HOME || process.env.USERPROFILE || "/";
|
|
146767
147592
|
const showHidden = req.query.showHidden === "true";
|
|
@@ -146786,7 +147611,7 @@ Description: ${step.description}`
|
|
|
146786
147611
|
for (const entry of dirEntries) {
|
|
146787
147612
|
if (!entry.isDirectory()) continue;
|
|
146788
147613
|
if (!showHidden && entry.name.startsWith(".")) continue;
|
|
146789
|
-
const entryPath =
|
|
147614
|
+
const entryPath = join69(resolvedPath, entry.name);
|
|
146790
147615
|
let hasChildren = false;
|
|
146791
147616
|
try {
|
|
146792
147617
|
const subEntries = await readdir12(entryPath, { withFileTypes: true });
|
|
@@ -147040,9 +147865,9 @@ function truncateAutomationOutput(stdout, stderr) {
|
|
|
147040
147865
|
return output;
|
|
147041
147866
|
}
|
|
147042
147867
|
async function executeSingleCommand(command, timeoutMs, startedAt) {
|
|
147043
|
-
const { exec:
|
|
147868
|
+
const { exec: exec13 } = await import("node:child_process");
|
|
147044
147869
|
const { promisify: promisify18 } = await import("node:util");
|
|
147045
|
-
const execAsyncFn = promisify18(
|
|
147870
|
+
const execAsyncFn = promisify18(exec13);
|
|
147046
147871
|
const isWindows = process.platform === "win32";
|
|
147047
147872
|
try {
|
|
147048
147873
|
const { stdout, stderr } = await execAsyncFn(command, {
|
|
@@ -147424,7 +148249,7 @@ function stripTaskListHeavyFields(task) {
|
|
|
147424
148249
|
const candidate = task;
|
|
147425
148250
|
const existingTimed = candidate.timedExecutionMs;
|
|
147426
148251
|
const timedExecutionMs = typeof existingTimed === "number" ? existingTimed : sumTimedLogEntries(candidate.log);
|
|
147427
|
-
return { ...task, log: [], timedExecutionMs };
|
|
148252
|
+
return { ...task, log: [], timedExecutionMs, tokenUsage: candidate.tokenUsage, workflowStepResults: candidate.workflowStepResults };
|
|
147428
148253
|
}
|
|
147429
148254
|
function sumTimedLogEntries(log18) {
|
|
147430
148255
|
if (!Array.isArray(log18)) return 0;
|
|
@@ -152327,8 +153152,8 @@ var init_auth_middleware = __esm({
|
|
|
152327
153152
|
// ../dashboard/src/server.ts
|
|
152328
153153
|
import express from "express";
|
|
152329
153154
|
import { randomUUID as randomUUID20 } from "node:crypto";
|
|
152330
|
-
import { join as
|
|
152331
|
-
import { existsSync as
|
|
153155
|
+
import { join as join51, dirname as dirname18 } from "node:path";
|
|
153156
|
+
import { existsSync as existsSync33, readFileSync as readFileSync13 } from "node:fs";
|
|
152332
153157
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
152333
153158
|
import { createSecureServer as createHttp2SecureServer } from "node:http2";
|
|
152334
153159
|
function parseVersion2(version) {
|
|
@@ -152430,11 +153255,11 @@ function loadTlsCredentialsFromEnv(env = process.env) {
|
|
|
152430
153255
|
"FUSION_TLS_* environment is incomplete: set both a cert and a key (inline via FUSION_TLS_CERT/FUSION_TLS_KEY or paths via *_FILE)."
|
|
152431
153256
|
);
|
|
152432
153257
|
}
|
|
152433
|
-
const cert = certInline ? Buffer.from(certInline) :
|
|
152434
|
-
const key = keyInline ? Buffer.from(keyInline) :
|
|
153258
|
+
const cert = certInline ? Buffer.from(certInline) : readFileSync13(certFile);
|
|
153259
|
+
const key = keyInline ? Buffer.from(keyInline) : readFileSync13(keyFile);
|
|
152435
153260
|
const caInline = env.FUSION_TLS_CA;
|
|
152436
153261
|
const caFile = env.FUSION_TLS_CA_FILE;
|
|
152437
|
-
const ca = caInline ? Buffer.from(caInline) : caFile ?
|
|
153262
|
+
const ca = caInline ? Buffer.from(caInline) : caFile ? readFileSync13(caFile) : void 0;
|
|
152438
153263
|
return { cert, key, ca };
|
|
152439
153264
|
}
|
|
152440
153265
|
function createServer(store, options) {
|
|
@@ -152510,11 +153335,11 @@ function createServer(store, options) {
|
|
|
152510
153335
|
getTerminalService(store.getRootDir());
|
|
152511
153336
|
const isHeadless = options?.headless === true;
|
|
152512
153337
|
const execDir = dirname18(process.execPath);
|
|
152513
|
-
const clientDir = process.env.FUSION_CLIENT_DIR ? process.env.FUSION_CLIENT_DIR :
|
|
153338
|
+
const clientDir = process.env.FUSION_CLIENT_DIR ? process.env.FUSION_CLIENT_DIR : existsSync33(join51(execDir, "client", "index.html")) ? join51(execDir, "client") : existsSync33(join51(__dirname, "..", "dist", "client")) ? join51(__dirname, "..", "dist", "client") : join51(__dirname, "..", "client");
|
|
152514
153339
|
if (!isHeadless) {
|
|
152515
153340
|
app.get("/version.json", (_req, res) => {
|
|
152516
153341
|
res.setHeader("Cache-Control", "no-store, max-age=0");
|
|
152517
|
-
res.sendFile(
|
|
153342
|
+
res.sendFile(join51(clientDir, "version.json"), (err) => {
|
|
152518
153343
|
if (err) {
|
|
152519
153344
|
res.status(404).json({ version: null });
|
|
152520
153345
|
}
|
|
@@ -152934,7 +153759,7 @@ data: ${JSON.stringify({ type: event.type, data: event.data })}
|
|
|
152934
153759
|
});
|
|
152935
153760
|
if (!isHeadless) {
|
|
152936
153761
|
app.get("/{*splat}", (_req, res) => {
|
|
152937
|
-
res.sendFile(
|
|
153762
|
+
res.sendFile(join51(clientDir, "index.html"));
|
|
152938
153763
|
});
|
|
152939
153764
|
}
|
|
152940
153765
|
const dashboardApp = app;
|
|
@@ -153389,7 +154214,7 @@ var init_server = __esm({
|
|
|
153389
154214
|
|
|
153390
154215
|
// ../dashboard/src/skills-adapter.ts
|
|
153391
154216
|
import { access as access11, readFile as readFile22, writeFile as writeFile17, mkdir as mkdir19, readdir as readdir10, stat as stat10 } from "node:fs/promises";
|
|
153392
|
-
import { join as
|
|
154217
|
+
import { join as join52, relative as relative13, dirname as dirname19 } from "node:path";
|
|
153393
154218
|
async function pathExists(path5) {
|
|
153394
154219
|
try {
|
|
153395
154220
|
await access11(path5);
|
|
@@ -153645,7 +154470,7 @@ function createSkillsAdapter(options) {
|
|
|
153645
154470
|
} catch {
|
|
153646
154471
|
skillDir = dirname19(skill.path);
|
|
153647
154472
|
}
|
|
153648
|
-
const skillMdPath =
|
|
154473
|
+
const skillMdPath = join52(skillDir, "SKILL.md");
|
|
153649
154474
|
let skillMd = "";
|
|
153650
154475
|
try {
|
|
153651
154476
|
skillMd = await readFile22(skillMdPath, "utf-8");
|
|
@@ -153853,7 +154678,7 @@ function normalizeEntry(entry) {
|
|
|
153853
154678
|
};
|
|
153854
154679
|
}
|
|
153855
154680
|
function getProjectSettingsPath(rootDir) {
|
|
153856
|
-
return
|
|
154681
|
+
return join52(rootDir, ".fusion", "settings.json");
|
|
153857
154682
|
}
|
|
153858
154683
|
var MIN_PUBLIC_SEARCH_QUERY_LENGTH;
|
|
153859
154684
|
var init_skills_adapter = __esm({
|
|
@@ -153917,7 +154742,7 @@ var init_src4 = __esm({
|
|
|
153917
154742
|
});
|
|
153918
154743
|
|
|
153919
154744
|
// src/commands/task-lifecycle.ts
|
|
153920
|
-
import { exec as
|
|
154745
|
+
import { exec as exec10 } from "node:child_process";
|
|
153921
154746
|
import { promisify as promisify14 } from "node:util";
|
|
153922
154747
|
function getMergeStrategy(settings) {
|
|
153923
154748
|
return settings.mergeStrategy ?? "direct";
|
|
@@ -154043,7 +154868,7 @@ var execAsync9;
|
|
|
154043
154868
|
var init_task_lifecycle = __esm({
|
|
154044
154869
|
"src/commands/task-lifecycle.ts"() {
|
|
154045
154870
|
"use strict";
|
|
154046
|
-
execAsync9 = promisify14(
|
|
154871
|
+
execAsync9 = promisify14(exec10);
|
|
154047
154872
|
}
|
|
154048
154873
|
});
|
|
154049
154874
|
|
|
@@ -154096,33 +154921,33 @@ var init_port_prompt = __esm({
|
|
|
154096
154921
|
});
|
|
154097
154922
|
|
|
154098
154923
|
// src/commands/provider-settings.ts
|
|
154099
|
-
import { existsSync as
|
|
154100
|
-
import { join as
|
|
154924
|
+
import { existsSync as existsSync34, readFileSync as readFileSync14, writeFileSync as writeFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
|
|
154925
|
+
import { join as join53, dirname as dirname20, basename as basename15 } from "node:path";
|
|
154101
154926
|
function siblingAgentDir2(agentDir, siblingRoot) {
|
|
154102
154927
|
if (basename15(agentDir) !== "agent") {
|
|
154103
154928
|
return void 0;
|
|
154104
154929
|
}
|
|
154105
|
-
return
|
|
154930
|
+
return join53(dirname20(dirname20(agentDir)), siblingRoot, "agent");
|
|
154106
154931
|
}
|
|
154107
154932
|
function readJsonObject4(path5) {
|
|
154108
|
-
if (!
|
|
154933
|
+
if (!existsSync34(path5)) {
|
|
154109
154934
|
return {};
|
|
154110
154935
|
}
|
|
154111
154936
|
try {
|
|
154112
|
-
const parsed = JSON.parse(
|
|
154937
|
+
const parsed = JSON.parse(readFileSync14(path5, "utf-8"));
|
|
154113
154938
|
return parsed !== null && typeof parsed === "object" ? parsed : {};
|
|
154114
154939
|
} catch {
|
|
154115
154940
|
return {};
|
|
154116
154941
|
}
|
|
154117
154942
|
}
|
|
154118
154943
|
function createReadOnlyProviderSettingsView(cwd, agentDir) {
|
|
154119
|
-
const fusionAgentDir = agentDir.includes(`${
|
|
154120
|
-
const legacyAgentDir = agentDir.includes(`${
|
|
154121
|
-
const legacyGlobalSettings = legacyAgentDir ? readJsonObject4(
|
|
154122
|
-
const fusionGlobalSettings = fusionAgentDir ? readJsonObject4(
|
|
154123
|
-
const directGlobalSettings = readJsonObject4(
|
|
154944
|
+
const fusionAgentDir = agentDir.includes(`${join53(".fusion", "agent")}`) ? agentDir : siblingAgentDir2(agentDir, ".fusion");
|
|
154945
|
+
const legacyAgentDir = agentDir.includes(`${join53(".pi", "agent")}`) ? agentDir : siblingAgentDir2(agentDir, ".pi");
|
|
154946
|
+
const legacyGlobalSettings = legacyAgentDir ? readJsonObject4(join53(legacyAgentDir, "settings.json")) : {};
|
|
154947
|
+
const fusionGlobalSettings = fusionAgentDir ? readJsonObject4(join53(fusionAgentDir, "settings.json")) : {};
|
|
154948
|
+
const directGlobalSettings = readJsonObject4(join53(agentDir, "settings.json"));
|
|
154124
154949
|
const globalSettings = { ...legacyGlobalSettings, ...directGlobalSettings, ...fusionGlobalSettings };
|
|
154125
|
-
const fusionProjectSettings = readJsonObject4(
|
|
154950
|
+
const fusionProjectSettings = readJsonObject4(join53(cwd, ".fusion", "settings.json"));
|
|
154126
154951
|
const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
|
|
154127
154952
|
return {
|
|
154128
154953
|
getGlobalSettings: () => structuredClone(globalSettings),
|
|
@@ -154137,7 +154962,7 @@ var init_provider_settings = __esm({
|
|
|
154137
154962
|
});
|
|
154138
154963
|
|
|
154139
154964
|
// src/commands/provider-auth.ts
|
|
154140
|
-
import { existsSync as
|
|
154965
|
+
import { existsSync as existsSync35, readFileSync as readFileSync15 } from "node:fs";
|
|
154141
154966
|
import { getOAuthProvider as getOAuthProvider2 } from "@mariozechner/pi-ai/oauth";
|
|
154142
154967
|
function getProviderDisplayName(providerId) {
|
|
154143
154968
|
const knownProviderNames = new Map(
|
|
@@ -154266,11 +155091,11 @@ function createReadOnlyAuthFileStorage(authPaths) {
|
|
|
154266
155091
|
const reload = () => {
|
|
154267
155092
|
const nextCredentials = {};
|
|
154268
155093
|
for (const authPath of authPaths) {
|
|
154269
|
-
if (!
|
|
155094
|
+
if (!existsSync35(authPath)) {
|
|
154270
155095
|
continue;
|
|
154271
155096
|
}
|
|
154272
155097
|
try {
|
|
154273
|
-
const parsed = JSON.parse(
|
|
155098
|
+
const parsed = JSON.parse(readFileSync15(authPath, "utf-8"));
|
|
154274
155099
|
for (const [provider, credential] of Object.entries(parsed)) {
|
|
154275
155100
|
nextCredentials[provider] ??= credential;
|
|
154276
155101
|
}
|
|
@@ -154306,46 +155131,46 @@ var init_provider_auth = __esm({
|
|
|
154306
155131
|
});
|
|
154307
155132
|
|
|
154308
155133
|
// src/commands/auth-paths.ts
|
|
154309
|
-
import { homedir as
|
|
154310
|
-
import { existsSync as
|
|
154311
|
-
import { join as
|
|
154312
|
-
function getFusionAgentDir3(home = process.env.HOME || process.env.USERPROFILE ||
|
|
154313
|
-
return
|
|
155134
|
+
import { homedir as homedir9 } from "node:os";
|
|
155135
|
+
import { existsSync as existsSync36, readFileSync as readFileSync16 } from "node:fs";
|
|
155136
|
+
import { join as join54 } from "node:path";
|
|
155137
|
+
function getFusionAgentDir3(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
155138
|
+
return join54(home, ".fusion", "agent");
|
|
154314
155139
|
}
|
|
154315
|
-
function getLegacyAgentDir(home = process.env.HOME || process.env.USERPROFILE ||
|
|
154316
|
-
return
|
|
155140
|
+
function getLegacyAgentDir(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
155141
|
+
return join54(home, ".pi", "agent");
|
|
154317
155142
|
}
|
|
154318
|
-
function getFusionAuthPath3(home = process.env.HOME || process.env.USERPROFILE ||
|
|
154319
|
-
return
|
|
155143
|
+
function getFusionAuthPath3(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
155144
|
+
return join54(getFusionAgentDir3(home), "auth.json");
|
|
154320
155145
|
}
|
|
154321
|
-
function getLegacyAuthPaths2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
155146
|
+
function getLegacyAuthPaths2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
154322
155147
|
return [
|
|
154323
|
-
|
|
154324
|
-
|
|
155148
|
+
join54(home, ".pi", "agent", "auth.json"),
|
|
155149
|
+
join54(home, ".pi", "auth.json")
|
|
154325
155150
|
];
|
|
154326
155151
|
}
|
|
154327
|
-
function getFusionModelsPath2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
154328
|
-
return
|
|
155152
|
+
function getFusionModelsPath2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
155153
|
+
return join54(getFusionAgentDir3(home), "models.json");
|
|
154329
155154
|
}
|
|
154330
|
-
function getLegacyModelsPaths2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
155155
|
+
function getLegacyModelsPaths2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
154331
155156
|
return [
|
|
154332
|
-
|
|
154333
|
-
|
|
155157
|
+
join54(home, ".pi", "agent", "models.json"),
|
|
155158
|
+
join54(home, ".pi", "models.json")
|
|
154334
155159
|
];
|
|
154335
155160
|
}
|
|
154336
|
-
function getModelRegistryModelsPath2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
155161
|
+
function getModelRegistryModelsPath2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
154337
155162
|
const fusionModelsPath = getFusionModelsPath2(home);
|
|
154338
|
-
if (
|
|
155163
|
+
if (existsSync36(fusionModelsPath)) {
|
|
154339
155164
|
return fusionModelsPath;
|
|
154340
155165
|
}
|
|
154341
|
-
return getLegacyModelsPaths2(home).find((modelsPath) =>
|
|
155166
|
+
return getLegacyModelsPaths2(home).find((modelsPath) => existsSync36(modelsPath)) ?? fusionModelsPath;
|
|
154342
155167
|
}
|
|
154343
155168
|
function readJsonObject5(path5) {
|
|
154344
|
-
if (!
|
|
155169
|
+
if (!existsSync36(path5)) {
|
|
154345
155170
|
return {};
|
|
154346
155171
|
}
|
|
154347
155172
|
try {
|
|
154348
|
-
const parsed = JSON.parse(
|
|
155173
|
+
const parsed = JSON.parse(readFileSync16(path5, "utf-8"));
|
|
154349
155174
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
154350
155175
|
} catch {
|
|
154351
155176
|
return {};
|
|
@@ -154354,18 +155179,18 @@ function readJsonObject5(path5) {
|
|
|
154354
155179
|
function hasPackageManagerSettings3(settings) {
|
|
154355
155180
|
return Array.isArray(settings.packages) || Array.isArray(settings.npmCommand);
|
|
154356
155181
|
}
|
|
154357
|
-
function getPackageManagerAgentDir2(home = process.env.HOME || process.env.USERPROFILE ||
|
|
155182
|
+
function getPackageManagerAgentDir2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
|
|
154358
155183
|
const fusionAgentDir = getFusionAgentDir3(home);
|
|
154359
155184
|
const legacyAgentDir = getLegacyAgentDir(home);
|
|
154360
|
-
const fusionSettings = readJsonObject5(
|
|
154361
|
-
const legacySettings = readJsonObject5(
|
|
154362
|
-
if (hasPackageManagerSettings3(fusionSettings) || !
|
|
155185
|
+
const fusionSettings = readJsonObject5(join54(fusionAgentDir, "settings.json"));
|
|
155186
|
+
const legacySettings = readJsonObject5(join54(legacyAgentDir, "settings.json"));
|
|
155187
|
+
if (hasPackageManagerSettings3(fusionSettings) || !existsSync36(legacyAgentDir)) {
|
|
154363
155188
|
return fusionAgentDir;
|
|
154364
155189
|
}
|
|
154365
155190
|
if (hasPackageManagerSettings3(legacySettings)) {
|
|
154366
155191
|
return legacyAgentDir;
|
|
154367
155192
|
}
|
|
154368
|
-
return
|
|
155193
|
+
return existsSync36(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
|
|
154369
155194
|
}
|
|
154370
155195
|
var init_auth_paths2 = __esm({
|
|
154371
155196
|
"src/commands/auth-paths.ts"() {
|
|
@@ -154523,7 +155348,7 @@ var init_project_context = __esm({
|
|
|
154523
155348
|
// src/commands/claude-skills.ts
|
|
154524
155349
|
import {
|
|
154525
155350
|
cpSync,
|
|
154526
|
-
existsSync as
|
|
155351
|
+
existsSync as existsSync37,
|
|
154527
155352
|
lstatSync as lstatSync2,
|
|
154528
155353
|
mkdirSync as mkdirSync7,
|
|
154529
155354
|
readlinkSync,
|
|
@@ -154531,7 +155356,7 @@ import {
|
|
|
154531
155356
|
symlinkSync,
|
|
154532
155357
|
unlinkSync
|
|
154533
155358
|
} from "node:fs";
|
|
154534
|
-
import { dirname as dirname22, join as
|
|
155359
|
+
import { dirname as dirname22, join as join55, resolve as resolve28 } from "node:path";
|
|
154535
155360
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
154536
155361
|
function isPiClaudeCliConfigured(globalSettings) {
|
|
154537
155362
|
if (!globalSettings || typeof globalSettings !== "object") {
|
|
@@ -154552,10 +155377,10 @@ function isPiClaudeCliConfigured(globalSettings) {
|
|
|
154552
155377
|
function resolveFusionSkillSource() {
|
|
154553
155378
|
const here = fileURLToPath6(import.meta.url);
|
|
154554
155379
|
const candidate = resolve28(dirname22(here), "..", "..", "skill", FUSION_SKILL_NAME);
|
|
154555
|
-
return
|
|
155380
|
+
return existsSync37(candidate) ? candidate : null;
|
|
154556
155381
|
}
|
|
154557
155382
|
function installFusionSkillIntoProject(projectPath, options = {}) {
|
|
154558
|
-
const target =
|
|
155383
|
+
const target = join55(projectPath, ".claude", "skills", FUSION_SKILL_NAME);
|
|
154559
155384
|
if (options.enabled === false) {
|
|
154560
155385
|
return { outcome: "skipped", target, reason: "pi-claude-cli not configured" };
|
|
154561
155386
|
}
|
|
@@ -154570,7 +155395,7 @@ function installFusionSkillIntoProject(projectPath, options = {}) {
|
|
|
154570
155395
|
try {
|
|
154571
155396
|
mkdirSync7(dirname22(target), { recursive: true });
|
|
154572
155397
|
let replaced = false;
|
|
154573
|
-
if (
|
|
155398
|
+
if (existsSync37(target) || isBrokenSymlink(target)) {
|
|
154574
155399
|
const stat12 = lstatSync2(target);
|
|
154575
155400
|
if (stat12.isSymbolicLink()) {
|
|
154576
155401
|
const current = safeReadlink(target);
|
|
@@ -154580,8 +155405,8 @@ function installFusionSkillIntoProject(projectPath, options = {}) {
|
|
|
154580
155405
|
unlinkSync(target);
|
|
154581
155406
|
replaced = true;
|
|
154582
155407
|
} else {
|
|
154583
|
-
const skillMd =
|
|
154584
|
-
if (!
|
|
155408
|
+
const skillMd = join55(target, "SKILL.md");
|
|
155409
|
+
if (!existsSync37(skillMd)) {
|
|
154585
155410
|
return {
|
|
154586
155411
|
outcome: "failed",
|
|
154587
155412
|
target,
|
|
@@ -154627,7 +155452,7 @@ function ensureFusionSkillForProjects(projects, options = { enabled: false }) {
|
|
|
154627
155452
|
if (!options.enabled) {
|
|
154628
155453
|
return projects.map((p) => ({
|
|
154629
155454
|
outcome: "skipped",
|
|
154630
|
-
target:
|
|
155455
|
+
target: join55(p.path, ".claude", "skills", FUSION_SKILL_NAME),
|
|
154631
155456
|
reason: "pi-claude-cli not configured"
|
|
154632
155457
|
}));
|
|
154633
155458
|
}
|
|
@@ -154640,7 +155465,7 @@ function isBrokenSymlink(path5) {
|
|
|
154640
155465
|
try {
|
|
154641
155466
|
const stat12 = lstatSync2(path5);
|
|
154642
155467
|
if (!stat12.isSymbolicLink()) return false;
|
|
154643
|
-
return !
|
|
155468
|
+
return !existsSync37(path5);
|
|
154644
155469
|
} catch {
|
|
154645
155470
|
return false;
|
|
154646
155471
|
}
|
|
@@ -154737,7 +155562,7 @@ var init_claude_skills_runner = __esm({
|
|
|
154737
155562
|
});
|
|
154738
155563
|
|
|
154739
155564
|
// src/commands/claude-cli-extension.ts
|
|
154740
|
-
import { existsSync as
|
|
155565
|
+
import { existsSync as existsSync38, readFileSync as readFileSync17 } from "node:fs";
|
|
154741
155566
|
import { createRequire as createRequire4 } from "node:module";
|
|
154742
155567
|
import { dirname as dirname23, resolve as resolve29 } from "node:path";
|
|
154743
155568
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
@@ -154746,7 +155571,7 @@ function resolveClaudeCliExtensionFromModuleUrl(moduleUrl) {
|
|
|
154746
155571
|
const here = dirname23(fileURLToPath7(moduleUrl));
|
|
154747
155572
|
for (const rel of ["pi-claude-cli", "../pi-claude-cli", "../../pi-claude-cli"]) {
|
|
154748
155573
|
const candidate = resolve29(here, rel, "package.json");
|
|
154749
|
-
if (
|
|
155574
|
+
if (existsSync38(candidate)) {
|
|
154750
155575
|
pkgJsonPath = candidate;
|
|
154751
155576
|
break;
|
|
154752
155577
|
}
|
|
@@ -154760,7 +155585,7 @@ function resolveClaudeCliExtensionFromModuleUrl(moduleUrl) {
|
|
|
154760
155585
|
}
|
|
154761
155586
|
let pkgJson;
|
|
154762
155587
|
try {
|
|
154763
|
-
pkgJson = JSON.parse(
|
|
155588
|
+
pkgJson = JSON.parse(readFileSync17(pkgJsonPath, "utf-8"));
|
|
154764
155589
|
} catch (err) {
|
|
154765
155590
|
return {
|
|
154766
155591
|
status: "error",
|
|
@@ -154782,7 +155607,7 @@ function resolveClaudeCliExtensionFromModuleUrl(moduleUrl) {
|
|
|
154782
155607
|
};
|
|
154783
155608
|
}
|
|
154784
155609
|
const entryPath = resolve29(dirname23(pkgJsonPath), rawEntry);
|
|
154785
|
-
if (!
|
|
155610
|
+
if (!existsSync38(entryPath)) {
|
|
154786
155611
|
return {
|
|
154787
155612
|
status: "missing-entry",
|
|
154788
155613
|
reason: `@fusion/pi-claude-cli extension file not found at ${entryPath}`
|
|
@@ -154833,12 +155658,12 @@ var init_claude_cli_extension = __esm({
|
|
|
154833
155658
|
});
|
|
154834
155659
|
|
|
154835
155660
|
// src/update-cache.ts
|
|
154836
|
-
import { readFileSync as
|
|
154837
|
-
import { join as
|
|
155661
|
+
import { readFileSync as readFileSync18 } from "node:fs";
|
|
155662
|
+
import { join as join56 } from "node:path";
|
|
154838
155663
|
function getCachedUpdateStatus(currentVersion) {
|
|
154839
155664
|
try {
|
|
154840
|
-
const cachePath =
|
|
154841
|
-
const raw =
|
|
155665
|
+
const cachePath = join56(resolveGlobalDir(), "update-check.json");
|
|
155666
|
+
const raw = readFileSync18(cachePath, "utf-8");
|
|
154842
155667
|
const parsed = JSON.parse(raw);
|
|
154843
155668
|
if (parsed.updateAvailable === true && typeof parsed.latestVersion === "string" && parsed.latestVersion.length > 0 && typeof parsed.currentVersion === "string" && parsed.currentVersion.length > 0) {
|
|
154844
155669
|
if (typeof currentVersion === "string" && currentVersion.length > 0 && parsed.currentVersion !== currentVersion) {
|
|
@@ -154869,7 +155694,7 @@ var init_update_cache = __esm({
|
|
|
154869
155694
|
});
|
|
154870
155695
|
|
|
154871
155696
|
// src/commands/self-extension.ts
|
|
154872
|
-
import { existsSync as
|
|
155697
|
+
import { existsSync as existsSync39, readFileSync as readFileSync19 } from "node:fs";
|
|
154873
155698
|
import { dirname as dirname24, resolve as resolve30 } from "node:path";
|
|
154874
155699
|
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
154875
155700
|
function resolveSelfExtension() {
|
|
@@ -154877,9 +155702,9 @@ function resolveSelfExtension() {
|
|
|
154877
155702
|
let pkgDir;
|
|
154878
155703
|
let cur = here;
|
|
154879
155704
|
for (let i = 0; i < 5; i++) {
|
|
154880
|
-
if (
|
|
155705
|
+
if (existsSync39(resolve30(cur, "package.json"))) {
|
|
154881
155706
|
try {
|
|
154882
|
-
const parsed = JSON.parse(
|
|
155707
|
+
const parsed = JSON.parse(readFileSync19(resolve30(cur, "package.json"), "utf-8"));
|
|
154883
155708
|
if (parsed.name === "@runfusion/fusion") {
|
|
154884
155709
|
pkgDir = cur;
|
|
154885
155710
|
break;
|
|
@@ -154896,12 +155721,12 @@ function resolveSelfExtension() {
|
|
|
154896
155721
|
}
|
|
154897
155722
|
let pkgJson;
|
|
154898
155723
|
try {
|
|
154899
|
-
pkgJson = JSON.parse(
|
|
155724
|
+
pkgJson = JSON.parse(readFileSync19(resolve30(pkgDir, "package.json"), "utf-8"));
|
|
154900
155725
|
} catch (err) {
|
|
154901
155726
|
return { status: "missing", reason: `Failed to read @runfusion/fusion package.json: ${err instanceof Error ? err.message : String(err)}` };
|
|
154902
155727
|
}
|
|
154903
155728
|
const srcEntry = resolve30(pkgDir, "src", "extension.ts");
|
|
154904
|
-
if (
|
|
155729
|
+
if (existsSync39(srcEntry)) {
|
|
154905
155730
|
return { status: "ok", path: srcEntry, packageVersion: pkgJson.version ?? "unknown" };
|
|
154906
155731
|
}
|
|
154907
155732
|
const extensions = pkgJson.pi?.extensions;
|
|
@@ -154913,7 +155738,7 @@ function resolveSelfExtension() {
|
|
|
154913
155738
|
return { status: "missing", reason: "@runfusion/fusion pi.extensions[0] is not a valid path string" };
|
|
154914
155739
|
}
|
|
154915
155740
|
const entryPath = resolve30(pkgDir, rawEntry);
|
|
154916
|
-
if (!
|
|
155741
|
+
if (!existsSync39(entryPath)) {
|
|
154917
155742
|
return { status: "missing", reason: `@runfusion/fusion extension file not found at ${entryPath}` };
|
|
154918
155743
|
}
|
|
154919
155744
|
return { status: "ok", path: entryPath, packageVersion: pkgJson.version ?? "unknown" };
|
|
@@ -155125,7 +155950,7 @@ var init_use_projects = __esm({
|
|
|
155125
155950
|
});
|
|
155126
155951
|
|
|
155127
155952
|
// src/commands/dashboard-tui/utils.ts
|
|
155128
|
-
import { spawn as
|
|
155953
|
+
import { spawn as spawn12 } from "node:child_process";
|
|
155129
155954
|
function isTTYAvailable() {
|
|
155130
155955
|
return Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
155131
155956
|
}
|
|
@@ -155138,7 +155963,7 @@ async function copyToClipboard(text) {
|
|
|
155138
155963
|
for (const { cmd, args } of candidates) {
|
|
155139
155964
|
const ok = await new Promise((resolve39) => {
|
|
155140
155965
|
try {
|
|
155141
|
-
const child =
|
|
155966
|
+
const child = spawn12(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
|
|
155142
155967
|
child.once("error", () => resolve39(false));
|
|
155143
155968
|
child.once("close", (code) => resolve39(code === 0));
|
|
155144
155969
|
child.stdin.end(text);
|
|
@@ -155165,7 +155990,7 @@ import { useState as useState2, useSyncExternalStore, useCallback as useCallback
|
|
|
155165
155990
|
import { Box, Text, useInput, useApp, useStdout } from "ink";
|
|
155166
155991
|
import Spinner from "ink-spinner";
|
|
155167
155992
|
import TextInput from "ink-text-input";
|
|
155168
|
-
import { spawn as
|
|
155993
|
+
import { spawn as spawn13 } from "node:child_process";
|
|
155169
155994
|
import { appendFileSync } from "node:fs";
|
|
155170
155995
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
155171
155996
|
function tuiDebug(tag, data) {
|
|
@@ -155191,7 +156016,7 @@ function openInBrowser(url) {
|
|
|
155191
156016
|
args = [url];
|
|
155192
156017
|
}
|
|
155193
156018
|
try {
|
|
155194
|
-
const child =
|
|
156019
|
+
const child = spawn13(cmd, args, { detached: true, stdio: "ignore" });
|
|
155195
156020
|
child.unref();
|
|
155196
156021
|
} catch {
|
|
155197
156022
|
}
|
|
@@ -159168,7 +159993,7 @@ __export(dashboard_exports, {
|
|
|
159168
159993
|
promptForPort: () => promptForPort,
|
|
159169
159994
|
runDashboard: () => runDashboard
|
|
159170
159995
|
});
|
|
159171
|
-
import { join as
|
|
159996
|
+
import { join as join57, resolve as pathResolve } from "node:path";
|
|
159172
159997
|
import { execFile as execFileCb } from "node:child_process";
|
|
159173
159998
|
import { promisify as promisify15 } from "node:util";
|
|
159174
159999
|
import { stat as stat11, readdir as readdir11, readFile as fsReadFile3 } from "node:fs/promises";
|
|
@@ -159324,7 +160149,7 @@ function setDiagnosticStoreListenerCheck(check) {
|
|
|
159324
160149
|
diagnosticStoreListenerCheck = check;
|
|
159325
160150
|
}
|
|
159326
160151
|
async function gitExec(cwd, args) {
|
|
159327
|
-
const { stdout } = await
|
|
160152
|
+
const { stdout } = await execFileAsync6("git", args, { cwd, maxBuffer: 4 * 1024 * 1024 });
|
|
159328
160153
|
return stdout;
|
|
159329
160154
|
}
|
|
159330
160155
|
async function buildGitStatus(projectPath) {
|
|
@@ -159506,7 +160331,7 @@ async function buildFileListDirectory(projectPath, relativePath) {
|
|
|
159506
160331
|
let size = 0;
|
|
159507
160332
|
let modifiedAt = (/* @__PURE__ */ new Date(0)).toISOString();
|
|
159508
160333
|
try {
|
|
159509
|
-
const s = await stat11(
|
|
160334
|
+
const s = await stat11(join57(absDir, d.name));
|
|
159510
160335
|
size = d.isDirectory() ? 0 : s.size;
|
|
159511
160336
|
modifiedAt = s.mtime.toISOString();
|
|
159512
160337
|
} catch {
|
|
@@ -159939,7 +160764,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
159939
160764
|
...claudeCliPaths
|
|
159940
160765
|
],
|
|
159941
160766
|
cwd,
|
|
159942
|
-
|
|
160767
|
+
join57(cwd, ".fusion", "disabled-auto-extension-discovery")
|
|
159943
160768
|
);
|
|
159944
160769
|
for (const { path: path5, error } of extensionsResult.errors) {
|
|
159945
160770
|
logSink.log(`Failed to load ${path5}: ${error}`, "extensions");
|
|
@@ -160756,7 +161581,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
160756
161581
|
listWorktrees: (projectPath) => buildGitWorktrees(projectPath),
|
|
160757
161582
|
push: async (projectPath) => {
|
|
160758
161583
|
try {
|
|
160759
|
-
const { stdout, stderr } = await
|
|
161584
|
+
const { stdout, stderr } = await execFileAsync6("git", ["push"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
|
|
160760
161585
|
return { success: true, output: (stdout + stderr).trim() };
|
|
160761
161586
|
} catch (err) {
|
|
160762
161587
|
const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
|
|
@@ -160765,7 +161590,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
160765
161590
|
},
|
|
160766
161591
|
fetch: async (projectPath) => {
|
|
160767
161592
|
try {
|
|
160768
|
-
const { stdout, stderr } = await
|
|
161593
|
+
const { stdout, stderr } = await execFileAsync6("git", ["fetch"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
|
|
160769
161594
|
return { success: true, output: (stdout + stderr).trim() };
|
|
160770
161595
|
} catch (err) {
|
|
160771
161596
|
const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
|
|
@@ -160894,7 +161719,7 @@ async function runDashboard(port, opts = {}) {
|
|
|
160894
161719
|
});
|
|
160895
161720
|
return { dispose };
|
|
160896
161721
|
}
|
|
160897
|
-
var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer,
|
|
161722
|
+
var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer, execFileAsync6, FILES_DENYLIST2, FILE_SIZE_LIMIT, BINARY_CHECK_BYTES, MAX_PREVIEW_LINES;
|
|
160898
161723
|
var init_dashboard = __esm({
|
|
160899
161724
|
"src/commands/dashboard.ts"() {
|
|
160900
161725
|
"use strict";
|
|
@@ -160973,7 +161798,7 @@ var init_dashboard = __esm({
|
|
|
160973
161798
|
}
|
|
160974
161799
|
}
|
|
160975
161800
|
};
|
|
160976
|
-
|
|
161801
|
+
execFileAsync6 = promisify15(execFileCb);
|
|
160977
161802
|
FILES_DENYLIST2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "target", "build"]);
|
|
160978
161803
|
FILE_SIZE_LIMIT = 1024 * 1024;
|
|
160979
161804
|
BINARY_CHECK_BYTES = 8 * 1024;
|
|
@@ -161428,7 +162253,7 @@ var serve_exports = {};
|
|
|
161428
162253
|
__export(serve_exports, {
|
|
161429
162254
|
runServe: () => runServe
|
|
161430
162255
|
});
|
|
161431
|
-
import { join as
|
|
162256
|
+
import { join as join58 } from "node:path";
|
|
161432
162257
|
import {
|
|
161433
162258
|
AuthStorage as AuthStorage3,
|
|
161434
162259
|
DefaultPackageManager as DefaultPackageManager3,
|
|
@@ -161700,7 +162525,7 @@ async function runServe(port, opts = {}) {
|
|
|
161700
162525
|
...claudeCliPaths
|
|
161701
162526
|
],
|
|
161702
162527
|
cwd,
|
|
161703
|
-
|
|
162528
|
+
join58(cwd, ".fusion", "disabled-auto-extension-discovery")
|
|
161704
162529
|
);
|
|
161705
162530
|
for (const { path: path5, error } of extensionsResult.errors) {
|
|
161706
162531
|
console.log(`[extensions] Failed to load ${path5}: ${error}`);
|
|
@@ -162037,7 +162862,7 @@ var daemon_exports = {};
|
|
|
162037
162862
|
__export(daemon_exports, {
|
|
162038
162863
|
runDaemon: () => runDaemon
|
|
162039
162864
|
});
|
|
162040
|
-
import { join as
|
|
162865
|
+
import { join as join59 } from "node:path";
|
|
162041
162866
|
import {
|
|
162042
162867
|
AuthStorage as AuthStorage4,
|
|
162043
162868
|
DefaultPackageManager as DefaultPackageManager4,
|
|
@@ -162307,7 +163132,7 @@ async function runDaemon(opts = {}) {
|
|
|
162307
163132
|
const extensionsResult = await discoverAndLoadExtensions4(
|
|
162308
163133
|
reconciledExtensionPaths,
|
|
162309
163134
|
cwd,
|
|
162310
|
-
|
|
163135
|
+
join59(cwd, ".fusion", "disabled-auto-extension-discovery")
|
|
162311
163136
|
);
|
|
162312
163137
|
for (const { path: path5, error } of extensionsResult.errors) {
|
|
162313
163138
|
console.log(`[extensions] Failed to load ${path5}: ${error}`);
|
|
@@ -162508,13 +163333,13 @@ var desktop_exports = {};
|
|
|
162508
163333
|
__export(desktop_exports, {
|
|
162509
163334
|
runDesktop: () => runDesktop
|
|
162510
163335
|
});
|
|
162511
|
-
import { spawn as
|
|
163336
|
+
import { spawn as spawn14 } from "node:child_process";
|
|
162512
163337
|
import { once as once2 } from "node:events";
|
|
162513
|
-
import { join as
|
|
163338
|
+
import { join as join60 } from "node:path";
|
|
162514
163339
|
import { createRequire as createRequire5 } from "node:module";
|
|
162515
163340
|
function runCommand(command, args, cwd) {
|
|
162516
163341
|
return new Promise((resolve39, reject2) => {
|
|
162517
|
-
const child =
|
|
163342
|
+
const child = spawn14(command, args, {
|
|
162518
163343
|
cwd,
|
|
162519
163344
|
stdio: "inherit",
|
|
162520
163345
|
env: process.env
|
|
@@ -162589,7 +163414,7 @@ async function runDesktop(options = {}) {
|
|
|
162589
163414
|
}
|
|
162590
163415
|
const runtime = await startDashboardRuntime(rootDir, Boolean(options.paused));
|
|
162591
163416
|
const electronBinary = resolveElectronBinary();
|
|
162592
|
-
const desktopEntry =
|
|
163417
|
+
const desktopEntry = join60(rootDir, "packages", "desktop", "dist", "main.js");
|
|
162593
163418
|
const electronArgs = ["--enable-source-maps", desktopEntry, ...options.dev ? ["--dev"] : []];
|
|
162594
163419
|
const electronEnv = {
|
|
162595
163420
|
...process.env,
|
|
@@ -162599,7 +163424,7 @@ async function runDesktop(options = {}) {
|
|
|
162599
163424
|
electronEnv.FUSION_DASHBOARD_URL = process.env.FUSION_DASHBOARD_URL ?? "http://localhost:5173";
|
|
162600
163425
|
electronEnv.NODE_ENV = "development";
|
|
162601
163426
|
}
|
|
162602
|
-
const electronProcess =
|
|
163427
|
+
const electronProcess = spawn14(electronBinary, electronArgs, {
|
|
162603
163428
|
cwd: rootDir,
|
|
162604
163429
|
stdio: "inherit",
|
|
162605
163430
|
env: electronEnv
|
|
@@ -162674,8 +163499,8 @@ __export(task_exports, {
|
|
|
162674
163499
|
runTaskUpdate: () => runTaskUpdate
|
|
162675
163500
|
});
|
|
162676
163501
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
162677
|
-
import { watchFile, unwatchFile, statSync as statSync6, existsSync as
|
|
162678
|
-
import { basename as basename17, join as
|
|
163502
|
+
import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync40, readFileSync as readFileSync20 } from "node:fs";
|
|
163503
|
+
import { basename as basename17, join as join61 } from "node:path";
|
|
162679
163504
|
function getGitHubIssueUrl(sourceMetadata) {
|
|
162680
163505
|
if (!sourceMetadata || typeof sourceMetadata !== "object") return void 0;
|
|
162681
163506
|
const issueUrl = sourceMetadata.issueUrl;
|
|
@@ -162975,8 +163800,8 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
162975
163800
|
printEntries(filteredEntries);
|
|
162976
163801
|
if (options.follow) {
|
|
162977
163802
|
const projectPath = projectContext?.projectPath ?? process.cwd();
|
|
162978
|
-
const logPath =
|
|
162979
|
-
if (!
|
|
163803
|
+
const logPath = join61(projectPath, ".fusion", "tasks", id, "agent.log");
|
|
163804
|
+
if (!existsSync40(logPath)) {
|
|
162980
163805
|
console.log(`
|
|
162981
163806
|
Waiting for log file to be created...`);
|
|
162982
163807
|
}
|
|
@@ -163005,7 +163830,7 @@ async function runTaskLogs(id, options = {}, projectName) {
|
|
|
163005
163830
|
lastPosition = 0;
|
|
163006
163831
|
}
|
|
163007
163832
|
if (stats.size > lastPosition) {
|
|
163008
|
-
const content =
|
|
163833
|
+
const content = readFileSync20(logPath, "utf-8");
|
|
163009
163834
|
const lines = content.slice(lastPosition).split("\n");
|
|
163010
163835
|
for (const line of lines) {
|
|
163011
163836
|
if (!line.trim()) continue;
|
|
@@ -164253,7 +165078,7 @@ __export(settings_export_exports, {
|
|
|
164253
165078
|
runSettingsExport: () => runSettingsExport
|
|
164254
165079
|
});
|
|
164255
165080
|
import { writeFile as writeFile18 } from "node:fs/promises";
|
|
164256
|
-
import { resolve as resolve31, join as
|
|
165081
|
+
import { resolve as resolve31, join as join62 } from "node:path";
|
|
164257
165082
|
async function runSettingsExport(options = {}) {
|
|
164258
165083
|
const scope = options.scope ?? "both";
|
|
164259
165084
|
const project = options.projectName ? await resolveProject(options.projectName) : void 0;
|
|
@@ -164267,7 +165092,7 @@ async function runSettingsExport(options = {}) {
|
|
|
164267
165092
|
targetPath = resolve31(outputPath);
|
|
164268
165093
|
} else {
|
|
164269
165094
|
const filename = generateExportFilename();
|
|
164270
|
-
targetPath =
|
|
165095
|
+
targetPath = join62(process.cwd(), filename);
|
|
164271
165096
|
}
|
|
164272
165097
|
const jsonContent = JSON.stringify(exportData, null, 2);
|
|
164273
165098
|
await writeFile18(targetPath, jsonContent);
|
|
@@ -164313,7 +165138,7 @@ var settings_import_exports = {};
|
|
|
164313
165138
|
__export(settings_import_exports, {
|
|
164314
165139
|
runSettingsImport: () => runSettingsImport
|
|
164315
165140
|
});
|
|
164316
|
-
import { existsSync as
|
|
165141
|
+
import { existsSync as existsSync41 } from "node:fs";
|
|
164317
165142
|
import { resolve as resolve32 } from "node:path";
|
|
164318
165143
|
async function runSettingsImport(filePath, options = {}) {
|
|
164319
165144
|
const scope = options.scope ?? "both";
|
|
@@ -164324,7 +165149,7 @@ async function runSettingsImport(filePath, options = {}) {
|
|
|
164324
165149
|
const skipConfirm = options.yes ?? false;
|
|
164325
165150
|
try {
|
|
164326
165151
|
const resolvedPath = resolve32(filePath);
|
|
164327
|
-
if (!
|
|
165152
|
+
if (!existsSync41(resolvedPath)) {
|
|
164328
165153
|
console.error(`Error: File not found: ${filePath}`);
|
|
164329
165154
|
process.exit(1);
|
|
164330
165155
|
}
|
|
@@ -164421,7 +165246,7 @@ __export(git_exports, {
|
|
|
164421
165246
|
runGitPush: () => runGitPush,
|
|
164422
165247
|
runGitStatus: () => runGitStatus
|
|
164423
165248
|
});
|
|
164424
|
-
import { exec as
|
|
165249
|
+
import { exec as exec11 } from "node:child_process";
|
|
164425
165250
|
import { promisify as promisify16 } from "node:util";
|
|
164426
165251
|
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
164427
165252
|
async function resolveGitCwd(projectName) {
|
|
@@ -164696,7 +165521,7 @@ var init_git = __esm({
|
|
|
164696
165521
|
"src/commands/git.ts"() {
|
|
164697
165522
|
"use strict";
|
|
164698
165523
|
init_project_context();
|
|
164699
|
-
execAsync10 = promisify16(
|
|
165524
|
+
execAsync10 = promisify16(exec11);
|
|
164700
165525
|
}
|
|
164701
165526
|
});
|
|
164702
165527
|
|
|
@@ -164797,7 +165622,7 @@ var init_backup2 = __esm({
|
|
|
164797
165622
|
});
|
|
164798
165623
|
|
|
164799
165624
|
// src/project-resolver.ts
|
|
164800
|
-
import { existsSync as
|
|
165625
|
+
import { existsSync as existsSync42, statSync as statSync7 } from "node:fs";
|
|
164801
165626
|
import { basename as basename18, dirname as dirname25, resolve as resolve33, normalize as normalize5 } from "node:path";
|
|
164802
165627
|
import { createInterface as createInterface5 } from "node:readline/promises";
|
|
164803
165628
|
async function getCentralCore() {
|
|
@@ -164873,7 +165698,7 @@ async function resolveProject2(options = {}) {
|
|
|
164873
165698
|
{ searchedName: options.project, availableProjects: projects.map((p) => p.name) }
|
|
164874
165699
|
);
|
|
164875
165700
|
}
|
|
164876
|
-
if (!
|
|
165701
|
+
if (!existsSync42(match.path)) {
|
|
164877
165702
|
throw new ProjectResolutionError(
|
|
164878
165703
|
`Project "${match.name}" is registered but the directory no longer exists: ${match.path}
|
|
164879
165704
|
|
|
@@ -164891,7 +165716,7 @@ Run \`fn project remove ` + match.name + "` to clean up the registry entry.",
|
|
|
164891
165716
|
const normalizedKbDir = normalize5(fusionDir);
|
|
164892
165717
|
const match = allProjects2.find((p) => normalize5(p.path) === normalizedKbDir);
|
|
164893
165718
|
if (match) {
|
|
164894
|
-
if (!
|
|
165719
|
+
if (!existsSync42(match.path)) {
|
|
164895
165720
|
throw new ProjectResolutionError(
|
|
164896
165721
|
`Project "${match.name}" is registered but the directory no longer exists: ${match.path}
|
|
164897
165722
|
|
|
@@ -164956,7 +165781,7 @@ Run \`fn project add ` + fusionDir + "` to register it, or use --project <name>.
|
|
|
164956
165781
|
}
|
|
164957
165782
|
if (allProjects.length === 1) {
|
|
164958
165783
|
const project = allProjects[0];
|
|
164959
|
-
if (!
|
|
165784
|
+
if (!existsSync42(project.path)) {
|
|
164960
165785
|
throw new ProjectResolutionError(
|
|
164961
165786
|
`The only registered project "${project.name}" has a missing directory: ${project.path}
|
|
164962
165787
|
|
|
@@ -165396,8 +166221,8 @@ __export(project_exports, {
|
|
|
165396
166221
|
runProjectSetDefault: () => runProjectSetDefault,
|
|
165397
166222
|
runProjectShow: () => runProjectShow
|
|
165398
166223
|
});
|
|
165399
|
-
import { resolve as resolve34, isAbsolute as
|
|
165400
|
-
import { existsSync as
|
|
166224
|
+
import { resolve as resolve34, isAbsolute as isAbsolute19, relative as relative14, basename as basename19 } from "node:path";
|
|
166225
|
+
import { existsSync as existsSync43, statSync as statSync8 } from "node:fs";
|
|
165401
166226
|
import { createInterface as createInterface7 } from "node:readline/promises";
|
|
165402
166227
|
function formatDisplayPath(projectPath) {
|
|
165403
166228
|
const rel = relative14(process.cwd(), projectPath);
|
|
@@ -165525,8 +166350,8 @@ async function runProjectAdd(name, path5, options = {}) {
|
|
|
165525
166350
|
const pathInput = await rl.question(` Project path [${defaultPath}]: `);
|
|
165526
166351
|
projectPath = pathInput.trim() || defaultPath;
|
|
165527
166352
|
}
|
|
165528
|
-
const absolutePath2 =
|
|
165529
|
-
if (!
|
|
166353
|
+
const absolutePath2 = isAbsolute19(projectPath) ? projectPath : resolve34(process.cwd(), projectPath);
|
|
166354
|
+
if (!existsSync43(absolutePath2)) {
|
|
165530
166355
|
console.error(`
|
|
165531
166356
|
\u2717 Path does not exist: ${projectPath}`);
|
|
165532
166357
|
rl.close();
|
|
@@ -165539,7 +166364,7 @@ async function runProjectAdd(name, path5, options = {}) {
|
|
|
165539
166364
|
process.exit(1);
|
|
165540
166365
|
}
|
|
165541
166366
|
const kbDbPath2 = resolve34(absolutePath2, ".fusion", "fusion.db");
|
|
165542
|
-
if (!
|
|
166367
|
+
if (!existsSync43(kbDbPath2) && !options.force) {
|
|
165543
166368
|
console.log(`
|
|
165544
166369
|
No fn project found at ${formatDisplayPath(absolutePath2)}`);
|
|
165545
166370
|
const init = await rl.question(" Initialize fn here first? [Y/n] ");
|
|
@@ -165570,8 +166395,8 @@ async function runProjectAdd(name, path5, options = {}) {
|
|
|
165570
166395
|
console.error(" Name must be 1-64 characters and contain only: a-z, A-Z, 0-9, _, -\n");
|
|
165571
166396
|
process.exit(1);
|
|
165572
166397
|
}
|
|
165573
|
-
const absolutePath =
|
|
165574
|
-
if (!
|
|
166398
|
+
const absolutePath = isAbsolute19(projectPath) ? projectPath : resolve34(process.cwd(), projectPath);
|
|
166399
|
+
if (!existsSync43(absolutePath)) {
|
|
165575
166400
|
console.error(`
|
|
165576
166401
|
\u2717 Path does not exist: ${projectPath}
|
|
165577
166402
|
`);
|
|
@@ -165584,7 +166409,7 @@ async function runProjectAdd(name, path5, options = {}) {
|
|
|
165584
166409
|
process.exit(1);
|
|
165585
166410
|
}
|
|
165586
166411
|
const kbDbPath = resolve34(absolutePath, ".fusion", "fusion.db");
|
|
165587
|
-
if (!
|
|
166412
|
+
if (!existsSync43(kbDbPath) && !options.force) {
|
|
165588
166413
|
console.error(`
|
|
165589
166414
|
\u2717 No fn project found at ${formatDisplayPath(absolutePath)}`);
|
|
165590
166415
|
console.error(" Run `fn init` first to initialize the project.\n");
|
|
@@ -165840,21 +166665,21 @@ var init_project = __esm({
|
|
|
165840
166665
|
});
|
|
165841
166666
|
|
|
165842
166667
|
// src/commands/skill-installation.ts
|
|
165843
|
-
import { cpSync as cpSync2, existsSync as
|
|
165844
|
-
import { homedir as
|
|
165845
|
-
import { dirname as dirname26, join as
|
|
166668
|
+
import { cpSync as cpSync2, existsSync as existsSync44, mkdirSync as mkdirSync8 } from "node:fs";
|
|
166669
|
+
import { homedir as homedir10 } from "node:os";
|
|
166670
|
+
import { dirname as dirname26, join as join63, resolve as resolve35 } from "node:path";
|
|
165846
166671
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
165847
|
-
function getSupportedSkillInstallTargets(homeDir = process.env.HOME || process.env.USERPROFILE ||
|
|
166672
|
+
function getSupportedSkillInstallTargets(homeDir = process.env.HOME || process.env.USERPROFILE || homedir10()) {
|
|
165848
166673
|
return [
|
|
165849
|
-
{ client: "claude", targetDir:
|
|
165850
|
-
{ client: "codex", targetDir:
|
|
165851
|
-
{ client: "gemini", targetDir:
|
|
166674
|
+
{ client: "claude", targetDir: join63(homeDir, ".claude", "skills", FUSION_SKILL_NAME2) },
|
|
166675
|
+
{ client: "codex", targetDir: join63(homeDir, ".codex", "skills", FUSION_SKILL_NAME2) },
|
|
166676
|
+
{ client: "gemini", targetDir: join63(homeDir, ".gemini", "skills", FUSION_SKILL_NAME2) }
|
|
165852
166677
|
];
|
|
165853
166678
|
}
|
|
165854
166679
|
function resolveBundledFusionSkillSource() {
|
|
165855
166680
|
const here = fileURLToPath9(import.meta.url);
|
|
165856
166681
|
const source = resolve35(dirname26(here), "..", "..", "skill", FUSION_SKILL_NAME2);
|
|
165857
|
-
return
|
|
166682
|
+
return existsSync44(source) ? source : null;
|
|
165858
166683
|
}
|
|
165859
166684
|
function installBundledFusionSkill(options = {}) {
|
|
165860
166685
|
const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
|
|
@@ -165872,7 +166697,7 @@ function installBundledFusionSkill(options = {}) {
|
|
|
165872
166697
|
}
|
|
165873
166698
|
const results = targets.map((target) => {
|
|
165874
166699
|
try {
|
|
165875
|
-
if (
|
|
166700
|
+
if (existsSync44(target.targetDir)) {
|
|
165876
166701
|
return {
|
|
165877
166702
|
client: target.client,
|
|
165878
166703
|
targetDir: target.targetDir,
|
|
@@ -165911,17 +166736,17 @@ var init_exports = {};
|
|
|
165911
166736
|
__export(init_exports, {
|
|
165912
166737
|
runInit: () => runInit
|
|
165913
166738
|
});
|
|
165914
|
-
import { existsSync as
|
|
165915
|
-
import { join as
|
|
165916
|
-
import { exec as
|
|
166739
|
+
import { existsSync as existsSync45, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync21 } from "node:fs";
|
|
166740
|
+
import { join as join64, resolve as resolve36, basename as basename20 } from "node:path";
|
|
166741
|
+
import { exec as exec12 } from "node:child_process";
|
|
165917
166742
|
import { promisify as promisify17 } from "node:util";
|
|
165918
166743
|
async function runInit(options = {}) {
|
|
165919
166744
|
const cwd = options.path ? resolve36(options.path) : process.cwd();
|
|
165920
|
-
const fusionDir =
|
|
165921
|
-
const dbPath =
|
|
165922
|
-
const hasDbPath =
|
|
166745
|
+
const fusionDir = join64(cwd, ".fusion");
|
|
166746
|
+
const dbPath = join64(fusionDir, "fusion.db");
|
|
166747
|
+
const hasDbPath = existsSync45(dbPath);
|
|
165923
166748
|
const hasValidDb = hasDbPath && isValidSqliteDatabaseFile(dbPath);
|
|
165924
|
-
if (
|
|
166749
|
+
if (existsSync45(fusionDir) && hasDbPath && hasValidDb) {
|
|
165925
166750
|
const central2 = new CentralCore();
|
|
165926
166751
|
await central2.init();
|
|
165927
166752
|
const existing = await central2.getProjectByPath(cwd);
|
|
@@ -165943,7 +166768,7 @@ async function runInit(options = {}) {
|
|
|
165943
166768
|
await central2.close();
|
|
165944
166769
|
return;
|
|
165945
166770
|
}
|
|
165946
|
-
if (
|
|
166771
|
+
if (existsSync45(fusionDir) && hasDbPath && !hasValidDb) {
|
|
165947
166772
|
throw new Error(
|
|
165948
166773
|
`Existing database at ${dbPath} is not a valid SQLite database. Restore it from .fusion/backups or move it aside before re-running fn init.`
|
|
165949
166774
|
);
|
|
@@ -165951,7 +166776,7 @@ async function runInit(options = {}) {
|
|
|
165951
166776
|
const projectName = options.name ?? await detectProjectName(cwd);
|
|
165952
166777
|
console.log(`Initializing fn project: "${projectName}"`);
|
|
165953
166778
|
console.log(` Path: ${cwd}`);
|
|
165954
|
-
if (!
|
|
166779
|
+
if (!existsSync45(fusionDir)) {
|
|
165955
166780
|
mkdirSync9(fusionDir, { recursive: true });
|
|
165956
166781
|
console.log(` \u2713 Created .fusion/ directory`);
|
|
165957
166782
|
}
|
|
@@ -165964,7 +166789,7 @@ async function runInit(options = {}) {
|
|
|
165964
166789
|
}
|
|
165965
166790
|
await addLocalStorageToGitignore(cwd);
|
|
165966
166791
|
await warnIfQmdMissing();
|
|
165967
|
-
if (!
|
|
166792
|
+
if (!existsSync45(dbPath)) {
|
|
165968
166793
|
writeFileSync3(dbPath, "");
|
|
165969
166794
|
console.log(` \u2713 Created fusion.db`);
|
|
165970
166795
|
}
|
|
@@ -166014,7 +166839,7 @@ async function runInit(options = {}) {
|
|
|
166014
166839
|
}
|
|
166015
166840
|
}
|
|
166016
166841
|
async function detectProjectName(dir2) {
|
|
166017
|
-
if (!
|
|
166842
|
+
if (!existsSync45(join64(dir2, ".git"))) {
|
|
166018
166843
|
return basename20(dir2) || "my-project";
|
|
166019
166844
|
}
|
|
166020
166845
|
try {
|
|
@@ -166034,11 +166859,11 @@ async function detectProjectName(dir2) {
|
|
|
166034
166859
|
return basename20(dir2) || "my-project";
|
|
166035
166860
|
}
|
|
166036
166861
|
async function addLocalStorageToGitignore(cwd) {
|
|
166037
|
-
const gitignorePath =
|
|
166862
|
+
const gitignorePath = join64(cwd, ".gitignore");
|
|
166038
166863
|
let content = "";
|
|
166039
|
-
if (
|
|
166864
|
+
if (existsSync45(gitignorePath)) {
|
|
166040
166865
|
try {
|
|
166041
|
-
content =
|
|
166866
|
+
content = readFileSync21(gitignorePath, "utf-8");
|
|
166042
166867
|
} catch {
|
|
166043
166868
|
}
|
|
166044
166869
|
}
|
|
@@ -166077,8 +166902,8 @@ async function initializeGitRepo(cwd) {
|
|
|
166077
166902
|
}
|
|
166078
166903
|
await ensureGitConfig(cwd, "user.name", "Fusion");
|
|
166079
166904
|
await ensureGitConfig(cwd, "user.email", "noreply@runfusion.ai");
|
|
166080
|
-
const gitkeepPath =
|
|
166081
|
-
if (!
|
|
166905
|
+
const gitkeepPath = join64(cwd, ".gitkeep");
|
|
166906
|
+
if (!existsSync45(gitkeepPath)) {
|
|
166082
166907
|
writeFileSync3(gitkeepPath, "\n");
|
|
166083
166908
|
}
|
|
166084
166909
|
await execAsync11("git add .gitkeep", { cwd, timeout: 1e4 });
|
|
@@ -166129,7 +166954,7 @@ var init_init = __esm({
|
|
|
166129
166954
|
init_claude_skills_runner();
|
|
166130
166955
|
init_git();
|
|
166131
166956
|
init_skill_installation();
|
|
166132
|
-
execAsync11 = promisify17(
|
|
166957
|
+
execAsync11 = promisify17(exec12);
|
|
166133
166958
|
}
|
|
166134
166959
|
});
|
|
166135
166960
|
|
|
@@ -166216,7 +167041,7 @@ var agent_import_exports = {};
|
|
|
166216
167041
|
__export(agent_import_exports, {
|
|
166217
167042
|
runAgentImport: () => runAgentImport
|
|
166218
167043
|
});
|
|
166219
|
-
import { existsSync as
|
|
167044
|
+
import { existsSync as existsSync46, mkdirSync as mkdirSync10, readFileSync as readFileSync22, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
|
|
166220
167045
|
import { resolve as resolve37 } from "node:path";
|
|
166221
167046
|
function slugifyPathSegment(input) {
|
|
166222
167047
|
if (!input || typeof input !== "string") {
|
|
@@ -166275,7 +167100,7 @@ async function importSkillsToProject(projectPath, skills, companySlug, dryRun) {
|
|
|
166275
167100
|
const skillSlug = slugifyPathSegment(skill.name);
|
|
166276
167101
|
const skillDir = resolve37(baseSkillsDir, skillSlug);
|
|
166277
167102
|
const skillPath = resolve37(skillDir, "SKILL.md");
|
|
166278
|
-
if (
|
|
167103
|
+
if (existsSync46(skillPath)) {
|
|
166279
167104
|
result.skipped.push(skill.name);
|
|
166280
167105
|
continue;
|
|
166281
167106
|
}
|
|
@@ -166348,7 +167173,7 @@ async function runAgentImport(source, options) {
|
|
|
166348
167173
|
const dryRun = options?.dryRun ?? false;
|
|
166349
167174
|
const skipExisting = options?.skipExisting ?? false;
|
|
166350
167175
|
const sourcePath = resolve37(source);
|
|
166351
|
-
if (!
|
|
167176
|
+
if (!existsSync46(sourcePath)) {
|
|
166352
167177
|
console.error(`Path not found: ${sourcePath}`);
|
|
166353
167178
|
process.exit(1);
|
|
166354
167179
|
}
|
|
@@ -166394,7 +167219,7 @@ async function runAgentImport(source, options) {
|
|
|
166394
167219
|
isPackageImport = true;
|
|
166395
167220
|
({ items: importItems, result } = prepareAgentCompaniesImport(pkg, conversionOptions));
|
|
166396
167221
|
} else if (sourcePath.endsWith(".md")) {
|
|
166397
|
-
const content =
|
|
167222
|
+
const content = readFileSync22(sourcePath, "utf-8");
|
|
166398
167223
|
const { manifest } = parseSingleAgentManifest(content);
|
|
166399
167224
|
const pkg = {
|
|
166400
167225
|
company: void 0,
|
|
@@ -166743,8 +167568,8 @@ __export(plugin_exports, {
|
|
|
166743
167568
|
runPluginList: () => runPluginList,
|
|
166744
167569
|
runPluginUninstall: () => runPluginUninstall
|
|
166745
167570
|
});
|
|
166746
|
-
import { existsSync as
|
|
166747
|
-
import { join as
|
|
167571
|
+
import { existsSync as existsSync47 } from "node:fs";
|
|
167572
|
+
import { join as join65 } from "node:path";
|
|
166748
167573
|
import { readFile as readFile23 } from "node:fs/promises";
|
|
166749
167574
|
import * as readline from "node:readline";
|
|
166750
167575
|
async function getProjectPath6(projectName) {
|
|
@@ -166782,8 +167607,8 @@ async function createPluginLoader(pluginStore, projectName) {
|
|
|
166782
167607
|
return { store: pluginStore, loader };
|
|
166783
167608
|
}
|
|
166784
167609
|
async function loadManifestFromPath(pluginPath) {
|
|
166785
|
-
const manifestPath =
|
|
166786
|
-
if (!
|
|
167610
|
+
const manifestPath = join65(pluginPath, "manifest.json");
|
|
167611
|
+
if (!existsSync47(manifestPath)) {
|
|
166787
167612
|
throw new Error(`Plugin manifest not found at: ${manifestPath}`);
|
|
166788
167613
|
}
|
|
166789
167614
|
const content = await readFile23(manifestPath, "utf-8");
|
|
@@ -166841,7 +167666,7 @@ async function runPluginInstall(source, options) {
|
|
|
166841
167666
|
console.error("Please provide a local path to the plugin directory.");
|
|
166842
167667
|
process.exit(1);
|
|
166843
167668
|
}
|
|
166844
|
-
if (!
|
|
167669
|
+
if (!existsSync47(source)) {
|
|
166845
167670
|
console.error(`Plugin path does not exist: ${source}`);
|
|
166846
167671
|
process.exit(1);
|
|
166847
167672
|
}
|
|
@@ -166973,8 +167798,8 @@ var plugin_scaffold_exports = {};
|
|
|
166973
167798
|
__export(plugin_scaffold_exports, {
|
|
166974
167799
|
runPluginCreate: () => runPluginCreate
|
|
166975
167800
|
});
|
|
166976
|
-
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, existsSync as
|
|
166977
|
-
import { join as
|
|
167801
|
+
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, existsSync as existsSync48 } from "node:fs";
|
|
167802
|
+
import { join as join66 } from "node:path";
|
|
166978
167803
|
function toTitleCase(str) {
|
|
166979
167804
|
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
166980
167805
|
}
|
|
@@ -167107,24 +167932,24 @@ async function runPluginCreate(name, options) {
|
|
|
167107
167932
|
process.exit(1);
|
|
167108
167933
|
}
|
|
167109
167934
|
const targetDir = options?.output ?? name;
|
|
167110
|
-
const targetPath =
|
|
167111
|
-
if (
|
|
167935
|
+
const targetPath = join66(process.cwd(), targetDir);
|
|
167936
|
+
if (existsSync48(targetPath)) {
|
|
167112
167937
|
console.error(`Error: Directory '${targetDir}' already exists.`);
|
|
167113
167938
|
console.error("Please choose a different name or remove the existing directory.");
|
|
167114
167939
|
process.exit(1);
|
|
167115
167940
|
}
|
|
167116
167941
|
try {
|
|
167117
167942
|
mkdirSync11(targetPath, { recursive: true });
|
|
167118
|
-
mkdirSync11(
|
|
167119
|
-
writeFileSync5(
|
|
167120
|
-
writeFileSync5(
|
|
167121
|
-
writeFileSync5(
|
|
167122
|
-
writeFileSync5(
|
|
167943
|
+
mkdirSync11(join66(targetPath, "src", "__tests__"), { recursive: true });
|
|
167944
|
+
writeFileSync5(join66(targetPath, "package.json"), generatePackageJson(name));
|
|
167945
|
+
writeFileSync5(join66(targetPath, "tsconfig.json"), generateTsconfig());
|
|
167946
|
+
writeFileSync5(join66(targetPath, "vitest.config.ts"), generateVitestConfig());
|
|
167947
|
+
writeFileSync5(join66(targetPath, "src", "index.ts"), generateIndexTs(name));
|
|
167123
167948
|
writeFileSync5(
|
|
167124
|
-
|
|
167949
|
+
join66(targetPath, "src", "__tests__", "index.test.ts"),
|
|
167125
167950
|
generateTestTs(name)
|
|
167126
167951
|
);
|
|
167127
|
-
writeFileSync5(
|
|
167952
|
+
writeFileSync5(join66(targetPath, "README.md"), generateReadme(name));
|
|
167128
167953
|
} catch (err) {
|
|
167129
167954
|
console.error(
|
|
167130
167955
|
`Error creating plugin files: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -167158,7 +167983,7 @@ __export(skills_exports, {
|
|
|
167158
167983
|
runSkillsSearch: () => runSkillsSearch,
|
|
167159
167984
|
searchSkills: () => searchSkills
|
|
167160
167985
|
});
|
|
167161
|
-
import { spawn as
|
|
167986
|
+
import { spawn as spawn15 } from "node:child_process";
|
|
167162
167987
|
async function searchSkills(query, limit = 10) {
|
|
167163
167988
|
const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
|
|
167164
167989
|
try {
|
|
@@ -167236,7 +168061,7 @@ async function runSkillsInstall(args, options) {
|
|
|
167236
168061
|
npxArgs.push("--skill", options.skill);
|
|
167237
168062
|
}
|
|
167238
168063
|
npxArgs.push("-y", "-a", "pi");
|
|
167239
|
-
const child =
|
|
168064
|
+
const child = spawn15("npx", npxArgs, {
|
|
167240
168065
|
cwd: process.cwd(),
|
|
167241
168066
|
stdio: "inherit",
|
|
167242
168067
|
shell: true
|
|
@@ -167274,21 +168099,21 @@ __export(native_patch_exports, {
|
|
|
167274
168099
|
isTerminalAvailable: () => isTerminalAvailable,
|
|
167275
168100
|
setupNativeResolution: () => setupNativeResolution
|
|
167276
168101
|
});
|
|
167277
|
-
import { join as
|
|
167278
|
-
import { existsSync as
|
|
168102
|
+
import { join as join67, basename as basename21, dirname as dirname27 } from "node:path";
|
|
168103
|
+
import { existsSync as existsSync49, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
|
|
167279
168104
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
167280
168105
|
function findStagedNativeDir2() {
|
|
167281
168106
|
const platform3 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
|
|
167282
168107
|
const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
|
|
167283
168108
|
const prebuildName = `${platform3}-${arch}`;
|
|
167284
168109
|
const execDir = dirname27(process.execPath);
|
|
167285
|
-
const nextToBinary =
|
|
167286
|
-
if (
|
|
168110
|
+
const nextToBinary = join67(execDir, "runtime", prebuildName);
|
|
168111
|
+
if (existsSync49(join67(nextToBinary, "pty.node"))) {
|
|
167287
168112
|
return nextToBinary;
|
|
167288
168113
|
}
|
|
167289
168114
|
if (process.env.FUSION_RUNTIME_DIR) {
|
|
167290
|
-
const envPath =
|
|
167291
|
-
if (
|
|
168115
|
+
const envPath = join67(process.env.FUSION_RUNTIME_DIR, prebuildName);
|
|
168116
|
+
if (existsSync49(join67(envPath, "pty.node"))) {
|
|
167292
168117
|
return envPath;
|
|
167293
168118
|
}
|
|
167294
168119
|
}
|
|
@@ -167298,11 +168123,11 @@ function cleanupStaleBunfsLinks() {
|
|
|
167298
168123
|
if (process.platform === "win32") return;
|
|
167299
168124
|
const bunfsRoot = "/$bunfs/root";
|
|
167300
168125
|
try {
|
|
167301
|
-
if (
|
|
168126
|
+
if (existsSync49(bunfsRoot)) {
|
|
167302
168127
|
const stats = lstatSync3(bunfsRoot);
|
|
167303
168128
|
if (stats.isSymbolicLink()) {
|
|
167304
168129
|
const target = readlinkSync2(bunfsRoot);
|
|
167305
|
-
if (target.includes("fn-bunfs-") && !
|
|
168130
|
+
if (target.includes("fn-bunfs-") && !existsSync49(target)) {
|
|
167306
168131
|
rmSync5(bunfsRoot);
|
|
167307
168132
|
console.log("[fn-native-patch] Cleaned up stale /$bunfs/root symlink");
|
|
167308
168133
|
}
|
|
@@ -167321,23 +168146,23 @@ function setupNativeResolution() {
|
|
|
167321
168146
|
process.env.NODE_PTY_SPAWN_HELPER_DIR = nativeDir;
|
|
167322
168147
|
}
|
|
167323
168148
|
process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
|
|
167324
|
-
const tmpRoot =
|
|
167325
|
-
const fnDir =
|
|
167326
|
-
const prebuildsDir =
|
|
167327
|
-
const platformDir =
|
|
168149
|
+
const tmpRoot = join67(tmpdir4(), `fn-bunfs-${process.pid}`);
|
|
168150
|
+
const fnDir = join67(tmpRoot, "fn");
|
|
168151
|
+
const prebuildsDir = join67(fnDir, "prebuilds");
|
|
168152
|
+
const platformDir = join67(prebuildsDir, basename21(nativeDir));
|
|
167328
168153
|
try {
|
|
167329
168154
|
cleanupStaleBunfsLinks();
|
|
167330
168155
|
mkdirSync12(platformDir, { recursive: true });
|
|
167331
|
-
const ptyNodeDest =
|
|
167332
|
-
copyFileSync(
|
|
167333
|
-
if (
|
|
167334
|
-
copyFileSync(
|
|
168156
|
+
const ptyNodeDest = join67(platformDir, "pty.node");
|
|
168157
|
+
copyFileSync(join67(nativeDir, "pty.node"), ptyNodeDest);
|
|
168158
|
+
if (existsSync49(join67(nativeDir, "spawn-helper"))) {
|
|
168159
|
+
copyFileSync(join67(nativeDir, "spawn-helper"), join67(platformDir, "spawn-helper"));
|
|
167335
168160
|
}
|
|
167336
168161
|
process.env.FUSION_FAKE_BUNFS_ROOT = tmpRoot;
|
|
167337
168162
|
if (process.platform !== "win32") {
|
|
167338
168163
|
const bunfsRoot = "/$bunfs/root";
|
|
167339
168164
|
try {
|
|
167340
|
-
if (
|
|
168165
|
+
if (existsSync49(bunfsRoot)) {
|
|
167341
168166
|
const stats = lstatSync3(bunfsRoot);
|
|
167342
168167
|
if (stats.isSymbolicLink()) {
|
|
167343
168168
|
rmSync5(bunfsRoot);
|
|
@@ -167360,7 +168185,7 @@ function setupNativeResolution() {
|
|
|
167360
168185
|
function cleanupNativeResolution() {
|
|
167361
168186
|
if (bunfsSymlinkPath && process.platform !== "win32") {
|
|
167362
168187
|
try {
|
|
167363
|
-
if (
|
|
168188
|
+
if (existsSync49(bunfsSymlinkPath)) {
|
|
167364
168189
|
const stats = lstatSync3(bunfsSymlinkPath);
|
|
167365
168190
|
if (stats.isSymbolicLink()) {
|
|
167366
168191
|
rmSync5(bunfsSymlinkPath);
|
|
@@ -167406,9 +168231,9 @@ var init_native_patch = __esm({
|
|
|
167406
168231
|
});
|
|
167407
168232
|
|
|
167408
168233
|
// src/bin.ts
|
|
167409
|
-
import { existsSync as
|
|
168234
|
+
import { existsSync as existsSync50, mkdtempSync as mkdtempSync2, readFileSync as readFileSync23, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
167410
168235
|
import { createRequire as createRequire6 } from "node:module";
|
|
167411
|
-
import { join as
|
|
168236
|
+
import { join as join68, dirname as dirname28 } from "node:path";
|
|
167412
168237
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
167413
168238
|
import { performance as performance3 } from "node:perf_hooks";
|
|
167414
168239
|
var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
|
|
@@ -167416,7 +168241,7 @@ function configurePiPackage() {
|
|
|
167416
168241
|
if (process.env.PI_PACKAGE_DIR) {
|
|
167417
168242
|
return;
|
|
167418
168243
|
}
|
|
167419
|
-
const tmp = mkdtempSync2(
|
|
168244
|
+
const tmp = mkdtempSync2(join68(tmpdir5(), "fn-pkg-"));
|
|
167420
168245
|
let packageJson = {
|
|
167421
168246
|
name: "pi",
|
|
167422
168247
|
version: "0.1.0",
|
|
@@ -167426,11 +168251,11 @@ function configurePiPackage() {
|
|
|
167426
168251
|
const require4 = createRequire6(import.meta.url);
|
|
167427
168252
|
const piPackagePath = require4.resolve("@mariozechner/pi-coding-agent/package.json");
|
|
167428
168253
|
const piPackageDir = dirname28(piPackagePath);
|
|
167429
|
-
packageJson = JSON.parse(
|
|
168254
|
+
packageJson = JSON.parse(readFileSync23(piPackagePath, "utf-8"));
|
|
167430
168255
|
for (const entry of ["dist", "docs", "examples", "README.md", "CHANGELOG.md"]) {
|
|
167431
|
-
const source =
|
|
167432
|
-
if (
|
|
167433
|
-
symlinkSync3(source,
|
|
168256
|
+
const source = join68(piPackageDir, entry);
|
|
168257
|
+
if (existsSync50(source)) {
|
|
168258
|
+
symlinkSync3(source, join68(tmp, entry));
|
|
167434
168259
|
}
|
|
167435
168260
|
}
|
|
167436
168261
|
} catch {
|
|
@@ -167439,7 +168264,7 @@ function configurePiPackage() {
|
|
|
167439
168264
|
...packageJson.piConfig ?? {},
|
|
167440
168265
|
configDir: ".fusion"
|
|
167441
168266
|
};
|
|
167442
|
-
writeFileSync6(
|
|
168267
|
+
writeFileSync6(join68(tmp, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
167443
168268
|
process.env.PI_PACKAGE_DIR = tmp;
|
|
167444
168269
|
}
|
|
167445
168270
|
configurePiPackage();
|
|
@@ -167448,8 +168273,8 @@ setInterval(() => {
|
|
|
167448
168273
|
performance3.clearMarks();
|
|
167449
168274
|
}, 3e4).unref();
|
|
167450
168275
|
function loadEnvFile(path5) {
|
|
167451
|
-
if (!
|
|
167452
|
-
const contents =
|
|
168276
|
+
if (!existsSync50(path5)) return;
|
|
168277
|
+
const contents = readFileSync23(path5, "utf-8");
|
|
167453
168278
|
for (const rawLine of contents.split(/\r?\n/)) {
|
|
167454
168279
|
const line = rawLine.trim();
|
|
167455
168280
|
if (!line || line.startsWith("#")) continue;
|
|
@@ -167467,8 +168292,8 @@ function loadEnvFile(path5) {
|
|
|
167467
168292
|
}
|
|
167468
168293
|
function loadLocalEnv() {
|
|
167469
168294
|
const cwd = process.cwd();
|
|
167470
|
-
loadEnvFile(
|
|
167471
|
-
loadEnvFile(
|
|
168295
|
+
loadEnvFile(join68(cwd, ".env"));
|
|
168296
|
+
loadEnvFile(join68(cwd, ".env.local"));
|
|
167472
168297
|
}
|
|
167473
168298
|
loadLocalEnv();
|
|
167474
168299
|
async function loadCommandHandlers() {
|