pi-ultracode 0.3.0 → 0.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ultracode",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Claude-Code-style \"ultracode\" for Pi: an effort mode that defaults to deterministic multi-agent workflow orchestration, with worktree isolation, per-agent model overrides, custom agent types, nested workflows, resumable runs, and a /workflows manager.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -53,10 +53,10 @@
53
53
  "acorn": "^8.11.0"
54
54
  },
55
55
  "devDependencies": {
56
- "@earendil-works/pi-agent-core": "^0.83.0",
57
- "@earendil-works/pi-ai": "^0.83.0",
58
- "@earendil-works/pi-coding-agent": "^0.83.0",
59
- "@earendil-works/pi-tui": "^0.83.0",
56
+ "@earendil-works/pi-agent-core": "^0.84.0",
57
+ "@earendil-works/pi-ai": "^0.84.0",
58
+ "@earendil-works/pi-coding-agent": "^0.84.0",
59
+ "@earendil-works/pi-tui": "^0.84.0",
60
60
  "@types/node": "^24.12.4",
61
61
  "typebox": "1.3.7",
62
62
  "typescript": "^5.9.3"
@@ -619,9 +619,27 @@ export function resolveSessionThinkingLevel(
619
619
 
620
620
  function matchExactModelIn(models: ModelLike[] | undefined, pattern: string): ModelLike | undefined {
621
621
  if (!models || !pattern.trim()) return undefined;
622
- const lower = pattern.trim().toLowerCase();
623
- return models.find((m) => `${m.provider}/${m.id}`.toLowerCase() === lower)
624
- ?? models.find((m) => m.id.toLowerCase() === lower);
622
+ const trimmed = pattern.trim();
623
+ const lower = trimmed.toLowerCase();
624
+ const canonicalMatches = models.filter((model) =>
625
+ `${model.provider}/${model.id}`.toLowerCase() === lower
626
+ );
627
+ if (canonicalMatches.length === 1) return canonicalMatches[0];
628
+ if (canonicalMatches.length > 1) throw ambiguousModelReferenceError(trimmed, canonicalMatches);
629
+
630
+ const idMatches = models.filter((model) => model.id.toLowerCase() === lower);
631
+ if (idMatches.length === 1) return idMatches[0];
632
+ if (idMatches.length > 1) throw ambiguousModelReferenceError(trimmed, idMatches);
633
+ return undefined;
634
+ }
635
+
636
+ function ambiguousModelReferenceError(pattern: string, matches: ModelLike[]): Error {
637
+ const references = [...new Set(matches.map((model) => `${model.provider}/${model.id}`))]
638
+ .sort((left, right) => left.localeCompare(right))
639
+ .join(", ");
640
+ return new Error(
641
+ `Model "${safeDisplayText(pattern, 80)}" is ambiguous across providers: ${safeDisplayText(references, 400)}. Use provider/model.`,
642
+ );
625
643
  }
626
644
 
627
645
  /** Match a model pattern against a registry list: exact provider/id, then exact id, then substring. */
@@ -24,6 +24,12 @@ const MIN_SPLIT_WIDTH = 100;
24
24
  const MAX_RENDER_FPS_INTERVAL_MS = 100;
25
25
  let overlayOpen = false;
26
26
 
27
+ type OverlayNavigationKey = "pageUp" | "pageDown" | "end";
28
+
29
+ function matchesOverlayNavigationKey(data: string, key: OverlayNavigationKey): boolean {
30
+ return matchesKey(data, key) || matchesKey(data, `ctrl+${key}`);
31
+ }
32
+
27
33
  interface TaskViewport {
28
34
  offset: number;
29
35
  follow: boolean;
@@ -318,8 +324,8 @@ export class WorkflowOverlayComponent implements Component {
318
324
  if (currentIndex >= 0) this.selectedTaskIndex = currentIndex;
319
325
  if (matchesKey(data, "up")) this.selectedTaskIndex = Math.max(0, this.selectedTaskIndex - 1);
320
326
  else if (matchesKey(data, "down")) this.selectedTaskIndex = Math.min(tasks.length - 1, this.selectedTaskIndex + 1);
321
- else if (matchesKey(data, "pageUp")) this.selectedTaskIndex = Math.max(0, this.selectedTaskIndex - 10);
322
- else if (matchesKey(data, "pageDown")) this.selectedTaskIndex = Math.min(tasks.length - 1, this.selectedTaskIndex + 10);
327
+ else if (matchesOverlayNavigationKey(data, "pageUp")) this.selectedTaskIndex = Math.max(0, this.selectedTaskIndex - 10);
328
+ else if (matchesOverlayNavigationKey(data, "pageDown")) this.selectedTaskIndex = Math.min(tasks.length - 1, this.selectedTaskIndex + 10);
323
329
  else if (matchesKey(data, "enter") || matchesKey(data, "return")) {
324
330
  if (split) this.focus = "detail";
325
331
  else {
@@ -341,13 +347,13 @@ export class WorkflowOverlayComponent implements Component {
341
347
  } else if (matchesKey(data, "down")) {
342
348
  viewport.offset++;
343
349
  viewport.follow = false;
344
- } else if (matchesKey(data, "pageUp")) {
350
+ } else if (matchesOverlayNavigationKey(data, "pageUp")) {
345
351
  viewport.offset = Math.max(0, viewport.offset - page);
346
352
  viewport.follow = false;
347
- } else if (matchesKey(data, "pageDown")) {
353
+ } else if (matchesOverlayNavigationKey(data, "pageDown")) {
348
354
  viewport.offset += page;
349
355
  viewport.follow = false;
350
- } else if (matchesKey(data, "end")) {
356
+ } else if (matchesOverlayNavigationKey(data, "end")) {
351
357
  viewport.follow = true;
352
358
  viewport.newLines = 0;
353
359
  }
@@ -456,9 +462,13 @@ export class WorkflowOverlayComponent implements Component {
456
462
  viewport.offset = Math.max(0, Math.min(viewport.offset, maxOffset));
457
463
  }
458
464
  const visible = body.slice(viewport.offset, viewport.offset + bodyHeight);
465
+ // Fullscreen owns unmodified paging keys before focused overlays receive input.
466
+ // Ctrl variants remain routed to the overlay under Pi 0.84's default bindings.
467
+ const pageKeys = this.tui.mode === "fullscreen" ? "Ctrl+PgUp/Dn" : "PgUp/Dn";
468
+ const endKey = this.tui.mode === "fullscreen" ? "Ctrl+End" : "End";
459
469
  const footer = viewport.follow
460
- ? "↑↓/PgUp scroll · End follow · p prompt · Tab tasks · Esc back"
461
- : `↓ ${viewport.newLines} new lines · End to follow`;
470
+ ? `↑↓/${pageKeys} scroll · ${endKey} follow · p prompt · Tab tasks · Esc back`
471
+ : `↓ ${viewport.newLines} new lines · ${endKey} to follow`;
462
472
  return fitHeight([...fixed, "", ...visible, this.theme.fg("dim", footer)], height);
463
473
  }
464
474