@runfusion/fusion 0.5.0 → 0.6.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 (30) hide show
  1. package/dist/bin.js +799 -430
  2. package/dist/client/assets/{AgentDetailView-CnedHKqd.js → AgentDetailView-PYfYi6rl.js} +1 -1
  3. package/dist/client/assets/{AgentsView-TCAMvB-N.js → AgentsView-CjqSko8c.js} +3 -3
  4. package/dist/client/assets/{ChatView-B7djAFGF.js → ChatView-CY7Mdd3y.js} +1 -1
  5. package/dist/client/assets/{DevServerView-B_zgnvEn.js → DevServerView-Jt4PTXWB.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-B3ejJOl0.js → DirectoryPicker-Bcoh2_ft.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-DzmbEKrl.js → DocumentsView-B2YRSmIS.js} +1 -1
  8. package/dist/client/assets/{InsightsView-CoRsf8DC.js → InsightsView-C4-vGe-H.js} +1 -1
  9. package/dist/client/assets/{MemoryView-oFUIaoM5.js → MemoryView-Ch7agzuP.js} +1 -1
  10. package/dist/client/assets/{NodesView-BXUaViVf.js → NodesView-D9rm5-_6.js} +1 -1
  11. package/dist/client/assets/{PiExtensionsManager-DJRC5zRv.js → PiExtensionsManager-hpV1-dOB.js} +1 -1
  12. package/dist/client/assets/{PluginManager-OQhiY2zP.js → PluginManager-DHHM1Xeg.js} +1 -1
  13. package/dist/client/assets/{RoadmapsView-DwdewIMm.js → RoadmapsView-BpqARpKn.js} +1 -1
  14. package/dist/client/assets/{SettingsModal-Bmz_YrW5.js → SettingsModal-B8Q5r_TT.js} +3 -3
  15. package/dist/client/assets/{SettingsModal-CMRJwSFd.js → SettingsModal-DFitE2kE.js} +1 -1
  16. package/dist/client/assets/{SetupWizardModal-BnPgyQho.js → SetupWizardModal-CjUbb4VZ.js} +1 -1
  17. package/dist/client/assets/{SkillsView-pHEyP-vg.js → SkillsView-CaQNaA67.js} +1 -1
  18. package/dist/client/assets/{TodoView-Cg-xDGeH.js → TodoView-Bj0DcDdf.js} +1 -1
  19. package/dist/client/assets/{folder-open-DJxjQmf1.js → folder-open-DDanUhRq.js} +1 -1
  20. package/dist/client/assets/{index-Cf4wCcWp.js → index-BRTEq_-7.js} +5 -5
  21. package/dist/client/assets/index-w9ci2GQ7.css +1 -0
  22. package/dist/client/assets/{list-checks-NjVTpQgK.js → list-checks-eKLuZO4Y.js} +1 -1
  23. package/dist/client/assets/{upload-BQA5FG0G.js → upload-DDJr7Sat.js} +1 -1
  24. package/dist/client/assets/{users-p7CqLoIz.js → users-zMb4VibZ.js} +1 -1
  25. package/dist/client/index.html +2 -2
  26. package/dist/client/version.json +1 -1
  27. package/dist/extension.js +352 -154
  28. package/dist/pi-claude-cli/package.json +1 -1
  29. package/package.json +5 -5
  30. package/dist/client/assets/index-B7TDbn3p.css +0 -1
