@runfusion/fusion 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/bin.js +51800 -49478
  2. package/dist/client/assets/{AgentDetailView-CJIxNRq-.js → AgentDetailView-DJwWfkpv.js} +3 -3
  3. package/dist/client/assets/{AgentsView-BS17exn3.js → AgentsView-DegK8aw-.js} +3 -3
  4. package/dist/client/assets/ChatView-CYpEShLS.js +1 -0
  5. package/dist/client/assets/{DevServerView-qMPpnXRb.js → DevServerView-DfCTA9fx.js} +1 -1
  6. package/dist/client/assets/{DirectoryPicker-CTwgv9LY.js → DirectoryPicker-B0qNpfLW.js} +1 -1
  7. package/dist/client/assets/{DocumentsView-DOz1KFGN.js → DocumentsView-CsQxuyz3.js} +1 -1
  8. package/dist/client/assets/{InsightsView-CHZTJUic.js → InsightsView-Bzs7A2jv.js} +1 -1
  9. package/dist/client/assets/MemoryView-Cl5ASqjW.js +2 -0
  10. package/dist/client/assets/MemoryView-DiajLXby.css +1 -0
  11. package/dist/client/assets/{NodesView-BtGNRj2z.js → NodesView-BpiqRlvc.js} +1 -1
  12. package/dist/client/assets/{PiExtensionsManager-D9Ye2Vak.js → PiExtensionsManager-Cr6EoC7S.js} +3 -3
  13. package/dist/client/assets/{PluginManager-LeHp0jJ_.js → PluginManager-DXtQdfns.js} +1 -1
  14. package/dist/client/assets/{RoadmapsView-C413ISVU.js → RoadmapsView-CYPLTTB0.js} +1 -1
  15. package/dist/client/assets/{SettingsModal-olTBmYJs.js → SettingsModal-CNdVTVqD.js} +1 -1
  16. package/dist/client/assets/SettingsModal-CyCC7MzL.js +31 -0
  17. package/dist/client/assets/SettingsModal-G0ESQXRD.css +1 -0
  18. package/dist/client/assets/{SetupWizardModal-WdaR2eQQ.js → SetupWizardModal-BLiljNn7.js} +1 -1
  19. package/dist/client/assets/{SkillsView-BcE57w8i.js → SkillsView-Dlpw5LKI.js} +1 -1
  20. package/dist/client/assets/{folder-open-Ec4hU1xL.js → folder-open-B_38R5AA.js} +1 -1
  21. package/dist/client/assets/index-DQKtk17v.js +616 -0
  22. package/dist/client/assets/index-DjOxzdj3.css +1 -0
  23. package/dist/client/assets/{upload-BksRDuGJ.js → upload-DNQF7XCK.js} +1 -1
  24. package/dist/client/assets/{users-EFU4n9Qr.js → users-CG2_rCdk.js} +1 -1
  25. package/dist/client/index.html +2 -2
  26. package/dist/client/version.json +1 -0
  27. package/dist/extension.js +2101 -877
  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 +17 -17
  31. package/LICENSE +0 -21
  32. package/dist/client/assets/ChatView-BUlq3WNJ.js +0 -1
  33. package/dist/client/assets/MemoryView-DhinauGs.css +0 -1
  34. package/dist/client/assets/MemoryView-V0QdeO3e.js +0 -2
  35. package/dist/client/assets/SettingsModal--vWmKBpT.css +0 -1
  36. package/dist/client/assets/SettingsModal-BZLL2xAP.js +0 -31
  37. package/dist/client/assets/index-CCYdhck-.js +0 -616
  38. package/dist/client/assets/index-lJ5WOmO9.css +0 -1
package/dist/extension.js CHANGED
@@ -193,6 +193,39 @@ 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
+ tunnelName: "",
208
+ tunnelToken: null,
209
+ ingressUrl: ""
210
+ }
211
+ },
212
+ tokenStrategy: {
213
+ persistent: {
214
+ enabled: true,
215
+ token: null
216
+ },
217
+ shortLived: {
218
+ enabled: false,
219
+ ttlMs: 9e5,
220
+ maxTtlMs: 864e5
221
+ }
222
+ },
223
+ lifecycle: {
224
+ rememberLastRunning: false,
225
+ wasRunningOnShutdown: false,
226
+ lastRunningProvider: null
227
+ }
228
+ },
196
229
  reflectionEnabled: false,
197
230
  reflectionIntervalMs: 36e5,
198
231
  reflectionAfterTask: true,
