@runfusion/fusion 0.2.0 → 0.2.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/dist/bin.js +2188 -323
  2. package/dist/client/assets/{AgentDetailView-CDZED6Dy.css → AgentDetailView-BMrHuWGs.css} +1 -1
  3. package/dist/client/assets/{AgentDetailView-zycSdnO8.js → AgentDetailView-CkuuGA1O.js} +1 -1
  4. package/dist/client/assets/AgentsView-CWFLMIDP.js +522 -0
  5. package/dist/client/assets/AgentsView-DETxUmHS.css +1 -0
  6. package/dist/client/assets/{ChatView-BOd-sxbT.js → ChatView-C_T91ebd.js} +1 -1
  7. package/dist/client/assets/{DevServerView-09GQf34f.js → DevServerView-ChWTzTvy.js} +1 -1
  8. package/dist/client/assets/{DirectoryPicker-CcdN1Zs7.js → DirectoryPicker-BMJIT7HD.js} +1 -1
  9. package/dist/client/assets/{DocumentsView-CS8aiwtz.js → DocumentsView-CjfVl8mZ.js} +1 -1
  10. package/dist/client/assets/{InsightsView-Bu9Cv8Ol.js → InsightsView-Rb735C9_.js} +1 -1
  11. package/dist/client/assets/{MemoryView-CtqgDtV9.js → MemoryView-LLc_uNtG.js} +1 -1
  12. package/dist/client/assets/NodesView-BYVG2yY-.css +1 -0
  13. package/dist/client/assets/{NodesView-BInPcedy.js → NodesView-BviqBWRA.js} +1 -1
  14. package/dist/client/assets/{PiExtensionsManager-COxkYM2m.js → PiExtensionsManager-CriZBkQe.js} +1 -1
  15. package/dist/client/assets/{PluginManager-CXUWZBOc.js → PluginManager-BywTPbLB.js} +1 -1
  16. package/dist/client/assets/{RoadmapsView-BbCexaoi.js → RoadmapsView-Dhl--4vY.js} +1 -1
  17. package/dist/client/assets/{SetupWizardModal-Cakxqkad.js → SetupWizardModal-CVtmwoJC.js} +1 -1
  18. package/dist/client/assets/{SkillsView-D3iqYCVf.js → SkillsView-CG9y4fsE.js} +1 -1
  19. package/dist/client/assets/{folder-open-kO5Hsk66.js → folder-open-BVDq27HP.js} +1 -1
  20. package/dist/client/assets/index-CikysL-d.js +644 -0
  21. package/dist/client/assets/index-Da1qmtc7.css +1 -0
  22. package/dist/client/assets/{upload-DHBQat92.js → upload-BDvpReDO.js} +1 -1
  23. package/dist/client/index.html +2 -2
  24. package/dist/extension.js +12 -10
  25. package/package.json +4 -4
  26. package/dist/client/assets/AgentsView-DoQkkDLf.css +0 -1
  27. package/dist/client/assets/AgentsView-pO7WiBS5.js +0 -522
  28. package/dist/client/assets/NodesView-DlQZHGXA.css +0 -1
  29. package/dist/client/assets/index-BiSuUXCa.css +0 -1
  30. package/dist/client/assets/index-y194HxzU.js +0 -644
