pi-extended-teams 2.2.8 → 2.2.9

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,4 @@
1
- import { Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
1
+ import { Box, Key, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@mariozechner/pi-tui";
2
2
  import {
3
3
  clearGlobalFavoriteModels,
4
4
  FAVORITE_MODEL_SLOTS,
@@ -116,7 +116,7 @@ function padToWidth(value: string, width: number): string {
116
116
 
117
117
  function styledTitle(theme: any, label: string, active: boolean): string {
118
118
  const title = active ? `▶ ${label}` : ` ${label}`;
119
- return active ? theme.fg("accent", theme.bold(title)) : theme.fg("muted", title);
119
+ return active ? theme.inverse(theme.fg("accent", theme.bold(title))) : theme.fg("muted", title);
120
120
  }
121
121
 
122
122
  function defaultTheme(theme: any) {
@@ -133,12 +133,12 @@ async function loadScopedModels(ctx: any): Promise<AvailableModelOption[]> {
133
133
  return sortAvailableModels(await getAvailableModels(ctx));
134
134
  }
135
135
 
136
- async function showFavoriteModelsPicker(ctx: any): Promise<"saved" | "cancelled" | undefined> {
136
+ async function showFavoriteModelsPicker(ctx: any): Promise<void> {
137
137
  const availableModels = await loadScopedModels(ctx);
138
138
  const availableSet = new Set(availableModels.map((model) => model.qualified));
139
- const draft = cloneFavoriteModels(loadSettings().favoriteModels);
139
+ let draft = cloneFavoriteModels(loadSettings().favoriteModels);
140
140
 
141
- return ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: (value: "saved" | "cancelled") => void) => {
141
+ return ctx.ui.custom((tui: any, theme: any, _keybindings: any, done: () => void) => {
142
142
  const colors = defaultTheme(theme);
143
143
  let selectedSlotIndex = 0;
144
144
  let activeColumnIndex = 0;
@@ -216,11 +216,6 @@ async function showFavoriteModelsPicker(ctx: any): Promise<"saved" | "cancelled"
216
216
  notice = undefined;
217
217
  };
218
218
 
219
- const saveAndClose = () => {
220
- replaceGlobalFavoriteModels(draft);
221
- done("saved");
222
- };
223
-
224
219
  const moveColumn = (delta: number) => {
225
220
  activeColumnIndex = Math.max(0, Math.min(COLUMNS.length - 1, activeColumnIndex + delta));
226
221
  };
@@ -276,35 +271,41 @@ async function showFavoriteModelsPicker(ctx: any): Promise<"saved" | "cancelled"
276
271
  return levels.map((thinking) => `${thinking === config.thinking ? "›" : " "} ${thinking}`);
277
272
  };
278
273
 
279
- const columnRows = (title: string, rows: string[], width: number, active: boolean): string[] => {
280
- return [styledTitle(theme, title, active), ...rows].map((row) => padToWidth(row, width));
274
+ const columnRows = (title: string, rows: string[], active: boolean): string[] => {
275
+ return [styledTitle(theme, title, active), ...rows];
281
276
  };
282
277
 
283
278
  const render = (width: number): string[] => {
284
- const innerWidth = Math.max(1, width);
279
+ const margin = Math.floor(width * 0.1);
280
+ const innerWidth = Math.max(1, width - margin * 2);
285
281
  const thinkingWidth = 16;
286
282
  const slotWidth = Math.max(28, Math.floor(innerWidth * 0.38));
287
283
  const modelWidth = Math.max(28, innerWidth - slotWidth - thinkingWidth - 6);
288
284
  const modelRowCount = modelRowsForTerminal();
289
- const slotRows = columnRows("slots", buildSlotRows(), slotWidth, activeColumn() === "slots");
285
+ const slotRows = columnRows("slots", buildSlotRows(), activeColumn() === "slots");
290
286
  const modelRows = columnRows(
291
- `scoped models${modelFilter ? ` /${modelFilter}` : ""}`,
287
+ "scoped models",
292
288
  buildModelRows(modelRowCount),
293
- modelWidth,
294
289
  activeColumn() === "models",
295
290
  );
296
- const thinkingRows = columnRows("thinking", buildThinkingRows(), thinkingWidth, activeColumn() === "thinking");
291
+ const thinkingRows = columnRows("thinking", buildThinkingRows(), activeColumn() === "thinking");
297
292
  const bodyRows = Math.max(slotRows.length, modelRows.length, thinkingRows.length);
298
293
  const lines = [
299
294
  colors.accent(colors.bold("Agent favorite models")),
300
295
  colors.dim(`${availableModels.length} scoped model(s) available from this Pi session · saves to ${globalSettingsPath()}`),
301
- colors.dim("←/→ or tab: move columns · ↑/↓: change selection · type while in scoped models to filter"),
302
- colors.dim("enter: save · esc: cancel · delete: clear selected slot · ctrl+a: clear all"),
296
+ colors.dim("←/→: move columns · ↑/↓: change selection · type anywhere to filter model names"),
297
+ colors.success("Changes auto-save · esc/enter: leave · delete: clear slot · ctrl+a: clear all"),
303
298
  "",
304
299
  ];
305
300
 
301
+ const columns = [slotRows, modelRows, thinkingRows];
302
+ const widths = [slotWidth, modelWidth, thinkingWidth];
306
303
  for (let index = 0; index < bodyRows; index += 1) {
307
- lines.push(`${slotRows[index] ?? " ".repeat(slotWidth)} │ ${modelRows[index] ?? " ".repeat(modelWidth)} ${thinkingRows[index] ?? ""}`);
304
+ const cells = columns.map((rows, column) => {
305
+ const cell = padToWidth(rows[index] ?? "", widths[column] - 2);
306
+ return column === activeColumnIndex ? `${colors.accent("▌ ")}${cell}` : ` ${cell}`;
307
+ });
308
+ lines.push(cells.join(" │ "));
308
309
  }
309
310
 
310
311
  const config = selectedConfig();
@@ -316,52 +317,60 @@ async function showFavoriteModelsPicker(ctx: any): Promise<"saved" | "cancelled"
316
317
  if (config?.model && !availableSet.has(config.model)) {
317
318
  lines.push(colors.warning("This saved model is not in the scoped model list for the current session."));
318
319
  }
319
- if (activeColumn() === "models" && modelFilter) {
320
+ if (modelFilter) {
320
321
  lines.push(colors.dim(`Filter: ${modelFilter} (backspace clears characters)`));
321
322
  }
322
323
 
323
- return lines.flatMap((line) => wrapTextWithAnsi(line, innerWidth));
324
+ const content = lines.flatMap((line) => wrapTextWithAnsi(line, innerWidth));
325
+ const height = Math.max(1, tui.terminal?.rows ?? 24);
326
+ const top = Math.max(0, Math.floor((height - content.length) / 2));
327
+ return Array.from({ length: height }, (_, index) => `${" ".repeat(margin)}${content[index - top] ?? ""}`);
324
328
  };
325
329
 
326
330
  const handleInput = (data: string) => {
327
- if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
328
- done("cancelled");
329
- return;
330
- }
331
- if (matchesKey(data, Key.enter) || data === "\r" || data === "\n") {
332
- saveAndClose();
331
+ if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.enter) || data === "\r" || data === "\n") {
332
+ done();
333
333
  return;
334
334
  }
335
- if (activeColumn() !== "models" && (data === "q" || data === "Q")) {
336
- done("cancelled");
337
- return;
338
- }
339
- if (matchesKey(data, Key.right) || data === "l" || data === "L" || matchesKey(data, Key.tab)) {
335
+ const previous = cloneFavoriteModels(draft);
336
+ if (matchesKey(data, Key.right)) {
340
337
  moveColumn(1);
341
- } else if (matchesKey(data, Key.left) || data === "h" || data === "H" || matchesKey(data, Key.shift("tab"))) {
338
+ } else if (matchesKey(data, Key.left)) {
342
339
  moveColumn(-1);
343
- } else if (matchesKey(data, Key.down) || (activeColumn() !== "models" && (data === "j" || data === "J"))) {
340
+ } else if (matchesKey(data, Key.down)) {
344
341
  moveActiveSelection(1);
345
- } else if (matchesKey(data, Key.up) || (activeColumn() !== "models" && (data === "k" || data === "K"))) {
342
+ } else if (matchesKey(data, Key.up)) {
346
343
  moveActiveSelection(-1);
347
344
  } else if (matchesKey(data, Key.delete)) {
348
345
  clearSelectedSlot();
349
346
  } else if (matchesKey(data, Key.ctrl("a"))) {
350
347
  clearAllSlots();
351
- } else if (activeColumn() === "models" && matchesKey(data, Key.backspace)) {
348
+ } else if (matchesKey(data, Key.backspace)) {
352
349
  modelFilter = modelFilter.slice(0, -1);
353
350
  modelScroll = 0;
354
- } else if (activeColumn() === "models" && data.length === 1 && data.charCodeAt(0) >= 32) {
351
+ } else if (data.length === 1 && data.charCodeAt(0) >= 32) {
355
352
  modelFilter += data;
356
353
  modelScroll = 0;
357
354
  }
355
+ if (JSON.stringify(draft) !== JSON.stringify(previous)) {
356
+ try {
357
+ replaceGlobalFavoriteModels(draft);
358
+ } catch (error) {
359
+ draft = previous;
360
+ notice = `Could not save changes: ${error instanceof Error ? error.message : String(error)}`;
361
+ }
362
+ }
358
363
  tui.requestRender();
359
364
  };
360
365
 
361
- return { render, invalidate() {}, handleInput };
366
+ // Truncation emits full ANSI resets; repaint the background after each one.
367
+ const box = new Box(0, 0, (text: string) => text.split("\x1b[0m")
368
+ .map(part => theme.bg("toolPendingBg", part)).join("\x1b[0m"));
369
+ box.addChild({ render, invalidate() {} });
370
+ return { render: (width: number) => box.render(width), invalidate: () => box.invalidate(), handleInput };
362
371
  }, {
363
372
  overlay: true,
364
- overlayOptions: { width: "94%", maxHeight: "86%", anchor: "center" },
373
+ overlayOptions: { width: "100%", maxHeight: "100%", anchor: "center", margin: 0 },
365
374
  });
366
375
  }
367
376
 
@@ -375,8 +384,7 @@ export function registerFavoriteModelsCommand(pi: any): void {
375
384
  try {
376
385
  if (!action) {
377
386
  if (ctx.mode === "tui" && typeof ctx.ui.custom === "function") {
378
- const result = await showFavoriteModelsPicker(ctx);
379
- if (result === "saved") ctx.ui.notify("Agent favorite models saved.", "info");
387
+ await showFavoriteModelsPicker(ctx);
380
388
  return;
381
389
  }
382
390
  ctx.ui.notify(formatCurrentSettings(), "info");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extended-teams",
3
- "version": "2.2.8",
3
+ "version": "2.2.9",
4
4
  "description": "Control-first, session-connected subagents for Pi with live navigation and intent-tier routing",
5
5
  "repository": {
6
6
  "type": "git",