@multiclawcli/dashboard 0.11.0 → 0.12.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.
@@ -1,5 +1,4 @@
1
1
  import type { SessionMeta } from "../sessions";
2
- import { type TurnView } from "../state";
3
2
  interface HelpGroup {
4
3
  title: string;
5
4
  rows: Array<[keys: string, description: string]>;
@@ -16,8 +15,8 @@ interface HelpGroup {
16
15
  * enumeration belongs.
17
16
  */
18
17
  export declare const HELP_GROUPS: readonly HelpGroup[];
19
- /** How many turns fit in a body of `maxRows`. Never fewer than one. */
20
- export declare function historyCapacity(maxRows: number): number;
18
+ /** How many items fit in a body of `maxRows`. Never fewer than one. */
19
+ export declare function overlayCapacity(maxRows: number): number;
21
20
  /**
22
21
  * The first item to draw, given the one the cursor is on.
23
22
  *
@@ -44,32 +43,12 @@ export declare function HelpOverlay({ width, maxRows }: {
44
43
  width: number;
45
44
  maxRows: number;
46
45
  }): import("react").JSX.Element;
47
- /**
48
- * The finished turns, newest first.
49
- *
50
- * Two rows per turn on purpose: the requirement is the thing a reader is
51
- * scanning for, and folding it onto the same line as the outcome would truncate
52
- * exactly the part that identifies the turn.
53
- *
54
- * Only turns a newer turn has superseded are listed — the one being run is
55
- * already on screen, so listing it would offer to show the user what they are
56
- * looking at.
57
- */
58
- export declare function HistoryOverlay({ turns, selected, width, maxRows, }: {
59
- turns: TurnView[];
60
- selected: number;
61
- width: number;
62
- /** Rows available in the body. The list is windowed against this. */
63
- maxRows: number;
64
- }): import("react").JSX.Element;
65
46
  /**
66
47
  * The session picker: every session in the workspace, newest activity first.
67
48
  *
68
- * The level above {@link HistoryOverlay}. Where the history list browses the
69
- * turns *within* a session, this switches between whole sessions — the thing a
70
- * restart, a `/new`, or opening the console again all create. The one the
71
- * console is attached to is marked, because "which am I on" is the first
72
- * question the list has to answer.
49
+ * Switches between whole sessions — the thing a restart, a `/new`, or opening
50
+ * the console again all create. The one the console is attached to is marked,
51
+ * because "which am I on" is the first question the list has to answer.
73
52
  */
74
53
  export declare function SessionsOverlay({ sessions, selected, currentId, width, maxRows, now, }: {
75
54
  sessions: SessionMeta[];
package/dist/tui.js CHANGED
@@ -66,11 +66,6 @@ var COMMANDS = [
66
66
  completes: "agent"
67
67
  },
68
68
  { name: "clear", description: "Clear the focused agent's log", availability: "always" },
69
- {
70
- name: "history",
71
- description: "Browse finished turns, or continue one",
72
- availability: "always"
73
- },
74
69
  {
75
70
  name: "sessions",
76
71
  description: "Switch between, or delete, saved sessions",
@@ -803,14 +798,14 @@ function reduceEvent(state, event) {
803
798
  case "stage:complete": {
804
799
  const stage = state.stages.find((stage2) => stage2.stageIndex === event.payload.stageIndex);
805
800
  if (!stage || stage.outcome !== void 0) return state;
806
- const outcome2 = stageStatus(stage, state.agents);
801
+ const outcome = stageStatus(stage, state.agents);
807
802
  const memberStatuses = Object.fromEntries(
808
803
  stage.agents.map((agent) => [agent.id, state.agents[agent.id]?.status ?? "pending"])
809
804
  );
810
805
  return {
811
806
  ...state,
812
807
  stages: state.stages.map(
813
- (entry) => entry === stage ? { ...entry, outcome: outcome2, memberStatuses } : entry
808
+ (entry) => entry === stage ? { ...entry, outcome, memberStatuses } : entry
814
809
  )
815
810
  };
816
811
  }
@@ -1503,10 +1498,10 @@ var HELP_GROUPS = [
1503
1498
  }
1504
1499
  ];
1505
1500
  var KEY_COLUMN = 16;
1506
- var TURN_ROWS = 3;
1507
- var HISTORY_CHROME_ROWS = 4;
1508
- function historyCapacity(maxRows) {
1509
- return Math.max(1, Math.floor((maxRows - HISTORY_CHROME_ROWS) / TURN_ROWS));
1501
+ var ITEM_ROWS = 3;
1502
+ var OVERLAY_CHROME_ROWS = 4;
1503
+ function overlayCapacity(maxRows) {
1504
+ return Math.max(1, Math.floor((maxRows - OVERLAY_CHROME_ROWS) / ITEM_ROWS));
1510
1505
  }
1511
1506
  function listWindowStart(selected, total, capacity) {
1512
1507
  const maxStart = Math.max(0, total - capacity);
@@ -1532,81 +1527,6 @@ function HelpOverlay({ width, maxRows }) {
1532
1527
  /* @__PURE__ */ jsx2(Box2, { marginTop: gap, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "esc or ? to close" }) })
1533
1528
  ] });
1534
1529
  }
1535
- function outcome(turn) {
1536
- if (turn.reply) {
1537
- return turn.reply.mode === "ask" ? { text: `${GLYPH.ask} needs input`, color: COLOR.warn } : { text: `${STATUS.success.glyph} answered`, color: COLOR.ok };
1538
- }
1539
- if (turn.interrupted || turn.phase !== "complete") {
1540
- return { text: "interrupted", color: COLOR.warn };
1541
- }
1542
- if (turn.acceptance) {
1543
- switch (turn.acceptance.status) {
1544
- case "approved":
1545
- return { text: `${STATUS.success.glyph} approved`, color: COLOR.ok };
1546
- case "changes_required":
1547
- return { text: `${GLYPH.warn} changes required`, color: COLOR.warn };
1548
- case "failed":
1549
- return { text: `${STATUS.failed.glyph} acceptance failed`, color: COLOR.error };
1550
- case "cancelled":
1551
- return { text: `${STATUS.cancelled.glyph} acceptance cancelled`, color: COLOR.warn };
1552
- }
1553
- }
1554
- if (turn.cancelled) return { text: `${STATUS.cancelled.glyph} cancelled`, color: COLOR.warn };
1555
- return turn.success ? { text: `${STATUS.success.glyph} done`, color: COLOR.ok } : { text: `${STATUS.failed.glyph} failed`, color: COLOR.error };
1556
- }
1557
- function HistoryOverlay({
1558
- turns,
1559
- selected,
1560
- width,
1561
- maxRows
1562
- }) {
1563
- const capacity = historyCapacity(maxRows);
1564
- const start = listWindowStart(selected, turns.length, capacity);
1565
- const visible = turns.slice(start, start + capacity);
1566
- const range = turns.length > capacity ? ` \xB7 showing ${start + 1}\u2013${start + visible.length} of ${turns.length}` : "";
1567
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexShrink: 0, paddingLeft: 2, paddingTop: 1, width, children: [
1568
- /* @__PURE__ */ jsxs2(Box2, { justifyContent: "space-between", children: [
1569
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "HISTORY" }),
1570
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1571
- turns.length,
1572
- " earlier"
1573
- ] })
1574
- ] }),
1575
- turns.length === 0 ? /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "No earlier turns yet." }) }) : /* @__PURE__ */ jsx2(Box2, { flexDirection: "column", marginTop: 1, children: visible.map((turn, offset) => {
1576
- const index = start + offset;
1577
- const focused = index === selected;
1578
- const state = outcome(turn);
1579
- const { done, total } = progress(turn);
1580
- const time = turn.totalDuration === void 0 ? "" : ` \xB7 ${formatDuration(turn.totalDuration)}`;
1581
- return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", marginTop: index === 0 ? 0 : 1, children: [
1582
- /* @__PURE__ */ jsxs2(Box2, { children: [
1583
- /* @__PURE__ */ jsx2(Text2, { color: focused ? COLOR.accent : void 0, children: focused ? `${GLYPH.caret} ` : " " }),
1584
- /* @__PURE__ */ jsxs2(Text2, { bold: focused, color: focused ? COLOR.accent : void 0, children: [
1585
- "turn ",
1586
- turn.turn
1587
- ] }),
1588
- /* @__PURE__ */ jsxs2(Text2, { color: state.color, children: [
1589
- " \xB7 ",
1590
- state.text
1591
- ] }),
1592
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
1593
- time,
1594
- " \xB7 ",
1595
- done,
1596
- "/",
1597
- total,
1598
- " agents"
1599
- ] })
1600
- ] }),
1601
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, wrap: "truncate-end", children: [
1602
- " ",
1603
- truncate(turn.requirement ?? "(no requirement)", Math.max(8, width - 6))
1604
- ] })
1605
- ] }, turn.turn);
1606
- }) }),
1607
- /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: `\u2191\u2193 select \xB7 enter open \xB7 c continue \xB7 esc close${range}` }) })
1608
- ] });
1609
- }
1610
1530
  function SessionsOverlay({
1611
1531
  sessions,
1612
1532
  selected,
@@ -1615,7 +1535,7 @@ function SessionsOverlay({
1615
1535
  maxRows,
1616
1536
  now
1617
1537
  }) {
1618
- const capacity = historyCapacity(maxRows);
1538
+ const capacity = overlayCapacity(maxRows);
1619
1539
  const start = listWindowStart(selected, sessions.length, capacity);
1620
1540
  const visible = sessions.slice(start, start + capacity);
1621
1541
  const range = sessions.length > capacity ? ` \xB7 showing ${start + 1}\u2013${start + visible.length} of ${sessions.length}` : "";
@@ -2483,10 +2403,10 @@ function StatusLine({
2483
2403
  }
2484
2404
  if (state.phase === "complete") {
2485
2405
  const time = state.totalDuration === void 0 ? "" : ` ${formatDuration(state.totalDuration)}`;
2486
- const outcome2 = state.acceptance ? `${state.success ? STATUS.success.glyph : STATUS.failed.glyph} ${state.acceptance.status === "approved" ? "delivery approved" : state.acceptance.status === "changes_required" ? "changes required" : "acceptance failed"}${time}` : state.success ? `${STATUS.success.glyph} done${time}` : `${STATUS.failed.glyph} finished with failures${time}`;
2406
+ const outcome = state.acceptance ? `${state.success ? STATUS.success.glyph : STATUS.failed.glyph} ${state.acceptance.status === "approved" ? "delivery approved" : state.acceptance.status === "changes_required" ? "changes required" : "acceptance failed"}${time}` : state.success ? `${STATUS.success.glyph} done${time}` : `${STATUS.failed.glyph} finished with failures${time}`;
2487
2407
  const cost = state.totalCostUsd === void 0 ? "" : ` \xB7 ${formatCost(state.totalCostUsd)}`;
2488
2408
  return /* @__PURE__ */ jsxs6(Text6, { color: state.success ? COLOR.ok : COLOR.error, wrap: "truncate-end", children: [
2489
- outcome2,
2409
+ outcome,
2490
2410
  cost,
2491
2411
  state.warning ? ` \xB7 ${GLYPH.warn} ${state.warning}` : ""
2492
2412
  ] });
@@ -2554,8 +2474,6 @@ function App({
2554
2474
  launchNoticeRef.current ?? restored.notice
2555
2475
  );
2556
2476
  const [helpOpen, setHelpOpen] = useState(false);
2557
- const [historyOpen, setHistoryOpen] = useState(false);
2558
- const [historyCursor, setHistoryCursor] = useState(0);
2559
2477
  const [sessionsOpen, setSessionsOpen] = useState(false);
2560
2478
  const [sessionCursor, setSessionCursor] = useState(0);
2561
2479
  const [sessionList, setSessionList] = useState([]);
@@ -2567,6 +2485,14 @@ function App({
2567
2485
  const sessionsRef = useRef(null);
2568
2486
  if (!sessionsRef.current) sessionsRef.current = providedSessions ?? new AgentSessionManager();
2569
2487
  const continuationRef = useRef(null);
2488
+ useEffect(() => {
2489
+ if (initialSession?.kind !== "resume" || !("id" in initialSession)) return;
2490
+ const history = restoredRef.current?.history ?? [];
2491
+ const last = history[history.length - 1];
2492
+ if (last) dispatch({ type: "view:open", turn: last.turn });
2493
+ const resumable = [...history].reverse().find((turn) => Object.keys(turnSessions(turn)).length > 0);
2494
+ continuationRef.current = resumable ? { turn: resumable.turn, sessions: turnSessions(resumable) } : null;
2495
+ }, [initialSession]);
2570
2496
  const activeTurnRef = useRef(null);
2571
2497
  const reporterErrorRef = useRef(null);
2572
2498
  const closingRef = useRef(false);
@@ -2876,6 +2802,10 @@ function App({
2876
2802
  leaderIds: leaderAgentIds(config),
2877
2803
  history: restored2.history
2878
2804
  });
2805
+ const lastTurn = restored2.history[restored2.history.length - 1];
2806
+ if (lastTurn) {
2807
+ dispatch({ type: "view:open", turn: lastTurn.turn });
2808
+ }
2879
2809
  setQueue(restored2.queue);
2880
2810
  queuePausedRef.current = restored2.queue.length > 0;
2881
2811
  setQueuePaused(restored2.queue.length > 0);
@@ -2960,8 +2890,6 @@ function App({
2960
2890
  () => query === void 0 ? [] : matchCommands(query, running),
2961
2891
  [query, running]
2962
2892
  );
2963
- const historyTurns = useMemo(() => [...state.history].reverse(), [state.history]);
2964
- const historyIndex = historyTurns.length === 0 ? 0 : Math.min(historyCursor, historyTurns.length - 1);
2965
2893
  const shown = state.view ?? state;
2966
2894
  const arg = argQuery(line.text);
2967
2895
  const argRows = arg ? completeAgents(arg.partial, Object.keys(shown.agents)).map((id) => ({
@@ -2976,8 +2904,8 @@ function App({
2976
2904
  const paletteTotal = allRows.length;
2977
2905
  const menuIndex = menuCursor.query === paletteKey ? menuCursor.index : 0;
2978
2906
  const selected = paletteTotal === 0 ? 0 : Math.min(menuIndex, paletteTotal - 1);
2979
- const menuOpen = !helpOpen && !historyOpen && !sessionsOpen && query !== void 0 && matches.length > 0;
2980
- const argOpen = !helpOpen && !historyOpen && !sessionsOpen && arg !== void 0 && argRows.length > 0;
2907
+ const menuOpen = !helpOpen && !sessionsOpen && query !== void 0 && matches.length > 0;
2908
+ const argOpen = !helpOpen && !sessionsOpen && arg !== void 0 && argRows.length > 0;
2981
2909
  const paletteOpen = menuOpen || argOpen;
2982
2910
  const front = interactions[0];
2983
2911
  const menu = front?.kind === "permission" && permissionMenu.id === front.shortId ? permissionMenu : { id: front?.shortId ?? "", cursor: 0, released: false };
@@ -2998,7 +2926,7 @@ function App({
2998
2926
  columns,
2999
2927
  rows,
3000
2928
  turn: state.turn,
3001
- overlay: helpOpen || historyOpen || sessionsOpen,
2929
+ overlay: helpOpen || sessionsOpen,
3002
2930
  menuRows: paletteOpen ? paletteTotal : 0,
3003
2931
  queueRows: queue.length,
3004
2932
  interactionRows: interactionRows(interactions, Math.max(MIN_COLUMNS, columns)),
@@ -3242,15 +3170,6 @@ function App({
3242
3170
  dispatch({ type: "stream:clear" });
3243
3171
  setNotice(`cleared ${shown2.focusId}`);
3244
3172
  return;
3245
- case "history":
3246
- if (state.history.length === 0) {
3247
- setNotice("no earlier turns yet");
3248
- return;
3249
- }
3250
- setNotice(void 0);
3251
- setHistoryCursor(0);
3252
- setHistoryOpen(true);
3253
- return;
3254
3173
  case "sessions": {
3255
3174
  const list = listSessions(config.workDir);
3256
3175
  setSessionList(list);
@@ -3456,8 +3375,6 @@ function App({
3456
3375
  if (key.ctrl && char === "c") {
3457
3376
  if (helpOpen) {
3458
3377
  setHelpOpen(false);
3459
- } else if (historyOpen) {
3460
- setHistoryOpen(false);
3461
3378
  } else if (sessionsOpen) {
3462
3379
  setSessionsOpen(false);
3463
3380
  } else if (abortRef.current) {
@@ -3477,43 +3394,6 @@ function App({
3477
3394
  if (key.escape || char === "?") setHelpOpen(false);
3478
3395
  return;
3479
3396
  }
3480
- if (historyOpen) {
3481
- if (key.escape) {
3482
- setHistoryOpen(false);
3483
- return;
3484
- }
3485
- if (historyTurns.length === 0) return;
3486
- if (key.upArrow) {
3487
- setHistoryCursor((historyIndex - 1 + historyTurns.length) % historyTurns.length);
3488
- return;
3489
- }
3490
- if (key.downArrow) {
3491
- setHistoryCursor((historyIndex + 1) % historyTurns.length);
3492
- return;
3493
- }
3494
- if (key.return) {
3495
- dispatch({ type: "view:open", turn: historyTurns[historyIndex].turn });
3496
- setHistoryOpen(false);
3497
- return;
3498
- }
3499
- if (char === "c") {
3500
- const target = historyTurns[historyIndex];
3501
- const sessions = turnSessions(target);
3502
- const count = Object.keys(sessions).length;
3503
- if (count === 0) {
3504
- setNotice(`turn ${target.turn} has no agent sessions to continue`);
3505
- return;
3506
- }
3507
- continuationRef.current = { turn: target.turn, sessions };
3508
- dispatch({ type: "view:open", turn: target.turn });
3509
- setHistoryOpen(false);
3510
- setNotice(
3511
- `continuing turn ${target.turn} \u2014 ${count} ${count === 1 ? "session" : "sessions"} \xB7 type your next instruction`
3512
- );
3513
- return;
3514
- }
3515
- return;
3516
- }
3517
3397
  if (sessionsOpen) {
3518
3398
  if (key.escape) {
3519
3399
  setSessionsOpen(false);
@@ -3804,7 +3684,7 @@ function App({
3804
3684
  const handlerRef = useRef(handleKey);
3805
3685
  handlerRef.current = handleKey;
3806
3686
  useInput((char, key) => handlerRef.current(char, key));
3807
- const keys = helpOpen ? "esc close" : historyOpen ? "\u2191\u2193 select \xB7 enter open \xB7 esc close" : sessionsOpen ? "\u2191\u2193 select \xB7 enter open \xB7 d delete \xB7 esc close" : argOpen ? `\u2191\u2193 select \xB7 tab complete \xB7 enter ${arg?.command.name} \xB7 esc close` : menuOpen ? "\u2191\u2193 select \xB7 tab complete \xB7 enter run \xB7 esc close" : running ? checkpointResolveRef.current ? `/continue \xB7 /modify <instruction> \xB7 /cancel` : interactions.length > 0 ? layout.interactionRows === 0 ? (
3687
+ const keys = helpOpen ? "esc close" : sessionsOpen ? "\u2191\u2193 select \xB7 enter open \xB7 d delete \xB7 esc close" : argOpen ? `\u2191\u2193 select \xB7 tab complete \xB7 enter ${arg?.command.name} \xB7 esc close` : menuOpen ? "\u2191\u2193 select \xB7 tab complete \xB7 enter run \xB7 esc close" : running ? checkpointResolveRef.current ? `/continue \xB7 /modify <instruction> \xB7 /cancel` : interactions.length > 0 ? layout.interactionRows === 0 ? (
3808
3688
  // Too short to dock the bar, so the commands are the only way in.
3809
3689
  "decide with /approve 1 \xB7 /deny \xB7 /respond"
3810
3690
  ) : interactions[0].kind === "question" ? "tab field \xB7 \u2191\u2193 pick \xB7 enter next \xB7 /dismiss" : menu.released ? "/approve 1 and /deny still reach it" : "\u2191\u2193 or 1-9 choose \xB7 enter confirm \xB7 esc to type" : "enter queues \xB7 ctrl+c cancel" : state.view ? "esc live \xB7 tab agent \xB7 ? help" : state.reply ? "enter sends \xB7 / commands \xB7 ? help" : "tab switch \xB7 / commands \xB7 ? help";
@@ -3834,15 +3714,7 @@ function App({
3834
3714
  flexShrink: 0,
3835
3715
  flexDirection: "column",
3836
3716
  overflow: "hidden",
3837
- children: helpOpen ? /* @__PURE__ */ jsx6(HelpOverlay, { width: layout.columns, maxRows: layout.bodyHeight }) : historyOpen ? /* @__PURE__ */ jsx6(
3838
- HistoryOverlay,
3839
- {
3840
- turns: historyTurns,
3841
- selected: historyIndex,
3842
- width: layout.columns,
3843
- maxRows: layout.bodyHeight
3844
- }
3845
- ) : sessionsOpen ? /* @__PURE__ */ jsx6(
3717
+ children: helpOpen ? /* @__PURE__ */ jsx6(HelpOverlay, { width: layout.columns, maxRows: layout.bodyHeight }) : sessionsOpen ? /* @__PURE__ */ jsx6(
3846
3718
  SessionsOverlay,
3847
3719
  {
3848
3720
  sessions: sessionList,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@multiclawcli/dashboard",
3
- "version": "0.11.0",
3
+ "version": "0.12.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./tui": {
@@ -25,7 +25,7 @@
25
25
  "access": "public"
26
26
  },
27
27
  "dependencies": {
28
- "@multiclawcli/core": "0.11.0",
28
+ "@multiclawcli/core": "0.12.0",
29
29
  "ink": "^8.0.0",
30
30
  "md4x": "^0.0.30",
31
31
  "react": "^19.3.1",