@runfusion/fusion 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bin.js +825 -749
  3. package/dist/client/assets/{AgentDetailView-DJwWfkpv.js → AgentDetailView-sy6Hg1Xr.js} +1 -1
  4. package/dist/client/assets/{AgentsView-DegK8aw-.js → AgentsView-DpEpW88a.js} +3 -3
  5. package/dist/client/assets/{ChatView-CYpEShLS.js → ChatView-BUt3C4cf.js} +1 -1
  6. package/dist/client/assets/{DevServerView-DfCTA9fx.js → DevServerView-BOyWtQJM.js} +1 -1
  7. package/dist/client/assets/{DirectoryPicker-B0qNpfLW.js → DirectoryPicker-BJyEEso5.js} +1 -1
  8. package/dist/client/assets/{DocumentsView-CsQxuyz3.js → DocumentsView-Dx0M1YHw.js} +1 -1
  9. package/dist/client/assets/{InsightsView-Bzs7A2jv.js → InsightsView-BtXdAo7D.js} +1 -1
  10. package/dist/client/assets/{MemoryView-Cl5ASqjW.js → MemoryView-kKPuqmwf.js} +1 -1
  11. package/dist/client/assets/{NodesView-BpiqRlvc.js → NodesView-B_ZwUORz.js} +1 -1
  12. package/dist/client/assets/{PiExtensionsManager-Cr6EoC7S.js → PiExtensionsManager-Db0EGr0Q.js} +1 -1
  13. package/dist/client/assets/{PluginManager-DXtQdfns.js → PluginManager-mOwWndfg.js} +1 -1
  14. package/dist/client/assets/{RoadmapsView-CYPLTTB0.js → RoadmapsView-DbUzBLeF.js} +1 -1
  15. package/dist/client/assets/SettingsModal-Bs_lNs5B.js +31 -0
  16. package/dist/client/assets/{SettingsModal-CNdVTVqD.js → SettingsModal-TRJu_mTn.js} +1 -1
  17. package/dist/client/assets/{SetupWizardModal-BLiljNn7.js → SetupWizardModal-D1bmCQrf.js} +1 -1
  18. package/dist/client/assets/{SkillsView-Dlpw5LKI.js → SkillsView-Dzzpd5Md.js} +1 -1
  19. package/dist/client/assets/{folder-open-B_38R5AA.js → folder-open-BcuByk6U.js} +1 -1
  20. package/dist/client/assets/index-BipedNj4.css +1 -0
  21. package/dist/client/assets/{index-DQKtk17v.js → index-k2c4LrUr.js} +5 -5
  22. package/dist/client/assets/{upload-DNQF7XCK.js → upload-BzNbXYEj.js} +1 -1
  23. package/dist/client/assets/{users-CG2_rCdk.js → users-BvIqhSXp.js} +1 -1
  24. package/dist/client/index.html +2 -2
  25. package/dist/client/version.json +1 -1
  26. package/dist/extension.js +68 -12
  27. package/dist/pi-claude-cli/package.json +1 -1
  28. package/package.json +17 -17
  29. package/dist/client/assets/SettingsModal-CyCC7MzL.js +0 -31
  30. package/dist/client/assets/index-DjOxzdj3.css +0 -1
