@multiclawcli/dashboard 0.12.0 → 0.13.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.
package/README.md CHANGED
@@ -30,12 +30,13 @@ rules are therefore testable without a terminal.
30
30
  | Type | Holds |
31
31
  |------|-------|
32
32
  | `TurnView` | Everything that describes *a turn*: agents, stages, focus, scroll, phase, reply |
33
- | `TuiState extends TurnView` | The above, for the live turn, plus `name`, `history` and `view` |
33
+ | `TuiState extends TurnView` | The above, for the live turn, plus `name` and `history` |
34
34
 
35
35
  A finished turn is just a `TurnView` kept in `history`, and every pane takes a `TurnView` — so a past
36
- turn renders through exactly the same components as the live one. `view` is a *detached* copy of the
37
- turn being read: `mapShown` routes `tab`, scroll and `/clear` to `view` when one is open, so browsing
38
- cannot move the focus out from under a run in progress, and cannot rewrite history either.
36
+ turn renders through exactly the same components as the live one. The stream panel reads *past* the
37
+ single turn: `visibleTimeline` concatenates the focused agent's logs from `history` and the live turn,
38
+ with a divider row per turn, and `stream:scroll` / `stream:search` clamp against that whole timeline.
39
+ History is never edited in place — `stream:clear` only empties the live turn's buffer.
39
40
 
40
41
  ## Source layout
41
42
 
@@ -25,12 +25,20 @@ export interface CommandSpec {
25
25
  completes?: "agent";
26
26
  }
27
27
  export declare const COMMANDS: readonly CommandSpec[];
28
+ export interface CommandContext {
29
+ history: boolean;
30
+ retry: boolean;
31
+ queue: number;
32
+ paused: boolean;
33
+ awaitingAnswer: boolean;
34
+ checkpoint: boolean;
35
+ }
28
36
  export interface ParsedCommand {
29
37
  name: string;
30
38
  args: string[];
31
39
  }
32
40
  /** Commands that make sense in the current phase. */
33
- export declare function availableCommands(running: boolean): CommandSpec[];
41
+ export declare function availableCommands(running: boolean, context?: CommandContext): CommandSpec[];
34
42
  /**
35
43
  * Whether a command may run in the current phase.
36
44
  *
@@ -39,26 +47,26 @@ export declare function availableCommands(running: boolean): CommandSpec[];
39
47
  * entirely. An unknown name is not "unavailable" — it is let through so the
40
48
  * caller's `default:` branch can report it.
41
49
  */
42
- export declare function canRun(name: string, running: boolean): boolean;
50
+ export declare function canRun(name: string, running: boolean, context?: CommandContext): boolean;
43
51
  /**
44
52
  * Why a command may not run in the current phase, or `undefined` when it may.
45
53
  *
46
54
  * The mirror of {@link canRun}, and the message names the phase the command
47
55
  * *wants* rather than just saying it is unavailable: the two directions mean
48
56
  * different things to the user. A mid-run `/replay` has to read as "wait for the
49
- * turn to finish", while an idle `/cancel` has to read as "this only applies to
57
+ * turn to finish", while an idle `/steer` has to read as "this only applies to
50
58
  * a running turn" — one string for both would leave the user without the fact
51
59
  * they need to act on. An unknown name has no reason; it is let through so the
52
60
  * caller's `default:` branch can report it.
53
61
  */
54
- export declare function unavailableReason(name: string, running: boolean): string | undefined;
62
+ export declare function unavailableReason(name: string, running: boolean, context?: CommandContext): string | undefined;
55
63
  /**
56
64
  * Commands matching what has been typed after the slash.
57
65
  *
58
- * Name prefixes outrank description hits: someone typing `/ag` wants `/agents`,
66
+ * Name prefixes outrank description hits: someone typing `/fo` wants `/focus`,
59
67
  * not whichever command happens to mention "agent" in its blurb.
60
68
  */
61
- export declare function matchCommands(query: string, running: boolean): CommandSpec[];
69
+ export declare function matchCommands(query: string, running: boolean, context?: CommandContext): CommandSpec[];
62
70
  /** True once the line has committed to command mode. */
63
71
  export declare function isCommandLine(text: string): boolean;
