@timurproko/a1 0.2.7-dev.1269 → 0.2.7-dev.1275

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.
Files changed (117) hide show
  1. package/README.md +6 -0
  2. package/dist/app/session-shell/apps-dialog.d.ts +33 -0
  3. package/dist/app/session-shell/apps-dialog.js +61 -0
  4. package/dist/app/session-shell/dialog-coordinator.d.ts +8 -0
  5. package/dist/app/session-shell/dialog-coordinator.js +72 -3
  6. package/dist/app/session-shell/session-selectors.d.ts +1 -1
  7. package/dist/app/session-shell/session-selectors.js +1 -1
  8. package/dist/app/session-shell/session-shell.js +9 -3
  9. package/dist/app/session-shell/shell-host.d.ts +1 -0
  10. package/dist/app/session-shell/shell-host.js +22 -11
  11. package/dist/app/session-shell/workflow-output.js +12 -0
  12. package/dist/app/session-shell/workflow-router.d.ts +1 -1
  13. package/dist/composition/owned-ui.js +17 -13
  14. package/dist/contracts/owned-ui/session-presenters.d.ts +1 -1
  15. package/dist/contracts/owned-ui/session-workflows.d.ts +3 -1
  16. package/dist/contracts/owned-ui/shell-components.d.ts +2 -0
  17. package/dist/contracts/presentation/progress-palette.generated.d.ts +1 -1
  18. package/dist/contracts/presentation/progress-palette.generated.js +1 -1
  19. package/dist/contracts/presentation/terminal.d.ts +4 -0
  20. package/dist/contracts/presentation/theme.d.ts +2 -0
  21. package/dist/foundation/lifecycle/user-exports.d.ts +5 -0
  22. package/dist/foundation/lifecycle/user-exports.js +37 -0
  23. package/dist/foundation/startup/startup-descriptor.js +2 -2
  24. package/dist/integrations/pi/components/models-dialog-loader.d.ts +11 -0
  25. package/dist/integrations/pi/components/models-dialog-loader.js +45 -0
  26. package/dist/integrations/pi/components/models-dialog.d.ts +2 -0
  27. package/dist/integrations/pi/components/models-dialog.js +13 -7
  28. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  29. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  30. package/dist/integrations/pi/components/shell-editor-autocomplete.js +21 -15
  31. package/dist/integrations/pi/components/shell-footer-status.js +3 -2
  32. package/dist/integrations/pi/components/shell-presenters-info.js +1 -1
  33. package/dist/integrations/pi/components/shell-presenters-transcript.js +3 -1
  34. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +2 -1
  35. package/dist/integrations/pi/components/shell-selectors-dialogs.js +2 -8
  36. package/dist/integrations/pi/components/theme.d.ts +2 -0
  37. package/dist/integrations/pi/components/theme.js +5 -0
  38. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +8 -7
  39. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +13 -7
  40. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  41. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  42. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  43. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  44. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  45. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  46. package/dist/integrations/pi/components/upstream/components/extension-selector.js +9 -2
  47. package/dist/integrations/pi/components/upstream/components/mermaid.d.ts +2 -1
  48. package/dist/integrations/pi/components/upstream/components/mermaid.js +26 -21
  49. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  50. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  51. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  52. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  53. package/dist/integrations/pi/components/upstream/components/session-footer.js +9 -14
  54. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  55. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  56. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +6 -2
  57. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +27 -3
  58. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  59. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  60. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  61. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  62. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  63. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  64. package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
  65. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  66. package/dist/integrations/pi/components/upstream/components/tree-selector.js +7 -6
  67. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  68. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  69. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  70. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -2
  71. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  72. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  73. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  74. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  75. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  76. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  77. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  78. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  79. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  80. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  81. package/dist/integrations/pi/engine/adapter.d.ts +1 -1
  82. package/dist/integrations/pi/engine/adapter.js +3 -3
  83. package/dist/integrations/pi/engine/host.d.ts +1 -1
  84. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  85. package/dist/integrations/pi/engine/workflow-export.d.ts +4 -0
  86. package/dist/integrations/pi/engine/workflow-export.js +25 -0
  87. package/dist/integrations/pi/engine/workflow-runner.d.ts +1 -0
  88. package/dist/integrations/pi/engine/workflow-runner.js +6 -10
  89. package/dist/integrations/pi/engine/workflow-support.d.ts +2 -1
  90. package/dist/integrations/pi/engine/workflow-support.js +28 -25
  91. package/dist/integrations/pi/engine/workflows.d.ts +6 -1
  92. package/dist/integrations/pi/engine/workflows.js +1 -1
  93. package/dist/integrations/pi/session-presenters/presentation.js +2 -1
  94. package/dist/integrations/pi/session-presenters/presenters.js +22 -10
  95. package/dist/integrations/pi/startup-public.js +289 -240
  96. package/dist/integrations/pi/startup-public.manifest.json +579 -363
  97. package/dist/integrations/pi/tui-runtime/adapter.js +3 -1
  98. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  99. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  100. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +5 -2
  101. package/dist/integrations/pi/tui-runtime/overlay-geometry.js +12 -2
  102. package/dist/runtime-payload-inventory.json +8044 -6930
  103. package/dist/ui/components/dialog-panel.d.ts +4 -3
  104. package/dist/ui/components/dialog-panel.js +9 -13
  105. package/dist/ui/components/index.d.ts +1 -1
  106. package/dist/ui/components/index.js +1 -1
  107. package/dist/ui/components/scrollbar.d.ts +2 -2
  108. package/dist/ui/components/scrollbar.js +2 -2
  109. package/dist/ui/components/spans.d.ts +7 -0
  110. package/dist/ui/components/spans.js +63 -1
  111. package/dist/ui/components/surface.js +1 -1
  112. package/docs/architecture/prompt-suggestions.md +1 -1
  113. package/docs/architecture/ui-reference-provenance.md +2 -1
  114. package/docs/features/prompt-history.md +1 -1
  115. package/docs/features/session-exports.md +15 -0
  116. package/docs/local-worktree-cleanup.md +24 -0
  117. package/package.json +6 -6