package/dist/bin.js CHANGED
@@ -14150,7 +14150,7 @@ var require_multicast_dns = __commonJS({
14150
14150
  var dgram = __require("dgram");
14151
14151
  var thunky = require_thunky();
14152
14152
  var events = __require("events");
14153
- var os3 = __require("os");
14153
+ var os4 = __require("os");
14154
14154
  var noop = function() {
14155
14155
  };
14156
14156
  module.exports = function(opts) {
@@ -14280,14 +14280,14 @@ var require_multicast_dns = __commonJS({
14280
14280
  return that;
14281
14281
  };
14282
14282
  function defaultInterface() {
14283
- var networks = os3.networkInterfaces();
14283
+ var networks = os4.networkInterfaces();
14284
14284
  var names = Object.keys(networks);
14285
14285
  for (var i = 0; i < names.length; i++) {
14286
14286
  var net = networks[names[i]];
14287
14287
  for (var j = 0; j < net.length; j++) {
14288
14288
  var iface = net[j];
14289
14289
  if (isIPv4(iface.family) && !iface.internal) {
14290
- if (os3.platform() === "darwin" && names[i] === "en0") return iface.address;
14290
+ if (os4.platform() === "darwin" && names[i] === "en0") return iface.address;
14291
14291
  return "0.0.0.0";
14292
14292
  }
14293
14293
  }
@@ -14295,7 +14295,7 @@ var require_multicast_dns = __commonJS({
14295
14295
  return "127.0.0.1";
14296
14296
  }
14297
14297
  function allInterfaces() {
14298
- var networks = os3.networkInterfaces();
14298
+ var networks = os4.networkInterfaces();
14299
14299
  var names = Object.keys(networks);
14300
14300
  var res = [];
14301
14301
  for (var i = 0; i < names.length; i++) {
@@ -16717,9 +16717,9 @@ var init_central_core = __esm({
16717
16717
  async generateProjectName(projectPath) {
16718
16718
  try {
16719
16719
  const { execFile: execFile5 } = await import("node:child_process");
16720
- const { promisify: promisify14 } = await import("node:util");
16721
- const execFileAsync3 = promisify14(execFile5);
16722
- const { stdout } = await execFileAsync3(
16720
+ const { promisify: promisify15 } = await import("node:util");
16721
+ const execFileAsync4 = promisify15(execFile5);
16722
+ const { stdout } = await execFileAsync4(
16723
16723
  "git",
16724
16724
  ["remote", "get-url", "origin"],
16725
16725
  { cwd: projectPath, timeout: 5e3 }
@@ -17204,8 +17204,8 @@ function hasProjectDbFile(dir2, folderName, dbName) {
17204
17204
  if (!existsSync8(projectDir)) return false;
17205
17205
  if (!existsSync8(dbPath)) return false;
17206
17206
  try {
17207
- const stat10 = statSync2(dbPath);
17208
- return stat10.isFile() && stat10.size > 0;
17207
+ const stat11 = statSync2(dbPath);
17208
+ return stat11.isFile() && stat11.size > 0;
17209
17209
  } catch {
17210
17210
  return false;
17211
17211
  }
@@ -17333,9 +17333,9 @@ var init_migration = __esm({
17333
17333
  }
17334
17334
  try {
17335
17335
  const { execFile: execFile5 } = await import("node:child_process");
17336
- const { promisify: promisify14 } = await import("node:util");
17337
- const execFileAsync3 = promisify14(execFile5);
17338
- const { stdout } = await execFileAsync3(
17336
+ const { promisify: promisify15 } = await import("node:util");
17337
+ const execFileAsync4 = promisify15(execFile5);
17338
+ const { stdout } = await execFileAsync4(
17339
17339
  "git",
17340
17340
  ["remote", "get-url", "origin"],
17341
17341
  { cwd: projectPath, timeout: 1e3 }
@@ -28036,12 +28036,12 @@ async function searchWithQmd(rootDir, options) {
28036
28036
  const limit = Math.max(1, Math.min(options.limit ?? 5, 20));
28037
28037
  try {
28038
28038
  const { execFile: execFile5 } = await import("node:child_process");
28039
- const { promisify: promisify14 } = await import("node:util");
28040
- const execFileAsync3 = promisify14(execFile5);
28041
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync3);
28039
+ const { promisify: promisify15 } = await import("node:util");
28040
+ const execFileAsync4 = promisify15(execFile5);
28041
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync4);
28042
28042
  scheduleQmdProjectMemoryRefresh(rootDir);
28043
28043
  const args = buildQmdSearchArgs(rootDir, options);
28044
- const { stdout } = await execFileAsync3(command, args, {
28044
+ const { stdout } = await execFileAsync4(command, args, {
28045
28045
  cwd: rootDir,
28046
28046
  timeout: 4e3,
28047
28047
  maxBuffer: 1024 * 1024
@@ -28066,12 +28066,12 @@ async function searchWithQmd(rootDir, options) {
28066
28066
  return [];
28067
28067
  }
28068
28068
  }
28069
- async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync3) {
28069
+ async function ensureQmdProjectMemoryCollection(rootDir, execFileAsync4) {
28070
28070
  const collectionName = qmdMemoryCollectionName(rootDir);
28071
28071
  const memoryDir = memoryWorkspacePath(rootDir);
28072
28072
  await mkdir6(memoryDir, { recursive: true });
28073
28073
  try {
28074
- await execFileAsync3("qmd", buildQmdCollectionAddArgs(rootDir), {
28074
+ await execFileAsync4("qmd", buildQmdCollectionAddArgs(rootDir), {
28075
28075
  cwd: rootDir,
28076
28076
  timeout: 4e3,
28077
28077
  maxBuffer: 512 * 1024
@@ -28088,8 +28088,8 @@ ${stderr}`)) {
28088
28088
  }
28089
28089
  async function getDefaultExecFileAsync() {
28090
28090
  const { execFile: execFile5 } = await import("node:child_process");
28091
- const { promisify: promisify14 } = await import("node:util");
28092
- return promisify14(execFile5);
28091
+ const { promisify: promisify15 } = await import("node:util");
28092
+ return promisify15(execFile5);
28093
28093
  }
28094
28094
  async function refreshQmdProjectMemoryIndex(rootDir, options) {
28095
28095
  const key = resolve5(rootDir);
@@ -28104,14 +28104,14 @@ async function refreshQmdProjectMemoryIndex(rootDir, options) {
28104
28104
  }
28105
28105
  }
28106
28106
  const promise = (async () => {
28107
- const execFileAsync3 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28108
- await ensureQmdProjectMemoryCollection(rootDir, execFileAsync3);
28109
- await execFileAsync3("qmd", ["update"], {
28107
+ const execFileAsync4 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28108
+ await ensureQmdProjectMemoryCollection(rootDir, execFileAsync4);
28109
+ await execFileAsync4("qmd", ["update"], {
28110
28110
  cwd: rootDir,
28111
28111
  timeout: 3e4,
28112
28112
  maxBuffer: 1024 * 1024
28113
28113
  });
28114
- await execFileAsync3("qmd", ["embed"], {
28114
+ await execFileAsync4("qmd", ["embed"], {
28115
28115
  cwd: rootDir,
28116
28116
  timeout: 12e4,
28117
28117
  maxBuffer: 1024 * 1024
@@ -28136,8 +28136,8 @@ function scheduleQmdProjectMemoryRefresh(rootDir) {
28136
28136
  }
28137
28137
  async function isQmdAvailable() {
28138
28138
  try {
28139
- const execFileAsync3 = await getDefaultExecFileAsync();
28140
- await execFileAsync3("qmd", ["--help"], {
28139
+ const execFileAsync4 = await getDefaultExecFileAsync();
28140
+ await execFileAsync4("qmd", ["--help"], {
28141
28141
  timeout: 3e3,
28142
28142
  maxBuffer: 128 * 1024
28143
28143
  });
@@ -28147,12 +28147,12 @@ async function isQmdAvailable() {
28147
28147
  }
28148
28148
  }
28149
28149
  async function installQmd(options) {
28150
- const execFileAsync3 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28150
+ const execFileAsync4 = options?.execFileAsync ?? await getDefaultExecFileAsync();
28151
28151
  const [command, ...args] = QMD_INSTALL_COMMAND.split(" ");
28152
28152
  if (!command || args.length === 0) {
28153
28153
  throw new MemoryBackendError("BACKEND_UNAVAILABLE", "qmd install command is not configured", "qmd");
28154
28154
  }
28155
- await execFileAsync3(command, args, {
28155
+ await execFileAsync4(command, args, {
28156
28156
  timeout: 12e4,
28157
28157
  maxBuffer: 1024 * 1024
28158
28158
  });
@@ -37215,7 +37215,7 @@ var require_has_flag = __commonJS({
37215
37215
  var require_supports_color = __commonJS({
37216
37216
  "../../node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
37217
37217
  "use strict";
37218
- var os3 = __require("os");
37218
+ var os4 = __require("os");
37219
37219
  var tty = __require("tty");
37220
37220
  var hasFlag = require_has_flag();
37221
37221
  var { env } = process;
@@ -37263,7 +37263,7 @@ var require_supports_color = __commonJS({
37263
37263
  return min;
37264
37264
  }
37265
37265
  if (process.platform === "win32") {
37266
- const osRelease = os3.release().split(".");
37266
+ const osRelease = os4.release().split(".");
37267
37267
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
37268
37268
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
37269
37269
  }
@@ -39096,11 +39096,11 @@ var require_extract_zip = __commonJS({
39096
39096
  var { createWriteStream: createWriteStream2, promises: fs2 } = __require("fs");
39097
39097
  var getStream = require_get_stream();
39098
39098
  var path4 = __require("path");
39099
- var { promisify: promisify14 } = __require("util");
39099
+ var { promisify: promisify15 } = __require("util");
39100
39100
  var stream = __require("stream");
39101
39101
  var yauzl = require_yauzl();
39102
- var openZip = promisify14(yauzl.open);
39103
- var pipeline = promisify14(stream.pipeline);
39102
+ var openZip = promisify15(yauzl.open);
39103
+ var pipeline = promisify15(stream.pipeline);
39104
39104
  var Extractor = class {
39105
39105
  constructor(zipPath, opts) {
39106
39106
  this.zipPath = zipPath;
@@ -39182,7 +39182,7 @@ var require_extract_zip = __commonJS({
39182
39182
  await fs2.mkdir(destDir, mkdirOptions);
39183
39183
  if (isDir) return;
39184
39184
  debug("opening read stream", dest);
39185
- const readStream = await promisify14(this.zipfile.openReadStream.bind(this.zipfile))(entry);
39185
+ const readStream = await promisify15(this.zipfile.openReadStream.bind(this.zipfile))(entry);
39186
39186
  if (symlink) {
39187
39187
  const link = await getStream(readStream);
39188
39188
  debug("creating symlink", link, dest);
@@ -46843,12 +46843,12 @@ function resolveExtractionRoot(tempDir) {
46843
46843
  return tempDir;
46844
46844
  }
46845
46845
  async function extractTarArchive(archivePath, outputDir) {
46846
- const [{ execFile: execFile5 }, { promisify: promisify14 }] = await Promise.all([
46846
+ const [{ execFile: execFile5 }, { promisify: promisify15 }] = await Promise.all([
46847
46847
  import("node:child_process"),
46848
46848
  import("node:util")
46849
46849
  ]);
46850
- const execFileAsync3 = promisify14(execFile5);
46851
- await execFileAsync3("tar", ["xzf", archivePath, "-C", outputDir]);
46850
+ const execFileAsync4 = promisify15(execFile5);
46851
+ await execFileAsync4("tar", ["xzf", archivePath, "-C", outputDir]);
46852
46852
  }
46853
46853
  async function parseCompanyArchive(archivePath) {
46854
46854
  const resolvedArchivePath = resolve8(archivePath);
@@ -50654,7 +50654,7 @@ var require_windowsPtyAgent = __commonJS({
50654
50654
  Object.defineProperty(exports, "__esModule", { value: true });
50655
50655
  exports.argsToCommandLine = exports.WindowsPtyAgent = void 0;
50656
50656
  var fs2 = __require("fs");
50657
- var os3 = __require("os");
50657
+ var os4 = __require("os");
50658
50658
  var path4 = __require("path");
50659
50659
  var child_process_1 = __require("child_process");
50660
50660
  var net_1 = __require("net");
@@ -50836,7 +50836,7 @@ var require_windowsPtyAgent = __commonJS({
50836
50836
  configurable: true
50837
50837
  });
50838
50838
  WindowsPtyAgent2.prototype._getWindowsBuildNumber = function() {
50839
- var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os3.release());
50839
+ var osVersion = /(\d+)\.(\d+)\.(\d+)/g.exec(os4.release());
50840
50840
  var buildNumber = 0;
50841
50841
  if (osVersion && osVersion.length === 4) {
50842
50842
  buildNumber = parseInt(osVersion[3]);
@@ -51735,8 +51735,8 @@ var init_terminal_service = __esm({
51735
51735
  return this.projectRoot;
51736
51736
  }
51737
51737
  try {
51738
- const stat10 = await import("node:fs/promises").then((fs2) => fs2.stat(cwd));
51739
- if (stat10.isDirectory()) {
51738
+ const stat11 = await import("node:fs/promises").then((fs2) => fs2.stat(cwd));
51739
+ if (stat11.isDirectory()) {
51740
51740
  return cwd;
51741
51741
  }
51742
51742
  } catch {
@@ -60177,10 +60177,10 @@ async function refreshAgentMemoryQmdIndex(rootDir, agentMemory) {
60177
60177
  }
60178
60178
  const promise = (async () => {
60179
60179
  const { execFile: execFile5 } = await import("node:child_process");
60180
- const { promisify: promisify14 } = await import("node:util");
60181
- const execFileAsync3 = promisify14(execFile5);
60180
+ const { promisify: promisify15 } = await import("node:util");
60181
+ const execFileAsync4 = promisify15(execFile5);
60182
60182
  try {
60183
- await execFileAsync3("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
60183
+ await execFileAsync4("qmd", buildQmdAgentMemoryCollectionAddArgs(rootDir, agentMemory.agentId), {
60184
60184
  cwd: rootDir,
60185
60185
  timeout: 4e3,
60186
60186
  maxBuffer: 512 * 1024
@@ -60193,8 +60193,8 @@ ${stderr}`)) {
60193
60193
  throw error;
60194
60194
  }
60195
60195
  }
60196
- await execFileAsync3("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
60197
- await execFileAsync3("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
60196
+ await execFileAsync4("qmd", ["update"], { cwd: rootDir, timeout: 3e4, maxBuffer: 1024 * 1024 });
60197
+ await execFileAsync4("qmd", ["embed"], { cwd: rootDir, timeout: 12e4, maxBuffer: 1024 * 1024 });
60198
60198
  })();
60199
60199
  agentQmdRefreshState.set(key, { lastStartedAt: now, inFlight: promise });
60200
60200
  try {
@@ -60216,9 +60216,9 @@ async function searchAgentMemoryWithQmd(rootDir, agentMemory, query, limit) {
60216
60216
  try {
60217
60217
  await refreshAgentMemoryQmdIndex(rootDir, agentMemory);
60218
60218
  const { execFile: execFile5 } = await import("node:child_process");
60219
- const { promisify: promisify14 } = await import("node:util");
60220
- const execFileAsync3 = promisify14(execFile5);
60221
- const { stdout } = await execFileAsync3("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
60219
+ const { promisify: promisify15 } = await import("node:util");
60220
+ const execFileAsync4 = promisify15(execFile5);
60221
+ const { stdout } = await execFileAsync4("qmd", buildQmdAgentMemorySearchArgs(rootDir, agentMemory.agentId, query, limit), {
60222
60222
  cwd: rootDir,
60223
60223
  timeout: 4e3,
60224
60224
  maxBuffer: 1024 * 1024
@@ -79190,7 +79190,7 @@ var init_self_healing = __esm({
79190
79190
  const recoverFn = this.options.recoverCompletedTask;
79191
79191
  if (!recoverFn) return 0;
79192
79192
  try {
79193
- const tasks = await this.store.listTasks({ column: "in-progress" });
79193
+ const tasks = await this.store.listTasks({ column: "in-progress", slim: true });
79194
79194
  const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
79195
79195
  const stuckCompleted = tasks.filter(
79196
79196
  (t) => t.column === "in-progress" && !t.paused && !executingIds.has(t.id) && t.steps.length > 0 && t.steps.every((s) => s.status === "done" || s.status === "skipped")
@@ -79224,7 +79224,7 @@ var init_self_healing = __esm({
79224
79224
  */
79225
79225
  async recoverMergeableReviewTasks() {
79226
79226
  try {
79227
- const tasks = await this.store.listTasks({ column: "in-review" });
79227
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79228
79228
  const mergeable = tasks.filter(
79229
79229
  (t) => t.column === "in-review" && Boolean(t.worktree) && t.mergeDetails?.mergeConfirmed !== true && getTaskMergeBlocker(t) === void 0
79230
79230
  );
@@ -79281,7 +79281,7 @@ var init_self_healing = __esm({
79281
79281
  const settings = await this.store.getSettings();
79282
79282
  const maxFixes = settings.maxPostReviewFixes ?? 1;
79283
79283
  if (!Number.isFinite(maxFixes) || maxFixes <= 0) return 0;
79284
- const tasks = await this.store.listTasks({ column: "in-review" });
79284
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79285
79285
  const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
79286
79286
  const candidates = tasks.filter((task) => {
79287
79287
  if (task.column !== "in-review") return false;
@@ -79346,7 +79346,7 @@ var init_self_healing = __esm({
79346
79346
  const timeoutMs = settings.taskStuckTimeoutMs;
79347
79347
  if (!timeoutMs || timeoutMs <= 0) return 0;
79348
79348
  const now = Date.now();
79349
- const tasks = await this.store.listTasks({ column: "in-review" });
79349
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79350
79350
  const staleIncomplete = tasks.filter(
79351
79351
  (task) => task.column === "in-review" && !task.paused && !task.status && task.steps.length > 0 && task.steps.some((step) => NON_TERMINAL_STEP_STATUSES2.has(step.status)) && now - new Date(task.updatedAt).getTime() >= timeoutMs
79352
79352
  );
@@ -79393,7 +79393,7 @@ var init_self_healing = __esm({
79393
79393
  const settings = await this.store.getSettings();
79394
79394
  const timeoutMs = settings.taskStuckTimeoutMs;
79395
79395
  if (!timeoutMs || timeoutMs <= 0) return 0;
79396
- const tasks = await this.store.listTasks({ column: "in-review" });
79396
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79397
79397
  const candidates = tasks.filter(
79398
79398
  (task) => task.column === "in-review" && Boolean(task.status && ACTIVE_MERGE_STATUSES.has(task.status)) && this.isPastInterruptedMergeGrace(task, timeoutMs)
79399
79399
  );
@@ -79464,7 +79464,7 @@ var init_self_healing = __esm({
79464
79464
  */
79465
79465
  async recoverMergedReviewTasks() {
79466
79466
  try {
79467
- const tasks = await this.store.listTasks({ column: "in-review" });
79467
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79468
79468
  const mergedButNotDone = tasks.filter(
79469
79469
  (t) => t.column === "in-review" && t.mergeDetails?.mergeConfirmed === true
79470
79470
  );
@@ -79512,7 +79512,7 @@ var init_self_healing = __esm({
79512
79512
  */
79513
79513
  async recoverMisclassifiedFailures() {
79514
79514
  try {
79515
- const tasks = await this.store.listTasks({ column: "in-review" });
79515
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79516
79516
  const misclassified = tasks.filter(
79517
79517
  (t) => t.column === "in-review" && t.status === "failed" && t.error?.includes("without calling task_done") && t.steps.length > 0 && t.steps.every((s) => s.status === "done" || s.status === "skipped")
79518
79518
  );
@@ -79553,7 +79553,7 @@ var init_self_healing = __esm({
79553
79553
  */
79554
79554
  async recoverOrphanedExecutions() {
79555
79555
  try {
79556
- const tasks = await this.store.listTasks({ column: "in-progress" });
79556
+ const tasks = await this.store.listTasks({ column: "in-progress", slim: true });
79557
79557
  const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
79558
79558
  const now = Date.now();
79559
79559
  const orphaned = tasks.filter((t) => {
@@ -79610,7 +79610,7 @@ var init_self_healing = __esm({
79610
79610
  */
79611
79611
  async recoverNoProgressNoTaskDoneFailures() {
79612
79612
  try {
79613
- const tasks = await this.store.listTasks({ column: "in-progress" });
79613
+ const tasks = await this.store.listTasks({ column: "in-progress", slim: true });
79614
79614
  const executingIds = this.options.getExecutingTaskIds?.() ?? /* @__PURE__ */ new Set();
79615
79615
  const candidates = tasks.filter(
79616
79616
  (task) => task.column === "in-progress" && task.status === "failed" && isNoTaskDoneFailure(task) && !task.paused && !executingIds.has(task.id) && !isTaskWorkComplete(task) && !hasStepProgress(task)
@@ -79672,7 +79672,7 @@ var init_self_healing = __esm({
79672
79672
  */
79673
79673
  async recoverPartialProgressNoTaskDoneFailures() {
79674
79674
  try {
79675
- const tasks = await this.store.listTasks({ column: "in-review" });
79675
+ const tasks = await this.store.listTasks({ column: "in-review", slim: true });
79676
79676
  const candidates = tasks.filter(
79677
79677
  (task) => task.column === "in-review" && task.status === "failed" && isNoTaskDoneFailure(task) && !task.paused && !isTaskWorkComplete(task) && hasStepProgress(task) && (task.taskDoneRetryCount ?? 0) < MAX_TASK_DONE_RETRIES
79678
79678
  );
@@ -89559,7 +89559,7 @@ var require_readdir_glob = __commonJS({
89559
89559
  var { EventEmitter: EventEmitter33 } = __require("events");
89560
89560
  var { Minimatch } = require_minimatch();
89561
89561
  var { resolve: resolve31 } = __require("path");
89562
- function readdir11(dir2, strict) {
89562
+ function readdir12(dir2, strict) {
89563
89563
  return new Promise((resolve32, reject2) => {
89564
89564
  fs2.readdir(dir2, { withFileTypes: true }, (err, files) => {
89565
89565
  if (err) {
@@ -89592,7 +89592,7 @@ var require_readdir_glob = __commonJS({
89592
89592
  });
89593
89593
  });
89594
89594
  }
89595
- function stat10(file, followSymlinks) {
89595
+ function stat11(file, followSymlinks) {
89596
89596
  return new Promise((resolve32, reject2) => {
89597
89597
  const statFunc = followSymlinks ? fs2.stat : fs2.lstat;
89598
89598
  statFunc(file, (err, stats) => {
@@ -89600,7 +89600,7 @@ var require_readdir_glob = __commonJS({
89600
89600
  switch (err.code) {
89601
89601
  case "ENOENT":
89602
89602
  if (followSymlinks) {
89603
- resolve32(stat10(file, false));
89603
+ resolve32(stat11(file, false));
89604
89604
  } else {
89605
89605
  resolve32(null);
89606
89606
  }
@@ -89616,7 +89616,7 @@ var require_readdir_glob = __commonJS({
89616
89616
  });
89617
89617
  }
89618
89618
  async function* exploreWalkAsync(dir2, path4, followSymlinks, useStat, shouldSkip, strict) {
89619
- let files = await readdir11(path4 + dir2, strict);
89619
+ let files = await readdir12(path4 + dir2, strict);
89620
89620
  for (const file of files) {
89621
89621
  let name = file.name;
89622
89622
  if (name === void 0) {
@@ -89628,7 +89628,7 @@ var require_readdir_glob = __commonJS({
89628
89628
  const absolute = path4 + "/" + relative12;
89629
89629
  let stats = null;
89630
89630
  if (useStat || followSymlinks) {
89631
- stats = await stat10(absolute, followSymlinks);
89631
+ stats = await stat11(absolute, followSymlinks);
89632
89632
  }
89633
89633
  if (!stats && file.name !== void 0) {
89634
89634
  stats = file;
@@ -92197,9 +92197,9 @@ var require_graceful_fs = __commonJS({
92197
92197
  }
92198
92198
  }
92199
92199
  var fs$readdir = fs3.readdir;
92200
- fs3.readdir = readdir11;
92200
+ fs3.readdir = readdir12;
92201
92201
  var noReaddirOptionVersions = /^v[0-5]\./;
92202
- function readdir11(path4, options, cb) {
92202
+ function readdir12(path4, options, cb) {
92203
92203
  if (typeof options === "function")
92204
92204
  cb = options, options = null;
92205
92205
  var go$readdir = noReaddirOptionVersions.test(process.version) ? function go$readdir2(path5, options2, cb2, startTime) {
@@ -115707,9 +115707,9 @@ async function getRemoteCommits(remoteRef, limit = 10, cwd) {
115707
115707
  async function getCommitDiff(hash, cwd) {
115708
115708
  try {
115709
115709
  await runGitCommand(["cat-file", "-t", hash], cwd, 5e3);
115710
- const stat10 = (await runGitCommand(["show", "--stat", "--format=", hash], cwd, 1e4)).trim();
115710
+ const stat11 = (await runGitCommand(["show", "--stat", "--format=", hash], cwd, 1e4)).trim();
115711
115711
  const patch = await runGitCommand(["show", "--format=", hash], cwd, 1e4);
115712
- return { stat: stat10, patch };
115712
+ return { stat: stat11, patch };
115713
115713
  } catch {
115714
115714
  return null;
115715
115715
  }
@@ -116111,17 +116111,56 @@ async function getGitFileChanges(cwd) {
116111
116111
  }
116112
116112
  async function getGitWorkingDiff(cwd) {
116113
116113
  try {
116114
- const stat10 = (await runGitCommand(["diff", "--stat"], cwd, 1e4)).trim();
116114
+ const stat11 = (await runGitCommand(["diff", "--stat"], cwd, 1e4)).trim();
116115
116115
  const patch = await runGitCommand(["diff"], cwd, 1e4);
116116
- return { stat: stat10, patch };
116116
+ return { stat: stat11, patch };
116117
116117
  } catch {
116118
116118
  return { stat: "", patch: "" };
116119
116119
  }
116120
116120
  }
116121
+ function isValidGitFilePath(filePath) {
116122
+ if (!filePath || !filePath.trim()) return false;
116123
+ if (filePath.startsWith("-")) return false;
116124
+ if (isAbsolute12(filePath)) return false;
116125
+ if (filePath.includes("\0")) return false;
116126
+ if (filePath.includes("..")) return false;
116127
+ if (/[;&|`$(){}[\]\r\n]/.test(filePath)) return false;
116128
+ return true;
116129
+ }
116130
+ async function runNoIndexDiff(args, cwd) {
116131
+ try {
116132
+ return await runGitCommand(args, cwd, 1e4);
116133
+ } catch (error) {
116134
+ const commandError = error;
116135
+ if (typeof commandError.stdout === "string") {
116136
+ return commandError.stdout;
116137
+ }
116138
+ throw error;
116139
+ }
116140
+ }
116141
+ async function getGitFileDiff(filePath, staged, cwd) {
116142
+ if (!isValidGitFilePath(filePath)) {
116143
+ throw new Error(`Invalid file path: ${filePath}`);
116144
+ }
116145
+ if (staged) {
116146
+ const stat12 = (await runGitCommand(["diff", "--cached", "--stat", "--", filePath], cwd, 1e4)).trim();
116147
+ const patch2 = await runGitCommand(["diff", "--cached", "--", filePath], cwd, 1e4);
116148
+ return { stat: stat12, patch: patch2 };
116149
+ }
116150
+ const untracked = (await runGitCommand(["ls-files", "--others", "--exclude-standard", "--", filePath], cwd, 5e3)).trim();
116151
+ if (untracked === filePath) {
116152
+ const stat12 = (await runNoIndexDiff(["diff", "--no-index", "--stat", "/dev/null", filePath], cwd)).trim();
116153
+ const patch2 = await runNoIndexDiff(["diff", "--no-index", "/dev/null", filePath], cwd);
116154
+ return { stat: stat12, patch: patch2 };
116155
+ }
116156
+ const stat11 = (await runGitCommand(["diff", "--stat", "--", filePath], cwd, 1e4)).trim();
116157
+ const patch = await runGitCommand(["diff", "--", filePath], cwd, 1e4);
116158
+ return { stat: stat11, patch };
116159
+ }
116121
116160
  async function stageGitFiles(files, cwd) {
116122
116161
  if (!files.length) throw new Error("No files specified");
116123
116162
  for (const f of files) {
116124
- if (/[;&|`$(){}[\]\r\n]/.test(f)) {
116163
+ if (!isValidGitFilePath(f)) {
116125
116164
  throw new Error(`Invalid file path: ${f}`);
116126
116165
  }
116127
116166
  }
@@ -116131,7 +116170,7 @@ async function stageGitFiles(files, cwd) {
116131
116170
  async function unstageGitFiles(files, cwd) {
116132
116171
  if (!files.length) throw new Error("No files specified");
116133
116172
  for (const f of files) {
116134
- if (/[;&|`$(){}[\]\r\n]/.test(f)) {
116173
+ if (!isValidGitFilePath(f)) {
116135
116174
  throw new Error(`Invalid file path: ${f}`);
116136
116175
  }
116137
116176
  }
@@ -116149,7 +116188,7 @@ async function createGitCommit(message, cwd) {
116149
116188
  async function discardGitChanges(files, cwd) {
116150
116189
  if (!files.length) throw new Error("No files specified");
116151
116190
  for (const f of files) {
116152
- if (/[;&|`$(){}[\]\r\n]/.test(f)) {
116191
+ if (!isValidGitFilePath(f)) {
116153
116192
  throw new Error(`Invalid file path: ${f}`);
116154
116193
  }
116155
116194
  }
@@ -119470,6 +119509,33 @@ function createApiRoutes(store, options) {
119470
119509
  rethrowAsApiError3(err);
119471
119510
  }
119472
119511
  });
119512
+ router.get("/git/diff/file", async (req, res) => {
119513
+ try {
119514
+ const { store: scopedStore } = await getProjectContext2(req);
119515
+ const rootDir = scopedStore.getRootDir();
119516
+ if (!await isGitRepo(rootDir)) {
119517
+ throw badRequest("Not a git repository");
119518
+ }
119519
+ const rawPath = req.query.path;
119520
+ const rawStaged = req.query.staged;
119521
+ if (typeof rawPath !== "string" || !rawPath.trim()) {
119522
+ throw badRequest("path query parameter is required");
119523
+ }
119524
+ if (rawStaged !== "true" && rawStaged !== "false") {
119525
+ throw badRequest("staged query parameter must be 'true' or 'false'");
119526
+ }
119527
+ if (!isValidGitFilePath(rawPath)) {
119528
+ throw badRequest(`Invalid file path: ${rawPath}`);
119529
+ }
119530
+ const diff = await getGitFileDiff(rawPath, rawStaged === "true", rootDir);
119531
+ res.json(diff);
119532
+ } catch (err) {
119533
+ if (err instanceof ApiError) {
119534
+ throw err;
119535
+ }
119536
+ rethrowAsApiError3(err);
119537
+ }
119538
+ });
119473
119539
  router.get("/git/changes", async (req, res) => {
119474
119540
  try {
119475
119541
  const { store: scopedStore } = await getProjectContext2(req);
@@ -125858,7 +125924,7 @@ ${body}`;
125858
125924
  }
125859
125925
  }
125860
125926
  const { resolve: resolve31, dirname: dirname25, join: join60 } = await import("node:path");
125861
- const { readdir: readdir11, stat: stat10 } = await import("node:fs/promises");
125927
+ const { readdir: readdir12, stat: stat11 } = await import("node:fs/promises");
125862
125928
  const rawPath = req.query.path || process.env.HOME || process.env.USERPROFILE || "/";
125863
125929
  const showHidden = req.query.showHidden === "true";
125864
125930
  const resolvedPath = resolve31(rawPath);
@@ -125870,14 +125936,14 @@ ${body}`;
125870
125936
  }
125871
125937
  let pathStat;
125872
125938
  try {
125873
- pathStat = await stat10(resolvedPath);
125939
+ pathStat = await stat11(resolvedPath);
125874
125940
  } catch {
125875
125941
  throw notFound("Directory not found");
125876
125942
  }
125877
125943
  if (!pathStat.isDirectory()) {
125878
125944
  throw badRequest("Path is not a directory");
125879
125945
  }
125880
- const dirEntries = await readdir11(resolvedPath, { withFileTypes: true });
125946
+ const dirEntries = await readdir12(resolvedPath, { withFileTypes: true });
125881
125947
  const entries = [];
125882
125948
  for (const entry of dirEntries) {
125883
125949
  if (!entry.isDirectory()) continue;
@@ -125885,7 +125951,7 @@ ${body}`;
125885
125951
  const entryPath = join60(resolvedPath, entry.name);
125886
125952
  let hasChildren = false;
125887
125953
  try {
125888
- const subEntries = await readdir11(entryPath, { withFileTypes: true });
125954
+ const subEntries = await readdir12(entryPath, { withFileTypes: true });
125889
125955
  hasChildren = subEntries.some((e) => e.isDirectory());
125890
125956
  } catch {
125891
125957
  }
@@ -128405,8 +128471,8 @@ function validateAutomationSteps(steps) {
128405
128471
  }
128406
128472
  async function executeSingleCommand(command, timeoutMs, startedAt) {
128407
128473
  const { exec: exec12 } = await import("node:child_process");
128408
- const { promisify: promisify14 } = await import("node:util");
128409
- const execAsyncFn = promisify14(exec12);
128474
+ const { promisify: promisify15 } = await import("node:util");
128475
+ const execAsyncFn = promisify15(exec12);
128410
128476
  const DEFAULT_TIMEOUT_MS3 = 5 * 60 * 1e3;
128411
128477
  const MAX_BUFFER3 = 1024 * 1024;
128412
128478
  const MAX_OUTPUT = 10240;
@@ -129060,11 +129126,14 @@ function markSSEClientAlive(clientId, projectId) {
129060
129126
  }
129061
129127
  function safeWrite(res, data) {
129062
129128
  try {
129063
- if (res.writableEnded || res.destroyed) return false;
129129
+ if (res.writableEnded || res.destroyed) return "dead";
129130
+ if (typeof res.writableLength === "number" && res.writableLength > SSE_MAX_BUFFERED_BYTES) {
129131
+ return "backpressure";
129132
+ }
129064
129133
  res.write(data);
129065
- return true;
129134
+ return "ok";
129066
129135
  } catch {
129067
- return false;
129136
+ return "dead";
129068
129137
  }
129069
129138
  }
129070
129139
  function stripTaskListHeavyFields(task) {
@@ -129143,7 +129212,16 @@ function createSSE(store, missionStore, aiSessionStore, pluginStore, options, ag
129143
129212
  console.log(`[sse] + connection (active=${activeConnections}, hwm=${highWaterMark})`);
129144
129213
  res.write(": connected\n\n");
129145
129214
  const send = (data) => {
129146
- if (!safeWrite(res, data)) cleanup("send-failed");
129215
+ const result = safeWrite(res, data);
129216
+ if (result === "ok") return;
129217
+ if (result === "backpressure") {
129218
+ console.warn(
129219
+ `[sse] connection ${connectionId} backpressure exceeded (buffered=${res.writableLength}B, threshold=${SSE_MAX_BUFFERED_BYTES}B); closing`
129220
+ );
129221
+ closeConnection("backpressure");
129222
+ return;
129223
+ }
129224
+ cleanup("send-failed");
129147
129225
  };
129148
129226
  const onCreated = (task) => {
129149
129227
  send(`event: task:created
@@ -129636,7 +129714,7 @@ data: ${JSON.stringify({ id: messageId })}
129636
129714
  }
129637
129715
  };
129638
129716
  }
129639
- var activeConnections, highWaterMark, nextConnectionId, SSE_CLIENT_ID_MAX_LENGTH, SSE_CLIENT_STALE_MS, managedConnections;
129717
+ var activeConnections, highWaterMark, nextConnectionId, SSE_CLIENT_ID_MAX_LENGTH, SSE_CLIENT_STALE_MS, SSE_MAX_BUFFERED_BYTES, managedConnections;
129640
129718
  var init_sse = __esm({
129641
129719
  "../dashboard/src/sse.ts"() {
129642
129720
  "use strict";
@@ -129645,6 +129723,7 @@ var init_sse = __esm({
129645
129723
  nextConnectionId = 1;
129646
129724
  SSE_CLIENT_ID_MAX_LENGTH = 128;
129647
129725
  SSE_CLIENT_STALE_MS = 5e3;
129726
+ SSE_MAX_BUFFERED_BYTES = 4 * 1024 * 1024;
129648
129727
  managedConnections = /* @__PURE__ */ new Map();
129649
129728
  }
129650
129729
  });
@@ -136000,8 +136079,8 @@ function installFusionSkillIntoProject(projectPath, options = {}) {
136000
136079
  mkdirSync7(dirname19(target), { recursive: true });
136001
136080
  let replaced = false;
136002
136081
  if (existsSync35(target) || isBrokenSymlink(target)) {
136003
- const stat10 = lstatSync2(target);
136004
- if (stat10.isSymbolicLink()) {
136082
+ const stat11 = lstatSync2(target);
136083
+ if (stat11.isSymbolicLink()) {
136005
136084
  const current = safeReadlink(target);
136006
136085
  if (current && resolve21(dirname19(target), current) === resolve21(source)) {
136007
136086
  return { outcome: "already-installed", target, source };
@@ -136067,8 +136146,8 @@ function ensureFusionSkillForProjects(projects, options = { enabled: false }) {
136067
136146
  }
136068
136147
  function isBrokenSymlink(path4) {
136069
136148
  try {
136070
- const stat10 = lstatSync2(path4);
136071
- if (!stat10.isSymbolicLink()) return false;
136149
+ const stat11 = lstatSync2(path4);
136150
+ if (!stat11.isSymbolicLink()) return false;
136072
136151
  return !existsSync35(path4);
136073
136152
  } catch {
136074
136153
  return false;
@@ -136306,7 +136385,7 @@ var init_state = __esm({
136306
136385
  });
136307
136386
 
136308
136387
  // src/commands/dashboard-tui/logo.ts
136309
- var FUSION_LOGO_LINES, FUSION_TAGLINE;
136388
+ var FUSION_LOGO_LINES, FUSION_LOGO_LARGE_LINES, FUSION_TAGLINE;
136310
136389
  var init_logo = __esm({
136311
136390
  "src/commands/dashboard-tui/logo.ts"() {
136312
136391
  "use strict";
@@ -136318,6 +136397,20 @@ var init_logo = __esm({
136318
136397
  "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551",
136319
136398
  "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"
136320
136399
  ];
136400
+ FUSION_LOGO_LARGE_LINES = [
136401
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557",
136402
+ "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557 \u2588\u2588\u2557",
136403
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551",
136404
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551",
136405
+ "\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551",
136406
+ "\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551",
136407
+ "\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551",
136408
+ "\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551",
136409
+ "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551",
136410
+ "\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551",
136411
+ "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D",
136412
+ "\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D"
136413
+ ];
136321
136414
  FUSION_TAGLINE = "AI coding agent dashboard";
136322
136415
  }
136323
136416
  });
@@ -136431,43 +136524,48 @@ function formatRelativeTime(iso) {
136431
136524
  const h = Math.floor(m / 60);
136432
136525
  return `${h}h ago`;
136433
136526
  }
136434
- function logoColor(index2) {
136435
- return LOGO_COLORS[Math.min(index2, LOGO_COLORS.length - 1)];
136527
+ function logoColor(index2, total) {
136528
+ const slot = Math.min(
136529
+ LOGO_COLORS.length - 1,
136530
+ Math.floor(index2 / Math.max(1, total - 1) * (LOGO_COLORS.length - 1))
136531
+ );
136532
+ return LOGO_COLORS[slot];
136436
136533
  }
136437
- function AnimatedFusionLogo() {
136438
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", children: FUSION_LOGO_LINES.map((line, i) => /* @__PURE__ */ jsx(Text, { color: logoColor(i), bold: true, children: line }, i)) });
136534
+ function AnimatedFusionLogo({ lines }) {
136535
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "column", alignItems: "center", children: lines.map((line, i) => /* @__PURE__ */ jsx(Text, { color: logoColor(i, lines.length), bold: true, children: line }, i)) });
136439
136536
  }
136440
136537
  function SplashScreen({ loadingStatus }) {
136441
136538
  const { stdout } = useStdout();
136442
136539
  const cols = stdout?.columns ?? 80;
136443
136540
  const rows = stdout?.rows ?? 24;
136444
136541
  const compact = cols < SPLASH_MIN_COLS || rows < SPLASH_MIN_ROWS;
136542
+ const large = cols >= LARGE_LOGO_MIN_COLS && rows >= LARGE_LOGO_MIN_ROWS;
136445
136543
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
136446
- compact ? /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "FUSION" }) : /* @__PURE__ */ jsx(AnimatedFusionLogo, {}),
136447
- /* @__PURE__ */ jsx(Text, { color: "blueBright", dimColor: true, children: FUSION_TAGLINE }),
136544
+ compact ? /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "FUSION" }) : /* @__PURE__ */ jsx(AnimatedFusionLogo, { lines: large ? FUSION_LOGO_LARGE_LINES : FUSION_LOGO_LINES }),
136545
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", dimColor: true, children: FUSION_TAGLINE }),
136448
136546
  /* @__PURE__ */ jsx(Box, { height: 1 }),
136449
136547
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136450
136548
  /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
136451
- /* @__PURE__ */ jsx(Text, { color: "blueBright", dimColor: true, children: loadingStatus })
136549
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", dimColor: true, children: loadingStatus })
136452
136550
  ] })
136453
136551
  ] });
136454
136552
  }
136455
136553
  function MiniLogo() {
136456
- return /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 0, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "FUSION" }) });
136554
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 0, children: /* @__PURE__ */ jsx(Text, { color: "cyanBright", bold: true, children: "FUSION" }) });
136457
136555
  }
136458
136556
  function Panel({ title, isFocused, children, flexGrow, flexShrink, width }) {
136459
136557
  return /* @__PURE__ */ jsxs(
136460
136558
  Box,
136461
136559
  {
136462
136560
  borderStyle: "round",
136463
- borderColor: isFocused ? "cyan" : "gray",
136561
+ borderColor: isFocused ? "cyanBright" : "gray",
136464
136562
  flexDirection: "column",
136465
136563
  flexGrow,
136466
136564
  flexShrink,
136467
136565
  width,
136468
136566
  overflow: "hidden",
136469
136567
  children: [
136470
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: isFocused, color: isFocused ? "cyan" : void 0, dimColor: !isFocused, children: title }) }),
136568
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: isFocused, color: isFocused ? "cyanBright" : void 0, dimColor: !isFocused, children: title }) }),
136471
136569
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children })
136472
136570
  ]
136473
136571
  }
@@ -136486,7 +136584,7 @@ function SystemPanel({ state, isFocused }) {
136486
136584
  ] }),
136487
136585
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136488
136586
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "URL:" }),
136489
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: info.baseUrl })
136587
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: info.baseUrl })
136490
136588
  ] }),
136491
136589
  info.authEnabled ? /* @__PURE__ */ jsxs(Fragment, { children: [
136492
136590
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
@@ -136499,24 +136597,12 @@ function SystemPanel({ state, isFocused }) {
136499
136597
  ] }),
136500
136598
  info.tokenizedUrl && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136501
136599
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Open:" }),
136502
- /* @__PURE__ */ jsx(Text, { wrap: "truncate", color: "cyanBright", children: info.tokenizedUrl })
136503
- ] }),
136504
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136505
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press" }),
136506
- /* @__PURE__ */ jsx(Text, { color: "cyanBright", bold: true, children: "[Enter]" }),
136507
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "to open in browser" })
136508
- ] })
136509
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
136510
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136511
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Auth:" }),
136512
- /* @__PURE__ */ jsx(Text, { color: "yellow", children: "no auth" })
136513
- ] }),
136514
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136515
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press" }),
136516
- /* @__PURE__ */ jsx(Text, { color: "cyanBright", bold: true, children: "[Enter]" }),
136517
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "to open in browser" })
136600
+ /* @__PURE__ */ jsx(Text, { wrap: "truncate", color: "white", children: info.tokenizedUrl })
136518
136601
  ] })
136519
- ] }),
136602
+ ] }) : /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136603
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Auth:" }),
136604
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: "no auth" })
136605
+ ] }) }),
136520
136606
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136521
136607
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Engine:" }),
136522
136608
  info.engineMode === "dev" && /* @__PURE__ */ jsx(Text, { color: "yellow", children: "dev" }),
@@ -136533,40 +136619,113 @@ function SystemPanel({ state, isFocused }) {
136533
136619
  ] })
136534
136620
  ] }) });
136535
136621
  }
136622
+ function formatBytes2(bytes) {
136623
+ if (!Number.isFinite(bytes) || bytes < 0) return "\u2014";
136624
+ const mb = bytes / (1024 * 1024);
136625
+ if (mb < 1024) return `${mb.toFixed(0)} MB`;
136626
+ return `${(mb / 1024).toFixed(2)} GB`;
136627
+ }
136628
+ function heapColor(used, limit) {
136629
+ if (limit <= 0) return "green";
136630
+ const pct = used / limit;
136631
+ if (pct >= 0.85) return "red";
136632
+ if (pct >= 0.65) return "yellow";
136633
+ return "green";
136634
+ }
136635
+ function rssColor(rss, totalSystemMem) {
136636
+ if (totalSystemMem <= 0) return void 0;
136637
+ const pct = rss / totalSystemMem;
136638
+ if (pct >= 0.5) return "red";
136639
+ if (pct >= 0.25) return "yellow";
136640
+ return void 0;
136641
+ }
136642
+ function sysMemColor(used, total) {
136643
+ if (total <= 0) return void 0;
136644
+ const pct = used / total;
136645
+ if (pct >= 0.9) return "red";
136646
+ if (pct >= 0.75) return "yellow";
136647
+ return void 0;
136648
+ }
136649
+ function cpuColor(percent, cores) {
136650
+ const norm = cores > 0 ? percent / cores : percent;
136651
+ if (norm >= 80) return "red";
136652
+ if (norm >= 50) return "yellow";
136653
+ return void 0;
136654
+ }
136655
+ function StatRow({ label, children }) {
136656
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
136657
+ /* @__PURE__ */ jsx(Box, { width: 10, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: label }) }),
136658
+ /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 2, children })
136659
+ ] });
136660
+ }
136661
+ function SectionHeader({ title }) {
136662
+ return /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: title }) });
136663
+ }
136536
136664
  function StatsPanel({ state, isFocused }) {
136537
136665
  const stats = state.taskStats;
136538
- return /* @__PURE__ */ jsx(Panel, { title: "Stats", isFocused, flexGrow: 1, children: !stats ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Statistics not available." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
136539
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136540
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Total:" }),
136541
- /* @__PURE__ */ jsx(Text, { children: stats.total })
136542
- ] }),
136543
- Object.entries(stats.byColumn).map(([col, count]) => {
136544
- const name = col.replace(/-/g, " ");
136545
- const isActive = (col === "in-progress" || col === "in-review") && count > 0;
136546
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
136547
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
136548
- name,
136549
- ":"
136666
+ const sys = state.systemStats;
136667
+ return /* @__PURE__ */ jsx(Panel, { title: "Stats", isFocused, flexGrow: 1, children: /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
136668
+ sys && /* @__PURE__ */ jsxs(Fragment, { children: [
136669
+ /* @__PURE__ */ jsx(SectionHeader, { title: "Process" }),
136670
+ /* @__PURE__ */ jsxs(StatRow, { label: "RSS", children: [
136671
+ /* @__PURE__ */ jsx(Text, { color: rssColor(sys.rss, sys.systemTotalMem), children: formatBytes2(sys.rss) }),
136672
+ sys.systemTotalMem > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
136673
+ (sys.rss / sys.systemTotalMem * 100).toFixed(1),
136674
+ "%"
136675
+ ] })
136676
+ ] }),
136677
+ /* @__PURE__ */ jsxs(StatRow, { label: "Heap", children: [
136678
+ /* @__PURE__ */ jsx(Text, { color: heapColor(sys.heapUsed, sys.heapLimit), children: formatBytes2(sys.heapUsed) }),
136679
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "/" }),
136680
+ /* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.heapTotal) })
136681
+ ] }),
136682
+ /* @__PURE__ */ jsxs(StatRow, { label: "", children: [
136683
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "limit" }),
136684
+ /* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.heapLimit) })
136685
+ ] }),
136686
+ /* @__PURE__ */ jsxs(StatRow, { label: "External", children: [
136687
+ /* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.external) }),
136688
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "buffers" }),
136689
+ /* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.arrayBuffers) })
136690
+ ] }),
136691
+ /* @__PURE__ */ jsxs(StatRow, { label: "CPU", children: [
136692
+ /* @__PURE__ */ jsxs(Text, { color: cpuColor(sys.cpuPercent, sys.cpuCount), children: [
136693
+ sys.cpuPercent.toFixed(1),
136694
+ "%"
136550
136695
  ] }),
136551
- /* @__PURE__ */ jsx(Text, { color: isActive ? "green" : void 0, children: count })
136552
- ] }, col);
136553
- }),
136554
- /* @__PURE__ */ jsx(Box, { height: 1 }),
136555
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Agents:" }),
136556
- /* @__PURE__ */ jsxs(Box, { marginLeft: 1, flexDirection: "column", children: [
136557
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
136558
- "idle: ",
136559
- /* @__PURE__ */ jsx(Text, { color: "white", children: stats.agents.idle })
136696
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "load" }),
136697
+ /* @__PURE__ */ jsx(Text, { children: sys.loadAvg.map((n) => n.toFixed(2)).join(" ") })
136560
136698
  ] }),
136561
- /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
136562
- "active: ",
136563
- /* @__PURE__ */ jsx(Text, { color: "green", children: stats.agents.active })
136699
+ /* @__PURE__ */ jsx(SectionHeader, { title: "System" }),
136700
+ /* @__PURE__ */ jsxs(StatRow, { label: "Memory", children: [
136701
+ /* @__PURE__ */ jsx(Text, { color: sysMemColor(sys.systemTotalMem - sys.systemFreeMem, sys.systemTotalMem), children: formatBytes2(sys.systemTotalMem - sys.systemFreeMem) }),
136702
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "used" }),
136703
+ /* @__PURE__ */ jsx(Text, { children: formatBytes2(sys.systemFreeMem) }),
136704
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "free" })
136564
136705
  ] }),
136565
- /* @__PURE__ */ jsxs(Text, { color: stats.agents.error > 0 ? "red" : void 0, dimColor: stats.agents.error === 0, children: [
136566
- "error: ",
136567
- stats.agents.error
136706
+ /* @__PURE__ */ jsxs(StatRow, { label: "Cores", children: [
136707
+ /* @__PURE__ */ jsx(Text, { children: sys.cpuCount }),
136708
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: sys.platform })
136709
+ ] }),
136710
+ /* @__PURE__ */ jsxs(StatRow, { label: "Node", children: [
136711
+ /* @__PURE__ */ jsx(Text, { children: sys.nodeVersion }),
136712
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "pid" }),
136713
+ /* @__PURE__ */ jsx(Text, { children: sys.pid })
136568
136714
  ] })
136569
- ] })
136715
+ ] }),
136716
+ stats && /* @__PURE__ */ jsxs(Fragment, { children: [
136717
+ /* @__PURE__ */ jsx(SectionHeader, { title: "Tasks" }),
136718
+ Object.entries(stats.byColumn).map(([col, count]) => {
136719
+ const name = col.replace(/-/g, " ");
136720
+ const isActive = (col === "in-progress" || col === "in-review") && count > 0;
136721
+ return /* @__PURE__ */ jsx(StatRow, { label: name, children: /* @__PURE__ */ jsx(Text, { color: isActive ? "green" : void 0, children: count }) }, col);
136722
+ }),
136723
+ /* @__PURE__ */ jsx(SectionHeader, { title: "Agents" }),
136724
+ /* @__PURE__ */ jsx(StatRow, { label: "idle", children: /* @__PURE__ */ jsx(Text, { children: stats.agents.idle }) }),
136725
+ /* @__PURE__ */ jsx(StatRow, { label: "active", children: /* @__PURE__ */ jsx(Text, { color: "green", children: stats.agents.active }) }),
136726
+ /* @__PURE__ */ jsx(StatRow, { label: "error", children: /* @__PURE__ */ jsx(Text, { color: stats.agents.error > 0 ? "red" : void 0, children: stats.agents.error }) })
136727
+ ] }),
136728
+ !sys && !stats && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Stats not available." })
136570
136729
  ] }) });
136571
136730
  }
136572
136731
  function SettingsPanel({ state, isFocused }) {
@@ -136646,7 +136805,7 @@ function LogsPanel({
136646
136805
  visibleEntries.map((entry, displayIdx) => {
136647
136806
  const absoluteIndex = visibleStart + displayIdx;
136648
136807
  const isSelected = absoluteIndex === cursor;
136649
- const bg = isSelected ? "blue" : void 0;
136808
+ const bg = isSelected ? "cyan" : void 0;
136650
136809
  const fg = isSelected ? "whiteBright" : void 0;
136651
136810
  const ts = formatTimestamp3(entry.timestamp);
136652
136811
  const lvl = entry.level === "error" ? "\u2717" : entry.level === "warn" ? "\u26A0" : "\u2713";
@@ -136659,7 +136818,7 @@ function LogsPanel({
136659
136818
  backgroundColor: bg,
136660
136819
  wrap: logsWrapEnabled ? "wrap" : "truncate-end",
136661
136820
  children: [
136662
- /* @__PURE__ */ jsx(Text, { color: isSelected ? "cyanBright" : "gray", bold: isSelected, children: marker }),
136821
+ /* @__PURE__ */ jsx(Text, { color: isSelected ? "white" : "gray", bold: isSelected, children: marker }),
136663
136822
  /* @__PURE__ */ jsxs(Text, { color: fg, dimColor: !isSelected, children: [
136664
136823
  ts,
136665
136824
  " "
@@ -136675,7 +136834,7 @@ function LogsPanel({
136675
136834
  ] }) });
136676
136835
  }
136677
136836
  function ExpandedLog({ entry, index: index2, total }) {
136678
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
136837
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, width: "100%", children: [
136679
136838
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
136680
136839
  "Entry ",
136681
136840
  index2 + 1,
@@ -136722,6 +136881,7 @@ function HelpOverlay() {
136722
136881
  ["[b]", "Board view (interactive mode)"],
136723
136882
  ["[a]", "Agents view"],
136724
136883
  ["[g]", "Settings view"],
136884
+ ["[t]", "Git view"],
136725
136885
  ["[s]", "Status mode"],
136726
136886
  ["[1] / [2] / [3]", "Board / Agents / Settings (interactive)"],
136727
136887
  ["[Tab]", "Cycle focused panel forward"],
@@ -136750,8 +136910,8 @@ function HelpOverlay() {
136750
136910
  const rowDescWidth = Math.max(...shortcuts.map(([, d]) => d.length));
136751
136911
  const innerWidth = rowKeyWidth + 2 + rowDescWidth + 2;
136752
136912
  const titleRow = " KEYBOARD SHORTCUTS".padEnd(innerWidth);
136753
- return /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", backgroundColor: "black", children: [
136754
- /* @__PURE__ */ jsx(Text, { backgroundColor: "black", bold: true, color: "cyanBright", children: titleRow }),
136913
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "round", borderColor: "cyanBright", flexDirection: "column", backgroundColor: "black", children: [
136914
+ /* @__PURE__ */ jsx(Text, { backgroundColor: "black", bold: true, color: "white", children: titleRow }),
136755
136915
  /* @__PURE__ */ jsx(Text, { backgroundColor: "black", children: " " }),
136756
136916
  shortcuts.map(([key, desc]) => {
136757
136917
  const keyCell = ` ${key.padEnd(rowKeyWidth - 1)} `;
@@ -136772,24 +136932,13 @@ function StatusModeGrid({
136772
136932
  const bodyRows = Math.max(8, rows - 7);
136773
136933
  const logsAvailableRows = Math.max(4, bodyRows - 4);
136774
136934
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
136775
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingX: 1, paddingY: 0, children: [
136776
- /* @__PURE__ */ jsx(MiniLogo, {}),
136777
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
136778
- SECTION_ORDER.map((section, i) => {
136779
- const isActive = section === focused;
136780
- const label = section.charAt(0).toUpperCase() + section.slice(1);
136781
- return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` [${i + 1}] ${label} ` }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${i + 1}] ${label}` }) }, section);
136782
- }),
136783
- /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
136784
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[b] board [a] agents [g] settings [?] help [q] quit" })
136785
- ] }),
136935
+ /* @__PURE__ */ jsx(MainHeader, { state }),
136786
136936
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
136787
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
136937
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 5, overflow: "hidden", children: [
136788
136938
  /* @__PURE__ */ jsx(SystemPanel, { state, isFocused: focused === "system" }),
136789
- /* @__PURE__ */ jsx(StatsPanel, { state, isFocused: focused === "stats" }),
136790
- /* @__PURE__ */ jsx(SettingsPanel, { state, isFocused: focused === "settings" })
136939
+ /* @__PURE__ */ jsx(StatsPanel, { state, isFocused: focused === "stats" })
136791
136940
  ] }),
136792
- /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 2, overflow: "hidden", children: [
136941
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 6, overflow: "hidden", children: [
136793
136942
  /* @__PURE__ */ jsx(
136794
136943
  LogsPanel,
136795
136944
  {
@@ -136798,7 +136947,10 @@ function StatusModeGrid({
136798
136947
  availableRows: logsAvailableRows
136799
136948
  }
136800
136949
  ),
136801
- /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: focused === "utilities" })
136950
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", overflow: "hidden", children: [
136951
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(UtilitiesPanel, { isFocused: focused === "utilities" }) }),
136952
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: /* @__PURE__ */ jsx(SettingsPanel, { state, isFocused: focused === "settings" }) })
136953
+ ] })
136802
136954
  ] })
136803
136955
  ] }),
136804
136956
  /* @__PURE__ */ jsx(StatusBar, { state, controller })
@@ -136824,15 +136976,7 @@ function StatusModeSingle({
136824
136976
  }
136825
136977
  };
136826
136978
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
136827
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingX: 1, children: [
136828
- /* @__PURE__ */ jsx(MiniLogo, {}),
136829
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
136830
- SECTION_ORDER.map((section, i) => {
136831
- const isActive = section === focused;
136832
- const label = section.charAt(0).toUpperCase() + section.slice(1);
136833
- return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` [${i + 1}] ${label} ` }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${i + 1}] ${label}` }) }, section);
136834
- })
136835
- ] }),
136979
+ /* @__PURE__ */ jsx(MainHeader, { state }),
136836
136980
  /* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", overflow: "hidden", children: activePanel() }),
136837
136981
  /* @__PURE__ */ jsx(StatusBar, { state, controller })
136838
136982
  ] });
@@ -136857,21 +137001,53 @@ function StatusBar({ state, controller: _controller }) {
136857
137001
  statusParts.length > 0 && /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusParts.join(" | ") })
136858
137002
  ] });
136859
137003
  }
136860
- function InteractiveHeader({ activeView }) {
136861
- const tabs = [
137004
+ function MainHeader({ state }) {
137005
+ const inInteractive = state.mode === "interactive";
137006
+ const focused = state.activeSection;
137007
+ const interactiveView = state.interactiveView;
137008
+ const { stdout } = useStdout();
137009
+ const cols = stdout?.columns ?? 80;
137010
+ const rows = stdout?.rows ?? 24;
137011
+ const interactiveTabs = [
136862
137012
  { key: "b", label: "Board", view: "board" },
136863
137013
  { key: "a", label: "Agents", view: "agents" },
136864
- { key: "g", label: "Settings", view: "settings" }
137014
+ { key: "g", label: "Settings", view: "settings" },
137015
+ { key: "t", label: "Git", view: "git" },
137016
+ { key: "e", label: "Explorer", view: "files" }
136865
137017
  ];
136866
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, paddingX: 1, children: [
137018
+ if (rows < 10) return null;
137019
+ const showHelpHint = cols >= 110;
137020
+ const compactInteractive = cols < 100;
137021
+ const compactSections = cols < 90;
137022
+ const minimal = cols < 70;
137023
+ const tiny = cols < 50;
137024
+ if (tiny) {
137025
+ const activeSectionIdx = inInteractive ? -1 : SECTION_ORDER.indexOf(focused);
137026
+ const activeSectionLabel = activeSectionIdx >= 0 ? SECTION_ORDER[activeSectionIdx].charAt(0).toUpperCase() + SECTION_ORDER[activeSectionIdx].slice(1) : null;
137027
+ const activeInteractive = inInteractive ? interactiveTabs.find((t) => t.view === interactiveView) ?? null : null;
137028
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingX: 1, children: [
137029
+ /* @__PURE__ */ jsx(MiniLogo, {}),
137030
+ activeSectionLabel && /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` ${activeSectionIdx + 1} ${activeSectionLabel} ` }),
137031
+ activeInteractive && /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` ${activeInteractive.key} ${activeInteractive.label} ` })
137032
+ ] });
137033
+ }
137034
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, paddingX: 1, paddingY: 0, children: [
136867
137035
  /* @__PURE__ */ jsx(MiniLogo, {}),
136868
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
136869
- tabs.map(({ key, label, view }) => {
136870
- const isActive = view === activeView;
136871
- return isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: ` [${key}] ${label} ` }, view) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${key}] ${label}` }, view);
137036
+ !minimal && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
137037
+ SECTION_ORDER.map((section, i) => {
137038
+ const isActive = !inInteractive && section === focused;
137039
+ const label = section.charAt(0).toUpperCase() + section.slice(1);
137040
+ if (minimal && !isActive) return null;
137041
+ return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: compactSections ? ` ${i + 1} ${label} ` : ` [${i + 1}] ${label} ` }) : compactSections ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${i + 1}]` }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${i + 1}] ${label}` }) }, section);
137042
+ }),
137043
+ !minimal && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
137044
+ interactiveTabs.map(({ key, label, view }) => {
137045
+ const isActive = inInteractive && view === interactiveView;
137046
+ if (minimal && !isActive) return null;
137047
+ return /* @__PURE__ */ jsx(Box, { marginRight: 1, children: isActive ? /* @__PURE__ */ jsx(Text, { backgroundColor: "cyan", color: "black", bold: true, children: compactInteractive ? ` ${key} ${label} ` : ` [${key}] ${label} ` }) : compactInteractive ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${key}]` }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: `[${key}] ${label}` }) }, view);
136872
137048
  }),
136873
137049
  /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
136874
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[s] status [?] help [q] quit" })
137050
+ showHelpHint && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[?] help [q] quit" })
136875
137051
  ] });
