@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.
Files changed (40) hide show
  1. package/dist/bin.js +1382 -557
  2. package/dist/client/assets/{AgentDetailView-5W1q48YS.js → AgentDetailView-BtpZ4jxh.js} +1 -1
  3. package/dist/client/assets/{AgentsView-DcEnemu0.js → AgentsView-Dxdtt0Bm.js} +3 -3
  4. package/dist/client/assets/ChatView-Bra9fNAG.js +1 -0
  5. package/dist/client/assets/{DevServerView-LOrDrAYm.js → DevServerView-UkgjEw9-.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-Bgp6PCbu.js → DirectoryPicker-Cls4HWxP.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-CNbnZ7Q3.js → DocumentsView-BRBUPFVA.js} +1 -1
  8. package/dist/client/assets/{InsightsView-CmJwV-ZC.js → InsightsView-BRDqHCLb.js} +1 -1
  9. package/dist/client/assets/{MemoryView-Bwi5p79s.js → MemoryView-DvTrwFnQ.js} +1 -1
  10. package/dist/client/assets/{NodesView-1pZii99I.js → NodesView-C4Ffl_o0.js} +1 -1
  11. package/dist/client/assets/{PiExtensionsManager-CokhM-MB.js → PiExtensionsManager-CeI1syeZ.js} +3 -3
  12. package/dist/client/assets/{PluginManager-cHaGKMgY.js → PluginManager-BgeoYhLk.js} +1 -1
  13. package/dist/client/assets/{ResearchView-CQDI2y7Q.js → ResearchView-fmEOm4A2.js} +1 -1
  14. package/dist/client/assets/{RoadmapsView-BTo3BT0I.js → RoadmapsView-DNb4x75S.js} +1 -1
  15. package/dist/client/assets/SettingsModal-CDPDmHhd.css +1 -0
  16. package/dist/client/assets/{SettingsModal-D5slUUsC.js → SettingsModal-CRMr4tL6.js} +1 -1
  17. package/dist/client/assets/SettingsModal-D1xq0WZm.js +31 -0
  18. package/dist/client/assets/{SetupWizardModal-1qSn8Yl0.js → SetupWizardModal-tF8B_aG_.js} +1 -1
  19. package/dist/client/assets/{SkillsView-CY3I5OYc.js → SkillsView-uyl47gSf.js} +1 -1
  20. package/dist/client/assets/{TodoView-B1GDwwhR.js → TodoView-CbzDtV53.js} +1 -1
  21. package/dist/client/assets/{folder-open-DPESt6bg.js → folder-open-DPpmGJ-v.js} +1 -1
  22. package/dist/client/assets/{index-2_pvFDiN.css → index-BqK6TvSa.css} +1 -1
  23. package/dist/client/assets/index-DyXZm9QN.js +656 -0
  24. package/dist/client/assets/{list-checks-D7D9kx7Y.js → list-checks-D62pw1I8.js} +1 -1
  25. package/dist/client/assets/{star-C59_6aNu.js → star-B8EbxNgI.js} +1 -1
  26. package/dist/client/assets/{upload-1I0eQddJ.js → upload-CpnLno9z.js} +1 -1
  27. package/dist/client/assets/{users-DH50eBCX.js → users-B_C_0qzA.js} +1 -1
  28. package/dist/client/index.html +2 -2
  29. package/dist/client/version.json +1 -1
  30. package/dist/extension.js +1025 -358
  31. package/dist/pi-claude-cli/index.ts +31 -5
  32. package/dist/pi-claude-cli/package.json +1 -1
  33. package/dist/pi-claude-cli/src/__tests__/process-manager.test.ts +90 -0
  34. package/dist/pi-claude-cli/src/__tests__/provider.test.ts +13 -3
  35. package/dist/pi-claude-cli/src/process-manager.ts +65 -0
  36. package/package.json +1 -1
  37. package/dist/client/assets/ChatView-CTc6mP8y.js +0 -1
  38. package/dist/client/assets/SettingsModal-EEQwF0Ql.js +0 -31
  39. package/dist/client/assets/SettingsModal-FfIAhzcJ.css +0 -1
  40. 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: execFile8 } = await import("node:child_process");
18143
+ const { execFile: execFile9 } = await import("node:child_process");
18114
18144
  const { promisify: promisify18 } = await import("node:util");
18115
- const execFileAsync6 = promisify18(execFile8);
18116
- const { stdout } = await execFileAsync6(
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: execFile8 } = await import("node:child_process");
18793
+ const { execFile: execFile9 } = await import("node:child_process");
18764
18794
  const { promisify: promisify18 } = await import("node:util");
18765
- const execFileAsync6 = promisify18(execFile8);
18766
- const { stdout } = await execFileAsync6(
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: readFileSync23 } = __require("fs");
28233
- const data = readFileSync23(filePath, "utf8");
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: execFile8 } = await import("node:child_process");
29544
+ const { execFile: execFile9 } = await import("node:child_process");
29497
29545
  const { promisify: promisify18 } = await import("node:util");