package/dist/bin.js CHANGED
@@ -205,6 +205,7 @@ var init_settings_schema = __esm({
205
205
  },
206
206
  cloudflare: {
207
207
  enabled: false,
208
+ quickTunnel: false,
208
209
  tunnelName: "",
209
210
  tunnelToken: null,
210
211
  ingressUrl: ""
@@ -388,18 +389,18 @@ Call \`task_done()\` to signal completion.
388
389
  },
389
390
  "triage-welcome": {
390
391
  key: "triage-welcome",
391
- name: "Triage Welcome",
392
+ name: "Planning Welcome",
392
393
  roles: ["triage"],
393
- description: "Introductory section for the triage/specification agent",
394
+ description: "Introductory section for the planning agent",
394
395
  defaultContent: `You are a task specification agent for "fn", an AI-orchestrated task board.
395
396
 
396
397
  Your job: take a rough task description and produce a fully specified PROMPT.md that another AI agent can execute autonomously in a fresh context with zero memory of this conversation.`
397
398
  },
398
399
  "triage-context": {
399
400
  key: "triage-context",
400
- name: "Triage Context",
401
+ name: "Planning Context",
401
402
  roles: ["triage"],
402
- description: "Context-gathering instructions for triage",
403
+ description: "Context-gathering instructions for planning",
403
404
  defaultContent: `## What you receive
404
405
  - A raw task title and optional description (the user's rough idea)
405
406
  - Access to the project's files so you can understand context`
@@ -1088,7 +1089,7 @@ Output Requirements:
1088
1089
  }
1089
1090
  };
1090
1091
  COLUMN_LABELS = {
1091
- triage: "Triage",
1092
+ triage: "Planning",
1092
1093
  todo: "Todo",
1093
1094
  "in-progress": "In Progress",
1094
1095
  "in-review": "In Review",
@@ -1096,7 +1097,7 @@ Output Requirements:
1096
1097
  archived: "Archived"
1097
1098
  };
1098
1099
  COLUMN_DESCRIPTIONS = {
1099
- triage: "Raw ideas \u2014 AI will specify these",
1100
+ triage: "Raw ideas \u2014 AI will plan these",
1100
1101
  todo: "Specified and ready to start",
1101
1102
  "in-progress": "AI is working on this in a worktree",
1102
1103
  "in-review": "Complete \u2014 ready to merge",
@@ -2222,7 +2223,7 @@ var init_db = __esm({
2222
2223
  "../core/src/db.ts"() {
2223
2224
  "use strict";
2224
2225
  init_types();
2225
- SCHEMA_VERSION = 46;
2226
+ SCHEMA_VERSION = 47;
2226
2227
  SCHEMA_SQL = `
2227
2228
  -- Tasks table with JSON columns for nested data
2228
2229
  CREATE TABLE IF NOT EXISTS tasks (
@@ -3660,6 +3661,14 @@ CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder
3660
3661
  this.db.exec("CREATE INDEX IF NOT EXISTS idxTodoItemsSortOrder ON todo_items(listId, sortOrder)");
3661
3662
  });
3662
3663
  }
3664
+ if (version < 47) {
3665
+ this.applyMigration(47, () => {
3666
+ if (this.hasTable("tasks") && this.hasColumn("tasks", "status")) {
3667
+ this.db.exec("UPDATE tasks SET status = 'planning' WHERE status = 'specifying'");
3668
+ this.db.exec("UPDATE tasks SET status = 'needs-replan' WHERE status = 'needs-respecify'");
3669
+ }
3670
+ });
3671
+ }
3663
3672
  }
3664
3673
  /**
3665
3674
  * Run a single migration step inside a transaction and bump the version.
@@ -32830,7 +32839,7 @@ ${task.description}
32830
32839
  let invalidatedStatus = false;
32831
32840
  try {
32832
32841
  await this.updateTask(id, {
32833
- status: "needs-respecify"
32842
+ status: "needs-replan"
32834
32843
  });
32835
32844
  invalidatedStatus = true;
32836
32845
  } catch (err) {
@@ -32838,7 +32847,7 @@ ${task.description}
32838
32847
  ...commentContextBase,
32839
32848
  phase: "addComment:awaiting-approval-invalidation",
32840
32849
  stage: "status-update",
32841
- nextStatus: "needs-respecify",
32850
+ nextStatus: "needs-replan",
32842
32851
  error: err instanceof Error ? err.message : String(err)
32843
32852
  });
32844
32853
  }
@@ -32855,7 +32864,7 @@ ${task.description}
32855
32864
  ...commentContextBase,
32856
32865
  phase: "addComment:awaiting-approval-invalidation",
32857
32866
  stage: "post-invalidation-log-entry",
32858
- nextStatus: "needs-respecify",
32867
+ nextStatus: "needs-replan",
32859
32868
  error: err instanceof Error ? err.message : String(err)
32860
32869
  });
32861
32870
  }
@@ -55219,6 +55228,15 @@ function rateLimited(message, retryAfter) {
55219
55228
  function internalError(message) {
55220
55229
  return new ApiError(500, message);
55221
55230
  }
55231
+ function rethrowAsApiError(error, fallbackMessage = "Internal server error") {
55232
+ if (error instanceof ApiError) {
55233
+ throw error;
55234
+ }
55235
+ if (error instanceof Error && error.message) {
55236
+ throw internalError(error.message);
55237
+ }
55238
+ throw internalError(fallbackMessage);
55239
+ }
55222
55240
  var ApiError;
55223
55241
  var init_api_error = __esm({
55224
55242
  "../dashboard/src/api-error.ts"() {
@@ -55383,7 +55401,7 @@ var init_project_store_resolver = __esm({
55383
55401
  // ../dashboard/src/routes/context.ts
55384
55402
  import { Router as Router2 } from "express";
55385
55403
  import { resolve as resolve11, sep as sep4 } from "node:path";
55386
- function rethrowAsApiError(error, fallbackMessage = "Internal server error") {
55404
+ function rethrowAsApiError2(error, fallbackMessage = "Internal server error") {
55387
55405
  if (error instanceof ApiError) {
55388
55406
  throw error;
55389
55407
  }
@@ -55700,7 +55718,7 @@ function createApiRoutesContext(store, options) {
55700
55718
  resolveAutomationStore,
55701
55719
  resolveRoutineStore,
55702
55720
  resolveRoutineRunner,
55703
- rethrowAsApiError
55721
+ rethrowAsApiError: rethrowAsApiError2
55704
55722
  };
55705
55723
  }
55706
55724
  var init_context = __esm({
@@ -55714,7 +55732,6 @@ var init_context = __esm({
55714
55732
 
55715
55733
  // ../dashboard/src/routes/register-task-workflow-routes.ts
55716
55734
  import { createReadStream } from "node:fs";
55717
- import { access as access4 } from "node:fs/promises";
55718
55735
  function registerTaskWorkflowRoutes(ctx, deps) {
55719
55736
  const { router, options, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError5 } = ctx;
55720
55737
  const {
@@ -55724,7 +55741,6 @@ function registerTaskWorkflowRoutes(ctx, deps) {
55724
55741
  validateOptionalModelField: validateOptionalModelField2,
55725
55742
  normalizeModelSelectionPair: normalizeModelSelectionPair2,
55726
55743
  runGitCommand: runGitCommand2,
55727
- resolveDiffBase: resolveDiffBase2,
55728
55744
  trimTaskDetailActivityLog: trimTaskDetailActivityLog2,
55729
55745
  triggerCommentWakeForAssignedAgent
55730
55746
  } = deps;
@@ -56978,156 +56994,6 @@ function registerTaskWorkflowRoutes(ctx, deps) {
56978
56994
  rethrowAsApiError5(err);
56979
56995
  }
56980
56996
  });
56981
- router.get("/tasks/:id/diff", async (req, res) => {
56982
- try {
56983
- const { store: scopedStore } = await getProjectContext3(req);
56984
- const task = await scopedStore.getTask(req.params.id);
56985
- if (!task) {
56986
- res.status(404).json({ error: "Task not found" });
56987
- return;
56988
- }
56989
- if (task.column === "done" && task.mergeDetails?.commitSha) {
56990
- const rootDir = scopedStore.getRootDir();
56991
- const sha = task.mergeDetails.commitSha;
56992
- let mergeBase;
56993
- try {
56994
- mergeBase = (await runGitCommand2(["rev-parse", `${sha}^`], rootDir, 5e3)).trim();
56995
- } catch {
56996
- res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
56997
- return;
56998
- }
56999
- const nameStatus = (await runGitCommand2(["diff", "--name-status", `${mergeBase}..${sha}`], rootDir, 1e4)).trim();
57000
- const doneFiles = [];
57001
- for (const line of nameStatus.split("\n").filter(Boolean)) {
57002
- const parts = line.split(" ");
57003
- const statusCode = parts[0] ?? "M";
57004
- const filePath = parts[1] ?? "";
57005
- if (!filePath) continue;
57006
- let status = "modified";
57007
- if (statusCode.startsWith("A")) status = "added";
57008
- else if (statusCode.startsWith("D")) status = "deleted";
57009
- let patch = "";
57010
- try {
57011
- patch = await runGitCommand2(["diff", `${mergeBase}..${sha}`, "--", filePath], rootDir, 1e4);
57012
- } catch {
57013
- }
57014
- const additions = (patch.match(/^\+[^+]/gm) || []).length;
57015
- const deletions = (patch.match(/^-[^-]/gm) || []).length;
57016
- doneFiles.push({ path: filePath, status, additions, deletions, patch });
57017
- }
57018
- const doneStats = {
57019
- filesChanged: doneFiles.length,
57020
- additions: doneFiles.reduce((s, f) => s + f.additions, 0),
57021
- deletions: doneFiles.reduce((s, f) => s + f.deletions, 0)
57022
- };
57023
- res.json({ files: doneFiles, stats: doneStats });
57024
- return;
57025
- }
57026
- if (task.column === "done") {
57027
- const md = task.mergeDetails;
57028
- res.json({
57029
- files: [],
57030
- stats: {
57031
- filesChanged: md?.filesChanged ?? 0,
57032
- additions: md?.insertions ?? 0,
57033
- deletions: md?.deletions ?? 0
57034
- }
57035
- });
57036
- return;
57037
- }
57038
- const worktree = typeof req.query.worktree === "string" ? req.query.worktree : void 0;
57039
- const resolvedWorktree = worktree || task.worktree;
57040
- if (!resolvedWorktree) {
57041
- res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
57042
- return;
57043
- }
57044
- let worktreeExists = false;
57045
- try {
57046
- await access4(resolvedWorktree);
57047
- worktreeExists = true;
57048
- } catch {
57049
- worktreeExists = false;
57050
- }
57051
- if (!worktreeExists) {
57052
- res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
57053
- return;
57054
- }
57055
- const cwd = resolvedWorktree;
57056
- const diffBase = await resolveDiffBase2(task, cwd);
57057
- const diffRange = diffBase ? `${diffBase}..HEAD` : "HEAD";
57058
- const fileMap = /* @__PURE__ */ new Map();
57059
- if (diffBase) {
57060
- try {
57061
- const committedOutput = (await runGitCommand2(["diff", "--name-status", `${diffBase}..HEAD`], cwd, 1e4)).trim();
57062
- for (const line of committedOutput.split("\n").filter(Boolean)) {
57063
- const parts = line.split(" ");
57064
- fileMap.set(parts[1] ?? "", parts[0] ?? "M");
57065
- }
57066
- } catch {
57067
- }
57068
- }
57069
- try {
57070
- const stagedOutput = (await runGitCommand2(["diff", "--cached", "--name-status"], cwd, 1e4)).trim();
57071
- for (const line of stagedOutput.split("\n").filter(Boolean)) {
57072
- const parts = line.split(" ");
57073
- const filePath = parts[1] ?? "";
57074
- if (filePath && !fileMap.has(filePath)) {
57075
- fileMap.set(filePath, parts[0] ?? "M");
57076
- }
57077
- }
57078
- } catch {
57079
- }
57080
- try {
57081
- const workingTreeOutput = (await runGitCommand2(["diff", "--name-status"], cwd, 1e4)).trim();
57082
- for (const line of workingTreeOutput.split("\n").filter(Boolean)) {
57083
- const parts = line.split(" ");
57084
- const filePath = parts[1] ?? "";
57085
- if (filePath && !fileMap.has(filePath)) {
57086
- fileMap.set(filePath, parts[0] ?? "M");
57087
- }
57088
- }
57089
- } catch {
57090
- }
57091
- try {
57092
- const untrackedOutput = (await runGitCommand2(["ls-files", "--others", "--exclude-standard"], cwd, 1e4)).trim();
57093
- for (const line of untrackedOutput.split("\n").filter(Boolean)) {
57094
- fileMap.set(line, "U");
57095
- }
57096
- } catch {
57097
- }
57098
- const files = [];
57099
- for (const [filePath, statusCode] of fileMap) {
57100
- if (!filePath) continue;
57101
- let status;
57102
- if (statusCode.startsWith("A") || statusCode === "U") status = "added";
57103
- else if (statusCode.startsWith("D")) status = "deleted";
57104
- else status = "modified";
57105
- let patch = "";
57106
- try {
57107
- if (statusCode === "U") {
57108
- patch = await runGitCommand2(["diff", "--no-index", "/dev/null", filePath], cwd, 1e4).catch(() => "");
57109
- } else {
57110
- patch = await runGitCommand2(["diff", diffRange, "--", filePath], cwd, 1e4);
57111
- }
57112
- } catch {
57113
- }
57114
- const additions = (patch.match(/^\+[^+]/gm) || []).length;
57115
- const deletions = (patch.match(/^-[^-]/gm) || []).length;
57116
- files.push({ path: filePath, status, additions, deletions, patch });
57117
- }
57118
- const stats = {
57119
- filesChanged: files.length,
57120
- additions: files.reduce((sum, f) => sum + f.additions, 0),
57121
- deletions: files.reduce((sum, f) => sum + f.deletions, 0)
57122
- };
57123
- res.json({ files, stats });
57124
- } catch (err) {
57125
- if (err instanceof ApiError) {
57126
- throw err;
57127
- }
57128
- rethrowAsApiError5(err);
57129
- }
57130
- });
57131
56997
  }
57132
56998
  var init_register_task_workflow_routes = __esm({
57133
56999
  "../dashboard/src/routes/register-task-workflow-routes.ts"() {
@@ -83032,6 +82898,9 @@ var init_provider_adapters = __esm({
83032
82898
  provider: "cloudflare",
83033
82899
  validateConfig(config) {
83034
82900
  validateBaseConfig(config, "cloudflare");
82901
+ if (config.provider === "cloudflare" && config.quickTunnel === true) {
82902
+ return;
82903
+ }
83035
82904
  if ("credentialsPath" in config && config.credentialsPath !== void 0) {
83036
82905
  assertNonEmpty(config.credentialsPath, "credentialsPath");
83037
82906
  if (isAbsoluteOrPathLike(config.credentialsPath)) {
@@ -83922,6 +83791,21 @@ var init_project_engine = __esm({
83922
83791
  if (!cloudflare.enabled) {
83923
83792
  return { provider, reason: "provider_not_enabled", message: "Cloudflare provider is disabled" };
83924
83793
  }
83794
+ if (cloudflare.quickTunnel === true) {
83795
+ const executable2 = await this.checkExecutableAvailable("cloudflared");
83796
+ if (!executable2.available) {
83797
+ return { provider, reason: "runtime_prerequisite_missing", message: executable2.message };
83798
+ }
83799
+ return {
83800
+ provider,
83801
+ config: {
83802
+ provider: "cloudflare",
83803
+ quickTunnel: true,
83804
+ executablePath: "cloudflared",
83805
+ args: ["tunnel", "--url", "http://localhost:4040"]
83806
+ }
83807
+ };
83808
+ }
83925
83809
  if (!cloudflare.tunnelName?.trim() || !cloudflare.ingressUrl?.trim()) {
83926
83810
  return { provider, reason: "provider_not_configured", message: "Cloudflare tunnel name and ingress URL must be configured" };
83927
83811
  }
@@ -85413,18 +85297,25 @@ var init_src2 = __esm({
85413
85297
  function registerSettingsMemoryRoutes(ctx, deps) {
85414
85298
  const { router, options, store, runtimeLogger, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError5 } = ctx;
85415
85299
  const { githubToken, validateModelPresets: validateModelPresets2, sanitizeOverlapIgnorePaths: sanitizeOverlapIgnorePaths2, discoverDashboardPiExtensions: discoverDashboardPiExtensions2 } = deps;
85416
- function resolveRemoteBaseUrl(remoteAccess) {
85300
+ function resolveRemoteBaseUrl(remoteAccess, tunnelUrl) {
85417
85301
  if (!remoteAccess.activeProvider) {
85418
85302
  throw new ApiError(409, "No active remote provider configured", { code: "REMOTE_PROVIDER_NOT_CONFIGURED" });
85419
85303
  }
85420
85304
  if (remoteAccess.activeProvider === "cloudflare") {
85421
- const ingressUrl = remoteAccess.providers.cloudflare.ingressUrl?.trim();
85422
- if (!ingressUrl) {
85305
+ const cloudflare = remoteAccess.providers.cloudflare;
85306
+ const ingressUrl = cloudflare.ingressUrl?.trim();
85307
+ const candidateUrl = cloudflare.quickTunnel === true && !ingressUrl ? tunnelUrl?.trim() ?? "" : ingressUrl;
85308
+ if (!candidateUrl) {
85309
+ if (cloudflare.quickTunnel === true) {
85310
+ throw new ApiError(409, "Cloudflare quick tunnel has not started yet", {
85311
+ code: "REMOTE_URL_NOT_READY"
85312
+ });
85313
+ }
85423
85314
  throw new ApiError(409, "Cloudflare ingress URL is not configured", { code: "REMOTE_URL_NOT_CONFIGURED" });
85424
85315
  }
85425
85316
  let parsed;
85426
85317
  try {
85427
- parsed = new URL(ingressUrl);
85318
+ parsed = new URL(candidateUrl);
85428
85319
  } catch {
85429
85320
  throw new ApiError(409, "Cloudflare ingress URL is invalid", { code: "REMOTE_URL_INVALID" });
85430
85321
  }
@@ -85464,13 +85355,17 @@ function registerSettingsMemoryRoutes(ctx, deps) {
85464
85355
  });
85465
85356
  return token;
85466
85357
  }
85467
- async function buildRemoteLoginUrlForTokenType(scopedStore, mode) {
85358
+ function getCurrentTunnelUrl(engine) {
85359
+ const manager = engine?.getRemoteTunnelManager?.();
85360
+ return manager?.getStatus?.().url ?? null;
85361
+ }
85362
+ async function buildRemoteLoginUrlForTokenType(scopedStore, mode, tunnelUrl) {
85468
85363
  const settings = await scopedStore.getSettings();
85469
85364
  const remoteAccess = settings.remoteAccess;
85470
85365
  if (!remoteAccess || remoteAccess.activeProvider == null || !remoteAccess.providers[remoteAccess.activeProvider]?.enabled) {
85471
85366
  throw new ApiError(409, "No remote provider is enabled", { code: "REMOTE_ACCESS_DISABLED" });
85472
85367
  }
85473
- const baseUrl = resolveRemoteBaseUrl(remoteAccess);
85368
+ const baseUrl = resolveRemoteBaseUrl(remoteAccess, tunnelUrl);
85474
85369
  if (mode === "persistent") {
85475
85370
  if (!remoteAccess.tokenStrategy.persistent.enabled) {
85476
85371
  throw new ApiError(409, "Persistent remote token strategy is disabled", { code: "REMOTE_TOKEN_DISABLED" });
@@ -85584,6 +85479,7 @@ function registerSettingsMemoryRoutes(ctx, deps) {
85584
85479
  remoteTailscaleTargetPort: Number(remoteAccess.providers.tailscale.targetPort ?? 4040),
85585
85480
  remoteTailscaleAcceptRoutes: Boolean(remoteAccess.providers.tailscale.acceptRoutes),
85586
85481
  remoteCloudflareEnabled: Boolean(remoteAccess.providers.cloudflare.enabled),
85482
+ remoteCloudflareQuickTunnel: Boolean(remoteAccess.providers.cloudflare.quickTunnel),
85587
85483
  remoteCloudflareTunnelName: remoteAccess.providers.cloudflare.tunnelName,
85588
85484
  remoteCloudflareTunnelToken: remoteAccess.providers.cloudflare.tunnelToken,
85589
85485
  remoteCloudflareIngressUrl: remoteAccess.providers.cloudflare.ingressUrl,
@@ -85633,6 +85529,7 @@ function registerSettingsMemoryRoutes(ctx, deps) {
85633
85529
  cloudflare: {
85634
85530
  ...remoteAccess.providers.cloudflare,
85635
85531
  enabled: body.remoteCloudflareEnabled === void 0 ? remoteAccess.providers.cloudflare.enabled : Boolean(body.remoteCloudflareEnabled),
85532
+ quickTunnel: body.remoteCloudflareQuickTunnel === void 0 ? Boolean(remoteAccess.providers.cloudflare.quickTunnel) : Boolean(body.remoteCloudflareQuickTunnel),
85636
85533
  tunnelName: body.remoteCloudflareTunnelName === void 0 ? remoteAccess.providers.cloudflare.tunnelName : String(body.remoteCloudflareTunnelName ?? ""),
85637
85534
  tunnelToken: body.remoteCloudflareTunnelToken === void 0 ? remoteAccess.providers.cloudflare.tunnelToken : body.remoteCloudflareTunnelToken ? String(body.remoteCloudflareTunnelToken) : null,
85638
85535
  ingressUrl: body.remoteCloudflareIngressUrl === void 0 ? remoteAccess.providers.cloudflare.ingressUrl : String(body.remoteCloudflareIngressUrl ?? "")
@@ -85828,8 +85725,8 @@ function registerSettingsMemoryRoutes(ctx, deps) {
85828
85725
  if (mode !== "persistent" && mode !== "short-lived") {
85829
85726
  throw new ApiError(400, "mode must be 'persistent' or 'short-lived'", { code: "INVALID_REMOTE_AUTH_MODE" });
85830
85727
  }
85831
- const { store: scopedStore } = await getProjectContext3(req);
85832
- const payload = await buildRemoteLoginUrlForTokenType(scopedStore, mode);
85728
+ const { store: scopedStore, engine } = await getProjectContext3(req);
85729
+ const payload = await buildRemoteLoginUrlForTokenType(scopedStore, mode, getCurrentTunnelUrl(engine ?? options?.engine));
85833
85730
  res.json({
85834
85731
  loginUrl: payload.loginUrl,
85835
85732
  tokenType: payload.tokenType,
@@ -85842,9 +85739,9 @@ function registerSettingsMemoryRoutes(ctx, deps) {
85842
85739
  });
85843
85740
  router.get("/remote/url", async (req, res) => {
85844
85741
  try {
85845
- const { store: scopedStore } = await getProjectContext3(req);
85742
+ const { store: scopedStore, engine } = await getProjectContext3(req);
85846
85743
  const tokenType = req.query.tokenType === "short-lived" ? "short-lived" : "persistent";
85847
- const payload = await buildRemoteLoginUrlForTokenType(scopedStore, tokenType);
85744
+ const payload = await buildRemoteLoginUrlForTokenType(scopedStore, tokenType, getCurrentTunnelUrl(engine ?? options?.engine));
85848
85745
  res.json({ url: payload.loginUrl, tokenType: payload.tokenType, expiresAt: payload.expiresAt });
85849
85746
  } catch (err) {
85850
85747
  if (err instanceof ApiError) throw err;
@@ -85853,10 +85750,10 @@ function registerSettingsMemoryRoutes(ctx, deps) {
85853
85750
  });
85854
85751
  router.get("/remote/qr", async (req, res) => {
85855
85752
  try {
85856
- const { store: scopedStore } = await getProjectContext3(req);
85753
+ const { store: scopedStore, engine } = await getProjectContext3(req);
85857
85754
  const tokenType = req.query.tokenType === "short-lived" ? "short-lived" : "persistent";
85858
85755
  const format = req.query.format === "image/svg" ? "image/svg" : "text";
85859
- const payload = await buildRemoteLoginUrlForTokenType(scopedStore, tokenType);
85756
+ const payload = await buildRemoteLoginUrlForTokenType(scopedStore, tokenType, getCurrentTunnelUrl(engine ?? options?.engine));
85860
85757
  if (format === "image/svg") {
85861
85758
  const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="320" height="80"><rect width="100%" height="100%" fill="white"/><text x="10" y="42" font-size="12" fill="black">${payload.loginUrl.replace(/&/g, "&amp;").replace(/</g, "&lt;")}</text></svg>`;
85862
85759
  res.json({ url: payload.loginUrl, tokenType: payload.tokenType, expiresAt: payload.expiresAt, format, data: svg });
@@ -88586,17 +88483,9 @@ var init_github_poll = __esm({
88586
88483
  }
88587
88484
  });
88588
88485
 
88589
- // ../dashboard/src/routes/register-git-github.ts
88486
+ // ../dashboard/src/routes/resolve-diff-base.ts
88590
88487
  import { execFile as execFile4 } from "node:child_process";
88591
- import { isAbsolute as isAbsolute12 } from "node:path";
88592
88488
  import { promisify as promisify11 } from "node:util";
88593
- function getCommandErrorMessage2(error) {
88594
- if (error instanceof Error) {
88595
- const anyError = error;
88596
- return [anyError.stderr, anyError.stdout, anyError.message].filter(Boolean).join("\n").trim() || anyError.message;
88597
- }
88598
- return String(error);
88599
- }
88600
88489
  async function runGitCommand(args, cwd, timeout2 = 1e4) {
88601
88490
  const result = await execFileAsync2("git", args, {
88602
88491
  cwd,
@@ -88615,6 +88504,55 @@ async function runGitCommand(args, cwd, timeout2 = 1e4) {
88615
88504
  }
88616
88505
  return "";
88617
88506
  }
88507
+ async function resolveDiffBase(task, cwd, headRef = "HEAD", runGit = runGitCommand) {
88508
+ const baseBranch = task.baseBranch ?? "main";
88509
+ let mergeBase;
88510
+ try {
88511
+ try {
88512
+ mergeBase = (await runGit(["merge-base", headRef, baseBranch], cwd, 5e3)).trim() || void 0;
88513
+ } catch {
88514
+ mergeBase = (await runGit(["merge-base", headRef, `origin/${baseBranch}`], cwd, 5e3)).trim() || void 0;
88515
+ }
88516
+ } catch {
88517
+ }
88518
+ if (mergeBase) {
88519
+ try {
88520
+ const head = (await runGit(["rev-parse", headRef], cwd, 5e3)).trim();
88521
+ if (head && head !== mergeBase) return mergeBase;
88522
+ } catch {
88523
+ return mergeBase;
88524
+ }
88525
+ }
88526
+ if (task.baseCommitSha) {
88527
+ try {
88528
+ await runGit(["merge-base", "--is-ancestor", task.baseCommitSha, headRef], cwd, 5e3);
88529
+ return task.baseCommitSha;
88530
+ } catch {
88531
+ }
88532
+ }
88533
+ try {
88534
+ return (await runGit(["rev-parse", `${headRef}~1`], cwd, 5e3)).trim() || void 0;
88535
+ } catch {
88536
+ return void 0;
88537
+ }
88538
+ }
88539
+ var execFileAsync2;
88540
+ var init_resolve_diff_base = __esm({
88541
+ "../dashboard/src/routes/resolve-diff-base.ts"() {
88542
+ "use strict";
88543
+ execFileAsync2 = promisify11(execFile4);
88544
+ }
88545
+ });
88546
+
88547
+ // ../dashboard/src/routes/register-git-github.ts
88548
+ import { isAbsolute as isAbsolute12 } from "node:path";
88549
+ function getCommandErrorMessage2(error) {
88550
+ if (error instanceof Error) {
88551
+ const anyError = error;
88552
+ return [anyError.stderr, anyError.stdout, anyError.message].filter(Boolean).join("\n").trim() || anyError.message;
88553
+ }
88554
+ return String(error);
88555
+ }
88618
88556
  function parseGitHubUrl(url) {
88619
88557
  const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/i);
88620
88558
  if (httpsMatch) {
@@ -90956,7 +90894,7 @@ ${body}`;
90956
90894
  }
90957
90895
  });
90958
90896
  }
90959
- var execFileAsync2, batchImportWindowMs, batchImportInstances, batchImportCleanupInterval;
90897
+ var batchImportWindowMs, batchImportInstances, batchImportCleanupInterval;
90960
90898
  var init_register_git_github = __esm({
90961
90899
  "../dashboard/src/routes/register-git-github.ts"() {
90962
90900
  "use strict";
@@ -90965,7 +90903,7 @@ var init_register_git_github = __esm({
90965
90903
  init_github();
90966
90904
  init_github_poll();
90967
90905
  init_github_webhooks();
90968
- execFileAsync2 = promisify11(execFile4);
90906
+ init_resolve_diff_base();
90969
90907
  batchImportWindowMs = 1e4;
90970
90908
  batchImportInstances = [];
90971
90909
  }
@@ -90973,13 +90911,13 @@ var init_register_git_github = __esm({
90973
90911
 
90974
90912
  // ../dashboard/src/file-service.ts
90975
90913
  import { join as join38, resolve as resolve17, relative as relative8, dirname as dirname11, basename as basename10 } from "node:path";
90976
- import { readdir as readdir8, readFile as fsReadFile, writeFile as fsWriteFile, stat as stat7, copyFile as fsCopyFile, rename as fsRename, rm as fsRm, mkdir as mkdir12, access as access5 } from "node:fs/promises";
90914
+ import { readdir as readdir8, readFile as fsReadFile, writeFile as fsWriteFile, stat as stat7, copyFile as fsCopyFile, rename as fsRename, rm as fsRm, mkdir as mkdir12, access as access4 } from "node:fs/promises";
90977
90915
  async function getTaskBasePath(store, taskId) {
90978
90916
  try {
90979
90917
  const task = await store.getTask(taskId);
90980
90918
  if (task.worktree) {
90981
90919
  try {
90982
- await access5(task.worktree);
90920
+ await access4(task.worktree);
90983
90921
  return resolve17(task.worktree);
90984
90922
  } catch {
90985
90923
  }
@@ -117241,16 +117179,15 @@ var require_archiver = __commonJS({
117241
117179
  });
117242
117180
 
117243
117181
  // ../dashboard/src/routes/register-file-workspace-routes.ts
117244
- import { access as access6 } from "node:fs/promises";
117182
+ import { access as access5 } from "node:fs/promises";
117245
117183
  import { createReadStream as createReadStream2 } from "node:fs";
117246
117184
  function extractFileParams(req) {
117247
117185
  const filePath = Array.isArray(req.params.filepath) ? req.params.filepath[0] : req.params.filepath ?? "";
117248
117186
  const workspace = typeof req.query.workspace === "string" && req.query.workspace.length > 0 ? req.query.workspace : "project";
117249
117187
  return { filePath, workspace };
117250
117188
  }
117251
- function registerFileWorkspaceRoutes(ctx, deps) {
117189
+ function registerFileWorkspaceRoutes(ctx) {
117252
117190
  const { router, getProjectContext: getProjectContext3, rethrowAsApiError: rethrowAsApiError5 } = ctx;
117253
- const { runGitCommand: runGitCommand2, resolveDiffBase: resolveDiffBase2 } = deps;
117254
117191
  router.get("/tasks/:id/files", async (req, res) => {
117255
117192
  try {
117256
117193
  const { store: scopedStore } = await getProjectContext3(req);
@@ -117317,7 +117254,7 @@ function registerFileWorkspaceRoutes(ctx, deps) {
117317
117254
  return null;
117318
117255
  }
117319
117256
  try {
117320
- await access6(task.worktree);
117257
+ await access5(task.worktree);
117321
117258
  return {
117322
117259
  id: task.id,
117323
117260
  title: task.title,
@@ -117563,243 +117500,6 @@ function registerFileWorkspaceRoutes(ctx, deps) {
117563
117500
  rethrowAsApiError5(err, "Internal server error");
117564
117501
  }
117565
117502
  });
117566
- router.get("/tasks/:id/session-files", async (req, res) => {
117567
- try {
117568
- const { store: scopedStore } = await getProjectContext3(req);
117569
- const task = await scopedStore.getTask(req.params.id);
117570
- if (!task) {
117571
- res.status(404).json({ error: "Task not found" });
117572
- return;
117573
- }
117574
- if (!task.worktree) {
117575
- res.json([]);
117576
- return;
117577
- }
117578
- let worktreeExists = false;
117579
- try {
117580
- await access6(task.worktree);
117581
- worktreeExists = true;
117582
- } catch {
117583
- worktreeExists = false;
117584
- }
117585
- if (!worktreeExists) {
117586
- res.json([]);
117587
- return;
117588
- }
117589
- const worktree = task.worktree;
117590
- const cached = sessionFilesCache.get(task.id);
117591
- if (cached && cached.expiresAt > Date.now()) {
117592
- res.json(cached.files);
117593
- return;
117594
- }
117595
- let files = [];
117596
- try {
117597
- const fileSet = /* @__PURE__ */ new Set();
117598
- const baseRef = await resolveDiffBase2(task, worktree);
117599
- if (baseRef) {
117600
- const committedOutput = (await runGitCommand2(["diff", "--name-only", `${baseRef}..HEAD`], worktree, 5e3)).trim();
117601
- for (const file of committedOutput.split("\n").filter(Boolean)) {
117602
- fileSet.add(file);
117603
- }
117604
- }
117605
- const stagedOutput = (await runGitCommand2(["diff", "--cached", "--name-only"], worktree, 5e3)).trim();
117606
- for (const file of stagedOutput.split("\n").filter(Boolean)) {
117607
- fileSet.add(file);
117608
- }
117609
- const workingTreeOutput = (await runGitCommand2(["diff", "--name-only"], worktree, 5e3)).trim();
117610
- for (const file of workingTreeOutput.split("\n").filter(Boolean)) {
117611
- fileSet.add(file);
117612
- }
117613
- const untrackedOutput = (await runGitCommand2(["ls-files", "--others", "--exclude-standard"], worktree, 5e3)).trim();
117614
- for (const file of untrackedOutput.split("\n").filter(Boolean)) {
117615
- fileSet.add(file);
117616
- }
117617
- files = Array.from(fileSet);
117618
- } catch {
117619
- files = [];
117620
- }
117621
- sessionFilesCache.set(task.id, {
117622
- files,
117623
- expiresAt: Date.now() + 1e4
117624
- });
117625
- res.json(files);
117626
- } catch (err) {
117627
- if (err instanceof ApiError) {
117628
- throw err;
117629
- }
117630
- if (err.code === "ENOENT") {
117631
- throw notFound(`Task ${req.params.id} not found`);
117632
- }
117633
- rethrowAsApiError5(err, "Internal server error");
117634
- }
117635
- });
117636
- router.get("/tasks/:id/file-diffs", async (req, res) => {
117637
- try {
117638
- const { store: scopedStore } = await getProjectContext3(req);
117639
- const task = await scopedStore.getTask(req.params.id);
117640
- if (!task) {
117641
- res.status(404).json({ error: "Task not found" });
117642
- return;
117643
- }
117644
- if (task.column === "done" && task.mergeDetails?.commitSha) {
117645
- const rootDir = scopedStore.getRootDir();
117646
- const sha = task.mergeDetails.commitSha;
117647
- let mergeBase;
117648
- try {
117649
- mergeBase = (await runGitCommand2(["rev-parse", `${sha}^`], rootDir, 5e3)).trim();
117650
- } catch {
117651
- res.json([]);
117652
- return;
117653
- }
117654
- try {
117655
- const nameStatus = (await runGitCommand2(["diff", "--name-status", `${mergeBase}..${sha}`], rootDir, 5e3)).trim();
117656
- const doneFiles = [];
117657
- for (const line of nameStatus.split("\n").filter(Boolean)) {
117658
- const parts = line.split(" ");
117659
- const statusCode = parts[0] ?? "M";
117660
- const filePath = parts[1] ?? "";
117661
- let status = "modified";
117662
- if (statusCode.startsWith("A")) status = "added";
117663
- else if (statusCode.startsWith("D")) status = "deleted";
117664
- else if (statusCode.startsWith("R")) status = "renamed";
117665
- let diff = "";
117666
- try {
117667
- diff = await runGitCommand2(["diff", `${mergeBase}..${sha}`, "--", filePath], rootDir, 5e3);
117668
- } catch {
117669
- }
117670
- doneFiles.push({ path: filePath, status, diff });
117671
- }
117672
- res.json(doneFiles);
117673
- } catch {
117674
- res.json([]);
117675
- }
117676
- return;
117677
- }
117678
- if (task.column === "done") {
117679
- res.json([]);
117680
- return;
117681
- }
117682
- if (!task.worktree) {
117683
- res.json([]);
117684
- return;
117685
- }
117686
- let worktreeExists = false;
117687
- try {
117688
- await access6(task.worktree);
117689
- worktreeExists = true;
117690
- } catch {
117691
- worktreeExists = false;
117692
- }
117693
- if (!worktreeExists) {
117694
- res.json([]);
117695
- return;
117696
- }
117697
- const worktree = task.worktree;
117698
- const cached = fileDiffsCache.get(task.id);
117699
- if (cached && cached.expiresAt > Date.now()) {
117700
- res.json(cached.files);
117701
- return;
117702
- }
117703
- const cwd = worktree;
117704
- const diffBase = await resolveDiffBase2(task, cwd);
117705
- const fileMap = /* @__PURE__ */ new Map();
117706
- if (diffBase) {
117707
- try {
117708
- const committedOutput = (await runGitCommand2(["diff", "--name-status", `${diffBase}..HEAD`], cwd, 5e3)).trim();
117709
- for (const line of committedOutput.split("\n").filter(Boolean)) {
117710
- const parts = line.split(" ");
117711
- const statusCode = parts[0] ?? "M";
117712
- if (statusCode.startsWith("R")) {
117713
- fileMap.set(parts[2] ?? parts[1] ?? "", { statusCode, oldPath: parts[1] });
117714
- } else {
117715
- fileMap.set(parts[1] ?? "", { statusCode });
117716
- }
117717
- }
117718
- } catch {
117719
- }
117720
- }
117721
- try {
117722
- const stagedOutput = (await runGitCommand2(["diff", "--cached", "--name-status"], cwd, 5e3)).trim();
117723
- for (const line of stagedOutput.split("\n").filter(Boolean)) {
117724
- const parts = line.split(" ");
117725
- const statusCode = parts[0] ?? "M";
117726
- const filePath = parts[1] ?? "";
117727
- if (filePath && !fileMap.has(filePath)) {
117728
- if (statusCode.startsWith("R")) {
117729
- fileMap.set(filePath, { statusCode, oldPath: parts[2] });
117730
- } else {
117731
- fileMap.set(filePath, { statusCode });
117732
- }
117733
- }
117734
- }
117735
- } catch {
117736
- }
117737
- try {
117738
- const workingTreeOutput = (await runGitCommand2(["diff", "--name-status"], cwd, 5e3)).trim();
117739
- for (const line of workingTreeOutput.split("\n").filter(Boolean)) {
117740
- const parts = line.split(" ");
117741
- const statusCode = parts[0] ?? "M";
117742
- const filePath = parts[1] ?? "";
117743
- if (filePath && !fileMap.has(filePath)) {
117744
- if (statusCode.startsWith("R")) {
117745
- fileMap.set(filePath, { statusCode, oldPath: parts[2] });
117746
- } else {
117747
- fileMap.set(filePath, { statusCode });
117748
- }
117749
- }
117750
- }
117751
- } catch {
117752
- }
117753
- try {
117754
- const untrackedOutput = (await runGitCommand2(["ls-files", "--others", "--exclude-standard"], cwd, 5e3)).trim();
117755
- for (const line of untrackedOutput.split("\n").filter(Boolean)) {
117756
- if (line && !fileMap.has(line)) {
117757
- fileMap.set(line, { statusCode: "U", isUntracked: true });
117758
- }
117759
- }
117760
- } catch {
117761
- }
117762
- const diffRange = diffBase ? `${diffBase}..HEAD` : "HEAD";
117763
- const files = [];
117764
- for (const [filePath, { statusCode, oldPath, isUntracked }] of fileMap.entries()) {
117765
- let status = "modified";
117766
- if (statusCode.startsWith("A") || statusCode === "U") {
117767
- status = "added";
117768
- } else if (statusCode.startsWith("D")) {
117769
- status = "deleted";
117770
- } else if (statusCode.startsWith("R")) {
117771
- status = "renamed";
117772
- }
117773
- let diff = "";
117774
- try {
117775
- if (isUntracked) {
117776
- diff = await runGitCommand2(["diff", "--no-index", "/dev/null", filePath], cwd, 5e3).catch(() => "");
117777
- } else {
117778
- diff = await runGitCommand2(["diff", diffRange, "--", filePath], cwd, 5e3);
117779
- }
117780
- } catch {
117781
- diff = "";
117782
- }
117783
- if (!diff && !isUntracked) {
117784
- continue;
117785
- }
117786
- files.push(oldPath ? { path: filePath, status, diff, oldPath } : { path: filePath, status, diff });
117787
- }
117788
- fileDiffsCache.set(task.id, {
117789
- files,
117790
- expiresAt: Date.now() + 1e4
117791
- });
117792
- res.json(files);
117793
- } catch (err) {
117794
- if (err instanceof ApiError) {
117795
- throw err;
117796
- }
117797
- if (err.code === "ENOENT") {
117798
- throw notFound(`Task ${req.params.id} not found`);
117799
- }
117800
- rethrowAsApiError5(err, "Internal server error");
117801
- }
117802
- });
117803
117503
  router.get("/project-files/md", async (req, res) => {
117804
117504
  try {
117805
117505
  const { store: scopedStore } = await getProjectContext3(req);
@@ -117815,14 +117515,11 @@ function registerFileWorkspaceRoutes(ctx, deps) {
117815
117515
  }
117816
117516
  });
117817
117517
  }
117818
- var sessionFilesCache, fileDiffsCache;
117819
117518
  var init_register_file_workspace_routes = __esm({
117820
117519
  "../dashboard/src/routes/register-file-workspace-routes.ts"() {
117821
117520
  "use strict";
117822
117521
  init_api_error();
117823
117522
  init_file_service();
117824
- sessionFilesCache = /* @__PURE__ */ new Map();
117825
- fileDiffsCache = /* @__PURE__ */ new Map();
117826
117523
  }
117827
117524
  });
117828
117525
 
@@ -117840,7 +117537,7 @@ import { execFile as execFile5 } from "node:child_process";
117840
117537
  import * as fsPromises from "node:fs/promises";
117841
117538
  import { dirname as dirname12, isAbsolute as isAbsolute13, join as join39 } from "node:path";
117842
117539
  import { promisify as promisify12 } from "node:util";
117843
- var access7, stat8, mkdir13, readdir9, rm2, execFileAsync3, registerProjectRoutes;
117540
+ var access6, stat8, mkdir13, readdir9, rm2, execFileAsync3, registerProjectRoutes;
117844
117541
  var init_register_project_routes = __esm({
117845
117542
  "../dashboard/src/routes/register-project-routes.ts"() {
117846
117543
  "use strict";
@@ -117848,7 +117545,7 @@ var init_register_project_routes = __esm({
117848
117545
  init_api_error();
117849
117546
  init_project_store_resolver();
117850
117547
  ({
117851
- access: access7,
117548
+ access: access6,
117852
117549
  stat: stat8,
117853
117550
  mkdir: mkdir13,
117854
117551
  readdir: readdir9,
@@ -117975,14 +117672,14 @@ var init_register_project_routes = __esm({
117975
117672
  let destinationCreatedForClone = false;
117976
117673
  if (!isCloneMode) {
117977
117674
  try {
117978
- await access7(normalizedPath);
117675
+ await access6(normalizedPath);
117979
117676
  } catch {
117980
117677
  throw badRequest("Project path does not exist");
117981
117678
  }
117982
117679
  } else {
117983
117680
  const destinationParent = dirname12(normalizedPath);
117984
117681
  try {
117985
- await access7(destinationParent);
117682
+ await access6(destinationParent);
117986
117683
  } catch {
117987
117684
  throw badRequest("Clone destination parent directory does not exist");
117988
117685
  }
@@ -118032,7 +117729,7 @@ var init_register_project_routes = __esm({
118032
117729
  let hasFusionDir = false;
118033
117730
  const fusionDirPath = join39(normalizedPath, ".fusion");
118034
117731
  try {
118035
- await access7(fusionDirPath);
117732
+ await access6(fusionDirPath);
118036
117733
  hasFusionDir = true;
118037
117734
  } catch {
118038
117735
  hasFusionDir = false;
@@ -118077,7 +117774,7 @@ var init_register_project_routes = __esm({
118077
117774
  const { basePath } = req.body;
118078
117775
  const searchPath = basePath || process.env.HOME || process.env.USERPROFILE || ".";
118079
117776
  try {
118080
- await access7(searchPath);
117777
+ await access6(searchPath);
118081
117778
  } catch {
118082
117779
  throw badRequest("Base path does not exist");
118083
117780
  }
@@ -118096,14 +117793,14 @@ var init_register_project_routes = __esm({
118096
117793
  let hasKbDb = false;
118097
117794
  let hasFusionDir = false;
118098
117795
  try {
118099
- await access7(join39(dirPath, ".fusion", "fusion.db"));
117796
+ await access6(join39(dirPath, ".fusion", "fusion.db"));
118100
117797
  hasKbDb = true;
118101
117798
  } catch {
118102
117799
  hasKbDb = false;
118103
117800
  }
118104
117801
  if (!hasKbDb) {
118105
117802
  try {
118106
- await access7(join39(dirPath, ".fusion"));
117803
+ await access6(join39(dirPath, ".fusion"));
118107
117804
  hasFusionDir = true;
118108
117805
  } catch {
118109
117806
  hasFusionDir = false;
@@ -121692,7 +121389,7 @@ ${body}`;
121692
121389
  const skillDir = join40(skillsBaseDir, skillSlug);
121693
121390
  const skillPath = join40(skillDir, "SKILL.md");
121694
121391
  try {
121695
- await access8(skillPath);
121392
+ await access7(skillPath);
121696
121393
  result.skipped.push(name);
121697
121394
  continue;
121698
121395
  } catch {
@@ -122113,14 +121810,14 @@ function registerAgentGenerationRoutes(ctx) {
122113
121810
  }
122114
121811
  });
122115
121812
  }
122116
- var mkdtemp, access8, stat9, mkdir14, rm3, fsWriteFile2;
121813
+ var mkdtemp, access7, stat9, mkdir14, rm3, fsWriteFile2;
122117
121814
  var init_register_agent_import_export_generation_routes = __esm({
122118
121815
  "../dashboard/src/routes/register-agent-import-export-generation-routes.ts"() {
122119
121816
  "use strict";
122120
121817
  init_api_error();
122121
121818
  init_ai_session_diagnostics();
122122
121819
  init_agent_generation();
122123
- ({ mkdtemp, access: access8, stat: stat9, mkdir: mkdir14, rm: rm3, writeFile: fsWriteFile2 } = fsPromises2);
121820
+ ({ mkdtemp, access: access7, stat: stat9, mkdir: mkdir14, rm: rm3, writeFile: fsWriteFile2 } = fsPromises2);
122124
121821
  }
122125
121822
  });
122126
121823
 
@@ -127182,7 +126879,7 @@ Do NOT include any markdown formatting, code fences, or additional text. Only ou
127182
126879
  // ../dashboard/src/roadmap-routes.ts
127183
126880
  import { Router as Router4 } from "express";
127184
126881
  import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
127185
- function rethrowAsApiError2(error, fallbackMessage = "Internal server error") {
126882
+ function rethrowAsApiError3(error, fallbackMessage = "Internal server error") {
127186
126883
  if (error instanceof ApiError) throw error;
127187
126884
  if (error instanceof Error) throw new ApiError(500, error.message);
127188
126885
  throw new ApiError(500, fallbackMessage);
@@ -127247,7 +126944,7 @@ function createRoadmapRouter(store) {
127247
126944
  res.json(roadmaps);
127248
126945
  } catch (err) {
127249
126946
  if (err instanceof ApiError) throw err;
127250
- rethrowAsApiError2(err, "Failed to list roadmaps");
126947
+ rethrowAsApiError3(err, "Failed to list roadmaps");
127251
126948
  }
127252
126949
  });
127253
126950
  router.post("/", async (req, res) => {
@@ -127263,7 +126960,7 @@ function createRoadmapRouter(store) {
127263
126960
  res.status(201).json(roadmap);
127264
126961
  } catch (err) {
127265
126962
  if (err instanceof ApiError) throw err;
127266
- rethrowAsApiError2(err, "Failed to create roadmap");
126963
+ rethrowAsApiError3(err, "Failed to create roadmap");
127267
126964
  }
127268
126965
  });
127269
126966
  router.get("/:roadmapId", async (req, res) => {
@@ -127277,7 +126974,7 @@ function createRoadmapRouter(store) {
127277
126974
  res.json(roadmap);
127278
126975
  } catch (err) {
127279
126976
  if (err instanceof ApiError) throw err;
127280
- rethrowAsApiError2(err, "Failed to get roadmap");
126977
+ rethrowAsApiError3(err, "Failed to get roadmap");
127281
126978
  }
127282
126979
  });
127283
126980
  router.patch("/:roadmapId", async (req, res) => {
@@ -127292,7 +126989,7 @@ function createRoadmapRouter(store) {
127292
126989
  res.json(roadmap);
127293
126990
  } catch (err) {
127294
126991
  if (err instanceof ApiError) throw err;
127295
- rethrowAsApiError2(err, "Failed to update roadmap");
126992
+ rethrowAsApiError3(err, "Failed to update roadmap");
127296
126993
  }
127297
126994
  });
127298
126995
  router.delete("/:roadmapId", async (req, res) => {
@@ -127303,7 +127000,7 @@ function createRoadmapRouter(store) {
127303
127000
  res.status(204).send();
127304
127001
  } catch (err) {
127305
127002
  if (err instanceof ApiError) throw err;
127306
- rethrowAsApiError2(err, "Failed to delete roadmap");
127003
+ rethrowAsApiError3(err, "Failed to delete roadmap");
127307
127004
  }
127308
127005
  });
127309
127006
  router.post("/:roadmapId/milestones", async (req, res) => {
@@ -127320,7 +127017,7 @@ function createRoadmapRouter(store) {
127320
127017
  res.status(201).json(milestone);
127321
127018
  } catch (err) {
127322
127019
  if (err instanceof ApiError) throw err;
127323
- rethrowAsApiError2(err, "Failed to create milestone");
127020
+ rethrowAsApiError3(err, "Failed to create milestone");
127324
127021
  }
127325
127022
  });
127326
127023
  router.post("/:roadmapId/milestones/reorder", async (req, res) => {
@@ -127333,7 +127030,7 @@ function createRoadmapRouter(store) {
127333
127030
  res.status(204).send();
127334
127031
  } catch (err) {
127335
127032
  if (err instanceof ApiError) throw err;
127336
- rethrowAsApiError2(err, "Failed to reorder milestones");
127033
+ rethrowAsApiError3(err, "Failed to reorder milestones");
127337
127034
  }
127338
127035
  });
127339
127036
  router.patch("/milestones/:milestoneId", async (req, res) => {
@@ -127348,7 +127045,7 @@ function createRoadmapRouter(store) {
127348
127045
  res.json(milestone);
127349
127046
  } catch (err) {
127350
127047
  if (err instanceof ApiError) throw err;
127351
- rethrowAsApiError2(err, "Failed to update milestone");
127048
+ rethrowAsApiError3(err, "Failed to update milestone");
127352
127049
  }
127353
127050
  });
127354
127051
  router.delete("/milestones/:milestoneId", async (req, res) => {
@@ -127359,7 +127056,7 @@ function createRoadmapRouter(store) {
127359
127056
  res.status(204).send();
127360
127057
  } catch (err) {
127361
127058
  if (err instanceof ApiError) throw err;
127362
- rethrowAsApiError2(err, "Failed to delete milestone");
127059
+ rethrowAsApiError3(err, "Failed to delete milestone");
127363
127060
  }
127364
127061
  });
127365
127062
  router.post("/milestones/:milestoneId/features", async (req, res) => {
@@ -127376,7 +127073,7 @@ function createRoadmapRouter(store) {
127376
127073
  res.status(201).json(feature);
127377
127074
  } catch (err) {
127378
127075
  if (err instanceof ApiError) throw err;
127379
- rethrowAsApiError2(err, "Failed to create feature");
127076
+ rethrowAsApiError3(err, "Failed to create feature");
127380
127077
  }
127381
127078
  });
127382
127079
  router.post("/milestones/:milestoneId/features/reorder", async (req, res) => {
@@ -127397,7 +127094,7 @@ function createRoadmapRouter(store) {
127397
127094
  res.status(204).send();
127398
127095
  } catch (err) {
127399
127096
  if (err instanceof ApiError) throw err;
127400
- rethrowAsApiError2(err, "Failed to reorder features");
127097
+ rethrowAsApiError3(err, "Failed to reorder features");
127401
127098
  }
127402
127099
  });
127403
127100
  router.patch("/features/:featureId", async (req, res) => {
@@ -127412,7 +127109,7 @@ function createRoadmapRouter(store) {
127412
127109
  res.json(feature);
127413
127110
  } catch (err) {
127414
127111
  if (err instanceof ApiError) throw err;
127415
- rethrowAsApiError2(err, "Failed to update feature");
127112
+ rethrowAsApiError3(err, "Failed to update feature");
127416
127113
  }
127417
127114
  });
127418
127115
  router.delete("/features/:featureId", async (req, res) => {
@@ -127423,7 +127120,7 @@ function createRoadmapRouter(store) {
127423
127120
  res.status(204).send();
127424
127121
  } catch (err) {
127425
127122
  if (err instanceof ApiError) throw err;
127426
- rethrowAsApiError2(err, "Failed to delete feature");
127123
+ rethrowAsApiError3(err, "Failed to delete feature");
127427
127124
  }
127428
127125
  });
127429
127126
  router.post("/features/:featureId/move", async (req, res) => {
@@ -127459,7 +127156,7 @@ function createRoadmapRouter(store) {
127459
127156
  res.status(204).send();
127460
127157
  } catch (err) {
127461
127158
  if (err instanceof ApiError) throw err;
127462
- rethrowAsApiError2(err, "Failed to move feature");
127159
+ rethrowAsApiError3(err, "Failed to move feature");
127463
127160
  }
127464
127161
  });
127465
127162
  router.post("/:roadmapId/suggestions/milestones", async (req, res) => {
@@ -127510,7 +127207,7 @@ function createRoadmapRouter(store) {
127510
127207
  }
127511
127208
  } catch (err) {
127512
127209
  if (err instanceof ApiError) throw err;
127513
- rethrowAsApiError2(err, "Failed to generate milestone suggestions");
127210
+ rethrowAsApiError3(err, "Failed to generate milestone suggestions");
127514
127211
  } finally {
127515
127212
  if (routeTimeoutId) clearTimeout(routeTimeoutId);
127516
127213
  }
@@ -127577,7 +127274,7 @@ function createRoadmapRouter(store) {
127577
127274
  }
127578
127275
  } catch (err) {
127579
127276
  if (err instanceof ApiError) throw err;
127580
- rethrowAsApiError2(err, "Failed to generate feature suggestions");
127277
+ rethrowAsApiError3(err, "Failed to generate feature suggestions");
127581
127278
  } finally {
127582
127279
  if (routeTimeoutId) clearTimeout(routeTimeoutId);
127583
127280
  }
@@ -127590,7 +127287,7 @@ function createRoadmapRouter(store) {
127590
127287
  res.json(export_);
127591
127288
  } catch (err) {
127592
127289
  if (err instanceof ApiError) throw err;
127593
- rethrowAsApiError2(err, "Failed to export roadmap");
127290
+ rethrowAsApiError3(err, "Failed to export roadmap");
127594
127291
  }
127595
127292
  });
127596
127293
  router.get("/:roadmapId/handoff", async (req, res) => {
@@ -127608,7 +127305,7 @@ function createRoadmapRouter(store) {
127608
127305
  if (err instanceof Error && err.message.includes("not found")) {
127609
127306
  throw notFound(err.message);
127610
127307
  }
127611
- rethrowAsApiError2(err, "Failed to generate handoff");
127308
+ rethrowAsApiError3(err, "Failed to generate handoff");
127612
127309
  }
127613
127310
  });
127614
127311
  router.get("/:roadmapId/handoff/mission", async (req, res) => {
@@ -127622,7 +127319,7 @@ function createRoadmapRouter(store) {
127622
127319
  if (err instanceof Error && err.message.includes("not found")) {
127623
127320
  throw notFound(err.message);
127624
127321
  }
127625
- rethrowAsApiError2(err, "Failed to generate mission handoff");
127322
+ rethrowAsApiError3(err, "Failed to generate mission handoff");
127626
127323
  }
127627
127324
  });
127628
127325
  router.get("/:roadmapId/milestones/:milestoneId/features/:featureId/handoff/task", async (req, res) => {
@@ -127633,7 +127330,7 @@ function createRoadmapRouter(store) {
127633
127330
  res.json(handoff);
127634
127331
  } catch (err) {
127635
127332
  if (err instanceof ApiError) throw err;
127636
- rethrowAsApiError2(err, "Failed to generate task handoff");
127333
+ rethrowAsApiError3(err, "Failed to generate task handoff");
127637
127334
  }
127638
127335
  });
127639
127336
  return router;
@@ -127650,7 +127347,7 @@ var init_roadmap_routes = __esm({
127650
127347
  // ../dashboard/src/insights-routes.ts
127651
127348
  import { Router as Router5 } from "express";
127652
127349
  import { AsyncLocalStorage as AsyncLocalStorage3 } from "node:async_hooks";
127653
- function rethrowAsApiError3(error, fallbackMessage = "Internal server error") {
127350
+ function rethrowAsApiError4(error, fallbackMessage = "Internal server error") {
127654
127351
  if (error instanceof ApiError) throw error;
127655
127352
  if (error instanceof Error) throw new ApiError(500, error.message);
127656
127353
  throw new ApiError(500, fallbackMessage);
@@ -127686,7 +127383,7 @@ function createInsightsRouter(store) {
127686
127383
  next();
127687
127384
  });
127688
127385
  }).catch((err) => {
127689
- rethrowAsApiError3(err, "Failed to get project store");
127386
+ rethrowAsApiError4(err, "Failed to get project store");
127690
127387
  });
127691
127388
  });
127692
127389
  } else {
@@ -127741,7 +127438,7 @@ function createInsightsRouter(store) {
127741
127438
  const count = store2.countInsights(options);
127742
127439
  res.json({ insights, count });
127743
127440
  } catch (error) {
127744
- rethrowAsApiError3(error, "Failed to list insights");
127441
+ rethrowAsApiError4(error, "Failed to list insights");
127745
127442
  }
127746
127443
  });
