@runfusion/fusion 0.3.0 → 0.4.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.
Files changed (37) hide show
  1. package/dist/bin.js +67600 -65202
  2. package/dist/client/assets/{AgentDetailView-CJIxNRq-.js → AgentDetailView-sy6Hg1Xr.js} +3 -3
  3. package/dist/client/assets/{AgentsView-BS17exn3.js → AgentsView-DpEpW88a.js} +3 -3
  4. package/dist/client/assets/ChatView-BUt3C4cf.js +1 -0
  5. package/dist/client/assets/{DevServerView-qMPpnXRb.js → DevServerView-BOyWtQJM.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-CTwgv9LY.js → DirectoryPicker-BJyEEso5.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-DOz1KFGN.js → DocumentsView-Dx0M1YHw.js} +1 -1
  8. package/dist/client/assets/{InsightsView-CHZTJUic.js → InsightsView-BtXdAo7D.js} +1 -1
  9. package/dist/client/assets/MemoryView-DiajLXby.css +1 -0
  10. package/dist/client/assets/MemoryView-kKPuqmwf.js +2 -0
  11. package/dist/client/assets/{NodesView-BtGNRj2z.js → NodesView-B_ZwUORz.js} +1 -1
  12. package/dist/client/assets/{PiExtensionsManager-D9Ye2Vak.js → PiExtensionsManager-Db0EGr0Q.js} +3 -3
  13. package/dist/client/assets/{PluginManager-LeHp0jJ_.js → PluginManager-mOwWndfg.js} +1 -1
  14. package/dist/client/assets/{RoadmapsView-C413ISVU.js → RoadmapsView-DbUzBLeF.js} +1 -1
  15. package/dist/client/assets/SettingsModal-Bs_lNs5B.js +31 -0
  16. package/dist/client/assets/SettingsModal-G0ESQXRD.css +1 -0
  17. package/dist/client/assets/{SettingsModal-olTBmYJs.js → SettingsModal-TRJu_mTn.js} +1 -1
  18. package/dist/client/assets/{SetupWizardModal-WdaR2eQQ.js → SetupWizardModal-D1bmCQrf.js} +1 -1
  19. package/dist/client/assets/{SkillsView-BcE57w8i.js → SkillsView-Dzzpd5Md.js} +1 -1
  20. package/dist/client/assets/{folder-open-Ec4hU1xL.js → folder-open-BcuByk6U.js} +1 -1
  21. package/dist/client/assets/index-BipedNj4.css +1 -0
  22. package/dist/client/assets/index-k2c4LrUr.js +616 -0
  23. package/dist/client/assets/{upload-BksRDuGJ.js → upload-BzNbXYEj.js} +1 -1
  24. package/dist/client/assets/{users-EFU4n9Qr.js → users-BvIqhSXp.js} +1 -1
  25. package/dist/client/index.html +2 -2
  26. package/dist/client/version.json +1 -0
  27. package/dist/extension.js +2165 -885
  28. package/dist/pi-claude-cli/package.json +1 -1
  29. package/dist/pi-claude-cli/src/provider.ts +0 -1
  30. package/package.json +5 -5
  31. package/dist/client/assets/ChatView-BUlq3WNJ.js +0 -1
  32. package/dist/client/assets/MemoryView-DhinauGs.css +0 -1
  33. package/dist/client/assets/MemoryView-V0QdeO3e.js +0 -2
  34. package/dist/client/assets/SettingsModal--vWmKBpT.css +0 -1
  35. package/dist/client/assets/SettingsModal-BZLL2xAP.js +0 -31
  36. package/dist/client/assets/index-CCYdhck-.js +0 -616
  37. package/dist/client/assets/index-lJ5WOmO9.css +0 -1
