@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.
@@ -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
- private host: DialogHost,
81
- private theme: Theme,
82
- private readonly items: readonly ImportPickerItem[],
83
- private done: (ids: string[] | undefined) => void,
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 (let offset = 0; offset < rows.length; offset++)
159
- body.push(this.row(rows[offset]!, start + offset === this.cursor));
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 { dialogText, type DialogHost } from "./dialog.ts";
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<object, { width: number; detail: TranscriptDetail; lines: string[] }>();
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 = this.nerdFontIcons && !this.disposed && !this.error &&
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(String(block.thinking ?? "[redacted thinking]"), width, true);
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 = [`${title("ls")} ${path ?? "."}${limit != null ? detail(` (limit ${limit})`) : ""}`];
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
- // @ts-nocheck: vendored upstream pi-subagent-manager 0.14.0 compiled under looser options; see src/subagents/VENDORED.md
2
- import fuzzysort, { type SnapshotKeys } from "fuzzysort";
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
- type SelectItem,
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, DIALOG_OPTIONS } from "./dialog.ts";
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
- [this.draft[index], this.draft[next]] = [this.draft[next], this.draft[index]];
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))! - this.suggestedScores.get(modelIdentity(a))!;
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
- scoped.length || other.length ? "All other models · A–Z" : "All models · A–Z",
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
- // @ts-nocheck: vendored upstream pi-subagent-manager 0.14.0 compiled under looser options; see src/subagents/VENDORED.md
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)!.label,
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)!.label,
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)!.label,
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)!.label,
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((field) => field.id === action);
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
- !/^\d+$/.test(value.trim()) ||
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
  }