127747
127444
  router.post("/run", async (req, res) => {
@@ -127856,7 +127553,7 @@ function createInsightsRouter(store) {
127856
127553
  throw err;
127857
127554
  }
127858
127555
  } catch (error) {
127859
- rethrowAsApiError3(error, "Failed to create insight run");
127556
+ rethrowAsApiError4(error, "Failed to create insight run");
127860
127557
  }
127861
127558
  });
127862
127559
  router.get("/runs", (req, res) => {
@@ -127865,7 +127562,7 @@ function createInsightsRouter(store) {
127865
127562
  const runs = store2.listRuns({});
127866
127563
  res.json({ runs });
127867
127564
  } catch (error) {
127868
- rethrowAsApiError3(error, "Failed to list runs");
127565
+ rethrowAsApiError4(error, "Failed to list runs");
127869
127566
  }
127870
127567
  });
127871
127568
  router.get("/runs/:id", (req, res) => {
@@ -127878,7 +127575,7 @@ function createInsightsRouter(store) {
127878
127575
  }
127879
127576
  res.json(run);
127880
127577
  } catch (error) {
127881
- rethrowAsApiError3(error, "Failed to get run");
127578
+ rethrowAsApiError4(error, "Failed to get run");
127882
127579
  }
127883
127580
  });