136876
137052
  }
136877
137053
  function columnLabel(col) {
@@ -136886,6 +137062,7 @@ function TaskCard({
136886
137062
  const borderColor = selected ? "cyanBright" : "gray";
136887
137063
  const titleColor = selected ? "whiteBright" : void 0;
136888
137064
  const shortId = task.id.length > 10 ? task.id.slice(0, 8) : task.id;
137065
+ const title = task.title ?? task.description ?? "(untitled)";
136889
137066
  return /* @__PURE__ */ jsxs(
136890
137067
  Box,
136891
137068
  {
@@ -136903,7 +137080,7 @@ function TaskCard({
136903
137080
  task.agentState
136904
137081
  ] })
136905
137082
  ] }),
136906
- /* @__PURE__ */ jsx(Text, { bold: selected, color: titleColor, wrap: "truncate-end", children: task.title ?? task.id })
137083
+ /* @__PURE__ */ jsx(Text, { bold: selected, color: titleColor, wrap: "wrap", children: title })
136907
137084
  ]
136908
137085
  }
136909
137086
  );
@@ -136913,27 +137090,60 @@ function KanbanColumnView({
136913
137090
  tasks,
136914
137091
  isFocused,
136915
137092
  selectedIndex,
136916
- width
137093
+ width,
137094
+ availableRows
136917
137095
  }) {
136918
137096
  const accent = COLUMN_COLORS[column];
136919
137097
  const headerColor = isFocused ? "whiteBright" : accent;
136920
- const cardWidth = Math.max(12, width - 4);
136921
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width, flexShrink: 0, paddingX: 1, children: [
136922
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136923
- /* @__PURE__ */ jsx(Text, { bold: true, color: headerColor, backgroundColor: isFocused ? accent : void 0, children: ` ${columnLabel(column).toUpperCase()} ` }),
136924
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: tasks.length })
136925
- ] }),
136926
- /* @__PURE__ */ jsx(Box, { height: 1 }),
136927
- tasks.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2014" }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 0, children: tasks.map((task, i) => /* @__PURE__ */ jsx(
136928
- TaskCard,
136929
- {
136930
- task,
136931
- selected: isFocused && i === selectedIndex,
136932
- width: cardWidth
136933
- },
136934
- task.id
136935
- )) })
136936
- ] });
137098
+ const cardWidth = Math.max(16, width - 2);
137099
+ const innerHeaderWidth = Math.max(8, width - 2);
137100
+ const label = `${columnLabel(column).toUpperCase()} (${tasks.length})`;
137101
+ const headerText = ` ${label} `.length > innerHeaderWidth ? ` ${label} `.slice(0, innerHeaderWidth) : ` ${label} `.padEnd(innerHeaderWidth, " ");
137102
+ const cardRowsBudget = Math.max(0, availableRows - 4);
137103
+ const visibleCount = Math.max(1, Math.floor(cardRowsBudget / 4));
137104
+ const halfWindow = Math.floor(visibleCount / 2);
137105
+ const maxStart = Math.max(0, tasks.length - visibleCount);
137106
+ const windowStart = Math.max(0, Math.min(selectedIndex - halfWindow, maxStart));
137107
+ const windowEnd = Math.min(tasks.length, windowStart + visibleCount);
137108
+ const visibleTasks = tasks.slice(windowStart, windowEnd);
137109
+ const hiddenAbove = windowStart;
137110
+ const hiddenBelow = tasks.length - windowEnd;
137111
+ return /* @__PURE__ */ jsxs(
137112
+ Box,
137113
+ {
137114
+ flexDirection: "column",
137115
+ width,
137116
+ flexShrink: 1,
137117
+ flexGrow: 1,
137118
+ paddingX: 1,
137119
+ overflow: "hidden",
137120
+ children: [
137121
+ /* @__PURE__ */ jsx(Box, { width: innerHeaderWidth, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { bold: true, color: headerColor, backgroundColor: isFocused ? accent : void 0, children: headerText }) }),
137122
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137123
+ tasks.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2014" }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", gap: 0, flexShrink: 1, overflow: "hidden", children: [
137124
+ hiddenAbove > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
137125
+ "\u2191 ",
137126
+ hiddenAbove,
137127
+ " more"
137128
+ ] }),
137129
+ visibleTasks.map((task, i) => /* @__PURE__ */ jsx(
137130
+ TaskCard,
137131
+ {
137132
+ task,
137133
+ selected: isFocused && windowStart + i === selectedIndex,
137134
+ width: cardWidth
137135
+ },
137136
+ task.id
137137
+ )),
137138
+ hiddenBelow > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
137139
+ "\u2193 ",
137140
+ hiddenBelow,
137141
+ " more"
137142
+ ] })
137143
+ ] })
137144
+ ]
137145
+ }
137146
+ );
136937
137147
  }
136938
137148
  function ProjectSelector({
136939
137149
  open,
@@ -136945,7 +137155,7 @@ function ProjectSelector({
136945
137155
  if (!open) {
136946
137156
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
136947
137157
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Project:" }),
136948
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: current?.name ?? "(none)" }),
137158
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: current?.name ?? "(none)" }),
136949
137159
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[p] change" })
136950
137160
  ] });
136951
137161
  }