package/dist/extension.js CHANGED
@@ -193,6 +193,40 @@ var init_settings_schema = __esm({
193
193
  missionHealthCheckIntervalMs: 3e5,
194
194
  agentPrompts: void 0,
195
195
  promptOverrides: void 0,
196
+ remoteAccess: {
197
+ activeProvider: null,
198
+ providers: {
199
+ tailscale: {
200
+ enabled: false,
201
+ hostname: "",
202
+ targetPort: 0,
203
+ acceptRoutes: false
204
+ },
205
+ cloudflare: {
206
+ enabled: false,
207
+ quickTunnel: false,
208
+ tunnelName: "",
209
+ tunnelToken: null,
210
+ ingressUrl: ""
211
+ }
212
+ },
213
+ tokenStrategy: {
214
+ persistent: {
215
+ enabled: true,
216
+ token: null
217
+ },
218
+ shortLived: {
219
+ enabled: false,
220
+ ttlMs: 9e5,
221
+ maxTtlMs: 864e5
222
+ }
223
+ },
224
+ lifecycle: {
225
+ rememberLastRunning: false,
226
+ wasRunningOnShutdown: false,
227
+ lastRunningProvider: null
228
+ }
229
+ },
196
230
  reflectionEnabled: false,
197
231
  reflectionIntervalMs: 36e5,
198
232
  reflectionAfterTask: true,
@@ -354,18 +388,18 @@ Call \`task_done()\` to signal completion.
354
388
  },
355
389
  "triage-welcome": {
356
390
  key: "triage-welcome",
357
- name: "Triage Welcome",
391
+ name: "Planning Welcome",
358
392
  roles: ["triage"],
359
- description: "Introductory section for the triage/specification agent",
393
+ description: "Introductory section for the planning agent",
360
394
  defaultContent: `You are a task specification agent for "fn", an AI-orchestrated task board.
361
395
 
362
396
  Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.`
363
397
  },
364
398
  "triage-context": {
365
399
  key: "triage-context",
366
- name: "Triage Context",
400
+ name: "Planning Context",
367
401
  roles: ["triage"],
368
- description: "Context-gathering instructions for triage",
402
+ description: "Context-gathering instructions for planning",
369
403
  defaultContent: `## What you receive
370
404
  - A raw task title and optional description (the user's rough idea)
371
405
  - Access to the project's files so you can understand context`
@@ -1054,7 +1088,7 @@ Output Requirements:
1054
1088
  }
1055
1089
  };
1056
1090
  COLUMN_LABELS = {
1057
- triage: "Triage",
1091
+ triage: "Planning",
1058
1092
  todo: "Todo",
1059
1093
  "in-progress": "In Progress",
1060
1094
  "in-review": "In Review",
@@ -1062,7 +1096,7 @@ Output Requirements:
1062
1096
  archived: "Archived"
1063
1097
  };
1064
1098
  COLUMN_DESCRIPTIONS = {
1065
- triage: "Raw ideas \u2014 AI will specify these",
1099
+ triage: "Raw ideas \u2014 AI will plan these",
1066
1100
  todo: "Specified and ready to start",
1067
1101
  "in-progress": "AI is working on this in a worktree",
1068
1102
  "in-review": "Complete \u2014 ready to merge",
@@ -2188,7 +2222,7 @@ var init_db = __esm({
2188
2222
  "../core/src/db.ts"() {
2189
2223
  "use strict";
2190
2224
  init_types();
2191
- SCHEMA_VERSION = 46;
2225
+ SCHEMA_VERSION = 47;
2192
2226
  SCHEMA_SQL = `
2193
2227
  -- Tasks table with JSON columns for nested data
2194
2228
  CREATE TABLE IF NOT EXISTS tasks (
@@ -3626,6 +3660,14 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
3626
3660
  this.db.exec("CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder)");
3627
3661
  });
3628
3662
  }
3663
+ if (version < 47) {
3664
+ this.applyMigration(47, () => {
3665
+ if (this.hasTable("tasks") && this.hasColumn("tasks", "status")) {
3666
+ this.db.exec("UPDATE tasks SET status = 'planning' WHERE status = 'specifying'");
3667
+ this.db.exec("UPDATE tasks SET status = 'needs-replan' WHERE status = 'needs-respecify'");
3668
+ }
3669
+ });
3670
+ }
3629
3671
  }
3630
3672
  /**
3631
3673
  * Run a single migration step inside a transaction and bump the version.
@@ -17106,10 +17148,10 @@ var init_central_core = __esm({
17106
17148
  */
17107
17149
  async generateProjectName(projectPath) {
17108
17150
  try {
17109
- const { execFile: execFile4 } = await import("node:child_process");
17110
- const { promisify: promisify11 } = await import("node:util");
17111
- const execFileAsync2 = promisify11(execFile4);
17112
- const { stdout } = await execFileAsync2(
17151
+ const { execFile: execFile6 } = await import("node:child_process");
17152
+ const { promisify: promisify13 } = await import("node:util");
17153
+ const execFileAsync4 = promisify13(execFile6);
17154
+ const { stdout } = await execFileAsync4(
17113
17155
  "git",
17114
17156
  ["remote", "get-url", "origin"],
17115
17157
  { cwd: projectPath, timeout: 5e3 }
@@ -17594,8 +17636,8 @@ function hasProjectDbFile(dir, folderName, dbName) {
17594
17636
  if (!existsSync8(projectDir)) return false;
17595
17637
  if (!existsSync8(dbPath)) return false;
17596
17638
  try {
17597
- const stat7 = statSync2(dbPath);
17598
- return stat7.isFile() && stat7.size > 0;
17639
+ const stat8 = statSync2(dbPath);
17640
+ return stat8.isFile() && stat8.size > 0;
17599
17641
  } catch {
17600
17642
  return false;
17601
17643
  }
@@ -17722,10 +17764,10 @@ var init_migration = __esm({
17722
17764
  return basename3(projectPath);
17723
17765
  }
17724
17766
  try {
17725
- const { execFile: execFile4 } = await import("node:child_process");
17726
- const { promisify: promisify11 } = await import("node:util");
17727
- const execFileAsync2 = promisify11(execFile4);
17728
- const { stdout } = await execFileAsync2(
17767
+ const { execFile: execFile6 } = await import("node:child_process");
17768
+ const { promisify: promisify13 } = await import("node:util");
17769
+ const execFileAsync4 = promisify13(execFile6);
17770
+ const { stdout } = await execFileAsync4(
17729
17771
  "git",
17730
17772
  ["remote", "get-url", "origin"],
17731
17773
  { cwd: projectPath, timeout: 1e3 }
@@ -28431,13 +28473,13 @@ async function searchWithQmd(rootDir, options) {
28431
28473
  const command = "qmd";
28432
28474
  const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
28433
28475
  try {
28434
- const { execFile: execFile4 } = await import("node:child_process");
28435
- const { promisify: promisify11 } = await import("node:util");
28436
- const execFileAsync2 = promisify11(execFile4);
28437
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync2);
28476
+ const { execFile: execFile6 } = await import("node:child_process");
28477
+ const { promisify: promisify13 } = await import("node:util");
28478
+ const execFileAsync4 = promisify13(execFile6);
28479
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync4);
28438
28480
  scheduleQmdProjectMemoryRefresh(rootDir);
28439
28481
  const args = buildQmdSearchArgs(rootDir, options);
28440
- const { stdout } = await execFileAsync2(command, args, {
28482
+ const { stdout } = await execFileAsync4(command, args, {
28441
28483
  cwd: rootDir,
28442
28484
  timeout: 4e3,
28443
28485
  maxBuffer: 1024 * 1024
@@ -28462,12 +28504,12 @@ async function searchWithQmd(rootDir, options) {
28462
28504
  return [];
28463
28505
  }
28464
28506
  }
28465
- async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync2) {
28507
+ async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync4) {
28466
28508
  const collectionName = qmdMemoryCollectionName(rootDir);
28467
28509
  const memoryDir = memoryWorkspacePath(rootDir);
28468
28510
  await mkdir6(memoryDir, { recursive: true });
28469
28511
  try {
28470
- await execFileAsync2("qmd", buildQmdCollectionAddArgs(rootDir), {
28512
+ await execFileAsync4("qmd", buildQmdCollectionAddArgs(rootDir), {
28471
28513
  cwd: rootDir,
28472
28514
  timeout: 4e3,
28473
28515
  maxBuffer: 512 * 1024
@@ -28483,9 +28525,9 @@ ${stderr}`)) {
28483
28525
  return collectionName;
28484
28526
  }
28485
28527
  async function getDefaultExecFileAsync() {
28486
- const { execFile: execFile4 } = await import("node:child_process");
28487
- const { promisify: promisify11 } = await import("node:util");
28488
- return promisify11(execFile4);
28528
+ const { execFile: execFile6 } = await import("node:child_process");
28529
+ const { promisify: promisify13 } = await import("node:util");
28530
+ return promisify13(execFile6);
28489
28531
  }
28490
28532
  async function refreshQmdProjectMemoryIndex(rootDir, options) {
28491
28533
  const key = resolve5(rootDir);
@@ -28500,14 +28542,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
28500
28542
  }
28501
28543
  }
28502
28544
  const promise = (async () => {
28503
- const execFileAsync2 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28504
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync2);
28505
- await execFileAsync2("qmd", ["update"], {
28545
+ const execFileAsync4 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28546
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync4);
28547
+ await execFileAsync4("qmd", ["update"], {
28506
28548
  cwd: rootDir,
28507
28549
  timeout: 3e4,
28508
28550
  maxBuffer: 1024 * 1024
28509
28551
  });
28510
- await execFileAsync2("qmd", ["embed"], {
28552
+ await execFileAsync4("qmd", ["embed"], {
28511
28553
  cwd: rootDir,
28512
28554
  timeout: 12e4,
28513
28555
  maxBuffer: 1024 * 1024
@@ -28532,8 +28574,8 @@ function scheduleQmdProjectMemoryRefresh(rootDir) {
28532
28574
  }
28533
28575
  async function isQmdAvailable() {
28534
28576
  try {
28535
- const execFileAsync2 = await getDefaultExecFileAsync();
28536
- await execFileAsync2("qmd", ["--help"], {
28577
+ const execFileAsync4 = await getDefaultExecFileAsync();
28578
+ await execFileAsync4("qmd", ["--help"], {
28537
28579
  timeout: 3e3,
28538
28580
  maxBuffer: 128 * 1024
28539
28581
  });
@@ -28543,12 +28585,12 @@ async function isQmdAvailable() {
28543
28585
  }
28544
28586
  }
28545
28587
  async function installQmd(options) {
28546
- const execFileAsync2 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28588
+ const execFileAsync4 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28547
28589
  const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
28548
28590
  if (!command || args.length === 0) {
28549
28591
  throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
28550
28592
  }
28551
- await execFileAsync2(command, args, {
28593
+ await execFileAsync4(command, args, {
28552
28594
  timeout: 12e4,
28553
28595
  maxBuffer: 1024 * 1024
28554
28596
  });
@@ -29354,6 +29396,29 @@ function canonicalizeSettings(settings) {
29354
29396
  }
29355
29397
  return base;
29356
29398
  }
29399
+ function isPlainObject(value) {
29400
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29401
+ }
29402
+ function deepMergeWithNullDelete(existingValue, patchValue) {
29403
+ const merged = isPlainObject(existingValue) ? { ...existingValue } : {};
29404
+ for (const [key, value] of Object.entries(patchValue)) {
29405
+ if (value === null) {
29406
+ delete merged[key];
29407
+ continue;
29408
+ }
29409
+ if (isPlainObject(value)) {
29410
+ const nested = deepMergeWithNullDelete(merged[key], value);
29411
+ if (nested === void 0) {
29412
+ delete merged[key];
29413
+ } else {
29414
+ merged[key] = nested;
29415
+ }
29416
+ continue;
29417
+ }
29418
+ merged[key] = value;
29419
+ }
29420
+ return Object.keys(merged).length > 0 ? merged : void 0;
29421
+ }
29357
29422
  var TASK_ACTIVITY_LOG_ENTRY_LIMIT, TASK_ACTIVITY_LOG_OUTCOME_LIMIT, ARCHIVE_AGENT_LOG_SNAPSHOT_LIMIT, ARCHIVE_AGENT_LOG_SNIPPET_LIMIT, storeLog, TaskHasDependentsError, TaskStore;
29358
29423
  var init_store = __esm({
29359
29424
  "../core/src/store.ts"() {
@@ -30524,6 +30589,21 @@ ${recentText}` : void 0
30524
30589
  projectPatch["promptOverrides"] = mergedMap;
30525
30590
  }
30526
30591
  }
30592
+ const incomingRemoteAccess = projectPatch["remoteAccess"];
30593
+ if (incomingRemoteAccess === null) {
30594
+ delete config.settings["remoteAccess"];
30595
+ delete projectPatch["remoteAccess"];
30596
+ } else if (isPlainObject(incomingRemoteAccess)) {
30597
+ const existingRemoteAccess = config.settings["remoteAccess"];
30598
+ const mergedRemoteAccess = deepMergeWithNullDelete(existingRemoteAccess, incomingRemoteAccess);
30599
+ if (mergedRemoteAccess === void 0) {
30600
+ delete config.settings["remoteAccess"];
30601
+ delete projectPatch["remoteAccess"];
30602
+ } else {
30603
+ config.settings["remoteAccess"] = mergedRemoteAccess;
30604
+ projectPatch["remoteAccess"] = mergedRemoteAccess;
30605
+ }
30606
+ }
30527
30607
  for (const key of Object.keys(projectPatch)) {
30528
30608
  if (projectPatch[key] === null) {
30529
30609
  delete config.settings[key];
@@ -31914,8 +31994,8 @@ ${task.description}
31914
31994
  if (this.isWatching) this.taskCache.delete(id);
31915
31995
  const dir = this.taskDir(id);
31916
31996
  if (existsSync12(dir)) {
31917
- const { rm: rm3 } = await import("node:fs/promises");
31918
- await rm3(dir, { recursive: true });
31997
+ const { rm: rm4 } = await import("node:fs/promises");
31998
+ await rm4(dir, { recursive: true });
31919
31999
  }
31920
32000
  for (const dependentTask of rewrittenDependents) {
31921
32001
  this.emit("task:updated", dependentTask);
@@ -32250,8 +32330,8 @@ ${task.description}
32250
32330
  this.archiveDb.upsert(entry);
32251
32331
  this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
32252
32332
  this.db.bumpLastModified();
32253
- const { rm: rm3 } = await import("node:fs/promises");
32254
- await rm3(dir, { recursive: true, force: true });
32333
+ const { rm: rm4 } = await import("node:fs/promises");
32334
+ await rm4(dir, { recursive: true, force: true });
32255
32335
  if (this.isWatching) {
32256
32336
  this.taskCache.delete(id);
32257
32337
  }
@@ -32758,7 +32838,7 @@ ${task.description}
32758
32838
  let invalidatedStatus = false;
32759
32839
  try {
32760
32840
  await this.updateTask(id, {
32761
- status: "needs-respecify"
32841
+ status: "needs-replan"
32762
32842
  });
32763
32843
  invalidatedStatus = true;
32764
32844
  } catch (err) {
@@ -32766,7 +32846,7 @@ ${task.description}
32766
32846
  ...commentContextBase,
32767
32847
  phase: "addComment:awaiting-approval-invalidation",
32768
32848
  stage: "status-update",
32769
- nextStatus: "needs-respecify",
32849
+ nextStatus: "needs-replan",
32770
32850
  error: err instanceof Error ? err.message : String(err)
32771
32851
  });
32772
32852
  }
@@ -32783,7 +32863,7 @@ ${task.description}
32783
32863
  ...commentContextBase,
32784
32864
  phase: "addComment:awaiting-approval-invalidation",
32785
32865
  stage: "post-invalidation-log-entry",
32786
- nextStatus: "needs-respecify",
32866
+ nextStatus: "needs-replan",
32787
32867
  error: err instanceof Error ? err.message : String(err)
32788
32868
  });
32789
32869
  }
@@ -33203,14 +33283,14 @@ ${task.description}
33203
33283
  if (rows.length === 0) {
33204
33284
  return;
33205
33285
  }
33206
- const { rm: rm3 } = await import("node:fs/promises");
33286
+ const { rm: rm4 } = await import("node:fs/promises");
33207
33287
  for (const row of rows) {
33208
33288
  const task = this.rowToTask(row);
33209
33289
  const archivedAt = task.columnMovedAt ?? task.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
33210
33290
  const entry = await this.taskToArchiveEntry(task, archivedAt);
33211
33291
  this.archiveDb.upsert(entry);
33212
33292
  this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
33213
- await rm3(this.taskDir(task.id), { recursive: true, force: true });
33293
+ await rm4(this.taskDir(task.id), { recursive: true, force: true });
33214
33294
  if (this.isWatching) {
33215
33295
  this.taskCache.delete(task.id);
33216
33296
  }
@@ -33233,8 +33313,8 @@ ${task.description}
33233
33313
  this.archiveDb.upsert(entry);
33234
33314
  this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
33235
33315
  this.db.bumpLastModified();
33236
- const { rm: rm3 } = await import("node:fs/promises");
33237
- await rm3(dir, { recursive: true, force: true });
33316
+ const { rm: rm4 } = await import("node:fs/promises");
33317
+ await rm4(dir, { recursive: true, force: true });
33238
33318
  if (this.isWatching) {
33239
33319
  this.taskCache.delete(task.id);
33240
33320
  }
@@ -36345,6 +36425,47 @@ async function writeMemoryAudit(rootDir, content) {
36345
36425
  }
36346
36426
  await writeFile8(filePath, content, "utf-8");
36347
36427
  }
36428
+ async function readMemoryAuditState(rootDir) {
36429
+ const filePath = join18(rootDir, MEMORY_AUDIT_STATE_PATH);
36430
+ if (!existsSync15(filePath)) {
36431
+ return null;
36432
+ }
36433
+ try {
36434
+ const raw = await readFile10(filePath, "utf-8");
36435
+ const parsed = JSON.parse(raw);
36436
+ const extraction = isValidExtractionMetadata(parsed.extraction) ? parsed.extraction : void 0;
36437
+ const pruning = isValidPruneOutcome(parsed.pruning) ? parsed.pruning : void 0;
36438
+ return {
36439
+ extraction,
36440
+ pruning,
36441
+ updatedAt: typeof parsed.updatedAt === "string" && parsed.updatedAt.trim() ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
36442
+ };
36443
+ } catch {
36444
+ return null;
36445
+ }
36446
+ }
36447
+ async function writeMemoryAuditState(rootDir, state) {
36448
+ const filePath = join18(rootDir, MEMORY_AUDIT_STATE_PATH);
36449
+ const dir = join18(rootDir, ".fusion");
36450
+ if (!existsSync15(dir)) {
36451
+ await mkdir9(dir, { recursive: true });
36452
+ }
36453
+ await writeFile8(filePath, JSON.stringify(state, null, 2), "utf-8");
36454
+ }
36455
+ function isValidExtractionMetadata(value) {
36456
+ if (!value || typeof value !== "object") {
36457
+ return false;
36458
+ }
36459
+ const candidate = value;
36460
+ return typeof candidate.runAt === "string" && typeof candidate.success === "boolean" && typeof candidate.insightCount === "number" && typeof candidate.duplicateCount === "number" && typeof candidate.skippedCount === "number" && typeof candidate.summary === "string" && (candidate.error === void 0 || typeof candidate.error === "string");
36461
+ }
36462
+ function isValidPruneOutcome(value) {
36463
+ if (!value || typeof value !== "object") {
36464
+ return false;
36465
+ }
36466
+ const candidate = value;
36467
+ return typeof candidate.applied === "boolean" && typeof candidate.reason === "string" && typeof candidate.sizeDelta === "number" && typeof candidate.originalSize === "number" && typeof candidate.newSize === "number";
36468
+ }
36348
36469
  function buildInsightExtractionPrompt(workingMemory, existingInsights) {
36349
36470
  const existingSection = existingInsights ? `
36350
36471
  ## Existing Insights (already captured \u2014 do not duplicate)
@@ -36799,6 +36920,9 @@ function countInsightsInMarkdown(markdown) {
36799
36920
  async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36800
36921
  const checks = [];
36801
36922
  const now = (/* @__PURE__ */ new Date()).toISOString();
36923
+ const persistedState = lastExtraction === void 0 || pruningOutcome === void 0 ? await readMemoryAuditState(rootDir) : null;
36924
+ const effectiveExtraction = lastExtraction ?? persistedState?.extraction;
36925
+ const effectivePruning = pruningOutcome ?? persistedState?.pruning;
36802
36926
  const workingMemoryPath = join18(rootDir, MEMORY_WORKING_PATH);
36803
36927
  const workingMemoryExists = existsSync15(workingMemoryPath);
36804
36928
  let workingMemorySize = 0;
@@ -36917,20 +37041,20 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36917
37041
  details: totalInsights > 0 ? `Contains ${totalInsights} insights across categories: patterns=${categoryCounts.pattern}, principles=${categoryCounts.principle}, conventions=${categoryCounts.convention}, pitfalls=${categoryCounts.pitfall}, context=${categoryCounts.context}` : "No insights extracted yet"
36918
37042
  });
36919
37043
  }
36920
- if (lastExtraction) {
36921
- const extractionAge = Date.now() - new Date(lastExtraction.runAt).getTime();
37044
+ if (effectiveExtraction) {
37045
+ const extractionAge = Date.now() - new Date(effectiveExtraction.runAt).getTime();
36922
37046
  const oneWeekMs = 7 * 24 * 60 * 60 * 1e3;
36923
37047
  checks.push({
36924
37048
  id: "recent-extraction",
36925
37049
  name: "Recent extraction activity",
36926
- passed: lastExtraction.success && extractionAge < oneWeekMs,
36927
- details: lastExtraction.success ? `Last successful extraction ${formatTimeAgo(lastExtraction.runAt)} (${lastExtraction.insightCount} insights, ${lastExtraction.duplicateCount} duplicates skipped)` : `Last extraction failed: ${lastExtraction.error || "Unknown error"}`
37050
+ passed: effectiveExtraction.success && extractionAge < oneWeekMs,
37051
+ details: effectiveExtraction.success ? `Last successful extraction ${formatTimeAgo(effectiveExtraction.runAt)} (${effectiveExtraction.insightCount} insights, ${effectiveExtraction.duplicateCount} duplicates skipped)` : `Last extraction failed: ${effectiveExtraction.error || "Unknown error"}`
36928
37052
  });
36929
37053
  checks.push({
36930
37054
  id: "extraction-summary",
36931
37055
  name: "Extraction produces meaningful summaries",
36932
- passed: lastExtraction.success && lastExtraction.summary.length > 10,
36933
- details: lastExtraction.success ? `Summary: "${lastExtraction.summary.slice(0, 100)}${lastExtraction.summary.length > 100 ? "..." : ""}"` : "No meaningful summary available"
37056
+ passed: effectiveExtraction.success && effectiveExtraction.summary.length > 10,
37057
+ details: effectiveExtraction.success ? `Summary: "${effectiveExtraction.summary.slice(0, 100)}${effectiveExtraction.summary.length > 100 ? "..." : ""}"` : "No meaningful summary available"
36934
37058
  });
36935
37059
  } else {
36936
37060
  checks.push({
@@ -36940,12 +37064,12 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36940
37064
  details: "No extraction runs recorded"
36941
37065
  });
36942
37066
  }
36943
- if (pruningOutcome) {
37067
+ if (effectivePruning) {
36944
37068
  checks.push({
36945
37069
  id: "pruning-applied",
36946
37070
  name: "Memory pruning outcome",
36947
- passed: pruningOutcome.applied,
36948
- details: pruningOutcome.applied ? `Pruning applied: ${pruningOutcome.originalSize} \u2192 ${pruningOutcome.newSize} chars (${pruningOutcome.sizeDelta >= 0 ? "+" : ""}${pruningOutcome.sizeDelta} chars)` : `Pruning skipped: ${pruningOutcome.reason}`
37071
+ passed: effectivePruning.applied,
37072
+ details: effectivePruning.applied ? `Pruning applied: ${effectivePruning.originalSize} \u2192 ${effectivePruning.newSize} chars (${effectivePruning.sizeDelta >= 0 ? "+" : ""}${effectivePruning.sizeDelta} chars)` : `Pruning skipped: ${effectivePruning.reason}`
36949
37073
  });
36950
37074
  }
36951
37075
  const failedChecks = checks.filter((c) => !c.passed);
@@ -36972,14 +37096,14 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36972
37096
  categories: categoryCounts,
36973
37097
  lastUpdated
36974
37098
  },
36975
- extraction: lastExtraction ? {
36976
- runAt: lastExtraction.runAt,
36977
- success: lastExtraction.success,
36978
- insightCount: lastExtraction.insightCount,
36979
- duplicateCount: lastExtraction.duplicateCount,
36980
- skippedCount: lastExtraction.skippedCount,
36981
- summary: lastExtraction.summary,
36982
- error: lastExtraction.error
37099
+ extraction: effectiveExtraction ? {
37100
+ runAt: effectiveExtraction.runAt,
37101
+ success: effectiveExtraction.success,
37102
+ insightCount: effectiveExtraction.insightCount,
37103
+ duplicateCount: effectiveExtraction.duplicateCount,
37104
+ skippedCount: effectiveExtraction.skippedCount,
37105
+ summary: effectiveExtraction.summary,
37106
+ error: effectiveExtraction.error
36983
37107
  } : {
36984
37108
  runAt: "",
36985
37109
  success: false,
@@ -36988,7 +37112,7 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36988
37112
  skippedCount: 0,
36989
37113
  summary: "No extraction runs recorded"
36990
37114
  },
36991
- pruning: pruningOutcome ?? {
37115
+ pruning: effectivePruning ?? {
36992
37116
  applied: false,
36993
37117
  reason: "No pruning run recorded",
36994
37118
  sizeDelta: 0,
@@ -37129,6 +37253,17 @@ async function processAndAuditInsightExtraction(rootDir, input) {
37129
37253
  newSize: currentMemory.length
37130
37254
  };
37131
37255
  }
37256
+ try {
37257
+ await writeMemoryAuditState(rootDir, {
37258
+ extraction: extractionInfo,
37259
+ pruning: pruneOutcome,
37260
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
37261
+ });
37262
+ } catch (err) {
37263
+ console.error(
37264
+ `[memory-audit] Failed to persist audit state: ${err instanceof Error ? err.message : String(err)}`
37265
+ );
37266
+ }
37132
37267
  const auditReport = await generateMemoryAudit(rootDir, extractionInfo, pruneOutcome);
37133
37268
  try {
37134
37269
  const auditMarkdown = renderMemoryAuditMarkdown(auditReport);
@@ -37140,13 +37275,14 @@ async function processAndAuditInsightExtraction(rootDir, input) {
37140
37275
  }
37141
37276
  return auditReport;
37142
37277
  }
37143
- var MEMORY_WORKING_PATH, MEMORY_INSIGHTS_PATH, MEMORY_AUDIT_PATH, DEFAULT_INSIGHT_SCHEDULE, DEFAULT_MIN_INTERVAL_MS, MIN_INSIGHT_GROWTH_CHARS, INSIGHT_EXTRACTION_SCHEDULE_NAME, REQUIRED_MEMORY_SECTIONS;
37278
+ var MEMORY_WORKING_PATH, MEMORY_INSIGHTS_PATH, MEMORY_AUDIT_PATH, MEMORY_AUDIT_STATE_PATH, DEFAULT_INSIGHT_SCHEDULE, DEFAULT_MIN_INTERVAL_MS, MIN_INSIGHT_GROWTH_CHARS, INSIGHT_EXTRACTION_SCHEDULE_NAME, REQUIRED_MEMORY_SECTIONS;
37144
37279
  var init_memory_insights = __esm({
37145
37280
  "../core/src/memory-insights.ts"() {
37146
37281
  "use strict";
37147
37282
  MEMORY_WORKING_PATH = ".fusion/memory/MEMORY.md";
37148
37283
  MEMORY_INSIGHTS_PATH = ".fusion/memory-insights.md";
37149
37284
  MEMORY_AUDIT_PATH = ".fusion/memory-audit.md";
37285
+ MEMORY_AUDIT_STATE_PATH = ".fusion/memory-audit-state.json";
37150
37286
  DEFAULT_INSIGHT_SCHEDULE = "0 2 * * *";
37151
37287
  DEFAULT_MIN_INTERVAL_MS = 24 * 60 * 60 * 1e3;
37152
37288
  MIN_INSIGHT_GROWTH_CHARS = 1e3;
@@ -38324,15 +38460,15 @@ var require_fd_slicer = __commonJS({
38324
38460
  var Writable = stream.Writable;
38325
38461
  var PassThrough = stream.PassThrough;
38326
38462
  var Pend = require_pend();
38327
- var EventEmitter25 = __require("events").EventEmitter;
38463
+ var EventEmitter26 = __require("events").EventEmitter;
38328
38464
  exports.createFromBuffer = createFromBuffer;
38329
38465
  exports.createFromFd = createFromFd;
38330
38466
  exports.BufferSlicer = BufferSlicer;
38331
38467
  exports.FdSlicer = FdSlicer;
38332
- util.inherits(FdSlicer, EventEmitter25);
38468
+ util.inherits(FdSlicer, EventEmitter26);
38333
38469
  function FdSlicer(fd, options) {
38334
38470
  options = options || {};
38335
- EventEmitter25.call(this);
38471
+ EventEmitter26.call(this);
38336
38472
  this.fd = fd;
38337
38473
  this.pend = new Pend();
38338
38474
  this.pend.max = 1;
@@ -38476,9 +38612,9 @@ var require_fd_slicer = __commonJS({
38476
38612
  this.destroyed = true;
38477
38613
  this.context.unref();
38478
38614
  };
38479
- util.inherits(BufferSlicer, EventEmitter25);
38615
+ util.inherits(BufferSlicer, EventEmitter26);
38480
38616
  function BufferSlicer(buffer, options) {
38481
- EventEmitter25.call(this);
38617
+ EventEmitter26.call(this);
38482
38618
  options = options || {};
38483
38619
  this.refCount = 0;
38484
38620
  this.buffer = buffer;
@@ -38890,7 +39026,7 @@ var require_yauzl = __commonJS({
38890
39026
  var fd_slicer = require_fd_slicer();
38891
39027
  var crc32 = require_buffer_crc32();
38892
39028
  var util = __require("util");
38893
- var EventEmitter25 = __require("events").EventEmitter;
39029
+ var EventEmitter26 = __require("events").EventEmitter;
38894
39030
  var Transform = __require("stream").Transform;
38895
39031
  var PassThrough = __require("stream").PassThrough;
38896
39032
  var Writable = __require("stream").Writable;
@@ -39022,10 +39158,10 @@ var require_yauzl = __commonJS({
39022
39158
  callback(new Error("end of central directory record signature not found"));
39023
39159
  });
39024
39160
  }
39025
- util.inherits(ZipFile, EventEmitter25);
39161
+ util.inherits(ZipFile, EventEmitter26);
39026
39162
  function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) {
39027
39163
  var self = this;
39028
- EventEmitter25.call(self);
39164
+ EventEmitter26.call(self);
39029
39165
  self.reader = reader;
39030
39166
  self.reader.on("error", function(err) {
39031
39167
  emitError(self, err);
@@ -39386,9 +39522,9 @@ var require_yauzl = __commonJS({
39386
39522
  }
39387
39523
  cb();
39388
39524
  };
39389
- util.inherits(RandomAccessReader, EventEmitter25);
39525
+ util.inherits(RandomAccessReader, EventEmitter26);
39390
39526
  function RandomAccessReader() {
39391
- EventEmitter25.call(this);
39527
+ EventEmitter26.call(this);
39392
39528
  this.refCount = 0;
39393
39529
  }
39394
39530
  RandomAccessReader.prototype.ref = function() {
@@ -39519,11 +39655,11 @@ var require_extract_zip = __commonJS({
39519
39655
  var { createWriteStream, promises: fs } = __require("fs");
39520
39656
  var getStream = require_get_stream();
39521
39657
  var path = __require("path");
39522
- var { promisify: promisify11 } = __require("util");
39658
+ var { promisify: promisify13 } = __require("util");
39523
39659
  var stream = __require("stream");
39524
39660
  var yauzl = require_yauzl();
39525
- var openZip = promisify11(yauzl.open);
39526
- var pipeline = promisify11(stream.pipeline);
39661
+ var openZip = promisify13(yauzl.open);
39662
+ var pipeline = promisify13(stream.pipeline);
39527
39663
  var Extractor = class {
39528
39664
  constructor(zipPath, opts) {
39529
39665
  this.zipPath = zipPath;
@@ -39605,7 +39741,7 @@ var require_extract_zip = __commonJS({
39605
39741
  await fs.mkdir(destDir, mkdirOptions);
39606
39742
  if (isDir) return;
39607
39743
  debug("opening read stream", dest);
39608
- const readStream = await promisify11(this.zipfile.openReadStream.bind(this.zipfile))(entry);
39744
+ const readStream = await promisify13(this.zipfile.openReadStream.bind(this.zipfile))(entry);
39609
39745
  if (symlink) {
39610
39746
  const link = await getStream(readStream);
39611
39747
  debug("creating symlink", link, dest);
@@ -47266,12 +47402,12 @@ function resolveExtractionRoot(tempDir) {
47266
47402
  return tempDir;
47267
47403
  }
47268
47404
  async function extractTarArchive(archivePath, outputDir) {
47269
- const [{ execFile: execFile4 }, { promisify: promisify11 }] = await Promise.all([
47405
+ const [{ execFile: execFile6 }, { promisify: promisify13 }] = await Promise.all([
47270
47406
  import("node:child_process"),
47271
47407
  import("node:util")
47272
47408
  ]);
47273
- const execFileAsync2 = promisify11(execFile4);
47274
- await execFileAsync2("tar", ["xzf", archivePath, "-C", outputDir]);
47409
+ const execFileAsync4 = promisify13(execFile6);
47410
+ await execFileAsync4("tar", ["xzf", archivePath, "-C", outputDir]);
47275
47411
  }
47276
47412
  async function parseCompanyArchive(archivePath) {
47277
47413
  const resolvedArchivePath = resolve8(archivePath);
@@ -48451,7 +48587,7 @@ ${stack}` : message2;
48451
48587
  }
48452
48588
  return { message, detail: message };
48453
48589
  }
48454
- var LOG_LEVEL_MARKER_PREFIX2, LOG_LEVEL_MARKER_SUFFIX2, schedulerLog, executorLog, triageLog, piLog, extensionsLog, mergerLog, worktreePoolLog, reviewerLog, prMonitorLog, runtimeLog, ipcLog, projectManagerLog, hybridExecutorLog, autopilotLog, heartbeatLog, remoteNodeLog, nodeHealthMonitorLog, peerExchangeLog;
48590
+ var LOG_LEVEL_MARKER_PREFIX2, LOG_LEVEL_MARKER_SUFFIX2, schedulerLog, executorLog, triageLog, piLog, extensionsLog, mergerLog, worktreePoolLog, reviewerLog, prMonitorLog, runtimeLog, ipcLog, projectManagerLog, hybridExecutorLog, autopilotLog, heartbeatLog, remoteNodeLog, remoteTunnelLog, nodeHealthMonitorLog, peerExchangeLog;
48455
48591
  var init_logger2 = __esm({
48456
48592
  "../engine/src/logger.ts"() {
48457
48593
  "use strict";
@@ -48473,6 +48609,7 @@ var init_logger2 = __esm({
48473
48609
  autopilotLog = createLogger2("autopilot");
48474
48610
  heartbeatLog = createLogger2("heartbeat");
48475
48611
  remoteNodeLog = createLogger2("remote-node");
48612
+ remoteTunnelLog = createLogger2("remote-tunnel");
48476
48613
  nodeHealthMonitorLog = createLogger2("node-health-monitor");
48477
48614
  peerExchangeLog = createLogger2("peer-exchange");
48478
48615
  }
@@ -48809,11 +48946,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
48809
48946
  return;
48810
48947
  }
48811
48948
  const promise = (async () => {
48812
- const { execFile: execFile4 } = await import("node:child_process");
48813
- const { promisify: promisify11 } = await import("node:util");
48814
- const execFileAsync2 = promisify11(execFile4);
48949
+ const { execFile: execFile6 } = await import("node:child_process");
48950
+ const { promisify: promisify13 } = await import("node:util");
48951
+ const execFileAsync4 = promisify13(execFile6);
48815
48952
  try {
48816
- await execFileAsync2("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
48953
+ await execFileAsync4("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
48817
48954
  cwd: rootDir,
48818
48955
  timeout: 4e3,
48819
48956
  maxBuffer: 512 * 1024
@@ -48826,8 +48963,8 @@ ${stderr}`)) {
48826
48963
  throw error;
48827
48964
  }
48828
48965
  }
48829
- await execFileAsync2("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
48830
- await execFileAsync2("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
48966
+ await execFileAsync4("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
48967
+ await execFileAsync4("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
48831
48968
  })();
48832
48969
  agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
48833
48970
  try {
@@ -48848,10 +48985,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
48848
48985
  }
48849
48986
  try {
48850
48987
  await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
48851
- const { execFile: execFile4 } = await import("node:child_process");
48852
- const { promisify: promisify11 } = await import("node:util");
48853
- const execFileAsync2 = promisify11(execFile4);
48854
- const { stdout } = await execFileAsync2("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
48988
+ const { execFile: execFile6 } = await import("node:child_process");
48989
+ const { promisify: promisify13 } = await import("node:util");
48990
+ const execFileAsync4 = promisify13(execFile6);
48991
+ const { stdout } = await execFileAsync4("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
48855
48992
  cwd: rootDir,
48856
48993
  timeout: 4e3,
48857
48994
  maxBuffer: 1024 * 1024
@@ -54044,14 +54181,14 @@ function rethrowIfMergeAborted(error) {
54044
54181
  throw error;
54045
54182
  }
54046
54183
  }
54047
- async function runDeterministicVerification(store, rootDir, taskId, testCommand, buildCommand, testSource, buildSource, signal) {
54184
+ async function runDeterministicVerification(store, rootDir, taskId, testCommand, buildCommand2, testSource, buildSource, signal) {
54048
54185
  const result = { allPassed: true };
54049
- if (!testCommand && !buildCommand) {
54186
+ if (!testCommand && !buildCommand2) {
54050
54187
  mergerLog.log(`${taskId}: no verification commands configured \u2014 skipping`);
54051
54188
  return result;
54052
54189
  }
54053
54190
  const normalizedTestCommand = testCommand?.trim();
54054
- const normalizedBuildCommand = buildCommand?.trim();
54191
+ const normalizedBuildCommand = buildCommand2?.trim();
54055
54192
  const hasTestCommand = !!normalizedTestCommand;
54056
54193
  const hasBuildCommand = !!normalizedBuildCommand;
54057
54194
  const testSourceLabel = testSource === "inferred" ? " [inferred]" : "";
@@ -55545,7 +55682,7 @@ async function executeMergeAttempt(params, aiTracker) {
55545
55682
  result,
55546
55683
  settings,
55547
55684
  testCommand,
55548
- buildCommand,
55685
+ buildCommand: buildCommand2,
55549
55686
  testSource,
55550
55687
  buildSource
55551
55688
  } = params;
@@ -55614,14 +55751,14 @@ async function executeMergeAttempt(params, aiTracker) {
55614
55751
  );
55615
55752
  mergerLog.log(`${taskId}: committed after auto-resolving all conflicts`);
55616
55753
  }
55617
- if (testCommand || buildCommand) {
55754
+ if (testCommand || buildCommand2) {
55618
55755
  throwIfAborted(options.signal, taskId);
55619
55756
  await runDeterministicVerification(
55620
55757
  store,
55621
55758
  rootDir,
55622
55759
  taskId,
55623
55760
  testCommand,
55624
- buildCommand,
55761
+ buildCommand2,
55625
55762
  testSource,
55626
55763
  buildSource,
55627
55764
  options.signal
@@ -55637,14 +55774,14 @@ async function executeMergeAttempt(params, aiTracker) {
55637
55774
  ).trim() === "0";
55638
55775
  if (squashIsEmpty) {
55639
55776
  mergerLog.log(`${taskId}: squash merge staged nothing \u2014 already merged`);
55640
- if (testCommand || buildCommand) {
55777
+ if (testCommand || buildCommand2) {
55641
55778
  throwIfAborted(options.signal, taskId);
55642
55779
  await runDeterministicVerification(
55643
55780
  store,
55644
55781
  rootDir,
55645
55782
  taskId,
55646
55783
  testCommand,
55647
- buildCommand,
55784
+ buildCommand2,
55648
55785
  testSource,
55649
55786
  buildSource,
55650
55787
  options.signal
@@ -55664,14 +55801,14 @@ async function executeMergeAttempt(params, aiTracker) {
55664
55801
  ).trim() === "0";
55665
55802
  if (squashIsEmpty) {
55666
55803
  mergerLog.log(`${taskId}: squash merge staged nothing \u2014 already merged`);
55667
- if (testCommand || buildCommand) {
55804
+ if (testCommand || buildCommand2) {
55668
55805
  throwIfAborted(options.signal, taskId);
55669
55806
  await runDeterministicVerification(
55670
55807
  store,
55671
55808
  rootDir,
55672
55809
  taskId,
55673
55810
  testCommand,
55674
- buildCommand,
55811
+ buildCommand2,
55675
55812
  testSource,
55676
55813
  buildSource,
55677
55814
  options.signal
@@ -55691,7 +55828,7 @@ async function executeMergeAttempt(params, aiTracker) {
55691
55828
  return false;
55692
55829
  }
55693
55830
  }
55694
- if (buildCommand) {
55831
+ if (buildCommand2) {
55695
55832
  throwIfAborted(options.signal, taskId);
55696
55833
  const stagedFiles = await getStagedFiles(rootDir);
55697
55834
  if (shouldSyncDependenciesForMerge(stagedFiles, hasInstallState(rootDir))) {
@@ -55712,7 +55849,7 @@ async function executeMergeAttempt(params, aiTracker) {
55712
55849
  simplifiedContext: attemptNum === 2,
55713
55850
  options,
55714
55851
  testCommand,
55715
- buildCommand
55852
+ buildCommand: buildCommand2
55716
55853
  });
55717
55854
  if (!agentResult.success) {
55718
55855
  const errorMessage = agentResult.error || "Build verification failed";
@@ -55727,14 +55864,14 @@ async function executeMergeAttempt(params, aiTracker) {
55727
55864
  }
55728
55865
  throw new Error(`Build verification failed for ${taskId}: ${errorMessage}`);
55729
55866
  }
55730
- if (testCommand || buildCommand) {
55867
+ if (testCommand || buildCommand2) {
55731
55868
  throwIfAborted(options.signal, taskId);
55732
55869
  await runDeterministicVerification(
55733
55870
  store,
55734
55871
  rootDir,
55735
55872
  taskId,
55736
55873
  testCommand,
55737
- buildCommand,
55874
+ buildCommand2,
55738
55875
  testSource,
55739
55876
  buildSource,
55740
55877
  options.signal
@@ -55762,7 +55899,7 @@ async function executeMergeAttempt(params, aiTracker) {
55762
55899
  }
55763
55900
  }
55764
55901
  async function attemptWithTheirsStrategy(params) {
55765
- const { rootDir, branch, commitLog, includeTaskId, taskId, store, settings, testCommand, buildCommand, testSource, buildSource } = params;
55902
+ const { rootDir, branch, commitLog, includeTaskId, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
55766
55903
  mergerLog.log(`${taskId}: attempting merge with -X theirs strategy`);
55767
55904
  try {
55768
55905
  throwIfAborted(params.options.signal, taskId);
@@ -55782,14 +55919,14 @@ async function attemptWithTheirsStrategy(params) {
55782
55919
  encoding: "utf-8"
55783
55920
  }).trim();
55784
55921
  if (staged === "0") {
55785
- if (testCommand || buildCommand) {
55922
+ if (testCommand || buildCommand2) {
55786
55923
  throwIfAborted(params.options.signal, taskId);
55787
55924
  await runDeterministicVerification(
55788
55925
  store,
55789
55926
  rootDir,
55790
55927
  taskId,
55791
55928
  testCommand,
55792
- buildCommand,
55929
+ buildCommand2,
55793
55930
  testSource,
55794
55931
  buildSource,
55795
55932
  params.options.signal
@@ -55806,14 +55943,14 @@ async function attemptWithTheirsStrategy(params) {
55806
55943
  { cwd: rootDir }
55807
55944
  );
55808
55945
  mergerLog.log(`${taskId}: committed with -X theirs auto-resolution`);
55809
- if (testCommand || buildCommand) {
55946
+ if (testCommand || buildCommand2) {
55810
55947
  throwIfAborted(params.options.signal, taskId);
55811
55948
  await runDeterministicVerification(
55812
55949
  store,
55813
55950
  rootDir,
55814
55951
  taskId,
55815
55952
  testCommand,
55816
- buildCommand,
55953
+ buildCommand2,
55817
55954
  testSource,
55818
55955
  buildSource,
55819
55956
  params.options.signal
@@ -55841,7 +55978,7 @@ async function runAiAgentForCommit(params) {
55841
55978
  simplifiedContext,
55842
55979
  options,
55843
55980
  testCommand,
55844
- buildCommand
55981
+ buildCommand: buildCommand2
55845
55982
  } = params;
55846
55983
  const settings = await store.getSettings();
55847
55984
  let buildFailed = false;
@@ -55930,7 +56067,7 @@ async function runAiAgentForCommit(params) {
55930
56067
  hasConflicts,
55931
56068
  simplifiedContext,
55932
56069
  testCommand,
55933
- buildCommand,
56070
+ buildCommand: buildCommand2,
55934
56071
  authorArg
55935
56072
  });
55936
56073
  mergerLog.log(`${taskId}: starting fresh merge agent session`);
@@ -55962,7 +56099,7 @@ async function runAiAgentForCommit(params) {
55962
56099
  simplifiedContext: true,
55963
56100
  // Also skip detailed context
55964
56101
  testCommand,
55965
- buildCommand,
56102
+ buildCommand: buildCommand2,
55966
56103
  authorArg
55967
56104
  });
55968
56105
  try {
@@ -55998,7 +56135,7 @@ async function runAiAgentForCommit(params) {
55998
56135
  encoding: "utf-8"
55999
56136
  }).trim();
56000
56137
  if (staged !== "0") {
56001
- if (!buildCommand) {
56138
+ if (!buildCommand2) {
56002
56139
  throwIfAborted(options.signal, taskId);
56003
56140
  mergerLog.log("Agent didn't commit \u2014 committing with fallback message");
56004
56141
  const escapedLog = commitLog.replace(/"/g, '\\"');
@@ -56025,7 +56162,7 @@ async function runAiAgentForCommit(params) {
56025
56162
  }
56026
56163
  }
56027
56164
  function buildMergePrompt(params) {
56028
- const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, testCommand, buildCommand, authorArg } = params;
56165
+ const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, testCommand, buildCommand: buildCommand2, authorArg } = params;
56029
56166
  const truncatedCommitLog = truncateWithEllipsis(commitLog, MERGE_COMMIT_LOG_MAX_CHARS);
56030
56167
  const truncatedDiffStat = truncateWithEllipsis(diffStat, MERGE_DIFF_STAT_MAX_CHARS);
56031
56168
  const parts = [
@@ -56073,11 +56210,11 @@ function buildMergePrompt(params) {
56073
56210
  "If it exits non-zero, call `fn_report_build_failure` with the concrete error output and stop without committing."
56074
56211
  );
56075
56212
  }
56076
- if (buildCommand) {
56213
+ if (buildCommand2) {
56077
56214
  parts.push(
56078
56215
  "",
56079
56216
  "## Build command",
56080
- `Build command: \`${buildCommand}\``,
56217
+ `Build command: \`${buildCommand2}\``,
56081
56218
  "",
56082
56219
  "This command is mandatory before commit.",
56083
56220
  "Run it with the bash tool in the current worktree and inspect the actual exit code.",
@@ -58487,6 +58624,8 @@ Lint, tests, and typecheck are also hard quality gates:
58487
58624
  loopRecoveryState = /* @__PURE__ */ new Map();
58488
58625
  /** Spawned child agent IDs per parent task ID. Used for lifecycle tracking. */
58489
58626
  spawnedAgents = /* @__PURE__ */ new Map();
58627
+ /** Per-task baseline of agent cumulative token counters used for delta persistence. */
58628
+ tokenUsageBaselines = /* @__PURE__ */ new Map();
58490
58629
  async finalizeAlreadyReviewedTask(taskId) {
58491
58630
  const latestTask = await this.store.getTask(taskId);
58492
58631
  if (!latestTask || latestTask.column !== "in-review") {
@@ -58602,6 +58741,65 @@ Lint, tests, and typecheck are also hard quality gates:
58602
58741
  async getTaskCompletionBlocker(task) {
58603
58742
  return getTaskCompletionBlockerForStore(this.store, task);
58604
58743
  }
58744
+ async initializeTokenUsageBaseline(taskId, task) {
58745
+ if (!this.options.agentStore) return;
58746
+ const currentTask = task ?? await this.store.getTask(taskId);
58747
+ const assignedAgentId = currentTask.assignedAgentId?.trim();
58748
+ if (!assignedAgentId) return;
58749
+ const existing = this.tokenUsageBaselines.get(taskId);
58750
+ if (existing?.agentId === assignedAgentId) return;
58751
+ const agent = await this.options.agentStore.getAgent(assignedAgentId);
58752
+ if (!agent) return;
58753
+ this.tokenUsageBaselines.set(taskId, {
58754
+ agentId: assignedAgentId,
58755
+ inputTokens: agent.totalInputTokens ?? 0,
58756
+ outputTokens: agent.totalOutputTokens ?? 0
58757
+ });
58758
+ }
58759
+ async persistTokenUsage(taskId) {
58760
+ if (!this.options.agentStore) return;
58761
+ const task = await this.store.getTask(taskId);
58762
+ const assignedAgentId = task.assignedAgentId?.trim();
58763
+ if (!assignedAgentId) return;
58764
+ const agent = await this.options.agentStore.getAgent(assignedAgentId);
58765
+ if (!agent) return;
58766
+ const currentInputTokens = agent.totalInputTokens ?? 0;
58767
+ const currentOutputTokens = agent.totalOutputTokens ?? 0;
58768
+ const baseline = this.tokenUsageBaselines.get(taskId);
58769
+ if (!baseline || baseline.agentId !== assignedAgentId) {
58770
+ this.tokenUsageBaselines.set(taskId, {
58771
+ agentId: assignedAgentId,
58772
+ inputTokens: currentInputTokens,
58773
+ outputTokens: currentOutputTokens
58774
+ });
58775
+ return;
58776
+ }
58777
+ const inputDelta = Math.max(0, currentInputTokens - baseline.inputTokens);
58778
+ const outputDelta = Math.max(0, currentOutputTokens - baseline.outputTokens);
58779
+ this.tokenUsageBaselines.set(taskId, {
58780
+ agentId: assignedAgentId,
58781
+ inputTokens: currentInputTokens,
58782
+ outputTokens: currentOutputTokens
58783
+ });
58784
+ if (inputDelta === 0 && outputDelta === 0 && !task.tokenUsage) {
58785
+ return;
58786
+ }
58787
+ const now = (/* @__PURE__ */ new Date()).toISOString();
58788
+ const mergedInputTokens = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
58789
+ const mergedOutputTokens = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
58790
+ const cachedTokens = task.tokenUsage?.cachedTokens ?? 0;
58791
+ const totalTokens = mergedInputTokens + mergedOutputTokens + cachedTokens;
58792
+ await this.store.updateTask(taskId, {
58793
+ tokenUsage: {
58794
+ inputTokens: mergedInputTokens,
58795
+ outputTokens: mergedOutputTokens,
58796
+ cachedTokens,
58797
+ totalTokens,
58798
+ firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
58799
+ lastUsedAt: now
58800
+ }
58801
+ });
58802
+ }
58605
58803
  /**
58606
58804
  * Execute a review handoff: move the task to in-review column with
58607
58805
  * awaiting-user-review status, assign the requesting user, and dispose
@@ -58624,6 +58822,7 @@ Lint, tests, and typecheck are also hard quality gates:
58624
58822
  },
58625
58823
  this.currentRunContext
58626
58824
  );
58825
+ await this.persistTokenUsage(task.id);
58627
58826
  await this.store.moveTask(task.id, "in-review");
58628
58827
  if (this.activeSessions.has(task.id)) {
58629
58828
  const { session: activeSession } = this.activeSessions.get(task.id);
@@ -58662,6 +58861,7 @@ Lint, tests, and typecheck are also hard quality gates:
58662
58861
  executorLog.log(`${task.id}: fast mode \u2014 skipping workflow steps on auto-recovery`);
58663
58862
  }
58664
58863
  }
58864
+ await this.persistTokenUsage(task.id);
58665
58865
  await this.store.moveTask(task.id, "in-review");
58666
58866
  await this.store.logEntry(task.id, "Auto-recovered: task work was complete but stuck in in-progress \u2014 moved to in-review");
58667
58867
  executorLog.log(`\u2713 ${task.id} auto-recovered completed task \u2192 in-review`);
@@ -59041,6 +59241,7 @@ Lint, tests, and typecheck are also hard quality gates:
59041
59241
  this.options.onStart?.(task, worktreePath);
59042
59242
  const detail = await this.store.getTask(task.id);
59043
59243
  executorLog.log(`${task.id}: fetched task detail (${detail.steps.length} steps, prompt length=${detail.prompt?.length ?? 0})`);
59244
+ await this.initializeTokenUsageBaseline(task.id, detail);
59044
59245
  if (detail.steps.length === 0) {
59045
59246
  const steps = await this.store.parseStepsFromPrompt(task.id);
59046
59247
  if (steps.length > 0) {
@@ -59088,6 +59289,9 @@ Lint, tests, and typecheck are also hard quality gates:
59088
59289
  } catch (err) {
59089
59290
  executorLog.warn(`${task.id}: failed to update step ${stepIndex} status: ${err}`);
59090
59291
  }
59292
+ this.persistTokenUsage(task.id).catch((err) => {
59293
+ executorLog.warn(`${task.id}: failed to persist token usage on step ${stepIndex} complete: ${err}`);
59294
+ });
59091
59295
  }
59092
59296
  });
59093
59297
  this.activeStepExecutors.set(task.id, stepExecutor);
@@ -59137,6 +59341,7 @@ Lint, tests, and typecheck are also hard quality gates:
59137
59341
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
59138
59342
  }
59139
59343
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
59344
+ await this.persistTokenUsage(task.id);
59140
59345
  await this.store.moveTask(task.id, "in-review");
59141
59346
  await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
59142
59347
  executorLog.log(`\u2713 ${task.id} completed (step-session) \u2192 in-review`);
@@ -59145,6 +59350,7 @@ Lint, tests, and typecheck are also hard quality gates:
59145
59350
  const failedSteps = results.filter((r) => !r.success);
59146
59351
  const errorSummary = failedSteps.map((r) => `Step ${r.stepIndex}: ${r.error || "unknown error"}`).join("; ");
59147
59352
  await this.store.updateTask(task.id, { status: "failed", error: errorSummary });
59353
+ await this.persistTokenUsage(task.id);
59148
59354
  await this.store.moveTask(task.id, "in-review");
59149
59355
  executorLog.log(`\u2717 ${task.id} step-session failed \u2192 in-review: ${errorSummary}`);
59150
59356
  this.options.onError?.(task, new Error(errorSummary));
@@ -59221,6 +59427,7 @@ Lint, tests, and typecheck are also hard quality gates:
59221
59427
  recoveryRetryCount: null,
59222
59428
  nextRecoveryAt: null
59223
59429
  });
59430
+ await this.persistTokenUsage(task.id);
59224
59431
  await this.store.moveTask(task.id, "in-review");
59225
59432
  executorLog.log(`\u2717 ${task.id} transient retries exhausted \u2192 in-review`);
59226
59433
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59228,6 +59435,7 @@ Lint, tests, and typecheck are also hard quality gates:
59228
59435
  executorLog.error(`\u2717 ${task.id} step-session execution failed:`, errorDetail);
59229
59436
  await this.store.logEntry(task.id, `Step-session execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
59230
59437
  await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
59438
+ await this.persistTokenUsage(task.id);
59231
59439
  await this.store.moveTask(task.id, "in-review");
59232
59440
  executorLog.log(`\u2717 ${task.id} step-session execution failed \u2192 in-review`);
59233
59441
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59462,6 +59670,7 @@ Lint, tests, and typecheck are also hard quality gates:
59462
59670
  if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
59463
59671
  executorLog.log(`${task.id} paused after completion (graceful session exit) \u2014 finalizing to in-review`);
59464
59672
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
59673
+ await this.persistTokenUsage(task.id);
59465
59674
  await this.store.moveTask(task.id, "in-review");
59466
59675
  this.options.onComplete?.(task);
59467
59676
  } else {
@@ -59511,6 +59720,7 @@ Lint, tests, and typecheck are also hard quality gates:
59511
59720
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
59512
59721
  }
59513
59722
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
59723
+ await this.persistTokenUsage(task.id);
59514
59724
  await this.store.moveTask(task.id, "in-review");
59515
59725
  executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
59516
59726
  this.options.onComplete?.(task);
@@ -59607,6 +59817,7 @@ Lint, tests, and typecheck are also hard quality gates:
59607
59817
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
59608
59818
  }
59609
59819
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
59820
+ await this.persistTokenUsage(task.id);
59610
59821
  await this.store.moveTask(task.id, "in-review");
59611
59822
  executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
59612
59823
  this.options.onComplete?.(task);
@@ -59631,6 +59842,7 @@ Lint, tests, and typecheck are also hard quality gates:
59631
59842
  } else {
59632
59843
  await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
59633
59844
  await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
59845
+ await this.persistTokenUsage(task.id);
59634
59846
  await this.store.moveTask(task.id, "in-review");
59635
59847
  executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no fn_task_done \u2192 in-review`);
59636
59848
  }
@@ -59694,6 +59906,7 @@ Lint, tests, and typecheck are also hard quality gates:
59694
59906
  if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
59695
59907
  executorLog.log(`${task.id} paused after completion \u2014 finalizing to in-review`);
59696
59908
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review", void 0, this.currentRunContext);
59909
+ await this.persistTokenUsage(task.id);
59697
59910
  await this.store.moveTask(task.id, "in-review");
59698
59911
  this.options.onComplete?.(task);
59699
59912
  } else {
@@ -59819,6 +60032,7 @@ Lint, tests, and typecheck are also hard quality gates:
59819
60032
  recoveryRetryCount: null,
59820
60033
  nextRecoveryAt: null
59821
60034
  });
60035
+ await this.persistTokenUsage(task.id);
59822
60036
  await this.store.moveTask(task.id, "in-review");
59823
60037
  executorLog.log(`\u2717 ${task.id} transient retries exhausted \u2192 in-review`);
59824
60038
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59827,6 +60041,7 @@ Lint, tests, and typecheck are also hard quality gates:
59827
60041
  executorLog.error(`\u2717 ${task.id} execution failed:`, errorDetail);
59828
60042
  await this.store.logEntry(task.id, `Execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
59829
60043
  await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
60044
+ await this.persistTokenUsage(task.id);
59830
60045
  await this.store.moveTask(task.id, "in-review");
59831
60046
  executorLog.log(`\u2717 ${task.id} execution failed \u2192 in-review`);
59832
60047
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59840,6 +60055,7 @@ Lint, tests, and typecheck are also hard quality gates:
59840
60055
  executorLog.warn(`terminateAllChildren failed for ${task.id}: ${err instanceof Error ? err.message : String(err)}`);
59841
60056
  }
59842
60057
  this.loopRecoveryState.delete(task.id);
60058
+ this.tokenUsageBaselines.delete(task.id);
59843
60059
  if (stuckRequeue === true) {
59844
60060
  try {
59845
60061
  const latestTask = await this.store.getTask(task.id);
@@ -70580,8 +70796,571 @@ var init_project_manager = __esm({
70580
70796
  }
70581
70797
  });
70582
70798
 
70799
+ // ../engine/src/remote-access/provider-adapters.ts
70800
+ import { accessSync, constants as fsConstants } from "node:fs";
70801
+ function isAbsoluteOrPathLike(input) {
70802
+ return input.startsWith("/") || input.startsWith("./") || input.startsWith("../");
70803
+ }
70804
+ function assertNonEmpty(value, label) {
70805
+ if (typeof value !== "string" || value.trim().length === 0) {
70806
+ throw new Error(`invalid_config:${label} must be a non-empty string`);
70807
+ }
70808
+ }
70809
+ function ensureNumberInRange(value, label) {
70810
+ if (value === void 0) {
70811
+ return void 0;
70812
+ }
70813
+ if (!Number.isFinite(value) || value <= 0) {
70814
+ throw new Error(`invalid_config:${label} must be a positive number`);
70815
+ }
70816
+ return Math.floor(value);
70817
+ }
70818
+ function collectSensitiveValues(config) {
70819
+ const values = /* @__PURE__ */ new Set();
70820
+ const env = config.env ?? {};
70821
+ const tokenEnvVar = config.tokenEnvVar;
70822
+ if (typeof tokenEnvVar === "string" && tokenEnvVar.trim().length > 0) {
70823
+ const tokenValue = env[tokenEnvVar] ?? process.env[tokenEnvVar];
70824
+ if (!tokenValue) {
70825
+ throw new Error(`invalid_config:missing credential in env var ${tokenEnvVar}`);
70826
+ }
70827
+ values.add(tokenValue);
70828
+ }
70829
+ for (const envName of config.sensitiveEnvVars ?? []) {
70830
+ const envValue = env[envName] ?? process.env[envName];
70831
+ if (envValue) {
70832
+ values.add(envValue);
70833
+ }
70834
+ }
70835
+ return [...values].sort((a, b) => b.length - a.length);
70836
+ }
70837
+ function redactValue(input, sensitiveValues) {
70838
+ let redacted = input;
70839
+ for (const value of sensitiveValues) {
70840
+ redacted = redacted.split(value).join("[REDACTED]");
70841
+ }
70842
+ return redacted;
70843
+ }
70844
+ function redactArgs(args, sensitiveValues) {
70845
+ return args.map((arg) => redactValue(arg, sensitiveValues));
70846
+ }
70847
+ function buildCommand(config) {
70848
+ const command = config.executablePath.trim();
70849
+ const args = [...config.args];
70850
+ const env = {
70851
+ ...process.env,
70852
+ ...config.env ?? {}
70853
+ };
70854
+ const sensitiveValues = collectSensitiveValues(config);
70855
+ const redactedPreview = [command, ...redactArgs(args, sensitiveValues)].join(" ").trim();
70856
+ return {
70857
+ provider: config.provider,
70858
+ command,
70859
+ args,
70860
+ cwd: config.cwd,
70861
+ env,
70862
+ redactedPreview,
70863
+ sensitiveValues,
70864
+ readinessTimeoutMs: config.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS,
70865
+ stopTimeoutMs: config.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS
70866
+ };
70867
+ }
70868
+ function parseCommonReadiness(line) {
70869
+ const normalized = line.trim();
70870
+ if (!normalized) {
70871
+ return null;
70872
+ }
70873
+ const urlMatch = normalized.match(URL_PATTERN);
70874
+ const url = urlMatch?.[1];
70875
+ if (/\b(connected|ready|available|started|serving)\b/i.test(normalized)) {
70876
+ return { ready: true, url };
70877
+ }
70878
+ if (url && /\b(trycloudflare|tailscale|ts\.net|serve|funnel)\b/i.test(normalized)) {
70879
+ return { ready: true, url };
70880
+ }
70881
+ return null;
70882
+ }
70883
+ function validateBaseConfig(config, provider) {
70884
+ if (config.provider !== provider) {
70885
+ throw new Error(`invalid_config:config provider ${config.provider} does not match ${provider}`);
70886
+ }
70887
+ assertNonEmpty(config.executablePath, "executablePath");
70888
+ if (!Array.isArray(config.args)) {
70889
+ throw new Error("invalid_config:args must be an array");
70890
+ }
70891
+ for (const [index, arg] of config.args.entries()) {
70892
+ assertNonEmpty(arg, `args[${index}]`);
70893
+ }
70894
+ if (config.cwd !== void 0) {
70895
+ assertNonEmpty(config.cwd, "cwd");
70896
+ }
70897
+ if (config.tokenEnvVar !== void 0) {
70898
+ assertNonEmpty(config.tokenEnvVar, "tokenEnvVar");
70899
+ }
70900
+ ensureNumberInRange(config.readinessTimeoutMs, "readinessTimeoutMs");
70901
+ ensureNumberInRange(config.stopTimeoutMs, "stopTimeoutMs");
70902
+ }
70903
+ function getTunnelProviderAdapter(provider) {
70904
+ return ADAPTERS[provider];
70905
+ }
70906
+ function redactTunnelText(input, sensitiveValues) {
70907
+ return redactValue(input, sensitiveValues);
70908
+ }
70909
+ var DEFAULT_READINESS_TIMEOUT_MS, DEFAULT_STOP_TIMEOUT_MS, URL_PATTERN, tailscaleAdapter, cloudflareAdapter, ADAPTERS;
70910
+ var init_provider_adapters = __esm({
70911
+ "../engine/src/remote-access/provider-adapters.ts"() {
70912
+ "use strict";
70913
+ DEFAULT_READINESS_TIMEOUT_MS = 2e4;
70914
+ DEFAULT_STOP_TIMEOUT_MS = 5e3;
70915
+ URL_PATTERN = /(https?:\/\/[^\s]+)/i;
70916
+ tailscaleAdapter = {
70917
+ provider: "tailscale",
70918
+ validateConfig(config) {
70919
+ validateBaseConfig(config, "tailscale");
70920
+ },
70921
+ buildCommand(config) {
70922
+ this.validateConfig(config);
70923
+ return buildCommand(config);
70924
+ },
70925
+ parseReadiness(line, _stream) {
70926
+ return parseCommonReadiness(line);
70927
+ }
70928
+ };
70929
+ cloudflareAdapter = {
70930
+ provider: "cloudflare",
70931
+ validateConfig(config) {
70932
+ validateBaseConfig(config, "cloudflare");
70933
+ if (config.provider === "cloudflare" && config.quickTunnel === true) {
70934
+ return;
70935
+ }
70936
+ if ("credentialsPath" in config && config.credentialsPath !== void 0) {
70937
+ assertNonEmpty(config.credentialsPath, "credentialsPath");
70938
+ if (isAbsoluteOrPathLike(config.credentialsPath)) {
70939
+ accessSync(config.credentialsPath, fsConstants.R_OK);
70940
+ }
70941
+ }
70942
+ },
70943
+ buildCommand(config) {
70944
+ this.validateConfig(config);
70945
+ return buildCommand(config);
70946
+ },
70947
+ parseReadiness(line, _stream) {
70948
+ return parseCommonReadiness(line);
70949
+ }
70950
+ };
70951
+ ADAPTERS = {
70952
+ tailscale: tailscaleAdapter,
70953
+ cloudflare: cloudflareAdapter
70954
+ };
70955
+ }
70956
+ });
70957
+
70958
+ // ../engine/src/remote-access/tunnel-process-manager.ts
70959
+ import { EventEmitter as EventEmitter18 } from "node:events";
70960
+ import { spawn as spawn2 } from "node:child_process";
70961
+ function nowIso() {
70962
+ return (/* @__PURE__ */ new Date()).toISOString();
70963
+ }
70964
+ function normalizeError(input) {
70965
+ if (input instanceof Error) {
70966
+ return input;
70967
+ }
70968
+ return new Error(String(input));
70969
+ }
70970
+ function maskSensitive(message, processHandle) {
70971
+ if (!processHandle) {
70972
+ return message;
70973
+ }
70974
+ return redactTunnelText(message, processHandle.command.sensitiveValues);
70975
+ }
70976
+ function killManagedProcess(child, signal) {
70977
+ if (typeof child.pid !== "number") {
70978
+ return;
70979
+ }
70980
+ if (process.platform !== "win32") {
70981
+ try {
70982
+ process.kill(-child.pid, signal);
70983
+ return;
70984
+ } catch {
70985
+ }
70986
+ }
70987
+ try {
70988
+ process.kill(child.pid, signal);
70989
+ } catch {
70990
+ }
70991
+ }
70992
+ function toStateError(code, err) {
70993
+ const normalized = normalizeError(err);
70994
+ return {
70995
+ code,
70996
+ message: normalized.message,
70997
+ at: nowIso()
70998
+ };
70999
+ }
71000
+ var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, LineBuffer, TunnelProcessManager;
71001
+ var init_tunnel_process_manager = __esm({
71002
+ "../engine/src/remote-access/tunnel-process-manager.ts"() {
71003
+ "use strict";
71004
+ init_logger2();
71005
+ init_provider_adapters();
71006
+ DEFAULT_MAX_LOG_ENTRIES = 400;
71007
+ DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
71008
+ LineBuffer = class {
71009
+ pending = "";
71010
+ push(chunk) {
71011
+ this.pending += chunk;
71012
+ const lines = this.pending.split(/\r?\n/);
71013
+ this.pending = lines.pop() ?? "";
71014
+ return lines.map((line) => line.trim()).filter(Boolean);
71015
+ }
71016
+ flush() {
71017
+ const tail = this.pending.trim();
71018
+ this.pending = "";
71019
+ return tail ? [tail] : [];
71020
+ }
71021
+ };
71022
+ TunnelProcessManager = class extends EventEmitter18 {
71023
+ maxLogEntries;
71024
+ defaultStopTimeoutMs;
71025
+ spawnImpl;
71026
+ status = {
71027
+ provider: null,
71028
+ state: "stopped",
71029
+ pid: null,
71030
+ startedAt: null,
71031
+ stoppedAt: null,
71032
+ url: null,
71033
+ lastError: null
71034
+ };
71035
+ logs = [];
71036
+ statusListeners = /* @__PURE__ */ new Set();
71037
+ logListeners = /* @__PURE__ */ new Set();
71038
+ processHandle = null;
71039
+ readinessTimer = null;
71040
+ stopTimer = null;
71041
+ operationChain = Promise.resolve();
71042
+ expectedStop = false;
71043
+ activeStopPromise = null;
71044
+ constructor(options = {}) {
71045
+ super();
71046
+ this.maxLogEntries = options.maxLogEntries ?? DEFAULT_MAX_LOG_ENTRIES;
71047
+ this.defaultStopTimeoutMs = options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS2;
71048
+ this.spawnImpl = options.spawnImpl ?? spawn2;
71049
+ }
71050
+ getStatus() {
71051
+ return { ...this.status, lastError: this.status.lastError ? { ...this.status.lastError } : null };
71052
+ }
71053
+ subscribeStatus(listener) {
71054
+ this.statusListeners.add(listener);
71055
+ listener(this.getStatus());
71056
+ return () => {
71057
+ this.statusListeners.delete(listener);
71058
+ };
71059
+ }
71060
+ subscribeLogs(listener) {
71061
+ this.logListeners.add(listener);
71062
+ return () => {
71063
+ this.logListeners.delete(listener);
71064
+ };
71065
+ }
71066
+ async start(provider, config) {
71067
+ return this.runExclusive(async () => {
71068
+ if (this.processHandle || this.status.state === "starting" || this.status.state === "running") {
71069
+ throw new Error("already_running:tunnel process is already active");
71070
+ }
71071
+ await this.startInternal(provider, config);
71072
+ });
71073
+ }
71074
+ async stop() {
71075
+ return this.runExclusive(async () => {
71076
+ await this.stopInternal();
71077
+ });
71078
+ }
71079
+ async switchProvider(target, config) {
71080
+ return this.runExclusive(async () => {
71081
+ const previousProvider = this.status.provider;
71082
+ if (this.processHandle) {
71083
+ await this.stopInternal();
71084
+ }
71085
+ try {
71086
+ await this.startInternal(target, config);
71087
+ } catch (error) {
71088
+ const stateError = toStateError("switch_failed", error);
71089
+ const redactedMessage = this.redactForProviderConfig(target, config, stateError.message);
71090
+ this.updateStatus({
71091
+ provider: target,
71092
+ state: "failed",
71093
+ pid: null,
71094
+ startedAt: null,
71095
+ stoppedAt: nowIso(),
71096
+ url: null,
71097
+ lastError: {
71098
+ ...stateError,
71099
+ message: redactedMessage
71100
+ }
71101
+ });
71102
+ this.emitLog("error", "manager", `Provider switch failed (${previousProvider ?? "none"} -> ${target}): ${redactedMessage}`);
71103
+ throw error;
71104
+ }
71105
+ });
71106
+ }
71107
+ redactForProviderConfig(provider, config, message) {
71108
+ try {
71109
+ const adapter = getTunnelProviderAdapter(provider);
71110
+ const command = adapter.buildCommand(config);
71111
+ return redactTunnelText(message, command.sensitiveValues);
71112
+ } catch {
71113
+ return message;
71114
+ }
71115
+ }
71116
+ async runExclusive(operation) {
71117
+ const next = this.operationChain.then(operation);
71118
+ this.operationChain = next.catch(() => void 0);
71119
+ return next;
71120
+ }
71121
+ async startInternal(provider, config) {
71122
+ const adapter = getTunnelProviderAdapter(provider);
71123
+ if (config.provider !== provider) {
71124
+ throw new Error(`invalid_config:provider mismatch (${config.provider} vs ${provider})`);
71125
+ }
71126
+ try {
71127
+ adapter.validateConfig(config);
71128
+ } catch (error) {
71129
+ const stateError = toStateError("invalid_config", error);
71130
+ this.updateStatus({
71131
+ provider,
71132
+ state: "failed",
71133
+ pid: null,
71134
+ startedAt: null,
71135
+ stoppedAt: nowIso(),
71136
+ url: null,
71137
+ lastError: stateError
71138
+ });
71139
+ this.emitLog("error", "manager", `Configuration validation failed for ${provider}: ${stateError.message}`);
71140
+ throw error;
71141
+ }
71142
+ const command = adapter.buildCommand(config);
71143
+ this.updateStatus({
71144
+ provider,
71145
+ state: "starting",
71146
+ pid: null,
71147
+ startedAt: nowIso(),
71148
+ stoppedAt: null,
71149
+ url: null,
71150
+ lastError: null
71151
+ });
71152
+ this.emitLog("info", "manager", `Starting ${provider} tunnel: ${command.redactedPreview}`);
71153
+ const child = this.spawnImpl(command.command, command.args, {
71154
+ cwd: command.cwd,
71155
+ env: command.env,
71156
+ detached: process.platform !== "win32",
71157
+ shell: false,
71158
+ stdio: ["ignore", "pipe", "pipe"]
71159
+ });
71160
+ this.processHandle = {
71161
+ provider,
71162
+ child,
71163
+ command
71164
+ };
71165
+ this.expectedStop = false;
71166
+ this.updateStatus({ pid: child.pid ?? null });
71167
+ const stdoutBuffer = new LineBuffer();
71168
+ const stderrBuffer = new LineBuffer();
71169
+ const attachStream = (stream, source, buffer) => {
71170
+ stream?.on("data", (chunk) => {
71171
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
71172
+ for (const line of buffer.push(text)) {
71173
+ this.handleOutputLine(source, line);
71174
+ }
71175
+ });
71176
+ };
71177
+ attachStream(child.stdout, "stdout", stdoutBuffer);
71178
+ attachStream(child.stderr, "stderr", stderrBuffer);
71179
+ child.once("error", (error) => {
71180
+ const maskedMessage = maskSensitive(normalizeError(error).message, this.processHandle);
71181
+ this.emitLog("error", "manager", `Spawn failure for ${provider}: ${maskedMessage}`);
71182
+ this.handleUnexpectedExit("start_failed", `Spawn failure: ${maskedMessage}`);
71183
+ });
71184
+ child.once("close", (code, signal) => {
71185
+ for (const line of stdoutBuffer.flush()) {
71186
+ this.handleOutputLine("stdout", line);
71187
+ }
71188
+ for (const line of stderrBuffer.flush()) {
71189
+ this.handleOutputLine("stderr", line);
71190
+ }
71191
+ const reason = signal ? `signal ${signal}` : `exit code ${code ?? 0}`;
71192
+ if (this.expectedStop) {
71193
+ this.emitLog("info", "manager", `Tunnel process stopped (${reason})`);
71194
+ this.finalizeStoppedState();
71195
+ return;
71196
+ }
71197
+ this.emitLog("error", "manager", `Tunnel process exited unexpectedly (${reason})`);
71198
+ this.handleUnexpectedExit("process_exit", `Process exited unexpectedly (${reason})`);
71199
+ });
71200
+ this.readinessTimer = setTimeout(() => {
71201
+ if (this.status.state === "starting" && this.processHandle?.provider === provider) {
71202
+ this.emitLog("error", "manager", `Readiness timed out after ${command.readinessTimeoutMs}ms`);
71203
+ this.handleUnexpectedExit("readiness_timeout", `Tunnel readiness timeout after ${command.readinessTimeoutMs}ms`);
71204
+ }
71205
+ }, command.readinessTimeoutMs);
71206
+ this.readinessTimer.unref?.();
71207
+ }
71208
+ async stopInternal() {
71209
+ if (!this.processHandle) {
71210
+ this.updateStatus({
71211
+ provider: null,
71212
+ state: "stopped",
71213
+ pid: null,
71214
+ stoppedAt: nowIso(),
71215
+ url: null
71216
+ });
71217
+ return;
71218
+ }
71219
+ if (this.activeStopPromise) {
71220
+ await this.activeStopPromise;
71221
+ return;
71222
+ }
71223
+ const currentHandle = this.processHandle;
71224
+ const stopTimeoutMs = currentHandle.command.stopTimeoutMs || this.defaultStopTimeoutMs;
71225
+ this.expectedStop = true;
71226
+ this.updateStatus({
71227
+ state: "stopping",
71228
+ provider: currentHandle.provider,
71229
+ pid: currentHandle.child.pid ?? null,
71230
+ lastError: null
71231
+ });
71232
+ this.emitLog("info", "manager", `Stopping ${currentHandle.provider} tunnel (pid=${currentHandle.child.pid ?? "n/a"})`);
71233
+ this.activeStopPromise = new Promise((resolve16) => {
71234
+ const onClose = () => {
71235
+ currentHandle.child.removeListener("close", onClose);
71236
+ resolve16();
71237
+ };
71238
+ currentHandle.child.once("close", onClose);
71239
+ killManagedProcess(currentHandle.child, "SIGTERM");
71240
+ this.stopTimer = setTimeout(() => {
71241
+ if (this.processHandle === currentHandle) {
71242
+ this.emitLog("warn", "manager", `Graceful stop timed out after ${stopTimeoutMs}ms, sending SIGKILL`);
71243
+ killManagedProcess(currentHandle.child, "SIGKILL");
71244
+ }
71245
+ }, stopTimeoutMs);
71246
+ this.stopTimer.unref?.();
71247
+ }).finally(() => {
71248
+ this.activeStopPromise = null;
71249
+ if (this.stopTimer) {
71250
+ clearTimeout(this.stopTimer);
71251
+ this.stopTimer = null;
71252
+ }
71253
+ });
71254
+ await this.activeStopPromise;
71255
+ }
71256
+ handleOutputLine(source, rawLine) {
71257
+ const processHandle = this.processHandle;
71258
+ const maskedLine = maskSensitive(rawLine, processHandle);
71259
+ this.emitLog("info", source, maskedLine);
71260
+ if (!processHandle || this.status.state !== "starting") {
71261
+ return;
71262
+ }
71263
+ const adapter = getTunnelProviderAdapter(processHandle.provider);
71264
+ const readiness = adapter.parseReadiness(maskedLine, source);
71265
+ if (!readiness?.ready) {
71266
+ return;
71267
+ }
71268
+ this.clearReadinessTimer();
71269
+ this.updateStatus({
71270
+ state: "running",
71271
+ provider: processHandle.provider,
71272
+ pid: processHandle.child.pid ?? null,
71273
+ url: readiness.url ?? this.status.url,
71274
+ startedAt: this.status.startedAt ?? nowIso(),
71275
+ lastError: null
71276
+ });
71277
+ this.emitLog("info", "manager", `${processHandle.provider} tunnel is running`);
71278
+ }
71279
+ handleUnexpectedExit(code, message) {
71280
+ this.clearReadinessTimer();
71281
+ if (this.stopTimer) {
71282
+ clearTimeout(this.stopTimer);
71283
+ this.stopTimer = null;
71284
+ }
71285
+ this.expectedStop = false;
71286
+ const provider = this.processHandle?.provider ?? this.status.provider;
71287
+ this.processHandle = null;
71288
+ this.updateStatus({
71289
+ provider,
71290
+ state: "failed",
71291
+ pid: null,
71292
+ stoppedAt: nowIso(),
71293
+ url: null,
71294
+ lastError: {
71295
+ code,
71296
+ message,
71297
+ at: nowIso()
71298
+ }
71299
+ });
71300
+ }
71301
+ finalizeStoppedState() {
71302
+ this.clearReadinessTimer();
71303
+ if (this.stopTimer) {
71304
+ clearTimeout(this.stopTimer);
71305
+ this.stopTimer = null;
71306
+ }
71307
+ this.expectedStop = false;
71308
+ this.processHandle = null;
71309
+ this.updateStatus({
71310
+ provider: null,
71311
+ state: "stopped",
71312
+ pid: null,
71313
+ stoppedAt: nowIso(),
71314
+ url: null,
71315
+ lastError: null
71316
+ });
71317
+ }
71318
+ clearReadinessTimer() {
71319
+ if (this.readinessTimer) {
71320
+ clearTimeout(this.readinessTimer);
71321
+ this.readinessTimer = null;
71322
+ }
71323
+ }
71324
+ updateStatus(patch) {
71325
+ this.status = {
71326
+ ...this.status,
71327
+ ...patch,
71328
+ lastError: patch.lastError === void 0 ? this.status.lastError : patch.lastError
71329
+ };
71330
+ const snapshot = this.getStatus();
71331
+ for (const listener of this.statusListeners) {
71332
+ listener(snapshot);
71333
+ }
71334
+ this.emit("status", snapshot);
71335
+ }
71336
+ emitLog(level, source, message) {
71337
+ const safeMessage = maskSensitive(message, this.processHandle);
71338
+ const entry = {
71339
+ timestamp: nowIso(),
71340
+ provider: this.status.provider,
71341
+ level,
71342
+ source,
71343
+ message: safeMessage
71344
+ };
71345
+ this.logs.push(entry);
71346
+ if (this.logs.length > this.maxLogEntries) {
71347
+ this.logs.splice(0, this.logs.length - this.maxLogEntries);
71348
+ }
71349
+ const logMethod = level === "error" ? "error" : level === "warn" ? "warn" : "log";
71350
+ remoteTunnelLog[logMethod](safeMessage);
71351
+ for (const listener of this.logListeners) {
71352
+ listener(entry);
71353
+ }
71354
+ this.emit("log", entry);
71355
+ }
71356
+ };
71357
+ }
71358
+ });
71359
+
70583
71360
  // ../engine/src/project-engine.ts
70584
- var ProjectEngine;
71361
+ import { execFile as execFile3 } from "node:child_process";
71362
+ import { promisify as promisify10 } from "node:util";
71363
+ var execFileAsync, isRemoteActive, ProjectEngine;
70585
71364
  var init_project_engine = __esm({
70586
71365
  "../engine/src/project-engine.ts"() {
70587
71366
  "use strict";
@@ -70593,6 +71372,9 @@ var init_project_engine = __esm({
70593
71372
  init_merger();
70594
71373
  init_concurrency();
70595
71374
  init_logger2();
71375
+ init_tunnel_process_manager();
71376
+ execFileAsync = promisify10(execFile3);
71377
+ isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
70596
71378
  ProjectEngine = class _ProjectEngine {
70597
71379
  constructor(config, centralCore, options = {}) {
70598
71380
  this.config = config;
@@ -70606,6 +71388,13 @@ var init_project_engine = __esm({
70606
71388
  notifier;
70607
71389
  cronRunner;
70608
71390
  automationStore;
71391
+ remoteTunnelManager;
71392
+ remoteTunnelRestoreDiagnostics = {
71393
+ outcome: "skipped",
71394
+ reason: "not_attempted",
71395
+ at: (/* @__PURE__ */ new Date()).toISOString(),
71396
+ provider: null
71397
+ };
70609
71398
  // ── Auto-merge state ──
70610
71399
  mergeQueue = [];
70611
71400
  mergeActive = /* @__PURE__ */ new Set();
@@ -70633,6 +71422,14 @@ var init_project_engine = __esm({
70633
71422
  await this.runtime.start();
70634
71423
  const store = this.runtime.getTaskStore();
70635
71424
  const cwd = this.config.workingDirectory;
71425
+ this.remoteTunnelManager = new TunnelProcessManager();
71426
+ try {
71427
+ await this.restoreRemoteTunnelIfNeeded(store);
71428
+ } catch (error) {
71429
+ const message = error instanceof Error ? error.message : String(error);
71430
+ this.setRestoreDiagnostics("failed", "restore_start_failed", null, message);
71431
+ runtimeLog.warn(`Remote tunnel restore evaluation failed (continuing startup): ${message}`);
71432
+ }
70636
71433
  this.prMonitor = new PrMonitor();
70637
71434
  this.prCommentHandler = new PrCommentHandler(store);
70638
71435
  this.prMonitor.onNewComments(
@@ -70732,6 +71529,30 @@ var init_project_engine = __esm({
70732
71529
  }
70733
71530
  this.notifier?.stop();
70734
71531
  this.cronRunner?.stop();
71532
+ const tunnelManager = this.remoteTunnelManager;
71533
+ this.remoteTunnelManager = void 0;
71534
+ if (tunnelManager) {
71535
+ let shutdownStore = null;
71536
+ try {
71537
+ shutdownStore = this.runtime.getTaskStore();
71538
+ } catch {
71539
+ shutdownStore = null;
71540
+ }
71541
+ if (shutdownStore) {
71542
+ try {
71543
+ await this.persistShutdownRemoteLifecycle(shutdownStore, tunnelManager.getStatus());
71544
+ } catch (error) {
71545
+ const message = error instanceof Error ? error.message : String(error);
71546
+ runtimeLog.warn(`Failed to persist remote lifecycle shutdown markers: ${message}`);
71547
+ }
71548
+ }
71549
+ try {
71550
+ await tunnelManager.stop();
71551
+ } catch (error) {
71552
+ const message = error instanceof Error ? error.message : String(error);
71553
+ runtimeLog.warn(`Tunnel process manager stop failed (continuing shutdown): ${message}`);
71554
+ }
71555
+ }
70735
71556
  await this.runtime.stop();
70736
71557
  runtimeLog.log(`ProjectEngine stopped for ${this.config.projectId}`);
70737
71558
  }
@@ -70776,6 +71597,71 @@ var init_project_engine = __esm({
70776
71597
  getRoutineStore() {
70777
71598
  return this.runtime.getRoutineStore();
70778
71599
  }
71600
+ /** Get the remote tunnel manager (available after start()). */
71601
+ getRemoteTunnelManager() {
71602
+ return this.remoteTunnelManager;
71603
+ }
71604
+ getRemoteTunnelRestoreDiagnostics() {
71605
+ return { ...this.remoteTunnelRestoreDiagnostics };
71606
+ }
71607
+ async startRemoteTunnel() {
71608
+ const manager = this.remoteTunnelManager;
71609
+ if (!manager) {
71610
+ throw new Error("remote_tunnel_unavailable:remote tunnel manager is not initialized");
71611
+ }
71612
+ const store = this.runtime.getTaskStore();
71613
+ const settings = await store.getSettings();
71614
+ const remoteAccess = settings.remoteAccess;
71615
+ if (!remoteAccess || !isRemoteActive(remoteAccess)) {
71616
+ throw new Error("invalid_config:no remote access provider enabled");
71617
+ }
71618
+ const provider = remoteAccess.activeProvider;
71619
+ if (!provider) {
71620
+ throw new Error("invalid_config:no active remote provider configured");
71621
+ }
71622
+ const lifecycle = await this.evaluateRemoteLifecycle(settings, provider);
71623
+ if (!lifecycle.config) {
71624
+ throw new Error(`${lifecycle.reason ?? "invalid_config"}:${lifecycle.message ?? "remote provider prerequisites are not met"}`);
71625
+ }
71626
+ const current = manager.getStatus();
71627
+ if (current.state === "running" && current.provider === provider) {
71628
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71629
+ ...remoteAccess.lifecycle,
71630
+ wasRunningOnShutdown: true,
71631
+ lastRunningProvider: provider
71632
+ });
71633
+ return manager.getStatus();
71634
+ }
71635
+ if (current.state === "running" && current.provider && current.provider !== provider) {
71636
+ await manager.switchProvider(provider, lifecycle.config);
71637
+ } else {
71638
+ await manager.start(provider, lifecycle.config);
71639
+ }
71640
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71641
+ ...remoteAccess.lifecycle,
71642
+ wasRunningOnShutdown: true,
71643
+ lastRunningProvider: provider
71644
+ });
71645
+ return manager.getStatus();
71646
+ }
71647
+ async stopRemoteTunnel() {
71648
+ const manager = this.remoteTunnelManager;
71649
+ if (!manager) {
71650
+ throw new Error("remote_tunnel_unavailable:remote tunnel manager is not initialized");
71651
+ }
71652
+ await manager.stop();
71653
+ const store = this.runtime.getTaskStore();
71654
+ const settings = await store.getSettings();
71655
+ const remoteAccess = settings.remoteAccess;
71656
+ if (remoteAccess) {
71657
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71658
+ ...remoteAccess.lifecycle,
71659
+ wasRunningOnShutdown: false,
71660
+ lastRunningProvider: null
71661
+ });
71662
+ }
71663
+ return manager.getStatus();
71664
+ }
70779
71665
  /** Get the RoutineRunner (if initialized). */
70780
71666
  getRoutineRunner() {
70781
71667
  return this.runtime.getRoutineRunner();
@@ -70810,6 +71696,183 @@ var init_project_engine = __esm({
70810
71696
  this.internalEnqueueMerge(taskId);
70811
71697
  });
70812
71698
  }
71699
+ setRestoreDiagnostics(outcome, reason, provider, message) {
71700
+ this.remoteTunnelRestoreDiagnostics = {
71701
+ outcome,
71702
+ reason,
71703
+ provider,
71704
+ message,
71705
+ at: (/* @__PURE__ */ new Date()).toISOString()
71706
+ };
71707
+ }
71708
+ async restoreRemoteTunnelIfNeeded(store) {
71709
+ const manager = this.remoteTunnelManager;
71710
+ if (!manager) {
71711
+ return;
71712
+ }
71713
+ const settings = await store.getSettings();
71714
+ const remoteAccess = settings.remoteAccess;
71715
+ if (!remoteAccess || !isRemoteActive(remoteAccess)) {
71716
+ this.setRestoreDiagnostics("skipped", "remote_access_disabled", null);
71717
+ return;
71718
+ }
71719
+ const lifecycle = remoteAccess.lifecycle;
71720
+ if (!lifecycle.rememberLastRunning) {
71721
+ this.setRestoreDiagnostics("skipped", "remember_last_running_disabled", null);
71722
+ if (lifecycle.wasRunningOnShutdown || lifecycle.lastRunningProvider) {
71723
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71724
+ ...lifecycle,
71725
+ wasRunningOnShutdown: false,
71726
+ lastRunningProvider: null
71727
+ });
71728
+ }
71729
+ return;
71730
+ }
71731
+ if (!lifecycle.wasRunningOnShutdown) {
71732
+ this.setRestoreDiagnostics("skipped", "no_prior_running_marker", null);
71733
+ return;
71734
+ }
71735
+ const provider = lifecycle.lastRunningProvider ?? remoteAccess.activeProvider;
71736
+ if (!provider) {
71737
+ this.setRestoreDiagnostics("skipped", "provider_missing", null);
71738
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71739
+ ...lifecycle,
71740
+ wasRunningOnShutdown: false,
71741
+ lastRunningProvider: null
71742
+ });
71743
+ return;
71744
+ }
71745
+ const evaluation = await this.evaluateRemoteLifecycle(settings, provider);
71746
+ if (!evaluation.config) {
71747
+ this.setRestoreDiagnostics("skipped", evaluation.reason ?? "provider_not_configured", provider, evaluation.message);
71748
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71749
+ ...lifecycle,
71750
+ wasRunningOnShutdown: false,
71751
+ lastRunningProvider: null
71752
+ });
71753
+ return;
71754
+ }
71755
+ try {
71756
+ await manager.start(provider, evaluation.config);
71757
+ this.setRestoreDiagnostics("applied", "restore_started", provider);
71758
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71759
+ ...lifecycle,
71760
+ wasRunningOnShutdown: true,
71761
+ lastRunningProvider: provider
71762
+ }, provider);
71763
+ } catch (error) {
71764
+ const message = error instanceof Error ? error.message : String(error);
71765
+ this.setRestoreDiagnostics("failed", "restore_start_failed", provider, message);
71766
+ runtimeLog.warn(`Remote tunnel restore failed for ${provider}: ${message}`);
71767
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71768
+ ...lifecycle,
71769
+ wasRunningOnShutdown: false,
71770
+ lastRunningProvider: null
71771
+ });
71772
+ }
71773
+ }
71774
+ async persistShutdownRemoteLifecycle(store, status) {
71775
+ const settings = await store.getSettings();
71776
+ const remoteAccess = settings.remoteAccess;
71777
+ if (!remoteAccess) {
71778
+ return;
71779
+ }
71780
+ const shouldRememberRunning = (status.state === "running" || status.state === "starting" || status.state === "stopping") && status.provider !== null;
71781
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71782
+ ...remoteAccess.lifecycle,
71783
+ wasRunningOnShutdown: shouldRememberRunning,
71784
+ lastRunningProvider: shouldRememberRunning ? status.provider : null
71785
+ }, shouldRememberRunning ? status.provider : remoteAccess.activeProvider);
71786
+ }
71787
+ async writeRemoteLifecycleState(store, remoteAccess, lifecycle, activeProviderOverride) {
71788
+ await store.updateSettings({
71789
+ remoteAccess: {
71790
+ ...remoteAccess,
71791
+ activeProvider: activeProviderOverride === void 0 ? remoteAccess.activeProvider : activeProviderOverride,
71792
+ lifecycle
71793
+ }
71794
+ });
71795
+ }
71796
+ async evaluateRemoteLifecycle(settings, provider) {
71797
+ const remoteAccess = settings.remoteAccess;
71798
+ if (!remoteAccess || !isRemoteActive(remoteAccess)) {
71799
+ return { provider, reason: "remote_access_disabled", message: "No remote provider is enabled" };
71800
+ }
71801
+ if (provider === "tailscale") {
71802
+ const tailscale = remoteAccess.providers.tailscale;
71803
+ if (!tailscale.enabled) {
71804
+ return { provider, reason: "provider_not_enabled", message: "Tailscale provider is disabled" };
71805
+ }
71806
+ if (!tailscale.hostname?.trim() || !Number.isFinite(tailscale.targetPort) || tailscale.targetPort <= 0) {
71807
+ return { provider, reason: "provider_not_configured", message: "Tailscale hostname and target port must be configured" };
71808
+ }
71809
+ const executable2 = await this.checkExecutableAvailable("tailscale");
71810
+ if (!executable2.available) {
71811
+ return { provider, reason: "runtime_prerequisite_missing", message: executable2.message };
71812
+ }
71813
+ return {
71814
+ provider,
71815
+ config: {
71816
+ provider: "tailscale",
71817
+ executablePath: "tailscale",
71818
+ args: ["funnel", String(Math.floor(tailscale.targetPort))]
71819
+ }
71820
+ };
71821
+ }
71822
+ const cloudflare = remoteAccess.providers.cloudflare;
71823
+ if (!cloudflare.enabled) {
71824
+ return { provider, reason: "provider_not_enabled", message: "Cloudflare provider is disabled" };
71825
+ }
71826
+ if (cloudflare.quickTunnel === true) {
71827
+ const executable2 = await this.checkExecutableAvailable("cloudflared");
71828
+ if (!executable2.available) {
71829
+ return { provider, reason: "runtime_prerequisite_missing", message: executable2.message };
71830
+ }
71831
+ return {
71832
+ provider,
71833
+ config: {
71834
+ provider: "cloudflare",
71835
+ quickTunnel: true,
71836
+ executablePath: "cloudflared",
71837
+ args: ["tunnel", "--url", "http://localhost:4040"]
71838
+ }
71839
+ };
71840
+ }
71841
+ if (!cloudflare.tunnelName?.trim() || !cloudflare.ingressUrl?.trim()) {
71842
+ return { provider, reason: "provider_not_configured", message: "Cloudflare tunnel name and ingress URL must be configured" };
71843
+ }
71844
+ if (!cloudflare.tunnelToken?.trim()) {
71845
+ return { provider, reason: "provider_not_configured", message: "Cloudflare tunnel token is required" };
71846
+ }
71847
+ const executable = await this.checkExecutableAvailable("cloudflared");
71848
+ if (!executable.available) {
71849
+ return { provider, reason: "runtime_prerequisite_missing", message: executable.message };
71850
+ }
71851
+ return {
71852
+ provider,
71853
+ config: {
71854
+ provider: "cloudflare",
71855
+ executablePath: "cloudflared",
71856
+ args: ["tunnel", "--no-autoupdate", "run", cloudflare.tunnelName.trim()],
71857
+ tokenEnvVar: "TUNNEL_TOKEN",
71858
+ env: {
71859
+ TUNNEL_TOKEN: cloudflare.tunnelToken
71860
+ }
71861
+ }
71862
+ };
71863
+ }
71864
+ async checkExecutableAvailable(command) {
71865
+ const checker = process.platform === "win32" ? "where" : "which";
71866
+ try {
71867
+ await execFileAsync(checker, [command]);
71868
+ return { available: true };
71869
+ } catch {
71870
+ return {
71871
+ available: false,
71872
+ message: `${command} is not available on PATH`
71873
+ };
71874
+ }
71875
+ }
70813
71876
  // ── Merge eligibility helpers (richer logic from dashboard.ts) ──
70814
71877
  /**
70815
71878
  * True when a retry-exhausted task in "in-review" has a verification buffer
@@ -71422,6 +72485,15 @@ var init_peer_exchange_service = __esm({
71422
72485
  }
71423
72486
  });
71424
72487
 
72488
+ // ../engine/src/remote-access/index.ts
72489
+ var init_remote_access = __esm({
72490
+ "../engine/src/remote-access/index.ts"() {
72491
+ "use strict";
72492
+ init_provider_adapters();
72493
+ init_tunnel_process_manager();
72494
+ }
72495
+ });
72496
+
71425
72497
  // ../engine/src/index.ts
71426
72498
  var init_src2 = __esm({
71427
72499
  "../engine/src/index.ts"() {
@@ -71462,6 +72534,7 @@ var init_src2 = __esm({
71462
72534
  init_project_engine_manager();
71463
72535
  init_node_health_monitor();
71464
72536
  init_peer_exchange_service();
72537
+ init_remote_access();
71465
72538
  init_remote_node_client();
71466
72539
  init_remote_node_runtime();
71467
72540
  init_step_session_executor();
@@ -71615,7 +72688,7 @@ var init_ai_session_diagnostics = __esm({
71615
72688
 
71616
72689
  // ../dashboard/src/planning.ts
71617
72690
  import { randomUUID as randomUUID10 } from "node:crypto";
71618
- import { EventEmitter as EventEmitter18 } from "node:events";
72691
+ import { EventEmitter as EventEmitter19 } from "node:events";
71619
72692
  async function initEngine2() {
71620
72693
  try {
71621
72694
  const engineModule = "@fusion/engine";
@@ -72449,7 +73522,7 @@ For completion:
72449
73522
  process.on("beforeExit", () => {
72450
73523
  clearInterval(cleanupInterval2);
72451
73524
  });
72452
- PlanningStreamManager = class extends EventEmitter18 {
73525
+ PlanningStreamManager = class extends EventEmitter19 {
72453
73526
  constructor(bufferSize = 100) {
72454
73527
  super();
72455
73528
  this.bufferSize = bufferSize;
@@ -72561,10 +73634,756 @@ For completion:
72561
73634
  }
72562
73635
  });
72563
73636
 
72564
- // ../dashboard/src/claude-cli-probe.ts
72565
- var init_claude_cli_probe = __esm({
72566
- "../dashboard/src/claude-cli-probe.ts"() {
73637
+ // ../dashboard/src/terminal.ts
73638
+ import { spawn as spawn3 } from "node:child_process";
73639
+ import { randomUUID as randomUUID11 } from "node:crypto";
73640
+ import { EventEmitter as EventEmitter20 } from "node:events";
73641
+ function extractBaseCommand(command) {
73642
+ let trimmed = command.trim();
73643
+ while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) {
73644
+ const match2 = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*=(?:[^\s]*|"[^"]*"|'[^']*'))\s*/);
73645
+ if (match2) {
73646
+ trimmed = trimmed.slice(match2[0].length).trim();
73647
+ } else {
73648
+ break;
73649
+ }
73650
+ }
73651
+ if (trimmed.startsWith("sudo ")) {
73652
+ trimmed = trimmed.slice(5).trim();
73653
+ }
73654
+ const match = trimmed.match(/^([A-Za-z0-9._-]+)/);
73655
+ return match ? match[1].toLowerCase() : null;
73656
+ }
73657
+ function validateCommand(command) {
73658
+ for (const pattern of SUBSTITUTION_PATTERNS) {
73659
+ if (pattern.test(command)) {
73660
+ return { valid: false, error: "Command substitution is not allowed" };
73661
+ }
73662
+ }
73663
+ if (/[\0\r\n]/.test(command)) {
73664
+ return { valid: false, error: "Command contains invalid control characters" };
73665
+ }
73666
+ for (const pattern of BLOCKED_PATTERNS) {
73667
+ if (pattern.test(command)) {
73668
+ return { valid: false, error: "Command contains dangerous patterns and is not allowed" };
73669
+ }
73670
+ }
73671
+ const segments = command.split(CHAIN_OPERATORS);
73672
+ for (const raw of segments) {
73673
+ const segment = raw.trim();
73674
+ if (segment.length === 0) continue;
73675
+ const baseCommand = extractBaseCommand(segment);
73676
+ if (!baseCommand) {
73677
+ return { valid: false, error: "Could not parse command" };
73678
+ }
73679
+ if (!ALLOWED_COMMANDS.has(baseCommand)) {
73680
+ return {
73681
+ valid: false,
73682
+ error: `Command '${baseCommand}' is not in the allowed command list. Allowed commands: ${Array.from(ALLOWED_COMMANDS).sort().join(", ")}`
73683
+ };
73684
+ }
73685
+ }
73686
+ return { valid: true };
73687
+ }
73688
+ var ALLOWED_COMMANDS, BLOCKED_PATTERNS, SUBSTITUTION_PATTERNS, CHAIN_OPERATORS, TerminalSessionManager, terminalSessionManager;
73689
+ var init_terminal = __esm({
73690
+ "../dashboard/src/terminal.ts"() {
73691
+ "use strict";
73692
+ ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
73693
+ // Version control
73694
+ "git",
73695
+ // Package managers
73696
+ "npm",
73697
+ "pnpm",
73698
+ "yarn",
73699
+ "bun",
73700
+ // File operations
73701
+ "ls",
73702
+ "cat",
73703
+ "echo",
73704
+ "pwd",
73705
+ "cd",
73706
+ "mkdir",
73707
+ "touch",
73708
+ "cp",
73709
+ "mv",
73710
+ "rm",
73711
+ "head",
73712
+ "tail",
73713
+ "less",
73714
+ "more",
73715
+ "find",
73716
+ "grep",
73717
+ // System info
73718
+ "clear",
73719
+ "which",
73720
+ "whoami",
73721
+ "uname",
73722
+ "date",
73723
+ // Node/JS
73724
+ "node",
73725
+ "npx",
73726
+ "tsx",
73727
+ // Python
73728
+ "python",
73729
+ "python3",
73730
+ "pip",
73731
+ "pip3",
73732
+ // Network
73733
+ "curl",
73734
+ "wget",
73735
+ // Build tools
73736
+ "make",
73737
+ "cmake",
73738
+ // Process management
73739
+ "ps",
73740
+ "top",
73741
+ "htop",
73742
+ "kill",
73743
+ "pkill",
73744
+ // Shell builtins that are safe
73745
+ "source",
73746
+ ".",
73747
+ "export",
73748
+ "env",
73749
+ "printenv",
73750
+ "alias",
73751
+ // Editors (for viewing)
73752
+ "code",
73753
+ // VS Code CLI
73754
+ "vim",
73755
+ "vi",
73756
+ "nano"
73757
+ ]);
73758
+ BLOCKED_PATTERNS = [
73759
+ // System destruction
73760
+ /rm\s+(-[rf]+\s+)?\/\s*$/i,
73761
+ // rm -rf / or rm /
73762
+ /rm\s+(-[rf]+\s+)?\/\*/i,
73763
+ // rm -rf /*
73764
+ />\s*\/dev\/sda/i,
73765
+ // Direct disk write
73766
+ /:\(\)\s*\{\s*:\s*\|:\s*&\s*\};\s*:/i,
73767
+ // Fork bomb
73768
+ /mkfs\.\w+\s+/i,
73769
+ // Filesystem formatting
73770
+ /dd\s+if=/i,
73771
+ // dd with input file
73772
+ /\.\s*\/dev\/null/i
73773
+ // Sourcing /dev/null tricks
73774
+ ];
73775
+ SUBSTITUTION_PATTERNS = [
73776
+ /\$\(/,
73777
+ // $(...) — command substitution
73778
+ /`/,
73779
+ // `...` — backtick command substitution
73780
+ /<\(/,
73781
+ // <(...) — process substitution (bash)
73782
+ />\(/
73783
+ // >(...) — process substitution (bash)
73784
+ ];
73785
+ CHAIN_OPERATORS = /&&|\|\||;|(?<!\|)\|(?!\|)/;
73786
+ TerminalSessionManager = class extends EventEmitter20 {
73787
+ sessions = /* @__PURE__ */ new Map();
73788
+ defaultTimeout = 3e4;
73789
+ // 30 seconds
73790
+ /**
73791
+ * Creates a new terminal session and spawns the command.
73792
+ * Returns the session ID for tracking.
73793
+ */
73794
+ createSession(command, cwd) {
73795
+ const validation = validateCommand(command);
73796
+ if (!validation.valid) {
73797
+ return { sessionId: "", error: validation.error };
73798
+ }
73799
+ const sessionId = randomUUID11();
73800
+ const childProcess = spawn3(command, [], {
73801
+ cwd,
73802
+ shell: true,
73803
+ stdio: ["pipe", "pipe", "pipe"],
73804
+ env: { ...process.env, FORCE_COLOR: "1", TERM: "xterm-256color" }
73805
+ });
73806
+ const session = {
73807
+ id: sessionId,
73808
+ command,
73809
+ process: childProcess,
73810
+ startTime: /* @__PURE__ */ new Date(),
73811
+ output: [],
73812
+ exitCode: null,
73813
+ killed: false
73814
+ };
73815
+ this.sessions.set(sessionId, session);
73816
+ childProcess.stdout?.on("data", (data) => {
73817
+ const chunk = data.toString("utf-8");
73818
+ session.output.push(chunk);
73819
+ this.emit("output", { sessionId, type: "stdout", data: chunk });
73820
+ });
73821
+ childProcess.stderr?.on("data", (data) => {
73822
+ const chunk = data.toString("utf-8");
73823
+ session.output.push(chunk);
73824
+ this.emit("output", { sessionId, type: "stderr", data: chunk });
73825
+ });
73826
+ childProcess.on("exit", (code) => {
73827
+ session.exitCode = code ?? 0;
73828
+ this.emit("output", {
73829
+ sessionId,
73830
+ type: "exit",
73831
+ data: `Process exited with code ${code ?? 0}`,
73832
+ exitCode: code ?? 0
73833
+ });
73834
+ setTimeout(() => this.cleanupSession(sessionId), 5e3);
73835
+ });
73836
+ childProcess.on("error", (err) => {
73837
+ const errorMsg = err.message;
73838
+ session.output.push(errorMsg);
73839
+ session.exitCode = 127;
73840
+ this.emit("output", { sessionId, type: "stderr", data: errorMsg });
73841
+ this.emit("output", {
73842
+ sessionId,
73843
+ type: "exit",
73844
+ data: `Process failed: ${errorMsg}`,
73845
+ exitCode: 127
73846
+ });
73847
+ setTimeout(() => this.cleanupSession(sessionId), 5e3);
73848
+ });
73849
+ const timeout = setTimeout(() => {
73850
+ if (!session.exitCode && !session.killed) {
73851
+ this.killSession(sessionId, "SIGTERM");
73852
+ this.emit("output", {
73853
+ sessionId,
73854
+ type: "stderr",
73855
+ data: "\n[Command timed out after 30 seconds]\n"
73856
+ });
73857
+ }
73858
+ }, this.defaultTimeout);
73859
+ childProcess.on("exit", () => clearTimeout(timeout));
73860
+ return { sessionId };
73861
+ }
73862
+ /**
73863
+ * Gets a session by ID.
73864
+ */
73865
+ getSession(sessionId) {
73866
+ return this.sessions.get(sessionId);
73867
+ }
73868
+ /**
73869
+ * Kills a running session's process.
73870
+ * Returns true if killed, false if not found or already exited.
73871
+ */
73872
+ killSession(sessionId, signal = "SIGTERM") {
73873
+ const session = this.sessions.get(sessionId);
73874
+ if (!session || session.exitCode !== null || session.killed) {
73875
+ return false;
73876
+ }
73877
+ session.killed = true;
73878
+ try {
73879
+ if (session.process.pid) {
73880
+ process.kill(-session.process.pid, signal);
73881
+ }
73882
+ } catch {
73883
+ session.process.kill(signal);
73884
+ }
73885
+ return true;
73886
+ }
73887
+ /**
73888
+ * Cleans up a session from memory.
73889
+ */
73890
+ cleanupSession(sessionId) {
73891
+ const session = this.sessions.get(sessionId);
73892
+ if (!session) return false;
73893
+ if (session.exitCode === null && !session.killed) {
73894
+ this.killSession(sessionId, "SIGKILL");
73895
+ }
73896
+ this.sessions.delete(sessionId);
73897
+ return true;
73898
+ }
73899
+ /**
73900
+ * Lists all active sessions.
73901
+ */
73902
+ listSessions() {
73903
+ return Array.from(this.sessions.values()).map((s) => ({
73904
+ id: s.id,
73905
+ command: s.command,
73906
+ running: s.exitCode === null && !s.killed,
73907
+ startTime: s.startTime
73908
+ }));
73909
+ }
73910
+ /**
73911
+ * Cleans up all sessions (useful for shutdown).
73912
+ */
73913
+ cleanupAll() {
73914
+ for (const [sessionId] of this.sessions) {
73915
+ this.cleanupSession(sessionId);
73916
+ }
73917
+ }
73918
+ };
73919
+ terminalSessionManager = new TerminalSessionManager();
73920
+ }
73921
+ });
73922
+
73923
+ // ../dashboard/src/terminal-service.ts
73924
+ import { createRequire as createRequire2 } from "node:module";
73925
+ var isBunBinary, require2;
73926
+ var init_terminal_service = __esm({
73927
+ "../dashboard/src/terminal-service.ts"() {
72567
73928
  "use strict";
73929
+ isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
73930
+ require2 = createRequire2(import.meta.url);
73931
+ }
73932
+ });
73933
+
73934
+ // ../dashboard/src/github-webhooks.ts
73935
+ var init_github_webhooks = __esm({
73936
+ "../dashboard/src/github-webhooks.ts"() {
73937
+ "use strict";
73938
+ }
73939
+ });
73940
+
73941
+ // ../dashboard/src/ai-session-store.ts
73942
+ var MAX_THINKING_BYTES, SESSION_CLEANUP_DEFAULT_MAX_AGE_MS, SESSION_CLEANUP_INTERVAL_MS, diagnostics2;
73943
+ var init_ai_session_store = __esm({
73944
+ "../dashboard/src/ai-session-store.ts"() {
73945
+ "use strict";
73946
+ init_ai_session_diagnostics();
73947
+ MAX_THINKING_BYTES = 50 * 1024;
73948
+ SESSION_CLEANUP_DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
73949
+ SESSION_CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
73950
+ diagnostics2 = createSessionDiagnostics("ai-session-store");
73951
+ }
73952
+ });
73953
+
73954
+ // ../dashboard/src/subtask-breakdown.ts
73955
+ import { EventEmitter as EventEmitter21 } from "node:events";
73956
+ function cleanupInMemorySubtaskSession(sessionId) {
73957
+ const session = sessions2.get(sessionId);
73958
+ if (!session) {
73959
+ return false;
73960
+ }
73961
+ try {
73962
+ session.agent?.session?.dispose?.();
73963
+ } catch {
73964
+ }
73965
+ subtaskStreamManager.cleanupSession(sessionId);
73966
+ sessions2.delete(sessionId);
73967
+ return true;
73968
+ }
73969
+ function cleanupExpiredSessions2() {
73970
+ const now = Date.now();
73971
+ for (const [id, session] of sessions2) {
73972
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS2) {
73973
+ cleanupInMemorySubtaskSession(id);
73974
+ }
73975
+ }
73976
+ }
73977
+ var diagnostics3, SESSION_TTL_MS2, CLEANUP_INTERVAL_MS3, sessions2, cleanupInterval3, SubtaskStreamManager, subtaskStreamManager;
73978
+ var init_subtask_breakdown = __esm({
73979
+ "../dashboard/src/subtask-breakdown.ts"() {
73980
+ "use strict";
73981
+ init_src();
73982
+ init_sse_buffer();
73983
+ init_ai_session_diagnostics();
73984
+ diagnostics3 = createSessionDiagnostics("subtask-breakdown");
73985
+ SESSION_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
73986
+ CLEANUP_INTERVAL_MS3 = 5 * 60 * 1e3;
73987
+ sessions2 = /* @__PURE__ */ new Map();
73988
+ cleanupInterval3 = setInterval(cleanupExpiredSessions2, CLEANUP_INTERVAL_MS3);
73989
+ cleanupInterval3.unref?.();
73990
+ process.on("beforeExit", () => {
73991
+ clearInterval(cleanupInterval3);
73992
+ });
73993
+ SubtaskStreamManager = class extends EventEmitter21 {
73994
+ constructor(bufferSize = 100) {
73995
+ super();
73996
+ this.bufferSize = bufferSize;
73997
+ }
73998
+ sessions = /* @__PURE__ */ new Map();
73999
+ buffers = /* @__PURE__ */ new Map();
74000
+ subscribe(sessionId, callback) {
74001
+ if (!this.sessions.has(sessionId)) {
74002
+ this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74003
+ }
74004
+ const callbacks = this.sessions.get(sessionId);
74005
+ callbacks.add(callback);
74006
+ return () => {
74007
+ callbacks.delete(callback);
74008
+ if (callbacks.size === 0) {
74009
+ this.sessions.delete(sessionId);
74010
+ }
74011
+ };
74012
+ }
74013
+ getBuffer(sessionId) {
74014
+ let buffer = this.buffers.get(sessionId);
74015
+ if (!buffer) {
74016
+ buffer = new SessionEventBuffer(this.bufferSize);
74017
+ this.buffers.set(sessionId, buffer);
74018
+ }
74019
+ return buffer;
74020
+ }
74021
+ broadcast(sessionId, event) {
74022
+ const serialized = JSON.stringify(event.data ?? {});
74023
+ const eventData = typeof serialized === "string" ? serialized : "{}";
74024
+ const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74025
+ const callbacks = this.sessions.get(sessionId);
74026
+ if (!callbacks) return eventId;
74027
+ for (const callback of callbacks) {
74028
+ try {
74029
+ callback(event, eventId);
74030
+ } catch {
74031
+ }
74032
+ }
74033
+ return eventId;
74034
+ }
74035
+ getBufferedEvents(sessionId, sinceId) {
74036
+ const buffer = this.buffers.get(sessionId);
74037
+ if (!buffer) return [];
74038
+ return buffer.getEventsSince(sinceId);
74039
+ }
74040
+ cleanupSession(sessionId) {
74041
+ this.sessions.delete(sessionId);
74042
+ this.buffers.delete(sessionId);
74043
+ }
74044
+ reset() {
74045
+ this.sessions.clear();
74046
+ this.buffers.clear();
74047
+ this.removeAllListeners();
74048
+ }
74049
+ };
74050
+ subtaskStreamManager = new SubtaskStreamManager();
74051
+ }
74052
+ });
74053
+
74054
+ // ../dashboard/src/mission-interview.ts
74055
+ import { EventEmitter as EventEmitter22 } from "node:events";
74056
+ function cleanupInMemoryMissionSession(sessionId) {
74057
+ const session = sessions3.get(sessionId);
74058
+ if (!session) {
74059
+ return false;
74060
+ }
74061
+ if (session.agent) {
74062
+ try {
74063
+ session.agent.session.dispose?.();
74064
+ } catch {
74065
+ }
74066
+ session.agent = void 0;
74067
+ }
74068
+ missionInterviewStreamManager.cleanupSession(sessionId);
74069
+ sessions3.delete(sessionId);
74070
+ return true;
74071
+ }
74072
+ function cleanupExpiredSessions3() {
74073
+ const now = Date.now();
74074
+ for (const [id, session] of sessions3) {
74075
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS3) {
74076
+ cleanupInMemoryMissionSession(id);
74077
+ }
74078
+ }
74079
+ for (const [ip, entry] of rateLimits3) {
74080
+ if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS3) {
74081
+ rateLimits3.delete(ip);
74082
+ }
74083
+ }
74084
+ }
74085
+ var diagnostics4, SESSION_TTL_MS3, CLEANUP_INTERVAL_MS4, RATE_LIMIT_WINDOW_MS3, sessions3, rateLimits3, cleanupInterval4, MissionInterviewStreamManager, missionInterviewStreamManager;
74086
+ var init_mission_interview = __esm({
74087
+ "../dashboard/src/mission-interview.ts"() {
74088
+ "use strict";
74089
+ init_src();
74090
+ init_sse_buffer();
74091
+ init_ai_session_diagnostics();
74092
+ diagnostics4 = createSessionDiagnostics("mission-interview");
74093
+ SESSION_TTL_MS3 = 7 * 24 * 60 * 60 * 1e3;
74094
+ CLEANUP_INTERVAL_MS4 = 5 * 60 * 1e3;
74095
+ RATE_LIMIT_WINDOW_MS3 = 60 * 60 * 1e3;
74096
+ sessions3 = /* @__PURE__ */ new Map();
74097
+ rateLimits3 = /* @__PURE__ */ new Map();
74098
+ cleanupInterval4 = setInterval(cleanupExpiredSessions3, CLEANUP_INTERVAL_MS4);
74099
+ cleanupInterval4.unref?.();
74100
+ process.on("beforeExit", () => clearInterval(cleanupInterval4));
74101
+ MissionInterviewStreamManager = class extends EventEmitter22 {
74102
+ constructor(bufferSize = 100) {
74103
+ super();
74104
+ this.bufferSize = bufferSize;
74105
+ }
74106
+ sessions = /* @__PURE__ */ new Map();
74107
+ buffers = /* @__PURE__ */ new Map();
74108
+ subscribe(sessionId, callback) {
74109
+ if (!this.sessions.has(sessionId)) {
74110
+ this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74111
+ }
74112
+ const callbacks = this.sessions.get(sessionId);
74113
+ callbacks.add(callback);
74114
+ return () => {
74115
+ callbacks.delete(callback);
74116
+ if (callbacks.size === 0) {
74117
+ this.sessions.delete(sessionId);
74118
+ }
74119
+ };
74120
+ }
74121
+ getBuffer(sessionId) {
74122
+ let buffer = this.buffers.get(sessionId);
74123
+ if (!buffer) {
74124
+ buffer = new SessionEventBuffer(this.bufferSize);
74125
+ this.buffers.set(sessionId, buffer);
74126
+ }
74127
+ return buffer;
74128
+ }
74129
+ broadcast(sessionId, event) {
74130
+ const serialized = JSON.stringify(event.data ?? {});
74131
+ const eventData = typeof serialized === "string" ? serialized : "{}";
74132
+ const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74133
+ const callbacks = this.sessions.get(sessionId);
74134
+ if (!callbacks) return eventId;
74135
+ for (const callback of callbacks) {
74136
+ nonfatal(
74137
+ () => callback(event, eventId),
74138
+ diagnostics4,
74139
+ "Error broadcasting to client",
74140
+ { sessionId, operation: "broadcast" }
74141
+ );
74142
+ }
74143
+ return eventId;
74144
+ }
74145
+ getBufferedEvents(sessionId, sinceId) {
74146
+ const buffer = this.buffers.get(sessionId);
74147
+ if (!buffer) return [];
74148
+ return buffer.getEventsSince(sinceId);
74149
+ }
74150
+ hasSubscribers(sessionId) {
74151
+ const callbacks = this.sessions.get(sessionId);
74152
+ return callbacks !== void 0 && callbacks.size > 0;
74153
+ }
74154
+ cleanupSession(sessionId) {
74155
+ this.sessions.delete(sessionId);
74156
+ this.buffers.delete(sessionId);
74157
+ }
74158
+ reset() {
74159
+ this.sessions.clear();
74160
+ this.buffers.clear();
74161
+ this.removeAllListeners();
74162
+ }
74163
+ };
74164
+ missionInterviewStreamManager = new MissionInterviewStreamManager();
74165
+ }
74166
+ });
74167
+
74168
+ // ../dashboard/src/milestone-slice-interview.ts
74169
+ import { EventEmitter as EventEmitter23 } from "node:events";
74170
+ function cleanupInMemorySession2(sessionId) {
74171
+ const session = sessions4.get(sessionId);
74172
+ if (!session) {
74173
+ return false;
74174
+ }
74175
+ if (session.agent) {
74176
+ try {
74177
+ session.agent.session.dispose?.();
74178
+ } catch {
74179
+ }
74180
+ session.agent = void 0;
74181
+ }
74182
+ milestoneSliceInterviewStreamManager.cleanupSession(sessionId);
74183
+ sessions4.delete(sessionId);
74184
+ return true;
74185
+ }
74186
+ function cleanupExpiredSessions4() {
74187
+ const now = Date.now();
74188
+ for (const [id, session] of sessions4) {
74189
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS4) {
74190
+ cleanupInMemorySession2(id);
74191
+ }
74192
+ }
74193
+ for (const [ip, entry] of rateLimits4) {
74194
+ if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS4) {
74195
+ rateLimits4.delete(ip);
74196
+ }
74197
+ }
74198
+ }
74199
+ var diagnostics5, SESSION_TTL_MS4, CLEANUP_INTERVAL_MS5, RATE_LIMIT_WINDOW_MS4, sessions4, rateLimits4, cleanupInterval5, MilestoneSliceInterviewStreamManager, milestoneSliceInterviewStreamManager;
74200
+ var init_milestone_slice_interview = __esm({
74201
+ "../dashboard/src/milestone-slice-interview.ts"() {
74202
+ "use strict";
74203
+ init_sse_buffer();
74204
+ init_mission_interview();
74205
+ init_ai_session_diagnostics();
74206
+ init_mission_interview();
74207
+ diagnostics5 = createSessionDiagnostics("milestone-slice-interview");
74208
+ SESSION_TTL_MS4 = 7 * 24 * 60 * 60 * 1e3;
74209
+ CLEANUP_INTERVAL_MS5 = 5 * 60 * 1e3;
74210
+ RATE_LIMIT_WINDOW_MS4 = 60 * 60 * 1e3;
74211
+ sessions4 = /* @__PURE__ */ new Map();
74212
+ rateLimits4 = /* @__PURE__ */ new Map();
74213
+ cleanupInterval5 = setInterval(cleanupExpiredSessions4, CLEANUP_INTERVAL_MS5);
74214
+ cleanupInterval5.unref?.();
74215
+ process.on("beforeExit", () => clearInterval(cleanupInterval5));
74216
+ MilestoneSliceInterviewStreamManager = class extends EventEmitter23 {
74217
+ constructor(bufferSize = 100) {
74218
+ super();
74219
+ this.bufferSize = bufferSize;
74220
+ }
74221
+ sessions = /* @__PURE__ */ new Map();
74222
+ buffers = /* @__PURE__ */ new Map();
74223
+ subscribe(sessionId, callback) {
74224
+ if (!this.sessions.has(sessionId)) {
74225
+ this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74226
+ }
74227
+ const callbacks = this.sessions.get(sessionId);
74228
+ callbacks.add(callback);
74229
+ return () => {
74230
+ callbacks.delete(callback);
74231
+ if (callbacks.size === 0) {
74232
+ this.sessions.delete(sessionId);
74233
+ }
74234
+ };
74235
+ }
74236
+ getBuffer(sessionId) {
74237
+ let buffer = this.buffers.get(sessionId);
74238
+ if (!buffer) {
74239
+ buffer = new SessionEventBuffer(this.bufferSize);
74240
+ this.buffers.set(sessionId, buffer);
74241
+ }
74242
+ return buffer;
74243
+ }
74244
+ broadcast(sessionId, event) {
74245
+ const serialized = JSON.stringify(event.data ?? {});
74246
+ const eventData = typeof serialized === "string" ? serialized : "{}";
74247
+ const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74248
+ const callbacks = this.sessions.get(sessionId);
74249
+ if (!callbacks) return eventId;
74250
+ for (const callback of callbacks) {
74251
+ nonfatal(
74252
+ () => callback(event, eventId),
74253
+ diagnostics5,
74254
+ "Error broadcasting to client",
74255
+ { sessionId, operation: "broadcast" }
74256
+ );
74257
+ }
74258
+ return eventId;
74259
+ }
74260
+ getBufferedEvents(sessionId, sinceId) {
74261
+ const buffer = this.buffers.get(sessionId);
74262
+ if (!buffer) return [];
74263
+ return buffer.getEventsSince(sinceId);
74264
+ }
74265
+ hasSubscribers(sessionId) {
74266
+ const callbacks = this.sessions.get(sessionId);
74267
+ return callbacks !== void 0 && callbacks.size > 0;
74268
+ }
74269
+ cleanupSession(sessionId) {
74270
+ this.sessions.delete(sessionId);
74271
+ this.buffers.delete(sessionId);
74272
+ }
74273
+ reset() {
74274
+ this.sessions.clear();
74275
+ this.buffers.clear();
74276
+ this.removeAllListeners();
74277
+ }
74278
+ };
74279
+ milestoneSliceInterviewStreamManager = new MilestoneSliceInterviewStreamManager();
74280
+ }
74281
+ });
74282
+
74283
+ // ../dashboard/src/runtime-logger.ts
74284
+ var init_runtime_logger = __esm({
74285
+ "../dashboard/src/runtime-logger.ts"() {
74286
+ "use strict";
74287
+ }
74288
+ });
74289
+
74290
+ // ../dashboard/src/api-error.ts
74291
+ var init_api_error = __esm({
74292
+ "../dashboard/src/api-error.ts"() {
74293
+ "use strict";
74294
+ init_runtime_logger();
74295
+ }
74296
+ });
74297
+
74298
+ // ../dashboard/src/plugin-routes.ts
74299
+ import { Router } from "express";
74300
+ var init_plugin_routes = __esm({
74301
+ "../dashboard/src/plugin-routes.ts"() {
74302
+ "use strict";
74303
+ init_src();
74304
+ init_api_error();
74305
+ }
74306
+ });
74307
+
74308
+ // ../dashboard/src/project-store-resolver.ts
74309
+ var init_project_store_resolver = __esm({
74310
+ "../dashboard/src/project-store-resolver.ts"() {
74311
+ "use strict";
74312
+ }
74313
+ });
74314
+
74315
+ // ../dashboard/src/routes/context.ts
74316
+ import { Router as Router2 } from "express";
74317
+ var init_context = __esm({
74318
+ "../dashboard/src/routes/context.ts"() {
74319
+ "use strict";
74320
+ init_api_error();
74321
+ init_project_store_resolver();
74322
+ init_runtime_logger();
74323
+ }
74324
+ });
74325
+
74326
+ // ../dashboard/src/routes/register-task-workflow-routes.ts
74327
+ var init_register_task_workflow_routes = __esm({
74328
+ "../dashboard/src/routes/register-task-workflow-routes.ts"() {
74329
+ "use strict";
74330
+ init_src();
74331
+ init_api_error();
74332
+ }
74333
+ });
74334
+
74335
+ // ../dashboard/src/routes/register-planning-subtask-routes.ts
74336
+ var init_register_planning_subtask_routes = __esm({
74337
+ "../dashboard/src/routes/register-planning-subtask-routes.ts"() {
74338
+ "use strict";
74339
+ init_api_error();
74340
+ init_sse_buffer();
74341
+ }
74342
+ });
74343
+
74344
+ // ../dashboard/src/rate-limit.ts
74345
+ var init_rate_limit = __esm({
74346
+ "../dashboard/src/rate-limit.ts"() {
74347
+ "use strict";
74348
+ init_api_error();
74349
+ }
74350
+ });
74351
+
74352
+ // ../dashboard/src/routes/register-chat-routes.ts
74353
+ var init_register_chat_routes = __esm({
74354
+ "../dashboard/src/routes/register-chat-routes.ts"() {
74355
+ "use strict";
74356
+ init_api_error();
74357
+ init_rate_limit();
74358
+ init_sse_buffer();
74359
+ }
74360
+ });
74361
+
74362
+ // ../dashboard/src/remote-auth.ts
74363
+ var init_remote_auth = __esm({
74364
+ "../dashboard/src/remote-auth.ts"() {
74365
+ "use strict";
74366
+ }
74367
+ });
74368
+
74369
+ // ../dashboard/src/routes/register-settings-memory-routes.ts
74370
+ var init_register_settings_memory_routes = __esm({
74371
+ "../dashboard/src/routes/register-settings-memory-routes.ts"() {
74372
+ "use strict";
74373
+ init_src();
74374
+ init_api_error();
74375
+ init_remote_auth();
74376
+ init_project_store_resolver();
74377
+ }
74378
+ });
74379
+
74380
+ // ../dashboard/src/routes/register-messaging-scripts.ts
74381
+ var init_register_messaging_scripts = __esm({
74382
+ "../dashboard/src/routes/register-messaging-scripts.ts"() {
74383
+ "use strict";
74384
+ init_src();
74385
+ init_api_error();
74386
+ init_terminal_service();
72568
74387
  }
72569
74388
  });
72570
74389
 
@@ -74048,7 +75867,7 @@ var init_github = __esm({
74048
75867
  });
74049
75868
 
74050
75869
  // ../dashboard/src/github-poll.ts
74051
- import { EventEmitter as EventEmitter19 } from "node:events";
75870
+ import { EventEmitter as EventEmitter24 } from "node:events";
74052
75871
  function toAlias(type, number) {
74053
75872
  return `${type}_${number}`;
74054
75873
  }
@@ -74094,7 +75913,7 @@ var init_github_poll = __esm({
74094
75913
  }
74095
75914
  };
74096
75915
  githubRateLimiter = new GitHubRateLimiter();
74097
- GitHubPollingService = class extends EventEmitter19 {
75916
+ GitHubPollingService = class extends EventEmitter24 {
74098
75917
  watches = /* @__PURE__ */ new Map();
74099
75918
  rateLimiter;
74100
75919
  pollingIntervalMs;
@@ -74338,309 +76157,82 @@ var init_github_poll = __esm({
74338
76157
  }
74339
76158
  });
74340
76159
 
74341
- // ../dashboard/src/terminal.ts
74342
- import { spawn as spawn2 } from "node:child_process";
74343
- import { randomUUID as randomUUID11 } from "node:crypto";
74344
- import { EventEmitter as EventEmitter20 } from "node:events";
74345
- function extractBaseCommand(command) {
74346
- let trimmed = command.trim();
74347
- while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) {
74348
- const match2 = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*=(?:[^\s]*|"[^"]*"|'[^']*'))\s*/);
74349
- if (match2) {
74350
- trimmed = trimmed.slice(match2[0].length).trim();
74351
- } else {
74352
- break;
74353
- }
74354
- }
74355
- if (trimmed.startsWith("sudo ")) {
74356
- trimmed = trimmed.slice(5).trim();
74357
- }
74358
- const match = trimmed.match(/^([A-Za-z0-9._-]+)/);
74359
- return match ? match[1].toLowerCase() : null;
74360
- }
74361
- function validateCommand(command) {
74362
- for (const pattern of SUBSTITUTION_PATTERNS) {
74363
- if (pattern.test(command)) {
74364
- return { valid: false, error: "Command substitution is not allowed" };
74365
- }
74366
- }
74367
- if (/[\0\r\n]/.test(command)) {
74368
- return { valid: false, error: "Command contains invalid control characters" };
76160
+ // ../dashboard/src/routes/resolve-diff-base.ts
76161
+ import { execFile as execFile4 } from "node:child_process";
76162
+ import { promisify as promisify11 } from "node:util";
76163
+ var execFileAsync2;
76164
+ var init_resolve_diff_base = __esm({
76165
+ "../dashboard/src/routes/resolve-diff-base.ts"() {
76166
+ "use strict";
76167
+ execFileAsync2 = promisify11(execFile4);
74369
76168
  }
74370
- for (const pattern of BLOCKED_PATTERNS) {
74371
- if (pattern.test(command)) {
74372
- return { valid: false, error: "Command contains dangerous patterns and is not allowed" };
74373
- }
76169
+ });
76170
+
76171
+ // ../dashboard/src/routes/register-git-github.ts
76172
+ var init_register_git_github = __esm({
76173
+ "../dashboard/src/routes/register-git-github.ts"() {
76174
+ "use strict";
76175
+ init_src();
76176
+ init_api_error();
76177
+ init_github();
76178
+ init_github_poll();
76179
+ init_github_webhooks();
76180
+ init_resolve_diff_base();
74374
76181
  }
74375
- const segments = command.split(CHAIN_OPERATORS);
74376
- for (const raw of segments) {
74377
- const segment = raw.trim();
74378
- if (segment.length === 0) continue;
74379
- const baseCommand = extractBaseCommand(segment);
74380
- if (!baseCommand) {
74381
- return { valid: false, error: "Could not parse command" };
74382
- }
74383
- if (!ALLOWED_COMMANDS.has(baseCommand)) {
74384
- return {
74385
- valid: false,
74386
- error: `Command '${baseCommand}' is not in the allowed command list. Allowed commands: ${Array.from(ALLOWED_COMMANDS).sort().join(", ")}`
74387
- };
74388
- }
76182
+ });
76183
+
76184
+ // ../dashboard/src/file-service.ts
76185
+ var MAX_FILE_SIZE;
76186
+ var init_file_service = __esm({
76187
+ "../dashboard/src/file-service.ts"() {
76188
+ "use strict";
76189
+ MAX_FILE_SIZE = 1024 * 1024;
74389
76190
  }
74390
- return { valid: true };
74391
- }
74392
- var ALLOWED_COMMANDS, BLOCKED_PATTERNS, SUBSTITUTION_PATTERNS, CHAIN_OPERATORS, TerminalSessionManager, terminalSessionManager;
74393
- var init_terminal = __esm({
74394
- "../dashboard/src/terminal.ts"() {
76191
+ });
76192
+
76193
+ // ../dashboard/src/routes/register-file-workspace-routes.ts
76194
+ var init_register_file_workspace_routes = __esm({
76195
+ "../dashboard/src/routes/register-file-workspace-routes.ts"() {
74395
76196
  "use strict";
74396
- ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
74397
- // Version control
74398
- "git",
74399
- // Package managers
74400
- "npm",
74401
- "pnpm",
74402
- "yarn",
74403
- "bun",
74404
- // File operations
74405
- "ls",
74406
- "cat",
74407
- "echo",
74408
- "pwd",
74409
- "cd",
74410
- "mkdir",
74411
- "touch",
74412
- "cp",
74413
- "mv",
74414
- "rm",
74415
- "head",
74416
- "tail",
74417
- "less",
74418
- "more",
74419
- "find",
74420
- "grep",
74421
- // System info
74422
- "clear",
74423
- "which",
74424
- "whoami",
74425
- "uname",
74426
- "date",
74427
- // Node/JS
74428
- "node",
74429
- "npx",
74430
- "tsx",
74431
- // Python
74432
- "python",
74433
- "python3",
74434
- "pip",
74435
- "pip3",
74436
- // Network
74437
- "curl",
74438
- "wget",
74439
- // Build tools
74440
- "make",
74441
- "cmake",
74442
- // Process management
74443
- "ps",
74444
- "top",
74445
- "htop",
74446
- "kill",
74447
- "pkill",
74448
- // Shell builtins that are safe
74449
- "source",
74450
- ".",
74451
- "export",
74452
- "env",
74453
- "printenv",
74454
- "alias",
74455
- // Editors (for viewing)
74456
- "code",
74457
- // VS Code CLI
74458
- "vim",
74459
- "vi",
74460
- "nano"
74461
- ]);
74462
- BLOCKED_PATTERNS = [
74463
- // System destruction
74464
- /rm\s+(-[rf]+\s+)?\/\s*$/i,
74465
- // rm -rf / or rm /
74466
- /rm\s+(-[rf]+\s+)?\/\*/i,
74467
- // rm -rf /*
74468
- />\s*\/dev\/sda/i,
74469
- // Direct disk write
74470
- /:\(\)\s*\{\s*:\s*\|:\s*&\s*\};\s*:/i,
74471
- // Fork bomb
74472
- /mkfs\.\w+\s+/i,
74473
- // Filesystem formatting
74474
- /dd\s+if=/i,
74475
- // dd with input file
74476
- /\.\s*\/dev\/null/i
74477
- // Sourcing /dev/null tricks
74478
- ];
74479
- SUBSTITUTION_PATTERNS = [
74480
- /\$\(/,
74481
- // $(...) — command substitution
74482
- /`/,
74483
- // `...` — backtick command substitution
74484
- /<\(/,
74485
- // <(...) — process substitution (bash)
74486
- />\(/
74487
- // >(...) — process substitution (bash)
74488
- ];
74489
- CHAIN_OPERATORS = /&&|\|\||;|(?<!\|)\|(?!\|)/;
74490
- TerminalSessionManager = class extends EventEmitter20 {
74491
- sessions = /* @__PURE__ */ new Map();
74492
- defaultTimeout = 3e4;
74493
- // 30 seconds
74494
- /**
74495
- * Creates a new terminal session and spawns the command.
74496
- * Returns the session ID for tracking.
74497
- */
74498
- createSession(command, cwd) {
74499
- const validation = validateCommand(command);
74500
- if (!validation.valid) {
74501
- return { sessionId: "", error: validation.error };
74502
- }
74503
- const sessionId = randomUUID11();
74504
- const childProcess = spawn2(command, [], {
74505
- cwd,
74506
- shell: true,
74507
- stdio: ["pipe", "pipe", "pipe"],
74508
- env: { ...process.env, FORCE_COLOR: "1", TERM: "xterm-256color" }
74509
- });
74510
- const session = {
74511
- id: sessionId,
74512
- command,
74513
- process: childProcess,
74514
- startTime: /* @__PURE__ */ new Date(),
74515
- output: [],
74516
- exitCode: null,
74517
- killed: false
74518
- };
74519
- this.sessions.set(sessionId, session);
74520
- childProcess.stdout?.on("data", (data) => {
74521
- const chunk = data.toString("utf-8");
74522
- session.output.push(chunk);
74523
- this.emit("output", { sessionId, type: "stdout", data: chunk });
74524
- });
74525
- childProcess.stderr?.on("data", (data) => {
74526
- const chunk = data.toString("utf-8");
74527
- session.output.push(chunk);
74528
- this.emit("output", { sessionId, type: "stderr", data: chunk });
74529
- });
74530
- childProcess.on("exit", (code) => {
74531
- session.exitCode = code ?? 0;
74532
- this.emit("output", {
74533
- sessionId,
74534
- type: "exit",
74535
- data: `Process exited with code ${code ?? 0}`,
74536
- exitCode: code ?? 0
74537
- });
74538
- setTimeout(() => this.cleanupSession(sessionId), 5e3);
74539
- });
74540
- childProcess.on("error", (err) => {
74541
- const errorMsg = err.message;
74542
- session.output.push(errorMsg);
74543
- session.exitCode = 127;
74544
- this.emit("output", { sessionId, type: "stderr", data: errorMsg });
74545
- this.emit("output", {
74546
- sessionId,
74547
- type: "exit",
74548
- data: `Process failed: ${errorMsg}`,
74549
- exitCode: 127
74550
- });
74551
- setTimeout(() => this.cleanupSession(sessionId), 5e3);
74552
- });
74553
- const timeout = setTimeout(() => {
74554
- if (!session.exitCode && !session.killed) {
74555
- this.killSession(sessionId, "SIGTERM");
74556
- this.emit("output", {
74557
- sessionId,
74558
- type: "stderr",
74559
- data: "\n[Command timed out after 30 seconds]\n"
74560
- });
74561
- }
74562
- }, this.defaultTimeout);
74563
- childProcess.on("exit", () => clearTimeout(timeout));
74564
- return { sessionId };
74565
- }
74566
- /**
74567
- * Gets a session by ID.
74568
- */
74569
- getSession(sessionId) {
74570
- return this.sessions.get(sessionId);
74571
- }
74572
- /**
74573
- * Kills a running session's process.
74574
- * Returns true if killed, false if not found or already exited.
74575
- */
74576
- killSession(sessionId, signal = "SIGTERM") {
74577
- const session = this.sessions.get(sessionId);
74578
- if (!session || session.exitCode !== null || session.killed) {
74579
- return false;
74580
- }
74581
- session.killed = true;
74582
- try {
74583
- if (session.process.pid) {
74584
- process.kill(-session.process.pid, signal);
74585
- }
74586
- } catch {
74587
- session.process.kill(signal);
74588
- }
74589
- return true;
74590
- }
74591
- /**
74592
- * Cleans up a session from memory.
74593
- */
74594
- cleanupSession(sessionId) {
74595
- const session = this.sessions.get(sessionId);
74596
- if (!session) return false;
74597
- if (session.exitCode === null && !session.killed) {
74598
- this.killSession(sessionId, "SIGKILL");
74599
- }
74600
- this.sessions.delete(sessionId);
74601
- return true;
74602
- }
74603
- /**
74604
- * Lists all active sessions.
74605
- */
74606
- listSessions() {
74607
- return Array.from(this.sessions.values()).map((s) => ({
74608
- id: s.id,
74609
- command: s.command,
74610
- running: s.exitCode === null && !s.killed,
74611
- startTime: s.startTime
74612
- }));
74613
- }
74614
- /**
74615
- * Cleans up all sessions (useful for shutdown).
74616
- */
74617
- cleanupAll() {
74618
- for (const [sessionId] of this.sessions) {
74619
- this.cleanupSession(sessionId);
74620
- }
74621
- }
74622
- };
74623
- terminalSessionManager = new TerminalSessionManager();
76197
+ init_api_error();
76198
+ init_file_service();
74624
76199
  }
74625
76200
  });
74626
76201
 
74627
- // ../dashboard/src/terminal-service.ts
74628
- import { createRequire as createRequire2 } from "node:module";
74629
- var isBunBinary, require2;
74630
- var init_terminal_service = __esm({
74631
- "../dashboard/src/terminal-service.ts"() {
76202
+ // ../dashboard/src/routes/register-agents-projects-nodes.ts
76203
+ var init_register_agents_projects_nodes = __esm({
76204
+ "../dashboard/src/routes/register-agents-projects-nodes.ts"() {
74632
76205
  "use strict";
74633
- isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
74634
- require2 = createRequire2(import.meta.url);
74635
76206
  }
74636
76207
  });
74637
76208
 
74638
- // ../dashboard/src/file-service.ts
74639
- var MAX_FILE_SIZE;
74640
- var init_file_service = __esm({
74641
- "../dashboard/src/file-service.ts"() {
76209
+ // ../dashboard/src/routes/register-project-routes.ts
76210
+ import { execFile as execFile5 } from "node:child_process";
76211
+ import * as fsPromises from "node:fs/promises";
76212
+ import { promisify as promisify12 } from "node:util";
76213
+ var access3, stat6, mkdir12, readdir8, rm2, execFileAsync3;
76214
+ var init_register_project_routes = __esm({
76215
+ "../dashboard/src/routes/register-project-routes.ts"() {
74642
76216
  "use strict";
74643
- MAX_FILE_SIZE = 1024 * 1024;
76217
+ init_src();
76218
+ init_api_error();
76219
+ init_project_store_resolver();
76220
+ ({
76221
+ access: access3,
76222
+ stat: stat6,
76223
+ mkdir: mkdir12,
76224
+ readdir: readdir8,
76225
+ rm: rm2
76226
+ } = fsPromises);
76227
+ execFileAsync3 = promisify12(execFile5);
76228
+ }
76229
+ });
76230
+
76231
+ // ../dashboard/src/routes/register-node-routes.ts
76232
+ var init_register_node_routes = __esm({
76233
+ "../dashboard/src/routes/register-node-routes.ts"() {
76234
+ "use strict";
76235
+ init_api_error();
74644
76236
  }
74645
76237
  });
74646
76238
 
@@ -74651,138 +76243,72 @@ var init_auth_paths = __esm({
74651
76243
  }
74652
76244
  });
74653
76245
 
74654
- // ../dashboard/src/usage.ts
74655
- var init_usage = __esm({
74656
- "../dashboard/src/usage.ts"() {
76246
+ // ../dashboard/src/routes/register-settings-sync-helpers.ts
76247
+ var init_register_settings_sync_helpers = __esm({
76248
+ "../dashboard/src/routes/register-settings-sync-helpers.ts"() {
74657
76249
  "use strict";
76250
+ init_api_error();
74658
76251
  init_auth_paths();
74659
76252
  }
74660
76253
  });
74661
76254
 
74662
- // ../dashboard/src/github-webhooks.ts
74663
- var init_github_webhooks = __esm({
74664
- "../dashboard/src/github-webhooks.ts"() {
76255
+ // ../dashboard/src/routes/register-settings-sync-routes.ts
76256
+ var init_register_settings_sync_routes = __esm({
76257
+ "../dashboard/src/routes/register-settings-sync-routes.ts"() {
74665
76258
  "use strict";
76259
+ init_api_error();
76260
+ init_auth_paths();
76261
+ init_register_settings_sync_helpers();
74666
76262
  }
74667
76263
  });
74668
76264
 
74669
- // ../dashboard/src/project-store-resolver.ts
74670
- var init_project_store_resolver = __esm({
74671
- "../dashboard/src/project-store-resolver.ts"() {
76265
+ // ../dashboard/src/routes/register-mesh-routes.ts
76266
+ var init_register_mesh_routes = __esm({
76267
+ "../dashboard/src/routes/register-mesh-routes.ts"() {
74672
76268
  "use strict";
76269
+ init_api_error();
74673
76270
  }
74674
76271
  });
74675
76272
 
74676
- // ../dashboard/src/ai-session-store.ts
74677
- var MAX_THINKING_BYTES, SESSION_CLEANUP_DEFAULT_MAX_AGE_MS, SESSION_CLEANUP_INTERVAL_MS, diagnostics2;
74678
- var init_ai_session_store = __esm({
74679
- "../dashboard/src/ai-session-store.ts"() {
76273
+ // ../dashboard/src/routes/register-discovery-routes.ts
76274
+ var init_register_discovery_routes = __esm({
76275
+ "../dashboard/src/routes/register-discovery-routes.ts"() {
74680
76276
  "use strict";
74681
- init_ai_session_diagnostics();
74682
- MAX_THINKING_BYTES = 50 * 1024;
74683
- SESSION_CLEANUP_DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
74684
- SESSION_CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
74685
- diagnostics2 = createSessionDiagnostics("ai-session-store");
76277
+ init_api_error();
74686
76278
  }
74687
76279
  });
74688
76280
 
74689
- // ../dashboard/src/subtask-breakdown.ts
74690
- import { EventEmitter as EventEmitter21 } from "node:events";
74691
- function cleanupInMemorySubtaskSession(sessionId) {
74692
- const session = sessions2.get(sessionId);
74693
- if (!session) {
74694
- return false;
76281
+ // ../dashboard/src/routes/register-settings-sync-inbound-routes.ts
76282
+ var init_register_settings_sync_inbound_routes = __esm({
76283
+ "../dashboard/src/routes/register-settings-sync-inbound-routes.ts"() {
76284
+ "use strict";
76285
+ init_api_error();
76286
+ init_auth_paths();
76287
+ init_register_settings_sync_helpers();
74695
76288
  }
74696
- try {
74697
- session.agent?.session?.dispose?.();
74698
- } catch {
76289
+ });
76290
+
76291
+ // ../dashboard/src/routes/register-agent-core-routes.ts
76292
+ var init_register_agent_core_routes = __esm({
76293
+ "../dashboard/src/routes/register-agent-core-routes.ts"() {
76294
+ "use strict";
76295
+ init_api_error();
74699
76296
  }
74700
- subtaskStreamManager.cleanupSession(sessionId);
74701
- sessions2.delete(sessionId);
74702
- return true;
74703
- }
74704
- function cleanupExpiredSessions2() {
74705
- const now = Date.now();
74706
- for (const [id, session] of sessions2) {
74707
- if (now - session.updatedAt.getTime() > SESSION_TTL_MS2) {
74708
- cleanupInMemorySubtaskSession(id);
74709
- }
76297
+ });
76298
+
76299
+ // ../dashboard/src/routes/register-agent-runtime-routes.ts
76300
+ var init_register_agent_runtime_routes = __esm({
76301
+ "../dashboard/src/routes/register-agent-runtime-routes.ts"() {
76302
+ "use strict";
76303
+ init_api_error();
74710
76304
  }
74711
- }
74712
- var diagnostics3, SESSION_TTL_MS2, CLEANUP_INTERVAL_MS3, sessions2, cleanupInterval3, SubtaskStreamManager, subtaskStreamManager;
74713
- var init_subtask_breakdown = __esm({
74714
- "../dashboard/src/subtask-breakdown.ts"() {
76305
+ });
76306
+
76307
+ // ../dashboard/src/routes/register-agent-reflection-rating-routes.ts
76308
+ var init_register_agent_reflection_rating_routes = __esm({
76309
+ "../dashboard/src/routes/register-agent-reflection-rating-routes.ts"() {
74715
76310
  "use strict";
74716
- init_src();
74717
- init_sse_buffer();
74718
- init_ai_session_diagnostics();
74719
- diagnostics3 = createSessionDiagnostics("subtask-breakdown");
74720
- SESSION_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
74721
- CLEANUP_INTERVAL_MS3 = 5 * 60 * 1e3;
74722
- sessions2 = /* @__PURE__ */ new Map();
74723
- cleanupInterval3 = setInterval(cleanupExpiredSessions2, CLEANUP_INTERVAL_MS3);
74724
- cleanupInterval3.unref?.();
74725
- process.on("beforeExit", () => {
74726
- clearInterval(cleanupInterval3);
74727
- });
74728
- SubtaskStreamManager = class extends EventEmitter21 {
74729
- constructor(bufferSize = 100) {
74730
- super();
74731
- this.bufferSize = bufferSize;
74732
- }
74733
- sessions = /* @__PURE__ */ new Map();
74734
- buffers = /* @__PURE__ */ new Map();
74735
- subscribe(sessionId, callback) {
74736
- if (!this.sessions.has(sessionId)) {
74737
- this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74738
- }
74739
- const callbacks = this.sessions.get(sessionId);
74740
- callbacks.add(callback);
74741
- return () => {
74742
- callbacks.delete(callback);
74743
- if (callbacks.size === 0) {
74744
- this.sessions.delete(sessionId);
74745
- }
74746
- };
74747
- }
74748
- getBuffer(sessionId) {
74749
- let buffer = this.buffers.get(sessionId);
74750
- if (!buffer) {
74751
- buffer = new SessionEventBuffer(this.bufferSize);
74752
- this.buffers.set(sessionId, buffer);
74753
- }
74754
- return buffer;
74755
- }
74756
- broadcast(sessionId, event) {
74757
- const serialized = JSON.stringify(event.data ?? {});
74758
- const eventData = typeof serialized === "string" ? serialized : "{}";
74759
- const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74760
- const callbacks = this.sessions.get(sessionId);
74761
- if (!callbacks) return eventId;
74762
- for (const callback of callbacks) {
74763
- try {
74764
- callback(event, eventId);
74765
- } catch {
74766
- }
74767
- }
74768
- return eventId;
74769
- }
74770
- getBufferedEvents(sessionId, sinceId) {
74771
- const buffer = this.buffers.get(sessionId);
74772
- if (!buffer) return [];
74773
- return buffer.getEventsSince(sinceId);
74774
- }
74775
- cleanupSession(sessionId) {
74776
- this.sessions.delete(sessionId);
74777
- this.buffers.delete(sessionId);
74778
- }
74779
- reset() {
74780
- this.sessions.clear();
74781
- this.buffers.clear();
74782
- this.removeAllListeners();
74783
- }
74784
- };
74785
- subtaskStreamManager = new SubtaskStreamManager();
76311
+ init_api_error();
74786
76312
  }
74787
76313
  });
74788
76314
 
@@ -74798,33 +76324,33 @@ async function initPromptCatalog() {
74798
76324
  promptCatalogReady = true;
74799
76325
  }
74800
76326
  }
74801
- function cleanupExpiredSessions3() {
76327
+ function cleanupExpiredSessions5() {
74802
76328
  const now = Date.now();
74803
76329
  let cleanedSessions = 0;
74804
76330
  let cleanedRateLimits = 0;
74805
- for (const [id, session] of sessions3) {
74806
- if (now - session.updatedAt.getTime() > SESSION_TTL_MS3) {
74807
- sessions3.delete(id);
76331
+ for (const [id, session] of sessions5) {
76332
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS5) {
76333
+ sessions5.delete(id);
74808
76334
  cleanedSessions++;
74809
76335
  }
74810
76336
  }
74811
- for (const [ip, entry] of rateLimits3) {
74812
- if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS3) {
74813
- rateLimits3.delete(ip);
76337
+ for (const [ip, entry] of rateLimits5) {
76338
+ if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS5) {
76339
+ rateLimits5.delete(ip);
74814
76340
  cleanedRateLimits++;
74815
76341
  }
74816
76342
  }
74817
76343
  if (cleanedSessions > 0 || cleanedRateLimits > 0) {
74818
- diagnostics4.info("Cleanup completed", {
76344
+ diagnostics6.info("Cleanup completed", {
74819
76345
  cleanedSessions,
74820
76346
  cleanedRateLimits,
74821
- ttlMs: SESSION_TTL_MS3,
74822
- rateLimitWindowMs: RATE_LIMIT_WINDOW_MS3,
76347
+ ttlMs: SESSION_TTL_MS5,
76348
+ rateLimitWindowMs: RATE_LIMIT_WINDOW_MS5,
74823
76349
  operation: "cleanup-expired"
74824
76350
  });
74825
76351
  }
74826
76352
  }
74827
- var resolvePrompt2, promptCatalogReady, promptCatalogReadyPromise, SESSION_TTL_MS3, CLEANUP_INTERVAL_MS4, RATE_LIMIT_WINDOW_MS3, sessions3, rateLimits3, diagnostics4, cleanupInterval4;
76353
+ var resolvePrompt2, promptCatalogReady, promptCatalogReadyPromise, SESSION_TTL_MS5, CLEANUP_INTERVAL_MS6, RATE_LIMIT_WINDOW_MS5, sessions5, rateLimits5, diagnostics6, cleanupInterval6;
74828
76354
  var init_agent_generation = __esm({
74829
76355
  "../dashboard/src/agent-generation.ts"() {
74830
76356
  "use strict";
@@ -74832,357 +76358,97 @@ var init_agent_generation = __esm({
74832
76358
  resolvePrompt2 = () => "";
74833
76359
  promptCatalogReady = false;
74834
76360
  promptCatalogReadyPromise = initPromptCatalog();
74835
- SESSION_TTL_MS3 = 30 * 60 * 1e3;
74836
- CLEANUP_INTERVAL_MS4 = 5 * 60 * 1e3;
74837
- RATE_LIMIT_WINDOW_MS3 = 60 * 60 * 1e3;
74838
- sessions3 = /* @__PURE__ */ new Map();
74839
- rateLimits3 = /* @__PURE__ */ new Map();
74840
- diagnostics4 = createSessionDiagnostics("agent-generation");
74841
- cleanupInterval4 = setInterval(cleanupExpiredSessions3, CLEANUP_INTERVAL_MS4);
74842
- cleanupInterval4.unref?.();
74843
- process.on("beforeExit", () => {
74844
- clearInterval(cleanupInterval4);
74845
- });
74846
- }
74847
- });
74848
-
74849
- // ../dashboard/src/mission-interview.ts
74850
- import { EventEmitter as EventEmitter22 } from "node:events";
74851
- function cleanupInMemoryMissionSession(sessionId) {
74852
- const session = sessions4.get(sessionId);
74853
- if (!session) {
74854
- return false;
74855
- }
74856
- if (session.agent) {
74857
- try {
74858
- session.agent.session.dispose?.();
74859
- } catch {
74860
- }
74861
- session.agent = void 0;
74862
- }
74863
- missionInterviewStreamManager.cleanupSession(sessionId);
74864
- sessions4.delete(sessionId);
74865
- return true;
74866
- }
74867
- function cleanupExpiredSessions4() {
74868
- const now = Date.now();
74869
- for (const [id, session] of sessions4) {
74870
- if (now - session.updatedAt.getTime() > SESSION_TTL_MS4) {
74871
- cleanupInMemoryMissionSession(id);
74872
- }
74873
- }
74874
- for (const [ip, entry] of rateLimits4) {
74875
- if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS4) {
74876
- rateLimits4.delete(ip);
74877
- }
74878
- }
74879
- }
74880
- var diagnostics5, SESSION_TTL_MS4, CLEANUP_INTERVAL_MS5, RATE_LIMIT_WINDOW_MS4, sessions4, rateLimits4, cleanupInterval5, MissionInterviewStreamManager, missionInterviewStreamManager;
74881
- var init_mission_interview = __esm({
74882
- "../dashboard/src/mission-interview.ts"() {
74883
- "use strict";
74884
- init_src();
74885
- init_sse_buffer();
74886
- init_ai_session_diagnostics();
74887
- diagnostics5 = createSessionDiagnostics("mission-interview");
74888
- SESSION_TTL_MS4 = 7 * 24 * 60 * 60 * 1e3;
74889
- CLEANUP_INTERVAL_MS5 = 5 * 60 * 1e3;
74890
- RATE_LIMIT_WINDOW_MS4 = 60 * 60 * 1e3;
74891
- sessions4 = /* @__PURE__ */ new Map();
74892
- rateLimits4 = /* @__PURE__ */ new Map();
74893
- cleanupInterval5 = setInterval(cleanupExpiredSessions4, CLEANUP_INTERVAL_MS5);
74894
- cleanupInterval5.unref?.();
74895
- process.on("beforeExit", () => clearInterval(cleanupInterval5));
74896
- MissionInterviewStreamManager = class extends EventEmitter22 {
74897
- constructor(bufferSize = 100) {
74898
- super();
74899
- this.bufferSize = bufferSize;
74900
- }
74901
- sessions = /* @__PURE__ */ new Map();
74902
- buffers = /* @__PURE__ */ new Map();
74903
- subscribe(sessionId, callback) {
74904
- if (!this.sessions.has(sessionId)) {
74905
- this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74906
- }
74907
- const callbacks = this.sessions.get(sessionId);
74908
- callbacks.add(callback);
74909
- return () => {
74910
- callbacks.delete(callback);
74911
- if (callbacks.size === 0) {
74912
- this.sessions.delete(sessionId);
74913
- }
74914
- };
74915
- }
74916
- getBuffer(sessionId) {
74917
- let buffer = this.buffers.get(sessionId);
74918
- if (!buffer) {
74919
- buffer = new SessionEventBuffer(this.bufferSize);
74920
- this.buffers.set(sessionId, buffer);
74921
- }
74922
- return buffer;
74923
- }
74924
- broadcast(sessionId, event) {
74925
- const serialized = JSON.stringify(event.data ?? {});
74926
- const eventData = typeof serialized === "string" ? serialized : "{}";
74927
- const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74928
- const callbacks = this.sessions.get(sessionId);
74929
- if (!callbacks) return eventId;
74930
- for (const callback of callbacks) {
74931
- nonfatal(
74932
- () => callback(event, eventId),
74933
- diagnostics5,
74934
- "Error broadcasting to client",
74935
- { sessionId, operation: "broadcast" }
74936
- );
74937
- }
74938
- return eventId;
74939
- }
74940
- getBufferedEvents(sessionId, sinceId) {
74941
- const buffer = this.buffers.get(sessionId);
74942
- if (!buffer) return [];
74943
- return buffer.getEventsSince(sinceId);
74944
- }
74945
- hasSubscribers(sessionId) {
74946
- const callbacks = this.sessions.get(sessionId);
74947
- return callbacks !== void 0 && callbacks.size > 0;
74948
- }
74949
- cleanupSession(sessionId) {
74950
- this.sessions.delete(sessionId);
74951
- this.buffers.delete(sessionId);
74952
- }
74953
- reset() {
74954
- this.sessions.clear();
74955
- this.buffers.clear();
74956
- this.removeAllListeners();
74957
- }
74958
- };
74959
- missionInterviewStreamManager = new MissionInterviewStreamManager();
74960
- }
74961
- });
74962
-
74963
- // ../dashboard/src/milestone-slice-interview.ts
74964
- import { EventEmitter as EventEmitter23 } from "node:events";
74965
- function cleanupInMemorySession2(sessionId) {
74966
- const session = sessions5.get(sessionId);
74967
- if (!session) {
74968
- return false;
74969
- }
74970
- if (session.agent) {
74971
- try {
74972
- session.agent.session.dispose?.();
74973
- } catch {
74974
- }
74975
- session.agent = void 0;
74976
- }
74977
- milestoneSliceInterviewStreamManager.cleanupSession(sessionId);
74978
- sessions5.delete(sessionId);
74979
- return true;
74980
- }
74981
- function cleanupExpiredSessions5() {
74982
- const now = Date.now();
74983
- for (const [id, session] of sessions5) {
74984
- if (now - session.updatedAt.getTime() > SESSION_TTL_MS5) {
74985
- cleanupInMemorySession2(id);
74986
- }
74987
- }
74988
- for (const [ip, entry] of rateLimits5) {
74989
- if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS5) {
74990
- rateLimits5.delete(ip);
74991
- }
74992
- }
74993
- }
74994
- var diagnostics6, SESSION_TTL_MS5, CLEANUP_INTERVAL_MS6, RATE_LIMIT_WINDOW_MS5, sessions5, rateLimits5, cleanupInterval6, MilestoneSliceInterviewStreamManager, milestoneSliceInterviewStreamManager;
74995
- var init_milestone_slice_interview = __esm({
74996
- "../dashboard/src/milestone-slice-interview.ts"() {
74997
- "use strict";
74998
- init_sse_buffer();
74999
- init_mission_interview();
75000
- init_ai_session_diagnostics();
75001
- init_mission_interview();
75002
- diagnostics6 = createSessionDiagnostics("milestone-slice-interview");
75003
- SESSION_TTL_MS5 = 7 * 24 * 60 * 60 * 1e3;
76361
+ SESSION_TTL_MS5 = 30 * 60 * 1e3;
75004
76362
  CLEANUP_INTERVAL_MS6 = 5 * 60 * 1e3;
75005
76363
  RATE_LIMIT_WINDOW_MS5 = 60 * 60 * 1e3;
75006
76364
  sessions5 = /* @__PURE__ */ new Map();
75007
76365
  rateLimits5 = /* @__PURE__ */ new Map();
76366
+ diagnostics6 = createSessionDiagnostics("agent-generation");
75008
76367
  cleanupInterval6 = setInterval(cleanupExpiredSessions5, CLEANUP_INTERVAL_MS6);
75009
76368
  cleanupInterval6.unref?.();
75010
- process.on("beforeExit", () => clearInterval(cleanupInterval6));
75011
- MilestoneSliceInterviewStreamManager = class extends EventEmitter23 {
75012
- constructor(bufferSize = 100) {
75013
- super();
75014
- this.bufferSize = bufferSize;
75015
- }
75016
- sessions = /* @__PURE__ */ new Map();
75017
- buffers = /* @__PURE__ */ new Map();
75018
- subscribe(sessionId, callback) {
75019
- if (!this.sessions.has(sessionId)) {
75020
- this.sessions.set(sessionId, /* @__PURE__ */ new Set());
75021
- }
75022
- const callbacks = this.sessions.get(sessionId);
75023
- callbacks.add(callback);
75024
- return () => {
75025
- callbacks.delete(callback);
75026
- if (callbacks.size === 0) {
75027
- this.sessions.delete(sessionId);
75028
- }
75029
- };
75030
- }
75031
- getBuffer(sessionId) {
75032
- let buffer = this.buffers.get(sessionId);
75033
- if (!buffer) {
75034
- buffer = new SessionEventBuffer(this.bufferSize);
75035
- this.buffers.set(sessionId, buffer);
75036
- }
75037
- return buffer;
75038
- }
75039
- broadcast(sessionId, event) {
75040
- const serialized = JSON.stringify(event.data ?? {});
75041
- const eventData = typeof serialized === "string" ? serialized : "{}";
75042
- const eventId = this.getBuffer(sessionId).push(event.type, eventData);
75043
- const callbacks = this.sessions.get(sessionId);
75044
- if (!callbacks) return eventId;
75045
- for (const callback of callbacks) {
75046
- nonfatal(
75047
- () => callback(event, eventId),
75048
- diagnostics6,
75049
- "Error broadcasting to client",
75050
- { sessionId, operation: "broadcast" }
75051
- );
75052
- }
75053
- return eventId;
75054
- }
75055
- getBufferedEvents(sessionId, sinceId) {
75056
- const buffer = this.buffers.get(sessionId);
75057
- if (!buffer) return [];
75058
- return buffer.getEventsSince(sinceId);
75059
- }
75060
- hasSubscribers(sessionId) {
75061
- const callbacks = this.sessions.get(sessionId);
75062
- return callbacks !== void 0 && callbacks.size > 0;
75063
- }
75064
- cleanupSession(sessionId) {
75065
- this.sessions.delete(sessionId);
75066
- this.buffers.delete(sessionId);
75067
- }
75068
- reset() {
75069
- this.sessions.clear();
75070
- this.buffers.clear();
75071
- this.removeAllListeners();
75072
- }
75073
- };
75074
- milestoneSliceInterviewStreamManager = new MilestoneSliceInterviewStreamManager();
75075
- }
75076
- });
75077
-
75078
- // ../dashboard/src/runtime-logger.ts
75079
- var init_runtime_logger = __esm({
75080
- "../dashboard/src/runtime-logger.ts"() {
75081
- "use strict";
75082
- }
75083
- });
75084
-
75085
- // ../dashboard/src/api-error.ts
75086
- var init_api_error = __esm({
75087
- "../dashboard/src/api-error.ts"() {
75088
- "use strict";
75089
- init_runtime_logger();
75090
- }
75091
- });
75092
-
75093
- // ../dashboard/src/rate-limit.ts
75094
- var init_rate_limit = __esm({
75095
- "../dashboard/src/rate-limit.ts"() {
75096
- "use strict";
75097
- init_api_error();
76369
+ process.on("beforeExit", () => {
76370
+ clearInterval(cleanupInterval6);
76371
+ });
75098
76372
  }
75099
76373
  });
75100
76374
 
75101
- // ../dashboard/src/plugin-routes.ts
75102
- import { Router } from "express";
75103
- var init_plugin_routes = __esm({
75104
- "../dashboard/src/plugin-routes.ts"() {
76375
+ // ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
76376
+ import * as fsPromises2 from "node:fs/promises";
76377
+ var mkdtemp, access4, stat7, mkdir13, rm3, fsWriteFile;
76378
+ var init_register_agent_import_export_generation_routes = __esm({
76379
+ "../dashboard/src/routes/register-agent-import-export-generation-routes.ts"() {
75105
76380
  "use strict";
75106
- init_src();
75107
76381
  init_api_error();
76382
+ init_ai_session_diagnostics();
76383
+ init_agent_generation();
76384
+ ({ mkdtemp, access: access4, stat: stat7, mkdir: mkdir13, rm: rm3, writeFile: fsWriteFile } = fsPromises2);
75108
76385
  }
75109
76386
  });
75110
76387
 
75111
- // ../dashboard/src/routes/context.ts
75112
- import { Router as Router2 } from "express";
75113
- var init_context = __esm({
75114
- "../dashboard/src/routes/context.ts"() {
76388
+ // ../dashboard/src/routes/register-agent-skills-routes.ts
76389
+ var init_register_agent_skills_routes = __esm({
76390
+ "../dashboard/src/routes/register-agent-skills-routes.ts"() {
75115
76391
  "use strict";
75116
76392
  init_api_error();
75117
- init_project_store_resolver();
75118
- init_runtime_logger();
75119
76393
  }
75120
76394
  });
75121
76395
 
75122
- // ../dashboard/src/routes/register-tasks.ts
75123
- var init_register_tasks = __esm({
75124
- "../dashboard/src/routes/register-tasks.ts"() {
75125
- "use strict";
75126
- }
75127
- });
75128
-
75129
- // ../dashboard/src/routes/register-planning-chat.ts
75130
- var init_register_planning_chat = __esm({
75131
- "../dashboard/src/routes/register-planning-chat.ts"() {
75132
- "use strict";
75133
- }
75134
- });
75135
-
75136
- // ../dashboard/src/routes/register-settings-memory.ts
75137
- var init_register_settings_memory = __esm({
75138
- "../dashboard/src/routes/register-settings-memory.ts"() {
76396
+ // ../dashboard/src/routes/register-plugins-automation.ts
76397
+ var init_register_plugins_automation = __esm({
76398
+ "../dashboard/src/routes/register-plugins-automation.ts"() {
75139
76399
  "use strict";
75140
76400
  }
75141
76401
  });
75142
76402
 
75143
- // ../dashboard/src/routes/register-messaging-scripts.ts
75144
- var init_register_messaging_scripts = __esm({
75145
- "../dashboard/src/routes/register-messaging-scripts.ts"() {
76403
+ // ../dashboard/src/routes/register-proxy.ts
76404
+ var init_register_proxy = __esm({
76405
+ "../dashboard/src/routes/register-proxy.ts"() {
75146
76406
  "use strict";
75147
- init_src();
75148
76407
  init_api_error();
75149
- init_terminal_service();
75150
76408
  }
75151
76409
  });
75152
76410
 
75153
- // ../dashboard/src/routes/register-git-github.ts
75154
- var init_register_git_github = __esm({
75155
- "../dashboard/src/routes/register-git-github.ts"() {
76411
+ // ../dashboard/src/routes/register-model-routes.ts
76412
+ var init_register_model_routes = __esm({
76413
+ "../dashboard/src/routes/register-model-routes.ts"() {
75156
76414
  "use strict";
76415
+ init_api_error();
75157
76416
  }
75158
76417
  });
75159
76418
 
75160
- // ../dashboard/src/routes/register-files-terminal-workspaces.ts
75161
- var init_register_files_terminal_workspaces = __esm({
75162
- "../dashboard/src/routes/register-files-terminal-workspaces.ts"() {
76419
+ // ../dashboard/src/usage.ts
76420
+ var init_usage = __esm({
76421
+ "../dashboard/src/usage.ts"() {
75163
76422
  "use strict";
76423
+ init_auth_paths();
75164
76424
  }
75165
76425
  });
75166
76426
 
75167
- // ../dashboard/src/routes/register-agents-projects-nodes.ts
75168
- var init_register_agents_projects_nodes = __esm({
75169
- "../dashboard/src/routes/register-agents-projects-nodes.ts"() {
76427
+ // ../dashboard/src/routes/register-usage-routes.ts
76428
+ var init_register_usage_routes = __esm({
76429
+ "../dashboard/src/routes/register-usage-routes.ts"() {
75170
76430
  "use strict";
76431
+ init_api_error();
76432
+ init_usage();
75171
76433
  }
75172
76434
  });
75173
76435
 
75174
- // ../dashboard/src/routes/register-plugins-automation.ts
75175
- var init_register_plugins_automation = __esm({
75176
- "../dashboard/src/routes/register-plugins-automation.ts"() {
76436
+ // ../dashboard/src/claude-cli-probe.ts
76437
+ var init_claude_cli_probe = __esm({
76438
+ "../dashboard/src/claude-cli-probe.ts"() {
75177
76439
  "use strict";
75178
76440
  }
75179
76441
  });
75180
76442
 
75181
- // ../dashboard/src/routes/register-proxy.ts
75182
- var init_register_proxy = __esm({
75183
- "../dashboard/src/routes/register-proxy.ts"() {
76443
+ // ../dashboard/src/routes/register-auth-routes.ts
76444
+ var init_register_auth_routes = __esm({
76445
+ "../dashboard/src/routes/register-auth-routes.ts"() {
75184
76446
  "use strict";
76447
+ init_src();
76448
+ init_claude_cli_probe();
75185
76449
  init_api_error();
76450
+ init_usage();
76451
+ init_project_store_resolver();
75186
76452
  }
75187
76453
  });
75188
76454
 
@@ -75280,11 +76546,25 @@ var init_register_integrated_routers = __esm({
75280
76546
  }
75281
76547
  });
75282
76548
 
76549
+ // ../dashboard/src/routes/register-terminal-routes.ts
76550
+ var init_register_terminal_routes = __esm({
76551
+ "../dashboard/src/routes/register-terminal-routes.ts"() {
76552
+ "use strict";
76553
+ init_api_error();
76554
+ }
76555
+ });
76556
+
76557
+ // ../dashboard/src/routes/register-session-diff-routes.ts
76558
+ var init_register_session_diff_routes = __esm({
76559
+ "../dashboard/src/routes/register-session-diff-routes.ts"() {
76560
+ "use strict";
76561
+ init_api_error();
76562
+ init_resolve_diff_base();
76563
+ }
76564
+ });
76565
+
75283
76566
  // ../dashboard/src/routes.ts
75284
76567
  import multer from "multer";
75285
- import * as fsPromises from "node:fs/promises";
75286
- import { execFile as execFile3 } from "node:child_process";
75287
- import { promisify as promisify10 } from "node:util";
75288
76568
  async function initPromptOverrides() {
75289
76569
  if (promptOverridesReady) return;
75290
76570
  try {
@@ -75296,60 +76576,59 @@ async function initPromptOverrides() {
75296
76576
  promptOverridesReady = true;
75297
76577
  }
75298
76578
  }
75299
- var mkdtemp, access3, stat6, mkdir12, readdir8, rm2, fsReadFile, fsWriteFile, upload, execFileAsync, resolveWorkflowStepRefinePrompt, promptOverridesReady, DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
76579
+ var upload, resolveWorkflowStepRefinePrompt, promptOverridesReady, DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
75300
76580
  var init_routes = __esm({
75301
76581
  "../dashboard/src/routes.ts"() {
75302
76582
  "use strict";
75303
76583
  init_src();
75304
- init_claude_cli_probe();
75305
- init_github();
75306
- init_github_poll();
75307
76584
  init_terminal();
75308
76585
  init_terminal_service();
75309
- init_file_service();
75310
- init_usage();
75311
76586
  init_github_webhooks();
75312
- init_project_store_resolver();
75313
76587
  init_ai_session_store();
75314
76588
  init_planning();
75315
76589
  init_subtask_breakdown();
75316
- init_agent_generation();
75317
76590
  init_mission_interview();
75318
76591
  init_milestone_slice_interview();
75319
76592
  init_sse_buffer();
75320
76593
  init_api_error();
75321
- init_rate_limit();
75322
76594
  init_plugin_routes();
75323
- init_auth_paths();
75324
- init_runtime_logger();
75325
76595
  init_ai_session_diagnostics();
75326
76596
  init_context();
75327
- init_register_tasks();
75328
- init_register_planning_chat();
75329
- init_register_settings_memory();
76597
+ init_register_task_workflow_routes();
76598
+ init_register_planning_subtask_routes();
76599
+ init_register_chat_routes();
76600
+ init_register_settings_memory_routes();
75330
76601
  init_register_messaging_scripts();
75331
76602
  init_register_git_github();
75332
- init_register_files_terminal_workspaces();
76603
+ init_register_file_workspace_routes();
75333
76604
  init_register_agents_projects_nodes();
76605
+ init_register_project_routes();
76606
+ init_register_node_routes();
76607
+ init_register_settings_sync_routes();
76608
+ init_register_mesh_routes();
76609
+ init_register_discovery_routes();
76610
+ init_register_settings_sync_inbound_routes();
76611
+ init_register_agent_core_routes();
76612
+ init_register_agent_runtime_routes();
76613
+ init_register_agent_reflection_rating_routes();
76614
+ init_register_agent_import_export_generation_routes();
76615
+ init_register_agent_skills_routes();
75334
76616
  init_register_plugins_automation();
75335
76617
  init_register_proxy();
76618
+ init_register_model_routes();
76619
+ init_register_usage_routes();
76620
+ init_register_auth_routes();
75336
76621
  init_register_integrated_routers();
75337
- ({
75338
- mkdtemp,
75339
- access: access3,
75340
- stat: stat6,
75341
- mkdir: mkdir12,
75342
- readdir: readdir8,
75343
- rm: rm2,
75344
- readFile: fsReadFile,
75345
- writeFile: fsWriteFile
75346
- } = fsPromises);
76622
+ init_register_terminal_routes();
76623
+ init_register_session_diff_routes();
76624
+ init_resolve_diff_base();
76625
+ init_register_git_github();
76626
+ init_resolve_diff_base();
75347
76627
  upload = multer({
75348
76628
  storage: multer.memoryStorage(),
75349
76629
  limits: { fileSize: 5 * 1024 * 1024 }
75350
76630
  // 5MB
75351
76631
  });
75352
- execFileAsync = promisify10(execFile3);
75353
76632
  resolveWorkflowStepRefinePrompt = () => DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
75354
76633
  promptOverridesReady = false;
75355
76634
  initPromptOverrides();
@@ -77577,7 +78856,7 @@ var require_extension = __commonJS({
77577
78856
  var require_websocket = __commonJS({
77578
78857
  "../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports, module) {
77579
78858
  "use strict";
77580
- var EventEmitter25 = __require("events");
78859
+ var EventEmitter26 = __require("events");
77581
78860
  var https = __require("https");
77582
78861
  var http = __require("http");
77583
78862
  var net = __require("net");
@@ -77609,7 +78888,7 @@ var require_websocket = __commonJS({
77609
78888
  var protocolVersions = [8, 13];
77610
78889
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
77611
78890
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
77612
- var WebSocket2 = class _WebSocket extends EventEmitter25 {
78891
+ var WebSocket2 = class _WebSocket extends EventEmitter26 {
77613
78892
  /**
77614
78893
  * Create a new `WebSocket`.
77615
78894
  *
@@ -78606,7 +79885,7 @@ var require_subprotocol = __commonJS({
78606
79885
  var require_websocket_server = __commonJS({
78607
79886
  "../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
78608
79887
  "use strict";
78609
- var EventEmitter25 = __require("events");
79888
+ var EventEmitter26 = __require("events");
78610
79889
  var http = __require("http");
78611
79890
  var { Duplex } = __require("stream");
78612
79891
  var { createHash: createHash5 } = __require("crypto");
@@ -78619,7 +79898,7 @@ var require_websocket_server = __commonJS({
78619
79898
  var RUNNING = 0;
78620
79899
  var CLOSING = 1;
78621
79900
  var CLOSED = 2;
78622
- var WebSocketServer2 = class extends EventEmitter25 {
79901
+ var WebSocketServer2 = class extends EventEmitter26 {
78623
79902
  /**
78624
79903
  * Create a `WebSocketServer` instance.
78625
79904
  *
@@ -79035,7 +80314,7 @@ var init_terminal_websocket_diagnostics = __esm({
79035
80314
  });
79036
80315
 
79037
80316
  // ../dashboard/src/chat.ts
79038
- import { EventEmitter as EventEmitter24 } from "node:events";
80317
+ import { EventEmitter as EventEmitter25 } from "node:events";
79039
80318
  var defaultDiagnostics, _diagnostics, diagnostics7, RATE_LIMIT_WINDOW_MS6, MAX_REFERENCED_FILE_SIZE, ChatStreamManager, chatStreamManager;
79040
80319
  var init_chat = __esm({
79041
80320
  "../dashboard/src/chat.ts"() {
@@ -79067,7 +80346,7 @@ var init_chat = __esm({
79067
80346
  };
79068
80347
  RATE_LIMIT_WINDOW_MS6 = 60 * 1e3;
79069
80348
  MAX_REFERENCED_FILE_SIZE = 50 * 1024;
79070
- ChatStreamManager = class extends EventEmitter24 {
80349
+ ChatStreamManager = class extends EventEmitter25 {
79071
80350
  constructor(bufferSize = 100) {
79072
80351
  super();
79073
80352
  this.bufferSize = bufferSize;
@@ -79202,6 +80481,7 @@ var init_server = __esm({
79202
80481
  init_chat();
79203
80482
  init_dev_server_routes();
79204
80483
  init_auth_middleware();
80484
+ init_remote_auth();
79205
80485
  __dirname = dirname8(fileURLToPath2(import.meta.url));
79206
80486
  MIN_AI_SESSION_TTL_MS = 10 * 60 * 1e3;
79207
80487
  MAX_AI_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -80617,7 +81897,7 @@ __export(skills_exports, {
80617
81897
  runSkillsSearch: () => runSkillsSearch,
80618
81898
  searchSkills: () => searchSkills
80619
81899
  });
80620
- import { spawn as spawn3 } from "node:child_process";
81900
+ import { spawn as spawn4 } from "node:child_process";
80621
81901
  async function searchSkills(query, limit = 10) {
80622
81902
  const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
80623
81903
  try {
@@ -80695,7 +81975,7 @@ async function runSkillsInstall(args, options) {
80695
81975
  npxArgs.push("--skill", options.skill);
80696
81976
  }
80697
81977
  npxArgs.push("-y", "-a", "pi");
80698
- const child = spawn3("npx", npxArgs, {
81978
+ const child = spawn4("npx", npxArgs, {
80699
81979
  cwd: process.cwd(),
80700
81980
  stdio: "inherit"
80701
81981
  });
@@ -80731,7 +82011,7 @@ import { StringEnum } from "@mariozechner/pi-ai";
80731
82011
  import { resolve as resolve15, basename as basename8, extname as extname2, join as join36 } from "node:path";
80732
82012
  import { readFile as readFile18 } from "node:fs/promises";
80733
82013
  import { existsSync as existsSync30 } from "node:fs";
80734
- import { spawn as spawn4 } from "node:child_process";
82014
+ import { spawn as spawn5 } from "node:child_process";
80735
82015
  var MIME_TYPES2 = {
80736
82016
  ".png": "image/png",
80737
82017
  ".jpg": "image/jpeg",
@@ -82132,7 +83412,7 @@ Status: ${updated.status}`
82132
83412
  npxArgs.push("--skill", params.skill);
82133
83413
  }
82134
83414
  npxArgs.push("-y", "-a", "pi");
82135
- const child = spawn4("npx", npxArgs, {
83415
+ const child = spawn5("npx", npxArgs, {
82136
83416
  cwd: resolveProjectRoot(ctx.cwd),
82137
83417
  stdio: "pipe"
82138
83418
  });
@@ -82213,7 +83493,7 @@ Status: ${updated.status}`
82213
83493
  return;
82214
83494
  }
82215
83495
  const port = trimmed ? parseInt(trimmed, 10) || 4040 : 4040;
82216
- const child = spawn4("fn", ["dashboard", "--port", String(port)], {
83496
+ const child = spawn5("fn", ["dashboard", "--port", String(port)], {
82217
83497
  cwd: resolveProjectRoot(ctx.cwd),
82218
83498
  stdio: ["ignore", "pipe", "pipe"],
82219
83499
  detached: false,