127884
127581
  router.get("/:id", (req, res) => {
@@ -127891,7 +127588,7 @@ function createInsightsRouter(store) {
127891
127588
  }
127892
127589
  res.json(insight);
127893
127590
  } catch (error) {
127894
- rethrowAsApiError3(error, "Failed to get insight");
127591
+ rethrowAsApiError4(error, "Failed to get insight");
127895
127592
  }
127896
127593
  });
127897
127594
  router.patch("/:id", (req, res) => {
@@ -127923,7 +127620,7 @@ function createInsightsRouter(store) {
127923
127620
  }
127924
127621
  res.json(insight);
127925
127622
  } catch (error) {
127926
- rethrowAsApiError3(error, "Failed to update insight");
127623
+ rethrowAsApiError4(error, "Failed to update insight");
127927
127624
  }
127928
127625
  });
127929
127626
  router.delete("/:id", (req, res) => {
@@ -127936,7 +127633,7 @@ function createInsightsRouter(store) {
127936
127633
  }
127937
127634
  res.status(204).send();
127938
127635
  } catch (error) {
127939
- rethrowAsApiError3(error, "Failed to delete insight");
127636
+ rethrowAsApiError4(error, "Failed to delete insight");
127940
127637
  }
127941
127638
  });
127942
127639
  router.post("/:id/dismiss", (req, res) => {
@@ -127949,7 +127646,7 @@ function createInsightsRouter(store) {
127949
127646
  }
127950
127647
  res.json(insight);
127951
127648
  } catch (error) {
127952
- rethrowAsApiError3(error, "Failed to dismiss insight");
127649
+ rethrowAsApiError4(error, "Failed to dismiss insight");
127953
127650
  }
127954
127651
  });
