@jwilger/pi-development-system 0.50.0 → 0.51.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/package.json +2 -2
- package/skills/strict-lints/SKILL.md +2 -1
- package/src/core/spawn-overrides.ts +2 -2
- package/src/core/test-runner.ts +8 -2
- package/src/review/digest.ts +7 -1
- package/src/subagents/VENDORED.md +1 -1
- package/src/subagents/index.ts +144 -60
- package/src/subagents/orch/inherited-tools.ts +3 -3
- package/src/subagents/orch/mailbox.ts +11 -7
- package/src/subagents/orch/manager.ts +7 -1
- package/src/subagents/orch/runtime.ts +38 -33
- package/src/subagents/orch/tools.ts +12 -10
- package/src/subagents/prefs/agent-import.ts +13 -13
- package/src/subagents/prefs/config.ts +85 -185
- package/src/subagents/prefs/import-discovery.ts +6 -6
- package/src/subagents/prefs/models.ts +6 -10
- package/src/subagents/prefs/settings.ts +38 -38
- package/src/subagents/types.ts +22 -19
- package/src/subagents/ui/agent-navigation-editor.ts +20 -13
- package/src/subagents/ui/dialog.ts +99 -84
- package/src/subagents/ui/import-picker.ts +28 -11
- package/src/subagents/ui/live-agent-view.ts +17 -5
- package/src/subagents/ui/model-picker.ts +26 -17
- package/src/subagents/ui/settings-ui.ts +16 -34
- package/src/subagents/ui/status-ui.ts +140 -51
- package/src/subagents/ui/thread-tree.ts +3 -9
- package/src/subagents/ui/ui.ts +162 -265
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// @ts-nocheck: vendored upstream pi-subagent-manager 0.14.0 compiled under looser options; see src/subagents/VENDORED.md
|
|
2
1
|
import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
3
2
|
import {
|
|
4
3
|
CURSOR_MARKER,
|
|
@@ -10,10 +9,10 @@ import {
|
|
|
10
9
|
} from "@earendil-works/pi-tui";
|
|
11
10
|
import {
|
|
12
11
|
DIALOG_OPTIONS,
|
|
12
|
+
type DialogHost,
|
|
13
13
|
dialogHeight,
|
|
14
14
|
dialogText,
|
|
15
15
|
frameDialog,
|
|
16
|
-
type DialogHost,
|
|
17
16
|
} from "./dialog.ts";
|
|
18
17
|
|
|
19
18
|
export interface ImportPickerItem {
|
|
@@ -76,12 +75,28 @@ class ImportPicker {
|
|
|
76
75
|
private viewport = 1;
|
|
77
76
|
private readonly checked = new Set<string>();
|
|
78
77
|
|
|
78
|
+
private host: DialogHost;
|
|
79
|
+
|
|
80
|
+
private theme: Theme;
|
|
81
|
+
|
|
82
|
+
private readonly items: readonly ImportPickerItem[];
|
|
83
|
+
|
|
84
|
+
private done: (ids: string[] | undefined) => void;
|
|
85
|
+
|
|
79
86
|
constructor(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
) {
|
|
87
|
+
host: DialogHost,
|
|
88
|
+
theme: Theme,
|
|
89
|
+
items: readonly ImportPickerItem[],
|
|
90
|
+
done: (ids: string[] | undefined) => void,
|
|
91
|
+
) {
|
|
92
|
+
this.host = host;
|
|
93
|
+
|
|
94
|
+
this.theme = theme;
|
|
95
|
+
|
|
96
|
+
this.items = items;
|
|
97
|
+
|
|
98
|
+
this.done = done;
|
|
99
|
+
}
|
|
85
100
|
|
|
86
101
|
/** Selected ids in source order. Empty until the user toggles a row. */
|
|
87
102
|
getSelectedIds(): string[] {
|
|
@@ -99,7 +114,9 @@ class ImportPicker {
|
|
|
99
114
|
return this.cursor;
|
|
100
115
|
}
|
|
101
116
|
|
|
102
|
-
invalidate(): void {
|
|
117
|
+
invalidate(): void {
|
|
118
|
+
// Nothing is cached, so there is nothing to invalidate.
|
|
119
|
+
}
|
|
103
120
|
|
|
104
121
|
handleInput(data: string): void {
|
|
105
122
|
const keys = getKeybindings();
|
|
@@ -155,8 +172,8 @@ class ImportPicker {
|
|
|
155
172
|
if (listCount > 0) body.push(this.theme.fg("muted", " No external agents to import"));
|
|
156
173
|
} else {
|
|
157
174
|
const rows = this.items.slice(start, start + listCount);
|
|
158
|
-
for (
|
|
159
|
-
body.push(this.row(
|
|
175
|
+
for (const [offset, row] of rows.entries())
|
|
176
|
+
body.push(this.row(row, start + offset === this.cursor));
|
|
160
177
|
}
|
|
161
178
|
if (gap) body.push("");
|
|
162
179
|
for (const line of detail.slice(0, detailCount)) body.push(this.theme.fg("muted", ` ${line}`));
|
|
@@ -164,7 +181,7 @@ class ImportPicker {
|
|
|
164
181
|
}
|
|
165
182
|
|
|
166
183
|
private countLine(start: number, shown: number): string {
|
|
167
|
-
const position = this.items.length ? this.cursor + 1 : 0;
|
|
184
|
+
const position = this.items.length > 0 ? this.cursor + 1 : 0;
|
|
168
185
|
return this.theme.fg(
|
|
169
186
|
"dim",
|
|
170
187
|
` ${this.scrollHint(start, shown)}${this.checked.size} selected · ${position}/${this.items.length}`,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
wrapTextWithAnsi,
|
|
14
14
|
} from "@earendil-works/pi-tui";
|
|
15
15
|
import type { ThreadService, TranscriptSnapshot } from "../types.ts";
|
|
16
|
-
import {
|
|
16
|
+
import { type DialogHost, dialogText } from "./dialog.ts";
|
|
17
17
|
import { AGENT_PROGRESS_INTERVAL, agentProgressIcon, agentTypeLabel } from "./ui.ts";
|
|
18
18
|
|
|
19
19
|
/** Transcript detail level cycled by the t key: full → preview → compact. */
|
|
@@ -66,7 +66,10 @@ export class LiveAgentView {
|
|
|
66
66
|
private unsubscribe: (() => void) | undefined;
|
|
67
67
|
private renderTimer: ReturnType<typeof setTimeout> | undefined;
|
|
68
68
|
private progressTimer: ReturnType<typeof setInterval> | undefined;
|
|
69
|
-
private cache = new WeakMap<
|
|
69
|
+
private cache = new WeakMap<
|
|
70
|
+
object,
|
|
71
|
+
{ width: number; detail: TranscriptDetail; lines: string[] }
|
|
72
|
+
>();
|
|
70
73
|
private scroll: ScrollView;
|
|
71
74
|
private content: string[] = [];
|
|
72
75
|
private restoreViewport = true;
|
|
@@ -188,7 +191,10 @@ export class LiveAgentView {
|
|
|
188
191
|
|
|
189
192
|
private syncProgress(): void {
|
|
190
193
|
const state = this.snapshot?.thread?.state;
|
|
191
|
-
const active =
|
|
194
|
+
const active =
|
|
195
|
+
this.nerdFontIcons &&
|
|
196
|
+
!this.disposed &&
|
|
197
|
+
!this.error &&
|
|
192
198
|
(state === "starting" || state === "running");
|
|
193
199
|
if (!active) {
|
|
194
200
|
if (this.progressTimer !== undefined) clearInterval(this.progressTimer);
|
|
@@ -333,7 +339,11 @@ export class LiveAgentView {
|
|
|
333
339
|
if (this.detail === "compact")
|
|
334
340
|
return [this.theme.fg("dim", "[Thinking hidden · t cycles view]")];
|
|
335
341
|
// Thinking is model-authored Markdown; preview truncates the rendered lines, not the source.
|
|
336
|
-
const rendered = this.markdown(
|
|
342
|
+
const rendered = this.markdown(
|
|
343
|
+
String(block.thinking ?? "[redacted thinking]"),
|
|
344
|
+
width,
|
|
345
|
+
true,
|
|
346
|
+
);
|
|
337
347
|
if (this.detail === "full" || rendered.length <= 3) return rendered;
|
|
338
348
|
return [...rendered.slice(0, 3), this.theme.fg("dim", "...")];
|
|
339
349
|
}
|
|
@@ -449,7 +459,9 @@ export class LiveAgentView {
|
|
|
449
459
|
}
|
|
450
460
|
case "ls": {
|
|
451
461
|
const limit = num("limit");
|
|
452
|
-
lines = [
|
|
462
|
+
lines = [
|
|
463
|
+
`${title("ls")} ${path ?? "."}${limit != null ? detail(` (limit ${limit})`) : ""}`,
|
|
464
|
+
];
|
|
453
465
|
break;
|
|
454
466
|
}
|
|
455
467
|
default:
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
1
|
+
// biome-ignore-all lint/suspicious/noControlCharactersInRegex: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
|
+
// biome-ignore-all lint/suspicious/useAwait: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
3
3
|
import type { Api, Model } from "@earendil-works/pi-ai";
|
|
4
4
|
import {
|
|
5
|
-
getSelectListTheme,
|
|
6
5
|
type ExtensionCommandContext,
|
|
6
|
+
getSelectListTheme,
|
|
7
7
|
type ScopedModel,
|
|
8
8
|
type Theme,
|
|
9
9
|
} from "@earendil-works/pi-coding-agent";
|
|
@@ -13,14 +13,15 @@ import {
|
|
|
13
13
|
Input,
|
|
14
14
|
Key,
|
|
15
15
|
matchesKey,
|
|
16
|
+
type SelectItem,
|
|
16
17
|
SelectList,
|
|
17
18
|
stripTerminalSequences,
|
|
18
|
-
truncateToWidth,
|
|
19
19
|
Text,
|
|
20
|
-
|
|
20
|
+
truncateToWidth,
|
|
21
21
|
} from "@earendil-works/pi-tui";
|
|
22
|
+
import fuzzysort, { type SnapshotKeys } from "fuzzysort";
|
|
22
23
|
import { modelIdentity } from "../prefs/models.ts";
|
|
23
|
-
import { dialogHeight, frameDialog
|
|
24
|
+
import { DIALOG_OPTIONS, dialogHeight, frameDialog } from "./dialog.ts";
|
|
24
25
|
|
|
25
26
|
const MODEL_EDITOR_LAYOUT = {
|
|
26
27
|
minPrimaryColumnWidth: 24,
|
|
@@ -44,8 +45,8 @@ interface OrderedModelEditorComponentOptions {
|
|
|
44
45
|
theme: Theme;
|
|
45
46
|
availableModels: readonly AvailableModel[];
|
|
46
47
|
scopedModels: readonly ScopedModel[];
|
|
47
|
-
initialModels?: readonly string[];
|
|
48
|
-
modelSuggestions?: readonly string[];
|
|
48
|
+
initialModels?: readonly string[] | undefined;
|
|
49
|
+
modelSuggestions?: readonly string[] | undefined;
|
|
49
50
|
onDone(models: string[]): void;
|
|
50
51
|
onCancel(): void;
|
|
51
52
|
}
|
|
@@ -133,7 +134,7 @@ class OrderedModelEditorComponent extends Container {
|
|
|
133
134
|
this.theme.fg(
|
|
134
135
|
"muted",
|
|
135
136
|
this.withScopeWarning(
|
|
136
|
-
this.suggestedScores.size
|
|
137
|
+
this.suggestedScores.size > 0
|
|
137
138
|
? "Selected first; suggested matches scoped first, then other models; the rest A–Z. Type to search."
|
|
138
139
|
: "Selected first. Search by provider/id or model name; other models A–Z.",
|
|
139
140
|
),
|
|
@@ -244,7 +245,12 @@ class OrderedModelEditorComponent extends Container {
|
|
|
244
245
|
const next = index + (earlier ? -1 : 1);
|
|
245
246
|
// Consume reorder keys even on unselected models or at a list boundary.
|
|
246
247
|
if (index >= 0 && next >= 0 && next < this.draft.length) {
|
|
247
|
-
|
|
248
|
+
const moved = this.draft[index];
|
|
249
|
+
const other = this.draft[next];
|
|
250
|
+
if (moved !== undefined && other !== undefined) {
|
|
251
|
+
this.draft[index] = other;
|
|
252
|
+
this.draft[next] = moved;
|
|
253
|
+
}
|
|
248
254
|
this.refreshPickerList(selected);
|
|
249
255
|
this.tui.requestRender();
|
|
250
256
|
}
|
|
@@ -271,6 +277,11 @@ class OrderedModelEditorComponent extends Container {
|
|
|
271
277
|
this.tui.requestRender();
|
|
272
278
|
}
|
|
273
279
|
|
|
280
|
+
private fallbackIndex(focusSearchResult: boolean, previousIndex: number): number {
|
|
281
|
+
if (focusSearchResult) return 0;
|
|
282
|
+
return Math.max(0, Math.min(previousIndex, this.currentItems.length - 1));
|
|
283
|
+
}
|
|
284
|
+
|
|
274
285
|
private refreshPickerList(selectedValue?: string, focusSearchResult = false): void {
|
|
275
286
|
const previousIndex = this.currentItems.findIndex(
|
|
276
287
|
(item) => item.value === this.selectList.getSelectedItem()?.value,
|
|
@@ -314,11 +325,7 @@ class OrderedModelEditorComponent extends Container {
|
|
|
314
325
|
)
|
|
315
326
|
: this.currentItems.findIndex((item) => item.value === selectedValue);
|
|
316
327
|
list.setSelectedIndex(
|
|
317
|
-
preferredIndex >= 0
|
|
318
|
-
? preferredIndex
|
|
319
|
-
: focusSearchResult
|
|
320
|
-
? 0
|
|
321
|
-
: Math.max(0, Math.min(previousIndex, this.currentItems.length - 1)),
|
|
328
|
+
preferredIndex >= 0 ? preferredIndex : this.fallbackIndex(focusSearchResult, previousIndex),
|
|
322
329
|
);
|
|
323
330
|
list.onSelect = (item) => {
|
|
324
331
|
if (this.currentItems.find((row) => row.value === item.value)?.group === ACTIONS_GROUP) {
|
|
@@ -380,7 +387,8 @@ class OrderedModelEditorComponent extends Container {
|
|
|
380
387
|
}
|
|
381
388
|
}
|
|
382
389
|
const bySuggestionScore = (a: AvailableModel, b: AvailableModel) =>
|
|
383
|
-
this.suggestedScores.get(modelIdentity(b))
|
|
390
|
+
(this.suggestedScores.get(modelIdentity(b)) ?? 0) -
|
|
391
|
+
(this.suggestedScores.get(modelIdentity(a)) ?? 0);
|
|
384
392
|
// Stable score sorting preserves alphabetical order for ties. The last
|
|
385
393
|
// tier retains the registry's alphabetical order, irrespective of scope.
|
|
386
394
|
scoped.sort(bySuggestionScore);
|
|
@@ -397,7 +405,8 @@ class OrderedModelEditorComponent extends Container {
|
|
|
397
405
|
...items(other, "Suggested matches · other models"),
|
|
398
406
|
...items(
|
|
399
407
|
remaining,
|
|
400
|
-
|
|
408
|
+
|
|
409
|
+
scoped.length > 0 || other.length > 0 ? "All other models · A–Z" : "All models · A–Z",
|
|
401
410
|
),
|
|
402
411
|
];
|
|
403
412
|
}
|
|
@@ -1,23 +1,19 @@
|
|
|
1
|
-
//
|
|
1
|
+
// biome-ignore-all lint/complexity/noExcessiveCognitiveComplexity: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
|
+
// biome-ignore-all lint/suspicious/noUnnecessaryConditions: vendored upstream TUI/config code with no tests; rewriting it risks behaviour (see src/subagents/VENDORED.md)
|
|
2
3
|
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
3
4
|
import type { ConfigStore } from "../prefs/config.ts";
|
|
4
|
-
import {
|
|
5
|
-
canOpenDialog,
|
|
6
|
-
dialogInput,
|
|
7
|
-
dialogMenu,
|
|
8
|
-
withDialogSession,
|
|
9
|
-
} from "./dialog.ts";
|
|
10
5
|
import {
|
|
11
6
|
DEFAULT_MANAGER_SETTINGS,
|
|
12
7
|
loadManagerSaveScope,
|
|
13
|
-
saveManagerSaveScope,
|
|
14
|
-
saveManagerSettings,
|
|
15
8
|
type ManagerSettings,
|
|
16
9
|
type ModelSelectionMode,
|
|
17
10
|
type SubagentMode,
|
|
11
|
+
saveManagerSaveScope,
|
|
12
|
+
saveManagerSettings,
|
|
18
13
|
type ToolFilteringMode,
|
|
19
14
|
type WidgetMode,
|
|
20
15
|
} from "../prefs/settings.ts";
|
|
16
|
+
import { canOpenDialog, dialogInput, dialogMenu, withDialogSession } from "./dialog.ts";
|
|
21
17
|
import { editAgentTypes } from "./ui.ts";
|
|
22
18
|
|
|
23
19
|
const FIELDS = [
|
|
@@ -138,9 +134,7 @@ export async function configureAgents(
|
|
|
138
134
|
},
|
|
139
135
|
): Promise<void> {
|
|
140
136
|
if (!canOpenDialog(ctx)) return;
|
|
141
|
-
await withDialogSession(ctx, (scoped) =>
|
|
142
|
-
configureAgentsDialog(scoped, options),
|
|
143
|
-
);
|
|
137
|
+
await withDialogSession(ctx, (scoped) => configureAgentsDialog(scoped, options));
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
async function configureAgentsDialog(
|
|
@@ -167,13 +161,13 @@ async function configureAgentsDialog(
|
|
|
167
161
|
{
|
|
168
162
|
id: "subagentMode",
|
|
169
163
|
label: "Subagent Mode",
|
|
170
|
-
value: MODE_OPTIONS.find((mode) => mode.id === draft.subagentMode)
|
|
164
|
+
value: MODE_OPTIONS.find((mode) => mode.id === draft.subagentMode)?.label,
|
|
171
165
|
help: MODE_HELP,
|
|
172
166
|
},
|
|
173
167
|
{
|
|
174
168
|
id: "toolFiltering",
|
|
175
169
|
label: "Tool Filtering",
|
|
176
|
-
value: TOOL_FILTERING_OPTIONS.find((mode) => mode.id === draft.toolFiltering)
|
|
170
|
+
value: TOOL_FILTERING_OPTIONS.find((mode) => mode.id === draft.toolFiltering)?.label,
|
|
177
171
|
help: TOOL_FILTERING_HELP,
|
|
178
172
|
},
|
|
179
173
|
...FIELDS.map((field) => ({
|
|
@@ -183,13 +177,13 @@ async function configureAgentsDialog(
|
|
|
183
177
|
{
|
|
184
178
|
id: "modelSelection",
|
|
185
179
|
label: "Model Picking",
|
|
186
|
-
value: MODEL_SELECTION_OPTIONS.find((mode) => mode.id === draft.modelSelection)
|
|
180
|
+
value: MODEL_SELECTION_OPTIONS.find((mode) => mode.id === draft.modelSelection)?.label,
|
|
187
181
|
help: MODEL_SELECTION_HELP,
|
|
188
182
|
},
|
|
189
183
|
{
|
|
190
184
|
id: "widgetMode",
|
|
191
185
|
label: "Status Widget",
|
|
192
|
-
value: WIDGET_MODE_OPTIONS.find((mode) => mode.id === draft.widgetMode)
|
|
186
|
+
value: WIDGET_MODE_OPTIONS.find((mode) => mode.id === draft.widgetMode)?.label,
|
|
193
187
|
help: WIDGET_MODE_HELP,
|
|
194
188
|
},
|
|
195
189
|
{
|
|
@@ -225,7 +219,7 @@ async function configureAgentsDialog(
|
|
|
225
219
|
id: "save",
|
|
226
220
|
label: "Save and apply",
|
|
227
221
|
value: dirty ? "(changes)" : undefined,
|
|
228
|
-
valueColor: dirty ? "warning" as const : undefined,
|
|
222
|
+
valueColor: dirty ? ("warning" as const) : undefined,
|
|
229
223
|
help: "Save all values to the selected scope and reload settings now.\nProject overrides still take precedence.\nRunning agents and retained sessions are kept.",
|
|
230
224
|
},
|
|
231
225
|
{
|
|
@@ -243,19 +237,13 @@ async function configureAgentsDialog(
|
|
|
243
237
|
if (!action || action === "cancel") return;
|
|
244
238
|
selectedId = action;
|
|
245
239
|
try {
|
|
246
|
-
const field = FIELDS.find((
|
|
240
|
+
const field = FIELDS.find((candidate) => candidate.id === action);
|
|
247
241
|
if (field) {
|
|
248
|
-
const value = await dialogInput(
|
|
249
|
-
ctx,
|
|
250
|
-
field.label,
|
|
251
|
-
String(draft[field.id]),
|
|
252
|
-
field.help,
|
|
253
|
-
);
|
|
242
|
+
const value = await dialogInput(ctx, field.label, String(draft[field.id]), field.help);
|
|
254
243
|
if (value === undefined) continue;
|
|
255
244
|
const number = Number(value.trim());
|
|
256
245
|
if (
|
|
257
|
-
|
|
258
|
-
!Number.isSafeInteger(number) ||
|
|
246
|
+
!(/^\d+$/.test(value.trim()) && Number.isSafeInteger(number)) ||
|
|
259
247
|
number < 1 ||
|
|
260
248
|
(field.id === "maxLevels" && number > 32)
|
|
261
249
|
)
|
|
@@ -291,10 +279,7 @@ async function configureAgentsDialog(
|
|
|
291
279
|
draft.finalRecap = !draft.finalRecap;
|
|
292
280
|
} else if (action === "scope") {
|
|
293
281
|
if (!ctx.isProjectTrusted())
|
|
294
|
-
ctx.ui.notify(
|
|
295
|
-
"Project settings require a trusted project; saving globally.",
|
|
296
|
-
"warning",
|
|
297
|
-
);
|
|
282
|
+
ctx.ui.notify("Project settings require a trusted project; saving globally.", "warning");
|
|
298
283
|
else {
|
|
299
284
|
const nextScope = scope === "user" ? "project" : "user";
|
|
300
285
|
saveManagerSaveScope(options.agentDir, nextScope);
|
|
@@ -320,10 +305,7 @@ async function configureAgentsDialog(
|
|
|
320
305
|
return;
|
|
321
306
|
}
|
|
322
307
|
} catch (error) {
|
|
323
|
-
ctx.ui.notify(
|
|
324
|
-
error instanceof Error ? error.message : String(error),
|
|
325
|
-
"error",
|
|
326
|
-
);
|
|
308
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
|
|
327
309
|
}
|
|
328
310
|
}
|
|
329
311
|
}
|