@madarco/agentbox 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,8 +3,8 @@ import {
3
3
  createBox,
4
4
  defaultBoxName,
5
5
  sanitizeBasename
6
- } from "./chunk-3OPLNXQ5.js";
7
- import "./chunk-3NIZKZPJ.js";
6
+ } from "./chunk-3NCUES35.js";
7
+ import "./chunk-MOC54XL6.js";
8
8
  import "./chunk-IDR4HVIC.js";
9
9
  import "./chunk-SOMIKEN2.js";
10
10
  export {
@@ -12,4 +12,4 @@ export {
12
12
  defaultBoxName,
13
13
  sanitizeBasename
14
14
  };
15
- //# sourceMappingURL=create-SE6H4B5U-ESNDTXAI.js.map
15
+ //# sourceMappingURL=create-SE6H4B5U-IWAZHJHV.js.map
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createBox
4
- } from "./chunk-3OPLNXQ5.js";
4
+ } from "./chunk-3NCUES35.js";
5
5
  import {
6
6
  AmbiguousBoxError,
7
7
  BoxNotFoundError,
@@ -15,7 +15,7 @@ import {
15
15
  startBox,
16
16
  stopBox,
17
17
  unpauseBox
18
- } from "./chunk-3NU4PS5W.js";
18
+ } from "./chunk-7NQFIBQG.js";
19
19
  import {
20
20
  ClaudeSessionError,
21
21
  SHARED_CLAUDE_VOLUME,
@@ -37,7 +37,7 @@ import {
37
37
  resolveClaudeVolume,
38
38
  startClaudeSession,
39
39
  stopRelay
40
- } from "./chunk-3NIZKZPJ.js";
40
+ } from "./chunk-MOC54XL6.js";
41
41
  import {
42
42
  STATE_DIR,
43
43
  readState,
@@ -1600,6 +1600,7 @@ var InputParser = class {
1600
1600
  if (b === LEADER) {
1601
1601
  this.flush();
1602
1602
  this.state = "leader";
1603
+ this.onEvent({ type: "leader", active: true });
1603
1604
  this.arm(this.leaderMs, "leader");
1604
1605
  } else if (b === ESC) {
1605
1606
  this.flush();
@@ -1622,14 +1623,18 @@ var InputParser = class {
1622
1623
  if (c === "v") this.onEvent({ type: "action", name: "vnc" });
1623
1624
  else if (c === "w") this.onEvent({ type: "action", name: "web" });
1624
1625
  else if (c === "c") this.onEvent({ type: "action", name: "code" });
1625
- else if (c === "q" || c === "d") this.onEvent({ type: "quit" });
1626
- else if (c === "k" || c === "p" || c === "P") this.onEvent({ type: "switch", dir: "prev" });
1626
+ else if (c === "s") this.onEvent({ type: "action", name: "stop" });
1627
+ else if (c === "p") this.onEvent({ type: "action", name: "pause" });
1628
+ else if (c === "d") this.onEvent({ type: "action", name: "destroy" });
1629
+ else if (c === "q") this.onEvent({ type: "quit" });
1630
+ else if (c === "k") this.onEvent({ type: "switch", dir: "prev" });
1627
1631
  else if (c === "j" || c === "n" || c === "N") this.onEvent({ type: "switch", dir: "next" });
1628
1632
  else {
1629
1633
  this.fwd.push(b);
1630
1634
  this.flush();
1631
1635
  }
1632
1636
  }
1637
+ this.onEvent({ type: "leader", active: false });
1633
1638
  this.state = "normal";
1634
1639
  i++;
1635
1640
  continue;
@@ -1737,6 +1742,7 @@ var InputParser = class {
1737
1742
  if (kind === "leader" && this.state === "leader") {
1738
1743
  this.fwd.push(LEADER);
1739
1744
  this.flush();
1745
+ this.onEvent({ type: "leader", active: false });
1740
1746
  this.state = "normal";
1741
1747
  } else if (kind === "esc" && (this.state === "esc" || this.state === "mouseX10")) {
1742
1748
  this.forwardVerbatim(this.esc);
@@ -1861,6 +1867,18 @@ var PtySession = class {
1861
1867
  };
1862
1868
 
1863
1869
  // src/dashboard/sidebar.ts
1870
+ function ellipsize(s, max) {
1871
+ if (max <= 0) return "";
1872
+ if (s.length <= max) return s;
1873
+ if (max === 1) return "\u2026";
1874
+ return s.slice(0, max - 1) + "\u2026";
1875
+ }
1876
+ function ellipsizeHead(s, max) {
1877
+ if (max <= 0) return "";
1878
+ if (s.length <= max) return s;
1879
+ if (max === 1) return "\u2026";
1880
+ return "\u2026" + s.slice(s.length - (max - 1));
1881
+ }
1864
1882
  function activityCell(b) {
1865
1883
  if (b.state !== "running") return `[${b.state}]`;
1866
1884
  switch (b.claudeActivity) {
@@ -1876,8 +1894,12 @@ function activityCell(b) {
1876
1894
  }
1877
1895
  var NEW_BOX_ID = "__agentbox_new__";
1878
1896
  var NEW_BOX_LABEL = "+ New box";
1879
- var SIDEBAR_HEADER = "\u2550 AgentBox \u2550";
1880
- var SIDEBAR_HEADER_LINES = 2;
1897
+ var SIDEBAR_HEADER = "AgentBox";
1898
+ function topBorder(label, w) {
1899
+ const lead = `\u256D\u2500\u2500\u2500 ${label} `;
1900
+ if (lead.length >= w) return lead.slice(0, w);
1901
+ return lead + "\u2500".repeat(w - lead.length);
1902
+ }
1881
1903
  function fit(s, w) {
1882
1904
  if (s.length === w) return s;
1883
1905
  if (s.length > w) return s.slice(0, w);
@@ -1889,17 +1911,55 @@ function center(s, w) {
1889
1911
  const leftPad = Math.floor(pad / 2);
1890
1912
  return " ".repeat(leftPad) + s + " ".repeat(pad - leftPad);
1891
1913
  }
1914
+ function projectLabel(project) {
1915
+ if (!project) return "(no project)";
1916
+ const parts = project.split("/").filter(Boolean);
1917
+ return parts[parts.length - 1] ?? project;
1918
+ }
1919
+ function stripTitleGlyph(s) {
1920
+ const t = s.replace(/^[\s\p{S}*·]+/u, "");
1921
+ return t.length > 0 ? t : s.trim();
1922
+ }
1923
+ function boxRow(b, marker, w) {
1924
+ const numStr = b.index != null ? `${b.index} ` : "";
1925
+ const status = activityCell(b);
1926
+ const left = `${marker}${numStr}`;
1927
+ const room = w - left.length - status.length - 1;
1928
+ if (room <= 0) return fit(`${left}${status}`, w);
1929
+ const middle = b.state === "running" && b.sessionTitle ? ellipsize(stripTitleGlyph(b.sessionTitle), room) : ellipsizeHead(b.name, room);
1930
+ return fit(`${left}${middle}`, w - status.length) + status;
1931
+ }
1892
1932
  function sidebarLines(boxes, selectedId, w, h) {
1893
- const lines = [center(SIDEBAR_HEADER, w), fit("", w)];
1894
- const nameW = Math.min(16, Math.max(6, ...boxes.map((b) => b.name.length), 6));
1933
+ const lines = [topBorder(SIDEBAR_HEADER, w), fit("", w)];
1934
+ const rowOwner = [null, null];
1935
+ const headerRows = [true, false];
1936
+ const push = (line, owner, header) => {
1937
+ lines.push(fit(line, w));
1938
+ rowOwner.push(owner);
1939
+ headerRows.push(header);
1940
+ };
1941
+ let prevProject;
1942
+ let seenGroup = false;
1895
1943
  for (const b of boxes) {
1896
- const marker = b.id === selectedId ? "\u25B8 " : " ";
1897
- const row2 = b.id === NEW_BOX_ID ? `${marker}${NEW_BOX_LABEL}` : `${marker}${fit(b.name, nameW)} ${activityCell(b)}`;
1898
- lines.push(fit(row2, w));
1944
+ const marker = b.id === selectedId ? "\u25B8" : " ";
1945
+ if (b.id === NEW_BOX_ID) {
1946
+ push(`${marker}${NEW_BOX_LABEL}`, b.id, false);
1947
+ continue;
1948
+ }
1949
+ if (!seenGroup || b.project !== prevProject) {
1950
+ push(center(` \u2500\u2500 ${projectLabel(b.project)} \u2500\u2500 `, w), null, true);
1951
+ prevProject = b.project;
1952
+ seenGroup = true;
1953
+ }
1954
+ push(boxRow(b, marker, w), b.id, false);
1899
1955
  }
1900
- if (boxes.length === 0) lines.push(fit(" (no boxes)", w));
1901
- while (lines.length < h) lines.push(fit("", w));
1902
- return lines.slice(0, h);
1956
+ if (boxes.length === 0) push(" (no boxes)", null, false);
1957
+ while (lines.length < h) push("", null, false);
1958
+ return {
1959
+ lines: lines.slice(0, h),
1960
+ rowOwner: rowOwner.slice(0, h),
1961
+ headerRows: headerRows.slice(0, h)
1962
+ };
1903
1963
  }
1904
1964
  function menuLines(boxName, w, h) {
1905
1965
  const body = [
@@ -1963,29 +2023,58 @@ var BRAND_NOBOLD = "\x1B[22m";
1963
2023
  var HINT_KEY = "\x1B[38;5;255m";
1964
2024
  var HINT_TXT = "\x1B[38;5;245m";
1965
2025
  var BAR_RESET = "\x1B[0m";
2026
+ var SWITCH_HINT = ["Control+Option+\u2191/\u2193", "switch"];
1966
2027
  var HINT_GROUPS = [
1967
- ["Control+Option+Up/Down", "switch"],
2028
+ SWITCH_HINT,
1968
2029
  ["Control+a c", "code"],
1969
2030
  ["Control+a v", "vnc"],
1970
2031
  ["Control+a w", "web"],
1971
2032
  ["Control+a q", "quit"]
1972
2033
  ];
1973
- function statusLine(box, w, stateLabel) {
2034
+ var COLLAPSED_HINT_GROUPS = [
2035
+ SWITCH_HINT,
2036
+ ["Control+a", "more"]
2037
+ ];
2038
+ var ADVANCED_HINT_GROUPS = [
2039
+ ["c", "code"],
2040
+ ["v", "vnc"],
2041
+ ["w", "web"],
2042
+ ["s", "stop"],
2043
+ ["p", "pause"],
2044
+ ["d", "destroy"],
2045
+ ["q", "quit"]
2046
+ ];
2047
+ function statusLine(box, w, stateLabel, groups = HINT_GROUPS) {
1974
2048
  const state = stateLabel ?? (box ? box.state === "running" ? box.claudeActivity ?? "unknown" : box.state : "");
1975
2049
  const brandPrefix = box ? " agentbox \u25B8 " : " agentbox ";
1976
- const brandMain = box ? `${box.name} (${state}) ` : "";
1977
- const left = brandPrefix + brandMain;
1978
- const leftStyled = BAR_BRAND + brandPrefix + BRAND_BOLD + brandMain + BRAND_NOBOLD;
2050
+ const base = box ? `${box.name} (${state})` : "";
2051
+ const coreMain = box ? `${base} ` : "";
2052
+ const corePlain = brandPrefix + coreMain;
1979
2053
  const SEP = " \u2502 ";
1980
- const rightPlain = HINT_GROUPS.map(([k, l]) => `${k}: ${l}`).join(SEP) + " ";
1981
- const rightStyled = HINT_GROUPS.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(
1982
- `${HINT_TXT}${SEP}`
1983
- ) + " ";
1984
- if (left.length + rightPlain.length + 1 > w) {
1985
- return BAR_BASE + BAR_BRAND + fit(left, w) + BAR_RESET;
1986
- }
1987
- const gap = w - left.length - rightPlain.length;
1988
- return BAR_BASE + leftStyled + BAR_BASE + " ".repeat(gap) + rightStyled + BAR_RESET;
2054
+ const renderHints = (g) => ({
2055
+ plain: g.map(([k, l]) => `${k}: ${l}`).join(SEP) + " ",
2056
+ styled: g.map(([k, l]) => `${HINT_KEY}${k}${HINT_TXT}: ${l}`).join(`${HINT_TXT}${SEP}`) + " "
2057
+ });
2058
+ let hints = null;
2059
+ for (const g of [groups, COLLAPSED_HINT_GROUPS]) {
2060
+ const h = renderHints(g);
2061
+ if (corePlain.length + h.plain.length + 1 <= w) {
2062
+ hints = h;
2063
+ break;
2064
+ }
2065
+ }
2066
+ if (!hints) {
2067
+ return BAR_BASE + BAR_BRAND + fit(corePlain, w) + BAR_RESET;
2068
+ }
2069
+ const room = w - corePlain.length - hints.plain.length - 1;
2070
+ let titleSeg = "";
2071
+ if (box?.sessionTitle && room >= 7) {
2072
+ titleSeg = ` \u2014 ${ellipsize(box.sessionTitle, Math.min(40, room - 3))}`;
2073
+ }
2074
+ const leftPlain = brandPrefix + base + titleSeg + (box ? " " : "");
2075
+ const leftStyled = BAR_BRAND + brandPrefix + BRAND_BOLD + base + titleSeg + (box ? " " : "") + BRAND_NOBOLD;
2076
+ const gap = w - leftPlain.length - hints.plain.length;
2077
+ return BAR_BASE + leftStyled + BAR_BASE + " ".repeat(gap) + hints.styled + BAR_RESET;
1989
2078
  }
1990
2079
 
1991
2080
  // src/dashboard/compositor.ts
@@ -1996,6 +2085,7 @@ var SGR_RESET = "\x1B[0m";
1996
2085
  var POLL_MS = 1e3;
1997
2086
  var FRAME_MS = 16;
1998
2087
  var RESIZE_DEBOUNCE_MS = 120;
2088
+ var LEADER_LINGER_MS = 1500;
1999
2089
  var SYNC_BEGIN = "\x1B[?2026h";
2000
2090
  var SYNC_END = "\x1B[?2026l";
2001
2091
  function cursorTo2(x, y) {
@@ -2008,10 +2098,40 @@ var Compositor = class {
2008
2098
  this.layout = computeLayout(this.out.columns ?? 100, this.out.rows ?? 30);
2009
2099
  this.parser = new InputParser({
2010
2100
  onEvent: (e) => {
2101
+ if (e.type === "leader") {
2102
+ if (this.leaderLingerTimer) {
2103
+ clearTimeout(this.leaderLingerTimer);
2104
+ this.leaderLingerTimer = null;
2105
+ }
2106
+ if (e.active) {
2107
+ this.leaderActive = true;
2108
+ } else {
2109
+ this.leaderLingerTimer = setTimeout(() => {
2110
+ this.leaderLingerTimer = null;
2111
+ this.leaderActive = false;
2112
+ this.drawChrome();
2113
+ }, LEADER_LINGER_MS);
2114
+ }
2115
+ this.drawChrome();
2116
+ return;
2117
+ }
2118
+ if (this.pendingConfirm) {
2119
+ if (e.type === "forward") {
2120
+ this.handleConfirmKey(e.bytes);
2121
+ return;
2122
+ }
2123
+ this.pendingConfirm = null;
2124
+ this.drawChrome();
2125
+ }
2011
2126
  if (e.type === "quit") this.onSig();
2012
2127
  else if (e.type === "switch") this.switchBox(e.dir);
2013
- else if (e.type === "action") void this.doAction(e.name);
2014
- else if (this.createMenu) this.handleCreateMenuKey(e.bytes);
2128
+ else if (e.type === "action") {
2129
+ if (e.name === "pause" || e.name === "stop" || e.name === "destroy") {
2130
+ void this.doLifecycle(e.name);
2131
+ } else {
2132
+ void this.doAction(e.name);
2133
+ }
2134
+ } else if (this.createMenu) this.handleCreateMenuKey(e.bytes);
2015
2135
  else if (this.lifecycleMenu) this.handleLifecycleMenuKey(e.bytes);
2016
2136
  else if (this.menu) this.handleMenuKey(e.bytes);
2017
2137
  else this.session?.write(e.bytes);
@@ -2038,6 +2158,14 @@ var Compositor = class {
2038
2158
  menu = null;
2039
2159
  lifecycleMenu = null;
2040
2160
  createMenu = null;
2161
+ /** True while the Ctrl-a leader is pending — swaps the footer to the
2162
+ * expanded chord menu (chrome only; never touches the right pane). */
2163
+ leaderActive = false;
2164
+ /** Holds the expanded footer for LEADER_LINGER_MS after the leader resolves
2165
+ * (so the chord menu doesn't flash by). */
2166
+ leaderLingerTimer = null;
2167
+ /** Set while a destroy confirm is pending in the status bar. */
2168
+ pendingConfirm = null;
2041
2169
  activeMode = "claude";
2042
2170
  flashMsg = null;
2043
2171
  flashTimer = null;
@@ -2097,7 +2225,9 @@ var Compositor = class {
2097
2225
  return this.boxes.find((b) => b.id === this.selectedId);
2098
2226
  }
2099
2227
  async poll() {
2100
- const before = JSON.stringify(this.boxes.map((b) => [b.id, b.state, b.claudeActivity]));
2228
+ const before = JSON.stringify(
2229
+ this.boxes.map((b) => [b.id, b.state, b.claudeActivity, b.sessionTitle])
2230
+ );
2101
2231
  await this.refreshBoxes();
2102
2232
  if (this.busy) {
2103
2233
  } else if (!this.boxes.some((b) => b.id === this.selectedId) && this.boxes[0]) {
@@ -2109,7 +2239,9 @@ var Compositor = class {
2109
2239
  const reresolve = this.session && !running || this.placeholder && running || this.menu && !running || this.lifecycleMenu != null && box?.state !== this.lifecycleMenu.state;
2110
2240
  if (reresolve) await this.spawnActive();
2111
2241
  }
2112
- if (JSON.stringify(this.boxes.map((b) => [b.id, b.state, b.claudeActivity])) !== before) {
2242
+ if (JSON.stringify(
2243
+ this.boxes.map((b) => [b.id, b.state, b.claudeActivity, b.sessionTitle])
2244
+ ) !== before) {
2113
2245
  this.drawChrome();
2114
2246
  }
2115
2247
  }
@@ -2124,6 +2256,7 @@ var Compositor = class {
2124
2256
  this.menu = null;
2125
2257
  this.lifecycleMenu = null;
2126
2258
  this.createMenu = null;
2259
+ this.pendingConfirm = null;
2127
2260
  this.clearRightPane();
2128
2261
  const id = this.selectedId;
2129
2262
  const target = await this.deps.resolveTarget(id);
@@ -2137,6 +2270,7 @@ var Compositor = class {
2137
2270
  this.menu = null;
2138
2271
  this.lifecycleMenu = null;
2139
2272
  this.createMenu = null;
2273
+ this.pendingConfirm = null;
2140
2274
  if (target.kind === "attach") {
2141
2275
  this.activeMode = target.mode ?? "claude";
2142
2276
  this.session = new PtySession(
@@ -2214,7 +2348,7 @@ var Compositor = class {
2214
2348
  for (const b of bytes) {
2215
2349
  if (m.confirmDestroy) {
2216
2350
  if (b === 121 || b === 13 || b === 10) {
2217
- void this.choosePaused("destroy");
2351
+ void this.runDestroy(this.selectedId, this.selectedBox()?.name ?? this.selectedId);
2218
2352
  } else {
2219
2353
  m.confirmDestroy = false;
2220
2354
  this.drawChrome();
@@ -2224,7 +2358,7 @@ var Compositor = class {
2224
2358
  }
2225
2359
  const resumeKey = m.state === "paused" ? 117 : 115;
2226
2360
  if (b === resumeKey) {
2227
- void this.choosePaused("resume");
2361
+ void this.resumeSelected();
2228
2362
  return;
2229
2363
  }
2230
2364
  if (b === 100) {
@@ -2235,37 +2369,27 @@ var Compositor = class {
2235
2369
  }
2236
2370
  }
2237
2371
  }
2238
- async choosePaused(which) {
2372
+ async resumeSelected() {
2239
2373
  if (this.busy) return;
2240
2374
  const id = this.selectedId;
2241
2375
  const name = this.selectedBox()?.name ?? id;
2242
- const resumeVerb = this.lifecycleMenu?.state === "stopped" ? "start" : "unpause";
2376
+ const verb = this.lifecycleMenu?.state === "stopped" ? "start" : "unpause";
2243
2377
  this.busy = true;
2244
2378
  this.menu = null;
2245
2379
  this.lifecycleMenu = null;
2246
2380
  this.createMenu = null;
2247
- this.placeholder = ["", which === "resume" ? " Resuming\u2026" : " Destroying\u2026"];
2381
+ this.placeholder = ["", " Resuming\u2026"];
2248
2382
  this.prevRows = null;
2249
2383
  this.drawChrome();
2250
2384
  this.scheduleRender();
2251
2385
  try {
2252
- if (which === "resume") {
2253
- await this.deps.resumeBox(id);
2254
- if (this.selectedId !== id || this.tornDown) return;
2255
- await this.refreshBoxes();
2256
- await this.spawnActive();
2257
- } else {
2258
- await this.deps.destroyBox(id);
2259
- if (this.tornDown) return;
2260
- await this.refreshBoxes();
2261
- if (this.boxes[0]) this.selectedId = this.boxes[0].id;
2262
- await this.spawnActive();
2263
- this.flash(`destroyed ${name}`);
2264
- }
2386
+ await this.deps.resumeBox(id);
2387
+ if (this.selectedId !== id || this.tornDown) return;
2388
+ await this.refreshBoxes();
2389
+ await this.spawnActive();
2265
2390
  } catch (err) {
2266
2391
  if (this.selectedId !== id || this.tornDown) return;
2267
2392
  const msg = err instanceof Error ? err.message : String(err);
2268
- const verb = which === "resume" ? resumeVerb : "destroy";
2269
2393
  this.placeholder = [
2270
2394
  "",
2271
2395
  ` Failed to ${verb} ${name}:`,
@@ -2279,6 +2403,102 @@ var Compositor = class {
2279
2403
  this.busy = false;
2280
2404
  }
2281
2405
  }
2406
+ /** Destroy `id` and recover the selection. Shared by the lifecycle-menu
2407
+ * confirm and the running-box `Ctrl-a d` status-bar confirm. */
2408
+ async runDestroy(id, name) {
2409
+ if (this.busy) return;
2410
+ this.busy = true;
2411
+ this.menu = null;
2412
+ this.lifecycleMenu = null;
2413
+ this.createMenu = null;
2414
+ this.placeholder = ["", " Destroying\u2026"];
2415
+ this.prevRows = null;
2416
+ this.drawChrome();
2417
+ this.scheduleRender();
2418
+ try {
2419
+ await this.deps.destroyBox(id);
2420
+ if (this.tornDown) return;
2421
+ await this.refreshBoxes();
2422
+ if (this.boxes[0]) this.selectedId = this.boxes[0].id;
2423
+ await this.spawnActive();
2424
+ this.flash(`destroyed ${name}`);
2425
+ } catch (err) {
2426
+ if (this.tornDown) return;
2427
+ const msg = err instanceof Error ? err.message : String(err);
2428
+ this.placeholder = [
2429
+ "",
2430
+ ` Failed to destroy ${name}:`,
2431
+ ` ${msg}`,
2432
+ "",
2433
+ ` Try from a shell: agentbox destroy ${name}`
2434
+ ];
2435
+ this.prevRows = null;
2436
+ this.scheduleRender();
2437
+ } finally {
2438
+ this.busy = false;
2439
+ }
2440
+ }
2441
+ /** Ctrl-a p/s/d on the selected box. pause/stop transition state (the pane
2442
+ * re-resolves to the lifecycle menu); destroy asks to confirm first. */
2443
+ async doLifecycle(name) {
2444
+ if (this.selectedId === NEW_BOX_ID) {
2445
+ this.flash("select a box first");
2446
+ return;
2447
+ }
2448
+ const id = this.selectedId;
2449
+ const boxName = this.selectedBox()?.name ?? id;
2450
+ if (name === "destroy") {
2451
+ this.pendingConfirm = { boxId: id, name: boxName };
2452
+ this.drawChrome();
2453
+ return;
2454
+ }
2455
+ if (this.selectedBox()?.state !== "running") {
2456
+ this.flash(`${boxName} is not running`);
2457
+ return;
2458
+ }
2459
+ if (this.busy) return;
2460
+ this.busy = true;
2461
+ this.menu = null;
2462
+ this.lifecycleMenu = null;
2463
+ this.createMenu = null;
2464
+ this.placeholder = ["", name === "pause" ? " Pausing\u2026" : " Stopping\u2026"];
2465
+ this.prevRows = null;
2466
+ this.drawChrome();
2467
+ this.scheduleRender();
2468
+ try {
2469
+ if (name === "pause") await this.deps.pauseBox(id);
2470
+ else await this.deps.stopBox(id);
2471
+ if (this.selectedId !== id || this.tornDown) return;
2472
+ await this.refreshBoxes();
2473
+ await this.spawnActive();
2474
+ this.flash(`${name === "pause" ? "paused" : "stopped"} ${boxName}`);
2475
+ } catch (err) {
2476
+ if (this.selectedId !== id || this.tornDown) return;
2477
+ const msg = err instanceof Error ? err.message : String(err);
2478
+ this.placeholder = [
2479
+ "",
2480
+ ` Failed to ${name} ${boxName}:`,
2481
+ ` ${msg}`,
2482
+ "",
2483
+ ` Try from a shell: agentbox ${name} ${boxName}`
2484
+ ];
2485
+ this.prevRows = null;
2486
+ this.scheduleRender();
2487
+ } finally {
2488
+ this.busy = false;
2489
+ }
2490
+ }
2491
+ handleConfirmKey(bytes) {
2492
+ const c = this.pendingConfirm;
2493
+ if (!c) return;
2494
+ const b = bytes[0];
2495
+ this.pendingConfirm = null;
2496
+ if (b === 121 || b === 13 || b === 10) {
2497
+ void this.runDestroy(c.boxId, c.name);
2498
+ } else {
2499
+ this.drawChrome();
2500
+ }
2501
+ }
2282
2502
  handleCreateMenuKey(bytes) {
2283
2503
  for (const b of bytes) {
2284
2504
  if (b === 99 || b === 13 || b === 10) {
@@ -2360,6 +2580,7 @@ var Compositor = class {
2360
2580
  }
2361
2581
  switchBox(dir) {
2362
2582
  if (this.boxes.length === 0) return;
2583
+ this.pendingConfirm = null;
2363
2584
  const i = Math.max(
2364
2585
  0,
2365
2586
  this.boxes.findIndex((b) => b.id === this.selectedId)
@@ -2431,23 +2652,36 @@ var Compositor = class {
2431
2652
  drawChrome() {
2432
2653
  if (this.tornDown || this.layout.tooSmall) return;
2433
2654
  const { sidebar, sepX, statusY } = this.layout;
2434
- const lines = sidebarLines(this.boxes, this.selectedId, sidebar.w, sidebar.h);
2435
- const selIdx = this.boxes.findIndex((b) => b.id === this.selectedId);
2436
- const selRow = selIdx >= 0 ? SIDEBAR_HEADER_LINES + selIdx : -1;
2655
+ const { lines, rowOwner, headerRows } = sidebarLines(
2656
+ this.boxes,
2657
+ this.selectedId,
2658
+ sidebar.w,
2659
+ sidebar.h
2660
+ );
2437
2661
  let s = SYNC_BEGIN + "\x1B[0m";
2438
2662
  for (let i = 0; i < lines.length; i++) {
2439
- const style = i === 0 ? SB_HEADER : i === selRow ? SB_SELECTED : SB_BODY;
2663
+ const style = headerRows[i] ? SB_HEADER : rowOwner[i] === this.selectedId ? SB_SELECTED : SB_BODY;
2440
2664
  s += cursorTo2(0, i) + style + lines[i] + SGR_RESET;
2441
2665
  }
2442
- for (let y = 0; y < sidebar.h; y++) s += cursorTo2(sepX, y) + "\u2502";
2666
+ for (let y = 0; y < sidebar.h; y++)
2667
+ s += cursorTo2(sepX, y) + SB_HEADER + (y === 0 ? "\u256E" : "\u2502") + SGR_RESET;
2443
2668
  let status;
2444
- if (this.flashMsg) {
2669
+ if (this.pendingConfirm) {
2670
+ const w = this.layout.cols;
2671
+ const txt = ` Destroy ${this.pendingConfirm.name}? y = confirm \xB7 any other key = cancel `.slice(0, w).padEnd(w);
2672
+ status = `\x1B[7m${txt}\x1B[0m`;
2673
+ } else if (this.flashMsg) {
2445
2674
  const w = this.layout.cols;
2446
2675
  const txt = ` ${this.flashMsg} `.slice(0, w).padEnd(w);
2447
2676
  status = `\x1B[7m${txt}\x1B[0m`;
2448
2677
  } else {
2449
2678
  const stateLabel = this.selectedId === NEW_BOX_ID ? "create" : this.menu ? "menu" : this.session && this.activeMode === "shell" ? "shell" : void 0;
2450
- status = statusLine(this.selectedBox(), this.layout.cols, stateLabel);
2679
+ status = statusLine(
2680
+ this.selectedBox(),
2681
+ this.layout.cols,
2682
+ stateLabel,
2683
+ this.leaderActive ? ADVANCED_HINT_GROUPS : void 0
2684
+ );
2451
2685
  }
2452
2686
  s += cursorTo2(0, statusY) + status;
2453
2687
  this.out.write(s + SYNC_END);
@@ -2474,6 +2708,7 @@ var Compositor = class {
2474
2708
  if (this.pollTimer) clearInterval(this.pollTimer);
2475
2709
  if (this.resizeTimer) clearTimeout(this.resizeTimer);
2476
2710
  if (this.flashTimer) clearTimeout(this.flashTimer);
2711
+ if (this.leaderLingerTimer) clearTimeout(this.leaderLingerTimer);
2477
2712
  this.parser.dispose();
2478
2713
  this.disposeSession();
2479
2714
  this.inp.off("data", this.onData);
@@ -2501,7 +2736,15 @@ function scoped(all, projectRoot, boxes) {
2501
2736
  return sortBoxes(all ? boxes : boxes.filter((b) => b.projectRoot === projectRoot));
2502
2737
  }
2503
2738
  function toSidebar(b) {
2504
- return { id: b.id, name: b.name, state: b.state, claudeActivity: b.claudeActivity };
2739
+ return {
2740
+ id: b.id,
2741
+ name: b.name,
2742
+ state: b.state,
2743
+ claudeActivity: b.claudeActivity,
2744
+ sessionTitle: b.claudeSessionTitle,
2745
+ index: b.projectIndex,
2746
+ project: b.projectRoot
2747
+ };
2505
2748
  }
2506
2749
  var dashboardCommand = new Command7("dashboard").description("Box list + the selected box live Agent session").argument("[box]", "initial box (default: first running box; -p restricts to the cwd project)").option("-p, --project", "only this project's boxes (default: all boxes globally)").action(async (idOrName, opts) => {
2507
2750
  try {
@@ -2675,6 +2918,12 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
2675
2918
  if (box.state === "paused") await unpauseBox(box.id);
2676
2919
  else await startBox(box.id);
2677
2920
  };
2921
+ const pauseBoxAction = async (boxId) => {
2922
+ await pauseBox(boxId);
2923
+ };
2924
+ const stopBoxAction = async (boxId) => {
2925
+ await stopBox(boxId);
2926
+ };
2678
2927
  const destroyBoxAction = async (boxId) => {
2679
2928
  await destroyBox(boxId);
2680
2929
  };
@@ -2688,6 +2937,8 @@ var dashboardCommand = new Command7("dashboard").description("Box list + the sel
2688
2937
  openShell,
2689
2938
  createNewBox,
2690
2939
  resumeBox,
2940
+ pauseBox: pauseBoxAction,
2941
+ stopBox: stopBoxAction,
2691
2942
  destroyBox: destroyBoxAction,
2692
2943
  openVnc,
2693
2944
  openCode,