@@ -136953,17 +137163,17 @@ function ProjectSelector({
136953
137163
  Box,
136954
137164
  {
136955
137165
  borderStyle: "round",
136956
- borderColor: "cyan",
137166
+ borderColor: "cyanBright",
136957
137167
  flexDirection: "column",
136958
137168
  paddingX: 1,
136959
137169
  backgroundColor: "black",
136960
137170
  width: Math.max(30, ...projects.map((p) => p.name.length + 4)),
136961
137171
  children: [
136962
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", backgroundColor: "black", children: "Pick a project" }),
137172
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", backgroundColor: "black", children: "Pick a project" }),
136963
137173
  projects.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, backgroundColor: "black", children: "(no projects registered)" }) : projects.map((p, i) => {
136964
137174
  const isSel = i === selectedIndex;
136965
137175
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, backgroundColor: "black", children: [
136966
- /* @__PURE__ */ jsx(Text, { color: isSel ? "cyanBright" : "gray", backgroundColor: "black", children: isSel ? "\u25B6" : " " }),
137176
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "white" : "gray", backgroundColor: "black", children: isSel ? "\u25B6" : " " }),
136967
137177
  /* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, backgroundColor: "black", children: p.name })
136968
137178
  ] }, p.id);
136969
137179
  }),
@@ -136973,40 +137183,226 @@ function ProjectSelector({
136973
137183
  }
136974
137184
  );
136975
137185
  }
136976
- function TaskDetailScreen({ task }) {
137186
+ function formatDurationMs(ms) {
137187
+ const s = Math.floor(ms / 1e3);
137188
+ const m = Math.floor(s / 60);
137189
+ if (m > 0) return `${m}m ${s % 60}s`;
137190
+ return `${s}s`;
137191
+ }
137192
+ function formatLogTime(iso) {
137193
+ try {
137194
+ const d = new Date(iso);
137195
+ return `${d.getHours().toString().padStart(2, "0")}:${d.getMinutes().toString().padStart(2, "0")}:${d.getSeconds().toString().padStart(2, "0")}`;
137196
+ } catch {
137197
+ return "??:??:??";
137198
+ }
137199
+ }
137200
+ function TaskDetailScreen({
137201
+ task,
137202
+ projectPath,
137203
+ interactiveData
137204
+ }) {
137205
+ const { stdout } = useStdout();
137206
+ const rows = stdout?.rows ?? 24;
137207
+ const [detail, setDetail] = useState2(null);
137208
+ const [logScrollOffset, setLogScrollOffset] = useState2(0);
137209
+ const [autoFollow, setAutoFollow] = useState2(true);
137210
+ const FIXED_ROWS = 22;
137211
+ const logPaneRows = Math.max(4, rows - FIXED_ROWS);
137212
+ useEffect2(() => {
137213
+ if (!projectPath || !interactiveData) {
137214
+ setDetail("unavailable");
137215
+ return;
137216
+ }
137217
+ let cancelled = false;
137218
+ void interactiveData.tasks.getTaskDetail(projectPath, task.id).then((d) => {
137219
+ if (cancelled) return;
137220
+ if (d === null) {
137221
+ setDetail("unavailable");
137222
+ } else {
137223
+ const trimmed = { ...d, recentLogs: d.recentLogs.slice(-INITIAL_LOG_LIMIT) };
137224
+ setDetail(trimmed);
137225
+ }
137226
+ });
137227
+ const unsub = interactiveData.tasks.subscribeTaskEvents(
137228
+ projectPath,
137229
+ task.id,
137230
+ (event) => {
137231
+ if (cancelled) return;
137232
+ setDetail((prev) => {
137233
+ if (!prev || prev === "unavailable") return prev;
137234
+ if (event.kind === "step:updated") {
137235
+ const steps = prev.steps.map(
137236
+ (s) => s.index === event.step.index ? event.step : s
137237
+ );
137238
+ return { ...prev, steps };
137239
+ }
137240
+ if (event.kind === "log:appended") {
137241
+ const logs = [...prev.recentLogs, event.entry];
137242
+ const trimmed = logs.length > MAX_LOG_ENTRIES2 ? logs.slice(logs.length - MAX_LOG_ENTRIES2) : logs;
137243
+ return { ...prev, recentLogs: trimmed };
137244
+ }
137245
+ if (event.kind === "task:updated") {
137246
+ const merged = [...prev.recentLogs, ...event.task.recentLogs];
137247
+ const deduped = Array.from(
137248
+ new Map(merged.map((e) => [e.timestamp + e.text, e])).values()
137249
+ );
137250
+ const trimmed = deduped.length > MAX_LOG_ENTRIES2 ? deduped.slice(deduped.length - MAX_LOG_ENTRIES2) : deduped;
137251
+ return { ...event.task, recentLogs: trimmed };
137252
+ }
137253
+ return prev;
137254
+ });
137255
+ }
137256
+ );
137257
+ return () => {
137258
+ cancelled = true;
137259
+ unsub();
137260
+ };
137261
+ }, [projectPath, task.id]);
137262
+ const logCount = detail && detail !== "unavailable" ? detail.recentLogs.length : 0;
137263
+ useEffect2(() => {
137264
+ if (autoFollow) setLogScrollOffset(0);
137265
+ }, [autoFollow, logCount]);
137266
+ useInput((input, key) => {
137267
+ if (detail && detail !== "unavailable" && detail.recentLogs.length > 0) {
137268
+ const maxOffset = Math.max(0, detail.recentLogs.length - logPaneRows);
137269
+ if (key.upArrow || input === "k") {
137270
+ setAutoFollow(false);
137271
+ setLogScrollOffset((o) => Math.min(maxOffset, o + 1));
137272
+ return;
137273
+ }
137274
+ if (key.downArrow || input === "j") {
137275
+ setLogScrollOffset((o) => {
137276
+ const next = Math.max(0, o - 1);
137277
+ if (next === 0) setAutoFollow(true);
137278
+ return next;
137279
+ });
137280
+ return;
137281
+ }
137282
+ if (key.pageUp) {
137283
+ setAutoFollow(false);
137284
+ setLogScrollOffset((o) => Math.min(maxOffset, o + Math.floor(logPaneRows / 2)));
137285
+ return;
137286
+ }
137287
+ if (key.pageDown) {
137288
+ setLogScrollOffset((o) => {
137289
+ const next = Math.max(0, o - Math.floor(logPaneRows / 2));
137290
+ if (next === 0) setAutoFollow(true);
137291
+ return next;
137292
+ });
137293
+ return;
137294
+ }
137295
+ if (input === "G") {
137296
+ setLogScrollOffset(0);
137297
+ setAutoFollow(true);
137298
+ return;
137299
+ }
137300
+ if (input === "g") {
137301
+ setAutoFollow(false);
137302
+ setLogScrollOffset(maxOffset);
137303
+ return;
137304
+ }
137305
+ }
137306
+ });
136977
137307
  const accent = COLUMN_COLORS[task.column] ?? "white";
136978
137308
  return /* @__PURE__ */ jsxs(
136979
137309
  Box,
136980
137310
  {
136981
137311
  borderStyle: "round",
136982
- borderColor: "cyan",
137312
+ borderColor: "cyanBright",
136983
137313
  flexDirection: "column",
136984
137314
  paddingX: 2,
136985
137315
  paddingY: 1,
136986
137316
  flexGrow: 1,
136987
137317
  children: [
136988
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: task.id }),
136989
- /* @__PURE__ */ jsx(Box, { height: 1 }),
136990
- /* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", wrap: "wrap", children: task.title ?? task.id }),
136991
- /* @__PURE__ */ jsx(Box, { height: 1 }),
136992
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
136993
- /* @__PURE__ */ jsxs(Box, { children: [
136994
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Column " }),
136995
- /* @__PURE__ */ jsx(Text, { color: accent, bold: true, children: columnLabel(task.column) })
137318
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", flexShrink: 0, children: [
137319
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, children: [
137320
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: task.id }),
137321
+ /* @__PURE__ */ jsxs(Text, { color: accent, bold: true, children: [
137322
+ "[",
137323
+ columnLabel(task.column),
137324
+ "]"
137325
+ ] }),
137326
+ task.agentState && /* @__PURE__ */ jsxs(Text, { color: accent, children: [
137327
+ "\u25B6 ",
137328
+ task.agentState
137329
+ ] })
136996
137330
  ] }),
136997
- task.agentState && /* @__PURE__ */ jsxs(Box, { children: [
136998
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Agent " }),
136999
- /* @__PURE__ */ jsx(Text, { color: accent, bold: true, children: task.agentState })
137000
- ] })
137331
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Esc] back" })
137001
137332
  ] }),
137002
- task.description && /* @__PURE__ */ jsxs(Fragment, { children: [
137003
- /* @__PURE__ */ jsx(Box, { height: 1 }),
137004
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500\u2500\u2500 Description \u2500\u2500\u2500\u2500" }),
137005
- /* @__PURE__ */ jsx(Box, { height: 1 }),
137006
- /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: task.description })
137333
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137334
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", wrap: "wrap", children: task.title ?? task.id }),
137335
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137336
+ detail === null && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, flexShrink: 0, children: [
137337
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137338
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading task details\u2026" })
137339
+ ] }),
137340
+ detail === "unavailable" && /* @__PURE__ */ jsx(Text, { color: "yellow", children: "Task no longer available \u2014 Esc to go back" }),
137341
+ detail && detail !== "unavailable" && /* @__PURE__ */ jsxs(Fragment, { children: [
137342
+ (detail.branch || detail.worktree) && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, flexShrink: 0, children: [
137343
+ detail.branch && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137344
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Branch" }),
137345
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: detail.branch })
137346
+ ] }),
137347
+ detail.worktree && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137348
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Worktree" }),
137349
+ /* @__PURE__ */ jsx(Text, { color: "cyan", wrap: "truncate", children: detail.worktree })
137350
+ ] })
137351
+ ] }),
137352
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137353
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500 Steps \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
137354
+ detail.steps.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no steps yet)" }) : detail.steps.map((step) => {
137355
+ const icon = STEP_ICON[step.status] ?? "\xB7";
137356
+ const color = STEP_COLOR[step.status] ?? "white";
137357
+ const isRunning = step.status === "running";
137358
+ const isDone = step.status === "done" || step.status === "failed" || step.status === "skipped";
137359
+ let durationText = "";
137360
+ if (isRunning && step.startedAt) {
137361
+ const elapsed = Date.now() - new Date(step.startedAt).getTime();
137362
+ durationText = ` (running \u2014 ${formatDurationMs(elapsed)})`;
137363
+ } else if (isDone && step.startedAt && step.endedAt) {
137364
+ const elapsed = new Date(step.endedAt).getTime() - new Date(step.startedAt).getTime();
137365
+ durationText = ` (${step.status} \u2014 ${formatDurationMs(elapsed)})`;
137366
+ }
137367
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, flexShrink: 0, children: [
137368
+ /* @__PURE__ */ jsx(Text, { color, children: icon }),
137369
+ /* @__PURE__ */ jsxs(Text, { bold: isRunning, color: isRunning ? "whiteBright" : void 0, dimColor: step.status === "pending", children: [
137370
+ step.index + 1,
137371
+ ". ",
137372
+ step.name
137373
+ ] }),
137374
+ durationText !== "" && /* @__PURE__ */ jsx(Text, { dimColor: true, children: durationText })
137375
+ ] }, step.index);
137376
+ }),
137377
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137378
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", justifyContent: "space-between", flexShrink: 0, children: [
137379
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500\u2500 Logs \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500" }),
137380
+ /* @__PURE__ */ jsx(Text, { color: autoFollow ? "cyanBright" : void 0, dimColor: !autoFollow, children: autoFollow ? "[live]" : "[paused]" })
137381
+ ] }),
137382
+ detail.recentLogs.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(no log entries yet)" }) : /* @__PURE__ */ jsx(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: (() => {
137383
+ const logs = detail.recentLogs;
137384
+ const total = logs.length;
137385
+ const endIdx = total - logScrollOffset;
137386
+ const startIdx = Math.max(0, endIdx - logPaneRows);
137387
+ const visible = logs.slice(startIdx, endIdx);
137388
+ return visible.map((entry, i) => {
137389
+ const levelColor = entry.level === "warn" ? "yellow" : entry.level === "error" ? "red" : entry.level === "debug" ? "gray" : "white";
137390
+ const levelLabel = entry.level.toUpperCase().padEnd(5);
137391
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, flexShrink: 0, children: [
137392
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatLogTime(entry.timestamp) }),
137393
+ /* @__PURE__ */ jsx(Text, { color: levelColor, children: levelLabel }),
137394
+ entry.source && /* @__PURE__ */ jsxs(Text, { color: "cyan", dimColor: true, children: [
137395
+ "[",
137396
+ entry.source,
137397
+ "]"
137398
+ ] }),
137399
+ /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: entry.text })
137400
+ ] }, startIdx + i);
137401
+ });
137402
+ })() })
137007
137403
  ] }),
137008
137404
  /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
137009
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Esc / Backspace] back to board \xB7 [q] quit" })
137405
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193/j/k scroll \xB7 PgUp/PgDn half-page \xB7 g top \xB7 G bottom \xB7 Esc back" })
137010
137406
  ]
137011
137407
  }
137012
137408
  );
@@ -137027,10 +137423,13 @@ function groupTasksByColumn(tasks) {
137027
137423
  }
137028
137424
  return out;
137029
137425
  }
137030
- function BoardView({ state }) {
137426
+ function BoardView({ state, controller }) {
137031
137427
  const { stdout } = useStdout();
137032
137428
  const cols = stdout?.columns ?? 80;
137033
- const columnWidth = Math.max(20, Math.floor((cols - 2) / KANBAN_COLUMNS.length));
137429
+ const rows = stdout?.rows ?? 24;
137430
+ const isNarrow = cols < NARROW_THRESHOLD;
137431
+ const columnWidth = isNarrow ? Math.max(20, cols - 2) : Math.max(20, Math.floor((cols - 2) / KANBAN_COLUMNS.length));
137432
+ const availableCardRows = Math.max(8, rows - 8);
137034
137433
  const [subView, setSubView] = useState2("board");
137035
137434
  const [projectIndex, setProjectIndex] = useState2(0);
137036
137435
  const [colIndex, setColIndex] = useState2(0);
@@ -137048,6 +137447,12 @@ function BoardView({ state }) {
137048
137447
  const selectedProject = projectsState.projects[projectIndex] ?? null;
137049
137448
  const tasksState = useTasks(state.interactiveData, selectedProject);
137050
137449
  const grouped = groupTasksByColumn(tasksState.tasks);
137450
+ useEffect2(() => {
137451
+ controller.setBoardScopedProjectPath(selectedProject?.path ?? null);
137452
+ return () => {
137453
+ controller.setBoardScopedProjectPath(null);
137454
+ };
137455
+ }, [controller, selectedProject?.path]);
137051
137456
  const focusedColumn = KANBAN_COLUMNS[colIndex];
137052
137457
  const focusedTasks = grouped[focusedColumn];
137053
137458
  const focusedRow = clamp2(rowByColumn[focusedColumn] ?? 0, 0, Math.max(0, focusedTasks.length - 1));
@@ -137087,6 +137492,18 @@ function BoardView({ state }) {
137087
137492
  }
137088
137493
  return;
137089
137494
  }
137495
+ if (input === "g") {
137496
+ controller.setInteractiveView("settings");
137497
+ return;
137498
+ }
137499
+ if (input === "a" || input === "A") {
137500
+ controller.setInteractiveView("agents");
137501
+ return;
137502
+ }
137503
+ if (input === "t" || input === "T") {
137504
+ controller.setInteractiveView("git");
137505
+ return;
137506
+ }
137090
137507
  if (input === "p" || input === "P") {
137091
137508
  setPickerOriginal(projectIndex);
137092
137509
  setSubView("picker");
@@ -137126,7 +137543,8 @@ function BoardView({ state }) {
137126
137543
  return;
137127
137544
  }
137128
137545
  });
137129
- const hintText = subView === "picker" ? "\u2191\u2193 pick \xB7 Enter confirm \xB7 Esc cancel" : subView === "detail" ? "Esc back \xB7 q quit" : subView === "create" ? "type a task title \xB7 Enter create \xB7 Esc cancel" : "\u2190\u2192 column \xB7 \u2191\u2193 task \xB7 Enter open \xB7 n new \xB7 p project";
137546
+ const narrowColumnIndicator = isNarrow ? ` \xB7 ${colIndex + 1}/${KANBAN_COLUMNS.length} ${columnLabel(focusedColumn).toUpperCase()} (${focusedTasks.length})` : "";
137547
+ const hintText = subView === "picker" ? "\u2191\u2193 pick \xB7 Enter confirm \xB7 Esc cancel" : subView === "detail" ? "Esc back \xB7 q quit" : subView === "create" ? "type a task title \xB7 Enter create \xB7 Esc cancel" : `\u2190\u2192 column \xB7 \u2191\u2193 task \xB7 Enter open \xB7 n new \xB7 p project${narrowColumnIndicator}`;
137130
137548
  const submitNewTask = async () => {
137131
137549
  const title = newTaskTitle.trim();
137132
137550
  if (!title) {
@@ -137151,7 +137569,7 @@ function BoardView({ state }) {
137151
137569
  }
137152
137570
  };
137153
137571
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
137154
- /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, paddingX: 1, children: [
137572
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, paddingX: 1, flexShrink: 0, children: [
137155
137573
  /* @__PURE__ */ jsx(
137156
137574
  ProjectSelector,
137157
137575
  {
@@ -137164,18 +137582,18 @@ function BoardView({ state }) {
137164
137582
  /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
137165
137583
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: hintText })
137166
137584
  ] }),
137167
- /* @__PURE__ */ jsx(Box, { height: 1 }),
137585
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137168
137586
  subView === "create" ? /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsxs(
137169
137587
  Box,
137170
137588
  {
137171
137589
  borderStyle: "round",
137172
- borderColor: "cyan",
137590
+ borderColor: "cyanBright",
137173
137591
  flexDirection: "column",
137174
137592
  paddingX: 2,
137175
137593
  paddingY: 1,
137176
137594
  width: Math.min(80, Math.max(40, cols - 8)),
137177
137595
  children: [
137178
- /* @__PURE__ */ jsx(Text, { bold: true, color: "cyanBright", children: "New Task" }),
137596
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "New Task" }),
137179
137597
  /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
137180
137598
  "Project: ",
137181
137599
  selectedProject?.name ?? "(none)"
@@ -137183,7 +137601,7 @@ function BoardView({ state }) {
137183
137601
  /* @__PURE__ */ jsx(Box, { height: 1 }),
137184
137602
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Title" }),
137185
137603
  /* @__PURE__ */ jsxs(Box, { children: [
137186
- /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: "\u25B8 " }),
137604
+ /* @__PURE__ */ jsx(Text, { color: "white", children: "\u25B8 " }),
137187
137605
  /* @__PURE__ */ jsx(
137188
137606
  TextInput,
137189
137607
  {
@@ -137197,25 +137615,44 @@ function BoardView({ state }) {
137197
137615
  /* @__PURE__ */ jsx(Box, { height: 1 }),
137198
137616
  createError && /* @__PURE__ */ jsx(Text, { color: "red", children: createError }),
137199
137617
  creating ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137200
- /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137618
+ /* @__PURE__ */ jsx(Text, { color: "white", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137201
137619
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Creating\u2026" })
137202
137620
  ] }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Enter to create \xB7 Esc to cancel" })
137203
137621
  ]
137204
137622
  }
137205
- ) }) : subView === "detail" && selectedTask ? /* @__PURE__ */ jsx(Box, { flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx(TaskDetailScreen, { task: selectedTask }) }) : tasksState.loading ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, gap: 1, children: [
137206
- /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137623
+ ) }) : subView === "detail" && selectedTask ? /* @__PURE__ */ jsx(Box, { flexGrow: 1, paddingX: 1, children: /* @__PURE__ */ jsx(
137624
+ TaskDetailScreen,
137625
+ {
137626
+ task: selectedTask,
137627
+ projectPath: selectedProject?.path ?? null,
137628
+ interactiveData: state.interactiveData
137629
+ }
137630
+ ) }) : tasksState.loading ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, gap: 1, children: [
137631
+ /* @__PURE__ */ jsx(Text, { color: "white", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137207
137632
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading tasks\u2026" })
137208
137633
  ] }) : tasksState.tasks.length === 0 ? /* @__PURE__ */ jsxs(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, flexDirection: "column", children: [
137209
137634
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "No tasks in this project." }),
137210
137635
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Press [p] to switch projects." })
137211
- ] }) : /* @__PURE__ */ jsx(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: KANBAN_COLUMNS.map((col, i) => /* @__PURE__ */ jsx(
137636
+ ] }) : /* @__PURE__ */ jsx(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: isNarrow ? /* @__PURE__ */ jsx(
137637
+ KanbanColumnView,
137638
+ {
137639
+ column: focusedColumn,
137640
+ tasks: focusedTasks,
137641
+ isFocused: true,
137642
+ selectedIndex: focusedRow,
137643
+ width: columnWidth,
137644
+ availableRows: availableCardRows
137645
+ },
137646
+ focusedColumn
137647
+ ) : KANBAN_COLUMNS.map((col, i) => /* @__PURE__ */ jsx(
137212
137648
  KanbanColumnView,
137213
137649
  {
137214
137650
  column: col,
137215
137651
  tasks: grouped[col],
137216
137652
  isFocused: i === colIndex,
137217
137653
  selectedIndex: rowByColumn[col] ?? 0,
137218
- width: columnWidth
137654
+ width: columnWidth,
137655
+ availableRows: availableCardRows
137219
137656
  },
137220
137657
  col
137221
137658
  )) })
@@ -137224,7 +137661,7 @@ function BoardView({ state }) {
137224
137661
  function agentStateColor(state) {
137225
137662
  switch (state) {
137226
137663
  case "active":
137227
- return "cyan";
137664
+ return "cyanBright";
137228
137665
  case "running":
137229
137666
  return "green";
137230
137667
  case "error":
@@ -137241,7 +137678,7 @@ function heartbeatFreshness(lastHeartbeatAt) {
137241
137678
  function AgentsView({ state }) {
137242
137679
  const { stdout } = useStdout();
137243
137680
  const cols = stdout?.columns ?? 80;
137244
- const isNarrow = cols < 80;
137681
+ const isNarrow = cols < NARROW_THRESHOLD;
137245
137682
  const [selectedIndex, setSelectedIndex] = useState2(0);
137246
137683
  const [agents, setAgents] = useState2([]);
137247
137684
  const [detail, setDetail] = useState2(null);
@@ -137306,6 +137743,14 @@ function AgentsView({ state }) {
137306
137743
  setDetailFocused((f) => !f);
137307
137744
  return;
137308
137745
  }
137746
+ if (key.leftArrow) {
137747
+ setDetailFocused(false);
137748
+ return;
137749
+ }
137750
+ if (key.rightArrow) {
137751
+ setDetailFocused(true);
137752
+ return;
137753
+ }
137309
137754
  if (!detailFocused) {
137310
137755
  if (key.upArrow || input === "k") {
137311
137756
  setSelectedIndex((i) => Math.max(0, i - 1));
@@ -137359,19 +137804,19 @@ function AgentsView({ state }) {
137359
137804
  }
137360
137805
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
137361
137806
  statusMsg && /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: statusMsg }) }),
137362
- /* @__PURE__ */ jsxs(Box, { flexDirection: isNarrow ? "column" : "row", flexGrow: 1, overflow: "hidden", children: [
137363
- /* @__PURE__ */ jsxs(
137807
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
137808
+ (!isNarrow || !detailFocused) && /* @__PURE__ */ jsxs(
137364
137809
  Box,
137365
137810
  {
137366
137811
  borderStyle: "round",
137367
- borderColor: detailFocused ? "gray" : "cyan",
137812
+ borderColor: detailFocused ? "gray" : "cyanBright",
137368
137813
  flexDirection: "column",
137369
137814
  width: isNarrow ? void 0 : "30%",
137370
137815
  flexGrow: isNarrow ? 1 : 0,
137371
137816
  flexShrink: 0,
137372
137817
  overflow: "hidden",
137373
137818
  children: [
137374
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { bold: !detailFocused, color: !detailFocused ? "cyan" : void 0, dimColor: detailFocused, children: [
137819
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { bold: !detailFocused, color: !detailFocused ? "cyanBright" : void 0, dimColor: detailFocused, children: [
137375
137820
  "Agents (",
137376
137821
  agents.length,
137377
137822
  ")"
@@ -137380,7 +137825,7 @@ function AgentsView({ state }) {
137380
137825
  const isSel = i === selectedIndex;
137381
137826
  const { fresh, label } = heartbeatFreshness(agent.lastHeartbeatAt);
137382
137827
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137383
- /* @__PURE__ */ jsx(Text, { color: isSel ? "cyanBright" : "gray", children: isSel ? "\u25B6" : " " }),
137828
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "white" : "gray", children: isSel ? "\u25B6" : " " }),
137384
137829
  /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
137385
137830
  /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137386
137831
  /* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, wrap: "truncate", children: agent.name }),
@@ -137396,18 +137841,18 @@ function AgentsView({ state }) {
137396
137841
  ]
137397
137842
  }
137398
137843
  ),
137399
- /* @__PURE__ */ jsxs(
137844
+ (!isNarrow || detailFocused) && /* @__PURE__ */ jsxs(
137400
137845
  Box,
137401
137846
  {
137402
137847
  borderStyle: "round",
137403
- borderColor: detailFocused ? "cyan" : "gray",
137848
+ borderColor: detailFocused ? "cyanBright" : "gray",
137404
137849
  flexDirection: "column",
137405
137850
  flexGrow: 1,
137406
137851
  overflow: "hidden",
137407
137852
  children: [
137408
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: detailFocused, color: detailFocused ? "cyan" : void 0, dimColor: !detailFocused, children: "Agent Detail" }) }),
137853
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: detailFocused, color: detailFocused ? "cyanBright" : void 0, dimColor: !detailFocused, children: "Agent Detail" }) }),
137409
137854
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: !selectedAgent ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Select an agent from the list." }) : loadingDetail ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137410
- /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137855
+ /* @__PURE__ */ jsx(Text, { color: "white", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
137411
137856
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" })
137412
137857
  ] }) : !detail ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Could not load agent detail." }) : /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
137413
137858
  /* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", children: detail.name }),
@@ -137427,7 +137872,7 @@ function AgentsView({ state }) {
137427
137872
  ] }),
137428
137873
  detail.taskId && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137429
137874
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Task:" }),
137430
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: detail.taskId })
137875
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: detail.taskId })
137431
137876
  ] }),
137432
137877
  detail.capabilities.length > 0 && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137433
137878
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Caps:" }),
@@ -137447,7 +137892,13 @@ function AgentsView({ state }) {
137447
137892
  }
137448
137893
  )
137449
137894
  ] }),
137450
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[s] start [x] stop [D] delete [r] refresh [Tab] focus \u2191\u2193 select" }) })
137895
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, flexDirection: "row", gap: 1, children: [
137896
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[s] start [x] stop [D] delete [r] refresh [Tab] focus \u2191\u2193 select" }),
137897
+ isNarrow && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
137898
+ "[narrow] ",
137899
+ detailFocused ? "detail" : "list"
137900
+ ] })
137901
+ ] })
137451
137902
  ] });
137452
137903
  }
137453
137904
  function SettingsInteractiveView({ state }) {
@@ -137546,7 +137997,7 @@ function SettingsInteractiveView({ state }) {
137546
137997
  return /* @__PURE__ */ jsx(Text, { color: v ? "green" : "yellow", children: v ? "enabled" : "disabled" });
137547
137998
  }
137548
137999
  if (def.type === "enum") {
137549
- return /* @__PURE__ */ jsx(Text, { color: "cyan", children: String(v) });
138000
+ return /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: String(v) });
137550
138001
  }
137551
138002
  return /* @__PURE__ */ jsx(Text, { children: String(v) });
137552
138003
  }
@@ -137557,16 +138008,16 @@ function SettingsInteractiveView({ state }) {
137557
138008
  Box,
137558
138009
  {
137559
138010
  borderStyle: "round",
137560
- borderColor: detailFocused ? "gray" : "cyan",
138011
+ borderColor: detailFocused ? "gray" : "cyanBright",
137561
138012
  flexDirection: "column",
137562
138013
  width: "35%",
137563
138014
  overflow: "hidden",
137564
138015
  children: [
137565
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: !detailFocused, color: !detailFocused ? "cyan" : void 0, dimColor: detailFocused, children: "Settings" }) }),
138016
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: !detailFocused, color: !detailFocused ? "cyanBright" : void 0, dimColor: detailFocused, children: "Settings" }) }),
137566
138017
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: !localSettings ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" }) : SETTING_DEFS.map((def, i) => {
137567
138018
  const isSel = i === selectedIndex;