package/dist/bin.js CHANGED
@@ -165,6 +165,8 @@ var init_settings_schema = __esm({
165
165
  archiveAgentLogMode: "compact",
166
166
  autoUpdatePrStatus: false,
167
167
  autoCreatePr: false,
168
+ githubCommentOnDone: false,
169
+ githubCommentTemplate: void 0,
168
170
  autoBackupEnabled: false,
169
171
  autoBackupSchedule: "0 2 * * *",
170
172
  autoBackupRetention: 7,
@@ -2275,8 +2277,8 @@ function normalizeTaskComments(steeringComments, comments) {
2275
2277
  comments: normalizedComments
2276
2278
  };
2277
2279
  }
2278
- function createDatabase(fusionDir) {
2279
- return new Database(fusionDir);
2280
+ function createDatabase(fusionDir, options) {
2281
+ return new Database(fusionDir, options);
2280
2282
  }
2281
2283
  var SCHEMA_VERSION, SCHEMA_SQL, Database;
2282
2284
  var init_db = __esm({
@@ -2284,7 +2286,7 @@ var init_db = __esm({
2284
2286
  "use strict";
2285
2287
  init_sqlite_adapter();
2286
2288
  init_types();
2287
- SCHEMA_VERSION = 47;
2289
+ SCHEMA_VERSION = 48;
2288
2290
  SCHEMA_SQL = `
2289
2291
  -- Tasks table with JSON columns for nested data
2290
2292
  CREATE TABLE IF NOT EXISTS tasks (
@@ -2792,16 +2794,19 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
2792
2794
  /** Tracks transaction nesting depth for savepoint-based nested transactions. */
2793
2795
  transactionDepth = 0;
2794
2796
  _fts5Available;
2795
- constructor(fusionDir) {
2796
- this.dbPath = join(fusionDir, "fusion.db");
2797
- if (!isAbsolute(fusionDir)) {
2797
+ constructor(fusionDir, options) {
2798
+ const inMemory = options?.inMemory === true;
2799
+ this.dbPath = inMemory ? ":memory:" : join(fusionDir, "fusion.db");
2800
+ if (!inMemory && !isAbsolute(fusionDir)) {
2798
2801
  throw new Error(`[fusion] Database constructor requires an absolute fusionDir path, got: ${fusionDir}`);
2799
2802
  }
2800
- if (!existsSync(fusionDir)) {
2803
+ if (!inMemory && !existsSync(fusionDir)) {
2801
2804
  mkdirSync(fusionDir, { recursive: true });
2802
2805
  }
2803
2806
  this.db = new DatabaseSync(this.dbPath);
2804
- this.db.exec("PRAGMA journal_mode = WAL");
2807
+ if (!inMemory) {
2808
+ this.db.exec("PRAGMA journal_mode = WAL");
2809
+ }
2805
2810
  this.db.exec("PRAGMA busy_timeout = 5000");
2806
2811
  this.db.exec("PRAGMA foreign_keys = ON");
2807
2812
  this._fts5Available = probeFts5(this.db);
@@ -3730,6 +3735,11 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
3730
3735
  }
3731
3736
  });
3732
3737
  }
3738
+ if (version < 48) {
3739
+ this.applyMigration(48, () => {
3740
+ this.addColumnIfMissing("tasks", "verificationFailureCount", "INTEGER DEFAULT 0");
3741
+ });
3742
+ }
3733
3743
  }
3734
3744
  /**
3735
3745
  * Run a single migration step inside a transaction and bump the version.
@@ -3919,6 +3929,7 @@ var init_agent_store = __esm({
3919
3929
  locks = /* @__PURE__ */ new Map();
3920
3930
  _db = null;
3921
3931
  taskStore;
3932
+ inMemoryDb;
3922
3933
  constructor(options = {}) {
3923
3934
  super();
3924
3935
  if (!options.rootDir && process.env.VITEST === "true") {
@@ -3929,10 +3940,11 @@ var init_agent_store = __esm({
3929
3940
  this.rootDir = options.rootDir ?? resolve(".fusion");
3930
3941
  this.agentsDir = join2(this.rootDir, "agents");
3931
3942
  this.taskStore = options.taskStore;
3943
+ this.inMemoryDb = options.inMemoryDb === true;
3932
3944
  }
3933
3945
  get db() {
3934
3946
  if (!this._db) {
3935
- this._db = new Database(this.rootDir);
3947
+ this._db = new Database(this.rootDir, { inMemory: this.inMemoryDb });
3936
3948
  this._db.init();
3937
3949
  }
3938
3950
  return this._db;
@@ -6232,9 +6244,9 @@ var init_global_settings = __esm({
6232
6244
  * Serialize operations via promise chain to prevent lost-update races.
6233
6245
  */
6234
6246
  withLock(fn) {
6235
- let resolve34;
6247
+ let resolve36;
6236
6248
  const next = new Promise((r) => {
6237
- resolve34 = r;
6249
+ resolve36 = r;
6238
6250
  });
6239
6251
  const prev = this.lock;
6240
6252
  this.lock = next;
@@ -6242,7 +6254,7 @@ var init_global_settings = __esm({
6242
6254
  try {
6243
6255
  return await fn();
6244
6256
  } finally {
6245
- resolve34();
6257
+ resolve36();
6246
6258
  }
6247
6259
  });
6248
6260
  }
@@ -6306,12 +6318,15 @@ END;
6306
6318
  ArchiveDatabase = class {
6307
6319
  db;
6308
6320
  _fts5Available;
6309
- constructor(fusionDir) {
6310
- if (!existsSync4(fusionDir)) {
6321
+ constructor(fusionDir, options) {
6322
+ const inMemory = options?.inMemory === true;
6323
+ if (!inMemory && !existsSync4(fusionDir)) {
6311
6324
  mkdirSync3(fusionDir, { recursive: true });
6312
6325
  }
6313
- this.db = new DatabaseSync(join5(fusionDir, "archive.db"));
6314
- this.db.exec("PRAGMA journal_mode = WAL");
6326
+ this.db = new DatabaseSync(inMemory ? ":memory:" : join5(fusionDir, "archive.db"));
6327
+ if (!inMemory) {
6328
+ this.db.exec("PRAGMA journal_mode = WAL");
6329
+ }
6315
6330
  this.db.exec("PRAGMA busy_timeout = 5000");
6316
6331
  this._fts5Available = probeFts5(this.db);
6317
6332
  }
@@ -9526,19 +9541,21 @@ var init_plugin_store = __esm({
9526
9541
  init_db();
9527
9542
  init_plugin_types();
9528
9543
  PluginStore = class extends EventEmitter5 {
9529
- constructor(rootDir) {
9544
+ constructor(rootDir, options) {
9530
9545
  super();
9531
9546
  this.rootDir = rootDir;
9547
+ this.inMemoryDb = options?.inMemoryDb === true;
9532
9548
  }
9533
9549
  /** SQLite database instance */
9534
9550
  _db = null;
9551
+ inMemoryDb;
9535
9552
  /**
9536
9553
  * Get the SQLite database, initializing it on first access.
9537
9554
  */
9538
9555
  get db() {
9539
9556
  if (!this._db) {
9540
9557
  const fusionDir = join7(this.rootDir, ".fusion");
9541
- this._db = new Database(fusionDir);
9558
+ this._db = new Database(fusionDir, { inMemory: this.inMemoryDb });
9542
9559
  this._db.init();
9543
9560
  }
9544
9561
  return this._db;
@@ -27274,8 +27291,8 @@ var require_CronFileParser = __commonJS({
27274
27291
  * @throws If file cannot be read
27275
27292
  */
27276
27293
  static parseFileSync(filePath) {
27277
- const { readFileSync: readFileSync19 } = __require("fs");
27278
- const data = readFileSync19(filePath, "utf8");
27294
+ const { readFileSync: readFileSync20 } = __require("fs");
27295
+ const data = readFileSync20(filePath, "utf8");
27279
27296
  return _CronFileParser.#parseContent(data);
27280
27297
  }
27281
27298
  /**
@@ -27411,21 +27428,23 @@ var init_automation_store = __esm({
27411
27428
  init_db();
27412
27429
  CRON_TIMEZONE = "UTC";
27413
27430
  AutomationStore = class _AutomationStore extends EventEmitter11 {
27414
- constructor(rootDir) {
27431
+ constructor(rootDir, options) {
27415
27432
  super();
27416
27433
  this.rootDir = rootDir;
27434
+ this.inMemoryDb = options?.inMemoryDb === true;
27417
27435
  }
27418
27436
  /** Per-schedule promise chain for serializing writes. */
27419
27437
  scheduleLocks = /* @__PURE__ */ new Map();
27420
27438
  /** SQLite database instance */
27421
27439
  _db = null;
27440
+ inMemoryDb;
27422
27441
  /**
27423
27442
  * Get the SQLite database, initializing it on first access.
27424
27443
  */
27425
27444
  get db() {
27426
27445
  if (!this._db) {
27427
27446
  const fusionDir = join12(this.rootDir, ".fusion");
27428
- this._db = new Database(fusionDir);
27447
+ this._db = new Database(fusionDir, { inMemory: this.inMemoryDb });
27429
27448
  this._db.init();
27430
27449
  }
27431
27450
  return this._db;
@@ -27490,9 +27509,9 @@ var init_automation_store = __esm({
27490
27509
  */
27491
27510
  withScheduleLock(id, fn) {
27492
27511
  const prev = this.scheduleLocks.get(id) ?? Promise.resolve();
27493
- let resolve34;
27512
+ let resolve36;
27494
27513
  const next = new Promise((r) => {
27495
- resolve34 = r;
27514
+ resolve36 = r;
27496
27515
  });
27497
27516
  this.scheduleLocks.set(id, next);
27498
27517
  return prev.then(async () => {
@@ -27502,7 +27521,7 @@ var init_automation_store = __esm({
27502
27521
  if (this.scheduleLocks.get(id) === next) {
27503
27522
  this.scheduleLocks.delete(id);
27504
27523
  }
27505
- resolve34();
27524
+ resolve36();
27506
27525
  }
27507
27526
  });
27508
27527
  }
@@ -29290,7 +29309,7 @@ var init_project_memory = __esm({
29290
29309
  // ../core/src/run-command.ts
29291
29310
  import { spawn } from "node:child_process";
29292
29311
  function runCommandAsync(command, options = {}) {
29293
- return new Promise((resolve34) => {
29312
+ return new Promise((resolve36) => {
29294
29313
  const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
29295
29314
  let stdout = "";
29296
29315
  let stderr = "";
@@ -29349,7 +29368,7 @@ function runCommandAsync(command, options = {}) {
29349
29368
  clearTimeout(forceKillTimer);
29350
29369
  forceKillTimer = null;
29351
29370
  }
29352
- resolve34({
29371
+ resolve36({
29353
29372
  stdout,
29354
29373
  stderr,
29355
29374
  exitCode: null,
@@ -29367,7 +29386,7 @@ function runCommandAsync(command, options = {}) {
29367
29386
  }
29368
29387
  signalProcessGroup("SIGTERM");
29369
29388
  scheduleForceKill(NORMAL_CLEANUP_FORCE_KILL_DELAY_MS);
29370
- resolve34({
29389
+ resolve36({
29371
29390
  stdout,
29372
29391
  stderr,
29373
29392
  exitCode: code,
@@ -29427,8 +29446,8 @@ function assertSafeGitBranchName(name) {
29427
29446
  }
29428
29447
  }
29429
29448
  function assertSafeAbsolutePath(path4) {
29430
- const isAbsolute16 = path4.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path4);
29431
- if (!path4 || path4.length > 4096 || !isAbsolute16 || path4.startsWith("-") || // Reject shell metacharacters, quotes, control chars, and NULs.
29449
+ const isAbsolute17 = path4.startsWith("/") || /^[A-Za-z]:[\\/]/.test(path4);
29450
+ if (!path4 || path4.length > 4096 || !isAbsolute17 || path4.startsWith("-") || // Reject shell metacharacters, quotes, control chars, and NULs.
29432
29451
  /["'`$\n\r\t;&|<>()*?[\]{}\\\0]/.test(
29433
29452
  path4.replace(/^[A-Za-z]:/, "")
29434
29453
  // ignore the drive-letter colon on Windows
@@ -29520,13 +29539,14 @@ var init_store = __esm({
29520
29539
  }
29521
29540
  };
29522
29541
  TaskStore = class _TaskStore extends EventEmitter12 {
29523
- constructor(rootDir, globalSettingsDir) {
29542
+ constructor(rootDir, globalSettingsDir, options) {
29524
29543
  super();
29525
29544
  this.rootDir = rootDir;
29526
29545
  this.setMaxListeners(100);
29527
29546
  this.fusionDir = join15(rootDir, ".fusion");
29528
29547
  this.tasksDir = join15(this.fusionDir, "tasks");
29529
29548
  this.configPath = join15(this.fusionDir, "config.json");
29549
+ this.inMemoryDb = options?.inMemoryDb === true;
29530
29550
  const resolvedGlobalSettingsDir = globalSettingsDir ?? (process.env.VITEST === "true" ? join15(rootDir, ".fusion-global-settings") : void 0);
29531
29551
  this.globalSettingsStore = new GlobalSettingsStore(resolvedGlobalSettingsDir);
29532
29552
  }
@@ -29569,6 +29589,7 @@ var init_store = __esm({
29569
29589
  configPath;
29570
29590
  /** SQLite database for structured data storage */
29571
29591
  _db = null;
29592
+ activityListenersWired = false;
29572
29593
  /** Separate SQLite database for compact archived task snapshots. */
29573
29594
  _archiveDb = null;
29574
29595
  /** File-system watcher instance */
@@ -29611,13 +29632,19 @@ var init_store = __esm({
29611
29632
  insightStore = null;
29612
29633
  /** Cached TodoStore instance */
29613
29634
  todoStore = null;
29635
+ // Test-only: when true, both fusion.db and archive.db open as `:memory:`
29636
+ // SQLite connections instead of disk-backed files. Production code never
29637
+ // sets this; it's gated through an opt-in TaskStoreOptions field below.
29638
+ // Tests that need cross-instance persistence (open store A, close,
29639
+ // open store B on the same dir, expect data) must leave this false.
29640
+ inMemoryDb;
29614
29641
  /**
29615
29642
  * Get the SQLite database, initializing it on first access.
29616
29643
  * Also performs auto-migration from legacy file-based storage if needed.
29617
29644
  */
29618
29645
  get db() {
29619
29646
  if (!this._db) {
29620
- this._db = new Database(this.fusionDir);
29647
+ this._db = new Database(this.fusionDir, { inMemory: this.inMemoryDb });
29621
29648
  this._db.init();
29622
29649
  if (detectLegacyData(this.fusionDir)) {
29623
29650
  }
@@ -29626,7 +29653,7 @@ var init_store = __esm({
29626
29653
  }
29627
29654
  get archiveDb() {
29628
29655
  if (!this._archiveDb) {
29629
- this._archiveDb = new ArchiveDatabase(this.fusionDir);
29656
+ this._archiveDb = new ArchiveDatabase(this.fusionDir, { inMemory: this.inMemoryDb });
29630
29657
  this._archiveDb.init();
29631
29658
  this.migrateLegacyArchiveEntriesToArchiveDb();
29632
29659
  }
@@ -29635,7 +29662,7 @@ var init_store = __esm({
29635
29662
  async init() {
29636
29663
  await mkdir7(this.tasksDir, { recursive: true });
29637
29664
  if (!this._db) {
29638
- this._db = new Database(this.fusionDir);
29665
+ this._db = new Database(this.fusionDir, { inMemory: this.inMemoryDb });
29639
29666
  this._db.init();
29640
29667
  }
29641
29668
  if (detectLegacyData(this.fusionDir)) {
@@ -29704,6 +29731,7 @@ var init_store = __esm({
29704
29731
  postReviewFixCount: row.postReviewFixCount ?? void 0,
29705
29732
  recoveryRetryCount: row.recoveryRetryCount ?? void 0,
29706
29733
  taskDoneRetryCount: row.taskDoneRetryCount ?? void 0,
29734
+ verificationFailureCount: row.verificationFailureCount ?? void 0,
29707
29735
  nextRecoveryAt: row.nextRecoveryAt || void 0,
29708
29736
  error: row.error || void 0,
29709
29737
  summary: row.summary || void 0,
@@ -29988,6 +30016,7 @@ ${recentText}` : void 0
29988
30016
  "postReviewFixCount",
29989
30017
  "recoveryRetryCount",
29990
30018
  "taskDoneRetryCount",
30019
+ "verificationFailureCount",
29991
30020
  "nextRecoveryAt",
29992
30021
  "error",
29993
30022
  "summary",
@@ -30057,6 +30086,7 @@ ${recentText}` : void 0
30057
30086
  "postReviewFixCount",
30058
30087
  "recoveryRetryCount",
30059
30088
  "taskDoneRetryCount",
30089
+ "verificationFailureCount",
30060
30090
  "nextRecoveryAt",
30061
30091
  "error",
30062
30092
  "summary",
@@ -30124,7 +30154,7 @@ ${recentText}` : void 0
30124
30154
  id, title, description, priority, "column", status, size, reviewLevel, currentStep,
30125
30155
  worktree, blockedBy, paused, baseBranch, branch, baseCommitSha, modelPresetId, modelProvider,
30126
30156
  modelId, validatorModelProvider, validatorModelId, planningModelProvider, planningModelId, mergeRetries,
30127
- workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, nextRecoveryAt, error,
30157
+ workflowStepRetries, stuckKillCount, postReviewFixCount, recoveryRetryCount, taskDoneRetryCount, verificationFailureCount, nextRecoveryAt, error,
30128
30158
  summary, thinkingLevel, executionMode, tokenUsageInputTokens, tokenUsageOutputTokens, tokenUsageCachedTokens,
30129
30159
  tokenUsageTotalTokens, tokenUsageFirstUsedAt, tokenUsageLastUsedAt, createdAt, updatedAt, columnMovedAt,
30130
30160
  dependencies, steps, log, attachments, steeringComments,
@@ -30132,7 +30162,7 @@ ${recentText}` : void 0
30132
30162
  sourceIssueProvider, sourceIssueRepository, sourceIssueExternalIssueId, sourceIssueNumber, sourceIssueUrl,
30133
30163
  mergeDetails, breakIntoSubtasks, enabledWorkflowSteps, modifiedFiles, missionId, sliceId, assignedAgentId, assigneeUserId, checkedOutBy, checkedOutAt
30134
30164
  ) VALUES (
30135
- ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
30165
+ ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
30136
30166
  )
30137
30167
  ON CONFLICT(id) DO UPDATE SET
30138
30168
  title = excluded.title,
@@ -30162,6 +30192,7 @@ ${recentText}` : void 0
30162
30192
  postReviewFixCount = excluded.postReviewFixCount,
30163
30193
  recoveryRetryCount = excluded.recoveryRetryCount,
30164
30194
  taskDoneRetryCount = excluded.taskDoneRetryCount,
30195
+ verificationFailureCount = excluded.verificationFailureCount,
30165
30196
  nextRecoveryAt = excluded.nextRecoveryAt,
30166
30197
  error = excluded.error,
30167
30198
  summary = excluded.summary,
@@ -30229,6 +30260,7 @@ ${recentText}` : void 0
30229
30260
  task.postReviewFixCount ?? 0,
30230
30261
  task.recoveryRetryCount ?? null,
30231
30262
  task.taskDoneRetryCount ?? 0,
30263
+ task.verificationFailureCount ?? 0,
30232
30264
  task.nextRecoveryAt ?? null,
30233
30265
  task.error ?? null,
30234
30266
  task.summary ?? null,
@@ -30304,8 +30336,14 @@ ${recentText}` : void 0
30304
30336
  /**
30305
30337
  * Set up event listeners for activity logging.
30306
30338
  * Call after init() to record task lifecycle events.
30339
+ *
30340
+ * Idempotent — repeated calls are no-ops. Without this guard, each duplicate
30341
+ * call double-registers handlers, causing the activity log to record every
30342
+ * `task:created` / `task:moved` event N times where N = number of init() calls.
30307
30343
  */
30308
30344
  setupActivityLogListeners() {
30345
+ if (this.activityListenersWired) return;
30346
+ this.activityListenersWired = true;
30309
30347
  this.on("task:created", (task) => {
30310
30348
  this.recordActivityFromListener(
30311
30349
  {
@@ -30409,9 +30447,9 @@ ${recentText}` : void 0
30409
30447
  * lost-update races on the nextId counter.
30410
30448
  */
30411
30449
  withConfigLock(fn) {
30412
- let resolve34;
30450
+ let resolve36;
30413
30451
  const next = new Promise((r) => {
30414
- resolve34 = r;
30452
+ resolve36 = r;
30415
30453
  });
30416
30454
  const prev = this.configLock;
30417
30455
  this.configLock = next;
@@ -30419,7 +30457,7 @@ ${recentText}` : void 0
30419
30457
  try {
30420
30458
  return await fn();
30421
30459
  } finally {
30422
- resolve34();
30460
+ resolve36();
30423
30461
  }
30424
30462
  });
30425
30463
  }
@@ -30429,9 +30467,9 @@ ${recentText}` : void 0
30429
30467
  */
30430
30468
  withTaskLock(id, fn) {
30431
30469
  const prev = this.taskLocks.get(id) ?? Promise.resolve();
30432
- let resolve34;
30470
+ let resolve36;
30433
30471
  const next = new Promise((r) => {
30434
- resolve34 = r;
30472
+ resolve36 = r;
30435
30473
  });
30436
30474
  this.taskLocks.set(id, next);
30437
30475
  return prev.then(async () => {
@@ -30441,7 +30479,7 @@ ${recentText}` : void 0
30441
30479
  if (this.taskLocks.get(id) === next) {
30442
30480
  this.taskLocks.delete(id);
30443
30481
  }
30444
- resolve34();
30482
+ resolve36();
30445
30483
  }
30446
30484
  });
30447
30485
  }
@@ -31551,6 +31589,11 @@ ${newTask.description}
31551
31589
  } else if (updates.taskDoneRetryCount !== void 0) {
31552
31590
  task.taskDoneRetryCount = updates.taskDoneRetryCount;
31553
31591
  }
31592
+ if (updates.verificationFailureCount === null) {
31593
+ task.verificationFailureCount = void 0;
31594
+ } else if (updates.verificationFailureCount !== void 0) {
31595
+ task.verificationFailureCount = updates.verificationFailureCount;
31596
+ }
31554
31597
  if (updates.nextRecoveryAt === null) {
31555
31598
  task.nextRecoveryAt = void 0;
31556
31599
  } else if (updates.nextRecoveryAt !== void 0) {
@@ -32542,7 +32585,7 @@ ${task.description}
32542
32585
  this.emit("task:deleted", cached);
32543
32586
  }
32544
32587
  }
32545
- await new Promise((resolve34) => setImmediate(resolve34));
32588
+ await new Promise((resolve36) => setImmediate(resolve36));
32546
32589
  const selectClause = this.getTaskSelectClause(true);
32547
32590
  const changedRows = this.lastPollTime ? this.db.prepare(`SELECT ${selectClause} FROM tasks WHERE updatedAt > ? OR columnMovedAt > ?`).all(this.lastPollTime, this.lastPollTime) : this.db.prepare(`SELECT ${selectClause} FROM tasks`).all();
32548
32591
  this.lastPollTime = (/* @__PURE__ */ new Date()).toISOString();
@@ -32562,7 +32605,7 @@ ${task.description}
32562
32605
  this.emit("task:updated", task);
32563
32606
  }
32564
32607
  if (i > 0 && i % 50 === 0) {
32565
- await new Promise((resolve34) => setImmediate(resolve34));
32608
+ await new Promise((resolve36) => setImmediate(resolve36));
32566
32609
  }
32567
32610
  }
32568
32611
  const elapsed = Date.now() - startTime;
@@ -34383,7 +34426,7 @@ function runGh(args, cwd) {
34383
34426
  }
34384
34427
  }
34385
34428
  function runGhAsync(args, cwd) {
34386
- return new Promise((resolve34, reject2) => {
34429
+ return new Promise((resolve36, reject2) => {
34387
34430
  execFile2(
34388
34431
  "gh",
34389
34432
  args,
@@ -34399,7 +34442,7 @@ function runGhAsync(args, cwd) {
34399
34442
  ghError.stderr = stderr ?? "";
34400
34443
  reject2(ghError);
34401
34444
  } else {
34402
- resolve34(stdout ?? "");
34445
+ resolve36(stdout ?? "");
34403
34446
  }
34404
34447
  }
34405
34448
  );
@@ -34517,14 +34560,16 @@ var init_routine_store = __esm({
34517
34560
  init_routine();
34518
34561
  CRON_TIMEZONE2 = "UTC";
34519
34562
  RoutineStore = class _RoutineStore extends EventEmitter13 {
34520
- constructor(rootDir) {
34563
+ constructor(rootDir, options) {
34521
34564
  super();
34522
34565
  this.rootDir = rootDir;
34566
+ this.inMemoryDb = options?.inMemoryDb === true;
34523
34567
  }
34524
34568
  /** SQLite database instance (lazy init). */
34525
34569
  _db = null;
34526
34570
  /** Per-routine promise chain for serializing writes. */
34527
34571
  routineLocks = /* @__PURE__ */ new Map();
34572
+ inMemoryDb;
34528
34573
  // ── Database Access ────────────────────────────────────────────────
34529
34574
  /**
34530
34575
  * Get the SQLite database, initializing it on first access.
@@ -34532,7 +34577,7 @@ var init_routine_store = __esm({
34532
34577
  get db() {
34533
34578
  if (!this._db) {
34534
34579
  const fusionDir = `${this.rootDir}/.fusion`;
34535
- this._db = new Database(fusionDir);
34580
+ this._db = new Database(fusionDir, { inMemory: this.inMemoryDb });
34536
34581
  this._db.init();
34537
34582
  }
34538
34583
  return this._db;
@@ -34653,9 +34698,9 @@ var init_routine_store = __esm({
34653
34698
  */
34654
34699
  withRoutineLock(id, fn) {
34655
34700
  const prev = this.routineLocks.get(id) ?? Promise.resolve();
34656
- let resolve34;
34701
+ let resolve36;
34657
34702
  const next = new Promise((r) => {
34658
- resolve34 = r;
34703
+ resolve36 = r;
34659
34704
  });
34660
34705
  this.routineLocks.set(id, next);
34661
34706
  return prev.then(async () => {
@@ -34665,7 +34710,7 @@ var init_routine_store = __esm({
34665
34710
  if (this.routineLocks.get(id) === next) {
34666
34711
  this.routineLocks.delete(id);
34667
34712
  }
34668
- resolve34();
34713
+ resolve36();
34669
34714
  }
34670
34715
  });
34671
34716
  }
@@ -35187,13 +35232,13 @@ var init_plugin_loader = __esm({
35187
35232
  * Execute a promise with a timeout.
35188
35233
  */
35189
35234
  withTimeout(promise, ms, timeoutMessage) {
35190
- return new Promise((resolve34, reject2) => {
35235
+ return new Promise((resolve36, reject2) => {
35191
35236
  const timer = setTimeout(() => {
35192
35237
  reject2(new Error(timeoutMessage));
35193
35238
  }, ms);
35194
35239
  promise.then((result) => {
35195
35240
  clearTimeout(timer);
35196
- resolve34(result);
35241
+ resolve36(result);
35197
35242
  }).catch((err) => {
35198
35243
  clearTimeout(timer);
35199
35244
  reject2(err);
@@ -38400,7 +38445,7 @@ var require_get_stream = __commonJS({
38400
38445
  };
38401
38446
  const { maxBuffer } = options;
38402
38447
  let stream;
38403
- await new Promise((resolve34, reject2) => {
38448
+ await new Promise((resolve36, reject2) => {
38404
38449
  const rejectPromise = (error) => {
38405
38450
  if (error && stream.getBufferedLength() <= BufferConstants.MAX_LENGTH) {
38406
38451
  error.bufferedData = stream.getBufferedValue();
@@ -38412,7 +38457,7 @@ var require_get_stream = __commonJS({
38412
38457
  rejectPromise(error);
38413
38458
  return;
38414
38459
  }
38415
- resolve34();
38460
+ resolve36();
38416
38461
  });
38417
38462
  stream.on("data", () => {
38418
38463
  if (stream.getBufferedLength() > maxBuffer) {
@@ -39706,7 +39751,7 @@ var require_extract_zip = __commonJS({
39706
39751
  debug("opening", this.zipPath, "with opts", this.opts);
39707
39752
  this.zipfile = await openZip(this.zipPath, { lazyEntries: true });
39708
39753
  this.canceled = false;
39709
- return new Promise((resolve34, reject2) => {
39754
+ return new Promise((resolve36, reject2) => {
39710
39755
  this.zipfile.on("error", (err) => {
39711
39756
  this.canceled = true;
39712
39757
  reject2(err);
@@ -39715,7 +39760,7 @@ var require_extract_zip = __commonJS({
39715
39760
  this.zipfile.on("close", () => {
39716
39761
  if (!this.canceled) {
39717
39762
  debug("zip extraction complete");
39718
- resolve34();
39763
+ resolve36();
39719
39764
  }
39720
39765
  });
39721
39766
  this.zipfile.on("entry", async (entry) => {
@@ -50414,12 +50459,12 @@ var init_concurrency = __esm({
50414
50459
  this._active++;
50415
50460
  return Promise.resolve();
50416
50461
  }
50417
- return new Promise((resolve34) => {
50462
+ return new Promise((resolve36) => {
50418
50463
  this._waiters.push({
50419
50464
  priority,
50420
50465
  resolve: () => {
50421
50466
  this._active++;
50422
- resolve34();
50467
+ resolve36();
50423
50468
  }
50424
50469
  });
50425
50470
  });
@@ -52839,20 +52884,20 @@ async function withRateLimitRetry(fn, options = {}) {
52839
52884
  throw lastError ?? new Error("withRateLimitRetry: unexpected state");
52840
52885
  }
52841
52886
  function sleep(ms, signal) {
52842
- return new Promise((resolve34, reject2) => {
52887
+ return new Promise((resolve36, reject2) => {
52843
52888
  if (signal?.aborted) {
52844
52889
  reject2(signal.reason ?? new Error("Aborted"));
52845
52890
  return;
52846
52891
  }
52847
- const timer = setTimeout(resolve34, ms);
52892
+ const timer = setTimeout(resolve36, ms);
52848
52893
  if (signal) {
52849
52894
  const onAbort = () => {
52850
52895
  clearTimeout(timer);
52851
52896
  reject2(signal.reason ?? new Error("Aborted"));
52852
52897
  };
52853
52898
  signal.addEventListener("abort", onAbort, { once: true });
52854
- const origResolve = resolve34;
52855
- resolve34 = () => {
52899
+ const origResolve = resolve36;
52900
+ resolve36 = () => {
52856
52901
  signal.removeEventListener("abort", onAbort);
52857
52902
  origResolve();
52858
52903
  };
@@ -54747,7 +54792,14 @@ import { existsSync as existsSync21 } from "node:fs";
54747
54792
  import { join as join26 } from "node:path";
54748
54793
  import { Type as Type3 } from "typebox";
54749
54794
  async function execWithProcessGroup(command, options) {
54750
- return new Promise((resolve34, reject2) => {
54795
+ return new Promise((resolve36, reject2) => {
54796
+ if (options.signal?.aborted) {
54797
+ reject2(Object.assign(
54798
+ new Error(`Command aborted before start: ${command}`),
54799
+ { code: "ABORT_ERR", aborted: true, stdout: "", stderr: "" }
54800
+ ));
54801
+ return;
54802
+ }
54751
54803
  const child = spawn2(command, {
54752
54804
  cwd: options.cwd,
54753
54805
  shell: true,
@@ -54759,24 +54811,33 @@ async function execWithProcessGroup(command, options) {
54759
54811
  let stdoutOverflow = false;
54760
54812
  let stderrOverflow = false;
54761
54813
  let timedOut = false;
54814
+ let aborted = false;
54762
54815
  let settled = false;
54816
+ const killTree = (sig) => {
54817
+ if (child.pid === void 0) return;
54818
+ try {
54819
+ process.kill(-child.pid, sig);
54820
+ } catch {
54821
+ }
54822
+ };
54763
54823
  const timer = setTimeout(() => {
54764
54824
  timedOut = true;
54765
- if (child.pid !== void 0) {
54766
- try {
54767
- process.kill(-child.pid, "SIGTERM");
54768
- } catch {
54769
- }
54770
- setTimeout(() => {
54771
- if (settled) return;
54772
- try {
54773
- process.kill(-child.pid, "SIGKILL");
54774
- } catch {
54775
- }
54776
- }, 5e3).unref();
54777
- }
54825
+ killTree("SIGTERM");
54826
+ setTimeout(() => {
54827
+ if (settled) return;
54828
+ killTree("SIGKILL");
54829
+ }, 5e3).unref();
54778
54830
  }, options.timeout);
54779
54831
  timer.unref();
54832
+ const onAbort = () => {
54833
+ aborted = true;
54834
+ killTree("SIGTERM");
54835
+ setTimeout(() => {
54836
+ if (settled) return;
54837
+ killTree("SIGKILL");
54838
+ }, 5e3).unref();
54839
+ };
54840
+ options.signal?.addEventListener("abort", onAbort, { once: true });
54780
54841
  child.stdout?.on("data", (chunk) => {
54781
54842
  if (stdoutOverflow) return;
54782
54843
  if (stdout.length + chunk.length > options.maxBuffer) {
@@ -54799,6 +54860,14 @@ async function execWithProcessGroup(command, options) {
54799
54860
  if (settled) return;
54800
54861
  settled = true;
54801
54862
  clearTimeout(timer);
54863
+ options.signal?.removeEventListener("abort", onAbort);
54864
+ if (aborted) {
54865
+ reject2(Object.assign(
54866
+ new Error(`Command aborted: ${command}`),
54867
+ { code: "ABORT_ERR", aborted: true, stdout, stderr, killed: true }
54868
+ ));
54869
+ return;
54870
+ }
54802
54871
  if (timedOut) {
54803
54872
  reject2(Object.assign(
54804
54873
  new Error(`Command timed out after ${options.timeout}ms: ${command}`),
@@ -54811,7 +54880,7 @@ async function execWithProcessGroup(command, options) {
54811
54880
  return;
54812
54881
  }
54813
54882
  if (code === 0) {
54814
- resolve34({ stdout, stderr, bufferOverflow: stdoutOverflow || stderrOverflow });
54883
+ resolve36({ stdout, stderr, bufferOverflow: stdoutOverflow || stderrOverflow });
54815
54884
  return;
54816
54885
  }
54817
54886
  reject2(Object.assign(
@@ -55152,7 +55221,8 @@ async function runVerificationCommand(store, rootDir, taskId, command, type, sig
55152
55221
  const { stdout, stderr, bufferOverflow } = await execWithProcessGroup(command, {
55153
55222
  cwd: rootDir,
55154
55223
  timeout: VERIFICATION_COMMAND_TIMEOUT_MS,
55155
- maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER
55224
+ maxBuffer: VERIFICATION_COMMAND_MAX_BUFFER,
55225
+ signal
55156
55226
  });
55157
55227
  throwIfAborted(signal, taskId);
55158
55228
  result.stdout = stdout?.toString?.() || "";
@@ -58269,7 +58339,7 @@ function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
58269
58339
  return { provider: void 0, modelId: void 0 };
58270
58340
  }
58271
58341
  function sleep2(ms) {
58272
- return new Promise((resolve34) => setTimeout(resolve34, ms));
58342
+ return new Promise((resolve36) => setTimeout(resolve36, ms));
58273
58343
  }
58274
58344
  var execAsync4, stepExecLog, MAX_STEP_RETRIES, RETRY_DELAYS_MS, NOOP_TASK_STORE, StepSessionExecutor;
58275
58345
  var init_step_session_executor = __esm({
@@ -62072,7 +62142,7 @@ Review the work done in this worktree and evaluate it against the criteria in yo
62072
62142
  );
62073
62143
  }
62074
62144
  const delay2 = this.WORKTREE_RETRY_DELAYS[attempt] || 1e3;
62075
- await new Promise((resolve34) => setTimeout(resolve34, delay2));
62145
+ await new Promise((resolve36) => setTimeout(resolve36, delay2));
62076
62146
  }
62077
62147
  }
62078
62148
  throw new Error("Unexpected exit from worktree creation retry loop");
@@ -68960,8 +69030,8 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
68960
69030
  // ../engine/src/self-healing.ts
68961
69031
  import { exec as exec8 } from "node:child_process";
68962
69032
  import { promisify as promisify9 } from "node:util";
68963
- import { existsSync as existsSync27, readdirSync as readdirSync5, statSync as statSync5 } from "node:fs";
68964
- import { join as join33 } from "node:path";
69033
+ import { existsSync as existsSync27, readdirSync as readdirSync5, rmSync as rmSync3, statSync as statSync5 } from "node:fs";
69034
+ import { isAbsolute as isAbsolute11, join as join33, relative as relative7, resolve as resolve14 } from "node:path";
68965
69035
  function shellQuote(value) {
68966
69036
  return `'${value.replace(/'/g, "'\\''")}'`;
68967
69037
  }
@@ -70117,15 +70187,26 @@ var init_self_healing = __esm({
70117
70187
  log7.error(`Worktree prune failed: ${errorMessage}`);
70118
70188
  }
70119
70189
  }
70120
- /** Remove orphaned worktrees not assigned to any active task. */
70190
+ /**
70191
+ * Remove orphaned worktrees not assigned to any active task.
70192
+ *
70193
+ * When `recycleWorktrees` is OFF: removes registered idle worktrees too —
70194
+ * they would otherwise pile up since the pool isn't keeping them.
70195
+ *
70196
+ * When `recycleWorktrees` is ON: leaves registered idle worktrees alone
70197
+ * (the pool wants them for reuse) but still reaps unregistered stale dirs
70198
+ * left behind by killed runs (e.g., `clear-hawk-broken`, `*-bak`). Those
70199
+ * dirs can never be recycled — they aren't git worktrees — so they only
70200
+ * waste disk.
70201
+ */
70121
70202
  async cleanupOrphans() {
70122
70203
  try {
70123
- const orphaned = await scanIdleWorktrees(this.options.rootDir, this.store);
70124
- if (orphaned.length === 0) return 0;
70125
70204
  const settings = await this.store.getSettings();
70126
70205
  if (settings.recycleWorktrees) {
70127
- return 0;
70206
+ return await this.reapUnregisteredOrphans();
70128
70207
  }
70208
+ const orphaned = await scanIdleWorktrees(this.options.rootDir, this.store);
70209
+ if (orphaned.length === 0) return 0;
70129
70210
  let cleaned = 0;
70130
70211
  for (const worktreePath of orphaned) {
70131
70212
  try {
@@ -70149,6 +70230,45 @@ var init_self_healing = __esm({
70149
70230
  return 0;
70150
70231
  }
70151
70232
  }
70233
+ /**
70234
+ * Sweep unregistered stale directories under `<rootDir>/.worktrees/` —
70235
+ * directories that exist on disk but are NOT registered git worktrees.
70236
+ * Safe to run alongside `recycleWorktrees: true` because the pool only
70237
+ * tracks registered idle worktrees, never these orphans.
70238
+ */
70239
+ async reapUnregisteredOrphans() {
70240
+ const worktreesDir = join33(this.options.rootDir, ".worktrees");
70241
+ if (!existsSync27(worktreesDir)) return 0;
70242
+ let dirs;
70243
+ try {
70244
+ dirs = readdirSync5(worktreesDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => join33(worktreesDir, e.name));
70245
+ } catch (err) {
70246
+ log7.warn(`Failed to read .worktrees/ for unregistered orphan reap: ${err instanceof Error ? err.message : String(err)}`);
70247
+ return 0;
70248
+ }
70249
+ if (dirs.length === 0) return 0;
70250
+ const registered = await getRegisteredWorktreePaths(this.options.rootDir);
70251
+ const unregistered = dirs.filter((d) => !registered.has(resolve14(d)));
70252
+ let cleaned = 0;
70253
+ for (const path4 of unregistered) {
70254
+ const rel = relative7(worktreesDir, path4);
70255
+ if (rel === "" || rel.startsWith("..") || isAbsolute11(rel)) {
70256
+ log7.warn(`Refusing to remove path outside .worktrees: ${path4}`);
70257
+ continue;
70258
+ }
70259
+ try {
70260
+ rmSync3(path4, { recursive: true, force: true });
70261
+ log7.log(`Cleaned unregistered worktree dir: ${path4}`);
70262
+ cleaned++;
70263
+ } catch (err) {
70264
+ log7.warn(`Failed to remove unregistered worktree dir ${path4}: ${err instanceof Error ? err.message : String(err)}`);
70265
+ }
70266
+ }
70267
+ if (cleaned > 0) {
70268
+ log7.log(`Cleaned ${cleaned} unregistered worktree dir(s) (recycle mode preserves registered idle worktrees)`);
70269
+ }
70270
+ return cleaned;
70271
+ }
70152
70272
  /**
70153
70273
  * Remove orphaned `fusion/*` branches that are not associated with any
70154
70274
  * active (non-archived, non-merger-managed) task.
@@ -70765,13 +70885,13 @@ var init_plugin_runner = __esm({
70765
70885
  * Returns the result on success, throws on timeout.
70766
70886
  */
70767
70887
  withTimeout(promise, ms, timeoutMessage) {
70768
- return new Promise((resolve34, reject2) => {
70888
+ return new Promise((resolve36, reject2) => {
70769
70889
  const timer = setTimeout(() => {
70770
70890
  reject2(new Error(timeoutMessage));
70771
70891
  }, ms);
70772
70892
  promise.then((result) => {
70773
70893
  clearTimeout(timer);
70774
- resolve34(result);
70894
+ resolve36(result);
70775
70895
  }).catch((err) => {
70776
70896
  clearTimeout(timer);
70777
70897
  reject2(err);
@@ -71429,7 +71549,7 @@ var init_in_process_runtime = __esm({
71429
71549
  runtimeLog.log(
71430
71550
  `Waiting for ${metrics.inFlightTasks} in-flight tasks to complete...`
71431
71551
  );
71432
- await new Promise((resolve34) => setTimeout(resolve34, 1e3));
71552
+ await new Promise((resolve36) => setTimeout(resolve36, 1e3));
71433
71553
  }
71434
71554
  const finalMetrics = this.getMetrics();
71435
71555
  if (finalMetrics.inFlightTasks > 0) {
@@ -71818,13 +71938,13 @@ var init_ipc_host = __esm({
71818
71938
  }
71819
71939
  const id = generateCorrelationId();
71820
71940
  const message = { type, id, payload };
71821
- return new Promise((resolve34, reject2) => {
71941
+ return new Promise((resolve36, reject2) => {
71822
71942
  const timeout2 = setTimeout(() => {
71823
71943
  this.pendingCommands.delete(id);
71824
71944
  reject2(new Error(`Command ${type} timed out after ${timeoutMs ?? this.commandTimeoutMs}ms`));
71825
71945
  }, timeoutMs ?? this.commandTimeoutMs);
71826
71946
  this.pendingCommands.set(id, {
71827
- resolve: resolve34,
71947
+ resolve: resolve36,
71828
71948
  reject: reject2,
71829
71949
  timeout: timeout2,
71830
71950
  type
@@ -72633,8 +72753,8 @@ var init_remote_node_client = __esm({
72633
72753
  return error instanceof TypeError;
72634
72754
  }
72635
72755
  async sleep(ms) {
72636
- await new Promise((resolve34) => {
72637
- setTimeout(resolve34, ms);
72756
+ await new Promise((resolve36) => {
72757
+ setTimeout(resolve36, ms);
72638
72758
  });
72639
72759
  }
72640
72760
  };
@@ -72898,14 +73018,14 @@ var init_remote_node_runtime = __esm({
72898
73018
  return error instanceof Error ? error : new Error(String(error));
72899
73019
  }
72900
73020
  async sleep(ms, signal) {
72901
- await new Promise((resolve34) => {
73021
+ await new Promise((resolve36) => {
72902
73022
  const timeout2 = setTimeout(() => {
72903
73023
  cleanup();
72904
- resolve34();
73024
+ resolve36();
72905
73025
  }, ms);
72906
73026
  const onAbort = () => {
72907
73027
  cleanup();
72908
- resolve34();
73028
+ resolve36();
72909
73029
  };
72910
73030
  const cleanup = () => {
72911
73031
  clearTimeout(timeout2);
@@ -73666,10 +73786,10 @@ var init_tunnel_process_manager = __esm({
73666
73786
  lastError: null
73667
73787
  });
73668
73788
  this.emitLog("info", "manager", `Stopping ${currentHandle.provider} tunnel (pid=${currentHandle.child.pid ?? "n/a"})`);
73669
- this.activeStopPromise = new Promise((resolve34) => {
73789
+ this.activeStopPromise = new Promise((resolve36) => {
73670
73790
  const onClose = () => {
73671
73791
  currentHandle.child.removeListener("close", onClose);
73672
- resolve34();
73792
+ resolve36();
73673
73793
  };
73674
73794
  currentHandle.child.once("close", onClose);
73675
73795
  killManagedProcess(currentHandle.child, "SIGTERM");
@@ -73848,6 +73968,12 @@ var init_project_engine = __esm({
73848
73968
  manualMergeResolvers = /* @__PURE__ */ new Map();
73849
73969
  shuttingDown = false;
73850
73970
  static MAX_AUTO_MERGE_RETRIES = 3;
73971
+ /** Cap on outer in-review→in-progress bounces caused by deterministic
73972
+ * verification failures during auto-merge. After this many failed merges
73973
+ * for the same task, we stop bouncing it back, mark it failed, and create
73974
+ * a follow-up triage task so a fresh agent (or human) can investigate
73975
+ * the underlying flake/regression instead of looping forever. */
73976
+ static MAX_VERIFICATION_FAILURE_BOUNCES = 3;
73851
73977
  /** 30-minute cooldown before a retry-exhausted task gets another sweep attempt */
73852
73978
  static AUTO_MERGE_COOLDOWN_MS = 30 * 60 * 1e3;
73853
73979
  // Event handler references for cleanup
@@ -74132,12 +74258,12 @@ var init_project_engine = __esm({
74132
74258
  */
74133
74259
  async onMerge(taskId) {
74134
74260
  if (this.mergeActive.has(taskId)) {
74135
- return new Promise((resolve34, reject2) => {
74136
- this.manualMergeResolvers.set(taskId, { resolve: resolve34, reject: reject2 });
74261
+ return new Promise((resolve36, reject2) => {
74262
+ this.manualMergeResolvers.set(taskId, { resolve: resolve36, reject: reject2 });
74137
74263
  });
74138
74264
  }
74139
- return new Promise((resolve34, reject2) => {
74140
- this.manualMergeResolvers.set(taskId, { resolve: resolve34, reject: reject2 });
74265
+ return new Promise((resolve36, reject2) => {
74266
+ this.manualMergeResolvers.set(taskId, { resolve: resolve36, reject: reject2 });
74141
74267
  this.internalEnqueueMerge(taskId);
74142
74268
  });
74143
74269
  }
@@ -74524,20 +74650,61 @@ var init_project_engine = __esm({
74524
74650
  const isVerificationError = err instanceof Error && err.name === "VerificationError" || errorMsg.includes("Deterministic test verification failed") || errorMsg.includes("Deterministic build verification failed");
74525
74651
  if (taskOnErr && isVerificationError) {
74526
74652
  const failedKind = errorMsg.includes("build verification") ? "build" : "test";
74653
+ const previousBounces = taskOnErr.verificationFailureCount ?? 0;
74654
+ const nextBounces = previousBounces + 1;
74655
+ const cap = _ProjectEngine.MAX_VERIFICATION_FAILURE_BOUNCES;
74656
+ if (nextBounces >= cap) {
74657
+ try {
74658
+ await store.updateTask(taskId, {
74659
+ status: "failed",
74660
+ verificationFailureCount: nextBounces,
74661
+ error: `Deterministic ${failedKind} verification failed ${nextBounces}\xD7 \u2014 auto-merge giving up to avoid infinite retry loop. See follow-up task for investigation.`
74662
+ });
74663
+ const followUpDescription = `Investigate repeated ${failedKind} verification failure on ${taskId} (${taskOnErr.title || "untitled"}). Auto-merge attempted to fix and re-verify ${nextBounces} times without success \u2014 likely a flaky test or unrelated regression rather than a fix this task can produce on its own. Look at the most recent [verification] log entries on ${taskId} for the failing command and output, then either fix the underlying issue or quarantine the flake.`;
74664
+ const followUp = await store.createTask({
74665
+ description: followUpDescription,
74666
+ column: "triage",
74667
+ priority: "high"
74668
+ });
74669
+ await store.addTaskComment(
74670
+ taskId,
74671
+ `Auto-merge giving up after ${nextBounces} verification-failure bounces. Created follow-up ${followUp.id} to investigate.`,
74672
+ "agent"
74673
+ );
74674
+ await store.logEntry(
74675
+ taskId,
74676
+ `Auto-merge gave up after ${nextBounces} verification-failure bounces \u2014 created follow-up ${followUp.id}`,
74677
+ "VerificationError"
74678
+ );
74679
+ runtimeLog.warn(
74680
+ `Auto-merge: ${taskId} hit verification-failure cap (${nextBounces}/${cap}) \u2014 failed task and created follow-up ${followUp.id}`
74681
+ );
74682
+ } catch (followUpErr) {
74683
+ runtimeLog.error(
74684
+ `Auto-merge: failed to fail-and-followup ${taskId} after verification cap: ${followUpErr instanceof Error ? followUpErr.message : String(followUpErr)}`
74685
+ );
74686
+ }
74687
+ continue;
74688
+ }
74527
74689
  try {
74528
74690
  await store.addTaskComment(
74529
74691
  taskId,
74530
- `Deterministic ${failedKind} verification failed during merge. See the prior [verification] log entry for the truncated command output. Please fix the failing ${failedKind} and push the update so the merge can retry.`,
74692
+ `Deterministic ${failedKind} verification failed during merge (attempt ${nextBounces}/${cap}). See the prior [verification] log entry for the truncated command output. Please fix the failing ${failedKind} and push the update so the merge can retry.`,
74531
74693
  "agent"
74532
74694
  );
74533
- await store.updateTask(taskId, { status: null, mergeRetries: 0, error: null });
74695
+ await store.updateTask(taskId, {
74696
+ status: null,
74697
+ mergeRetries: 0,
74698
+ error: null,
74699
+ verificationFailureCount: nextBounces
74700
+ });
74534
74701
  await store.moveTask(taskId, "in-progress");
74535
74702
  await store.logEntry(
74536
74703
  taskId,
74537
- `Deterministic ${failedKind} verification failed \u2014 moved back to in-progress for remediation`
74704
+ `Deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved back to in-progress for remediation`
74538
74705
  );
74539
74706
  runtimeLog.log(
74540
- `Auto-merge: ${taskId} deterministic ${failedKind} verification failed \u2014 moved to in-progress`
74707
+ `Auto-merge: ${taskId} deterministic ${failedKind} verification failed (${nextBounces}/${cap}) \u2014 moved to in-progress`
74541
74708
  );
74542
74709
  } catch {
74543
74710
  runtimeLog.error(
@@ -74751,6 +74918,11 @@ var init_project_engine = __esm({
74751
74918
  wireSettingsListeners(store) {
74752
74919
  const onGlobalPause = ({ settings, previous }) => {
74753
74920
  if (settings.globalPause && !previous.globalPause) {
74921
+ if (this.mergeAbortController) {
74922
+ runtimeLog.log("Global pause \u2014 aborting in-flight merge verification");
74923
+ this.mergeAbortController.abort();
74924
+ this.mergeAbortController = null;
74925
+ }
74754
74926
  if (this.activeMergeSession) {
74755
74927
  runtimeLog.log("Global pause \u2014 terminating active merge session");
74756
74928
  this.activeMergeSession.dispose();
@@ -75793,7 +75965,6 @@ __export(src_exports2, {
75793
75965
  taskLogParams: () => taskLogParams,
75794
75966
  withRateLimitRetry: () => withRateLimitRetry
75795
75967
  });
75796
- var fusionCoreExports;
75797
75968
  var init_src2 = __esm({
75798
75969
  "../engine/src/index.ts"() {
75799
75970
  "use strict";
@@ -75808,7 +75979,6 @@ var init_src2 = __esm({
75808
75979
  init_merger();
75809
75980
  init_reviewer();
75810
75981
  init_pi();
75811
- init_src();
75812
75982
  init_pi();
75813
75983
  init_skill_resolver();
75814
75984
  init_agent_reflection();
@@ -75839,12 +76009,12 @@ var init_src2 = __esm({
75839
76009
  init_remote_node_client();
75840
76010
  init_remote_node_runtime();
75841
76011
  init_step_session_executor();
75842
- fusionCoreExports = src_exports;
75843
- if ("setCreateFnAgent" in fusionCoreExports && typeof fusionCoreExports["setCreateFnAgent"] === "function") {
75844
- fusionCoreExports["setCreateFnAgent"](
75845
- createFnAgent2
75846
- );
75847
- }
76012
+ void Promise.resolve().then(() => (init_src(), src_exports)).then((core) => {
76013
+ if ("setCreateFnAgent" in core && typeof core.setCreateFnAgent === "function") {
76014
+ core.setCreateFnAgent(createFnAgent2);
76015
+ }
76016
+ }).catch(() => {
76017
+ });
75848
76018
  }
75849
76019
  });
75850
76020
 
@@ -75882,17 +76052,6 @@ __export(planning_exports, {
75882
76052
  });
75883
76053
  import { randomUUID as randomUUID10 } from "node:crypto";
75884
76054
  import { EventEmitter as EventEmitter24 } from "node:events";
75885
- function buildDefaultPlanningNtfyHelpers() {
75886
- const isNtfyEventEnabled2 = "isNtfyEventEnabled" in src_exports2 ? isNtfyEventEnabled : (events, event) => Array.isArray(events) ? events.includes(event) : false;
75887
- const buildNtfyClickUrl2 = "buildNtfyClickUrl" in src_exports2 ? buildNtfyClickUrl : () => void 0;
75888
- const sendNtfyNotification2 = "sendNtfyNotification" in src_exports2 ? sendNtfyNotification : async () => {
75889
- };
75890
- return {
75891
- isNtfyEventEnabled: isNtfyEventEnabled2,
75892
- buildNtfyClickUrl: buildNtfyClickUrl2,
75893
- sendNtfyNotification: sendNtfyNotification2
75894
- };
75895
- }
75896
76055
  function __getPlanningDiagnostics() {
75897
76056
  return diagnostics2;
75898
76057
  }
@@ -76860,7 +77019,7 @@ function __resetPlanningState() {
76860
77019
  }
76861
77020
  _aiSessionDeletedListener = void 0;
76862
77021
  _aiSessionStore = void 0;
76863
- planningNtfyHelpers = buildDefaultPlanningNtfyHelpers();
77022
+ planningNtfyHelpers = void 0;
76864
77023
  resetDiagnosticsSink();
76865
77024
  }
76866
77025
  function __setCreateFnAgent(mock) {
@@ -76869,7 +77028,7 @@ function __setCreateFnAgent(mock) {
76869
77028
  function __setPlanningNtfyHelpers(mock) {
76870
77029
  planningNtfyHelpers = mock;
76871
77030
  }
76872
- var createFnAgent4, engineExports, engineIsNtfyEventEnabled, engineBuildNtfyClickUrl, engineSendNtfyNotification, planningNtfyHelpers, diagnostics2, PLANNING_SYSTEM_PROMPT, SESSION_TTL_MS, CLEANUP_INTERVAL_MS2, MAX_SESSIONS_PER_IP_PER_HOUR, RATE_LIMIT_WINDOW_MS2, sessions, rateLimits2, _aiSessionStore, _aiSessionDeletedListener, cleanupInterval2, PlanningStreamManager, planningStreamManager, MAX_PARSE_RETRIES, RateLimitError2, SessionNotFoundError, InvalidSessionStateError;
77031
+ var createFnAgent4, planningNtfyHelpers, diagnostics2, PLANNING_SYSTEM_PROMPT, SESSION_TTL_MS, CLEANUP_INTERVAL_MS2, MAX_SESSIONS_PER_IP_PER_HOUR, RATE_LIMIT_WINDOW_MS2, sessions, rateLimits2, _aiSessionStore, _aiSessionDeletedListener, cleanupInterval2, PlanningStreamManager, planningStreamManager, MAX_PARSE_RETRIES, RateLimitError2, SessionNotFoundError, InvalidSessionStateError;
76873
77032
  var init_planning = __esm({
76874
77033
  "../dashboard/src/planning.ts"() {
76875
77034
  "use strict";
@@ -76878,12 +77037,6 @@ var init_planning = __esm({
76878
77037
  init_ai_session_diagnostics();
76879
77038
  init_src2();
76880
77039
  createFnAgent4 = createFnAgent2;
76881
- engineExports = src_exports2;
76882
- engineIsNtfyEventEnabled = "isNtfyEventEnabled" in engineExports && typeof engineExports["isNtfyEventEnabled"] === "function" ? engineExports["isNtfyEventEnabled"] : (events, event) => Array.isArray(events) && events.includes(event);
76883
- engineBuildNtfyClickUrl = "buildNtfyClickUrl" in engineExports && typeof engineExports["buildNtfyClickUrl"] === "function" ? engineExports["buildNtfyClickUrl"] : (_options) => void 0;
76884
- engineSendNtfyNotification = "sendNtfyNotification" in engineExports && typeof engineExports["sendNtfyNotification"] === "function" ? engineExports["sendNtfyNotification"] : async (_input) => {
76885
- };
76886
- planningNtfyHelpers = buildDefaultPlanningNtfyHelpers();
76887
77040
  diagnostics2 = createSessionDiagnostics("planning");
76888
77041
  PLANNING_SYSTEM_PROMPT = `You are a planning assistant for the fn task board system.
76889
77042
 
@@ -79511,7 +79664,7 @@ var init_api_error = __esm({
79511
79664
  // ../dashboard/src/plugin-routes.ts
79512
79665
  import { Router } from "express";
79513
79666
  import { access as access3, stat as stat6, readFile as readFile17 } from "node:fs/promises";
79514
- import { join as join35, isAbsolute as isAbsolute11, dirname as dirname9, basename as basename8 } from "node:path";
79667
+ import { join as join35, isAbsolute as isAbsolute12, dirname as dirname9, basename as basename8 } from "node:path";
79515
79668
  async function resolvePluginManifest(sourcePath) {
79516
79669
  try {
79517
79670
  await access3(sourcePath);
@@ -79651,7 +79804,7 @@ var init_project_store_resolver = __esm({
79651
79804
 
79652
79805
  // ../dashboard/src/routes/context.ts
79653
79806
  import { Router as Router2 } from "express";
79654
- import { resolve as resolve14, sep as sep5 } from "node:path";
79807
+ import { resolve as resolve15, sep as sep5 } from "node:path";
79655
79808
  function rethrowAsApiError2(error, fallbackMessage = "Internal server error") {
79656
79809
  if (error instanceof ApiError) {
79657
79810
  throw error;
@@ -79753,9 +79906,9 @@ function createApiRoutesContext(store, options) {
79753
79906
  const planningLogger = runtimeLogger.child("planning");
79754
79907
  const chatLogger = runtimeLogger.child("chat");
79755
79908
  function prioritizeProjectsForCurrentDirectory(projects) {
79756
- const cwd = resolve14(process.cwd());
79909
+ const cwd = resolve15(process.cwd());
79757
79910
  const rankProject = (projectPath) => {
79758
- const normalizedProjectPath = resolve14(projectPath);
79911
+ const normalizedProjectPath = resolve15(projectPath);
79759
79912
  if (normalizedProjectPath === cwd) {
79760
79913
  return Number.MAX_SAFE_INTEGER;
79761
79914
  }
@@ -79769,6 +79922,7 @@ function createApiRoutesContext(store, options) {
79769
79922
  }
79770
79923
  const resolveScopedStore2 = (req) => getScopedStore(req, store);
79771
79924
  const resolveProjectContext = (req) => getProjectContext(req, store, options);
79925
+ const disposeCallbacks = [];
79772
79926
  function emitAuthSyncAuditLog(input) {
79773
79927
  const logger2 = runtimeLogger.child("settings-sync").child("auth");
79774
79928
  const level = input.level ?? "info";
@@ -79890,6 +80044,19 @@ function createApiRoutesContext(store, options) {
79890
80044
  resolveAutomationStore,
79891
80045
  resolveRoutineStore,
79892
80046
  resolveRoutineRunner,
80047
+ registerDispose: (callback) => {
80048
+ disposeCallbacks.push(callback);
80049
+ },
80050
+ dispose: () => {
80051
+ while (disposeCallbacks.length > 0) {
80052
+ const callback = disposeCallbacks.pop();
80053
+ if (!callback) continue;
80054
+ try {
80055
+ callback();
80056
+ } catch {
80057
+ }
80058
+ }
80059
+ },
79893
80060
  rethrowAsApiError: rethrowAsApiError2
79894
80061
  };
79895
80062
  }
@@ -82033,7 +82200,7 @@ __export(chat_exports, {
82033
82200
  resolveFileReferences: () => resolveFileReferences
82034
82201
  });
82035
82202
  import { EventEmitter as EventEmitter28 } from "node:events";
82036
- import { join as join36, resolve as resolve15, relative as relative7 } from "node:path";
82203
+ import { join as join36, resolve as resolve16, relative as relative8 } from "node:path";
82037
82204
  function __getChatDiagnostics() {
82038
82205
  return _diagnostics;
82039
82206
  }
@@ -82060,9 +82227,9 @@ function validateFilePath(basePath, filePath) {
82060
82227
  if (decodedPath.startsWith("/") || decodedPath.match(/^[a-zA-Z]:/)) {
82061
82228
  throw new Error(`Access denied: Absolute paths not allowed`);
82062
82229
  }
82063
- const resolvedBase = resolve15(basePath);
82064
- const resolvedPath = resolve15(join36(resolvedBase, decodedPath));
82065
- const relativePath = relative7(resolvedBase, resolvedPath);
82230
+ const resolvedBase = resolve16(basePath);
82231
+ const resolvedPath = resolve16(join36(resolvedBase, decodedPath));
82232
+ const relativePath = relative8(resolvedBase, resolvedPath);
82066
82233
  if (relativePath.startsWith("..") || relativePath.startsWith("../") || relativePath === "..") {
82067
82234
  throw new Error(`Access denied: Path traversal detected`);
82068
82235
  }
@@ -82144,20 +82311,17 @@ function __setBuildAgentChatPrompt(mock) {
82144
82311
  function __resetChatState() {
82145
82312
  chatStreamManager.reset();
82146
82313
  rateLimits5.clear();
82147
- buildAgentChatPromptFn = defaultBuildAgentChatPromptFn;
82314
+ buildAgentChatPromptFn = void 0;
82148
82315
  __setChatDiagnostics(null);
82149
82316
  }
82150
- var engineExports2, createFnAgent8, defaultBuildAgentChatPromptFn, buildAgentChatPromptFn, defaultDiagnostics, _diagnostics, diagnostics6, CHAT_SYSTEM_PROMPT, RATE_LIMIT_WINDOW_MS5, MAX_MESSAGES_PER_IP_PER_MINUTE, MAX_REFERENCED_FILE_SIZE, rateLimits5, ChatStreamManager, chatStreamManager, ChatManager;
82317
+ var createFnAgent8, buildAgentChatPromptFn, defaultDiagnostics, _diagnostics, diagnostics6, CHAT_SYSTEM_PROMPT, RATE_LIMIT_WINDOW_MS5, MAX_MESSAGES_PER_IP_PER_MINUTE, MAX_REFERENCED_FILE_SIZE, rateLimits5, ChatStreamManager, chatStreamManager, ChatManager;
82151
82318
  var init_chat = __esm({
82152
82319
  "../dashboard/src/chat.ts"() {
82153
82320
  "use strict";
82154
82321
  init_src();
82155
82322
  init_sse_buffer();
82156
82323
  init_src2();
82157
- engineExports2 = src_exports2;
82158
82324
  createFnAgent8 = createFnAgent2;
82159
- defaultBuildAgentChatPromptFn = "buildAgentChatPrompt" in engineExports2 && typeof engineExports2["buildAgentChatPrompt"] === "function" ? engineExports2["buildAgentChatPrompt"] : void 0;
82160
- buildAgentChatPromptFn = defaultBuildAgentChatPromptFn;
82161
82325
  defaultDiagnostics = {
82162
82326
  log(message, ...args) {
82163
82327
  console.log(`[chat] ${message}`, ...args);
@@ -85259,7 +85423,7 @@ var init_register_messaging_scripts = __esm({
85259
85423
 
85260
85424
  // ../dashboard/src/github.ts
85261
85425
  function delay(ms) {
85262
- return new Promise((resolve34) => setTimeout(resolve34, ms));
85426
+ return new Promise((resolve36) => setTimeout(resolve36, ms));
85263
85427
  }
85264
85428
  function normalizeCheckState(state) {
85265
85429
  switch ((state ?? "").toLowerCase()) {
@@ -86003,6 +86167,43 @@ var init_github = __esm({
86003
86167
  }
86004
86168
  return response.json();
86005
86169
  }
86170
+ async commentOnIssue(owner, repo, issueNumber, body) {
86171
+ if (this.hasGhAuth()) {
86172
+ try {
86173
+ runGh([
86174
+ "issue",
86175
+ "comment",
86176
+ String(issueNumber),
86177
+ "--repo",
86178
+ `${owner}/${repo}`,
86179
+ "--body",
86180
+ body
86181
+ ]);
86182
+ return;
86183
+ } catch (err) {
86184
+ if (!this.token) {
86185
+ throw new Error(getGhErrorMessage(err));
86186
+ }
86187
+ }
86188
+ }
86189
+ if (!this.token) {
86190
+ throw new Error("GitHub CLI (gh) is not available or not authenticated, and no GITHUB_TOKEN provided.");
86191
+ }
86192
+ const url = `${this.baseUrl}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}/issues/${issueNumber}/comments`;
86193
+ const result = await this.fetchThrottled(
86194
+ url,
86195
+ {
86196
+ method: "POST",
86197
+ headers: {
86198
+ "Content-Type": "application/json"
86199
+ },
86200
+ body: JSON.stringify({ body })
86201
+ }
86202
+ );
86203
+ if (!result.success) {
86204
+ throw new Error(result.error ?? "Failed to comment on GitHub issue");
86205
+ }
86206
+ }
86006
86207
  /**
86007
86208
  * Fetch current issue status using gh CLI if available, otherwise REST API.
86008
86209
  * Returns null if the issue is not found or is a pull request.
@@ -86764,6 +86965,79 @@ var init_github = __esm({
86764
86965
  }
86765
86966
  });
86766
86967
 
86968
+ // ../dashboard/src/github-issue-comment.ts
86969
+ var DEFAULT_COMMENT_TEMPLATE, GitHubIssueCommentService;
86970
+ var init_github_issue_comment = __esm({
86971
+ "../dashboard/src/github-issue-comment.ts"() {
86972
+ "use strict";
86973
+ init_github();
86974
+ DEFAULT_COMMENT_TEMPLATE = "\u2705 Task {taskId} ({taskTitle}) has been completed and resolved.";
86975
+ GitHubIssueCommentService = class {
86976
+ store;
86977
+ getGitHubToken;
86978
+ onTaskMoved = (event) => {
86979
+ void this.handleTaskMoved(event);
86980
+ };
86981
+ started = false;
86982
+ constructor(store, getGitHubToken) {
86983
+ this.store = store;
86984
+ this.getGitHubToken = getGitHubToken ?? (() => process.env.GITHUB_TOKEN);
86985
+ }
86986
+ start() {
86987
+ if (this.started) return;
86988
+ this.started = true;
86989
+ this.store.on("task:moved", this.onTaskMoved);
86990
+ }
86991
+ stop() {
86992
+ if (!this.started) return;
86993
+ this.started = false;
86994
+ this.store.off("task:moved", this.onTaskMoved);
86995
+ }
86996
+ async handleTaskMoved(event) {
86997
+ if (event.to !== "done") {
86998
+ return;
86999
+ }
87000
+ const task = event.task;
87001
+ const settings = await this.store.getSettings();
87002
+ if (!settings.githubCommentOnDone) {
87003
+ return;
87004
+ }
87005
+ const sourceIssue = task.sourceIssue;
87006
+ if (!sourceIssue || sourceIssue.provider !== "github") {
87007
+ return;
87008
+ }
87009
+ const [owner, repo] = sourceIssue.repository.split("/");
87010
+ if (!owner || !repo) {
87011
+ await this.store.logEntry(
87012
+ task.id,
87013
+ "Failed to post GitHub issue comment",
87014
+ `Invalid GitHub repository format: ${sourceIssue.repository}`
87015
+ );
87016
+ return;
87017
+ }
87018
+ const template = settings.githubCommentTemplate || DEFAULT_COMMENT_TEMPLATE;
87019
+ const commentBody = template.replaceAll("{taskId}", task.id).replaceAll("{taskTitle}", task.title ?? "");
87020
+ try {
87021
+ const client = new GitHubClient(this.getGitHubToken());
87022
+ await client.commentOnIssue(owner, repo, sourceIssue.issueNumber, commentBody);
87023
+ await this.store.logEntry(
87024
+ task.id,
87025
+ "Posted GitHub issue completion comment",
87026
+ `${sourceIssue.repository}#${sourceIssue.issueNumber}`
87027
+ );
87028
+ } catch (error) {
87029
+ const message = error instanceof Error ? error.message : String(error);
87030
+ await this.store.logEntry(
87031
+ task.id,
87032
+ "Failed to post GitHub issue comment",
87033
+ message
87034
+ );
87035
+ }
87036
+ }
87037
+ };
87038
+ }
87039
+ });
87040
+
86767
87041
  // ../dashboard/src/github-poll.ts
86768
87042
  import { EventEmitter as EventEmitter30 } from "node:events";
86769
87043
  function toAlias(type, number) {
@@ -87117,7 +87391,7 @@ var init_resolve_diff_base = __esm({
87117
87391
  });
87118
87392
 
87119
87393
  // ../dashboard/src/routes/register-git-github.ts
87120
- import { isAbsolute as isAbsolute12 } from "node:path";
87394
+ import { isAbsolute as isAbsolute13 } from "node:path";
87121
87395
  function getCommandErrorMessage2(error) {
87122
87396
  if (error instanceof Error) {
87123
87397
  const anyError = error;
@@ -87717,7 +87991,7 @@ async function getGitWorkingDiff(cwd) {
87717
87991
  function isValidGitFilePath(filePath) {
87718
87992
  if (!filePath || !filePath.trim()) return false;
87719
87993
  if (filePath.startsWith("-")) return false;
87720
- if (isAbsolute12(filePath)) return false;
87994
+ if (isAbsolute13(filePath)) return false;
87721
87995
  if (filePath.includes("\0")) return false;
87722
87996
  if (filePath.includes("..")) return false;
87723
87997
  if (/[;&|`$(){}[\]\r\n]/.test(filePath)) return false;
@@ -87929,8 +88203,16 @@ async function refreshIssueInBackground(store, taskId, currentIssueInfo, token)
87929
88203
  }
87930
88204
  }
87931
88205
  function registerGitGitHubRoutes(ctx) {
87932
- const { router, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError7 } = ctx;
88206
+ const { router, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError7, store } = ctx;
87933
88207
  const githubToken = ctx.options?.githubToken ?? process.env.GITHUB_TOKEN;
88208
+ if (typeof store.on === "function" && typeof store.off === "function") {
88209
+ const githubIssueCommentService = new GitHubIssueCommentService(
88210
+ store,
88211
+ () => ctx.options?.githubToken ?? process.env.GITHUB_TOKEN
88212
+ );
88213
+ githubIssueCommentService.start();
88214
+ ctx.registerDispose(() => githubIssueCommentService.stop());
88215
+ }
87934
88216
  router.get("/git/remotes", async (req, res) => {
87935
88217
  try {
87936
88218
  const { store: scopedStore } = await getProjectContext3(req);
@@ -89473,6 +89755,7 @@ var init_register_git_github = __esm({
89473
89755
  init_src();
89474
89756
  init_api_error();
89475
89757
  init_github();
89758
+ init_github_issue_comment();
89476
89759
  init_github_poll();
89477
89760
  init_github_webhooks();
89478
89761
  init_resolve_diff_base();
@@ -89768,7 +90051,7 @@ var init_terminal = __esm({
89768
90051
  });
89769
90052
 
89770
90053
  // ../dashboard/src/file-service.ts
89771
- import { join as join38, resolve as resolve17, relative as relative8, dirname as dirname11, basename as basename10 } from "node:path";
90054
+ import { join as join38, resolve as resolve18, relative as relative9, dirname as dirname11, basename as basename10 } from "node:path";
89772
90055
  import { readdir as readdir8, readFile as fsReadFile, writeFile as fsWriteFile, stat as stat7, copyFile as fsCopyFile, rename as fsRename, rm as fsRm, mkdir as mkdir12, access as access4 } from "node:fs/promises";
89773
90056
  async function getTaskBasePath(store, taskId) {
89774
90057
  try {
@@ -89776,12 +90059,12 @@ async function getTaskBasePath(store, taskId) {
89776
90059
  if (task.worktree) {
89777
90060
  try {
89778
90061
  await access4(task.worktree);
89779
- return resolve17(task.worktree);
90062
+ return resolve18(task.worktree);
89780
90063
  } catch {
89781
90064
  }
89782
90065
  }
89783
90066
  const rootDir = store.getRootDir();
89784
- return resolve17(join38(rootDir, ".fusion", "tasks", taskId));
90067
+ return resolve18(join38(rootDir, ".fusion", "tasks", taskId));
89785
90068
  } catch (err) {
89786
90069
  const error = err;
89787
90070
  if (error.code === "ENOENT" || error.message && error.message.includes("not found")) {
@@ -89791,7 +90074,7 @@ async function getTaskBasePath(store, taskId) {
89791
90074
  }
89792
90075
  }
89793
90076
  function getProjectBasePath(store) {
89794
- return resolve17(store.getRootDir());
90077
+ return resolve18(store.getRootDir());
89795
90078
  }
89796
90079
  async function getWorkspaceBasePath(store, workspace) {
89797
90080
  if (workspace === "project") {
@@ -89807,9 +90090,9 @@ function validatePath(basePath, filePath) {
89807
90090
  if (decodedPath.startsWith("/") || decodedPath.match(/^[a-zA-Z]:/)) {
89808
90091
  throw new FileServiceError(`Access denied: Absolute paths not allowed`, "EINVAL");
89809
90092
  }
89810
- const resolvedBase = resolve17(basePath);
89811
- const resolvedPath = resolve17(join38(resolvedBase, decodedPath));
89812
- const relativePath = relative8(resolvedBase, resolvedPath);
90093
+ const resolvedBase = resolve18(basePath);
90094
+ const resolvedPath = resolve18(join38(resolvedBase, decodedPath));
90095
+ const relativePath = relative9(resolvedBase, resolvedPath);
89813
90096
  if (relativePath.startsWith("..") || relativePath.startsWith("../") || relativePath === "..") {
89814
90097
  throw new FileServiceError(`Access denied: Path traversal detected`, "EINVAL");
89815
90098
  }
@@ -89852,7 +90135,7 @@ async function listFilesForBasePath(basePath, subPath) {
89852
90135
  }
89853
90136
  return a.name.localeCompare(b.name);
89854
90137
  });
89855
- const relativeBase = relative8(basePath, targetPath);
90138
+ const relativeBase = relative9(basePath, targetPath);
89856
90139
  return {
89857
90140
  path: relativeBase || ".",
89858
90141
  entries: fileNodes
@@ -89986,7 +90269,7 @@ async function writeWorkspaceFile(store, workspace, filePath, content) {
89986
90269
  function validateSourceAndDestination(basePath, sourcePath, destinationPath) {
89987
90270
  const resolvedSource = validatePath(basePath, sourcePath);
89988
90271
  const resolvedDest = validatePath(basePath, destinationPath);
89989
- const sourceRelative = relative8(resolve17(basePath), resolvedSource);
90272
+ const sourceRelative = relative9(resolve18(basePath), resolvedSource);
89990
90273
  if (!sourceRelative || sourceRelative === "." || sourceRelative === "") {
89991
90274
  throw new FileServiceError("Cannot operate on workspace root directory", "EINVAL");
89992
90275
  }
@@ -90110,7 +90393,7 @@ async function deleteWorkspaceFile(store, workspace, filePath) {
90110
90393
  }
90111
90394
  const workspaceBase = await getWorkspaceBasePath(store, workspace);
90112
90395
  const resolvedPath = validatePath(workspaceBase, filePath);
90113
- const relativePath = relative8(resolve17(workspaceBase), resolvedPath);
90396
+ const relativePath = relative9(resolve18(workspaceBase), resolvedPath);
90114
90397
  if (!relativePath || relativePath === "." || relativePath === "") {
90115
90398
  throw new FileServiceError("Cannot delete workspace root directory", "EINVAL");
90116
90399
  }
@@ -90151,7 +90434,7 @@ async function renameWorkspaceFile(store, workspace, filePath, newName) {
90151
90434
  }
90152
90435
  const workspaceBase = await getWorkspaceBasePath(store, workspace);
90153
90436
  const resolvedPath = validatePath(workspaceBase, filePath);
90154
- const relativePath = relative8(resolve17(workspaceBase), resolvedPath);
90437
+ const relativePath = relative9(resolve18(workspaceBase), resolvedPath);
90155
90438
  if (!relativePath || relativePath === "." || relativePath === "") {
90156
90439
  throw new FileServiceError("Cannot rename workspace root directory", "EINVAL");
90157
90440
  }
@@ -90165,11 +90448,11 @@ async function renameWorkspaceFile(store, workspace, filePath, newName) {
90165
90448
  throw err;
90166
90449
  }
90167
90450
  const destPath = join38(dirname11(resolvedPath), newName);
90168
- const destRelative = relative8(resolve17(workspaceBase), destPath);
90451
+ const destRelative = relative9(resolve18(workspaceBase), destPath);
90169
90452
  if (destRelative.startsWith("..") || destRelative.startsWith("../") || destRelative === "..") {
90170
90453
  throw new FileServiceError("Destination would be outside workspace", "EINVAL");
90171
90454
  }
90172
- if (!destPath.startsWith(resolve17(workspaceBase))) {
90455
+ if (!destPath.startsWith(resolve18(workspaceBase))) {
90173
90456
  throw new FileServiceError("Destination would be outside workspace", "EINVAL");
90174
90457
  }
90175
90458
  try {
@@ -90198,7 +90481,7 @@ async function getWorkspaceFileForDownload(store, workspace, filePath) {
90198
90481
  }
90199
90482
  const workspaceBase = await getWorkspaceBasePath(store, workspace);
90200
90483
  const resolvedPath = validatePath(workspaceBase, filePath);
90201
- const relativePath = relative8(resolve17(workspaceBase), resolvedPath);
90484
+ const relativePath = relative9(resolve18(workspaceBase), resolvedPath);
90202
90485
  if (!relativePath || relativePath === "." || relativePath === "") {
90203
90486
  throw new FileServiceError("Cannot download workspace root", "EINVAL");
90204
90487
  }
@@ -90231,7 +90514,7 @@ async function getWorkspaceFolderForZip(store, workspace, dirPath) {
90231
90514
  }
90232
90515
  const workspaceBase = await getWorkspaceBasePath(store, workspace);
90233
90516
  const resolvedPath = validatePath(workspaceBase, dirPath);
90234
- const relativePath = relative8(resolve17(workspaceBase), resolvedPath);
90517
+ const relativePath = relative9(resolve18(workspaceBase), resolvedPath);
90235
90518
  if (!relativePath || relativePath === "." || relativePath === "") {
90236
90519
  throw new FileServiceError("Cannot download workspace root as ZIP", "EINVAL");
90237
90520
  }
@@ -91362,9 +91645,9 @@ var require_readdir_glob = __commonJS({
91362
91645
  var fs2 = __require("fs");
91363
91646
  var { EventEmitter: EventEmitter35 } = __require("events");
91364
91647
  var { Minimatch } = require_minimatch();
91365
- var { resolve: resolve34 } = __require("path");
91648
+ var { resolve: resolve36 } = __require("path");
91366
91649
  function readdir12(dir2, strict) {
91367
- return new Promise((resolve35, reject2) => {
91650
+ return new Promise((resolve37, reject2) => {
91368
91651
  fs2.readdir(dir2, { withFileTypes: true }, (err, files) => {
91369
91652
  if (err) {
91370
91653
  switch (err.code) {
@@ -91372,7 +91655,7 @@ var require_readdir_glob = __commonJS({
91372
91655
  if (strict) {
91373
91656
  reject2(err);
91374
91657
  } else {
91375
- resolve35([]);
91658
+ resolve37([]);
91376
91659
  }
91377
91660
  break;
91378
91661
  case "ENOTSUP":
@@ -91382,7 +91665,7 @@ var require_readdir_glob = __commonJS({
91382
91665
  case "ENAMETOOLONG":
91383
91666
  // Filename too long
91384
91667
  case "UNKNOWN":
91385
- resolve35([]);
91668
+ resolve37([]);
91386
91669
  break;
91387
91670
  case "ELOOP":
91388
91671
  // Too many levels of symbolic links
@@ -91391,30 +91674,30 @@ var require_readdir_glob = __commonJS({
91391
91674
  break;
91392
91675
  }
91393
91676
  } else {
91394
- resolve35(files);
91677
+ resolve37(files);
91395
91678
  }
91396
91679
  });
91397
91680
  });
91398
91681
  }
91399
91682
  function stat12(file, followSymlinks) {
91400
- return new Promise((resolve35, reject2) => {
91683
+ return new Promise((resolve37, reject2) => {
91401
91684
  const statFunc = followSymlinks ? fs2.stat : fs2.lstat;
91402
91685
  statFunc(file, (err, stats) => {
91403
91686
  if (err) {
91404
91687
  switch (err.code) {
91405
91688
  case "ENOENT":
91406
91689
  if (followSymlinks) {
91407
- resolve35(stat12(file, false));
91690
+ resolve37(stat12(file, false));
91408
91691
  } else {
91409
- resolve35(null);
91692
+ resolve37(null);
91410
91693
  }
91411
91694
  break;
91412
91695
  default:
91413
- resolve35(null);
91696
+ resolve37(null);
91414
91697
  break;
91415
91698
  }
91416
91699
  } else {
91417
- resolve35(stats);
91700
+ resolve37(stats);
91418
91701
  }
91419
91702
  });
91420
91703
  });
@@ -91428,8 +91711,8 @@ var require_readdir_glob = __commonJS({
91428
91711
  useStat = true;
91429
91712
  }
91430
91713
  const filename = dir2 + "/" + name;
91431
- const relative12 = filename.slice(1);
91432
- const absolute = path4 + "/" + relative12;
91714
+ const relative13 = filename.slice(1);
91715
+ const absolute = path4 + "/" + relative13;
91433
91716
  let stats = null;
91434
91717
  if (useStat || followSymlinks) {
91435
91718
  stats = await stat12(absolute, followSymlinks);
@@ -91441,12 +91724,12 @@ var require_readdir_glob = __commonJS({
91441
91724
  stats = { isDirectory: () => false };
91442
91725
  }
91443
91726
  if (stats.isDirectory()) {
91444
- if (!shouldSkip(relative12)) {
91445
- yield { relative: relative12, absolute, stats };
91727
+ if (!shouldSkip(relative13)) {
91728
+ yield { relative: relative13, absolute, stats };
91446
91729
  yield* exploreWalkAsync(filename, path4, followSymlinks, useStat, shouldSkip, false);
91447
91730
  }
91448
91731
  } else {
91449
- yield { relative: relative12, absolute, stats };
91732
+ yield { relative: relative13, absolute, stats };
91450
91733
  }
91451
91734
  }
91452
91735
  }
@@ -91504,7 +91787,7 @@ var require_readdir_glob = __commonJS({
91504
91787
  (skip) => new Minimatch(skip, { dot: true })
91505
91788
  );
91506
91789
  }
91507
- this.iterator = explore(resolve34(cwd || "."), this.options.follow, this.options.stat, this._shouldSkipDirectory.bind(this));
91790
+ this.iterator = explore(resolve36(cwd || "."), this.options.follow, this.options.stat, this._shouldSkipDirectory.bind(this));
91508
91791
  this.paused = false;
91509
91792
  this.inactive = false;
91510
91793
  this.aborted = false;
@@ -91516,11 +91799,11 @@ var require_readdir_glob = __commonJS({
91516
91799
  }
91517
91800
  setTimeout(() => this._next(), 0);
91518
91801
  }
91519
- _shouldSkipDirectory(relative12) {
91520
- return this.skipMatchers.some((m) => m.match(relative12));
91802
+ _shouldSkipDirectory(relative13) {
91803
+ return this.skipMatchers.some((m) => m.match(relative13));
91521
91804
  }
91522
- _fileMatches(relative12, isDirectory) {
91523
- const file = relative12 + (isDirectory ? "/" : "");
91805
+ _fileMatches(relative13, isDirectory) {
91806
+ const file = relative13 + (isDirectory ? "/" : "");
91524
91807
  return (this.matchers.length === 0 || this.matchers.some((m) => m.match(file))) && !this.ignoreMatchers.some((m) => m.match(file)) && (!this.options.nodir || !isDirectory);
91525
91808
  }
91526
91809
  _next() {
@@ -91529,16 +91812,16 @@ var require_readdir_glob = __commonJS({
91529
91812
  if (!obj.done) {
91530
91813
  const isDirectory = obj.value.stats.isDirectory();
91531
91814
  if (this._fileMatches(obj.value.relative, isDirectory)) {
91532
- let relative12 = obj.value.relative;
91815
+ let relative13 = obj.value.relative;
91533
91816
  let absolute = obj.value.absolute;
91534
91817
  if (this.options.mark && isDirectory) {
91535
- relative12 += "/";
91818
+ relative13 += "/";
91536
91819
  absolute += "/";
91537
91820
  }
91538
91821
  if (this.options.stat) {
91539
- this.emit("match", { relative: relative12, absolute, stat: obj.value.stats });
91822
+ this.emit("match", { relative: relative13, absolute, stat: obj.value.stats });
91540
91823
  } else {
91541
- this.emit("match", { relative: relative12, absolute });
91824
+ this.emit("match", { relative: relative13, absolute });
91542
91825
  }
91543
91826
  }
91544
91827
  this._next(this.iterator);
@@ -91758,10 +92041,10 @@ function awaitify(asyncFn, arity) {
91758
92041
  if (typeof args[arity - 1] === "function") {
91759
92042
  return asyncFn.apply(this, args);
91760
92043
  }
91761
- return new Promise((resolve34, reject2) => {
92044
+ return new Promise((resolve36, reject2) => {
91762
92045
  args[arity - 1] = (err, ...cbArgs) => {
91763
92046
  if (err) return reject2(err);
91764
- resolve34(cbArgs.length > 1 ? cbArgs : cbArgs[0]);
92047
+ resolve36(cbArgs.length > 1 ? cbArgs : cbArgs[0]);
91765
92048
  };
91766
92049
  asyncFn.apply(this, args);
91767
92050
  });
@@ -91943,13 +92226,13 @@ function mapSeries(coll, iteratee, callback) {
91943
92226
  return _asyncMap(eachOfSeries$1, coll, iteratee, callback);
91944
92227
  }
91945
92228
  function promiseCallback() {
91946
- let resolve34, reject2;
92229
+ let resolve36, reject2;
91947
92230
  function callback(err, ...args) {
91948
92231
  if (err) return reject2(err);
91949
- resolve34(args.length > 1 ? args : args[0]);
92232
+ resolve36(args.length > 1 ? args : args[0]);
91950
92233
  }
91951
92234
  callback[PROMISE_SYMBOL] = new Promise((res, rej) => {
91952
- resolve34 = res, reject2 = rej;
92235
+ resolve36 = res, reject2 = rej;
91953
92236
  });
91954
92237
  return callback;
91955
92238
  }
@@ -92222,8 +92505,8 @@ function queue$1(worker, concurrency, payload) {
92222
92505
  });
92223
92506
  }
92224
92507
  if (rejectOnError || !callback) {
92225
- return new Promise((resolve34, reject2) => {
92226
- res = resolve34;
92508
+ return new Promise((resolve36, reject2) => {
92509
+ res = resolve36;
92227
92510
  rej = reject2;
92228
92511
  });
92229
92512
  }
@@ -92262,10 +92545,10 @@ function queue$1(worker, concurrency, payload) {
92262
92545
  }
92263
92546
  const eventMethod = (name) => (handler) => {
92264
92547
  if (!handler) {
92265
- return new Promise((resolve34, reject2) => {
92548
+ return new Promise((resolve36, reject2) => {
92266
92549
  once3(name, (err, data) => {
92267
92550
  if (err) return reject2(err);
92268
- resolve34(data);
92551
+ resolve36(data);
92269
92552
  });
92270
92553
  });
92271
92554
  }
@@ -98271,25 +98554,25 @@ var require_util2 = __commonJS({
98271
98554
  };
98272
98555
  },
98273
98556
  createDeferredPromise: function() {
98274
- let resolve34;
98557
+ let resolve36;
98275
98558
  let reject2;
98276
98559
  const promise = new Promise((res, rej) => {
98277
- resolve34 = res;
98560
+ resolve36 = res;
98278
98561
  reject2 = rej;
98279
98562
  });
98280
98563
  return {
98281
98564
  promise,
98282
- resolve: resolve34,
98565
+ resolve: resolve36,
98283
98566
  reject: reject2
98284
98567
  };
98285
98568
  },
98286
98569
  promisify(fn) {
98287
- return new Promise((resolve34, reject2) => {
98570
+ return new Promise((resolve36, reject2) => {
98288
98571
  fn((err, ...args) => {
98289
98572
  if (err) {
98290
98573
  return reject2(err);
98291
98574
  }
98292
- return resolve34(...args);
98575
+ return resolve36(...args);
98293
98576
  });
98294
98577
  });
98295
98578
  },
@@ -99081,7 +99364,7 @@ var require_end_of_stream2 = __commonJS({
99081
99364
  validateBoolean3(opts.cleanup, "cleanup");
99082
99365
  autoCleanup = opts.cleanup;
99083
99366
  }
99084
- return new Promise2((resolve34, reject2) => {
99367
+ return new Promise2((resolve36, reject2) => {
99085
99368
  const cleanup = eos(stream, opts, (err) => {
99086
99369
  if (autoCleanup) {
99087
99370
  cleanup();
@@ -99089,7 +99372,7 @@ var require_end_of_stream2 = __commonJS({
99089
99372
  if (err) {
99090
99373
  reject2(err);
99091
99374
  } else {
99092
- resolve34();
99375
+ resolve36();
99093
99376
  }
99094
99377
  });
99095
99378
  });
@@ -100256,7 +100539,7 @@ var require_readable2 = __commonJS({
100256
100539
  error = this.readableEnded ? null : new AbortError();
100257
100540
  this.destroy(error);
100258
100541
  }
100259
- return new Promise2((resolve34, reject2) => eos(this, (err) => err && err !== error ? reject2(err) : resolve34(null)));
100542
+ return new Promise2((resolve36, reject2) => eos(this, (err) => err && err !== error ? reject2(err) : resolve36(null)));
100260
100543
  };
100261
100544
  Readable2.prototype.push = function(chunk, encoding) {
100262
100545
  return readableAddChunk(this, chunk, encoding, false);
@@ -100800,12 +101083,12 @@ var require_readable2 = __commonJS({
100800
101083
  }
100801
101084
  async function* createAsyncIterator(stream, options) {
100802
101085
  let callback = nop;
100803
- function next(resolve34) {
101086
+ function next(resolve36) {
100804
101087
  if (this === stream) {
100805
101088
  callback();
100806
101089
  callback = nop;
100807
101090
  } else {
100808
- callback = resolve34;
101091
+ callback = resolve36;
100809
101092
  }
100810
101093
  }
100811
101094
  stream.on("readable", next);
@@ -101858,7 +102141,7 @@ var require_duplexify = __commonJS({
101858
102141
  );
101859
102142
  };
101860
102143
  function fromAsyncGen(fn) {
101861
- let { promise, resolve: resolve34 } = createDeferredPromise();
102144
+ let { promise, resolve: resolve36 } = createDeferredPromise();
101862
102145
  const ac = new AbortController2();
101863
102146
  const signal = ac.signal;
101864
102147
  const value = fn(
@@ -101873,7 +102156,7 @@ var require_duplexify = __commonJS({
101873
102156
  throw new AbortError(void 0, {
101874
102157
  cause: signal.reason
101875
102158
  });
101876
- ({ promise, resolve: resolve34 } = createDeferredPromise());
102159
+ ({ promise, resolve: resolve36 } = createDeferredPromise());
101877
102160
  yield chunk;
101878
102161
  }
101879
102162
  })(),
@@ -101884,8 +102167,8 @@ var require_duplexify = __commonJS({
101884
102167
  return {
101885
102168
  value,
101886
102169
  write(chunk, encoding, cb) {
101887
- const _resolve = resolve34;
101888
- resolve34 = null;
102170
+ const _resolve = resolve36;
102171
+ resolve36 = null;
101889
102172
  _resolve({
101890
102173
  chunk,
101891
102174
  done: false,
@@ -101893,8 +102176,8 @@ var require_duplexify = __commonJS({
101893
102176
  });
101894
102177
  },
101895
102178
  final(cb) {
101896
- const _resolve = resolve34;
101897
- resolve34 = null;
102179
+ const _resolve = resolve36;
102180
+ resolve36 = null;
101898
102181
  _resolve({
101899
102182
  done: true,
101900
102183
  cb
@@ -102346,7 +102629,7 @@ var require_pipeline = __commonJS({
102346
102629
  callback();
102347
102630
  }
102348
102631
  };
102349
- const wait = () => new Promise2((resolve34, reject2) => {
102632
+ const wait = () => new Promise2((resolve36, reject2) => {
102350
102633
  if (error) {
102351
102634
  reject2(error);
102352
102635
  } else {
@@ -102354,7 +102637,7 @@ var require_pipeline = __commonJS({
102354
102637
  if (error) {
102355
102638
  reject2(error);
102356
102639
  } else {
102357
- resolve34();
102640
+ resolve36();
102358
102641
  }
102359
102642
  };
102360
102643
  }
@@ -102998,8 +103281,8 @@ var require_operators = __commonJS({
102998
103281
  next = null;
102999
103282
  }
103000
103283
  if (!done && (queue2.length >= highWaterMark2 || cnt >= concurrency)) {
103001
- await new Promise2((resolve34) => {
103002
- resume = resolve34;
103284
+ await new Promise2((resolve36) => {
103285
+ resume = resolve36;
103003
103286
  });
103004
103287
  }
103005
103288
  }
@@ -103033,8 +103316,8 @@ var require_operators = __commonJS({
103033
103316
  queue2.shift();
103034
103317
  maybeResume();
103035
103318
  }
103036
- await new Promise2((resolve34) => {
103037
- next = resolve34;
103319
+ await new Promise2((resolve36) => {
103320
+ next = resolve36;
103038
103321
  });
103039
103322
  }
103040
103323
  } finally {
@@ -103292,7 +103575,7 @@ var require_promises = __commonJS({
103292
103575
  var { finished } = require_end_of_stream2();
103293
103576
  require_stream2();
103294
103577
  function pipeline(...streams) {
103295
- return new Promise2((resolve34, reject2) => {
103578
+ return new Promise2((resolve36, reject2) => {
103296
103579
  let signal;
103297
103580
  let end;
103298
103581
  const lastArg = streams[streams.length - 1];
@@ -103307,7 +103590,7 @@ var require_promises = __commonJS({
103307
103590
  if (err) {
103308
103591
  reject2(err);
103309
103592
  } else {
103310
- resolve34(value);
103593
+ resolve36(value);
103311
103594
  }
103312
103595
  },
103313
103596
  {
@@ -108080,10 +108363,10 @@ var require_commonjs3 = __commonJS({
108080
108363
  * Return a void Promise that resolves once the stream ends.
108081
108364
  */
108082
108365
  async promise() {
108083
- return new Promise((resolve34, reject2) => {
108366
+ return new Promise((resolve36, reject2) => {
108084
108367
  this.on(DESTROYED, () => reject2(new Error("stream destroyed")));
108085
108368
  this.on("error", (er) => reject2(er));
108086
- this.on("end", () => resolve34());
108369
+ this.on("end", () => resolve36());
108087
108370
  });
108088
108371
  }
108089
108372
  /**
@@ -108107,7 +108390,7 @@ var require_commonjs3 = __commonJS({
108107
108390
  return Promise.resolve({ done: false, value: res });
108108
108391
  if (this[EOF])
108109
108392
  return stop();
108110
- let resolve34;
108393
+ let resolve36;
108111
108394
  let reject2;
108112
108395
  const onerr = (er) => {
108113
108396
  this.off("data", ondata);
@@ -108121,19 +108404,19 @@ var require_commonjs3 = __commonJS({
108121
108404
  this.off("end", onend);
108122
108405
  this.off(DESTROYED, ondestroy);
108123
108406
  this.pause();
108124
- resolve34({ value, done: !!this[EOF] });
108407
+ resolve36({ value, done: !!this[EOF] });
108125
108408
  };
108126
108409
  const onend = () => {
108127
108410
  this.off("error", onerr);
108128
108411
  this.off("data", ondata);
108129
108412
  this.off(DESTROYED, ondestroy);
108130
108413
  stop();
108131
- resolve34({ done: true, value: void 0 });
108414
+ resolve36({ done: true, value: void 0 });
108132
108415
  };
108133
108416
  const ondestroy = () => onerr(new Error("stream destroyed"));
108134
108417
  return new Promise((res2, rej) => {
108135
108418
  reject2 = rej;
108136
- resolve34 = res2;
108419
+ resolve36 = res2;
108137
108420
  this.once(DESTROYED, ondestroy);
108138
108421
  this.once("error", onerr);
108139
108422
  this.once("end", onend);
@@ -109149,9 +109432,9 @@ var require_commonjs4 = __commonJS({
109149
109432
  if (this.#asyncReaddirInFlight) {
109150
109433
  await this.#asyncReaddirInFlight;
109151
109434
  } else {
109152
- let resolve34 = () => {
109435
+ let resolve36 = () => {
109153
109436
  };
109154
- this.#asyncReaddirInFlight = new Promise((res) => resolve34 = res);
109437
+ this.#asyncReaddirInFlight = new Promise((res) => resolve36 = res);
109155
109438
  try {
109156
109439
  for (const e of await this.#fs.promises.readdir(fullpath, {
109157
109440
  withFileTypes: true
@@ -109164,7 +109447,7 @@ var require_commonjs4 = __commonJS({
109164
109447
  children.provisional = 0;
109165
109448
  }
109166
109449
  this.#asyncReaddirInFlight = void 0;
109167
- resolve34();
109450
+ resolve36();
109168
109451
  }
109169
109452
  return children.slice(0, children.provisional);
109170
109453
  }
@@ -110251,10 +110534,10 @@ var require_ignore = __commonJS({
110251
110534
  ignored(p) {
110252
110535
  const fullpath = p.fullpath();
110253
110536
  const fullpaths = `${fullpath}/`;
110254
- const relative12 = p.relative() || ".";
110255
- const relatives = `${relative12}/`;
110537
+ const relative13 = p.relative() || ".";
110538
+ const relatives = `${relative13}/`;
110256
110539
  for (const m of this.relative) {
110257
- if (m.match(relative12) || m.match(relatives))
110540
+ if (m.match(relative13) || m.match(relatives))
110258
110541
  return true;
110259
110542
  }
110260
110543
  for (const m of this.absolute) {
@@ -110265,9 +110548,9 @@ var require_ignore = __commonJS({
110265
110548
  }
110266
110549
  childrenIgnored(p) {
110267
110550
  const fullpath = p.fullpath() + "/";
110268
- const relative12 = (p.relative() || ".") + "/";
110551
+ const relative13 = (p.relative() || ".") + "/";
110269
110552
  for (const m of this.relativeChildren) {
110270
- if (m.match(relative12))
110553
+ if (m.match(relative13))
110271
110554
  return true;
110272
110555
  }
110273
110556
  for (const m of this.absoluteChildren) {
@@ -111937,11 +112220,11 @@ var require_core = __commonJS({
111937
112220
  this._finalize();
111938
112221
  }
111939
112222
  var self2 = this;
111940
- return new Promise(function(resolve34, reject2) {
112223
+ return new Promise(function(resolve36, reject2) {
111941
112224
  var errored;
111942
112225
  self2._module.on("end", function() {
111943
112226
  if (!errored) {
111944
- resolve34();
112227
+ resolve36();
111945
112228
  }
111946
112229
  });
111947
112230
  self2._module.on("error", function(err) {
@@ -114381,8 +114664,8 @@ var require_streamx = __commonJS({
114381
114664
  return this;
114382
114665
  },
114383
114666
  next() {
114384
- return new Promise(function(resolve34, reject2) {
114385
- promiseResolve = resolve34;
114667
+ return new Promise(function(resolve36, reject2) {
114668
+ promiseResolve = resolve36;
114386
114669
  promiseReject = reject2;
114387
114670
  const data = stream.read();
114388
114671
  if (data !== null) ondata(data);
@@ -114412,11 +114695,11 @@ var require_streamx = __commonJS({
114412
114695
  }
114413
114696
  function destroy(err) {
114414
114697
  stream.destroy(err);
114415
- return new Promise((resolve34, reject2) => {
114416
- if (stream._duplexState & DESTROYED) return resolve34({ value: void 0, done: true });
114698
+ return new Promise((resolve36, reject2) => {
114699
+ if (stream._duplexState & DESTROYED) return resolve36({ value: void 0, done: true });
114417
114700
  stream.once("close", function() {
114418
114701
  if (err) reject2(err);
114419
- else resolve34({ value: void 0, done: true });
114702
+ else resolve36({ value: void 0, done: true });
114420
114703
  });
114421
114704
  });
114422
114705
  }
@@ -114460,8 +114743,8 @@ var require_streamx = __commonJS({
114460
114743
  const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0);
114461
114744
  if (writes === 0) return Promise.resolve(true);
114462
114745
  if (state.drains === null) state.drains = [];
114463
- return new Promise((resolve34) => {
114464
- state.drains.push({ writes, resolve: resolve34 });
114746
+ return new Promise((resolve36) => {
114747
+ state.drains.push({ writes, resolve: resolve36 });
114465
114748
  });
114466
114749
  }
114467
114750
  write(data) {
@@ -114566,10 +114849,10 @@ var require_streamx = __commonJS({
114566
114849
  cb(null);
114567
114850
  }
114568
114851
  function pipelinePromise(...streams) {
114569
- return new Promise((resolve34, reject2) => {
114852
+ return new Promise((resolve36, reject2) => {
114570
114853
  return pipeline(...streams, (err) => {
114571
114854
  if (err) return reject2(err);
114572
- resolve34();
114855
+ resolve36();
114573
114856
  });
114574
114857
  });
114575
114858
  }
@@ -115226,16 +115509,16 @@ var require_extract = __commonJS({
115226
115509
  entryCallback = null;
115227
115510
  cb(err);
115228
115511
  }
115229
- function onnext(resolve34, reject2) {
115512
+ function onnext(resolve36, reject2) {
115230
115513
  if (error) {
115231
115514
  return reject2(error);
115232
115515
  }
115233
115516
  if (entryStream) {
115234
- resolve34({ value: entryStream, done: false });
115517
+ resolve36({ value: entryStream, done: false });
115235
115518
  entryStream = null;
115236
115519
  return;
115237
115520
  }
115238
- promiseResolve = resolve34;
115521
+ promiseResolve = resolve36;
115239
115522
  promiseReject = reject2;
115240
115523
  consumeCallback(null);
115241
115524
  if (extract._finished && promiseResolve) {
@@ -115263,11 +115546,11 @@ var require_extract = __commonJS({
115263
115546
  function destroy(err) {
115264
115547
  extract.destroy(err);
115265
115548
  consumeCallback(err);
115266
- return new Promise((resolve34, reject2) => {
115267
- if (extract.destroyed) return resolve34({ value: void 0, done: true });
115549
+ return new Promise((resolve36, reject2) => {
115550
+ if (extract.destroyed) return resolve36({ value: void 0, done: true });
115268
115551
  extract.once("close", function() {
115269
115552
  if (err) reject2(err);
115270
- else resolve34({ value: void 0, done: true });
115553
+ else resolve36({ value: void 0, done: true });
115271
115554
  });
115272
115555
  });
115273
115556
  }
@@ -117015,7 +117298,7 @@ var init_register_agents_projects_nodes = __esm({
117015
117298
  // ../dashboard/src/routes/register-project-routes.ts
117016
117299
  import { execFile as execFile5 } from "node:child_process";
117017
117300
  import * as fsPromises from "node:fs/promises";
117018
- import { dirname as dirname12, isAbsolute as isAbsolute13, join as join39 } from "node:path";
117301
+ import { dirname as dirname12, isAbsolute as isAbsolute14, join as join39 } from "node:path";
117019
117302
  import { promisify as promisify12 } from "node:util";
117020
117303
  var access7, stat8, mkdir13, readdir9, rm2, execFileAsync3, registerProjectRoutes;
117021
117304
  var init_register_project_routes = __esm({
@@ -117132,7 +117415,7 @@ var init_register_project_routes = __esm({
117132
117415
  if (normalizedPath.includes("\0")) {
117133
117416
  throw badRequest("path cannot contain null bytes");
117134
117417
  }
117135
- if (!isAbsolute13(normalizedPath)) {
117418
+ if (!isAbsolute14(normalizedPath)) {
117136
117419
  throw badRequest("path must be an absolute path");
117137
117420
  }
117138
117421
  if (cloneUrl !== void 0) {
@@ -120668,7 +120951,7 @@ You MUST respond with ONLY valid JSON (no markdown, no explanation):
120668
120951
  import { createWriteStream } from "node:fs";
120669
120952
  import * as fsPromises2 from "node:fs/promises";
120670
120953
  import { tmpdir as tmpdir2 } from "node:os";
120671
- import { join as join40, resolve as resolve18 } from "node:path";
120954
+ import { join as join40, resolve as resolve19 } from "node:path";
120672
120955
  import { Readable } from "node:stream";
120673
120956
  import { pipeline as streamPipeline } from "node:stream/promises";
120674
120957
  function registerAgentImportExportRoutes(ctx) {
@@ -120705,7 +120988,7 @@ function registerAgentImportExportRoutes(ctx) {
120705
120988
  }
120706
120989
  let resolvedOutputDir;
120707
120990
  if (typeof outputDir === "string" && outputDir.trim().length > 0) {
120708
- resolvedOutputDir = resolve18(outputDir.trim());
120991
+ resolvedOutputDir = resolve19(outputDir.trim());
120709
120992
  } else if (typeof outputDir === "string") {
120710
120993
  throw badRequest("outputDir cannot be empty");
120711
120994
  } else {
@@ -120940,7 +121223,7 @@ ${body}`;
120940
121223
  tasks: []
120941
121224
  };
120942
121225
  } else if (typeof source === "string" && source.trim()) {
120943
- const sourcePath = resolve18(source);
121226
+ const sourcePath = resolve19(source);
120944
121227
  const isArchive = sourcePath.endsWith(".tar.gz") || sourcePath.endsWith(".tgz") || sourcePath.endsWith(".zip");
120945
121228
  if (isArchive) {
120946
121229
  try {
@@ -121741,9 +122024,9 @@ function registerProxyRoutes(router, deps) {
121741
122024
  if (req.rawBody && req.rawBody.length > 0) {
121742
122025
  body = req.rawBody;
121743
122026
  } else {
121744
- await new Promise((resolve34, reject2) => {
122027
+ await new Promise((resolve36, reject2) => {
121745
122028
  req.on("data", (chunk) => chunks.push(chunk));
121746
- req.on("end", resolve34);
122029
+ req.on("end", resolve36);
121747
122030
  req.on("error", reject2);
121748
122031
  });
121749
122032
  if (chunks.length > 0) {
@@ -121918,13 +122201,13 @@ import { readFile as readFile19 } from "node:fs/promises";
121918
122201
  import * as https from "node:https";
121919
122202
  import * as child_process from "node:child_process";
121920
122203
  function execFileAsync4(file, args, options) {
121921
- return new Promise((resolve34, reject2) => {
122204
+ return new Promise((resolve36, reject2) => {
121922
122205
  child_process.execFile(file, args, options, (error, stdout, stderr) => {
121923
122206
  if (error) {
121924
122207
  reject2(error);
121925
122208
  return;
121926
122209
  }
121927
- resolve34({ stdout: String(stdout), stderr: String(stderr) });
122210
+ resolve36({ stdout: String(stdout), stderr: String(stderr) });
121928
122211
  });
121929
122212
  });
121930
122213
  }
@@ -121983,7 +122266,7 @@ function formatDuration(ms) {
121983
122266
  return remHours > 0 ? `${days}d ${remHours}h` : `${days}d`;
121984
122267
  }
121985
122268
  function httpsRequest(url, options) {
121986
- return new Promise((resolve34, reject2) => {
122269
+ return new Promise((resolve36, reject2) => {
121987
122270
  const parsed = new URL(url);
121988
122271
  const req = https.request(
121989
122272
  {
@@ -122003,7 +122286,7 @@ function httpsRequest(url, options) {
122003
122286
  if (typeof v === "string") hdrs[k.toLowerCase()] = v;
122004
122287
  else if (Array.isArray(v)) hdrs[k.toLowerCase()] = v.join(", ");
122005
122288
  }
122006
- resolve34({
122289
+ resolve36({
122007
122290
  status: res.statusCode || 0,
122008
122291
  headers: hdrs,
122009
122292
  body: Buffer.concat(chunks).toString("utf-8")
@@ -122250,7 +122533,7 @@ async function fetchClaudeUsageViaCli() {
122250
122533
  env: { ...process.env, TERM: "xterm-256color" }
122251
122534
  };
122252
122535
  if (isWindows) ptyOptions.useConpty = false;
122253
- const output = await new Promise((resolve34, reject2) => {
122536
+ const output = await new Promise((resolve36, reject2) => {
122254
122537
  let buf = "";
122255
122538
  let settled = false;
122256
122539
  let sentCommand = false;
@@ -122266,7 +122549,7 @@ async function fetchClaudeUsageViaCli() {
122266
122549
  }
122267
122550
  const clean = _stripClaudeAnsi(buf);
122268
122551
  if (clean.includes("Current session") || clean.includes("% left") || clean.includes("% used")) {
122269
- resolve34(buf);
122552
+ resolve36(buf);
122270
122553
  } else {
122271
122554
  reject2(new Error("Claude CLI timed out after 60s \u2014 got output but no usage data. Try running `claude /usage` manually."));
122272
122555
  }
@@ -122317,7 +122600,7 @@ async function fetchClaudeUsageViaCli() {
122317
122600
  ptyProcess.kill();
122318
122601
  } catch {
122319
122602
  }
122320
- resolve34(buf);
122603
+ resolve36(buf);
122321
122604
  }
122322
122605
  }, 2e3);
122323
122606
  }
@@ -122328,7 +122611,7 @@ async function fetchClaudeUsageViaCli() {
122328
122611
  if (settled) return;
122329
122612
  settled = true;
122330
122613
  clearTimeout(timeout2);
122331
- resolve34(buf);
122614
+ resolve36(buf);
122332
122615
  });
122333
122616
  });
122334
122617
  const cleanOutput = _stripClaudeAnsi(output);
@@ -122996,9 +123279,9 @@ async function fetchGitHubCopilotUsage() {
122996
123279
  return usage;
122997
123280
  }
122998
123281
  function withTimeout(providerPromise, providerName, timeoutMs = PROVIDER_FETCH_TIMEOUT_MS) {
122999
- return new Promise((resolve34) => {
123282
+ return new Promise((resolve36) => {
123000
123283
  const timer = setTimeout(() => {
123001
- resolve34({
123284
+ resolve36({
123002
123285
  name: providerName,
123003
123286
  icon: "\u23F1\uFE0F",
123004
123287
  status: "error",
@@ -123008,10 +123291,10 @@ function withTimeout(providerPromise, providerName, timeoutMs = PROVIDER_FETCH_T
123008
123291
  }, timeoutMs);
123009
123292
  providerPromise.then((result) => {
123010
123293
  clearTimeout(timer);
123011
- resolve34(result);
123294
+ resolve36(result);
123012
123295
  }).catch((err) => {
123013
123296
  clearTimeout(timer);
123014
- resolve34({
123297
+ resolve36({
123015
123298
  name: providerName,
123016
123299
  icon: "\u23F1\uFE0F",
123017
123300
  status: "error",
@@ -123065,7 +123348,7 @@ var init_usage = __esm({
123065
123348
  ANTHROPIC_OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
123066
123349
  ANTHROPIC_OAUTH_BETA = "oauth-2025-04-20";
123067
123350
  CLAUDE_USAGE_USER_AGENT = "claude-code-fusion-dashboard";
123068
- _sleep = (ms) => new Promise((resolve34) => setTimeout(resolve34, ms));
123351
+ _sleep = (ms) => new Promise((resolve36) => setTimeout(resolve36, ms));
123069
123352
  sleepFn = _sleep;
123070
123353
  PROVIDER_FETCH_TIMEOUT_MS = 1e4;
123071
123354
  CLAUDE_FETCH_TIMEOUT_MS = 75e3;
@@ -123379,8 +123662,8 @@ var init_register_auth_routes = __esm({
123379
123662
  loginInProgress.set(provider, abortController);
123380
123663
  let authResolve;
123381
123664
  let authReject;
123382
- const authUrlPromise = new Promise((resolve34, reject2) => {
123383
- authResolve = resolve34;
123665
+ const authUrlPromise = new Promise((resolve36, reject2) => {
123666
+ authResolve = resolve36;
123384
123667
  authReject = reject2;
123385
123668
  });
123386
123669
  const loginPromise = storage.login(provider, {
@@ -127423,7 +127706,7 @@ var init_todo_routes = __esm({
127423
127706
 
127424
127707
  // ../dashboard/src/dev-server-detect.ts
127425
127708
  import { glob, readFile as readFile20 } from "node:fs/promises";
127426
- import { dirname as dirname13, join as join42, relative as relative9, resolve as resolve19 } from "node:path";
127709
+ import { dirname as dirname13, join as join42, relative as relative10, resolve as resolve20 } from "node:path";
127427
127710
  async function readPackageJson(filePath) {
127428
127711
  try {
127429
127712
  const raw = await readFile20(filePath, "utf-8");
@@ -127473,7 +127756,7 @@ async function collectWorkspacePackageJsons(projectRoot) {
127473
127756
  try {
127474
127757
  for await (const match of glob(pattern, { cwd: projectRoot })) {
127475
127758
  if (typeof match === "string") {
127476
- discovered.add(resolve19(projectRoot, match));
127759
+ discovered.add(resolve20(projectRoot, match));
127477
127760
  }
127478
127761
  }
127479
127762
  } catch {
@@ -127494,11 +127777,11 @@ function extractScripts(pkg) {
127494
127777
  }
127495
127778
  function toSource(projectRoot, packageJsonPath) {
127496
127779
  const packageDir = dirname13(packageJsonPath);
127497
- const rel = relative9(projectRoot, packageDir).replace(/\\/g, "/");
127780
+ const rel = relative10(projectRoot, packageDir).replace(/\\/g, "/");
127498
127781
  return rel.length > 0 ? rel : "root";
127499
127782
  }
127500
127783
  async function detectDevServerScripts(projectRoot) {
127501
- const root = resolve19(projectRoot);
127784
+ const root = resolve20(projectRoot);
127502
127785
  const candidates = [];
127503
127786
  const rootPackagePath = join42(root, "package.json");
127504
127787
  const rootPackage = await readPackageJson(rootPackagePath);
@@ -127567,9 +127850,9 @@ var init_dev_server_detect = __esm({
127567
127850
 
127568
127851
  // ../dashboard/src/dev-server-store.ts
127569
127852
  import { mkdir as mkdir15, readFile as readFile21, writeFile as writeFile13 } from "node:fs/promises";
127570
- import { dirname as dirname14, join as join43, resolve as resolve20 } from "node:path";
127853
+ import { dirname as dirname14, join as join43, resolve as resolve21 } from "node:path";
127571
127854
  function devServerFilePath(projectDir) {
127572
- return join43(resolve20(projectDir), ".fusion", "dev-server.json");
127855
+ return join43(resolve21(projectDir), ".fusion", "dev-server.json");
127573
127856
  }
127574
127857
  function normalizeState(candidate) {
127575
127858
  const defaults = DEV_SERVER_DEFAULT_STATE();
@@ -127604,7 +127887,7 @@ function normalizeConfig(candidate) {
127604
127887
  };
127605
127888
  }
127606
127889
  async function loadDevServerStore(projectDir) {
127607
- const storeKey = resolve20(projectDir);
127890
+ const storeKey = resolve21(projectDir);
127608
127891
  let store = storeInstances.get(storeKey);
127609
127892
  if (!store) {
127610
127893
  store = new DevServerStore(projectDir);
@@ -127882,7 +128165,7 @@ function detectPortFromLogLine(line) {
127882
128165
  return detectViteLine(cleanLine) ?? detectNextLine(cleanLine) ?? detectStorybookLine(cleanLine) ?? detectAngularLine(cleanLine) ?? detectGenericUrl(cleanLine) ?? detectGenericPortLine(cleanLine);
127883
128166
  }
127884
128167
  function probePort(host, port, timeoutMs) {
127885
- return new Promise((resolve34) => {
128168
+ return new Promise((resolve36) => {
127886
128169
  let settled = false;
127887
128170
  const socket = createConnection({ host, port });
127888
128171
  const settle = (isOpen) => {
@@ -127896,7 +128179,7 @@ function probePort(host, port, timeoutMs) {
127896
128179
  } else {
127897
128180
  socket.destroy();
127898
128181
  }
127899
- resolve34(isOpen);
128182
+ resolve36(isOpen);
127900
128183
  };
127901
128184
  socket.setTimeout(timeoutMs);
127902
128185
  socket.once("connect", () => settle(true));
@@ -128023,8 +128306,8 @@ var init_dev_server_process = __esm({
128023
128306
  stdio: ["pipe", "pipe", "pipe"]
128024
128307
  });
128025
128308
  this.childProcess = child;
128026
- this.closePromise = new Promise((resolve34) => {
128027
- this.resolveClosePromise = resolve34;
128309
+ this.closePromise = new Promise((resolve36) => {
128310
+ this.resolveClosePromise = resolve36;
128028
128311
  });
128029
128312
  const runningState = await this.store.updateState({
128030
128313
  pid: child.pid,
@@ -128821,7 +129104,7 @@ Your job is to refine task descriptions based on the user's selected refinement
128821
129104
 
128822
129105
  // ../dashboard/src/routes.ts
128823
129106
  import multer from "multer";
128824
- import { resolve as resolve21, sep as sep6, join as join44, isAbsolute as isAbsolute14 } from "node:path";
129107
+ import { resolve as resolve22, sep as sep6, join as join44, isAbsolute as isAbsolute15 } from "node:path";
128825
129108
  import * as nodeFs from "node:fs";
128826
129109
  function readJsonObject3(path4) {
128827
129110
  if (!nodeFs.existsSync(path4)) {
@@ -128851,7 +129134,7 @@ function getPiPackageManagerAgentDir() {
128851
129134
  return nodeFs.existsSync(fusionAgentDir) ? fusionAgentDir : legacyAgentDir;
128852
129135
  }
128853
129136
  function packageExtensionName(extensionPath, source) {
128854
- const base = resolve21(extensionPath).split(sep6).pop()?.replace(/\.(ts|js)$/i, "") || source;
129137
+ const base = resolve22(extensionPath).split(sep6).pop()?.replace(/\.(ts|js)$/i, "") || source;
128855
129138
  if (base !== "index") {
128856
129139
  return base;
128857
129140
  }
@@ -128859,7 +129142,7 @@ function packageExtensionName(extensionPath, source) {
128859
129142
  }
128860
129143
  async function discoverDashboardPiExtensions(cwd) {
128861
129144
  const settings = discoverPiExtensions(cwd);
128862
- const disabled = new Set(settings.disabledIds.map((id) => resolve21(id)));
129145
+ const disabled = new Set(settings.disabledIds.map((id) => resolve22(id)));
128863
129146
  const byPath = new Map(settings.extensions.map((entry) => [entry.id, entry]));
128864
129147
  try {
128865
129148
  const { DefaultPackageManager: DefaultPackageManager5 } = await import("@mariozechner/pi-coding-agent");
@@ -128881,7 +129164,7 @@ async function discoverDashboardPiExtensions(cwd) {
128881
129164
  });
128882
129165
  const resolved = await packageManager.resolve(async () => "skip");
128883
129166
  for (const extension2 of resolved.extensions) {
128884
- const id = resolve21(extension2.path);
129167
+ const id = resolve22(extension2.path);
128885
129168
  const source = extension2.metadata?.source || "package";
128886
129169
  byPath.set(id, {
128887
129170
  id,
@@ -129003,7 +129286,7 @@ function sanitizeOverlapIgnorePaths(value) {
129003
129286
  if (!trimmed) {
129004
129287
  throw badRequest(`overlapIgnorePaths[${index2}] cannot be empty`);
129005
129288
  }
129006
- if (isAbsolute14(trimmed) || /^[a-zA-Z]:\//.test(trimmed)) {
129289
+ if (isAbsolute15(trimmed) || /^[a-zA-Z]:\//.test(trimmed)) {
129007
129290
  throw badRequest(`overlapIgnorePaths[${index2}] must be a project-relative path`);
129008
129291
  }
129009
129292
  if (/^\.{1,2}(\/|$)/.test(trimmed) || /(^|\/)\.\.(\/|$)/.test(trimmed)) {
@@ -129218,7 +129501,9 @@ function createApiRoutes(store, options) {
129218
129501
  parseScopeParam,
129219
129502
  resolveAutomationStore,
129220
129503
  resolveRoutineStore,
129221
- resolveRoutineRunner
129504
+ resolveRoutineRunner,
129505
+ registerDispose,
129506
+ dispose
129222
129507
  } = createApiRoutesContext(store, options);
129223
129508
  const summarizeDiagnostics = createSessionDiagnostics("ai-summarize");
129224
129509
  const routeContext = {
@@ -129238,6 +129523,8 @@ function createApiRoutes(store, options) {
129238
129523
  resolveAutomationStore,
129239
129524
  resolveRoutineStore,
129240
129525
  resolveRoutineRunner,
129526
+ registerDispose,
129527
+ dispose,
129241
129528
  rethrowAsApiError
129242
129529
  };
129243
129530
  const githubToken = options?.githubToken ?? process.env.GITHUB_TOKEN;
@@ -129279,8 +129566,8 @@ function createApiRoutes(store, options) {
129279
129566
  function isHeartbeatMonitorForProject(scopedStore) {
129280
129567
  if (!heartbeatMonitor?.rootDir) return true;
129281
129568
  try {
129282
- const monitorRoot = resolve21(heartbeatMonitor.rootDir);
129283
- const storeRoot = resolve21(scopedStore.getRootDir());
129569
+ const monitorRoot = resolve22(heartbeatMonitor.rootDir);
129570
+ const storeRoot = resolve22(scopedStore.getRootDir());
129284
129571
  return monitorRoot === storeRoot;
129285
129572
  } catch {
129286
129573
  return true;
@@ -131066,15 +131353,15 @@ Description: ${step.description}`
131066
131353
  return;
131067
131354
  }
131068
131355
  }
131069
- const { resolve: resolve34, dirname: dirname26, join: join62 } = await import("node:path");
131356
+ const { resolve: resolve36, dirname: dirname27, join: join62 } = await import("node:path");
131070
131357
  const { readdir: readdir12, stat: stat12 } = await import("node:fs/promises");
131071
131358
  const rawPath = req.query.path || process.env.HOME || process.env.USERPROFILE || "/";
131072
131359
  const showHidden = req.query.showHidden === "true";
131073
- const resolvedPath = resolve34(rawPath);
131360
+ const resolvedPath = resolve36(rawPath);
131074
131361
  if (rawPath.includes("..")) {
131075
131362
  throw badRequest("Path must not contain '..' traversal");
131076
131363
  }
131077
- if (resolvedPath !== resolve34(resolvedPath)) {
131364
+ if (resolvedPath !== resolve36(resolvedPath)) {
131078
131365
  throw badRequest("Path must be absolute");
131079
131366
  }
131080
131367
  let pathStat;
@@ -131101,7 +131388,7 @@ Description: ${step.description}`
131101
131388
  entries.push({ name: entry.name, path: entryPath, hasChildren });
131102
131389
  }
131103
131390
  entries.sort((a, b) => a.name.localeCompare(b.name));
131104
- const parentPath = resolvedPath === "/" ? null : dirname26(resolvedPath);
131391
+ const parentPath = resolvedPath === "/" ? null : dirname27(resolvedPath);
131105
131392
  res.json({ currentPath: resolvedPath, parentPath, entries });
131106
131393
  } catch (err) {
131107
131394
  if (err instanceof ApiError) {
@@ -131260,6 +131547,7 @@ Description: ${step.description}`
131260
131547
  });
131261
131548
  registerAgentSkillsRoutes(routeContext);
131262
131549
  registerProxyRoutes(router, { store, runtimeLogger });
131550
+ router.dispose = dispose;
131263
131551
  return router;
131264
131552
  }
131265
131553
  function validateAutomationSteps(steps) {
@@ -136927,14 +137215,15 @@ data: ${JSON.stringify({ type: event.type, data: event.data })}
136927
137215
  }
136928
137216
  res.redirect(302, "/");
136929
137217
  });
136930
- app.use("/api", createApiRoutes(store, {
137218
+ const apiRouter = createApiRoutes(store, {
136931
137219
  ...options,
136932
137220
  runtimeLogger,
136933
137221
  aiSessionStore,
136934
137222
  chatStore,
136935
137223
  chatManager,
136936
137224
  skillsAdapter: options?.skillsAdapter
136937
- }));
137225
+ });
137226
+ app.use("/api", apiRouter);
136938
137227
  app.use("/api", (_req, res) => {
136939
137228
  sendErrorResponse(res, 404, "Not found");
136940
137229
  });
@@ -136983,6 +137272,7 @@ data: ${JSON.stringify({ type: event.type, data: event.data })}
136983
137272
  server.once("close", () => {
136984
137273
  clearAiSessionCleanupInterval();
136985
137274
  aiSessionStore.stopScheduledCleanup();
137275
+ apiRouter.dispose?.();
136986
137276
  void stopAllDevServers().catch((error) => {
136987
137277
  runtimeLogger.warn("Failed to shutdown dev-server managers", {
136988
137278
  message: "Failed to shutdown dev-server managers",
@@ -137416,7 +137706,7 @@ var init_server = __esm({
137416
137706
 
137417
137707
  // ../dashboard/src/skills-adapter.ts
137418
137708
  import { access as access9, readFile as readFile22, writeFile as writeFile14, mkdir as mkdir16, readdir as readdir10, stat as stat10 } from "node:fs/promises";
137419
- import { join as join46, relative as relative10, dirname as dirname16 } from "node:path";
137709
+ import { join as join46, relative as relative11, dirname as dirname16 } from "node:path";
137420
137710
  async function pathExists(path4) {
137421
137711
  try {
137422
137712
  await access9(path4);
@@ -137480,7 +137770,7 @@ function createSkillsAdapter(options) {
137480
137770
  }
137481
137771
  const discoveredSkills = [];
137482
137772
  for (const resource of skillResources) {
137483
- const skillRelativePath = "skills/" + relative10(resource.metadata.baseDir ?? "", resource.path);
137773
+ const skillRelativePath = "skills/" + relative11(resource.metadata.baseDir ?? "", resource.path);
137484
137774
  const skillId = computeSkillId(resource.metadata.source, skillRelativePath);
137485
137775
  const skillName = extractSkillName(skillRelativePath, resource.metadata.source);
137486
137776
  discoveredSkills.push({
@@ -137935,6 +138225,7 @@ var init_src3 = __esm({
137935
138225
  init_github();
137936
138226
  init_rate_limit();
137937
138227
  init_github_poll();
138228
+ init_github_issue_comment();
137938
138229
  init_api_error();
137939
138230
  init_badge_pubsub();
137940
138231
  init_plugins();
@@ -138054,7 +138345,7 @@ var init_task_lifecycle = __esm({
138054
138345
  // src/commands/port-prompt.ts
138055
138346
  import { createInterface } from "node:readline";
138056
138347
  function promptForPort(defaultPort = 4040, input = process.stdin) {
138057
- return new Promise((resolve34, reject2) => {
138348
+ return new Promise((resolve36, reject2) => {
138058
138349
  const rl = createInterface({
138059
138350
  input,
138060
138351
  output: process.stdout
@@ -138071,7 +138362,7 @@ function promptForPort(defaultPort = 4040, input = process.stdin) {
138071
138362
  if (trimmed === "") {
138072
138363
  process.removeListener("SIGINT", sigintHandler);
138073
138364
  rl.close();
138074
- resolve34(defaultPort);
138365
+ resolve36(defaultPort);
138075
138366
  return;
138076
138367
  }
138077
138368
  const port = parseInt(trimmed, 10);
@@ -138087,7 +138378,7 @@ function promptForPort(defaultPort = 4040, input = process.stdin) {
138087
138378
  }
138088
138379
  process.removeListener("SIGINT", sigintHandler);
138089
138380
  rl.close();
138090
- resolve34(port);
138381
+ resolve36(port);
138091
138382
  });
138092
138383
  };
138093
138384
  ask();
@@ -138378,7 +138669,7 @@ var init_auth_paths2 = __esm({
138378
138669
  });
138379
138670
 
138380
138671
  // src/project-context.ts
138381
- import { resolve as resolve22, dirname as dirname18 } from "node:path";
138672
+ import { resolve as resolve23, dirname as dirname18 } from "node:path";
138382
138673
  import { existsSync as existsSync34 } from "node:fs";
138383
138674
  async function resolveProject(projectNameFlag, cwd = process.cwd(), globalDir) {
138384
138675
  const central = new CentralCore(globalDir);
@@ -138470,9 +138761,9 @@ async function clearDefaultProject(globalDir) {
138470
138761
  await globalStore.updateSettings(rest);
138471
138762
  }
138472
138763
  async function detectProjectFromCwd(cwd, central) {
138473
- let currentDir = resolve22(cwd);
138764
+ let currentDir = resolve23(cwd);
138474
138765
  while (true) {
138475
- const kbPath = resolve22(currentDir, ".fusion", "fusion.db");
138766
+ const kbPath = resolve23(currentDir, ".fusion", "fusion.db");
138476
138767
  if (existsSync34(kbPath)) {
138477
138768
  const project = await central.getProjectByPath(currentDir);
138478
138769
  if (project) {
@@ -138532,11 +138823,11 @@ import {
138532
138823
  lstatSync as lstatSync2,
138533
138824
  mkdirSync as mkdirSync7,
138534
138825
  readlinkSync,
138535
- rmSync as rmSync3,
138826
+ rmSync as rmSync4,
138536
138827
  symlinkSync,
138537
138828
  unlinkSync
138538
138829
  } from "node:fs";
138539
- import { dirname as dirname19, join as join49, resolve as resolve23 } from "node:path";
138830
+ import { dirname as dirname19, join as join49, resolve as resolve24 } from "node:path";
138540
138831
  import { fileURLToPath as fileURLToPath4 } from "node:url";
138541
138832
  function isPiClaudeCliConfigured(globalSettings) {
138542
138833
  if (!globalSettings || typeof globalSettings !== "object") {
@@ -138556,7 +138847,7 @@ function isPiClaudeCliConfigured(globalSettings) {
138556
138847
  }
138557
138848
  function resolveFusionSkillSource() {
138558
138849
  const here = fileURLToPath4(import.meta.url);
138559
- const candidate = resolve23(dirname19(here), "..", "..", "skill", FUSION_SKILL_NAME);
138850
+ const candidate = resolve24(dirname19(here), "..", "..", "skill", FUSION_SKILL_NAME);
138560
138851
  return existsSync35(candidate) ? candidate : null;
138561
138852
  }
138562
138853
  function installFusionSkillIntoProject(projectPath, options = {}) {
@@ -138579,7 +138870,7 @@ function installFusionSkillIntoProject(projectPath, options = {}) {
138579
138870
  const stat12 = lstatSync2(target);
138580
138871
  if (stat12.isSymbolicLink()) {
138581
138872
  const current = safeReadlink(target);
138582
- if (current && resolve23(dirname19(target), current) === resolve23(source)) {
138873
+ if (current && resolve24(dirname19(target), current) === resolve24(source)) {
138583
138874
  return { outcome: "already-installed", target, source };
138584
138875
  }
138585
138876
  unlinkSync(target);
@@ -138658,7 +138949,7 @@ function safeReadlink(path4) {
138658
138949
  }
138659
138950
  }
138660
138951
  function removeRecursive(path4) {
138661
- rmSync3(path4, { recursive: true, force: true });
138952
+ rmSync4(path4, { recursive: true, force: true });
138662
138953
  }
138663
138954
  var FUSION_SKILL_NAME;
138664
138955
  var init_claude_skills = __esm({
@@ -138744,13 +139035,13 @@ var init_claude_skills_runner = __esm({
138744
139035
  // src/commands/claude-cli-extension.ts
138745
139036
  import { existsSync as existsSync36, readFileSync as readFileSync13 } from "node:fs";
138746
139037
  import { createRequire as createRequire4 } from "node:module";
138747
- import { dirname as dirname20, resolve as resolve24 } from "node:path";
139038
+ import { dirname as dirname20, resolve as resolve25 } from "node:path";
138748
139039
  import { fileURLToPath as fileURLToPath5 } from "node:url";
138749
139040
  function resolveClaudeCliExtension() {
138750
139041
  let pkgJsonPath;
138751
139042
  const here = dirname20(fileURLToPath5(import.meta.url));
138752
139043
  for (const rel of ["pi-claude-cli", "../pi-claude-cli", "../../pi-claude-cli"]) {
138753
- const candidate = resolve24(here, rel, "package.json");
139044
+ const candidate = resolve25(here, rel, "package.json");
138754
139045
  if (existsSync36(candidate)) {
138755
139046
  pkgJsonPath = candidate;
138756
139047
  break;
@@ -138786,7 +139077,7 @@ function resolveClaudeCliExtension() {
138786
139077
  reason: "@fusion/pi-claude-cli pi.extensions[0] is not a valid path string"
138787
139078
  };
138788
139079
  }
138789
- const entryPath = resolve24(dirname20(pkgJsonPath), rawEntry);
139080
+ const entryPath = resolve25(dirname20(pkgJsonPath), rawEntry);
138790
139081
  if (!existsSync36(entryPath)) {
138791
139082
  return {
138792
139083
  status: "missing-entry",
@@ -138836,16 +139127,16 @@ var init_claude_cli_extension = __esm({
138836
139127
 
138837
139128
  // src/commands/self-extension.ts
138838
139129
  import { existsSync as existsSync37, readFileSync as readFileSync14 } from "node:fs";
138839
- import { dirname as dirname21, resolve as resolve25 } from "node:path";
139130
+ import { dirname as dirname21, resolve as resolve26 } from "node:path";
138840
139131
  import { fileURLToPath as fileURLToPath6 } from "node:url";
138841
139132
  function resolveSelfExtension() {
138842
139133
  const here = dirname21(fileURLToPath6(import.meta.url));
138843
139134
  let pkgDir;
138844
139135
  let cur = here;
138845
139136
  for (let i = 0; i < 5; i++) {
138846
- if (existsSync37(resolve25(cur, "package.json"))) {
139137
+ if (existsSync37(resolve26(cur, "package.json"))) {
138847
139138
  try {
138848
- const parsed = JSON.parse(readFileSync14(resolve25(cur, "package.json"), "utf-8"));
139139
+ const parsed = JSON.parse(readFileSync14(resolve26(cur, "package.json"), "utf-8"));
138849
139140
  if (parsed.name === "@runfusion/fusion") {
138850
139141
  pkgDir = cur;
138851
139142
  break;
@@ -138853,7 +139144,7 @@ function resolveSelfExtension() {
138853
139144
  } catch {
138854
139145
  }
138855
139146
  }
138856
- const parent2 = resolve25(cur, "..");
139147
+ const parent2 = resolve26(cur, "..");
138857
139148
  if (parent2 === cur) break;
138858
139149
  cur = parent2;
138859
139150
  }
@@ -138862,11 +139153,11 @@ function resolveSelfExtension() {
138862
139153
  }
138863
139154
  let pkgJson;
138864
139155
  try {
138865
- pkgJson = JSON.parse(readFileSync14(resolve25(pkgDir, "package.json"), "utf-8"));
139156
+ pkgJson = JSON.parse(readFileSync14(resolve26(pkgDir, "package.json"), "utf-8"));
138866
139157
  } catch (err) {
138867
139158
  return { status: "missing", reason: `Failed to read @runfusion/fusion package.json: ${err instanceof Error ? err.message : String(err)}` };
138868
139159
  }
138869
- const srcEntry = resolve25(pkgDir, "src", "extension.ts");
139160
+ const srcEntry = resolve26(pkgDir, "src", "extension.ts");
138870
139161
  if (existsSync37(srcEntry)) {
138871
139162
  return { status: "ok", path: srcEntry, packageVersion: pkgJson.version ?? "unknown" };
138872
139163
  }
@@ -138878,7 +139169,7 @@ function resolveSelfExtension() {
138878
139169
  if (typeof rawEntry !== "string" || rawEntry.length === 0) {
138879
139170
  return { status: "missing", reason: "@runfusion/fusion pi.extensions[0] is not a valid path string" };
138880
139171
  }
138881
- const entryPath = resolve25(pkgDir, rawEntry);
139172
+ const entryPath = resolve26(pkgDir, rawEntry);
138882
139173
  if (!existsSync37(entryPath)) {
138883
139174
  return { status: "missing", reason: `@runfusion/fusion extension file not found at ${entryPath}` };
138884
139175
  }
@@ -138938,7 +139229,29 @@ var init_state = __esm({
138938
139229
  });
138939
139230
 
138940
139231
  // src/commands/dashboard-tui/logo.ts
138941
- var FUSION_LOGO_LINES, FUSION_LOGO_LARGE_LINES, FUSION_TAGLINE;
139232
+ import { existsSync as existsSync38, readFileSync as readFileSync15 } from "node:fs";
139233
+ import { dirname as dirname22, resolve as resolve27 } from "node:path";
139234
+ import { fileURLToPath as fileURLToPath7 } from "node:url";
139235
+ function readFusionVersion() {
139236
+ try {
139237
+ let cur = dirname22(fileURLToPath7(import.meta.url));
139238
+ for (let i = 0; i < 6; i++) {
139239
+ const pkgPath = resolve27(cur, "package.json");
139240
+ if (existsSync38(pkgPath)) {
139241
+ const parsed = JSON.parse(readFileSync15(pkgPath, "utf-8"));
139242
+ if (parsed.name === "@runfusion/fusion" && typeof parsed.version === "string") {
139243
+ return parsed.version;
139244
+ }
139245
+ }
139246
+ const parent2 = resolve27(cur, "..");
139247
+ if (parent2 === cur) break;
139248
+ cur = parent2;
139249
+ }
139250
+ } catch {
139251
+ }
139252
+ return "unknown";
139253
+ }
139254
+ var FUSION_LOGO_LINES, FUSION_LOGO_LARGE_LINES, FUSION_TAGLINE, FUSION_URL, FUSION_VERSION2;
138942
139255
  var init_logo = __esm({
138943
139256
  "src/commands/dashboard-tui/logo.ts"() {
138944
139257
  "use strict";
@@ -138964,7 +139277,9 @@ var init_logo = __esm({
138964
139277
  "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D",
138965
139278
  "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"
138966
139279
  ];
138967
- FUSION_TAGLINE = "AI coding agent dashboard";
139280
+ FUSION_TAGLINE = "multi node agent orchestrator";
139281
+ FUSION_URL = "runfusion.ai";
139282
+ FUSION_VERSION2 = readFusionVersion();
138968
139283
  }
138969
139284
  });
138970
139285
 
@@ -139096,6 +139411,8 @@ function SplashScreen({ loadingStatus }) {
139096
139411
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
139097
139412
  compact ? /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "FUSION" }) : /* @__PURE__ */ jsx(AnimatedFusionLogo, { lines: large ? FUSION_LOGO_LARGE_LINES : FUSION_LOGO_LINES }),
139098
139413
  /* @__PURE__ */ jsx(Text, { color: "cyanBright", dimColor: true, children: FUSION_TAGLINE }),
139414
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", dimColor: true, children: FUSION_URL }),
139415
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", dimColor: true, children: `v${FUSION_VERSION2}` }),
139099
139416
  /* @__PURE__ */ jsx(Box, { height: 1 }),
139100
139417
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
139101
139418
  /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
@@ -139127,6 +139444,10 @@ function Panel({ title, isFocused, children, flexGrow, flexShrink, width }) {
139127
139444
  function SystemPanel({ state, isFocused }) {
139128
139445
  const info = state.systemInfo;
139129
139446
  return /* @__PURE__ */ jsx(Panel, { title: "System", isFocused, flexGrow: 1, children: !info ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "System information not available." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
139447
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
139448
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Version:" }),
139449
+ /* @__PURE__ */ jsx(Text, { children: `v${FUSION_VERSION2}` })
139450
+ ] }),
139130
139451
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
139131
139452
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Host:" }),
139132
139453
  /* @__PURE__ */ jsx(Text, { children: info.host })
@@ -139560,7 +139881,7 @@ function StatusBar({ state, controller: _controller }) {
139560
139881
  }
139561
139882
  const statusParts = [];
139562
139883
  if (systemInfo) {
139563
- statusParts.push(systemInfo.baseUrl);
139884
+ statusParts.push(`${systemInfo.baseUrl} v${FUSION_VERSION2}`);
139564
139885
  statusParts.push(formatUptime(Date.now() - systemInfo.startTimeMs));
139565
139886
  }
139566
139887
  return /* @__PURE__ */ jsxs(Box, { height: 1, justifyContent: "space-between", paddingX: 1, flexShrink: 0, overflow: "hidden", children: [
@@ -145380,8 +145701,8 @@ async function runServe(port, opts = {}) {
145380
145701
  https: loadTlsCredentialsFromEnv()
145381
145702
  });
145382
145703
  const server = app.listen(selectedPort, selectedHost);
145383
- await new Promise((resolve34, reject2) => {
145384
- server.once("listening", resolve34);
145704
+ await new Promise((resolve36, reject2) => {
145705
+ server.once("listening", resolve36);
145385
145706
  server.once("error", reject2);
145386
145707
  });
145387
145708
  const actualPort = server.address().port;
@@ -145891,8 +146212,8 @@ async function runDaemon(opts = {}) {
145891
146212
  https: loadTlsCredentialsFromEnv()
145892
146213
  });
145893
146214
  const server = app.listen(selectedPort, selectedHost);
145894
- await new Promise((resolve34, reject2) => {
145895
- server.once("listening", resolve34);
146215
+ await new Promise((resolve36, reject2) => {
146216
+ server.once("listening", resolve36);
145896
146217
  server.once("error", reject2);
145897
146218
  });
145898
146219
  const actualPort = server.address().port;
@@ -146006,7 +146327,7 @@ import { once as once2 } from "node:events";
146006
146327
  import { join as join53 } from "node:path";
146007
146328
  import { createRequire as createRequire5 } from "node:module";
146008
146329
  function runCommand(command, args, cwd) {
146009
- return new Promise((resolve34, reject2) => {
146330
+ return new Promise((resolve36, reject2) => {
146010
146331
  const child = spawn8(command, args, {
146011
146332
  cwd,
146012
146333
  stdio: "inherit",
@@ -146015,7 +146336,7 @@ function runCommand(command, args, cwd) {
146015
146336
  child.on("error", (error) => reject2(error));
146016
146337
  child.on("exit", (code) => {
146017
146338
  if (code === 0) {
146018
- resolve34();
146339
+ resolve36();
146019
146340
  return;
146020
146341
  }
146021
146342
  reject2(new Error(`${command} ${args.join(" ")} exited with code ${code ?? "unknown"}`));
@@ -146058,8 +146379,8 @@ async function startDashboardRuntime(rootDir, paused) {
146058
146379
  };
146059
146380
  }
146060
146381
  async function closeDashboardRuntime(runtime) {
146061
- await new Promise((resolve34) => {
146062
- runtime.server.close(() => resolve34());
146382
+ await new Promise((resolve36) => {
146383
+ runtime.server.close(() => resolve36());
146063
146384
  });
146064
146385
  runtime.store.close();
146065
146386
  }
@@ -146165,7 +146486,7 @@ __export(task_exports, {
146165
146486
  runTaskUpdate: () => runTaskUpdate
146166
146487
  });
146167
146488
  import { createInterface as createInterface3 } from "node:readline/promises";
146168
- import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync38, readFileSync as readFileSync15 } from "node:fs";
146489
+ import { watchFile, unwatchFile, statSync as statSync6, existsSync as existsSync39, readFileSync as readFileSync16 } from "node:fs";
146169
146490
  import { join as join54 } from "node:path";
146170
146491
  function asLocalProjectContext(store) {
146171
146492
  const cwd = process.cwd();
@@ -146256,9 +146577,9 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName)
146256
146577
  console.log(` Path: .fusion/tasks/${task.id}/`);
146257
146578
  if (attachFiles && attachFiles.length > 0) {
146258
146579
  const { readFile: readFile24 } = await import("node:fs/promises");
146259
- const { basename: basename16, extname: extname2, resolve: resolve34 } = await import("node:path");
146580
+ const { basename: basename16, extname: extname2, resolve: resolve36 } = await import("node:path");
146260
146581
  for (const filePath of attachFiles) {
146261
- const resolvedPath = resolve34(filePath);
146582
+ const resolvedPath = resolve36(filePath);
146262
146583
  const filename = basename16(resolvedPath);
146263
146584
  const ext = extname2(filename).toLowerCase();
146264
146585
  const mimeType = MIME_TYPES[ext];
@@ -146393,7 +146714,7 @@ async function runTaskLogs(id, options = {}, projectName) {
146393
146714
  if (options.follow) {
146394
146715
  const projectPath = projectContext?.projectPath ?? process.cwd();
146395
146716
  const logPath = join54(projectPath, ".fusion", "tasks", id, "agent.log");
146396
- if (!existsSync38(logPath)) {
146717
+ if (!existsSync39(logPath)) {
146397
146718
  console.log(`
146398
146719
  Waiting for log file to be created...`);
146399
146720
  }
@@ -146422,7 +146743,7 @@ async function runTaskLogs(id, options = {}, projectName) {
146422
146743
  lastPosition = 0;
146423
146744
  }
146424
146745
  if (stats.size > lastPosition) {
146425
- const content = readFileSync15(logPath, "utf-8");
146746
+ const content = readFileSync16(logPath, "utf-8");
146426
146747
  const lines = content.slice(lastPosition).split("\n");
146427
146748
  for (const line of lines) {
146428
146749
  if (!line.trim()) continue;
@@ -146505,8 +146826,8 @@ async function runTaskMerge(id, projectName) {
146505
146826
  async function runTaskAttach(id, filePath, projectName) {
146506
146827
  const { readFile: readFile24 } = await import("node:fs/promises");
146507
146828
  const { basename: basename16, extname: extname2 } = await import("node:path");
146508
- const { resolve: resolve34 } = await import("node:path");
146509
- const resolvedPath = resolve34(filePath);
146829
+ const { resolve: resolve36 } = await import("node:path");
146830
+ const resolvedPath = resolve36(filePath);
146510
146831
  const filename = basename16(resolvedPath);
146511
146832
  const ext = extname2(filename).toLowerCase();
146512
146833
  const mimeType = MIME_TYPES[ext];
@@ -147023,12 +147344,12 @@ async function promptText(question) {
147023
147344
  console.log(" (Enter your response. Type DONE on its own line when finished):\n");
147024
147345
  const rl = createInterface3({ input: process.stdin, output: process.stdout });
147025
147346
  const lines = [];
147026
- return new Promise((resolve34) => {
147347
+ return new Promise((resolve36) => {
147027
147348
  const askLine = () => {
147028
147349
  rl.question(" ").then((line) => {
147029
147350
  if (line.trim() === "DONE") {
147030
147351
  rl.close();
147031
- resolve34(lines.join("\n"));
147352
+ resolve36(lines.join("\n"));
147032
147353
  } else {
147033
147354
  lines.push(line);
147034
147355
  askLine();
@@ -147586,7 +147907,7 @@ __export(settings_export_exports, {
147586
147907
  runSettingsExport: () => runSettingsExport
147587
147908
  });
147588
147909
  import { writeFile as writeFile15 } from "node:fs/promises";
147589
- import { resolve as resolve26, join as join55 } from "node:path";
147910
+ import { resolve as resolve28, join as join55 } from "node:path";
147590
147911
  async function runSettingsExport(options = {}) {
147591
147912
  const scope = options.scope ?? "both";
147592
147913
  const project = options.projectName ? await resolveProject(options.projectName) : void 0;
@@ -147597,7 +147918,7 @@ async function runSettingsExport(options = {}) {
147597
147918
  const exportData = await exportSettings(store, { scope });
147598
147919
  let targetPath;
147599
147920
  if (outputPath) {
147600
- targetPath = resolve26(outputPath);
147921
+ targetPath = resolve28(outputPath);
147601
147922
  } else {
147602
147923
  const filename = generateExportFilename();
147603
147924
  targetPath = join55(process.cwd(), filename);
@@ -147646,8 +147967,8 @@ var settings_import_exports = {};
147646
147967
  __export(settings_import_exports, {
147647
147968
  runSettingsImport: () => runSettingsImport
147648
147969
  });
147649
- import { existsSync as existsSync39 } from "node:fs";
147650
- import { resolve as resolve27 } from "node:path";
147970
+ import { existsSync as existsSync40 } from "node:fs";
147971
+ import { resolve as resolve29 } from "node:path";
147651
147972
  async function runSettingsImport(filePath, options = {}) {
147652
147973
  const scope = options.scope ?? "both";
147653
147974
  const project = options.projectName ? await resolveProject(options.projectName) : void 0;
@@ -147656,8 +147977,8 @@ async function runSettingsImport(filePath, options = {}) {
147656
147977
  const merge = options.merge ?? true;
147657
147978
  const skipConfirm = options.yes ?? false;
147658
147979
  try {
147659
- const resolvedPath = resolve27(filePath);
147660
- if (!existsSync39(resolvedPath)) {
147980
+ const resolvedPath = resolve29(filePath);
147981
+ if (!existsSync40(resolvedPath)) {
147661
147982
  console.error(`Error: File not found: ${filePath}`);
147662
147983
  process.exit(1);
147663
147984
  }
@@ -148130,8 +148451,8 @@ var init_backup2 = __esm({
148130
148451
  });
148131
148452
 
148132
148453
  // src/project-resolver.ts
148133
- import { existsSync as existsSync40, statSync as statSync7 } from "node:fs";
148134
- import { dirname as dirname22, resolve as resolve28, normalize as normalize5 } from "node:path";
148454
+ import { existsSync as existsSync41, statSync as statSync7 } from "node:fs";
148455
+ import { dirname as dirname23, resolve as resolve30, normalize as normalize5 } from "node:path";
148135
148456
  import { createInterface as createInterface5 } from "node:readline/promises";
148136
148457
  async function getCentralCore() {
148137
148458
  if (!centralCoreInstance) {
@@ -148148,13 +148469,13 @@ async function getProjectManager() {
148148
148469
  return projectManagerInstance;
148149
148470
  }
148150
148471
  function findKbDir(startPath) {
148151
- let current = resolve28(startPath);
148472
+ let current = resolve30(startPath);
148152
148473
  for (let i = 0; i < 100; i++) {
148153
- const kbPath = resolve28(current, ".fusion");
148154
- if (existsSync40(kbPath) && statSync7(kbPath).isDirectory()) {
148474
+ const kbPath = resolve30(current, ".fusion");
148475
+ if (existsSync41(kbPath) && statSync7(kbPath).isDirectory()) {
148155
148476
  return current;
148156
148477
  }
148157
- const parent2 = dirname22(current);
148478
+ const parent2 = dirname23(current);
148158
148479
  if (parent2 === current) {
148159
148480
  break;
148160
148481
  }
@@ -148206,7 +148527,7 @@ async function resolveProject2(options = {}) {
148206
148527
  { searchedName: options.project, availableProjects: projects.map((p) => p.name) }
148207
148528
  );
148208
148529
  }
148209
- if (!existsSync40(match.path)) {
148530
+ if (!existsSync41(match.path)) {
148210
148531
  throw new ProjectResolutionError(
148211
148532
  `Project "${match.name}" is registered but the directory no longer exists: ${match.path}
148212
148533
 
@@ -148217,14 +148538,14 @@ Run \`fn project remove ` + match.name + "` to clean up the registry entry.",
148217
148538
  }
148218
148539
  return createResolvedProject(match);
148219
148540
  }
148220
- const cwd = options.cwd ? resolve28(options.cwd) : process.cwd();
148541
+ const cwd = options.cwd ? resolve30(options.cwd) : process.cwd();
148221
148542
  const fusionDir = findKbDir(cwd);
148222
148543
  if (fusionDir) {
148223
148544
  const allProjects2 = await central.listProjects();
148224
148545
  const normalizedKbDir = normalize5(fusionDir);
148225
148546
  const match = allProjects2.find((p) => normalize5(p.path) === normalizedKbDir);
148226
148547
  if (match) {
148227
- if (!existsSync40(match.path)) {
148548
+ if (!existsSync41(match.path)) {
148228
148549
  throw new ProjectResolutionError(
148229
148550
  `Project "${match.name}" is registered but the directory no longer exists: ${match.path}
148230
148551
 
@@ -148289,7 +148610,7 @@ Run \`fn project add ` + fusionDir + "` to register it, or use --project <name>.
148289
148610
  }
148290
148611
  if (allProjects.length === 1) {
148291
148612
  const project = allProjects[0];
148292
- if (!existsSync40(project.path)) {
148613
+ if (!existsSync41(project.path)) {
148293
148614
  throw new ProjectResolutionError(
148294
148615
  `The only registered project "${project.name}" has a missing directory: ${project.path}
148295
148616
 
@@ -148729,11 +149050,11 @@ __export(project_exports, {
148729
149050
  runProjectSetDefault: () => runProjectSetDefault,
148730
149051
  runProjectShow: () => runProjectShow
148731
149052
  });
148732
- import { resolve as resolve29, isAbsolute as isAbsolute15, relative as relative11, basename as basename13 } from "node:path";
148733
- import { existsSync as existsSync41, statSync as statSync8 } from "node:fs";
149053
+ import { resolve as resolve31, isAbsolute as isAbsolute16, relative as relative12, basename as basename13 } from "node:path";
149054
+ import { existsSync as existsSync42, statSync as statSync8 } from "node:fs";
148734
149055
  import { createInterface as createInterface7 } from "node:readline/promises";
148735
149056
  function formatDisplayPath(projectPath) {
148736
- const rel = relative11(process.cwd(), projectPath);
149057
+ const rel = relative12(process.cwd(), projectPath);
148737
149058
  if (rel && !rel.startsWith("..") && rel !== "") {
148738
149059
  return rel;
148739
149060
  }
@@ -148858,8 +149179,8 @@ async function runProjectAdd(name, path4, options = {}) {
148858
149179
  const pathInput = await rl.question(` Project path [${defaultPath}]: `);
148859
149180
  projectPath = pathInput.trim() || defaultPath;
148860
149181
  }
148861
- const absolutePath2 = isAbsolute15(projectPath) ? projectPath : resolve29(process.cwd(), projectPath);
148862
- if (!existsSync41(absolutePath2)) {
149182
+ const absolutePath2 = isAbsolute16(projectPath) ? projectPath : resolve31(process.cwd(), projectPath);
149183
+ if (!existsSync42(absolutePath2)) {
148863
149184
  console.error(`
148864
149185
  \u2717 Path does not exist: ${projectPath}`);
148865
149186
  rl.close();
@@ -148871,8 +149192,8 @@ async function runProjectAdd(name, path4, options = {}) {
148871
149192
  rl.close();
148872
149193
  process.exit(1);
148873
149194
  }
148874
- const kbDbPath2 = resolve29(absolutePath2, ".fusion", "fusion.db");
148875
- if (!existsSync41(kbDbPath2) && !options.force) {
149195
+ const kbDbPath2 = resolve31(absolutePath2, ".fusion", "fusion.db");
149196
+ if (!existsSync42(kbDbPath2) && !options.force) {
148876
149197
  console.log(`
148877
149198
  No fn project found at ${formatDisplayPath(absolutePath2)}`);
148878
149199
  const init = await rl.question(" Initialize fn here first? [Y/n] ");
@@ -148903,8 +149224,8 @@ async function runProjectAdd(name, path4, options = {}) {
148903
149224
  console.error(" Name must be 1-64 characters and contain only: a-z, A-Z, 0-9, _, -\n");
148904
149225
  process.exit(1);
148905
149226
  }
148906
- const absolutePath = isAbsolute15(projectPath) ? projectPath : resolve29(process.cwd(), projectPath);
148907
- if (!existsSync41(absolutePath)) {
149227
+ const absolutePath = isAbsolute16(projectPath) ? projectPath : resolve31(process.cwd(), projectPath);
149228
+ if (!existsSync42(absolutePath)) {
148908
149229
  console.error(`
148909
149230
  \u2717 Path does not exist: ${projectPath}
148910
149231
  `);
@@ -148916,8 +149237,8 @@ async function runProjectAdd(name, path4, options = {}) {
148916
149237
  `);
148917
149238
  process.exit(1);
148918
149239
  }
148919
- const kbDbPath = resolve29(absolutePath, ".fusion", "fusion.db");
148920
- if (!existsSync41(kbDbPath) && !options.force) {
149240
+ const kbDbPath = resolve31(absolutePath, ".fusion", "fusion.db");
149241
+ if (!existsSync42(kbDbPath) && !options.force) {
148921
149242
  console.error(`
148922
149243
  \u2717 No fn project found at ${formatDisplayPath(absolutePath)}`);
148923
149244
  console.error(" Run `fn init` first to initialize the project.\n");
@@ -149173,10 +149494,10 @@ var init_project = __esm({
149173
149494
  });
149174
149495
 
149175
149496
  // src/commands/skill-installation.ts
149176
- import { cpSync as cpSync2, existsSync as existsSync42, mkdirSync as mkdirSync8 } from "node:fs";
149497
+ import { cpSync as cpSync2, existsSync as existsSync43, mkdirSync as mkdirSync8 } from "node:fs";
149177
149498
  import { homedir as homedir7 } from "node:os";
149178
- import { dirname as dirname23, join as join56, resolve as resolve30 } from "node:path";
149179
- import { fileURLToPath as fileURLToPath7 } from "node:url";
149499
+ import { dirname as dirname24, join as join56, resolve as resolve32 } from "node:path";
149500
+ import { fileURLToPath as fileURLToPath8 } from "node:url";
149180
149501
  function getSupportedSkillInstallTargets(homeDir = process.env.HOME || process.env.USERPROFILE || homedir7()) {
149181
149502
  return [
149182
149503
  { client: "claude", targetDir: join56(homeDir, ".claude", "skills", FUSION_SKILL_NAME2) },
@@ -149185,9 +149506,9 @@ function getSupportedSkillInstallTargets(homeDir = process.env.HOME || process.e
149185
149506
  ];
149186
149507
  }
149187
149508
  function resolveBundledFusionSkillSource() {
149188
- const here = fileURLToPath7(import.meta.url);
149189
- const source = resolve30(dirname23(here), "..", "..", "skill", FUSION_SKILL_NAME2);
149190
- return existsSync42(source) ? source : null;
149509
+ const here = fileURLToPath8(import.meta.url);
149510
+ const source = resolve32(dirname24(here), "..", "..", "skill", FUSION_SKILL_NAME2);
149511
+ return existsSync43(source) ? source : null;
149191
149512
  }
149192
149513
  function installBundledFusionSkill(options = {}) {
149193
149514
  const sourceDir = options.sourceDir ?? resolveBundledFusionSkillSource();
@@ -149205,7 +149526,7 @@ function installBundledFusionSkill(options = {}) {
149205
149526
  }
149206
149527
  const results = targets.map((target) => {
149207
149528
  try {
149208
- if (existsSync42(target.targetDir)) {
149529
+ if (existsSync43(target.targetDir)) {
149209
149530
  return {
149210
149531
  client: target.client,
149211
149532
  targetDir: target.targetDir,
@@ -149213,7 +149534,7 @@ function installBundledFusionSkill(options = {}) {
149213
149534
  reason: "existing install preserved"
149214
149535
  };
149215
149536
  }
149216
- mkdirSync8(dirname23(target.targetDir), { recursive: true });
149537
+ mkdirSync8(dirname24(target.targetDir), { recursive: true });
149217
149538
  cpSync2(sourceDir, target.targetDir, { recursive: true });
149218
149539
  return {
149219
149540
  client: target.client,
@@ -149244,15 +149565,15 @@ var init_exports = {};
149244
149565
  __export(init_exports, {
149245
149566
  runInit: () => runInit
149246
149567
  });
149247
- import { existsSync as existsSync43, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync16 } from "node:fs";
149248
- import { join as join57, resolve as resolve31, basename as basename14 } from "node:path";
149568
+ import { existsSync as existsSync44, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync17 } from "node:fs";
149569
+ import { join as join57, resolve as resolve33, basename as basename14 } from "node:path";
149249
149570
  import { exec as exec11 } from "node:child_process";
149250
149571
  import { promisify as promisify16 } from "node:util";
149251
149572
  async function runInit(options = {}) {
149252
- const cwd = options.path ? resolve31(options.path) : process.cwd();
149573
+ const cwd = options.path ? resolve33(options.path) : process.cwd();
149253
149574
  const fusionDir = join57(cwd, ".fusion");
149254
149575
  const dbPath = join57(fusionDir, "fusion.db");
149255
- if (existsSync43(fusionDir) && existsSync43(dbPath)) {
149576
+ if (existsSync44(fusionDir) && existsSync44(dbPath)) {
149256
149577
  const central2 = new CentralCore();
149257
149578
  await central2.init();
149258
149579
  const existing = await central2.getProjectByPath(cwd);
@@ -149277,13 +149598,20 @@ async function runInit(options = {}) {
149277
149598
  const projectName = options.name ?? await detectProjectName(cwd);
149278
149599
  console.log(`Initializing fn project: "${projectName}"`);
149279
149600
  console.log(` Path: ${cwd}`);
149280
- if (!existsSync43(fusionDir)) {
149601
+ if (!existsSync44(fusionDir)) {
149281
149602
  mkdirSync9(fusionDir, { recursive: true });
149282
149603
  console.log(` \u2713 Created .fusion/ directory`);
149283
149604
  }
149605
+ const hasGitRepo = await isGitRepo2(cwd);
149606
+ if (!hasGitRepo && options.git) {
149607
+ await initializeGitRepo(cwd);
149608
+ console.log(` \u2713 Initialized git repository`);
149609
+ } else if (!hasGitRepo) {
149610
+ console.log(` \u26A0 Not a git repository. Run 'fn init --git' to auto-initialize one.`);
149611
+ }
149284
149612
  await addLocalStorageToGitignore(cwd);
149285
149613
  await warnIfQmdMissing();
149286
- if (!existsSync43(dbPath)) {
149614
+ if (!existsSync44(dbPath)) {
149287
149615
  const sqliteHeader = Buffer.from([
149288
149616
  83,
149289
149617
  81,
@@ -149351,7 +149679,7 @@ async function runInit(options = {}) {
149351
149679
  }
149352
149680
  }
149353
149681
  async function detectProjectName(dir2) {
149354
- if (!existsSync43(join57(dir2, ".git"))) {
149682
+ if (!existsSync44(join57(dir2, ".git"))) {
149355
149683
  return basename14(dir2) || "my-project";
149356
149684
  }
149357
149685
  try {
@@ -149373,9 +149701,9 @@ async function detectProjectName(dir2) {
149373
149701
  async function addLocalStorageToGitignore(cwd) {
149374
149702
  const gitignorePath = join57(cwd, ".gitignore");
149375
149703
  let content = "";
149376
- if (existsSync43(gitignorePath)) {
149704
+ if (existsSync44(gitignorePath)) {
149377
149705
  try {
149378
- content = readFileSync16(gitignorePath, "utf-8");
149706
+ content = readFileSync17(gitignorePath, "utf-8");
149379
149707
  } catch {
149380
149708
  }
149381
149709
  }
@@ -149395,6 +149723,45 @@ async function addLocalStorageToGitignore(cwd) {
149395
149723
  console.log(` \u26A0 Could not update .gitignore (best-effort)`);
149396
149724
  }
149397
149725
  }
149726
+ async function initializeGitRepo(cwd) {
149727
+ await execAsync11("git init", { cwd, timeout: 1e4 });
149728
+ try {
149729
+ const { stdout } = await execAsync11("git symbolic-ref --quiet --short HEAD", {
149730
+ cwd,
149731
+ timeout: 1e4
149732
+ });
149733
+ if (stdout.trim() !== "main") {
149734
+ await execAsync11("git checkout -b main", { cwd, timeout: 1e4 });
149735
+ }
149736
+ } catch {
149737
+ try {
149738
+ await execAsync11("git checkout -b main", { cwd, timeout: 1e4 });
149739
+ } catch {
149740
+ await execAsync11("git checkout main", { cwd, timeout: 1e4 });
149741
+ }
149742
+ }
149743
+ await ensureGitConfig(cwd, "user.name", "Fusion");
149744
+ await ensureGitConfig(cwd, "user.email", "noreply@runfusion.ai");
149745
+ const gitkeepPath = join57(cwd, ".gitkeep");
149746
+ if (!existsSync44(gitkeepPath)) {
149747
+ writeFileSync3(gitkeepPath, "\n");
149748
+ }
149749
+ await execAsync11("git add .gitkeep", { cwd, timeout: 1e4 });
149750
+ await execAsync11('git commit --allow-empty -m "chore: initial commit"', {
149751
+ cwd,
149752
+ timeout: 1e4
149753
+ });
149754
+ }
149755
+ async function ensureGitConfig(cwd, key, value) {
149756
+ try {
149757
+ const { stdout } = await execAsync11(`git config --get ${key}`, { cwd, timeout: 1e4 });
149758
+ if (stdout.trim().length > 0) {
149759
+ return;
149760
+ }
149761
+ } catch {
149762
+ }
149763
+ await execAsync11(`git config ${key} "${value}"`, { cwd, timeout: 1e4 });
149764
+ }
149398
149765
  async function warnIfQmdMissing() {
149399
149766
  if (await isQmdAvailable()) {
149400
149767
  console.log(` \u2713 qmd available for memory search`);
@@ -149425,6 +149792,7 @@ var init_init = __esm({
149425
149792
  "use strict";
149426
149793
  init_src();
149427
149794
  init_claude_skills_runner();
149795
+ init_git();
149428
149796
  init_skill_installation();
149429
149797
  execAsync11 = promisify16(exec11);
149430
149798
  }
@@ -149513,8 +149881,8 @@ var agent_import_exports = {};
149513
149881
  __export(agent_import_exports, {
149514
149882
  runAgentImport: () => runAgentImport
149515
149883
  });
149516
- import { existsSync as existsSync44, mkdirSync as mkdirSync10, readFileSync as readFileSync17, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
149517
- import { resolve as resolve32 } from "node:path";
149884
+ import { existsSync as existsSync45, mkdirSync as mkdirSync10, readFileSync as readFileSync18, statSync as statSync9, writeFileSync as writeFileSync4 } from "node:fs";
149885
+ import { resolve as resolve34 } from "node:path";
149518
149886
  function slugifyPathSegment(input) {
149519
149887
  if (!input || typeof input !== "string") {
149520
149888
  return "unnamed";
@@ -149563,16 +149931,16 @@ async function importSkillsToProject(projectPath, skills, companySlug, dryRun) {
149563
149931
  errors: []
149564
149932
  };
149565
149933
  const companyDir = slugifyPathSegment(companySlug ?? "unknown-company");
149566
- const baseSkillsDir = resolve32(projectPath, "skills", "imported", companyDir);
149934
+ const baseSkillsDir = resolve34(projectPath, "skills", "imported", companyDir);
149567
149935
  for (const skill of skills) {
149568
149936
  if (!skill.name || typeof skill.name !== "string" || skill.name.trim().length === 0) {
149569
149937
  result.errors.push({ name: "(unnamed)", error: "Skill is missing required 'name' field" });
149570
149938
  continue;
149571
149939
  }
149572
149940
  const skillSlug = slugifyPathSegment(skill.name);
149573
- const skillDir = resolve32(baseSkillsDir, skillSlug);
149574
- const skillPath = resolve32(skillDir, "SKILL.md");
149575
- if (existsSync44(skillPath)) {
149941
+ const skillDir = resolve34(baseSkillsDir, skillSlug);
149942
+ const skillPath = resolve34(skillDir, "SKILL.md");
149943
+ if (existsSync45(skillPath)) {
149576
149944
  result.skipped.push(skill.name);
149577
149945
  continue;
149578
149946
  }
@@ -149644,8 +150012,8 @@ function isArchivePath(path4) {
149644
150012
  async function runAgentImport(source, options) {
149645
150013
  const dryRun = options?.dryRun ?? false;
149646
150014
  const skipExisting = options?.skipExisting ?? false;
149647
- const sourcePath = resolve32(source);
149648
- if (!existsSync44(sourcePath)) {
150015
+ const sourcePath = resolve34(source);
150016
+ if (!existsSync45(sourcePath)) {
149649
150017
  console.error(`Path not found: ${sourcePath}`);
149650
150018
  process.exit(1);
149651
150019
  }
@@ -149691,7 +150059,7 @@ async function runAgentImport(source, options) {
149691
150059
  isPackageImport = true;
149692
150060
  ({ items: importItems, result } = prepareAgentCompaniesImport(pkg, conversionOptions));
149693
150061
  } else if (sourcePath.endsWith(".md")) {
149694
- const content = readFileSync17(sourcePath, "utf-8");
150062
+ const content = readFileSync18(sourcePath, "utf-8");
149695
150063
  const { manifest } = parseSingleAgentManifest(content);
149696
150064
  const pkg = {
149697
150065
  company: void 0,
@@ -149783,7 +150151,7 @@ var agent_export_exports = {};
149783
150151
  __export(agent_export_exports, {
149784
150152
  runAgentExport: () => runAgentExport
149785
150153
  });
149786
- import { resolve as resolve33 } from "node:path";
150154
+ import { resolve as resolve35 } from "node:path";
149787
150155
  async function getProjectPath4(projectName) {
149788
150156
  if (projectName) {
149789
150157
  const context = await resolveProject(projectName);
@@ -149821,7 +150189,7 @@ async function runAgentExport(outputDir, options) {
149821
150189
  console.error("No agents found to export");
149822
150190
  process.exit(1);
149823
150191
  }
149824
- const result = await exportAgentsToDirectory(agents, resolve33(outputDir), {
150192
+ const result = await exportAgentsToDirectory(agents, resolve35(outputDir), {
149825
150193
  companyName: options?.companyName,
149826
150194
  companySlug: options?.companySlug
149827
150195
  });
@@ -150040,7 +150408,7 @@ __export(plugin_exports, {
150040
150408
  runPluginList: () => runPluginList,
150041
150409
  runPluginUninstall: () => runPluginUninstall
150042
150410
  });
150043
- import { existsSync as existsSync45 } from "node:fs";
150411
+ import { existsSync as existsSync46 } from "node:fs";
150044
150412
  import { join as join58 } from "node:path";
150045
150413
  import { readFile as readFile23 } from "node:fs/promises";
150046
150414
  import * as readline from "node:readline";
@@ -150079,7 +150447,7 @@ async function createPluginLoader(pluginStore, projectName) {
150079
150447
  }
150080
150448
  async function loadManifestFromPath(pluginPath) {
150081
150449
  const manifestPath = join58(pluginPath, "manifest.json");
150082
- if (!existsSync45(manifestPath)) {
150450
+ if (!existsSync46(manifestPath)) {
150083
150451
  throw new Error(`Plugin manifest not found at: ${manifestPath}`);
150084
150452
  }
150085
150453
  const content = await readFile23(manifestPath, "utf-8");
@@ -150137,7 +150505,7 @@ async function runPluginInstall(source, options) {
150137
150505
  console.error("Please provide a local path to the plugin directory.");
150138
150506
  process.exit(1);
150139
150507
  }
150140
- if (!existsSync45(source)) {
150508
+ if (!existsSync46(source)) {
150141
150509
  console.error(`Plugin path does not exist: ${source}`);
150142
150510
  process.exit(1);
150143
150511
  }
@@ -150182,14 +150550,14 @@ async function runPluginUninstall(id, options) {
150182
150550
  console.log(` Uninstall "${plugin.name}"?`);
150183
150551
  console.log(` This will stop and remove the plugin.`);
150184
150552
  console.log();
150185
- const response = await new Promise((resolve34) => {
150553
+ const response = await new Promise((resolve36) => {
150186
150554
  const rl = readline.createInterface({
150187
150555
  input: process.stdin,
150188
150556
  output: process.stdout
150189
150557
  });
150190
150558
  rl.question(" Continue? [y/N] ", (answer) => {
150191
150559
  rl.close();
150192
- resolve34(answer.toLowerCase());
150560
+ resolve36(answer.toLowerCase());
150193
150561
  });
150194
150562
  });
150195
150563
  if (response !== "y" && response !== "yes") {
@@ -150269,7 +150637,7 @@ var plugin_scaffold_exports = {};
150269
150637
  __export(plugin_scaffold_exports, {
150270
150638
  runPluginCreate: () => runPluginCreate
150271
150639
  });
150272
- import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, existsSync as existsSync46 } from "node:fs";
150640
+ import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync5, existsSync as existsSync47 } from "node:fs";
150273
150641
  import { join as join59 } from "node:path";
150274
150642
  function toTitleCase(str) {
150275
150643
  return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
@@ -150404,7 +150772,7 @@ async function runPluginCreate(name, options) {
150404
150772
  }
150405
150773
  const targetDir = options?.output ?? name;
150406
150774
  const targetPath = join59(process.cwd(), targetDir);
150407
- if (existsSync46(targetPath)) {
150775
+ if (existsSync47(targetPath)) {
150408
150776
  console.error(`Error: Directory '${targetDir}' already exists.`);
150409
150777
  console.error("Please choose a different name or remove the existing directory.");
150410
150778
  process.exit(1);
@@ -150536,9 +150904,9 @@ async function runSkillsInstall(args, options) {
150536
150904
  cwd: process.cwd(),
150537
150905
  stdio: "inherit"
150538
150906
  });
150539
- const exitCode = await new Promise((resolve34, reject2) => {
150907
+ const exitCode = await new Promise((resolve36, reject2) => {
150540
150908
  child.on("exit", (code) => {
150541
- resolve34(code ?? 1);
150909
+ resolve36(code ?? 1);
150542
150910
  });
150543
150911
  child.on("error", (err) => {
150544
150912
  reject2(err);
@@ -150569,21 +150937,21 @@ __export(native_patch_exports, {
150569
150937
  isTerminalAvailable: () => isTerminalAvailable,
150570
150938
  setupNativeResolution: () => setupNativeResolution
150571
150939
  });
150572
- import { join as join60, basename as basename15, dirname as dirname24 } from "node:path";
150573
- import { existsSync as existsSync47, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync4, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
150940
+ import { join as join60, basename as basename15, dirname as dirname25 } from "node:path";
150941
+ import { existsSync as existsSync48, copyFileSync, mkdirSync as mkdirSync12, symlinkSync as symlinkSync2, rmSync as rmSync5, lstatSync as lstatSync3, readlinkSync as readlinkSync2 } from "node:fs";
150574
150942
  import { tmpdir as tmpdir3 } from "node:os";
150575
150943
  function findStagedNativeDir2() {
150576
150944
  const platform3 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
150577
150945
  const arch = process.arch === "arm64" ? "arm64" : process.arch === "x64" ? "x64" : "unknown";
150578
150946
  const prebuildName = `${platform3}-${arch}`;
150579
- const execDir = dirname24(process.execPath);
150947
+ const execDir = dirname25(process.execPath);
150580
150948
  const nextToBinary = join60(execDir, "runtime", prebuildName);
150581
- if (existsSync47(join60(nextToBinary, "pty.node"))) {
150949
+ if (existsSync48(join60(nextToBinary, "pty.node"))) {
150582
150950
  return nextToBinary;
150583
150951
  }
150584
150952
  if (process.env.FUSION_RUNTIME_DIR) {
150585
150953
  const envPath = join60(process.env.FUSION_RUNTIME_DIR, prebuildName);
150586
- if (existsSync47(join60(envPath, "pty.node"))) {
150954
+ if (existsSync48(join60(envPath, "pty.node"))) {
150587
150955
  return envPath;
150588
150956
  }
150589
150957
  }
@@ -150593,12 +150961,12 @@ function cleanupStaleBunfsLinks() {
150593
150961
  if (process.platform === "win32") return;
150594
150962
  const bunfsRoot = "/$bunfs/root";
150595
150963
  try {
150596
- if (existsSync47(bunfsRoot)) {
150964
+ if (existsSync48(bunfsRoot)) {
150597
150965
  const stats = lstatSync3(bunfsRoot);
150598
150966
  if (stats.isSymbolicLink()) {
150599
150967
  const target = readlinkSync2(bunfsRoot);
150600
- if (target.includes("fn-bunfs-") && !existsSync47(target)) {
150601
- rmSync4(bunfsRoot);
150968
+ if (target.includes("fn-bunfs-") && !existsSync48(target)) {
150969
+ rmSync5(bunfsRoot);
150602
150970
  console.log("[fn-native-patch] Cleaned up stale /$bunfs/root symlink");
150603
150971
  }
150604
150972
  }
@@ -150625,17 +150993,17 @@ function setupNativeResolution() {
150625
150993
  mkdirSync12(platformDir, { recursive: true });
150626
150994
  const ptyNodeDest = join60(platformDir, "pty.node");
150627
150995
  copyFileSync(join60(nativeDir, "pty.node"), ptyNodeDest);
150628
- if (existsSync47(join60(nativeDir, "spawn-helper"))) {
150996
+ if (existsSync48(join60(nativeDir, "spawn-helper"))) {
150629
150997
  copyFileSync(join60(nativeDir, "spawn-helper"), join60(platformDir, "spawn-helper"));
150630
150998
  }
150631
150999
  process.env.FUSION_FAKE_BUNFS_ROOT = tmpRoot;
150632
151000
  if (process.platform !== "win32") {
150633
151001
  const bunfsRoot = "/$bunfs/root";
150634
151002
  try {
150635
- if (existsSync47(bunfsRoot)) {
151003
+ if (existsSync48(bunfsRoot)) {
150636
151004
  const stats = lstatSync3(bunfsRoot);
150637
151005
  if (stats.isSymbolicLink()) {
150638
- rmSync4(bunfsRoot);
151006
+ rmSync5(bunfsRoot);
150639
151007
  }
150640
151008
  }
150641
151009
  symlinkSync2(fnDir, bunfsRoot);
@@ -150655,10 +151023,10 @@ function setupNativeResolution() {
150655
151023
  function cleanupNativeResolution() {
150656
151024
  if (bunfsSymlinkPath && process.platform !== "win32") {
150657
151025
  try {
150658
- if (existsSync47(bunfsSymlinkPath)) {
151026
+ if (existsSync48(bunfsSymlinkPath)) {
150659
151027
  const stats = lstatSync3(bunfsSymlinkPath);
150660
151028
  if (stats.isSymbolicLink()) {
150661
- rmSync4(bunfsSymlinkPath);
151029
+ rmSync5(bunfsSymlinkPath);
150662
151030
  }
150663
151031
  }
150664
151032
  } catch {
@@ -150701,9 +151069,9 @@ var init_native_patch = __esm({
150701
151069
  });
150702
151070
 
150703
151071
  // src/bin.ts
150704
- import { existsSync as existsSync48, mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
151072
+ import { existsSync as existsSync49, mkdtempSync as mkdtempSync2, readFileSync as readFileSync19, symlinkSync as symlinkSync3, writeFileSync as writeFileSync6 } from "node:fs";
150705
151073
  import { createRequire as createRequire6 } from "node:module";
150706
- import { join as join61, dirname as dirname25 } from "node:path";
151074
+ import { join as join61, dirname as dirname26 } from "node:path";
150707
151075
  import { tmpdir as tmpdir4 } from "node:os";
150708
151076
  import { performance as performance3 } from "node:perf_hooks";
150709
151077
  var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
@@ -150720,11 +151088,11 @@ function configurePiPackage() {
150720
151088
  try {
150721
151089
  const require4 = createRequire6(import.meta.url);
150722
151090
  const piPackagePath = require4.resolve("@mariozechner/pi-coding-agent/package.json");
150723
- const piPackageDir = dirname25(piPackagePath);
150724
- packageJson = JSON.parse(readFileSync18(piPackagePath, "utf-8"));
151091
+ const piPackageDir = dirname26(piPackagePath);
151092
+ packageJson = JSON.parse(readFileSync19(piPackagePath, "utf-8"));
150725
151093
  for (const entry of ["dist", "docs", "examples", "README.md", "CHANGELOG.md"]) {
150726
151094
  const source = join61(piPackageDir, entry);
150727
- if (existsSync48(source)) {
151095
+ if (existsSync49(source)) {
150728
151096
  symlinkSync3(source, join61(tmp, entry));
150729
151097
  }
150730
151098
  }
@@ -150743,8 +151111,8 @@ setInterval(() => {
150743
151111
  performance3.clearMarks();
150744
151112
  }, 3e4).unref();
150745
151113
  function loadEnvFile(path4) {
150746
- if (!existsSync48(path4)) return;
150747
- const contents = readFileSync18(path4, "utf-8");
151114
+ if (!existsSync49(path4)) return;
151115
+ const contents = readFileSync19(path4, "utf-8");
150748
151116
  for (const rawLine of contents.split(/\r?\n/)) {
150749
151117
  const line = rawLine.trim();
150750
151118
  if (!line || line.startsWith("#")) continue;
@@ -150872,7 +151240,7 @@ fn \u2014 AI-orchestrated task board
150872
151240
 
150873
151241
  Usage:
150874
151242
  fn Launch the dashboard (same as fn dashboard)
150875
- fn init [opts] Initialize a new fn project in the current directory
151243
+ fn init [opts] Initialize a new fn project (--name, --path, --git)
150876
151244
  fn dashboard Start the board web UI
150877
151245
  fn dashboard --paused Start with automation paused
150878
151246
  fn dashboard --dev Start web UI only (no AI engine)
@@ -151131,7 +151499,8 @@ async function main() {
151131
151499
  const name = nameIdx !== -1 && nameIdx + 1 < args.length ? args[nameIdx + 1] : void 0;
151132
151500
  const pathIdx = args.indexOf("--path");
151133
151501
  const path4 = pathIdx !== -1 && pathIdx + 1 < args.length ? args[pathIdx + 1] : void 0;
151134
- await runInit2({ name, path: path4 });
151502
+ const git = args.includes("--git");
151503
+ await runInit2({ name, path: path4, git });
151135
151504
  break;
151136
151505
  }
151137
151506
  case "dashboard": {