@multiclawcli/dashboard 0.4.0 → 0.5.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.
- package/dist/tui/App.d.ts +10 -0
- package/dist/tui/commands.d.ts +122 -0
- package/dist/tui/components/chrome.d.ts +58 -0
- package/dist/tui/components/overlays.d.ts +64 -0
- package/dist/tui/components/panels.d.ts +72 -0
- package/dist/tui/components/prompt.d.ts +36 -0
- package/dist/tui/format.d.ts +65 -0
- package/dist/tui/history.d.ts +34 -0
- package/dist/tui/index.d.ts +17 -0
- package/dist/tui/layout.d.ts +87 -0
- package/dist/tui/state.d.ts +292 -0
- package/dist/tui/theme.d.ts +66 -0
- package/dist/tui.js +642 -114
- package/package.json +9 -8
- package/dist/tui.d.ts +0 -299
|
@@ -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
|
+
};
|