137568
138019
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
137569
- /* @__PURE__ */ jsx(Text, { color: isSel ? "cyanBright" : "gray", children: isSel ? "\u25B6" : " " }),
138020
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "white" : "gray", children: isSel ? "\u25B6" : " " }),
137570
138021
  /* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, wrap: "truncate", children: def.label }),
137571
138022
  /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
137572
138023
  renderValue(def, localSettings)
@@ -137579,12 +138030,12 @@ function SettingsInteractiveView({ state }) {
137579
138030
  Box,
137580
138031
  {
137581
138032
  borderStyle: "round",
137582
- borderColor: detailFocused ? "cyan" : "gray",
138033
+ borderColor: detailFocused ? "cyanBright" : "gray",
137583
138034
  flexDirection: "column",
137584
138035
  flexGrow: 1,
137585
138036
  overflow: "hidden",
137586
138037
  children: [
137587
- /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: detailFocused, color: detailFocused ? "cyan" : void 0, dimColor: !detailFocused, children: "Edit / Models" }) }),
138038
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: detailFocused, color: detailFocused ? "cyanBright" : void 0, dimColor: !detailFocused, children: "Edit / Models" }) }),
137588
138039
  /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: !localSettings ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading settings\u2026" }) : !selectedDef ? null : /* @__PURE__ */ jsxs(Fragment, { children: [
137589
138040
  /* @__PURE__ */ jsx(Text, { bold: true, color: "whiteBright", children: selectedDef.label }),
137590
138041
  /* @__PURE__ */ jsx(Box, { height: 1 }),
@@ -137598,7 +138049,7 @@ function SettingsInteractiveView({ state }) {
137598
138049
  selectedDef.type === "enum" && selectedDef.options && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
137599
138050
  /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[\u2190/\u2192] cycle options:" }),
137600
138051
  selectedDef.options.map((opt) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
137601
- /* @__PURE__ */ jsx(Text, { color: localSettings[selectedDef.key] === opt ? "cyanBright" : "gray", children: localSettings[selectedDef.key] === opt ? "\u25B6" : " " }),
138052
+ /* @__PURE__ */ jsx(Text, { color: localSettings[selectedDef.key] === opt ? "white" : "gray", children: localSettings[selectedDef.key] === opt ? "\u25B6" : " " }),
137602
138053
  /* @__PURE__ */ jsx(Text, { children: opt })
137603
138054
  ] }, opt))
137604
138055
  ] }),
@@ -137629,20 +138080,989 @@ function SettingsInteractiveView({ state }) {
137629
138080
  /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Tab] switch panel \u2191\u2193 select setting [Space] toggle bool [+/-] adjust num [\u2190/\u2192] cycle enum" }) })
137630
138081
  ] });
137631
138082
  }
