@multiclawcli/dashboard 0.8.2 → 0.10.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/dist/tui/App.d.ts +17 -1
- package/dist/tui/atomic.d.ts +10 -0
- package/dist/tui/components/overlays.d.ts +19 -0
- package/dist/tui/format.d.ts +8 -0
- package/dist/tui/history.d.ts +17 -16
- package/dist/tui/index.d.ts +4 -1
- package/dist/tui/session.d.ts +30 -33
- package/dist/tui/sessions.d.ts +48 -0
- package/dist/tui/state.d.ts +21 -0
- package/dist/tui.js +533 -81
- package/package.json +2 -2
package/dist/tui/App.d.ts
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import type { EventBus, MultiClawConfig } from "@multiclawcli/core";
|
|
2
2
|
import { AgentSessionManager } from "@multiclawcli/core";
|
|
3
|
+
/**
|
|
4
|
+
* How the console should choose its session on launch.
|
|
5
|
+
*
|
|
6
|
+
* Absent means the default: continue the most recent session, or start one when
|
|
7
|
+
* the workspace has none. `new` always starts fresh. `resume` opens the named
|
|
8
|
+
* session, or — with no id — the most recent one with the picker already open,
|
|
9
|
+
* so `multiclaw -r` lands on the list the way `claude -r` does.
|
|
10
|
+
*/
|
|
11
|
+
export type InitialSession = {
|
|
12
|
+
kind: "new";
|
|
13
|
+
} | {
|
|
14
|
+
kind: "resume";
|
|
15
|
+
id?: string;
|
|
16
|
+
};
|
|
3
17
|
export interface AppProps {
|
|
4
18
|
config: MultiClawConfig;
|
|
5
19
|
/**
|
|
@@ -11,5 +25,7 @@ export interface AppProps {
|
|
|
11
25
|
sessions?: AgentSessionManager;
|
|
12
26
|
/** Gives the terminal host an awaitable cleanup barrier, including render failures. */
|
|
13
27
|
onCleanupReady?: (cleanup: () => Promise<void>) => void;
|
|
28
|
+
/** How to choose the session on launch; defaults to continuing the latest. */
|
|
29
|
+
initialSession?: InitialSession;
|
|
14
30
|
}
|
|
15
|
-
export declare function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady }: AppProps): import("react").JSX.Element;
|
|
31
|
+
export declare function App({ config, onTurnStart, sessions: providedSessions, onCleanupReady, initialSession, }: AppProps): import("react").JSX.Element;
|
package/dist/tui/atomic.d.ts
CHANGED
|
@@ -19,3 +19,13 @@ export declare function writeJsonFile(target: string, json: string): Promise<voi
|
|
|
19
19
|
* the temporary file behind and the previous — older — file in place.
|
|
20
20
|
*/
|
|
21
21
|
export declare function pendingWrite(target: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Append newline-terminated lines to `target`, serialized per path on the same
|
|
24
|
+
* chain as {@link writeJsonFile}.
|
|
25
|
+
*
|
|
26
|
+
* Unlike the rewrite path this does not go through a temp file: an append is the
|
|
27
|
+
* cheap O(1) write a JSONL log wants, and a crash mid-append can only tear the
|
|
28
|
+
* *last* line, which every line-oriented reader here already skips. Sharing the
|
|
29
|
+
* `writes` chain keeps a `pendingWrite` on the same path able to await it.
|
|
30
|
+
*/
|
|
31
|
+
export declare function appendLines(target: string, lines: readonly string[]): Promise<void>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SessionMeta } from "../sessions";
|
|
1
2
|
import { type TurnView } from "../state";
|
|
2
3
|
interface HelpGroup {
|
|
3
4
|
title: string;
|
|
@@ -61,4 +62,22 @@ export declare function HistoryOverlay({ turns, selected, width, maxRows, }: {
|
|
|
61
62
|
/** Rows available in the body. The list is windowed against this. */
|
|
62
63
|
maxRows: number;
|
|
63
64
|
}): import("react").JSX.Element;
|
|
65
|
+
/**
|
|
66
|
+
* The session picker: every session in the workspace, newest activity first.
|
|
67
|
+
*
|
|
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.
|
|
73
|
+
*/
|
|
74
|
+
export declare function SessionsOverlay({ sessions, selected, currentId, width, maxRows, now, }: {
|
|
75
|
+
sessions: SessionMeta[];
|
|
76
|
+
selected: number;
|
|
77
|
+
currentId: string | undefined;
|
|
78
|
+
width: number;
|
|
79
|
+
/** Rows available in the body. The list is windowed against this. */
|
|
80
|
+
maxRows: number;
|
|
81
|
+
now: number;
|
|
82
|
+
}): import("react").JSX.Element;
|
|
64
83
|
export {};
|
package/dist/tui/format.d.ts
CHANGED
|
@@ -43,6 +43,14 @@ export declare function wrapLine(line: string, width: number): string[];
|
|
|
43
43
|
* scan a stack of durations only if they share a shape.
|
|
44
44
|
*/
|
|
45
45
|
export declare function formatDuration(ms: number): string;
|
|
46
|
+
/**
|
|
47
|
+
* A coarse "how long ago", for the session list.
|
|
48
|
+
*
|
|
49
|
+
* Deliberately low-resolution — the picker wants "which one did I touch last",
|
|
50
|
+
* not a precise clock. Future timestamps (a clock that moved backwards) read as
|
|
51
|
+
* "just now" rather than a negative age.
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatAgo(timestamp: number, now: number): string;
|
|
46
54
|
/** Stage markers read as an ordered sequence at a glance; past twenty, fall back. */
|
|
47
55
|
export declare function circled(n: number): string;
|
|
48
56
|
/** Pad to a fixed column so sibling rows line up. */
|
package/dist/tui/history.d.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { type TurnView } from "./state";
|
|
2
|
-
/**
|
|
3
|
-
export declare const
|
|
4
|
-
/** The finished-turn record the console keeps across sessions. */
|
|
5
|
-
export declare const HISTORY_FILE = "history.json";
|
|
2
|
+
/** The finished-turn log a session keeps, one JSON turn per line. */
|
|
3
|
+
export declare const TURNS_FILE = "turns.jsonl";
|
|
6
4
|
/** Where exported turn markdown files are written by default. */
|
|
7
5
|
export declare const EXPORT_DIR = "exports";
|
|
8
|
-
/** Where
|
|
9
|
-
export declare function
|
|
6
|
+
/** Where a session keeps its finished turns. */
|
|
7
|
+
export declare function turnsPath(workDir: string, sessionId: string): string;
|
|
10
8
|
/**
|
|
11
|
-
* Read
|
|
9
|
+
* Read a session's turn history.
|
|
12
10
|
*
|
|
13
11
|
* Synchronous on purpose: it runs once, inside the reducer's lazy initialiser,
|
|
14
12
|
* before the first frame. An async load would race the user's first keystroke
|
|
@@ -16,18 +14,21 @@ export declare function historyPath(workDir: string): string;
|
|
|
16
14
|
*
|
|
17
15
|
* Never throws. A missing directory, malformed JSON, or a value of the wrong
|
|
18
16
|
* shape all mean "no history": the console can always start from empty, and
|
|
19
|
-
* crashing on startup because a side file got corrupted would be far worse.
|
|
17
|
+
* crashing on startup because a side file got corrupted would be far worse. The
|
|
18
|
+
* log is line-oriented, so a single torn line (a crash mid-append) is skipped
|
|
19
|
+
* rather than costing the whole session's history.
|
|
20
20
|
*/
|
|
21
|
-
export declare function
|
|
21
|
+
export declare function loadTurns(workDir: string, sessionId: string): TurnView[];
|
|
22
22
|
/**
|
|
23
|
-
*
|
|
23
|
+
* Append finished turns to a session's log, one JSON object per line.
|
|
24
24
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* Append-only and serialized per path (see {@link appendLines}): each settled
|
|
26
|
+
* turn is an O(1) write rather than a rewrite of the whole history, and a crash
|
|
27
|
+
* mid-append can only tear the last line, which {@link loadTurns} skips. Callers
|
|
28
|
+
* pass only the turns not yet persisted, tracked by the highest turn number
|
|
29
|
+
* already written.
|
|
29
30
|
*/
|
|
30
|
-
export declare function
|
|
31
|
-
export declare function exportPath(workDir: string, turn: number): string;
|
|
31
|
+
export declare function appendTurns(workDir: string, sessionId: string, turns: readonly TurnView[]): Promise<void>;
|
|
32
|
+
export declare function exportPath(workDir: string, sessionId: string, turn: number): string;
|
|
32
33
|
export declare function exportTurn(turn: TurnView, filePath: string): Promise<void>;
|
|
33
34
|
export declare function formatTurnMarkdown(turn: TurnView): string;
|
package/dist/tui/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { type EventBus, type MultiClawConfig } from "@multiclawcli/core";
|
|
2
|
-
|
|
2
|
+
import { type InitialSession } from "./App";
|
|
3
|
+
export type { AppProps, InitialSession } from "./App";
|
|
3
4
|
export * from "./layout";
|
|
4
5
|
export * from "./state";
|
|
5
6
|
export interface TuiOptions {
|
|
6
7
|
config: MultiClawConfig;
|
|
7
8
|
/** Attach per-turn reporters (e.g. a file log writer) here. */
|
|
8
9
|
onTurnStart?: (eventBus: EventBus, logDir: string) => void | (() => void | Promise<void>);
|
|
10
|
+
/** How to choose the session on launch; defaults to continuing the latest. */
|
|
11
|
+
initialSession?: InitialSession;
|
|
9
12
|
}
|
|
10
13
|
/**
|
|
11
14
|
* Render the interactive team console.
|
package/dist/tui/session.d.ts
CHANGED
|
@@ -5,10 +5,10 @@ import { type AgentView, type TuiStageInfo, type TuiState, type TurnView } from
|
|
|
5
5
|
* Live agent sessions are child processes, and a pending approval is an RPC in
|
|
6
6
|
* flight to one of them — neither survives the process that owns them. So this
|
|
7
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,
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* instead: which turn was running, what the user had typed but not run, and how
|
|
9
|
+
* many decisions were waiting. A restart can then say what happened, show the
|
|
10
|
+
* interrupted turn as a turn, and hand the queued input back — without
|
|
11
|
+
* pretending a dead agent is still working.
|
|
12
12
|
*/
|
|
13
13
|
export declare const SESSION_FILE = "session.json";
|
|
14
14
|
/** Bumped when the on-disk shape changes in a way an older console cannot read. */
|
|
@@ -48,29 +48,21 @@ export interface JournalTurn {
|
|
|
48
48
|
}
|
|
49
49
|
export interface SessionJournal {
|
|
50
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
51
|
/** Follow-ups typed but never run, in the order they were typed. */
|
|
60
52
|
queue?: string[];
|
|
61
53
|
inFlight?: JournalTurn;
|
|
62
54
|
}
|
|
63
|
-
/** Where
|
|
64
|
-
export declare function sessionPath(workDir: string): string;
|
|
55
|
+
/** Where a session keeps its journal. */
|
|
56
|
+
export declare function sessionPath(workDir: string, sessionId: string): string;
|
|
65
57
|
/**
|
|
66
|
-
* Read
|
|
58
|
+
* Read a session's journal.
|
|
67
59
|
*
|
|
68
|
-
* Never throws, for the same reason `
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
60
|
+
* Never throws, for the same reason `loadTurns` does not: a corrupted side file
|
|
61
|
+
* means "no journal", and a console that refuses to start over it would be far
|
|
62
|
+
* worse than one that starts with an empty past. A journal written by a newer
|
|
63
|
+
* console (unknown `version`) is ignored rather than guessed at.
|
|
72
64
|
*/
|
|
73
|
-
export declare function loadJournal(workDir: string): SessionJournal | undefined;
|
|
65
|
+
export declare function loadJournal(workDir: string, sessionId: string): SessionJournal | undefined;
|
|
74
66
|
/**
|
|
75
67
|
* Write the workspace's session journal.
|
|
76
68
|
*
|
|
@@ -78,12 +70,12 @@ export declare function loadJournal(workDir: string): SessionJournal | undefined
|
|
|
78
70
|
* to survive a process that dies mid-write, so the write itself must never be
|
|
79
71
|
* the thing that leaves a torn file behind.
|
|
80
72
|
*
|
|
81
|
-
* An empty journal does not create the file. A
|
|
82
|
-
* without doing anything should not leave
|
|
83
|
-
*
|
|
84
|
-
*
|
|
73
|
+
* An empty journal does not create the file. A session that was opened and quit
|
|
74
|
+
* without doing anything should not leave a journal behind — but an *existing*
|
|
75
|
+
* journal is still rewritten when it empties, because "nothing is in flight" is
|
|
76
|
+
* exactly what the next start needs to read.
|
|
85
77
|
*/
|
|
86
|
-
export declare function saveJournal(workDir: string, journal: SessionJournal): Promise<void>;
|
|
78
|
+
export declare function saveJournal(workDir: string, sessionId: string, journal: SessionJournal): Promise<void>;
|
|
87
79
|
/**
|
|
88
80
|
* The journal entry for a turn that is running right now.
|
|
89
81
|
*
|
|
@@ -115,20 +107,25 @@ export declare function interruptedTurn(entry: JournalTurn): TurnView;
|
|
|
115
107
|
export interface RestoredConsole {
|
|
116
108
|
history: TurnView[];
|
|
117
109
|
queue: string[];
|
|
118
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Highest turn number already on disk in `turns.jsonl` — the watermark the
|
|
112
|
+
* console appends past. Excludes a journal-recovered interrupted turn, which
|
|
113
|
+
* lives only in the journal until the next save writes it to the log.
|
|
114
|
+
*/
|
|
115
|
+
persistedTurn: number;
|
|
119
116
|
/** The last turn ended by asking the user something, so the console reopens ready for it. */
|
|
120
117
|
awaitingAnswer: boolean;
|
|
121
118
|
/** One line for the notice bar, when the previous session left work behind. */
|
|
122
119
|
notice?: string;
|
|
123
120
|
}
|
|
124
121
|
/**
|
|
125
|
-
* Everything the console should open with, read once at startup.
|
|
122
|
+
* Everything the console should open a session with, read once at startup.
|
|
126
123
|
*
|
|
127
124
|
* The interrupted turn is appended to the restored history rather than replacing
|
|
128
|
-
* it: the turns before it still happened. It is skipped when
|
|
129
|
-
* holds that turn number, because a restored turn is written
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
125
|
+
* it: the turns before it still happened. It is skipped when the log already
|
|
126
|
+
* holds that turn number, because a restored turn is written to the log on the
|
|
127
|
+
* very next save — a second restart must not add it twice. A journal *behind*
|
|
128
|
+
* the last recorded turn is stale (the log was written by a later session) and
|
|
129
|
+
* is dropped for the same reason.
|
|
133
130
|
*/
|
|
134
|
-
export declare function restoreConsole(workDir: string): RestoredConsole;
|
|
131
|
+
export declare function restoreConsole(workDir: string, sessionId: string): RestoredConsole;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sessions: the unit of history and resumption.
|
|
3
|
+
*
|
|
4
|
+
* A session is one continuous line of work — a sequence of turns, each fanning
|
|
5
|
+
* out to the same team of agents. It is what Claude Code / Codex call a session:
|
|
6
|
+
* an independent, listable, resumable, deletable record. Everything a session
|
|
7
|
+
* owns lives under its own directory (`turns.jsonl`, `session.json`, exports),
|
|
8
|
+
* and a small `index.json` holds just the summaries the picker needs so it never
|
|
9
|
+
* has to open a session to list one.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SESSIONS_DIR = "sessions";
|
|
12
|
+
/** The summary index, read by the picker; one entry per session. */
|
|
13
|
+
export declare const INDEX_FILE = "index.json";
|
|
14
|
+
/** How long a session title may grow before it is truncated for the index. */
|
|
15
|
+
export declare const MAX_TITLE_CHARS = 80;
|
|
16
|
+
export interface SessionMeta {
|
|
17
|
+
id: string;
|
|
18
|
+
/** First turn's requirement, truncated. Empty until the session runs a turn. */
|
|
19
|
+
title: string;
|
|
20
|
+
createdAt: number;
|
|
21
|
+
lastActiveAt: number;
|
|
22
|
+
/** Highest turn number the session has recorded. */
|
|
23
|
+
turnCount: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function sessionsRoot(workDir: string): string;
|
|
26
|
+
export declare function sessionDir(workDir: string, id: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Read the session index, newest-first by last activity.
|
|
29
|
+
*
|
|
30
|
+
* Never throws, like `loadTurns`: a missing or corrupt index means "no sessions
|
|
31
|
+
* the picker can show", which is always a safe place to start from. A single bad
|
|
32
|
+
* entry is dropped rather than losing the whole list.
|
|
33
|
+
*/
|
|
34
|
+
export declare function listSessions(workDir: string): SessionMeta[];
|
|
35
|
+
/** The most recently active session, or undefined when there is none. */
|
|
36
|
+
export declare function latestSession(workDir: string): SessionMeta | undefined;
|
|
37
|
+
/**
|
|
38
|
+
* Register a new, empty session and return its metadata.
|
|
39
|
+
*
|
|
40
|
+
* The directory is not created here — `appendTurns` and `saveJournal` both
|
|
41
|
+
* `mkdir` lazily, so a session that is created and never run leaves nothing but
|
|
42
|
+
* an index entry behind.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createSession(workDir: string): SessionMeta;
|
|
45
|
+
/** Remove a session's directory and its index entry. Idempotent. */
|
|
46
|
+
export declare function deleteSession(workDir: string, id: string): void;
|
|
47
|
+
/** Update a session's summary in the index. `lastActiveAt` defaults to now. */
|
|
48
|
+
export declare function touchSession(workDir: string, id: string, patch: Partial<Pick<SessionMeta, "title" | "turnCount" | "lastActiveAt">>): void;
|
package/dist/tui/state.d.ts
CHANGED
|
@@ -210,6 +210,27 @@ export type TuiAction = {
|
|
|
210
210
|
/** Flip whether tool detail is shown in the stream. */
|
|
211
211
|
| {
|
|
212
212
|
type: "verbose:toggle";
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Start a fresh session in place: empty history, turn counter back to zero,
|
|
216
|
+
* roster reseeded. Carries the seeded agents and leader ids the same way
|
|
217
|
+
* `turn:start` does, so no module-level object is shared into live state.
|
|
218
|
+
*/
|
|
219
|
+
| {
|
|
220
|
+
type: "session:reset";
|
|
221
|
+
agents: Record<string, AgentView>;
|
|
222
|
+
leaderIds: string[];
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Switch to an existing session: like `session:reset`, but seeded with that
|
|
226
|
+
* session's finished turns so its history is on screen and the turn counter
|
|
227
|
+
* continues from where it left off.
|
|
228
|
+
*/
|
|
229
|
+
| {
|
|
230
|
+
type: "session:load";
|
|
231
|
+
agents: Record<string, AgentView>;
|
|
232
|
+
leaderIds: string[];
|
|
233
|
+
history: TurnView[];
|
|
213
234
|
};
|
|
214
235
|
export declare function stripAnsi(text: string): string;
|
|
215
236
|
/** Append a chunk, keeping only the last {@link MAX_LOG_CHARS} characters. */
|