@ian-pascoe/pi-advisor 0.6.0 → 0.8.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/README.md +37 -13
- package/package.json +6 -6
- package/skills/pi-advisor/SKILL.md +1 -1
- package/src/advisor-calibration.ts +62 -0
- package/src/advisor-command.ts +26 -47
- package/src/advisor-evidence.ts +45 -273
- package/src/advisor-menu.ts +52 -257
- package/src/advisor-observer.ts +118 -18
- package/src/advisor-rendering.ts +185 -112
- package/src/advisor-session.ts +32 -1
- package/src/advisor-settings.ts +53 -128
- package/src/index.ts +27 -13
package/src/advisor-menu.ts
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
|
+
import { getSettingsListTheme } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { SettingsList, type Component, type SettingItem } from "@earendil-works/pi-tui";
|
|
1
3
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
SettingsList,
|
|
14
|
-
type Component,
|
|
15
|
-
type SelectItem,
|
|
16
|
-
type SettingItem,
|
|
17
|
-
type TUI,
|
|
18
|
-
type TuiMouseEvent,
|
|
19
|
-
type TuiMouseEventResult,
|
|
20
|
-
} from "@earendil-works/pi-tui";
|
|
4
|
+
cycleDisplay,
|
|
5
|
+
EditorChooser,
|
|
6
|
+
effectiveWithSource,
|
|
7
|
+
errorText,
|
|
8
|
+
ModelPicker,
|
|
9
|
+
nextCycleValue,
|
|
10
|
+
scopeRow,
|
|
11
|
+
SettingsMenu,
|
|
12
|
+
ValueInput,
|
|
13
|
+
type SettingsMenuUi,
|
|
14
|
+
} from "@ian-pascoe/pi-utils/settings-menu";
|
|
21
15
|
import { Value } from "typebox/value";
|
|
22
16
|
import { formatAdvisorOption, type AdvisorRenderTheme } from "./advisor-rendering.js";
|
|
23
17
|
import {
|
|
@@ -61,11 +55,8 @@ export interface AdvisorMenuHost {
|
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
/** Native UI collaborators supplied by `ctx.ui.custom`. */
|
|
64
|
-
export interface AdvisorMenuUi {
|
|
65
|
-
tui: TUI;
|
|
66
|
-
keybindings: KeybindingsManager;
|
|
58
|
+
export interface AdvisorMenuUi extends SettingsMenuUi {
|
|
67
59
|
theme: AdvisorRenderTheme;
|
|
68
|
-
externalEditorCommand?: string;
|
|
69
60
|
}
|
|
70
61
|
|
|
71
62
|
const thinkingLevels = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
|
|
@@ -73,12 +64,18 @@ const thinkingLevels = ["off", "minimal", "low", "medium", "high", "xhigh", "max
|
|
|
73
64
|
const cycleValues = {
|
|
74
65
|
enabled: ["inherit", "on", "off"],
|
|
75
66
|
includeSubagents: ["inherit", "on", "off"],
|
|
67
|
+
anthropicLongCache: ["inherit", "on", "off"],
|
|
76
68
|
thinkingLevel: ["inherit", ...thinkingLevels],
|
|
77
69
|
} as const satisfies Record<string, readonly string[]>;
|
|
78
70
|
type CycleKey = keyof typeof cycleValues;
|
|
79
71
|
|
|
80
72
|
function isCycleKey(key: keyof AdvisorOptions): key is CycleKey {
|
|
81
|
-
return
|
|
73
|
+
return (
|
|
74
|
+
key === "enabled" ||
|
|
75
|
+
key === "includeSubagents" ||
|
|
76
|
+
key === "anthropicLongCache" ||
|
|
77
|
+
key === "thinkingLevel"
|
|
78
|
+
);
|
|
82
79
|
}
|
|
83
80
|
|
|
84
81
|
/** A cycled option's value as shown and cycled; undefined when absent. */
|
|
@@ -92,7 +89,7 @@ const descriptions = {
|
|
|
92
89
|
includeSubagents: "Also review Minimal Subagents Child Agents",
|
|
93
90
|
prompt: "Advisor Prompt; replaces the whole prompt",
|
|
94
91
|
model: "Advisor model; inherit follows the observed agent",
|
|
95
|
-
thinkingLevel: "Advisor thinking level; inherit
|
|
92
|
+
thinkingLevel: "Advisor thinking level; inherit uses the default (high)",
|
|
96
93
|
allowedTools: "Tools the Advisor may call (Tool Grant)",
|
|
97
94
|
catchUpThreshold: "Review Backlog that starts a Catch-up Wait; a positive integer or off",
|
|
98
95
|
reviewTimeoutMs: "Deadline for each Review, in seconds",
|
|
@@ -101,11 +98,15 @@ const descriptions = {
|
|
|
101
98
|
maxFindingsPerReview: "Findings accepted from one Review (1–32)",
|
|
102
99
|
maxNitsPerRequest: "Nits delivered per request; further Nits are dropped (0 delivers none)",
|
|
103
100
|
seedBudgetTokens:
|
|
104
|
-
"Token budget for the Context Seed; auto is a quarter of the Advisor model's context window, at most
|
|
101
|
+
"Token budget for the Context Seed; auto is a quarter of the Advisor model's context window, at most 50k",
|
|
105
102
|
reviewEvery:
|
|
106
103
|
"When Reviews run: every turn, every N turns, or once per request; a tool error reviews at once",
|
|
107
104
|
maxSessionTokens:
|
|
108
|
-
"Advisor Session size that triggers native compaction; auto is half the Advisor model's context window, at most
|
|
105
|
+
"Advisor Session size that triggers native compaction; auto is half the Advisor model's context window, at most 100k",
|
|
106
|
+
maxToolResultChars:
|
|
107
|
+
"Longest tool-result text kept whole in Review Evidence, in characters; longer results keep their head and tail",
|
|
108
|
+
anthropicLongCache:
|
|
109
|
+
"Ask Anthropic for 1h prompt caching on Advisor requests; cache writes cost 2× instead of 1.25×",
|
|
109
110
|
} satisfies Record<keyof AdvisorOptions, string>;
|
|
110
111
|
const inheritRow = "\u0000inherit";
|
|
111
112
|
const inputHints = {
|
|
@@ -118,13 +119,8 @@ const inputHints = {
|
|
|
118
119
|
seedBudgetTokens: "a token count, auto, or inherit",
|
|
119
120
|
reviewEvery: "turn, request, a number of turns, or inherit",
|
|
120
121
|
maxSessionTokens: "a token count, auto, or inherit",
|
|
122
|
+
maxToolResultChars: "a number of characters, or inherit",
|
|
121
123
|
} as const;
|
|
122
|
-
const listActions = [
|
|
123
|
-
"tui.select.up",
|
|
124
|
-
"tui.select.down",
|
|
125
|
-
"tui.select.confirm",
|
|
126
|
-
"tui.select.cancel",
|
|
127
|
-
] as const;
|
|
128
124
|
|
|
129
125
|
/** Convert one typed or selected menu value into a validated change. */
|
|
130
126
|
function parseAdvisorMenuValue(
|
|
@@ -139,6 +135,7 @@ function parseAdvisorMenuValue(
|
|
|
139
135
|
switch (key) {
|
|
140
136
|
case "enabled":
|
|
141
137
|
case "includeSubagents":
|
|
138
|
+
case "anthropicLongCache":
|
|
142
139
|
return { [key]: value === "on" ? true : value === "off" ? false : value };
|
|
143
140
|
case "catchUpThreshold":
|
|
144
141
|
return { catchUpThreshold: value === "off" ? value : Number(value) };
|
|
@@ -156,6 +153,7 @@ function parseAdvisorMenuValue(
|
|
|
156
153
|
case "maxCorrectiveTurns":
|
|
157
154
|
case "maxFindingsPerReview":
|
|
158
155
|
case "maxNitsPerRequest":
|
|
156
|
+
case "maxToolResultChars":
|
|
159
157
|
return { [key]: Number(value) };
|
|
160
158
|
default:
|
|
161
159
|
return { [key]: value };
|
|
@@ -164,188 +162,34 @@ function parseAdvisorMenuValue(
|
|
|
164
162
|
return { action: "set", key, patch: parseAdvisorOptions(patch, scope) };
|
|
165
163
|
}
|
|
166
164
|
|
|
167
|
-
function errorText(cause: unknown): string {
|
|
168
|
-
return cause instanceof Error ? cause.message : String(cause);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Single-line value entry with inline validation. */
|
|
172
|
-
class ValueInput implements Component {
|
|
173
|
-
private readonly input: Input;
|
|
174
|
-
private error: string | undefined;
|
|
175
|
-
|
|
176
|
-
constructor(
|
|
177
|
-
private readonly title: string,
|
|
178
|
-
private readonly hint: string,
|
|
179
|
-
private readonly theme: AdvisorRenderTheme,
|
|
180
|
-
submit: (text: string) => void,
|
|
181
|
-
cancel: () => void,
|
|
182
|
-
) {
|
|
183
|
-
this.input = new Input({ placeholder: hint });
|
|
184
|
-
this.input.focused = true;
|
|
185
|
-
this.input.onSubmit = (text) => {
|
|
186
|
-
try {
|
|
187
|
-
submit(text);
|
|
188
|
-
} catch (cause) {
|
|
189
|
-
this.error = errorText(cause);
|
|
190
|
-
}
|
|
191
|
-
};
|
|
192
|
-
this.input.onEscape = cancel;
|
|
193
|
-
}
|
|
194
|
-
handleInput(data: string): void {
|
|
195
|
-
this.error = undefined;
|
|
196
|
-
this.input.handleInput(data);
|
|
197
|
-
}
|
|
198
|
-
render(width: number): string[] {
|
|
199
|
-
return [
|
|
200
|
-
this.theme.bold(this.title),
|
|
201
|
-
...this.input.render(width),
|
|
202
|
-
this.error ? this.theme.fg("error", `✖ ${this.error}`) : this.theme.fg("dim", this.hint),
|
|
203
|
-
];
|
|
204
|
-
}
|
|
205
|
-
invalidate(): void {
|
|
206
|
-
this.input.invalidate();
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** Fuzzy-searchable model list with an inherit choice. */
|
|
211
|
-
class ModelPicker implements Component {
|
|
212
|
-
private readonly input = new Input({ placeholder: "type to search" });
|
|
213
|
-
private list: SelectList;
|
|
214
|
-
|
|
215
|
-
constructor(
|
|
216
|
-
private readonly models: readonly string[],
|
|
217
|
-
private readonly choose: (value: string) => void,
|
|
218
|
-
private readonly cancel: () => void,
|
|
219
|
-
) {
|
|
220
|
-
this.input.focused = true;
|
|
221
|
-
this.list = this.createList("");
|
|
222
|
-
}
|
|
223
|
-
private createList(query: string): SelectList {
|
|
224
|
-
const items: SelectItem[] = ["inherit", ...this.models].map((value) => ({
|
|
225
|
-
value,
|
|
226
|
-
label: value,
|
|
227
|
-
}));
|
|
228
|
-
const list = new SelectList(
|
|
229
|
-
query ? fuzzyFilter(items, query, (item) => item.value) : items,
|
|
230
|
-
10,
|
|
231
|
-
getSelectListTheme(),
|
|
232
|
-
);
|
|
233
|
-
list.onSelect = (item) => this.choose(item.value);
|
|
234
|
-
list.onCancel = this.cancel;
|
|
235
|
-
return list;
|
|
236
|
-
}
|
|
237
|
-
handleInput(data: string): void {
|
|
238
|
-
const bindings = getKeybindings();
|
|
239
|
-
if (listActions.some((action) => bindings.matches(data, action))) {
|
|
240
|
-
this.list.handleInput(data);
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
this.input.handleInput(data);
|
|
244
|
-
this.list = this.createList(this.input.getValue());
|
|
245
|
-
}
|
|
246
|
-
render(width: number): string[] {
|
|
247
|
-
return [...this.input.render(width), ...this.list.render(width)];
|
|
248
|
-
}
|
|
249
|
-
invalidate(): void {
|
|
250
|
-
this.input.invalidate();
|
|
251
|
-
this.list.invalidate();
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/** Choose between editing the prompt in Pi's editor component and inheriting it. */
|
|
256
|
-
class PromptChooser implements Component {
|
|
257
|
-
private readonly list: SelectList;
|
|
258
|
-
private editor: ExtensionEditorComponent | undefined;
|
|
259
|
-
private error: string | undefined;
|
|
260
|
-
|
|
261
|
-
/** `submit` throws a user-facing message for an invalid prompt; the editor stays open. */
|
|
262
|
-
constructor(
|
|
263
|
-
ui: AdvisorMenuUi,
|
|
264
|
-
prompt: string,
|
|
265
|
-
submit: (text: string) => void,
|
|
266
|
-
inherit: () => void,
|
|
267
|
-
cancel: () => void,
|
|
268
|
-
) {
|
|
269
|
-
this.list = new SelectList(
|
|
270
|
-
[
|
|
271
|
-
{ value: "edit", label: "Edit…" },
|
|
272
|
-
{ value: "inherit", label: "inherit" },
|
|
273
|
-
],
|
|
274
|
-
2,
|
|
275
|
-
getSelectListTheme(),
|
|
276
|
-
);
|
|
277
|
-
this.list.onCancel = cancel;
|
|
278
|
-
this.list.onSelect = (item) => {
|
|
279
|
-
if (item.value === "inherit") {
|
|
280
|
-
inherit();
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
this.editor = new ExtensionEditorComponent(
|
|
284
|
-
ui.tui,
|
|
285
|
-
ui.keybindings,
|
|
286
|
-
"Advisor Prompt",
|
|
287
|
-
prompt,
|
|
288
|
-
(text) => {
|
|
289
|
-
try {
|
|
290
|
-
submit(text);
|
|
291
|
-
} catch (cause) {
|
|
292
|
-
this.error = ui.theme.fg("error", `✖ ${errorText(cause)}`);
|
|
293
|
-
}
|
|
294
|
-
},
|
|
295
|
-
cancel,
|
|
296
|
-
undefined,
|
|
297
|
-
ui.externalEditorCommand,
|
|
298
|
-
);
|
|
299
|
-
this.editor.focused = true;
|
|
300
|
-
};
|
|
301
|
-
}
|
|
302
|
-
handleInput(data: string): void {
|
|
303
|
-
this.error = undefined;
|
|
304
|
-
(this.editor ?? this.list).handleInput(data);
|
|
305
|
-
}
|
|
306
|
-
render(width: number): string[] {
|
|
307
|
-
const lines = (this.editor ?? this.list).render(width);
|
|
308
|
-
return this.error ? [...lines, this.error] : lines;
|
|
309
|
-
}
|
|
310
|
-
invalidate(): void {
|
|
311
|
-
(this.editor ?? this.list).invalidate();
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
165
|
/** Menu row ids that are not Advisor options. */
|
|
316
166
|
const actionRows = { resume: "resume", scope: "scope" } as const;
|
|
317
167
|
|
|
318
168
|
/** `/advisor` settings menu built from Pi's native settings list. */
|
|
319
|
-
export class AdvisorSettingsMenu
|
|
169
|
+
export class AdvisorSettingsMenu extends SettingsMenu {
|
|
320
170
|
private scope: AdvisorSettingScope = "session";
|
|
321
171
|
private view: AdvisorMenuView;
|
|
322
172
|
private rows: SettingItem[] = [];
|
|
323
|
-
private list: SettingsList;
|
|
324
|
-
private error: string | undefined;
|
|
325
173
|
private lastRow: string = actionRows.scope;
|
|
326
|
-
/** Edits run one at a time so each reads the result of the previous one. */
|
|
327
|
-
private pending: Promise<void> = Promise.resolve();
|
|
328
174
|
private submenuOpen = false;
|
|
329
175
|
private rebuildPending = false;
|
|
330
|
-
private readonly border: DynamicBorder;
|
|
331
176
|
|
|
332
177
|
constructor(
|
|
333
178
|
private readonly host: AdvisorMenuHost,
|
|
334
|
-
|
|
335
|
-
|
|
179
|
+
protected override readonly ui: AdvisorMenuUi,
|
|
180
|
+
done: () => void,
|
|
336
181
|
) {
|
|
182
|
+
super("Advisor settings", ui, done);
|
|
337
183
|
this.view = host.view();
|
|
338
|
-
this.
|
|
339
|
-
this.list = this.createList();
|
|
184
|
+
this.createList();
|
|
340
185
|
}
|
|
341
186
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return this.pending;
|
|
187
|
+
protected headline(): readonly string[] {
|
|
188
|
+
return this.view.headline;
|
|
345
189
|
}
|
|
346
190
|
|
|
347
191
|
/** Re-read the host after external state changes, such as a Review starting. */
|
|
348
|
-
refresh(): void {
|
|
192
|
+
override refresh(): void {
|
|
349
193
|
const paused = this.view.paused;
|
|
350
194
|
try {
|
|
351
195
|
this.view = this.host.view();
|
|
@@ -365,8 +209,7 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
365
209
|
|
|
366
210
|
private rebuild(): void {
|
|
367
211
|
this.rebuildPending = false;
|
|
368
|
-
this.
|
|
369
|
-
this.list.selectItem(this.view.paused ? actionRows.resume : this.lastRow);
|
|
212
|
+
this.createList().selectItem(this.view.paused ? actionRows.resume : this.lastRow);
|
|
370
213
|
}
|
|
371
214
|
|
|
372
215
|
private createList(): SettingsList {
|
|
@@ -376,13 +219,7 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
376
219
|
...advisorOptionKeys,
|
|
377
220
|
];
|
|
378
221
|
this.rows = ids.map((id) => this.createRow(id));
|
|
379
|
-
return
|
|
380
|
-
this.rows,
|
|
381
|
-
this.rows.length,
|
|
382
|
-
getSettingsListTheme(),
|
|
383
|
-
(id, value) => this.change(id, value),
|
|
384
|
-
this.done,
|
|
385
|
-
);
|
|
222
|
+
return this.setList(this.rows, (id, value) => this.change(id, value));
|
|
386
223
|
}
|
|
387
224
|
|
|
388
225
|
private createRow(id: string): SettingItem {
|
|
@@ -394,14 +231,7 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
394
231
|
values: ["retry"],
|
|
395
232
|
description: "Retry the Paused Advisor with its current settings",
|
|
396
233
|
};
|
|
397
|
-
if (id === actionRows.scope)
|
|
398
|
-
return {
|
|
399
|
-
id,
|
|
400
|
-
label: "Scope",
|
|
401
|
-
currentValue: this.scope,
|
|
402
|
-
values: [...this.view.scopes],
|
|
403
|
-
description: "Where edits are written",
|
|
404
|
-
};
|
|
234
|
+
if (id === actionRows.scope) return scopeRow(this.scope, this.view.scopes);
|
|
405
235
|
return this.optionRow(advisorOptionKey(id));
|
|
406
236
|
}
|
|
407
237
|
|
|
@@ -432,6 +262,7 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
432
262
|
switch (key) {
|
|
433
263
|
case "enabled":
|
|
434
264
|
case "includeSubagents":
|
|
265
|
+
case "anthropicLongCache":
|
|
435
266
|
case "thinkingLevel":
|
|
436
267
|
// The row shows and cycles this scope's own value, so every state is reachable.
|
|
437
268
|
return {
|
|
@@ -470,8 +301,9 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
470
301
|
submenu: this.submenu(
|
|
471
302
|
key,
|
|
472
303
|
(close) =>
|
|
473
|
-
new
|
|
304
|
+
new EditorChooser(
|
|
474
305
|
this.ui,
|
|
306
|
+
"Advisor Prompt",
|
|
475
307
|
settings.prompt ?? "",
|
|
476
308
|
(text) => {
|
|
477
309
|
// Throws for an invalid prompt, keeping the editor open.
|
|
@@ -560,12 +392,14 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
560
392
|
|
|
561
393
|
/** This scope's own value, or what it inherits; notes when another scope overrides it. */
|
|
562
394
|
private cycleDisplay(key: CycleKey): string {
|
|
563
|
-
const own = cycleValue(key, this.view.authored[this.scope] ?? {});
|
|
564
395
|
const effective = cycleValue(key, this.view.settings);
|
|
565
396
|
const source = this.view.sources[key] ?? "default";
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
397
|
+
return cycleDisplay({
|
|
398
|
+
own: cycleValue(key, this.view.authored[this.scope] ?? {}),
|
|
399
|
+
inEffect: effective === undefined ? "observed agent" : effectiveWithSource(effective, source),
|
|
400
|
+
source,
|
|
401
|
+
scope: this.scope,
|
|
402
|
+
});
|
|
569
403
|
}
|
|
570
404
|
|
|
571
405
|
private change(id: string, value: string): void {
|
|
@@ -586,8 +420,7 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
586
420
|
// text is not one of the values, so ignore its suggestion.
|
|
587
421
|
const values = cycleValues[key];
|
|
588
422
|
const own = cycleValue(key, this.view.authored[this.scope] ?? {}) ?? "inherit";
|
|
589
|
-
const next =
|
|
590
|
-
values[(values.findIndex((option) => option === own) + 1) % values.length] ?? "inherit";
|
|
423
|
+
const next = nextCycleValue(values, own) ?? "inherit";
|
|
591
424
|
let change: AdvisorChange;
|
|
592
425
|
try {
|
|
593
426
|
change = parseAdvisorMenuValue(key, next, this.scope);
|
|
@@ -608,42 +441,4 @@ export class AdvisorSettingsMenu implements Component {
|
|
|
608
441
|
const scope = this.scope;
|
|
609
442
|
this.run(() => this.host.apply(scope, change));
|
|
610
443
|
}
|
|
611
|
-
|
|
612
|
-
private run(task: () => Promise<void>): void {
|
|
613
|
-
this.error = undefined;
|
|
614
|
-
this.pending = this.pending
|
|
615
|
-
.then(task)
|
|
616
|
-
.catch((cause: unknown) => {
|
|
617
|
-
this.error = errorText(cause);
|
|
618
|
-
})
|
|
619
|
-
.finally(() => {
|
|
620
|
-
this.refresh();
|
|
621
|
-
this.ui.tui.requestRender();
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
handleInput(data: string): void {
|
|
626
|
-
this.list.handleInput(data);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
handleMouse(event: TuiMouseEvent): TuiMouseEventResult | undefined {
|
|
630
|
-
return this.list.handleMouse(event);
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
render(width: number): string[] {
|
|
634
|
-
const { theme } = this.ui;
|
|
635
|
-
return [
|
|
636
|
-
...this.border.render(width),
|
|
637
|
-
` ${theme.bold("Advisor settings")}`,
|
|
638
|
-
...this.view.headline.map((line) => ` ${line}`),
|
|
639
|
-
"",
|
|
640
|
-
...this.list.render(width),
|
|
641
|
-
...(this.error ? [theme.fg("error", ` ✖ ${this.error}`)] : []),
|
|
642
|
-
...this.border.render(width),
|
|
643
|
-
];
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
invalidate(): void {
|
|
647
|
-
this.list.invalidate();
|
|
648
|
-
}
|
|
649
444
|
}
|