127955
127652
  router.post("/:id/create-task", (req, res) => {
@@ -127967,7 +127664,7 @@ function createInsightsRouter(store) {
127967
127664
  suggestedDescription: insight.content ?? ""
127968
127665
  });
127969
127666
  } catch (error) {
127970
- rethrowAsApiError3(error, "Failed to create task from insight");
127667
+ rethrowAsApiError4(error, "Failed to create task from insight");
127971
127668
  }
127972
127669
  });
127973
127670
  return router;
@@ -129166,6 +128863,606 @@ var init_register_integrated_routers = __esm({
129166
128863
  }
129167
128864
  });
129168
128865
 
128866
+ // ../dashboard/src/routes/register-terminal-routes.ts
128867
+ function registerTerminalRoutes(router, deps) {
128868
+ const { getProjectContext: getProjectContext3, terminalSessionManager: terminalSessionManager2, getTerminalService: getTerminalService2 } = deps;
128869
+ router.post("/terminal/exec", async (req, res) => {
128870
+ try {
128871
+ const { command } = req.body;
128872
+ if (!command || typeof command !== "string") {
128873
+ throw badRequest("command is required and must be a string");
128874
+ }
128875
+ if (command.length > 4096) {
128876
+ throw badRequest("command exceeds maximum length of 4096 characters");
128877
+ }
128878
+ const { store: scopedStore } = await getProjectContext3(req);
128879
+ const rootDir = scopedStore.getRootDir();
128880
+ const result = terminalSessionManager2.createSession(command, rootDir);
128881
+ if (result.error) {
128882
+ throw new ApiError(403, result.error);
128883
+ }
128884
+ res.status(201).json({ sessionId: result.sessionId });
128885
+ } catch (err) {
128886
+ if (err instanceof ApiError) {
128887
+ throw err;
128888
+ }
128889
+ rethrowAsApiError(err, "Failed to execute command");
128890
+ }
128891
+ });
128892
+ router.post("/terminal/sessions/:id/kill", (req, res) => {
128893
+ try {
128894
+ const { id } = req.params;
128895
+ const { signal } = req.body;
128896
+ const validSignals = ["SIGTERM", "SIGKILL", "SIGINT"];
128897
+ const killSignal = validSignals.includes(signal) ? signal : "SIGTERM";
128898
+ const killed = terminalSessionManager2.killSession(id, killSignal);
128899
+ if (!killed) {
128900
+ const session = terminalSessionManager2.getSession(id);
128901
+ if (!session) {
128902
+ throw notFound("Session not found");
128903
+ }
128904
+ throw badRequest("Session is not running");
128905
+ }
128906
+ res.json({ killed: true, sessionId: id });
128907
+ } catch (err) {
128908
+ if (err instanceof ApiError) {
128909
+ throw err;
128910
+ }
128911
+ rethrowAsApiError(err);
128912
+ }
128913
+ });
128914
+ router.get("/terminal/sessions/:id", (req, res) => {
128915
+ try {
128916
+ const session = terminalSessionManager2.getSession(req.params.id);
128917
+ if (!session) {
128918
+ throw notFound("Session not found");
128919
+ }
128920
+ res.json({
128921
+ id: session.id,
128922
+ command: session.command,
128923
+ running: session.exitCode === null && !session.killed,
128924
+ exitCode: session.exitCode,
128925
+ output: session.output.join(""),
128926
+ startTime: session.startTime.toISOString()
128927
+ });
128928
+ } catch (err) {
128929
+ if (err instanceof ApiError) {
128930
+ throw err;
128931
+ }
128932
+ rethrowAsApiError(err);
128933
+ }
128934
+ });
128935
+ router.get("/terminal/sessions/:id/stream", (req, res) => {
128936
+ try {
128937
+ const { id } = req.params;
128938
+ const session = terminalSessionManager2.getSession(id);
128939
+ if (!session) {
128940
+ throw notFound("Session not found");
128941
+ }
128942
+ res.setHeader("Content-Type", "text/event-stream");
128943
+ res.setHeader("Cache-Control", "no-cache");
128944
+ res.setHeader("Connection", "keep-alive");
128945
+ res.setHeader("X-Accel-Buffering", "no");
128946
+ res.write(`event: connected
128947
+ data: ${JSON.stringify({ sessionId: id })}
128948
+
128949
+ `);
128950
+ const onOutput = (event) => {
128951
+ if (event.sessionId !== id) return;
128952
+ const eventName = event.type === "exit" ? "terminal:exit" : "terminal:output";
128953
+ const data = JSON.stringify({
128954
+ type: event.type,
128955
+ data: event.data,
128956
+ ...event.exitCode !== void 0 && { exitCode: event.exitCode }
128957
+ });
128958
+ res.write(`event: ${eventName}
128959
+ data: ${data}
128960
+
128961
+ `);
128962
+ if (event.type === "exit") {
128963
+ setTimeout(() => {
128964
+ res.end();
128965
+ }, 100);
128966
+ }
128967
+ };
128968
+ terminalSessionManager2.on("output", onOutput);
128969
+ req.on("close", () => {
128970
+ terminalSessionManager2.off("output", onOutput);
128971
+ });
128972
+ req.on("error", () => {
128973
+ terminalSessionManager2.off("output", onOutput);
128974
+ });
128975
+ } catch (err) {
128976
+ if (err instanceof ApiError) {
128977
+ throw err;
128978
+ }
128979
+ rethrowAsApiError(err);
128980
+ }
128981
+ });
128982
+ router.post("/terminal/sessions", async (req, res) => {
128983
+ try {
128984
+ const { cwd, cols, rows } = req.body;
128985
+ const { store: scopedStore } = await getProjectContext3(req);
128986
+ const terminalService = getTerminalService2(scopedStore.getRootDir());
128987
+ const result = await terminalService.createSession({
128988
+ cwd,
128989
+ cols: typeof cols === "number" ? cols : void 0,
128990
+ rows: typeof rows === "number" ? rows : void 0
128991
+ });
128992
+ if (!result.success) {
128993
+ const statusByCode = {
128994
+ max_sessions: 503,
128995
+ invalid_shell: 400,
128996
+ pty_load_failed: 503,
128997
+ pty_spawn_failed: 500
128998
+ };
128999
+ throw new ApiError(statusByCode[result.code], result.error, { code: result.code });
129000
+ }
129001
+ res.status(201).json({
129002
+ sessionId: result.session.id,
129003
+ shell: result.session.shell,
129004
+ cwd: result.session.cwd
129005
+ });
129006
+ } catch (err) {
129007
+ if (err instanceof ApiError) {
129008
+ throw err;
129009
+ }
129010
+ rethrowAsApiError(err, "Failed to create terminal session");
129011
+ }
129012
+ });
129013
+ router.get("/terminal/sessions", async (req, res) => {
129014
+ try {
129015
+ const { store: scopedStore } = await getProjectContext3(req);
129016
+ const terminalService = getTerminalService2(scopedStore.getRootDir());
129017
+ const sessions6 = terminalService.getAllSessions();
129018
+ res.json(
129019
+ sessions6.map((session) => ({
129020
+ id: session.id,
129021
+ cwd: session.cwd,
129022
+ shell: session.shell,
129023
+ createdAt: session.createdAt.toISOString(),
129024
+ lastActivityAt: session.lastActivityAt.toISOString()
129025
+ }))
129026
+ );
129027
+ } catch (err) {
129028
+ if (err instanceof ApiError) {
129029
+ throw err;
129030
+ }
129031
+ rethrowAsApiError(err, "Failed to list sessions");
129032
+ }
129033
+ });
129034
+ router.delete("/terminal/sessions/:id", async (req, res) => {
129035
+ try {
129036
+ const { id } = req.params;
129037
+ const { store: scopedStore } = await getProjectContext3(req);
129038
+ const terminalService = getTerminalService2(scopedStore.getRootDir());
129039
+ const killed = terminalService.killSession(id);
129040
+ if (!killed) {
129041
+ const session = terminalService.getSession(id);
129042
+ if (!session) {
129043
+ throw notFound("Session not found");
129044
+ }
129045
+ throw badRequest("Failed to kill session");
129046
+ }
129047
+ res.json({ killed: true });
129048
+ } catch (err) {
129049
+ if (err instanceof ApiError) {
129050
+ throw err;
129051
+ }
129052
+ rethrowAsApiError(err);
129053
+ }
129054
+ });
129055
+ }
129056
+ var init_register_terminal_routes = __esm({
129057
+ "../dashboard/src/routes/register-terminal-routes.ts"() {
129058
+ "use strict";
129059
+ init_api_error();
129060
+ }
129061
+ });
129062
+
129063
+ // ../dashboard/src/routes/register-session-diff-routes.ts
129064
+ import { access as access8 } from "node:fs/promises";
129065
+ function registerSessionDiffRoutes(router, deps) {
129066
+ const { getProjectContext: getProjectContext3 } = deps;
129067
+ router.get("/tasks/:id/session-files", async (req, res) => {
129068
+ try {
129069
+ const { store: scopedStore } = await getProjectContext3(req);
129070
+ const task = await scopedStore.getTask(req.params.id);
129071
+ if (!task) {
129072
+ res.status(404).json({ error: "Task not found" });
129073
+ return;
129074
+ }
129075
+ if (!task.worktree) {
129076
+ res.json([]);
129077
+ return;
129078
+ }
129079
+ let worktreeExists = false;
129080
+ try {
129081
+ await access8(task.worktree);
129082
+ worktreeExists = true;
129083
+ } catch {
129084
+ worktreeExists = false;
129085
+ }
129086
+ if (!worktreeExists) {
129087
+ res.json([]);
129088
+ return;
129089
+ }
129090
+ const worktree = task.worktree;
129091
+ const cached = sessionFilesCache.get(task.id);
129092
+ if (cached && cached.expiresAt > Date.now()) {
129093
+ res.json(cached.files);
129094
+ return;
129095
+ }
129096
+ let files = [];
129097
+ try {
129098
+ const fileSet = /* @__PURE__ */ new Set();
129099
+ const baseRef = await resolveDiffBase(task, worktree);
129100
+ if (baseRef) {
129101
+ const committedOutput = (await runGitCommand(["diff", "--name-only", `${baseRef}..HEAD`], worktree, 5e3)).trim();
129102
+ for (const file of committedOutput.split("\n").filter(Boolean)) {
129103
+ fileSet.add(file);
129104
+ }
129105
+ }
129106
+ const stagedOutput = (await runGitCommand(["diff", "--cached", "--name-only"], worktree, 5e3)).trim();
129107
+ for (const file of stagedOutput.split("\n").filter(Boolean)) {
129108
+ fileSet.add(file);
129109
+ }
129110
+ const workingTreeOutput = (await runGitCommand(["diff", "--name-only"], worktree, 5e3)).trim();
129111
+ for (const file of workingTreeOutput.split("\n").filter(Boolean)) {
129112
+ fileSet.add(file);
129113
+ }
129114
+ const untrackedOutput = (await runGitCommand(["ls-files", "--others", "--exclude-standard"], worktree, 5e3)).trim();
129115
+ for (const file of untrackedOutput.split("\n").filter(Boolean)) {
129116
+ fileSet.add(file);
129117
+ }
129118
+ files = Array.from(fileSet);
129119
+ } catch {
129120
+ files = [];
129121
+ }
129122
+ sessionFilesCache.set(task.id, {
129123
+ files,
129124
+ expiresAt: Date.now() + 1e4
129125
+ });
129126
+ res.json(files);
129127
+ } catch (err) {
129128
+ if (err instanceof ApiError) {
129129
+ throw err;
129130
+ }
129131
+ if (err.code === "ENOENT") {
129132
+ throw notFound(`Task ${req.params.id} not found`);
129133
+ }
129134
+ rethrowAsApiError(err, "Internal server error");
129135
+ }
129136
+ });
129137
+ router.get("/tasks/:id/diff", async (req, res) => {
129138
+ try {
129139
+ const { store: scopedStore } = await getProjectContext3(req);
129140
+ const task = await scopedStore.getTask(req.params.id);
129141
+ if (!task) {
129142
+ res.status(404).json({ error: "Task not found" });
129143
+ return;
129144
+ }
129145
+ if (task.column === "done" && task.mergeDetails?.commitSha) {
129146
+ const rootDir = scopedStore.getRootDir();
129147
+ const sha = task.mergeDetails.commitSha;
129148
+ let mergeBase;
129149
+ try {
129150
+ mergeBase = (await runGitCommand(["rev-parse", `${sha}^`], rootDir, 5e3)).trim();
129151
+ } catch {
129152
+ res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
129153
+ return;
129154
+ }
129155
+ const nameStatus = (await runGitCommand(["diff", "--name-status", `${mergeBase}..${sha}`], rootDir, 1e4)).trim();
129156
+ const doneFiles = [];
129157
+ for (const line of nameStatus.split("\n").filter(Boolean)) {
129158
+ const parts = line.split(" ");
129159
+ const statusCode = parts[0] ?? "M";
129160
+ const filePath = parts[1] ?? "";
129161
+ if (!filePath) continue;
129162
+ let status = "modified";
129163
+ if (statusCode.startsWith("A")) status = "added";
129164
+ else if (statusCode.startsWith("D")) status = "deleted";
129165
+ let patch = "";
129166
+ try {
129167
+ patch = await runGitCommand(["diff", `${mergeBase}..${sha}`, "--", filePath], rootDir, 1e4);
129168
+ } catch {
129169
+ }
129170
+ const additions = (patch.match(/^\+[^+]/gm) || []).length;
129171
+ const deletions = (patch.match(/^-[^-]/gm) || []).length;
129172
+ doneFiles.push({ path: filePath, status, additions, deletions, patch });
129173
+ }
129174
+ const doneStats = {
129175
+ filesChanged: doneFiles.length,
129176
+ additions: doneFiles.reduce((s, f) => s + f.additions, 0),
129177
+ deletions: doneFiles.reduce((s, f) => s + f.deletions, 0)
129178
+ };
129179
+ res.json({ files: doneFiles, stats: doneStats });
129180
+ return;
129181
+ }
129182
+ if (task.column === "done") {
129183
+ const md = task.mergeDetails;
129184
+ res.json({
129185
+ files: [],
129186
+ stats: {
129187
+ filesChanged: md?.filesChanged ?? 0,
129188
+ additions: md?.insertions ?? 0,
129189
+ deletions: md?.deletions ?? 0
129190
+ }
129191
+ });
129192
+ return;
129193
+ }
129194
+ const worktree = typeof req.query.worktree === "string" ? req.query.worktree : void 0;
129195
+ const resolvedWorktree = worktree || task.worktree;
129196
+ if (!resolvedWorktree) {
129197
+ res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
129198
+ return;
129199
+ }
129200
+ let worktreeExists = false;
129201
+ try {
129202
+ await access8(resolvedWorktree);
129203
+ worktreeExists = true;
129204
+ } catch {
129205
+ worktreeExists = false;
129206
+ }
129207
+ if (!worktreeExists) {
129208
+ res.json({ files: [], stats: { filesChanged: 0, additions: 0, deletions: 0 } });
129209
+ return;
129210
+ }
129211
+ const cwd = resolvedWorktree;
129212
+ const diffBase = await resolveDiffBase(task, cwd);
129213
+ const diffRange = diffBase ? `${diffBase}..HEAD` : "HEAD";
129214
+ const fileMap = /* @__PURE__ */ new Map();
129215
+ if (diffBase) {
129216
+ try {
129217
+ const committedOutput = (await runGitCommand(["diff", "--name-status", `${diffBase}..HEAD`], cwd, 1e4)).trim();
129218
+ for (const line of committedOutput.split("\n").filter(Boolean)) {
129219
+ const parts = line.split(" ");
129220
+ fileMap.set(parts[1] ?? "", parts[0] ?? "M");
129221
+ }
129222
+ } catch {
129223
+ }
129224
+ }
129225
+ try {
129226
+ const stagedOutput = (await runGitCommand(["diff", "--cached", "--name-status"], cwd, 1e4)).trim();
129227
+ for (const line of stagedOutput.split("\n").filter(Boolean)) {
129228
+ const parts = line.split(" ");
129229
+ const filePath = parts[1] ?? "";
129230
+ if (filePath && !fileMap.has(filePath)) {
129231
+ fileMap.set(filePath, parts[0] ?? "M");
129232
+ }
129233
+ }
129234
+ } catch {
129235
+ }
129236
+ try {
129237
+ const workingTreeOutput = (await runGitCommand(["diff", "--name-status"], cwd, 1e4)).trim();
129238
+ for (const line of workingTreeOutput.split("\n").filter(Boolean)) {
129239
+ const parts = line.split(" ");
129240
+ const filePath = parts[1] ?? "";
129241
+ if (filePath && !fileMap.has(filePath)) {
129242
+ fileMap.set(filePath, parts[0] ?? "M");
129243
+ }
129244
+ }
129245
+ } catch {
129246
+ }
129247
+ try {
129248
+ const untrackedOutput = (await runGitCommand(["ls-files", "--others", "--exclude-standard"], cwd, 1e4)).trim();
129249
+ for (const line of untrackedOutput.split("\n").filter(Boolean)) {
129250
+ fileMap.set(line, "U");
129251
+ }
129252
+ } catch {
129253
+ }
129254
+ const files = [];
129255
+ for (const [filePath, statusCode] of fileMap) {
129256
+ if (!filePath) continue;
129257
+ let status;
129258
+ if (statusCode.startsWith("A") || statusCode === "U") status = "added";
129259
+ else if (statusCode.startsWith("D")) status = "deleted";
129260
+ else status = "modified";
129261
+ let patch = "";
129262
+ try {
129263
+ if (statusCode === "U") {
129264
+ patch = await runGitCommand(["diff", "--no-index", "/dev/null", filePath], cwd, 1e4).catch(() => "");
129265
+ } else {
129266
+ patch = await runGitCommand(["diff", diffRange, "--", filePath], cwd, 1e4);
129267
+ }
129268
+ } catch {
129269
+ }
129270
+ const additions = (patch.match(/^\+[^+]/gm) || []).length;
129271
+ const deletions = (patch.match(/^-[^-]/gm) || []).length;
129272
+ files.push({ path: filePath, status, additions, deletions, patch });
129273
+ }
129274
+ const stats = {
129275
+ filesChanged: files.length,
129276
+ additions: files.reduce((sum, f) => sum + f.additions, 0),
129277
+ deletions: files.reduce((sum, f) => sum + f.deletions, 0)
129278
+ };
129279
+ res.json({ files, stats });
129280
+ } catch (err) {
129281
+ if (err instanceof ApiError) {
129282
+ throw err;
129283
+ }
129284
+ rethrowAsApiError(err);
129285
+ }
129286
+ });
129287
+ router.get("/tasks/:id/file-diffs", async (req, res) => {
129288
+ try {
129289
+ const { store: scopedStore } = await getProjectContext3(req);
129290
+ const task = await scopedStore.getTask(req.params.id);
129291
+ if (!task) {
129292
+ res.status(404).json({ error: "Task not found" });
129293
+ return;
129294
+ }
129295
+ if (task.column === "done" && task.mergeDetails?.commitSha) {
129296
+ const rootDir = scopedStore.getRootDir();
129297
+ const sha = task.mergeDetails.commitSha;
129298
+ let mergeBase;
129299
+ try {
129300
+ mergeBase = (await runGitCommand(["rev-parse", `${sha}^`], rootDir, 5e3)).trim();
129301
+ } catch {
129302
+ res.json([]);
129303
+ return;
129304
+ }
129305
+ try {
129306
+ const nameStatus = (await runGitCommand(["diff", "--name-status", `${mergeBase}..${sha}`], rootDir, 5e3)).trim();
129307
+ const doneFiles = [];
129308
+ for (const line of nameStatus.split("\n").filter(Boolean)) {
129309
+ const parts = line.split(" ");
129310
+ const statusCode = parts[0] ?? "M";
129311
+ const filePath = parts[1] ?? "";
129312
+ let status = "modified";
129313
+ if (statusCode.startsWith("A")) status = "added";
129314
+ else if (statusCode.startsWith("D")) status = "deleted";
129315
+ else if (statusCode.startsWith("R")) status = "renamed";
129316
+ let diff = "";
129317
+ try {
129318
+ diff = await runGitCommand(["diff", `${mergeBase}..${sha}`, "--", filePath], rootDir, 5e3);
129319
+ } catch {
129320
+ }
129321
+ doneFiles.push({ path: filePath, status, diff });
129322
+ }
129323
+ res.json(doneFiles);
129324
+ } catch {
129325
+ res.json([]);
129326
+ }
129327
+ return;
129328
+ }
129329
+ if (task.column === "done") {
129330
+ res.json([]);
129331
+ return;
129332
+ }
129333
+ if (!task.worktree) {
129334
+ res.json([]);
129335
+ return;
129336
+ }
129337
+ let worktreeExists = false;
129338
+ try {
129339
+ await access8(task.worktree);
129340
+ worktreeExists = true;
129341
+ } catch {
129342
+ worktreeExists = false;
129343
+ }
129344
+ if (!worktreeExists) {
129345
+ res.json([]);
129346
+ return;
129347
+ }
129348
+ const worktree = task.worktree;
129349
+ const cached = fileDiffsCache.get(task.id);
129350
+ if (cached && cached.expiresAt > Date.now()) {
129351
+ res.json(cached.files);
129352
+ return;
129353
+ }
129354
+ const cwd = worktree;
129355
+ const diffBase = await resolveDiffBase(task, cwd);
129356
+ const fileMap = /* @__PURE__ */ new Map();
129357
+ if (diffBase) {
129358
+ try {
129359
+ const committedOutput = (await runGitCommand(["diff", "--name-status", `${diffBase}..HEAD`], cwd, 5e3)).trim();
129360
+ for (const line of committedOutput.split("\n").filter(Boolean)) {
129361
+ const parts = line.split(" ");
129362
+ const statusCode = parts[0] ?? "M";
129363
+ if (statusCode.startsWith("R")) {
129364
+ fileMap.set(parts[2] ?? parts[1] ?? "", { statusCode, oldPath: parts[1] });
129365
+ } else {
129366
+ fileMap.set(parts[1] ?? "", { statusCode });
129367
+ }
129368
+ }
129369
+ } catch {
129370
+ }
129371
+ }
129372
+ try {
129373
+ const stagedOutput = (await runGitCommand(["diff", "--cached", "--name-status"], cwd, 5e3)).trim();
129374
+ for (const line of stagedOutput.split("\n").filter(Boolean)) {
129375
+ const parts = line.split(" ");
129376
+ const statusCode = parts[0] ?? "M";
129377
+ const filePath = parts[1] ?? "";
129378
+ if (filePath && !fileMap.has(filePath)) {
129379
+ if (statusCode.startsWith("R")) {
129380
+ fileMap.set(filePath, { statusCode, oldPath: parts[2] });
129381
+ } else {
129382
+ fileMap.set(filePath, { statusCode });
129383
+ }
129384
+ }
129385
+ }
129386
+ } catch {
129387
+ }
129388
+ try {
129389
+ const workingTreeOutput = (await runGitCommand(["diff", "--name-status"], cwd, 5e3)).trim();
129390
+ for (const line of workingTreeOutput.split("\n").filter(Boolean)) {
129391
+ const parts = line.split(" ");
129392
+ const statusCode = parts[0] ?? "M";
129393
+ const filePath = parts[1] ?? "";
129394
+ if (filePath && !fileMap.has(filePath)) {
129395
+ if (statusCode.startsWith("R")) {
129396
+ fileMap.set(filePath, { statusCode, oldPath: parts[2] });
129397
+ } else {
129398
+ fileMap.set(filePath, { statusCode });
129399
+ }
129400
+ }
129401
+ }
129402
+ } catch {
129403
+ }
129404
+ try {
129405
+ const untrackedOutput = (await runGitCommand(["ls-files", "--others", "--exclude-standard"], cwd, 5e3)).trim();
129406
+ for (const line of untrackedOutput.split("\n").filter(Boolean)) {
129407
+ if (line && !fileMap.has(line)) {
129408
+ fileMap.set(line, { statusCode: "U", isUntracked: true });
129409
+ }
129410
+ }
129411
+ } catch {
129412
+ }
129413
+ const diffRange = diffBase ? `${diffBase}..HEAD` : "HEAD";
129414
+ const files = [];
129415
+ for (const [filePath, { statusCode, oldPath, isUntracked }] of fileMap.entries()) {
129416
+ let status = "modified";
129417
+ if (statusCode.startsWith("A") || statusCode === "U") {
129418
+ status = "added";
129419
+ } else if (statusCode.startsWith("D")) {
129420
+ status = "deleted";
129421
+ } else if (statusCode.startsWith("R")) {
129422
+ status = "renamed";
129423
+ }
129424
+ let diff = "";
129425
+ try {
129426
+ if (isUntracked) {
129427
+ diff = await runGitCommand(["diff", "--no-index", "/dev/null", filePath], cwd, 5e3).catch(() => "");
129428
+ } else {
129429
+ diff = await runGitCommand(["diff", diffRange, "--", filePath], cwd, 5e3);
129430
+ }
129431
+ } catch {
129432
+ diff = "";
129433
+ }
129434
+ if (!diff && !isUntracked) {
129435
+ continue;
129436
+ }
129437
+ files.push(oldPath ? { path: filePath, status, diff, oldPath } : { path: filePath, status, diff });
129438
+ }
129439
+ fileDiffsCache.set(task.id, {
129440
+ files,
129441
+ expiresAt: Date.now() + 1e4
129442
+ });
129443
+ res.json(files);
129444
+ } catch (err) {
129445
+ if (err instanceof ApiError) {
129446
+ throw err;
129447
+ }
129448
+ if (err.code === "ENOENT") {
129449
+ throw notFound(`Task ${req.params.id} not found`);
129450
+ }
129451
+ rethrowAsApiError(err, "Internal server error");
129452
+ }
129453
+ });
129454
+ }
129455
+ var sessionFilesCache, fileDiffsCache;
129456
+ var init_register_session_diff_routes = __esm({
129457
+ "../dashboard/src/routes/register-session-diff-routes.ts"() {
129458
+ "use strict";
129459
+ init_api_error();
129460
+ init_resolve_diff_base();
129461
+ sessionFilesCache = /* @__PURE__ */ new Map();
129462
+ fileDiffsCache = /* @__PURE__ */ new Map();
129463
+ }
129464
+ });
129465
+
129169
129466
  // ../dashboard/src/ai-refine.ts
