@multiclawcli/dashboard 0.3.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +66 -2
  2. package/dist/tui/App.d.ts +10 -0
  3. package/dist/tui/commands.d.ts +122 -0
  4. package/dist/tui/components/chrome.d.ts +58 -0
  5. package/dist/tui/components/overlays.d.ts +64 -0
  6. package/dist/tui/components/panels.d.ts +72 -0
  7. package/dist/tui/components/prompt.d.ts +36 -0
  8. package/dist/tui/format.d.ts +65 -0
  9. package/dist/tui/history.d.ts +34 -0
  10. package/dist/tui/index.d.ts +17 -0
  11. package/dist/tui/layout.d.ts +87 -0
  12. package/dist/tui/state.d.ts +292 -0
  13. package/dist/tui/theme.d.ts +66 -0
  14. package/dist/tui.js +2076 -0
  15. package/package.json +20 -28
  16. package/components.json +0 -16
  17. package/index.html +0 -12
  18. package/postcss.config.js +0 -3
  19. package/src/client/App.tsx +0 -96
  20. package/src/client/components/AgentCard.tsx +0 -60
  21. package/src/client/components/LogPanel.tsx +0 -43
  22. package/src/client/components/PipelineBar.tsx +0 -234
  23. package/src/client/components/ui/badge.tsx +0 -28
  24. package/src/client/components/ui/button.tsx +0 -43
  25. package/src/client/components/ui/card.tsx +0 -22
  26. package/src/client/components/ui/scroll-area.tsx +0 -43
  27. package/src/client/components/ui/separator.tsx +0 -23
  28. package/src/client/hooks/useWebSocket.ts +0 -33
  29. package/src/client/index.css +0 -31
  30. package/src/client/lib/utils.ts +0 -6
  31. package/src/client/main.tsx +0 -8
  32. package/src/client/store/useOrchestrationStore.ts +0 -132
  33. package/src/server/DashboardServer.ts +0 -56
  34. package/src/server/WebSocketHub.ts +0 -35
  35. package/src/server/index.ts +0 -2
  36. package/tailwind.config.js +0 -50
  37. package/tsconfig.json +0 -12
  38. package/tsconfig.server.json +0 -9
  39. package/vite.config.ts +0 -25
