@multiclawcli/dashboard 0.5.1 → 0.6.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 +7 -2
- package/dist/tui/atomic.d.ts +21 -0
- package/dist/tui/components/overlays.d.ts +17 -0
- package/dist/tui/format.d.ts +19 -0
- package/dist/tui/history.d.ts +4 -5
- package/dist/tui/index.d.ts +2 -2
- package/dist/tui/session.d.ts +134 -0
- package/dist/tui/state.d.ts +41 -3
- package/dist/tui.js +930 -70
- package/package.json +2 -2
package/dist/tui/App.d.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { EventBus, MultiClawConfig } from "@multiclawcli/core";
|
|
2
|
+
import { AgentSessionManager } from "@multiclawcli/core";
|
|
2
3
|
export interface AppProps {
|
|
3
4
|
config: MultiClawConfig;
|
|
4
5
|
/**
|
|
5
6
|
* Called once per turn, after that turn's orchestrator exists. The CLI uses it
|
|
6
7
|
* to attach a file log writer without the TUI depending on the CLI package.
|
|
7
8
|
*/
|
|
8
|
-
onTurnStart?: (eventBus: EventBus, logDir: string) => void;
|
|
9
|
+
onTurnStart?: (eventBus: EventBus, logDir: string) => void | (() => void | Promise<void>);
|
|
10
|
+
/** Caller-owned resources; startTui also closes them on render failures. */
|
|
11
|
+
sessions?: AgentSessionManager;
|
|
12
|
+
/** Gives the terminal host an awaitable cleanup barrier, including render failures. */
|
|
13
|
+
onCleanupReady?: (cleanup: () => Promise<void>) => void;
|
|
9
14
|
}
|
|
10
|
-
export declare function App({ config, onTurnStart }: AppProps): import("react").JSX.Element;
|
|
15
|
+
export declare function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }: AppProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Write JSON to `target` atomically, serialized per path.
|
|
3
|
+
*
|
|
4
|
+
* Written to a temp file and renamed into place: `rename` is atomic within a
|
|
5
|
+
* filesystem, so a crash mid-write leaves the *previous* file intact. Writing
|
|
6
|
+
* the target directly would instead leave half a JSON document, and since every
|
|
7
|
+
* reader here can only drop a file it cannot parse, that would cost the user
|
|
8
|
+
* *all* of their state rather than the last write.
|
|
9
|
+
*
|
|
10
|
+
* The returned promise rejects on failure; the serialization chain swallows it
|
|
11
|
+
* so one failed write cannot poison the next.
|
|
12
|
+
*/
|
|
13
|
+
export declare function writeJsonFile(target: string, json: string): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* The last write queued for `target`, or a resolved promise when there is none.
|
|
16
|
+
*
|
|
17
|
+
* Lets a shutdown wait for state that is already on its way to disk. Without it
|
|
18
|
+
* the process can exit between the `writeFile` and the `rename`, which leaves
|
|
19
|
+
* the temporary file behind and the previous — older — file in place.
|
|
20
|
+
*/
|
|
21
|
+
export declare function pendingWrite(target: string): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RuntimePermissionRequest, RuntimeQuestionRequest } from "@multiclawcli/core";
|
|
1
2
|
import { type TurnView } from "../state";
|
|
2
3
|
interface HelpGroup {
|
|
3
4
|
title: string;
|
|
@@ -43,6 +44,22 @@ export declare function HelpOverlay({ width, maxRows }: {
|
|
|
43
44
|
width: number;
|
|
44
45
|
maxRows: number;
|
|
45
46
|
}): import("react").JSX.Element;
|
|
47
|
+
export declare function PermissionOverlay({ request, shortId, queued, width, maxRows, }: {
|
|
48
|
+
request: RuntimePermissionRequest;
|
|
49
|
+
shortId: string;
|
|
50
|
+
queued: number;
|
|
51
|
+
width: number;
|
|
52
|
+
maxRows: number;
|
|
53
|
+
}): import("react").JSX.Element;
|
|
54
|
+
export declare function questionLines(request: RuntimeQuestionRequest, width: number): string[];
|
|
55
|
+
export declare function QuestionOverlay({ request, shortId, queued, width, maxRows, offset, }: {
|
|
56
|
+
request: RuntimeQuestionRequest;
|
|
57
|
+
shortId: string;
|
|
58
|
+
queued: number;
|
|
59
|
+
width: number;
|
|
60
|
+
maxRows: number;
|
|
61
|
+
offset: number;
|
|
62
|
+
}): import("react").JSX.Element;
|
|
46
63
|
/**
|
|
47
64
|
* The finished turns, newest first.
|
|
48
65
|
*
|
package/dist/tui/format.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SupplementReport } from "@multiclawcli/core";
|
|
1
2
|
/**
|
|
2
3
|
* Truncate to `max` columns, ending in an ellipsis when it does not fit.
|
|
3
4
|
*
|
|
@@ -63,3 +64,21 @@ export declare function formatTokens(n: number): string;
|
|
|
63
64
|
* than rounding it away to "$0.00" — a real but tiny cost must not read as free.
|
|
64
65
|
*/
|
|
65
66
|
export declare function formatCost(usd: number): string;
|
|
67
|
+
/**
|
|
68
|
+
* What to tell the user after a mid-turn message.
|
|
69
|
+
*
|
|
70
|
+
* Reports are per agent and each carries its own reason, so the sentence names
|
|
71
|
+
* agents rather than summarising an outcome: "steered architect · leader was
|
|
72
|
+
* between turns" tells the user what happened to whom, while "steering failed"
|
|
73
|
+
* tells them nothing they can act on.
|
|
74
|
+
*
|
|
75
|
+
* `error` and `idle` must never be collapsed: the first is a delivery that went
|
|
76
|
+
* wrong, the second is a message that arrived at the wrong moment. Calling the
|
|
77
|
+
* first the second would tell the user to simply wait when they should be
|
|
78
|
+
* looking at a failure.
|
|
79
|
+
*
|
|
80
|
+
* When nothing was delivered the caller queues the text, so the sentence says
|
|
81
|
+
* so — otherwise the user is left believing their message is inside the turn
|
|
82
|
+
* they are watching.
|
|
83
|
+
*/
|
|
84
|
+
export declare function describeSteering(reports: readonly SupplementReport[]): string;
|
package/dist/tui/history.d.ts
CHANGED
|
@@ -22,11 +22,10 @@ export declare function loadHistory(workDir: string): TurnView[];
|
|
|
22
22
|
/**
|
|
23
23
|
* Write the workspace's turn history.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
* *all* of their history rather than the last write.
|
|
25
|
+
* Atomic and serialized per path (see {@link writeJsonFile}), because a crash
|
|
26
|
+
* mid-write must leave the previous history readable rather than half a
|
|
27
|
+
* document: `loadHistory` can only drop a file it cannot parse, so a torn write
|
|
28
|
+
* would cost the user *all* of their history rather than the last one.
|
|
30
29
|
*/
|
|
31
30
|
export declare function saveHistory(workDir: string, turns: readonly TurnView[]): Promise<void>;
|
|
32
31
|
export declare function exportPath(workDir: string, turn: number): string;
|
package/dist/tui/index.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type EventBus, type MultiClawConfig } from "@multiclawcli/core";
|
|
2
2
|
export type { AppProps } from "./App";
|
|
3
3
|
export * from "./layout";
|
|
4
4
|
export * from "./state";
|
|
5
5
|
export interface TuiOptions {
|
|
6
6
|
config: MultiClawConfig;
|
|
7
7
|
/** Attach per-turn reporters (e.g. a file log writer) here. */
|
|
8
|
-
onTurnStart?: (eventBus: EventBus, logDir: string) => void;
|
|
8
|
+
onTurnStart?: (eventBus: EventBus, logDir: string) => void | (() => void | Promise<void>);
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
11
11
|
* Render the interactive team console.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { type AgentView, type TuiStageInfo, type TuiState, type TurnView } from "./state";
|
|
2
|
+
/**
|
|
3
|
+
* The session journal: what a restart needs to pick the console back up.
|
|
4
|
+
*
|
|
5
|
+
* Live agent sessions are child processes, and a pending approval is an RPC in
|
|
6
|
+
* flight to one of them — neither survives the process that owns them. So this
|
|
7
|
+
* file deliberately does not try to *continue* a turn. It records the boundary
|
|
8
|
+
* instead: which turn was running, what the user had typed but not run, where
|
|
9
|
+
* the current task began, and how many decisions were waiting. A restart can
|
|
10
|
+
* then say what happened, show the interrupted turn as a turn, and hand the
|
|
11
|
+
* queued input back — without pretending a dead agent is still working.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SESSION_FILE = "session.json";
|
|
14
|
+
/** Bumped when the on-disk shape changes in a way an older console cannot read. */
|
|
15
|
+
export declare const SESSION_VERSION = 1;
|
|
16
|
+
/**
|
|
17
|
+
* How much of each agent's log the journal keeps.
|
|
18
|
+
*
|
|
19
|
+
* The live buffer holds `MAX_LOG_CHARS` per agent and a turn can have a dozen
|
|
20
|
+
* agents, so journaling it whole would rewrite a multi-hundred-kilobyte file
|
|
21
|
+
* every few seconds. The tail is what answers "what was it doing when it died",
|
|
22
|
+
* and it is the only part worth the write.
|
|
23
|
+
*/
|
|
24
|
+
export declare const MAX_JOURNAL_LOG_CHARS = 1200;
|
|
25
|
+
/** The turn that was in flight when the journal was last written. */
|
|
26
|
+
export interface JournalTurn {
|
|
27
|
+
turn: number;
|
|
28
|
+
requirement: string;
|
|
29
|
+
startedAt?: number;
|
|
30
|
+
effectiveRequirement?: string;
|
|
31
|
+
reason?: string;
|
|
32
|
+
warning?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Wall clock of this write — the last moment the console was known to be
|
|
35
|
+
* alive. The restored turn ends here, so its duration reads as "how long it
|
|
36
|
+
* ran", not "how long ago it was".
|
|
37
|
+
*/
|
|
38
|
+
savedAt: number;
|
|
39
|
+
leaderIds: string[];
|
|
40
|
+
focusId?: string;
|
|
41
|
+
stages: TuiStageInfo[];
|
|
42
|
+
agents: Record<string, AgentView>;
|
|
43
|
+
/** Decisions the agents were waiting on. A restart cannot answer them. */
|
|
44
|
+
pending?: {
|
|
45
|
+
permissions: number;
|
|
46
|
+
questions: number;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export interface SessionJournal {
|
|
50
|
+
version: number;
|
|
51
|
+
/**
|
|
52
|
+
* Turn number the current task began at.
|
|
53
|
+
*
|
|
54
|
+
* `/task` closes the previous task's context, and that boundary has to outlive
|
|
55
|
+
* the process: without it a restart would hand the leader every turn ever run
|
|
56
|
+
* in the workspace, including the ones the user explicitly closed.
|
|
57
|
+
*/
|
|
58
|
+
taskStart?: number;
|
|
59
|
+
/** Follow-ups typed but never run, in the order they were typed. */
|
|
60
|
+
queue?: string[];
|
|
61
|
+
inFlight?: JournalTurn;
|
|
62
|
+
}
|
|
63
|
+
/** Where the console keeps its session journal for a workspace. */
|
|
64
|
+
export declare function sessionPath(workDir: string): string;
|
|
65
|
+
/**
|
|
66
|
+
* Read the workspace's session journal.
|
|
67
|
+
*
|
|
68
|
+
* Never throws, for the same reason `loadHistory` does not: a corrupted side
|
|
69
|
+
* file means "no journal", and a console that refuses to start over it would be
|
|
70
|
+
* far worse than one that starts with an empty past. A journal written by a
|
|
71
|
+
* newer console (unknown `version`) is ignored rather than guessed at.
|
|
72
|
+
*/
|
|
73
|
+
export declare function loadJournal(workDir: string): SessionJournal | undefined;
|
|
74
|
+
/**
|
|
75
|
+
* Write the workspace's session journal.
|
|
76
|
+
*
|
|
77
|
+
* Atomic and serialized per path (see {@link writeJsonFile}): the whole point is
|
|
78
|
+
* to survive a process that dies mid-write, so the write itself must never be
|
|
79
|
+
* the thing that leaves a torn file behind.
|
|
80
|
+
*
|
|
81
|
+
* An empty journal does not create the file. A console that was opened and quit
|
|
82
|
+
* without doing anything should not leave `.multiclaw/console/` behind — but an
|
|
83
|
+
* *existing* journal is still rewritten when it empties, because "nothing is in
|
|
84
|
+
* flight" is exactly what the next start needs to read.
|
|
85
|
+
*/
|
|
86
|
+
export declare function saveJournal(workDir: string, journal: SessionJournal): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* The journal entry for a turn that is running right now.
|
|
89
|
+
*
|
|
90
|
+
* A pure projection of what the panes are showing, so the journal can never
|
|
91
|
+
* describe a turn the console is not actually running. Logs are trimmed; every
|
|
92
|
+
* other field is small enough to keep whole.
|
|
93
|
+
*/
|
|
94
|
+
export declare function journalTurn(state: TuiState, pending: {
|
|
95
|
+
permissions: number;
|
|
96
|
+
questions: number;
|
|
97
|
+
}): JournalTurn;
|
|
98
|
+
/**
|
|
99
|
+
* Rebuild an interrupted turn as a browsable record.
|
|
100
|
+
*
|
|
101
|
+
* Two deliberate rewrites, both because the record must not claim work that is
|
|
102
|
+
* not happening:
|
|
103
|
+
*
|
|
104
|
+
* - An agent that never reported back becomes `cancelled`. Leaving it `running`
|
|
105
|
+
* would animate a spinner and a growing clock for a process that died with
|
|
106
|
+
* the console.
|
|
107
|
+
* - Stage outcomes are dropped so they are re-derived from those statuses. A
|
|
108
|
+
* stage that had finished before the crash keeps its success, and one that was
|
|
109
|
+
* mid-flight reads as cancelled instead of frozen at `running` for ever.
|
|
110
|
+
*
|
|
111
|
+
* The turn itself is `complete` — nothing will be added to it — with
|
|
112
|
+
* `interrupted` set so every surface can say *how* it ended.
|
|
113
|
+
*/
|
|
114
|
+
export declare function interruptedTurn(entry: JournalTurn): TurnView;
|
|
115
|
+
export interface RestoredConsole {
|
|
116
|
+
history: TurnView[];
|
|
117
|
+
queue: string[];
|
|
118
|
+
taskStart: number;
|
|
119
|
+
/** The last turn ended by asking the user something, so the console reopens ready for it. */
|
|
120
|
+
awaitingAnswer: boolean;
|
|
121
|
+
/** One line for the notice bar, when the previous session left work behind. */
|
|
122
|
+
notice?: string;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Everything the console should open with, read once at startup.
|
|
126
|
+
*
|
|
127
|
+
* The interrupted turn is appended to the restored history rather than replacing
|
|
128
|
+
* it: the turns before it still happened. It is skipped when history already
|
|
129
|
+
* holds that turn number, because a restored turn is written back to
|
|
130
|
+
* `history.json` on the very next save — a second restart must not add it twice.
|
|
131
|
+
* A journal *behind* the last recorded turn is stale (the history was written by
|
|
132
|
+
* a later session) and is dropped for the same reason.
|
|
133
|
+
*/
|
|
134
|
+
export declare function restoreConsole(workDir: string): RestoredConsole;
|
package/dist/tui/state.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AgentDefinition, AgentOutput, AgentStatus, LeaderMessage, MultiClawConfig, MultiClawEvent, StageInfo, TurnRecord } from "@multiclawcli/core";
|
|
1
|
+
import type { AcceptanceResult, AgentDefinition, AgentOutput, AgentStatus, LeaderMessage, MultiClawConfig, MultiClawEvent, StageInfo, TurnRecord } from "@multiclawcli/core";
|
|
2
2
|
/**
|
|
3
3
|
* Per-agent log buffer cap, in characters.
|
|
4
4
|
*
|
|
@@ -41,6 +41,11 @@ export interface AgentView {
|
|
|
41
41
|
logs: string;
|
|
42
42
|
}
|
|
43
43
|
export type TuiPhase = "idle" | "running" | "complete";
|
|
44
|
+
export interface TuiStageInfo extends StageInfo {
|
|
45
|
+
/** Frozen when this stage finishes, independent of later runs of its agents. */
|
|
46
|
+
outcome?: AgentStatus;
|
|
47
|
+
memberStatuses?: Record<string, AgentStatus>;
|
|
48
|
+
}
|
|
44
49
|
/**
|
|
45
50
|
* One turn, as the panes need to see it.
|
|
46
51
|
*
|
|
@@ -52,12 +57,14 @@ export type TuiPhase = "idle" | "running" | "complete";
|
|
|
52
57
|
export interface TurnView {
|
|
53
58
|
turn: number;
|
|
54
59
|
requirement?: string;
|
|
60
|
+
effectiveRequirement?: string;
|
|
61
|
+
cancelled?: boolean;
|
|
55
62
|
phase: TuiPhase;
|
|
56
63
|
/** Wall clock at which the turn started. */
|
|
57
64
|
startedAt?: number;
|
|
58
65
|
/** Wall clock at which the turn finished. */
|
|
59
66
|
endedAt?: number;
|
|
60
|
-
stages:
|
|
67
|
+
stages: TuiStageInfo[];
|
|
61
68
|
agents: Record<string, AgentView>;
|
|
62
69
|
/**
|
|
63
70
|
* Agents that run *before* the pipeline exists (the leader), so the pipeline
|
|
@@ -82,6 +89,19 @@ export interface TurnView {
|
|
|
82
89
|
*/
|
|
83
90
|
scroll: number;
|
|
84
91
|
success?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Set on a turn that was recovered from a journal after the console died
|
|
94
|
+
* mid-run.
|
|
95
|
+
*
|
|
96
|
+
* Such a turn is `complete` — it is over, and its record is final — but it
|
|
97
|
+
* did not *finish*, so it must not be reported as a success or a failure.
|
|
98
|
+
* `cancelled` is deliberately not reused: that means the user stopped the
|
|
99
|
+
* run, while this one was stopped by the process going away, and the console
|
|
100
|
+
* has to be able to tell the two apart when the user asks "what happened to
|
|
101
|
+
* turn 3".
|
|
102
|
+
*/
|
|
103
|
+
interrupted?: boolean;
|
|
104
|
+
acceptance?: AcceptanceResult;
|
|
85
105
|
totalDuration?: number;
|
|
86
106
|
/**
|
|
87
107
|
* Sum of the per-agent costs the runtime reported, in USD. Absent when none
|
|
@@ -166,6 +186,18 @@ export type TuiAction = {
|
|
|
166
186
|
| {
|
|
167
187
|
type: "stream:clear";
|
|
168
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* Append a console marker to one agent's stream — what the user did, not what
|
|
191
|
+
* the agent said.
|
|
192
|
+
*
|
|
193
|
+
* Always the live turn: a mid-turn message can only reach what is running
|
|
194
|
+
* now, so there is no past turn for it to belong to.
|
|
195
|
+
*/
|
|
196
|
+
| {
|
|
197
|
+
type: "stream:note";
|
|
198
|
+
agentId: string;
|
|
199
|
+
text: string;
|
|
200
|
+
}
|
|
169
201
|
/** Open a finished turn for reading, by its turn number. */
|
|
170
202
|
| {
|
|
171
203
|
type: "view:open";
|
|
@@ -244,6 +276,12 @@ export declare function recordedTurns(state: TuiState): TurnView[];
|
|
|
244
276
|
* Only turns that ended are carried, which makes the call site's timing
|
|
245
277
|
* irrelevant: whether this is read before or after `turn:start` moves the turn
|
|
246
278
|
* on screen into `history`, the set is the same.
|
|
279
|
+
*
|
|
280
|
+
* An *interrupted* turn is carried even though it has no reply. A cancelled one
|
|
281
|
+
* is not: the user chose to abandon it. An interrupted turn was abandoned by
|
|
282
|
+
* the process, so its requirement is unfinished business — and it is the only
|
|
283
|
+
* thing that makes a follow-up like "carry on with that" readable after a
|
|
284
|
+
* restart.
|
|
247
285
|
*/
|
|
248
286
|
export declare function conversationTurns(state: TuiState): TurnRecord[];
|
|
249
287
|
export declare function tuiReducer(state: TuiState, action: TuiAction): TuiState;
|
|
@@ -257,7 +295,7 @@ export declare function tuiReducer(state: TuiState, action: TuiAction): TuiState
|
|
|
257
295
|
* to start, and reporting it as `pending` would be the same lie B2-6 fixed one
|
|
258
296
|
* level down.
|
|
259
297
|
*/
|
|
260
|
-
export declare function stageStatus(stage:
|
|
298
|
+
export declare function stageStatus(stage: TuiStageInfo, agents: Record<string, AgentView>): AgentStatus;
|
|
261
299
|
/** How many agents have finished, out of how many the turn will run. */
|
|
262
300
|
export declare function progress(turn: TurnView): {
|
|
263
301
|
done: number;
|