@@ -17106,10 +17139,10 @@ var init_central_core = __esm({
17106
17139
  */
17107
17140
  async generateProjectName(projectPath) {
17108
17141
  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(
17142
+ const { execFile: execFile6 } = await import("node:child_process");
17143
+ const { promisify: promisify13 } = await import("node:util");
17144
+ const execFileAsync4 = promisify13(execFile6);
17145
+ const { stdout } = await execFileAsync4(
17113
17146
  "git",
17114
17147
  ["remote", "get-url", "origin"],
17115
17148
  { cwd: projectPath, timeout: 5e3 }
@@ -17594,8 +17627,8 @@ function hasProjectDbFile(dir, folderName, dbName) {
17594
17627
  if (!existsSync8(projectDir)) return false;
17595
17628
  if (!existsSync8(dbPath)) return false;
17596
17629
  try {
17597
- const stat7 = statSync2(dbPath);
17598
- return stat7.isFile() && stat7.size > 0;
17630
+ const stat8 = statSync2(dbPath);
17631
+ return stat8.isFile() && stat8.size > 0;
17599
17632
  } catch {
17600
17633
  return false;
17601
17634
  }
@@ -17722,10 +17755,10 @@ var init_migration = __esm({
17722
17755
  return basename3(projectPath);
17723
17756
  }
17724
17757
  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(
17758
+ const { execFile: execFile6 } = await import("node:child_process");
17759
+ const { promisify: promisify13 } = await import("node:util");
17760
+ const execFileAsync4 = promisify13(execFile6);
17761
+ const { stdout } = await execFileAsync4(
17729
17762
  "git",
17730
17763
  ["remote", "get-url", "origin"],
17731
17764
  { cwd: projectPath, timeout: 1e3 }
@@ -28431,13 +28464,13 @@ async function searchWithQmd(rootDir, options) {
28431
28464
  const command = "qmd";
28432
28465
  const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
28433
28466
  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);
28467
+ const { execFile: execFile6 } = await import("node:child_process");
28468
+ const { promisify: promisify13 } = await import("node:util");
28469
+ const execFileAsync4 = promisify13(execFile6);
28470
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync4);
28438
28471
  scheduleQmdProjectMemoryRefresh(rootDir);
28439
28472
  const args = buildQmdSearchArgs(rootDir, options);
28440
- const { stdout } = await execFileAsync2(command, args, {
28473
+ const { stdout } = await execFileAsync4(command, args, {
28441
28474
  cwd: rootDir,
28442
28475
  timeout: 4e3,
28443
28476
  maxBuffer: 1024 * 1024
@@ -28462,12 +28495,12 @@ async function searchWithQmd(rootDir, options) {
28462
28495
  return [];
28463
28496
  }
28464
28497
  }
28465
- async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync2) {
28498
+ async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync4) {
28466
28499
  const collectionName = qmdMemoryCollectionName(rootDir);
28467
28500
  const memoryDir = memoryWorkspacePath(rootDir);
28468
28501
  await mkdir6(memoryDir, { recursive: true });
28469
28502
  try {
28470
- await execFileAsync2("qmd", buildQmdCollectionAddArgs(rootDir), {
28503
+ await execFileAsync4("qmd", buildQmdCollectionAddArgs(rootDir), {
28471
28504
  cwd: rootDir,
28472
28505
  timeout: 4e3,
28473
28506
  maxBuffer: 512 * 1024
@@ -28483,9 +28516,9 @@ ${stderr}`)) {
28483
28516
  return collectionName;
28484
28517
  }
28485
28518
  async function getDefaultExecFileAsync() {
28486
- const { execFile: execFile4 } = await import("node:child_process");
28487
- const { promisify: promisify11 } = await import("node:util");
28488
- return promisify11(execFile4);
28519
+ const { execFile: execFile6 } = await import("node:child_process");
28520
+ const { promisify: promisify13 } = await import("node:util");
28521
+ return promisify13(execFile6);
28489
28522
  }
28490
28523
  async function refreshQmdProjectMemoryIndex(rootDir, options) {
28491
28524
  const key = resolve5(rootDir);
@@ -28500,14 +28533,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
28500
28533
  }
28501
28534
  }
28502
28535
  const promise = (async () => {
28503
- const execFileAsync2 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28504
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync2);
28505
- await execFileAsync2("qmd", ["update"], {
28536
+ const execFileAsync4 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28537
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync4);
28538
+ await execFileAsync4("qmd", ["update"], {
28506
28539
  cwd: rootDir,
28507
28540
  timeout: 3e4,
28508
28541
  maxBuffer: 1024 * 1024
28509
28542
  });
28510
- await execFileAsync2("qmd", ["embed"], {
28543
+ await execFileAsync4("qmd", ["embed"], {
28511
28544
  cwd: rootDir,
28512
28545
  timeout: 12e4,
28513
28546
  maxBuffer: 1024 * 1024
@@ -28532,8 +28565,8 @@ function scheduleQmdProjectMemoryRefresh(rootDir) {
28532
28565
  }
28533
28566
  async function isQmdAvailable() {
28534
28567
  try {
28535
- const execFileAsync2 = await getDefaultExecFileAsync();
28536
- await execFileAsync2("qmd", ["--help"], {
28568
+ const execFileAsync4 = await getDefaultExecFileAsync();
28569
+ await execFileAsync4("qmd", ["--help"], {
28537
28570
  timeout: 3e3,
28538
28571
  maxBuffer: 128 * 1024
28539
28572
  });
@@ -28543,12 +28576,12 @@ async function isQmdAvailable() {
28543
28576
  }
28544
28577
  }
28545
28578
  async function installQmd(options) {
28546
- const execFileAsync2 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28579
+ const execFileAsync4 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28547
28580
  const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
28548
28581
  if (!command || args.length === 0) {
28549
28582
  throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
28550
28583
  }
28551
- await execFileAsync2(command, args, {
28584
+ await execFileAsync4(command, args, {
28552
28585
  timeout: 12e4,
28553
28586
  maxBuffer: 1024 * 1024
28554
28587
  });
@@ -29354,6 +29387,29 @@ function canonicalizeSettings(settings) {
29354
29387
  }
29355
29388
  return base;
29356
29389
  }
29390
+ function isPlainObject(value) {
29391
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29392
+ }
29393
+ function deepMergeWithNullDelete(existingValue, patchValue) {
29394
+ const merged = isPlainObject(existingValue) ? { ...existingValue } : {};
29395
+ for (const [key, value] of Object.entries(patchValue)) {
29396
+ if (value === null) {
29397
+ delete merged[key];
29398
+ continue;
29399
+ }
29400
+ if (isPlainObject(value)) {
29401
+ const nested = deepMergeWithNullDelete(merged[key], value);
29402
+ if (nested === void 0) {
29403
+ delete merged[key];
29404
+ } else {
29405
+ merged[key] = nested;
29406
+ }
29407
+ continue;
29408
+ }
29409
+ merged[key] = value;
29410
+ }
29411
+ return Object.keys(merged).length > 0 ? merged : void 0;
29412
+ }
29357
29413
  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
29414
  var init_store = __esm({
29359
29415
  "../core/src/store.ts"() {
@@ -30524,6 +30580,21 @@ ${recentText}` : void 0
30524
30580
  projectPatch["promptOverrides"] = mergedMap;
30525
30581
  }
30526
30582
  }
30583
+ const incomingRemoteAccess = projectPatch["remoteAccess"];
30584
+ if (incomingRemoteAccess === null) {
30585
+ delete config.settings["remoteAccess"];
30586
+ delete projectPatch["remoteAccess"];
30587
+ } else if (isPlainObject(incomingRemoteAccess)) {
30588
+ const existingRemoteAccess = config.settings["remoteAccess"];
30589
+ const mergedRemoteAccess = deepMergeWithNullDelete(existingRemoteAccess, incomingRemoteAccess);
30590
+ if (mergedRemoteAccess === void 0) {
30591
+ delete config.settings["remoteAccess"];
30592
+ delete projectPatch["remoteAccess"];
30593
+ } else {
30594
+ config.settings["remoteAccess"] = mergedRemoteAccess;
30595
+ projectPatch["remoteAccess"] = mergedRemoteAccess;
30596
+ }
30597
+ }
30527
30598
  for (const key of Object.keys(projectPatch)) {
30528
30599
  if (projectPatch[key] === null) {
30529
30600
  delete config.settings[key];
@@ -31914,8 +31985,8 @@ ${task.description}
31914
31985
  if (this.isWatching) this.taskCache.delete(id);
31915
31986
  const dir = this.taskDir(id);
31916
31987
  if (existsSync12(dir)) {
31917
- const { rm: rm3 } = await import("node:fs/promises");
31918
- await rm3(dir, { recursive: true });
31988
+ const { rm: rm4 } = await import("node:fs/promises");
31989
+ await rm4(dir, { recursive: true });
31919
31990
  }
31920
31991
  for (const dependentTask of rewrittenDependents) {
31921
31992
  this.emit("task:updated", dependentTask);
@@ -32250,8 +32321,8 @@ ${task.description}
32250
32321
  this.archiveDb.upsert(entry);
32251
32322
  this.db.prepare("DELETE FROM tasks WHERE id = ?").run(id);
32252
32323
  this.db.bumpLastModified();
32253
- const { rm: rm3 } = await import("node:fs/promises");
32254
- await rm3(dir, { recursive: true, force: true });
32324
+ const { rm: rm4 } = await import("node:fs/promises");
32325
+ await rm4(dir, { recursive: true, force: true });
32255
32326
  if (this.isWatching) {
32256
32327
  this.taskCache.delete(id);
32257
32328
  }
@@ -33203,14 +33274,14 @@ ${task.description}
33203
33274
  if (rows.length === 0) {
33204
33275
  return;
33205
33276
  }
33206
- const { rm: rm3 } = await import("node:fs/promises");
33277
+ const { rm: rm4 } = await import("node:fs/promises");
33207
33278
  for (const row of rows) {
33208
33279
  const task = this.rowToTask(row);
33209
33280
  const archivedAt = task.columnMovedAt ?? task.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
33210
33281
  const entry = await this.taskToArchiveEntry(task, archivedAt);
33211
33282
  this.archiveDb.upsert(entry);
33212
33283
  this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
33213
- await rm3(this.taskDir(task.id), { recursive: true, force: true });
33284
+ await rm4(this.taskDir(task.id), { recursive: true, force: true });
33214
33285
  if (this.isWatching) {
33215
33286
  this.taskCache.delete(task.id);
33216
33287
  }
@@ -33233,8 +33304,8 @@ ${task.description}
33233
33304
  this.archiveDb.upsert(entry);
33234
33305
  this.db.prepare("DELETE FROM tasks WHERE id = ?").run(task.id);
33235
33306
  this.db.bumpLastModified();
33236
- const { rm: rm3 } = await import("node:fs/promises");
33237
- await rm3(dir, { recursive: true, force: true });
33307
+ const { rm: rm4 } = await import("node:fs/promises");
33308
+ await rm4(dir, { recursive: true, force: true });
33238
33309
  if (this.isWatching) {
33239
33310
  this.taskCache.delete(task.id);
33240
33311
  }
@@ -36345,6 +36416,47 @@ async function writeMemoryAudit(rootDir, content) {
36345
36416
  }
36346
36417
  await writeFile8(filePath, content, "utf-8");
36347
36418
  }
36419
+ async function readMemoryAuditState(rootDir) {
36420
+ const filePath = join18(rootDir, MEMORY_AUDIT_STATE_PATH);
36421
+ if (!existsSync15(filePath)) {
36422
+ return null;
36423
+ }
36424
+ try {
36425
+ const raw = await readFile10(filePath, "utf-8");
36426
+ const parsed = JSON.parse(raw);
36427
+ const extraction = isValidExtractionMetadata(parsed.extraction) ? parsed.extraction : void 0;
36428
+ const pruning = isValidPruneOutcome(parsed.pruning) ? parsed.pruning : void 0;
36429
+ return {
36430
+ extraction,
36431
+ pruning,
36432
+ updatedAt: typeof parsed.updatedAt === "string" && parsed.updatedAt.trim() ? parsed.updatedAt : (/* @__PURE__ */ new Date()).toISOString()
36433
+ };
36434
+ } catch {
36435
+ return null;
36436
+ }
36437
+ }
36438
+ async function writeMemoryAuditState(rootDir, state) {
36439
+ const filePath = join18(rootDir, MEMORY_AUDIT_STATE_PATH);
36440
+ const dir = join18(rootDir, ".fusion");
36441
+ if (!existsSync15(dir)) {
36442
+ await mkdir9(dir, { recursive: true });
36443
+ }
36444
+ await writeFile8(filePath, JSON.stringify(state, null, 2), "utf-8");
36445
+ }
36446
+ function isValidExtractionMetadata(value) {
36447
+ if (!value || typeof value !== "object") {
36448
+ return false;
36449
+ }
36450
+ const candidate = value;
36451
+ 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");
36452
+ }
36453
+ function isValidPruneOutcome(value) {
36454
+ if (!value || typeof value !== "object") {
36455
+ return false;
36456
+ }
36457
+ const candidate = value;
36458
+ return typeof candidate.applied === "boolean" && typeof candidate.reason === "string" && typeof candidate.sizeDelta === "number" && typeof candidate.originalSize === "number" && typeof candidate.newSize === "number";
36459
+ }
36348
36460
  function buildInsightExtractionPrompt(workingMemory, existingInsights) {
36349
36461
  const existingSection = existingInsights ? `
36350
36462
  ## Existing Insights (already captured \u2014 do not duplicate)
@@ -36799,6 +36911,9 @@ function countInsightsInMarkdown(markdown) {
36799
36911
  async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36800
36912
  const checks = [];
36801
36913
  const now = (/* @__PURE__ */ new Date()).toISOString();
36914
+ const persistedState = lastExtraction === void 0 || pruningOutcome === void 0 ? await readMemoryAuditState(rootDir) : null;
36915
+ const effectiveExtraction = lastExtraction ?? persistedState?.extraction;
36916
+ const effectivePruning = pruningOutcome ?? persistedState?.pruning;
36802
36917
  const workingMemoryPath = join18(rootDir, MEMORY_WORKING_PATH);
36803
36918
  const workingMemoryExists = existsSync15(workingMemoryPath);
36804
36919
  let workingMemorySize = 0;
@@ -36917,20 +37032,20 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36917
37032
  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
37033
  });
36919
37034
  }
36920
- if (lastExtraction) {
36921
- const extractionAge = Date.now() - new Date(lastExtraction.runAt).getTime();
37035
+ if (effectiveExtraction) {
37036
+ const extractionAge = Date.now() - new Date(effectiveExtraction.runAt).getTime();
36922
37037
  const oneWeekMs = 7 * 24 * 60 * 60 * 1e3;
36923
37038
  checks.push({
36924
37039
  id: "recent-extraction",
36925
37040
  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"}`
37041
+ passed: effectiveExtraction.success && extractionAge < oneWeekMs,
37042
+ details: effectiveExtraction.success ? `Last successful extraction ${formatTimeAgo(effectiveExtraction.runAt)} (${effectiveExtraction.insightCount} insights, ${effectiveExtraction.duplicateCount} duplicates skipped)` : `Last extraction failed: ${effectiveExtraction.error || "Unknown error"}`
36928
37043
  });
36929
37044
  checks.push({
36930
37045
  id: "extraction-summary",
36931
37046
  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"
37047
+ passed: effectiveExtraction.success && effectiveExtraction.summary.length > 10,
37048
+ details: effectiveExtraction.success ? `Summary: "${effectiveExtraction.summary.slice(0, 100)}${effectiveExtraction.summary.length > 100 ? "..." : ""}"` : "No meaningful summary available"
36934
37049
  });
36935
37050
  } else {
36936
37051
  checks.push({
@@ -36940,12 +37055,12 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36940
37055
  details: "No extraction runs recorded"
36941
37056
  });
36942
37057
  }
36943
- if (pruningOutcome) {
37058
+ if (effectivePruning) {
36944
37059
  checks.push({
36945
37060
  id: "pruning-applied",
36946
37061
  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}`
37062
+ passed: effectivePruning.applied,
37063
+ details: effectivePruning.applied ? `Pruning applied: ${effectivePruning.originalSize} \u2192 ${effectivePruning.newSize} chars (${effectivePruning.sizeDelta >= 0 ? "+" : ""}${effectivePruning.sizeDelta} chars)` : `Pruning skipped: ${effectivePruning.reason}`
36949
37064
  });
36950
37065
  }
36951
37066
  const failedChecks = checks.filter((c) => !c.passed);
@@ -36972,14 +37087,14 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36972
37087
  categories: categoryCounts,
36973
37088
  lastUpdated
36974
37089
  },
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
37090
+ extraction: effectiveExtraction ? {
37091
+ runAt: effectiveExtraction.runAt,
37092
+ success: effectiveExtraction.success,
37093
+ insightCount: effectiveExtraction.insightCount,
37094
+ duplicateCount: effectiveExtraction.duplicateCount,
37095
+ skippedCount: effectiveExtraction.skippedCount,
37096
+ summary: effectiveExtraction.summary,
37097
+ error: effectiveExtraction.error
36983
37098
  } : {
36984
37099
  runAt: "",
36985
37100
  success: false,
@@ -36988,7 +37103,7 @@ async function generateMemoryAudit(rootDir, lastExtraction, pruningOutcome) {
36988
37103
  skippedCount: 0,
36989
37104
  summary: "No extraction runs recorded"
36990
37105
  },
36991
- pruning: pruningOutcome ?? {
37106
+ pruning: effectivePruning ?? {
36992
37107
  applied: false,
36993
37108
  reason: "No pruning run recorded",
36994
37109
  sizeDelta: 0,
@@ -37129,6 +37244,17 @@ async function processAndAuditInsightExtraction(rootDir, input) {
37129
37244
  newSize: currentMemory.length
37130
37245
  };
37131
37246
  }
37247
+ try {
37248
+ await writeMemoryAuditState(rootDir, {
37249
+ extraction: extractionInfo,
37250
+ pruning: pruneOutcome,
37251
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
37252
+ });
37253
+ } catch (err) {
37254
+ console.error(
37255
+ `[memory-audit] Failed to persist audit state: ${err instanceof Error ? err.message : String(err)}`
37256
+ );
37257
+ }
37132
37258
  const auditReport = await generateMemoryAudit(rootDir, extractionInfo, pruneOutcome);
37133
37259
  try {
37134
37260
  const auditMarkdown = renderMemoryAuditMarkdown(auditReport);
@@ -37140,13 +37266,14 @@ async function processAndAuditInsightExtraction(rootDir, input) {
37140
37266
  }
37141
37267
  return auditReport;
37142
37268
  }
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;
37269
+ 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
37270
  var init_memory_insights = __esm({
37145
37271
  "../core/src/memory-insights.ts"() {
37146
37272
  "use strict";
37147
37273
  MEMORY_WORKING_PATH = ".fusion/memory/MEMORY.md";
37148
37274
  MEMORY_INSIGHTS_PATH = ".fusion/memory-insights.md";
37149
37275
  MEMORY_AUDIT_PATH = ".fusion/memory-audit.md";
37276
+ MEMORY_AUDIT_STATE_PATH = ".fusion/memory-audit-state.json";
37150
37277
  DEFAULT_INSIGHT_SCHEDULE = "0 2 * * *";
37151
37278
  DEFAULT_MIN_INTERVAL_MS = 24 * 60 * 60 * 1e3;
37152
37279
  MIN_INSIGHT_GROWTH_CHARS = 1e3;
@@ -38324,15 +38451,15 @@ var require_fd_slicer = __commonJS({
38324
38451
  var Writable = stream.Writable;
38325
38452
  var PassThrough = stream.PassThrough;
38326
38453
  var Pend = require_pend();
38327
- var EventEmitter25 = __require("events").EventEmitter;
38454
+ var EventEmitter26 = __require("events").EventEmitter;
38328
38455
  exports.createFromBuffer = createFromBuffer;
38329
38456
  exports.createFromFd = createFromFd;
38330
38457
  exports.BufferSlicer = BufferSlicer;
38331
38458
  exports.FdSlicer = FdSlicer;
38332
- util.inherits(FdSlicer, EventEmitter25);
38459
+ util.inherits(FdSlicer, EventEmitter26);
38333
38460
  function FdSlicer(fd, options) {
38334
38461
  options = options || {};
38335
- EventEmitter25.call(this);
38462
+ EventEmitter26.call(this);
38336
38463
  this.fd = fd;
38337
38464
  this.pend = new Pend();
38338
38465
  this.pend.max = 1;
@@ -38476,9 +38603,9 @@ var require_fd_slicer = __commonJS({
38476
38603
  this.destroyed = true;
38477
38604
  this.context.unref();
38478
38605
  };
38479
- util.inherits(BufferSlicer, EventEmitter25);
38606
+ util.inherits(BufferSlicer, EventEmitter26);
38480
38607
  function BufferSlicer(buffer, options) {
38481
- EventEmitter25.call(this);
38608
+ EventEmitter26.call(this);
38482
38609
  options = options || {};
38483
38610
  this.refCount = 0;
38484
38611
  this.buffer = buffer;
@@ -38890,7 +39017,7 @@ var require_yauzl = __commonJS({
38890
39017
  var fd_slicer = require_fd_slicer();
38891
39018
  var crc32 = require_buffer_crc32();
38892
39019
  var util = __require("util");
38893
- var EventEmitter25 = __require("events").EventEmitter;
39020
+ var EventEmitter26 = __require("events").EventEmitter;
38894
39021
  var Transform = __require("stream").Transform;
38895
39022
  var PassThrough = __require("stream").PassThrough;
38896
39023
  var Writable = __require("stream").Writable;
@@ -39022,10 +39149,10 @@ var require_yauzl = __commonJS({
39022
39149
  callback(new Error("end of central directory record signature not found"));
39023
39150
  });
39024
39151
  }
39025
- util.inherits(ZipFile, EventEmitter25);
39152
+ util.inherits(ZipFile, EventEmitter26);
39026
39153
  function ZipFile(reader, centralDirectoryOffset, fileSize, entryCount, comment, autoClose, lazyEntries, decodeStrings, validateEntrySizes, strictFileNames) {
39027
39154
  var self = this;
39028
- EventEmitter25.call(self);
39155
+ EventEmitter26.call(self);
39029
39156
  self.reader = reader;
39030
39157
  self.reader.on("error", function(err) {
39031
39158
  emitError(self, err);
@@ -39386,9 +39513,9 @@ var require_yauzl = __commonJS({
39386
39513
  }
39387
39514
  cb();
39388
39515
  };
39389
- util.inherits(RandomAccessReader, EventEmitter25);
39516
+ util.inherits(RandomAccessReader, EventEmitter26);
39390
39517
  function RandomAccessReader() {
39391
- EventEmitter25.call(this);
39518
+ EventEmitter26.call(this);
39392
39519
  this.refCount = 0;
39393
39520
  }
39394
39521
  RandomAccessReader.prototype.ref = function() {
@@ -39519,11 +39646,11 @@ var require_extract_zip = __commonJS({
39519
39646
  var { createWriteStream, promises: fs } = __require("fs");
39520
39647
  var getStream = require_get_stream();
39521
39648
  var path = __require("path");
39522
- var { promisify: promisify11 } = __require("util");
39649
+ var { promisify: promisify13 } = __require("util");
39523
39650
  var stream = __require("stream");
39524
39651
  var yauzl = require_yauzl();
39525
- var openZip = promisify11(yauzl.open);
39526
- var pipeline = promisify11(stream.pipeline);
39652
+ var openZip = promisify13(yauzl.open);
39653
+ var pipeline = promisify13(stream.pipeline);
39527
39654
  var Extractor = class {
39528
39655
  constructor(zipPath, opts) {
39529
39656
  this.zipPath = zipPath;
@@ -39605,7 +39732,7 @@ var require_extract_zip = __commonJS({
39605
39732
  await fs.mkdir(destDir, mkdirOptions);
39606
39733
  if (isDir) return;
39607
39734
  debug("opening read stream", dest);
39608
- const readStream = await promisify11(this.zipfile.openReadStream.bind(this.zipfile))(entry);
39735
+ const readStream = await promisify13(this.zipfile.openReadStream.bind(this.zipfile))(entry);
39609
39736
  if (symlink) {
39610
39737
  const link = await getStream(readStream);
39611
39738
  debug("creating symlink", link, dest);
@@ -47266,12 +47393,12 @@ function resolveExtractionRoot(tempDir) {
47266
47393
  return tempDir;
47267
47394
  }
47268
47395
  async function extractTarArchive(archivePath, outputDir) {
47269
- const [{ execFile: execFile4 }, { promisify: promisify11 }] = await Promise.all([
47396
+ const [{ execFile: execFile6 }, { promisify: promisify13 }] = await Promise.all([
47270
47397
  import("node:child_process"),
47271
47398
  import("node:util")
47272
47399
  ]);
47273
- const execFileAsync2 = promisify11(execFile4);
47274
- await execFileAsync2("tar", ["xzf", archivePath, "-C", outputDir]);
47400
+ const execFileAsync4 = promisify13(execFile6);
47401
+ await execFileAsync4("tar", ["xzf", archivePath, "-C", outputDir]);
47275
47402
  }
47276
47403
  async function parseCompanyArchive(archivePath) {
47277
47404
  const resolvedArchivePath = resolve8(archivePath);
@@ -48451,7 +48578,7 @@ ${stack}` : message2;
48451
48578
  }
48452
48579
  return { message, detail: message };
48453
48580
  }
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;
48581
+ 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
48582
  var init_logger2 = __esm({
48456
48583
  "../engine/src/logger.ts"() {
48457
48584
  "use strict";
@@ -48473,6 +48600,7 @@ var init_logger2 = __esm({
48473
48600
  autopilotLog = createLogger2("autopilot");
48474
48601
  heartbeatLog = createLogger2("heartbeat");
48475
48602
  remoteNodeLog = createLogger2("remote-node");
48603
+ remoteTunnelLog = createLogger2("remote-tunnel");
48476
48604
  nodeHealthMonitorLog = createLogger2("node-health-monitor");
48477
48605
  peerExchangeLog = createLogger2("peer-exchange");
48478
48606
  }
@@ -48809,11 +48937,11 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
48809
48937
  return;
48810
48938
  }
48811
48939
  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);
48940
+ const { execFile: execFile6 } = await import("node:child_process");
48941
+ const { promisify: promisify13 } = await import("node:util");
48942
+ const execFileAsync4 = promisify13(execFile6);
48815
48943
  try {
48816
- await execFileAsync2("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
48944
+ await execFileAsync4("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
48817
48945
  cwd: rootDir,
48818
48946
  timeout: 4e3,
48819
48947
  maxBuffer: 512 * 1024
@@ -48826,8 +48954,8 @@ ${stderr}`)) {
48826
48954
  throw error;
48827
48955
  }
48828
48956
  }
48829
- await execFileAsync2("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
48830
- await execFileAsync2("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
48957
+ await execFileAsync4("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
48958
+ await execFileAsync4("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
48831
48959
  })();
48832
48960
  agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
48833
48961
  try {
@@ -48848,10 +48976,10 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
48848
48976
  }
48849
48977
  try {
48850
48978
  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), {
48979
+ const { execFile: execFile6 } = await import("node:child_process");
48980
+ const { promisify: promisify13 } = await import("node:util");
48981
+ const execFileAsync4 = promisify13(execFile6);
48982
+ const { stdout } = await execFileAsync4("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
48855
48983
  cwd: rootDir,
48856
48984
  timeout: 4e3,
48857
48985
  maxBuffer: 1024 * 1024
@@ -54044,14 +54172,14 @@ function rethrowIfMergeAborted(error) {
54044
54172
  throw error;
54045
54173
  }
54046
54174
  }
54047
- async function runDeterministicVerification(store, rootDir, taskId, testCommand, buildCommand, testSource, buildSource, signal) {
54175
+ async function runDeterministicVerification(store, rootDir, taskId, testCommand, buildCommand2, testSource, buildSource, signal) {
54048
54176
  const result = { allPassed: true };
54049
- if (!testCommand && !buildCommand) {
54177
+ if (!testCommand && !buildCommand2) {
54050
54178
  mergerLog.log(`${taskId}: no verification commands configured \u2014 skipping`);
54051
54179
  return result;
54052
54180
  }
54053
54181
  const normalizedTestCommand = testCommand?.trim();
54054
- const normalizedBuildCommand = buildCommand?.trim();
54182
+ const normalizedBuildCommand = buildCommand2?.trim();
54055
54183
  const hasTestCommand = !!normalizedTestCommand;
54056
54184
  const hasBuildCommand = !!normalizedBuildCommand;
54057
54185
  const testSourceLabel = testSource === "inferred" ? " [inferred]" : "";
@@ -55545,7 +55673,7 @@ async function executeMergeAttempt(params, aiTracker) {
55545
55673
  result,
55546
55674
  settings,
55547
55675
  testCommand,
55548
- buildCommand,
55676
+ buildCommand: buildCommand2,
55549
55677
  testSource,
55550
55678
  buildSource
55551
55679
  } = params;
@@ -55614,14 +55742,14 @@ async function executeMergeAttempt(params, aiTracker) {
55614
55742
  );
55615
55743
  mergerLog.log(`${taskId}: committed after auto-resolving all conflicts`);
55616
55744
  }
55617
- if (testCommand || buildCommand) {
55745
+ if (testCommand || buildCommand2) {
55618
55746
  throwIfAborted(options.signal, taskId);
55619
55747
  await runDeterministicVerification(
55620
55748
  store,
55621
55749
  rootDir,
55622
55750
  taskId,
55623
55751
  testCommand,
55624
- buildCommand,
55752
+ buildCommand2,
55625
55753
  testSource,
55626
55754
  buildSource,
55627
55755
  options.signal
@@ -55637,14 +55765,14 @@ async function executeMergeAttempt(params, aiTracker) {
55637
55765
  ).trim() === "0";
55638
55766
  if (squashIsEmpty) {
55639
55767
  mergerLog.log(`${taskId}: squash merge staged nothing \u2014 already merged`);
55640
- if (testCommand || buildCommand) {
55768
+ if (testCommand || buildCommand2) {
55641
55769
  throwIfAborted(options.signal, taskId);
55642
55770
  await runDeterministicVerification(
55643
55771
  store,
55644
55772
  rootDir,
55645
55773
  taskId,
55646
55774
  testCommand,
55647
- buildCommand,
55775
+ buildCommand2,
55648
55776
  testSource,
55649
55777
  buildSource,
55650
55778
  options.signal
@@ -55664,14 +55792,14 @@ async function executeMergeAttempt(params, aiTracker) {
55664
55792
  ).trim() === "0";
55665
55793
  if (squashIsEmpty) {
55666
55794
  mergerLog.log(`${taskId}: squash merge staged nothing \u2014 already merged`);
55667
- if (testCommand || buildCommand) {
55795
+ if (testCommand || buildCommand2) {
55668
55796
  throwIfAborted(options.signal, taskId);
55669
55797
  await runDeterministicVerification(
55670
55798
  store,
55671
55799
  rootDir,
55672
55800
  taskId,
55673
55801
  testCommand,
55674
- buildCommand,
55802
+ buildCommand2,
55675
55803
  testSource,
55676
55804
  buildSource,
55677
55805
  options.signal
@@ -55691,7 +55819,7 @@ async function executeMergeAttempt(params, aiTracker) {
55691
55819
  return false;
55692
55820
  }
55693
55821
  }
55694
- if (buildCommand) {
55822
+ if (buildCommand2) {
55695
55823
  throwIfAborted(options.signal, taskId);
55696
55824
  const stagedFiles = await getStagedFiles(rootDir);
55697
55825
  if (shouldSyncDependenciesForMerge(stagedFiles, hasInstallState(rootDir))) {
@@ -55712,7 +55840,7 @@ async function executeMergeAttempt(params, aiTracker) {
55712
55840
  simplifiedContext: attemptNum === 2,
55713
55841
  options,
55714
55842
  testCommand,
55715
- buildCommand
55843
+ buildCommand: buildCommand2
55716
55844
  });
55717
55845
  if (!agentResult.success) {
55718
55846
  const errorMessage = agentResult.error || "Build verification failed";
@@ -55727,14 +55855,14 @@ async function executeMergeAttempt(params, aiTracker) {
55727
55855
  }
55728
55856
  throw new Error(`Build verification failed for ${taskId}: ${errorMessage}`);
55729
55857
  }
55730
- if (testCommand || buildCommand) {
55858
+ if (testCommand || buildCommand2) {
55731
55859
  throwIfAborted(options.signal, taskId);
55732
55860
  await runDeterministicVerification(
55733
55861
  store,
55734
55862
  rootDir,
55735
55863
  taskId,
55736
55864
  testCommand,
55737
- buildCommand,
55865
+ buildCommand2,
55738
55866
  testSource,
55739
55867
  buildSource,
55740
55868
  options.signal
@@ -55762,7 +55890,7 @@ async function executeMergeAttempt(params, aiTracker) {
55762
55890
  }
55763
55891
  }
55764
55892
  async function attemptWithTheirsStrategy(params) {
55765
- const { rootDir, branch, commitLog, includeTaskId, taskId, store, settings, testCommand, buildCommand, testSource, buildSource } = params;
55893
+ const { rootDir, branch, commitLog, includeTaskId, taskId, store, settings, testCommand, buildCommand: buildCommand2, testSource, buildSource } = params;
55766
55894
  mergerLog.log(`${taskId}: attempting merge with -X theirs strategy`);
55767
55895
  try {
55768
55896
  throwIfAborted(params.options.signal, taskId);
@@ -55782,14 +55910,14 @@ async function attemptWithTheirsStrategy(params) {
55782
55910
  encoding: "utf-8"
55783
55911
  }).trim();
55784
55912
  if (staged === "0") {
55785
- if (testCommand || buildCommand) {
55913
+ if (testCommand || buildCommand2) {
55786
55914
  throwIfAborted(params.options.signal, taskId);
55787
55915
  await runDeterministicVerification(
55788
55916
  store,
55789
55917
  rootDir,
55790
55918
  taskId,
55791
55919
  testCommand,
55792
- buildCommand,
55920
+ buildCommand2,
55793
55921
  testSource,
55794
55922
  buildSource,
55795
55923
  params.options.signal
@@ -55806,14 +55934,14 @@ async function attemptWithTheirsStrategy(params) {
55806
55934
  { cwd: rootDir }
55807
55935
  );
55808
55936
  mergerLog.log(`${taskId}: committed with -X theirs auto-resolution`);
55809
- if (testCommand || buildCommand) {
55937
+ if (testCommand || buildCommand2) {
55810
55938
  throwIfAborted(params.options.signal, taskId);
55811
55939
  await runDeterministicVerification(
55812
55940
  store,
55813
55941
  rootDir,
55814
55942
  taskId,
55815
55943
  testCommand,
55816
- buildCommand,
55944
+ buildCommand2,
55817
55945
  testSource,
55818
55946
  buildSource,
55819
55947
  params.options.signal
@@ -55841,7 +55969,7 @@ async function runAiAgentForCommit(params) {
55841
55969
  simplifiedContext,
55842
55970
  options,
55843
55971
  testCommand,
55844
- buildCommand
55972
+ buildCommand: buildCommand2
55845
55973
  } = params;
55846
55974
  const settings = await store.getSettings();
55847
55975
  let buildFailed = false;
@@ -55930,7 +56058,7 @@ async function runAiAgentForCommit(params) {
55930
56058
  hasConflicts,
55931
56059
  simplifiedContext,
55932
56060
  testCommand,
55933
- buildCommand,
56061
+ buildCommand: buildCommand2,
55934
56062
  authorArg
55935
56063
  });
55936
56064
  mergerLog.log(`${taskId}: starting fresh merge agent session`);
@@ -55962,7 +56090,7 @@ async function runAiAgentForCommit(params) {
55962
56090
  simplifiedContext: true,
55963
56091
  // Also skip detailed context
55964
56092
  testCommand,
55965
- buildCommand,
56093
+ buildCommand: buildCommand2,
55966
56094
  authorArg
55967
56095
  });
55968
56096
  try {
@@ -55998,7 +56126,7 @@ async function runAiAgentForCommit(params) {
55998
56126
  encoding: "utf-8"
55999
56127
  }).trim();
56000
56128
  if (staged !== "0") {
56001
- if (!buildCommand) {
56129
+ if (!buildCommand2) {
56002
56130
  throwIfAborted(options.signal, taskId);
56003
56131
  mergerLog.log("Agent didn't commit \u2014 committing with fallback message");
56004
56132
  const escapedLog = commitLog.replace(/"/g, '\\"');
@@ -56025,7 +56153,7 @@ async function runAiAgentForCommit(params) {
56025
56153
  }
56026
56154
  }
56027
56155
  function buildMergePrompt(params) {
56028
- const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, testCommand, buildCommand, authorArg } = params;
56156
+ const { taskId, branch, commitLog, diffStat, hasConflicts, simplifiedContext, testCommand, buildCommand: buildCommand2, authorArg } = params;
56029
56157
  const truncatedCommitLog = truncateWithEllipsis(commitLog, MERGE_COMMIT_LOG_MAX_CHARS);
56030
56158
  const truncatedDiffStat = truncateWithEllipsis(diffStat, MERGE_DIFF_STAT_MAX_CHARS);
56031
56159
  const parts = [
@@ -56073,11 +56201,11 @@ function buildMergePrompt(params) {
56073
56201
  "If it exits non-zero, call `fn_report_build_failure` with the concrete error output and stop without committing."
56074
56202
  );
56075
56203
  }
56076
- if (buildCommand) {
56204
+ if (buildCommand2) {
56077
56205
  parts.push(
56078
56206
  "",
56079
56207
  "## Build command",
56080
- `Build command: \`${buildCommand}\``,
56208
+ `Build command: \`${buildCommand2}\``,
56081
56209
  "",
56082
56210
  "This command is mandatory before commit.",
56083
56211
  "Run it with the bash tool in the current worktree and inspect the actual exit code.",
@@ -58487,6 +58615,8 @@ Lint, tests, and typecheck are also hard quality gates:
58487
58615
  loopRecoveryState = /* @__PURE__ */ new Map();
58488
58616
  /** Spawned child agent IDs per parent task ID. Used for lifecycle tracking. */
58489
58617
  spawnedAgents = /* @__PURE__ */ new Map();
58618
+ /** Per-task baseline of agent cumulative token counters used for delta persistence. */
58619
+ tokenUsageBaselines = /* @__PURE__ */ new Map();
58490
58620
  async finalizeAlreadyReviewedTask(taskId) {
58491
58621
  const latestTask = await this.store.getTask(taskId);
58492
58622
  if (!latestTask || latestTask.column !== "in-review") {
@@ -58602,6 +58732,65 @@ Lint, tests, and typecheck are also hard quality gates:
58602
58732
  async getTaskCompletionBlocker(task) {
58603
58733
  return getTaskCompletionBlockerForStore(this.store, task);
58604
58734
  }
58735
+ async initializeTokenUsageBaseline(taskId, task) {
58736
+ if (!this.options.agentStore) return;
58737
+ const currentTask = task ?? await this.store.getTask(taskId);
58738
+ const assignedAgentId = currentTask.assignedAgentId?.trim();
58739
+ if (!assignedAgentId) return;
58740
+ const existing = this.tokenUsageBaselines.get(taskId);
58741
+ if (existing?.agentId === assignedAgentId) return;
58742
+ const agent = await this.options.agentStore.getAgent(assignedAgentId);
58743
+ if (!agent) return;
58744
+ this.tokenUsageBaselines.set(taskId, {
58745
+ agentId: assignedAgentId,
58746
+ inputTokens: agent.totalInputTokens ?? 0,
58747
+ outputTokens: agent.totalOutputTokens ?? 0
58748
+ });
58749
+ }
58750
+ async persistTokenUsage(taskId) {
58751
+ if (!this.options.agentStore) return;
58752
+ const task = await this.store.getTask(taskId);
58753
+ const assignedAgentId = task.assignedAgentId?.trim();
58754
+ if (!assignedAgentId) return;
58755
+ const agent = await this.options.agentStore.getAgent(assignedAgentId);
58756
+ if (!agent) return;
58757
+ const currentInputTokens = agent.totalInputTokens ?? 0;
58758
+ const currentOutputTokens = agent.totalOutputTokens ?? 0;
58759
+ const baseline = this.tokenUsageBaselines.get(taskId);
58760
+ if (!baseline || baseline.agentId !== assignedAgentId) {
58761
+ this.tokenUsageBaselines.set(taskId, {
58762
+ agentId: assignedAgentId,
58763
+ inputTokens: currentInputTokens,
58764
+ outputTokens: currentOutputTokens
58765
+ });
58766
+ return;
58767
+ }
58768
+ const inputDelta = Math.max(0, currentInputTokens - baseline.inputTokens);
58769
+ const outputDelta = Math.max(0, currentOutputTokens - baseline.outputTokens);
58770
+ this.tokenUsageBaselines.set(taskId, {
58771
+ agentId: assignedAgentId,
58772
+ inputTokens: currentInputTokens,
58773
+ outputTokens: currentOutputTokens
58774
+ });
58775
+ if (inputDelta === 0 && outputDelta === 0 && !task.tokenUsage) {
58776
+ return;
58777
+ }
58778
+ const now = (/* @__PURE__ */ new Date()).toISOString();
58779
+ const mergedInputTokens = (task.tokenUsage?.inputTokens ?? 0) + inputDelta;
58780
+ const mergedOutputTokens = (task.tokenUsage?.outputTokens ?? 0) + outputDelta;
58781
+ const cachedTokens = task.tokenUsage?.cachedTokens ?? 0;
58782
+ const totalTokens = mergedInputTokens + mergedOutputTokens + cachedTokens;
58783
+ await this.store.updateTask(taskId, {
58784
+ tokenUsage: {
58785
+ inputTokens: mergedInputTokens,
58786
+ outputTokens: mergedOutputTokens,
58787
+ cachedTokens,
58788
+ totalTokens,
58789
+ firstUsedAt: task.tokenUsage?.firstUsedAt ?? now,
58790
+ lastUsedAt: now
58791
+ }
58792
+ });
58793
+ }
58605
58794
  /**
58606
58795
  * Execute a review handoff: move the task to in-review column with
58607
58796
  * awaiting-user-review status, assign the requesting user, and dispose
@@ -58624,6 +58813,7 @@ Lint, tests, and typecheck are also hard quality gates:
58624
58813
  },
58625
58814
  this.currentRunContext
58626
58815
  );
58816
+ await this.persistTokenUsage(task.id);
58627
58817
  await this.store.moveTask(task.id, "in-review");
58628
58818
  if (this.activeSessions.has(task.id)) {
58629
58819
  const { session: activeSession } = this.activeSessions.get(task.id);
@@ -58662,6 +58852,7 @@ Lint, tests, and typecheck are also hard quality gates:
58662
58852
  executorLog.log(`${task.id}: fast mode \u2014 skipping workflow steps on auto-recovery`);
58663
58853
  }
58664
58854
  }
58855
+ await this.persistTokenUsage(task.id);
58665
58856
  await this.store.moveTask(task.id, "in-review");
58666
58857
  await this.store.logEntry(task.id, "Auto-recovered: task work was complete but stuck in in-progress \u2014 moved to in-review");
58667
58858
  executorLog.log(`\u2713 ${task.id} auto-recovered completed task \u2192 in-review`);
@@ -59041,6 +59232,7 @@ Lint, tests, and typecheck are also hard quality gates:
59041
59232
  this.options.onStart?.(task, worktreePath);
59042
59233
  const detail = await this.store.getTask(task.id);
59043
59234
  executorLog.log(`${task.id}: fetched task detail (${detail.steps.length} steps, prompt length=${detail.prompt?.length ?? 0})`);
59235
+ await this.initializeTokenUsageBaseline(task.id, detail);
59044
59236
  if (detail.steps.length === 0) {
59045
59237
  const steps = await this.store.parseStepsFromPrompt(task.id);
59046
59238
  if (steps.length > 0) {
@@ -59088,6 +59280,9 @@ Lint, tests, and typecheck are also hard quality gates:
59088
59280
  } catch (err) {
59089
59281
  executorLog.warn(`${task.id}: failed to update step ${stepIndex} status: ${err}`);
59090
59282
  }
59283
+ this.persistTokenUsage(task.id).catch((err) => {
59284
+ executorLog.warn(`${task.id}: failed to persist token usage on step ${stepIndex} complete: ${err}`);
59285
+ });
59091
59286
  }
59092
59287
  });
59093
59288
  this.activeStepExecutors.set(task.id, stepExecutor);
@@ -59137,6 +59332,7 @@ Lint, tests, and typecheck are also hard quality gates:
59137
59332
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
59138
59333
  }
59139
59334
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
59335
+ await this.persistTokenUsage(task.id);
59140
59336
  await this.store.moveTask(task.id, "in-review");
59141
59337
  await audit.database({ type: "task:move", target: task.id, metadata: { to: "in-review" } });
59142
59338
  executorLog.log(`\u2713 ${task.id} completed (step-session) \u2192 in-review`);
@@ -59145,6 +59341,7 @@ Lint, tests, and typecheck are also hard quality gates:
59145
59341
  const failedSteps = results.filter((r) => !r.success);
59146
59342
  const errorSummary = failedSteps.map((r) => `Step ${r.stepIndex}: ${r.error || "unknown error"}`).join("; ");
59147
59343
  await this.store.updateTask(task.id, { status: "failed", error: errorSummary });
59344
+ await this.persistTokenUsage(task.id);
59148
59345
  await this.store.moveTask(task.id, "in-review");
59149
59346
  executorLog.log(`\u2717 ${task.id} step-session failed \u2192 in-review: ${errorSummary}`);
59150
59347
  this.options.onError?.(task, new Error(errorSummary));
@@ -59221,6 +59418,7 @@ Lint, tests, and typecheck are also hard quality gates:
59221
59418
  recoveryRetryCount: null,
59222
59419
  nextRecoveryAt: null
59223
59420
  });
59421
+ await this.persistTokenUsage(task.id);
59224
59422
  await this.store.moveTask(task.id, "in-review");
59225
59423
  executorLog.log(`\u2717 ${task.id} transient retries exhausted \u2192 in-review`);
59226
59424
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59228,6 +59426,7 @@ Lint, tests, and typecheck are also hard quality gates:
59228
59426
  executorLog.error(`\u2717 ${task.id} step-session execution failed:`, errorDetail);
59229
59427
  await this.store.logEntry(task.id, `Step-session execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
59230
59428
  await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
59429
+ await this.persistTokenUsage(task.id);
59231
59430
  await this.store.moveTask(task.id, "in-review");
59232
59431
  executorLog.log(`\u2717 ${task.id} step-session execution failed \u2192 in-review`);
59233
59432
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59462,6 +59661,7 @@ Lint, tests, and typecheck are also hard quality gates:
59462
59661
  if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
59463
59662
  executorLog.log(`${task.id} paused after completion (graceful session exit) \u2014 finalizing to in-review`);
59464
59663
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review");
59664
+ await this.persistTokenUsage(task.id);
59465
59665
  await this.store.moveTask(task.id, "in-review");
59466
59666
  this.options.onComplete?.(task);
59467
59667
  } else {
@@ -59511,6 +59711,7 @@ Lint, tests, and typecheck are also hard quality gates:
59511
59711
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
59512
59712
  }
59513
59713
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
59714
+ await this.persistTokenUsage(task.id);
59514
59715
  await this.store.moveTask(task.id, "in-review");
59515
59716
  executorLog.log(`\u2713 ${task.id} completed \u2192 in-review`);
59516
59717
  this.options.onComplete?.(task);
@@ -59607,6 +59808,7 @@ Lint, tests, and typecheck are also hard quality gates:
59607
59808
  await this.store.logEntry(task.id, "Fast mode \u2014 pre-merge workflow steps skipped", void 0, this.currentRunContext);
59608
59809
  }
59609
59810
  await this.store.updateTask(task.id, { workflowStepRetries: void 0, taskDoneRetryCount: null });
59811
+ await this.persistTokenUsage(task.id);
59610
59812
  await this.store.moveTask(task.id, "in-review");
59611
59813
  executorLog.log(`\u2713 ${task.id} completed on retry \u2192 in-review`);
59612
59814
  this.options.onComplete?.(task);
@@ -59631,6 +59833,7 @@ Lint, tests, and typecheck are also hard quality gates:
59631
59833
  } else {
59632
59834
  await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
59633
59835
  await this.store.logEntry(task.id, `${errorMessage} \u2014 moved to in-review for inspection`, void 0, this.currentRunContext);
59836
+ await this.persistTokenUsage(task.id);
59634
59837
  await this.store.moveTask(task.id, "in-review");
59635
59838
  executorLog.log(`\u2717 ${task.id} failed after ${MAX_TASK_DONE_SESSION_RETRIES} retries \u2014 no fn_task_done \u2192 in-review`);
59636
59839
  }
@@ -59694,6 +59897,7 @@ Lint, tests, and typecheck are also hard quality gates:
59694
59897
  if (await this.shouldFinalizeCompletedTask(task.id, taskDone)) {
59695
59898
  executorLog.log(`${task.id} paused after completion \u2014 finalizing to in-review`);
59696
59899
  await this.store.logEntry(task.id, "Execution paused after completion \u2014 finalizing to in-review", void 0, this.currentRunContext);
59900
+ await this.persistTokenUsage(task.id);
59697
59901
  await this.store.moveTask(task.id, "in-review");
59698
59902
  this.options.onComplete?.(task);
59699
59903
  } else {
@@ -59819,6 +60023,7 @@ Lint, tests, and typecheck are also hard quality gates:
59819
60023
  recoveryRetryCount: null,
59820
60024
  nextRecoveryAt: null
59821
60025
  });
60026
+ await this.persistTokenUsage(task.id);
59822
60027
  await this.store.moveTask(task.id, "in-review");
59823
60028
  executorLog.log(`\u2717 ${task.id} transient retries exhausted \u2192 in-review`);
59824
60029
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59827,6 +60032,7 @@ Lint, tests, and typecheck are also hard quality gates:
59827
60032
  executorLog.error(`\u2717 ${task.id} execution failed:`, errorDetail);
59828
60033
  await this.store.logEntry(task.id, `Execution failed: ${errorMessage}`, errorStack ?? errorDetail, this.currentRunContext);
59829
60034
  await this.store.updateTask(task.id, { status: "failed", error: errorMessage });
60035
+ await this.persistTokenUsage(task.id);
59830
60036
  await this.store.moveTask(task.id, "in-review");
59831
60037
  executorLog.log(`\u2717 ${task.id} execution failed \u2192 in-review`);
59832
60038
  this.options.onError?.(task, err instanceof Error ? err : new Error(errorMessage));
@@ -59840,6 +60046,7 @@ Lint, tests, and typecheck are also hard quality gates:
59840
60046
  executorLog.warn(`terminateAllChildren failed for ${task.id}: ${err instanceof Error ? err.message : String(err)}`);
59841
60047
  }
59842
60048
  this.loopRecoveryState.delete(task.id);
60049
+ this.tokenUsageBaselines.delete(task.id);
59843
60050
  if (stuckRequeue === true) {
59844
60051
  try {
59845
60052
  const latestTask = await this.store.getTask(task.id);
@@ -70580,8 +70787,568 @@ var init_project_manager = __esm({
70580
70787
  }
70581
70788
  });
70582
70789
 
70790
+ // ../engine/src/remote-access/provider-adapters.ts
70791
+ import { accessSync, constants as fsConstants } from "node:fs";
70792
+ function isAbsoluteOrPathLike(input) {
70793
+ return input.startsWith("/") || input.startsWith("./") || input.startsWith("../");
70794
+ }
70795
+ function assertNonEmpty(value, label) {
70796
+ if (typeof value !== "string" || value.trim().length === 0) {
70797
+ throw new Error(`invalid_config:${label} must be a non-empty string`);
70798
+ }
70799
+ }
70800
+ function ensureNumberInRange(value, label) {
70801
+ if (value === void 0) {
70802
+ return void 0;
70803
+ }
70804
+ if (!Number.isFinite(value) || value <= 0) {
70805
+ throw new Error(`invalid_config:${label} must be a positive number`);
70806
+ }
70807
+ return Math.floor(value);
70808
+ }
70809
+ function collectSensitiveValues(config) {
70810
+ const values = /* @__PURE__ */ new Set();
70811
+ const env = config.env ?? {};
70812
+ const tokenEnvVar = config.tokenEnvVar;
70813
+ if (typeof tokenEnvVar === "string" && tokenEnvVar.trim().length > 0) {
70814
+ const tokenValue = env[tokenEnvVar] ?? process.env[tokenEnvVar];
70815
+ if (!tokenValue) {
70816
+ throw new Error(`invalid_config:missing credential in env var ${tokenEnvVar}`);
70817
+ }
70818
+ values.add(tokenValue);
70819
+ }
70820
+ for (const envName of config.sensitiveEnvVars ?? []) {
70821
+ const envValue = env[envName] ?? process.env[envName];
70822
+ if (envValue) {
70823
+ values.add(envValue);
70824
+ }
70825
+ }
70826
+ return [...values].sort((a, b) => b.length - a.length);
70827
+ }
70828
+ function redactValue(input, sensitiveValues) {
70829
+ let redacted = input;
70830
+ for (const value of sensitiveValues) {
70831
+ redacted = redacted.split(value).join("[REDACTED]");
70832
+ }
70833
+ return redacted;
70834
+ }
70835
+ function redactArgs(args, sensitiveValues) {
70836
+ return args.map((arg) => redactValue(arg, sensitiveValues));
70837
+ }
70838
+ function buildCommand(config) {
70839
+ const command = config.executablePath.trim();
70840
+ const args = [...config.args];
70841
+ const env = {
70842
+ ...process.env,
70843
+ ...config.env ?? {}
70844
+ };
70845
+ const sensitiveValues = collectSensitiveValues(config);
70846
+ const redactedPreview = [command, ...redactArgs(args, sensitiveValues)].join(" ").trim();
70847
+ return {
70848
+ provider: config.provider,
70849
+ command,
70850
+ args,
70851
+ cwd: config.cwd,
70852
+ env,
70853
+ redactedPreview,
70854
+ sensitiveValues,
70855
+ readinessTimeoutMs: config.readinessTimeoutMs ?? DEFAULT_READINESS_TIMEOUT_MS,
70856
+ stopTimeoutMs: config.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS
70857
+ };
70858
+ }
70859
+ function parseCommonReadiness(line) {
70860
+ const normalized = line.trim();
70861
+ if (!normalized) {
70862
+ return null;
70863
+ }
70864
+ const urlMatch = normalized.match(URL_PATTERN);
70865
+ const url = urlMatch?.[1];
70866
+ if (/\b(connected|ready|available|started|serving)\b/i.test(normalized)) {
70867
+ return { ready: true, url };
70868
+ }
70869
+ if (url && /\b(trycloudflare|tailscale|ts\.net|serve|funnel)\b/i.test(normalized)) {
70870
+ return { ready: true, url };
70871
+ }
70872
+ return null;
70873
+ }
70874
+ function validateBaseConfig(config, provider) {
70875
+ if (config.provider !== provider) {
70876
+ throw new Error(`invalid_config:config provider ${config.provider} does not match ${provider}`);
70877
+ }
70878
+ assertNonEmpty(config.executablePath, "executablePath");
70879
+ if (!Array.isArray(config.args)) {
70880
+ throw new Error("invalid_config:args must be an array");
70881
+ }
70882
+ for (const [index, arg] of config.args.entries()) {
70883
+ assertNonEmpty(arg, `args[${index}]`);
70884
+ }
70885
+ if (config.cwd !== void 0) {
70886
+ assertNonEmpty(config.cwd, "cwd");
70887
+ }
70888
+ if (config.tokenEnvVar !== void 0) {
70889
+ assertNonEmpty(config.tokenEnvVar, "tokenEnvVar");
70890
+ }
70891
+ ensureNumberInRange(config.readinessTimeoutMs, "readinessTimeoutMs");
70892
+ ensureNumberInRange(config.stopTimeoutMs, "stopTimeoutMs");
70893
+ }
70894
+ function getTunnelProviderAdapter(provider) {
70895
+ return ADAPTERS[provider];
70896
+ }
70897
+ function redactTunnelText(input, sensitiveValues) {
70898
+ return redactValue(input, sensitiveValues);
70899
+ }
70900
+ var DEFAULT_READINESS_TIMEOUT_MS, DEFAULT_STOP_TIMEOUT_MS, URL_PATTERN, tailscaleAdapter, cloudflareAdapter, ADAPTERS;
70901
+ var init_provider_adapters = __esm({
70902
+ "../engine/src/remote-access/provider-adapters.ts"() {
70903
+ "use strict";
70904
+ DEFAULT_READINESS_TIMEOUT_MS = 2e4;
70905
+ DEFAULT_STOP_TIMEOUT_MS = 5e3;
70906
+ URL_PATTERN = /(https?:\/\/[^\s]+)/i;
70907
+ tailscaleAdapter = {
70908
+ provider: "tailscale",
70909
+ validateConfig(config) {
70910
+ validateBaseConfig(config, "tailscale");
70911
+ },
70912
+ buildCommand(config) {
70913
+ this.validateConfig(config);
70914
+ return buildCommand(config);
70915
+ },
70916
+ parseReadiness(line, _stream) {
70917
+ return parseCommonReadiness(line);
70918
+ }
70919
+ };
70920
+ cloudflareAdapter = {
70921
+ provider: "cloudflare",
70922
+ validateConfig(config) {
70923
+ validateBaseConfig(config, "cloudflare");
70924
+ if ("credentialsPath" in config && config.credentialsPath !== void 0) {
70925
+ assertNonEmpty(config.credentialsPath, "credentialsPath");
70926
+ if (isAbsoluteOrPathLike(config.credentialsPath)) {
70927
+ accessSync(config.credentialsPath, fsConstants.R_OK);
70928
+ }
70929
+ }
70930
+ },
70931
+ buildCommand(config) {
70932
+ this.validateConfig(config);
70933
+ return buildCommand(config);
70934
+ },
70935
+ parseReadiness(line, _stream) {
70936
+ return parseCommonReadiness(line);
70937
+ }
70938
+ };
70939
+ ADAPTERS = {
70940
+ tailscale: tailscaleAdapter,
70941
+ cloudflare: cloudflareAdapter
70942
+ };
70943
+ }
70944
+ });
70945
+
70946
+ // ../engine/src/remote-access/tunnel-process-manager.ts
70947
+ import { EventEmitter as EventEmitter18 } from "node:events";
70948
+ import { spawn as spawn2 } from "node:child_process";
70949
+ function nowIso() {
70950
+ return (/* @__PURE__ */ new Date()).toISOString();
70951
+ }
70952
+ function normalizeError(input) {
70953
+ if (input instanceof Error) {
70954
+ return input;
70955
+ }
70956
+ return new Error(String(input));
70957
+ }
70958
+ function maskSensitive(message, processHandle) {
70959
+ if (!processHandle) {
70960
+ return message;
70961
+ }
70962
+ return redactTunnelText(message, processHandle.command.sensitiveValues);
70963
+ }
70964
+ function killManagedProcess(child, signal) {
70965
+ if (typeof child.pid !== "number") {
70966
+ return;
70967
+ }
70968
+ if (process.platform !== "win32") {
70969
+ try {
70970
+ process.kill(-child.pid, signal);
70971
+ return;
70972
+ } catch {
70973
+ }
70974
+ }
70975
+ try {
70976
+ process.kill(child.pid, signal);
70977
+ } catch {
70978
+ }
70979
+ }
70980
+ function toStateError(code, err) {
70981
+ const normalized = normalizeError(err);
70982
+ return {
70983
+ code,
70984
+ message: normalized.message,
70985
+ at: nowIso()
70986
+ };
70987
+ }
70988
+ var DEFAULT_MAX_LOG_ENTRIES, DEFAULT_STOP_TIMEOUT_MS2, LineBuffer, TunnelProcessManager;
70989
+ var init_tunnel_process_manager = __esm({
70990
+ "../engine/src/remote-access/tunnel-process-manager.ts"() {
70991
+ "use strict";
70992
+ init_logger2();
70993
+ init_provider_adapters();
70994
+ DEFAULT_MAX_LOG_ENTRIES = 400;
70995
+ DEFAULT_STOP_TIMEOUT_MS2 = 5e3;
70996
+ LineBuffer = class {
70997
+ pending = "";
70998
+ push(chunk) {
70999
+ this.pending += chunk;
71000
+ const lines = this.pending.split(/\r?\n/);
71001
+ this.pending = lines.pop() ?? "";
71002
+ return lines.map((line) => line.trim()).filter(Boolean);
71003
+ }
71004
+ flush() {
71005
+ const tail = this.pending.trim();
71006
+ this.pending = "";
71007
+ return tail ? [tail] : [];
71008
+ }
71009
+ };
71010
+ TunnelProcessManager = class extends EventEmitter18 {
71011
+ maxLogEntries;
71012
+ defaultStopTimeoutMs;
71013
+ spawnImpl;
71014
+ status = {
71015
+ provider: null,
71016
+ state: "stopped",
71017
+ pid: null,
71018
+ startedAt: null,
71019
+ stoppedAt: null,
71020
+ url: null,
71021
+ lastError: null
71022
+ };
71023
+ logs = [];
71024
+ statusListeners = /* @__PURE__ */ new Set();
71025
+ logListeners = /* @__PURE__ */ new Set();
71026
+ processHandle = null;
71027
+ readinessTimer = null;
71028
+ stopTimer = null;
71029
+ operationChain = Promise.resolve();
71030
+ expectedStop = false;
71031
+ activeStopPromise = null;
71032
+ constructor(options = {}) {
71033
+ super();
71034
+ this.maxLogEntries = options.maxLogEntries ?? DEFAULT_MAX_LOG_ENTRIES;
71035
+ this.defaultStopTimeoutMs = options.stopTimeoutMs ?? DEFAULT_STOP_TIMEOUT_MS2;
71036
+ this.spawnImpl = options.spawnImpl ?? spawn2;
71037
+ }
71038
+ getStatus() {
71039
+ return { ...this.status, lastError: this.status.lastError ? { ...this.status.lastError } : null };
71040
+ }
71041
+ subscribeStatus(listener) {
71042
+ this.statusListeners.add(listener);
71043
+ listener(this.getStatus());
71044
+ return () => {
71045
+ this.statusListeners.delete(listener);
71046
+ };
71047
+ }
71048
+ subscribeLogs(listener) {
71049
+ this.logListeners.add(listener);
71050
+ return () => {
71051
+ this.logListeners.delete(listener);
71052
+ };
71053
+ }
71054
+ async start(provider, config) {
71055
+ return this.runExclusive(async () => {
71056
+ if (this.processHandle || this.status.state === "starting" || this.status.state === "running") {
71057
+ throw new Error("already_running:tunnel process is already active");
71058
+ }
71059
+ await this.startInternal(provider, config);
71060
+ });
71061
+ }
71062
+ async stop() {
71063
+ return this.runExclusive(async () => {
71064
+ await this.stopInternal();
71065
+ });
71066
+ }
71067
+ async switchProvider(target, config) {
71068
+ return this.runExclusive(async () => {
71069
+ const previousProvider = this.status.provider;
71070
+ if (this.processHandle) {
71071
+ await this.stopInternal();
71072
+ }
71073
+ try {
71074
+ await this.startInternal(target, config);
71075
+ } catch (error) {
71076
+ const stateError = toStateError("switch_failed", error);
71077
+ const redactedMessage = this.redactForProviderConfig(target, config, stateError.message);
71078
+ this.updateStatus({
71079
+ provider: target,
71080
+ state: "failed",
71081
+ pid: null,
71082
+ startedAt: null,
71083
+ stoppedAt: nowIso(),
71084
+ url: null,
71085
+ lastError: {
71086
+ ...stateError,
71087
+ message: redactedMessage
71088
+ }
71089
+ });
71090
+ this.emitLog("error", "manager", `Provider switch failed (${previousProvider ?? "none"} -> ${target}): ${redactedMessage}`);
71091
+ throw error;
71092
+ }
71093
+ });
71094
+ }
71095
+ redactForProviderConfig(provider, config, message) {
71096
+ try {
71097
+ const adapter = getTunnelProviderAdapter(provider);
71098
+ const command = adapter.buildCommand(config);
71099
+ return redactTunnelText(message, command.sensitiveValues);
71100
+ } catch {
71101
+ return message;
71102
+ }
71103
+ }
71104
+ async runExclusive(operation) {
71105
+ const next = this.operationChain.then(operation);
71106
+ this.operationChain = next.catch(() => void 0);
71107
+ return next;
71108
+ }
71109
+ async startInternal(provider, config) {
71110
+ const adapter = getTunnelProviderAdapter(provider);
71111
+ if (config.provider !== provider) {
71112
+ throw new Error(`invalid_config:provider mismatch (${config.provider} vs ${provider})`);
71113
+ }
71114
+ try {
71115
+ adapter.validateConfig(config);
71116
+ } catch (error) {
71117
+ const stateError = toStateError("invalid_config", error);
71118
+ this.updateStatus({
71119
+ provider,
71120
+ state: "failed",
71121
+ pid: null,
71122
+ startedAt: null,
71123
+ stoppedAt: nowIso(),
71124
+ url: null,
71125
+ lastError: stateError
71126
+ });
71127
+ this.emitLog("error", "manager", `Configuration validation failed for ${provider}: ${stateError.message}`);
71128
+ throw error;
71129
+ }
71130
+ const command = adapter.buildCommand(config);
71131
+ this.updateStatus({
71132
+ provider,
71133
+ state: "starting",
71134
+ pid: null,
71135
+ startedAt: nowIso(),
71136
+ stoppedAt: null,
71137
+ url: null,
71138
+ lastError: null
71139
+ });
71140
+ this.emitLog("info", "manager", `Starting ${provider} tunnel: ${command.redactedPreview}`);
71141
+ const child = this.spawnImpl(command.command, command.args, {
71142
+ cwd: command.cwd,
71143
+ env: command.env,
71144
+ detached: process.platform !== "win32",
71145
+ shell: false,
71146
+ stdio: ["ignore", "pipe", "pipe"]
71147
+ });
71148
+ this.processHandle = {
71149
+ provider,
71150
+ child,
71151
+ command
71152
+ };
71153
+ this.expectedStop = false;
71154
+ this.updateStatus({ pid: child.pid ?? null });
71155
+ const stdoutBuffer = new LineBuffer();
71156
+ const stderrBuffer = new LineBuffer();
71157
+ const attachStream = (stream, source, buffer) => {
71158
+ stream?.on("data", (chunk) => {
71159
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
71160
+ for (const line of buffer.push(text)) {
71161
+ this.handleOutputLine(source, line);
71162
+ }
71163
+ });
71164
+ };
71165
+ attachStream(child.stdout, "stdout", stdoutBuffer);
71166
+ attachStream(child.stderr, "stderr", stderrBuffer);
71167
+ child.once("error", (error) => {
71168
+ const maskedMessage = maskSensitive(normalizeError(error).message, this.processHandle);
71169
+ this.emitLog("error", "manager", `Spawn failure for ${provider}: ${maskedMessage}`);
71170
+ this.handleUnexpectedExit("start_failed", `Spawn failure: ${maskedMessage}`);
71171
+ });
71172
+ child.once("close", (code, signal) => {
71173
+ for (const line of stdoutBuffer.flush()) {
71174
+ this.handleOutputLine("stdout", line);
71175
+ }
71176
+ for (const line of stderrBuffer.flush()) {
71177
+ this.handleOutputLine("stderr", line);
71178
+ }
71179
+ const reason = signal ? `signal ${signal}` : `exit code ${code ?? 0}`;
71180
+ if (this.expectedStop) {
71181
+ this.emitLog("info", "manager", `Tunnel process stopped (${reason})`);
71182
+ this.finalizeStoppedState();
71183
+ return;
71184
+ }
71185
+ this.emitLog("error", "manager", `Tunnel process exited unexpectedly (${reason})`);
71186
+ this.handleUnexpectedExit("process_exit", `Process exited unexpectedly (${reason})`);
71187
+ });
71188
+ this.readinessTimer = setTimeout(() => {
71189
+ if (this.status.state === "starting" && this.processHandle?.provider === provider) {
71190
+ this.emitLog("error", "manager", `Readiness timed out after ${command.readinessTimeoutMs}ms`);
71191
+ this.handleUnexpectedExit("readiness_timeout", `Tunnel readiness timeout after ${command.readinessTimeoutMs}ms`);
71192
+ }
71193
+ }, command.readinessTimeoutMs);
71194
+ this.readinessTimer.unref?.();
71195
+ }
71196
+ async stopInternal() {
71197
+ if (!this.processHandle) {
71198
+ this.updateStatus({
71199
+ provider: null,
71200
+ state: "stopped",
71201
+ pid: null,
71202
+ stoppedAt: nowIso(),
71203
+ url: null
71204
+ });
71205
+ return;
71206
+ }
71207
+ if (this.activeStopPromise) {
71208
+ await this.activeStopPromise;
71209
+ return;
71210
+ }
71211
+ const currentHandle = this.processHandle;
71212
+ const stopTimeoutMs = currentHandle.command.stopTimeoutMs || this.defaultStopTimeoutMs;
71213
+ this.expectedStop = true;
71214
+ this.updateStatus({
71215
+ state: "stopping",
71216
+ provider: currentHandle.provider,
71217
+ pid: currentHandle.child.pid ?? null,
71218
+ lastError: null
71219
+ });
71220
+ this.emitLog("info", "manager", `Stopping ${currentHandle.provider} tunnel (pid=${currentHandle.child.pid ?? "n/a"})`);
71221
+ this.activeStopPromise = new Promise((resolve16) => {
71222
+ const onClose = () => {
71223
+ currentHandle.child.removeListener("close", onClose);
71224
+ resolve16();
71225
+ };
71226
+ currentHandle.child.once("close", onClose);
71227
+ killManagedProcess(currentHandle.child, "SIGTERM");
71228
+ this.stopTimer = setTimeout(() => {
71229
+ if (this.processHandle === currentHandle) {
71230
+ this.emitLog("warn", "manager", `Graceful stop timed out after ${stopTimeoutMs}ms, sending SIGKILL`);
71231
+ killManagedProcess(currentHandle.child, "SIGKILL");
71232
+ }
71233
+ }, stopTimeoutMs);
71234
+ this.stopTimer.unref?.();
71235
+ }).finally(() => {
71236
+ this.activeStopPromise = null;
71237
+ if (this.stopTimer) {
71238
+ clearTimeout(this.stopTimer);
71239
+ this.stopTimer = null;
71240
+ }
71241
+ });
71242
+ await this.activeStopPromise;
71243
+ }
71244
+ handleOutputLine(source, rawLine) {
71245
+ const processHandle = this.processHandle;
71246
+ const maskedLine = maskSensitive(rawLine, processHandle);
71247
+ this.emitLog("info", source, maskedLine);
71248
+ if (!processHandle || this.status.state !== "starting") {
71249
+ return;
71250
+ }
71251
+ const adapter = getTunnelProviderAdapter(processHandle.provider);
71252
+ const readiness = adapter.parseReadiness(maskedLine, source);
71253
+ if (!readiness?.ready) {
71254
+ return;
71255
+ }
71256
+ this.clearReadinessTimer();
71257
+ this.updateStatus({
71258
+ state: "running",
71259
+ provider: processHandle.provider,
71260
+ pid: processHandle.child.pid ?? null,
71261
+ url: readiness.url ?? this.status.url,
71262
+ startedAt: this.status.startedAt ?? nowIso(),
71263
+ lastError: null
71264
+ });
71265
+ this.emitLog("info", "manager", `${processHandle.provider} tunnel is running`);
71266
+ }
71267
+ handleUnexpectedExit(code, message) {
71268
+ this.clearReadinessTimer();
71269
+ if (this.stopTimer) {
71270
+ clearTimeout(this.stopTimer);
71271
+ this.stopTimer = null;
71272
+ }
71273
+ this.expectedStop = false;
71274
+ const provider = this.processHandle?.provider ?? this.status.provider;
71275
+ this.processHandle = null;
71276
+ this.updateStatus({
71277
+ provider,
71278
+ state: "failed",
71279
+ pid: null,
71280
+ stoppedAt: nowIso(),
71281
+ url: null,
71282
+ lastError: {
71283
+ code,
71284
+ message,
71285
+ at: nowIso()
71286
+ }
71287
+ });
71288
+ }
71289
+ finalizeStoppedState() {
71290
+ this.clearReadinessTimer();
71291
+ if (this.stopTimer) {
71292
+ clearTimeout(this.stopTimer);
71293
+ this.stopTimer = null;
71294
+ }
71295
+ this.expectedStop = false;
71296
+ this.processHandle = null;
71297
+ this.updateStatus({
71298
+ provider: null,
71299
+ state: "stopped",
71300
+ pid: null,
71301
+ stoppedAt: nowIso(),
71302
+ url: null,
71303
+ lastError: null
71304
+ });
71305
+ }
71306
+ clearReadinessTimer() {
71307
+ if (this.readinessTimer) {
71308
+ clearTimeout(this.readinessTimer);
71309
+ this.readinessTimer = null;
71310
+ }
71311
+ }
71312
+ updateStatus(patch) {
71313
+ this.status = {
71314
+ ...this.status,
71315
+ ...patch,
71316
+ lastError: patch.lastError === void 0 ? this.status.lastError : patch.lastError
71317
+ };
71318
+ const snapshot = this.getStatus();
71319
+ for (const listener of this.statusListeners) {
71320
+ listener(snapshot);
71321
+ }
71322
+ this.emit("status", snapshot);
71323
+ }
71324
+ emitLog(level, source, message) {
71325
+ const safeMessage = maskSensitive(message, this.processHandle);
71326
+ const entry = {
71327
+ timestamp: nowIso(),
71328
+ provider: this.status.provider,
71329
+ level,
71330
+ source,
71331
+ message: safeMessage
71332
+ };
71333
+ this.logs.push(entry);
71334
+ if (this.logs.length > this.maxLogEntries) {
71335
+ this.logs.splice(0, this.logs.length - this.maxLogEntries);
71336
+ }
71337
+ const logMethod = level === "error" ? "error" : level === "warn" ? "warn" : "log";
71338
+ remoteTunnelLog[logMethod](safeMessage);
71339
+ for (const listener of this.logListeners) {
71340
+ listener(entry);
71341
+ }
71342
+ this.emit("log", entry);
71343
+ }
71344
+ };
71345
+ }
71346
+ });
71347
+
70583
71348
  // ../engine/src/project-engine.ts
70584
- var ProjectEngine;
71349
+ import { execFile as execFile3 } from "node:child_process";
71350
+ import { promisify as promisify10 } from "node:util";
71351
+ var execFileAsync, isRemoteActive, ProjectEngine;
70585
71352
  var init_project_engine = __esm({
70586
71353
  "../engine/src/project-engine.ts"() {
70587
71354
  "use strict";
@@ -70593,6 +71360,9 @@ var init_project_engine = __esm({
70593
71360
  init_merger();
70594
71361
  init_concurrency();
70595
71362
  init_logger2();
71363
+ init_tunnel_process_manager();
71364
+ execFileAsync = promisify10(execFile3);
71365
+ isRemoteActive = (ra) => ra?.activeProvider != null && (ra.providers[ra.activeProvider]?.enabled ?? false);
70596
71366
  ProjectEngine = class _ProjectEngine {
70597
71367
  constructor(config, centralCore, options = {}) {
70598
71368
  this.config = config;
@@ -70606,6 +71376,13 @@ var init_project_engine = __esm({
70606
71376
  notifier;
70607
71377
  cronRunner;
70608
71378
  automationStore;
71379
+ remoteTunnelManager;
71380
+ remoteTunnelRestoreDiagnostics = {
71381
+ outcome: "skipped",
71382
+ reason: "not_attempted",
71383
+ at: (/* @__PURE__ */ new Date()).toISOString(),
71384
+ provider: null
71385
+ };
70609
71386
  // ── Auto-merge state ──
70610
71387
  mergeQueue = [];
70611
71388
  mergeActive = /* @__PURE__ */ new Set();
@@ -70633,6 +71410,14 @@ var init_project_engine = __esm({
70633
71410
  await this.runtime.start();
70634
71411
  const store = this.runtime.getTaskStore();
70635
71412
  const cwd = this.config.workingDirectory;
71413
+ this.remoteTunnelManager = new TunnelProcessManager();
71414
+ try {
71415
+ await this.restoreRemoteTunnelIfNeeded(store);
71416
+ } catch (error) {
71417
+ const message = error instanceof Error ? error.message : String(error);
71418
+ this.setRestoreDiagnostics("failed", "restore_start_failed", null, message);
71419
+ runtimeLog.warn(`Remote tunnel restore evaluation failed (continuing startup): ${message}`);
71420
+ }
70636
71421
  this.prMonitor = new PrMonitor();
70637
71422
  this.prCommentHandler = new PrCommentHandler(store);
70638
71423
  this.prMonitor.onNewComments(
@@ -70732,6 +71517,30 @@ var init_project_engine = __esm({
70732
71517
  }
70733
71518
  this.notifier?.stop();
70734
71519
  this.cronRunner?.stop();
71520
+ const tunnelManager = this.remoteTunnelManager;
71521
+ this.remoteTunnelManager = void 0;
71522
+ if (tunnelManager) {
71523
+ let shutdownStore = null;
71524
+ try {
71525
+ shutdownStore = this.runtime.getTaskStore();
71526
+ } catch {
71527
+ shutdownStore = null;
71528
+ }
71529
+ if (shutdownStore) {
71530
+ try {
71531
+ await this.persistShutdownRemoteLifecycle(shutdownStore, tunnelManager.getStatus());
71532
+ } catch (error) {
71533
+ const message = error instanceof Error ? error.message : String(error);
71534
+ runtimeLog.warn(`Failed to persist remote lifecycle shutdown markers: ${message}`);
71535
+ }
71536
+ }
71537
+ try {
71538
+ await tunnelManager.stop();
71539
+ } catch (error) {
71540
+ const message = error instanceof Error ? error.message : String(error);
71541
+ runtimeLog.warn(`Tunnel process manager stop failed (continuing shutdown): ${message}`);
71542
+ }
71543
+ }
70735
71544
  await this.runtime.stop();
70736
71545
  runtimeLog.log(`ProjectEngine stopped for ${this.config.projectId}`);
70737
71546
  }
@@ -70776,6 +71585,71 @@ var init_project_engine = __esm({
70776
71585
  getRoutineStore() {
70777
71586
  return this.runtime.getRoutineStore();
70778
71587
  }
71588
+ /** Get the remote tunnel manager (available after start()). */
71589
+ getRemoteTunnelManager() {
71590
+ return this.remoteTunnelManager;
71591
+ }
71592
+ getRemoteTunnelRestoreDiagnostics() {
71593
+ return { ...this.remoteTunnelRestoreDiagnostics };
71594
+ }
71595
+ async startRemoteTunnel() {
71596
+ const manager = this.remoteTunnelManager;
71597
+ if (!manager) {
71598
+ throw new Error("remote_tunnel_unavailable:remote tunnel manager is not initialized");
71599
+ }
71600
+ const store = this.runtime.getTaskStore();
71601
+ const settings = await store.getSettings();
71602
+ const remoteAccess = settings.remoteAccess;
71603
+ if (!remoteAccess || !isRemoteActive(remoteAccess)) {
71604
+ throw new Error("invalid_config:no remote access provider enabled");
71605
+ }
71606
+ const provider = remoteAccess.activeProvider;
71607
+ if (!provider) {
71608
+ throw new Error("invalid_config:no active remote provider configured");
71609
+ }
71610
+ const lifecycle = await this.evaluateRemoteLifecycle(settings, provider);
71611
+ if (!lifecycle.config) {
71612
+ throw new Error(`${lifecycle.reason ?? "invalid_config"}:${lifecycle.message ?? "remote provider prerequisites are not met"}`);
71613
+ }
71614
+ const current = manager.getStatus();
71615
+ if (current.state === "running" && current.provider === provider) {
71616
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71617
+ ...remoteAccess.lifecycle,
71618
+ wasRunningOnShutdown: true,
71619
+ lastRunningProvider: provider
71620
+ });
71621
+ return manager.getStatus();
71622
+ }
71623
+ if (current.state === "running" && current.provider && current.provider !== provider) {
71624
+ await manager.switchProvider(provider, lifecycle.config);
71625
+ } else {
71626
+ await manager.start(provider, lifecycle.config);
71627
+ }
71628
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71629
+ ...remoteAccess.lifecycle,
71630
+ wasRunningOnShutdown: true,
71631
+ lastRunningProvider: provider
71632
+ });
71633
+ return manager.getStatus();
71634
+ }
71635
+ async stopRemoteTunnel() {
71636
+ const manager = this.remoteTunnelManager;
71637
+ if (!manager) {
71638
+ throw new Error("remote_tunnel_unavailable:remote tunnel manager is not initialized");
71639
+ }
71640
+ await manager.stop();
71641
+ const store = this.runtime.getTaskStore();
71642
+ const settings = await store.getSettings();
71643
+ const remoteAccess = settings.remoteAccess;
71644
+ if (remoteAccess) {
71645
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71646
+ ...remoteAccess.lifecycle,
71647
+ wasRunningOnShutdown: false,
71648
+ lastRunningProvider: null
71649
+ });
71650
+ }
71651
+ return manager.getStatus();
71652
+ }
70779
71653
  /** Get the RoutineRunner (if initialized). */
70780
71654
  getRoutineRunner() {
70781
71655
  return this.runtime.getRoutineRunner();
@@ -70810,6 +71684,168 @@ var init_project_engine = __esm({
70810
71684
  this.internalEnqueueMerge(taskId);
70811
71685
  });
70812
71686
  }
71687
+ setRestoreDiagnostics(outcome, reason, provider, message) {
71688
+ this.remoteTunnelRestoreDiagnostics = {
71689
+ outcome,
71690
+ reason,
71691
+ provider,
71692
+ message,
71693
+ at: (/* @__PURE__ */ new Date()).toISOString()
71694
+ };
71695
+ }
71696
+ async restoreRemoteTunnelIfNeeded(store) {
71697
+ const manager = this.remoteTunnelManager;
71698
+ if (!manager) {
71699
+ return;
71700
+ }
71701
+ const settings = await store.getSettings();
71702
+ const remoteAccess = settings.remoteAccess;
71703
+ if (!remoteAccess || !isRemoteActive(remoteAccess)) {
71704
+ this.setRestoreDiagnostics("skipped", "remote_access_disabled", null);
71705
+ return;
71706
+ }
71707
+ const lifecycle = remoteAccess.lifecycle;
71708
+ if (!lifecycle.rememberLastRunning) {
71709
+ this.setRestoreDiagnostics("skipped", "remember_last_running_disabled", null);
71710
+ if (lifecycle.wasRunningOnShutdown || lifecycle.lastRunningProvider) {
71711
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71712
+ ...lifecycle,
71713
+ wasRunningOnShutdown: false,
71714
+ lastRunningProvider: null
71715
+ });
71716
+ }
71717
+ return;
71718
+ }
71719
+ if (!lifecycle.wasRunningOnShutdown) {
71720
+ this.setRestoreDiagnostics("skipped", "no_prior_running_marker", null);
71721
+ return;
71722
+ }
71723
+ const provider = lifecycle.lastRunningProvider ?? remoteAccess.activeProvider;
71724
+ if (!provider) {
71725
+ this.setRestoreDiagnostics("skipped", "provider_missing", null);
71726
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71727
+ ...lifecycle,
71728
+ wasRunningOnShutdown: false,
71729
+ lastRunningProvider: null
71730
+ });
71731
+ return;
71732
+ }
71733
+ const evaluation = await this.evaluateRemoteLifecycle(settings, provider);
71734
+ if (!evaluation.config) {
71735
+ this.setRestoreDiagnostics("skipped", evaluation.reason ?? "provider_not_configured", provider, evaluation.message);
71736
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71737
+ ...lifecycle,
71738
+ wasRunningOnShutdown: false,
71739
+ lastRunningProvider: null
71740
+ });
71741
+ return;
71742
+ }
71743
+ try {
71744
+ await manager.start(provider, evaluation.config);
71745
+ this.setRestoreDiagnostics("applied", "restore_started", provider);
71746
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71747
+ ...lifecycle,
71748
+ wasRunningOnShutdown: true,
71749
+ lastRunningProvider: provider
71750
+ }, provider);
71751
+ } catch (error) {
71752
+ const message = error instanceof Error ? error.message : String(error);
71753
+ this.setRestoreDiagnostics("failed", "restore_start_failed", provider, message);
71754
+ runtimeLog.warn(`Remote tunnel restore failed for ${provider}: ${message}`);
71755
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71756
+ ...lifecycle,
71757
+ wasRunningOnShutdown: false,
71758
+ lastRunningProvider: null
71759
+ });
71760
+ }
71761
+ }
71762
+ async persistShutdownRemoteLifecycle(store, status) {
71763
+ const settings = await store.getSettings();
71764
+ const remoteAccess = settings.remoteAccess;
71765
+ if (!remoteAccess) {
71766
+ return;
71767
+ }
71768
+ const shouldRememberRunning = (status.state === "running" || status.state === "starting" || status.state === "stopping") && status.provider !== null;
71769
+ await this.writeRemoteLifecycleState(store, remoteAccess, {
71770
+ ...remoteAccess.lifecycle,
71771
+ wasRunningOnShutdown: shouldRememberRunning,
71772
+ lastRunningProvider: shouldRememberRunning ? status.provider : null
71773
+ }, shouldRememberRunning ? status.provider : remoteAccess.activeProvider);
71774
+ }
71775
+ async writeRemoteLifecycleState(store, remoteAccess, lifecycle, activeProviderOverride) {
71776
+ await store.updateSettings({
71777
+ remoteAccess: {
71778
+ ...remoteAccess,
71779
+ activeProvider: activeProviderOverride === void 0 ? remoteAccess.activeProvider : activeProviderOverride,
71780
+ lifecycle
71781
+ }
71782
+ });
71783
+ }
71784
+ async evaluateRemoteLifecycle(settings, provider) {
71785
+ const remoteAccess = settings.remoteAccess;
71786
+ if (!remoteAccess || !isRemoteActive(remoteAccess)) {
71787
+ return { provider, reason: "remote_access_disabled", message: "No remote provider is enabled" };
71788
+ }
71789
+ if (provider === "tailscale") {
71790
+ const tailscale = remoteAccess.providers.tailscale;
71791
+ if (!tailscale.enabled) {
71792
+ return { provider, reason: "provider_not_enabled", message: "Tailscale provider is disabled" };
71793
+ }
71794
+ if (!tailscale.hostname?.trim() || !Number.isFinite(tailscale.targetPort) || tailscale.targetPort <= 0) {
71795
+ return { provider, reason: "provider_not_configured", message: "Tailscale hostname and target port must be configured" };
71796
+ }
71797
+ const executable2 = await this.checkExecutableAvailable("tailscale");
71798
+ if (!executable2.available) {
71799
+ return { provider, reason: "runtime_prerequisite_missing", message: executable2.message };
71800
+ }
71801
+ return {
71802
+ provider,
71803
+ config: {
71804
+ provider: "tailscale",
71805
+ executablePath: "tailscale",
71806
+ args: ["funnel", String(Math.floor(tailscale.targetPort))]
71807
+ }
71808
+ };
71809
+ }
71810
+ const cloudflare = remoteAccess.providers.cloudflare;
71811
+ if (!cloudflare.enabled) {
71812
+ return { provider, reason: "provider_not_enabled", message: "Cloudflare provider is disabled" };
71813
+ }
71814
+ if (!cloudflare.tunnelName?.trim() || !cloudflare.ingressUrl?.trim()) {
71815
+ return { provider, reason: "provider_not_configured", message: "Cloudflare tunnel name and ingress URL must be configured" };
71816
+ }
71817
+ if (!cloudflare.tunnelToken?.trim()) {
71818
+ return { provider, reason: "provider_not_configured", message: "Cloudflare tunnel token is required" };
71819
+ }
71820
+ const executable = await this.checkExecutableAvailable("cloudflared");
71821
+ if (!executable.available) {
71822
+ return { provider, reason: "runtime_prerequisite_missing", message: executable.message };
71823
+ }
71824
+ return {
71825
+ provider,
71826
+ config: {
71827
+ provider: "cloudflare",
71828
+ executablePath: "cloudflared",
71829
+ args: ["tunnel", "--no-autoupdate", "run", cloudflare.tunnelName.trim()],
71830
+ tokenEnvVar: "TUNNEL_TOKEN",
71831
+ env: {
71832
+ TUNNEL_TOKEN: cloudflare.tunnelToken
71833
+ }
71834
+ }
71835
+ };
71836
+ }
71837
+ async checkExecutableAvailable(command) {
71838
+ const checker = process.platform === "win32" ? "where" : "which";
71839
+ try {
71840
+ await execFileAsync(checker, [command]);
71841
+ return { available: true };
71842
+ } catch {
71843
+ return {
71844
+ available: false,
71845
+ message: `${command} is not available on PATH`
71846
+ };
71847
+ }
71848
+ }
70813
71849
  // ── Merge eligibility helpers (richer logic from dashboard.ts) ──
70814
71850
  /**
70815
71851
  * True when a retry-exhausted task in "in-review" has a verification buffer
@@ -71422,6 +72458,15 @@ var init_peer_exchange_service = __esm({
71422
72458
  }
71423
72459
  });
71424
72460
 
72461
+ // ../engine/src/remote-access/index.ts
72462
+ var init_remote_access = __esm({
72463
+ "../engine/src/remote-access/index.ts"() {
72464
+ "use strict";
72465
+ init_provider_adapters();
72466
+ init_tunnel_process_manager();
72467
+ }
72468
+ });
72469
+
71425
72470
  // ../engine/src/index.ts
71426
72471
  var init_src2 = __esm({
71427
72472
  "../engine/src/index.ts"() {
@@ -71462,6 +72507,7 @@ var init_src2 = __esm({
71462
72507
  init_project_engine_manager();
71463
72508
  init_node_health_monitor();
71464
72509
  init_peer_exchange_service();
72510
+ init_remote_access();
71465
72511
  init_remote_node_client();
71466
72512
  init_remote_node_runtime();
71467
72513
  init_step_session_executor();
@@ -71615,7 +72661,7 @@ var init_ai_session_diagnostics = __esm({
71615
72661
 
71616
72662
  // ../dashboard/src/planning.ts
71617
72663
  import { randomUUID as randomUUID10 } from "node:crypto";
71618
- import { EventEmitter as EventEmitter18 } from "node:events";
72664
+ import { EventEmitter as EventEmitter19 } from "node:events";
71619
72665
  async function initEngine2() {
71620
72666
  try {
71621
72667
  const engineModule = "@fusion/engine";
@@ -72449,7 +73495,7 @@ For completion:
72449
73495
  process.on("beforeExit", () => {
72450
73496
  clearInterval(cleanupInterval2);
72451
73497
  });
72452
- PlanningStreamManager = class extends EventEmitter18 {
73498
+ PlanningStreamManager = class extends EventEmitter19 {
72453
73499
  constructor(bufferSize = 100) {
72454
73500
  super();
72455
73501
  this.bufferSize = bufferSize;
@@ -72561,10 +73607,756 @@ For completion:
72561
73607
  }
72562
73608
  });
72563
73609
 
72564
- // ../dashboard/src/claude-cli-probe.ts
72565
- var init_claude_cli_probe = __esm({
72566
- "../dashboard/src/claude-cli-probe.ts"() {
73610
+ // ../dashboard/src/terminal.ts
73611
+ import { spawn as spawn3 } from "node:child_process";
73612
+ import { randomUUID as randomUUID11 } from "node:crypto";
73613
+ import { EventEmitter as EventEmitter20 } from "node:events";
73614
+ function extractBaseCommand(command) {
73615
+ let trimmed = command.trim();
73616
+ while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(trimmed)) {
73617
+ const match2 = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*=(?:[^\s]*|"[^"]*"|'[^']*'))\s*/);
73618
+ if (match2) {
73619
+ trimmed = trimmed.slice(match2[0].length).trim();
73620
+ } else {
73621
+ break;
73622
+ }
73623
+ }
73624
+ if (trimmed.startsWith("sudo ")) {
73625
+ trimmed = trimmed.slice(5).trim();
73626
+ }
73627
+ const match = trimmed.match(/^([A-Za-z0-9._-]+)/);
73628
+ return match ? match[1].toLowerCase() : null;
73629
+ }
73630
+ function validateCommand(command) {
73631
+ for (const pattern of SUBSTITUTION_PATTERNS) {
73632
+ if (pattern.test(command)) {
73633
+ return { valid: false, error: "Command substitution is not allowed" };
73634
+ }
73635
+ }
73636
+ if (/[\0\r\n]/.test(command)) {
73637
+ return { valid: false, error: "Command contains invalid control characters" };
73638
+ }
73639
+ for (const pattern of BLOCKED_PATTERNS) {
73640
+ if (pattern.test(command)) {
73641
+ return { valid: false, error: "Command contains dangerous patterns and is not allowed" };
73642
+ }
73643
+ }
73644
+ const segments = command.split(CHAIN_OPERATORS);
73645
+ for (const raw of segments) {
73646
+ const segment = raw.trim();
73647
+ if (segment.length === 0) continue;
73648
+ const baseCommand = extractBaseCommand(segment);
73649
+ if (!baseCommand) {
73650
+ return { valid: false, error: "Could not parse command" };
73651
+ }
73652
+ if (!ALLOWED_COMMANDS.has(baseCommand)) {
73653
+ return {
73654
+ valid: false,
73655
+ error: `Command '${baseCommand}' is not in the allowed command list. Allowed commands: ${Array.from(ALLOWED_COMMANDS).sort().join(", ")}`
73656
+ };
73657
+ }
73658
+ }
73659
+ return { valid: true };
73660
+ }
73661
+ var ALLOWED_COMMANDS, BLOCKED_PATTERNS, SUBSTITUTION_PATTERNS, CHAIN_OPERATORS, TerminalSessionManager, terminalSessionManager;
73662
+ var init_terminal = __esm({
73663
+ "../dashboard/src/terminal.ts"() {
73664
+ "use strict";
73665
+ ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
73666
+ // Version control
73667
+ "git",
73668
+ // Package managers
73669
+ "npm",
73670
+ "pnpm",
73671
+ "yarn",
73672
+ "bun",
73673
+ // File operations
73674
+ "ls",
73675
+ "cat",
73676
+ "echo",
73677
+ "pwd",
73678
+ "cd",
73679
+ "mkdir",
73680
+ "touch",
73681
+ "cp",
73682
+ "mv",
73683
+ "rm",
73684
+ "head",
73685
+ "tail",
73686
+ "less",
73687
+ "more",
73688
+ "find",
73689
+ "grep",
73690
+ // System info
73691
+ "clear",
73692
+ "which",
73693
+ "whoami",
73694
+ "uname",
73695
+ "date",
73696
+ // Node/JS
73697
+ "node",
73698
+ "npx",
73699
+ "tsx",
73700
+ // Python
73701
+ "python",
73702
+ "python3",
73703
+ "pip",
73704
+ "pip3",
73705
+ // Network
73706
+ "curl",
73707
+ "wget",
73708
+ // Build tools
73709
+ "make",
73710
+ "cmake",
73711
+ // Process management
73712
+ "ps",
73713
+ "top",
73714
+ "htop",
73715
+ "kill",
73716
+ "pkill",
73717
+ // Shell builtins that are safe
73718
+ "source",
73719
+ ".",
73720
+ "export",
73721
+ "env",
73722
+ "printenv",
73723
+ "alias",
73724
+ // Editors (for viewing)
73725
+ "code",
73726
+ // VS Code CLI
73727
+ "vim",
73728
+ "vi",
73729
+ "nano"
73730
+ ]);
73731
+ BLOCKED_PATTERNS = [
73732
+ // System destruction
73733
+ /rm\s+(-[rf]+\s+)?\/\s*$/i,
73734
+ // rm -rf / or rm /
73735
+ /rm\s+(-[rf]+\s+)?\/\*/i,
73736
+ // rm -rf /*
73737
+ />\s*\/dev\/sda/i,
73738
+ // Direct disk write
73739
+ /:\(\)\s*\{\s*:\s*\|:\s*&\s*\};\s*:/i,
73740
+ // Fork bomb
73741
+ /mkfs\.\w+\s+/i,
73742
+ // Filesystem formatting
73743
+ /dd\s+if=/i,
73744
+ // dd with input file
73745
+ /\.\s*\/dev\/null/i
73746
+ // Sourcing /dev/null tricks
73747
+ ];
73748
+ SUBSTITUTION_PATTERNS = [
73749
+ /\$\(/,
73750
+ // $(...) — command substitution
73751
+ /`/,
73752
+ // `...` — backtick command substitution
73753
+ /<\(/,
73754
+ // <(...) — process substitution (bash)
73755
+ />\(/
73756
+ // >(...) — process substitution (bash)
73757
+ ];
73758
+ CHAIN_OPERATORS = /&&|\|\||;|(?<!\|)\|(?!\|)/;
73759
+ TerminalSessionManager = class extends EventEmitter20 {
73760
+ sessions = /* @__PURE__ */ new Map();
73761
+ defaultTimeout = 3e4;
73762
+ // 30 seconds
73763
+ /**
73764
+ * Creates a new terminal session and spawns the command.
73765
+ * Returns the session ID for tracking.
73766
+ */
73767
+ createSession(command, cwd) {
73768
+ const validation = validateCommand(command);
73769
+ if (!validation.valid) {
73770
+ return { sessionId: "", error: validation.error };
73771
+ }
73772
+ const sessionId = randomUUID11();
73773
+ const childProcess = spawn3(command, [], {
73774
+ cwd,
73775
+ shell: true,
73776
+ stdio: ["pipe", "pipe", "pipe"],
73777
+ env: { ...process.env, FORCE_COLOR: "1", TERM: "xterm-256color" }
73778
+ });
73779
+ const session = {
73780
+ id: sessionId,
73781
+ command,
73782
+ process: childProcess,
73783
+ startTime: /* @__PURE__ */ new Date(),
73784
+ output: [],
73785
+ exitCode: null,
73786
+ killed: false
73787
+ };
73788
+ this.sessions.set(sessionId, session);
73789
+ childProcess.stdout?.on("data", (data) => {
73790
+ const chunk = data.toString("utf-8");
73791
+ session.output.push(chunk);
73792
+ this.emit("output", { sessionId, type: "stdout", data: chunk });
73793
+ });
73794
+ childProcess.stderr?.on("data", (data) => {
73795
+ const chunk = data.toString("utf-8");
73796
+ session.output.push(chunk);
73797
+ this.emit("output", { sessionId, type: "stderr", data: chunk });
73798
+ });
73799
+ childProcess.on("exit", (code) => {
73800
+ session.exitCode = code ?? 0;
73801
+ this.emit("output", {
73802
+ sessionId,
73803
+ type: "exit",
73804
+ data: `Process exited with code ${code ?? 0}`,
73805
+ exitCode: code ?? 0
73806
+ });
73807
+ setTimeout(() => this.cleanupSession(sessionId), 5e3);
73808
+ });
73809
+ childProcess.on("error", (err) => {
73810
+ const errorMsg = err.message;
73811
+ session.output.push(errorMsg);
73812
+ session.exitCode = 127;
73813
+ this.emit("output", { sessionId, type: "stderr", data: errorMsg });
73814
+ this.emit("output", {
73815
+ sessionId,
73816
+ type: "exit",
73817
+ data: `Process failed: ${errorMsg}`,
73818
+ exitCode: 127
73819
+ });
73820
+ setTimeout(() => this.cleanupSession(sessionId), 5e3);
73821
+ });
73822
+ const timeout = setTimeout(() => {
73823
+ if (!session.exitCode && !session.killed) {
73824
+ this.killSession(sessionId, "SIGTERM");
73825
+ this.emit("output", {
73826
+ sessionId,
73827
+ type: "stderr",
73828
+ data: "\n[Command timed out after 30 seconds]\n"
73829
+ });
73830
+ }
73831
+ }, this.defaultTimeout);
73832
+ childProcess.on("exit", () => clearTimeout(timeout));
73833
+ return { sessionId };
73834
+ }
73835
+ /**
73836
+ * Gets a session by ID.
73837
+ */
73838
+ getSession(sessionId) {
73839
+ return this.sessions.get(sessionId);
73840
+ }
73841
+ /**
73842
+ * Kills a running session's process.
73843
+ * Returns true if killed, false if not found or already exited.
73844
+ */
73845
+ killSession(sessionId, signal = "SIGTERM") {
73846
+ const session = this.sessions.get(sessionId);
73847
+ if (!session || session.exitCode !== null || session.killed) {
73848
+ return false;
73849
+ }
73850
+ session.killed = true;
73851
+ try {
73852
+ if (session.process.pid) {
73853
+ process.kill(-session.process.pid, signal);
73854
+ }
73855
+ } catch {
73856
+ session.process.kill(signal);
73857
+ }
73858
+ return true;
73859
+ }
73860
+ /**
73861
+ * Cleans up a session from memory.
73862
+ */
73863
+ cleanupSession(sessionId) {
73864
+ const session = this.sessions.get(sessionId);
73865
+ if (!session) return false;
73866
+ if (session.exitCode === null && !session.killed) {
73867
+ this.killSession(sessionId, "SIGKILL");
73868
+ }
73869
+ this.sessions.delete(sessionId);
73870
+ return true;
73871
+ }
73872
+ /**
73873
+ * Lists all active sessions.
73874
+ */
73875
+ listSessions() {
73876
+ return Array.from(this.sessions.values()).map((s) => ({
73877
+ id: s.id,
73878
+ command: s.command,
73879
+ running: s.exitCode === null && !s.killed,
73880
+ startTime: s.startTime
73881
+ }));
73882
+ }
73883
+ /**
73884
+ * Cleans up all sessions (useful for shutdown).
73885
+ */
73886
+ cleanupAll() {
73887
+ for (const [sessionId] of this.sessions) {
73888
+ this.cleanupSession(sessionId);
73889
+ }
73890
+ }
73891
+ };
73892
+ terminalSessionManager = new TerminalSessionManager();
73893
+ }
73894
+ });
73895
+
73896
+ // ../dashboard/src/terminal-service.ts
73897
+ import { createRequire as createRequire2 } from "node:module";
73898
+ var isBunBinary, require2;
73899
+ var init_terminal_service = __esm({
73900
+ "../dashboard/src/terminal-service.ts"() {
73901
+ "use strict";
73902
+ isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
73903
+ require2 = createRequire2(import.meta.url);
73904
+ }
73905
+ });
73906
+
73907
+ // ../dashboard/src/github-webhooks.ts
73908
+ var init_github_webhooks = __esm({
73909
+ "../dashboard/src/github-webhooks.ts"() {
73910
+ "use strict";
73911
+ }
73912
+ });
73913
+
73914
+ // ../dashboard/src/ai-session-store.ts
73915
+ var MAX_THINKING_BYTES, SESSION_CLEANUP_DEFAULT_MAX_AGE_MS, SESSION_CLEANUP_INTERVAL_MS, diagnostics2;
73916
+ var init_ai_session_store = __esm({
73917
+ "../dashboard/src/ai-session-store.ts"() {
73918
+ "use strict";
73919
+ init_ai_session_diagnostics();
73920
+ MAX_THINKING_BYTES = 50 * 1024;
73921
+ SESSION_CLEANUP_DEFAULT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1e3;
73922
+ SESSION_CLEANUP_INTERVAL_MS = 6 * 60 * 60 * 1e3;
73923
+ diagnostics2 = createSessionDiagnostics("ai-session-store");
73924
+ }
73925
+ });
73926
+
73927
+ // ../dashboard/src/subtask-breakdown.ts
73928
+ import { EventEmitter as EventEmitter21 } from "node:events";
73929
+ function cleanupInMemorySubtaskSession(sessionId) {
73930
+ const session = sessions2.get(sessionId);
73931
+ if (!session) {
73932
+ return false;
73933
+ }
73934
+ try {
73935
+ session.agent?.session?.dispose?.();
73936
+ } catch {
73937
+ }
73938
+ subtaskStreamManager.cleanupSession(sessionId);
73939
+ sessions2.delete(sessionId);
73940
+ return true;
73941
+ }
73942
+ function cleanupExpiredSessions2() {
73943
+ const now = Date.now();
73944
+ for (const [id, session] of sessions2) {
73945
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS2) {
73946
+ cleanupInMemorySubtaskSession(id);
73947
+ }
73948
+ }
73949
+ }
73950
+ var diagnostics3, SESSION_TTL_MS2, CLEANUP_INTERVAL_MS3, sessions2, cleanupInterval3, SubtaskStreamManager, subtaskStreamManager;
73951
+ var init_subtask_breakdown = __esm({
73952
+ "../dashboard/src/subtask-breakdown.ts"() {
72567
73953
  "use strict";
73954
+ init_src();
73955
+ init_sse_buffer();
73956
+ init_ai_session_diagnostics();
73957
+ diagnostics3 = createSessionDiagnostics("subtask-breakdown");
73958
+ SESSION_TTL_MS2 = 7 * 24 * 60 * 60 * 1e3;
73959
+ CLEANUP_INTERVAL_MS3 = 5 * 60 * 1e3;
73960
+ sessions2 = /* @__PURE__ */ new Map();
73961
+ cleanupInterval3 = setInterval(cleanupExpiredSessions2, CLEANUP_INTERVAL_MS3);
73962
+ cleanupInterval3.unref?.();
73963
+ process.on("beforeExit", () => {
73964
+ clearInterval(cleanupInterval3);
73965
+ });
73966
+ SubtaskStreamManager = class extends EventEmitter21 {
73967
+ constructor(bufferSize = 100) {
73968
+ super();
73969
+ this.bufferSize = bufferSize;
73970
+ }
73971
+ sessions = /* @__PURE__ */ new Map();
73972
+ buffers = /* @__PURE__ */ new Map();
73973
+ subscribe(sessionId, callback) {
73974
+ if (!this.sessions.has(sessionId)) {
73975
+ this.sessions.set(sessionId, /* @__PURE__ */ new Set());
73976
+ }
73977
+ const callbacks = this.sessions.get(sessionId);
73978
+ callbacks.add(callback);
73979
+ return () => {
73980
+ callbacks.delete(callback);
73981
+ if (callbacks.size === 0) {
73982
+ this.sessions.delete(sessionId);
73983
+ }
73984
+ };
73985
+ }
73986
+ getBuffer(sessionId) {
73987
+ let buffer = this.buffers.get(sessionId);
73988
+ if (!buffer) {
73989
+ buffer = new SessionEventBuffer(this.bufferSize);
73990
+ this.buffers.set(sessionId, buffer);
73991
+ }
73992
+ return buffer;
73993
+ }
73994
+ broadcast(sessionId, event) {
73995
+ const serialized = JSON.stringify(event.data ?? {});
73996
+ const eventData = typeof serialized === "string" ? serialized : "{}";
73997
+ const eventId = this.getBuffer(sessionId).push(event.type, eventData);
73998
+ const callbacks = this.sessions.get(sessionId);
73999
+ if (!callbacks) return eventId;
74000
+ for (const callback of callbacks) {
74001
+ try {
74002
+ callback(event, eventId);
74003
+ } catch {
74004
+ }
74005
+ }
74006
+ return eventId;
74007
+ }
74008
+ getBufferedEvents(sessionId, sinceId) {
74009
+ const buffer = this.buffers.get(sessionId);
74010
+ if (!buffer) return [];
74011
+ return buffer.getEventsSince(sinceId);
74012
+ }
74013
+ cleanupSession(sessionId) {
74014
+ this.sessions.delete(sessionId);
74015
+ this.buffers.delete(sessionId);
74016
+ }
74017
+ reset() {
74018
+ this.sessions.clear();
74019
+ this.buffers.clear();
74020
+ this.removeAllListeners();
74021
+ }
74022
+ };
74023
+ subtaskStreamManager = new SubtaskStreamManager();
74024
+ }
74025
+ });
74026
+
74027
+ // ../dashboard/src/mission-interview.ts
74028
+ import { EventEmitter as EventEmitter22 } from "node:events";
74029
+ function cleanupInMemoryMissionSession(sessionId) {
74030
+ const session = sessions3.get(sessionId);
74031
+ if (!session) {
74032
+ return false;
74033
+ }
74034
+ if (session.agent) {
74035
+ try {
74036
+ session.agent.session.dispose?.();
74037
+ } catch {
74038
+ }
74039
+ session.agent = void 0;
74040
+ }
74041
+ missionInterviewStreamManager.cleanupSession(sessionId);
74042
+ sessions3.delete(sessionId);
74043
+ return true;
74044
+ }
74045
+ function cleanupExpiredSessions3() {
74046
+ const now = Date.now();
74047
+ for (const [id, session] of sessions3) {
74048
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS3) {
74049
+ cleanupInMemoryMissionSession(id);
74050
+ }
74051
+ }
74052
+ for (const [ip, entry] of rateLimits3) {
74053
+ if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS3) {
74054
+ rateLimits3.delete(ip);
74055
+ }
74056
+ }
74057
+ }
74058
+ var diagnostics4, SESSION_TTL_MS3, CLEANUP_INTERVAL_MS4, RATE_LIMIT_WINDOW_MS3, sessions3, rateLimits3, cleanupInterval4, MissionInterviewStreamManager, missionInterviewStreamManager;
74059
+ var init_mission_interview = __esm({
74060
+ "../dashboard/src/mission-interview.ts"() {
74061
+ "use strict";
74062
+ init_src();
74063
+ init_sse_buffer();
74064
+ init_ai_session_diagnostics();
74065
+ diagnostics4 = createSessionDiagnostics("mission-interview");
74066
+ SESSION_TTL_MS3 = 7 * 24 * 60 * 60 * 1e3;
74067
+ CLEANUP_INTERVAL_MS4 = 5 * 60 * 1e3;
74068
+ RATE_LIMIT_WINDOW_MS3 = 60 * 60 * 1e3;
74069
+ sessions3 = /* @__PURE__ */ new Map();
74070
+ rateLimits3 = /* @__PURE__ */ new Map();
74071
+ cleanupInterval4 = setInterval(cleanupExpiredSessions3, CLEANUP_INTERVAL_MS4);
74072
+ cleanupInterval4.unref?.();
74073
+ process.on("beforeExit", () => clearInterval(cleanupInterval4));
74074
+ MissionInterviewStreamManager = class extends EventEmitter22 {
74075
+ constructor(bufferSize = 100) {
74076
+ super();
74077
+ this.bufferSize = bufferSize;
74078
+ }
74079
+ sessions = /* @__PURE__ */ new Map();
74080
+ buffers = /* @__PURE__ */ new Map();
74081
+ subscribe(sessionId, callback) {
74082
+ if (!this.sessions.has(sessionId)) {
74083
+ this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74084
+ }
74085
+ const callbacks = this.sessions.get(sessionId);
74086
+ callbacks.add(callback);
74087
+ return () => {
74088
+ callbacks.delete(callback);
74089
+ if (callbacks.size === 0) {
74090
+ this.sessions.delete(sessionId);
74091
+ }
74092
+ };
74093
+ }
74094
+ getBuffer(sessionId) {
74095
+ let buffer = this.buffers.get(sessionId);
74096
+ if (!buffer) {
74097
+ buffer = new SessionEventBuffer(this.bufferSize);
74098
+ this.buffers.set(sessionId, buffer);
74099
+ }
74100
+ return buffer;
74101
+ }
74102
+ broadcast(sessionId, event) {
74103
+ const serialized = JSON.stringify(event.data ?? {});
74104
+ const eventData = typeof serialized === "string" ? serialized : "{}";
74105
+ const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74106
+ const callbacks = this.sessions.get(sessionId);
74107
+ if (!callbacks) return eventId;
74108
+ for (const callback of callbacks) {
74109
+ nonfatal(
74110
+ () => callback(event, eventId),
74111
+ diagnostics4,
74112
+ "Error broadcasting to client",
74113
+ { sessionId, operation: "broadcast" }
74114
+ );
74115
+ }
74116
+ return eventId;
74117
+ }
74118
+ getBufferedEvents(sessionId, sinceId) {
74119
+ const buffer = this.buffers.get(sessionId);
74120
+ if (!buffer) return [];
74121
+ return buffer.getEventsSince(sinceId);
74122
+ }
74123
+ hasSubscribers(sessionId) {
74124
+ const callbacks = this.sessions.get(sessionId);
74125
+ return callbacks !== void 0 && callbacks.size > 0;
74126
+ }
74127
+ cleanupSession(sessionId) {
74128
+ this.sessions.delete(sessionId);
74129
+ this.buffers.delete(sessionId);
74130
+ }
74131
+ reset() {
74132
+ this.sessions.clear();
74133
+ this.buffers.clear();
74134
+ this.removeAllListeners();
74135
+ }
74136
+ };
74137
+ missionInterviewStreamManager = new MissionInterviewStreamManager();
74138
+ }
74139
+ });
74140
+
74141
+ // ../dashboard/src/milestone-slice-interview.ts
74142
+ import { EventEmitter as EventEmitter23 } from "node:events";
74143
+ function cleanupInMemorySession2(sessionId) {
74144
+ const session = sessions4.get(sessionId);
74145
+ if (!session) {
74146
+ return false;
74147
+ }
74148
+ if (session.agent) {
74149
+ try {
74150
+ session.agent.session.dispose?.();
74151
+ } catch {
74152
+ }
74153
+ session.agent = void 0;
74154
+ }
74155
+ milestoneSliceInterviewStreamManager.cleanupSession(sessionId);
74156
+ sessions4.delete(sessionId);
74157
+ return true;
74158
+ }
74159
+ function cleanupExpiredSessions4() {
74160
+ const now = Date.now();
74161
+ for (const [id, session] of sessions4) {
74162
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS4) {
74163
+ cleanupInMemorySession2(id);
74164
+ }
74165
+ }
74166
+ for (const [ip, entry] of rateLimits4) {
74167
+ if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS4) {
74168
+ rateLimits4.delete(ip);
74169
+ }
74170
+ }
74171
+ }
74172
+ var diagnostics5, SESSION_TTL_MS4, CLEANUP_INTERVAL_MS5, RATE_LIMIT_WINDOW_MS4, sessions4, rateLimits4, cleanupInterval5, MilestoneSliceInterviewStreamManager, milestoneSliceInterviewStreamManager;
74173
+ var init_milestone_slice_interview = __esm({
74174
+ "../dashboard/src/milestone-slice-interview.ts"() {
74175
+ "use strict";
74176
+ init_sse_buffer();
74177
+ init_mission_interview();
74178
+ init_ai_session_diagnostics();
74179
+ init_mission_interview();
74180
+ diagnostics5 = createSessionDiagnostics("milestone-slice-interview");
74181
+ SESSION_TTL_MS4 = 7 * 24 * 60 * 60 * 1e3;
74182
+ CLEANUP_INTERVAL_MS5 = 5 * 60 * 1e3;
74183
+ RATE_LIMIT_WINDOW_MS4 = 60 * 60 * 1e3;
74184
+ sessions4 = /* @__PURE__ */ new Map();
74185
+ rateLimits4 = /* @__PURE__ */ new Map();
74186
+ cleanupInterval5 = setInterval(cleanupExpiredSessions4, CLEANUP_INTERVAL_MS5);
74187
+ cleanupInterval5.unref?.();
74188
+ process.on("beforeExit", () => clearInterval(cleanupInterval5));
74189
+ MilestoneSliceInterviewStreamManager = class extends EventEmitter23 {
74190
+ constructor(bufferSize = 100) {
74191
+ super();
74192
+ this.bufferSize = bufferSize;
74193
+ }
74194
+ sessions = /* @__PURE__ */ new Map();
74195
+ buffers = /* @__PURE__ */ new Map();
74196
+ subscribe(sessionId, callback) {
74197
+ if (!this.sessions.has(sessionId)) {
74198
+ this.sessions.set(sessionId, /* @__PURE__ */ new Set());
74199
+ }
74200
+ const callbacks = this.sessions.get(sessionId);
74201
+ callbacks.add(callback);
74202
+ return () => {
74203
+ callbacks.delete(callback);
74204
+ if (callbacks.size === 0) {
74205
+ this.sessions.delete(sessionId);
74206
+ }
74207
+ };
74208
+ }
74209
+ getBuffer(sessionId) {
74210
+ let buffer = this.buffers.get(sessionId);
74211
+ if (!buffer) {
74212
+ buffer = new SessionEventBuffer(this.bufferSize);
74213
+ this.buffers.set(sessionId, buffer);
74214
+ }
74215
+ return buffer;
74216
+ }
74217
+ broadcast(sessionId, event) {
74218
+ const serialized = JSON.stringify(event.data ?? {});
74219
+ const eventData = typeof serialized === "string" ? serialized : "{}";
74220
+ const eventId = this.getBuffer(sessionId).push(event.type, eventData);
74221
+ const callbacks = this.sessions.get(sessionId);
74222
+ if (!callbacks) return eventId;
74223
+ for (const callback of callbacks) {
74224
+ nonfatal(
74225
+ () => callback(event, eventId),
74226
+ diagnostics5,
74227
+ "Error broadcasting to client",
74228
+ { sessionId, operation: "broadcast" }
74229
+ );
74230
+ }
74231
+ return eventId;
74232
+ }
74233
+ getBufferedEvents(sessionId, sinceId) {
74234
+ const buffer = this.buffers.get(sessionId);
74235
+ if (!buffer) return [];
74236
+ return buffer.getEventsSince(sinceId);
74237
+ }
74238
+ hasSubscribers(sessionId) {
74239
+ const callbacks = this.sessions.get(sessionId);
74240
+ return callbacks !== void 0 && callbacks.size > 0;
74241
+ }
74242
+ cleanupSession(sessionId) {
74243
+ this.sessions.delete(sessionId);
74244
+ this.buffers.delete(sessionId);
74245
+ }
74246
+ reset() {
74247
+ this.sessions.clear();
74248
+ this.buffers.clear();
74249
+ this.removeAllListeners();
74250
+ }
74251
+ };
74252
+ milestoneSliceInterviewStreamManager = new MilestoneSliceInterviewStreamManager();
74253
+ }
74254
+ });
74255
+
74256
+ // ../dashboard/src/runtime-logger.ts
74257
+ var init_runtime_logger = __esm({
74258
+ "../dashboard/src/runtime-logger.ts"() {
74259
+ "use strict";
74260
+ }
74261
+ });
74262
+
74263
+ // ../dashboard/src/api-error.ts
74264
+ var init_api_error = __esm({
74265
+ "../dashboard/src/api-error.ts"() {
74266
+ "use strict";
74267
+ init_runtime_logger();
74268
+ }
74269
+ });
74270
+
74271
+ // ../dashboard/src/plugin-routes.ts
74272
+ import { Router } from "express";
74273
+ var init_plugin_routes = __esm({
74274
+ "../dashboard/src/plugin-routes.ts"() {
74275
+ "use strict";
74276
+ init_src();
74277
+ init_api_error();
74278
+ }
74279
+ });
74280
+
74281
+ // ../dashboard/src/project-store-resolver.ts
74282
+ var init_project_store_resolver = __esm({
74283
+ "../dashboard/src/project-store-resolver.ts"() {
74284
+ "use strict";
74285
+ }
74286
+ });
74287
+
74288
+ // ../dashboard/src/routes/context.ts
74289
+ import { Router as Router2 } from "express";
74290
+ var init_context = __esm({
74291
+ "../dashboard/src/routes/context.ts"() {
74292
+ "use strict";
74293
+ init_api_error();
74294
+ init_project_store_resolver();
74295
+ init_runtime_logger();
74296
+ }
74297
+ });
74298
+
74299
+ // ../dashboard/src/routes/register-task-workflow-routes.ts
74300
+ var init_register_task_workflow_routes = __esm({
74301
+ "../dashboard/src/routes/register-task-workflow-routes.ts"() {
74302
+ "use strict";
74303
+ init_src();
74304
+ init_api_error();
74305
+ }
74306
+ });
74307
+
74308
+ // ../dashboard/src/routes/register-planning-subtask-routes.ts
74309
+ var init_register_planning_subtask_routes = __esm({
74310
+ "../dashboard/src/routes/register-planning-subtask-routes.ts"() {
74311
+ "use strict";
74312
+ init_api_error();
74313
+ init_sse_buffer();
74314
+ }
74315
+ });
74316
+
74317
+ // ../dashboard/src/rate-limit.ts
74318
+ var init_rate_limit = __esm({
74319
+ "../dashboard/src/rate-limit.ts"() {
74320
+ "use strict";
74321
+ init_api_error();
74322
+ }
74323
+ });
74324
+
74325
+ // ../dashboard/src/routes/register-chat-routes.ts
74326
+ var init_register_chat_routes = __esm({
74327
+ "../dashboard/src/routes/register-chat-routes.ts"() {
74328
+ "use strict";
74329
+ init_api_error();
74330
+ init_rate_limit();
74331
+ init_sse_buffer();
74332
+ }
74333
+ });
74334
+
74335
+ // ../dashboard/src/remote-auth.ts
74336
+ var init_remote_auth = __esm({
74337
+ "../dashboard/src/remote-auth.ts"() {
74338
+ "use strict";
74339
+ }
74340
+ });
74341
+
74342
+ // ../dashboard/src/routes/register-settings-memory-routes.ts
74343
+ var init_register_settings_memory_routes = __esm({
74344
+ "../dashboard/src/routes/register-settings-memory-routes.ts"() {
74345
+ "use strict";
74346
+ init_src();
74347
+ init_api_error();
74348
+ init_remote_auth();
74349
+ init_project_store_resolver();
74350
+ }
74351
+ });
74352
+
74353
+ // ../dashboard/src/routes/register-messaging-scripts.ts
74354
+ var init_register_messaging_scripts = __esm({
74355
+ "../dashboard/src/routes/register-messaging-scripts.ts"() {
74356
+ "use strict";
74357
+ init_src();
74358
+ init_api_error();
74359
+ init_terminal_service();
72568
74360
  }
72569
74361
  });
72570
74362
 
@@ -74048,7 +75840,7 @@ var init_github = __esm({
74048
75840
  });
74049
75841
 
74050
75842
  // ../dashboard/src/github-poll.ts
74051
- import { EventEmitter as EventEmitter19 } from "node:events";
75843
+ import { EventEmitter as EventEmitter24 } from "node:events";
74052
75844
  function toAlias(type, number) {
74053
75845
  return `${type}_${number}`;
74054
75846
  }
@@ -74094,7 +75886,7 @@ var init_github_poll = __esm({
74094
75886
  }
74095
75887
  };
74096
75888
  githubRateLimiter = new GitHubRateLimiter();
74097
- GitHubPollingService = class extends EventEmitter19 {
75889
+ GitHubPollingService = class extends EventEmitter24 {
74098
75890
  watches = /* @__PURE__ */ new Map();
74099
75891
  rateLimiter;
74100
75892
  pollingIntervalMs;
@@ -74338,300 +76130,19 @@ var init_github_poll = __esm({
74338
76130
  }
74339
76131
  });
74340
76132
 
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" };
74369
- }
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
- }
74374
- }
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
- }
74389
- }
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"() {
74395
- "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();
74624
- }
74625
- });
74626
-
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"() {
76133
+ // ../dashboard/src/routes/register-git-github.ts
76134
+ import { execFile as execFile4 } from "node:child_process";
76135
+ import { promisify as promisify11 } from "node:util";
76136
+ var execFileAsync2;
76137
+ var init_register_git_github = __esm({
76138
+ "../dashboard/src/routes/register-git-github.ts"() {
74632
76139
  "use strict";
74633
- isBunBinary = typeof Bun !== "undefined" && !!Bun.embeddedFiles;
74634
- require2 = createRequire2(import.meta.url);
76140
+ init_src();
76141
+ init_api_error();
76142
+ init_github();
76143
+ init_github_poll();
76144
+ init_github_webhooks();
76145
+ execFileAsync2 = promisify11(execFile4);
74635
76146
  }
74636
76147
  });
74637
76148
 
@@ -74644,6 +76155,52 @@ var init_file_service = __esm({
74644
76155
  }
74645
76156
  });
74646
76157
 
76158
+ // ../dashboard/src/routes/register-file-workspace-routes.ts
76159
+ var init_register_file_workspace_routes = __esm({
76160
+ "../dashboard/src/routes/register-file-workspace-routes.ts"() {
76161
+ "use strict";
76162
+ init_api_error();
76163
+ init_file_service();
76164
+ }
76165
+ });
76166
+
76167
+ // ../dashboard/src/routes/register-agents-projects-nodes.ts
76168
+ var init_register_agents_projects_nodes = __esm({
76169
+ "../dashboard/src/routes/register-agents-projects-nodes.ts"() {
76170
+ "use strict";
76171
+ }
76172
+ });
76173
+
76174
+ // ../dashboard/src/routes/register-project-routes.ts
76175
+ import { execFile as execFile5 } from "node:child_process";
76176
+ import * as fsPromises from "node:fs/promises";
76177
+ import { promisify as promisify12 } from "node:util";
76178
+ var access3, stat6, mkdir12, readdir8, rm2, execFileAsync3;
76179
+ var init_register_project_routes = __esm({
76180
+ "../dashboard/src/routes/register-project-routes.ts"() {
76181
+ "use strict";
76182
+ init_src();
76183
+ init_api_error();
76184
+ init_project_store_resolver();
76185
+ ({
76186
+ access: access3,
76187
+ stat: stat6,
76188
+ mkdir: mkdir12,
76189
+ readdir: readdir8,
76190
+ rm: rm2
76191
+ } = fsPromises);
76192
+ execFileAsync3 = promisify12(execFile5);
76193
+ }
76194
+ });
76195
+
76196
+ // ../dashboard/src/routes/register-node-routes.ts
76197
+ var init_register_node_routes = __esm({
76198
+ "../dashboard/src/routes/register-node-routes.ts"() {
76199
+ "use strict";
76200
+ init_api_error();
76201
+ }
76202
+ });
76203
+
74647
76204
  // ../dashboard/src/auth-paths.ts
74648
76205
  var init_auth_paths = __esm({
74649
76206
  "../dashboard/src/auth-paths.ts"() {
@@ -74651,138 +76208,72 @@ var init_auth_paths = __esm({
74651
76208
  }
74652
76209
  });
74653
76210
 
74654
- // ../dashboard/src/usage.ts
74655
- var init_usage = __esm({
74656
- "../dashboard/src/usage.ts"() {
76211
+ // ../dashboard/src/routes/register-settings-sync-helpers.ts
76212
+ var init_register_settings_sync_helpers = __esm({
76213
+ "../dashboard/src/routes/register-settings-sync-helpers.ts"() {
74657
76214
  "use strict";
76215
+ init_api_error();
74658
76216
  init_auth_paths();
74659
76217
  }
74660
76218
  });
74661
76219
 
74662
- // ../dashboard/src/github-webhooks.ts
74663
- var init_github_webhooks = __esm({
74664
- "../dashboard/src/github-webhooks.ts"() {
76220
+ // ../dashboard/src/routes/register-settings-sync-routes.ts
76221
+ var init_register_settings_sync_routes = __esm({
76222
+ "../dashboard/src/routes/register-settings-sync-routes.ts"() {
74665
76223
  "use strict";
76224
+ init_api_error();
76225
+ init_auth_paths();
76226
+ init_register_settings_sync_helpers();
74666
76227
  }
74667
76228
  });
74668
76229
 
74669
- // ../dashboard/src/project-store-resolver.ts
74670
- var init_project_store_resolver = __esm({
74671
- "../dashboard/src/project-store-resolver.ts"() {
76230
+ // ../dashboard/src/routes/register-mesh-routes.ts
76231
+ var init_register_mesh_routes = __esm({
76232
+ "../dashboard/src/routes/register-mesh-routes.ts"() {
74672
76233
  "use strict";
76234
+ init_api_error();
74673
76235
  }
74674
76236
  });
74675
76237
 
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"() {
76238
+ // ../dashboard/src/routes/register-discovery-routes.ts
76239
+ var init_register_discovery_routes = __esm({
76240
+ "../dashboard/src/routes/register-discovery-routes.ts"() {
74680
76241
  "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");
76242
+ init_api_error();
74686
76243
  }
74687
76244
  });
74688
76245
 
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;
76246
+ // ../dashboard/src/routes/register-settings-sync-inbound-routes.ts
76247
+ var init_register_settings_sync_inbound_routes = __esm({
76248
+ "../dashboard/src/routes/register-settings-sync-inbound-routes.ts"() {
76249
+ "use strict";
76250
+ init_api_error();
76251
+ init_auth_paths();
76252
+ init_register_settings_sync_helpers();
74695
76253
  }
74696
- try {
74697
- session.agent?.session?.dispose?.();
74698
- } catch {
76254
+ });
76255
+
76256
+ // ../dashboard/src/routes/register-agent-core-routes.ts
76257
+ var init_register_agent_core_routes = __esm({
76258
+ "../dashboard/src/routes/register-agent-core-routes.ts"() {
76259
+ "use strict";
76260
+ init_api_error();
74699
76261
  }
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
- }
76262
+ });
76263
+
76264
+ // ../dashboard/src/routes/register-agent-runtime-routes.ts
76265
+ var init_register_agent_runtime_routes = __esm({
76266
+ "../dashboard/src/routes/register-agent-runtime-routes.ts"() {
76267
+ "use strict";
76268
+ init_api_error();
74710
76269
  }
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"() {
76270
+ });
76271
+
76272
+ // ../dashboard/src/routes/register-agent-reflection-rating-routes.ts
76273
+ var init_register_agent_reflection_rating_routes = __esm({
76274
+ "../dashboard/src/routes/register-agent-reflection-rating-routes.ts"() {
74715
76275
  "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();
76276
+ init_api_error();
74786
76277
  }
74787
76278
  });
74788
76279
 
@@ -74798,33 +76289,33 @@ async function initPromptCatalog() {
74798
76289
  promptCatalogReady = true;
74799
76290
  }
74800
76291
  }
74801
- function cleanupExpiredSessions3() {
76292
+ function cleanupExpiredSessions5() {
74802
76293
  const now = Date.now();
74803
76294
  let cleanedSessions = 0;
74804
76295
  let cleanedRateLimits = 0;
74805
- for (const [id, session] of sessions3) {
74806
- if (now - session.updatedAt.getTime() > SESSION_TTL_MS3) {
74807
- sessions3.delete(id);
76296
+ for (const [id, session] of sessions5) {
76297
+ if (now - session.updatedAt.getTime() > SESSION_TTL_MS5) {
76298
+ sessions5.delete(id);
74808
76299
  cleanedSessions++;
74809
76300
  }
74810
76301
  }
74811
- for (const [ip, entry] of rateLimits3) {
74812
- if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS3) {
74813
- rateLimits3.delete(ip);
76302
+ for (const [ip, entry] of rateLimits5) {
76303
+ if (now - entry.firstRequestAt.getTime() > RATE_LIMIT_WINDOW_MS5) {
76304
+ rateLimits5.delete(ip);
74814
76305
  cleanedRateLimits++;
74815
76306
  }
74816
76307
  }
74817
76308
  if (cleanedSessions > 0 || cleanedRateLimits > 0) {
74818
- diagnostics4.info("Cleanup completed", {
76309
+ diagnostics6.info("Cleanup completed", {
74819
76310
  cleanedSessions,
74820
76311
  cleanedRateLimits,
74821
- ttlMs: SESSION_TTL_MS3,
74822
- rateLimitWindowMs: RATE_LIMIT_WINDOW_MS3,
76312
+ ttlMs: SESSION_TTL_MS5,
76313
+ rateLimitWindowMs: RATE_LIMIT_WINDOW_MS5,
74823
76314
  operation: "cleanup-expired"
74824
76315
  });
74825
76316
  }
74826
76317
  }
74827
- var resolvePrompt2, promptCatalogReady, promptCatalogReadyPromise, SESSION_TTL_MS3, CLEANUP_INTERVAL_MS4, RATE_LIMIT_WINDOW_MS3, sessions3, rateLimits3, diagnostics4, cleanupInterval4;
76318
+ var resolvePrompt2, promptCatalogReady, promptCatalogReadyPromise, SESSION_TTL_MS5, CLEANUP_INTERVAL_MS6, RATE_LIMIT_WINDOW_MS5, sessions5, rateLimits5, diagnostics6, cleanupInterval6;
74828
76319
  var init_agent_generation = __esm({
74829
76320
  "../dashboard/src/agent-generation.ts"() {
74830
76321
  "use strict";
@@ -74832,357 +76323,97 @@ var init_agent_generation = __esm({
74832
76323
  resolvePrompt2 = () => "";
74833
76324
  promptCatalogReady = false;
74834
76325
  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;
76326
+ SESSION_TTL_MS5 = 30 * 60 * 1e3;
75004
76327
  CLEANUP_INTERVAL_MS6 = 5 * 60 * 1e3;
75005
76328
  RATE_LIMIT_WINDOW_MS5 = 60 * 60 * 1e3;
75006
76329
  sessions5 = /* @__PURE__ */ new Map();
75007
76330
  rateLimits5 = /* @__PURE__ */ new Map();
76331
+ diagnostics6 = createSessionDiagnostics("agent-generation");
75008
76332
  cleanupInterval6 = setInterval(cleanupExpiredSessions5, CLEANUP_INTERVAL_MS6);
75009
76333
  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();
76334
+ process.on("beforeExit", () => {
76335
+ clearInterval(cleanupInterval6);
76336
+ });
75098
76337
  }
75099
76338
  });
75100
76339
 
75101
- // ../dashboard/src/plugin-routes.ts
75102
- import { Router } from "express";
75103
- var init_plugin_routes = __esm({
75104
- "../dashboard/src/plugin-routes.ts"() {
76340
+ // ../dashboard/src/routes/register-agent-import-export-generation-routes.ts
76341
+ import * as fsPromises2 from "node:fs/promises";
76342
+ var mkdtemp, access4, stat7, mkdir13, rm3, fsWriteFile;
76343
+ var init_register_agent_import_export_generation_routes = __esm({
76344
+ "../dashboard/src/routes/register-agent-import-export-generation-routes.ts"() {
75105
76345
  "use strict";
75106
- init_src();
75107
76346
  init_api_error();
76347
+ init_ai_session_diagnostics();
76348
+ init_agent_generation();
76349
+ ({ mkdtemp, access: access4, stat: stat7, mkdir: mkdir13, rm: rm3, writeFile: fsWriteFile } = fsPromises2);
75108
76350
  }
75109
76351
  });
75110
76352
 
75111
- // ../dashboard/src/routes/context.ts
75112
- import { Router as Router2 } from "express";
75113
- var init_context = __esm({
75114
- "../dashboard/src/routes/context.ts"() {
76353
+ // ../dashboard/src/routes/register-agent-skills-routes.ts
76354
+ var init_register_agent_skills_routes = __esm({
76355
+ "../dashboard/src/routes/register-agent-skills-routes.ts"() {
75115
76356
  "use strict";
75116
76357
  init_api_error();
75117
- init_project_store_resolver();
75118
- init_runtime_logger();
75119
- }
75120
- });
75121
-
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
76358
  }
75134
76359
  });
75135
76360
 
75136
- // ../dashboard/src/routes/register-settings-memory.ts
75137
- var init_register_settings_memory = __esm({
75138
- "../dashboard/src/routes/register-settings-memory.ts"() {
76361
+ // ../dashboard/src/routes/register-plugins-automation.ts
76362
+ var init_register_plugins_automation = __esm({
76363
+ "../dashboard/src/routes/register-plugins-automation.ts"() {
75139
76364
  "use strict";
75140
76365
  }
75141
76366
  });
75142
76367
 
75143
- // ../dashboard/src/routes/register-messaging-scripts.ts
75144
- var init_register_messaging_scripts = __esm({
75145
- "../dashboard/src/routes/register-messaging-scripts.ts"() {
76368
+ // ../dashboard/src/routes/register-proxy.ts
76369
+ var init_register_proxy = __esm({
76370
+ "../dashboard/src/routes/register-proxy.ts"() {
75146
76371
  "use strict";
75147
- init_src();
75148
76372
  init_api_error();
75149
- init_terminal_service();
75150
76373
  }
75151
76374
  });
75152
76375
 
75153
- // ../dashboard/src/routes/register-git-github.ts
75154
- var init_register_git_github = __esm({
75155
- "../dashboard/src/routes/register-git-github.ts"() {
76376
+ // ../dashboard/src/routes/register-model-routes.ts
76377
+ var init_register_model_routes = __esm({
76378
+ "../dashboard/src/routes/register-model-routes.ts"() {
75156
76379
  "use strict";
76380
+ init_api_error();
75157
76381
  }
75158
76382
  });
75159
76383
 
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"() {
76384
+ // ../dashboard/src/usage.ts
76385
+ var init_usage = __esm({
76386
+ "../dashboard/src/usage.ts"() {
75163
76387
  "use strict";
76388
+ init_auth_paths();
75164
76389
  }
75165
76390
  });
75166
76391
 
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"() {
76392
+ // ../dashboard/src/routes/register-usage-routes.ts
76393
+ var init_register_usage_routes = __esm({
76394
+ "../dashboard/src/routes/register-usage-routes.ts"() {
75170
76395
  "use strict";
76396
+ init_api_error();
76397
+ init_usage();
75171
76398
  }
75172
76399
  });
75173
76400
 
75174
- // ../dashboard/src/routes/register-plugins-automation.ts
75175
- var init_register_plugins_automation = __esm({
75176
- "../dashboard/src/routes/register-plugins-automation.ts"() {
76401
+ // ../dashboard/src/claude-cli-probe.ts
76402
+ var init_claude_cli_probe = __esm({
76403
+ "../dashboard/src/claude-cli-probe.ts"() {
75177
76404
  "use strict";
75178
76405
  }
75179
76406
  });
75180
76407
 
75181
- // ../dashboard/src/routes/register-proxy.ts
75182
- var init_register_proxy = __esm({
75183
- "../dashboard/src/routes/register-proxy.ts"() {
76408
+ // ../dashboard/src/routes/register-auth-routes.ts
76409
+ var init_register_auth_routes = __esm({
76410
+ "../dashboard/src/routes/register-auth-routes.ts"() {
75184
76411
  "use strict";
76412
+ init_src();
76413
+ init_claude_cli_probe();
75185
76414
  init_api_error();
76415
+ init_usage();
76416
+ init_project_store_resolver();
75186
76417
  }
75187
76418
  });
75188
76419
 
@@ -75282,9 +76513,6 @@ var init_register_integrated_routers = __esm({
75282
76513
 
75283
76514
  // ../dashboard/src/routes.ts
75284
76515
  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
76516
  async function initPromptOverrides() {
75289
76517
  if (promptOverridesReady) return;
75290
76518
  try {
@@ -75296,60 +76524,55 @@ async function initPromptOverrides() {
75296
76524
  promptOverridesReady = true;
75297
76525
  }
75298
76526
  }
75299
- var mkdtemp, access3, stat6, mkdir12, readdir8, rm2, fsReadFile, fsWriteFile, upload, execFileAsync, resolveWorkflowStepRefinePrompt, promptOverridesReady, DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
76527
+ var upload, resolveWorkflowStepRefinePrompt, promptOverridesReady, DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
75300
76528
  var init_routes = __esm({
75301
76529
  "../dashboard/src/routes.ts"() {
75302
76530
  "use strict";
75303
76531
  init_src();
75304
- init_claude_cli_probe();
75305
- init_github();
75306
- init_github_poll();
75307
76532
  init_terminal();
75308
76533
  init_terminal_service();
75309
- init_file_service();
75310
- init_usage();
75311
76534
  init_github_webhooks();
75312
- init_project_store_resolver();
75313
76535
  init_ai_session_store();
75314
76536
  init_planning();
75315
76537
  init_subtask_breakdown();
75316
- init_agent_generation();
75317
76538
  init_mission_interview();
75318
76539
  init_milestone_slice_interview();
75319
76540
  init_sse_buffer();
75320
76541
  init_api_error();
75321
- init_rate_limit();
75322
76542
  init_plugin_routes();
75323
- init_auth_paths();
75324
- init_runtime_logger();
75325
76543
  init_ai_session_diagnostics();
75326
76544
  init_context();
75327
- init_register_tasks();
75328
- init_register_planning_chat();
75329
- init_register_settings_memory();
76545
+ init_register_task_workflow_routes();
76546
+ init_register_planning_subtask_routes();
76547
+ init_register_chat_routes();
76548
+ init_register_settings_memory_routes();
75330
76549
  init_register_messaging_scripts();
75331
76550
  init_register_git_github();
75332
- init_register_files_terminal_workspaces();
76551
+ init_register_file_workspace_routes();
75333
76552
  init_register_agents_projects_nodes();
76553
+ init_register_project_routes();
76554
+ init_register_node_routes();
76555
+ init_register_settings_sync_routes();
76556
+ init_register_mesh_routes();
76557
+ init_register_discovery_routes();
76558
+ init_register_settings_sync_inbound_routes();
76559
+ init_register_agent_core_routes();
76560
+ init_register_agent_runtime_routes();
76561
+ init_register_agent_reflection_rating_routes();
76562
+ init_register_agent_import_export_generation_routes();
76563
+ init_register_agent_skills_routes();
75334
76564
  init_register_plugins_automation();
75335
76565
  init_register_proxy();
76566
+ init_register_model_routes();
76567
+ init_register_usage_routes();
76568
+ init_register_auth_routes();
75336
76569
  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);
76570
+ init_register_git_github();
75347
76571
  upload = multer({
75348
76572
  storage: multer.memoryStorage(),
75349
76573
  limits: { fileSize: 5 * 1024 * 1024 }
75350
76574
  // 5MB
75351
76575
  });
75352
- execFileAsync = promisify10(execFile3);
75353
76576
  resolveWorkflowStepRefinePrompt = () => DEFAULT_WORKFLOW_STEP_REFINE_PROMPT;
75354
76577
  promptOverridesReady = false;
75355
76578
  initPromptOverrides();
@@ -77577,7 +78800,7 @@ var require_extension = __commonJS({
77577
78800
  var require_websocket = __commonJS({
77578
78801
  "../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket.js"(exports, module) {
77579
78802
  "use strict";
77580
- var EventEmitter25 = __require("events");
78803
+ var EventEmitter26 = __require("events");
77581
78804
  var https = __require("https");
77582
78805
  var http = __require("http");
77583
78806
  var net = __require("net");
@@ -77609,7 +78832,7 @@ var require_websocket = __commonJS({
77609
78832
  var protocolVersions = [8, 13];
77610
78833
  var readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"];
77611
78834
  var subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
77612
- var WebSocket2 = class _WebSocket extends EventEmitter25 {
78835
+ var WebSocket2 = class _WebSocket extends EventEmitter26 {
77613
78836
  /**
77614
78837
  * Create a new `WebSocket`.
77615
78838
  *
@@ -78606,7 +79829,7 @@ var require_subprotocol = __commonJS({
78606
79829
  var require_websocket_server = __commonJS({
78607
79830
  "../../node_modules/.pnpm/ws@8.20.0/node_modules/ws/lib/websocket-server.js"(exports, module) {
78608
79831
  "use strict";
78609
- var EventEmitter25 = __require("events");
79832
+ var EventEmitter26 = __require("events");
78610
79833
  var http = __require("http");
78611
79834
  var { Duplex } = __require("stream");
78612
79835
  var { createHash: createHash5 } = __require("crypto");
@@ -78619,7 +79842,7 @@ var require_websocket_server = __commonJS({
78619
79842
  var RUNNING = 0;
78620
79843
  var CLOSING = 1;
78621
79844
  var CLOSED = 2;
78622
- var WebSocketServer2 = class extends EventEmitter25 {
79845
+ var WebSocketServer2 = class extends EventEmitter26 {
78623
79846
  /**
78624
79847
  * Create a `WebSocketServer` instance.
78625
79848
  *
@@ -79035,7 +80258,7 @@ var init_terminal_websocket_diagnostics = __esm({
79035
80258
  });
79036
80259
 
79037
80260
  // ../dashboard/src/chat.ts
79038
- import { EventEmitter as EventEmitter24 } from "node:events";
80261
+ import { EventEmitter as EventEmitter25 } from "node:events";
79039
80262
  var defaultDiagnostics, _diagnostics, diagnostics7, RATE_LIMIT_WINDOW_MS6, MAX_REFERENCED_FILE_SIZE, ChatStreamManager, chatStreamManager;
79040
80263
  var init_chat = __esm({
79041
80264
  "../dashboard/src/chat.ts"() {
@@ -79067,7 +80290,7 @@ var init_chat = __esm({
79067
80290
  };
79068
80291
  RATE_LIMIT_WINDOW_MS6 = 60 * 1e3;
79069
80292
  MAX_REFERENCED_FILE_SIZE = 50 * 1024;
79070
- ChatStreamManager = class extends EventEmitter24 {
80293
+ ChatStreamManager = class extends EventEmitter25 {
79071
80294
  constructor(bufferSize = 100) {
79072
80295
  super();
79073
80296
  this.bufferSize = bufferSize;
@@ -79202,6 +80425,7 @@ var init_server = __esm({
79202
80425
  init_chat();
79203
80426
  init_dev_server_routes();
79204
80427
  init_auth_middleware();
80428
+ init_remote_auth();
79205
80429
  __dirname = dirname8(fileURLToPath2(import.meta.url));
79206
80430
  MIN_AI_SESSION_TTL_MS = 10 * 60 * 1e3;
79207
80431
  MAX_AI_SESSION_TTL_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -80617,7 +81841,7 @@ __export(skills_exports, {
80617
81841
  runSkillsSearch: () => runSkillsSearch,
80618
81842
  searchSkills: () => searchSkills
80619
81843
  });
80620
- import { spawn as spawn3 } from "node:child_process";
81844
+ import { spawn as spawn4 } from "node:child_process";
80621
81845
  async function searchSkills(query, limit = 10) {
80622
81846
  const url = `${SKILLS_API_BASE}/api/search?q=${encodeURIComponent(query)}&limit=${limit}`;
80623
81847
  try {
@@ -80695,7 +81919,7 @@ async function runSkillsInstall(args, options) {
80695
81919
  npxArgs.push("--skill", options.skill);
80696
81920
  }
80697
81921
  npxArgs.push("-y", "-a", "pi");
80698
- const child = spawn3("npx", npxArgs, {
81922
+ const child = spawn4("npx", npxArgs, {
80699
81923
  cwd: process.cwd(),
80700
81924
  stdio: "inherit"
80701
81925
  });
@@ -80731,7 +81955,7 @@ import { StringEnum } from "@mariozechner/pi-ai";
80731
81955
  import { resolve as resolve15, basename as basename8, extname as extname2, join as join36 } from "node:path";
80732
81956
  import { readFile as readFile18 } from "node:fs/promises";
80733
81957
  import { existsSync as existsSync30 } from "node:fs";
80734
- import { spawn as spawn4 } from "node:child_process";
81958
+ import { spawn as spawn5 } from "node:child_process";
80735
81959
  var MIME_TYPES2 = {
80736
81960
  ".png": "image/png",
80737
81961
  ".jpg": "image/jpeg",
@@ -82132,7 +83356,7 @@ Status: ${updated.status}`
82132
83356
  npxArgs.push("--skill", params.skill);
82133
83357
  }
82134
83358
  npxArgs.push("-y", "-a", "pi");
82135
- const child = spawn4("npx", npxArgs, {
83359
+ const child = spawn5("npx", npxArgs, {
82136
83360
  cwd: resolveProjectRoot(ctx.cwd),
82137
83361
  stdio: "pipe"
82138
83362
  });
@@ -82213,7 +83437,7 @@ Status: ${updated.status}`
82213
83437
  return;
82214
83438
  }
82215
83439
  const port = trimmed ? parseInt(trimmed, 10) || 4040 : 4040;
82216
- const child = spawn4("fn", ["dashboard", "--port", String(port)], {
83440
+ const child = spawn5("fn", ["dashboard", "--port", String(port)], {
82217
83441
  cwd: resolveProjectRoot(ctx.cwd),
82218
83442
  stdio: ["ignore", "pipe", "pipe"],
82219
83443
  detached: false,