129170
129467
  var ai_refine_exports = {};
129171
129468
  __export(ai_refine_exports, {
@@ -129526,47 +129823,6 @@ function assertConsistentOptionalPair(provider, modelId, pairName) {
129526
129823
  modelId: normalizedModelId
129527
129824
  };
129528
129825
  }
129529
- function rethrowAsApiError4(error, fallbackMessage = "Internal server error") {
129530
- if (error instanceof ApiError) {
129531
- throw error;
129532
- }
129533
- if (error instanceof Error && error.message) {
129534
- throw internalError(error.message);
129535
- }
129536
- throw internalError(fallbackMessage);
129537
- }
129538
- async function resolveDiffBase(task, cwd, headRef = "HEAD", runGit = runGitCommand) {
129539
- const baseBranch = task.baseBranch ?? "main";
129540
- let mergeBase;
129541
- try {
129542
- try {
129543
- mergeBase = (await runGit(["merge-base", headRef, baseBranch], cwd, 5e3)).trim() || void 0;
129544
- } catch {
129545
- mergeBase = (await runGit(["merge-base", headRef, `origin/${baseBranch}`], cwd, 5e3)).trim() || void 0;
129546
- }
129547
- } catch {
129548
- }
129549
- if (mergeBase) {
129550
- try {
129551
- const head = (await runGit(["rev-parse", headRef], cwd, 5e3)).trim();
129552
- if (head && head !== mergeBase) return mergeBase;
129553
- } catch {
129554
- return mergeBase;
129555
- }
129556
- }
129557
- if (task.baseCommitSha) {
129558
- try {
129559
- await runGit(["merge-base", "--is-ancestor", task.baseCommitSha, headRef], cwd, 5e3);
129560
- return task.baseCommitSha;
129561
- } catch {
129562
- }
129563
- }
129564
- try {
129565
- return (await runGit(["rev-parse", `${headRef}~1`], cwd, 5e3)).trim() || void 0;
129566
- } catch {
129567
- return void 0;
129568
- }
129569
- }
129570
129826
  function slugifyPresetName(name) {
129571
129827
  const slug = name.trim().toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/-+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 32);
129572
129828
  return slug || "preset";
@@ -129875,7 +130131,7 @@ function createApiRoutes(store, options) {
129875
130131
  resolveAutomationStore,
129876
130132
  resolveRoutineStore,
129877
130133
  resolveRoutineRunner,
129878
- rethrowAsApiError: rethrowAsApiError4
130134
+ rethrowAsApiError
129879
130135
  };
129880
130136
  const githubToken = options?.githubToken ?? process.env.GITHUB_TOKEN;
129881
130137
  const aiSessionStore = options?.aiSessionStore;
@@ -129892,7 +130148,6 @@ function createApiRoutes(store, options) {
129892
130148
  validateOptionalModelField,
129893
130149
  normalizeModelSelectionPair,
129894
130150
  runGitCommand,
129895
- resolveDiffBase,
129896
130151
  trimTaskDetailActivityLog,
129897
130152
  triggerCommentWakeForAssignedAgent: (...args) => triggerCommentWakeForAssignedAgent(...args)
129898
130153
  });