64
72
  /**
@@ -12,28 +12,24 @@ import { type FormField, type FormState } from "../interaction";
12
12
  * Everything here is one row per fact and clipped by the caller, which is why
13
13
  * the row count in `interaction.ts` only has to be an upper bound.
14
14
  */
15
- export declare function PermissionBar({ request, others, cursor, released, width, }: {
15
+ export declare function PermissionBar({ request, others, cursor, released, width, maxRows, }: {
16
16
  request: RuntimePermissionRequest;
17
17
  /** Requests behind this one, shown so "2 waiting" is not a bare number. */
18
18
  others: readonly RuntimePermissionRequest[];
19
19
  /** Highlighted option, or `undefined` when nothing has been picked yet. */
20
20
  cursor: number | undefined;
21
- /** The user asked for the keyboard back; only the commands still reach it. */
21
+ /** The user asked for the keyboard back; escape returns to its options. */
22
22
  released: boolean;
23
23
  width: number;
24
+ maxRows?: number;
24
25
  }): import("react").JSX.Element;
25
26
  /**
26
27
  * The question, as a form.
27
28
  *
28
- * The old path asked for `/respond q1 {"database":"postgres"}` — a hand-written
29
- * JSON object matching a schema the user could only read in the same terminal
30
- * that was now occupied by what they were typing. Here every field is a row, the
31
- * focused row is the one the keystrokes go to, and choices are picked rather
32
- * than spelled. The prompt below still does the typing: it already owns the
33
- * caret, the wrapping and the paste handling, so a text field reuses it instead
34
- * of growing a second editor.
29
+ * Fields are edited in the prompt, while choice fields use the arrow keys.
30
+ * Submit, skip and cancel are explicit actions after the last field.
35
31
  */
36
- export declare function QuestionForm({ request, others, fields, form, draft, width, }: {
32
+ export declare function QuestionForm({ request, others, fields, form, draft, width, maxRows, }: {
37
33
  request: RuntimeQuestionRequest;
38
34
  others: readonly RuntimeQuestionRequest[];
39
35
  fields: FormField[];
@@ -41,4 +37,5 @@ export declare function QuestionForm({ request, others, fields, form, draft, wid
41
37
  /** Text in the prompt, which is the focused text field's editor. */
42
38
  draft: string;
43
39
  width: number;
40
+ maxRows?: number;
44
41
  }): import("react").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import type { LeaderMessage } from "@multiclawcli/core";
2
- import { type TurnView } from "../state";
2
+ import { type TuiState, type TurnView } from "../state";
3
3
  /** The roster: one row per agent, with the focused one called out by a caret. */
4
4
  export declare function TeamPanel({ turn, width, now, spinner, }: {
5
5
  turn: TurnView;
@@ -23,8 +23,8 @@ export declare function Divider(): import("react").JSX.Element;
23
23
  * or parked. Output itself is rendered at normal brightness — the chrome is what
24
24
  * should recede, not the agent's work.
25
25
  */
26
- export declare function StreamPanel({ turn, height, width, now, spinner, }: {
27
- turn: TurnView;
26
+ export declare function StreamPanel({ state, height, width, now, spinner, }: {
27
+ state: TuiState;
28
28
  height: number;
29
29
  width: number;
30
30
  now: number;
@@ -2,19 +2,8 @@
2
2
  * The two things an agent can stop and ask for, modelled as something the user
3
3
  * can *point at* instead of something they have to spell out.
4
4
  *
5
- * Both used to be answered by command — `/approve p4 1`, `/respond q1
6
- * {"database":"postgres"}` — which put three transformations between reading a
7
- * question and answering it: remember an internal alias, map a number back onto
8
- * a list rendered somewhere else, and hand-write JSON that happens to match a
9
- * schema. None of those are the decision. So the rendered question becomes the
10
- * thing that is operated on: the option you are looking at is the option you
11
- * commit, and the field you are editing is the field that receives the
12
- * keystroke.
13
- *
14
- * Pure on purpose. The keyboard rules are the part of this easiest to get
15
- * subtly wrong — a bare key that both selects and types, a required field that
16
- * silently submits empty — so they live here, where they can be tested without
17
- * rendering a terminal.
5
+ * The docked controls keep permission decisions and structured answers tied to
6
+ * their requesting agent while leaving the execution stream visible.
18
7
  */
19
8
  import type { RuntimePermissionRequest, RuntimeQuestionRequest } from "@multiclawcli/core";
20
9
  export type PermissionOptionKind = RuntimePermissionRequest["options"][number]["kind"];
@@ -72,8 +72,6 @@ export interface LayoutInput {
72
72
  promptRows?: number;
73
73
  /** Whether a command result is waiting to be shown. */
74
74
  notice: boolean;
75
- /** A past turn is open for reading, which costs a banner row. */
76
- viewing?: boolean;
77
75
  /** Whether there is a leader or any stage to draw. */
78
76
  hasRail: boolean;
79
77
  /**
@@ -131,14 +131,6 @@ export interface TuiState extends TurnView {
131
131
  name: string;
132
132
  /** Finished turns, oldest first. The live turn is `state` itself. */
133
133
  history: TurnView[];
134
- /**
135
- * A finished turn opened for reading.
136
- *
137
- * Kept as a detached copy so scrolling and tabbing through it cannot disturb
138
- * the turn that is actually running. While it is set the panes render it, and
139
- * `focusId` / `scroll` edits land on it rather than on the live turn.
140
- */
141
- view?: TurnView;
142
134
  /**
143
135
  * Whether tool detail is shown in the stream.
144
136
  *
@@ -180,6 +172,7 @@ export type TuiAction = {
180
172
  | {
181
173
  type: "stream:search";
182
174
  query: string;
175
+ match?: number;
183
176
  viewport: number;
184
177
  width: number;
185
178
  }
@@ -203,15 +196,6 @@ export type TuiAction = {
203
196
  agentId: string;
204
197
  text: string;
205
198
  }
206
- /** Open a finished turn for reading, by its turn number. */
207
- | {
208
- type: "view:open";
209
- turn: number;
210
- }
211
- /** Go back to the live turn. */
212
- | {
213
- type: "view:close";
214
- }
215
199
  /** Flip whether tool detail is shown in the stream. */
216
200
  | {
217
201
  type: "verbose:toggle";
@@ -272,6 +256,8 @@ export declare function seedAgents(config: MultiClawConfig): Record<string, Agen
272
256
  */
273
257
  export declare function createInitialState(config: MultiClawConfig, history?: TurnView[]): TuiState;
274
258
  export declare function reduceEvent(state: TuiState, event: MultiClawEvent): TuiState;
259
+ /** Matching wrapped rows in the focused agent's entire session. */
260
+ export declare function streamMatches(state: TuiState, query: string, width: number): number[];
275
261
  /**
276
262
  * The turns worth keeping: those already in `history`, plus the turn on screen
277
263
  * once it has ended.
@@ -343,14 +329,20 @@ export interface LogWindow {
343
329
  following: boolean;
344
330
  }
345
331
  /**
346
- * The slice of the focused agent's output that fits the pane.
332
+ * The slice of the focused agent's session that fits the pane.
347
333
  *
348
334
  * Wrapped first, then windowed, and both steps are needed: the pane is a fixed
349
335
  * number of *rows*, while the buffer is a string of *lines*, and one line can
350
336
  * spend several rows. Slicing lines would hand the pane more rows than it has
351
337
  * and push the newest output out of sight.
352
338
  *
339
+ * Turn-divider rows are interspersed to give the reader an anchor: they show
340
+ * the turn number and a clipped requirement snippet so a reader scrolled back
341
+ * knows which request they are looking at.
342
+ *
353
343
  * Clamping here as well as in the reducer keeps the window correct even when
354
- * the pane is resized between two scroll actions.
344
+ * the pane is resized between two scroll actions. Scroll semantics: 0 means
345
+ * pinned to the newest output, positive values mean scrolled back, and
346
+ * `following` is true when the window is at the tail.
355
347
  */
356
- export declare function visibleLog(turn: TurnView, viewport: number, width: number): LogWindow;
348
+ export declare function visibleTimeline(state: TuiState, agentId: string, viewport: number, width: number, scroll: number): LogWindow;