137632
- function InteractiveMode({ state }) {
138083
+ function relMs(ms) {
138084
+ if (ms === null) return "never";
138085
+ const diff = Date.now() - ms;
138086
+ const s = Math.floor(diff / 1e3);
138087
+ if (s < 60) return `${s}s ago`;
138088
+ const m = Math.floor(s / 60);
138089
+ if (m < 60) return `${m}m ago`;
138090
+ const h = Math.floor(m / 60);
138091
+ if (h < 24) return `${h}h ago`;
138092
+ return `${Math.floor(h / 24)}d ago`;
138093
+ }
138094
+ function statusColor(s) {
138095
+ if (s === "A") return "green";
138096
+ if (s === "D") return "red";
138097
+ if (s === "R" || s === "C") return "cyan";
138098
+ if (s === "M" || s === "m") return "yellow";
138099
+ return "gray";
138100
+ }
138101
+ function truncatePath(p, maxLen) {
138102
+ if (p.length <= maxLen) return p;
138103
+ return "\u2026" + p.slice(-(maxLen - 1));
138104
+ }
138105
+ function authorInitials(name) {
138106
+ const parts = name.trim().split(/\s+/);
138107
+ if (parts.length === 1) return (parts[0] ?? "?").slice(0, 2).toUpperCase();
138108
+ return ((parts[0]?.[0] ?? "") + (parts[parts.length - 1]?.[0] ?? "")).toUpperCase();
138109
+ }
138110
+ function PushModal({
138111
+ status,
138112
+ commits,
138113
+ onConfirm,
138114
+ onCancel
138115
+ }) {
138116
+ useInput((_input, key) => {
138117
+ if (key.return) {
138118
+ onConfirm();
138119
+ return;
138120
+ }
138121
+ if (key.escape) {
138122
+ onCancel();
138123
+ return;
138124
+ }
138125
+ });
138126
+ const toPush = commits.slice(0, status.ahead);
138127
+ return /* @__PURE__ */ jsxs(
138128
+ Box,
138129
+ {
138130
+ position: "absolute",
138131
+ flexDirection: "column",
138132
+ borderStyle: "round",
138133
+ borderColor: "cyanBright",
138134
+ paddingX: 2,
138135
+ paddingY: 1,
138136
+ backgroundColor: "black",
138137
+ children: [
138138
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "Push to remote" }),
138139
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
138140
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138141
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Branch:" }),
138142
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: status.branch }),
138143
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "ahead" }),
138144
+ /* @__PURE__ */ jsx(Text, { color: "yellow", children: status.ahead })
138145
+ ] }),
138146
+ toPush.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
138147
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
138148
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Commits to push (oldest\u2192newest):" }),
138149
+ [...toPush].reverse().map((c) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, marginLeft: 1, children: [
138150
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: c.shortSha }),
138151
+ /* @__PURE__ */ jsx(Text, { wrap: "truncate", children: c.subject })
138152
+ ] }, c.sha))
138153
+ ] }),
138154
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
138155
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Enter] push [Esc] cancel" })
138156
+ ]
138157
+ }
138158
+ );
138159
+ }
138160
+ function GitView({ state }) {
138161
+ const { stdout } = useStdout();
138162
+ const cols = stdout?.columns ?? 80;
138163
+ const data = state.interactiveData;
138164
+ const [projectIndex, setProjectIndex] = useState2(0);
138165
+ const [pickerOpen, setPickerOpen] = useState2(false);
138166
+ const [pickerOriginal, setPickerOriginal] = useState2(0);
138167
+ const projectsState = useProjects(data);
138168
+ const selectedProject = projectsState.projects[projectIndex] ?? null;
138169
+ const projectPath = selectedProject?.path ?? null;
138170
+ const [gitStatus, setGitStatus] = useState2(null);
138171
+ const [commits, setCommits] = useState2([]);
138172
+ const [branches, setBranches] = useState2([]);
138173
+ const [worktrees, setWorktrees] = useState2([]);
138174
+ const [loading, setLoading] = useState2(false);
138175
+ const [statusMsg, setStatusMsg] = useState2(null);
138176
+ const [commitIndex, setCommitIndex] = useState2(0);
138177
+ const [commitDetail, setCommitDetail] = useState2(null);
138178
+ const [loadingDetail, setLoadingDetail] = useState2(false);
138179
+ const [branchIndex, setBranchIndex] = useState2(0);
138180
+ const [worktreeIndex, setWorktreeIndex] = useState2(0);
138181
+ const [activePane, setActivePane] = useState2("status");
138182
+ const [pushModal, setPushModal] = useState2(null);
138183
+ const refresh = useCallback2(async () => {
138184
+ if (!data || !projectPath) return;
138185
+ setLoading(true);
138186
+ try {
138187
+ const [s, c, b, w] = await Promise.all([
138188
+ data.git.getStatus(projectPath),
138189
+ data.git.listCommits(projectPath, 15),
138190
+ data.git.listBranches(projectPath),
138191
+ data.git.listWorktrees(projectPath)
138192
+ ]);
138193
+ setGitStatus(s);
138194
+ setCommits(c);
138195
+ setBranches(b);
138196
+ setWorktrees(w);
138197
+ } catch (err) {
138198
+ setStatusMsg(`Error: ${err instanceof Error ? err.message : String(err)}`);
138199
+ } finally {
138200
+ setLoading(false);
138201
+ }
138202
+ }, [data, projectPath]);
138203
+ useEffect2(() => {
138204
+ void refresh();
138205
+ }, [refresh]);
138206
+ useEffect2(() => {
138207
+ const id = setInterval(() => {
138208
+ void refresh();
138209
+ }, 5e3);
138210
+ return () => clearInterval(id);
138211
+ }, [refresh]);
138212
+ const selectedCommit = commits[commitIndex] ?? null;
138213
+ useEffect2(() => {
138214
+ if (!data || !projectPath || !selectedCommit) {
138215
+ setCommitDetail(null);
138216
+ return;
138217
+ }
138218
+ setLoadingDetail(true);
138219
+ data.git.showCommit(projectPath, selectedCommit.sha).then((d) => {
138220
+ setCommitDetail(d);
138221
+ setLoadingDetail(false);
138222
+ }).catch(() => {
138223
+ setCommitDetail(null);
138224
+ setLoadingDetail(false);
138225
+ });
138226
+ }, [data, projectPath, selectedCommit?.sha]);
138227
+ useInput((input, key) => {
138228
+ if (pushModal) {
138229
+ if (pushModal.phase === "confirm") {
138230
+ if (key.return) {
138231
+ setPushModal({ phase: "pushing" });
138232
+ if (data && projectPath) {
138233
+ data.git.push(projectPath).then((result) => {
138234
+ setPushModal({ phase: "done", message: result.output || (result.success ? "Push successful" : "Push failed"), isError: !result.success });
138235
+ if (result.success) {
138236
+ setTimeout(() => {
138237
+ setPushModal(null);
138238
+ void refresh();
138239
+ }, 2e3);
138240
+ }
138241
+ }).catch((err) => {
138242
+ setPushModal({ phase: "done", message: err instanceof Error ? err.message : String(err), isError: true });
138243
+ });
138244
+ }
138245
+ return;
138246
+ }
138247
+ if (key.escape) {
138248
+ setPushModal(null);
138249
+ return;
138250
+ }
138251
+ }
138252
+ if (pushModal.phase === "done" && (pushModal.isError || key.escape)) {
138253
+ setPushModal(null);
138254
+ return;
138255
+ }
138256
+ return;
138257
+ }
138258
+ if (pickerOpen) {
138259
+ if (key.upArrow || input === "k") {
138260
+ setProjectIndex((p) => Math.max(0, p - 1));
138261
+ return;
138262
+ }
138263
+ if (key.downArrow || input === "j") {
138264
+ setProjectIndex((p) => Math.min(projectsState.projects.length - 1, p + 1));
138265
+ return;
138266
+ }
138267
+ if (key.return) {
138268
+ setPickerOpen(false);
138269
+ return;
138270
+ }
138271
+ if (key.escape) {
138272
+ setProjectIndex(pickerOriginal);
138273
+ setPickerOpen(false);
138274
+ return;
138275
+ }
138276
+ return;
138277
+ }
138278
+ if (input === "p") {
138279
+ setPickerOriginal(projectIndex);
138280
+ setPickerOpen(true);
138281
+ return;
138282
+ }
138283
+ if (input === "r") {
138284
+ void refresh();
138285
+ return;
138286
+ }
138287
+ if (input === "P" && gitStatus && gitStatus.ahead > 0) {
138288
+ setPushModal({ phase: "confirm", commits });
138289
+ return;
138290
+ }
138291
+ if (input === "F" && data && projectPath) {
138292
+ setStatusMsg("Fetching\u2026");
138293
+ data.git.fetch(projectPath).then((result) => {
138294
+ setStatusMsg(result.success ? "Fetched" : `Fetch failed: ${result.output}`);
138295
+ void refresh();
138296
+ }).catch((err) => {
138297
+ setStatusMsg(`Fetch error: ${err instanceof Error ? err.message : String(err)}`);
138298
+ });
138299
+ return;
138300
+ }
138301
+ if (key.leftArrow || input === "h") {
138302
+ setActivePane((p) => {
138303
+ const order = worktrees.length > 1 ? ["status", "branches", "worktrees", "commits", "changes"] : ["status", "branches", "commits", "changes"];
138304
+ const i = order.indexOf(p);
138305
+ return order[Math.max(0, i - 1)] ?? p;
138306
+ });
138307
+ return;
138308
+ }
138309
+ if (key.rightArrow || input === "l") {
138310
+ setActivePane((p) => {
138311
+ const order = worktrees.length > 1 ? ["status", "branches", "worktrees", "commits", "changes"] : ["status", "branches", "commits", "changes"];
138312
+ const i = order.indexOf(p);
138313
+ return order[Math.min(order.length - 1, i + 1)] ?? p;
138314
+ });
138315
+ return;
138316
+ }
138317
+ if (activePane === "commits") {
138318
+ if (key.upArrow || input === "k") {
138319
+ setCommitIndex((i) => Math.max(0, i - 1));
138320
+ return;
138321
+ }
138322
+ if (key.downArrow || input === "j") {
138323
+ setCommitIndex((i) => Math.min(commits.length - 1, i + 1));
138324
+ return;
138325
+ }
138326
+ }
138327
+ if (activePane === "branches") {
138328
+ if (key.upArrow || input === "k") {
138329
+ setBranchIndex((i) => Math.max(0, i - 1));
138330
+ return;
138331
+ }
138332
+ if (key.downArrow || input === "j") {
138333
+ setBranchIndex((i) => Math.min(branches.length - 1, i + 1));
138334
+ return;
138335
+ }
138336
+ }
138337
+ if (activePane === "worktrees") {
138338
+ if (key.upArrow || input === "k") {
138339
+ setWorktreeIndex((i) => Math.max(0, i - 1));
138340
+ return;
138341
+ }
138342
+ if (key.downArrow || input === "j") {
138343
+ setWorktreeIndex((i) => Math.min(worktrees.length - 1, i + 1));
138344
+ return;
138345
+ }
138346
+ }
138347
+ });
138348
+ const isNarrow = cols < NARROW_THRESHOLD;
138349
+ const leftWidth = Math.max(24, Math.floor(cols * 0.35));
138350
+ const rightWidth = cols - leftWidth - 1;
138351
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
138352
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 2, paddingX: 1, children: [
138353
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138354
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Project:" }),
138355
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: selectedProject?.name ?? "(none)" }),
138356
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[p] change" })
138357
+ ] }),
138358
+ loading && /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138359
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
138360
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "refreshing" })
138361
+ ] }),
138362
+ statusMsg && /* @__PURE__ */ jsx(Text, { color: "yellow", children: statusMsg }),
138363
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1 }),
138364
+ gitStatus && /* @__PURE__ */ jsx(Box, { flexDirection: "row", gap: 1, children: gitStatus.detached ? /* @__PURE__ */ jsx(Text, { color: "yellow", children: "detached HEAD" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
138365
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: gitStatus.branch }),
138366
+ gitStatus.ahead > 0 && /* @__PURE__ */ jsxs(Text, { color: "green", children: [
138367
+ "\u2191",
138368
+ gitStatus.ahead
138369
+ ] }),
138370
+ gitStatus.behind > 0 && /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
138371
+ "\u2193",
138372
+ gitStatus.behind
138373
+ ] })
138374
+ ] }) })
138375
+ ] }),
138376
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
138377
+ (!isNarrow || activePane === "status" || activePane === "branches" || activePane === "worktrees") && /* @__PURE__ */ jsxs(
138378
+ Box,
138379
+ {
138380
+ flexDirection: "column",
138381
+ width: isNarrow ? void 0 : leftWidth,
138382
+ flexGrow: isNarrow ? 1 : 0,
138383
+ flexShrink: 0,
138384
+ overflow: "hidden",
138385
+ children: [
138386
+ (!isNarrow || activePane === "status") && /* @__PURE__ */ jsxs(
138387
+ Box,
138388
+ {
138389
+ borderStyle: "round",
138390
+ borderColor: activePane === "status" ? "cyanBright" : "gray",
138391
+ flexDirection: "column",
138392
+ flexShrink: 0,
138393
+ overflow: "hidden",
138394
+ children: [
138395
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: activePane === "status", color: activePane === "status" ? "blue" : void 0, dimColor: activePane !== "status", children: "Status" }) }),
138396
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: !gitStatus ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: projectPath ? "Loading\u2026" : "No project" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
138397
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138398
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Remote:" }),
138399
+ /* @__PURE__ */ jsx(Text, { color: "gray", wrap: "truncate", children: gitStatus.remoteUrl || "(none)" })
138400
+ ] }),
138401
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138402
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Fetched:" }),
138403
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: relMs(gitStatus.lastFetchAt) })
138404
+ ] }),
138405
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138406
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Staged:" }),
138407
+ /* @__PURE__ */ jsx(Text, { color: gitStatus.staged.length > 0 ? "green" : "gray", children: gitStatus.staged.length }),
138408
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Modified:" }),
138409
+ /* @__PURE__ */ jsx(Text, { color: gitStatus.unstaged.length > 0 ? "yellow" : "gray", children: gitStatus.unstaged.length }),
138410
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "New:" }),
138411
+ /* @__PURE__ */ jsx(Text, { color: gitStatus.untracked.length > 0 ? "cyan" : "gray", children: gitStatus.untracked.length })
138412
+ ] })
138413
+ ] }) })
138414
+ ]
138415
+ }
138416
+ ),
138417
+ (!isNarrow || activePane === "branches") && /* @__PURE__ */ jsxs(
138418
+ Box,
138419
+ {
138420
+ borderStyle: "round",
138421
+ borderColor: activePane === "branches" ? "cyanBright" : "gray",
138422
+ flexDirection: "column",
138423
+ flexGrow: isNarrow ? 1 : 1,
138424
+ overflow: "hidden",
138425
+ children: [
138426
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: activePane === "branches", color: activePane === "branches" ? "blue" : void 0, dimColor: activePane !== "branches", children: "Branches" }) }),
138427
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: [
138428
+ branches.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2014" }) : branches.slice(0, 8).map((b, bi) => {
138429
+ const isSel = activePane === "branches" && bi === branchIndex;
138430
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138431
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "white" : b.isCurrent ? "cyanBright" : "gray", children: isSel ? "\u25B6" : b.isCurrent ? "\u25B6" : " " }),
138432
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "whiteBright" : b.isCurrent ? "white" : "gray", bold: isSel || b.isCurrent, wrap: "truncate", children: b.name }),
138433
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: b.shortSha })
138434
+ ] }, b.name);
138435
+ }),
138436
+ branches.length > 8 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138437
+ "\u2026+",
138438
+ branches.length - 8,
138439
+ " more"
138440
+ ] })
138441
+ ] })
138442
+ ]
138443
+ }
138444
+ ),
138445
+ worktrees.length > 1 && (!isNarrow || activePane === "worktrees") && /* @__PURE__ */ jsxs(
138446
+ Box,
138447
+ {
138448
+ borderStyle: "round",
138449
+ borderColor: activePane === "worktrees" ? "cyanBright" : "gray",
138450
+ flexDirection: "column",
138451
+ flexShrink: 0,
138452
+ overflow: "hidden",
138453
+ children: [
138454
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { bold: activePane === "worktrees", color: activePane === "worktrees" ? "blue" : void 0, dimColor: activePane !== "worktrees", children: [
138455
+ "Worktrees (",
138456
+ worktrees.length,
138457
+ ")"
138458
+ ] }) }),
138459
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, overflow: "hidden", children: worktrees.map((wt, wi) => {
138460
+ const isSel = activePane === "worktrees" && wi === worktreeIndex;
138461
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138462
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "white" : wt.isCurrent ? "cyanBright" : "gray", children: isSel ? "\u25B6" : wt.isCurrent ? "\u25B6" : " " }),
138463
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "whiteBright" : wt.isCurrent ? "white" : "gray", bold: isSel, wrap: "truncate", children: wt.branch }),
138464
+ wt.isLocked && /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u{1F512}" })
138465
+ ] }, wt.path);
138466
+ }) })
138467
+ ]
138468
+ }
138469
+ )
138470
+ ]
138471
+ }
138472
+ ),
138473
+ (!isNarrow || activePane === "commits" || activePane === "changes") && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
138474
+ (!isNarrow || activePane === "commits") && /* @__PURE__ */ jsxs(
138475
+ Box,
138476
+ {
138477
+ borderStyle: "round",
138478
+ borderColor: activePane === "commits" ? "cyanBright" : "gray",
138479
+ flexDirection: "column",
138480
+ flexGrow: 1,
138481
+ overflow: "hidden",
138482
+ children: [
138483
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: activePane === "commits", color: activePane === "commits" ? "blue" : void 0, dimColor: activePane !== "commits", children: "Commits" }) }),
138484
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: commits.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: loading ? "Loading\u2026" : "No commits" }) : commits.map((c, i) => {
138485
+ const isSel = i === commitIndex;
138486
+ const initials = authorInitials(c.authorName);
138487
+ const subjectWidth = Math.max(10, isNarrow ? cols - 30 : rightWidth - 30);
138488
+ const subject = c.subject.length > subjectWidth ? c.subject.slice(0, subjectWidth - 1) + "\u2026" : c.subject;
138489
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138490
+ /* @__PURE__ */ jsx(Text, { color: isSel ? "white" : "gray", children: isSel ? "\u25B6" : " " }),
138491
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: c.shortSha }),
138492
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: c.relativeTime.slice(0, 8).padEnd(8) }),
138493
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: initials.padEnd(2) }),
138494
+ /* @__PURE__ */ jsx(Text, { bold: isSel, color: isSel ? "whiteBright" : void 0, wrap: "truncate", children: subject })
138495
+ ] }, c.sha);
138496
+ }) }),
138497
+ selectedCommit && /* @__PURE__ */ jsx(
138498
+ Box,
138499
+ {
138500
+ borderStyle: "round",
138501
+ borderColor: "gray",
138502
+ flexDirection: "column",
138503
+ paddingX: 1,
138504
+ flexShrink: 0,
138505
+ overflow: "hidden",
138506
+ children: loadingDetail ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138507
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
138508
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" })
138509
+ ] }) : commitDetail ? /* @__PURE__ */ jsxs(Fragment, { children: [
138510
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138511
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: commitDetail.shortSha }),
138512
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: commitDetail.isoTime.slice(0, 16) }),
138513
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "by" }),
138514
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: commitDetail.authorName })
138515
+ ] }),
138516
+ commitDetail.body && /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: commitDetail.body.slice(0, 200) }),
138517
+ commitDetail.stat && /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate", children: commitDetail.stat.split("\n").slice(-1)[0] })
138518
+ ] }) : null
138519
+ }
138520
+ )
138521
+ ]
138522
+ }
138523
+ ),
138524
+ (!isNarrow || activePane === "changes") && /* @__PURE__ */ jsxs(
138525
+ Box,
138526
+ {
138527
+ borderStyle: "round",
138528
+ borderColor: activePane === "changes" ? "cyanBright" : "gray",
138529
+ flexDirection: "column",
138530
+ flexShrink: 0,
138531
+ overflow: "hidden",
138532
+ children: [
138533
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: activePane === "changes", color: activePane === "changes" ? "blue" : void 0, dimColor: activePane !== "changes", children: "Changes" }) }),
138534
+ gitStatus && (gitStatus.staged.length > 0 || gitStatus.unstaged.length > 0 || gitStatus.untracked.length > 0) ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingX: 1, overflow: "hidden", children: [
138535
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", width: "50%", overflow: "hidden", children: [
138536
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138537
+ "Staged (",
138538
+ gitStatus.staged.length,
138539
+ ")"
138540
+ ] }),
138541
+ gitStatus.staged.slice(0, 6).map((f) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138542
+ /* @__PURE__ */ jsx(Text, { color: statusColor(f.status), children: f.status }),
138543
+ /* @__PURE__ */ jsx(Text, { color: "gray", wrap: "truncate", children: truncatePath(f.path, Math.floor(leftWidth / 2) - 4) })
138544
+ ] }, `s-${f.path}`)),
138545
+ gitStatus.staged.length > 6 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138546
+ "\u2026+",
138547
+ gitStatus.staged.length - 6
138548
+ ] })
138549
+ ] }),
138550
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
138551
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138552
+ "Unstaged (",
138553
+ gitStatus.unstaged.length + gitStatus.untracked.length,
138554
+ ")"
138555
+ ] }),
138556
+ gitStatus.unstaged.slice(0, 4).map((f) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138557
+ /* @__PURE__ */ jsx(Text, { color: statusColor(f.status), children: f.status }),
138558
+ /* @__PURE__ */ jsx(Text, { color: "gray", wrap: "truncate", children: truncatePath(f.path, Math.floor((isNarrow ? cols : rightWidth) / 2) - 4) })
138559
+ ] }, `u-${f.path}`)),
138560
+ gitStatus.untracked.slice(0, 2).map((f) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138561
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "?" }),
138562
+ /* @__PURE__ */ jsx(Text, { color: "gray", wrap: "truncate", children: truncatePath(f.path, Math.floor((isNarrow ? cols : rightWidth) / 2) - 4) })
138563
+ ] }, `n-${f.path}`)),
138564
+ gitStatus.unstaged.length + gitStatus.untracked.length > 6 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138565
+ "\u2026+",
138566
+ gitStatus.unstaged.length + gitStatus.untracked.length - 6
138567
+ ] })
138568
+ ] })
138569
+ ] }) : /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Working tree clean" }) })
138570
+ ]
138571
+ }
138572
+ )
138573
+ ] })
138574
+ ] }),
138575
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, flexDirection: "row", gap: 1, children: [
138576
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138577
+ "[r] refresh ",
138578
+ gitStatus && gitStatus.ahead > 0 ? "[P] push " : "",
138579
+ "[F] fetch [\u2191\u2193] rows [\u2190\u2192] status\u25B8branches",
138580
+ worktrees.length > 1 ? "\u25B8worktrees" : "",
138581
+ "\u25B8commits\u25B8changes [p] project [Esc/s] back"
138582
+ ] }),
138583
+ isNarrow && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138584
+ "[narrow] ",
138585
+ activePane
138586
+ ] })
138587
+ ] }),
138588
+ pickerOpen && /* @__PURE__ */ jsx(Box, { position: "absolute", marginTop: 1, marginLeft: 1, children: /* @__PURE__ */ jsx(
138589
+ ProjectSelector,
138590
+ {
138591
+ open: true,
138592
+ projects: projectsState.projects,
138593
+ selectedIndex: projectIndex,
138594
+ onSelect: setProjectIndex
138595
+ }
138596
+ ) }),
138597
+ pushModal && /* @__PURE__ */ jsxs(Box, { position: "absolute", marginTop: 2, marginLeft: 4, children: [
138598
+ pushModal.phase === "confirm" && gitStatus && /* @__PURE__ */ jsx(
138599
+ PushModal,
138600
+ {
138601
+ status: gitStatus,
138602
+ commits,
138603
+ onConfirm: () => {
138604
+ setPushModal({ phase: "pushing" });
138605
+ if (data && projectPath) {
138606
+ data.git.push(projectPath).then((result) => {
138607
+ setPushModal({ phase: "done", message: result.output || (result.success ? "Push successful" : "Push failed"), isError: !result.success });
138608
+ if (result.success) {
138609
+ setTimeout(() => {
138610
+ setPushModal(null);
138611
+ void refresh();
138612
+ }, 2e3);
138613
+ }
138614
+ }).catch((err) => {
138615
+ setPushModal({ phase: "done", message: err instanceof Error ? err.message : String(err), isError: true });
138616
+ });
138617
+ }
138618
+ },
138619
+ onCancel: () => setPushModal(null)
138620
+ }
138621
+ ),
138622
+ pushModal.phase === "pushing" && /* @__PURE__ */ jsxs(
138623
+ Box,
138624
+ {
138625
+ borderStyle: "round",
138626
+ borderColor: "cyanBright",
138627
+ flexDirection: "row",
138628
+ paddingX: 2,
138629
+ paddingY: 1,
138630
+ gap: 1,
138631
+ backgroundColor: "black",
138632
+ children: [
138633
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
138634
+ /* @__PURE__ */ jsxs(Text, { color: "white", children: [
138635
+ "Pushing to origin/",
138636
+ gitStatus?.branch ?? "\u2026"
138637
+ ] })
138638
+ ]
138639
+ }
138640
+ ),
138641
+ pushModal.phase === "done" && /* @__PURE__ */ jsxs(
138642
+ Box,
138643
+ {
138644
+ borderStyle: "round",
138645
+ borderColor: pushModal.isError ? "red" : "blue",
138646
+ flexDirection: "column",
138647
+ paddingX: 2,
138648
+ paddingY: 1,
138649
+ backgroundColor: "black",
138650
+ children: [
138651
+ /* @__PURE__ */ jsx(Text, { color: pushModal.isError ? "red" : "green", children: pushModal.message }),
138652
+ pushModal.isError && /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Esc] dismiss" })
138653
+ ]
138654
+ }
138655
+ )
138656
+ ] })
138657
+ ] });
138658
+ }
138659
+ function truncateMiddle(s, max) {
138660
+ if (s.length <= max) return s;
138661
+ const half = Math.floor((max - 1) / 2);
138662
+ return s.slice(0, half) + "\u2026" + s.slice(s.length - (max - half - 1));
138663
+ }
138664
+ function formatFileSize(bytes) {
138665
+ if (bytes < 1024) return `${bytes}B`;
138666
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
138667
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
138668
+ }
138669
+ function flattenTree(nodes, showHidden) {
138670
+ const result = [];
138671
+ function walk(list) {
138672
+ for (const node of list) {
138673
+ if (!showHidden && node.entry.name.startsWith(".")) continue;
138674
+ result.push(node);
138675
+ if (node.expanded && node.children) {
138676
+ walk(node.children);
138677
+ }
138678
+ }
138679
+ }
138680
+ walk(nodes);
138681
+ return result;
138682
+ }
138683
+ function entriesToNodes(entries, depth) {
138684
+ const filtered = entries.filter((e) => !FILES_DENYLIST.has(e.name));
138685
+ const dirs = filtered.filter((e) => e.isDirectory).sort((a, b) => a.name.localeCompare(b.name));
138686
+ const files = filtered.filter((e) => !e.isDirectory).sort((a, b) => a.name.localeCompare(b.name));
138687
+ return [...dirs, ...files].map((e) => ({ entry: e, depth, expanded: false, children: void 0 }));
138688
+ }
138689
+ function FilesView({ state }) {
138690
+ const { stdout } = useStdout();
138691
+ const cols = stdout?.columns ?? 80;
138692
+ const data = state.interactiveData;
138693
+ const [projectIndex, setProjectIndex] = useState2(0);
138694
+ const [pickerOpen, setPickerOpen] = useState2(false);
138695
+ const [pickerOriginal, setPickerOriginal] = useState2(0);
138696
+ const projectsState = useProjects(data);
138697
+ const selectedProject = projectsState.projects[projectIndex] ?? null;
138698
+ const projectPath = selectedProject?.path ?? null;
138699
+ const [rootNodes, setRootNodes] = useState2([]);
138700
+ const [treeLoading, setTreeLoading] = useState2(false);
138701
+ const [selectedIndex, setSelectedIndex] = useState2(0);
138702
+ const [showHidden, setShowHidden] = useState2(false);
138703
+ const [previewResult, setPreviewResult] = useState2(null);
138704
+ const [previewPath, setPreviewPath] = useState2(null);
138705
+ const [previewLoading, setPreviewLoading] = useState2(false);
138706
+ const [previewScroll, setPreviewScroll] = useState2(0);
138707
+ const [wrapEnabled, setWrapEnabled] = useState2(false);
138708
+ const [focusedPane, setFocusedPane] = useState2("tree");
138709
+ const flatNodes = flattenTree(rootNodes, showHidden);
138710
+ const selectedNode = flatNodes[selectedIndex] ?? null;
138711
+ const hiddenCount = rootNodes.filter((n) => n.entry.name.startsWith(".")).length;
138712
+ useEffect2(() => {
138713
+ if (!data || !projectPath) return;
138714
+ setTreeLoading(true);
138715
+ setRootNodes([]);
138716
+ setSelectedIndex(0);
138717
+ setPreviewResult(null);
138718
+ setPreviewPath(null);
138719
+ data.files.listDirectory(projectPath, "").then((entries) => {
138720
+ setRootNodes(entriesToNodes(entries, 0));
138721
+ }).catch(() => {
138722
+ setRootNodes([]);
138723
+ }).finally(() => {
138724
+ setTreeLoading(false);
138725
+ });
138726
+ }, [data, projectPath]);
138727
+ useEffect2(() => {
138728
+ if (!data || !projectPath || !selectedNode) return;
138729
+ if (selectedNode.entry.isDirectory) return;
138730
+ const rel = selectedNode.entry.path;
138731
+ if (rel === previewPath) return;
138732
+ setPreviewLoading(true);
138733
+ setPreviewScroll(0);
138734
+ data.files.readFile(projectPath, rel).then((result) => {
138735
+ setPreviewResult(result);
138736
+ setPreviewPath(rel);
138737
+ }).catch(() => {
138738
+ setPreviewResult(null);
138739
+ setPreviewPath(rel);
138740
+ }).finally(() => {
138741
+ setPreviewLoading(false);
138742
+ });
138743
+ }, [data, projectPath, selectedNode?.entry.path]);
138744
+ const expandNode = useCallback2(async (node) => {
138745
+ if (!data || !projectPath) return;
138746
+ if (!node.entry.isDirectory) return;
138747
+ if (node.children !== void 0) {
138748
+ node.expanded = !node.expanded;
138749
+ setRootNodes((prev) => [...prev]);
138750
+ return;
138751
+ }
138752
+ try {
138753
+ const entries = await data.files.listDirectory(projectPath, node.entry.path);
138754
+ node.children = entriesToNodes(entries, node.depth + 1);
138755
+ node.expanded = true;
138756
+ setRootNodes((prev) => [...prev]);
138757
+ } catch {
138758
+ node.children = [];
138759
+ node.expanded = true;
138760
+ setRootNodes((prev) => [...prev]);
138761
+ }
138762
+ }, [data, projectPath]);
138763
+ const collapseNode = useCallback2((node) => {
138764
+ node.expanded = false;
138765
+ setRootNodes((prev) => [...prev]);
138766
+ }, []);
138767
+ function findParentOf(nodes, target, depth) {
138768
+ for (const n of nodes) {
138769
+ if (n.depth === depth - 1 && n.expanded && n.children) {
138770
+ if (n.children.includes(target)) return n;
138771
+ const found = findParentOf(n.children, target, depth);
138772
+ if (found) return found;
138773
+ }
138774
+ }
138775
+ return null;
138776
+ }
138777
+ const previewHeight = Math.max(4, (stdout?.rows ?? 24) - 7);
138778
+ const halfPage = Math.max(1, Math.floor(previewHeight / 2));
138779
+ useInput((input, key) => {
138780
+ if (!data) return;
138781
+ if (pickerOpen) {
138782
+ if (key.upArrow || input === "k") {
138783
+ setProjectIndex((p) => Math.max(0, p - 1));
138784
+ return;
138785
+ }
138786
+ if (key.downArrow || input === "j") {
138787
+ setProjectIndex((p) => Math.min(projectsState.projects.length - 1, p + 1));
138788
+ return;
138789
+ }
138790
+ if (key.return) {
138791
+ setPickerOpen(false);
138792
+ return;
138793
+ }
138794
+ if (key.escape) {
138795
+ setProjectIndex(pickerOriginal);
138796
+ setPickerOpen(false);
138797
+ return;
138798
+ }
138799
+ return;
138800
+ }
138801
+ if (input === "p") {
138802
+ setPickerOriginal(projectIndex);
138803
+ setPickerOpen(true);
138804
+ return;
138805
+ }
138806
+ if (key.tab) {
138807
+ setFocusedPane((p) => p === "tree" ? "preview" : "tree");
138808
+ return;
138809
+ }
138810
+ if (input === ".") {
138811
+ setShowHidden((v) => !v);
138812
+ return;
138813
+ }
138814
+ if (input === "w" || input === "W") {
138815
+ setWrapEnabled((v) => !v);
138816
+ return;
138817
+ }
138818
+ if (input === "r" || input === "R") {
138819
+ if (data && projectPath && selectedNode && !selectedNode.entry.isDirectory) {
138820
+ setPreviewLoading(true);
138821
+ setPreviewScroll(0);
138822
+ setPreviewPath(null);
138823
+ }
138824
+ return;
138825
+ }
138826
+ if (focusedPane === "tree") {
138827
+ if (key.upArrow || input === "k") {
138828
+ setSelectedIndex((i) => Math.max(0, i - 1));
138829
+ return;
138830
+ }
138831
+ if (key.downArrow || input === "j") {
138832
+ setSelectedIndex((i) => Math.min(flatNodes.length - 1, i + 1));
138833
+ return;
138834
+ }
138835
+ if (key.rightArrow) {
138836
+ if (selectedNode?.entry.isDirectory) {
138837
+ if (selectedNode.expanded) {
138838
+ const ci = flatNodes.indexOf(selectedNode) + 1;
138839
+ if (ci < flatNodes.length) setSelectedIndex(ci);
138840
+ } else {
138841
+ void expandNode(selectedNode);
138842
+ }
138843
+ }
138844
+ return;
138845
+ }
138846
+ if (key.leftArrow) {
138847
+ if (selectedNode?.entry.isDirectory && selectedNode.expanded) {
138848
+ collapseNode(selectedNode);
138849
+ } else if (selectedNode && selectedNode.depth > 0) {
138850
+ const parent2 = findParentOf(rootNodes, selectedNode, selectedNode.depth);
138851
+ if (parent2) {
138852
+ const pi = flatNodes.indexOf(parent2);
138853
+ if (pi >= 0) setSelectedIndex(pi);
138854
+ }
138855
+ }
138856
+ return;
138857
+ }
138858
+ if (key.return) {
138859
+ if (selectedNode?.entry.isDirectory) {
138860
+ void expandNode(selectedNode);
138861
+ } else if (selectedNode) {
138862
+ setFocusedPane("preview");
138863
+ setPreviewScroll(0);
138864
+ }
138865
+ return;
138866
+ }
138867
+ }
138868
+ if (focusedPane === "preview") {
138869
+ const lineCount = previewResult?.lineCount ?? 0;
138870
+ const maxScroll = Math.max(0, lineCount - previewHeight);
138871
+ if (key.upArrow || input === "k") {
138872
+ setPreviewScroll((s) => Math.max(0, s - 1));
138873
+ return;
138874
+ }
138875
+ if (key.downArrow || input === "j") {
138876
+ setPreviewScroll((s) => Math.min(maxScroll, s + 1));
138877
+ return;
138878
+ }
138879
+ if (key.pageUp) {
138880
+ setPreviewScroll((s) => Math.max(0, s - halfPage));
138881
+ return;
138882
+ }
138883
+ if (key.pageDown) {
138884
+ setPreviewScroll((s) => Math.min(maxScroll, s + halfPage));
138885
+ return;
138886
+ }
138887
+ if (input === "g") {
138888
+ setPreviewScroll(0);
138889
+ return;
138890
+ }
138891
+ if (input === "G") {
138892
+ setPreviewScroll(maxScroll);
138893
+ return;
138894
+ }
138895
+ }
138896
+ }, { isActive: state.interactiveView === "files" });
138897
+ const isNarrow = cols < NARROW_THRESHOLD;
138898
+ const treeWidth = isNarrow ? Math.max(20, cols - 2) : Math.max(20, Math.floor(cols * 0.38));
138899
+ const previewEntry = selectedNode && !selectedNode.entry.isDirectory ? selectedNode.entry : null;
138900
+ const previewLines = previewResult?.content ? previewResult.content.split("\n").slice(previewScroll, previewScroll + previewHeight) : null;
138901
+ const totalLines = previewResult?.lineCount ?? 0;
138902
+ const lineNumWidth = Math.max(3, String(previewScroll + previewHeight).length);
138903
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, overflow: "hidden", children: [
138904
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", paddingX: 1, gap: 1, children: [
138905
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", bold: true, children: selectedProject?.name ?? "\u2014" }),
138906
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
138907
+ /* @__PURE__ */ jsx(Text, { dimColor: true, color: focusedPane === "tree" ? "cyanBright" : "gray", children: focusedPane === "tree" ? "tree" : "preview" }),
138908
+ previewEntry && /* @__PURE__ */ jsxs(Fragment, { children: [
138909
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
138910
+ /* @__PURE__ */ jsx(Text, { color: "white", wrap: "truncate", children: truncateMiddle(previewEntry.path, Math.max(10, cols - 40)) }),
138911
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2502" }),
138912
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatFileSize(previewEntry.size) }),
138913
+ previewResult && !previewResult.isBinary && !previewResult.tooLarge && /* @__PURE__ */ jsxs(Fragment, { children: [
138914
+ /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138915
+ previewResult.lineCount,
138916
+ "L"
138917
+ ] }),
138918
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: formatRelativeTime(previewEntry.modifiedAt) })
138919
+ ] })
138920
+ ] })
138921
+ ] }),
138922
+ /* @__PURE__ */ jsxs(Box, { flexDirection: "row", flexGrow: 1, overflow: "hidden", children: [
138923
+ (!isNarrow || focusedPane === "tree") && /* @__PURE__ */ jsxs(
138924
+ Box,
138925
+ {
138926
+ flexDirection: "column",
138927
+ width: isNarrow ? void 0 : treeWidth,
138928
+ flexGrow: isNarrow ? 1 : 0,
138929
+ flexShrink: 0,
138930
+ borderStyle: "round",
138931
+ borderColor: focusedPane === "tree" ? "cyanBright" : "gray",
138932
+ overflow: "hidden",
138933
+ children: [
138934
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: focusedPane === "tree", color: focusedPane === "tree" ? "cyanBright" : void 0, dimColor: focusedPane !== "tree", children: "Files" }) }),
138935
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: treeLoading ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138936
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
138937
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" })
138938
+ ] }) : flatNodes.length === 0 ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(empty)" }) : flatNodes.map((node, i) => {
138939
+ const isSelected = focusedPane === "tree" && i === selectedIndex;
138940
+ const indent = " ".repeat(node.depth);
138941
+ let prefix;
138942
+ if (node.entry.isDirectory) {
138943
+ prefix = node.expanded ? "\u25BE " : "\u25B8 ";
138944
+ } else {
138945
+ prefix = "\xB7 ";
138946
+ }
138947
+ const name = node.entry.name;
138948
+ const maxNameWidth = treeWidth - node.depth * 2 - 4;
138949
+ const displayName = name.length > maxNameWidth ? name.slice(0, maxNameWidth - 1) + "\u2026" : name;
138950
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
138951
+ isSelected && /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: "\u25B6" }),
138952
+ !isSelected && /* @__PURE__ */ jsx(Text, { children: " " }),
138953
+ /* @__PURE__ */ jsxs(
138954
+ Text,
138955
+ {
138956
+ color: isSelected ? "whiteBright" : node.entry.isDirectory ? "cyan" : void 0,
138957
+ dimColor: !isSelected && !node.entry.isDirectory,
138958
+ wrap: "truncate",
138959
+ children: [
138960
+ indent,
138961
+ prefix,
138962
+ displayName
138963
+ ]
138964
+ }
138965
+ )
138966
+ ] }, node.entry.path);
138967
+ }) }),
138968
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
138969
+ /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "truncate", children: selectedNode ? truncateMiddle(
138970
+ projectPath ? `${projectPath}/${selectedNode.entry.path}` : selectedNode.entry.path,
138971
+ treeWidth - 4
138972
+ ) : " " }),
138973
+ !showHidden && hiddenCount > 0 && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138974
+ " [",
138975
+ hiddenCount,
138976
+ " hidden]"
138977
+ ] })
138978
+ ] })
138979
+ ]
138980
+ }
138981
+ ),
138982
+ (!isNarrow || focusedPane === "preview") && /* @__PURE__ */ jsxs(
138983
+ Box,
138984
+ {
138985
+ flexDirection: "column",
138986
+ flexGrow: 1,
138987
+ borderStyle: "round",
138988
+ borderColor: focusedPane === "preview" ? "cyanBright" : "gray",
138989
+ overflow: "hidden",
138990
+ children: [
138991
+ /* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { bold: focusedPane === "preview", color: focusedPane === "preview" ? "cyanBright" : void 0, dimColor: focusedPane !== "preview", children: "Preview" }) }),
138992
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, overflow: "hidden", children: previewLoading ? /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
138993
+ /* @__PURE__ */ jsx(Text, { color: "cyanBright", children: /* @__PURE__ */ jsx(Spinner, { type: "dots" }) }),
138994
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Loading\u2026" })
138995
+ ] }) : !previewEntry ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Select a file to preview" }) : previewResult === null ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Unable to read file" }) : previewResult.isBinary ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
138996
+ "[binary file, ",
138997
+ formatFileSize(previewResult.size),
138998
+ "]"
138999
+ ] }) : previewResult.tooLarge ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
139000
+ formatFileSize(previewResult.size),
139001
+ " \u2014 [too large to preview]"
139002
+ ] }) : previewResult.content === "" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "(empty file)" }) : previewLines ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", overflow: "hidden", children: [
139003
+ previewLines.map((line, i) => {
139004
+ const lineNo = previewScroll + i + 1;
139005
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "row", children: [
139006
+ /* @__PURE__ */ jsx(Box, { width: lineNumWidth + 1, flexShrink: 0, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: String(lineNo).padStart(lineNumWidth) }) }),
139007
+ /* @__PURE__ */ jsx(Text, { wrap: wrapEnabled ? "wrap" : "truncate-end", children: line })
139008
+ ] }, lineNo);
139009
+ }),
139010
+ totalLines > previewScroll + previewHeight && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
139011
+ "\u2026 ",
139012
+ totalLines - previewScroll - previewHeight,
139013
+ " more lines"
139014
+ ] })
139015
+ ] }) : null })
139016
+ ]
139017
+ }
139018
+ )
139019
+ ] }),
139020
+ /* @__PURE__ */ jsxs(Box, { paddingX: 1, flexDirection: "row", gap: 1, children: [
139021
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[Tab] switch pane [\u2191\u2193/jk] move [Enter] open [\u2190/\u2192] collapse/expand [.] hidden [w] wrap [p] project [r] reload" }),
139022
+ isNarrow && /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
139023
+ "[narrow] ",
139024
+ focusedPane
139025
+ ] })
139026
+ ] }),
139027
+ pickerOpen && /* @__PURE__ */ jsx(Box, { position: "absolute", marginTop: 2, marginLeft: 2, children: /* @__PURE__ */ jsxs(
139028
+ Box,
139029
+ {
139030
+ borderStyle: "round",
139031
+ borderColor: "cyanBright",
139032
+ flexDirection: "column",
139033
+ paddingX: 2,
139034
+ paddingY: 1,
139035
+ backgroundColor: "black",
139036
+ children: [
139037
+ /* @__PURE__ */ jsx(Text, { bold: true, color: "white", children: "Select Project" }),
139038
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
139039
+ projectsState.projects.map((proj, i) => /* @__PURE__ */ jsxs(Box, { flexDirection: "row", gap: 1, children: [
139040
+ /* @__PURE__ */ jsx(Text, { color: i === projectIndex ? "cyanBright" : "gray", children: i === projectIndex ? "\u25B6" : " " }),
139041
+ /* @__PURE__ */ jsx(Text, { color: i === projectIndex ? "whiteBright" : void 0, children: proj.name })
139042
+ ] }, proj.id)),
139043
+ /* @__PURE__ */ jsx(Box, { height: 1 }),
139044
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: "[\u2191\u2193] move [Enter] select [Esc] cancel" })
139045
+ ]
139046
+ }
139047
+ ) })
139048
+ ] });
139049
+ }
139050
+ function InteractiveMode({ state, controller }) {
137633
139051
  if (state.interactiveData === null) {
137634
139052
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
137635
- /* @__PURE__ */ jsx(InteractiveHeader, { activeView: state.interactiveView }),
139053
+ /* @__PURE__ */ jsx(MainHeader, { state }),
137636
139054
  /* @__PURE__ */ jsx(Box, { justifyContent: "center", alignItems: "center", flexGrow: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "Interactive mode unavailable \u2014 no data source" }) })
