@multiclawcli/dashboard 0.10.0 → 0.11.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/format.d.ts +11 -0
- package/dist/tui/state.d.ts +7 -2
- package/dist/tui.js +89 -28
- package/package.json +3 -2
package/dist/tui/format.d.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import type { SupplementReport } from "@multiclawcli/core";
|
|
2
|
+
import type { LogSegment } from "./state";
|
|
3
|
+
/**
|
|
4
|
+
* Render a structured log buffer to a single ANSI string ready for `wrapLog`.
|
|
5
|
+
*
|
|
6
|
+
* - `text` segments are rendered as markdown via md4x (with `heal:true` to
|
|
7
|
+
* handle streaming-incomplete syntax gracefully).
|
|
8
|
+
* - `tool` segments are prefixed with a dim-cyan glyph.
|
|
9
|
+
* - `system` segments (errors, retries, steer notes) are rendered dim yellow.
|
|
10
|
+
* - `tool-detail` segments are already filtered at storage time; skipped here.
|
|
11
|
+
*/
|
|
12
|
+
export declare function renderSegments(segments: LogSegment[], _width: number): string;
|
|
2
13
|
/**
|
|
3
14
|
* Truncate to `max` columns, ending in an ellipsis when it does not fit.
|
|
4
15
|
*
|
package/dist/tui/state.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ import type { AcceptanceResult, AgentDefinition, AgentOutput, AgentStatus, Leade
|
|
|
7
7
|
* renders a tail window, so only the tail is worth keeping.
|
|
8
8
|
*/
|
|
9
9
|
export declare const MAX_LOG_CHARS = 8000;
|
|
10
|
+
export type LogKind = "text" | "tool" | "tool-detail" | "system";
|
|
11
|
+
export interface LogSegment {
|
|
12
|
+
kind: LogKind;
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
10
15
|
export interface AgentView {
|
|
11
16
|
id: string;
|
|
12
17
|
name: string;
|
|
@@ -38,7 +43,7 @@ export interface AgentView {
|
|
|
38
43
|
* re-exploring from scratch.
|
|
39
44
|
*/
|
|
40
45
|
sessionId?: string;
|
|
41
|
-
logs:
|
|
46
|
+
logs: LogSegment[];
|
|
42
47
|
}
|
|
43
48
|
export type TuiPhase = "idle" | "running" | "complete";
|
|
44
49
|
export interface TuiStageInfo extends StageInfo {
|
|
@@ -234,7 +239,7 @@ export type TuiAction = {
|
|
|
234
239
|
};
|
|
235
240
|
export declare function stripAnsi(text: string): string;
|
|
236
241
|
/** Append a chunk, keeping only the last {@link MAX_LOG_CHARS} characters. */
|
|
237
|
-
export declare function appendLog(previous:
|
|
242
|
+
export declare function appendLog(previous: LogSegment[], chunk: string, kind?: LogKind): LogSegment[];
|
|
238
243
|
/** Every agent that takes part in a run, in declaration order: leader first. */
|
|
239
244
|
export declare function plannedAgents(config: MultiClawConfig): AgentDefinition[];
|
|
240
245
|
export declare function leaderAgentIds(config: MultiClawConfig): string[];
|
package/dist/tui.js
CHANGED
|
@@ -260,7 +260,35 @@ function resolveAgentId(input, ids) {
|
|
|
260
260
|
import { Box, Text } from "ink";
|
|
261
261
|
|
|
262
262
|
// src/tui/format.ts
|
|
263
|
+
import { renderToAnsi } from "md4x/napi";
|
|
263
264
|
import stringWidth from "string-width";
|
|
265
|
+
var DIM = "\x1B[2m";
|
|
266
|
+
var CYAN = "\x1B[36m";
|
|
267
|
+
var YELLOW = "\x1B[33m";
|
|
268
|
+
var RESET = "\x1B[0m";
|
|
269
|
+
function renderSegments(segments, _width) {
|
|
270
|
+
const parts = [];
|
|
271
|
+
let textBuf = "";
|
|
272
|
+
const flushText = () => {
|
|
273
|
+
if (!textBuf) return;
|
|
274
|
+
parts.push(renderToAnsi(textBuf, { heal: true }));
|
|
275
|
+
textBuf = "";
|
|
276
|
+
};
|
|
277
|
+
for (const seg of segments) {
|
|
278
|
+
if (seg.kind === "text") {
|
|
279
|
+
textBuf += seg.text;
|
|
280
|
+
} else {
|
|
281
|
+
flushText();
|
|
282
|
+
if (seg.kind === "tool") {
|
|
283
|
+
parts.push(`${DIM}${CYAN}\u25B8 ${seg.text}${RESET}`);
|
|
284
|
+
} else if (seg.kind === "system") {
|
|
285
|
+
parts.push(`${DIM}${YELLOW}${seg.text}${RESET}`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
flushText();
|
|
290
|
+
return parts.join("\n");
|
|
291
|
+
}
|
|
264
292
|
function truncate(text, max) {
|
|
265
293
|
if (max <= 0) return "";
|
|
266
294
|
if (text.length <= max) return text;
|
|
@@ -650,12 +678,29 @@ var initialState = {
|
|
|
650
678
|
function stripAnsi(text) {
|
|
651
679
|
return text.replace(ANSI_PATTERN, "");
|
|
652
680
|
}
|
|
653
|
-
function appendLog(previous, chunk) {
|
|
654
|
-
const
|
|
655
|
-
|
|
681
|
+
function appendLog(previous, chunk, kind = "text") {
|
|
682
|
+
const text = stripAnsi(chunk);
|
|
683
|
+
if (!text) return previous;
|
|
684
|
+
const last = previous[previous.length - 1];
|
|
685
|
+
const next = last?.kind === kind ? [...previous.slice(0, -1), { kind, text: last.text + text }] : [...previous, { kind, text }];
|
|
686
|
+
let total = next.reduce((sum, s) => sum + s.text.length, 0);
|
|
687
|
+
if (total <= MAX_LOG_CHARS) return next;
|
|
688
|
+
const result = [...next];
|
|
689
|
+
while (total > MAX_LOG_CHARS && result.length > 0) {
|
|
690
|
+
const first = result[0];
|
|
691
|
+
const excess = total - MAX_LOG_CHARS;
|
|
692
|
+
if (first.text.length <= excess) {
|
|
693
|
+
total -= first.text.length;
|
|
694
|
+
result.shift();
|
|
695
|
+
} else {
|
|
696
|
+
result[0] = { ...first, text: first.text.slice(excess) };
|
|
697
|
+
break;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return result;
|
|
656
701
|
}
|
|
657
702
|
function blankAgent(id, name, patch = {}) {
|
|
658
|
-
return { id, name, status: "pending", logs:
|
|
703
|
+
return { id, name, status: "pending", logs: [], ...patch };
|
|
659
704
|
}
|
|
660
705
|
function epoch(iso) {
|
|
661
706
|
const value = Date.parse(iso);
|
|
@@ -787,7 +832,7 @@ function reduceEvent(state, event) {
|
|
|
787
832
|
error: void 0,
|
|
788
833
|
usage: void 0,
|
|
789
834
|
costUsd: void 0,
|
|
790
|
-
logs: existing.startedAt !== void 0 && existing.logs ? appendLog(existing.logs, "
|
|
835
|
+
logs: existing.startedAt !== void 0 && existing.logs.length > 0 ? appendLog(existing.logs, "[agent restarted]", "system") : existing.logs
|
|
791
836
|
} : blankAgent(agentId, agentName, {
|
|
792
837
|
icon,
|
|
793
838
|
model,
|
|
@@ -813,7 +858,7 @@ function reduceEvent(state, event) {
|
|
|
813
858
|
...state,
|
|
814
859
|
agents: {
|
|
815
860
|
...state.agents,
|
|
816
|
-
[agentId]: { ...existing, logs: appendLog(existing.logs, chunk) }
|
|
861
|
+
[agentId]: { ...existing, logs: appendLog(existing.logs, chunk, kind ?? "text") }
|
|
817
862
|
}
|
|
818
863
|
};
|
|
819
864
|
}
|
|
@@ -835,11 +880,10 @@ function reduceEvent(state, event) {
|
|
|
835
880
|
const existing = state.agents[result.agentId];
|
|
836
881
|
const next = existing ? { ...existing, ...patch } : blankAgent(result.agentId, result.agentName, patch);
|
|
837
882
|
if (event.type === "agent:failed") {
|
|
838
|
-
if (next.logs ===
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
`);
|
|
883
|
+
if (next.logs.length === 0 && result.output)
|
|
884
|
+
next.logs = appendLog([], result.output, "text");
|
|
885
|
+
if (result.error && !next.logs.some((s) => s.text.includes(stripAnsi(result.error)))) {
|
|
886
|
+
next.logs = appendLog(next.logs, result.error, "system");
|
|
843
887
|
}
|
|
844
888
|
}
|
|
845
889
|
return { ...state, agents: { ...state.agents, [result.agentId]: next } };
|
|
@@ -852,12 +896,14 @@ function reduceEvent(state, event) {
|
|
|
852
896
|
}
|
|
853
897
|
case "agent:retrying": {
|
|
854
898
|
const { agentId, agentName, attempt, maxAttempts, error } = event.payload;
|
|
855
|
-
const note = `
|
|
856
|
-
[retry ${attempt}/${maxAttempts}] ${error}
|
|
857
|
-
`;
|
|
899
|
+
const note = `[retry ${attempt}/${maxAttempts}] ${error}`;
|
|
858
900
|
const existing = state.agents[agentId];
|
|
859
901
|
const retry = { attempt, max: maxAttempts };
|
|
860
|
-
const next = existing ? { ...existing, status: "retrying", retry, logs: appendLog(existing.logs, note) } : blankAgent(agentId, agentName, {
|
|
902
|
+
const next = existing ? { ...existing, status: "retrying", retry, logs: appendLog(existing.logs, note, "system") } : blankAgent(agentId, agentName, {
|
|
903
|
+
status: "retrying",
|
|
904
|
+
retry,
|
|
905
|
+
logs: appendLog([], note, "system")
|
|
906
|
+
});
|
|
861
907
|
return { ...state, agents: { ...state.agents, [agentId]: next } };
|
|
862
908
|
}
|
|
863
909
|
case "orchestration:warning":
|
|
@@ -880,7 +926,9 @@ function reduceEvent(state, event) {
|
|
|
880
926
|
}
|
|
881
927
|
}
|
|
882
928
|
function wrapLog(logs, width) {
|
|
883
|
-
|
|
929
|
+
if (logs.length === 0) return [];
|
|
930
|
+
const rendered = renderSegments(logs, width);
|
|
931
|
+
return rendered.trimEnd().split("\n").flatMap((line) => wrapLine(line, width));
|
|
884
932
|
}
|
|
885
933
|
function focusedRowCount(turn, width) {
|
|
886
934
|
const agent = turn.focusId ? turn.agents[turn.focusId] : void 0;
|
|
@@ -1052,7 +1100,7 @@ function tuiReducer(state, action) {
|
|
|
1052
1100
|
const id = turn.focusId;
|
|
1053
1101
|
const agent = id ? turn.agents[id] : void 0;
|
|
1054
1102
|
if (!id || !agent) return turn;
|
|
1055
|
-
return { ...turn, agents: { ...turn.agents, [id]: { ...agent, logs:
|
|
1103
|
+
return { ...turn, agents: { ...turn.agents, [id]: { ...agent, logs: [] } }, scroll: 0 };
|
|
1056
1104
|
});
|
|
1057
1105
|
case "stream:note": {
|
|
1058
1106
|
const agent = state.agents[action.agentId];
|
|
@@ -1061,9 +1109,7 @@ function tuiReducer(state, action) {
|
|
|
1061
1109
|
...state,
|
|
1062
1110
|
agents: {
|
|
1063
1111
|
...state.agents,
|
|
1064
|
-
[action.agentId]: { ...agent, logs: appendLog(agent.logs,
|
|
1065
|
-
${action.text}
|
|
1066
|
-
`) }
|
|
1112
|
+
[action.agentId]: { ...agent, logs: appendLog(agent.logs, action.text, "system") }
|
|
1067
1113
|
}
|
|
1068
1114
|
};
|
|
1069
1115
|
}
|
|
@@ -1990,7 +2036,7 @@ function Prompt({
|
|
|
1990
2036
|
const caret = line.caret;
|
|
1991
2037
|
const rest = caret === void 0 ? "" : line.text.slice(caret);
|
|
1992
2038
|
const cell = [...ghost ? hint : rest][0] ?? " ";
|
|
1993
|
-
const
|
|
2039
|
+
const tail = ghost ? [...hint].slice(1).join("") : rest.slice(cell.length);
|
|
1994
2040
|
return (
|
|
1995
2041
|
// biome-ignore lint/suspicious/noArrayIndexKey: a text row has no identity beyond its position — the whole prompt is rebuilt on every keystroke anyway
|
|
1996
2042
|
/* @__PURE__ */ jsxs5(Box5, { flexShrink: 0, children: [
|
|
@@ -1998,7 +2044,7 @@ function Prompt({
|
|
|
1998
2044
|
caret === void 0 ? /* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children: line.text }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
|
|
1999
2045
|
/* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children: line.text.slice(0, caret) }),
|
|
2000
2046
|
/* @__PURE__ */ jsx5(Text5, { inverse: caretVisible, dimColor: !caretVisible && ghost, children: cell }),
|
|
2001
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children:
|
|
2047
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: ghost, children: tail })
|
|
2002
2048
|
] })
|
|
2003
2049
|
] }, start + index)
|
|
2004
2050
|
);
|
|
@@ -2207,7 +2253,8 @@ function formatTurnMarkdown(turn) {
|
|
|
2207
2253
|
lines.push("");
|
|
2208
2254
|
lines.push("## Logs", "");
|
|
2209
2255
|
for (const agent of agents) {
|
|
2210
|
-
|
|
2256
|
+
const logsText = agent.logs.map((s) => s.text).join("");
|
|
2257
|
+
lines.push(`### ${agent.id}: ${agent.name}`, "", "```", logsText ?? "", "```", "");
|
|
2211
2258
|
}
|
|
2212
2259
|
}
|
|
2213
2260
|
return `${lines.join("\n").trimEnd()}
|
|
@@ -2237,7 +2284,7 @@ var STATUSES = /* @__PURE__ */ new Set([
|
|
|
2237
2284
|
]);
|
|
2238
2285
|
function isAgentView(value) {
|
|
2239
2286
|
if (!isObject2(value)) return false;
|
|
2240
|
-
return typeof value.id === "string" && typeof value.name === "string" &&
|
|
2287
|
+
return typeof value.id === "string" && typeof value.name === "string" && Array.isArray(value.logs) && typeof value.status === "string" && STATUSES.has(value.status);
|
|
2241
2288
|
}
|
|
2242
2289
|
function isJournalTurn(value) {
|
|
2243
2290
|
if (!isObject2(value)) return false;
|
|
@@ -2265,13 +2312,27 @@ function saveJournal(workDir, sessionId, journal) {
|
|
|
2265
2312
|
if (isEmpty(journal) && !fs4.existsSync(target)) return Promise.resolve();
|
|
2266
2313
|
return writeJsonFile(target, JSON.stringify(journal));
|
|
2267
2314
|
}
|
|
2268
|
-
function
|
|
2269
|
-
|
|
2315
|
+
function tailSegments(segs, max) {
|
|
2316
|
+
let total = segs.reduce((sum, s) => sum + s.text.length, 0);
|
|
2317
|
+
if (total <= max) return segs;
|
|
2318
|
+
const result = [...segs];
|
|
2319
|
+
while (total > max && result.length > 0) {
|
|
2320
|
+
const first = result[0];
|
|
2321
|
+
const excess = total - max;
|
|
2322
|
+
if (first.text.length <= excess) {
|
|
2323
|
+
total -= first.text.length;
|
|
2324
|
+
result.shift();
|
|
2325
|
+
} else {
|
|
2326
|
+
result[0] = { ...first, text: `\u2026${first.text.slice(excess)}` };
|
|
2327
|
+
break;
|
|
2328
|
+
}
|
|
2329
|
+
}
|
|
2330
|
+
return result;
|
|
2270
2331
|
}
|
|
2271
2332
|
function journalTurn(state, pending) {
|
|
2272
2333
|
const agents = {};
|
|
2273
2334
|
for (const [id, agent] of Object.entries(state.agents)) {
|
|
2274
|
-
agents[id] = { ...agent, logs:
|
|
2335
|
+
agents[id] = { ...agent, logs: tailSegments(agent.logs, MAX_JOURNAL_LOG_CHARS) };
|
|
2275
2336
|
}
|
|
2276
2337
|
return {
|
|
2277
2338
|
turn: state.turn,
|
|
@@ -2919,7 +2980,7 @@ function App({
|
|
|
2919
2980
|
const argOpen = !helpOpen && !historyOpen && !sessionsOpen && arg !== void 0 && argRows.length > 0;
|
|
2920
2981
|
const paletteOpen = menuOpen || argOpen;
|
|
2921
2982
|
const front = interactions[0];
|
|
2922
|
-
const menu = front?.kind === "permission" && permissionMenu.id === front.shortId ? permissionMenu : { id: front?.shortId ?? "", cursor:
|
|
2983
|
+
const menu = front?.kind === "permission" && permissionMenu.id === front.shortId ? permissionMenu : { id: front?.shortId ?? "", cursor: 0, released: false };
|
|
2923
2984
|
const frontQuestion = front?.kind === "question" ? front : void 0;
|
|
2924
2985
|
const questionFields = useMemo(
|
|
2925
2986
|
() => frontQuestion ? formFields(frontQuestion.request) : [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@multiclawcli/dashboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./tui": {
|
|
@@ -25,8 +25,9 @@
|
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@multiclawcli/core": "0.
|
|
28
|
+
"@multiclawcli/core": "0.11.0",
|
|
29
29
|
"ink": "^8.0.0",
|
|
30
|
+
"md4x": "^0.0.30",
|
|
30
31
|
"react": "^19.3.1",
|
|
31
32
|
"string-width": "^8.3.0"
|
|
32
33
|
},
|