29498
- const execFileAsync6 = promisify18(execFile8);
29499
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync6);
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 execFileAsync6(command, args, {
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, execFileAsync6) {
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 execFileAsync6("qmd", buildQmdCollectionAddArgs(rootDir), {
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: execFile8 } = await import("node:child_process");
29596
+ const { execFile: execFile9 } = await import("node:child_process");
29549
29597
  const { promisify: promisify18 } = await import("node:util");
29550
- return promisify18(execFile8);
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 execFileAsync6 = options?.execFileAsync ?? await getDefaultExecFileAsync();
29566
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync6);
29567
- await execFileAsync6("qmd", ["update"], {
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 execFileAsync6("qmd", ["embed"], {
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 execFileAsync6 = await getDefaultExecFileAsync();
29598
- await execFileAsync6("qmd", ["--help"], {
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 execFileAsync6 = options?.execFileAsync ?? await getDefaultExecFileAsync();
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 execFileAsync6(command, args, {
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 isAbsolute19 = path5.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path5);
30409
- if (!path5 || path5.length > 4096 || !isAbsolute19 || path5.startsWith("-") || // Reject shell metacharacters, quotes, control chars, and NULs.
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
- task.executionStartedAt = void 0;
32588
- task.executionCompletedAt = void 0;
32589
- this.resetAllStepsToPending(task);
32590
- await this.resetPromptCheckboxes(dir2);
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 readFileSync2, statSync as statSync3, writeFileSync } from "node:fs";
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(readFileSync2(packageJsonPath, "utf-8"));
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(readFileSync2(settingsPath, "utf-8"));
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(readFileSync2(settingsPath, "utf-8"));
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, Type7) {
43696
- const map2 = Type7 ? new Type7() : ctx?.mapAsMap ? /* @__PURE__ */ new Map() : {};
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 readFileSync3, rmSync, statSync as statSync4 } from "node:fs";
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(readFileSync3(filePath, "utf-8"));
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: execFile8 }, { promisify: promisify18 }] = await Promise.all([
49299
+ const [{ execFile: execFile9 }, { promisify: promisify18 }] = await Promise.all([
49235
49300
  import("node:child_process"),
49236
49301
  import("node:util")
49237
49302
  ]);
49238
- const execFileAsync6 = promisify18(execFile8);
49239
- await execFileAsync6("tar", ["xzf", archivePath, "-C", outputDir]);
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 readFileSync4 } from "node:fs";
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 = readFileSync4(process.env.FUSION_GITHUB_APP_PRIVATE_KEY_PATH, "utf-8");
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: execFile8 } = await import("node:child_process");
51609
+ const { execFile: execFile9 } = await import("node:child_process");
51545
51610
  const { promisify: promisify18 } = await import("node:util");
51546
- const execFileAsync6 = promisify18(execFile8);
51611
+ const execFileAsync7 = promisify18(execFile9);
51547
51612
  try {
51548
- await execFileAsync6("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
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 execFileAsync6("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
51562
- await execFileAsync6("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
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: execFile8 } = await import("node:child_process");
51648
+ const { execFile: execFile9 } = await import("node:child_process");
51584
51649
  const { promisify: promisify18 } = await import("node:util");
51585
- const execFileAsync6 = promisify18(execFile8);
51586
- const { stdout } = await execFileAsync6("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
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 readFileSync5 } from "node:fs";
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(readFileSync5(path5, "utf-8"));
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 readFileSync6 } from "node:fs";
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(readFileSync6(authPath, "utf-8"));
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 readFileSync7 } from "node:fs";
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 = readFileSync7(settingsPath, "utf-8");
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 readFileSync8 } from "node:fs";
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(readFileSync8(path5, "utf-8"));
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(readFileSync8(pkgJsonPath, "utf-8"));
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: join68 } = await import("node:path");
55090
+ const { join: join69 } = await import("node:path");
55026
55091
  for (const att of attachments) {
55027
- const filePath = join68(
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: join68 } = await import("node:path");
56630
+ const { join: join69 } = await import("node:path");
56566
56631
  const promptContent = await readFile24(
56567
- join68(rootDir, promptPath),
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 { stdout } = await execAsync3("git worktree list --porcelain", {
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 { stdout } = await execAsync3("git branch --list 'fusion/*'", {
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 isAbsolute10, join as join33, relative as relative6, resolve as resolvePath } from "node:path";
62090
- import { existsSync as existsSync26 } from "node:fs";
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 Type4 } from "@mariozechner/pi-ai";
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 = Type4.Object({
62380
- step: Type4.Number({ description: "Step number (0-indexed)" }),
62381
- status: Type4.Union(
62382
- STEP_STATUSES.map((s) => Type4.Literal(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 = Type4.Object({
62387
- task_id: Type4.String({ description: 'The ID of the task to depend on (e.g. "KB-001")' }),
62388
- confirm: Type4.Optional(Type4.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." }))
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 = Type4.Object({
62391
- name: Type4.String({ description: "Name for the child agent" }),
62392
- role: Type4.Union([
62393
- Type4.Literal("triage"),
62394
- Type4.Literal("executor"),
62395
- Type4.Literal("reviewer"),
62396
- Type4.Literal("merger"),
62397
- Type4.Literal("engineer"),
62398
- Type4.Literal("custom")
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: Type4.String({ description: "Task description for the child agent to execute" })
62807
+ task: Type5.String({ description: "Task description for the child agent to execute" })
62401
62808
  });
62402
- reviewStepParams = Type4.Object({
62403
- step: Type4.Number({ description: "Step number to review" }),
62404
- type: Type4.Union(
62405
- [Type4.Literal("plan"), Type4.Literal("code")],
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: Type4.String({ description: "Name of the step being reviewed" }),
62409
- baseline: Type4.Optional(
62410
- Type4.String({
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
- await this.store.moveTask(taskId, "todo");
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 && existsSync26(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") && existsSync26(dep.worktree)) {
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 = join33(this.store.getFusionDir(), "tasks");
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 = join33(this.rootDir, ".worktrees", worktreeName);
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 = existsSync26(worktreePath);
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 = join33(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
63546
- isResume = existsSync26(worktreePath);
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 && existsSync26(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 && existsSync26(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 && existsSync26(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
- const retryPrompt = [
64231
- "Your previous session ended without calling the fn_task_done tool.",
64232
- "The task may already be complete \u2014 review the current state of the worktree and either:",
64233
- "1. If the work is done, call fn_task_done with a summary of what was accomplished.",
64234
- "2. If there is remaining work, finish it and then call fn_task_done.",
64235
- "",
64236
- "Original task:",
64237
- buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
64238
- ].join("\n");
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 && existsSync26(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 && existsSync26(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 && existsSync26(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
- if (status === "in-progress" && sessionRef.current) {
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
- const task = await store.updateStep(taskId, step, status);
64579
- const stepInfo = task.steps[step];
64580
- const progress = task.steps.filter((s) => s.status === "done").length;
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 ${status}. Progress: ${progress}/${task.steps.length} done.`
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: Type4.Object({
64680
- summary: Type4.Optional(Type4.String({
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 = join33(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
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 = join33(this.store.getFusionDir(), "tasks", task.id, "PROMPT.md");
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 = isAbsolute10(startPoint) && existsSync26(startPoint) ? `git -C "${startPoint}" rev-parse --verify HEAD^{commit}` : `git rev-parse --verify "${startPoint}^{commit}"`;
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 (existsSync26(path5)) {
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 = join33(this.rootDir, ".worktrees", generateWorktreeName(this.rootDir));
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("..") && !isAbsolute10(rel)) {
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 = join33(this.rootDir, ".worktrees", childWorktreeName);
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 existsSync27 } from "node:fs";
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 join34 } from "node:path";
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 = join34(this.store.getTasksDir(), id);
66960
- if (!existsSync27(taskDir)) {
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 = join34(taskDir, "PROMPT.md");
66964
- if (!existsSync27(promptPath)) {
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 join34(this.store.getRootDir(), ".worktrees", worktreeName);
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 isAbsolute11, resolve as resolve14 } from "node:path";
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 = isAbsolute11(agent.instructionsPath) ? agent.instructionsPath : resolve14(this.rootDir, agent.instructionsPath);
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 Type5 } from "@mariozechner/pi-ai";
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 = Type5.Object({
69596
- summary: Type5.Optional(Type5.String({ description: "Summary of what was accomplished this heartbeat" }))
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 join35, relative as relative7, resolve as resolve15 } from "node:path";
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(join35(this.projectRoot, entry.name));
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 = join35(dir2, entry.name);
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(join35(this.projectRoot, ".gitignore"), "utf-8");
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
- import { promisify as promisify7 } from "node:util";
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
- let combined = stdout;
73970
- if (stderr) {
73971
- combined += stdout ? "\n--- stderr ---\n" : "";
73972
- combined += stderr;
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 execAsync6, log14, DEFAULT_TIMEOUT_MS6, MAX_BUFFER, MAX_OUTPUT_LENGTH, DEFAULT_POLL_INTERVAL_MS, MIN_POLL_INTERVAL_MS, CronRunner, AI_AUTOMATION_SYSTEM_PROMPT;
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 execAsync6(schedule.command, {
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 execAsync6(step.command, {
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 output = response.length > MAX_OUTPUT_LENGTH ? response.slice(0, MAX_OUTPUT_LENGTH) + "\n[output truncated]" : response;
74322
- log14.log(` \u2713 AI prompt step "${step.name}" completed (${response.length} chars)`);
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 promisify8 } from "node:util";
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, execAsync7, DEFAULT_TIMEOUT_MS7, MAX_BUFFER2, MAX_OUTPUT_LENGTH2, MAX_CATCH_UP_INTERVALS, RoutineRunner;
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
- execAsync7 = promisify8(exec7);
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 execAsync7(command, {
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 promisify9 } from "node:util";
75295
- import { existsSync as existsSync28, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
75296
- import { isAbsolute as isAbsolute12, join as join36, relative as relative8, resolve as resolve16 } from "node:path";
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, execAsync8, 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;
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
- execAsync8 = promisify9(exec8);
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 execAsync8(
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 execAsync8(
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 execAsync8(
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 execAsync8(
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 execAsync8(`git show --shortstat --format= ${shellQuote(sha2)}`, {
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 execAsync8(command, {
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 execAsync8(`git show --shortstat --format= ${shellQuote(sha)}`, {
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 && existsSync28(task.worktree)) {
76278
+ if (task.worktree && existsSync29(task.worktree)) {
75684
76279
  try {
75685
- await execAsync8(`git worktree remove ${shellQuote(task.worktree)} --force`, {
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 execAsync8(`git branch -D ${shellQuote(branch)}`, {
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 && existsSync28(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 && existsSync28(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 && existsSync28(task.worktree)) {
77043
+ if (task.worktree && existsSync29(task.worktree)) {
76449
77044
  try {
76450
- const { stdout: status } = await execAsync8("git status --porcelain", {
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 execAsync8(`git rev-parse --verify "${branchName}"`, {
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 execAsync8(
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 execAsync8("git worktree prune", {
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 execAsync8(`git worktree remove "${worktreePath}" --force`, {
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 = join36(this.options.rootDir, ".worktrees");
76634
- if (!existsSync28(worktreesDir)) return 0;
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) => join36(worktreesDir, e.name));
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("..") || isAbsolute12(rel)) {
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 execAsync8(`git branch -d "${branch}"`, {
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 execAsync8(`git branch -D "${branch}"`, {
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 = join36(this.options.rootDir, ".worktrees");
76743
- if (!existsSync28(worktreesDir)) return;
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 execAsync8(`git worktree remove "${worktreePath}" --force`, {
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 Type6 } from "@mariozechner/pi-ai";
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 = Type6.Any();
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 join37 } from "node:path";
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 join37(currentDir, workerFile);
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 spawn3 } from "node:child_process";
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 ?? spawn3;
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 execFile3 } from "node:child_process";
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 execFileAsync, MERGE_HANDOFF_GRACE_MS, isRemoteActive, ProjectEngine;
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
- execFileAsync = promisify10(execFile3);
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 execFileAsync(checker, [command]);
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 join38, isAbsolute as isAbsolute13, dirname as dirname11, basename as basename9 } from "node:path";
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 = join38(sourcePath, "manifest.json");
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 = join38(parentDir, "manifest.json");
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 spawn4, spawnSync } from "node:child_process";
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 = spawn4(binary, ["profile", "list"], {
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 = spawn4(binary, args, {
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 spawn5 } from "node:child_process";
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 = spawn5(resolvedPath ?? binary, ["--version"], {
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 = spawn5(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
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 spawn6 } from "node:child_process";
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 = spawn6(config.binaryPath, args, {
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 spawn7 } from "node:child_process";
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 = spawn7(resolvedPath ?? binary, ["--version"], {
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 = spawn7(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
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: join68 } = await import("node:path");
88238
- const promptPath = join68(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
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: join68 } = await import("node:path");
88661
- const promptPath = join68(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
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: join69 } = await import("node:path");
88932
- const promptPath2 = join69(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
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: join68 } = await import("node:path");
88949
- const promptPath = join68(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
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: join69 } = await import("node:path");
88970
- const promptPath2 = join69(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
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: join68 } = await import("node:path");
88985
- const promptPath = join68(scopedStore.getRootDir(), ".fusion", "tasks", task.id, "PROMPT.md");
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 join39, resolve as resolve18, relative as relative9 } from "node:path";
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(join39(resolvedBase, decodedPath));
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 join40, resolve as resolve19 } from "node:path";
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 = join40(sessionDir, filename);
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 execFile4 } from "node:child_process";
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 execFileAsync6 = promisify11(execFile4);
96768
+ const execFileAsync7 = promisify11(execFile5);
96044
96769
  async function queryTailscaleFunnelUrl(targetPort) {
96045
96770
  try {
96046
- const { stdout } = await execFileAsync6("tailscale", ["status", "--json"], { timeout: 3e3 });
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 execFileAsync6(command, ["cloudflared"]);
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
- return "curl -L --output /usr/local/bin/cloudflared https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 && chmod +x /usr/local/bin/cloudflared";
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
- const command = resolveCloudflaredInstallCommand();
96076
- const shell = process.platform === "win32" ? "cmd" : "sh";
96077
- const shellArgs = process.platform === "win32" ? ["/c", command] : ["-c", command];
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 execFileAsync6(shell, shellArgs, { timeout: 12e4 });
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: tunnelStatus?.state ?? "stopped",
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 join41, dirname as dirname12 } from "node:path";
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 = join41(pkgRoot, "build", "Release");
97395
- if (fs2.existsSync(join41(releaseDir, "pty.node"))) {
98219
+ const releaseDir = join42(pkgRoot, "build", "Release");
98220
+ if (fs2.existsSync(join42(releaseDir, "pty.node"))) {
97396
98221
  return releaseDir;
97397
98222
  }
97398
- const prebuildDir = join41(pkgRoot, "prebuilds", getNativePrebuildName());
97399
- if (fs2.existsSync(join41(prebuildDir, "pty.node"))) {
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 = join41(nativeDir, "spawn-helper");
97426
- const nativeModulePath = join41(nativeDir, "pty.node");
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 = join41(process.env.FUSION_RUNTIME_DIR, prebuildName);
97445
- if (fs2.existsSync(join41(envPath, "pty.node"))) {
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 = join41(execDir, "runtime", prebuildName);
97451
- if (fs2.existsSync(join41(nextToBinary, "pty.node"))) {
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 = join41(nativeDir, "pty.node");
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 execFile5 } from "node:child_process";
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 execFileAsync2("git", args, {
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 execFileAsync2;
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
- execFileAsync2 = promisify12(execFile5);
101262
+ execFileAsync3 = promisify12(execFile6);
100438
101263
  }
100439
101264
  });
100440
101265
 
100441
101266
  // ../dashboard/src/routes/register-git-github.ts
100442
- import { isAbsolute as isAbsolute15 } from "node:path";
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 (isAbsolute15(filePath)) return false;
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 spawn8 } from "node:child_process";
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 = spawn8(command, [], {
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 join42, resolve as resolve21, relative as relative11, dirname as dirname13, basename as basename12 } from "node:path";
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(join42(rootDir, ".fusion", "tasks", taskId));
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(join42(resolvedBase, decodedPath));
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 = join42(targetPath, entry.name);
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 = join42(dirname13(resolvedPath), newName);
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 ? join42(basePath, currentRelative) : basePath;
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 ? join42(currentRelative, entry.name) : entry.name;
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 = join42(basePath, entryRelativePath);
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 ? join42(relativeDir, entry.name) : entry.name;
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 = join42(dir2, entry.name);
103784
- const relPath = join42(relativeDir, entry.name);
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 = join42(source, entry.name);
103806
- const destPath = join42(destination, entry.name);
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 join68(s) {
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 execFile6 } from "node:child_process";
131197
+ import { execFile as execFile7 } from "node:child_process";
130373
131198
  import { promisify as promisify13 } from "node:util";
130374
- var execFileAsync3;
131199
+ var execFileAsync4;
130375
131200
  var init_exec_file = __esm({
130376
131201
  "../dashboard/src/exec-file.ts"() {
130377
131202
  "use strict";
130378
- execFileAsync3 = promisify13(execFile6);
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 isAbsolute16, join as join43 } from "node:path";
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 (!isAbsolute16(normalizedPath)) {
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 execFileAsync3("git", ["clone", cloneSource, normalizedPath], {
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 = join43(normalizedPath, ".fusion");
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 = join43(searchPath, entry.name);
130672
- if (isValidSqliteDatabaseFile(join43(dirPath, ".fusion", "fusion.db"))) {
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 homedir6 } from "node:os";
131108
- function getFusionAgentDir2(home = process.env.HOME || process.env.USERPROFILE || homedir6()) {
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 || homedir6()) {
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 || homedir6()) {
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 join44, resolve as resolve22 } from "node:path";
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(join44(tmpdir3(), "fusion-agent-export-"));
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 = join44(projectRoot, "skills", "imported", safeCompanySlug);
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 = join44(skillsBaseDir, skillSlug);
134285
- const skillPath = join44(skillDir, "SKILL.md");
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(join44(tmpdir3(), `fn-agent-import-${importCompanySlug}-`));
134456
- const archivePath = join44(tempDir, "archive.tar.gz");
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 execFileAsync4(file, args, options) {
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 execFileAsync4(
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 execFileAsync4("gh", ["auth", "status"], { encoding: "utf-8", timeout: 5e3 });
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 execFileAsync4("gh", ["api", "/user/copilot", "--jq", "."], {
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 spawn9 } from "node:child_process";
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 = spawn9(binaryPath ?? "claude", ["--version"], {
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 = spawn9(which, [binary], { stdio: ["ignore", "pipe", "ignore"] });
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: spawn15 } = await import("node:child_process");
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 = spawn15(bin, args, { stdio: ["ignore", "pipe", "pipe"] });
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: spawn15 } = await import("node:child_process");
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 = spawn15(bin, fullArgs, { stdio: ["ignore", "pipe", "pipe"] });
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 readFileSync9 } from "node:fs";
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 join46 } from "node:path";
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 join46(fusionDir, CACHE_FILENAME);
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 = readFileSync9(getCachePath(fusionDir), "utf-8");
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 existsSync30, readFileSync as readFileSync10 } from "node:fs";
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 (!existsSync30(pkgPath)) {
139427
+ if (!existsSync31(pkgPath)) {
138603
139428
  return null;
138604
139429
  }
138605
139430
  try {
138606
- const parsed = JSON.parse(readFileSync10(pkgPath, "utf-8"));
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 join47, relative as relative12, resolve as resolve24 } from "node:path";
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(join47(projectRoot, "pnpm-workspace.yaml"), "utf-8");
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 = join47(root, "package.json");
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 join48, resolve as resolve25 } from "node:path";
143944
+ import { dirname as dirname17, join as join49, resolve as resolve25 } from "node:path";
143120
143945
  function devServerFilePath(projectDir) {
143121
- return join48(resolve25(projectDir), ".fusion", "dev-server.json");
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 spawn10 } from "node:child_process";
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 = spawn10(safeCommand, [], {
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 join49, isAbsolute as isAbsolute17 } from "node:path";
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(join49(fusionAgentDir, "settings.json"));
144397
- const legacySettings = readJsonObject3(join49(legacyAgentDir, "settings.json"));
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(join49(getLegacyPiAgentDir(), "settings.json"));
144421
- const fusionGlobalSettings = readJsonObject3(join49(getFusionAgentDir(), "settings.json"));
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(join49(cwd, ".fusion", "settings.json"));
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 (isAbsolute17(trimmed) || /^[a-zA-Z]:\//.test(trimmed)) {
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: join68 } = await import("node:path");
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 = join68(resolvedPath, entry.name);
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: exec12 } = await import("node:child_process");
147868
+ const { exec: exec13 } = await import("node:child_process");
147044
147869
  const { promisify: promisify18 } = await import("node:util");
147045
- const execAsyncFn = promisify18(exec12);
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 join50, dirname as dirname18 } from "node:path";
152331
- import { existsSync as existsSync32, readFileSync as readFileSync12 } from "node:fs";
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) : readFileSync12(certFile);
152434
- const key = keyInline ? Buffer.from(keyInline) : readFileSync12(keyFile);
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 ? readFileSync12(caFile) : void 0;
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 : existsSync32(join50(execDir, "client", "index.html")) ? join50(execDir, "client") : existsSync32(join50(__dirname, "..", "dist", "client")) ? join50(__dirname, "..", "dist", "client") : join50(__dirname, "..", "client");
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(join50(clientDir, "version.json"), (err) => {
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(join50(clientDir, "index.html"));
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 join51, relative as relative13, dirname as dirname19 } from "node:path";
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 = join51(skillDir, "SKILL.md");
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 join51(rootDir, ".fusion", "settings.json");
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 exec9 } from "node:child_process";
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(exec9);
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 existsSync33, readFileSync as readFileSync13, writeFileSync as writeFileSync2, mkdirSync as mkdirSync6 } from "node:fs";
154100
- import { join as join52, dirname as dirname20, basename as basename15 } from "node:path";
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 join52(dirname20(dirname20(agentDir)), siblingRoot, "agent");
154930
+ return join53(dirname20(dirname20(agentDir)), siblingRoot, "agent");
154106
154931
  }
154107
154932
  function readJsonObject4(path5) {
154108
- if (!existsSync33(path5)) {
154933
+ if (!existsSync34(path5)) {
154109
154934
  return {};
154110
154935
  }
154111
154936
  try {
154112
- const parsed = JSON.parse(readFileSync13(path5, "utf-8"));
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(`${join52(".fusion", "agent")}`) ? agentDir : siblingAgentDir2(agentDir, ".fusion");
154120
- const legacyAgentDir = agentDir.includes(`${join52(".pi", "agent")}`) ? agentDir : siblingAgentDir2(agentDir, ".pi");
154121
- const legacyGlobalSettings = legacyAgentDir ? readJsonObject4(join52(legacyAgentDir, "settings.json")) : {};
154122
- const fusionGlobalSettings = fusionAgentDir ? readJsonObject4(join52(fusionAgentDir, "settings.json")) : {};
154123
- const directGlobalSettings = readJsonObject4(join52(agentDir, "settings.json"));
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(join52(cwd, ".fusion", "settings.json"));
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 existsSync34, readFileSync as readFileSync14 } from "node:fs";
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 (!existsSync34(authPath)) {
155094
+ if (!existsSync35(authPath)) {
154270
155095
  continue;
154271
155096
  }
154272
155097
  try {
154273
- const parsed = JSON.parse(readFileSync14(authPath, "utf-8"));
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 homedir8 } from "node:os";
154310
- import { existsSync as existsSync35, readFileSync as readFileSync15 } from "node:fs";
154311
- import { join as join53 } from "node:path";
154312
- function getFusionAgentDir3(home = process.env.HOME || process.env.USERPROFILE || homedir8()) {
154313
- return join53(home, ".fusion", "agent");
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 || homedir8()) {
154316
- return join53(home, ".pi", "agent");
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 || homedir8()) {
154319
- return join53(getFusionAgentDir3(home), "auth.json");
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 || homedir8()) {
155146
+ function getLegacyAuthPaths2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
154322
155147
  return [
154323
- join53(home, ".pi", "agent", "auth.json"),
154324
- join53(home, ".pi", "auth.json")
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 || homedir8()) {
154328
- return join53(getFusionAgentDir3(home), "models.json");
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 || homedir8()) {
155155
+ function getLegacyModelsPaths2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
154331
155156
  return [
154332
- join53(home, ".pi", "agent", "models.json"),
154333
- join53(home, ".pi", "models.json")
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 || homedir8()) {
155161
+ function getModelRegistryModelsPath2(home = process.env.HOME || process.env.USERPROFILE || homedir9()) {
154337
155162
  const fusionModelsPath = getFusionModelsPath2(home);
154338
- if (existsSync35(fusionModelsPath)) {
155163
+ if (existsSync36(fusionModelsPath)) {
154339
155164
  return fusionModelsPath;
154340
155165
  }
154341
- return getLegacyModelsPaths2(home).find((modelsPath) => existsSync35(modelsPath)) ?? fusionModelsPath;
155166
+ return getLegacyModelsPaths2(home).find((modelsPath) => existsSync36(modelsPath)) ?? fusionModelsPath;
154342
155167
  }
154343
155168
  function readJsonObject5(path5) {
154344
- if (!existsSync35(path5)) {
155169
+ if (!existsSync36(path5)) {
154345
155170
  return {};
154346
155171
  }
154347
155172
  try {
154348
- const parsed = JSON.parse(readFileSync15(path5, "utf-8"));
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 || homedir8()) {
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(join53(fusionAgentDir, "settings.json"));
154361
- const legacySettings = readJsonObject5(join53(legacyAgentDir, "settings.json"));
154362
- if (hasPackageManagerSettings3(fusionSettings) || !existsSync35(legacyAgentDir)) {
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 existsSync35(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
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 existsSync36,
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 join54, resolve as resolve28 } from "node:path";
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 existsSync36(candidate) ? candidate : null;
155380
+ return existsSync37(candidate) ? candidate : null;
154556
155381
  }
154557
155382
  function installFusionSkillIntoProject(projectPath, options = {}) {
154558
- const target = join54(projectPath, ".claude", "skills", FUSION_SKILL_NAME);
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 (existsSync36(target) || isBrokenSymlink(target)) {
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 = join54(target, "SKILL.md");
154584
- if (!existsSync36(skillMd)) {
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: join54(p.path, ".claude", "skills", FUSION_SKILL_NAME),
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 !existsSync36(path5);
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 existsSync37, readFileSync as readFileSync16 } from "node:fs";
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 (existsSync37(candidate)) {
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(readFileSync16(pkgJsonPath, "utf-8"));
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 (!existsSync37(entryPath)) {
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 readFileSync17 } from "node:fs";
154837
- import { join as join55 } from "node:path";
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 = join55(resolveGlobalDir(), "update-check.json");
154841
- const raw = readFileSync17(cachePath, "utf-8");
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 existsSync38, readFileSync as readFileSync18 } from "node:fs";
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 (existsSync38(resolve30(cur, "package.json"))) {
155705
+ if (existsSync39(resolve30(cur, "package.json"))) {
154881
155706
  try {
154882
- const parsed = JSON.parse(readFileSync18(resolve30(cur, "package.json"), "utf-8"));
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(readFileSync18(resolve30(pkgDir, "package.json"), "utf-8"));
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 (existsSync38(srcEntry)) {
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 (!existsSync38(entryPath)) {
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 spawn11 } from "node:child_process";
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 = spawn11(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
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 spawn12 } from "node:child_process";
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 = spawn12(cmd, args, { detached: true, stdio: "ignore" });
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 join56, resolve as pathResolve } from "node:path";
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 execFileAsync5("git", args, { cwd, maxBuffer: 4 * 1024 * 1024 });
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(join56(absDir, d.name));
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
- join56(cwd, ".fusion", "disabled-auto-extension-discovery")
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 execFileAsync5("git", ["push"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
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 execFileAsync5("git", ["fetch"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
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, execFileAsync5, FILES_DENYLIST2, FILE_SIZE_LIMIT, BINARY_CHECK_BYTES, MAX_PREVIEW_LINES;
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
- execFileAsync5 = promisify15(execFileCb);
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 join57 } from "node:path";
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
- join57(cwd, ".fusion", "disabled-auto-extension-discovery")
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 join58 } from "node:path";
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
- join58(cwd, ".fusion", "disabled-auto-extension-discovery")
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 spawn13 } from "node:child_process";
163336
+ import { spawn as spawn14 } from "node:child_process";
162512
163337
  import { once as once2 } from "node:events";
162513
- import { join as join59 } from "node:path";
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 = spawn13(command, args, {
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 = join59(rootDir, "packages", "desktop", "dist", "main.js");
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 = spawn13(electronBinary, electronArgs, {
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 existsSync39, readFileSync as readFileSync19 } from "node:fs";
162678
- import { basename as basename17, join as join60 } from "node:path";
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 = join60(projectPath, ".fusion", "tasks", id, "agent.log");
162979
- if (!existsSync39(logPath)) {
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 = readFileSync19(logPath, "utf-8");
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 join61 } from "node:path";
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 = join61(process.cwd(), filename);
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 existsSync40 } from "node:fs";
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 (!existsSync40(resolvedPath)) {
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 exec10 } from "node:child_process";
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(exec10);
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 existsSync41, statSync as statSync7 } from "node:fs";
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 (!existsSync41(match.path)) {
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 (!existsSync41(match.path)) {
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 (!existsSync41(project.path)) {
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 isAbsolute18, relative as relative14, basename as basename19 } from "node:path";
165400
- import { existsSync as existsSync42, statSync as statSync8 } from "node:fs";
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 = isAbsolute18(projectPath) ? projectPath : resolve34(process.cwd(), projectPath);
165529
- if (!existsSync42(absolutePath2)) {
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 (!existsSync42(kbDbPath2) && !options.force) {
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 = isAbsolute18(projectPath) ? projectPath : resolve34(process.cwd(), projectPath);
165574
- if (!existsSync42(absolutePath)) {
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 (!existsSync42(kbDbPath) && !options.force) {
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 existsSync43, mkdirSync as mkdirSync8 } from "node:fs";
165844
- import { homedir as homedir9 } from "node:os";
165845
- import { dirname as dirname26, join as join62, resolve as resolve35 } from "node:path";
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 || homedir9()) {
166672
+ function getSupportedSkillInstallTargets(homeDir = process.env.HOME || process.env.USERPROFILE || homedir10()) {
165848
166673
  return [
165849
- { client: "claude", targetDir: join62(homeDir, ".claude", "skills", FUSION_SKILL_NAME2) },
165850
- { client: "codex", targetDir: join62(homeDir, ".codex", "skills", FUSION_SKILL_NAME2) },
165851
- { client: "gemini", targetDir: join62(homeDir, ".gemini", "skills", FUSION_SKILL_NAME2) }
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 existsSync43(source) ? source : null;
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 (existsSync43(target.targetDir)) {
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 existsSync44, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync20 } from "node:fs";
165915
- import { join as join63, resolve as resolve36, basename as basename20 } from "node:path";
165916
- import { exec as exec11 } from "node:child_process";
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 = join63(cwd, ".fusion");
165921
- const dbPath = join63(fusionDir, "fusion.db");
165922
- const hasDbPath = existsSync44(dbPath);
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 (existsSync44(fusionDir) && hasDbPath && hasValidDb) {
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 (existsSync44(fusionDir) && hasDbPath && !hasValidDb) {
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 (!existsSync44(fusionDir)) {
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 (!existsSync44(dbPath)) {
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 (!existsSync44(join63(dir2, ".git"))) {
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 = join63(cwd, ".gitignore");
166862
+ const gitignorePath = join64(cwd, ".gitignore");
166038
166863
  let content = "";
166039
- if (existsSync44(gitignorePath)) {
166864
+ if (existsSync45(gitignorePath)) {
166040
166865
  try {
166041
- content = readFileSync20(gitignorePath, "utf-8");
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 = join63(cwd, ".gitkeep");
166081
- if (!existsSync44(gitkeepPath)) {
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(exec11);
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 existsSync45, mkdirSync as mkdirSync10, readFileSync as readFileSync21, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
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 (existsSync45(skillPath)) {
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 (!existsSync45(sourcePath)) {
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 = readFileSync21(sourcePath, "utf-8");
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 existsSync46 } from "node:fs";
166747
- import { join as join64 } from "node:path";
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 = join64(pluginPath, "manifest.json");
166786
- if (!existsSync46(manifestPath)) {
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 (!existsSync46(source)) {
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 existsSync47 } from "node:fs";
166977
- import { join as join65 } from "node:path";
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 = join65(process.cwd(), targetDir);
167111
- if (existsSync47(targetPath)) {
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(join65(targetPath, "src", "__tests__"), { recursive: true });
167119
- writeFileSync5(join65(targetPath, "package.json"), generatePackageJson(name));
167120
- writeFileSync5(join65(targetPath, "tsconfig.json"), generateTsconfig());
167121
- writeFileSync5(join65(targetPath, "vitest.config.ts"), generateVitestConfig());
167122
- writeFileSync5(join65(targetPath, "src", "index.ts"), generateIndexTs(name));
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
- join65(targetPath, "src", "__tests__", "index.test.ts"),
167949
+ join66(targetPath, "src", "__tests__", "index.test.ts"),
167125
167950
  generateTestTs(name)
167126
167951
  );
167127
- writeFileSync5(join65(targetPath, "README.md"), generateReadme(name));
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 spawn14 } from "node:child_process";
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 = spawn14("npx", npxArgs, {
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 join66, basename as basename21, dirname as dirname27 } from "node:path";
167278
- import { existsSync as existsSync48, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
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 = join66(execDir, "runtime", prebuildName);
167286
- if (existsSync48(join66(nextToBinary, "pty.node"))) {
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 = join66(process.env.FUSION_RUNTIME_DIR, prebuildName);
167291
- if (existsSync48(join66(envPath, "pty.node"))) {
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 (existsSync48(bunfsRoot)) {
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-") && !existsSync48(target)) {
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 = join66(tmpdir4(), `fn-bunfs-${process.pid}`);
167325
- const fnDir = join66(tmpRoot, "fn");
167326
- const prebuildsDir = join66(fnDir, "prebuilds");
167327
- const platformDir = join66(prebuildsDir, basename21(nativeDir));
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 = join66(platformDir, "pty.node");
167332
- copyFileSync(join66(nativeDir, "pty.node"), ptyNodeDest);
167333
- if (existsSync48(join66(nativeDir, "spawn-helper"))) {
167334
- copyFileSync(join66(nativeDir, "spawn-helper"), join66(platformDir, "spawn-helper"));
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 (existsSync48(bunfsRoot)) {
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 (existsSync48(bunfsSymlinkPath)) {
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 existsSync49, mkdtempSync as mkdtempSync2, readFileSync as readFileSync22, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
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 join67, dirname as dirname28 } from "node:path";
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(join67(tmpdir5(), "fn-pkg-"));
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(readFileSync22(piPackagePath, "utf-8"));
168254
+ packageJson = JSON.parse(readFileSync23(piPackagePath, "utf-8"));
167430
168255
  for (const entry of ["dist", "docs", "examples", "README.md", "CHANGELOG.md"]) {
167431
- const source = join67(piPackageDir, entry);
167432
- if (existsSync49(source)) {
167433
- symlinkSync3(source, join67(tmp, entry));
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(join67(tmp, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
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 (!existsSync49(path5)) return;
167452
- const contents = readFileSync22(path5, "utf-8");
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(join67(cwd, ".env"));
167471
- loadEnvFile(join67(cwd, ".env.local"));
168295
+ loadEnvFile(join68(cwd, ".env"));
168296
+ loadEnvFile(join68(cwd, ".env.local"));
167472
168297
  }
167473
168298
  loadLocalEnv();
167474
168299
  async function loadCommandHandlers() {