@@ -129909,10 +130164,8 @@ function createApiRoutes(store, options) {
129909
130164
  });
129910
130165
  registerMessagingScriptRoutes(routeContext);
129911
130166
  registerGitGitHubRoutes(routeContext);
129912
- registerFileWorkspaceRoutes(routeContext, {
129913
- runGitCommand,
129914
- resolveDiffBase
129915
- });
130167
+ registerSessionDiffRoutes(router, { getProjectContext: getProjectContext3 });
130168
+ registerFileWorkspaceRoutes(routeContext);
129916
130169
  registerAgentsProjectsNodesRoutes(routeContext);
129917
130170
  registerPluginsAutomationRoutes(routeContext);
129918
130171
  registerProxyRoutes(routeContext);
@@ -129998,7 +130251,7 @@ function createApiRoutes(store, options) {
129998
130251
  if (err instanceof ApiError) {
129999
130252
  throw err;
130000
130253
  }
130001
- rethrowAsApiError4(err);
130254
+ rethrowAsApiError(err);
130002
130255
  }
130003
130256
  });
130004
130257
  router.put("/pi-settings", async (req, res) => {
@@ -130046,7 +130299,7 @@ function createApiRoutes(store, options) {
130046
130299
  if (err instanceof ApiError) {
130047
130300
  throw err;
130048
130301
  }
130049
- rethrowAsApiError4(err);
130302
+ rethrowAsApiError(err);
130050
130303
  }
130051
130304
  });
130052
130305
  router.post("/pi-settings/packages", async (req, res) => {
@@ -130072,7 +130325,7 @@ function createApiRoutes(store, options) {
130072
130325
  if (err instanceof ApiError) {
130073
130326
  throw err;
130074
130327
  }
130075
- rethrowAsApiError4(err);
130328
+ rethrowAsApiError(err);
130076
130329
  }
130077
130330
  });
130078
130331
  router.post("/pi-settings/reinstall-fusion", async (_req, res) => {
@@ -130093,7 +130346,7 @@ function createApiRoutes(store, options) {
130093
130346
  if (err instanceof ApiError) {
130094
130347
  throw err;
130095
130348
  }
130096
- rethrowAsApiError4(err);
130349
+ rethrowAsApiError(err);
130097
130350
  }
130098
130351
  });
130099
130352
  router.get("/executor/stats", async (req, res) => {
@@ -130118,7 +130371,7 @@ function createApiRoutes(store, options) {
130118
130371
  if (err instanceof ApiError) {
130119
130372
  throw err;
130120
130373
  }
130121
- rethrowAsApiError4(err);
130374
+ rethrowAsApiError(err);
130122
130375
  }
130123
130376
  });
130124
130377
  router.get("/backups", async (req, res) => {
@@ -130138,7 +130391,7 @@ function createApiRoutes(store, options) {
130138
130391
  if (err instanceof ApiError) {
130139
130392
  throw err;
130140
130393
  }
130141
- rethrowAsApiError4(err, "Failed to list backups");
130394
+ rethrowAsApiError(err, "Failed to list backups");
130142
130395
  }
130143
130396
  });
130144
130397
  router.post("/backups", async (req, res) => {
@@ -130161,196 +130414,15 @@ function createApiRoutes(store, options) {
130161
130414
  if (err instanceof ApiError) {
130162
130415
  throw err;
130163
130416
  }
130164
- rethrowAsApiError4(err, "Failed to create backup");
130417
+ rethrowAsApiError(err, "Failed to create backup");
130165
130418
  }
130166
130419
  });
130167
130420
  registerModelRoutes(routeContext);
130168
130421
  registerAuthRoutes(routeContext);
130169
- router.post("/terminal/exec", async (req, res) => {
130170
- try {
130171
- const { command } = req.body;
130172
- if (!command || typeof command !== "string") {
130173
- throw badRequest("command is required and must be a string");
130174
- }
130175
- if (command.length > 4096) {
130176
- throw badRequest("command exceeds maximum length of 4096 characters");
130177
- }
130178
- const { store: scopedStore } = await getProjectContext3(req);
130179
- const rootDir = scopedStore.getRootDir();
130180
- const result = terminalSessionManager.createSession(command, rootDir);
130181
- if (result.error) {
130182
- throw new ApiError(403, result.error);
130183
- }
130184
- res.status(201).json({ sessionId: result.sessionId });
130185
- } catch (err) {
130186
- if (err instanceof ApiError) {
130187
- throw err;
130188
- }
130189
- rethrowAsApiError4(err, "Failed to execute command");
130190
- }
130191
- });
130192
- router.post("/terminal/sessions/:id/kill", (req, res) => {
130193
- try {
130194
- const { id } = req.params;
130195
- const { signal } = req.body;
130196
- const validSignals = ["SIGTERM", "SIGKILL", "SIGINT"];
130197
- const killSignal = validSignals.includes(signal) ? signal : "SIGTERM";
130198
- const killed = terminalSessionManager.killSession(id, killSignal);
130199
- if (!killed) {
130200
- const session = terminalSessionManager.getSession(id);
130201
- if (!session) {
130202
- throw notFound("Session not found");
130203
- }
130204
- throw badRequest("Session is not running");
130205
- }
130206
- res.json({ killed: true, sessionId: id });
130207
- } catch (err) {
130208
- if (err instanceof ApiError) {
130209
- throw err;
130210
- }
130211
- rethrowAsApiError4(err);
130212
- }
130213
- });
130214
- router.get("/terminal/sessions/:id", (req, res) => {
130215
- try {
130216
- const session = terminalSessionManager.getSession(req.params.id);
130217
- if (!session) {
130218
- throw notFound("Session not found");
130219
- }
130220
- res.json({
130221
- id: session.id,
130222
- command: session.command,
130223
- running: session.exitCode === null && !session.killed,
130224
- exitCode: session.exitCode,
130225
- output: session.output.join(""),
130226
- startTime: session.startTime.toISOString()
130227
- });
130228
- } catch (err) {
130229
- if (err instanceof ApiError) {
130230
- throw err;
130231
- }
130232
- rethrowAsApiError4(err);
130233
- }
130234
- });
130235
- router.get("/terminal/sessions/:id/stream", (req, res) => {
130236
- try {
130237
- const { id } = req.params;
130238
- const session = terminalSessionManager.getSession(id);
130239
- if (!session) {
130240
- throw notFound("Session not found");
130241
- }
130242
- res.setHeader("Content-Type", "text/event-stream");
130243
- res.setHeader("Cache-Control", "no-cache");
130244
- res.setHeader("Connection", "keep-alive");
130245
- res.setHeader("X-Accel-Buffering", "no");
130246
- res.write(`event: connected
130247
- data: ${JSON.stringify({ sessionId: id })}
130248
-
130249
- `);
130250
- const onOutput = (event) => {
130251
- if (event.sessionId !== id) return;
130252
- const eventName = event.type === "exit" ? "terminal:exit" : "terminal:output";
130253
- const data = JSON.stringify({
130254
- type: event.type,
130255
- data: event.data,
130256
- ...event.exitCode !== void 0 && { exitCode: event.exitCode }
130257
- });
130258
- res.write(`event: ${eventName}
130259
- data: ${data}
130260
-
130261
- `);
130262
- if (event.type === "exit") {
130263
- setTimeout(() => {
130264
- res.end();
130265
- }, 100);
130266
- }
130267
- };
130268
- terminalSessionManager.on("output", onOutput);
130269
- req.on("close", () => {
130270
- terminalSessionManager.off("output", onOutput);
130271
- });
130272
- req.on("error", () => {
130273
- terminalSessionManager.off("output", onOutput);
130274
- });
130275
- } catch (err) {
130276
- if (err instanceof ApiError) {
130277
- throw err;
130278
- }
130279
- rethrowAsApiError4(err);
130280
- }
130281
- });
130282
- router.post("/terminal/sessions", async (req, res) => {
130283
- try {
130284
- const { cwd, cols, rows } = req.body;
130285
- const { store: scopedStore } = await getProjectContext3(req);
130286
- const terminalService = getTerminalService(scopedStore.getRootDir());
130287
- const result = await terminalService.createSession({
130288
- cwd,
130289
- cols: typeof cols === "number" ? cols : void 0,
130290
- rows: typeof rows === "number" ? rows : void 0
130291
- });
130292
- if (!result.success) {
130293
- const statusByCode = {
130294
- max_sessions: 503,
130295
- invalid_shell: 400,
130296
- pty_load_failed: 503,
130297
- pty_spawn_failed: 500
130298
- };
130299
- throw new ApiError(statusByCode[result.code], result.error, { code: result.code });
130300
- }
130301
- res.status(201).json({
130302
- sessionId: result.session.id,
130303
- shell: result.session.shell,
130304
- cwd: result.session.cwd
130305
- });
130306
- } catch (err) {
130307
- if (err instanceof ApiError) {
130308
- throw err;
130309
- }
130310
- rethrowAsApiError4(err, "Failed to create terminal session");
130311
- }
130312
- });
130313
- router.get("/terminal/sessions", async (req, res) => {
130314
- try {
130315
- const { store: scopedStore } = await getProjectContext3(req);
130316
- const terminalService = getTerminalService(scopedStore.getRootDir());
130317
- const sessions6 = terminalService.getAllSessions();
130318
- res.json(
130319
- sessions6.map((s) => ({
130320
- id: s.id,
130321
- cwd: s.cwd,
130322
- shell: s.shell,
130323
- createdAt: s.createdAt.toISOString(),
130324
- lastActivityAt: s.lastActivityAt.toISOString()
130325
- }))
130326
- );
130327
- } catch (err) {
130328
- if (err instanceof ApiError) {
130329
- throw err;
130330
- }
130331
- rethrowAsApiError4(err, "Failed to list sessions");
130332
- }
130333
- });
130334
- router.delete("/terminal/sessions/:id", async (req, res) => {
130335
- try {
130336
- const { id } = req.params;
130337
- const { store: scopedStore } = await getProjectContext3(req);
130338
- const terminalService = getTerminalService(scopedStore.getRootDir());
130339
- const killed = terminalService.killSession(id);
130340
- if (!killed) {
130341
- const session = terminalService.getSession(id);
130342
- if (!session) {
130343
- throw notFound("Session not found");
130344
- }
130345
- throw badRequest("Failed to kill session");
130346
- }
130347
- res.json({ killed: true });
130348
- } catch (err) {
130349
- if (err instanceof ApiError) {
130350
- throw err;
130351
- }
130352
- rethrowAsApiError4(err);
130353
- }
130422
+ registerTerminalRoutes(router, {
130423
+ getProjectContext: getProjectContext3,
130424
+ terminalSessionManager,
130425
+ getTerminalService
130354
130426
  });
130355
130427
  router.post("/ai/refine-text", async (req, res) => {
130356
130428
  try {
@@ -130399,9 +130471,9 @@ data: ${data}
130399
130471
  if (err instanceof Error && err.name === "RateLimitError") {
130400
130472
  throw rateLimited(err.message);
130401
130473
  } else if (err instanceof Error && err.name === "AiServiceError") {
130402
- rethrowAsApiError4(err, "AI service error");
130474
+ rethrowAsApiError(err, "AI service error");
130403
130475
  } else {
130404
- rethrowAsApiError4(err, "Failed to refine text");
130476
+ rethrowAsApiError(err, "Failed to refine text");
130405
130477
  }
130406
130478
  }
130407
130479
  });
@@ -130473,7 +130545,7 @@ data: ${data}
130473
130545
  summarizeDiagnostics.errorFromException("Unexpected summarize title error", err, {
130474
130546
  operation: "summarize-title"
130475
130547
  });
130476
- rethrowAsApiError4(err, "Failed to generate title");
130548
+ rethrowAsApiError(err, "Failed to generate title");
130477
130549
  }
130478
130550
  }
