@ferris1225/pi-subagents 0.32.2 → 1.0.1
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/README.md +45 -22
- package/agents/reviewer.md +2 -0
- package/package.json +2 -2
- package/src/agents.ts +2 -7
- package/src/announcements.ts +62 -0
- package/src/completion.ts +7 -36
- package/src/config.ts +327 -364
- package/src/dispatch.ts +1682 -1878
- package/src/fixloop.ts +0 -16
- package/src/format.ts +4 -8
- package/src/index.ts +8 -9
- package/src/models.ts +17 -39
- package/src/monitor.ts +64 -175
- package/src/rpc-run.ts +2 -41
- package/src/runtime.ts +272 -285
- package/src/session-fork.ts +0 -4
- package/src/setup.ts +4 -4
- package/src/spawn.ts +542 -562
- package/src/tools.ts +706 -748
- package/src/ui.ts +3 -7
- package/src/widget.ts +90 -178
- package/src/worktree.ts +1 -1
- package/src/inspector-panel.ts +0 -363
- package/src/inspector.ts +0 -369
- package/src/trajectory.ts +0 -503
package/src/inspector-panel.ts
DELETED
|
@@ -1,363 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* /subagents-inspect — a live overlay listing logical sub-agent threads and the
|
|
3
|
-
* selected thread's real-time work: header facts (run id, agent, generation,
|
|
4
|
-
* model chain, thinking, elapsed, usage/cost), the streaming transcript, recent
|
|
5
|
-
* tools, and the append-only orchestration trajectory.
|
|
6
|
-
*
|
|
7
|
-
* Data comes from a read-only snapshot (buildInspectorSnapshot → monitor +
|
|
8
|
-
* runtime threads + inspectorStore); the component itself never mutates
|
|
9
|
-
* runtime state, except for the explicit park/resume keyboard shortcut which
|
|
10
|
-
* delegates to the same thread control surface as subagent_control.
|
|
11
|
-
*
|
|
12
|
-
* Layout: a two-pane master/detail at wide widths, compact single-pane detail
|
|
13
|
-
* when narrow. Every line is fitted with truncateToWidth — pi hard-crashes on
|
|
14
|
-
* over-wide lines.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
18
|
-
import { stripVTControlCharacters } from "node:util";
|
|
19
|
-
import {
|
|
20
|
-
Key,
|
|
21
|
-
matchesKey,
|
|
22
|
-
truncateToWidth,
|
|
23
|
-
visibleWidth,
|
|
24
|
-
type Component,
|
|
25
|
-
type TUI,
|
|
26
|
-
} from "@earendil-works/pi-tui";
|
|
27
|
-
import { buildInspectorSnapshot, formatTrajectoryEvent, type InspectorThreadView } from "./inspector.ts";
|
|
28
|
-
import { formatDuration, formatUsageCompact, isRunActiveStatus, monitor, statusIcon, type RunStatus } from "./monitor.ts";
|
|
29
|
-
import type { SubagentRuntime } from "./runtime.ts";
|
|
30
|
-
import { inspectorStore } from "./trajectory.ts";
|
|
31
|
-
|
|
32
|
-
/** Below this width the overlay falls back to the single-pane layout. */
|
|
33
|
-
const WIDE_MIN = 110;
|
|
34
|
-
const LEFT_PANE_WIDTH = 42;
|
|
35
|
-
const MAX_LIST_ROWS = 12;
|
|
36
|
-
const MAX_TRANSCRIPT_LINES = 9;
|
|
37
|
-
const TICK_MS = 1_000;
|
|
38
|
-
|
|
39
|
-
export interface InspectorOverlayOptions {
|
|
40
|
-
runtime: Pick<SubagentRuntime, "threads">;
|
|
41
|
-
tui: TUI;
|
|
42
|
-
theme: Theme;
|
|
43
|
-
done: () => void;
|
|
44
|
-
notify?: (message: string) => void;
|
|
45
|
-
tickMs?: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export class InspectorOverlay implements Component {
|
|
49
|
-
private selectedId?: number;
|
|
50
|
-
private closed = false;
|
|
51
|
-
private readonly unsubscribeMonitor: () => void;
|
|
52
|
-
private readonly unsubscribeInspector: () => void;
|
|
53
|
-
private readonly timer: ReturnType<typeof setInterval>;
|
|
54
|
-
|
|
55
|
-
constructor(private readonly options: InspectorOverlayOptions) {
|
|
56
|
-
// Live updates: any monitor mutation or trajectory append rerenders.
|
|
57
|
-
this.unsubscribeMonitor = monitor.subscribe(() => this.rerender());
|
|
58
|
-
this.unsubscribeInspector = inspectorStore.subscribe(() => this.rerender());
|
|
59
|
-
this.timer = setInterval(() => this.rerender(), options.tickMs ?? TICK_MS);
|
|
60
|
-
if (typeof this.timer.unref === "function") this.timer.unref();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
private rerender(): void {
|
|
64
|
-
if (this.closed) return;
|
|
65
|
-
try {
|
|
66
|
-
this.options.tui.requestRender();
|
|
67
|
-
} catch {
|
|
68
|
-
/* a closed/failed TUI must not throw through store notifications */
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** Escape path (and any external teardown) unsubscribes cleanly. */
|
|
73
|
-
dispose(): void {
|
|
74
|
-
if (this.closed) return;
|
|
75
|
-
this.closed = true;
|
|
76
|
-
this.unsubscribeMonitor();
|
|
77
|
-
this.unsubscribeInspector();
|
|
78
|
-
clearInterval(this.timer);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
invalidate(): void {
|
|
82
|
-
/* stateless render — nothing cached */
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private move(items: readonly { id: number }[], delta: number): void {
|
|
86
|
-
if (items.length === 0) return;
|
|
87
|
-
const index = items.findIndex((item) => item.id === this.selectedId);
|
|
88
|
-
const current = index === -1 ? items.length - 1 : index;
|
|
89
|
-
const next = (current + delta + items.length) % items.length;
|
|
90
|
-
this.selectedId = items[next].id;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Non-text quick action: park a live thread / resume a parked one. Text
|
|
94
|
-
* actions (steer/retarget with payloads) stay on subagent_control. */
|
|
95
|
-
private togglePark(): void {
|
|
96
|
-
const id = this.selectedId;
|
|
97
|
-
if (id === undefined) return;
|
|
98
|
-
const thread = this.options.runtime.threads.get(id);
|
|
99
|
-
if (!thread) return;
|
|
100
|
-
if (thread.state === "parked") {
|
|
101
|
-
void thread
|
|
102
|
-
.resume(undefined)
|
|
103
|
-
.then((pending) => {
|
|
104
|
-
if (pending.exitCode !== -1) this.options.notify?.(`Could not resume run #${id}: no candidate could start.`);
|
|
105
|
-
})
|
|
106
|
-
.catch((error) => this.options.notify?.(`Could not resume run #${id}: ${errorMessageText(error)}`));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
const phase = thread.control.getPhase();
|
|
110
|
-
if (phase === "queued" || phase === "starting" || phase === "running" || phase === "steering" || phase === "interrupting" || phase === "retrying" || phase === "settled") {
|
|
111
|
-
void thread
|
|
112
|
-
.park()
|
|
113
|
-
.catch((error) => this.options.notify?.(`Could not park run #${id}: ${errorMessageText(error)}`));
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
handleInput(data: string): void {
|
|
118
|
-
if (matchesKey(data, Key.escape)) {
|
|
119
|
-
this.dispose();
|
|
120
|
-
this.options.done();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const { items } = buildInspectorSnapshot({ runtime: this.options.runtime });
|
|
124
|
-
if (this.selectedId === undefined || !items.some((item) => item.id === this.selectedId)) {
|
|
125
|
-
this.selectedId = items.length > 0 ? items[items.length - 1].id : undefined;
|
|
126
|
-
}
|
|
127
|
-
if (matchesKey(data, Key.up) || data === "k") this.move(items, -1);
|
|
128
|
-
else if (matchesKey(data, Key.down) || data === "j" || matchesKey(data, Key.tab)) this.move(items, 1);
|
|
129
|
-
else if (data === "p") this.togglePark();
|
|
130
|
-
this.rerender();
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
render(width: number): string[] {
|
|
134
|
-
const theme = this.options.theme;
|
|
135
|
-
const fit = (line: string): string => truncateToWidth(line, width);
|
|
136
|
-
const snapshot = buildInspectorSnapshot({ runtime: this.options.runtime, selectedId: this.selectedId });
|
|
137
|
-
const { items } = snapshot;
|
|
138
|
-
|
|
139
|
-
// Selection stability: keep the chosen id across updates; fall back to
|
|
140
|
-
// the newest thread only when there is no valid selection.
|
|
141
|
-
if (this.selectedId === undefined || !items.some((item) => item.id === this.selectedId)) {
|
|
142
|
-
this.selectedId = items.length > 0 ? items[items.length - 1].id : undefined;
|
|
143
|
-
}
|
|
144
|
-
const detail =
|
|
145
|
-
this.selectedId === undefined
|
|
146
|
-
? undefined
|
|
147
|
-
: (buildInspectorSnapshot({ runtime: this.options.runtime, selectedId: this.selectedId }).detail ??
|
|
148
|
-
snapshot.detail);
|
|
149
|
-
|
|
150
|
-
const border = fit(theme.fg("accent", "─".repeat(Math.max(1, width))));
|
|
151
|
-
const activeCount = items.filter((item) => isRunActiveStatus(item.status)).length;
|
|
152
|
-
const liveText = activeCount > 0 ? `${activeCount} active` : "no active runs";
|
|
153
|
-
const header = fit(
|
|
154
|
-
`${theme.fg("accent", theme.bold("sub-agents"))} ${theme.fg(activeCount > 0 ? "accent" : "dim", "●")} ${theme.fg("dim", `${liveText} · ${items.length} thread${items.length === 1 ? "" : "s"}`)}`,
|
|
155
|
-
);
|
|
156
|
-
const footer = fit(
|
|
157
|
-
theme.fg(
|
|
158
|
-
"dim",
|
|
159
|
-
"↑/↓ select · p park/resume · subagent_control steer/retarget/park/resume/fork · subagent_stop destroys · Esc close",
|
|
160
|
-
),
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const lines: string[] = [border, header, border];
|
|
164
|
-
if (items.length === 0) {
|
|
165
|
-
lines.push(fit(theme.fg("dim", "No sub-agent threads yet — delegate work with the subagent tool; live progress appears here.")));
|
|
166
|
-
lines.push(border, footer);
|
|
167
|
-
return lines;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (width >= WIDE_MIN && detail) {
|
|
171
|
-
const paneLines = this.renderListPane(items, LEFT_PANE_WIDTH - 2);
|
|
172
|
-
const detailLines = this.renderDetail(detail, width - LEFT_PANE_WIDTH - 3);
|
|
173
|
-
const rows = Math.max(paneLines.length, detailLines.length);
|
|
174
|
-
for (let i = 0; i < rows; i++) {
|
|
175
|
-
const left = padRight(paneLines[i] ?? "", LEFT_PANE_WIDTH);
|
|
176
|
-
const right = detailLines[i] ?? "";
|
|
177
|
-
lines.push(fit(`${left} ${theme.fg("borderMuted", "│")} ${right}`));
|
|
178
|
-
}
|
|
179
|
-
} else {
|
|
180
|
-
lines.push(...this.renderListPane(items, width));
|
|
181
|
-
lines.push(fit(theme.fg("borderMuted", "─ ".repeat(Math.max(1, Math.floor(width / 2))))));
|
|
182
|
-
if (detail) lines.push(...this.renderDetail(detail, width));
|
|
183
|
-
}
|
|
184
|
-
lines.push(border, footer);
|
|
185
|
-
return lines;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private renderListPane(items: ReadonlyArray<{ id: number; agent: string; label: string; status: RunStatus; stateText: string; generation?: number; elapsedMs?: number }>, width: number): string[] {
|
|
189
|
-
const theme = this.options.theme;
|
|
190
|
-
const selIndex = Math.max(0, items.findIndex((item) => item.id === this.selectedId));
|
|
191
|
-
const start = Math.max(0, Math.min(selIndex - Math.floor(MAX_LIST_ROWS / 2), items.length - MAX_LIST_ROWS));
|
|
192
|
-
const visible = items.slice(start, start + MAX_LIST_ROWS);
|
|
193
|
-
const lines: string[] = [];
|
|
194
|
-
for (let i = 0; i < visible.length; i++) {
|
|
195
|
-
const item = visible[i];
|
|
196
|
-
const selected = start + i === selIndex;
|
|
197
|
-
const cursor = selected ? theme.fg("accent", "❯ ") : " ";
|
|
198
|
-
const icon = statusIcon(item.status, theme);
|
|
199
|
-
const safeAgent = safeText(item.agent);
|
|
200
|
-
const safeLabel = safeText(item.label);
|
|
201
|
-
const safeState = safeText(item.stateText);
|
|
202
|
-
const name = selected ? theme.fg("accent", theme.bold(safeAgent)) : theme.fg("accent", safeAgent);
|
|
203
|
-
const labelPart = safeLabel ? theme.fg("dim", ` · ${safeLabel}`) : "";
|
|
204
|
-
const state = theme.fg("dim", ` ${item.elapsedMs !== undefined ? formatDuration(item.elapsedMs) + " " : ""}${safeState}`);
|
|
205
|
-
lines.push(truncateToWidth(`${cursor}${icon} ${theme.fg("dim", `#${item.id}`)} ${name}${labelPart}${state}`, width));
|
|
206
|
-
}
|
|
207
|
-
if (items.length > MAX_LIST_ROWS) {
|
|
208
|
-
lines.push(truncateToWidth(theme.fg("dim", ` ${selIndex + 1}/${items.length} · ↑/↓ for more`), width));
|
|
209
|
-
}
|
|
210
|
-
return lines;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private renderDetail(view: InspectorThreadView, width: number): string[] {
|
|
214
|
-
const theme = this.options.theme;
|
|
215
|
-
const fit = (line: string): string => truncateToWidth(line, width);
|
|
216
|
-
const dim = (text: string): string => theme.fg("dim", text);
|
|
217
|
-
const clean = safeText;
|
|
218
|
-
const lines: string[] = [];
|
|
219
|
-
|
|
220
|
-
// Identity + progress facts. Strip untrusted terminal controls BEFORE
|
|
221
|
-
// applying theme ANSI so OSC/CSI payloads can never reach the terminal.
|
|
222
|
-
const genPart = view.generation !== undefined ? ` · gen ${view.generation}` : "";
|
|
223
|
-
const safeState = clean(view.stateText);
|
|
224
|
-
const safePhase = clean(view.phase ?? "");
|
|
225
|
-
const phasePart = safePhase && safePhase !== safeState ? ` (${safePhase})` : "";
|
|
226
|
-
lines.push(fit(`${theme.fg("accent", theme.bold(`#${view.id} ${clean(view.agent)}`))}${dim(`${genPart} · ${safeState}${phasePart}`)}`));
|
|
227
|
-
if (view.label) lines.push(fit(dim(`label: ${clean(view.label)}`)));
|
|
228
|
-
lines.push(fit(dim(`task: ${oneLine(view.task)}`)));
|
|
229
|
-
const relations = [
|
|
230
|
-
view.forkedFromRunId !== undefined ? `forked from #${view.forkedFromRunId}` : undefined,
|
|
231
|
-
view.forkChildRunIds.length > 0 ? `fork children ${view.forkChildRunIds.map((id) => `#${id}`).join(", ")}` : undefined,
|
|
232
|
-
].filter(Boolean);
|
|
233
|
-
if (relations.length > 0) lines.push(fit(dim(`relation: ${relations.join(" · ")}`)));
|
|
234
|
-
if (view.isolation === "worktree") {
|
|
235
|
-
lines.push(fit(dim(`isolation: worktree · ${view.integrationStatus ?? "active"}`)));
|
|
236
|
-
if (view.originalCwd && view.isolationCwd) lines.push(fit(dim(`cwd: ${clean(view.originalCwd)} → ${clean(view.isolationCwd)}`)));
|
|
237
|
-
if (view.integrationWorktreePath) lines.push(fit(theme.fg("warning", `retained worktree: ${clean(view.integrationWorktreePath)}`)));
|
|
238
|
-
if (view.integrationPatchPath) lines.push(fit(theme.fg("warning", `retained patch: ${clean(view.integrationPatchPath)}`)));
|
|
239
|
-
if (view.integrationError) lines.push(fit(theme.fg("error", `integration: ${clean(view.integrationError)}`)));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Model & thinking.
|
|
243
|
-
const chain = view.modelChain.length > 0 ? view.modelChain.map(clean).join(" → ") : "";
|
|
244
|
-
const currentModel = clean(view.model ?? "");
|
|
245
|
-
const modelLine = view.modelFallbackFrom
|
|
246
|
-
? `${currentModel} (pool fallback from ${clean(view.modelFallbackFrom)})`
|
|
247
|
-
: currentModel;
|
|
248
|
-
const thinkingSuffix = view.thinking ? ` · thinking ${clean(view.thinking)}` : "";
|
|
249
|
-
if (modelLine || chain) {
|
|
250
|
-
lines.push(fit(`${theme.fg("text", modelLine)}${dim(`${chain ? ` · chain: ${chain}` : ""}${thinkingSuffix}`)}`));
|
|
251
|
-
} else if (view.thinking) {
|
|
252
|
-
lines.push(fit(dim(`thinking ${clean(view.thinking)}`)));
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Elapsed + usage/cost + current activity.
|
|
256
|
-
const elapsed = view.elapsedMs !== undefined ? formatDuration(view.elapsedMs) : "—";
|
|
257
|
-
const usage = formatUsageCompact(view.usage);
|
|
258
|
-
const toolsText = view.toolCount > 0 ? ` · ${view.toolCount} tool${view.toolCount === 1 ? "" : "s"}` : "";
|
|
259
|
-
lines.push(fit(dim(`elapsed ${elapsed} · ${usage || "no usage yet"}${toolsText}`)));
|
|
260
|
-
const activity = view.activity ?? view.currentTool;
|
|
261
|
-
if (activity) lines.push(fit(theme.fg("accent", `▸ ${clean(activity)}`)));
|
|
262
|
-
|
|
263
|
-
// Streaming transcript: thinking first (dim), then output text.
|
|
264
|
-
const { text, textTruncated, thinking, thinkingTruncated } = view.transcript;
|
|
265
|
-
if (thinking) {
|
|
266
|
-
const thinkingLines = tailLines(thinking, 3);
|
|
267
|
-
if (thinkingTruncated) lines.push(fit(dim("thinking: … (older output dropped)")));
|
|
268
|
-
else lines.push(fit(dim("thinking:")));
|
|
269
|
-
for (const line of thinkingLines) lines.push(fit(dim(` ${line}`)));
|
|
270
|
-
}
|
|
271
|
-
if (text) {
|
|
272
|
-
const textLines = tailLines(text, MAX_TRANSCRIPT_LINES);
|
|
273
|
-
if (textTruncated) lines.push(fit(dim("output: … (older output dropped)")));
|
|
274
|
-
else lines.push(fit(dim("output:")));
|
|
275
|
-
for (const line of textLines) lines.push(fit(line));
|
|
276
|
-
} else if (isRunActiveStatus(view.status) && !thinking) {
|
|
277
|
-
lines.push(fit(dim("(waiting for first output…)")));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Recent tools.
|
|
281
|
-
if (view.tools.length > 0) {
|
|
282
|
-
lines.push(fit(dim("recent tools:")));
|
|
283
|
-
for (const tool of view.tools) {
|
|
284
|
-
const icon = tool.running ? theme.fg("accent", "●") : tool.isError ? theme.fg("error", "✗") : theme.fg("success", "✓");
|
|
285
|
-
const summary = tool.summary ? dim(` ${clean(tool.summary)}`) : "";
|
|
286
|
-
lines.push(fit(` ${icon} ${clean(tool.tool)}${summary}`));
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Append-only trajectory (current generation, most recent last).
|
|
291
|
-
if (view.trajectory.length > 0) {
|
|
292
|
-
const genLabel = view.generation !== undefined ? `gen ${view.generation} ` : "";
|
|
293
|
-
lines.push(fit(dim(`trajectory (${genLabel}latest ${view.trajectory.length} of ${view.trajectoryTotal}):`)));
|
|
294
|
-
for (const event of view.trajectory) {
|
|
295
|
-
lines.push(fit(` ${dim(`${timeOf(event.at)} `)}${formatTrajectoryEvent(event)}`));
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
return lines;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function safeText(text: string): string {
|
|
303
|
-
return stripVTControlCharacters(text);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function oneLine(text: string): string {
|
|
307
|
-
return safeText(text).replace(/\s+/g, " ").trim();
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/** Last `max` lines of a possibly multi-line, already collected stream. */
|
|
311
|
-
function tailLines(text: string, max: number): string[] {
|
|
312
|
-
const lines = safeText(text).split("\n");
|
|
313
|
-
const tail = lines.slice(-max).map((line) => line.replace(/\s+$/, ""));
|
|
314
|
-
return tail;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function timeOf(at: number): string {
|
|
318
|
-
try {
|
|
319
|
-
const d = new Date(at);
|
|
320
|
-
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`;
|
|
321
|
-
} catch {
|
|
322
|
-
return "--:--:--";
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function padRight(line: string, width: number): string {
|
|
327
|
-
const visible = visibleWidth(line);
|
|
328
|
-
return visible >= width ? line : `${line}${" ".repeat(width - visible)}`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function errorMessageText(error: unknown): string {
|
|
332
|
-
return error instanceof Error ? error.message : String(error);
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
export function registerInspectorCommand(pi: ExtensionAPI, runtime: SubagentRuntime): void {
|
|
336
|
-
pi.registerCommand("subagents-inspect", {
|
|
337
|
-
description:
|
|
338
|
-
"Open the live sub-agent inspector overlay: threads, streaming output, recent tools, and the append-only trajectory per run",
|
|
339
|
-
handler: async (_args, ctx) => {
|
|
340
|
-
if (ctx.mode !== "tui") {
|
|
341
|
-
ctx.ui.notify("/subagents-inspect requires Pi's interactive TUI.", "error");
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
await ctx.ui.custom<undefined>(
|
|
345
|
-
(tui, theme, _keybindings, done) =>
|
|
346
|
-
new InspectorOverlay({
|
|
347
|
-
runtime,
|
|
348
|
-
tui,
|
|
349
|
-
theme,
|
|
350
|
-
done: () => done(undefined),
|
|
351
|
-
notify: (message) => {
|
|
352
|
-
try {
|
|
353
|
-
ctx.ui.notify(message, "warning");
|
|
354
|
-
} catch {
|
|
355
|
-
/* notification failures are non-fatal */
|
|
356
|
-
}
|
|
357
|
-
},
|
|
358
|
-
}),
|
|
359
|
-
{ overlay: true, overlayOptions: { width: "92%", maxHeight: "88%" } },
|
|
360
|
-
);
|
|
361
|
-
},
|
|
362
|
-
});
|
|
363
|
-
}
|