@hheei/pi-ext-ui 0.5.2 → 0.6.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/extension.js +12 -0
- package/dist/host-transcript-patch.d.ts +2 -0
- package/dist/host-transcript-patch.js +123 -164
- package/dist/index.d.ts +1 -1
- package/dist/message-view.d.ts +12 -0
- package/dist/message-view.js +22 -5
- package/dist/nested-calls-view.js +41 -27
- package/dist/payload-parser.js +5 -5
- package/dist/renderer.js +45 -0
- package/dist/tool-group-view.d.ts +45 -0
- package/dist/tool-group-view.js +458 -0
- package/dist/tool-view.d.ts +15 -0
- package/dist/tool-view.js +39 -5
- package/package.json +2 -2
package/dist/extension.js
CHANGED
|
@@ -32,6 +32,18 @@ export default function (pi) {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
const provider = createToolGroupSettings({ onEnabled: apply });
|
|
35
|
+
const onAgentStart = pi.on("agent_start", () => {
|
|
36
|
+
patch?.beginRun();
|
|
37
|
+
redrawGroups?.();
|
|
38
|
+
});
|
|
39
|
+
const onAgentEnd = pi.on("agent_end", () => {
|
|
40
|
+
patch?.endRun();
|
|
41
|
+
redrawGroups?.();
|
|
42
|
+
});
|
|
43
|
+
context.resources.add("run-clock", () => {
|
|
44
|
+
onAgentStart();
|
|
45
|
+
onAgentEnd();
|
|
46
|
+
});
|
|
35
47
|
context.resources.add("tool-group-settings", registerSettings(provider, getRuntimeSettingsRegistry(pi)));
|
|
36
48
|
context.resources.add("tool-groups", () => {
|
|
37
49
|
redrawGroups = undefined;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AssistantMessageComponent, InteractiveMode, ToolExecutionComponent, VERSION, } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Container,
|
|
3
|
-
import { fitRow } from "@hheei/pi-ext-core";
|
|
2
|
+
import { Container, Spacer, stripTerminalSequences, Text, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
|
|
4
3
|
import { MessageView } from "./message-view.js";
|
|
5
|
-
import {
|
|
4
|
+
import { ToolGroupView } from "./tool-group-view.js";
|
|
5
|
+
import { ToolView, toSingleRow } from "./tool-view.js";
|
|
6
6
|
const SEAMS = ["getRegisteredToolDefinition", "showExtensionNotify"];
|
|
7
7
|
function record(value) {
|
|
8
8
|
return typeof value === "object" && value !== null;
|
|
@@ -28,34 +28,56 @@ function readHost(value) {
|
|
|
28
28
|
const requestRender = tui.requestRender;
|
|
29
29
|
return { chat, ui, requestRender: () => requestRender.call(tui) };
|
|
30
30
|
}
|
|
31
|
+
function readUsage(value) {
|
|
32
|
+
if (!record(value))
|
|
33
|
+
return undefined;
|
|
34
|
+
const { input, output, cacheRead, cacheWrite } = value;
|
|
35
|
+
if (typeof input !== "number" ||
|
|
36
|
+
typeof output !== "number" ||
|
|
37
|
+
typeof cacheRead !== "number" ||
|
|
38
|
+
typeof cacheWrite !== "number")
|
|
39
|
+
return undefined;
|
|
40
|
+
return { input, output, cacheRead, cacheWrite };
|
|
41
|
+
}
|
|
31
42
|
function assistantInfo(component) {
|
|
32
43
|
const message = Reflect.get(component, "lastMessage");
|
|
33
44
|
if (!record(message) || !Array.isArray(message.content)) {
|
|
34
45
|
throw new Error("Pi assistant message shape changed");
|
|
35
46
|
}
|
|
47
|
+
const hidden = Reflect.get(component, "hideThinkingBlock");
|
|
36
48
|
const calls = new Set();
|
|
37
49
|
let eligible = !["length", "aborted", "error"].includes(String(message.stopReason));
|
|
38
|
-
let thinking =
|
|
50
|
+
let thinking = "";
|
|
39
51
|
for (const block of message.content) {
|
|
40
52
|
if (!record(block))
|
|
41
53
|
throw new Error("Pi assistant content shape changed");
|
|
42
54
|
if (block.type === "toolCall" && typeof block.id === "string")
|
|
43
55
|
calls.add(block.id);
|
|
44
56
|
else if (block.type === "thinking" && typeof block.thinking === "string")
|
|
45
|
-
thinking
|
|
57
|
+
thinking += block.thinking;
|
|
46
58
|
else if (block.type !== "text" || typeof block.text !== "string" || block.text.trim() !== "")
|
|
47
59
|
eligible = false;
|
|
48
60
|
}
|
|
49
|
-
return {
|
|
61
|
+
return {
|
|
62
|
+
calls,
|
|
63
|
+
eligible,
|
|
64
|
+
thinking,
|
|
65
|
+
hidden: hidden === true,
|
|
66
|
+
usage: readUsage(message.usage),
|
|
67
|
+
};
|
|
50
68
|
}
|
|
51
69
|
function toolMember(component, calls) {
|
|
52
70
|
const id = Reflect.get(component, "toolCallId");
|
|
53
71
|
const name = Reflect.get(component, "toolName");
|
|
54
72
|
const partial = Reflect.get(component, "isPartial");
|
|
73
|
+
const complete = Reflect.get(component, "argsComplete");
|
|
55
74
|
const result = Reflect.get(component, "result");
|
|
56
75
|
const renderer = Reflect.get(component, "resultRendererComponent");
|
|
57
76
|
const callRenderer = Reflect.get(component, "callRendererComponent");
|
|
58
|
-
if (typeof id !== "string" ||
|
|
77
|
+
if (typeof id !== "string" ||
|
|
78
|
+
typeof name !== "string" ||
|
|
79
|
+
typeof partial !== "boolean" ||
|
|
80
|
+
typeof complete !== "boolean") {
|
|
59
81
|
throw new Error("Pi tool component shape changed");
|
|
60
82
|
}
|
|
61
83
|
if (!calls.has(id) || !(callRenderer instanceof ToolView))
|
|
@@ -71,7 +93,24 @@ function toolMember(component, calls) {
|
|
|
71
93
|
: callRenderer.summary;
|
|
72
94
|
if (summary === undefined)
|
|
73
95
|
return undefined;
|
|
74
|
-
|
|
96
|
+
const status = !complete
|
|
97
|
+
? "streaming"
|
|
98
|
+
: partial
|
|
99
|
+
? "running"
|
|
100
|
+
: record(result) && result.isError === true
|
|
101
|
+
? "error"
|
|
102
|
+
: "ok";
|
|
103
|
+
const children = renderer instanceof ToolView ? renderer.children : callRenderer.children;
|
|
104
|
+
const childCount = renderer instanceof ToolView ? renderer.childCount : undefined;
|
|
105
|
+
return {
|
|
106
|
+
kind: "tool",
|
|
107
|
+
component,
|
|
108
|
+
title: toSingleRow(stripTerminalSequences(name)),
|
|
109
|
+
summary,
|
|
110
|
+
status,
|
|
111
|
+
...(children === undefined || children.length === 0 ? {} : { children }),
|
|
112
|
+
...(childCount === undefined ? {} : { childCount }),
|
|
113
|
+
};
|
|
75
114
|
}
|
|
76
115
|
function messageView(component) {
|
|
77
116
|
if (component instanceof MessageView)
|
|
@@ -84,165 +123,62 @@ function messageView(component) {
|
|
|
84
123
|
return undefined;
|
|
85
124
|
}
|
|
86
125
|
/** Shared transcript notices stay in the group. The frame is the contract, not each custom type. */
|
|
87
|
-
function
|
|
126
|
+
function noticeMember(component) {
|
|
88
127
|
const view = messageView(component);
|
|
89
|
-
|
|
128
|
+
if (view === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
return {
|
|
131
|
+
kind: "notice",
|
|
132
|
+
component,
|
|
133
|
+
title: view.kind,
|
|
134
|
+
summary: view.activitySummary,
|
|
135
|
+
// A notice with no preview and no body has nothing to open; the group draws a pin only when
|
|
136
|
+
// detail exists, so an unconditional function would give it an arrow onto an empty body.
|
|
137
|
+
...(view.hasDetail ? { detail: (width) => view.detailRows(width) } : {}),
|
|
138
|
+
};
|
|
90
139
|
}
|
|
91
140
|
class NotifyView extends Container {
|
|
92
141
|
#text = new Text("", 1, 0);
|
|
93
|
-
#
|
|
142
|
+
#ui;
|
|
94
143
|
#message;
|
|
95
144
|
#type;
|
|
96
|
-
constructor(message, type,
|
|
145
|
+
constructor(message, type, ui) {
|
|
97
146
|
super();
|
|
98
|
-
this.#
|
|
147
|
+
this.#ui = ui;
|
|
99
148
|
this.#message = message;
|
|
100
149
|
this.#type = type;
|
|
101
150
|
this.addChild(new Spacer(1));
|
|
102
151
|
this.addChild(this.#text);
|
|
103
152
|
}
|
|
104
153
|
get summary() {
|
|
105
|
-
return this.#
|
|
154
|
+
return this.#ui.theme.fg(this.#type === "info" ? "dim" : this.#type, toSingleRow(this.#content));
|
|
106
155
|
}
|
|
107
|
-
get
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
class ToolGroup extends Container {
|
|
117
|
-
#header = new Text("", 0, 0);
|
|
118
|
-
#originals = new Container();
|
|
119
|
-
#context;
|
|
120
|
-
#redraw;
|
|
121
|
-
#members = [];
|
|
122
|
-
#expanded = false;
|
|
123
|
-
#renderedExpanded = false;
|
|
124
|
-
#open = new Set();
|
|
125
|
-
#hits = [];
|
|
126
|
-
constructor(context, redraw) {
|
|
127
|
-
super();
|
|
128
|
-
this.#context = context;
|
|
129
|
-
this.#redraw = redraw;
|
|
130
|
-
const heading = new MouseRegion(this.#header, (event) => {
|
|
131
|
-
if (event.type !== "click" || event.button !== "left")
|
|
132
|
-
return undefined;
|
|
133
|
-
this.#expanded = this.#context.ui.getToolsExpanded() ? false : !this.#expanded;
|
|
134
|
-
if (!this.#expanded)
|
|
135
|
-
this.#open.clear();
|
|
136
|
-
redraw();
|
|
137
|
-
return { handled: true, render: true };
|
|
138
|
-
});
|
|
139
|
-
const body = {
|
|
140
|
-
render: (width) => this.#renderBody(width),
|
|
141
|
-
handleMouse: (event) => this.#hit(event),
|
|
142
|
-
invalidate() { },
|
|
156
|
+
get member() {
|
|
157
|
+
return {
|
|
158
|
+
kind: "notice",
|
|
159
|
+
component: this,
|
|
160
|
+
title: this.#type,
|
|
161
|
+
summary: this.summary,
|
|
162
|
+
detail: (width) => this.#detail(width),
|
|
143
163
|
};
|
|
144
|
-
this.children = [new Spacer(1), heading, body];
|
|
145
164
|
}
|
|
146
|
-
#
|
|
165
|
+
#detail(width) {
|
|
147
166
|
const inner = Math.max(1, width - 2);
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.#members.forEach((member, index) => {
|
|
152
|
-
const wrapper = this.#originals.children[index];
|
|
153
|
-
if (wrapper === undefined)
|
|
154
|
-
return;
|
|
155
|
-
const native = this.#renderedExpanded || this.#open.has(member.component);
|
|
156
|
-
const rows = native
|
|
157
|
-
? renderWithoutToolRails(wrapper, inner)
|
|
158
|
-
: member.summary === undefined
|
|
159
|
-
? []
|
|
160
|
-
: [member.summary];
|
|
161
|
-
if (rows.length === 0)
|
|
162
|
-
return;
|
|
163
|
-
hits.push({ component: member.component, wrapper, native, height: rows.length });
|
|
164
|
-
for (const row of rows) {
|
|
165
|
-
lines.push(fitRow(`${rail} ${row}`, Math.max(0, width), "…"));
|
|
166
|
-
}
|
|
167
|
+
return this.#content.split("\n").flatMap((line) => {
|
|
168
|
+
const wrapped = wrapTextWithAnsi(line, inner);
|
|
169
|
+
return (wrapped.length > 0 ? wrapped : [""]).map((row) => ` ${row}`);
|
|
167
170
|
});
|
|
168
|
-
this.#hits = hits;
|
|
169
|
-
return lines;
|
|
170
|
-
}
|
|
171
|
-
#hit(event) {
|
|
172
|
-
let y = event.y;
|
|
173
|
-
for (const hit of this.#hits) {
|
|
174
|
-
if (y >= hit.height) {
|
|
175
|
-
y -= hit.height;
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
if (!hit.native) {
|
|
179
|
-
if (event.type !== "click" || event.button !== "left")
|
|
180
|
-
return undefined;
|
|
181
|
-
this.#open.add(hit.component);
|
|
182
|
-
this.#redraw();
|
|
183
|
-
return { handled: true, render: true };
|
|
184
|
-
}
|
|
185
|
-
// The rail belongs to the group. Content, including header fold and copy, stays with the member.
|
|
186
|
-
if (event.x < 2) {
|
|
187
|
-
if (!this.#renderedExpanded && event.type === "click" && event.button === "left") {
|
|
188
|
-
this.#open.delete(hit.component);
|
|
189
|
-
this.#redraw();
|
|
190
|
-
return { handled: true, render: true };
|
|
191
|
-
}
|
|
192
|
-
return undefined;
|
|
193
|
-
}
|
|
194
|
-
return hit.wrapper.handleMouse?.({
|
|
195
|
-
...event,
|
|
196
|
-
y,
|
|
197
|
-
height: hit.height,
|
|
198
|
-
x: event.x - 2,
|
|
199
|
-
width: Math.max(1, event.width - 2),
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
return undefined;
|
|
203
171
|
}
|
|
204
|
-
|
|
205
|
-
this.#
|
|
206
|
-
|
|
207
|
-
let offset = 0;
|
|
208
|
-
return {
|
|
209
|
-
render(width) {
|
|
210
|
-
const rows = component.render(width);
|
|
211
|
-
// Pi adds one leading separator per tool; the group supplies its own spacing.
|
|
212
|
-
offset = rows[0] === "" ? 1 : 0;
|
|
213
|
-
return rows.slice(offset);
|
|
214
|
-
},
|
|
215
|
-
handleMouse(event) {
|
|
216
|
-
return component.handleMouse?.({
|
|
217
|
-
...event,
|
|
218
|
-
y: event.y + offset,
|
|
219
|
-
height: event.height + offset,
|
|
220
|
-
});
|
|
221
|
-
},
|
|
222
|
-
invalidate() { },
|
|
223
|
-
};
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
invalidate() {
|
|
227
|
-
// Original chatContainer.invalidate invalidates original members exactly once.
|
|
228
|
-
this.#header.invalidate();
|
|
172
|
+
get #content() {
|
|
173
|
+
const prefix = this.#type === "error" ? "Error: " : this.#type === "warning" ? "Warning: " : "";
|
|
174
|
+
return `${prefix}${this.#message}`;
|
|
229
175
|
}
|
|
230
176
|
render(width) {
|
|
231
|
-
this.#
|
|
232
|
-
const theme = this.#context.ui.theme;
|
|
233
|
-
const counts = new Map();
|
|
234
|
-
for (const member of this.#members) {
|
|
235
|
-
if (member.name !== undefined)
|
|
236
|
-
counts.set(member.name, (counts.get(member.name) ?? 0) + 1);
|
|
237
|
-
}
|
|
238
|
-
const heading = [...counts]
|
|
239
|
-
.map(([name, count]) => `${theme.fg("toolTitle", theme.bold(name))} ${theme.fg("dim", `×${count}`)}`)
|
|
240
|
-
.join(theme.fg("dim", " · "));
|
|
241
|
-
this.#header.setText(fitRow(`${theme.fg("dim", this.#renderedExpanded ? "▾" : "▸")} ${heading}`, Math.max(0, width), "…"));
|
|
177
|
+
this.#text.setText(this.#ui.theme.fg(this.#type === "info" ? "dim" : this.#type, this.#content));
|
|
242
178
|
return super.render(width);
|
|
243
179
|
}
|
|
244
180
|
}
|
|
245
|
-
function attach(host,
|
|
181
|
+
function attach(host, run) {
|
|
246
182
|
const chat = host.chat;
|
|
247
183
|
const original = {
|
|
248
184
|
render: chat.render,
|
|
@@ -264,13 +200,16 @@ function attach(host, context) {
|
|
|
264
200
|
const rebuild = () => {
|
|
265
201
|
const next = new Map();
|
|
266
202
|
const children = [];
|
|
203
|
+
const blocks = [];
|
|
267
204
|
let calls = new Set();
|
|
268
205
|
let members = [];
|
|
269
|
-
|
|
206
|
+
/** Closes the pending run. `ended` is false only for the run still open at the transcript tail. */
|
|
207
|
+
const flush = (ended) => {
|
|
270
208
|
const first = members[0]?.component;
|
|
271
|
-
if (first !== undefined && members.filter((member) => member.
|
|
272
|
-
const group = groups.get(first) ??
|
|
273
|
-
|
|
209
|
+
if (first !== undefined && members.filter((member) => member.kind === "tool").length > 1) {
|
|
210
|
+
const group = groups.get(first) ??
|
|
211
|
+
new ToolGroupView(host.ui, host.requestRender, run.current?.endedAt === undefined ? run.current : undefined);
|
|
212
|
+
blocks.push({ group, members, ended });
|
|
274
213
|
next.set(first, group);
|
|
275
214
|
children.push(group);
|
|
276
215
|
}
|
|
@@ -283,9 +222,13 @@ function attach(host, context) {
|
|
|
283
222
|
const info = assistantInfo(child);
|
|
284
223
|
calls = info.calls;
|
|
285
224
|
if (info.eligible) {
|
|
225
|
+
const thinking = info.thinking;
|
|
286
226
|
members.push({
|
|
227
|
+
kind: "assistant",
|
|
287
228
|
component: child,
|
|
288
|
-
...(
|
|
229
|
+
...(thinking === "" ? {} : { thinkingChars: thinking.length }),
|
|
230
|
+
...(thinking === "" || info.hidden ? {} : { thinking }),
|
|
231
|
+
...(info.usage === undefined ? {} : { usage: info.usage }),
|
|
289
232
|
});
|
|
290
233
|
continue;
|
|
291
234
|
}
|
|
@@ -298,20 +241,24 @@ function attach(host, context) {
|
|
|
298
241
|
}
|
|
299
242
|
}
|
|
300
243
|
else if (child instanceof NotifyView) {
|
|
301
|
-
members.push(
|
|
244
|
+
members.push(child.member);
|
|
302
245
|
continue;
|
|
303
246
|
}
|
|
304
247
|
else {
|
|
305
|
-
const continuation =
|
|
248
|
+
const continuation = noticeMember(child);
|
|
306
249
|
if (continuation !== undefined) {
|
|
307
250
|
members.push(continuation);
|
|
308
251
|
continue;
|
|
309
252
|
}
|
|
310
253
|
}
|
|
311
|
-
flush();
|
|
254
|
+
flush(true);
|
|
312
255
|
children.push(child);
|
|
313
256
|
}
|
|
314
|
-
flush();
|
|
257
|
+
flush(false);
|
|
258
|
+
const frozen = run.current === undefined || run.current.endedAt !== undefined;
|
|
259
|
+
for (const block of blocks) {
|
|
260
|
+
block.group.update(block.members, frozen || block.ended);
|
|
261
|
+
}
|
|
315
262
|
groups = next;
|
|
316
263
|
projection.children = children;
|
|
317
264
|
};
|
|
@@ -326,7 +273,7 @@ function attach(host, context) {
|
|
|
326
273
|
active = false;
|
|
327
274
|
groups.clear();
|
|
328
275
|
projection.clear();
|
|
329
|
-
|
|
276
|
+
host.ui.notify(`Tool grouping disabled: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
330
277
|
return original.render.call(chat, width);
|
|
331
278
|
}
|
|
332
279
|
// Real renderer errors propagate; never hide a tool-rendering failure.
|
|
@@ -348,7 +295,7 @@ function attach(host, context) {
|
|
|
348
295
|
notify(message, type) {
|
|
349
296
|
if (!active)
|
|
350
297
|
return false;
|
|
351
|
-
chat.addChild(new NotifyView(message, type,
|
|
298
|
+
chat.addChild(new NotifyView(message, type, host.ui));
|
|
352
299
|
host.requestRender();
|
|
353
300
|
return true;
|
|
354
301
|
},
|
|
@@ -372,7 +319,7 @@ function attach(host, context) {
|
|
|
372
319
|
Object.defineProperty(chat, key, descriptor);
|
|
373
320
|
}
|
|
374
321
|
if (conflict)
|
|
375
|
-
|
|
322
|
+
host.ui.notify("Tool grouping patch changed by another extension; restart Pi to fully unload it.", "warning");
|
|
376
323
|
host.requestRender();
|
|
377
324
|
},
|
|
378
325
|
};
|
|
@@ -385,7 +332,7 @@ function connect(owner, host) {
|
|
|
385
332
|
}
|
|
386
333
|
catch (error) {
|
|
387
334
|
owner.failed.add(host.chat);
|
|
388
|
-
owner.
|
|
335
|
+
owner.ui.notify(`Tool grouping unavailable: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
389
336
|
}
|
|
390
337
|
}
|
|
391
338
|
function patchState() {
|
|
@@ -426,7 +373,7 @@ function patchState() {
|
|
|
426
373
|
if (record(instance))
|
|
427
374
|
failed.add(instance);
|
|
428
375
|
for (const owner of owners.values())
|
|
429
|
-
owner.
|
|
376
|
+
owner.ui.notify(`Tool grouping unavailable: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
430
377
|
return undefined;
|
|
431
378
|
}
|
|
432
379
|
if (record(instance) && !seen.has(instance)) {
|
|
@@ -469,7 +416,8 @@ export function installHostToolGroups(context) {
|
|
|
469
416
|
!(major > 1 || (major === 1 && (minor > 0 || (minor === 0 && patch >= 1)))))
|
|
470
417
|
throw new Error(`Tool grouping requires Pi >=1.0.1; running ${VERSION}`);
|
|
471
418
|
const state = patchState();
|
|
472
|
-
|
|
419
|
+
const ui = context.ui;
|
|
420
|
+
if (state.owners.has(ui))
|
|
473
421
|
throw new Error("Tool grouping already owns this UI runtime");
|
|
474
422
|
if (state.owners.size === 0) {
|
|
475
423
|
for (const seam of SEAMS) {
|
|
@@ -483,13 +431,15 @@ export function installHostToolGroups(context) {
|
|
|
483
431
|
value: state.wrappers[seam],
|
|
484
432
|
});
|
|
485
433
|
}
|
|
434
|
+
const run = {};
|
|
486
435
|
const owner = {
|
|
487
|
-
|
|
436
|
+
ui,
|
|
488
437
|
hosts: new Map(),
|
|
489
438
|
failed: new WeakSet(),
|
|
490
|
-
|
|
439
|
+
run,
|
|
440
|
+
attach: (host) => attach(host, run),
|
|
491
441
|
};
|
|
492
|
-
state.owners.set(
|
|
442
|
+
state.owners.set(ui, owner);
|
|
493
443
|
// Reload rebuilds history before session_start; reconnect already discovered instances.
|
|
494
444
|
for (const reference of state.hosts) {
|
|
495
445
|
const instance = reference.deref();
|
|
@@ -499,7 +449,7 @@ export function installHostToolGroups(context) {
|
|
|
499
449
|
}
|
|
500
450
|
try {
|
|
501
451
|
const host = readHost(instance);
|
|
502
|
-
if (host.ui ===
|
|
452
|
+
if (host.ui === ui)
|
|
503
453
|
connect(owner, host);
|
|
504
454
|
}
|
|
505
455
|
catch {
|
|
@@ -512,11 +462,20 @@ export function installHostToolGroups(context) {
|
|
|
512
462
|
for (const host of owner.hosts.values())
|
|
513
463
|
host.requestRender();
|
|
514
464
|
},
|
|
465
|
+
beginRun() {
|
|
466
|
+
run.current = { startedAt: performance.now() };
|
|
467
|
+
},
|
|
468
|
+
endRun() {
|
|
469
|
+
const current = run.current;
|
|
470
|
+
if (current !== undefined && current.endedAt === undefined)
|
|
471
|
+
current.endedAt = performance.now();
|
|
472
|
+
},
|
|
515
473
|
dispose() {
|
|
516
474
|
if (disposed)
|
|
517
475
|
return;
|
|
518
476
|
disposed = true;
|
|
519
|
-
|
|
477
|
+
run.current = undefined;
|
|
478
|
+
state.owners.delete(ui);
|
|
520
479
|
for (const host of owner.hosts.values())
|
|
521
480
|
host.dispose();
|
|
522
481
|
owner.hosts.clear();
|
|
@@ -527,7 +486,7 @@ export function installHostToolGroups(context) {
|
|
|
527
486
|
state.wrappers[seam])
|
|
528
487
|
Object.defineProperty(InteractiveMode.prototype, seam, state.originals[seam]);
|
|
529
488
|
else
|
|
530
|
-
|
|
489
|
+
ui.notify("Tool grouping seam changed by another extension; restart Pi to fully unload it.", "warning");
|
|
531
490
|
}
|
|
532
491
|
},
|
|
533
492
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export { type DiffHunk, type DiffLine, type DiffSource, type DiffStats, DiffView
|
|
|
2
2
|
export { createMessageView, type MessageRows, type MessageTone, MessageView, type MessageViewHeader, type MessageViewSpec, } from "./message-view.js";
|
|
3
3
|
export { extractChildToolCopyText, NestedCallsView, type NestedCallsViewProps, } from "./nested-calls-view.js";
|
|
4
4
|
export { type CollapsibleToolRendererOptions, createCollapsibleToolRendererResolver, estimateStreamingTokens, inlinePresentation, isMcpTool, type NestedCallRecord, registerCollapsibleToolRenderer, SPECIALIZED_TOOL_RENDERERS, type SpecializedToolRendererName, type ToolRenderContext, } from "./renderer.js";
|
|
5
|
-
export { type Collapsible, createToolView, type FoldLevel, formatStatusGlyph, formatToolHeaderText, isCollapsible, type RailColorToken, type RailSection, type ToolStatusKind, ToolView, type ToolViewCopySpec, type ToolViewHeaderSpec, type ToolViewSpec, transitionFoldLevel, } from "./tool-view.js";
|
|
5
|
+
export { type Collapsible, createToolView, type FoldLevel, formatStatusGlyph, formatToolHeaderText, isCollapsible, type RailColorToken, type RailSection, type ToolStatusKind, ToolView, type ToolViewChild, type ToolViewCopySpec, type ToolViewHeaderSpec, type ToolViewSpec, transitionFoldLevel, } from "./tool-view.js";
|
|
6
6
|
export { type CapturedToolCall, clearCapturedNestedCalls, getCapturedNestedCalls, recordToolExecutionEnd, recordToolExecutionStart, } from "./tracker.js";
|
package/dist/message-view.d.ts
CHANGED
|
@@ -12,6 +12,18 @@ export declare class MessageView implements Component {
|
|
|
12
12
|
render(width: number): string[];
|
|
13
13
|
invalidate(): void;
|
|
14
14
|
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined;
|
|
15
|
+
/** Provider-declared notice type, used to group notices without reading their text. */
|
|
16
|
+
get kind(): string;
|
|
17
|
+
/**
|
|
18
|
+
* Detail rows inside an activity group: preview and body below the summary row the group already
|
|
19
|
+
* draws, regardless of local fold. Repeating the header there would print the summary twice.
|
|
20
|
+
*/
|
|
21
|
+
detailRows(width: number): string[];
|
|
15
22
|
/** Single-row label used when this notice sits inside an activity group. */
|
|
16
23
|
get activitySummary(): string;
|
|
24
|
+
/**
|
|
25
|
+
* Whether anything would render below the label. `expandable` asks about the body only; a
|
|
26
|
+
* preview-only notice still has detail to show, so the group keys its pin on this instead.
|
|
27
|
+
*/
|
|
28
|
+
get hasDetail(): boolean;
|
|
17
29
|
}
|
package/dist/message-view.js
CHANGED
|
@@ -106,12 +106,8 @@ export class MessageView {
|
|
|
106
106
|
invalidateRows(this.#spec.body);
|
|
107
107
|
}
|
|
108
108
|
handleMouse(event) {
|
|
109
|
-
if (!this.expandable ||
|
|
110
|
-
event.type === "wheel" ||
|
|
111
|
-
event.type !== "click" ||
|
|
112
|
-
event.button !== "left") {
|
|
109
|
+
if (!this.expandable || event.type !== "click" || event.button !== "left")
|
|
113
110
|
return undefined;
|
|
114
|
-
}
|
|
115
111
|
const effectiveWidth = event.width > 0 ? event.width : this.#lastWidth;
|
|
116
112
|
const pad = Math.max(0, this.#spec.outputPad ?? 0);
|
|
117
113
|
const innerX = event.x - pad;
|
|
@@ -125,6 +121,20 @@ export class MessageView {
|
|
|
125
121
|
this.toggle();
|
|
126
122
|
return { handled: true, render: true };
|
|
127
123
|
}
|
|
124
|
+
/** Provider-declared notice type, used to group notices without reading their text. */
|
|
125
|
+
get kind() {
|
|
126
|
+
return this.#spec.header.kind;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Detail rows inside an activity group: preview and body below the summary row the group already
|
|
130
|
+
* draws, regardless of local fold. Repeating the header there would print the summary twice.
|
|
131
|
+
*/
|
|
132
|
+
detailRows(width) {
|
|
133
|
+
const pad = Math.max(0, this.#spec.outputPad ?? 0);
|
|
134
|
+
const safeWidth = Math.max(1, width - pad * 2);
|
|
135
|
+
const leftPad = pad > 0 ? " ".repeat(pad) : "";
|
|
136
|
+
return [...layRows(this.#spec.preview, safeWidth), ...layRows(this.#spec.body, safeWidth)].map((line) => (leftPad === "" ? line : `${leftPad}${line}`));
|
|
137
|
+
}
|
|
128
138
|
/** Single-row label used when this notice sits inside an activity group. */
|
|
129
139
|
get activitySummary() {
|
|
130
140
|
const { theme, header } = this.#spec;
|
|
@@ -133,6 +143,13 @@ export class MessageView {
|
|
|
133
143
|
const summary = summaryRaw === "" ? "" : theme.fg(header.tone ?? "dim", summaryRaw);
|
|
134
144
|
return summary === "" ? kind : `${kind}${theme.fg("dim", " · ")}${summary}`;
|
|
135
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Whether anything would render below the label. `expandable` asks about the body only; a
|
|
148
|
+
* preview-only notice still has detail to show, so the group keys its pin on this instead.
|
|
149
|
+
*/
|
|
150
|
+
get hasDetail() {
|
|
151
|
+
return hasRows(this.#spec.preview) || hasRows(this.#spec.body);
|
|
152
|
+
}
|
|
136
153
|
#header(width, expanded) {
|
|
137
154
|
const pin = this.expandable ? `${this.#spec.theme.fg("dim", expanded ? "▾" : "▸")} ` : "";
|
|
138
155
|
return truncate(`${pin}${this.activitySummary}`, width);
|