@xgspz/cli-ui 0.6.15 → 0.6.16
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/CHANGELOG.md +24 -0
- package/README.md +21 -1
- package/dist/{cli-ui-embed-vIXVNs6k.js → cli-ui-embed-D9xgur2y.js} +18299 -18587
- package/dist/{cli-ui-index-R24x28OX.js → cli-ui-index-WBtxmw23.js} +2 -2
- package/dist/{cli-ui-jszip.min-DUghzByz.js → cli-ui-jszip.min-CU3ZzoXb.js} +1 -1
- package/dist/{cli-ui-jszip.min-DfH6rdQD.js → cli-ui-jszip.min-DdU0X3VJ.js} +2 -2
- package/dist/{cli-ui-xlsx-CP0lJhff.js → cli-ui-xlsx-Dx6T7qhC.js} +1 -1
- package/dist/cli-ui.css +1 -1
- package/dist/cli-ui.js +1 -1
- package/package.json +1 -1
- package/src/board/ChangesPane.tsx +10 -4
- package/src/board/CliBoard.tsx +48 -16
- package/src/board/FileViewer.tsx +34 -1
- package/src/board/diffRefresh.ts +63 -0
- package/src/board/linkage.ts +68 -0
- package/src/board/pathResolve.ts +108 -0
- package/src/chat/CliChat.tsx +76 -181
- package/src/chat/Composer.tsx +4 -3
- package/src/core/conversation.ts +9 -1
- package/src/core/store.ts +112 -38
- package/src/msglist/CliMsgList.tsx +3 -2
- package/src/operator/node/AgentConnect.tsx +19 -15
- package/src/operator/node/AppearancePanel.tsx +1 -1
- package/src/operator/node/BranchNavigator.tsx +1 -1
- package/src/operator/node/BrowserPane.tsx +1 -1
- package/src/operator/node/Chat.tsx +16 -261
- package/src/operator/node/CommandPalette.tsx +1 -1
- package/src/operator/node/Composer.tsx +1 -1
- package/src/operator/node/Layout.tsx +1 -1
- package/src/operator/node/Markdown.tsx +6 -36
- package/src/operator/node/Modal.tsx +1 -1
- package/src/operator/node/OnboardingWizard.tsx +9 -5
- package/src/operator/node/Orchestrator.tsx +1 -1
- package/src/operator/node/ProjectLanding.tsx +1 -1
- package/src/operator/node/ProjectsColumn.tsx +1 -1
- package/src/operator/node/ProviderAuth.tsx +12 -10
- package/src/operator/node/RunSwitch.tsx +1 -1
- package/src/operator/node/Services.tsx +1 -1
- package/src/operator/node/SessionRail.tsx +27 -8
- package/src/operator/node/SessionView.tsx +1 -1
- package/src/operator/node/SettingsView.tsx +1 -1
- package/src/operator/node/TaskBoard.tsx +1 -1
- package/src/operator/node/TaskChanges.tsx +34 -5
- package/src/operator/node/TaskHero.tsx +1 -1
- package/src/operator/node/TasksColumn.tsx +1 -1
- package/src/operator/node/Transcript.tsx +10 -237
- package/src/operator/node/Welcome.tsx +1 -1
- package/src/operator/node/WorktreePrune.tsx +1 -1
- package/src/operator/node/boardInteraction.ts +6 -3
- package/src/operator/node/boardState.ts +12 -13
- package/src/operator/node/fileLinks.ts +4 -38
- package/src/operator/node/format.ts +12 -104
- package/src/operator/node/github.tsx +10 -8
- package/src/operator/node/modals.tsx +15 -13
- package/src/operator/node/recheckOnReturn.ts +28 -0
- package/src/operator/node/shared.tsx +10 -50
- package/src/operator/node/useTaskStream.ts +2 -2
- package/src/styles.css +5 -0
- package/src/ui/Markdown.tsx +36 -21
- package/src/ui/Transcript.tsx +8 -5
- package/src/ui/fileLinks.ts +6 -3
- package/src/ui/format.ts +11 -5
- package/src/ui/shared.tsx +11 -7
- package/src/ui/transcriptModel.ts +40 -9
- package/src/ui/useTranscriptWindow.ts +253 -0
- package/src/operator/node/boundedPoll.ts +0 -47
- package/src/operator/node/icons.tsx +0 -88
- package/src/operator/node/transcriptModel.ts +0 -129
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
// Pure helpers behind the live transcript (useTaskStream + SessionView), kept
|
|
2
|
-
// free of React so they can be unit-tested. The rules they encode exist for
|
|
3
|
-
// long tasks: thousands of messages, megabytes of tool output.
|
|
4
|
-
|
|
5
|
-
import type { ToolData } from "./protocol/types";
|
|
6
|
-
import type { Msg } from "./types";
|
|
7
|
-
import { buildSessions } from "./format";
|
|
8
|
-
|
|
9
|
-
/** Messages the live view's snapshot carries (GET /messages?tail=N). */
|
|
10
|
-
export const SNAPSHOT_TAIL = 300;
|
|
11
|
-
/** Older messages fetched per page from /history. */
|
|
12
|
-
export const HISTORY_PAGE = 300;
|
|
13
|
-
/** Transcript rows kept in the DOM while following the newest output. */
|
|
14
|
-
export const RENDER_WINDOW = 120;
|
|
15
|
-
/** Rows revealed per step when scrolling back (or forward again). */
|
|
16
|
-
export const RENDER_STEP = 80;
|
|
17
|
-
/** Most rows ever in the DOM at once; the far end is dropped beyond this. */
|
|
18
|
-
export const RENDER_MAX = 360;
|
|
19
|
-
/**
|
|
20
|
-
* Transcripts one tab keeps in memory: the open task plus the most recently
|
|
21
|
-
* left ones (switching back to those shows at once). Each kept one is trimmed
|
|
22
|
-
* to a snapshot's size when left (trimToTail), so on real node data this is
|
|
23
|
-
* ~10 MB typical, well under 100 MB at worst. Per tab: several tabs on the
|
|
24
|
-
* same task each keep their own.
|
|
25
|
-
*/
|
|
26
|
-
export const KEEP_TRANSCRIPTS = 30;
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* A transcript the tab is leaving, brought back to what a fresh snapshot holds
|
|
30
|
-
* (the newest `tail` messages; `older` grows by what was dropped), so a task
|
|
31
|
-
* read far back doesn't keep thousands of messages around in the background.
|
|
32
|
-
* Returns the input untouched when it is already that small.
|
|
33
|
-
*/
|
|
34
|
-
export function trimToTail(msgs: Msg[], older: number, tail = SNAPSHOT_TAIL): { msgs: Msg[]; older: number } {
|
|
35
|
-
const queued = msgs.filter((m) => m.role === "queued");
|
|
36
|
-
const rest = msgs.filter((m) => m.role !== "queued");
|
|
37
|
-
if (rest.length <= tail) return { msgs, older };
|
|
38
|
-
return { msgs: [...rest.slice(rest.length - tail), ...queued], older: older + rest.length - tail };
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** The tab's kept-transcript order after opening `id`, and which tasks fall out. */
|
|
42
|
-
export function touchRecent(recent: string[], id: string, keep = KEEP_TRANSCRIPTS): { recent: string[]; evicted: string[] } {
|
|
43
|
-
const next = [id, ...recent.filter((t) => t !== id)];
|
|
44
|
-
return { recent: next.slice(0, keep), evicted: next.slice(keep) };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Parsed tool payloads, cached per message object. Message objects are replaced
|
|
48
|
-
// whenever their content changes, so a cached entry can never go stale, and the
|
|
49
|
-
// transcript stops re-parsing every tool row's JSON on each live event.
|
|
50
|
-
const toolCache = new WeakMap<Msg, ToolData>();
|
|
51
|
-
/** The tool card behind a "tool" message. Read-only: never mutate the result. */
|
|
52
|
-
export function toolDataOf(m: Msg): ToolData {
|
|
53
|
-
let data = toolCache.get(m);
|
|
54
|
-
if (!data) {
|
|
55
|
-
try { data = JSON.parse(m.content) as ToolData; } catch { data = { title: m.content }; }
|
|
56
|
-
toolCache.set(m, data);
|
|
57
|
-
}
|
|
58
|
-
return data;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Index of the message with this id, searching from the newest end (where live updates land). */
|
|
62
|
-
export function indexFromEnd(arr: Msg[], id: string): number {
|
|
63
|
-
for (let i = arr.length - 1; i >= 0; i--) if (arr[i].id === id) return i;
|
|
64
|
-
return -1;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/** True when an AskUserQuestion card near the end is still unanswered. */
|
|
68
|
-
export function hasOpenAsk(messages: Msg[], scan = 400): boolean {
|
|
69
|
-
for (let i = messages.length - 1, n = 0; i >= 0 && n < scan; i--, n++) {
|
|
70
|
-
const m = messages[i];
|
|
71
|
-
if (m.role !== "tool") continue;
|
|
72
|
-
const d = toolDataOf(m);
|
|
73
|
-
if (d.ask && !d.ask.answers) return true;
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Apply a (re)connect snapshot to what the tab already holds.
|
|
80
|
-
*
|
|
81
|
-
* - Unchanged messages keep their object identity, so memoized rows don't
|
|
82
|
-
* re-render (and re-parse their markdown) on every reconnect.
|
|
83
|
-
* - Older pages the user already scrolled in are kept when the snapshot's tail
|
|
84
|
-
* still overlaps them; `older` is reduced by what was kept.
|
|
85
|
-
*/
|
|
86
|
-
export function mergeSnapshot(prev: Msg[] | undefined, committed: Msg[], queued: Msg[], older: number): { msgs: Msg[]; older: number } {
|
|
87
|
-
const byId = new Map<string, Msg>();
|
|
88
|
-
for (const m of prev ?? []) byId.set(m.id, m);
|
|
89
|
-
const reuse = (m: Msg): Msg => {
|
|
90
|
-
const p = byId.get(m.id);
|
|
91
|
-
if (!p || p.role !== m.role || p.content !== m.content || p.generation !== m.generation) return m;
|
|
92
|
-
return p.ts === m.ts ? p : { ...p, ts: m.ts };
|
|
93
|
-
};
|
|
94
|
-
const fresh = committed.map(reuse);
|
|
95
|
-
let kept: Msg[] = [];
|
|
96
|
-
if (prev && fresh.length && older > 0) {
|
|
97
|
-
const at = prev.findIndex((m) => m.id === fresh[0].id);
|
|
98
|
-
// Only persisted rows (they carry ts) — local notices died with the old view.
|
|
99
|
-
if (at > 0) kept = prev.slice(0, at).filter((m) => m.role !== "queued" && m.ts != null);
|
|
100
|
-
}
|
|
101
|
-
return { msgs: [...kept, ...fresh, ...queued.map(reuse)], older: Math.max(0, older - kept.length) };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export type TranscriptItem =
|
|
105
|
-
| { kind: "label"; key: string; n: number; current: boolean }
|
|
106
|
-
| { kind: "break"; key: string; summary: string }
|
|
107
|
-
| { kind: "msg"; key: string; m: Msg; initial: boolean; hideWho: boolean };
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* The transcript as a flat row list (session labels, /clear breaks, messages),
|
|
111
|
-
* so the view can render any window of it. `hasOlder`: earlier messages aren't
|
|
112
|
-
* loaded yet, so the first loaded one is not necessarily its session's opener.
|
|
113
|
-
*/
|
|
114
|
-
export function buildTranscriptItems(messages: Msg[], hasOlder: boolean): TranscriptItem[] {
|
|
115
|
-
const sessions = buildSessions(messages);
|
|
116
|
-
const items: TranscriptItem[] = [];
|
|
117
|
-
sessions.forEach((s, si) => {
|
|
118
|
-
if (si > 0 && s.summaryBefore) items.push({ kind: "break", key: `b-${s.n}`, summary: s.summaryBefore });
|
|
119
|
-
items.push({ kind: "label", key: `s-${s.n}`, n: s.n, current: si === sessions.length - 1 });
|
|
120
|
-
s.messages.forEach((m, mi) => {
|
|
121
|
-
const prev = s.messages[mi - 1];
|
|
122
|
-
// collapse the repeated agent header across an assistant run (text → tool → text)
|
|
123
|
-
const hideWho = m.role === "assistant" && !!prev && (prev.role === "assistant" || prev.role === "tool");
|
|
124
|
-
const initial = mi === 0 && m.role === "user" && !(si === 0 && hasOlder);
|
|
125
|
-
items.push({ kind: "msg", key: m.id, m, initial, hideWho });
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
return items;
|
|
129
|
-
}
|