137637
139055
  ] });
137638
139056
  }
137639
139057
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
137640
- /* @__PURE__ */ jsx(InteractiveHeader, { activeView: state.interactiveView }),
137641
- /* @__PURE__ */ jsx(Box, { height: 1 }),
139058
+ /* @__PURE__ */ jsx(Box, { flexShrink: 0, children: /* @__PURE__ */ jsx(MainHeader, { state }) }),
139059
+ /* @__PURE__ */ jsx(Box, { height: 1, flexShrink: 0 }),
137642
139060
  /* @__PURE__ */ jsxs(Box, { flexGrow: 1, overflow: "hidden", children: [
137643
- state.interactiveView === "board" && /* @__PURE__ */ jsx(BoardView, { state }),
139061
+ state.interactiveView === "board" && /* @__PURE__ */ jsx(BoardView, { state, controller }),
137644
139062
  state.interactiveView === "agents" && /* @__PURE__ */ jsx(AgentsView, { state }),
137645
- state.interactiveView === "settings" && /* @__PURE__ */ jsx(SettingsInteractiveView, { state })
139063
+ state.interactiveView === "settings" && /* @__PURE__ */ jsx(SettingsInteractiveView, { state }),
139064
+ state.interactiveView === "git" && /* @__PURE__ */ jsx(GitView, { state }),
139065
+ state.interactiveView === "files" && /* @__PURE__ */ jsx(FilesView, { state })
137646
139066
  ] })
137647
139067
  ] });
137648
139068
  }
@@ -137671,30 +139091,47 @@ function DashboardApp({ controller }) {
137671
139091
  controller.setInteractiveView("agents");
137672
139092
  return;
137673
139093
  }