@@ -0,0 +1,292 @@
1
+ import type { AgentDefinition, AgentOutput, AgentStatus, LeaderMessage, MultiClawConfig, MultiClawEvent, StageInfo, TurnRecord } from "@multiclawcli/core";
2
+ /**
3
+ * Per-agent log buffer cap, in characters.
4
+ *
5
+ * The previous web store appended output forever (`logs: a.logs + chunk`). A
6
+ * chatty agent running for hours would grow that string without bound. The TUI
7
+ * renders a tail window, so only the tail is worth keeping.
8
+ */
9
+ export declare const MAX_LOG_CHARS = 8000;
10
+ export interface AgentView {
11
+ id: string;
12
+ name: string;
13
+ icon?: string;
14
+ model?: string;
15
+ /** Static role, from the config. Always available, even before turn one. */
16
+ description?: string;
17
+ /** Per-turn title assigned by the leader's task plan; absent until it plans. */
18
+ taskTitle?: string;
19
+ status: AgentStatus;
20
+ /** Wall clock at which the agent started, so its timer can tick while it runs. */
21
+ startedAt?: number;
22
+ /** Final duration, present once the agent has finished. */
23
+ duration?: number;
24
+ attempts?: number;
25
+ /** In-flight retry counter, present only while the agent is between attempts. */
26
+ retry?: {
27
+ attempt: number;
28
+ max: number;
29
+ };
30
+ error?: string;
31
+ /** Context-window usage the runtime reported, when it reported any. */
32
+ usage?: AgentOutput["usage"];
33
+ /** USD cost the runtime reported, when the provider priced itself. */
34
+ costUsd?: number;
35
+ /**
36
+ * Session the agent ran under, when the runtime reported one. Carried so
37
+ * `/retry` can ask the agent to continue where it left off instead of
38
+ * re-exploring from scratch.
39
+ */
40
+ sessionId?: string;
41
+ logs: string;
42
+ }
43
+ export type TuiPhase = "idle" | "running" | "complete";
44
+ /**
45
+ * One turn, as the panes need to see it.
46
+ *
47
+ * Everything that describes *a turn* lives here, and everything that describes
48
+ * *the console* lives on {@link TuiState}. The split is what makes history
49
+ * possible: a finished turn is just a `TurnView` kept around, and the panes take
50
+ * a `TurnView` so they can render one without knowing which it is.
51
+ */
52
+ export interface TurnView {
53
+ turn: number;
54
+ requirement?: string;
55
+ phase: TuiPhase;
56
+ /** Wall clock at which the turn started. */
57
+ startedAt?: number;
58
+ /** Wall clock at which the turn finished. */
59
+ endedAt?: number;
60
+ stages: StageInfo[];
61
+ agents: Record<string, AgentView>;
62
+ /**
63
+ * Agents that run *before* the pipeline exists (the leader), so the pipeline
64
+ * bar can label them without guessing. Derived from the config, not from
65
+ * "every agent that is not in a stage" — a configured agent that has not been
66
+ * scheduled yet is not a leader.
67
+ */
68
+ leaderIds: string[];
69
+ focusId?: string;
70
+ /**
71
+ * While true the focus pane follows whichever agent starts next. Cycling the
72
+ * focus by hand clears it, so the user's choice is not yanked away.
73
+ */
74
+ autoFollow: boolean;
75
+ /**
76
+ * How many rows the stream is scrolled back from the tail. Zero means the
77
+ * stream is live. Kept as an offset rather than an absolute index so new
78
+ * output arriving underneath does not shift what the user is reading.
79
+ *
80
+ * Counted in *terminal rows*, not log lines: a log line can wrap over
81
+ * several rows, and the window has to add up to the pane's height.
82
+ */
83
+ scroll: number;
84
+ success?: boolean;
85
+ totalDuration?: number;
86
+ /**
87
+ * Sum of the per-agent costs the runtime reported, in USD. Absent when none
88
+ * was reported — "no data" is not the same as "free", so the console must not
89
+ * print a cost that was never measured.
90
+ */
91
+ totalCostUsd?: number;
92
+ /** Short explanation the leader gave for choosing the current pipeline. */
93
+ reason?: string;
94
+ warning?: string;
95
+ /**
96
+ * Set when the turn ended in words rather than a pipeline: the leader answered
97
+ * or asked a question, so no agent ran. Distinct from `success`, because both
98
+ * a finished pipeline and a one-line answer are successful turns — and the
99
+ * console has to be able to say which one happened.
100
+ */
101
+ reply?: LeaderMessage;
102
+ }
103
+ /** How many finished turns the console keeps around to browse. */
104
+ export declare const MAX_HISTORY = 20;
105
+ export interface TuiState extends TurnView {
106
+ name: string;
107
+ /** Finished turns, oldest first. The live turn is `state` itself. */
108
+ history: TurnView[];
109
+ /**
110
+ * A finished turn opened for reading.
111
+ *
112
+ * Kept as a detached copy so scrolling and tabbing through it cannot disturb
113
+ * the turn that is actually running. While it is set the panes render it, and
114
+ * `focusId` / `scroll` edits land on it rather than on the live turn.
115
+ */
116
+ view?: TurnView;
117
+ /**
118
+ * Whether tool detail is shown in the stream.
119
+ *
120
+ * Console state, not turn state: it belongs here rather than on `TurnView` so
121
+ * `snapshot()` never writes it into a history record, and toggling it applies
122
+ * to whichever turn is on screen.
123
+ */
124
+ verbose: boolean;
125
+ }
126
+ export declare const initialState: TuiState;
127
+ export type TuiAction = {
128
+ type: "turn:start";
129
+ requirement: string;
130
+ agents: Record<string, AgentView>;
131
+ leaderIds: string[];
132
+ startedAt: number;
133
+ } | {
134
+ type: "event";
135
+ event: MultiClawEvent;
136
+ } | {
137
+ type: "focus:cycle";
138
+ delta: number;
139
+ } | {
140
+ type: "focus:set";
141
+ agentId: string;
142
+ }
143
+ /**
144
+ * Scroll the focused stream, in terminal rows. `viewport` is needed to clamp
145
+ * at the tail, and `width` because how many rows a log line spends depends on
146
+ * the pane's width — a line that wraps to three rows scrolls three rows.
147
+ */
148
+ | {
149
+ type: "stream:scroll";
150
+ delta: number;
151
+ viewport: number;
152
+ width: number;
153
+ }
154
+ /** Jump the focused stream to the newest wrapped row containing `query`. */
155
+ | {
156
+ type: "stream:search";
157
+ query: string;
158
+ viewport: number;
159
+ width: number;
160
+ }
161
+ /** Return the focused stream to the tail and re-arm live follow. */
162
+ | {
163
+ type: "stream:latest";
164
+ }
165
+ /** Drop the focused agent's buffered output without touching its status. */
166
+ | {
167
+ type: "stream:clear";
168
+ }
169
+ /** Open a finished turn for reading, by its turn number. */
170
+ | {
171
+ type: "view:open";
172
+ turn: number;
173
+ }
174
+ /** Go back to the live turn. */
175
+ | {
176
+ type: "view:close";
177
+ }
178
+ /** Flip whether tool detail is shown in the stream. */
179
+ | {
180
+ type: "verbose:toggle";
181
+ };
182
+ export declare function stripAnsi(text: string): string;
183
+ /** Append a chunk, keeping only the last {@link MAX_LOG_CHARS} characters. */
184
+ export declare function appendLog(previous: string, chunk: string): string;
185
+ /** Every agent that takes part in a run, in declaration order: leader first. */
186
+ export declare function plannedAgents(config: MultiClawConfig): AgentDefinition[];
187
+ export declare function leaderAgentIds(config: MultiClawConfig): string[];
188
+ /**
189
+ * Build the roster shown before (and between) runs.
190
+ *
191
+ * The orchestrator only reveals the team through `orchestration:start`, which it
192
+ * emits *after* the leader has already run — so a console that learned its
193
+ * agents purely from events would show an empty list until the first turn was
194
+ * well under way. Seeding from the config makes the idle screen show the team
195
+ * you are about to run, and gives every turn a known baseline to reset to.
196
+ */
197
+ export declare function seedAgents(config: MultiClawConfig): Record<string, AgentView>;
198
+ /**
199
+ * Build the console's opening state, optionally seeded with turns from disk.
200
+ *
201
+ * `history` is the second parameter rather than part of `config` because it is
202
+ * *session* state, not configuration: two runs of the same config start from the
203
+ * same config but different pasts.
204
+ *
205
+ * The turn counter continues from the last restored turn (`history`'s last
206
+ * entry) so numbering stays monotonic across sessions. If it restarted at zero,
207
+ * `/replay 3` would be ambiguous the moment a second session began — two turns
208
+ * would share the number three.
209
+ *
210
+ * The parameter is optional and defaults to `[]`, so every existing caller keeps
211
+ * the exact behaviour it had before history existed (turn 0, empty history). This
212
+ * module is re-exported wholesale by `src/tui/index.ts`, so widening the
213
+ * signature any other way would be a breaking change to the public API.
214
+ */
215
+ export declare function createInitialState(config: MultiClawConfig, history?: TurnView[]): TuiState;
216
+ export declare function reduceEvent(state: TuiState, event: MultiClawEvent): TuiState;
217
+ /**
218
+ * The turns worth keeping: those already in `history`, plus the turn on screen
219
+ * once it has ended.
220
+ *
221
+ * The live turn joins the set only when its `phase` is `complete`. While it is
222
+ * still `running` it is half a turn — the requirement is known but the outcome
223
+ * is not — and a record captured mid-flight would be written to disk as a turn
224
+ * that never finishes. The two places that persist turns (the on-disk history
225
+ * and the leader's conversation context) must agree on which turns exist, so
226
+ * they both derive from here rather than each rebuilding the set.
227
+ *
228
+ * `snapshot` is module-private, which is why this helper lives in `state.ts`
229
+ * rather than beside the file-writing code in `history.ts`.
230
+ */
231
+ export declare function recordedTurns(state: TuiState): TurnView[];
232
+ /**
233
+ * The turns behind this one, as dialogue the leader can read back.
234
+ *
235
+ * Core has no notion of a session — an orchestrator is built per turn — so the
236
+ * console is what carries earlier turns into the next run. Without them a bare
237
+ * answer arrives at the leader as a requirement with nothing to answer: turn one
238
+ * asks "which database?", turn two's requirement is the word "Postgres".
239
+ *
240
+ * Derived rather than tracked separately: the recorded turns *are* the finished
241
+ * turns, and a second copy is one more thing that could disagree with what the
242
+ * panes show.
243
+ *
244
+ * Only turns that ended are carried, which makes the call site's timing
245
+ * irrelevant: whether this is read before or after `turn:start` moves the turn
246
+ * on screen into `history`, the set is the same.
247
+ */
248
+ export declare function conversationTurns(state: TuiState): TurnRecord[];
249
+ export declare function tuiReducer(state: TuiState, action: TuiAction): TuiState;
250
+ /**
251
+ * Roll a stage's member statuses up into one status for the pipeline bar.
252
+ *
253
+ * Ordering is by severity: a failure anywhere outranks a run in flight, which
254
+ * outranks a uniform outcome. `cancelled` is a settled outcome like `success`
255
+ * and `skipped`, so a stage of cancelled members reads as `cancelled` rather
256
+ * than falling through to `pending` — a stopped stage is not one that has yet
257
+ * to start, and reporting it as `pending` would be the same lie B2-6 fixed one
258
+ * level down.
259
+ */
260
+ export declare function stageStatus(stage: StageInfo, agents: Record<string, AgentView>): AgentStatus;
261
+ /** How many agents have finished, out of how many the turn will run. */
262
+ export declare function progress(turn: TurnView): {
263
+ done: number;
264
+ total: number;
265
+ };
266
+ /**
267
+ * An agent's time: the live clock while it runs, the recorded duration once it
268
+ * has stopped. Returning the *same* number after completion is what stops the
269
+ * column from jittering when a stage ends.
270
+ */
271
+ export declare function agentElapsed(agent: AgentView, now: number): number | undefined;
272
+ /** The turn's time, live while running and frozen once the orchestrator reports back. */
273
+ export declare function turnElapsed(turn: TurnView, now: number): number | undefined;
274
+ export interface LogWindow {
275
+ /** The wrapped rows that fit, oldest first. */
276
+ rows: string[];
277
+ /** How many rows sit below the window. Zero means the stream is live. */
278
+ scrolledBack: number;
279
+ following: boolean;
280
+ }
281
+ /**
282
+ * The slice of the focused agent's output that fits the pane.
283
+ *
284
+ * Wrapped first, then windowed, and both steps are needed: the pane is a fixed
285
+ * number of *rows*, while the buffer is a string of *lines*, and one line can
286
+ * spend several rows. Slicing lines would hand the pane more rows than it has
287
+ * and push the newest output out of sight.
288
+ *
289
+ * Clamping here as well as in the reducer keeps the window correct even when
290
+ * the pane is resized between two scroll actions.
291
+ */
292
+ export declare function visibleLog(turn: TurnView, viewport: number, width: number): LogWindow;
@@ -0,0 +1,66 @@
1
+ import type { AgentStatus } from "@multiclawcli/core";
2
+ /**
3
+ * Semantic colours, in one place.
4
+ *
5
+ * Only the sixteen named ANSI colours are used. Every terminal theme remaps
6
+ * those to something legible against its own background; a hard-coded hex would
7
+ * look right on the author's machine and wrong on everyone else's.
8
+ */
9
+ export declare const COLOR: {
10
+ readonly accent: "cyan";
11
+ readonly ok: "green";
12
+ readonly warn: "yellow";
13
+ readonly error: "red";
14
+ readonly muted: "gray";
15
+ };
16
+ export interface StatusStyle {
17
+ glyph: string;
18
+ color: string;
19
+ dim?: boolean;
20
+ /** Word used in prose contexts (stream header, help). */
21
+ label: string;
22
+ }
23
+ /**
24
+ * Status is always a glyph *and* a colour.
25
+ *
26
+ * Colour alone would be invisible to a colour-blind user and would vanish the
27
+ * moment the output is copied into a log or an issue.
28
+ */
29
+ export declare const STATUS: Record<AgentStatus, StatusStyle>;
30
+ /** Braille spinner frames, cycled in place of the static running glyph. */
31
+ export declare const SPINNER: readonly ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
32
+ export declare const GLYPH: {
33
+ /** Marks the console itself in the header and the welcome screen. */
34
+ readonly brand: "◆";
35
+ /** The leader runs before the pipeline exists, so it gets its own mark. */
36
+ readonly leader: "◆";
37
+ readonly agent: "◇";
38
+ /** Points at the focused row in the team list. */
39
+ readonly caret: "›";
40
+ /** Pipeline flow between stages. */
41
+ readonly connector: "──▸";
42
+ /** The stream is tailing; new output appears as it arrives. */
43
+ readonly live: "▼";
44
+ /** The stream is parked while the user reads back. */
45
+ readonly paused: "⏸";
46
+ /**
47
+ * A follow-up waiting for the team to finish.
48
+ *
49
+ * Latin-1 on purpose: it renders as exactly one cell in every terminal, which
50
+ * a pictograph like ⏳ does not.
51
+ */
52
+ readonly queued: "»";
53
+ /** The leader is asking for a decision rather than reporting one. */
54
+ readonly ask: "?";
55
+ /** A mid-turn warning that rides alongside the outcome rather than hiding it. */
56
+ readonly warn: "⚠";
57
+ /**
58
+ * A pasted line break, drawn in the one-line prompt.
59
+ *
60
+ * MUST be exactly one column wide. `Prompt` maps `\n` to this glyph 1:1 — one
61
+ * character in, one character out — so its caret index stays valid; a glyph
62
+ * that rendered as two columns would push the caret off by one for every
63
+ * newline before it. If a terminal disagrees, this single glyph is the fix.
64
+ */
65
+ readonly newline: "↵";
66
+ };