@runfusion/fusion 0.0.6 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -138,7 +138,7 @@ var init_settings_schema = __esm({
138
138
  worktreeRebaseRemote: "",
139
139
  strictScopeEnforcement: false,
140
140
  buildRetryCount: 0,
141
- verificationFixRetries: 1,
141
+ verificationFixRetries: 3,
142
142
  buildTimeoutMs: 3e5,
143
143
  requirePlanApproval: false,
144
144
  specStalenessEnabled: false,
@@ -1975,6 +1975,18 @@ function fromJson(json) {
1975
1975
  return void 0;
1976
1976
  }
1977
1977
  }
1978
+ function probeFts5(db) {
1979
+ if (process.env.FUSION_DISABLE_FTS5 === "1" || process.env.FUSION_DISABLE_FTS5 === "true") {
1980
+ return false;
1981
+ }
1982
+ try {
1983
+ db.exec("CREATE VIRTUAL TABLE IF NOT EXISTS __fusion_fts5_probe USING fts5(x)");
1984
+ db.exec("DROP TABLE IF EXISTS __fusion_fts5_probe");
1985
+ return true;
1986
+ } catch {
1987
+ return false;
1988
+ }
1989
+ }
1978
1990
  function normalizeTaskComments(steeringComments, comments) {
1979
1991
  const normalizedComments = [];
1980
1992
  const seenKeys = /* @__PURE__ */ new Set();
@@ -2495,6 +2507,7 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
2495
2507
  dbPath;
2496
2508
  /** Tracks transaction nesting depth for savepoint-based nested transactions. */
2497
2509
  transactionDepth = 0;
2510
+ _fts5Available;
2498
2511
  constructor(kbDir) {
2499
2512
  this.dbPath = join(kbDir, "fusion.db");
2500
2513
  if (!isAbsolute(kbDir)) {
@@ -2507,6 +2520,16 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
2507
2520
  this.db.exec("PRAGMA journal_mode = WAL");
2508
2521
  this.db.exec("PRAGMA busy_timeout = 5000");
2509
2522
  this.db.exec("PRAGMA foreign_keys = ON");
2523
+ this._fts5Available = probeFts5(this.db);
2524
+ }
2525
+ /**
2526
+ * True when the underlying SQLite build has FTS5 (`CREATE VIRTUAL TABLE … USING fts5`).
2527
+ * Node's bundled SQLite only exposes FTS5 when built with `SQLITE_ENABLE_FTS5`;
2528
+ * older Node 22.x LTS builds do not. Consumers must fall back to LIKE-based scans
2529
+ * when this is false. Override with `FUSION_DISABLE_FTS5=1` to force the fallback path.
2530
+ */
2531
+ get fts5Available() {
2532
+ return this._fts5Available;
2510
2533
  }
2511
2534
  /**
2512
2535
  * Initialize the database: create tables if they don't exist
@@ -2816,6 +2839,9 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
2816
2839
  }
2817
2840
  if (version < 21) {
2818
2841
  this.applyMigration(21, () => {
2842
+ if (!this._fts5Available) {
2843
+ return;
2844
+ }
2819
2845
  this.db.exec(`
2820
2846
  CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
2821
2847
  id,
@@ -3225,6 +3251,9 @@ CREATE INDEX IF NOT EXISTS idxInsightRunsProjectId
3225
3251
  }
3226
3252
  if (version < 35) {
3227
3253
  this.applyMigration(35, () => {
3254
+ if (!this._fts5Available) {
3255
+ return;
3256
+ }
3228
3257
  const hasTaskTitle = this.hasColumn("tasks", "title");
3229
3258
  const updateColumns = hasTaskTitle ? "id, title, description, comments" : "id, description, comments";
3230
3259
  const oldTitle = hasTaskTitle ? "COALESCE(old.title, '')" : "''";
@@ -3518,6 +3547,9 @@ function resolveCreationRuntimeConfig(incoming, metadata) {
3518
3547
  return incoming;
3519
3548
  }
3520
3549
  const rc = { ...incoming ?? {} };
3550
+ if (typeof rc.enabled !== "boolean") {
3551
+ rc.enabled = true;
3552
+ }
3521
3553
  if (typeof rc.heartbeatIntervalMs !== "number" || !Number.isFinite(rc.heartbeatIntervalMs)) {
3522
3554
  rc.heartbeatIntervalMs = DEFAULT_AGENT_HEARTBEAT_INTERVAL_MS;
3523
3555
  }
@@ -3563,6 +3595,7 @@ var init_agent_store = __esm({
3563
3595
  const _2 = this.db;
3564
3596
  await mkdir(this.agentsDir, { recursive: true });
3565
3597
  await this.importLegacyFileDataOnce();
3598
+ await this.normalizeHeartbeatDefaultsOnce();
3566
3599
  }
3567
3600
  /**
3568
3601
  * One-way migration helper for projects that still have legacy agent JSON
@@ -3667,6 +3700,51 @@ var init_agent_store = __esm({
3667
3700
  `).run(migrationKey, migrationVersion);
3668
3701
  this.db.bumpLastModified();
3669
3702
  }
3703
+ /**
3704
+ * One-time normalization for durable agents created before the heartbeat
3705
+ * toggle was exposed in the UI. Those agents could persist
3706
+ * `runtimeConfig.enabled = false` even though users had no supported way to
3707
+ * manage that flag, which caused timers to stay disabled after restart.
3708
+ *
3709
+ * We normalize only once per project. After this migration lands, explicit
3710
+ * user choices are preserved because the version gate prevents reruns.
3711
+ */
3712
+ async normalizeHeartbeatDefaultsOnce() {
3713
+ const migrationKey = "agentHeartbeatDefaultVersion";
3714
+ const migrationVersion = "1";
3715
+ const row = this.db.prepare("SELECT value FROM __meta WHERE key = ?").get(migrationKey);
3716
+ if (row?.value === migrationVersion) {
3717
+ return;
3718
+ }
3719
+ const agents = await this.listAgents({ includeEphemeral: true });
3720
+ let changed = 0;
3721
+ for (const agent of agents) {
3722
+ if (isEphemeralAgent(agent)) {
3723
+ continue;
3724
+ }
3725
+ const nextRuntimeConfig = {
3726
+ ...resolveCreationRuntimeConfig(agent.runtimeConfig, agent.metadata) ?? {},
3727
+ enabled: true
3728
+ };
3729
+ const currentRuntimeConfig = agent.runtimeConfig ?? void 0;
3730
+ if (JSON.stringify(nextRuntimeConfig) === JSON.stringify(currentRuntimeConfig)) {
3731
+ continue;
3732
+ }
3733
+ await this.writeAgent({
3734
+ ...agent,
3735
+ runtimeConfig: nextRuntimeConfig
3736
+ });
3737
+ changed++;
3738
+ }
3739
+ this.db.prepare(`
3740
+ INSERT INTO __meta (key, value)
3741
+ VALUES (?, ?)
3742
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
3743
+ `).run(migrationKey, migrationVersion);
3744
+ if (changed > 0) {
3745
+ this.db.bumpLastModified();
3746
+ }
3747
+ }
3670
3748
  /**
3671
3749
  * Create a new agent with "idle" state.
3672
3750
  *
@@ -5776,11 +5854,12 @@ var init_global_settings = __esm({
5776
5854
  import { DatabaseSync as DatabaseSync2 } from "node:sqlite";
5777
5855
  import { existsSync as existsSync4, mkdirSync as mkdirSync3 } from "node:fs";
5778
5856
  import { join as join5 } from "node:path";
5779
- var ARCHIVE_SCHEMA_SQL, ArchiveDatabase;
5857
+ var BASE_SCHEMA_SQL, FTS5_SCHEMA_SQL, ArchiveDatabase;
5780
5858
  var init_archive_db = __esm({
5781
5859
  "../core/src/archive-db.ts"() {
5782
5860
  "use strict";
5783
- ARCHIVE_SCHEMA_SQL = `
5861
+ init_db();
5862
+ BASE_SCHEMA_SQL = `
5784
5863
  CREATE TABLE IF NOT EXISTS archived_tasks (
5785
5864
  id TEXT PRIMARY KEY,
5786
5865
  taskJson TEXT NOT NULL,
@@ -5796,7 +5875,8 @@ CREATE TABLE IF NOT EXISTS archived_tasks (
5796
5875
 
5797
5876
  CREATE INDEX IF NOT EXISTS idxArchivedTasksArchivedAt ON archived_tasks(archivedAt);
5798
5877
  CREATE INDEX IF NOT EXISTS idxArchivedTasksCreatedAt ON archived_tasks(createdAt);
5799
-
5878
+ `;
5879
+ FTS5_SCHEMA_SQL = `
5800
5880
  CREATE VIRTUAL TABLE IF NOT EXISTS archived_tasks_fts USING fts5(
5801
5881
  id,
5802
5882
  title,
@@ -5824,18 +5904,26 @@ CREATE TRIGGER IF NOT EXISTS archived_tasks_fts_ad AFTER DELETE ON archived_task
5824
5904
  END;
5825
5905
  `;
5826
5906
  ArchiveDatabase = class {
5907
+ db;
5908
+ _fts5Available;
5827
5909
  constructor(kbDir) {
5828
- this.kbDir = kbDir;
5829
5910
  if (!existsSync4(kbDir)) {
5830
5911
  mkdirSync3(kbDir, { recursive: true });
5831
5912
  }
5832
5913
  this.db = new DatabaseSync2(join5(kbDir, "archive.db"));
5833
5914
  this.db.exec("PRAGMA journal_mode = WAL");
5834
5915
  this.db.exec("PRAGMA busy_timeout = 5000");
5916
+ this._fts5Available = probeFts5(this.db);
5917
+ }
5918
+ /** True when this SQLite build has FTS5. See db.ts#probeFts5. */
5919
+ get fts5Available() {
5920
+ return this._fts5Available;
5835
5921
  }
5836
- db;
5837
5922
  init() {
5838
- this.db.exec(ARCHIVE_SCHEMA_SQL);
5923
+ this.db.exec(BASE_SCHEMA_SQL);
5924
+ if (this._fts5Available) {
5925
+ this.db.exec(FTS5_SCHEMA_SQL);
5926
+ }
5839
5927
  this.addColumnIfMissing("archived_tasks", "prompt", "TEXT");
5840
5928
  }
5841
5929
  upsert(entry) {
@@ -5876,15 +5964,48 @@ END;
5876
5964
  delete(id) {
5877
5965
  this.db.prepare("DELETE FROM archived_tasks WHERE id = ?").run(id);
5878
5966
  }
5967
+ /**
5968
+ * Full-text search over archived tasks. Accepts a raw user query and routes
5969
+ * through FTS5 when available, or a LIKE-based scan when not.
5970
+ */
5879
5971
  search(query, limit) {
5972
+ const trimmed = query?.trim();
5973
+ if (!trimmed) return [];
5974
+ const tokens = trimmed.split(/\s+/).filter((t) => t.length > 0).map((t) => t.replace(/["{}:*^+()]/g, "")).filter((t) => t.length > 0);
5975
+ if (tokens.length === 0) return [];
5976
+ if (this._fts5Available) {
5977
+ const ftsQuery = tokens.map((token) => {
5978
+ if (/[":(){}*^+-]/.test(token)) {
5979
+ return `"${token.replace(/"/g, '\\"')}"`;
5980
+ }
5981
+ return token;
5982
+ }).join(" OR ");
5983
+ const rows2 = this.db.prepare(`
5984
+ SELECT a.taskJson
5985
+ FROM archived_tasks a
5986
+ JOIN archived_tasks_fts fts ON a.rowid = fts.rowid
5987
+ WHERE archived_tasks_fts MATCH ?
5988
+ ORDER BY rank
5989
+ LIMIT ?
5990
+ `).all(ftsQuery, limit);
5991
+ return rows2.map((row) => JSON.parse(row.taskJson));
5992
+ }
5993
+ const searchColumns = ["id", "title", "description", "comments"];
5994
+ const perTokenClause = `(${searchColumns.map((c) => `"${c}" LIKE ? ESCAPE '\\'`).join(" OR ")})`;
5995
+ const whereTokens = tokens.map(() => perTokenClause).join(" OR ");
5996
+ const params = [];
5997
+ for (const token of tokens) {
5998
+ const pattern = `%${token.replace(/[\\%_]/g, "\\$&")}%`;
5999
+ for (let i = 0; i < searchColumns.length; i++) params.push(pattern);
6000
+ }
6001
+ params.push(limit);
5880
6002
  const rows = this.db.prepare(`
5881
- SELECT a.taskJson
5882
- FROM archived_tasks a
5883
- JOIN archived_tasks_fts fts ON a.rowid = fts.rowid
5884
- WHERE archived_tasks_fts MATCH ?
5885
- ORDER BY rank
6003
+ SELECT taskJson
6004
+ FROM archived_tasks
6005
+ WHERE ${whereTokens}
6006
+ ORDER BY archivedAt DESC
5886
6007
  LIMIT ?
5887
- `).all(query, limit);
6008
+ `).all(...params);
5888
6009
  return rows.map((row) => JSON.parse(row.taskJson));
5889
6010
  }
5890
6011
  close() {
@@ -12197,6 +12318,9 @@ var init_migration = __esm({
12197
12318
  * @returns Generated name
12198
12319
  */
12199
12320
  async generateProjectName(projectPath) {
12321
+ if (!existsSync7(join9(projectPath, ".git"))) {
12322
+ return basename3(projectPath);
12323
+ }
12200
12324
  try {
12201
12325
  const { execFile: execFile5 } = await import("node:child_process");
12202
12326
  const { promisify: promisify14 } = await import("node:util");
@@ -12204,7 +12328,7 @@ var init_migration = __esm({
12204
12328
  const { stdout } = await execFileAsync3(
12205
12329
  "git",
12206
12330
  ["remote", "get-url", "origin"],
12207
- { cwd: projectPath, timeout: 5e3 }
12331
+ { cwd: projectPath, timeout: 1e3 }
12208
12332
  );
12209
12333
  const remoteUrl = stdout.trim();
12210
12334
  if (remoteUrl) {
@@ -28698,6 +28822,27 @@ var init_run_command = __esm({
28698
28822
  }
28699
28823
  });
28700
28824
 
28825
+ // ../core/src/logger.ts
28826
+ function createLogger(prefix) {
28827
+ const tag = `[${prefix}]`;
28828
+ return {
28829
+ log(message, ...args) {
28830
+ console.error(`${tag} ${message}`, ...args);
28831
+ },
28832
+ warn(message, ...args) {
28833
+ console.warn(`${tag} ${message}`, ...args);
28834
+ },
28835
+ error(message, ...args) {
28836
+ console.error(`${tag} ${message}`, ...args);
28837
+ }
28838
+ };
28839
+ }
28840
+ var init_logger = __esm({
28841
+ "../core/src/logger.ts"() {
28842
+ "use strict";
28843
+ }
28844
+ });
28845
+
28701
28846
  // ../core/src/store.ts
28702
28847
  import { EventEmitter as EventEmitter11 } from "node:events";
28703
28848
  import { randomUUID as randomUUID6 } from "node:crypto";
@@ -28746,7 +28891,7 @@ function canonicalizeSettings(settings) {
28746
28891
  }
28747
28892
  return settings;
28748
28893
  }
28749
- var LEGACY_BACKUP_DIR, TASK_ACTIVITY_LOG_ENTRY_LIMIT, TASK_ACTIVITY_LOG_OUTCOME_LIMIT, ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT, ARCHIVE_AGENT_LOG_SNIPPET_LIMIT, TaskHasDependentsError, TaskStore;
28894
+ var LEGACY_BACKUP_DIR, TASK_ACTIVITY_LOG_ENTRY_LIMIT, TASK_ACTIVITY_LOG_OUTCOME_LIMIT, ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT, ARCHIVE_AGENT_LOG_SNIPPET_LIMIT, storeLog, TaskHasDependentsError, TaskStore;
28750
28895
  var init_store = __esm({
28751
28896
  "../core/src/store.ts"() {
28752
28897
  "use strict";
@@ -28764,11 +28909,13 @@ var init_store = __esm({
28764
28909
  init_task_merge();
28765
28910
  init_project_memory();
28766
28911
  init_run_command();
28912
+ init_logger();
28767
28913
  LEGACY_BACKUP_DIR = ".kb/backups";
28768
28914
  TASK_ACTIVITY_LOG_ENTRY_LIMIT = 1e3;
28769
28915
  TASK_ACTIVITY_LOG_OUTCOME_LIMIT = 4e3;
28770
28916
  ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT = 25;
28771
28917
  ARCHIVE_AGENT_LOG_SNIPPET_LIMIT = 160;
28918
+ storeLog = createLogger("task-store");
28772
28919
  TaskHasDependentsError = class extends Error {
28773
28920
  taskId;
28774
28921
  dependentIds;
@@ -29478,45 +29625,53 @@ ${recentText}` : void 0
29478
29625
  */
29479
29626
  setupActivityLogListeners() {
29480
29627
  this.on("task:created", (task) => {
29481
- this.recordActivity({
29482
- type: "task:created",
29483
- taskId: task.id,
29484
- taskTitle: task.title,
29485
- details: `Task ${task.id} created${task.title ? `: ${task.title}` : ""}`
29486
- }).catch(() => {
29487
- });
29628
+ this.recordActivityFromListener(
29629
+ {
29630
+ type: "task:created",
29631
+ taskId: task.id,
29632
+ taskTitle: task.title,
29633
+ details: `Task ${task.id} created${task.title ? `: ${task.title}` : ""}`
29634
+ },
29635
+ "task:created"
29636
+ );
29488
29637
  });
29489
29638
  this.on("task:moved", (data) => {
29490
- this.recordActivity({
29491
- type: "task:moved",
29492
- taskId: data.task.id,
29493
- taskTitle: data.task.title,
29494
- details: `Task ${data.task.id} moved: ${data.from} \u2192 ${data.to}`,
29495
- metadata: { from: data.from, to: data.to }
29496
- }).catch(() => {
29497
- });
29639
+ this.recordActivityFromListener(
29640
+ {
29641
+ type: "task:moved",
29642
+ taskId: data.task.id,
29643
+ taskTitle: data.task.title,
29644
+ details: `Task ${data.task.id} moved: ${data.from} \u2192 ${data.to}`,
29645
+ metadata: { from: data.from, to: data.to }
29646
+ },
29647
+ "task:moved"
29648
+ );
29498
29649
  });
29499
29650
  this.on("task:merged", (result) => {
29500
29651
  const status = result.merged ? "successfully merged" : "merge attempted";
29501
- this.recordActivity({
29502
- type: "task:merged",
29503
- taskId: result.task.id,
29504
- taskTitle: result.task.title,
29505
- details: `Task ${result.task.id} ${status} to main`,
29506
- metadata: { merged: result.merged, branch: result.branch }
29507
- }).catch(() => {
29508
- });
29652
+ this.recordActivityFromListener(
29653
+ {
29654
+ type: "task:merged",
29655
+ taskId: result.task.id,
29656
+ taskTitle: result.task.title,
29657
+ details: `Task ${result.task.id} ${status} to main`,
29658
+ metadata: { merged: result.merged, branch: result.branch }
29659
+ },
29660
+ "task:merged"
29661
+ );
29509
29662
  });
29510
29663
  this.on("task:updated", (task) => {
29511
29664
  if (task.status === "failed") {
29512
- this.recordActivity({
29513
- type: "task:failed",
29514
- taskId: task.id,
29515
- taskTitle: task.title,
29516
- details: `Task ${task.id} failed${task.error ? `: ${task.error}` : ""}`,
29517
- metadata: task.error ? { error: task.error } : void 0
29518
- }).catch(() => {
29519
- });
29665
+ this.recordActivityFromListener(
29666
+ {
29667
+ type: "task:failed",
29668
+ taskId: task.id,
29669
+ taskTitle: task.title,
29670
+ details: `Task ${task.id} failed${task.error ? `: ${task.error}` : ""}`,
29671
+ metadata: task.error ? { error: task.error } : void 0
29672
+ },
29673
+ "task:updated"
29674
+ );
29520
29675
  }
29521
29676
  });
29522
29677
  this.on("settings:updated", (data) => {
@@ -29534,21 +29689,35 @@ ${recentText}` : void 0
29534
29689
  importantChanges.push(`engine pause ${data.settings.enginePaused ? "enabled" : "disabled"}`);
29535
29690
  }
29536
29691
  if (importantChanges.length > 0) {
29537
- this.recordActivity({
29538
- type: "settings:updated",
29539
- details: `Settings updated: ${importantChanges.join(", ")}`,
29540
- metadata: { changes: importantChanges }
29541
- }).catch(() => {
29542
- });
29692
+ this.recordActivityFromListener(
29693
+ {
29694
+ type: "settings:updated",
29695
+ details: `Settings updated: ${importantChanges.join(", ")}`,
29696
+ metadata: { changes: importantChanges }
29697
+ },
29698
+ "settings:updated"
29699
+ );
29543
29700
  }
29544
29701
  });
29545
29702
  this.on("task:deleted", (task) => {
29546
- this.recordActivity({
29547
- type: "task:deleted",
29548
- taskId: task.id,
29549
- taskTitle: task.title,
29550
- details: `Task ${task.id} deleted${task.title ? `: ${task.title}` : ""}`
29551
- }).catch(() => {
29703
+ this.recordActivityFromListener(
29704
+ {
29705
+ type: "task:deleted",
29706
+ taskId: task.id,
29707
+ taskTitle: task.title,
29708
+ details: `Task ${task.id} deleted${task.title ? `: ${task.title}` : ""}`
29709
+ },
29710
+ "task:deleted"
29711
+ );
29712
+ });
29713
+ }
29714
+ recordActivityFromListener(entry, sourceEvent) {
29715
+ this.recordActivity(entry).catch((err) => {
29716
+ storeLog.warn("Activity logging listener failed", {
29717
+ sourceEvent,
29718
+ type: entry.type,
29719
+ taskId: entry.taskId,
29720
+ error: err instanceof Error ? err.message : String(err)
29552
29721
  });
29553
29722
  });
29554
29723
  }
@@ -30070,7 +30239,11 @@ ${recentText}` : void 0
30070
30239
  if (defaultOnSteps.length > 0) {
30071
30240
  resolvedWorkflowSteps = defaultOnSteps;
30072
30241
  }
30073
- } catch {
30242
+ } catch (err) {
30243
+ storeLog.warn("Failed to auto-apply default workflow steps during task creation", {
30244
+ error: err instanceof Error ? err.message : String(err),
30245
+ descriptionLength: input.description.length
30246
+ });
30074
30247
  }
30075
30248
  } else if (input.enabledWorkflowSteps.length === 0) {
30076
30249
  resolvedWorkflowSteps = void 0;
@@ -30088,13 +30261,26 @@ ${recentText}` : void 0
30088
30261
  }
30089
30262
  }
30090
30263
  } catch (err) {
30091
- const errorMsg = err instanceof Error ? err.message : String(err);
30092
30264
  const autoEnabled = options?.settings?.autoSummarizeTitles === true;
30093
- console.warn(
30094
- `[TaskStore] Title summarization failed for task ${id}: ${errorMsg} (desc length: ${input.description.length}, auto-summarize: ${autoEnabled})`
30265
+ const errorMessage = err instanceof Error ? err.message : String(err);
30266
+ storeLog.warn(
30267
+ `Title summarization failed for task ${id}: ${errorMessage} (desc length: ${input.description.length}, auto-summarize: ${autoEnabled})`,
30268
+ {
30269
+ taskId: id,
30270
+ descriptionLength: input.description.length,
30271
+ autoSummarizeEnabled: autoEnabled,
30272
+ error: errorMessage
30273
+ }
30095
30274
  );
30096
30275
  }
30097
- }).catch(() => {
30276
+ }).catch((err) => {
30277
+ const autoEnabled = options?.settings?.autoSummarizeTitles === true;
30278
+ storeLog.error("Unexpected title summarization promise-chain failure", {
30279
+ taskId: id,
30280
+ descriptionLength: input.description.length,
30281
+ autoSummarizeEnabled: autoEnabled,
30282
+ error: err instanceof Error ? err.message : String(err)
30283
+ });
30098
30284
  });
30099
30285
  }
30100
30286
  return task;
@@ -30361,26 +30547,46 @@ ${newTask.description}
30361
30547
  if (sanitizedTokens.length === 0) {
30362
30548
  return this.listTasks(options);
30363
30549
  }
30364
- const ftsQuery = sanitizedTokens.map((token) => {
30365
- if (/[":(){}*^+-]/.test(token)) {
30366
- return `"${token.replace(/"/g, '\\"')}"`;
30367
- }
30368
- return token;
30369
- }).join(" OR ");
30370
30550
  const limit = options?.limit ?? -1;
30371
30551
  const offset = options?.offset ?? 0;
30372
30552
  const offsetClause = offset > 0 ? ` OFFSET ${offset}` : "";
30373
30553
  const includeArchived = options?.includeArchived ?? true;
30374
- const whereClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
30375
- const selectClause = this.getTaskSelectClause(options?.slim ?? false, "t");
30376
- const rows = this.db.prepare(`
30377
- SELECT ${selectClause} FROM tasks t
30378
- JOIN tasks_fts fts ON t.rowid = fts.rowid
30379
- WHERE tasks_fts MATCH ?
30380
- ${whereClause}
30381
- ORDER BY rank
30382
- LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
30383
- `).all(ftsQuery);
30554
+ const slim = options?.slim ?? false;
30555
+ const selectClause = this.getTaskSelectClause(slim, "t");
30556
+ let rows;
30557
+ if (this.db.fts5Available) {
30558
+ const ftsQuery = sanitizedTokens.map((token) => {
30559
+ if (/[":(){}*^+-]/.test(token)) {
30560
+ return `"${token.replace(/"/g, '\\"')}"`;
30561
+ }
30562
+ return token;
30563
+ }).join(" OR ");
30564
+ const whereClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
30565
+ rows = this.db.prepare(`
30566
+ SELECT ${selectClause} FROM tasks t
30567
+ JOIN tasks_fts fts ON t.rowid = fts.rowid
30568
+ WHERE tasks_fts MATCH ?
30569
+ ${whereClause}
30570
+ ORDER BY rank
30571
+ LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
30572
+ `).all(ftsQuery);
30573
+ } else {
30574
+ const searchColumns = ["id", "title", "description", "comments"];
30575
+ const perTokenClause = `(${searchColumns.map((c) => `t."${c}" LIKE ? ESCAPE '\\'`).join(" OR ")})`;
30576
+ const whereTokens = sanitizedTokens.map(() => perTokenClause).join(" OR ");
30577
+ const params = [];
30578
+ for (const token of sanitizedTokens) {
30579
+ const pattern = `%${token.replace(/[\\%_]/g, "\\$&")}%`;
30580
+ for (let i = 0; i < searchColumns.length; i++) params.push(pattern);
30581
+ }
30582
+ const archivedClause = includeArchived ? "" : ` AND t."column" != 'archived'`;
30583
+ rows = this.db.prepare(`
30584
+ SELECT ${selectClause} FROM tasks t
30585
+ WHERE (${whereTokens})${archivedClause}
30586
+ ORDER BY t.createdAt ASC
30587
+ LIMIT ${limit >= 0 ? limit : -1}${offsetClause}
30588
+ `).all(...params);
30589
+ }
30384
30590
  const activeMatches = await Promise.all(rows.map(async (row) => {
30385
30591
  const task = this.rowToTask(row);
30386
30592
  if (task.steps.length > 0) {
@@ -30389,7 +30595,7 @@ ${newTask.description}
30389
30595
  const steps = await this.parseStepsFromPrompt(task.id);
30390
30596
  return steps.length > 0 ? { ...task, steps } : task;
30391
30597
  }));
30392
- const archiveMatches = includeArchived ? this.archiveDb.search(ftsQuery, limit >= 0 ? limit : 100).map((entry) => this.archiveEntryToTask(entry, options?.slim ?? false)) : [];
30598
+ const archiveMatches = includeArchived ? this.archiveDb.search(trimmedQuery, limit >= 0 ? limit : 100).map((entry) => this.archiveEntryToTask(entry, slim)) : [];
30393
30599
  const matches = [...activeMatches, ...archiveMatches];
30394
30600
  return limit >= 0 ? matches.slice(0, limit) : matches;
30395
30601
  }
@@ -31525,9 +31731,17 @@ ${task.description}
31525
31731
  try {
31526
31732
  this.watcher = watch(this.tasksDir, { recursive: true }, (_event, _filename) => {
31527
31733
  });
31528
- this.watcher.on("error", () => {
31734
+ this.watcher.on("error", (err) => {
31735
+ storeLog.warn("fs.watch emitted an error; polling will continue", {
31736
+ error: err instanceof Error ? err.message : String(err),
31737
+ tasksDir: this.tasksDir
31738
+ });
31739
+ });
31740
+ } catch (err) {
31741
+ storeLog.warn("fs.watch unavailable; falling back to polling-only updates", {
31742
+ error: err instanceof Error ? err.message : String(err),
31743
+ tasksDir: this.tasksDir
31529
31744
  });
31530
- } catch {
31531
31745
  }
31532
31746
  this.pollInterval = setInterval(() => {
31533
31747
  void this.checkForChanges();
@@ -31583,9 +31797,17 @@ ${task.description}
31583
31797
  }
31584
31798
  const elapsed = Date.now() - startTime;
31585
31799
  if (elapsed > 100) {
31586
- console.warn(`[TaskStore] checkForChanges took ${elapsed}ms \u2014 event loop may have been blocked`);
31800
+ storeLog.warn("checkForChanges took longer than expected", {
31801
+ elapsedMs: elapsed,
31802
+ thresholdMs: 100
31803
+ });
31587
31804
  }
31588
- } catch {
31805
+ } catch (err) {
31806
+ storeLog.warn("checkForChanges poll cycle failed", {
31807
+ lastKnownModified: this.lastKnownModified,
31808
+ lastPollTime: this.lastPollTime,
31809
+ error: err instanceof Error ? err.message : String(err)
31810
+ });
31589
31811
  } finally {
31590
31812
  this.pollingInProgress = false;
31591
31813
  }
@@ -32840,7 +33062,15 @@ ${notificationsSection}
32840
33062
  );
32841
33063
  this.db.bumpLastModified();
32842
33064
  } catch (err) {
32843
- console.error("Failed to record activity:", err);
33065
+ storeLog.error("Failed to record activity", {
33066
+ id: fullEntry.id,
33067
+ type: fullEntry.type,
33068
+ taskId: fullEntry.taskId,
33069
+ taskTitle: fullEntry.taskTitle,
33070
+ detailsLength: fullEntry.details.length,
33071
+ hasMetadata: fullEntry.metadata !== void 0,
33072
+ error: err instanceof Error ? err.message : String(err)
33073
+ });
32844
33074
  }
32845
33075
  return fullEntry;
32846
33076
  }
@@ -33822,27 +34052,6 @@ var init_routine_store = __esm({
33822
34052
  }
33823
34053
  });
33824
34054
 
33825
- // ../core/src/logger.ts
33826
- function createLogger(prefix) {
33827
- const tag = `[${prefix}]`;
33828
- return {
33829
- log(message, ...args) {
33830
- console.error(`${tag} ${message}`, ...args);
33831
- },
33832
- warn(message, ...args) {
33833
- console.warn(`${tag} ${message}`, ...args);
33834
- },
33835
- error(message, ...args) {
33836
- console.error(`${tag} ${message}`, ...args);
33837
- }
33838
- };
33839
- }
33840
- var init_logger = __esm({
33841
- "../core/src/logger.ts"() {
33842
- "use strict";
33843
- }
33844
- });
33845
-
33846
34055
  // ../core/src/plugin-loader.ts
33847
34056
  import { isAbsolute as isAbsolute5, resolve as resolve8 } from "node:path";
33848
34057
  import { EventEmitter as EventEmitter13 } from "node:events";
@@ -37141,9 +37350,9 @@ var require_pump = __commonJS({
37141
37350
  "use strict";
37142
37351
  var once3 = require_once();
37143
37352
  var eos = require_end_of_stream();
37144
- var fs;
37353
+ var fs2;
37145
37354
  try {
37146
- fs = __require("fs");
37355
+ fs2 = __require("fs");
37147
37356
  } catch (e) {
37148
37357
  }
37149
37358
  var noop = function() {
@@ -37154,8 +37363,8 @@ var require_pump = __commonJS({
37154
37363
  };
37155
37364
  var isFS = function(stream) {
37156
37365
  if (!ancient) return false;
37157
- if (!fs) return false;
37158
- return (stream instanceof (fs.ReadStream || noop) || stream instanceof (fs.WriteStream || noop)) && isFn(stream.close);
37366
+ if (!fs2) return false;
37367
+ return (stream instanceof (fs2.ReadStream || noop) || stream instanceof (fs2.WriteStream || noop)) && isFn(stream.close);
37159
37368
  };
37160
37369
  var isRequest = function(stream) {
37161
37370
  return stream.setHeader && isFn(stream.abort);
@@ -37369,7 +37578,7 @@ var require_pend = __commonJS({
37369
37578
  var require_fd_slicer = __commonJS({
37370
37579
  "../../node_modules/.pnpm/fd-slicer@1.1.0/node_modules/fd-slicer/index.js"(exports) {
37371
37580
  "use strict";
37372
- var fs = __require("fs");
37581
+ var fs2 = __require("fs");
37373
37582
  var util = __require("util");
37374
37583
  var stream = __require("stream");
37375
37584
  var Readable2 = stream.Readable;
@@ -37394,7 +37603,7 @@ var require_fd_slicer = __commonJS({
37394
37603
  FdSlicer.prototype.read = function(buffer, offset, length, position, callback) {
37395
37604
  var self2 = this;
37396
37605
  self2.pend.go(function(cb) {
37397
- fs.read(self2.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
37606
+ fs2.read(self2.fd, buffer, offset, length, position, function(err, bytesRead, buffer2) {
37398
37607
  cb();
37399
37608
  callback(err, bytesRead, buffer2);
37400
37609
  });
@@ -37403,7 +37612,7 @@ var require_fd_slicer = __commonJS({
37403
37612
  FdSlicer.prototype.write = function(buffer, offset, length, position, callback) {
37404
37613
  var self2 = this;
37405
37614
  self2.pend.go(function(cb) {
37406
- fs.write(self2.fd, buffer, offset, length, position, function(err, written, buffer2) {
37615
+ fs2.write(self2.fd, buffer, offset, length, position, function(err, written, buffer2) {
37407
37616
  cb();
37408
37617
  callback(err, written, buffer2);
37409
37618
  });
@@ -37424,7 +37633,7 @@ var require_fd_slicer = __commonJS({
37424
37633
  if (self2.refCount > 0) return;
37425
37634
  if (self2.refCount < 0) throw new Error("invalid unref");
37426
37635
  if (self2.autoClose) {
37427
- fs.close(self2.fd, onCloseDone);
37636
+ fs2.close(self2.fd, onCloseDone);
37428
37637
  }
37429
37638
  function onCloseDone(err) {
37430
37639
  if (err) {
@@ -37461,7 +37670,7 @@ var require_fd_slicer = __commonJS({
37461
37670
  self2.context.pend.go(function(cb) {
37462
37671
  if (self2.destroyed) return cb();
37463
37672
  var buffer = new Buffer(toRead);
37464
- fs.read(self2.context.fd, buffer, 0, toRead, self2.pos, function(err, bytesRead) {
37673
+ fs2.read(self2.context.fd, buffer, 0, toRead, self2.pos, function(err, bytesRead) {
37465
37674
  if (err) {
37466
37675
  self2.destroy(err);
37467
37676
  } else if (bytesRead === 0) {
@@ -37508,7 +37717,7 @@ var require_fd_slicer = __commonJS({
37508
37717
  }
37509
37718
  self2.context.pend.go(function(cb) {
37510
37719
  if (self2.destroyed) return cb();
37511
- fs.write(self2.context.fd, buffer, 0, buffer.length, self2.pos, function(err2, bytes) {
37720
+ fs2.write(self2.context.fd, buffer, 0, buffer.length, self2.pos, function(err2, bytes) {
37512
37721
  if (err2) {
37513
37722
  self2.destroy();
37514
37723
  cb();
@@ -37937,7 +38146,7 @@ var require_buffer_crc32 = __commonJS({
37937
38146
  var require_yauzl = __commonJS({
37938
38147
  "../../node_modules/.pnpm/yauzl@2.10.0/node_modules/yauzl/index.js"(exports) {
37939
38148
  "use strict";
37940
- var fs = __require("fs");
38149
+ var fs2 = __require("fs");
37941
38150
  var zlib = __require("zlib");
37942
38151
  var fd_slicer = require_fd_slicer();
37943
38152
  var crc32 = require_buffer_crc32();
@@ -37967,10 +38176,10 @@ var require_yauzl = __commonJS({
37967
38176
  if (options.validateEntrySizes == null) options.validateEntrySizes = true;
37968
38177
  if (options.strictFileNames == null) options.strictFileNames = false;
37969
38178
  if (callback == null) callback = defaultCallback;
37970
- fs.open(path4, "r", function(err, fd) {
38179
+ fs2.open(path4, "r", function(err, fd) {
37971
38180
  if (err) return callback(err);
37972
38181
  fromFd(fd, options, function(err2, zipfile) {
37973
- if (err2) fs.close(fd, defaultCallback);
38182
+ if (err2) fs2.close(fd, defaultCallback);
37974
38183
  callback(err2, zipfile);
37975
38184
  });
37976
38185
  });
@@ -37987,7 +38196,7 @@ var require_yauzl = __commonJS({
37987
38196
  if (options.validateEntrySizes == null) options.validateEntrySizes = true;
37988
38197
  if (options.strictFileNames == null) options.strictFileNames = false;
37989
38198
  if (callback == null) callback = defaultCallback;
37990
- fs.fstat(fd, function(err, stats) {
38199
+ fs2.fstat(fd, function(err, stats) {
37991
38200
  if (err) return callback(err);
37992
38201
  var reader = fd_slicer.createFromFd(fd, { autoClose: true });
37993
38202
  fromRandomAccessReader(reader, stats.size, options, callback);
@@ -38568,7 +38777,7 @@ var require_extract_zip = __commonJS({
38568
38777
  "../../node_modules/.pnpm/extract-zip@2.0.1/node_modules/extract-zip/index.js"(exports, module) {
38569
38778
  "use strict";
38570
38779
  var debug = require_src()("extract-zip");
38571
- var { createWriteStream: createWriteStream2, promises: fs } = __require("fs");
38780
+ var { createWriteStream: createWriteStream2, promises: fs2 } = __require("fs");
38572
38781
  var getStream = require_get_stream();
38573
38782
  var path4 = __require("path");
38574
38783
  var { promisify: promisify14 } = __require("util");
@@ -38609,8 +38818,8 @@ var require_extract_zip = __commonJS({
38609
38818
  }
38610
38819
  const destDir = path4.dirname(path4.join(this.opts.dir, entry.fileName));
38611
38820
  try {
38612
- await fs.mkdir(destDir, { recursive: true });
38613
- const canonicalDestDir = await fs.realpath(destDir);
38821
+ await fs2.mkdir(destDir, { recursive: true });
38822
+ const canonicalDestDir = await fs2.realpath(destDir);
38614
38823
  const relativeDestDir = path4.relative(this.opts.dir, canonicalDestDir);
38615
38824
  if (relativeDestDir.split(path4.sep).includes("..")) {
38616
38825
  throw new Error(`Out of bound path "${canonicalDestDir}" found while processing file ${entry.fileName}`);
@@ -38654,14 +38863,14 @@ var require_extract_zip = __commonJS({
38654
38863
  mkdirOptions.mode = procMode;
38655
38864
  }
38656
38865
  debug("mkdir", { dir: destDir, ...mkdirOptions });
38657
- await fs.mkdir(destDir, mkdirOptions);
38866
+ await fs2.mkdir(destDir, mkdirOptions);
38658
38867
  if (isDir) return;
38659
38868
  debug("opening read stream", dest);
38660
38869
  const readStream = await promisify14(this.zipfile.openReadStream.bind(this.zipfile))(entry);
38661
38870
  if (symlink) {
38662
38871
  const link = await getStream(readStream);
38663
38872
  debug("creating symlink", link, dest);
38664
- await fs.symlink(link, dest);
38873
+ await fs2.symlink(link, dest);
38665
38874
  } else {
38666
38875
  await pipeline(readStream, createWriteStream2(dest, { mode: procMode }));
38667
38876
  }
@@ -38693,8 +38902,8 @@ var require_extract_zip = __commonJS({
38693
38902
  if (!path4.isAbsolute(opts.dir)) {
38694
38903
  throw new Error("Target directory is expected to be absolute");
38695
38904
  }
38696
- await fs.mkdir(opts.dir, { recursive: true });
38697
- opts.dir = await fs.realpath(opts.dir);
38905
+ await fs2.mkdir(opts.dir, { recursive: true });
38906
+ opts.dir = await fs2.realpath(opts.dir);
38698
38907
  return new Extractor(zipPath, opts).extract();
38699
38908
  };
38700
38909
  }
@@ -45553,14 +45762,14 @@ var require_parser = __commonJS({
45553
45762
  case "scalar":
45554
45763
  case "single-quoted-scalar":
45555
45764
  case "double-quoted-scalar": {
45556
- const fs = this.flowScalar(this.type);
45765
+ const fs2 = this.flowScalar(this.type);
45557
45766
  if (atNextItem || it.value) {
45558
- map2.items.push({ start, key: fs, sep: [] });
45767
+ map2.items.push({ start, key: fs2, sep: [] });
45559
45768
  this.onKeyLine = true;
45560
45769
  } else if (it.sep) {
45561
- this.stack.push(fs);
45770
+ this.stack.push(fs2);
45562
45771
  } else {
45563
- Object.assign(it, { key: fs, sep: [] });
45772
+ Object.assign(it, { key: fs2, sep: [] });
45564
45773
  this.onKeyLine = true;
45565
45774
  }
45566
45775
  return;
@@ -45688,13 +45897,13 @@ var require_parser = __commonJS({
45688
45897
  case "scalar":
45689
45898
  case "single-quoted-scalar":
45690
45899
  case "double-quoted-scalar": {
45691
- const fs = this.flowScalar(this.type);
45900
+ const fs2 = this.flowScalar(this.type);
45692
45901
  if (!it || it.value)
45693
- fc.items.push({ start: [], key: fs, sep: [] });
45902
+ fc.items.push({ start: [], key: fs2, sep: [] });
45694
45903
  else if (it.sep)
45695
- this.stack.push(fs);
45904
+ this.stack.push(fs2);
45696
45905
  else
45697
- Object.assign(it, { key: fs, sep: [] });
45906
+ Object.assign(it, { key: fs2, sep: [] });
45698
45907
  return;
45699
45908
  }
45700
45909
  case "flow-map-end":
@@ -49975,7 +50184,7 @@ var require_windowsPtyAgent = __commonJS({
49975
50184
  "use strict";
49976
50185
  Object.defineProperty(exports, "__esModule", { value: true });
49977
50186
  exports.argsToCommandLine = exports.WindowsPtyAgent = void 0;
49978
- var fs = __require("fs");
50187
+ var fs2 = __require("fs");
49979
50188
  var os3 = __require("os");
49980
50189
  var path4 = __require("path");
49981
50190
  var child_process_1 = __require("child_process");
@@ -50034,7 +50243,7 @@ var require_windowsPtyAgent = __commonJS({
50034
50243
  this._outSocket.on("connect", function() {
50035
50244
  _this._outSocket.emit("ready_datapipe");
50036
50245
  });
50037
- var inSocketFD = fs.openSync(term.conin, "w");
50246
+ var inSocketFD = fs2.openSync(term.conin, "w");
50038
50247
  this._inSocket = new net_1.Socket({
50039
50248
  fd: inSocketFD,
50040
50249
  readable: false,
@@ -50468,7 +50677,7 @@ var require_unixTerminal = __commonJS({
50468
50677
  })();
50469
50678
  Object.defineProperty(exports, "__esModule", { value: true });
50470
50679
  exports.UnixTerminal = void 0;
50471
- var fs = __require("fs");
50680
+ var fs2 = __require("fs");
50472
50681
  var path4 = __require("path");
50473
50682
  var tty = __require("tty");
50474
50683
  var terminal_1 = require_terminal();
@@ -50731,7 +50940,7 @@ var require_unixTerminal = __commonJS({
50731
50940
  return;
50732
50941
  }
50733
50942
  var task = this._writeQueue[0];
50734
- fs.write(this._fd, task.buffer, task.offset, function(err, written) {
50943
+ fs2.write(this._fd, task.buffer, task.offset, function(err, written) {
50735
50944
  if (err) {
50736
50945
  if ("code" in err && err.code === "EAGAIN") {
50737
50946
  _this._writeImmediate = setImmediate(function() {
@@ -50793,7 +51002,8 @@ var require_lib = __commonJS({
50793
51002
  import { EventEmitter as EventEmitter17 } from "events";
50794
51003
  import * as os2 from "os";
50795
51004
  import * as path from "path";
50796
- import { existsSync as existsSync17, chmodSync } from "node:fs";
51005
+ import * as fs from "node:fs";
51006
+ import { createRequire } from "node:module";
50797
51007
  import { join as join22, dirname as dirname7 } from "node:path";
50798
51008
  function getNativePrebuildName() {
50799
51009
  const platform3 = process.platform === "darwin" ? "darwin" : process.platform === "linux" ? "linux" : process.platform === "win32" ? "win32" : "unknown";
@@ -50802,9 +51012,9 @@ function getNativePrebuildName() {
50802
51012
  }
50803
51013
  function findInstalledNodePtyNativeDir() {
50804
51014
  try {
50805
- const packageJsonPath = __require.resolve("node-pty/package.json");
51015
+ const packageJsonPath = require2.resolve("node-pty/package.json");
50806
51016
  const nativeDir = join22(dirname7(packageJsonPath), "prebuilds", getNativePrebuildName());
50807
- return existsSync17(join22(nativeDir, "pty.node")) ? nativeDir : null;
51017
+ return fs.existsSync(join22(nativeDir, "pty.node")) ? nativeDir : null;
50808
51018
  } catch {
50809
51019
  return null;
50810
51020
  }
@@ -50830,12 +51040,11 @@ function ensureNodePtyNativePermissions() {
50830
51040
  const helperPath = join22(nativeDir, "spawn-helper");
50831
51041
  const nativeModulePath = join22(nativeDir, "pty.node");
50832
51042
  try {
50833
- if (existsSync17(helperPath)) {
50834
- chmodSync(helperPath, 493);
50835
- }
50836
- if (existsSync17(nativeModulePath)) {
50837
- chmodSync(nativeModulePath, 493);
50838
- }
51043
+ fs.chmodSync(helperPath, 493);
51044
+ } catch {
51045
+ }
51046
+ try {
51047
+ fs.chmodSync(nativeModulePath, 493);
50839
51048
  } catch (err) {
50840
51049
  console.warn("[terminal] Failed to repair node-pty native permissions:", {
50841
51050
  nativeDir,
@@ -50848,18 +51057,19 @@ function findStagedNativeDir() {
50848
51057
  const prebuildName = getNativePrebuildName();
50849
51058
  if (process.env.FUSION_RUNTIME_DIR) {
50850
51059
  const envPath = join22(process.env.FUSION_RUNTIME_DIR, prebuildName);
50851
- if (existsSync17(join22(envPath, "pty.node"))) {
51060
+ if (fs.existsSync(join22(envPath, "pty.node"))) {
50852
51061
  return envPath;
50853
51062
  }
50854
51063
  }
50855
51064
  const execDir = dirname7(process.execPath);
50856
51065
  const nextToBinary = join22(execDir, "runtime", prebuildName);
50857
- if (existsSync17(join22(nextToBinary, "pty.node"))) {
51066
+ if (fs.existsSync(join22(nextToBinary, "pty.node"))) {
50858
51067
  return nextToBinary;
50859
51068
  }
50860
51069
  return null;
50861
51070
  }
50862
51071
  async function loadPtyModule() {
51072
+ ensureNodePtyNativePermissions();
50863
51073
  if (ptyModule) {
50864
51074
  return ptyModule;
50865
51075
  }
@@ -50874,7 +51084,7 @@ async function loadPtyModule() {
50874
51084
  }
50875
51085
  process.env.FUSION_NATIVE_ASSETS_PATH = nativeDir;
50876
51086
  const nativePath = join22(nativeDir, "pty.node");
50877
- if (existsSync17(nativePath)) {
51087
+ if (fs.existsSync(nativePath)) {
50878
51088
  try {
50879
51089
  const nativeModule = { exports: {} };
50880
51090
  process.dlopen(nativeModule, nativePath);
@@ -50886,7 +51096,6 @@ async function loadPtyModule() {
50886
51096
  }
50887
51097
  }
50888
51098
  try {
50889
- ensureNodePtyNativePermissions();
50890
51099
  const mod = await Promise.resolve().then(() => __toESM(require_lib(), 1));
50891
51100
  ptyModule = mod;
50892
51101
  return ptyModule;
@@ -50912,13 +51121,14 @@ function getTerminalService(projectRoot, maxSessions) {
50912
51121
  terminalServices.set(resolvedRoot, service);
50913
51122
  return service;
50914
51123
  }
50915
- var isBunBinary, ptyModule, ptyLoadError, MAX_SCROLLBACK_SIZE, MIN_MAX_SESSIONS, MAX_MAX_SESSIONS, DEFAULT_MAX_SESSIONS, OUTPUT_THROTTLE_MS, OUTPUT_BATCH_SIZE, STALE_SESSION_THRESHOLD_MS, SESSION_ID_PATTERN, ALLOWED_SHELL_PATHS, STRIP_ENV_VARS, TerminalService, terminalServices;
51124
+ var isBunBinary, ptyModule, ptyLoadError, require2, MAX_SCROLLBACK_SIZE, MIN_MAX_SESSIONS, MAX_MAX_SESSIONS, DEFAULT_MAX_SESSIONS, OUTPUT_THROTTLE_MS, OUTPUT_BATCH_SIZE, STALE_SESSION_THRESHOLD_MS, SESSION_ID_PATTERN, ALLOWED_SHELL_PATHS, STRIP_ENV_VARS, TerminalService, terminalServices;
50916
51125
  var init_terminal_service = __esm({
50917
51126
  "../dashboard/src/terminal-service.ts"() {
50918
51127
  "use strict";
50919
51128
  isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
50920
51129
  ptyModule = null;
50921
51130
  ptyLoadError = null;
51131
+ require2 = createRequire(import.meta.url);
50922
51132
  MAX_SCROLLBACK_SIZE = 5e4;
50923
51133
  MIN_MAX_SESSIONS = 1;
50924
51134
  MAX_MAX_SESSIONS = 100;
@@ -51008,13 +51218,13 @@ var init_terminal_service = __esm({
51008
51218
  const normalizedUserShell = this.isWindows ? userShell.toLowerCase() : userShell;
51009
51219
  for (const allowed of allowedShells) {
51010
51220
  const normalizedAllowed = this.isWindows ? allowed.toLowerCase() : allowed;
51011
- if (normalizedAllowed === normalizedUserShell && existsSync17(allowed)) {
51221
+ if (normalizedAllowed === normalizedUserShell && fs.existsSync(allowed)) {
51012
51222
  return { shell: allowed, args: getShellArgs(allowed) };
51013
51223
  }
51014
51224
  }
51015
51225
  }
51016
51226
  for (const shell of allowedShells) {
51017
- if (existsSync17(shell)) {
51227
+ if (fs.existsSync(shell)) {
51018
51228
  return { shell, args: getShellArgs(shell) };
51019
51229
  }
51020
51230
  }
@@ -51035,7 +51245,7 @@ var init_terminal_service = __esm({
51035
51245
  detectedShell,
51036
51246
  detectedArgs,
51037
51247
  envShell: process.env.SHELL ?? null,
51038
- allowedShells: this.getAllowedShells().filter((shellPath) => existsSync17(shellPath))
51248
+ allowedShells: this.getAllowedShells().filter((shellPath) => fs.existsSync(shellPath))
51039
51249
  };
51040
51250
  }
51041
51251
  /**
@@ -51056,7 +51266,7 @@ var init_terminal_service = __esm({
51056
51266
  return this.projectRoot;
51057
51267
  }
51058
51268
  try {
51059
- const stat11 = await import("node:fs/promises").then((fs) => fs.stat(cwd));
51269
+ const stat11 = await import("node:fs/promises").then((fs2) => fs2.stat(cwd));
51060
51270
  if (stat11.isDirectory()) {
51061
51271
  return cwd;
51062
51272
  }
@@ -51219,7 +51429,7 @@ var init_terminal_service = __esm({
51219
51429
  addSpawnAttempt(shell, [], "retry-without-login");
51220
51430
  }
51221
51431
  for (const allowedShell of this.getAllowedShells()) {
51222
- if (allowedShell === shell || !existsSync17(allowedShell)) continue;
51432
+ if (allowedShell === shell || !fs.existsSync(allowedShell)) continue;
51223
51433
  const shellName = path.basename(allowedShell).toLowerCase().replace(".exe", "");
51224
51434
  const fallbackArgs = shellName === "bash" || shellName === "zsh" ? [] : [];
51225
51435
  addSpawnAttempt(allowedShell, fallbackArgs, "allowed-fallback");
@@ -59047,13 +59257,13 @@ function createLogger2(prefix) {
59047
59257
  const tag = `[${prefix}]`;
59048
59258
  return {
59049
59259
  log(message, ...args) {
59050
- console.error(`${tag} ${message}`, ...args);
59260
+ globalThis.console.error(`${tag} ${message}`, ...args);
59051
59261
  },
59052
59262
  warn(message, ...args) {
59053
- console.warn(`${tag} ${message}`, ...args);
59263
+ globalThis.console.warn(`${tag} ${message}`, ...args);
59054
59264
  },
59055
59265
  error(message, ...args) {
59056
- console.error(`${tag} ${message}`, ...args);
59266
+ globalThis.console.error(`${tag} ${message}`, ...args);
59057
59267
  }
59058
59268
  };
59059
59269
  }
@@ -60371,7 +60581,6 @@ function createSkillsOverrideFromSelection(selection, options = {}) {
60371
60581
  }
60372
60582
  }
60373
60583
  if (newDiagnostics.length > 0) {
60374
- const _purpose = sessionPurpose ? `[${sessionPurpose}]` : "skills";
60375
60584
  for (const diag of newDiagnostics) {
60376
60585
  piLog.warn(`[skills] ${diag.type}: ${diag.message}`);
60377
60586
  }
@@ -60441,7 +60650,7 @@ import { join as join27 } from "node:path";
60441
60650
  import { AuthStorage } from "@mariozechner/pi-coding-agent";
60442
60651
  import { getOAuthProvider } from "@mariozechner/pi-ai/oauth";
60443
60652
  function getHomeDir2() {
60444
- return process.env.HOME || process.env.USERPROFILE || homedir6();
60653
+ return globalThis.process.env.HOME || globalThis.process.env.USERPROFILE || homedir6();
60445
60654
  }
60446
60655
  function getFusionAuthPath2(home = getHomeDir2()) {
60447
60656
  return join27(home, ".fusion", "agent", "auth.json");
@@ -60487,7 +60696,7 @@ function readLegacyCredentials(authPaths = getLegacyAuthPaths()) {
60487
60696
  function resolveStoredApiKey(key) {
60488
60697
  if (!key)
60489
60698
  return void 0;
60490
- return process.env[key] ?? key;
60699
+ return globalThis.process.env[key] ?? key;
60491
60700
  }
60492
60701
  function resolveOAuthApiKey(providerId, credential) {
60493
60702
  if (credential.type !== "oauth" || typeof credential.access !== "string" || typeof credential.refresh !== "string" || typeof credential.expires !== "number" || Date.now() >= credential.expires) {
@@ -60790,8 +60999,8 @@ function createReadOnlyPiSettingsView(cwd, agentDir) {
60790
60999
  const fusionProjectSettings = readJsonObject2(join28(projectRoot, ".fusion", "settings.json"));
60791
61000
  const mergedSettings = { ...globalSettings, ...fusionProjectSettings };
60792
61001
  return {
60793
- getGlobalSettings: () => structuredClone(globalSettings),
60794
- getProjectSettings: () => structuredClone(fusionProjectSettings),
61002
+ getGlobalSettings: () => globalThis.structuredClone(globalSettings),
61003
+ getProjectSettings: () => globalThis.structuredClone(fusionProjectSettings),
60795
61004
  getNpmCommand: () => Array.isArray(mergedSettings.npmCommand) ? [...mergedSettings.npmCommand] : void 0
60796
61005
  };
60797
61006
  }
@@ -60910,9 +61119,7 @@ function wrapToolsWithBoundary(tools, worktreePath, projectRoot) {
60910
61119
  return {
60911
61120
  ...tool,
60912
61121
  execute: async (...args) => {
60913
- const _toolCallId = args[0];
60914
61122
  const params = args[1];
60915
- const _signal = args[2];
60916
61123
  const pathArg = params.path;
60917
61124
  if (pathArg && !isWorktreeAllowedPath(worktreePath, projectRoot, pathArg)) {
60918
61125
  const relToProject = relative4(projectRoot, pathArg);
@@ -65286,7 +65493,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
65286
65493
  } catch (error) {
65287
65494
  if (error.name === "VerificationError") {
65288
65495
  const verificationErr = error;
65289
- const maxFixRetries = Math.min(settings.verificationFixRetries ?? 1, 3);
65496
+ const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
65290
65497
  if (maxFixRetries > 0 && (verificationErr.verificationResult.testResult || verificationErr.verificationResult.buildResult)) {
65291
65498
  mergerLog.log(`${taskId}: deterministic verification failed \u2014 attempting in-merge fix (up to ${maxFixRetries} attempts)`);
65292
65499
  await store.logEntry(taskId, `Verification failed during merge \u2014 attempting in-merge fix (up to ${maxFixRetries} attempts)`);
@@ -65331,7 +65538,7 @@ async function aiMergeTask(store, rootDir, taskId, options = {}) {
65331
65538
  throw error;
65332
65539
  }
65333
65540
  if (error.message?.includes("Build verification failed")) {
65334
- const maxFixRetries = Math.min(settings.verificationFixRetries ?? 1, 3);
65541
+ const maxFixRetries = Math.min(settings.verificationFixRetries ?? 3, 3);
65335
65542
  if (maxFixRetries > 0 && (effectiveTestCommand || effectiveBuildCommand)) {
65336
65543
  mergerLog.log(`${taskId}: build verification failed \u2014 attempting in-merge fix`);
65337
65544
  await store.logEntry(taskId, `Build verification failed during merge \u2014 attempting in-merge fix`);
@@ -67130,6 +67337,36 @@ function buildReducedStepPrompt(taskDetail, stepIndex) {
67130
67337
  ];
67131
67338
  return parts.join("\n").replace(/\n{3,}/g, "\n\n");
67132
67339
  }
67340
+ function resolveExecutorModelPair(taskModelProvider, taskModelId, settings) {
67341
+ if (taskModelProvider && taskModelId) {
67342
+ return { provider: taskModelProvider, modelId: taskModelId };
67343
+ }
67344
+ if (settings?.executionProvider && settings?.executionModelId) {
67345
+ return {
67346
+ provider: settings.executionProvider,
67347
+ modelId: settings.executionModelId
67348
+ };
67349
+ }
67350
+ if (settings?.executionGlobalProvider && settings?.executionGlobalModelId) {
67351
+ return {
67352
+ provider: settings.executionGlobalProvider,
67353
+ modelId: settings.executionGlobalModelId
67354
+ };
67355
+ }
67356
+ if (settings?.defaultProviderOverride && settings?.defaultModelIdOverride) {
67357
+ return {
67358
+ provider: settings.defaultProviderOverride,
67359
+ modelId: settings.defaultModelIdOverride
67360
+ };
67361
+ }
67362
+ if (settings?.defaultProvider && settings?.defaultModelId) {
67363
+ return {
67364
+ provider: settings.defaultProvider,
67365
+ modelId: settings.defaultModelId
67366
+ };
67367
+ }
67368
+ return { provider: void 0, modelId: void 0 };
67369
+ }
67133
67370
  function sleep2(ms) {
67134
67371
  return new Promise((resolve29) => setTimeout(resolve29, ms));
67135
67372
  }
@@ -67322,8 +67559,11 @@ var init_step_session_executor = __esm({
67322
67559
  createSendMessageTool(this.options.messageStore, taskDetail.assignedAgentId),
67323
67560
  createReadMessagesTool(this.options.messageStore, taskDetail.assignedAgentId)
67324
67561
  ] : [];
67325
- const executorProvider = taskDetail.modelProvider && taskDetail.modelId ? taskDetail.modelProvider : settings.executionProvider && settings.executionModelId ? settings.executionProvider : settings.executionGlobalProvider && settings.executionGlobalModelId ? settings.executionGlobalProvider : settings.defaultProvider;
67326
- const executorModelId = taskDetail.modelProvider && taskDetail.modelId ? taskDetail.modelId : settings.executionProvider && settings.executionModelId ? settings.executionModelId : settings.executionGlobalProvider && settings.executionGlobalModelId ? settings.executionGlobalModelId : settings.defaultModelId;
67562
+ const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair(
67563
+ taskDetail.modelProvider,
67564
+ taskDetail.modelId,
67565
+ settings
67566
+ );
67327
67567
  const createResult = await createFnAgent5({
67328
67568
  cwd: worktreePath,
67329
67569
  systemPrompt: `You are an AI agent executing step ${stepIndex} of task ${taskDetail.id}. Follow instructions precisely.`,
@@ -67757,6 +67997,36 @@ function getExecutorSystemPrompt(settings) {
67757
67997
  const customPrompt = resolveAgentPrompt("executor", settings.agentPrompts);
67758
67998
  return customPrompt || EXECUTOR_SYSTEM_PROMPT;
67759
67999
  }
68000
+ function resolveExecutorModelPair2(taskModelProvider, taskModelId, settings) {
68001
+ if (taskModelProvider && taskModelId) {
68002
+ return { provider: taskModelProvider, modelId: taskModelId };
68003
+ }
68004
+ if (settings?.executionProvider && settings?.executionModelId) {
68005
+ return {
68006
+ provider: settings.executionProvider,
68007
+ modelId: settings.executionModelId
68008
+ };
68009
+ }
68010
+ if (settings?.executionGlobalProvider && settings?.executionGlobalModelId) {
68011
+ return {
68012
+ provider: settings.executionGlobalProvider,
68013
+ modelId: settings.executionGlobalModelId
68014
+ };
68015
+ }
68016
+ if (settings?.defaultProviderOverride && settings?.defaultModelIdOverride) {
68017
+ return {
68018
+ provider: settings.defaultProviderOverride,
68019
+ modelId: settings.defaultModelIdOverride
68020
+ };
68021
+ }
68022
+ if (settings?.defaultProvider && settings?.defaultModelId) {
68023
+ return {
68024
+ provider: settings.defaultProvider,
68025
+ modelId: settings.defaultModelId
68026
+ };
68027
+ }
68028
+ return { provider: void 0, modelId: void 0 };
68029
+ }
67760
68030
  function formatTimestamp2(iso) {
67761
68031
  const date = new Date(iso);
67762
68032
  const now = /* @__PURE__ */ new Date();
@@ -67913,7 +68183,7 @@ function detectReviewHandoffIntent(commentText) {
67913
68183
  ];
67914
68184
  return handoffPhrases.some((phrase) => text.includes(phrase));
67915
68185
  }
67916
- var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
68186
+ var execAsync5, STEP_STATUSES, MAX_WORKFLOW_STEP_RETRIES, MAX_TASK_DONE_SESSION_RETRIES, MAX_TASK_DONE_REQUEUE_RETRIES, WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2, NonRetryableWorktreeError, taskUpdateParams, taskAddDepParams, spawnAgentParams, reviewStepParams, EXECUTOR_SYSTEM_PROMPT, TaskExecutor;
67917
68187
  var init_executor = __esm({
67918
68188
  "../engine/src/executor.ts"() {
67919
68189
  "use strict";
@@ -67946,6 +68216,8 @@ var init_executor = __esm({
67946
68216
  execAsync5 = promisify6(exec5);
67947
68217
  STEP_STATUSES = ["pending", "in-progress", "done", "skipped"];
67948
68218
  MAX_WORKFLOW_STEP_RETRIES = 3;
68219
+ MAX_TASK_DONE_SESSION_RETRIES = 3;
68220
+ MAX_TASK_DONE_REQUEUE_RETRIES = 3;
67949
68221
  WORKFLOW_SCRIPT_OUTPUT_MAX_CHARS2 = 4e3;
67950
68222
  NonRetryableWorktreeError = class extends Error {
67951
68223
  };
@@ -68233,8 +68505,11 @@ Lint, tests, and typecheck are also hard quality gates:
68233
68505
  activeEntry.lastModelProvider = task.modelProvider;
68234
68506
  activeEntry.lastModelId = task.modelId;
68235
68507
  const settings = await this.store.getSettings();
68236
- const newProvider = task.modelProvider && task.modelId ? task.modelProvider : settings?.executionProvider && settings?.executionModelId ? settings.executionProvider : settings?.executionGlobalProvider && settings?.executionGlobalModelId ? settings.executionGlobalProvider : settings?.defaultProvider;
68237
- const newModelId = task.modelProvider && task.modelId ? task.modelId : settings?.executionProvider && settings?.executionModelId ? settings.executionModelId : settings?.executionGlobalProvider && settings?.executionGlobalModelId ? settings.executionGlobalModelId : settings?.defaultModelId;
68508
+ const { provider: newProvider, modelId: newModelId } = resolveExecutorModelPair2(
68509
+ task.modelProvider,
68510
+ task.modelId,
68511
+ settings
68512
+ );
68238
68513
  if (newProvider && newModelId) {
68239
68514
  try {
68240
68515
  const model = this.modelRegistry.find(newProvider, newModelId);
@@ -69141,8 +69416,11 @@ Lint, tests, and typecheck are also hard quality gates:
69141
69416
  }
69142
69417
  });
69143
69418
  const agentWork = async () => {
69144
- const executorProvider = detail.modelProvider && detail.modelId ? detail.modelProvider : settings.executionProvider && settings.executionModelId ? settings.executionProvider : settings.executionGlobalProvider && settings.executionGlobalModelId ? settings.executionGlobalProvider : settings.defaultProvider;
69145
- const executorModelId = detail.modelProvider && detail.modelId ? detail.modelId : settings.executionProvider && settings.executionModelId ? settings.executionModelId : settings.executionGlobalProvider && settings.executionGlobalModelId ? settings.executionGlobalModelId : settings.defaultModelId;
69419
+ const { provider: executorProvider, modelId: executorModelId } = resolveExecutorModelPair2(
69420
+ detail.modelProvider,
69421
+ detail.modelId,
69422
+ settings
69423
+ );
69146
69424
  const executorFallbackProvider = settings.fallbackProvider;
69147
69425
  const executorFallbackModelId = settings.fallbackModelId;
69148
69426
  const executorThinkingLevel = detail.thinkingLevel ?? settings.defaultThinkingLevel;
@@ -69324,63 +69602,74 @@ Lint, tests, and typecheck are also hard quality gates:
69324
69602
  executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
69325
69603
  this.options.onComplete?.(task);
69326
69604
  } else {
69327
- executorLog.log(`\u26A0 ${task.id} finished without task_done \u2014 retrying with new session`);
69328
- await this.store.logEntry(task.id, "Agent finished without calling task_done \u2014 retrying with new session", void 0, this.currentRunContext);
69329
- this.activeSessions.delete(task.id);
69330
- session.dispose();
69331
- const { session: retrySession2, sessionFile: retrySessionFile } = await createResolvedAgentSession({
69332
- sessionPurpose: "executor",
69333
- pluginRunner: this.options.pluginRunner,
69334
- cwd: worktreePath,
69335
- systemPrompt: executorSystemPrompt,
69336
- tools: "coding",
69337
- customTools,
69338
- onText: agentLogger.onText,
69339
- onThinking: agentLogger.onThinking,
69340
- onToolStart: agentLogger.onToolStart,
69341
- onToolEnd: agentLogger.onToolEnd,
69342
- defaultProvider: executorProvider,
69343
- defaultModelId: executorModelId,
69344
- fallbackProvider: executorFallbackProvider,
69345
- fallbackModelId: executorFallbackModelId,
69346
- defaultThinkingLevel: executorThinkingLevel,
69347
- sessionManager: SessionManager2.create(worktreePath),
69348
- // Skill selection: use assigned agent skills if available, otherwise role fallback
69349
- ...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
69350
- });
69351
- if (retrySessionFile) {
69352
- this.store.updateTask(task.id, { sessionFile: retrySessionFile }).catch((err) => {
69353
- const msg = err instanceof Error ? err.message : String(err);
69354
- executorLog.warn(`${task.id} failed to persist retry sessionFile: ${msg}`);
69605
+ let taskDoneSessionRetries = 0;
69606
+ while (!taskDone && taskDoneSessionRetries < MAX_TASK_DONE_SESSION_RETRIES) {
69607
+ taskDoneSessionRetries++;
69608
+ executorLog.log(
69609
+ `\u26A0 ${task.id} finished without task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`
69610
+ );
69611
+ await this.store.logEntry(
69612
+ task.id,
69613
+ `Agent finished without calling task_done \u2014 retrying with new session (${taskDoneSessionRetries}/${MAX_TASK_DONE_SESSION_RETRIES})`,
69614
+ void 0,
69615
+ this.currentRunContext
69616
+ );
69617
+ this.activeSessions.delete(task.id);
69618
+ session.dispose();
69619
+ const { session: retrySession2, sessionFile: retrySessionFile } = await createResolvedAgentSession({
69620
+ sessionPurpose: "executor",
69621
+ pluginRunner: this.options.pluginRunner,
69622
+ cwd: worktreePath,
69623
+ systemPrompt: executorSystemPrompt,
69624
+ tools: "coding",
69625
+ customTools,
69626
+ onText: agentLogger.onText,
69627
+ onThinking: agentLogger.onThinking,
69628
+ onToolStart: agentLogger.onToolStart,
69629
+ onToolEnd: agentLogger.onToolEnd,
69630
+ defaultProvider: executorProvider,
69631
+ defaultModelId: executorModelId,
69632
+ fallbackProvider: executorFallbackProvider,
69633
+ fallbackModelId: executorFallbackModelId,
69634
+ defaultThinkingLevel: executorThinkingLevel,
69635
+ sessionManager: SessionManager2.create(worktreePath),
69636
+ // Skill selection: use assigned agent skills if available, otherwise role fallback
69637
+ ...skillContext.skillSelectionContext ? { skillSelection: skillContext.skillSelectionContext } : {}
69355
69638
  });
69356
- }
69357
- session = retrySession2;
69358
- sessionRef.current = retrySession2;
69359
- this.activeSessions.set(task.id, {
69360
- session: retrySession2,
69361
- seenSteeringIds,
69362
- lastModelProvider: detail.modelProvider,
69363
- lastModelId: detail.modelId
69364
- });
69365
- stuckDetector?.trackTask(task.id, retrySession2);
69366
- const retryPrompt = [
69367
- "Your previous session ended without calling the task_done tool.",
69368
- "The task may already be complete \u2014 review the current state of the worktree and either:",
69369
- "1. If the work is done, call task_done with a summary of what was accomplished.",
69370
- "2. If there is remaining work, finish it and then call task_done.",
69371
- "",
69372
- "Original task:",
69373
- buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
69374
- ].join("\n");
69375
- stuckDetector?.recordActivity(task.id);
69376
- await promptWithFallback(retrySession2, retryPrompt);
69377
- checkSessionError(retrySession2);
69378
- if (!taskDone) {
69379
- const implicitCheck = await this.store.getTask(task.id);
69380
- if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
69381
- taskDone = true;
69382
- executorLog.log(`${task.id} all steps done \u2014 treating as implicit task_done`);
69383
- await this.store.logEntry(task.id, "All steps complete \u2014 implicit task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
69639
+ if (retrySessionFile) {
69640
+ this.store.updateTask(task.id, { sessionFile: retrySessionFile }).catch((err) => {
69641
+ const msg = err instanceof Error ? err.message : String(err);
69642
+ executorLog.warn(`${task.id} failed to persist retry sessionFile: ${msg}`);
69643
+ });
69644
+ }
69645
+ session = retrySession2;
69646
+ sessionRef.current = retrySession2;
69647
+ this.activeSessions.set(task.id, {
69648
+ session: retrySession2,
69649
+ seenSteeringIds,
69650
+ lastModelProvider: detail.modelProvider,
69651
+ lastModelId: detail.modelId
69652
+ });
69653
+ stuckDetector?.trackTask(task.id, retrySession2);
69654
+ const retryPrompt = [
69655
+ "Your previous session ended without calling the task_done tool.",
69656
+ "The task may already be complete \u2014 review the current state of the worktree and either:",
69657
+ "1. If the work is done, call task_done with a summary of what was accomplished.",
69658
+ "2. If there is remaining work, finish it and then call task_done.",
69659
+ "",
69660
+ "Original task:",
69661
+ buildExecutionPrompt(detail, this.rootDir, settings, worktreePath)
69662
+ ].join("\n");
69663
+ stuckDetector?.recordActivity(task.id);
69664
+ await promptWithFallback(retrySession2, retryPrompt);
69665
+ checkSessionError(retrySession2);
69666
+ if (!taskDone) {
69667
+ const implicitCheck = await this.store.getTask(task.id);
69668
+ if (implicitCheck.steps.length > 0 && implicitCheck.steps.every((s) => s.status === "done" || s.status === "skipped")) {
69669
+ taskDone = true;
69670
+ executorLog.log(`${task.id} all steps done \u2014 treating as implicit task_done`);
69671
+ await this.store.logEntry(task.id, "All steps complete \u2014 implicit task_done (agent did not call tool explicitly)", void 0, this.currentRunContext);
69672
+ }
69384
69673
  }
69385
69674
  }
69386
69675
  if (taskDone) {
@@ -69409,11 +69698,29 @@ Lint, tests, and typecheck are also hard quality gates:
69409
69698
  executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
69410
69699
  this.options.onComplete?.(task);
69411
69700
  } else {
69412
- const errorMessage = "Agent finished without calling task_done (after retry)";
69413
- await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
69414
- await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
69415
- await this.store.moveTask(task.id, "in-review");
69416
- executorLog.log(`\u2717 ${task.id} failed after retry \u2014 no task_done \u2192 in-review`);
69701
+ const priorRequeues = task.taskDoneRetryCount ?? 0;
69702
+ const nextRequeueCount = priorRequeues + 1;
69703
+ const errorMessage = `Agent finished without calling task_done (after ${MAX_TASK_DONE_SESSION_RETRIES} retries)`;
69704
+ if (priorRequeues < MAX_TASK_DONE_REQUEUE_RETRIES) {
69705
+ await this.store.updateTask(task.id, {
69706
+ status: "failed",
69707
+ error: errorMessage,
69708
+ taskDoneRetryCount: nextRequeueCount
69709
+ });
69710
+ await this.store.logEntry(
69711
+ task.id,
69712
+ `${errorMessage} \u2014 requeued to todo immediately (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`,
69713
+ void 0,
69714
+ this.currentRunContext
69715
+ );
69716
+ await this.store.moveTask(task.id, "todo");
69717
+ executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 requeued to todo (${nextRequeueCount}/${MAX_TASK_DONE_REQUEUE_RETRIES})`);
69718
+ } else {
69719
+ await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
69720
+ await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
69721
+ await this.store.moveTask(task.id, "in-review");
69722
+ executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no task_done \u2192 in-review`);
69723
+ }
69417
69724
  this.options.onError?.(task, new Error(errorMessage));
69418
69725
  }
69419
69726
  }
@@ -75999,7 +76306,7 @@ async function getHeartbeatMemorySettings(taskStore) {
75999
76306
  return maybeGetSettings.call(taskStore);
76000
76307
  }
76001
76308
  function isTickableState(state) {
76002
- return state === "active" || state === "running";
76309
+ return state === "active" || state === "running" || state === "idle";
76003
76310
  }
76004
76311
  function isHeartbeatManaged(agent) {
76005
76312
  return !isEphemeralAgent(agent);
@@ -77329,8 +77636,13 @@ ${taskDetail.prompt}` : "No PROMPT.md available.",
77329
77636
  this.assignedListener = async (agent, taskId) => {
77330
77637
  if (!this.running) return;
77331
77638
  try {
77332
- if (!isHeartbeatManaged(agent) || !isTickableState(agent.state)) {
77333
- heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (state=${agent.state})`);
77639
+ if (!isHeartbeatManaged(agent)) {
77640
+ heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (ephemeral/internal)`);
77641
+ return;
77642
+ }
77643
+ const runtimeConfig = agent.runtimeConfig ?? {};
77644
+ if (runtimeConfig.enabled === false) {
77645
+ heartbeatLog.log(`Assignment trigger skipped for ${agent.id} (disabled)`);
77334
77646
  return;
77335
77647
  }
77336
77648
  const activeRun = await this.store.getActiveHeartbeatRun(agent.id);
@@ -77789,6 +78101,10 @@ var init_self_healing = __esm({
77789
78101
  const result = await readLog("HEAD");
77790
78102
  stdout = result.stdout;
77791
78103
  }
78104
+ if (!stdout.trim() && task.baseCommitSha) {
78105
+ const result = await readLog("HEAD");
78106
+ stdout = result.stdout;
78107
+ }
77792
78108
  const firstLine = stdout.trim().split("\n").find(Boolean);
77793
78109
  if (!firstLine) return null;
77794
78110
  const [sha, subject] = firstLine.split("");
@@ -79650,10 +79966,10 @@ var init_in_process_runtime = __esm({
79650
79966
  this.taskStore
79651
79967
  );
79652
79968
  this.triggerScheduler.start();
79653
- const isTickable = (agent) => !isEphemeralAgent(agent) && (agent.state === "active" || agent.state === "running");
79969
+ const isHeartbeatEnabledAgent = (agent) => !isEphemeralAgent(agent) && agent.runtimeConfig?.enabled !== false;
79654
79970
  this.agentCreatedListener = (agent) => {
79655
79971
  if (!this.triggerScheduler) return;
79656
- if (!isTickable(agent)) return;
79972
+ if (!isHeartbeatEnabledAgent(agent)) return;
79657
79973
  const rc = agent.runtimeConfig;
79658
79974
  this.triggerScheduler.registerAgent(agent.id, {
79659
79975
  heartbeatIntervalMs: rc?.heartbeatIntervalMs,
@@ -79664,9 +79980,9 @@ var init_in_process_runtime = __esm({
79664
79980
  this.agentStore.on("agent:created", this.agentCreatedListener);
79665
79981
  this.agentUpdatedListener = (agent) => {
79666
79982
  if (!this.triggerScheduler) return;
79667
- if (!isTickable(agent)) {
79983
+ if (!isHeartbeatEnabledAgent(agent)) {
79668
79984
  this.triggerScheduler.unregisterAgent(agent.id);
79669
- runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers (state=${agent.state})`);
79985
+ runtimeLog.log(`Unregistered agent ${agent.id} from heartbeat triggers`);
79670
79986
  return;
79671
79987
  }
79672
79988
  const rc = agent.runtimeConfig;
@@ -79674,7 +79990,7 @@ var init_in_process_runtime = __esm({
79674
79990
  heartbeatIntervalMs: rc?.heartbeatIntervalMs,
79675
79991
  maxConcurrentRuns: rc?.maxConcurrentRuns
79676
79992
  });
79677
- runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers (state=${agent.state})`);
79993
+ runtimeLog.log(`Re-registered agent ${agent.id} for heartbeat triggers`);
79678
79994
  };
79679
79995
  this.agentStore.on("agent:updated", this.agentUpdatedListener);
79680
79996
  this.ephemeralTerminationListener = (agentId, from, to) => {
@@ -79709,7 +80025,7 @@ var init_in_process_runtime = __esm({
79709
80025
  const agents = await this.agentStore.listAgents();
79710
80026
  let registeredCount = 0;
79711
80027
  for (const agent of agents) {
79712
- if (!isTickable(agent)) continue;
80028
+ if (!isHeartbeatEnabledAgent(agent)) continue;
79713
80029
  const rc = agent.runtimeConfig;
79714
80030
  this.triggerScheduler.registerAgent(agent.id, {
79715
80031
  heartbeatIntervalMs: rc?.heartbeatIntervalMs,
@@ -88192,13 +88508,13 @@ var require_readdir_glob = __commonJS({
88192
88508
  "../../node_modules/.pnpm/readdir-glob@1.1.3/node_modules/readdir-glob/index.js"(exports, module) {
88193
88509
  "use strict";
88194
88510
  module.exports = readdirGlob;
88195
- var fs = __require("fs");
88511
+ var fs2 = __require("fs");
88196
88512
  var { EventEmitter: EventEmitter33 } = __require("events");
88197
88513
  var { Minimatch } = require_minimatch();
88198
88514
  var { resolve: resolve29 } = __require("path");
88199
88515
  function readdir11(dir2, strict) {
88200
88516
  return new Promise((resolve30, reject2) => {
88201
- fs.readdir(dir2, { withFileTypes: true }, (err, files) => {
88517
+ fs2.readdir(dir2, { withFileTypes: true }, (err, files) => {
88202
88518
  if (err) {
88203
88519
  switch (err.code) {
88204
88520
  case "ENOTDIR":
@@ -88231,7 +88547,7 @@ var require_readdir_glob = __commonJS({
88231
88547
  }
88232
88548
  function stat11(file, followSymlinks) {
88233
88549
  return new Promise((resolve30, reject2) => {
88234
- const statFunc = followSymlinks ? fs.stat : fs.lstat;
88550
+ const statFunc = followSymlinks ? fs2.stat : fs2.lstat;
88235
88551
  statFunc(file, (err, stats) => {
88236
88552
  if (err) {
88237
88553
  switch (err.code) {
@@ -90294,54 +90610,54 @@ var require_polyfills = __commonJS({
90294
90610
  }
90295
90611
  var chdir;
90296
90612
  module.exports = patch;
90297
- function patch(fs) {
90613
+ function patch(fs2) {
90298
90614
  if (constants2.hasOwnProperty("O_SYMLINK") && process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) {
90299
- patchLchmod(fs);
90300
- }
90301
- if (!fs.lutimes) {
90302
- patchLutimes(fs);
90303
- }
90304
- fs.chown = chownFix(fs.chown);
90305
- fs.fchown = chownFix(fs.fchown);
90306
- fs.lchown = chownFix(fs.lchown);
90307
- fs.chmod = chmodFix(fs.chmod);
90308
- fs.fchmod = chmodFix(fs.fchmod);
90309
- fs.lchmod = chmodFix(fs.lchmod);
90310
- fs.chownSync = chownFixSync(fs.chownSync);
90311
- fs.fchownSync = chownFixSync(fs.fchownSync);
90312
- fs.lchownSync = chownFixSync(fs.lchownSync);
90313
- fs.chmodSync = chmodFixSync(fs.chmodSync);
90314
- fs.fchmodSync = chmodFixSync(fs.fchmodSync);
90315
- fs.lchmodSync = chmodFixSync(fs.lchmodSync);
90316
- fs.stat = statFix(fs.stat);
90317
- fs.fstat = statFix(fs.fstat);
90318
- fs.lstat = statFix(fs.lstat);
90319
- fs.statSync = statFixSync(fs.statSync);
90320
- fs.fstatSync = statFixSync(fs.fstatSync);
90321
- fs.lstatSync = statFixSync(fs.lstatSync);
90322
- if (fs.chmod && !fs.lchmod) {
90323
- fs.lchmod = function(path4, mode, cb) {
90615
+ patchLchmod(fs2);
90616
+ }
90617
+ if (!fs2.lutimes) {
90618
+ patchLutimes(fs2);
90619
+ }
90620
+ fs2.chown = chownFix(fs2.chown);
90621
+ fs2.fchown = chownFix(fs2.fchown);
90622
+ fs2.lchown = chownFix(fs2.lchown);
90623
+ fs2.chmod = chmodFix(fs2.chmod);
90624
+ fs2.fchmod = chmodFix(fs2.fchmod);
90625
+ fs2.lchmod = chmodFix(fs2.lchmod);
90626
+ fs2.chownSync = chownFixSync(fs2.chownSync);
90627
+ fs2.fchownSync = chownFixSync(fs2.fchownSync);
90628
+ fs2.lchownSync = chownFixSync(fs2.lchownSync);
90629
+ fs2.chmodSync = chmodFixSync(fs2.chmodSync);
90630
+ fs2.fchmodSync = chmodFixSync(fs2.fchmodSync);
90631
+ fs2.lchmodSync = chmodFixSync(fs2.lchmodSync);
90632
+ fs2.stat = statFix(fs2.stat);
90633
+ fs2.fstat = statFix(fs2.fstat);
90634
+ fs2.lstat = statFix(fs2.lstat);
90635
+ fs2.statSync = statFixSync(fs2.statSync);
90636
+ fs2.fstatSync = statFixSync(fs2.fstatSync);
90637
+ fs2.lstatSync = statFixSync(fs2.lstatSync);
90638
+ if (fs2.chmod && !fs2.lchmod) {
90639
+ fs2.lchmod = function(path4, mode, cb) {
90324
90640
  if (cb) process.nextTick(cb);
90325
90641
  };
90326
- fs.lchmodSync = function() {
90642
+ fs2.lchmodSync = function() {
90327
90643
  };
90328
90644
  }
90329
- if (fs.chown && !fs.lchown) {
90330
- fs.lchown = function(path4, uid, gid, cb) {
90645
+ if (fs2.chown && !fs2.lchown) {
90646
+ fs2.lchown = function(path4, uid, gid, cb) {
90331
90647
  if (cb) process.nextTick(cb);
90332
90648
  };
90333
- fs.lchownSync = function() {
90649
+ fs2.lchownSync = function() {
90334
90650
  };
90335
90651
  }
90336
90652
  if (platform3 === "win32") {
90337
- fs.rename = typeof fs.rename !== "function" ? fs.rename : (function(fs$rename) {
90653
+ fs2.rename = typeof fs2.rename !== "function" ? fs2.rename : (function(fs$rename) {
90338
90654
  function rename6(from, to, cb) {
90339
90655
  var start = Date.now();
90340
90656
  var backoff = 0;
90341
90657
  fs$rename(from, to, function CB(er) {
90342
90658
  if (er && (er.code === "EACCES" || er.code === "EPERM" || er.code === "EBUSY") && Date.now() - start < 6e4) {
90343
90659
  setTimeout(function() {
90344
- fs.stat(to, function(stater, st) {
90660
+ fs2.stat(to, function(stater, st) {
90345
90661
  if (stater && stater.code === "ENOENT")
90346
90662
  fs$rename(from, to, CB);
90347
90663
  else
@@ -90357,9 +90673,9 @@ var require_polyfills = __commonJS({
90357
90673
  }
90358
90674
  if (Object.setPrototypeOf) Object.setPrototypeOf(rename6, fs$rename);
90359
90675
  return rename6;
90360
- })(fs.rename);
90676
+ })(fs2.rename);
90361
90677
  }
90362
- fs.read = typeof fs.read !== "function" ? fs.read : (function(fs$read) {
90678
+ fs2.read = typeof fs2.read !== "function" ? fs2.read : (function(fs$read) {
90363
90679
  function read(fd, buffer, offset, length, position, callback_) {
90364
90680
  var callback;
90365
90681
  if (callback_ && typeof callback_ === "function") {
@@ -90367,22 +90683,22 @@ var require_polyfills = __commonJS({
90367
90683
  callback = function(er, _2, __) {
90368
90684
  if (er && er.code === "EAGAIN" && eagCounter < 10) {
90369
90685
  eagCounter++;
90370
- return fs$read.call(fs, fd, buffer, offset, length, position, callback);
90686
+ return fs$read.call(fs2, fd, buffer, offset, length, position, callback);
90371
90687
  }
90372
90688
  callback_.apply(this, arguments);
90373
90689
  };
90374
90690
  }
90375
- return fs$read.call(fs, fd, buffer, offset, length, position, callback);
90691
+ return fs$read.call(fs2, fd, buffer, offset, length, position, callback);
90376
90692
  }
90377
90693
  if (Object.setPrototypeOf) Object.setPrototypeOf(read, fs$read);
90378
90694
  return read;
90379
- })(fs.read);
90380
- fs.readSync = typeof fs.readSync !== "function" ? fs.readSync : /* @__PURE__ */ (function(fs$readSync) {
90695
+ })(fs2.read);
90696
+ fs2.readSync = typeof fs2.readSync !== "function" ? fs2.readSync : /* @__PURE__ */ (function(fs$readSync) {
90381
90697
  return function(fd, buffer, offset, length, position) {
90382
90698
  var eagCounter = 0;
90383
90699
  while (true) {
90384
90700
  try {
90385
- return fs$readSync.call(fs, fd, buffer, offset, length, position);
90701
+ return fs$readSync.call(fs2, fd, buffer, offset, length, position);
90386
90702
  } catch (er) {
90387
90703
  if (er.code === "EAGAIN" && eagCounter < 10) {
90388
90704
  eagCounter++;
@@ -90392,10 +90708,10 @@ var require_polyfills = __commonJS({
90392
90708
  }
90393
90709
  }
90394
90710
  };
90395
- })(fs.readSync);
90396
- function patchLchmod(fs2) {
90397
- fs2.lchmod = function(path4, mode, callback) {
90398
- fs2.open(
90711
+ })(fs2.readSync);
90712
+ function patchLchmod(fs3) {
90713
+ fs3.lchmod = function(path4, mode, callback) {
90714
+ fs3.open(
90399
90715
  path4,
90400
90716
  constants2.O_WRONLY | constants2.O_SYMLINK,
90401
90717
  mode,
@@ -90404,80 +90720,80 @@ var require_polyfills = __commonJS({
90404
90720
  if (callback) callback(err);
90405
90721
  return;
90406
90722
  }
90407
- fs2.fchmod(fd, mode, function(err2) {
90408
- fs2.close(fd, function(err22) {
90723
+ fs3.fchmod(fd, mode, function(err2) {
90724
+ fs3.close(fd, function(err22) {
90409
90725
  if (callback) callback(err2 || err22);
90410
90726
  });
90411
90727
  });
90412
90728
  }
90413
90729
  );
90414
90730
  };
90415
- fs2.lchmodSync = function(path4, mode) {
90416
- var fd = fs2.openSync(path4, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
90731
+ fs3.lchmodSync = function(path4, mode) {
90732
+ var fd = fs3.openSync(path4, constants2.O_WRONLY | constants2.O_SYMLINK, mode);
90417
90733
  var threw = true;
90418
90734
  var ret;
90419
90735
  try {
90420
- ret = fs2.fchmodSync(fd, mode);
90736
+ ret = fs3.fchmodSync(fd, mode);
90421
90737
  threw = false;
90422
90738
  } finally {
90423
90739
  if (threw) {
90424
90740
  try {
90425
- fs2.closeSync(fd);
90741
+ fs3.closeSync(fd);
90426
90742
  } catch (er) {
90427
90743
  }
90428
90744
  } else {
90429
- fs2.closeSync(fd);
90745
+ fs3.closeSync(fd);
90430
90746
  }
90431
90747
  }
90432
90748
  return ret;
90433
90749
  };
90434
90750
  }
90435
- function patchLutimes(fs2) {
90436
- if (constants2.hasOwnProperty("O_SYMLINK") && fs2.futimes) {
90437
- fs2.lutimes = function(path4, at, mt, cb) {
90438
- fs2.open(path4, constants2.O_SYMLINK, function(er, fd) {
90751
+ function patchLutimes(fs3) {
90752
+ if (constants2.hasOwnProperty("O_SYMLINK") && fs3.futimes) {
90753
+ fs3.lutimes = function(path4, at, mt, cb) {
90754
+ fs3.open(path4, constants2.O_SYMLINK, function(er, fd) {
90439
90755
  if (er) {
90440
90756
  if (cb) cb(er);
90441
90757
  return;
90442
90758
  }
90443
- fs2.futimes(fd, at, mt, function(er2) {
90444
- fs2.close(fd, function(er22) {
90759
+ fs3.futimes(fd, at, mt, function(er2) {
90760
+ fs3.close(fd, function(er22) {
90445
90761
  if (cb) cb(er2 || er22);
90446
90762
  });
90447
90763
  });
90448
90764
  });
90449
90765
  };
90450
- fs2.lutimesSync = function(path4, at, mt) {
90451
- var fd = fs2.openSync(path4, constants2.O_SYMLINK);
90766
+ fs3.lutimesSync = function(path4, at, mt) {
90767
+ var fd = fs3.openSync(path4, constants2.O_SYMLINK);
90452
90768
  var ret;
90453
90769
  var threw = true;
90454
90770
  try {
90455
- ret = fs2.futimesSync(fd, at, mt);
90771
+ ret = fs3.futimesSync(fd, at, mt);
90456
90772
  threw = false;
90457
90773
  } finally {
90458
90774
  if (threw) {
90459
90775
  try {
90460
- fs2.closeSync(fd);
90776
+ fs3.closeSync(fd);
90461
90777
  } catch (er) {
90462
90778
  }
90463
90779
  } else {
90464
- fs2.closeSync(fd);
90780
+ fs3.closeSync(fd);
90465
90781
  }
90466
90782
  }
90467
90783
  return ret;
90468
90784
  };
90469
- } else if (fs2.futimes) {
90470
- fs2.lutimes = function(_a, _b, _c, cb) {
90785
+ } else if (fs3.futimes) {
90786
+ fs3.lutimes = function(_a, _b, _c, cb) {
90471
90787
  if (cb) process.nextTick(cb);
90472
90788
  };
90473
- fs2.lutimesSync = function() {
90789
+ fs3.lutimesSync = function() {
90474
90790
  };
90475
90791
  }
90476
90792
  }
90477
90793
  function chmodFix(orig) {
90478
90794
  if (!orig) return orig;
90479
90795
  return function(target, mode, cb) {
90480
- return orig.call(fs, target, mode, function(er) {
90796
+ return orig.call(fs2, target, mode, function(er) {
90481
90797
  if (chownErOk(er)) er = null;
90482
90798
  if (cb) cb.apply(this, arguments);
90483
90799
  });
@@ -90487,7 +90803,7 @@ var require_polyfills = __commonJS({
90487
90803
  if (!orig) return orig;
90488
90804
  return function(target, mode) {
90489
90805
  try {
90490
- return orig.call(fs, target, mode);
90806
+ return orig.call(fs2, target, mode);
90491
90807
  } catch (er) {
90492
90808
  if (!chownErOk(er)) throw er;
90493
90809
  }
@@ -90496,7 +90812,7 @@ var require_polyfills = __commonJS({
90496
90812
  function chownFix(orig) {
90497
90813
  if (!orig) return orig;
90498
90814
  return function(target, uid, gid, cb) {
90499
- return orig.call(fs, target, uid, gid, function(er) {
90815
+ return orig.call(fs2, target, uid, gid, function(er) {
90500
90816
  if (chownErOk(er)) er = null;
90501
90817
  if (cb) cb.apply(this, arguments);
90502
90818
  });
@@ -90506,7 +90822,7 @@ var require_polyfills = __commonJS({
90506
90822
  if (!orig) return orig;
90507
90823
  return function(target, uid, gid) {
90508
90824
  try {
90509
- return orig.call(fs, target, uid, gid);
90825
+ return orig.call(fs2, target, uid, gid);
90510
90826
  } catch (er) {
90511
90827
  if (!chownErOk(er)) throw er;
90512
90828
  }
@@ -90526,13 +90842,13 @@ var require_polyfills = __commonJS({
90526
90842
  }
90527
90843
  if (cb) cb.apply(this, arguments);
90528
90844
  }
90529
- return options ? orig.call(fs, target, options, callback) : orig.call(fs, target, callback);
90845
+ return options ? orig.call(fs2, target, options, callback) : orig.call(fs2, target, callback);
90530
90846
  };
90531
90847
  }
90532
90848
  function statFixSync(orig) {
90533
90849
  if (!orig) return orig;
90534
90850
  return function(target, options) {
90535
- var stats = options ? orig.call(fs, target, options) : orig.call(fs, target);
90851
+ var stats = options ? orig.call(fs2, target, options) : orig.call(fs2, target);
90536
90852
  if (stats) {
90537
90853
  if (stats.uid < 0) stats.uid += 4294967296;
90538
90854
  if (stats.gid < 0) stats.gid += 4294967296;
@@ -90562,7 +90878,7 @@ var require_legacy_streams = __commonJS({
90562
90878
  "use strict";
90563
90879
  var Stream = __require("stream").Stream;
90564
90880
  module.exports = legacy;
90565
- function legacy(fs) {
90881
+ function legacy(fs2) {
90566
90882
  return {
90567
90883
  ReadStream,
90568
90884
  WriteStream
@@ -90605,7 +90921,7 @@ var require_legacy_streams = __commonJS({
90605
90921
  });
90606
90922
  return;
90607
90923
  }
90608
- fs.open(this.path, this.flags, this.mode, function(err, fd) {
90924
+ fs2.open(this.path, this.flags, this.mode, function(err, fd) {
90609
90925
  if (err) {
90610
90926
  self2.emit("error", err);
90611
90927
  self2.readable = false;
@@ -90644,7 +90960,7 @@ var require_legacy_streams = __commonJS({
90644
90960
  this.busy = false;
90645
90961
  this._queue = [];
90646
90962
  if (this.fd === null) {
90647
- this._open = fs.open;
90963
+ this._open = fs2.open;
90648
90964
  this._queue.push([this._open, this.path, this.flags, this.mode, void 0]);
90649
90965
  this.flush();
90650
90966
  }
@@ -90680,7 +90996,7 @@ var require_clone = __commonJS({
90680
90996
  var require_graceful_fs = __commonJS({
90681
90997
  "../../node_modules/.pnpm/graceful-fs@4.2.11/node_modules/graceful-fs/graceful-fs.js"(exports, module) {
90682
90998
  "use strict";
90683
- var fs = __require("fs");
90999
+ var fs2 = __require("fs");
90684
91000
  var polyfills = require_polyfills();
90685
91001
  var legacy = require_legacy_streams();
90686
91002
  var clone = require_clone();
@@ -90712,12 +91028,12 @@ var require_graceful_fs = __commonJS({
90712
91028
  m = "GFS4: " + m.split(/\n/).join("\nGFS4: ");
90713
91029
  console.error(m);
90714
91030
  };
90715
- if (!fs[gracefulQueue]) {
91031
+ if (!fs2[gracefulQueue]) {
90716
91032
  queue2 = global[gracefulQueue] || [];
90717
- publishQueue(fs, queue2);
90718
- fs.close = (function(fs$close) {
91033
+ publishQueue(fs2, queue2);
91034
+ fs2.close = (function(fs$close) {
90719
91035
  function close(fd, cb) {
90720
- return fs$close.call(fs, fd, function(err) {
91036
+ return fs$close.call(fs2, fd, function(err) {
90721
91037
  if (!err) {
90722
91038
  resetQueue();
90723
91039
  }
@@ -90729,40 +91045,40 @@ var require_graceful_fs = __commonJS({
90729
91045
  value: fs$close
90730
91046
  });
90731
91047
  return close;
90732
- })(fs.close);
90733
- fs.closeSync = (function(fs$closeSync) {
91048
+ })(fs2.close);
91049
+ fs2.closeSync = (function(fs$closeSync) {
90734
91050
  function closeSync(fd) {
90735
- fs$closeSync.apply(fs, arguments);
91051
+ fs$closeSync.apply(fs2, arguments);
90736
91052
  resetQueue();
90737
91053
  }
90738
91054
  Object.defineProperty(closeSync, previousSymbol, {
90739
91055
  value: fs$closeSync
90740
91056
  });
90741
91057
  return closeSync;
90742
- })(fs.closeSync);
91058
+ })(fs2.closeSync);
90743
91059
  if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || "")) {
90744
91060
  process.on("exit", function() {
90745
- debug(fs[gracefulQueue]);
90746
- __require("assert").equal(fs[gracefulQueue].length, 0);
91061
+ debug(fs2[gracefulQueue]);
91062
+ __require("assert").equal(fs2[gracefulQueue].length, 0);
90747
91063
  });
90748
91064
  }
90749
91065
  }
90750
91066
  var queue2;
90751
91067
  if (!global[gracefulQueue]) {
90752
- publishQueue(global, fs[gracefulQueue]);
90753
- }
90754
- module.exports = patch(clone(fs));
90755
- if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) {
90756
- module.exports = patch(fs);
90757
- fs.__patched = true;
90758
- }
90759
- function patch(fs2) {
90760
- polyfills(fs2);
90761
- fs2.gracefulify = patch;
90762
- fs2.createReadStream = createReadStream2;
90763
- fs2.createWriteStream = createWriteStream2;
90764
- var fs$readFile = fs2.readFile;
90765
- fs2.readFile = readFile24;
91068
+ publishQueue(global, fs2[gracefulQueue]);
91069
+ }
91070
+ module.exports = patch(clone(fs2));
91071
+ if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs2.__patched) {
91072
+ module.exports = patch(fs2);
91073
+ fs2.__patched = true;
91074
+ }
91075
+ function patch(fs3) {
91076
+ polyfills(fs3);
91077
+ fs3.gracefulify = patch;
91078
+ fs3.createReadStream = createReadStream2;
91079
+ fs3.createWriteStream = createWriteStream2;
91080
+ var fs$readFile = fs3.readFile;
91081
+ fs3.readFile = readFile24;
90766
91082
  function readFile24(path4, options, cb) {
90767
91083
  if (typeof options === "function")
90768
91084
  cb = options, options = null;
@@ -90778,8 +91094,8 @@ var require_graceful_fs = __commonJS({
90778
91094
  });
90779
91095
  }
90780
91096
  }
90781
- var fs$writeFile = fs2.writeFile;
90782
- fs2.writeFile = writeFile16;
91097
+ var fs$writeFile = fs3.writeFile;
91098
+ fs3.writeFile = writeFile16;
90783
91099
  function writeFile16(path4, data, options, cb) {
90784
91100
  if (typeof options === "function")
90785
91101
  cb = options, options = null;
@@ -90795,9 +91111,9 @@ var require_graceful_fs = __commonJS({
90795
91111
  });
90796
91112
  }
90797
91113
  }
90798
- var fs$appendFile = fs2.appendFile;
91114
+ var fs$appendFile = fs3.appendFile;
90799
91115
  if (fs$appendFile)
90800
- fs2.appendFile = appendFile3;
91116
+ fs3.appendFile = appendFile3;
90801
91117
  function appendFile3(path4, data, options, cb) {
90802
91118
  if (typeof options === "function")
90803
91119
  cb = options, options = null;
@@ -90813,9 +91129,9 @@ var require_graceful_fs = __commonJS({
90813
91129
  });
90814
91130
  }
90815
91131
  }
90816
- var fs$copyFile = fs2.copyFile;
91132
+ var fs$copyFile = fs3.copyFile;
90817
91133
  if (fs$copyFile)
90818
- fs2.copyFile = copyFile;
91134
+ fs3.copyFile = copyFile;
90819
91135
  function copyFile(src, dest, flags, cb) {
90820
91136
  if (typeof flags === "function") {
90821
91137
  cb = flags;
@@ -90833,8 +91149,8 @@ var require_graceful_fs = __commonJS({
90833
91149
  });
90834
91150
  }
90835
91151
  }
90836
- var fs$readdir = fs2.readdir;
90837
- fs2.readdir = readdir11;
91152
+ var fs$readdir = fs3.readdir;
91153
+ fs3.readdir = readdir11;
90838
91154
  var noReaddirOptionVersions = /^v[0-5]\./;
90839
91155
  function readdir11(path4, options, cb) {
90840
91156
  if (typeof options === "function")
@@ -90875,21 +91191,21 @@ var require_graceful_fs = __commonJS({
90875
91191
  }
90876
91192
  }
90877
91193
  if (process.version.substr(0, 4) === "v0.8") {
90878
- var legStreams = legacy(fs2);
91194
+ var legStreams = legacy(fs3);
90879
91195
  ReadStream = legStreams.ReadStream;
90880
91196
  WriteStream = legStreams.WriteStream;
90881
91197
  }
90882
- var fs$ReadStream = fs2.ReadStream;
91198
+ var fs$ReadStream = fs3.ReadStream;
90883
91199
  if (fs$ReadStream) {
90884
91200
  ReadStream.prototype = Object.create(fs$ReadStream.prototype);
90885
91201
  ReadStream.prototype.open = ReadStream$open;
90886
91202
  }
90887
- var fs$WriteStream = fs2.WriteStream;
91203
+ var fs$WriteStream = fs3.WriteStream;
90888
91204
  if (fs$WriteStream) {
90889
91205
  WriteStream.prototype = Object.create(fs$WriteStream.prototype);
90890
91206
  WriteStream.prototype.open = WriteStream$open;
90891
91207
  }
90892
- Object.defineProperty(fs2, "ReadStream", {
91208
+ Object.defineProperty(fs3, "ReadStream", {
90893
91209
  get: function() {
90894
91210
  return ReadStream;
90895
91211
  },
@@ -90899,7 +91215,7 @@ var require_graceful_fs = __commonJS({
90899
91215
  enumerable: true,
90900
91216
  configurable: true
90901
91217
  });
90902
- Object.defineProperty(fs2, "WriteStream", {
91218
+ Object.defineProperty(fs3, "WriteStream", {
90903
91219
  get: function() {
90904
91220
  return WriteStream;
90905
91221
  },
@@ -90910,7 +91226,7 @@ var require_graceful_fs = __commonJS({
90910
91226
  configurable: true
90911
91227
  });
90912
91228
  var FileReadStream = ReadStream;
90913
- Object.defineProperty(fs2, "FileReadStream", {
91229
+ Object.defineProperty(fs3, "FileReadStream", {
90914
91230
  get: function() {
90915
91231
  return FileReadStream;
90916
91232
  },
@@ -90921,7 +91237,7 @@ var require_graceful_fs = __commonJS({
90921
91237
  configurable: true
90922
91238
  });
90923
91239
  var FileWriteStream = WriteStream;
90924
- Object.defineProperty(fs2, "FileWriteStream", {
91240
+ Object.defineProperty(fs3, "FileWriteStream", {
90925
91241
  get: function() {
90926
91242
  return FileWriteStream;
90927
91243
  },
@@ -90970,13 +91286,13 @@ var require_graceful_fs = __commonJS({
90970
91286
  });
90971
91287
  }
90972
91288
  function createReadStream2(path4, options) {
90973
- return new fs2.ReadStream(path4, options);
91289
+ return new fs3.ReadStream(path4, options);
90974
91290
  }
90975
91291
  function createWriteStream2(path4, options) {
90976
- return new fs2.WriteStream(path4, options);
91292
+ return new fs3.WriteStream(path4, options);
90977
91293
  }
90978
- var fs$open = fs2.open;
90979
- fs2.open = open;
91294
+ var fs$open = fs3.open;
91295
+ fs3.open = open;
90980
91296
  function open(path4, flags, mode, cb) {
90981
91297
  if (typeof mode === "function")
90982
91298
  cb = mode, mode = null;
@@ -90992,20 +91308,20 @@ var require_graceful_fs = __commonJS({
90992
91308
  });
90993
91309
  }
90994
91310
  }
90995
- return fs2;
91311
+ return fs3;
90996
91312
  }
90997
91313
  function enqueue(elem) {
90998
91314
  debug("ENQUEUE", elem[0].name, elem[1]);
90999
- fs[gracefulQueue].push(elem);
91315
+ fs2[gracefulQueue].push(elem);
91000
91316
  retry2();
91001
91317
  }
91002
91318
  var retryTimer;
91003
91319
  function resetQueue() {
91004
91320
  var now = Date.now();
91005
- for (var i = 0; i < fs[gracefulQueue].length; ++i) {
91006
- if (fs[gracefulQueue][i].length > 2) {
91007
- fs[gracefulQueue][i][3] = now;
91008
- fs[gracefulQueue][i][4] = now;
91321
+ for (var i = 0; i < fs2[gracefulQueue].length; ++i) {
91322
+ if (fs2[gracefulQueue][i].length > 2) {
91323
+ fs2[gracefulQueue][i][3] = now;
91324
+ fs2[gracefulQueue][i][4] = now;
91009
91325
  }
91010
91326
  }
91011
91327
  retry2();
@@ -91013,9 +91329,9 @@ var require_graceful_fs = __commonJS({
91013
91329
  function retry2() {
91014
91330
  clearTimeout(retryTimer);
91015
91331
  retryTimer = void 0;
91016
- if (fs[gracefulQueue].length === 0)
91332
+ if (fs2[gracefulQueue].length === 0)
91017
91333
  return;
91018
- var elem = fs[gracefulQueue].shift();
91334
+ var elem = fs2[gracefulQueue].shift();
91019
91335
  var fn = elem[0];
91020
91336
  var args = elem[1];
91021
91337
  var err = elem[2];
@@ -91037,7 +91353,7 @@ var require_graceful_fs = __commonJS({
91037
91353
  debug("RETRY", fn.name, args);
91038
91354
  fn.apply(null, args.concat([startTime]));
91039
91355
  } else {
91040
- fs[gracefulQueue].push(elem);
91356
+ fs2[gracefulQueue].push(elem);
91041
91357
  }
91042
91358
  }
91043
91359
  if (retryTimer === void 0) {
@@ -106230,8 +106546,8 @@ var require_commonjs4 = __commonJS({
106230
106546
  *
106231
106547
  * @internal
106232
106548
  */
106233
- constructor(cwd = process.cwd(), pathImpl, sep6, { nocase, childrenCacheSize = 16 * 1024, fs = defaultFS } = {}) {
106234
- this.#fs = fsFromOption(fs);
106549
+ constructor(cwd = process.cwd(), pathImpl, sep6, { nocase, childrenCacheSize = 16 * 1024, fs: fs2 = defaultFS } = {}) {
106550
+ this.#fs = fsFromOption(fs2);
106235
106551
  if (cwd instanceof URL || cwd.startsWith("file://")) {
106236
106552
  cwd = (0, node_url_1.fileURLToPath)(cwd);
106237
106553
  }
@@ -106790,8 +107106,8 @@ var require_commonjs4 = __commonJS({
106790
107106
  /**
106791
107107
  * @internal
106792
107108
  */
106793
- newRoot(fs) {
106794
- return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs });
107109
+ newRoot(fs2) {
107110
+ return new PathWin32(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs2 });
106795
107111
  }
106796
107112
  /**
106797
107113
  * Return true if the provided path string is an absolute path
@@ -106820,8 +107136,8 @@ var require_commonjs4 = __commonJS({
106820
107136
  /**
106821
107137
  * @internal
106822
107138
  */
106823
- newRoot(fs) {
106824
- return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs });
107139
+ newRoot(fs2) {
107140
+ return new PathPosix(this.rootPath, IFDIR, void 0, this.roots, this.nocase, this.childrenCache(), { fs: fs2 });
106825
107141
  }
106826
107142
  /**
106827
107143
  * Return true if the provided path string is an absolute path
@@ -108007,7 +108323,7 @@ var require_commonjs5 = __commonJS({
108007
108323
  var require_file = __commonJS({
108008
108324
  "../../node_modules/.pnpm/archiver-utils@5.0.2/node_modules/archiver-utils/file.js"(exports, module) {
108009
108325
  "use strict";
108010
- var fs = require_graceful_fs();
108326
+ var fs2 = require_graceful_fs();
108011
108327
  var path4 = __require("path");
108012
108328
  var flatten = require_flatten();
108013
108329
  var difference = require_difference();
@@ -108034,7 +108350,7 @@ var require_file = __commonJS({
108034
108350
  };
108035
108351
  file.exists = function() {
108036
108352
  var filepath = path4.join.apply(path4, arguments);
108037
- return fs.existsSync(filepath);
108353
+ return fs2.existsSync(filepath);
108038
108354
  };
108039
108355
  file.expand = function(...args) {
108040
108356
  var options = isPlainObject(args[0]) ? args.shift() : {};
@@ -108052,7 +108368,7 @@ var require_file = __commonJS({
108052
108368
  if (typeof options.filter === "function") {
108053
108369
  return options.filter(filepath);
108054
108370
  } else {
108055
- return fs.statSync(filepath)[options.filter]();
108371
+ return fs2.statSync(filepath)[options.filter]();
108056
108372
  }
108057
108373
  } catch (e) {
108058
108374
  return false;
@@ -108161,7 +108477,7 @@ var require_file = __commonJS({
108161
108477
  var require_archiver_utils = __commonJS({
108162
108478
  "../../node_modules/.pnpm/archiver-utils@5.0.2/node_modules/archiver-utils/index.js"(exports, module) {
108163
108479
  "use strict";
108164
- var fs = require_graceful_fs();
108480
+ var fs2 = require_graceful_fs();
108165
108481
  var path4 = __require("path");
108166
108482
  var isStream = require_is_stream();
108167
108483
  var lazystream = require_lazystream();
@@ -108210,7 +108526,7 @@ var require_archiver_utils = __commonJS({
108210
108526
  };
108211
108527
  utils.lazyReadStream = function(filepath) {
108212
108528
  return new lazystream.Readable(function() {
108213
- return fs.createReadStream(filepath);
108529
+ return fs2.createReadStream(filepath);
108214
108530
  });
108215
108531
  };
108216
108532
  utils.normalizeInputSource = function(source) {
@@ -108238,7 +108554,7 @@ var require_archiver_utils = __commonJS({
108238
108554
  callback = base;
108239
108555
  base = dirpath;
108240
108556
  }
108241
- fs.readdir(dirpath, function(err, list) {
108557
+ fs2.readdir(dirpath, function(err, list) {
108242
108558
  var i = 0;
108243
108559
  var file;
108244
108560
  var filepath;
@@ -108251,7 +108567,7 @@ var require_archiver_utils = __commonJS({
108251
108567
  return callback(null, results);
108252
108568
  }
108253
108569
  filepath = path4.join(dirpath, file);
108254
- fs.stat(filepath, function(err2, stats) {
108570
+ fs2.stat(filepath, function(err2, stats) {
108255
108571
  results.push({
108256
108572
  path: filepath,
108257
108573
  relative: path4.relative(base, filepath).replace(/\\/g, "/"),
@@ -108315,7 +108631,7 @@ var require_error = __commonJS({
108315
108631
  var require_core = __commonJS({
108316
108632
  "../../node_modules/.pnpm/archiver@7.0.1/node_modules/archiver/lib/core.js"(exports, module) {
108317
108633
  "use strict";
108318
- var fs = __require("fs");
108634
+ var fs2 = __require("fs");
108319
108635
  var glob2 = require_readdir_glob();
108320
108636
  var async = (init_async(), __toCommonJS(async_exports));
108321
108637
  var path4 = __require("path");
@@ -108379,7 +108695,7 @@ var require_core = __commonJS({
108379
108695
  data.sourcePath = filepath;
108380
108696
  task.data = data;
108381
108697
  this._entriesCount++;
108382
- if (data.stats && data.stats instanceof fs.Stats) {
108698
+ if (data.stats && data.stats instanceof fs2.Stats) {
108383
108699
  task = this._updateQueueTaskWithStats(task, data.stats);
108384
108700
  if (task) {
108385
108701
  if (data.stats.size) {
@@ -108550,7 +108866,7 @@ var require_core = __commonJS({
108550
108866
  callback();
108551
108867
  return;
108552
108868
  }
108553
- fs.lstat(task.filepath, function(err, stats) {
108869
+ fs2.lstat(task.filepath, function(err, stats) {
108554
108870
  if (this._state.aborted) {
108555
108871
  setImmediate(callback);
108556
108872
  return;
@@ -108593,7 +108909,7 @@ var require_core = __commonJS({
108593
108909
  task.data.sourceType = "buffer";
108594
108910
  task.source = Buffer.concat([]);
108595
108911
  } else if (stats.isSymbolicLink() && this._moduleSupports("symlink")) {
108596
- var linkPath = fs.readlinkSync(task.filepath);
108912
+ var linkPath = fs2.readlinkSync(task.filepath);
108597
108913
  var dirName = path4.dirname(task.filepath);
108598
108914
  task.data.type = "symlink";
108599
108915
  task.data.linkname = path4.relative(dirName, path4.resolve(dirName, linkPath));
@@ -115135,9 +115451,10 @@ function createApiRoutes(store, options) {
115135
115451
  try {
115136
115452
  const { store: scopedStore } = await getProjectContext2(req);
115137
115453
  const settings = await scopedStore.getSettingsFast();
115454
+ const prAuthAvailable = isGhAvailable() && isGhAuthenticated() || Boolean(githubToken);
115138
115455
  res.json({
115139
115456
  ...settings,
115140
- githubTokenConfigured: Boolean(githubToken)
115457
+ prAuthAvailable
115141
115458
  });
115142
115459
  } catch (err) {
115143
115460
  if (err instanceof ApiError) {
@@ -115149,7 +115466,7 @@ function createApiRoutes(store, options) {
115149
115466
  router.put("/settings", async (req, res) => {
115150
115467
  try {
115151
115468
  const { store: scopedStore, engine } = await getProjectContext2(req);
115152
- const { githubTokenConfigured, ...clientSettings } = req.body;
115469
+ const { githubTokenConfigured, prAuthAvailable, ...clientSettings } = req.body;
115153
115470
  const globalKeySet = new Set(GLOBAL_SETTINGS_KEYS);
115154
115471
  const globalFieldsFound = Object.keys(clientSettings).filter((k) => globalKeySet.has(k));
115155
115472
  if (globalFieldsFound.length > 0) {
@@ -132288,7 +132605,7 @@ function setupTerminalWebSocket(app, server, store, options) {
132288
132605
  if (pathname !== "/api/terminal/ws") {
132289
132606
  return;
132290
132607
  }
132291
- if (wsDaemonToken && !authenticateUpgradeRequest(wsDaemonToken, req)) {
132608
+ if (wsDaemonToken && !options?.noAuth && !authenticateUpgradeRequest(wsDaemonToken, req)) {
132292
132609
  socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
132293
132610
  socket.destroy();
132294
132611
  return;
@@ -132479,7 +132796,7 @@ function setupBadgeWebSocket(app, server, store, options) {
132479
132796
  if (pathname !== "/api/ws") {
132480
132797
  return;
132481
132798
  }
132482
- if (badgeWsDaemonToken && !authenticateUpgradeRequest(badgeWsDaemonToken, req)) {
132799
+ if (badgeWsDaemonToken && !options?.noAuth && !authenticateUpgradeRequest(badgeWsDaemonToken, req)) {
132483
132800
  socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
132484
132801
  socket.destroy();
132485
132802
  return;
@@ -133738,9 +134055,1014 @@ var init_project_context = __esm({
133738
134055
  }
133739
134056
  });
133740
134057
 
134058
+ // src/commands/dashboard-tui.ts
134059
+ import * as readline from "node:readline";
134060
+ function moveCursorTo(x, y) {
134061
+ process.stdout.write(`\x1B[${y};${x}H`);
134062
+ }
134063
+ function clearLine() {
134064
+ process.stdout.write("\x1B[2K");
134065
+ }
134066
+ function clearScreen() {
134067
+ process.stdout.write("\x1B[2J");
134068
+ }
134069
+ function hideCursor() {
134070
+ process.stdout.write("\x1B[?25l");
134071
+ }
134072
+ function showCursor() {
134073
+ process.stdout.write("\x1B[?25h");
134074
+ }
134075
+ function enableAlternateScreen() {
134076
+ process.stdout.write("\x1B[?47h");
134077
+ }
134078
+ function disableAlternateScreen() {
134079
+ process.stdout.write("\x1B[?47l");
134080
+ }
134081
+ function colorize(text, color) {
134082
+ const colors = {
134083
+ reset: "\x1B[0m",
134084
+ bold: "\x1B[1m",
134085
+ dim: "\x1B[2m",
134086
+ red: "\x1B[31m",
134087
+ green: "\x1B[32m",
134088
+ yellow: "\x1B[33m",
134089
+ blue: "\x1B[34m",
134090
+ magenta: "\x1B[35m",
134091
+ cyan: "\x1B[36m",
134092
+ white: "\x1B[37m",
134093
+ gray: "\x1B[90m",
134094
+ brightRed: "\x1B[91m",
134095
+ brightGreen: "\x1B[92m",
134096
+ brightYellow: "\x1B[93m",
134097
+ brightBlue: "\x1B[94m",
134098
+ brightMagenta: "\x1B[95m",
134099
+ brightCyan: "\x1B[96m"
134100
+ };
134101
+ return `${colors[color] || ""}${text}${colors.reset}`;
134102
+ }
134103
+ function formatTimestamp3(date) {
134104
+ const h = date.getHours().toString().padStart(2, "0");
134105
+ const m = date.getMinutes().toString().padStart(2, "0");
134106
+ const s = date.getSeconds().toString().padStart(2, "0");
134107
+ const ms = date.getMilliseconds().toString().padStart(3, "0");
134108
+ return `${h}:${m}:${s}.${ms}`;
134109
+ }
134110
+ function formatUptime(ms) {
134111
+ const seconds = Math.floor(ms / 1e3);
134112
+ const minutes = Math.floor(seconds / 60);
134113
+ const hours = Math.floor(minutes / 60);
134114
+ const days = Math.floor(hours / 24);
134115
+ if (days > 0) return `${days}d ${hours % 24}h ${minutes % 60}m`;
134116
+ if (hours > 0) return `${hours}h ${minutes % 60}m ${seconds % 60}s`;
134117
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
134118
+ return `${seconds}s`;
134119
+ }
134120
+ function visibleLength(str) {
134121
+ return str.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").length;
134122
+ }
134123
+ function visibleTruncate(text, maxWidth) {
134124
+ if (maxWidth <= 0) return "";
134125
+ const currentLength = visibleLength(text);
134126
+ if (currentLength <= maxWidth) return text;
134127
+ const ansiRegex = /\x1b\[[0-9;]*[a-zA-Z]/g;
134128
+ let result = "";
134129
+ let visibleCount = 0;
134130
+ let match;
134131
+ const ansiMatches = [];
134132
+ while ((match = ansiRegex.exec(text)) !== null) {
134133
+ ansiMatches.push({
134134
+ start: match.index,
134135
+ end: match.index + match[0].length,
134136
+ seq: match[0]
134137
+ });
134138
+ }
134139
+ for (let i = 0; i < text.length; i++) {
134140
+ const char = text[i];
134141
+ const ansiMatch = ansiMatches.find((m) => m.start === i);
134142
+ if (ansiMatch) {
134143
+ result += ansiMatch.seq;
134144
+ continue;
134145
+ }
134146
+ visibleCount++;
134147
+ result += char;
134148
+ if (visibleCount >= maxWidth - 3) {
134149
+ break;
134150
+ }
134151
+ }
134152
+ if (visibleCount >= maxWidth - 3 && visibleCount < currentLength) {
134153
+ result += "...";
134154
+ }
134155
+ return result;
134156
+ }
134157
+ function formatConsoleArgs(args) {
134158
+ const stringified = args.map((arg) => {
134159
+ if (typeof arg === "string") return arg;
134160
+ if (arg instanceof Error) return arg.stack ?? arg.message;
134161
+ if (arg === null || arg === void 0) return String(arg);
134162
+ if (typeof arg === "object") {
134163
+ try {
134164
+ return JSON.stringify(arg);
134165
+ } catch {
134166
+ return String(arg);
134167
+ }
134168
+ }
134169
+ return String(arg);
134170
+ }).join(" ");
134171
+ const match = stringified.match(/^\[([^\]]+)\]\s*(.*)$/s);
134172
+ if (match) {
134173
+ return { prefix: match[1], message: match[2] };
134174
+ }
134175
+ return { message: stringified };
134176
+ }
134177
+ function centerText(text, width, padChar = " ") {
134178
+ const visibleLen = visibleLength(text);
134179
+ const padding = Math.max(0, width - visibleLen);
134180
+ const leftPad = Math.floor(padding / 2);
134181
+ const rightPad = padding - leftPad;
134182
+ return padChar.repeat(leftPad) + text + padChar.repeat(rightPad);
134183
+ }
134184
+ function isTTYAvailable() {
134185
+ return Boolean(process.stdout.isTTY && process.stdin.isTTY);
134186
+ }
134187
+ var MAX_LOG_ENTRIES, LogRingBuffer, SECTION_ORDER, DashboardTUI, DashboardLogSink;
134188
+ var init_dashboard_tui = __esm({
134189
+ "src/commands/dashboard-tui.ts"() {
134190
+ "use strict";
134191
+ MAX_LOG_ENTRIES = 1e3;
134192
+ LogRingBuffer = class {
134193
+ entries = [];
134194
+ count = 0;
134195
+ push(entry) {
134196
+ if (this.entries.length < MAX_LOG_ENTRIES) {
134197
+ this.entries.push(entry);
134198
+ } else {
134199
+ this.entries[this.count % MAX_LOG_ENTRIES] = entry;
134200
+ }
134201
+ this.count++;
134202
+ }
134203
+ getAll() {
134204
+ if (this.count <= MAX_LOG_ENTRIES) {
134205
+ return this.entries.slice();
134206
+ }
134207
+ const start = this.count % MAX_LOG_ENTRIES;
134208
+ return [
134209
+ ...this.entries.slice(start),
134210
+ ...this.entries.slice(0, start)
134211
+ ];
134212
+ }
134213
+ clear() {
134214
+ this.entries = [];
134215
+ this.count = 0;
134216
+ }
134217
+ get total() {
134218
+ return this.count;
134219
+ }
134220
+ };
134221
+ SECTION_ORDER = ["system", "logs", "utilities", "stats", "settings"];
134222
+ DashboardTUI = class {
134223
+ activeSection = "system";
134224
+ logBuffer;
134225
+ systemInfo = null;
134226
+ taskStats = null;
134227
+ settings = null;
134228
+ callbacks = null;
134229
+ isRunning = false;
134230
+ rl = null;
134231
+ originalHandlers = /* @__PURE__ */ new Map();
134232
+ lastRenderHeight = 0;
134233
+ showHelp = false;
134234
+ uptimeTimer = null;
134235
+ resizeHandler = null;
134236
+ // Logs interaction state
134237
+ selectedLogIndex = 0;
134238
+ logsViewportStart = 0;
134239
+ logsWrapEnabled = false;
134240
+ logsExpandedMode = false;
134241
+ constructor() {
134242
+ this.logBuffer = new LogRingBuffer();
134243
+ }
134244
+ // ── Public API ─────────────────────────────────────────────────────────────
134245
+ /** Returns whether the TUI is currently running */
134246
+ get running() {
134247
+ return this.isRunning;
134248
+ }
134249
+ setCallbacks(callbacks) {
134250
+ this.callbacks = callbacks;
134251
+ }
134252
+ setSystemInfo(info) {
134253
+ this.systemInfo = info;
134254
+ this.render();
134255
+ }
134256
+ setTaskStats(stats) {
134257
+ this.taskStats = stats;
134258
+ this.render();
134259
+ }
134260
+ setSettings(settings) {
134261
+ this.settings = settings;
134262
+ this.render();
134263
+ }
134264
+ addLog(entry) {
134265
+ this.logBuffer.push({
134266
+ ...entry,
134267
+ timestamp: /* @__PURE__ */ new Date()
134268
+ });
134269
+ const newLength = this.logBuffer.getAll().length;
134270
+ if (this.selectedLogIndex >= newLength) {
134271
+ this.selectedLogIndex = Math.max(0, newLength - 1);
134272
+ }
134273
+ this.render();
134274
+ }
134275
+ /**
134276
+ * Clear logs and reset selection state.
134277
+ */
134278
+ clearLogs() {
134279
+ this.logBuffer.clear();
134280
+ this.selectedLogIndex = 0;
134281
+ this.logsViewportStart = 0;
134282
+ this.logsExpandedMode = false;
134283
+ }
134284
+ log(message, prefix) {
134285
+ this.addLog({ level: "info", message, prefix });
134286
+ }
134287
+ warn(message, prefix) {
134288
+ this.addLog({ level: "warn", message, prefix });
134289
+ }
134290
+ error(message, prefix) {
134291
+ this.addLog({ level: "error", message, prefix });
134292
+ }
134293
+ async start() {
134294
+ if (this.isRunning) return;
134295
+ this.isRunning = true;
134296
+ enableAlternateScreen();
134297
+ hideCursor();
134298
+ this.saveSignalHandlers();
134299
+ this.rl = readline.createInterface({
134300
+ input: process.stdin,
134301
+ output: process.stdout,
134302
+ terminal: true
134303
+ });
134304
+ process.stdin.setRawMode?.(true);
134305
+ process.stdin.resume();
134306
+ process.stdin.setEncoding("utf8");
134307
+ readline.emitKeypressEvents(process.stdin);
134308
+ process.stdin.on("keypress", (str, key) => {
134309
+ if (str) {
134310
+ this.handleKeypress(str);
134311
+ } else if (key.ctrl && key.name === "c") {
134312
+ this.handleKeypress("");
134313
+ } else if (key.name === "return" || key.name === "enter") {
134314
+ this.handleKeypress("\r");
134315
+ } else if (key.name === "right") {
134316
+ this.handleKeypress("\x1B[C");
134317
+ } else if (key.name === "left") {
134318
+ this.handleKeypress("\x1B[D");
134319
+ } else if (key.name === "up") {
134320
+ this.handleKeypress("\x1B[A");
134321
+ } else if (key.name === "down") {
134322
+ this.handleKeypress("\x1B[B");
134323
+ } else if (key.name === "escape") {
134324
+ this.handleKeypress("\x1B");
134325
+ } else if (key.name === "home") {
134326
+ this.handleKeypress("Home");
134327
+ } else if (key.name === "end") {
134328
+ this.handleKeypress("End");
134329
+ } else if (key.name === "space") {
134330
+ this.handleKeypress(" ");
134331
+ }
134332
+ });
134333
+ this.uptimeTimer = setInterval(() => {
134334
+ if (this.isRunning) {
134335
+ this.renderFooter();
134336
+ }
134337
+ }, 5e3);
134338
+ this.resizeHandler = () => {
134339
+ if (this.isRunning) {
134340
+ this.render();
134341
+ }
134342
+ };
134343
+ process.stdout.on("resize", this.resizeHandler);
134344
+ this.render();
134345
+ }
134346
+ async stop() {
134347
+ if (!this.isRunning) return;
134348
+ this.isRunning = false;
134349
+ if (this.uptimeTimer) {
134350
+ clearInterval(this.uptimeTimer);
134351
+ this.uptimeTimer = null;
134352
+ }
134353
+ if (this.resizeHandler) {
134354
+ process.stdout.off("resize", this.resizeHandler);
134355
+ this.resizeHandler = null;
134356
+ }
134357
+ this.restoreTerminal();
134358
+ this.restoreSignalHandlers();
134359
+ if (this.rl) {
134360
+ this.rl.close();
134361
+ this.rl = null;
134362
+ }
134363
+ }
134364
+ // ── Private: Signal Handling ───────────────────────────────────────────────
134365
+ saveSignalHandlers() {
134366
+ const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
134367
+ for (const sig of signals) {
134368
+ const listeners = process.listeners(sig);
134369
+ if (listeners.length > 0) {
134370
+ this.originalHandlers.set(sig, listeners[listeners.length - 1]);
134371
+ }
134372
+ }
134373
+ }
134374
+ restoreSignalHandlers() {
134375
+ for (const [sig, handler] of this.originalHandlers) {
134376
+ process.on(sig, handler);
134377
+ }
134378
+ this.originalHandlers.clear();
134379
+ }
134380
+ // ── Private: Terminal Restoration ────────────────────────────────────────
134381
+ restoreTerminal() {
134382
+ showCursor();
134383
+ disableAlternateScreen();
134384
+ process.stdout.write("\n");
134385
+ process.stdin.pause?.();
134386
+ process.stdin.setRawMode?.(false);
134387
+ }
134388
+ // ── Private: Key Handling ────────────────────────────────────────────────
134389
+ handleKeypress(key) {
134390
+ if (key === "") {
134391
+ void this.stop();
134392
+ process.exit(0);
134393
+ return;
134394
+ }
134395
+ if (key === "q" || key === "Q") {
134396
+ void this.stop();
134397
+ process.exit(0);
134398
+ return;
134399
+ }
134400
+ if (key === "?" || key === "h" || key === "H") {
134401
+ this.showHelp = !this.showHelp;
134402
+ this.render();
134403
+ return;
134404
+ }
134405
+ if (key >= "1" && key <= "5") {
134406
+ const index2 = parseInt(key, 10) - 1;
134407
+ if (index2 >= 0 && index2 < SECTION_ORDER.length) {
134408
+ this.activeSection = SECTION_ORDER[index2];
134409
+ this.showHelp = false;
134410
+ this.render();
134411
+ }
134412
+ return;
134413
+ }
134414
+ if (key === "\x1B[C" || key === "n" || key === "N") {
134415
+ const currentIndex = SECTION_ORDER.indexOf(this.activeSection);
134416
+ this.activeSection = SECTION_ORDER[(currentIndex + 1) % SECTION_ORDER.length];
134417
+ this.showHelp = false;
134418
+ this.render();
134419
+ return;
134420
+ }
134421
+ if (key === "\x1B[D" || key === "p" || key === "P") {
134422
+ const currentIndex = SECTION_ORDER.indexOf(this.activeSection);
134423
+ this.activeSection = SECTION_ORDER[(currentIndex - 1 + SECTION_ORDER.length) % SECTION_ORDER.length];
134424
+ this.showHelp = false;
134425
+ this.render();
134426
+ return;
134427
+ }
134428
+ if (this.activeSection === "utilities") {
134429
+ this.handleUtilityKeypress(key);
134430
+ return;
134431
+ }
134432
+ if (this.activeSection === "logs") {
134433
+ this.handleLogsKeypress(key);
134434
+ return;
134435
+ }
134436
+ }
134437
+ handleLogsKeypress(key) {
134438
+ const entries = this.logBuffer.getAll();
134439
+ const maxIndex = Math.max(0, entries.length - 1);
134440
+ if (key === "\x1B") {
134441
+ if (this.logsExpandedMode) {
134442
+ this.logsExpandedMode = false;
134443
+ this.showHelp = false;
134444
+ this.render();
134445
+ } else if (this.showHelp) {
134446
+ this.showHelp = false;
134447
+ this.render();
134448
+ }
134449
+ return;
134450
+ }
134451
+ if (key === "\r") {
134452
+ if (entries.length === 0) {
134453
+ return;
134454
+ }
134455
+ this.logsExpandedMode = !this.logsExpandedMode;
134456
+ this.render();
134457
+ return;
134458
+ }
134459
+ if (key === "w" || key === "W") {
134460
+ this.logsWrapEnabled = !this.logsWrapEnabled;
134461
+ this.render();
134462
+ return;
134463
+ }
134464
+ if (key === "\x1B[A" || key === "k" || key === "K") {
134465
+ if (entries.length === 0) return;
134466
+ if (this.selectedLogIndex > 0) {
134467
+ this.selectedLogIndex--;
134468
+ this.render();
134469
+ }
134470
+ return;
134471
+ }
134472
+ if (key === "\x1B[B" || key === "j" || key === "J") {
134473
+ if (entries.length === 0) return;
134474
+ if (this.selectedLogIndex < maxIndex) {
134475
+ this.selectedLogIndex++;
134476
+ this.render();
134477
+ }
134478
+ return;
134479
+ }
134480
+ if (key === "Home") {
134481
+ if (entries.length === 0) return;
134482
+ if (this.selectedLogIndex !== 0) {
134483
+ this.selectedLogIndex = 0;
134484
+ this.render();
134485
+ }
134486
+ return;
134487
+ }
134488
+ if (key === "End") {
134489
+ if (entries.length === 0) return;
134490
+ if (this.selectedLogIndex !== maxIndex) {
134491
+ this.selectedLogIndex = maxIndex;
134492
+ this.render();
134493
+ }
134494
+ return;
134495
+ }
134496
+ if (key === " " || key === "e" || key === "E") {
134497
+ if (entries.length === 0) {
134498
+ return;
134499
+ }
134500
+ this.logsExpandedMode = !this.logsExpandedMode;
134501
+ this.render();
134502
+ return;
134503
+ }
134504
+ }
134505
+ async handleUtilityKeypress(key) {
134506
+ if (!this.callbacks) return;
134507
+ switch (key.toLowerCase()) {
134508
+ case "r":
134509
+ await this.callbacks.onRefreshStats();
134510
+ break;
134511
+ case "c":
134512
+ this.callbacks.onClearLogs();
134513
+ this.clearLogs();
134514
+ break;
134515
+ case "t":
134516
+ if (this.systemInfo) {
134517
+ const newPaused = this.systemInfo.engineMode !== "paused";
134518
+ const newSettings = await this.callbacks.onTogglePause(newPaused);
134519
+ const newEngineMode = newSettings.enginePaused ? "paused" : "active";
134520
+ this.setSystemInfo({ ...this.systemInfo, engineMode: newEngineMode });
134521
+ this.setSettings(newSettings);
134522
+ }
134523
+ break;
134524
+ }
134525
+ }
134526
+ // ── Private: Rendering ───────────────────────────────────────────────────
134527
+ render() {
134528
+ if (!this.isRunning) return;
134529
+ clearScreen();
134530
+ moveCursorTo(1, 1);
134531
+ this.renderHeader();
134532
+ this.renderSection();
134533
+ this.renderFooter();
134534
+ if (this.showHelp) {
134535
+ this.renderHelpOverlay();
134536
+ }
134537
+ }
134538
+ renderHeader() {
134539
+ const cols = process.stdout.columns || 80;
134540
+ const title = colorize(" fusion ", "cyan");
134541
+ const titleLen = visibleLength(title);
134542
+ process.stdout.write(title);
134543
+ if (cols >= 70) {
134544
+ for (let i = 0; i < SECTION_ORDER.length; i++) {
134545
+ const section = SECTION_ORDER[i];
134546
+ const isActive = section === this.activeSection;
134547
+ const num = (i + 1).toString();
134548
+ const label = section.charAt(0).toUpperCase() + section.slice(1);
134549
+ const tabText = `[${num}] ${label}`;
134550
+ const style = isActive ? "brightBlue" : "dim";
134551
+ process.stdout.write(colorize(` ${tabText} `, style));
134552
+ }
134553
+ } else if (cols >= 40) {
134554
+ const shortLabels = {
134555
+ logs: "L",
134556
+ system: "S",
134557
+ utilities: "U",
134558
+ stats: "St",
134559
+ settings: "Se"
134560
+ };
134561
+ for (let i = 0; i < SECTION_ORDER.length; i++) {
134562
+ const section = SECTION_ORDER[i];
134563
+ const isActive = section === this.activeSection;
134564
+ const num = (i + 1).toString();
134565
+ const shortLabel = shortLabels[section];
134566
+ const tabText = `[${num}]${shortLabel}`;
134567
+ const style = isActive ? "brightBlue" : "dim";
134568
+ process.stdout.write(colorize(` ${tabText} `, style));
134569
+ }
134570
+ } else {
134571
+ const activeIndex = SECTION_ORDER.indexOf(this.activeSection);
134572
+ const activeLabel = this.activeSection.charAt(0).toUpperCase() + this.activeSection.slice(1);
134573
+ process.stdout.write(colorize(` [${activeIndex + 1}]${activeLabel} `, "brightBlue"));
134574
+ process.stdout.write(colorize(" [n/p]nav ", "dim"));
134575
+ }
134576
+ const tabsLength = SECTION_ORDER.reduce((acc, s, i) => {
134577
+ let label;
134578
+ if (cols >= 70) {
134579
+ label = s.charAt(0).toUpperCase() + s.slice(1);
134580
+ } else if (cols >= 40) {
134581
+ const shortLabels = {
134582
+ logs: "L",
134583
+ system: "S",
134584
+ utilities: "U",
134585
+ stats: "St",
134586
+ settings: "Se"
134587
+ };
134588
+ label = shortLabels[s];
134589
+ } else {
134590
+ label = s.charAt(0).toUpperCase() + s.slice(1);
134591
+ }
134592
+ const tabText = `[${i + 1}]${label} `;
134593
+ return acc + tabText.length;
134594
+ }, 0);
134595
+ const headerLen = titleLen + tabsLength;
134596
+ const remaining = cols - headerLen;
134597
+ if (remaining > 0) {
134598
+ process.stdout.write(" ".repeat(remaining));
134599
+ }
134600
+ process.stdout.write("\n");
134601
+ process.stdout.write(colorize("\u2500".repeat(Math.max(20, cols)), "dim") + "\n");
134602
+ }
134603
+ renderSection() {
134604
+ switch (this.activeSection) {
134605
+ case "logs":
134606
+ this.renderLogsSection();
134607
+ break;
134608
+ case "system":
134609
+ this.renderSystemSection();
134610
+ break;
134611
+ case "utilities":
134612
+ this.renderUtilitiesSection();
134613
+ break;
134614
+ case "stats":
134615
+ this.renderStatsSection();
134616
+ break;
134617
+ case "settings":
134618
+ this.renderSettingsSection();
134619
+ break;
134620
+ }
134621
+ }
134622
+ getLogViewportStart(totalEntries, maxRows) {
134623
+ if (totalEntries <= 0) {
134624
+ this.logsViewportStart = 0;
134625
+ return 0;
134626
+ }
134627
+ const maxStart = Math.max(0, totalEntries - maxRows);
134628
+ let start = Math.min(this.logsViewportStart, maxStart);
134629
+ if (this.selectedLogIndex < start) {
134630
+ start = this.selectedLogIndex;
134631
+ } else if (this.selectedLogIndex >= start + maxRows) {
134632
+ start = this.selectedLogIndex - maxRows + 1;
134633
+ }
134634
+ this.logsViewportStart = Math.max(0, Math.min(start, maxStart));
134635
+ return this.logsViewportStart;
134636
+ }
134637
+ renderLogsSection() {
134638
+ const cols = process.stdout.columns || 80;
134639
+ const entries = this.logBuffer.getAll();
134640
+ const maxRows = Math.max(1, (process.stdout.rows ?? 38) - 9);
134641
+ process.stdout.write(colorize("\n LOGS\n", "bold"));
134642
+ process.stdout.write(colorize(` Ring buffer: ${this.logBuffer.total}/${MAX_LOG_ENTRIES} entries
134643
+ `, "dim"));
134644
+ if (entries.length === 0) {
134645
+ process.stdout.write(colorize(" No log entries yet.\n", "dim"));
134646
+ return;
134647
+ }
134648
+ const safeSelectedIndex = Math.min(this.selectedLogIndex, Math.max(0, entries.length - 1));
134649
+ if (safeSelectedIndex !== this.selectedLogIndex) {
134650
+ this.selectedLogIndex = safeSelectedIndex;
134651
+ }
134652
+ if (this.logsExpandedMode) {
134653
+ this.renderLogsExpandedPane(entries[safeSelectedIndex], safeSelectedIndex, entries.length);
134654
+ return;
134655
+ }
134656
+ const modeIndicator = this.logsWrapEnabled ? colorize(" [w] wrap on", "dim") : colorize(" [w] wrap off", "dim");
134657
+ process.stdout.write(modeIndicator + "\n\n");
134658
+ const startIndex = this.getLogViewportStart(entries.length, maxRows);
134659
+ const visibleEntries = entries.slice(startIndex, startIndex + maxRows);
134660
+ const visibleReversed = [...visibleEntries].reverse();
134661
+ const selectedDisplayIndex = safeSelectedIndex >= startIndex && safeSelectedIndex < startIndex + visibleEntries.length ? visibleEntries.length - 1 - (safeSelectedIndex - startIndex) : -1;
134662
+ const prefixLen = 30;
134663
+ const availableWidth = Math.max(8, cols - prefixLen);
134664
+ for (let displayIdx = 0; displayIdx < visibleReversed.length; displayIdx++) {
134665
+ const entry = visibleReversed[displayIdx];
134666
+ const isSelected = displayIdx === selectedDisplayIndex;
134667
+ const selector = isSelected ? colorize("\u25B8 ", "brightGreen") : " ";
134668
+ const ts = colorize(formatTimestamp3(entry.timestamp), "dim");
134669
+ const prefix = entry.prefix ? colorize(`[${entry.prefix}]`, "gray") : "";
134670
+ const levelChar = entry.level === "error" ? colorize("\u2717", "brightRed") : entry.level === "warn" ? colorize("\u26A0", "brightYellow") : colorize("\u2713", "brightGreen");
134671
+ if (this.logsWrapEnabled) {
134672
+ const wrappedLines = this.wrapText(entry.message, availableWidth);
134673
+ const firstLine = `${selector}${ts} ${levelChar} ${prefix ? prefix + " " : ""}${wrappedLines[0]}`;
134674
+ process.stdout.write(visibleTruncate(firstLine, cols - 1) + "\n");
134675
+ for (let i = 1; i < wrappedLines.length; i++) {
134676
+ const continuation = ` ${wrappedLines[i]}`;
134677
+ process.stdout.write(visibleTruncate(continuation, cols - 1) + "\n");
134678
+ }
134679
+ } else {
134680
+ const messageWidth = Math.max(8, cols - prefixLen);
134681
+ const message = visibleTruncate(entry.message, messageWidth);
134682
+ const line = `${selector}${ts} ${levelChar} ${prefix ? prefix + " " : ""}${message}`;
134683
+ process.stdout.write(visibleTruncate(line, cols - 1) + "\n");
134684
+ }
134685
+ }
134686
+ }
134687
+ /**
134688
+ * Render the expanded log entry detail pane.
134689
+ * Replaces the normal list view with a focused view of a single entry.
134690
+ */
134691
+ renderLogsExpandedPane(entry, index2, total) {
134692
+ const cols = process.stdout.columns || 80;
134693
+ const rows = process.stdout.rows ?? 24;
134694
+ const maxContentRows = Math.max(1, rows - 12);
134695
+ process.stdout.write(colorize(" EXPANDED LOG ENTRY\n", "bold"));
134696
+ const navHint = colorize(` Entry ${index2 + 1} of ${total} | [\u2191/k] older [\u2193/j] newer [Enter/Esc] close
134697
+ `, "dim");
134698
+ process.stdout.write(navHint);
134699
+ process.stdout.write(colorize(" " + "\u2500".repeat(Math.max(20, cols - 4)) + "\n", "dim"));
134700
+ const ts = formatTimestamp3(entry.timestamp);
134701
+ const levelLabel = entry.level === "error" ? colorize("ERROR", "brightRed") : entry.level === "warn" ? colorize("WARN", "brightYellow") : colorize("INFO", "brightGreen");
134702
+ process.stdout.write(colorize(` Timestamp: `, "gray") + colorize(ts, "white") + "\n");
134703
+ process.stdout.write(colorize(` Level: `, "gray") + levelLabel + "\n");
134704
+ if (entry.prefix) {
134705
+ process.stdout.write(colorize(` Prefix: `, "gray") + colorize(entry.prefix, "dim") + "\n");
134706
+ }
134707
+ process.stdout.write("\n");
134708
+ process.stdout.write(colorize(" MESSAGE\n", "bold"));
134709
+ const messageIndent = " ";
134710
+ const availableWidth = Math.max(8, cols - messageIndent.length);
134711
+ const wrappedMessage = this.wrapText(entry.message, availableWidth);
134712
+ let linesPrinted = 5;
134713
+ for (const line of wrappedMessage) {
134714
+ if (linesPrinted >= maxContentRows) {
134715
+ process.stdout.write(colorize(`
134716
+ ... (truncated)
134717
+ `, "dim"));
134718
+ break;
134719
+ }
134720
+ process.stdout.write(messageIndent + line + "\n");
134721
+ linesPrinted++;
134722
+ }
134723
+ const footerHint = colorize(`
134724
+ [Esc] or [Enter] to close expanded view
134725
+ `, "dim");
134726
+ process.stdout.write(footerHint);
134727
+ }
134728
+ /**
134729
+ * Wrap text to fit within available width, returning an array of lines.
134730
+ * Respects ANSI escape sequences via visibleLength.
134731
+ */
134732
+ wrapText(text, maxWidth) {
134733
+ if (maxWidth <= 0) return [""];
134734
+ if (visibleLength(text) <= maxWidth) return [text];
134735
+ const lines = [];
134736
+ let remaining = text;
134737
+ while (visibleLength(remaining) > maxWidth) {
134738
+ let breakIdx = 0;
134739
+ for (let i = 0; i < remaining.length; i++) {
134740
+ const char = remaining[i];
134741
+ if (char === " " || char === " ") {
134742
+ if (visibleLength(remaining.substring(0, i)) <= maxWidth) {
134743
+ breakIdx = i;
134744
+ }
134745
+ }
134746
+ if (visibleLength(remaining.substring(0, i + 1)) > maxWidth) {
134747
+ break;
134748
+ }
134749
+ }
134750
+ if (breakIdx === 0) {
134751
+ const firstTokenMatch = remaining.match(/^(\S+)/);
134752
+ if (firstTokenMatch) {
134753
+ const firstToken = firstTokenMatch[1];
134754
+ if (visibleLength(firstToken) > maxWidth) {
134755
+ const chunkSize = Math.max(1, maxWidth - 1);
134756
+ let chunkStart = 0;
134757
+ while (chunkStart < firstToken.length) {
134758
+ const chunk = firstToken.substring(chunkStart, chunkStart + chunkSize);
134759
+ lines.push(chunk);
134760
+ chunkStart += chunkSize;
134761
+ }
134762
+ remaining = remaining.substring(firstToken.length).trimStart();
134763
+ continue;
134764
+ }
134765
+ }
134766
+ breakIdx = Math.min(maxWidth, remaining.length);
134767
+ }
134768
+ lines.push(remaining.substring(0, breakIdx).trimEnd());
134769
+ remaining = remaining.substring(breakIdx).trimStart();
134770
+ }
134771
+ if (remaining.length > 0) {
134772
+ lines.push(remaining);
134773
+ }
134774
+ return lines.length > 0 ? lines : [""];
134775
+ }
134776
+ renderSystemSection() {
134777
+ if (!this.systemInfo) {
134778
+ process.stdout.write(colorize("\n System information not available.\n", "dim"));
134779
+ return;
134780
+ }
134781
+ const cols = process.stdout.columns || 80;
134782
+ const info = this.systemInfo;
134783
+ const rows = [];
134784
+ rows.push(colorize("\n SYSTEM INFORMATION\n", "bold"));
134785
+ rows.push("");
134786
+ const labelWidth = 12;
134787
+ const availableValueWidth = Math.max(8, cols - labelWidth - 1);
134788
+ rows.push(` ${colorize("Host:", "white")} ${info.host}`);
134789
+ rows.push(` ${colorize("Port:", "white")} ${info.port}`);
134790
+ rows.push(` ${colorize("URL:", "white")} ${colorize(visibleTruncate(info.baseUrl, availableValueWidth), "brightCyan")}`);
134791
+ rows.push("");
134792
+ if (info.authEnabled) {
134793
+ rows.push(` ${colorize("Auth:", "white")} ${colorize("bearer token required", "yellow")}`);
134794
+ if (info.authToken) {
134795
+ rows.push(` ${colorize("Token:", "white")} ${visibleTruncate(info.authToken, availableValueWidth)}`);
134796
+ }
134797
+ if (info.tokenizedUrl) {
134798
+ rows.push(` ${colorize("Open:", "white")} ${visibleTruncate(info.tokenizedUrl, availableValueWidth)}`);
134799
+ rows.push(colorize(" (browser stores token, click once)", "dim"));
134800
+ }
134801
+ } else {
134802
+ rows.push(` ${colorize("Auth:", "white")} ${colorize("disabled (--no-auth)", "dim")}`);
134803
+ }
134804
+ rows.push("");
134805
+ rows.push(` ${colorize("AI Engine:", "white")} ${info.engineMode === "dev" ? colorize("\u2717 disabled (dev mode)", "yellow") : info.engineMode === "paused" ? colorize("\u23F8 paused", "brightYellow") : colorize("\u2713 active", "brightGreen")}`);
134806
+ rows.push(` ${colorize("File Watcher:", "white")} ${info.fileWatcher ? colorize("\u2713 active", "brightGreen") : colorize("\u2717 inactive", "brightRed")}`);
134807
+ rows.push(` ${colorize("Uptime:", "white")} ${formatUptime(Date.now() - info.startTimeMs)}`);
134808
+ for (const row of rows) {
134809
+ process.stdout.write(visibleTruncate(row, cols) + "\n");
134810
+ }
134811
+ }
134812
+ renderUtilitiesSection() {
134813
+ const cols = process.stdout.columns || 80;
134814
+ const actions = [
134815
+ { id: "refresh", label: "Refresh Stats", key: "r", description: "Re-fetch task and agent counts" },
134816
+ { id: "clear", label: "Clear Logs", key: "c", description: "Clear the log ring buffer" },
134817
+ { id: "pause", label: "Toggle Engine Pause", key: "t", description: "Pause/resume AI engine automation" },
134818
+ { id: "help", label: "Help", key: "?", description: "Show keyboard shortcuts" }
134819
+ ];
134820
+ process.stdout.write(colorize("\n UTILITIES\n", "bold"));
134821
+ process.stdout.write(colorize(" Press key to execute action\n\n", "dim"));
134822
+ const prefixWidth = 2 + 3 + 1 + 20 + 3;
134823
+ const descriptionWidth = Math.max(8, cols - prefixWidth - 1);
134824
+ for (const action of actions) {
134825
+ const keyDisplay = colorize(`[${action.key}]`, "brightYellow");
134826
+ const label = colorize(action.label.padEnd(20), "white");
134827
+ const description = visibleTruncate(action.description, descriptionWidth);
134828
+ const line = ` ${keyDisplay} ${label} - ${description}`;
134829
+ process.stdout.write(visibleTruncate(line, cols - 1) + "\n");
134830
+ }
134831
+ }
134832
+ renderStatsSection() {
134833
+ const cols = process.stdout.columns || 80;
134834
+ if (!this.taskStats) {
134835
+ process.stdout.write(colorize("\n Statistics not available.\n", "dim"));
134836
+ return;
134837
+ }
134838
+ const stats = this.taskStats;
134839
+ const rows = [];
134840
+ rows.push(colorize("\n STATISTICS\n", "bold"));
134841
+ rows.push("");
134842
+ rows.push(` ${colorize("Total Tasks:", "white")} ${stats.total}`);
134843
+ rows.push("");
134844
+ rows.push(` ${colorize("By Column:", "dim")}`);
134845
+ for (const [column, count] of Object.entries(stats.byColumn)) {
134846
+ const colName = column.replace("-", " ").replace(/\b\w/g, (l) => l.toUpperCase());
134847
+ const activeMark = (column === "in-progress" || column === "in-review") && count > 0 ? colorize(" \u25CF", "brightGreen") : "";
134848
+ rows.push(` ${colName}: ${count}${activeMark}`);
134849
+ }
134850
+ rows.push("");
134851
+ rows.push(` ${colorize("Active Tasks:", "white")} ${stats.active} (in-progress + in-review)`);
134852
+ rows.push("");
134853
+ rows.push(` ${colorize("Agents:", "dim")}`);
134854
+ rows.push(` Idle: ${stats.agents.idle}`);
134855
+ rows.push(` Active: ${stats.agents.active}`);
134856
+ rows.push(` Running: ${stats.agents.running}`);
134857
+ rows.push(` Error: ${stats.agents.error}`);
134858
+ for (const row of rows) {
134859
+ process.stdout.write(visibleTruncate(row, cols) + "\n");
134860
+ }
134861
+ }
134862
+ renderSettingsSection() {
134863
+ const cols = process.stdout.columns || 80;
134864
+ if (!this.settings) {
134865
+ process.stdout.write(colorize("\n Settings not available.\n", "dim"));
134866
+ return;
134867
+ }
134868
+ const s = this.settings;
134869
+ const rows = [];
134870
+ rows.push(colorize("\n SETTINGS\n", "bold"));
134871
+ rows.push("");
134872
+ const settingsList = [
134873
+ ["maxConcurrent", s.maxConcurrent.toString()],
134874
+ ["maxWorktrees", s.maxWorktrees.toString()],
134875
+ ["autoMerge", s.autoMerge ? "enabled" : "disabled"],
134876
+ ["mergeStrategy", s.mergeStrategy],
134877
+ ["pollIntervalMs", `${s.pollIntervalMs}ms`],
134878
+ ["enginePaused", s.enginePaused ? "yes" : "no"],
134879
+ ["globalPause", s.globalPause ? "yes" : "no"]
134880
+ ];
134881
+ const keyWidth = Math.max(...settingsList.map(([k]) => k.length));
134882
+ for (const [key, value] of settingsList) {
134883
+ const keyPad = key.padEnd(keyWidth);
134884
+ const isEnabled = value === "enabled" || value === "yes";
134885
+ const isDisabled = value === "disabled" || value === "no";
134886
+ const valueColor = isEnabled ? "brightGreen" : isDisabled ? "brightYellow" : "white";
134887
+ rows.push(` ${colorize(keyPad, "gray")} ${colorize(value, valueColor)}`);
134888
+ }
134889
+ for (const row of rows) {
134890
+ process.stdout.write(visibleTruncate(row, cols) + "\n");
134891
+ }
134892
+ }
134893
+ renderFooter() {
134894
+ const cols = process.stdout.columns || 80;
134895
+ const footerY = Math.max(1, (process.stdout.rows ?? 22) - 2);
134896
+ moveCursorTo(1, footerY);
134897
+ clearLine();
134898
+ const status = this.systemInfo ? `${this.systemInfo.baseUrl} | ${formatUptime(Date.now() - this.systemInfo.startTimeMs)}` : "";
134899
+ const left = colorize("Press ? for help", "dim");
134900
+ const right = colorize(visibleTruncate(status, Math.max(20, cols - 20)), "dim");
134901
+ const leftLen = visibleLength(left);
134902
+ const rightLen = visibleLength(right);
134903
+ process.stdout.write(left);
134904
+ const padding = Math.max(1, cols - leftLen - rightLen - 2);
134905
+ process.stdout.write(" ".repeat(padding));
134906
+ process.stdout.write(right);
134907
+ process.stdout.write("\n");
134908
+ }
134909
+ /**
134910
+ * Build help overlay lines as an array of strings.
134911
+ * Uses dynamic visibleLength calculation to ensure consistent box width.
134912
+ * All box-drawing rows (including borders) have the same total visible width.
134913
+ *
134914
+ * @param boxWidth - The interior content width (excluding the two box characters)
134915
+ * @param useBoxDrawing - Whether to use box-drawing characters (true) or compact text (false)
134916
+ * @returns Array of help lines
134917
+ */
134918
+ buildHelpLines(boxWidth, useBoxDrawing) {
134919
+ if (useBoxDrawing) {
134920
+ const boxRow = (content) => {
134921
+ const padding = Math.max(0, boxWidth - visibleLength(content));
134922
+ return "\u2502" + content + " ".repeat(padding) + "\u2502";
134923
+ };
134924
+ return [
134925
+ "\u250C" + "\u2500".repeat(boxWidth) + "\u2510",
134926
+ boxRow(centerText("KEYBOARD SHORTCUTS", boxWidth, " ")),
134927
+ "\u251C" + "\u2500".repeat(boxWidth) + "\u2524",
134928
+ boxRow(" [1-5] Switch to tab by number"),
134929
+ boxRow(" [n] / \u2192 Next tab"),
134930
+ boxRow(" [p] / \u2190 Previous tab"),
134931
+ boxRow(" [r] Refresh stats (Utilities)"),
134932
+ boxRow(" [c] Clear logs (Utilities)"),
134933
+ boxRow(" [t] Toggle engine pause (Utilities)"),
134934
+ boxRow(" [\u2191/\u2193/k/j] Navigate log entries (Logs)"),
134935
+ boxRow(" [Home/End] First/last log entry (Logs)"),
134936
+ boxRow(" [Enter/Space/e] Expand log (Logs)"),
134937
+ boxRow(" [w] Toggle word wrap (Logs)"),
134938
+ boxRow(" [?] / [h] Toggle help"),
134939
+ boxRow(" [q] Quit"),
134940
+ boxRow(" [Ctrl+C] Force quit"),
134941
+ "\u2514" + "\u2500".repeat(boxWidth) + "\u2518"
134942
+ ];
134943
+ } else {
134944
+ return [
134945
+ "KEYBOARD SHORTCUTS",
134946
+ " [1-5] Switch tab | [n/p] Next/Prev | [q] Quit",
134947
+ " [\u2191\u2193/k/j] Navigate logs | [Home/End] First/Last (Logs)",
134948
+ " [Enter/Space/e] Expand log | [w] Toggle wrap (Logs)",
134949
+ " [r] Refresh | [c] Clear logs | [t] Toggle engine",
134950
+ " [?/h] Help | [Ctrl+C] Force quit"
134951
+ ];
134952
+ }
134953
+ }
134954
+ renderHelpOverlay() {
134955
+ const cols = process.stdout.columns || 80;
134956
+ const rows = process.stdout.rows || 24;
134957
+ const boxWidth = Math.min(62, Math.max(cols - 4, 20));
134958
+ const useBoxDrawing = cols >= boxWidth + 4;
134959
+ const rawHelpLines = this.buildHelpLines(boxWidth, useBoxDrawing);
134960
+ const compactBoxWidth = useBoxDrawing ? boxWidth : Math.max(...rawHelpLines.map(visibleLength));
134961
+ const boxHeight = rawHelpLines.length;
134962
+ const safeStartX = Math.max(1, Math.floor((cols - compactBoxWidth) / 2));
134963
+ const safeStartY = Math.max(1, Math.floor((rows - boxHeight) / 2));
134964
+ const clearTop = Math.max(1, safeStartY - 1);
134965
+ const clearBottom = Math.min(rows, safeStartY + boxHeight);
134966
+ for (let y = clearTop; y <= clearBottom; y++) {
134967
+ moveCursorTo(1, y);
134968
+ clearLine();
134969
+ }
134970
+ for (let i = 0; i < rawHelpLines.length; i++) {
134971
+ const color = i === 0 || i === 2 || i === rawHelpLines.length - 1 ? "brightBlue" : "white";
134972
+ moveCursorTo(safeStartX, safeStartY + i);
134973
+ process.stdout.write(colorize(rawHelpLines[i], color));
134974
+ }
134975
+ }
134976
+ };
134977
+ DashboardLogSink = class {
134978
+ tui = null;
134979
+ isTTY;
134980
+ originalConsole = null;
134981
+ constructor(tui) {
134982
+ this.tui = tui ?? null;
134983
+ this.isTTY = tui?.running ?? false;
134984
+ }
134985
+ setTUI(tui) {
134986
+ this.tui = tui;
134987
+ this.isTTY = true;
134988
+ }
134989
+ log(message, prefix) {
134990
+ const line = prefix ? `[${prefix}] ${message}` : message;
134991
+ if (this.tui && this.isTTY) {
134992
+ this.tui.log(message, prefix);
134993
+ } else if (this.originalConsole) {
134994
+ this.originalConsole.log.call(console, line);
134995
+ } else {
134996
+ console.log(line);
134997
+ }
134998
+ }
134999
+ warn(message, prefix) {
135000
+ const line = prefix ? `[${prefix}] ${message}` : message;
135001
+ if (this.tui && this.isTTY) {
135002
+ this.tui.warn(message, prefix);
135003
+ } else if (this.originalConsole) {
135004
+ this.originalConsole.warn.call(console, line);
135005
+ } else {
135006
+ console.warn(line);
135007
+ }
135008
+ }
135009
+ error(message, prefix) {
135010
+ const line = prefix ? `[${prefix}] ${message}` : message;
135011
+ if (this.tui && this.isTTY) {
135012
+ this.tui.error(message, prefix);
135013
+ } else if (this.originalConsole) {
135014
+ this.originalConsole.error.call(console, line);
135015
+ } else {
135016
+ console.error(line);
135017
+ }
135018
+ }
135019
+ /**
135020
+ * Monkey-patch `console.log/warn/error` so everything (including the engine's
135021
+ * createLogger() output, which writes directly to console.error) surfaces in
135022
+ * the TUI's log ring buffer. Without this, most runtime logs render beneath
135023
+ * the alt-screen TUI and are immediately overwritten on the next render,
135024
+ * leaving the Logs tab nearly empty.
135025
+ *
135026
+ * Messages that start with `[prefix] rest` are unpacked so the TUI stores
135027
+ * `prefix="prefix"` and `message="rest"`. Idempotent; call `releaseConsole()`
135028
+ * on TUI shutdown to restore the originals.
135029
+ */
135030
+ captureConsole() {
135031
+ if (this.originalConsole) return;
135032
+ this.originalConsole = {
135033
+ log: console.log,
135034
+ warn: console.warn,
135035
+ error: console.error
135036
+ };
135037
+ console.log = (...args) => {
135038
+ const { message, prefix } = formatConsoleArgs(args);
135039
+ this.log(message, prefix);
135040
+ };
135041
+ console.warn = (...args) => {
135042
+ const { message, prefix } = formatConsoleArgs(args);
135043
+ this.warn(message, prefix);
135044
+ };
135045
+ console.error = (...args) => {
135046
+ const { message, prefix } = formatConsoleArgs(args);
135047
+ this.error(message, prefix);
135048
+ };
135049
+ }
135050
+ /** Restore console.log/warn/error to their pre-capture implementations. */
135051
+ releaseConsole() {
135052
+ if (!this.originalConsole) return;
135053
+ console.log = this.originalConsole.log;
135054
+ console.warn = this.originalConsole.warn;
135055
+ console.error = this.originalConsole.error;
135056
+ this.originalConsole = null;
135057
+ }
135058
+ };
135059
+ }
135060
+ });
135061
+
133741
135062
  // src/commands/dashboard.ts
133742
135063
  var dashboard_exports = {};
133743
135064
  __export(dashboard_exports, {
135065
+ StreamedLogBuffer: () => StreamedLogBuffer,
133744
135066
  promptForPort: () => promptForPort,
133745
135067
  runDashboard: () => runDashboard
133746
135068
  });
@@ -133753,7 +135075,7 @@ function formatBytes2(bytes) {
133753
135075
  if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
133754
135076
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
133755
135077
  }
133756
- function formatUptime(ms) {
135078
+ function formatUptime2(ms) {
133757
135079
  const seconds = Math.floor(ms / 1e3);
133758
135080
  const minutes = Math.floor(seconds / 60);
133759
135081
  const hours = Math.floor(minutes / 60);
@@ -133790,7 +135112,7 @@ function logDiagnostics(prefix, startTime, dbHealthCheck) {
133790
135112
  } catch {
133791
135113
  }
133792
135114
  }
133793
- const logLine = `[${prefix}] diagnostics: uptime=${formatUptime(uptime)} rss=${formatBytes2(mem.rss)} heap=${formatBytes2(mem.heapUsed)}/${formatBytes2(mem.heapTotal)} external=${formatBytes2(mem.external)} arrayBuffers=${formatBytes2(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
135115
+ const logLine = `[${prefix}] diagnostics: uptime=${formatUptime2(uptime)} rss=${formatBytes2(mem.rss)} heap=${formatBytes2(mem.heapUsed)}/${formatBytes2(mem.heapTotal)} external=${formatBytes2(mem.external)} arrayBuffers=${formatBytes2(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
133794
135116
  console.log(logLine);
133795
135117
  }
133796
135118
  function ensureProcessDiagnostics() {
@@ -133813,11 +135135,11 @@ function ensureProcessDiagnostics() {
133813
135135
  requestCount = process._getActiveRequests?.()?.length ?? -1;
133814
135136
  } catch {
133815
135137
  }
133816
- console.log(`[dashboard] beforeExit code=${code} uptime=${formatUptime(uptime)} handles=${handleCount} requests=${requestCount}`);
135138
+ console.log(`[dashboard] beforeExit code=${code} uptime=${formatUptime2(uptime)} handles=${handleCount} requests=${requestCount}`);
133817
135139
  });
133818
135140
  process.on("exit", (code) => {
133819
135141
  const uptime = Date.now() - diagnosticStartTime;
133820
- console.log(`[dashboard] exit code=${code} uptime=${formatUptime(uptime)}`);
135142
+ console.log(`[dashboard] exit code=${code} uptime=${formatUptime2(uptime)}`);
133821
135143
  });
133822
135144
  process.on("uncaughtExceptionMonitor", (error) => {
133823
135145
  console.error(`[dashboard] uncaught exception pid=${process.pid}: ${error.stack || error.message}`);
@@ -133863,7 +135185,74 @@ async function runDashboard(port, opts = {}) {
133863
135185
  }
133864
135186
  }
133865
135187
  const cwd = await resolveRuntimeProjectPath();
133866
- const store = new TaskStore(cwd);
135188
+ const isTTY = isTTYAvailable();
135189
+ let tui;
135190
+ const dashboardStartedAt = Date.now();
135191
+ let store;
135192
+ let agentStore;
135193
+ const logSink = new DashboardLogSink();
135194
+ if (isTTY) {
135195
+ tui = new DashboardTUI();
135196
+ tui.setCallbacks({
135197
+ onRefreshStats: async () => {
135198
+ if (store && agentStore) {
135199
+ const tasks = await store.listTasks({ slim: true, includeArchived: false });
135200
+ const counts = /* @__PURE__ */ new Map();
135201
+ for (const task of tasks) {
135202
+ counts.set(task.column, (counts.get(task.column) ?? 0) + 1);
135203
+ }
135204
+ const active = tasks.filter(
135205
+ (task) => task.column === "in-progress" || task.column === "in-review"
135206
+ ).length;
135207
+ const agents = await agentStore.listAgents();
135208
+ const agentStats = { idle: 0, active: 0, running: 0, error: 0 };
135209
+ for (const agent of agents) {
135210
+ const state = agent.state;
135211
+ if (state in agentStats) {
135212
+ agentStats[state]++;
135213
+ }
135214
+ }
135215
+ tui.setTaskStats({
135216
+ total: tasks.length,
135217
+ byColumn: Object.fromEntries(counts),
135218
+ active,
135219
+ agents: agentStats
135220
+ });
135221
+ }
135222
+ },
135223
+ onClearLogs: () => {
135224
+ },
135225
+ onTogglePause: async (paused) => {
135226
+ if (store) {
135227
+ await store.updateSettings({ enginePaused: paused });
135228
+ tui.log(`Engine ${paused ? "paused" : "resumed"}`);
135229
+ const fullSettings = await store.getSettings();
135230
+ return {
135231
+ maxConcurrent: fullSettings.maxConcurrent ?? 1,
135232
+ maxWorktrees: fullSettings.maxWorktrees ?? 2,
135233
+ autoMerge: fullSettings.autoMerge ?? false,
135234
+ mergeStrategy: fullSettings.mergeStrategy ?? "direct",
135235
+ pollIntervalMs: fullSettings.pollIntervalMs ?? 6e4,
135236
+ enginePaused: fullSettings.enginePaused ?? false,
135237
+ globalPause: fullSettings.globalPause ?? false
135238
+ };
135239
+ }
135240
+ return {
135241
+ maxConcurrent: 1,
135242
+ maxWorktrees: 2,
135243
+ autoMerge: false,
135244
+ mergeStrategy: "direct",
135245
+ pollIntervalMs: 6e4,
135246
+ enginePaused: paused,
135247
+ globalPause: false
135248
+ };
135249
+ }
135250
+ });
135251
+ await tui.start();
135252
+ logSink.setTUI(tui);
135253
+ logSink.captureConsole();
135254
+ }
135255
+ store = new TaskStore(cwd);
133867
135256
  await store.init();
133868
135257
  await store.watch();
133869
135258
  setDiagnosticDbHealthCheck(() => store.healthCheck());
@@ -133875,15 +135264,78 @@ async function runDashboard(port, opts = {}) {
133875
135264
  "settings:updated": store.listenerCount("settings:updated"),
133876
135265
  "agent:log": store.listenerCount("agent:log")
133877
135266
  }));
135267
+ let tuiRefreshPending = false;
135268
+ let tuiRefreshDebounceTimer = null;
135269
+ async function refreshTUIStats() {
135270
+ if (!tui || !isTTY) return;
135271
+ if (!store || !agentStore) return;
135272
+ if (tuiRefreshPending) return;
135273
+ tuiRefreshPending = true;
135274
+ try {
135275
+ const tasks = await store.listTasks({ slim: true, includeArchived: false });
135276
+ const counts = /* @__PURE__ */ new Map();
135277
+ for (const task of tasks) {
135278
+ counts.set(task.column, (counts.get(task.column) ?? 0) + 1);
135279
+ }
135280
+ const active = tasks.filter(
135281
+ (task) => task.column === "in-progress" || task.column === "in-review"
135282
+ ).length;
135283
+ const agents = await agentStore.listAgents();
135284
+ const agentStats = { idle: 0, active: 0, running: 0, error: 0 };
135285
+ for (const agent of agents) {
135286
+ const state = agent.state;
135287
+ if (state in agentStats) {
135288
+ agentStats[state]++;
135289
+ }
135290
+ }
135291
+ tui.setTaskStats({
135292
+ total: tasks.length,
135293
+ byColumn: Object.fromEntries(counts),
135294
+ active,
135295
+ agents: agentStats
135296
+ });
135297
+ } finally {
135298
+ tuiRefreshPending = false;
135299
+ }
135300
+ }
135301
+ async function refreshTUISettings() {
135302
+ if (!tui || !isTTY) return;
135303
+ if (!store) return;
135304
+ try {
135305
+ const settings = await store.getSettings();
135306
+ tui.setSettings({
135307
+ maxConcurrent: settings.maxConcurrent ?? 1,
135308
+ maxWorktrees: settings.maxWorktrees ?? 2,
135309
+ autoMerge: settings.autoMerge ?? false,
135310
+ mergeStrategy: settings.mergeStrategy ?? "direct",
135311
+ pollIntervalMs: settings.pollIntervalMs ?? 6e4,
135312
+ enginePaused: settings.enginePaused ?? false,
135313
+ globalPause: settings.globalPause ?? false
135314
+ });
135315
+ } catch {
135316
+ }
135317
+ }
135318
+ function scheduleStatsRefresh() {
135319
+ if (tuiRefreshDebounceTimer) {
135320
+ clearTimeout(tuiRefreshDebounceTimer);
135321
+ }
135322
+ tuiRefreshDebounceTimer = setTimeout(() => {
135323
+ void refreshTUIStats();
135324
+ }, 500);
135325
+ }
133878
135326
  const handlers = [];
133879
135327
  const disposeCallbacks = [];
133880
135328
  let disposed = false;
133881
135329
  let shutdownInProgress = false;
133882
- const dashboardStartedAt = Date.now();
133883
135330
  async function logShutdownDiagnostics(reason) {
133884
135331
  const uptimeSeconds = Math.round((Date.now() - dashboardStartedAt) / 1e3);
133885
135332
  let taskSummary = "tasks=unknown";
133886
135333
  try {
135334
+ if (!store) {
135335
+ taskSummary = "tasks=unavailable (store not initialized)";
135336
+ logSink.log(`shutdown requested reason=${reason} pid=${process.pid} ppid=${process.ppid} uptime=${uptimeSeconds}s ${taskSummary}`, "dashboard");
135337
+ return;
135338
+ }
133887
135339
  const tasks = await store.listTasks({ slim: true, includeArchived: false });
133888
135340
  const counts = /* @__PURE__ */ new Map();
133889
135341
  for (const task of tasks) {
@@ -133897,8 +135349,9 @@ async function runDashboard(port, opts = {}) {
133897
135349
  const message = error instanceof Error ? error.message : String(error);
133898
135350
  taskSummary = `tasks=unavailable (${message})`;
133899
135351
  }
133900
- console.log(
133901
- `[dashboard] shutdown requested reason=${reason} pid=${process.pid} ppid=${process.ppid} uptime=${uptimeSeconds}s ${taskSummary}`
135352
+ logSink.log(
135353
+ `shutdown requested reason=${reason} pid=${process.pid} ppid=${process.ppid} uptime=${uptimeSeconds}s ${taskSummary}`,
135354
+ "dashboard"
133902
135355
  );
133903
135356
  }
133904
135357
  function registerHandler(target, event, handler) {
@@ -133907,8 +135360,20 @@ async function runDashboard(port, opts = {}) {
133907
135360
  }
133908
135361
  const automationStore = new AutomationStore(cwd);
133909
135362
  await automationStore.init();
133910
- const agentStore = new AgentStore({ rootDir: store.getFusionDir() });
135363
+ agentStore = new AgentStore({ rootDir: store.getFusionDir() });
133911
135364
  await agentStore.init();
135365
+ if (tui && isTTY) {
135366
+ registerHandler(store, "task:created", scheduleStatsRefresh);
135367
+ registerHandler(store, "task:moved", scheduleStatsRefresh);
135368
+ registerHandler(store, "task:updated", scheduleStatsRefresh);
135369
+ registerHandler(store, "task:deleted", scheduleStatsRefresh);
135370
+ registerHandler(store, "settings:updated", () => {
135371
+ void refreshTUISettings();
135372
+ });
135373
+ registerHandler(agentStore, "agent:created", scheduleStatsRefresh);
135374
+ registerHandler(agentStore, "agent:updated", scheduleStatsRefresh);
135375
+ registerHandler(agentStore, "agent:deleted", scheduleStatsRefresh);
135376
+ }
133912
135377
  const pluginStore = new PluginStore(store.getFusionDir());
133913
135378
  await pluginStore.init();
133914
135379
  const pluginLoader = new PluginLoader({
@@ -133919,12 +135384,23 @@ async function runDashboard(port, opts = {}) {
133919
135384
  let triggerScheduler;
133920
135385
  if (opts.paused) {
133921
135386
  await store.updateSettings({ enginePaused: true });
133922
- console.log("[engine] Starting in paused mode \u2014 automation disabled");
135387
+ logSink.log("Starting in paused mode \u2014 automation disabled", "engine");
133923
135388
  }
133924
- const onMergeImpl = (taskId) => aiMergeTask(store, cwd, taskId, {
133925
- agentStore,
133926
- onAgentText: (delta) => process.stdout.write(delta)
133927
- });
135389
+ const onMergeImpl = async (taskId) => {
135390
+ const streamedMergeLog = new StreamedLogBuffer(
135391
+ (line) => logSink.log(line, "merge"),
135392
+ STREAM_LOG_FLUSH_IDLE_MS
135393
+ );
135394
+ try {
135395
+ return await aiMergeTask(store, cwd, taskId, {
135396
+ agentStore,
135397
+ onAgentText: (delta) => streamedMergeLog.push(delta)
135398
+ });
135399
+ } finally {
135400
+ streamedMergeLog.flush();
135401
+ streamedMergeLog.dispose();
135402
+ }
135403
+ };
133928
135404
  const onMerge = (taskId) => onMergeImpl(taskId);
133929
135405
  const missionAutopilotImpl = new MissionAutopilot(store, store.getMissionStore());
133930
135406
  const missionExecutionLoopImpl = new MissionExecutionLoop({
@@ -133964,14 +135440,14 @@ async function runDashboard(port, opts = {}) {
133964
135440
  join48(cwd, ".fusion", "disabled-auto-extension-discovery")
133965
135441
  );
133966
135442
  for (const { path: path4, error } of extensionsResult.errors) {
133967
- console.log(`[extensions] Failed to load ${path4}: ${error}`);
135443
+ logSink.log(`Failed to load ${path4}: ${error}`, "extensions");
133968
135444
  }
133969
135445
  for (const { name, config, extensionPath } of extensionsResult.runtime.pendingProviderRegistrations) {
133970
135446
  try {
133971
135447
  modelRegistry.registerProvider(name, config);
133972
135448
  } catch (error) {
133973
135449
  const message = error instanceof Error ? error.message : String(error);
133974
- console.log(`[extensions] Failed to register provider from ${extensionPath}: ${message}`);
135450
+ logSink.log(`Failed to register provider from ${extensionPath}: ${message}`, "extensions");
133975
135451
  }
133976
135452
  }
133977
135453
  extensionsResult.runtime.pendingProviderRegistrations = [];
@@ -134011,15 +135487,15 @@ async function runDashboard(port, opts = {}) {
134011
135487
  api: "openai-completions",
134012
135488
  models: orModels
134013
135489
  });
134014
- console.log(`[openrouter] Synced ${orModels.length} models from OpenRouter API`);
135490
+ logSink.log(`Synced ${orModels.length} models from OpenRouter API`, "openrouter");
134015
135491
  } catch (err) {
134016
135492
  const message = err instanceof Error ? err.message : String(err);
134017
- console.log(`[openrouter] Failed to sync models: ${message}`);
135493
+ logSink.log(`Failed to sync models: ${message}`, "openrouter");
134018
135494
  }
134019
135495
  })();
134020
135496
  } catch (error) {
134021
135497
  const message = error instanceof Error ? error.message : String(error);
134022
- console.log(`[extensions] Failed to discover extensions: ${message}`);
135498
+ logSink.log(`Failed to discover extensions: ${message}`, "extensions");
134023
135499
  createExtensionRuntime2();
134024
135500
  modelRegistry.refresh();
134025
135501
  }
@@ -134031,6 +135507,14 @@ async function runDashboard(port, opts = {}) {
134031
135507
  function dispose() {
134032
135508
  if (disposed) return;
134033
135509
  disposed = true;
135510
+ if (tuiRefreshDebounceTimer) {
135511
+ clearTimeout(tuiRefreshDebounceTimer);
135512
+ tuiRefreshDebounceTimer = null;
135513
+ }
135514
+ if (tui) {
135515
+ logSink.releaseConsole();
135516
+ void tui.stop();
135517
+ }
134034
135518
  for (const { target, event, handler } of handlers) {
134035
135519
  target.off(event, handler);
134036
135520
  }
@@ -134062,7 +135546,7 @@ async function runDashboard(port, opts = {}) {
134062
135546
  peerExchangeService.start();
134063
135547
  } catch (err) {
134064
135548
  const message = err instanceof Error ? err.message : String(err);
134065
- console.warn(`[dashboard] Failed to start peer exchange service: ${message}`);
135549
+ logSink.warn(`Failed to start peer exchange service: ${message}`, "dashboard");
134066
135550
  }
134067
135551
  centralCoreForMesh = centralCoreForEngine;
134068
135552
  let cwdEngine;
@@ -134112,7 +135596,7 @@ async function runDashboard(port, opts = {}) {
134112
135596
  handleTypes[type] = (handleTypes[type] ?? 0) + 1;
134113
135597
  }
134114
135598
  const handleSummary = Object.entries(handleTypes).sort((a, b) => b[1] - a[1]).map(([type, count]) => `${type}:${count}`).join(", ");
134115
- console.log(`[dashboard] active handles at shutdown: ${handleSummary}`);
135599
+ logSink.log(`active handles at shutdown: ${handleSummary}`, "dashboard");
134116
135600
  } catch {
134117
135601
  }
134118
135602
  await logShutdownDiagnostics(signal);
@@ -134124,7 +135608,7 @@ async function runDashboard(port, opts = {}) {
134124
135608
  await peerExchangeService.stop();
134125
135609
  } catch (err) {
134126
135610
  const message = err instanceof Error ? err.message : String(err);
134127
- console.warn(`[dashboard] Failed to stop peer exchange service: ${message}`);
135611
+ logSink.warn(`Failed to stop peer exchange service: ${message}`, "dashboard");
134128
135612
  }
134129
135613
  }
134130
135614
  if (centralCoreForMesh && localNodeIdForMesh) {
@@ -134132,13 +135616,13 @@ async function runDashboard(port, opts = {}) {
134132
135616
  centralCoreForMesh.stopDiscovery();
134133
135617
  } catch (err) {
134134
135618
  const message = err instanceof Error ? err.message : String(err);
134135
- console.warn(`[dashboard] Failed to stop mDNS discovery: ${message}`);
135619
+ logSink.warn(`Failed to stop mDNS discovery: ${message}`, "dashboard");
134136
135620
  }
134137
135621
  try {
134138
135622
  await centralCoreForMesh.updateNode(localNodeIdForMesh, { status: "offline" });
134139
135623
  } catch (err) {
134140
135624
  const message = err instanceof Error ? err.message : String(err);
134141
- console.warn(`[dashboard] Failed to set local node offline: ${message}`);
135625
+ logSink.warn(`Failed to set local node offline: ${message}`, "dashboard");
134142
135626
  }
134143
135627
  }
134144
135628
  await centralCoreForEngine.close().catch(() => {
@@ -134149,7 +135633,7 @@ async function runDashboard(port, opts = {}) {
134149
135633
  registerHandler(process, "SIGINT", () => void shutdown("SIGINT"));
134150
135634
  registerHandler(process, "SIGTERM", () => void shutdown("SIGTERM"));
134151
135635
  registerHandler(process, "SIGHUP", () => {
134152
- console.log("[dashboard] Received SIGHUP (terminal disconnected) \u2014 ignoring");
135636
+ logSink.log("Received SIGHUP (terminal disconnected) \u2014 ignoring", "dashboard");
134153
135637
  });
134154
135638
  } else {
134155
135639
  try {
@@ -134159,7 +135643,7 @@ async function runDashboard(port, opts = {}) {
134159
135643
  peerExchangeService.start();
134160
135644
  } catch (err) {
134161
135645
  const message = err instanceof Error ? err.message : String(err);
134162
- console.warn(`[dashboard] Failed to initialize mesh networking: ${message}`);
135646
+ logSink.warn(`Failed to initialize mesh networking: ${message}`, "dashboard");
134163
135647
  }
134164
135648
  try {
134165
135649
  heartbeatMonitorImpl = new HeartbeatMonitor({
@@ -134168,10 +135652,10 @@ async function runDashboard(port, opts = {}) {
134168
135652
  taskStore: store,
134169
135653
  rootDir: cwd,
134170
135654
  onMissed: (agentId) => {
134171
- console.log(`[engine] Agent ${agentId} missed heartbeat`);
135655
+ logSink.log(`Agent ${agentId} missed heartbeat`, "engine");
134172
135656
  },
134173
135657
  onTerminated: (agentId) => {
134174
- console.log(`[engine] Agent ${agentId} terminated (unresponsive)`);
135658
+ logSink.log(`Agent ${agentId} terminated (unresponsive)`, "engine");
134175
135659
  }
134176
135660
  });
134177
135661
  heartbeatMonitorImpl.start();
@@ -134203,10 +135687,11 @@ async function runDashboard(port, opts = {}) {
134203
135687
  });
134204
135688
  }
134205
135689
  if (agents.length > 0) {
134206
- console.log(`[engine] Registered ${triggerScheduler.getRegisteredAgents().length} agents for heartbeat triggers`);
135690
+ logSink.log(`Registered ${triggerScheduler.getRegisteredAgents().length} agents for heartbeat triggers`, "engine");
134207
135691
  }
134208
135692
  } catch (err) {
134209
- console.log(`[engine] HeartbeatMonitor initialization failed (continuing without agent monitoring):`, err);
135693
+ const message = err instanceof Error ? err.message : String(err);
135694
+ logSink.log(`HeartbeatMonitor initialization failed (continuing without agent monitoring): ${message}`, "engine");
134210
135695
  }
134211
135696
  app = createServer(store, {
134212
135697
  onMerge,
@@ -134255,7 +135740,7 @@ async function runDashboard(port, opts = {}) {
134255
135740
  handleTypes[type] = (handleTypes[type] ?? 0) + 1;
134256
135741
  }
134257
135742
  const handleSummary = Object.entries(handleTypes).sort((a, b) => b[1] - a[1]).map(([type, count]) => `${type}:${count}`).join(", ");
134258
- console.log(`[dashboard] active handles at shutdown: ${handleSummary}`);
135743
+ logSink.log(`active handles at shutdown: ${handleSummary}`, "dashboard");
134259
135744
  } catch {
134260
135745
  }
134261
135746
  await logShutdownDiagnostics(signal);
@@ -134268,7 +135753,7 @@ async function runDashboard(port, opts = {}) {
134268
135753
  await peerExchangeService.stop();
134269
135754
  } catch (err) {
134270
135755
  const message = err instanceof Error ? err.message : String(err);
134271
- console.warn(`[dashboard] Failed to stop peer exchange service: ${message}`);
135756
+ logSink.warn(`Failed to stop peer exchange service: ${message}`, "dashboard");
134272
135757
  }
134273
135758
  }
134274
135759
  if (centralCoreForMesh && localNodeIdForMesh) {
@@ -134276,13 +135761,13 @@ async function runDashboard(port, opts = {}) {
134276
135761
  centralCoreForMesh.stopDiscovery();
134277
135762
  } catch (err) {
134278
135763
  const message = err instanceof Error ? err.message : String(err);
134279
- console.warn(`[dashboard] Failed to stop mDNS discovery: ${message}`);
135764
+ logSink.warn(`Failed to stop mDNS discovery: ${message}`, "dashboard");
134280
135765
  }
134281
135766
  try {
134282
135767
  await centralCoreForMesh.updateNode(localNodeIdForMesh, { status: "offline" });
134283
135768
  } catch (err) {
134284
135769
  const message = err instanceof Error ? err.message : String(err);
134285
- console.warn(`[dashboard] Failed to set local node offline: ${message}`);
135770
+ logSink.warn(`Failed to set local node offline: ${message}`, "dashboard");
134286
135771
  }
134287
135772
  }
134288
135773
  if (centralCoreForMesh) {
@@ -134295,7 +135780,7 @@ async function runDashboard(port, opts = {}) {
134295
135780
  registerHandler(process, "SIGINT", () => void devShutdown("SIGINT"));
134296
135781
  registerHandler(process, "SIGTERM", () => void devShutdown("SIGTERM"));
134297
135782
  registerHandler(process, "SIGHUP", () => {
134298
- console.log("[dashboard] Received SIGHUP (terminal disconnected) \u2014 ignoring");
135783
+ logSink.log("Received SIGHUP (terminal disconnected) \u2014 ignoring", "dashboard");
134299
135784
  });
134300
135785
  }
134301
135786
  const server = app.listen(selectedPort, selectedHost);
@@ -134303,14 +135788,14 @@ async function runDashboard(port, opts = {}) {
134303
135788
  if (err.code === "EADDRINUSE") {
134304
135789
  server.listen(0, selectedHost);
134305
135790
  } else {
134306
- console.error(`Failed to start server: ${err.message}`);
135791
+ logSink.error(`Failed to start server: ${err.message}`, "dashboard");
134307
135792
  process.exit(1);
134308
135793
  }
134309
135794
  });
134310
135795
  server.on("listening", async () => {
134311
135796
  const actualPort = server.address().port;
134312
135797
  if (actualPort !== selectedPort) {
134313
- console.log(`\u26A0 Port ${selectedPort} in use, using ${actualPort} instead`);
135798
+ logSink.warn(`Port ${selectedPort} in use, using ${actualPort} instead`, "dashboard");
134314
135799
  }
134315
135800
  if (centralCoreForMesh) {
134316
135801
  try {
@@ -134323,7 +135808,7 @@ async function runDashboard(port, opts = {}) {
134323
135808
  });
134324
135809
  } catch (err) {
134325
135810
  const message = err instanceof Error ? err.message : String(err);
134326
- console.warn(`[dashboard] Failed to start mDNS discovery: ${message}`);
135811
+ logSink.warn(`Failed to start mDNS discovery: ${message}`, "dashboard");
134327
135812
  }
134328
135813
  }
134329
135814
  if (centralCoreForMesh) {
@@ -134336,42 +135821,99 @@ async function runDashboard(port, opts = {}) {
134336
135821
  }
134337
135822
  } catch (err) {
134338
135823
  const message = err instanceof Error ? err.message : String(err);
134339
- console.warn(`[dashboard] Failed to set local node online: ${message}`);
135824
+ logSink.warn(`Failed to set local node online: ${message}`, "dashboard");
134340
135825
  }
134341
135826
  }
134342
135827
  const displayHost = selectedHost === "0.0.0.0" || selectedHost === "::" ? selectedHost : "localhost";
134343
135828
  const baseUrl = `http://${displayHost}:${actualPort}`;
134344
135829
  const tokenizedUrl = dashboardAuthToken ? `${baseUrl}/?token=${encodeURIComponent(dashboardAuthToken)}` : baseUrl;
134345
- console.log();
134346
- console.log(` fn board`);
134347
- console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
134348
- console.log(` \u2192 ${baseUrl}`);
134349
- if (dashboardAuthToken) {
134350
- console.log(` Auth: bearer token required`);
134351
- console.log(` Token: ${dashboardAuthToken}`);
134352
- console.log(` Open: ${tokenizedUrl}`);
134353
- console.log(` (the browser stores the token so you only need to click once)`);
134354
- } else {
134355
- console.log(` Auth: disabled (--no-auth)`);
134356
- }
134357
- console.log();
134358
- console.log(` Tasks stored in .fusion/tasks/`);
134359
- console.log(` Merge: AI-assisted (conflict resolution + commit messages)`);
134360
- if (opts.dev) {
134361
- console.log(` AI engine: \u2717 disabled (dev mode)`);
135830
+ if (isTTY && tui) {
135831
+ const settings = await store.getSettings();
135832
+ const engineMode = opts.dev ? "dev" : settings.enginePaused ? "paused" : "active";
135833
+ const systemInfo = {
135834
+ host: displayHost,
135835
+ port: actualPort,
135836
+ baseUrl,
135837
+ authEnabled: Boolean(dashboardAuthToken),
135838
+ authToken: dashboardAuthToken,
135839
+ tokenizedUrl: dashboardAuthToken ? tokenizedUrl : void 0,
135840
+ engineMode,
135841
+ fileWatcher: true,
135842
+ startTimeMs: dashboardStartedAt
135843
+ };
135844
+ tui.setSystemInfo(systemInfo);
135845
+ tui.setSettings({
135846
+ maxConcurrent: settings.maxConcurrent ?? 1,
135847
+ maxWorktrees: settings.maxWorktrees ?? 2,
135848
+ autoMerge: settings.autoMerge ?? false,
135849
+ mergeStrategy: settings.mergeStrategy ?? "direct",
135850
+ pollIntervalMs: settings.pollIntervalMs ?? 6e4,
135851
+ enginePaused: settings.enginePaused ?? false,
135852
+ globalPause: settings.globalPause ?? false
135853
+ });
135854
+ const tasks = await store.listTasks({ slim: true, includeArchived: false });
135855
+ const counts = /* @__PURE__ */ new Map();
135856
+ for (const task of tasks) {
135857
+ counts.set(task.column, (counts.get(task.column) ?? 0) + 1);
135858
+ }
135859
+ const active = tasks.filter(
135860
+ (task) => task.column === "in-progress" || task.column === "in-review"
135861
+ ).length;
135862
+ const agents = await agentStore.listAgents();
135863
+ const agentStats = { idle: 0, active: 0, running: 0, error: 0 };
135864
+ for (const agent of agents) {
135865
+ const state = agent.state;
135866
+ if (state in agentStats) {
135867
+ agentStats[state]++;
135868
+ }
135869
+ }
135870
+ tui.setTaskStats({
135871
+ total: tasks.length,
135872
+ byColumn: Object.fromEntries(counts),
135873
+ active,
135874
+ agents: agentStats
135875
+ });
135876
+ tui.log(`Dashboard started at ${baseUrl}`);
135877
+ if (engineMode === "active") {
135878
+ tui.log("AI engine active");
135879
+ } else if (engineMode === "dev") {
135880
+ tui.log("AI engine disabled (dev mode)");
135881
+ } else {
135882
+ tui.log("AI engine paused");
135883
+ }
135884
+ tui.log("File watcher active");
134362
135885
  } else {
134363
- console.log(` AI engine: \u2713 active`);
134364
- console.log(` \u2022 triage: auto-specifying tasks`);
134365
- console.log(` \u2022 scheduler: dependency-aware execution`);
134366
- console.log(` \u2022 cron: scheduled task execution`);
135886
+ console.log();
135887
+ console.log(` fn board`);
135888
+ console.log(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
135889
+ console.log(` \u2192 ${baseUrl}`);
135890
+ if (dashboardAuthToken) {
135891
+ console.log(` Auth: bearer token required`);
135892
+ console.log(` Token: ${dashboardAuthToken}`);
135893
+ console.log(` Open: ${tokenizedUrl}`);
135894
+ console.log(` (the browser stores the token so you only need to click once)`);
135895
+ } else {
135896
+ console.log(` Auth: disabled (--no-auth)`);
135897
+ }
135898
+ console.log();
135899
+ console.log(` Tasks stored in .fusion/tasks/`);
135900
+ console.log(` Merge: AI-assisted (conflict resolution + commit messages)`);
135901
+ if (opts.dev) {
135902
+ console.log(` AI engine: \u2717 disabled (dev mode)`);
135903
+ } else {
135904
+ console.log(` AI engine: \u2713 active`);
135905
+ console.log(` \u2022 triage: auto-specifying tasks`);
135906
+ console.log(` \u2022 scheduler: dependency-aware execution`);
135907
+ console.log(` \u2022 cron: scheduled task execution`);
135908
+ }
135909
+ console.log(` File watcher: \u2713 active`);
135910
+ console.log(` Press Ctrl+C to stop`);
135911
+ console.log();
134367
135912
  }
134368
- console.log(` File watcher: \u2713 active`);
134369
- console.log(` Press Ctrl+C to stop`);
134370
- console.log();
134371
135913
  });
134372
135914
  return { dispose };
134373
135915
  }
134374
- var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck;
135916
+ var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer;
134375
135917
  var init_dashboard = __esm({
134376
135918
  "src/commands/dashboard.ts"() {
134377
135919
  "use strict";
@@ -134384,12 +135926,67 @@ var init_dashboard = __esm({
134384
135926
  init_provider_auth();
134385
135927
  init_auth_paths2();
134386
135928
  init_project_context();
135929
+ init_dashboard_tui();
134387
135930
  processDiagnosticsRegistered = false;
134388
135931
  diagnosticIntervalHandle = null;
134389
135932
  DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1e3;
134390
135933
  diagnosticStartTime = 0;
134391
135934
  diagnosticDbHealthCheck = null;
134392
135935
  diagnosticStoreListenerCheck = null;
135936
+ STREAM_LOG_FLUSH_IDLE_MS = 100;
135937
+ StreamedLogBuffer = class {
135938
+ constructor(emitLine, flushIdleMs = STREAM_LOG_FLUSH_IDLE_MS) {
135939
+ this.emitLine = emitLine;
135940
+ this.flushIdleMs = flushIdleMs;
135941
+ }
135942
+ pending = "";
135943
+ flushTimer = null;
135944
+ push(delta) {
135945
+ if (!delta) return;
135946
+ this.pending += delta;
135947
+ this.flushCompletedLines();
135948
+ this.scheduleFlush();
135949
+ }
135950
+ flush() {
135951
+ this.clearFlushTimer();
135952
+ const trailing = this.pending.trim();
135953
+ if (trailing.length > 0) {
135954
+ this.emitLine(trailing);
135955
+ }
135956
+ this.pending = "";
135957
+ }
135958
+ dispose() {
135959
+ this.clearFlushTimer();
135960
+ this.pending = "";
135961
+ }
135962
+ flushCompletedLines() {
135963
+ if (!this.pending.includes("\n")) {
135964
+ return;
135965
+ }
135966
+ const splitLines = this.pending.split(/\r?\n/);
135967
+ const completeLines = splitLines.slice(0, -1);
135968
+ this.pending = splitLines[splitLines.length - 1] ?? "";
135969
+ for (const line of completeLines) {
135970
+ const normalized = line.trim();
135971
+ if (normalized.length > 0) {
135972
+ this.emitLine(normalized);
135973
+ }
135974
+ }
135975
+ }
135976
+ scheduleFlush() {
135977
+ this.clearFlushTimer();
135978
+ this.flushTimer = setTimeout(() => {
135979
+ this.flush();
135980
+ }, this.flushIdleMs);
135981
+ this.flushTimer.unref?.();
135982
+ }
135983
+ clearFlushTimer() {
135984
+ if (this.flushTimer) {
135985
+ clearTimeout(this.flushTimer);
135986
+ this.flushTimer = null;
135987
+ }
135988
+ }
135989
+ };
134393
135990
  }
134394
135991
  });
134395
135992
 
@@ -134400,7 +135997,7 @@ __export(node_exports, {
134400
135997
  formatBytes: () => formatBytes3,
134401
135998
  formatLastActivity: () => formatLastActivity,
134402
135999
  formatStatusBar: () => formatStatusBar,
134403
- formatUptime: () => formatUptime2,
136000
+ formatUptime: () => formatUptime3,
134404
136001
  isValidNodeName: () => isValidNodeName,
134405
136002
  maskApiKey: () => maskApiKey,
134406
136003
  runMeshStatus: () => runMeshStatus,
@@ -134413,7 +136010,7 @@ __export(node_exports, {
134413
136010
  runNodeRemove: () => runNodeRemove,
134414
136011
  runNodeShow: () => runNodeShow
134415
136012
  });
134416
- import { createInterface as createInterface2 } from "node:readline/promises";
136013
+ import { createInterface as createInterface3 } from "node:readline/promises";
134417
136014
  function maskApiKey(key) {
134418
136015
  if (!key) return "none";
134419
136016
  if (key.length < 4) return "****";
@@ -134430,7 +136027,7 @@ function formatBytes3(bytes) {
134430
136027
  if (value >= 10) return `${value.toFixed(1)} ${units[i]}`;
134431
136028
  return `${value.toFixed(2)} ${units[i]}`;
134432
136029
  }
134433
- function formatUptime2(ms) {
136030
+ function formatUptime3(ms) {
134434
136031
  if (ms < 0) return "0s";
134435
136032
  const seconds = Math.floor(ms / 1e3);
134436
136033
  const minutes = Math.floor(seconds / 60);
@@ -134608,7 +136205,7 @@ async function runNodeDisconnect(name, options = {}) {
134608
136205
  process.exit(1);
134609
136206
  }
134610
136207
  if (!options.force) {
134611
- const rl = createInterface2({ input: process.stdin, output: process.stdout });
136208
+ const rl = createInterface3({ input: process.stdin, output: process.stdout });
134612
136209
  const answer = await rl.question(`Disconnect node '${node.name}'? [y/N] `);
134613
136210
  rl.close();
134614
136211
  if (answer.trim().toLowerCase() !== "y") {
@@ -134678,7 +136275,7 @@ async function runNodeShow(name, options = {}) {
134678
136275
  console.log(` CPU Usage: ${formatStatusBar(metrics.cpuUsage)}`);
134679
136276
  console.log(` Memory: ${formatBytes3(metrics.memoryUsed)} / ${formatBytes3(metrics.memoryTotal)}`);
134680
136277
  console.log(` Storage: ${formatBytes3(metrics.storageUsed)} / ${formatBytes3(metrics.storageTotal)}`);
134681
- console.log(` Uptime: ${formatUptime2(metrics.uptime)}`);
136278
+ console.log(` Uptime: ${formatUptime3(metrics.uptime)}`);
134682
136279
  console.log(` Reported: ${formatLastActivity(metrics.reportedAt)}`);
134683
136280
  } else {
134684
136281
  console.log();
@@ -134834,7 +136431,7 @@ function formatBytes4(bytes) {
134834
136431
  if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
134835
136432
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
134836
136433
  }
134837
- function formatUptime3(ms) {
136434
+ function formatUptime4(ms) {
134838
136435
  const seconds = Math.floor(ms / 1e3);
134839
136436
  const minutes = Math.floor(seconds / 60);
134840
136437
  const hours = Math.floor(minutes / 60);
@@ -134869,7 +136466,7 @@ function logDiagnostics2(prefix, dbHealthCheck) {
134869
136466
  } catch {
134870
136467
  }
134871
136468
  }
134872
- const logLine = `[${prefix}] diagnostics: uptime=${formatUptime3(uptime)} rss=${formatBytes4(mem.rss)} heap=${formatBytes4(mem.heapUsed)}/${formatBytes4(mem.heapTotal)} external=${formatBytes4(mem.external)} arrayBuffers=${formatBytes4(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
136469
+ const logLine = `[${prefix}] diagnostics: uptime=${formatUptime4(uptime)} rss=${formatBytes4(mem.rss)} heap=${formatBytes4(mem.heapUsed)}/${formatBytes4(mem.heapTotal)} external=${formatBytes4(mem.external)} arrayBuffers=${formatBytes4(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
134873
136470
  console.log(logLine);
134874
136471
  }
134875
136472
  function stopDiagnosticInterval2() {
@@ -134896,11 +136493,11 @@ function ensureProcessDiagnostics2() {
134896
136493
  requestCount = process._getActiveRequests?.()?.length ?? -1;
134897
136494
  } catch {
134898
136495
  }
134899
- console.log(`[serve] beforeExit code=${code} uptime=${formatUptime3(uptime)} handles=${handleCount} requests=${requestCount}`);
136496
+ console.log(`[serve] beforeExit code=${code} uptime=${formatUptime4(uptime)} handles=${handleCount} requests=${requestCount}`);
134900
136497
  });
134901
136498
  process.on("exit", (code) => {
134902
136499
  const uptime = Date.now() - serveStartTime;
134903
- console.log(`[serve] exit code=${code} uptime=${formatUptime3(uptime)}`);
136500
+ console.log(`[serve] exit code=${code} uptime=${formatUptime4(uptime)}`);
134904
136501
  });
134905
136502
  process.on("uncaughtExceptionMonitor", (error) => {
134906
136503
  console.error(`[serve] uncaught exception pid=${process.pid}: ${error.stack || error.message}`);
@@ -135335,7 +136932,7 @@ function formatBytes5(bytes) {
135335
136932
  if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
135336
136933
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
135337
136934
  }
135338
- function formatUptime4(ms) {
136935
+ function formatUptime5(ms) {
135339
136936
  const seconds = Math.floor(ms / 1e3);
135340
136937
  const minutes = Math.floor(seconds / 60);
135341
136938
  const hours = Math.floor(minutes / 60);
@@ -135363,7 +136960,7 @@ function logDiagnostics3(dbHealthCheck) {
135363
136960
  dbHealth = "error";
135364
136961
  }
135365
136962
  }
135366
- const logLine = `[daemon] diagnostics: uptime=${formatUptime4(uptime)} rss=${formatBytes5(mem.rss)} heap=${formatBytes5(mem.heapUsed)}/${formatBytes5(mem.heapTotal)} external=${formatBytes5(mem.external)} arrayBuffers=${formatBytes5(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}`;
136963
+ const logLine = `[daemon] diagnostics: uptime=${formatUptime5(uptime)} rss=${formatBytes5(mem.rss)} heap=${formatBytes5(mem.heapUsed)}/${formatBytes5(mem.heapTotal)} external=${formatBytes5(mem.external)} arrayBuffers=${formatBytes5(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}`;
135367
136964
  console.log(logLine);
135368
136965
  }
135369
136966
  function maskToken(token) {
@@ -135706,7 +137303,7 @@ __export(desktop_exports, {
135706
137303
  import { spawn as spawn4 } from "node:child_process";
135707
137304
  import { once as once2 } from "node:events";
135708
137305
  import { join as join51 } from "node:path";
135709
- import { createRequire } from "node:module";
137306
+ import { createRequire as createRequire2 } from "node:module";
135710
137307
  function runCommand(command, args, cwd) {
135711
137308
  return new Promise((resolve29, reject2) => {
135712
137309
  const child = spawn4(command, args, {
@@ -135769,7 +137366,7 @@ function resolveElectronBinary() {
135769
137366
  if (process.env.FUSION_ELECTRON_BINARY) {
135770
137367
  return process.env.FUSION_ELECTRON_BINARY;
135771
137368
  }
135772
- return require2("electron");
137369
+ return require3("electron");
135773
137370
  }
135774
137371
  function terminateProcess(child, signal = "SIGTERM") {
135775
137372
  if (!child || child.killed) {
@@ -135827,13 +137424,13 @@ async function runDesktop(options = {}) {
135827
137424
  void shutdown(code ?? 0);
135828
137425
  });
135829
137426
  }
135830
- var require2;
137427
+ var require3;
135831
137428
  var init_desktop = __esm({
135832
137429
  "src/commands/desktop.ts"() {
135833
137430
  "use strict";
135834
137431
  init_src();
135835
137432
  init_src3();
135836
- require2 = createRequire(import.meta.url);
137433
+ require3 = createRequire2(import.meta.url);
135837
137434
  }
135838
137435
  });
135839
137436
 
@@ -135866,7 +137463,7 @@ __export(task_exports, {
135866
137463
  runTaskUnpause: () => runTaskUnpause,
135867
137464
  runTaskUpdate: () => runTaskUpdate
135868
137465
  });
135869
- import { createInterface as createInterface3 } from "node:readline/promises";
137466
+ import { createInterface as createInterface4 } from "node:readline/promises";
135870
137467
  import { watchFile, unwatchFile, statSync as statSync7, existsSync as existsSync35, readFileSync as readFileSync14 } from "node:fs";
135871
137468
  import { join as join52 } from "node:path";
135872
137469
  function asLocalProjectContext(store) {
@@ -135935,7 +137532,7 @@ async function runTaskCreate(descriptionArg, attachFiles, depends, projectName)
135935
137532
  let description = descriptionArg;
135936
137533
  const projectContext = await getProjectContext(projectName);
135937
137534
  if (!description) {
135938
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
137535
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
135939
137536
  description = await rl.question("Task description: ");
135940
137537
  rl.close();
135941
137538
  }
@@ -136036,11 +137633,11 @@ async function runTaskLog(id, message, outcome, projectName) {
136036
137633
  console.log(` \u2713 ${id}: logged "${message}"`);
136037
137634
  console.log();
136038
137635
  }
136039
- function formatTimestamp3(timestamp) {
137636
+ function formatTimestamp4(timestamp) {
136040
137637
  return new Date(timestamp).toLocaleTimeString();
136041
137638
  }
136042
137639
  function formatLogEntry(entry) {
136043
- const ts = formatTimestamp3(entry.timestamp);
137640
+ const ts = formatTimestamp4(entry.timestamp);
136044
137641
  const agent = entry.agent ? `[${entry.agent.toUpperCase()}] ` : "";
136045
137642
  switch (entry.type) {
136046
137643
  case "text":
@@ -136272,7 +137869,7 @@ async function runTaskRefine(id, feedbackArg, projectName) {
136272
137869
  const store = await getStore(projectName);
136273
137870
  let feedback = feedbackArg;
136274
137871
  if (feedback === void 0) {
136275
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
137872
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136276
137873
  feedback = await rl.question("What needs to be refined? ");
136277
137874
  rl.close();
136278
137875
  }
@@ -136344,7 +137941,7 @@ async function runTaskDelete(id, force, projectName) {
136344
137941
  return;
136345
137942
  }
136346
137943
  if (!force) {
136347
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
137944
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136348
137945
  const answer = await rl.question(`Are you sure you want to delete ${id}? [y/N] `);
136349
137946
  rl.close();
136350
137947
  const trimmed = answer.trim().toLowerCase();
@@ -136408,7 +138005,7 @@ async function runTaskImportGitHubInteractive(ownerRepo, options = {}, projectNa
136408
138005
  console.log(` ${i + 1}. #${issue.number} ${issue.title.slice(0, 80)}${issue.title.length > 80 ? "\u2026" : ""}${status}`);
136409
138006
  }
136410
138007
  console.log();
136411
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138008
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136412
138009
  let selectedIndices = [];
136413
138010
  let validInput = false;
136414
138011
  while (!validInput) {
@@ -136551,7 +138148,7 @@ async function runTaskComment(id, message, author = "user", projectName) {
136551
138148
  const store = await getStore(projectName);
136552
138149
  let text = message;
136553
138150
  if (text === void 0) {
136554
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138151
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136555
138152
  text = await rl.question("Comment: ");
136556
138153
  rl.close();
136557
138154
  }
@@ -136594,7 +138191,7 @@ async function runTaskSteer(id, message, projectName) {
136594
138191
  const store = await getStore(projectName);
136595
138192
  let text = message;
136596
138193
  if (text === void 0) {
136597
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138194
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136598
138195
  text = await rl.question("Message: ");
136599
138196
  rl.close();
136600
138197
  }
@@ -136724,7 +138321,7 @@ async function promptText(question) {
136724
138321
  console.log(` ${question.description}`);
136725
138322
  }
136726
138323
  console.log(" (Enter your response. Type DONE on its own line when finished):\n");
136727
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138324
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136728
138325
  const lines = [];
136729
138326
  return new Promise((resolve29) => {
136730
138327
  const askLine = () => {
@@ -136758,7 +138355,7 @@ async function promptSingleSelect(question) {
136758
138355
  console.log(` ${opt.description}`);
136759
138356
  }
136760
138357
  }
136761
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138358
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136762
138359
  while (true) {
136763
138360
  const answer = await rl.question("\n Select (1-" + question.options.length + "): ");
136764
138361
  const num = parseInt(answer.trim(), 10);
@@ -136786,7 +138383,7 @@ async function promptMultiSelect(question) {
136786
138383
  console.log(` ${opt.description}`);
136787
138384
  }
136788
138385
  }
136789
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138386
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136790
138387
  while (true) {
136791
138388
  const answer = await rl.question("\n Select (comma-separated): ");
136792
138389
  const nums = answer.split(",").map((s) => parseInt(s.trim(), 10)).filter((n) => !isNaN(n));
@@ -136809,7 +138406,7 @@ async function promptConfirm(question) {
136809
138406
  if (question.description) {
136810
138407
  console.log(` ${question.description}`);
136811
138408
  }
136812
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138409
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136813
138410
  const answer = await rl.question("\n [Y/n]: ");
136814
138411
  rl.close();
136815
138412
  const trimmed = answer.trim().toLowerCase();
@@ -136874,7 +138471,7 @@ function wrapText(text, width) {
136874
138471
  async function runTaskPlan(initialPlanArg, yesFlag = false, projectName) {
136875
138472
  let initialPlan = initialPlanArg;
136876
138473
  if (!initialPlan) {
136877
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138474
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136878
138475
  console.log("\n Let's plan your task. What would you like to accomplish?\n");
136879
138476
  initialPlan = await rl.question(" Describe your idea: ");
136880
138477
  rl.close();
@@ -136975,7 +138572,7 @@ async function runTaskPlan(initialPlanArg, yesFlag = false, projectName) {
136975
138572
  displaySummary(result.data);
136976
138573
  let confirmed = yesFlag;
136977
138574
  if (!yesFlag) {
136978
- const rl = createInterface3({ input: process.stdin, output: process.stdout });
138575
+ const rl = createInterface4({ input: process.stdin, output: process.stdout });
136979
138576
  const answer = await rl.question(" Create this task? [Y/n]: ");
136980
138577
  rl.close();
136981
138578
  const trimmed = answer.trim().toLowerCase();
@@ -137459,7 +139056,7 @@ __export(git_exports, {
137459
139056
  });
137460
139057
  import { exec as exec10 } from "node:child_process";
137461
139058
  import { promisify as promisify12 } from "node:util";
137462
- import { createInterface as createInterface4 } from "node:readline/promises";
139059
+ import { createInterface as createInterface5 } from "node:readline/promises";
137463
139060
  async function resolveGitCwd(projectName) {
137464
139061
  if (projectName) {
137465
139062
  return (await resolveProject(projectName)).projectPath;
@@ -137647,7 +139244,7 @@ async function runGitPull(options = {}) {
137647
139244
  console.log();
137648
139245
  console.log(" \u26A0 Warning: You have uncommitted changes.");
137649
139246
  console.log(` Branch: ${status.branch}`);
137650
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
139247
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
137651
139248
  const answer = await rl.question(" Continue with pull? [y/N] ");
137652
139249
  rl.close();
137653
139250
  const trimmed = answer.trim().toLowerCase();
@@ -137698,7 +139295,7 @@ async function runGitPush(options = {}) {
137698
139295
  }
137699
139296
  if (!options.skipConfirm) {
137700
139297
  console.log();
137701
- const rl = createInterface4({ input: process.stdin, output: process.stdout });
139298
+ const rl = createInterface5({ input: process.stdin, output: process.stdout });
137702
139299
  const answer = await rl.question(` Push branch ${status.branch} to remote? [Y/n] `);
137703
139300
  rl.close();
137704
139301
  const trimmed = answer.trim().toLowerCase();
@@ -137835,7 +139432,7 @@ var init_backup2 = __esm({
137835
139432
  // src/project-resolver.ts
137836
139433
  import { existsSync as existsSync37, statSync as statSync8 } from "node:fs";
137837
139434
  import { dirname as dirname18, resolve as resolve24, normalize as normalize4 } from "node:path";
137838
- import { createInterface as createInterface5 } from "node:readline/promises";
139435
+ import { createInterface as createInterface6 } from "node:readline/promises";
137839
139436
  async function getCentralCore() {
137840
139437
  if (!centralCoreInstance) {
137841
139438
  centralCoreInstance = new CentralCore();
@@ -137866,7 +139463,7 @@ function findKbDir(startPath) {
137866
139463
  return null;
137867
139464
  }
137868
139465
  async function promptProjectSelection(projects, message = "Select a project:") {
137869
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
139466
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
137870
139467
  console.log(`
137871
139468
  ${message}`);
137872
139469
  for (let i = 0; i < projects.length; i++) {
@@ -137883,7 +139480,7 @@ async function promptProjectSelection(projects, message = "Select a project:") {
137883
139480
  }
137884
139481
  }
137885
139482
  async function promptConfirm2(message, defaultYes = false) {
137886
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
139483
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
137887
139484
  const prompt = defaultYes ? "[Y/n]" : "[y/N]";
137888
139485
  const answer = await rl.question(` ${message} ${prompt}: `);
137889
139486
  rl.close();
@@ -137943,7 +139540,7 @@ Run \`fn project remove ` + match.name + "` to clean up the registry entry.",
137943
139540
  Found fn project at ${kbDir} but it's not registered.`);
137944
139541
  const shouldRegister = await promptConfirm2("Register this project now?", true);
137945
139542
  if (shouldRegister) {
137946
- const rl = createInterface5({ input: process.stdin, output: process.stdout });
139543
+ const rl = createInterface6({ input: process.stdin, output: process.stdout });
137947
139544
  const defaultName = kbDir.split("/").pop() || "unnamed";
137948
139545
  const name = await rl.question(` Project name [${defaultName}]: `);
137949
139546
  rl.close();
@@ -138104,12 +139701,12 @@ __export(mission_exports, {
138104
139701
  runMissionShow: () => runMissionShow,
138105
139702
  runSliceAdd: () => runSliceAdd
138106
139703
  });
138107
- import { createInterface as createInterface6 } from "node:readline/promises";
139704
+ import { createInterface as createInterface7 } from "node:readline/promises";
138108
139705
  async function promptForTitleAndDescription(titleArg, titlePrompt, descriptionPrompt) {
138109
139706
  let title = titleArg;
138110
139707
  let description;
138111
139708
  if (!title) {
138112
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
139709
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
138113
139710
  title = await rl.question(titlePrompt);
138114
139711
  if (!title?.trim()) {
138115
139712
  rl.close();
@@ -138238,7 +139835,7 @@ async function runMissionDelete(id, force, projectName) {
138238
139835
  process.exit(1);
138239
139836
  }
138240
139837
  if (!force) {
138241
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
139838
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
138242
139839
  const answer = await rl.question(`Are you sure you want to delete ${id}: "${mission.title}"? [y/N] `);
138243
139840
  rl.close();
138244
139841
  const trimmed = answer.trim().toLowerCase();
@@ -138339,7 +139936,7 @@ async function runFeatureAdd(sliceId, titleArg, descriptionArg, acceptanceCriter
138339
139936
  let description = descriptionArg?.trim() || void 0;
138340
139937
  let acceptanceCriteria = acceptanceCriteriaArg?.trim() || void 0;
138341
139938
  if (!title) {
138342
- const rl = createInterface6({ input: process.stdin, output: process.stdout });
139939
+ const rl = createInterface7({ input: process.stdin, output: process.stdout });
138343
139940
  title = await rl.question("Feature title: ");
138344
139941
  if (!title?.trim()) {
138345
139942
  rl.close();
@@ -138433,7 +140030,7 @@ __export(project_exports, {
138433
140030
  });
138434
140031
  import { resolve as resolve25, isAbsolute as isAbsolute13, relative as relative11, basename as basename11 } from "node:path";
138435
140032
  import { existsSync as existsSync38, statSync as statSync9 } from "node:fs";
138436
- import { createInterface as createInterface7 } from "node:readline/promises";
140033
+ import { createInterface as createInterface8 } from "node:readline/promises";
138437
140034
  function formatDisplayPath(projectPath) {
138438
140035
  const rel = relative11(process.cwd(), projectPath);
138439
140036
  if (rel && !rel.startsWith("..") && rel !== "") {
@@ -138554,7 +140151,7 @@ async function runProjectAdd(name, path4, options = {}) {
138554
140151
  let projectName = name;
138555
140152
  let projectPath = path4;
138556
140153
  if (!projectName || !projectPath || options.interactive) {
138557
- const rl = createInterface7({ input: process.stdin, output: process.stdout });
140154
+ const rl = createInterface8({ input: process.stdin, output: process.stdout });
138558
140155
  if (!projectPath) {
138559
140156
  const defaultPath = process.cwd();
138560
140157
  const pathInput = await rl.question(` Project path [${defaultPath}]: `);
@@ -138679,7 +140276,7 @@ async function runProjectRemove(name, options = {}) {
138679
140276
  process.exit(1);
138680
140277
  }
138681
140278
  if (!options.force) {
138682
- const rl = createInterface7({ input: process.stdin, output: process.stdout });
140279
+ const rl = createInterface8({ input: process.stdin, output: process.stdout });
138683
140280
  const answer = await rl.question(`Unregister project '${project.name}'? [y/N] `);
138684
140281
  rl.close();
138685
140282
  if (answer.trim().toLowerCase() !== "y") {
@@ -139651,7 +141248,7 @@ __export(plugin_exports, {
139651
141248
  import { existsSync as existsSync41 } from "node:fs";
139652
141249
  import { join as join55 } from "node:path";
139653
141250
  import { readFile as readFile23 } from "node:fs/promises";
139654
- import * as readline from "node:readline";
141251
+ import * as readline2 from "node:readline";
139655
141252
  async function getProjectPath6(projectName) {
139656
141253
  if (projectName) {
139657
141254
  const context = await resolveProject(projectName);
@@ -139791,7 +141388,7 @@ async function runPluginUninstall(id, options) {
139791
141388
  console.log(` This will stop and remove the plugin.`);
139792
141389
  console.log();
139793
141390
  const response = await new Promise((resolve29) => {
139794
- const rl = readline.createInterface({
141391
+ const rl = readline2.createInterface({
139795
141392
  input: process.stdin,
139796
141393
  output: process.stdout
139797
141394
  });
@@ -140309,7 +141906,7 @@ var init_native_patch = __esm({
140309
141906
 
140310
141907
  // src/bin.ts
140311
141908
  import { existsSync as existsSync44, mkdtempSync as mkdtempSync2, readFileSync as readFileSync17, symlinkSync as symlinkSync2, writeFileSync as writeFileSync6 } from "node:fs";
140312
- import { createRequire as createRequire2 } from "node:module";
141909
+ import { createRequire as createRequire3 } from "node:module";
140313
141910
  import { join as join58, dirname as dirname20 } from "node:path";
140314
141911
  import { tmpdir as tmpdir4 } from "node:os";
140315
141912
  var isBunBinary3 = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
@@ -140324,8 +141921,8 @@ function configurePiPackage() {
140324
141921
  type: "module"
140325
141922
  };
140326
141923
  try {
140327
- const require3 = createRequire2(import.meta.url);
140328
- const piPackagePath = require3.resolve("@mariozechner/pi-coding-agent/package.json");
141924
+ const require4 = createRequire3(import.meta.url);
141925
+ const piPackagePath = require4.resolve("@mariozechner/pi-coding-agent/package.json");
140329
141926
  const piPackageDir = dirname20(piPackagePath);
140330
141927
  packageJson = JSON.parse(readFileSync17(piPackagePath, "utf-8"));
140331
141928
  for (const entry of ["dist", "docs", "examples", "README.md", "CHANGELOG.md"]) {