137674
- if (input === "g" || input === "G") {
139094
+ if (input === "g") {
137675
139095
  controller.setMode("interactive");
137676
139096
  controller.setInteractiveView("settings");
137677
139097
  return;
137678
139098
  }
139099
+ if (input === "f" || input === "F") {
139100
+ if (state.mode === "status") {
139101
+ controller.cycleSeverityFilter();
139102
+ return;
139103
+ }
139104
+ }
139105
+ if (input === "t" || input === "T") {
139106
+ controller.setMode("interactive");
139107
+ controller.setInteractiveView("git");
139108
+ return;
139109
+ }
139110
+ if (input === "e" || input === "E") {
139111
+ controller.setMode("interactive");
139112
+ controller.setInteractiveView("files");
139113
+ return;
139114
+ }
137679
139115
  if (input === "s" || input === "S") {
137680
139116
  if (state.mode === "interactive") {
137681
139117
  controller.setMode("status");
137682
139118
  return;
137683
139119
  }
137684
139120
  }
139121
+ const sectionForNumber = {
139122
+ "1": "system",
139123
+ "2": "logs",
139124
+ "3": "utilities",
139125
+ "4": "stats",
139126
+ "5": "settings"
139127
+ };
139128
+ const targetSection = sectionForNumber[input];
139129
+ if (targetSection) {
139130
+ if (state.mode === "interactive") controller.setMode("status");
139131
+ controller.setActiveSection(targetSection);
139132
+ return;
139133
+ }
137685
139134
  if (state.mode === "interactive") {
137686
- if (input === "1") {
137687
- controller.setInteractiveView("board");
137688
- return;
137689
- }
137690
- if (input === "2") {
137691
- controller.setInteractiveView("agents");
137692
- return;
137693
- }
137694
- if (input === "3") {
137695
- controller.setInteractiveView("settings");
137696
- return;
137697
- }
137698
139135
  return;
137699
139136
  }
137700
139137
  if (input === "?" || input === "h" || input === "H") {
@@ -137760,10 +139197,6 @@ function DashboardApp({ controller }) {
137760
139197
  controller.setLogsWrapEnabled(!state.logsWrapEnabled);
137761
139198
  return;
137762
139199
  }
137763
- if (input === "f" || input === "F") {
137764
- controller.cycleSeverityFilter();
137765
- return;
137766
- }
137767
139200
  if (key.upArrow || input === "k" || input === "K") {
137768
139201
  if (state.selectedLogIndex > 0) {
137769
139202
  controller.setSelectedLogIndex(state.selectedLogIndex - 1);
@@ -137780,7 +139213,7 @@ function DashboardApp({ controller }) {
137780
139213
  controller.setSelectedLogIndex(0);
137781
139214
  return;
137782
139215
  }
137783
- if (key.end) {
139216
+ if (key.end || input === "G") {
137784
139217
  controller.setSelectedLogIndex(Math.max(0, filteredEntries.length - 1));
137785
139218
  return;
137786
139219
  }
@@ -137791,29 +139224,48 @@ function DashboardApp({ controller }) {
137791
139224
  }
137792
139225
  const isNarrow = cols < 80 || rows < 20;
137793
139226
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", height: rows, children: [
137794
- state.mode === "interactive" ? /* @__PURE__ */ jsx(InteractiveMode, { state }) : isNarrow ? /* @__PURE__ */ jsx(StatusModeSingle, { state, controller }) : /* @__PURE__ */ jsx(StatusModeGrid, { state, rows, controller }),
139227
+ state.mode === "interactive" ? /* @__PURE__ */ jsx(InteractiveMode, { state, controller }) : isNarrow ? /* @__PURE__ */ jsx(StatusModeSingle, { state, controller }) : /* @__PURE__ */ jsx(StatusModeGrid, { state, rows, controller }),
137795
139228
  state.showHelp && /* @__PURE__ */ jsx(Box, { position: "absolute", marginTop: 3, marginLeft: 4, children: /* @__PURE__ */ jsx(HelpOverlay, {}) })
137796
139229
  ] });
137797
139230
  }
137798
- var LOGO_COLORS, SPLASH_MIN_COLS, SPLASH_MIN_ROWS, PREFIX_WIDTH, PANEL_ORDER, KANBAN_COLUMNS, COLUMN_COLORS, SETTING_DEFS;
139231
+ var LOGO_COLORS, NARROW_THRESHOLD, SPLASH_MIN_COLS, SPLASH_MIN_ROWS, LARGE_LOGO_MIN_COLS, LARGE_LOGO_MIN_ROWS, PREFIX_WIDTH, PANEL_ORDER, KANBAN_COLUMNS, COLUMN_COLORS, STEP_ICON, STEP_COLOR, MAX_LOG_ENTRIES2, INITIAL_LOG_LIMIT, SETTING_DEFS, FILES_DENYLIST;
137799
139232
  var init_app = __esm({
137800
139233
  "src/commands/dashboard-tui/app.tsx"() {
137801
139234
  "use strict";
137802
139235
  init_state();
137803
139236
  init_logo();
137804
139237
  init_use_projects();
137805
- LOGO_COLORS = ["whiteBright", "cyanBright", "cyan", "blueBright", "blue", "blue"];
139238
+ LOGO_COLORS = ["whiteBright", "white", "white", "cyanBright", "cyanBright", "cyan", "cyan", "blue"];
139239
+ NARROW_THRESHOLD = 80;
137806
139240
  SPLASH_MIN_COLS = 56;
137807
- SPLASH_MIN_ROWS = 20;
139241
+ SPLASH_MIN_ROWS = 12;
139242
+ LARGE_LOGO_MIN_COLS = 70;
139243
+ LARGE_LOGO_MIN_ROWS = 16;
137808
139244
  PREFIX_WIDTH = 14;
137809
139245
  PANEL_ORDER = ["system", "logs", "utilities", "stats", "settings"];
137810
139246
  KANBAN_COLUMNS = ["todo", "in-progress", "in-review", "done"];
137811
139247
  COLUMN_COLORS = {
137812
139248
  todo: "yellow",
137813
- "in-progress": "cyan",
137814
- "in-review": "magenta",
139249
+ "in-progress": "cyanBright",
139250
+ "in-review": "cyan",
137815
139251
  done: "green"
137816
139252
  };
139253
+ STEP_ICON = {
139254
+ done: "\u2713",
139255
+ running: "\u25B6",
139256
+ pending: "\xB7",
139257
+ failed: "\u2717",
139258
+ skipped: "\u23ED"
139259
+ };
139260
+ STEP_COLOR = {
139261
+ done: "green",
139262
+ running: "blue",
139263
+ pending: "gray",
139264
+ failed: "red",
139265
+ skipped: "gray"
139266
+ };
139267
+ MAX_LOG_ENTRIES2 = 1e3;
139268
+ INITIAL_LOG_LIMIT = 200;
137817
139269
  SETTING_DEFS = [
137818
139270
  { key: "maxConcurrent", label: "Max Concurrent", type: "number" },
137819
139271
  { key: "maxWorktrees", label: "Max Worktrees", type: "number" },
@@ -137823,10 +139275,13 @@ var init_app = __esm({
137823
139275
  { key: "enginePaused", label: "Engine Paused", type: "boolean" },
137824
139276
  { key: "globalPause", label: "Global Pause", type: "boolean" }
137825
139277
  ];
139278
+ FILES_DENYLIST = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "target", "build"]);
137826
139279
  }
137827
139280
  });
137828
139281
 
137829
139282
  // src/commands/dashboard-tui/controller.ts
139283
+ import os3 from "node:os";
139284
+ import v8 from "node:v8";
137830
139285
  var DashboardTUI;
137831
139286
  var init_controller = __esm({
137832
139287
  "src/commands/dashboard-tui/controller.ts"() {
@@ -137841,6 +139296,11 @@ var init_controller = __esm({
137841
139296
  logBuffer;
137842
139297
  systemInfo = null;
137843
139298
  taskStats = null;
139299
+ systemStats = null;
139300
+ // When set, dashboard.ts refreshes task stats from this project path
139301
+ // instead of the launch cwd. Mirrors BoardView's selected project.
139302
+ boardScopedProjectPath = null;
139303
+ boardScopeListener = null;
137844
139304
  settings = null;
137845
139305
  callbacks = null;
137846
139306
  isRunning = false;
@@ -137864,6 +139324,10 @@ var init_controller = __esm({
137864
139324
  inkInstance = null;
137865
139325
  // Uptime ticker to keep footer time live.
137866
139326
  uptimeTimer = null;
139327
+ // System stats sampler — process memory + CPU%.
139328
+ systemStatsTimer = null;
139329
+ lastCpuUsage = null;
139330
+ lastCpuSampleAt = 0;
137867
139331
  constructor() {
137868
139332
  this.logBuffer = new LogRingBuffer();
137869
139333
  }
@@ -137879,6 +139343,7 @@ var init_controller = __esm({
137879
139343
  logEntries: this.logBuffer.getAll(),
137880
139344
  systemInfo: this.systemInfo,
137881
139345
  taskStats: this.taskStats,
139346
+ systemStats: this.systemStats,
137882
139347
  settings: this.settings,
137883
139348
  callbacks: this.callbacks,
137884
139349
  showHelp: this.showHelp,
@@ -137914,6 +139379,56 @@ var init_controller = __esm({
137914
139379
  this.taskStats = stats;
137915
139380
  this.notify();
137916
139381
  }
139382
+ setSystemStats(stats) {
139383
+ this.systemStats = stats;
139384
+ this.notify();
139385
+ }
139386
+ setBoardScopedProjectPath(path4) {
139387
+ if (this.boardScopedProjectPath === path4) return;
139388
+ this.boardScopedProjectPath = path4;
139389
+ this.boardScopeListener?.(path4);
139390
+ this.notify();
139391
+ }
139392
+ onBoardScopeChange(listener) {
139393
+ this.boardScopeListener = listener;
139394
+ return () => {
139395
+ if (this.boardScopeListener === listener) this.boardScopeListener = null;
139396
+ };
139397
+ }
139398
+ /** Sample process memory + CPU% in-place. Called from the sampler timer. */
139399
+ sampleSystemStats() {
139400
+ const mem = process.memoryUsage();
139401
+ const heapStats = v8.getHeapStatistics();
139402
+ const now = Date.now();
139403
+ const cpu = process.cpuUsage();
139404
+ let cpuPercent = 0;
139405
+ if (this.lastCpuUsage && this.lastCpuSampleAt > 0) {
139406
+ const elapsedMicros = (now - this.lastCpuSampleAt) * 1e3;
139407
+ if (elapsedMicros > 0) {
139408
+ const usedMicros = cpu.user - this.lastCpuUsage.user + (cpu.system - this.lastCpuUsage.system);
139409
+ cpuPercent = usedMicros / elapsedMicros * 100;
139410
+ }
139411
+ }
139412
+ this.lastCpuUsage = cpu;
139413
+ this.lastCpuSampleAt = now;
139414
+ const load = os3.loadavg();
139415
+ this.setSystemStats({
139416
+ rss: mem.rss,
139417
+ heapUsed: mem.heapUsed,
139418
+ heapTotal: mem.heapTotal,
139419
+ heapLimit: heapStats.heap_size_limit,
139420
+ external: mem.external,
139421
+ arrayBuffers: mem.arrayBuffers,
139422
+ cpuPercent,
139423
+ loadAvg: [load[0] ?? 0, load[1] ?? 0, load[2] ?? 0],
139424
+ cpuCount: os3.cpus().length,
139425
+ systemTotalMem: os3.totalmem(),
139426
+ systemFreeMem: os3.freemem(),
139427
+ pid: process.pid,
139428
+ nodeVersion: process.version,
139429
+ platform: `${process.platform}/${process.arch}`
139430
+ });
139431
+ }
137917
139432
  setSettings(settings) {
137918
139433
  this.settings = settings;
137919
139434
  this.notify();
@@ -138041,6 +139556,12 @@ var init_controller = __esm({
138041
139556
  this.uptimeTimer = setInterval(() => {
138042
139557
  if (this.isRunning) this.notify();
138043
139558
  }, 5e3);
139559
+ this.lastCpuUsage = process.cpuUsage();
139560
+ this.lastCpuSampleAt = Date.now();
139561
+ this.sampleSystemStats();
139562
+ this.systemStatsTimer = setInterval(() => {
139563
+ if (this.isRunning) this.sampleSystemStats();
139564
+ }, 2e3);
138044
139565
  }
138045
139566
  async stop() {
138046
139567
  if (!this.isRunning) return;
@@ -138049,6 +139570,10 @@ var init_controller = __esm({
138049
139570
  clearInterval(this.uptimeTimer);
138050
139571
  this.uptimeTimer = null;
138051
139572
  }
139573
+ if (this.systemStatsTimer) {
139574
+ clearInterval(this.systemStatsTimer);
139575
+ this.systemStatsTimer = null;
139576
+ }
138052
139577
  if (this.inkInstance) {
138053
139578
  this.inkInstance.unmount();
138054
139579
  this.inkInstance = null;
@@ -138217,7 +139742,10 @@ __export(dashboard_exports, {
138217
139742
  promptForPort: () => promptForPort,
138218
139743
  runDashboard: () => runDashboard
138219
139744
  });
138220
- import { join as join48 } from "node:path";
139745
+ import { join as join48, resolve as pathResolve } from "node:path";
139746
+ import { execFile as execFileCb } from "node:child_process";
139747
+ import { promisify as promisify12 } from "node:util";
139748
+ import { stat as stat10, readdir as readdir11, readFile as fsReadFile3 } from "node:fs/promises";
138221
139749
  import { AuthStorage as AuthStorage2, DefaultPackageManager as DefaultPackageManager2, ModelRegistry as ModelRegistry3, discoverAndLoadExtensions as discoverAndLoadExtensions2, createExtensionRuntime as createExtensionRuntime2 } from "@mariozechner/pi-coding-agent";
138222
139750
  function formatRuntimeContext(context) {
138223
139751
  if (context === void 0) {
@@ -138246,7 +139774,7 @@ function createDashboardRuntimeLogger(logSink, scope) {
138246
139774
  }
138247
139775
  };
138248
139776
  }
138249
- function formatBytes2(bytes) {
139777
+ function formatBytes3(bytes) {
138250
139778
  if (bytes < 1024) return `${bytes}B`;
138251
139779
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
138252
139780
  if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
@@ -138289,7 +139817,7 @@ function logDiagnostics(logger2, prefix, startTime, dbHealthCheck) {
138289
139817
  } catch {
138290
139818
  }
138291
139819
  }
138292
- const logLine = `[${prefix}] diagnostics: uptime=${formatUptime2(uptime)} rss=${formatBytes2(mem.rss)} heap=${formatBytes2(mem.heapUsed)}/${formatBytes2(mem.heapTotal)} external=${formatBytes2(mem.external)} arrayBuffers=${formatBytes2(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
139820
+ const logLine = `[${prefix}] diagnostics: uptime=${formatUptime2(uptime)} rss=${formatBytes3(mem.rss)} heap=${formatBytes3(mem.heapUsed)}/${formatBytes3(mem.heapTotal)} external=${formatBytes3(mem.external)} arrayBuffers=${formatBytes3(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
138293
139821
  logger2.info(logLine);
138294
139822
  }
138295
139823
  function ensureProcessDiagnostics(logger2) {
@@ -138338,6 +139866,229 @@ function setDiagnosticDbHealthCheck(check) {
138338
139866
  function setDiagnosticStoreListenerCheck(check) {
138339
139867
  diagnosticStoreListenerCheck = check;
138340
139868
  }
139869
+ async function gitExec(cwd, args) {
139870
+ const { stdout } = await execFileAsync3("git", args, { cwd, maxBuffer: 4 * 1024 * 1024 });
139871
+ return stdout;
139872
+ }
139873
+ async function buildGitStatus(projectPath) {
139874
+ const [sbOut, remoteOut] = await Promise.allSettled([
139875
+ gitExec(projectPath, ["status", "-sb", "--porcelain=v1"]),
139876
+ gitExec(projectPath, ["remote", "get-url", "origin"])
139877
+ ]);
139878
+ const sbRaw = sbOut.status === "fulfilled" ? sbOut.value : "";
139879
+ const remoteUrl = remoteOut.status === "fulfilled" ? remoteOut.value.trim() : "";
139880
+ const lines = sbRaw.split("\n");
139881
+ const header = lines[0] ?? "";
139882
+ let branch = "HEAD";
139883
+ let detached = false;
139884
+ let ahead = 0;
139885
+ let behind = 0;
139886
+ const noCommitMatch = header.match(/^## No commits yet on (.+)$/);
139887
+ if (noCommitMatch) {
139888
+ branch = noCommitMatch[1] ?? "HEAD";
139889
+ } else {
139890
+ const branchMatch = header.match(/^## ([^.]+?)(?:\.\.\.(\S+?)(?:\s+\[ahead (\d+)(?:, behind (\d+))?\]|\s+\[behind (\d+)\])?)?$/);
139891
+ if (branchMatch) {
139892
+ branch = branchMatch[1] ?? "HEAD";
139893
+ ahead = parseInt(branchMatch[3] ?? "0", 10);
139894
+ behind = parseInt(branchMatch[4] ?? branchMatch[5] ?? "0", 10);
139895
+ } else if (header.startsWith("## HEAD (no branch)")) {
139896
+ detached = true;
139897
+ branch = "HEAD";
139898
+ }
139899
+ }
139900
+ const staged = [];
139901
+ const unstaged = [];
139902
+ const untracked = [];
139903
+ for (const line of lines.slice(1)) {
139904
+ if (line.length < 3) continue;
139905
+ const x = line[0] ?? " ";
139906
+ const y = line[1] ?? " ";
139907
+ const path4 = line.slice(3);
139908
+ if (x === "?" && y === "?") {
139909
+ untracked.push({ path: path4 });
139910
+ } else {
139911
+ if (x !== " " && x !== "?") staged.push({ status: x, path: path4 });
139912
+ if (y !== " " && y !== "?") unstaged.push({ status: y, path: path4 });
139913
+ }
139914
+ }
139915
+ let lastFetchAt = null;
139916
+ try {
139917
+ const fetchHead = await stat10(`${projectPath}/.git/FETCH_HEAD`);
139918
+ lastFetchAt = fetchHead.mtimeMs;
139919
+ } catch {
139920
+ }
139921
+ return { branch, detached, ahead, behind, staged, unstaged, untracked, remoteUrl, lastFetchAt };
139922
+ }
139923
+ async function buildGitCommits(projectPath, limit = 15) {
139924
+ const sep7 = "";
139925
+ const recSep = "";
139926
+ const fmt = [`%H`, `%h`, `%s`, `%an`, `%ar`, `%aI`].join(sep7);
139927
+ let out = "";
139928
+ try {
139929
+ out = await gitExec(projectPath, ["log", `--max-count=${limit}`, `--format=${fmt}${recSep}`]);
139930
+ } catch {
139931
+ return [];
139932
+ }
139933
+ return out.split(recSep).flatMap((rec) => {
139934
+ const parts = rec.trim().split(sep7);
139935
+ if (parts.length < 6 || !parts[0]) return [];
139936
+ return [{
139937
+ sha: parts[0] ?? "",
139938
+ shortSha: parts[1] ?? "",
139939
+ subject: parts[2] ?? "",
139940
+ authorName: parts[3] ?? "",
139941
+ relativeTime: parts[4] ?? "",
139942
+ isoTime: parts[5] ?? ""
139943
+ }];
139944
+ });
139945
+ }
139946
+ async function buildGitCommitDetail(projectPath, sha) {
139947
+ const sep7 = "";
139948
+ const fmt = [`%H`, `%h`, `%s`, `%an`, `%ar`, `%aI`, `%b`].join(sep7);
139949
+ const [showOut, statOut] = await Promise.allSettled([
139950
+ gitExec(projectPath, ["show", `--format=${fmt}`, "--no-patch", sha]),
139951
+ gitExec(projectPath, ["show", "--stat", "--format=", sha])
139952
+ ]);
139953
+ const raw = showOut.status === "fulfilled" ? showOut.value.trim() : "";
139954
+ const parts = raw.split(sep7);
139955
+ return {
139956
+ sha: parts[0] ?? sha,
139957
+ shortSha: parts[1] ?? sha.slice(0, 7),
139958
+ subject: parts[2] ?? "",
139959
+ authorName: parts[3] ?? "",
139960
+ relativeTime: parts[4] ?? "",
139961
+ isoTime: parts[5] ?? "",
139962
+ body: (parts[6] ?? "").trim(),
139963
+ stat: statOut.status === "fulfilled" ? statOut.value.trim() : ""
139964
+ };
139965
+ }
139966
+ async function buildGitBranches(projectPath) {
139967
+ let out = "";
139968
+ try {
139969
+ out = await gitExec(projectPath, [
139970
+ "for-each-ref",
139971
+ "--sort=-committerdate",
139972
+ "refs/heads",
139973
+ "--format=%(refname:short)|%(objectname:short)|%(committerdate:relative)|%(upstream:track)|%(HEAD)"
139974
+ ]);
139975
+ } catch {
139976
+ return [];
139977
+ }
139978
+ return out.trim().split("\n").flatMap((line) => {
139979
+ if (!line) return [];
139980
+ const parts = line.split("|");
139981
+ return [{
139982
+ name: parts[0] ?? "",
139983
+ shortSha: parts[1] ?? "",
139984
+ relativeTime: parts[2] ?? "",
139985
+ upstreamTrack: parts[3] ?? "",
139986
+ isCurrent: (parts[4] ?? "") === "*"
139987
+ }];
139988
+ });
139989
+ }
139990
+ async function buildGitWorktrees(projectPath) {
139991
+ let out = "";
139992
+ try {
139993
+ out = await gitExec(projectPath, ["worktree", "list", "--porcelain"]);
139994
+ } catch {
139995
+ return [];
139996
+ }
139997
+ const worktrees = [];
139998
+ let current = {};
139999
+ let isFirst = true;
140000
+ for (const line of out.split("\n")) {
140001
+ if (line.startsWith("worktree ")) {
140002
+ if (current.rawPath) {
140003
+ worktrees.push({
140004
+ path: current.rawPath,
140005
+ branch: current.branch ?? "HEAD",
140006
+ sha: current.sha ?? "",
140007
+ isCurrent: current.isCurrent ?? false,
140008
+ isLocked: current.isLocked ?? false
140009
+ });
140010
+ }
140011
+ current = { rawPath: line.slice(9), isCurrent: isFirst };
140012
+ isFirst = false;
140013
+ } else if (line.startsWith("HEAD ")) {
140014
+ current.sha = line.slice(5);
140015
+ } else if (line.startsWith("branch ")) {
140016
+ current.branch = line.slice(7).replace("refs/heads/", "");
140017
+ } else if (line === "locked") {
140018
+ current.isLocked = true;
140019
+ } else if (line.startsWith("locked ")) {
140020
+ current.isLocked = true;
140021
+ }
140022
+ }
140023
+ if (current.rawPath) {
140024
+ worktrees.push({
140025
+ path: current.rawPath,
140026
+ branch: current.branch ?? "HEAD",
140027
+ sha: current.sha ?? "",
140028
+ isCurrent: current.isCurrent ?? false,
140029
+ isLocked: current.isLocked ?? false
140030
+ });
140031
+ }
140032
+ return worktrees;
140033
+ }
140034
+ function guardRelativePath(projectPath, relativePath) {
140035
+ const resolved = pathResolve(projectPath, relativePath);
140036
+ const base = projectPath.endsWith("/") ? projectPath : projectPath + "/";
140037
+ if (resolved !== projectPath && !resolved.startsWith(base)) {
140038
+ throw new Error(`Path traversal denied: ${relativePath}`);
140039
+ }
140040
+ return resolved;
140041
+ }
140042
+ async function buildFileListDirectory(projectPath, relativePath) {
140043
+ const absDir = guardRelativePath(projectPath, relativePath);
140044
+ const dirents = await readdir11(absDir, { withFileTypes: true });
140045
+ const entries = [];
140046
+ for (const d of dirents) {
140047
+ if (FILES_DENYLIST2.has(d.name)) continue;
140048
+ const entryRelPath = relativePath ? `${relativePath}/${d.name}` : d.name;
140049
+ let size = 0;
140050
+ let modifiedAt = (/* @__PURE__ */ new Date(0)).toISOString();
140051
+ try {
140052
+ const s = await stat10(join48(absDir, d.name));
140053
+ size = d.isDirectory() ? 0 : s.size;
140054
+ modifiedAt = s.mtime.toISOString();
140055
+ } catch {
140056
+ }
140057
+ entries.push({
140058
+ name: d.name,
140059
+ path: entryRelPath,
140060
+ isDirectory: d.isDirectory(),
140061
+ size,
140062
+ modifiedAt
140063
+ });
140064
+ }
140065
+ entries.sort((a, b) => {
140066
+ if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
140067
+ return a.name.localeCompare(b.name);
140068
+ });
140069
+ return entries;
140070
+ }
140071
+ async function buildFileReadFile(projectPath, relativePath) {
140072
+ const absFile = guardRelativePath(projectPath, relativePath);
140073
+ const s = await stat10(absFile);
140074
+ const modifiedAt = s.mtime.toISOString();
140075
+ const size = s.size;
140076
+ if (size > FILE_SIZE_LIMIT) {
140077
+ return { content: null, isBinary: false, tooLarge: true, size, modifiedAt, lineCount: 0 };
140078
+ }
140079
+ const buf = await fsReadFile3(absFile);
140080
+ const checkLen = Math.min(buf.length, BINARY_CHECK_BYTES);
140081
+ for (let i = 0; i < checkLen; i++) {
140082
+ if (buf[i] === 0) {
140083
+ return { content: null, isBinary: true, tooLarge: false, size, modifiedAt, lineCount: 0 };
140084
+ }
140085
+ }
140086
+ const text = buf.toString("utf8");
140087
+ const lines = text.split("\n");
140088
+ const lineCount = lines.length;
140089
+ const content = lineCount > MAX_PREVIEW_LINES ? lines.slice(0, MAX_PREVIEW_LINES).join("\n") : text;
140090
+ return { content, isBinary: false, tooLarge: false, size, modifiedAt, lineCount };
140091
+ }
138341
140092
  async function resolveRuntimeProjectPath() {
138342
140093
  try {
138343
140094
  return (await resolveProject(void 0)).projectPath;
@@ -138465,13 +140216,30 @@ async function runDashboard(port, opts = {}) {
138465
140216
  }));
138466
140217
  let tuiRefreshPending = false;
138467
140218
  let tuiRefreshDebounceTimer = null;
140219
+ const projectStores = /* @__PURE__ */ new Map();
140220
+ async function getProjectStore(projectPath) {
140221
+ const cached = projectStores.get(projectPath);
140222
+ if (cached) return cached;
140223
+ let projectStore;
140224
+ if (projectPath === cwd) {
140225
+ if (!store) throw new Error("cwd TaskStore not yet initialized");
140226
+ projectStore = store;
140227
+ } else {
140228
+ projectStore = new TaskStore(projectPath);
140229
+ await projectStore.init();
140230
+ }
140231
+ projectStores.set(projectPath, projectStore);
140232
+ return projectStore;
140233
+ }
138468
140234
  async function refreshTUIStats() {
138469
140235
  if (!tui || !isTTY) return;
138470
140236
  if (!store || !agentStore) return;
138471
140237
  if (tuiRefreshPending) return;
138472
140238
  tuiRefreshPending = true;
138473
140239
  try {
138474
- const tasks = await store.listTasks({ slim: true, includeArchived: false });
140240
+ const scopedPath = tui.boardScopedProjectPath;
140241
+ const taskStore = scopedPath ? await getProjectStore(scopedPath) : store;
140242
+ const tasks = await taskStore.listTasks({ slim: true, includeArchived: false });
138475
140243
  const counts = /* @__PURE__ */ new Map();
138476
140244
  for (const task of tasks) {
138477
140245
  counts.set(task.column, (counts.get(task.column) ?? 0) + 1);
@@ -138522,6 +140290,11 @@ async function runDashboard(port, opts = {}) {
138522
140290
  void refreshTUIStats();
138523
140291
  }, 500);
138524
140292
  }
140293
+ if (tui) {
140294
+ tui.onBoardScopeChange(() => {
140295
+ void refreshTUIStats();
140296
+ });
140297
+ }
138525
140298
  const handlers = [];
138526
140299
  const disposeCallbacks = [];
138527
140300
  let disposed = false;
@@ -139181,19 +140954,13 @@ async function runDashboard(port, opts = {}) {
139181
140954
  });
139182
140955
  if (centralCoreForMesh) {
139183
140956
  const centralCore = centralCoreForMesh;
139184
- const projectStores = /* @__PURE__ */ new Map();
139185
140957
  tui.setInteractiveData({
139186
140958
  listProjects: async () => {
139187
140959
  const projects = await centralCore.listProjects();
139188
140960
  return projects.map((p) => ({ id: p.id, name: p.name, path: p.path }));
139189
140961
  },
139190
140962
  listTasks: async (projectPath) => {
139191
- let projectStore = projectStores.get(projectPath);
139192
- if (!projectStore) {
139193
- projectStore = projectPath === cwd ? store : new TaskStore(projectPath);
139194
- if (projectPath !== cwd) await projectStore.init();
139195
- projectStores.set(projectPath, projectStore);
139196
- }
140963
+ const projectStore = await getProjectStore(projectPath);
139197
140964
  const tasks2 = await projectStore.listTasks({ slim: true, includeArchived: false });
139198
140965
  return tasks2.map((t) => ({
139199
140966
  id: t.id,
@@ -139204,12 +140971,7 @@ async function runDashboard(port, opts = {}) {
139204
140971
  }));
139205
140972
  },
139206
140973
  createTask: async (projectPath, input) => {
139207
- let projectStore = projectStores.get(projectPath);
139208
- if (!projectStore) {
139209
- projectStore = projectPath === cwd ? store : new TaskStore(projectPath);
139210
- if (projectPath !== cwd) await projectStore.init();
139211
- projectStores.set(projectPath, projectStore);
139212
- }
140974
+ const projectStore = await getProjectStore(projectPath);
139213
140975
  const created = await projectStore.createTask({
139214
140976
  title: input.title,
139215
140977
  description: input.description ?? input.title
@@ -139290,6 +141052,104 @@ async function runDashboard(port, opts = {}) {
139290
141052
  provider: m.provider ?? "unknown",
139291
141053
  contextWindow: m.contextWindow ?? 0
139292
141054
  }));
141055
+ },
141056
+ git: {
141057
+ getStatus: (projectPath) => buildGitStatus(projectPath),
141058
+ listCommits: (projectPath, limit) => buildGitCommits(projectPath, limit),
141059
+ showCommit: (projectPath, sha) => buildGitCommitDetail(projectPath, sha),
141060
+ listBranches: (projectPath) => buildGitBranches(projectPath),
141061
+ listWorktrees: (projectPath) => buildGitWorktrees(projectPath),
141062
+ push: async (projectPath) => {
141063
+ try {
141064
+ const { stdout, stderr } = await execFileAsync3("git", ["push"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
141065
+ return { success: true, output: (stdout + stderr).trim() };
141066
+ } catch (err) {
141067
+ const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
141068
+ return { success: false, output: msg.trim() };
141069
+ }
141070
+ },
141071
+ fetch: async (projectPath) => {
141072
+ try {
141073
+ const { stdout, stderr } = await execFileAsync3("git", ["fetch"], { cwd: projectPath, maxBuffer: 4 * 1024 * 1024 });
141074
+ return { success: true, output: (stdout + stderr).trim() };
141075
+ } catch (err) {
141076
+ const msg = err instanceof Error ? err.stderr ?? err.message : String(err);
141077
+ return { success: false, output: msg.trim() };
141078
+ }
141079
+ }
141080
+ },
141081
+ files: {
141082
+ listDirectory: (projectPath, relativePath) => buildFileListDirectory(projectPath, relativePath),
141083
+ readFile: (projectPath, relativePath) => buildFileReadFile(projectPath, relativePath)
141084
+ },
141085
+ tasks: {
141086
+ getTaskDetail: async (projectPath, taskId) => {
141087
+ try {
141088
+ const projectStore = await getProjectStore(projectPath);
141089
+ const t = await projectStore.getTask(taskId);
141090
+ const steps = t.steps.map((s, idx) => ({
141091
+ index: idx,
141092
+ name: s.name,
141093
+ status: s.status === "in-progress" ? "running" : s.status
141094
+ }));
141095
+ const recentLogs = t.log.slice(-200).map((entry) => ({
141096
+ timestamp: entry.timestamp,
141097
+ level: "info",
141098
+ text: entry.outcome ? `${entry.action} \u2192 ${entry.outcome}` : entry.action,
141099
+ source: entry.runContext?.agentId ? "agent" : "executor"
141100
+ }));
141101
+ return {
141102
+ id: t.id,
141103
+ title: t.title,
141104
+ description: t.description ?? "",
141105
+ column: t.column,
141106
+ agentState: t.agentState,
141107
+ branch: t.branch,
141108
+ worktree: t.worktree,
141109
+ currentStepIndex: t.currentStep,
141110
+ steps,
141111
+ recentLogs
141112
+ };
141113
+ } catch {
141114
+ return null;
141115
+ }
141116
+ },
141117
+ subscribeTaskEvents: (projectPath, taskId, handler) => {
141118
+ let projectStorePromise = null;
141119
+ let lastLogLength = 0;
141120
+ const listener = (task) => {
141121
+ if (task.id !== taskId) return;
141122
+ task.steps.forEach((s, idx) => {
141123
+ const status = s.status === "in-progress" ? "running" : s.status;
141124
+ handler({
141125
+ kind: "step:updated",
141126
+ step: { index: idx, name: s.name, status }
141127
+ });
141128
+ });
141129
+ const newEntries = task.log.slice(lastLogLength);
141130
+ lastLogLength = task.log.length;
141131
+ for (const entry of newEntries) {
141132
+ handler({
141133
+ kind: "log:appended",
141134
+ entry: {
141135
+ timestamp: entry.timestamp,
141136
+ level: "info",
141137
+ text: entry.outcome ? `${entry.action} \u2192 ${entry.outcome}` : entry.action,
141138
+ source: entry.runContext?.agentId ? "agent" : "executor"
141139
+ }
141140
+ });
141141
+ }
141142
+ };
141143
+ projectStorePromise = getProjectStore(projectPath).then((ps) => {
141144
+ ps.on("task:updated", listener);
141145
+ return ps;
141146
+ }).catch(() => null);
141147
+ return () => {
141148
+ void projectStorePromise?.then((ps) => {
141149
+ if (ps) ps.off("task:updated", listener);
141150
+ });
141151
+ };
141152
+ }
139293
141153
  }
139294
141154
  });
139295
141155
  }
@@ -139333,7 +141193,7 @@ async function runDashboard(port, opts = {}) {
139333
141193
  });
139334
141194
  return { dispose };
139335
141195
  }
139336
- var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer;
141196
+ var processDiagnosticsRegistered, diagnosticIntervalHandle, DIAGNOSTIC_INTERVAL_MS, diagnosticStartTime, diagnosticDbHealthCheck, diagnosticStoreListenerCheck, STREAM_LOG_FLUSH_IDLE_MS, StreamedLogBuffer, execFileAsync3, FILES_DENYLIST2, FILE_SIZE_LIMIT, BINARY_CHECK_BYTES, MAX_PREVIEW_LINES;
139337
141197
  var init_dashboard = __esm({
139338
141198
  "src/commands/dashboard.ts"() {
139339
141199
  "use strict";
@@ -139409,6 +141269,11 @@ var init_dashboard = __esm({
139409
141269
  }
139410
141270
  }
139411
141271
  };
141272
+ execFileAsync3 = promisify12(execFileCb);
141273
+ FILES_DENYLIST2 = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".next", "target", "build"]);
141274
+ FILE_SIZE_LIMIT = 1024 * 1024;
141275
+ BINARY_CHECK_BYTES = 8 * 1024;
141276
+ MAX_PREVIEW_LINES = 2e3;
139412
141277
  }
139413
141278
  });
139414
141279
 
@@ -139416,7 +141281,7 @@ var init_dashboard = __esm({
139416
141281
  var node_exports = {};
139417
141282
  __export(node_exports, {
139418
141283
  findNodeByNameOrId: () => findNodeByNameOrId,
139419
- formatBytes: () => formatBytes3,
141284
+ formatBytes: () => formatBytes4,
139420
141285
  formatLastActivity: () => formatLastActivity,
139421
141286
  formatStatusBar: () => formatStatusBar,
139422
141287
  formatUptime: () => formatUptime3,
@@ -139438,7 +141303,7 @@ function maskApiKey(key) {
139438
141303
  if (key.length < 4) return "****";
139439
141304
  return `****${key.slice(-4)}`;
139440
141305
  }
139441
- function formatBytes3(bytes) {
141306
+ function formatBytes4(bytes) {
139442
141307
  if (bytes === 0) return "0 B";
139443
141308
  const units = ["B", "KB", "MB", "GB", "TB"];
139444
141309
  const k = 1024;
@@ -139695,8 +141560,8 @@ async function runNodeShow(name, options = {}) {
139695
141560
  console.log();
139696
141561
  console.log(" System Metrics:");
139697
141562
  console.log(` CPU Usage: ${formatStatusBar(metrics.cpuUsage)}`);
139698
- console.log(` Memory: ${formatBytes3(metrics.memoryUsed)} / ${formatBytes3(metrics.memoryTotal)}`);
139699
- console.log(` Storage: ${formatBytes3(metrics.storageUsed)} / ${formatBytes3(metrics.storageTotal)}`);
141563
+ console.log(` Memory: ${formatBytes4(metrics.memoryUsed)} / ${formatBytes4(metrics.memoryTotal)}`);
141564
+ console.log(` Storage: ${formatBytes4(metrics.storageUsed)} / ${formatBytes4(metrics.storageTotal)}`);
139700
141565
  console.log(` Uptime: ${formatUptime3(metrics.uptime)}`);
139701
141566
  console.log(` Reported: ${formatLastActivity(metrics.reportedAt)}`);
139702
141567
  } else {
@@ -139847,7 +141712,7 @@ async function resolveRuntimeProjectPath2() {
139847
141712
  return process.cwd();
139848
141713
  }
139849
141714
  }
139850
- function formatBytes4(bytes) {
141715
+ function formatBytes5(bytes) {
139851
141716
  if (bytes < 1024) return `${bytes}B`;
139852
141717
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
139853
141718
  if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
@@ -139888,7 +141753,7 @@ function logDiagnostics2(prefix, dbHealthCheck) {
139888
141753
  } catch {
139889
141754
  }
139890
141755
  }
139891
- const logLine = `[${prefix}] diagnostics: uptime=${formatUptime4(uptime)} rss=${formatBytes4(mem.rss)} heap=${formatBytes4(mem.heapUsed)}/${formatBytes4(mem.heapTotal)} external=${formatBytes4(mem.external)} arrayBuffers=${formatBytes4(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
141756
+ const logLine = `[${prefix}] diagnostics: uptime=${formatUptime4(uptime)} rss=${formatBytes5(mem.rss)} heap=${formatBytes5(mem.heapUsed)}/${formatBytes5(mem.heapTotal)} external=${formatBytes5(mem.external)} arrayBuffers=${formatBytes5(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}${listenerInfo}`;
139892
141757
  console.log(logLine);
139893
141758
  }
139894
141759
  function stopDiagnosticInterval2() {
@@ -140415,7 +142280,7 @@ async function resolveRuntimeProjectPath3() {
140415
142280
  return process.cwd();
140416
142281
  }
140417
142282
  }
140418
- function formatBytes5(bytes) {
142283
+ function formatBytes6(bytes) {
140419
142284
  if (bytes < 1024) return `${bytes}B`;
140420
142285
  if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)}KB`;
140421
142286
  if (bytes < 1024 * 1024 * 1024) return `${Math.round(bytes / (1024 * 1024))}MB`;
@@ -140449,7 +142314,7 @@ function logDiagnostics3(dbHealthCheck) {
140449
142314
  dbHealth = "error";
140450
142315
  }
140451
142316
  }
140452
- const logLine = `[daemon] diagnostics: uptime=${formatUptime5(uptime)} rss=${formatBytes5(mem.rss)} heap=${formatBytes5(mem.heapUsed)}/${formatBytes5(mem.heapTotal)} external=${formatBytes5(mem.external)} arrayBuffers=${formatBytes5(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}`;
142317
+ const logLine = `[daemon] diagnostics: uptime=${formatUptime5(uptime)} rss=${formatBytes6(mem.rss)} heap=${formatBytes6(mem.heapUsed)}/${formatBytes6(mem.heapTotal)} external=${formatBytes6(mem.external)} arrayBuffers=${formatBytes6(mem.arrayBuffers)} handles=${handleCount} requests=${requestCount} db=${dbHealth}`;
140453
142318
  console.log(logLine);
140454
142319
  }
140455
142320
  function maskToken(token) {
@@ -142610,7 +144475,7 @@ __export(git_exports, {
142610
144475
  runGitStatus: () => runGitStatus
142611
144476
  });
142612
144477
  import { exec as exec10 } from "node:child_process";
142613
- import { promisify as promisify12 } from "node:util";
144478
+ import { promisify as promisify13 } from "node:util";
142614
144479
  import { createInterface as createInterface4 } from "node:readline/promises";
142615
144480
  async function resolveGitCwd(projectName) {
142616
144481
  if (projectName) {
@@ -142884,7 +144749,7 @@ var init_git = __esm({
142884
144749
  "src/commands/git.ts"() {
142885
144750
  "use strict";
142886
144751
  init_project_context();
142887
- execAsync10 = promisify12(exec10);
144752
+ execAsync10 = promisify13(exec10);
142888
144753
  }
142889
144754
  });
142890
144755
 
@@ -142935,12 +144800,12 @@ async function runBackupList(projectName) {
142935
144800
  console.log(`Found ${backups.length} backup(s):
142936
144801
  `);
142937
144802
  const totalSize = backups.reduce((sum, b) => sum + b.size, 0);
142938
- const formattedTotal = formatBytes6(totalSize);
144803
+ const formattedTotal = formatBytes7(totalSize);
142939
144804
  console.log("Date Size Filename");
142940
144805
  console.log("-".repeat(60));
142941
144806
  for (const backup of backups) {
142942
144807
  const date = new Date(backup.createdAt).toLocaleString();
142943
- const size = formatBytes6(backup.size).padEnd(8);
144808
+ const size = formatBytes7(backup.size).padEnd(8);
142944
144809
  console.log(`${date} ${size} ${backup.filename}`);
142945
144810
  }
142946
144811
  console.log("-".repeat(60));
@@ -142969,7 +144834,7 @@ async function runBackupCleanup(projectName) {
142969
144834
  console.log("No backups to clean up (within retention limit).");
142970
144835
  }
142971
144836
  }
142972
- function formatBytes6(bytes) {
144837
+ function formatBytes7(bytes) {
142973
144838
  if (bytes === 0) return "0 B";
142974
144839
  const k = 1024;
142975
144840
  const sizes = ["B", "KB", "MB", "GB"];
@@ -144102,7 +145967,7 @@ __export(init_exports, {
144102
145967
  import { existsSync as existsSync42, mkdirSync as mkdirSync9, writeFileSync as writeFileSync3, readFileSync as readFileSync15 } from "node:fs";
144103
145968
  import { join as join55, resolve as resolve28, basename as basename14 } from "node:path";
144104
145969
  import { exec as exec11 } from "node:child_process";
144105
- import { promisify as promisify13 } from "node:util";
145970
+ import { promisify as promisify14 } from "node:util";
144106
145971
  async function runInit(options = {}) {
144107
145972
  const cwd = options.path ? resolve28(options.path) : process.cwd();
144108
145973
  const fusionDir = join55(cwd, ".fusion");
@@ -144278,7 +146143,7 @@ var init_init = __esm({
144278
146143
  init_src();
144279
146144
  init_claude_skills_runner();
144280
146145
  init_skill_installation();
144281
- execAsync11 = promisify13(exec11);
146146
+ execAsync11 = promisify14(exec11);
144282
146147
  }
144283
146148
  });
144284
146149