130479
130551
  });
@@ -130496,7 +130568,7 @@ data: ${data}
130496
130568
  if (err instanceof ApiError) {
130497
130569
  throw err;
130498
130570
  }
130499
- rethrowAsApiError4(err);
130571
+ rethrowAsApiError(err);
130500
130572
  }
130501
130573
  });
130502
130574
  router.post("/automations", async (req, res) => {
@@ -130546,7 +130618,7 @@ data: ${data}
130546
130618
  if (err instanceof ApiError) {
130547
130619
  throw err;
130548
130620
  }
130549
- rethrowAsApiError4(err);
130621
+ rethrowAsApiError(err);
130550
130622
  }
130551
130623
  });
130552
130624
  router.get("/automations/:id", async (req, res) => {
@@ -130566,7 +130638,7 @@ data: ${data}
130566
130638
  if (err.code === "ENOENT") {
130567
130639
  throw notFound("Schedule not found");
130568
130640
  }
130569
- rethrowAsApiError4(err);
130641
+ rethrowAsApiError(err);
130570
130642
  }
130571
130643
  });
130572
130644
  router.patch("/automations/:id", async (req, res) => {
@@ -130613,7 +130685,7 @@ data: ${data}
130613
130685
  if ((err instanceof Error ? err.message : String(err)).includes("cannot be empty") || (err instanceof Error ? err.message : String(err)).includes("Invalid cron")) {
130614
130686
  throw badRequest(err instanceof Error ? err.message : String(err));
130615
130687
  }
130616
- rethrowAsApiError4(err);
130688
+ rethrowAsApiError(err);
130617
130689
  }
130618
130690
  });
130619
130691
  router.delete("/automations/:id", async (req, res) => {
@@ -130636,7 +130708,7 @@ data: ${data}
130636
130708
  if (err.code === "ENOENT") {
130637
130709
  throw notFound("Schedule not found");
130638
130710
  }
130639
- rethrowAsApiError4(err);
130711
+ rethrowAsApiError(err);
130640
130712
  }
130641
130713
  });
130642
130714
  router.post("/automations/:id/run", async (req, res) => {
@@ -130664,7 +130736,7 @@ data: ${data}
130664
130736
  if (err.code === "ENOENT") {
130665
130737
  throw notFound("Schedule not found");
130666
130738
  }
130667
- rethrowAsApiError4(err);
130739
+ rethrowAsApiError(err);
130668
130740
  }
130669
130741
  });
130670
130742
  router.post("/automations/:id/toggle", async (req, res) => {
@@ -130687,7 +130759,7 @@ data: ${data}
130687
130759
  if (err.code === "ENOENT") {
130688
130760
  throw notFound("Schedule not found");
130689
130761
  }
130690
- rethrowAsApiError4(err);
130762
+ rethrowAsApiError(err);
130691
130763
  }
130692
130764
  });
130693
130765
  router.post("/automations/:id/steps/reorder", async (req, res) => {
@@ -130717,7 +130789,7 @@ data: ${data}
130717
130789
  if ((err instanceof Error ? err.message : String(err)).includes("mismatch") || (err instanceof Error ? err.message : String(err)).includes("Unknown step") || (err instanceof Error ? err.message : String(err)).includes("no steps")) {
130718
130790
  throw badRequest(err instanceof Error ? err.message : String(err));
130719
130791
  }
130720
- rethrowAsApiError4(err);
130792
+ rethrowAsApiError(err);
130721
130793
  }
130722
130794
  });
130723
130795
  router.get("/routines", async (req, res) => {
@@ -130738,7 +130810,7 @@ data: ${data}
130738
130810
  if (err instanceof ApiError) {
130739
130811
  throw err;
130740
130812
  }
130741
- rethrowAsApiError4(err);
130813
+ rethrowAsApiError(err);
130742
130814
  }
130743
130815
  });
130744
130816
  router.post("/routines", async (req, res) => {
@@ -130813,7 +130885,7 @@ data: ${data}
130813
130885
  if (err instanceof ApiError) {
130814
130886
  throw err;
130815
130887
  }
130816
- rethrowAsApiError4(err);
130888
+ rethrowAsApiError(err);
130817
130889
  }
130818
130890
  });
130819
130891
  router.get("/routines/:id", async (req, res) => {
@@ -130833,7 +130905,7 @@ data: ${data}
130833
130905
  if (err.code === "ENOENT") {
130834
130906
  throw notFound("Routine not found");
130835
130907
  }
130836
- rethrowAsApiError4(err);
130908
+ rethrowAsApiError(err);
130837
130909
  }
130838
130910
  });
130839
130911
  router.patch("/routines/:id", async (req, res) => {
@@ -130899,7 +130971,7 @@ data: ${data}
130899
130971
  if ((err instanceof Error ? err.message : String(err)).includes("cannot be empty") || (err instanceof Error ? err.message : String(err)).includes("Invalid cron")) {
130900
130972
  throw badRequest(err instanceof Error ? err.message : String(err));
130901
130973
  }
130902
- rethrowAsApiError4(err);
130974
+ rethrowAsApiError(err);
130903
130975
  }
130904
130976
  });
130905
130977
  router.delete("/routines/:id", async (req, res) => {
@@ -130922,7 +130994,7 @@ data: ${data}
130922
130994
  if (err.code === "ENOENT") {
130923
130995
  throw notFound("Routine not found");
130924
130996
  }
130925
- rethrowAsApiError4(err);
130997
+ rethrowAsApiError(err);
130926
130998
  }
130927
130999
  });
130928
131000
  router.post("/routines/:id/run", async (req, res) => {
@@ -130948,7 +131020,7 @@ data: ${data}
130948
131020
  if (err.code === "ENOENT") {
130949
131021
  throw notFound("Routine not found");
130950
131022
  }
130951
- rethrowAsApiError4(err);
131023
+ rethrowAsApiError(err);
130952
131024
  }
130953
131025
  });
130954
131026
  router.post("/routines/:id/trigger", async (req, res) => {
@@ -130974,7 +131046,7 @@ data: ${data}
130974
131046
  if (err.code === "ENOENT") {
130975
131047
  throw notFound("Routine not found");
130976
131048
  }
130977
- rethrowAsApiError4(err);
131049
+ rethrowAsApiError(err);
130978
131050
  }
130979
131051
  });
130980
131052
  router.get("/routines/:id/runs", async (req, res) => {
@@ -130994,7 +131066,7 @@ data: ${data}
130994
131066
  if (err.code === "ENOENT") {
130995
131067
  throw notFound("Routine not found");
130996
131068
  }
130997
- rethrowAsApiError4(err);
131069
+ rethrowAsApiError(err);
130998
131070
  }
130999
131071
  });
131000
131072
  router.post("/routines/:id/webhook", async (req, res) => {
@@ -131038,7 +131110,7 @@ data: ${data}
131038
131110
  if (err.code === "ENOENT") {
131039
131111
  throw notFound("Routine not found");
131040
131112
  }
131041
- rethrowAsApiError4(err);
131113
+ rethrowAsApiError(err);
131042
131114
  }
131043
131115
  });
131044
131116
  router.get("/activity", async (req, res) => {
@@ -131070,7 +131142,7 @@ data: ${data}
131070
131142
  if (err instanceof ApiError) {
131071
131143
  throw err;
131072
131144
  }
131073
- rethrowAsApiError4(err);
131145
+ rethrowAsApiError(err);
131074
131146
  }
131075
131147
  });
131076
131148
  router.delete("/activity", async (req, res) => {
@@ -131082,7 +131154,7 @@ data: ${data}
131082
131154
  if (err instanceof ApiError) {
131083
131155
  throw err;
131084
131156
  }
131085
- rethrowAsApiError4(err);
131157
+ rethrowAsApiError(err);
131086
131158
  }
131087
131159
  });
131088
131160
  router.get("/workflow-steps", async (req, res) => {
@@ -131094,7 +131166,7 @@ data: ${data}
131094
131166
  if (err instanceof ApiError) {
131095
131167
  throw err;
131096
131168
  }
131097
- rethrowAsApiError4(err);
131169
+ rethrowAsApiError(err);
131098
131170
  }
131099
131171
  });
131100
131172
  router.post("/workflow-steps", async (req, res) => {
@@ -131269,7 +131341,7 @@ data: ${data}
131269
131341
  if ((err instanceof Error ? err.message : String(err)).includes("not found")) {
131270
131342
  throw notFound(err instanceof Error ? err.message : String(err));
131271
131343
  } else {
131272
- rethrowAsApiError4(err);
131344
+ rethrowAsApiError(err);
131273
131345
  }
131274
131346
  }
131275
131347
  });
@@ -131339,7 +131411,7 @@ Description: ${step.description}`
131339
131411
  if (err instanceof ApiError) {
131340
131412
  throw err;
131341
131413
  }
131342
- rethrowAsApiError4(err);
131414
+ rethrowAsApiError(err);
131343
131415
  }
131344
131416
  });
131345
131417
  router.get("/workflow-step-templates", async (_req, res) => {
@@ -131350,7 +131422,7 @@ Description: ${step.description}`
131350
131422
  if (err instanceof ApiError) {
131351
131423
  throw err;
131352
131424
  }
131353
- rethrowAsApiError4(err);
131425
+ rethrowAsApiError(err);
131354
131426
  }
131355
131427
  });
131356
131428
  router.post("/workflow-step-templates/:id/create", async (req, res) => {
@@ -131378,7 +131450,7 @@ Description: ${step.description}`
131378
131450
  if (err instanceof ApiError) {
131379
131451
  throw err;
131380
131452
  }
131381
- rethrowAsApiError4(err);
131453
+ rethrowAsApiError(err);
131382
131454
  }
131383
131455
  });
131384
131456
  const TERMINAL_TASK_STATUSES = /* @__PURE__ */ new Set(["done", "archived"]);
@@ -131943,7 +132015,7 @@ Description: ${step.description}`
131943
132015
  if (err instanceof ApiError) {
131944
132016
  throw err;
131945
132017
  }
131946
- rethrowAsApiError4(err);
132018
+ rethrowAsApiError(err);
131947
132019
  }
131948
132020
  });
131949
132021
  registerProjectRoutes(routeContext);
@@ -131968,7 +132040,7 @@ Description: ${step.description}`
131968
132040
  if (err instanceof ApiError) {
131969
132041
  throw err;
131970
132042
  }
131971
- rethrowAsApiError4(err);
132043
+ rethrowAsApiError(err);
131972
132044
  }
131973
132045
  });
131974
132046
  router.get("/global-concurrency", async (_req, res) => {
@@ -131983,7 +132055,7 @@ Description: ${step.description}`
131983
132055
  if (err instanceof ApiError) {
131984
132056
  throw err;
131985
132057
  }
131986
- rethrowAsApiError4(err);
132058
+ rethrowAsApiError(err);
131987
132059
  }
131988
132060
  });
131989
132061
  router.put("/global-concurrency", async (req, res) => {
@@ -132002,7 +132074,7 @@ Description: ${step.description}`
132002
132074
  if (err instanceof ApiError) {
132003
132075
  throw err;
132004
132076
  }
132005
- rethrowAsApiError4(err);
132077
+ rethrowAsApiError(err);
132006
132078
  }
132007
132079
  });
132008
132080
  router.get("/first-run-status", async (_req, res) => {
@@ -132019,7 +132091,7 @@ Description: ${step.description}`
132019
132091
  if (err instanceof ApiError) {
132020
132092
  throw err;
132021
132093
  }
132022
- rethrowAsApiError4(err);
132094
+ rethrowAsApiError(err);
132023
132095
  }
132024
132096
  });
132025
132097
  router.get("/setup-state", async (_req, res) => {
@@ -132047,7 +132119,7 @@ Description: ${step.description}`
132047
132119
  if (err instanceof ApiError) {
132048
132120
  throw err;
132049
132121
  }
132050
- rethrowAsApiError4(err);
132122
+ rethrowAsApiError(err);
132051
132123
  }
132052
132124
  });
132053
132125
  router.post("/complete-setup", async (req, res) => {
@@ -132075,7 +132147,7 @@ Description: ${step.description}`
132075
132147
  if (err instanceof ApiError) {
132076
132148
  throw err;
132077
132149
  }
132078
- rethrowAsApiError4(err);
132150
+ rethrowAsApiError(err);
132079
132151
  }
132080
132152
  });
132081
132153
  registerIntegratedDevServerRouter({ router, store });
@@ -132298,7 +132370,11 @@ var init_routes = __esm({
132298
132370
  init_register_usage_routes();
132299
132371
  init_register_auth_routes();
132300
132372
  init_register_integrated_routers();
132373
+ init_register_terminal_routes();
132374
+ init_register_session_diff_routes();
132375
+ init_resolve_diff_base();
132301
132376
  init_register_git_github();
132377
+ init_resolve_diff_base();
132302
132378
  TASK_DETAIL_ACTIVITY_LOG_LIMIT = 500;
132303
132379
  upload = multer({
132304
132380
  storage: multer.memoryStorage(),