pi-subagents-lite 1.4.0 → 1.4.2

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,19 +1,19 @@
1
1
  /**
2
2
  * helpers.ts — Shared helpers for menu modules:
3
3
  * theme builders for SettingsList/SelectList, numeric validation,
4
- * model-option building, and a swappable delegating component.
4
+ * model-option building, a swappable delegating component, and a
5
+ * searchable pick-list submenu factory.
5
6
  */
6
-
7
7
  import type { Component, SettingsListTheme } from "@earendil-works/pi-tui";
8
- import type { ModelOption } from "../../models/model-selector.js";
8
+ import { SearchableSelectDialog, type SelectOption } from "../searchable-select.js";
9
9
  import { parseModelKey } from "../../utils.js";
10
10
 
11
11
  /**
12
- * Build ModelOption[] from raw "provider/model-id" strings.
12
+ * Build SelectOption[] from raw "provider/model-id" strings.
13
13
  * Includes "(inherits parent)" as the first option.
14
14
  */
15
- export function buildModelOptions(rawOptions: string[]): ModelOption[] {
16
- const items: ModelOption[] = [
15
+ export function buildModelOptions(rawOptions: string[]): SelectOption[] {
16
+ const items: SelectOption[] = [
17
17
  { value: "(inherits parent)", label: "(inherits parent)", provider: "" },
18
18
  ];
19
19
 
@@ -33,7 +33,7 @@ export function buildSettingsListTheme(theme: { fg(color: string, text: string):
33
33
  return {
34
34
  label: (text, selected) => selected ? theme.fg("accent", text) : text,
35
35
  value: (text, selected) => selected ? theme.fg("accent", text) : theme.fg("muted", text),
36
- description: (text) => theme.fg("muted", text),
36
+ description: (text) => theme.fg("dim", text),
37
37
  // Use "→ " (2 chars) to match non-selected prefix " " (2 spaces)
38
38
  // This prevents menu items from shifting left/right when cursor moves
39
39
  cursor: theme.fg("accent", "→ "),
@@ -91,3 +91,33 @@ export function buildSelectListTheme(theme: { fg(color: string, text: string): s
91
91
  };
92
92
  }
93
93
 
94
+ /**
95
+ * Build a searchable pick-list submenu backed by SearchableSelectDialog.
96
+ *
97
+ * Hides the delegator-forward-declaration dance shared by every menu that
98
+ * needs "type to filter, Enter to pick" over a flat option list
99
+ * (provider/model/type/worktree selection). onSelect may return a Component
100
+ * to chain into next (e.g. a numeric-input submenu); returning void leaves
101
+ * the submenu as-is so the caller can close it via done().
102
+ */
103
+ export function createSearchableSelect(
104
+ items: SelectOption[],
105
+ callbacks: { onSelect: (value: string) => Component | void; onCancel: () => void },
106
+ theme: any,
107
+ ): Component {
108
+ let delegator: ReturnType<typeof createDelegatingComponent>;
109
+ const selector = new SearchableSelectDialog(
110
+ items,
111
+ null,
112
+ {
113
+ onSelect: (value) => {
114
+ const next = callbacks.onSelect(value);
115
+ if (next) delegator.setActive(next);
116
+ },
117
+ onCancel: callbacks.onCancel,
118
+ },
119
+ theme,
120
+ );
121
+ delegator = createDelegatingComponent(selector);
122
+ return delegator;
123
+ }
@@ -11,11 +11,18 @@
11
11
 
12
12
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
13
13
  import { SettingsList, SelectList, type SettingItem } from "@earendil-works/pi-tui";
14
- import { buildSettingsListTheme, buildSelectListTheme, createDelegatingComponent } from "./helpers.js";
14
+ import {
15
+ buildSettingsListTheme,
16
+ buildSelectListTheme,
17
+ buildModelOptions,
18
+ createDelegatingComponent,
19
+ createSearchableSelect,
20
+ } from "./helpers.js";
15
21
  import { createNumericSubmenu } from "./submenus/numeric-input.js";
16
22
  import { createConfirmSubmenu } from "./submenus/confirm.js";
17
23
  import { SettingsListWrapper } from "./wrappers/settings-list.js";
18
24
  import { getStore } from "../../shell.js";
25
+ import type { SelectOption } from "../searchable-select.js";
19
26
 
20
27
  export async function showConcurrencySettingsMenu(
21
28
  ctx: ExtensionCommandContext,
@@ -52,28 +59,28 @@ export async function showConcurrencySettingsMenu(
52
59
  return delegator;
53
60
  };
54
61
 
55
- // Submenu factory: pick a key from `options`, then enter a value.
56
- const addLimitSubmenu = (
57
- options: string[],
62
+ // Submenu factory: searchable-pick an option, then enter a numeric value.
63
+ // Used for both per-provider and per-model limits; items differ by caller.
64
+ const addPickThenValueSubmenu = (
65
+ items: SelectOption[],
58
66
  onPick: (key: string, parsed: number) => void,
59
- ): SettingItem["submenu"] => (_currentValue, subDone) => {
60
- const list = new SelectList(
61
- options.map((o) => ({ value: o, label: o })),
62
- 10, buildSelectListTheme(theme),
67
+ ): SettingItem["submenu"] => (_currentValue, subDone) =>
68
+ createSearchableSelect(
69
+ items,
70
+ {
71
+ onSelect: (key) =>
72
+ createNumericSubmenu(ctx, { min: 1 }, (parsed) => onPick(key, parsed))("1", subDone),
73
+ onCancel: () => subDone(),
74
+ },
75
+ theme,
63
76
  );
64
- const delegator = createDelegatingComponent(list);
65
- list.onSelect = (item) => {
66
- delegator.setActive(createNumericSubmenu(ctx, { min: 1 }, (parsed) => onPick(item.value, parsed))("1", subDone));
67
- };
68
- list.onCancel = () => subDone();
69
- return delegator;
70
- };
71
77
 
72
78
  // Global default
73
79
  items.push({
74
80
  id: "defaultConcurrency",
75
81
  label: "Default concurrency limit",
76
82
  currentValue: String(store.concurrency.default),
83
+ description: "Concurrent agent slots when no per-provider or per-model limit applies.",
77
84
  submenu: createNumericSubmenu(ctx, (parsed) => {
78
85
  store.mutate.concurrency.setDefault(parsed);
79
86
  ctx.ui.notify(`Default concurrency set to ${parsed}`, "info");
@@ -90,6 +97,7 @@ export async function showConcurrencySettingsMenu(
90
97
  id: `provider:${provider}`,
91
98
  label: provider,
92
99
  currentValue: `${limit} slots`,
100
+ description: `Concurrent slots reserved for agents using the ${provider} provider.`,
93
101
  submenu: editOrRemoveSubmenu(
94
102
  limit,
95
103
  (parsed) => {
@@ -111,7 +119,8 @@ export async function showConcurrencySettingsMenu(
111
119
  id: "addProviderLimit",
112
120
  label: "Add per-provider limit...",
113
121
  currentValue: "",
114
- submenu: addLimitSubmenu(providers, (provider, parsed) => {
122
+ description: "Cap how many agents run at once for a single provider.",
123
+ submenu: addPickThenValueSubmenu(providers.map((o) => ({ value: o, label: o })), (provider, parsed) => {
115
124
  store.mutate.concurrency.setProvider(provider, parsed);
116
125
  ctx.ui.notify(`${provider} concurrency set to ${parsed}`, "info");
117
126
  }),
@@ -128,6 +137,7 @@ export async function showConcurrencySettingsMenu(
128
137
  id: `model:${modelKey}`,
129
138
  label: modelKey,
130
139
  currentValue: `${limit} slots`,
140
+ description: `Concurrent slots reserved for agents using the ${modelKey} model.`,
131
141
  submenu: editOrRemoveSubmenu(
132
142
  limit,
133
143
  (parsed) => {
@@ -149,7 +159,8 @@ export async function showConcurrencySettingsMenu(
149
159
  id: "addModelLimit",
150
160
  label: "Add per-model limit...",
151
161
  currentValue: "",
152
- submenu: addLimitSubmenu(modelOptions, (modelKey, parsed) => {
162
+ description: "Cap how many agents run at once for a single model.",
163
+ submenu: addPickThenValueSubmenu(buildModelOptions(modelOptions), (modelKey, parsed) => {
153
164
  store.mutate.concurrency.setModel(modelKey, parsed);
154
165
  ctx.ui.notify(`${modelKey} concurrency set to ${parsed}`, "info");
155
166
  }),
@@ -162,6 +173,7 @@ export async function showConcurrencySettingsMenu(
162
173
  id: "resetAll",
163
174
  label: "Reset all to defaults",
164
175
  currentValue: "",
176
+ description: "Restore the default limit and remove all per-provider and per-model limits.",
165
177
  submenu: createConfirmSubmenu({
166
178
  message: "Reset all concurrency limits to defaults?",
167
179
  theme,
@@ -10,10 +10,10 @@
10
10
  */
11
11
 
12
12
  import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
13
- import { SettingsList, SelectList, type SettingItem } from "@earendil-works/pi-tui";
13
+ import { SettingsList, type SettingItem } from "@earendil-works/pi-tui";
14
14
  import { getAgentConfig, getAllTypes } from "../../agents/agent-types.js";
15
15
  import { CONFIG_AGENT_NON_MODEL_KEYS } from "../../config/types.js";
16
- import { buildSettingsListTheme, buildSelectListTheme, createDelegatingComponent } from "./helpers.js";
16
+ import { buildSettingsListTheme, createSearchableSelect } from "./helpers.js";
17
17
  import { createModelSelectSubmenu } from "./submenus/model-select.js";
18
18
  import { createConfirmSubmenu } from "./submenus/confirm.js";
19
19
  import { SettingsListWrapper } from "./wrappers/settings-list.js";
@@ -71,6 +71,7 @@ export async function showModelSettingsMenu(
71
71
  id: "defaultModel",
72
72
  label: "Global default model",
73
73
  currentValue: globalDisplayValue,
74
+ description: "Model used when no per-type override or frontmatter model applies.",
74
75
  submenu: createModelSelectSubmenu({
75
76
  modelOptions,
76
77
  showClear: false,
@@ -105,6 +106,7 @@ export async function showModelSettingsMenu(
105
106
  id: `type:${typeName}`,
106
107
  label: typeName,
107
108
  currentValue: `${frontmatterHint}${displayModel}`,
109
+ description: `Per-type model override for the ${typeName} agent type.`,
108
110
  submenu: createModelSelectSubmenu({
109
111
  modelOptions,
110
112
  showClear: hasPerm,
@@ -121,26 +123,26 @@ export async function showModelSettingsMenu(
121
123
  id: "overrideType",
122
124
  label: "Override another type...",
123
125
  currentValue: "",
124
- submenu: (_currentValue, subDone) => {
125
- const typeNames = nonOverridden.map(e => ({ value: e.typeName, label: e.typeName }));
126
- const typeList = new SelectList(typeNames, 10, buildSelectListTheme(theme));
127
- const delegator = createDelegatingComponent(typeList);
128
-
129
- typeList.onSelect = (item) => {
130
- const entry = nonOverridden.find(e => e.typeName === item.value)!;
131
- // Delegate to createModelSelectSubmenu for the 2-step model flow
132
- const modelSubmenu = createModelSelectSubmenu({
133
- modelOptions,
134
- showClear: false,
135
- theme,
136
- onSelect: modelOverrideOnSelect(entry.typeName, entry.typeName),
137
- });
138
- delegator.setActive(modelSubmenu(entry.effectiveModel, subDone));
139
- };
140
- typeList.onCancel = () => subDone();
141
-
142
- return delegator;
143
- },
126
+ description: "Add a model override for an agent type that currently inherits.",
127
+ submenu: (_currentValue, subDone) =>
128
+ createSearchableSelect(
129
+ nonOverridden.map(e => ({ value: e.typeName, label: e.typeName })),
130
+ {
131
+ onSelect: (typeName) => {
132
+ const entry = nonOverridden.find(e => e.typeName === typeName)!;
133
+ // Delegate to createModelSelectSubmenu for the 2-step model flow
134
+ const modelSubmenu = createModelSelectSubmenu({
135
+ modelOptions,
136
+ showClear: false,
137
+ theme,
138
+ onSelect: modelOverrideOnSelect(entry.typeName, entry.typeName),
139
+ });
140
+ return modelSubmenu(entry.effectiveModel, subDone);
141
+ },
142
+ onCancel: () => subDone(),
143
+ },
144
+ theme,
145
+ ),
144
146
  });
145
147
  }
146
148
 
@@ -153,6 +155,7 @@ export async function showModelSettingsMenu(
153
155
  id: "clearSession",
154
156
  label: "Clear session overrides",
155
157
  currentValue: "",
158
+ description: "Discard model overrides set only for this session.",
156
159
  submenu: createConfirmSubmenu({
157
160
  message: "Clear all session overrides?",
158
161
  theme,
@@ -169,6 +172,7 @@ export async function showModelSettingsMenu(
169
172
  id: "clearAll",
170
173
  label: "Clear all overrides",
171
174
  currentValue: "",
175
+ description: "Discard all model overrides (config and session).",
172
176
  submenu: createConfirmSubmenu({
173
177
  message: "Clear all model overrides?",
174
178
  theme,
@@ -27,6 +27,7 @@ export async function showSpawnOptionsMenu(ctx: ExtensionCommandContext): Promis
27
27
  label: "Force background",
28
28
  currentValue: store.agent.forceBackground ? "ON" : "OFF",
29
29
  values: ["ON", "OFF"],
30
+ description: "Spawn every agent in the background by default (no foreground wait).",
30
31
  },
31
32
  {
32
33
  id: "graceTurns",
@@ -36,6 +37,7 @@ export async function showSpawnOptionsMenu(ctx: ExtensionCommandContext): Promis
36
37
  store.mutate.agent.setGraceTurns(parsed);
37
38
  ctx.ui.notify(`Grace turns set to ${parsed}`, "info");
38
39
  }),
40
+ description: "Extra turns after the soft turn limit before a hard abort.",
39
41
  },
40
42
  {
41
43
  id: "defaultMaxTurns",
@@ -48,18 +50,21 @@ export async function showSpawnOptionsMenu(ctx: ExtensionCommandContext): Promis
48
50
  store.mutate.agent.setDefaultMaxTurns(undefined);
49
51
  ctx.ui.notify("Default max turns cleared", "info");
50
52
  }),
53
+ description: "Soft turn limit; agent is steered here, then hard-aborts after grace turns. Blank = unlimited.",
51
54
  },
52
55
  {
53
56
  id: "defaultThinking",
54
57
  label: "Default thinking level",
55
58
  currentValue: store.agent.defaultThinking ?? "inherit",
56
59
  values: ["off", "minimal", "low", "medium", "high", "xhigh", "inherit"],
60
+ description: "Thinking level applied when agent frontmatter omits one.",
57
61
  },
58
62
  {
59
63
  id: "disableDefaultAgents",
60
64
  label: "Disable default agents",
61
65
  currentValue: store.agent.disableDefaultAgents ? "ON" : "OFF",
62
66
  values: ["ON", "OFF"],
67
+ description: "Skip auto-loading built-in agent types next session; only .pi/agents types load.",
63
68
  },
64
69
  ];
65
70
 
@@ -13,7 +13,7 @@ import { SettingsList, SelectList, type SettingItem } from "@earendil-works/pi-t
13
13
  import type { ThinkingLevel } from "../../types.js";
14
14
  import { getAgentConfig, getAvailableTypes, resolveType, discoverNewAgents } from "../../agents/agent-types.js";
15
15
  import { findModelInRegistry } from "../../utils.js";
16
- import { buildSettingsListTheme, buildSelectListTheme } from "./helpers.js";
16
+ import { buildSettingsListTheme, buildSelectListTheme, createSearchableSelect } from "./helpers.js";
17
17
  import { DEFAULT_GRACE_TURNS } from "../../config/config-io.js";
18
18
  import { createModelSelectSubmenu } from "./submenus/model-select.js";
19
19
  import { createNumericSubmenu, createInputSubmenu } from "./submenus/numeric-input.js";
@@ -146,6 +146,7 @@ export async function showSpawnAgentMenu(
146
146
  id: t,
147
147
  label: t,
148
148
  currentValue: t,
149
+ description: getAgentConfig(t)?.description ?? "Agent type",
149
150
  submenu: (_v: string, _subDone: (value?: string) => void) => {
150
151
  done(t);
151
152
  return undefined as any;
@@ -214,6 +215,7 @@ export async function showSpawnAgentMenu(
214
215
  id: "spawn",
215
216
  label: "Spawn",
216
217
  currentValue: "",
218
+ description: "Spawn the agent with current settings",
217
219
  submenu: (_v, done) => {
218
220
  const gtItem = items.find(i => i.id === "graceTurns");
219
221
  const bgItem = items.find(i => i.id === "background");
@@ -304,6 +306,7 @@ export async function showSpawnAgentMenu(
304
306
  id: "model",
305
307
  label: "Model",
306
308
  currentValue: displayModel,
309
+ description: "Override the default model for this agent",
307
310
  submenu: createModelSelectSubmenu({
308
311
  modelOptions,
309
312
  showClear: false,
@@ -317,6 +320,7 @@ export async function showSpawnAgentMenu(
317
320
  id: "background",
318
321
  label: "Background",
319
322
  currentValue: currentBackground ? "ON" : "OFF",
323
+ description: "Run the agent in the background",
320
324
  values: ["ON", "OFF"],
321
325
  },
322
326
  ...(inGitRepo
@@ -324,28 +328,33 @@ export async function showSpawnAgentMenu(
324
328
  id: "worktree",
325
329
  label: "Worktree",
326
330
  currentValue: currentWorktreeLabel,
331
+ description: "Run in a linked git worktree instead of parent cwd",
327
332
  submenu: (_v: string, done: (v?: string) => void) => {
328
333
  const pickerItems = [
329
334
  { value: "Inherits parent cwd", label: "Inherits parent cwd" },
330
335
  ...worktrees.map(wt => {
331
336
  const branchLabel = wt.isDetached ? "detached" : (wt.branch ?? "detached");
332
337
  const truncPath = truncatePath(wt.path);
333
- return { value: wt.path, label: `${branchLabel} · ${truncPath}` };
338
+ return { value: wt.path, label: truncPath, provider: branchLabel };
334
339
  }),
335
340
  ];
336
- const list = new SelectList(pickerItems, 10, buildSelectListTheme(theme));
337
- list.onSelect = (item) => {
338
- if (item.value === "Inherits parent cwd") {
339
- currentWorktreePath = undefined;
340
- done("Inherits parent cwd");
341
- } else {
342
- const wt = worktrees.find(w => w.path === item.value);
343
- currentWorktreePath = wt?.path;
344
- done(wt?.branch ?? "detached");
345
- }
346
- };
347
- list.onCancel = () => done();
348
- return list;
341
+ return createSearchableSelect(
342
+ pickerItems,
343
+ {
344
+ onSelect: (value) => {
345
+ if (value === "Inherits parent cwd") {
346
+ currentWorktreePath = undefined;
347
+ done("Inherits parent cwd");
348
+ } else {
349
+ const wt = worktrees.find(w => w.path === value);
350
+ currentWorktreePath = wt?.path;
351
+ done(wt?.branch ?? "detached");
352
+ }
353
+ },
354
+ onCancel: () => done(),
355
+ },
356
+ theme,
357
+ );
349
358
  },
350
359
  } as SettingItem]
351
360
  : []),
@@ -353,24 +362,28 @@ export async function showSpawnAgentMenu(
353
362
  id: "thinkingLevel",
354
363
  label: "Thinking level",
355
364
  currentValue: currentThinking ?? "inherit",
365
+ description: "Set the reasoning effort level",
356
366
  values: [...THINKING_LEVELS, "inherit"],
357
367
  },
358
368
  {
359
369
  id: "maxTokens",
360
370
  label: "Max tokens",
361
371
  currentValue: fmtNum(currentMaxTokens),
372
+ description: "Maximum tokens the agent can consume",
362
373
  submenu: createNumericSubmenu(ctx, (parsed) => { currentMaxTokens = parsed; }, () => { currentMaxTokens = undefined; }),
363
374
  },
364
375
  {
365
376
  id: "maxTurns",
366
377
  label: "Max turns",
367
378
  currentValue: fmtNum(currentMaxTurns),
379
+ description: "Maximum conversation turns before hard stop",
368
380
  submenu: createNumericSubmenu(ctx, (parsed) => { currentMaxTurns = parsed; }, () => { currentMaxTurns = undefined; }),
369
381
  },
370
382
  {
371
383
  id: "graceTurns",
372
384
  label: "Grace turns",
373
385
  currentValue: String(currentGraceTurns),
386
+ description: "Extra turns after soft limit before abort",
374
387
  submenu: createNumericSubmenu(ctx, { min: 0, default: DEFAULT_GRACE_TURNS }, (parsed) => { currentGraceTurns = parsed; }),
375
388
  },
376
389
  { id: "__sep__", label: " ", currentValue: "" },
@@ -378,12 +391,14 @@ export async function showSpawnAgentMenu(
378
391
  id: "description",
379
392
  label: "Description",
380
393
  currentValue: currentDescription,
394
+ description: "Short label shown in the agents list",
381
395
  submenu: createInputSubmenu(ctx),
382
396
  },
383
397
  {
384
398
  id: "prompt",
385
399
  label: "Prompt",
386
400
  currentValue: prompt,
401
+ description: "The user message sent to the agent",
387
402
  submenu: createInputSubmenu(ctx, { required: true }),
388
403
  }
389
404
  ];
@@ -29,6 +29,7 @@ export async function showSystemPromptMenu(ctx: ExtensionCommandContext): Promis
29
29
  label: "System prompt mode",
30
30
  currentValue: store.agent.systemPromptMode,
31
31
  values: ["replace", "inherit", "custom"],
32
+ description: "How the subagent system prompt is built: replace, inherit, or custom.",
32
33
  },
33
34
  ];
34
35
 
@@ -39,6 +40,7 @@ export async function showSystemPromptMenu(ctx: ExtensionCommandContext): Promis
39
40
  label: "Create prompt file",
40
41
  currentValue: CUSTOM_PROMPT_PATH,
41
42
  values: ["Create"],
43
+ description: `Create ${CUSTOM_PROMPT_PATH} with a starter template for custom mode.`,
42
44
  });
43
45
  }
44
46
 
@@ -48,24 +50,26 @@ export async function showSystemPromptMenu(ctx: ExtensionCommandContext): Promis
48
50
  label: "Include AGENTS.md",
49
51
  currentValue: store.agent.includeContextFiles ? "ON" : "OFF",
50
52
  values: ["ON", "OFF"],
53
+ description: "Load project and ~/.pi/agent AGENTS.md as shared <project_context>.",
51
54
  },
52
55
  {
53
56
  id: "loadSkillsImplicitly",
54
57
  label: "Load skills implicitly",
55
58
  currentValue: store.agent.loadSkillsImplicitly ? "ON" : "OFF",
56
59
  values: ["ON", "OFF"],
60
+ description: "Give new agents all skills when frontmatter omits the field.",
57
61
  },
58
62
  {
59
63
  id: "loadExtensionsImplicitly",
60
64
  label: "Load extensions implicitly",
61
65
  currentValue: store.agent.loadExtensionsImplicitly ? "ON" : "OFF",
62
66
  values: ["ON", "OFF"],
67
+ description: "Give new agents all extensions when frontmatter omits the field.",
63
68
  },
64
69
  );
65
70
 
66
71
  return items;
67
72
  };
68
-
69
73
  let items = buildItems();
70
74
  let rebuild: ((newItems: SettingItem[]) => void) | null = null;
71
75
 
@@ -54,15 +54,29 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
54
54
  store.mutate.widget.setShortcut(newValue === "ON");
55
55
  ctx.ui.notify(`Ctrl+o shortcut ${newValue}`, "info");
56
56
  break;
57
+ case "thinkingBuffer":
58
+ store.mutate.agent.setOutputThinkingBufferSize(newValue === "OFF" ? 0 : Number(newValue));
59
+ ctx.ui.notify(`Thinking buffer ${newValue}`, "info");
60
+ break;
57
61
  }
58
62
  };
59
63
 
60
64
  await ctx.ui.custom((_tui, theme, _kb, done) => {
65
+ const statDescriptions: Record<string, string> = {
66
+ showTools: "Show tool count (🛠 ) in the widget.",
67
+ showTurns: "Show turn count (⟳ ) in the widget.",
68
+ showInput: "Show input tokens (↑) in the widget.",
69
+ showOutput: "Show output tokens (↓) in the widget.",
70
+ showContext: "Show context-fill percent (%) in the widget.",
71
+ showCost: "Show dollar cost ($) in the widget.",
72
+ showTime: "Show elapsed time in the widget.",
73
+ };
61
74
  const statItems: SettingItem[] = [...statConfig.entries()].map(([id, cfg]) => ({
62
75
  id,
63
76
  label: cfg.label,
64
77
  currentValue: cfg.get() ? "ON" : "OFF",
65
78
  values: ["ON", "OFF"],
79
+ description: statDescriptions[id],
66
80
  }));
67
81
 
68
82
  const items: SettingItem[] = [
@@ -71,6 +85,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
71
85
  label: "Force compact mode",
72
86
  currentValue: store.agent.widgetCompact ? "ON" : "OFF",
73
87
  values: ["ON", "OFF"],
88
+ description: "Force compact widget mode regardless of ctrl+o state.",
74
89
  },
75
90
  {
76
91
  id: "maxLines",
@@ -80,6 +95,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
80
95
  store.mutate.widget.setMaxLines(parsed);
81
96
  ctx.ui.notify(`Max lines (full) set to ${parsed}`, "info");
82
97
  }),
98
+ description: "Max body lines in full widget mode (excluding heading).",
83
99
  },
84
100
  {
85
101
  id: "descLengthFull",
@@ -89,6 +105,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
89
105
  store.mutate.widget.setDescLengthFull(parsed);
90
106
  ctx.ui.notify(`Description length (full) set to ${parsed}`, "info");
91
107
  }),
108
+ description: "Max description length shown in full widget mode.",
92
109
  },
93
110
  {
94
111
  id: "maxLinesCompact",
@@ -98,6 +115,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
98
115
  store.mutate.widget.setMaxLinesCompact(parsed);
99
116
  ctx.ui.notify(`Max lines (compact) set to ${parsed}`, "info");
100
117
  }),
118
+ description: "Max body lines in compact widget mode.",
101
119
  },
102
120
  {
103
121
  id: "descLengthCompact",
@@ -107,12 +125,21 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
107
125
  store.mutate.widget.setDescLengthCompact(parsed);
108
126
  ctx.ui.notify(`Description length (compact) set to ${parsed}`, "info");
109
127
  }),
128
+ description: "Max description length shown in compact widget mode.",
110
129
  },
111
130
  {
112
131
  id: "shortcut",
113
132
  label: "Ctrl+o shortcut",
114
133
  currentValue: store.agent.widgetShortcut ? "ON" : "OFF",
115
134
  values: ["ON", "OFF"],
135
+ description: "When ON, ctrl+o toggles compact mode; when OFF, compact is set manually.",
136
+ },
137
+ {
138
+ id: "thinkingBuffer",
139
+ label: "Log file thinking buffer",
140
+ currentValue: store.agent.outputThinkingBufferSize === 0 ? "OFF" : String(store.agent.outputThinkingBufferSize),
141
+ values: ["OFF", "80", "200", "500", "1000"],
142
+ description: "Controls log file thinking buffering in chars. OFF = only at turn end, 80 = flush after 80 chars.",
116
143
  },
117
144
  { id: "__sep__", label: " ", currentValue: "" },
118
145
  {
@@ -121,6 +148,7 @@ export async function showWidgetSettingsMenu(ctx: ExtensionCommandContext): Prom
121
148
  currentValue: "→",
122
149
  submenu: (_currentValue, done2) =>
123
150
  new SettingsList(statItems, 7, buildSettingsListTheme(theme), onChange, () => done2()),
151
+ description: "Toggle which usage stats appear in the widget.",
124
152
  },
125
153
  ];
126
154
 
@@ -2,13 +2,13 @@
2
2
  * model-select-submenu.ts — 2-step model override submenu.
3
3
  *
4
4
  * Step 1: SelectList with override mode (session/permanent/clear)
5
- * Step 2 (if session/permanent): ModelSelectorDialog for model selection
5
+ * Step 2 (if session/permanent): SearchableSelectDialog for model selection
6
6
  *
7
7
  * The submenu factory must be created inside ctx.ui.custom to capture the theme.
8
8
  */
9
9
 
10
10
  import { SelectList, type Component } from "@earendil-works/pi-tui";
11
- import { ModelSelectorDialog, type ModelOption } from "../../../models/model-selector.js";
11
+ import { SearchableSelectDialog, type SelectOption } from "../../../ui/searchable-select.js";
12
12
  import { buildModelOptions, buildSelectListTheme, createDelegatingComponent } from "../helpers.js";
13
13
 
14
14
  export interface ModelSelectSubmenuOptions {
@@ -52,7 +52,7 @@ export function createModelSelectSubmenu(
52
52
  modeList.onCancel = () => done();
53
53
 
54
54
  const modelOpts = buildModelOptions(options.modelOptions);
55
- const modelSelector = new ModelSelectorDialog(
55
+ const modelSelector = new SearchableSelectDialog(
56
56
  modelOpts,
57
57
  _currentValue === "(inherits parent)" ? null : _currentValue,
58
58
  {