pi-one-ui 0.2.1 → 0.3.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/CHANGELOG.md +39 -0
- package/README.en.md +256 -0
- package/README.md +195 -72
- package/extensions/app/config/renderer.ts +5 -17
- package/extensions/app/config/shell.ts +118 -422
- package/extensions/app/config/store.ts +23 -92
- package/extensions/app/presets.ts +1 -2
- package/extensions/layouts/context/renderer/tool/grouping.ts +267 -89
- package/extensions/layouts/context/renderer/tool/result.ts +76 -0
- package/package.json +10 -1
|
@@ -19,20 +19,12 @@ export type ConfigRecord = Record<string, unknown>;
|
|
|
19
19
|
|
|
20
20
|
export type ConfigStorePaths = {
|
|
21
21
|
canonical: string;
|
|
22
|
-
legacyUnified: string;
|
|
23
|
-
legacyShell: string;
|
|
24
|
-
legacyRenderer: string;
|
|
25
22
|
};
|
|
26
23
|
|
|
27
|
-
/**
|
|
28
|
-
* Returns canonical and legacy configuration locations for an agent directory.
|
|
29
|
-
*/
|
|
24
|
+
/** Returns the canonical configuration location for an agent directory. */
|
|
30
25
|
export function configPaths(agentDir = getAgentDir()): ConfigStorePaths {
|
|
31
26
|
return {
|
|
32
27
|
canonical: join(agentDir, "pi-one-ui.json"),
|
|
33
|
-
legacyUnified: join(agentDir, "pi-mine-ui.json"),
|
|
34
|
-
legacyShell: join(agentDir, "zentui.json"),
|
|
35
|
-
legacyRenderer: join(agentDir, "claude-code-style.json"),
|
|
36
28
|
};
|
|
37
29
|
}
|
|
38
30
|
|
|
@@ -138,7 +130,7 @@ function configReadError(path: string, error: unknown): Error {
|
|
|
138
130
|
}
|
|
139
131
|
|
|
140
132
|
/**
|
|
141
|
-
* Builds the
|
|
133
|
+
* Builds the safety error used when a config write is unsafe.
|
|
142
134
|
*/
|
|
143
135
|
function configWriteError(path: string, label: string, error: unknown): Error {
|
|
144
136
|
const detail = error instanceof Error ? ` (${error.message})` : "";
|
|
@@ -147,79 +139,15 @@ function configWriteError(path: string, label: string, error: unknown): Error {
|
|
|
147
139
|
);
|
|
148
140
|
}
|
|
149
141
|
|
|
150
|
-
|
|
151
|
-
record: ConfigRecord;
|
|
152
|
-
writePath: string;
|
|
153
|
-
mode?: number;
|
|
154
|
-
materialize: boolean;
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Reads an optional config source and turns corruption into an explicit error.
|
|
159
|
-
*/
|
|
160
|
-
function optionalConfig(
|
|
161
|
-
path: string,
|
|
162
|
-
): { record: ConfigRecord; writePath: string; mode: number } | undefined {
|
|
163
|
-
const state = readConfigFileState(path);
|
|
164
|
-
if (state.kind === "missing") return undefined;
|
|
165
|
-
if (state.kind === "corrupt") throw configReadError(path, state.error);
|
|
166
|
-
return state;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Selects the active source and the destination used by a later update.
|
|
171
|
-
*/
|
|
172
|
-
function selectUnifiedConfig(paths: ConfigStorePaths): SelectedConfig {
|
|
173
|
-
const canonical = optionalConfig(paths.canonical);
|
|
174
|
-
if (canonical) return { ...canonical, materialize: false };
|
|
175
|
-
|
|
176
|
-
const legacyUnified = optionalConfig(paths.legacyUnified);
|
|
177
|
-
if (legacyUnified)
|
|
178
|
-
return {
|
|
179
|
-
...legacyUnified,
|
|
180
|
-
writePath: paths.canonical,
|
|
181
|
-
materialize: true,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
const shell = optionalConfig(paths.legacyShell);
|
|
185
|
-
const renderer = optionalConfig(paths.legacyRenderer);
|
|
186
|
-
if (!shell && !renderer) {
|
|
187
|
-
return {
|
|
188
|
-
record: {},
|
|
189
|
-
writePath: paths.canonical,
|
|
190
|
-
materialize: false,
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return {
|
|
195
|
-
record: {
|
|
196
|
-
...(shell?.record ?? {}),
|
|
197
|
-
version: 1,
|
|
198
|
-
renderer: renderer?.record ?? {},
|
|
199
|
-
},
|
|
200
|
-
writePath: paths.canonical,
|
|
201
|
-
mode: shell?.mode ?? renderer?.mode,
|
|
202
|
-
materialize: true,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Read the one raw unified record used by all configuration domains.
|
|
208
|
-
* Legacy sources are best-effort materialized through the same atomic writer;
|
|
209
|
-
* the in-memory record remains usable if the agent directory is read-only.
|
|
210
|
-
*/
|
|
142
|
+
/** Reads the raw canonical record used by every configuration domain. */
|
|
211
143
|
export function readUnifiedConfigRecord(
|
|
212
144
|
paths: ConfigStorePaths = defaultConfigPaths,
|
|
213
145
|
): ConfigRecord {
|
|
214
|
-
const
|
|
215
|
-
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// Keep using the selected legacy record in memory when migration cannot be written.
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return selected.record;
|
|
146
|
+
const state = readConfigFileState(paths.canonical);
|
|
147
|
+
if (state.kind === "missing") return {};
|
|
148
|
+
if (state.kind === "corrupt")
|
|
149
|
+
throw configReadError(paths.canonical, state.error);
|
|
150
|
+
return state.record;
|
|
223
151
|
}
|
|
224
152
|
|
|
225
153
|
/**
|
|
@@ -247,22 +175,18 @@ export type ConfigStoreListener = (record: ConfigRecord) => void;
|
|
|
247
175
|
|
|
248
176
|
/**
|
|
249
177
|
* Process-wide raw configuration store. Domain config modules own parsing and
|
|
250
|
-
* selectors; this module owns
|
|
178
|
+
* selectors; this module owns canonical persistence.
|
|
251
179
|
*/
|
|
252
180
|
export class ConfigStore {
|
|
253
181
|
readonly paths: ConfigStorePaths;
|
|
254
182
|
private readonly listeners = new Set<ConfigStoreListener>();
|
|
255
183
|
|
|
256
|
-
/**
|
|
257
|
-
* Creates a store for canonical and legacy paths.
|
|
258
|
-
*/
|
|
184
|
+
/** Creates a store for the canonical path. */
|
|
259
185
|
constructor(paths: ConfigStorePaths = defaultConfigPaths) {
|
|
260
186
|
this.paths = paths;
|
|
261
187
|
}
|
|
262
188
|
|
|
263
|
-
/**
|
|
264
|
-
* Reads the current canonical or legacy-backed raw configuration record.
|
|
265
|
-
*/
|
|
189
|
+
/** Reads the current canonical raw configuration record. */
|
|
266
190
|
read(): ConfigRecord {
|
|
267
191
|
return readUnifiedConfigRecord(this.paths);
|
|
268
192
|
}
|
|
@@ -271,11 +195,18 @@ export class ConfigStore {
|
|
|
271
195
|
* Mutates and atomically persists the active raw configuration record.
|
|
272
196
|
*/
|
|
273
197
|
update(mutate: (record: ConfigRecord) => void): ConfigRecord {
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
198
|
+
const state = readConfigFileState(this.paths.canonical);
|
|
199
|
+
if (state.kind === "corrupt")
|
|
200
|
+
throw configReadError(this.paths.canonical, state.error);
|
|
201
|
+
|
|
202
|
+
mutate(state.record);
|
|
203
|
+
writeConfigAtomically(
|
|
204
|
+
state.writePath,
|
|
205
|
+
state.record,
|
|
206
|
+
state.kind === "valid" ? state.mode : undefined,
|
|
207
|
+
);
|
|
208
|
+
for (const listener of this.listeners) listener(state.record);
|
|
209
|
+
return state.record;
|
|
279
210
|
}
|
|
280
211
|
|
|
281
212
|
/**
|
|
@@ -21,7 +21,7 @@ export function applyPreset(preset: Preset): void {
|
|
|
21
21
|
saveUserMessagesComponentPatch({ enabled: false });
|
|
22
22
|
saveWorkingLineComponentPatch({ enabled: false });
|
|
23
23
|
saveFooterComponentPatch({ style: "native" });
|
|
24
|
-
updateRendererConfig({ mode: "off"
|
|
24
|
+
updateRendererConfig({ mode: "off" });
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -31,6 +31,5 @@ export function applyPreset(preset: Preset): void {
|
|
|
31
31
|
saveFooterComponentPatch({ style: "starship" });
|
|
32
32
|
updateRendererConfig({
|
|
33
33
|
mode: preset === "compact" ? "compact" : "on",
|
|
34
|
-
enableWorkingMessage: false,
|
|
35
34
|
});
|
|
36
35
|
}
|
|
@@ -9,10 +9,7 @@ import {
|
|
|
9
9
|
visibleWidth,
|
|
10
10
|
type Component,
|
|
11
11
|
} from "@earendil-works/pi-tui";
|
|
12
|
-
import {
|
|
13
|
-
TOOL_LOADING_INTERVAL_MS,
|
|
14
|
-
toolLoadingIcon,
|
|
15
|
-
} from "../../../../tools/tool-loading-icon.ts";
|
|
12
|
+
import { toolLoadingIcon } from "../../../../tools/tool-loading-icon.ts";
|
|
16
13
|
import { isToolTuiFullscreen, showMoreHintText } from "./show-more-hint.ts";
|
|
17
14
|
import {
|
|
18
15
|
stripAnsi,
|
|
@@ -21,6 +18,15 @@ import {
|
|
|
21
18
|
} from "../../../../tools/ansi-text.ts";
|
|
22
19
|
import { walkComponentTree } from "../../../../tools/component-tree.ts";
|
|
23
20
|
import { humanizeToolLabel, toolCallSummary } from "./names.ts";
|
|
21
|
+
import {
|
|
22
|
+
captureIoViewMarkers,
|
|
23
|
+
getActiveIoViewFrame,
|
|
24
|
+
isExpandedToolIoView,
|
|
25
|
+
replayIoViewMarkers,
|
|
26
|
+
type CapturedIoViewMarker,
|
|
27
|
+
type ExpandedToolIoView,
|
|
28
|
+
type ToolIoSection,
|
|
29
|
+
} from "./result.ts";
|
|
24
30
|
import {
|
|
25
31
|
patchRegistry,
|
|
26
32
|
TOOL_GROUPING_GENERATION_KEY as GENERATION_KEY,
|
|
@@ -41,7 +47,6 @@ type Patch = {
|
|
|
41
47
|
generation: number;
|
|
42
48
|
lastEnabled: boolean;
|
|
43
49
|
theme?: any;
|
|
44
|
-
animationTimer: ReturnType<typeof setTimeout> | null;
|
|
45
50
|
};
|
|
46
51
|
|
|
47
52
|
function toolName(tool: any): string {
|
|
@@ -93,23 +98,6 @@ function statusIcon(value: ToolStatus): string {
|
|
|
93
98
|
return toolLoadingIcon();
|
|
94
99
|
}
|
|
95
100
|
|
|
96
|
-
function scheduleGroupAnimation(patch: Patch): void {
|
|
97
|
-
if (patch.animationTimer || !patch.active) return;
|
|
98
|
-
patch.animationTimer = setTimeout(() => {
|
|
99
|
-
patch.animationTimer = null;
|
|
100
|
-
if (!patch.active) return;
|
|
101
|
-
for (const group of patch.groups) {
|
|
102
|
-
if (
|
|
103
|
-
(group.children as any[]).some(
|
|
104
|
-
(tool) => tool?.executionStarted && status(tool) === "pending",
|
|
105
|
-
)
|
|
106
|
-
)
|
|
107
|
-
group.invalidate();
|
|
108
|
-
}
|
|
109
|
-
}, TOOL_LOADING_INTERVAL_MS);
|
|
110
|
-
patch.animationTimer.unref?.();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
101
|
function visibleLines(lines: string[]): string[] {
|
|
114
102
|
return lines.filter((line) => stripAnsi(line).trim());
|
|
115
103
|
}
|
|
@@ -180,15 +168,74 @@ let nextGroupId = 1;
|
|
|
180
168
|
|
|
181
169
|
type SettledGroupCache = {
|
|
182
170
|
width: number;
|
|
171
|
+
expanded: boolean;
|
|
183
172
|
hover: boolean;
|
|
184
173
|
theme: unknown;
|
|
185
174
|
fullscreen: boolean;
|
|
186
175
|
children: readonly unknown[];
|
|
187
176
|
args: unknown[];
|
|
188
177
|
results: unknown[];
|
|
178
|
+
statuses: ToolStatus[];
|
|
179
|
+
callComponents: unknown[];
|
|
180
|
+
resultComponents: unknown[];
|
|
181
|
+
ioViews: Array<ExpandedToolIoView | undefined>;
|
|
182
|
+
ioHoveredSections: Array<ToolIoSection | null | undefined>;
|
|
183
|
+
ioRevisions: Array<number | undefined>;
|
|
184
|
+
capturedIoFrame: boolean;
|
|
185
|
+
lines: string[];
|
|
186
|
+
ioMarkers: CapturedIoViewMarker[];
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
type SettledGroupCacheSlots = {
|
|
190
|
+
collapsed?: SettledGroupCache;
|
|
191
|
+
expanded?: SettledGroupCache;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
type SettledExpandedChildCache = {
|
|
195
|
+
width: number;
|
|
196
|
+
theme: unknown;
|
|
197
|
+
fullscreen: boolean;
|
|
198
|
+
index: number;
|
|
199
|
+
total: number;
|
|
200
|
+
args: unknown;
|
|
201
|
+
result: unknown;
|
|
202
|
+
status: ToolStatus;
|
|
203
|
+
expanded: boolean;
|
|
204
|
+
callComponent: unknown;
|
|
205
|
+
resultComponent: unknown;
|
|
206
|
+
ioView: ExpandedToolIoView | undefined;
|
|
207
|
+
ioHoveredSection: ToolIoSection | null | undefined;
|
|
208
|
+
ioRevision: number | undefined;
|
|
209
|
+
capturedIoFrame: boolean;
|
|
189
210
|
lines: string[];
|
|
211
|
+
ioMarkers: CapturedIoViewMarker[];
|
|
190
212
|
};
|
|
191
213
|
|
|
214
|
+
function toolIoView(tool: any): ExpandedToolIoView | undefined {
|
|
215
|
+
for (const candidate of [
|
|
216
|
+
tool?.rendererState?.ccstyleIoView,
|
|
217
|
+
tool?.state?.ccstyleIoView,
|
|
218
|
+
tool?.resultRendererComponent,
|
|
219
|
+
]) {
|
|
220
|
+
if (isExpandedToolIoView(candidate)) {
|
|
221
|
+
return candidate;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function ioHoveredSection(
|
|
228
|
+
view: ExpandedToolIoView | undefined,
|
|
229
|
+
): ToolIoSection | null | undefined {
|
|
230
|
+
return view?.getHoveredSection();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function ioRenderRevision(
|
|
234
|
+
view: ExpandedToolIoView | undefined,
|
|
235
|
+
): number | undefined {
|
|
236
|
+
return view?.getRenderRevision();
|
|
237
|
+
}
|
|
238
|
+
|
|
192
239
|
export class ToolGroupComponent extends Container {
|
|
193
240
|
readonly toolCallId = `ccstyle-tool-group-${nextGroupId++}`;
|
|
194
241
|
readonly toolName = "Tool group";
|
|
@@ -199,8 +246,12 @@ export class ToolGroupComponent extends Container {
|
|
|
199
246
|
}
|
|
200
247
|
private hintHovered = false;
|
|
201
248
|
private readonly patch: Patch;
|
|
202
|
-
/**
|
|
203
|
-
private
|
|
249
|
+
/** 已完成分组按折叠/展开槽位跨帧缓存;pending 走子工具级缓存。 */
|
|
250
|
+
private settledCaches: SettledGroupCacheSlots = {};
|
|
251
|
+
private settledExpandedChildCaches = new WeakMap<
|
|
252
|
+
object,
|
|
253
|
+
SettledExpandedChildCache
|
|
254
|
+
>();
|
|
204
255
|
|
|
205
256
|
constructor(patch: Patch) {
|
|
206
257
|
super();
|
|
@@ -209,13 +260,13 @@ export class ToolGroupComponent extends Container {
|
|
|
209
260
|
}
|
|
210
261
|
|
|
211
262
|
addTool(tool: any): void {
|
|
212
|
-
this.
|
|
263
|
+
this.clearAllCaches();
|
|
213
264
|
this.children.push(tool);
|
|
214
265
|
tool[PARENT_KEY] = this;
|
|
215
266
|
}
|
|
216
267
|
|
|
217
268
|
releaseTools(): any[] {
|
|
218
|
-
this.
|
|
269
|
+
this.clearAllCaches();
|
|
219
270
|
const tools = [...this.children];
|
|
220
271
|
this.children.length = 0;
|
|
221
272
|
this.patch.groups.delete(this);
|
|
@@ -223,14 +274,13 @@ export class ToolGroupComponent extends Container {
|
|
|
223
274
|
}
|
|
224
275
|
|
|
225
276
|
removeTool(tool: any): void {
|
|
226
|
-
this.
|
|
277
|
+
this.clearAllCaches();
|
|
227
278
|
const index = this.children.indexOf(tool);
|
|
228
279
|
if (index >= 0) this.children.splice(index, 1);
|
|
229
280
|
if (tool?.[PARENT_KEY] === this) delete tool[PARENT_KEY];
|
|
230
281
|
}
|
|
231
282
|
|
|
232
283
|
setExpanded(expanded: boolean): void {
|
|
233
|
-
if (this._expanded !== expanded) this.settledCache = undefined;
|
|
234
284
|
this._expanded = expanded;
|
|
235
285
|
for (const tool of this.children)
|
|
236
286
|
(
|
|
@@ -239,7 +289,9 @@ export class ToolGroupComponent extends Container {
|
|
|
239
289
|
}
|
|
240
290
|
|
|
241
291
|
setHintHovered(hovered: boolean): void {
|
|
242
|
-
if (this.hintHovered !== hovered)
|
|
292
|
+
if (this.hintHovered !== hovered) {
|
|
293
|
+
this.clearSettledCaches();
|
|
294
|
+
}
|
|
243
295
|
this.hintHovered = hovered;
|
|
244
296
|
}
|
|
245
297
|
|
|
@@ -272,53 +324,205 @@ export class ToolGroupComponent extends Container {
|
|
|
272
324
|
}
|
|
273
325
|
|
|
274
326
|
invalidate(): void {
|
|
275
|
-
this.
|
|
327
|
+
this.clearAllCaches();
|
|
276
328
|
for (const tool of this.children) tool.invalidate?.();
|
|
277
329
|
}
|
|
278
330
|
|
|
331
|
+
private clearSettledCaches(): void {
|
|
332
|
+
this.settledCaches = {};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private clearAllCaches(): void {
|
|
336
|
+
this.clearSettledCaches();
|
|
337
|
+
this.settledExpandedChildCaches = new WeakMap();
|
|
338
|
+
}
|
|
339
|
+
|
|
279
340
|
private settledCacheHit(width: number): string[] | undefined {
|
|
280
|
-
const
|
|
281
|
-
|
|
341
|
+
const slot = this._expanded ? "expanded" : "collapsed";
|
|
342
|
+
const cache = this.settledCaches[slot];
|
|
343
|
+
if (!cache) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
282
346
|
if (
|
|
283
347
|
cache.width !== width ||
|
|
348
|
+
cache.expanded !== this._expanded ||
|
|
284
349
|
cache.hover !== this.hintHovered ||
|
|
285
350
|
cache.theme !== this.patch.theme ||
|
|
286
|
-
cache.fullscreen !== isToolTuiFullscreen()
|
|
351
|
+
cache.fullscreen !== isToolTuiFullscreen() ||
|
|
352
|
+
(getActiveIoViewFrame() !== null &&
|
|
353
|
+
!cache.capturedIoFrame &&
|
|
354
|
+
cache.ioViews.some(Boolean))
|
|
287
355
|
) {
|
|
288
356
|
return;
|
|
289
357
|
}
|
|
290
358
|
const tools = this.children as any[];
|
|
291
|
-
if (cache.children.length !== tools.length)
|
|
359
|
+
if (cache.children.length !== tools.length) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
292
362
|
for (let i = 0; i < tools.length; i++) {
|
|
293
363
|
const tool = tools[i];
|
|
364
|
+
const ioView = toolIoView(tool);
|
|
365
|
+
const toolStatus = status(tool);
|
|
294
366
|
if (
|
|
295
367
|
cache.children[i] !== tool ||
|
|
296
368
|
cache.args[i] !== tool?.args ||
|
|
297
369
|
cache.results[i] !== tool?.result ||
|
|
298
|
-
|
|
370
|
+
cache.statuses[i] !== toolStatus ||
|
|
371
|
+
cache.callComponents[i] !== tool?.callRendererComponent ||
|
|
372
|
+
cache.resultComponents[i] !== tool?.resultRendererComponent ||
|
|
373
|
+
cache.ioViews[i] !== ioView ||
|
|
374
|
+
cache.ioHoveredSections[i] !== ioHoveredSection(ioView) ||
|
|
375
|
+
cache.ioRevisions[i] !== ioRenderRevision(ioView) ||
|
|
376
|
+
toolStatus === "pending"
|
|
299
377
|
) {
|
|
300
378
|
return;
|
|
301
379
|
}
|
|
302
380
|
}
|
|
303
|
-
return cache.lines;
|
|
381
|
+
return replayIoViewMarkers(cache.lines, cache.ioMarkers);
|
|
304
382
|
}
|
|
305
383
|
|
|
306
384
|
private storeSettledCache(width: number, lines: string[]): void {
|
|
307
|
-
this.
|
|
385
|
+
const tools = this.children as any[];
|
|
386
|
+
const ioViews = tools.map(toolIoView);
|
|
387
|
+
const captured = captureIoViewMarkers(lines);
|
|
388
|
+
const cache: SettledGroupCache = {
|
|
308
389
|
width,
|
|
390
|
+
expanded: this._expanded,
|
|
309
391
|
hover: this.hintHovered,
|
|
310
392
|
theme: this.patch.theme,
|
|
311
393
|
fullscreen: isToolTuiFullscreen(),
|
|
312
|
-
children: [...
|
|
313
|
-
args:
|
|
314
|
-
results:
|
|
315
|
-
|
|
394
|
+
children: [...tools],
|
|
395
|
+
args: tools.map((tool) => tool?.args),
|
|
396
|
+
results: tools.map((tool) => tool?.result),
|
|
397
|
+
statuses: tools.map(status),
|
|
398
|
+
callComponents: tools.map((tool) => tool?.callRendererComponent),
|
|
399
|
+
resultComponents: tools.map((tool) => tool?.resultRendererComponent),
|
|
400
|
+
ioViews,
|
|
401
|
+
ioHoveredSections: ioViews.map(ioHoveredSection),
|
|
402
|
+
ioRevisions: ioViews.map(ioRenderRevision),
|
|
403
|
+
capturedIoFrame: getActiveIoViewFrame() !== null,
|
|
404
|
+
lines: captured.lines,
|
|
405
|
+
ioMarkers: captured.markers,
|
|
316
406
|
};
|
|
407
|
+
this.settledCaches[this._expanded ? "expanded" : "collapsed"] = cache;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private settledExpandedChildCacheHit(
|
|
411
|
+
tool: any,
|
|
412
|
+
index: number,
|
|
413
|
+
total: number,
|
|
414
|
+
width: number,
|
|
415
|
+
): string[] | undefined {
|
|
416
|
+
const cache = this.settledExpandedChildCaches.get(tool);
|
|
417
|
+
if (!cache) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const toolStatus = status(tool);
|
|
421
|
+
const ioView = toolIoView(tool);
|
|
422
|
+
if (
|
|
423
|
+
toolStatus === "pending" ||
|
|
424
|
+
cache.width !== width ||
|
|
425
|
+
cache.theme !== this.patch.theme ||
|
|
426
|
+
cache.fullscreen !== isToolTuiFullscreen() ||
|
|
427
|
+
cache.index !== index ||
|
|
428
|
+
cache.total !== total ||
|
|
429
|
+
cache.args !== tool?.args ||
|
|
430
|
+
cache.result !== tool?.result ||
|
|
431
|
+
cache.status !== toolStatus ||
|
|
432
|
+
cache.expanded !== (tool?.expanded === true) ||
|
|
433
|
+
cache.callComponent !== tool?.callRendererComponent ||
|
|
434
|
+
cache.resultComponent !== tool?.resultRendererComponent ||
|
|
435
|
+
cache.ioView !== ioView ||
|
|
436
|
+
cache.ioHoveredSection !== ioHoveredSection(ioView) ||
|
|
437
|
+
cache.ioRevision !== ioRenderRevision(ioView) ||
|
|
438
|
+
(getActiveIoViewFrame() !== null &&
|
|
439
|
+
!cache.capturedIoFrame &&
|
|
440
|
+
ioView !== undefined)
|
|
441
|
+
) {
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
return replayIoViewMarkers(cache.lines, cache.ioMarkers);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
private storeSettledExpandedChildCache(
|
|
448
|
+
tool: any,
|
|
449
|
+
index: number,
|
|
450
|
+
total: number,
|
|
451
|
+
width: number,
|
|
452
|
+
lines: string[],
|
|
453
|
+
): void {
|
|
454
|
+
const ioView = toolIoView(tool);
|
|
455
|
+
const captured = captureIoViewMarkers(lines);
|
|
456
|
+
this.settledExpandedChildCaches.set(tool, {
|
|
457
|
+
width,
|
|
458
|
+
theme: this.patch.theme,
|
|
459
|
+
fullscreen: isToolTuiFullscreen(),
|
|
460
|
+
index,
|
|
461
|
+
total,
|
|
462
|
+
args: tool?.args,
|
|
463
|
+
result: tool?.result,
|
|
464
|
+
status: status(tool),
|
|
465
|
+
expanded: tool?.expanded === true,
|
|
466
|
+
callComponent: tool?.callRendererComponent,
|
|
467
|
+
resultComponent: tool?.resultRendererComponent,
|
|
468
|
+
ioView,
|
|
469
|
+
ioHoveredSection: ioHoveredSection(ioView),
|
|
470
|
+
ioRevision: ioRenderRevision(ioView),
|
|
471
|
+
capturedIoFrame: getActiveIoViewFrame() !== null,
|
|
472
|
+
lines: captured.lines,
|
|
473
|
+
ioMarkers: captured.markers,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
private renderExpandedChildBlock(
|
|
478
|
+
tool: any,
|
|
479
|
+
index: number,
|
|
480
|
+
total: number,
|
|
481
|
+
width: number,
|
|
482
|
+
theme: any,
|
|
483
|
+
fg: (color: string, text: string) => string,
|
|
484
|
+
): string[] {
|
|
485
|
+
const cached = this.settledExpandedChildCacheHit(tool, index, total, width);
|
|
486
|
+
if (cached) {
|
|
487
|
+
return cached;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const toolStatus = status(tool);
|
|
491
|
+
const color = toolStatus === "pending" ? "accent" : toolStatus;
|
|
492
|
+
const branch = index === total - 1 ? "└" : "├";
|
|
493
|
+
const continuation = index === total - 1 ? " " : "│ ";
|
|
494
|
+
const rendered = visibleLines(tool.render(Math.max(1, width - 2)));
|
|
495
|
+
if (rendered.length) {
|
|
496
|
+
rendered[0] = stripLeadingStatusIcon(rendered[0])
|
|
497
|
+
.replace(/^ +/, "")
|
|
498
|
+
.replace(/^((?:\x1b\[[0-?]*[ -/]*[@-~])*) +/, "$1");
|
|
499
|
+
}
|
|
500
|
+
const childLines = rendered.length ? rendered : [toolSummary(tool).main];
|
|
501
|
+
const backgroundSlot = "userMessageBg";
|
|
502
|
+
const lines = childLines.map((line, lineIndex) => {
|
|
503
|
+
const content = lineIndex === 0 ? line : stripLeadingSpaces(line, 1);
|
|
504
|
+
const prefix =
|
|
505
|
+
lineIndex === 0
|
|
506
|
+
? `${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `
|
|
507
|
+
: fg("dim", continuation);
|
|
508
|
+
return paddedBackgroundRow(
|
|
509
|
+
theme,
|
|
510
|
+
backgroundSlot,
|
|
511
|
+
prefix + content,
|
|
512
|
+
width,
|
|
513
|
+
);
|
|
514
|
+
});
|
|
515
|
+
if (toolStatus !== "pending") {
|
|
516
|
+
this.storeSettledExpandedChildCache(tool, index, total, width, lines);
|
|
517
|
+
}
|
|
518
|
+
return lines;
|
|
317
519
|
}
|
|
318
520
|
|
|
319
521
|
render(width: number): string[] {
|
|
320
522
|
const cached = this.settledCacheHit(width);
|
|
321
|
-
if (cached)
|
|
523
|
+
if (cached) {
|
|
524
|
+
return cached;
|
|
525
|
+
}
|
|
322
526
|
const theme = this.patch.theme;
|
|
323
527
|
const fg = (color: string, text: string) =>
|
|
324
528
|
theme?.fg?.(color, text) ?? text;
|
|
@@ -343,12 +547,6 @@ export class ToolGroupComponent extends Container {
|
|
|
343
547
|
: counts.pending
|
|
344
548
|
? "pending"
|
|
345
549
|
: "success";
|
|
346
|
-
if (
|
|
347
|
-
(this.children as any[]).some(
|
|
348
|
-
(tool) => tool?.executionStarted && status(tool) === "pending",
|
|
349
|
-
)
|
|
350
|
-
)
|
|
351
|
-
scheduleGroupAnimation(this.patch);
|
|
352
550
|
const overallColor = overall === "pending" ? "accent" : overall;
|
|
353
551
|
const nameList =
|
|
354
552
|
names.size > 1 ? ` ${fg("dim", `• ${toolNameList(this.children)}`)}` : "";
|
|
@@ -363,52 +561,37 @@ export class ToolGroupComponent extends Container {
|
|
|
363
561
|
),
|
|
364
562
|
];
|
|
365
563
|
const total = this.children.length;
|
|
366
|
-
const expandedLines: string[] = [];
|
|
367
564
|
for (let index = 0; index < total; index++) {
|
|
368
565
|
const tool = this.children[index];
|
|
369
|
-
|
|
370
|
-
const color = toolStatus === "pending" ? "accent" : toolStatus;
|
|
371
|
-
const branch = index === total - 1 ? "└" : "├";
|
|
372
|
-
const continuation = index === total - 1 ? " " : "│ ";
|
|
373
|
-
if (!this._expanded) {
|
|
374
|
-
const summary = toolSummary(tool);
|
|
566
|
+
if (this._expanded) {
|
|
375
567
|
lines.push(
|
|
376
|
-
|
|
377
|
-
|
|
568
|
+
...this.renderExpandedChildBlock(
|
|
569
|
+
tool,
|
|
570
|
+
index,
|
|
571
|
+
total,
|
|
378
572
|
width,
|
|
379
|
-
|
|
573
|
+
theme,
|
|
574
|
+
fg,
|
|
380
575
|
),
|
|
381
576
|
);
|
|
382
577
|
continue;
|
|
383
578
|
}
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
? childLines[lineIndex]
|
|
396
|
-
: stripLeadingSpaces(childLines[lineIndex], 1);
|
|
397
|
-
const prefix =
|
|
398
|
-
lineIndex === 0
|
|
399
|
-
? `${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} `
|
|
400
|
-
: fg("dim", continuation);
|
|
401
|
-
expandedLines.push(prefix + content);
|
|
402
|
-
}
|
|
579
|
+
const toolStatus = status(tool);
|
|
580
|
+
const color = toolStatus === "pending" ? "accent" : toolStatus;
|
|
581
|
+
const branch = index === total - 1 ? "└" : "├";
|
|
582
|
+
const summary = toolSummary(tool);
|
|
583
|
+
lines.push(
|
|
584
|
+
truncateToWidth(
|
|
585
|
+
` ${fg("dim", branch)} ${fg(color, statusIcon(toolStatus))} ${fg("toolTitle", summary.main)}${fg("dim", summary.detail)}`,
|
|
586
|
+
width,
|
|
587
|
+
"…",
|
|
588
|
+
),
|
|
589
|
+
);
|
|
403
590
|
}
|
|
404
591
|
if (this._expanded) {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
lines.push(paddedBackgroundRow(theme, backgroundSlot, line, width));
|
|
409
|
-
}
|
|
410
|
-
lines.push(paddedBackgroundRow(theme, backgroundSlot, "", width));
|
|
411
|
-
} else if (counts.pending === 0) {
|
|
592
|
+
lines.push(paddedBackgroundRow(theme, "userMessageBg", "", width));
|
|
593
|
+
}
|
|
594
|
+
if (counts.pending === 0) {
|
|
412
595
|
this.storeSettledCache(width, lines);
|
|
413
596
|
}
|
|
414
597
|
return lines;
|
|
@@ -517,8 +700,6 @@ export function installToolGrouping(
|
|
|
517
700
|
if (previous) {
|
|
518
701
|
previous.active = false;
|
|
519
702
|
previous.enabled = () => false;
|
|
520
|
-
if (previous.animationTimer) clearTimeout(previous.animationTimer);
|
|
521
|
-
previous.animationTimer = null;
|
|
522
703
|
ungroup(previous);
|
|
523
704
|
}
|
|
524
705
|
const original = {
|
|
@@ -545,7 +726,6 @@ export function installToolGrouping(
|
|
|
545
726
|
enabled: getEnabled,
|
|
546
727
|
generation: 0,
|
|
547
728
|
lastEnabled: getEnabled(),
|
|
548
|
-
animationTimer: null,
|
|
549
729
|
};
|
|
550
730
|
patch.installed = {
|
|
551
731
|
addChild: function (this: any, component: any) {
|
|
@@ -604,8 +784,6 @@ export function installToolGrouping(
|
|
|
604
784
|
shutdown() {
|
|
605
785
|
if (!patch.active) return;
|
|
606
786
|
patch.active = false;
|
|
607
|
-
if (patch.animationTimer) clearTimeout(patch.animationTimer);
|
|
608
|
-
patch.animationTimer = null;
|
|
609
787
|
patch.enabled = () => false;
|
|
610
788
|
ungroup(patch);
|
|
611
789
|
if (prototype.addChild === patch.installed.addChild)
|