@@ -0,0 +1,37 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstat, mkdir, open, unlink } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { dirname, isAbsolute, join, resolve } from "node:path";
5
+ import { PRODUCT_IDENTITY } from "../../product-identity.js";
6
+ /** Persistent user-owned exports; development data/config overrides never relocate user files. */
7
+ function resolveUserExportsDirectory(home = process.env[PRODUCT_IDENTITY.environment.profileHome] ?? homedir()) {
8
+ if (!isAbsolute(home) || home.includes("\0"))
9
+ throw new TypeError("User export home must be absolute");
10
+ return resolve(home, dirname(PRODUCT_IDENTITY.state.piAgentProfile), "exports");
11
+ }
12
+ /** Exclusively reserve a default export; failure cleanup may remove only this same regular file. */
13
+ export async function reserveUserExport(sessionId, format, home) {
14
+ const directory = resolveUserExportsDirectory(home);
15
+ const parent = dirname(directory);
16
+ await mkdir(parent, { recursive: true });
17
+ if (!(await lstat(parent)).isDirectory())
18
+ throw new Error("User export directory must not traverse a link");
19
+ await mkdir(directory, { recursive: true });
20
+ if (!(await lstat(directory)).isDirectory())
21
+ throw new Error("User export directory must not traverse a link");
22
+ const id = sessionId.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 80) || "session";
23
+ const stamp = new Date().toISOString().replace(/[:.]/g, "-");
24
+ const path = join(directory, `${PRODUCT_IDENTITY.filesystem.slug}-session-${stamp}-${id}-${randomUUID()}.${format}`);
25
+ const file = await open(path, "wx", 0o600);
26
+ const identity = await file.stat();
27
+ await file.close();
28
+ return {
29
+ path,
30
+ async failed() {
31
+ const current = await lstat(path).catch(error => { if (error.code !== "ENOENT")
32
+ throw error; });
33
+ if (current?.isFile() && current.dev === identity.dev && current.ino === identity.ino)
34
+ await unlink(path);
35
+ },
36
+ };
37
+ }
@@ -10,7 +10,7 @@ export const STARTUP_DESCRIPTOR = Object.freeze({
10
10
  ],
11
11
  "generatedArtifact": {
12
12
  "path": "dist/integrations/pi/startup-public.js",
13
- "sha256": "97a7715b6a24a7449ecb61d3581e27396ebde40d3d2d0b6f701415c82e74f6b2"
13
+ "sha256": "e6a12f0ec3c5f2ada12bb29f8a02584f0724b37d107c29608b6629a4196cca13"
14
14
  },
15
15
  "dependencyLayerIdentity": {
16
16
  "source": "A1_RELEASE_LAYERS",
@@ -19,7 +19,7 @@ export const STARTUP_DESCRIPTOR = Object.freeze({
19
19
  "compileCacheNamespace": {
20
20
  "strategy": "node-abi-version-and-ordered-layer-sha256-v1"
21
21
  },
22
- "identity": "c1f84ec1184782905d628eba21b4c0e62c643e0a37481d4bb42e47e768d5d48f"
22
+ "identity": "1823698969cb8a551539b6351a7ba7b0756121369e6f9bf898347080d7892ece"
23
23
  });
24
24
 
25
25
  export function loadDeclaredStartupGraph() {
@@ -0,0 +1,11 @@
1
+ import type { OwnedUiShellComponentPort } from "../../../contracts/owned-ui/index.js";
2
+ import type { ModelsDialogConfig, ModelsDialogCallbacks, ModelsDialogModel } from "./models-dialog.js";
3
+ interface DeferredModelsDialogPort extends OwnedUiShellComponentPort {
4
+ readonly ready: Promise<void>;
5
+ updateModels(models: readonly ModelsDialogModel[]): void;
6
+ setRefreshStatus(message: string, kind: "muted" | "success" | "warning"): void;
7
+ dirty(): boolean;
8
+ }
9
+ /** Captures input immediately while keeping the optional Models controller outside the startup graph. */
10
+ export declare function createDeferredModelsDialog(options: ModelsDialogConfig & ModelsDialogCallbacks): DeferredModelsDialogPort;
11
+ export {};
@@ -0,0 +1,45 @@
1
+ /** Captures input immediately while keeping the optional Models controller outside the startup graph. */
2
+ export function createDeferredModelsDialog(options) {
3
+ let dialog;
4
+ let closed = false;
5
+ let focused = false;
6
+ let models = options.models;
7
+ let status;
8
+ let failure;
9
+ const pending = [];
10
+ const ready = import("./models-dialog.js").then(({ ModelsDialogComponent }) => {
11
+ if (closed)
12
+ return;
13
+ dialog = new ModelsDialogComponent({ ...options, models }, options);
14
+ dialog.focused = focused;
15
+ if (status !== undefined)
16
+ dialog.setRefreshStatus(...status);
17
+ for (const data of pending.splice(0)) {
18
+ if (!closed)
19
+ dialog.handleInput(data);
20
+ }
21
+ options.requestRender();
22
+ }).catch(() => { if (!closed) {
23
+ failure = "Models unavailable — Esc close";
24
+ options.requestRender();
25
+ } });
26
+ return {
27
+ ready,
28
+ render: width => dialog?.render(width) ?? (failure === undefined ? [] : [failure.slice(0, width)]),
29
+ handleInput: data => {
30
+ if (dialog !== undefined)
31
+ dialog.handleInput(data);
32
+ else if (data === "\u001b" || data === "\u0003")
33
+ options.onCancel();
34
+ else if (pending.length < 32)
35
+ pending.push(data);
36
+ },
37
+ invalidate: () => dialog?.invalidate(),
38
+ setFocused: value => { focused = value; if (dialog !== undefined)
39
+ dialog.focused = value; },
40
+ dispose: () => { closed = true; pending.length = 0; dialog?.dispose(); },
41
+ updateModels: next => { models = next; dialog?.updateModels(next); },
42
+ setRefreshStatus: (message, kind) => { status = [message, kind]; dialog?.setRefreshStatus(message, kind); },
43
+ dirty: () => dialog?.dirty ?? false,
44
+ };
45
+ }
@@ -19,6 +19,8 @@ export interface ModelsDialogConfig {
19
19
  readonly refreshStatus?: string;
20
20
  }
21
21
  export interface ModelsDialogCallbacks {
22
+ /** Live terminal height; omitted component consumers retain the ordinary 24-row budget. */
23
+ viewportHeight?(): number;
22
24
  /** Enter on a row: the shell switches through its workflow and closes the dialog on success. */
23
25
  onSelect(modelId: string): void;
24
26
  /** Every scope or order edit, session-only. */
@@ -208,20 +208,25 @@ export class ModelsDialogComponent {
208
208
  const push = (line = "") => { lines.push(truncateToWidth(line, width)); };
209
209
  const rows = this.#rows();
210
210
  this.#clampSelection(rows);
211
+ const height = this.#callbacks.viewportHeight?.() ?? 24;
212
+ const compact = height < 13;
213
+ const capacity = Math.max(1, Math.min(MAX_VISIBLE_ROWS, height - (compact ? 7 : 13)));
211
214
  push(theme.fg("dim", "Filter: ")
212
215
  + theme.fg(this.#filter === "all" ? "mdHeading" : "dim", "all")
213
216
  + theme.fg("dim", " | ")
214
217
  + theme.fg(this.#filter === "scoped" ? "mdHeading" : "dim", "scoped"));
215
- push();
218
+ if (!compact)
219
+ push();
216
220
  for (const line of renderPiModalSearchInput(this.#input, width))
217
221
  push(line);
218
- push();
222
+ if (!compact)
223
+ push();
219
224
  if (rows.length === 0) {
220
225
  push(theme.fg("muted", ` ${this.#emptyText()}`));
221
226
  }
222
227
  else {
223
- const startIndex = Math.max(0, Math.min(this.#selectedIndex - Math.floor(MAX_VISIBLE_ROWS / 2), rows.length - MAX_VISIBLE_ROWS));
224
- const endIndex = Math.min(startIndex + MAX_VISIBLE_ROWS, rows.length);
228
+ const startIndex = Math.max(0, Math.min(this.#selectedIndex - Math.floor(capacity / 2), rows.length - capacity));
229
+ const endIndex = Math.min(startIndex + capacity, rows.length);
225
230
  for (let index = startIndex; index < endIndex; index += 1) {
226
231
  const row = rows[index];
227
232
  const selected = index === this.#selectedIndex;
@@ -234,15 +239,16 @@ export class ModelsDialogComponent {
234
239
  const active = row.fullId === this.#activeModelId ? ` ${theme.fg("success", "✓")}` : "";
235
240
  push(renderPiModalListRow(`${prefix}${marker} ${label} ${provider}${active}`, width, selected));
236
241
  }
237
- if (startIndex > 0 || endIndex < rows.length)
242
+ if (!compact && (startIndex > 0 || endIndex < rows.length))
238
243
  push(theme.fg("muted", ` (${this.#selectedIndex + 1}/${rows.length})`));
239
244
  const selectedRow = rows[this.#selectedIndex];
240
- if (selectedRow) {
245
+ if (selectedRow && !compact) {
241
246
  push();
242
247
  push(theme.fg("muted", ` Model Name: ${selectedRow.model.name}`));
243
248
  }
244
249
  }
245
- push();
250
+ if (!compact)
251
+ push();
246
252
  if (this.#refreshStatus !== undefined)
247
253
  push(theme.fg(this.#refreshStatus.kind, ` ${this.#refreshStatus.message}`));
248
254
  push(renderHintsWithClose(this.#hints(), width));
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 1.5.0 (MIT),
3
- * commit f9a26365568127461c47a07a37dcb42c140a3ac5.
2
+ * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 1.5.1 (MIT),
3
+ * commit dd30101cfc5b0609411dd80b6f93adbc14db14cf.
4
4
  * Upstream dark.json SHA-256: e1f96982f8cb7d2a48eca7db03c9cb282cb1e14a243680cd855638ecd6a251b6
5
5
  * Upstream light.json SHA-256: 2dee6a4be7f765e7fd7728166b7ebf8c29e4ce313f8ec6b38ae50699e3abc2f3
6
6
  * Modifications: removed upstream schema/export metadata; runtime color data is unchanged.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 1.5.0 (MIT),
3
- * commit f9a26365568127461c47a07a37dcb42c140a3ac5.
2
+ * Attributed owned theme resources adapted from @earendil-works/pi-coding-agent 1.5.1 (MIT),
3
+ * commit dd30101cfc5b0609411dd80b6f93adbc14db14cf.
4
4
  * Upstream dark.json SHA-256: e1f96982f8cb7d2a48eca7db03c9cb282cb1e14a243680cd855638ecd6a251b6
5
5
  * Upstream light.json SHA-256: 2dee6a4be7f765e7fd7728166b7ebf8c29e4ce313f8ec6b38ae50699e3abc2f3
6
6
  * Modifications: removed upstream schema/export metadata; runtime color data is unchanged.
@@ -19,6 +19,17 @@ function renderOwnedAutocompleteSelection(text) {
19
19
  const description = row === null ? "" : theme.fg("muted", row[2]);
20
20
  return renderPiListSelection(arrow + label + description);
21
21
  }
22
+ /** Models and thinking share the active declarations, independent of input presentation. */
23
+ function bindModelActions(editor, options) {
24
+ if (options.onModelSelect !== undefined)
25
+ editor.onAction("app.model.select", options.onModelSelect);
26
+ if (options.onModelCycle !== undefined) {
27
+ editor.onAction("app.model.cycleForward", () => options.onModelCycle?.("forward"));
28
+ editor.onAction("app.model.cycleBackward", () => options.onModelCycle?.("backward"));
29
+ }
30
+ if (options.onThinkingCycle !== undefined)
31
+ editor.onAction("app.thinking.cycle", options.onThinkingCycle);
32
+ }
22
33
  const PINNED_THINKING_SLASH_COMMAND = { name: "thinking", description: "Set thinking level", argumentHint: "<level>" };
23
34
  const OWNED_THINKING_SLASH_COMMAND = { name: "thinking", description: "Set thinking level" };
24
35
  const OWNED_LOGIN_SLASH_COMMAND = { name: "login", description: "Configure provider authentication" };
@@ -52,8 +63,10 @@ export const OWNED_BUILTIN_SLASH_COMMANDS = PINNED_PI_BUILTIN_SLASH_COMMANDS.fla
52
63
  if (command.name === "model") {
53
64
  return [{ name: "models", description: "Switch models and manage scoped model cycling" }, OWNED_THINKING_SLASH_COMMAND];
54
65
  }
55
- if (command.name === "thinking" || command.name === "scoped-models")
66
+ if (command.name === "thinking" || command.name === "scoped-models" || command.name === "settings" || command.name === "changelog" || command.name === "hotkeys")
56
67
  return [];
68
+ if (command.name === "export")
69
+ return [{ ...command, description: "Export session to user storage (--jsonl or an explicit path)" }];
57
70
  if (command.name === "login")
58
71
  return [OWNED_LOGIN_SLASH_COMMAND];
59
72
  return [command];
@@ -179,14 +192,7 @@ export function createPiShellEditor(options) {
179
192
  }
180
193
  if (options.onExtensionShortcut !== undefined)
181
194
  editor.onExtensionShortcut = options.onExtensionShortcut;
182
- if (options.onModelSelect !== undefined)
183
- editor.onAction("app.model.select", options.onModelSelect);
184
- if (options.onModelCycle !== undefined) {
185
- editor.onAction("app.model.cycleForward", () => options.onModelCycle?.("forward"));
186
- editor.onAction("app.model.cycleBackward", () => options.onModelCycle?.("backward"));
187
- }
188
- if (options.onThinkingCycle !== undefined)
189
- editor.onAction("app.thinking.cycle", options.onThinkingCycle);
195
+ bindModelActions(editor, options);
190
196
  if (options.onThinkingToggle !== undefined)
191
197
  editor.onAction("app.thinking.toggle", options.onThinkingToggle);
192
198
  if (options.onToolsExpand !== undefined)
@@ -230,6 +236,7 @@ export function createPiShellEditor(options) {
230
236
  },
231
237
  activateKeybindings: () => host.ensureActive(),
232
238
  keybindingConfig: () => keybindings.getEffectiveConfig(),
239
+ matchesAction: (data, action) => keybindings.matches(data, action),
233
240
  reloadKeybindings: () => host.reload(),
234
241
  matchesTerminalKey: (data, key) => matchesKey(data, key),
235
242
  handleInput: data => {
@@ -348,12 +355,11 @@ function autocompleteCommandSetter(options, editor, state, enabled) {
348
355
  state.skills = collapsed.skills;
349
356
  const additions = new Map(collapsed.commands.map(command => [command.name, command]));
350
357
  const builtInNames = new Set(builtInCatalog.map(command => command.name));
351
- const builtIns = builtInCatalog.flatMap(command => [
352
- autocompleteCommand(command, additions.get(command.name)),
353
- // Rationale: the collapsed browser is a primary bare-A1 command, not a resource after quit.
354
- ...(command.name === "settings" && collapsed.collapsedCommand !== undefined
355
- ? [autocompleteCommand(collapsed.collapsedCommand)] : []),
356
- ]);
358
+ // Rationale: Skills is a primary command, independent of retired application commands.
359
+ const builtIns = [
360
+ ...(collapsed.collapsedCommand === undefined ? [] : [autocompleteCommand(collapsed.collapsedCommand)]),
361
+ ...builtInCatalog.map(command => autocompleteCommand(command, additions.get(command.name))),
362
+ ];
357
363
  const resources = collapsed.commands
358
364
  .filter(command => command.name !== collapsed.collapsedCommand?.name && command.source !== "builtin" && !builtInNames.has(command.name))
359
365
  .map(command => autocompleteCommand(command));
@@ -301,8 +301,9 @@ function expandedHeaderText(bindings) {
301
301
  rawKeyHint(process.platform === "win32" ? "" : "ctrl+z", "to suspend"),
302
302
  rawKeyHint("ctrl+k", "to delete to end"),
303
303
  rawKeyHint(keys?.getKeys("app.thinking.cycle").join("/") ?? "shift+tab", "to cycle thinking level"),
304
- rawKeyHint("ctrl+p/shift+ctrl+p", "to cycle models"),
305
- // Invariant: only bare A1 supplies live bindings, and its unbound fallback is the unified `/models` command.
304
+ rawKeyHint(keys === undefined ? "ctrl+p/shift+ctrl+p" : [...keys.getKeys("app.model.cycleForward"), ...keys.getKeys("app.model.cycleBackward")].join("/") || "unbound", "to cycle models"),
305
+ ...(keys === undefined ? [] : [rawKeyHint(keys.getKeys("owned.apps.open").join("/") || "unbound", "to open Launcher")]),
306
+ // Invariant: only bare A1 supplies live bindings; unbound selection remains reachable through `/models`.
306
307
  rawKeyHint(keys === undefined ? "ctrl+l" : keys.getKeys("app.model.select").join("/") || "/models", "to select model"),
307
308
  rawKeyHint("ctrl+o", "to expand tools"),
308
309
  rawKeyHint("ctrl+t", "to expand thinking"),
@@ -180,7 +180,7 @@ export function hotkeysMarkdown(bindings, getShortcuts, profile) {
180
180
  ? action === "app.model.select" ? "Unbound (`/models`)" : "Unbound"
181
181
  : `\`${label}\``;
182
182
  }).join(" / ")} | ${description} |`;
183
- let markdown = ["**Navigation**", "| Key | Action |", "|-----|--------|", row(["tui.editor.cursorUp", "tui.editor.cursorDown", "tui.editor.cursorLeft", "tui.editor.cursorRight"], "Move cursor / browse history"), row(["tui.editor.cursorWordLeft", "tui.editor.cursorWordRight"], "Move by word"), row(["tui.editor.cursorLineStart"], profile === "a1" ? "Start of prompt line" : "Start of line"), row(["tui.editor.cursorLineEnd"], profile === "a1" ? "End of prompt line" : "End of line"), ...(profile === "a1" ? ["| `Ctrl+Home` | Start of content |", "| `Ctrl+End` | End of content / follow output |"] : []), row(["tui.editor.jumpForward"], "Jump forward to character"), row(["tui.editor.jumpBackward"], "Jump backward to character"), row(["tui.editor.pageUp", "tui.editor.pageDown"], "Scroll by page"), "", "**Editing**", "| Key | Action |", "|-----|--------|", row(["tui.input.submit"], "Send message"), row(["tui.input.newLine"], `New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""}`), row(["tui.editor.deleteWordBackward"], "Delete word backwards"), row(["tui.editor.deleteWordForward"], "Delete word forwards"), row(["tui.editor.deleteToLineStart"], "Delete to start of line"), row(["tui.editor.deleteToLineEnd"], "Delete to end of line"), row(["tui.editor.yank"], "Paste the most-recently-deleted text"), row(["tui.editor.yankPop"], "Cycle through the deleted text after pasting"), row(["tui.editor.undo"], "Undo"), "", "**Other**", "| Key | Action |", "|-----|--------|", row(["tui.input.tab"], "Path completion / accept autocomplete"), row(["app.interrupt"], "Cancel autocomplete / abort streaming"), row(["app.clear"], "Clear editor (first) / exit (second)"), row(["app.exit"], "Exit (when editor is empty)"), row(["app.suspend"], "Suspend to background"), row(["app.thinking.cycle"], "Cycle thinking level"), row(["app.model.cycleForward", "app.model.cycleBackward"], "Cycle models"), row(["app.model.select"], profile === "a1" ? "Open the Models dialog" : "Open model selector"), row(["app.tools.expand"], "Toggle tool output expansion"), row(["app.thinking.toggle"], "Toggle thinking block visibility"), row(["app.editor.external"], "Edit message in external editor"), row(["app.message.copy"], "Copy selection or last assistant message"), row(["app.message.followUp"], "Queue follow-up message"), row(["app.message.dequeue"], "Restore queued messages"), row(["app.clipboard.pasteImage"], profile === "a1" ? "Paste image or text from clipboard" : "Paste files on macOS, images, or text from clipboard"), "| `/` | Slash commands |", "| `!` | Run bash command |", "| `!!` | Run bash command (excluded from context) |", ...(profile === "a1" ? ["", "**Models dialog**", "| Key | Action |", "|-----|--------|", "| `Space` | Toggle the selected model in the cycling scope |", "| `Tab` | Switch the all/scoped filter |", row(["app.models.save"], "Save the scope to settings"), row(["app.models.enableAll"], "Scope every listed model"), row(["app.models.clearAll"], "Clear the listed models from the scope"), row(["app.models.toggleProvider"], "Toggle the selected model's provider"), row(["app.models.reorderUp", "app.models.reorderDown"], "Reorder the cycling scope")] : [])].join("\n");
183
+ let markdown = ["**Navigation**", "| Key | Action |", "|-----|--------|", row(["tui.editor.cursorUp", "tui.editor.cursorDown", "tui.editor.cursorLeft", "tui.editor.cursorRight"], "Move cursor / browse history"), row(["tui.editor.cursorWordLeft", "tui.editor.cursorWordRight"], "Move by word"), row(["tui.editor.cursorLineStart"], profile === "a1" ? "Start of prompt line" : "Start of line"), row(["tui.editor.cursorLineEnd"], profile === "a1" ? "End of prompt line" : "End of line"), ...(profile === "a1" ? ["| `Ctrl+Home` | Start of content |", "| `Ctrl+End` | End of content / follow output |"] : []), row(["tui.editor.jumpForward"], "Jump forward to character"), row(["tui.editor.jumpBackward"], "Jump backward to character"), row(["tui.editor.pageUp", "tui.editor.pageDown"], "Scroll by page"), "", "**Editing**", "| Key | Action |", "|-----|--------|", row(["tui.input.submit"], "Send message"), row(["tui.input.newLine"], `New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""}`), row(["tui.editor.deleteWordBackward"], "Delete word backwards"), row(["tui.editor.deleteWordForward"], "Delete word forwards"), row(["tui.editor.deleteToLineStart"], "Delete to start of line"), row(["tui.editor.deleteToLineEnd"], "Delete to end of line"), row(["tui.editor.yank"], "Paste the most-recently-deleted text"), row(["tui.editor.yankPop"], "Cycle through the deleted text after pasting"), row(["tui.editor.undo"], "Undo"), "", "**Other**", "| Key | Action |", "|-----|--------|", row(["tui.input.tab"], "Path completion / accept autocomplete"), row(["app.interrupt"], "Cancel autocomplete / abort streaming"), row(["app.clear"], "Clear editor (first) / exit (second)"), row(["app.exit"], "Exit (when editor is empty)"), row(["app.suspend"], "Suspend to background"), row(["app.thinking.cycle"], "Cycle thinking level"), row(["app.model.cycleForward", "app.model.cycleBackward"], "Cycle models"), row(["app.model.select"], profile === "a1" ? "Open the in-place Models selector (`/models`)" : "Open model selector"), ...(profile === "a1" ? [row(["owned.apps.open"], "Open Launcher (Agent, Settings, Changelog, Hotkeys)")] : []), row(["app.tools.expand"], "Toggle tool output expansion"), row(["app.thinking.toggle"], "Toggle thinking block visibility"), row(["app.editor.external"], "Edit message in external editor"), row(["app.message.copy"], "Copy selection or last assistant message"), row(["app.message.followUp"], "Queue follow-up message"), row(["app.message.dequeue"], "Restore queued messages"), row(["app.clipboard.pasteImage"], profile === "a1" ? "Paste image or text from clipboard" : "Paste files on macOS, images, or text from clipboard"), "| `/` | Slash commands |", "| `!` | Run bash command |", "| `!!` | Run bash command (excluded from context) |", ...(profile === "a1" ? ["", "Legacy Ctrl+M that is indistinguishable from Enter keeps Enter behavior; use `/models` to select a model.", "", "**Models dialog**", "| Key | Action |", "|-----|--------|", "| `Space` | Toggle the selected model in the cycling scope |", "| `Tab` | Switch the all/scoped filter |", row(["app.models.save"], "Save the scope to settings"), row(["app.models.enableAll"], "Scope every listed model"), row(["app.models.clearAll"], "Clear the listed models from the scope"), row(["app.models.toggleProvider"], "Toggle the selected model's provider"), row(["app.models.reorderUp", "app.models.reorderDown"], "Reorder the cycling scope")] : [])].join("\n");
184
184
  const shortcuts = getShortcuts(bindings ?? keys.getEffectiveConfig());
185
185
  if (shortcuts.length > 0) {
186
186
  markdown += "\n\n**Extensions**\n| Key | Action |\n|-----|--------|\n";
@@ -206,7 +206,9 @@ function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt,
206
206
  summary: block.text,
207
207
  tokensBefore: numericPayload(block, "tokensBefore"),
208
208
  timestamp: numericPayload(block, "timestamp") || 0,
209
- }, getPiMarkdownTheme());
209
+ }, getPiMarkdownTheme(), outputPad, [
210
+ createMermaidMarkdownTransformer({ getMode: () => mermaidRenderingMode, theme: piTheme() }),
211
+ ], block.id);
210
212
  component.setExpanded(expanded);
211
213
  return component;
212
214
  }
@@ -1,6 +1,6 @@
1
1
  import { ModelSelectorComponent, type SessionInfo, type SettingsCallbacks, type SettingsConfig } from "../startup-public.js";
2
2
  import type { OwnedUiDialog } from "../../../contracts/owned-ui/index.js";
3
- import { type ModelsDialogCallbacks, type ModelsDialogConfig } from "./models-dialog.js";
3
+ import type { ModelsDialogCallbacks, ModelsDialogConfig } from "./models-dialog.js";
4
4
  import { type TrustDecision, type TrustOption, type TrustUpdate } from "./upstream/components/trust-selector.js";
5
5
  import { type PiShellComponentPort, type PiShellEditorOptions, type PiShellSelectorOption, type PiShellSelectorOptions } from "./shell-shared-facade.js";
6
6
  export { createPiShellSkillsSelector, type PiShellSkillsSelectorOptions } from "./skills-dialog.js";
@@ -50,6 +50,7 @@ export declare function createPiShellScopedModelsSelector(options: PiShellScoped
50
50
  export interface PiShellModelsDialogOptions extends ModelsDialogConfig, ModelsDialogCallbacks {
51
51
  }
52
52
  export interface PiShellModelsDialogPort extends PiShellComponentPort {
53
+ readonly ready?: Promise<void>;
53
54
  updateModels(models: readonly PiShellScopedModelDescriptor[]): void;
54
55
  setRefreshStatus(message: string, kind: "muted" | "success" | "warning"): void;
55
56
  readonly dirty: () => boolean;
@@ -4,7 +4,7 @@ import { ExtensionSelectorComponent } from "./upstream/components/extension-sele
4
4
  import { Box, Container, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
5
5
  import { ScopedModelsSelectorComponent } from "./upstream/components/scoped-models-selector.js";
6
6
  import { addPiModalSelectFooter, adoptPiModalFrame, adoptPiModalHeader, adoptPiModalSearchInput } from "./modal-frame.js";
7
- import { ModelsDialogComponent } from "./models-dialog.js";
7
+ import { createDeferredModelsDialog } from "./models-dialog-loader.js";
8
8
  import { TrustSelectorComponent } from "./upstream/components/trust-selector.js";
9
9
  import { canonicalizePiSettingsHint, DIALOG_CLOSE_SHORTCUT_HINT, PINNED_PI_LAYOUT, getPiSelectListTheme, paintPiBorder, piTheme, renderPiModalShortcutHints, } from "./theme.js";
10
10
  import { componentFromPort, componentPort, createTuiFacade, ensureTheme, isRecord, } from "./shell-shared-facade.js";
@@ -133,13 +133,7 @@ export function createPiShellScopedModelsSelector(options) {
133
133
  /** The bare-A1 unified Models dialog behind the owned component boundary; the pinned selectors above stay for `a1 pi`. */
134
134
  export function createPiShellModelsDialog(options) {
135
135
  ensureTheme();
136
- const dialog = new ModelsDialogComponent(options, options);
137
- return {
138
- ...componentPort(dialog),
139
- updateModels: models => dialog.updateModels(models),
140
- setRefreshStatus: (message, kind) => dialog.setRefreshStatus(message, kind),
141
- dirty: () => dialog.dirty,
142
- };
136
+ return createDeferredModelsDialog(options);
143
137
  }
144
138
  export function createPiShellTrustSelector(options) {
145
139
  ensureTheme();
@@ -2,6 +2,8 @@ import { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel, type SemanticShort
2
2
  import { type MarkdownTheme, type SelectListTheme } from "@earendil-works/pi-tui";
3
3
  export { PINNED_PI_LAYOUT, applyPiTheme, applyPiThemeInstance, currentPiThemeName, ensurePiTheme, getAvailablePiThemes, loadPiTheme, piCanvasBackgroundAnsi, piTheme, setPiAccentColor, setPiPackageBorderProjectionEnabled, } from "./upstream/theme/theme.js";
4
4
  type Paint = (text: string) => string;
5
+ /** The owned floating-panel background in the live terminal color capability. */
6
+ export declare function paintPiFloatingDialog(text: string): string;
5
7
  /** The live Pi theme in neutral roles; `panel` and `disabled` default to plain and dim. */
6
8
  export declare function piUiTheme(options?: {
7
9
  readonly panel?: Paint;
@@ -1,8 +1,13 @@
1
1
  import { DIALOG_CLOSE_SHORTCUT_HINT, displayShortcutKeyLabel, renderSemanticShortcutHints, } from "../../../contracts/presentation/index.js";
2
+ import { renderFloatingDialogSurface } from "../../../ui/components/spans.js";
2
3
  import { stripTerminalSequences, truncateToWidth } from "@earendil-works/pi-tui";
3
4
  import { getMarkdownTheme } from "../startup-public.js";
4
5
  import { onPiThemeChange, piTheme, renderPiAccentPreview } from "./upstream/theme/theme.js";
5
6
  export { PINNED_PI_LAYOUT, applyPiTheme, applyPiThemeInstance, currentPiThemeName, ensurePiTheme, getAvailablePiThemes, loadPiTheme, piCanvasBackgroundAnsi, piTheme, setPiAccentColor, setPiPackageBorderProjectionEnabled, } from "./upstream/theme/theme.js";
7
+ /** The owned floating-panel background in the live terminal color capability. */
8
+ export function paintPiFloatingDialog(text) {
9
+ return renderFloatingDialogSurface(text, piTheme().getColorMode());
10
+ }
6
11
  /** The live Pi theme in neutral roles; `panel` and `disabled` default to plain and dim. */
7
12
  export function piUiTheme(options = {}) {
8
13
  return {
@@ -1,16 +1,17 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/core/keybindings.ts.
4
4
  * Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
5
- * resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
6
- * selection, Tab tree-filter cycling, tree-local navigation aliases, cross-platform Alt+Up
7
- * queued-message restoration, and an implicit Ctrl+C selection-cancel alias omitted from shortcut
8
- * labels.
9
- * Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
5
+ * resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, Ctrl+M model cycling
6
+ * with ordinary Enter preserved, Ctrl+P Launcher, command-only model selection, Tab tree-filter
7
+ * cycling, tree-local navigation aliases, cross-platform Alt+Up queued-message restoration, and an
8
+ * implicit Ctrl+C selection-cancel alias omitted from shortcut labels.
9
+ * Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut, owned-apps-shortcuts,
10
10
  * owned-input-keybinding-aliases, owned-session-tree-dialog, owned-dialog-ctrl-c-cancel.
11
11
  */
12
12
  import { type Keybinding, type KeybindingDefinitions, type KeybindingsConfig, type KeyId, KeybindingsManager as TuiKeybindingsManager } from "@earendil-works/pi-tui";
13
13
  export interface AppKeybindings {
14
+ "owned.apps.open": true;
14
15
  "app.interrupt": true;
15
16
  "app.clear": true;
16
17
  "app.exit": true;
@@ -153,7 +154,7 @@ export declare const KEYBINDINGS: {
153
154
  readonly description: "Restore queued messages";
154
155
  };
155
156
  readonly "app.clipboard.pasteImage": {
156
- readonly defaultKeys: "ctrl+v" | "alt+v";
157
+ readonly defaultKeys: "ctrl+v" | ("ctrl+v" | "alt+v")[];
157
158
  readonly description: "Paste files on macOS, images, or text from clipboard";
158
159
  };
159
160
  readonly "app.session.new": {
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/core/keybindings.ts.
4
4
  * Modifications: Mechanical source port with Node import prefixes, public package-root agent-directory
5
- * resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, unbound model
6
- * selection, Tab tree-filter cycling, tree-local navigation aliases, cross-platform Alt+Up
7
- * queued-message restoration, and an implicit Ctrl+C selection-cancel alias omitted from shortcut
8
- * labels.
9
- * Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut,
5
+ * resolution, and an opt-in bare-A1 input profile including Ctrl+L level cycling, Ctrl+M model cycling
6
+ * with ordinary Enter preserved, Ctrl+P Launcher, command-only model selection, Tab tree-filter
7
+ * cycling, tree-local navigation aliases, cross-platform Alt+Up queued-message restoration, and an
8
+ * implicit Ctrl+C selection-cancel alias omitted from shortcut labels.
9
+ * Deviations: keybindings-public-config-boundary, owned-level-cycle-shortcut, owned-apps-shortcuts,
10
10
  * owned-input-keybinding-aliases, owned-session-tree-dialog, owned-dialog-ctrl-c-cancel.
11
11
  */
12
12
  import { Key, matchesKey, TUI_KEYBINDINGS, KeybindingsManager as TuiKeybindingsManager, } from "@earendil-works/pi-tui";
@@ -85,7 +85,7 @@ export const KEYBINDINGS = {
85
85
  description: "Restore queued messages",
86
86
  },
87
87
  "app.clipboard.pasteImage": {
88
- defaultKeys: windowsKeybindings ? "alt+v" : "ctrl+v",
88
+ defaultKeys: windowsKeybindings ? ["alt+v", "ctrl+v"] : "ctrl+v",
89
89
  description: "Paste files on macOS, images, or text from clipboard",
90
90
  },
91
91
  "app.session.new": { defaultKeys: [], description: "Start a new session" },
@@ -186,6 +186,8 @@ const OWNED_INPUT_KEYBINDINGS = {
186
186
  ...KEYBINDINGS,
187
187
  "app.thinking.cycle": { ...KEYBINDINGS["app.thinking.cycle"], defaultKeys: "ctrl+l" },
188
188
  "app.model.select": { ...KEYBINDINGS["app.model.select"], defaultKeys: [] },
189
+ "app.model.cycleForward": { ...KEYBINDINGS["app.model.cycleForward"], defaultKeys: "ctrl+m" },
190
+ "owned.apps.open": { defaultKeys: "ctrl+p", description: "Open Launcher" },
189
191
  "app.message.dequeue": { ...KEYBINDINGS["app.message.dequeue"], defaultKeys: "alt+up" },
190
192
  "app.tree.filter.cycleForward": { ...KEYBINDINGS["app.tree.filter.cycleForward"], defaultKeys: "tab" },
191
193
  "tui.editor.deleteWordBackward": {
@@ -369,6 +371,10 @@ export class KeybindingsManager extends TuiKeybindingsManager {
369
371
  }
370
372
  // Protocol: Ctrl+C is a conventional dialog escape hatch, not advertised keybinding chrome.
371
373
  matches(data, keybinding) {
374
+ // Compatibility: legacy Ctrl+M is indistinguishable from Enter; never steal ordinary submission.
375
+ if (data === "\r" && this.getKeys(keybinding).includes("ctrl+m")) {
376
+ return this.getKeys(keybinding).some(key => key !== "ctrl+m" && matchesKey(data, key));
377
+ }
372
378
  return keybinding === "tui.select.cancel" && matchesKey(data, "ctrl+c")
373
379
  || super.matches(data, keybinding);
374
380
  }
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "sourcePackage": "@earendil-works/pi-coding-agent",
3
- "sourceVersion": "1.5.0",
3
+ "sourceVersion": "1.5.1",
4
4
  "upstreamRepository": "https://github.com/earendil-works/pi.git",
5
- "upstreamCommit": "f9a26365568127461c47a07a37dcb42c140a3ac5",
5
+ "upstreamCommit": "dd30101cfc5b0609411dd80b6f93adbc14db14cf",
6
6
  "sourcePath": "dist/modes/interactive/assets/clankolas.png",
7
7
  "sourceSha256": "169acd0dfe6fbb8d8742ed24a3fc654fd0b2e2d4223c733249c5493723f1b72d",
8
8
  "license": "MIT",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/modes/interactive/components/countdown-timer.ts.
4
4
  * Modifications: Mechanical source port with ECMAScript private fields; imports remain on the public
5
5
  * Pi TUI package root.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/modes/interactive/components/earendil-announcement.ts.
4
4
  * Modifications: Mechanical pinned source port with private imports remapped to public package-root
5
5
  * types/APIs and A1-owned theme boundaries. The unchanged image is loaded lazily from its attributed,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/modes/interactive/components/extension-editor.ts.
4
4
  * Modifications: Mechanical port: remap pi-tui to the root public singleton, use owned
5
5
  * keybindings/theme and external-editor seams, preserve editor layout, hints, focus, submission,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/modes/interactive/components/extension-editor.ts.
4
4
  * Modifications: Mechanical port: remap pi-tui to the root public singleton, use owned
5
5
  * keybindings/theme and external-editor seams, preserve editor layout, hints, focus, submission,
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/modes/interactive/components/extension-input.ts.
4
4
  * Modifications: Mechanical port: remap public imports, use ECMAScript private fields, and route the
5
5
  * bare-A1 instruction row and chrome through the shared semantic shortcut and compact padded
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Provenance: @earendil-works/pi-coding-agent 1.5.0 (MIT), commit f9a26365568127461c47a07a37dcb42c140a3ac5,
2
+ * Provenance: @earendil-works/pi-coding-agent 1.5.1 (MIT), commit dd30101cfc5b0609411dd80b6f93adbc14db14cf,
3
3
  * packages/coding-agent/src/modes/interactive/components/extension-selector.ts.
4
4
  * Modifications: Mechanical port: remap public imports, use ECMAScript private fields, and route the
5
5
  * bare-A1 instruction row and chrome through the shared semantic shortcut and compact padded
@@ -59,12 +59,19 @@ export class ExtensionSelectorComponent extends Container {
59
59
  #updateList() {
60
60
  this.#listContainer.clear();
61
61
  const theme = piTheme();
62
- for (let index = 0; index < this.#options.length; index += 1) {
62
+ const maxVisible = 10;
63
+ // Center the window on the selection, then clamp it to the list boundaries.
64
+ const startIndex = Math.max(0, Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), this.#options.length - maxVisible));
65
+ const endIndex = Math.min(startIndex + maxVisible, this.#options.length);
66
+ for (let index = startIndex; index < endIndex; index += 1) {
63
67
  const option = this.#options[index];
64
68
  this.#listContainer.addChild(new Text(index === this.#selectedIndex
65
69
  ? theme.fg("accent", "→ ") + theme.fg("accent", option)
66
70
  : ` ${theme.fg("text", option)}`, 0, 0));
67
71
  }
72
+ if (startIndex > 0 || endIndex < this.#options.length) {
73
+ this.#listContainer.addChild(new Text(theme.fg("muted", ` (${this.#selectedIndex + 1}/${this.#options.length})`), 0, 0));
74
+ }
68
75
  }
69
76
  handleInput(keyData) {
70
77
  const keys = getKeybindings();
@@ -1,8 +1,9 @@
1
1
  import type { MarkdownTransformer, Theme } from "@earendil-works/pi-coding-agent";
2
2
  export type MermaidRenderingMode = "off" | "final" | "streaming";
3
+ type MermaidTheme = Pick<Theme, "fg" | "getFgAnsi">;
3
4
  interface MermaidTransformerOptions {
4
5
  getMode: () => MermaidRenderingMode;
5
- theme?: Theme;
6
+ theme?: MermaidTheme;
6
7
  }
7
8
  /** Create a transformer that replaces top-level Mermaid code blocks with Unicode terminal diagrams. */
8
9
  export declare function createMermaidMarkdownTransformer(options: MermaidTransformerOptions): MarkdownTransformer;