@timurproko/a1 0.2.7-dev.1270 → 0.2.7-dev.1276

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 (154) 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 +62 -0
  4. package/dist/app/session-shell/dialog-coordinator.d.ts +12 -5
  5. package/dist/app/session-shell/dialog-coordinator.js +113 -15
  6. package/dist/app/session-shell/input-route.d.ts +23 -0
  7. package/dist/app/session-shell/input-route.js +59 -0
  8. package/dist/app/session-shell/session-selectors.d.ts +4 -4
  9. package/dist/app/session-shell/session-selectors.js +8 -3
  10. package/dist/app/session-shell/session-shell-root.js +6 -2
  11. package/dist/app/session-shell/session-shell.js +13 -5
  12. package/dist/app/session-shell/shell-host.d.ts +1 -0
  13. package/dist/app/session-shell/shell-host.js +22 -11
  14. package/dist/app/session-shell/viewport-pointer.js +5 -1
  15. package/dist/app/session-shell/workflow-output.js +12 -0
  16. package/dist/app/session-shell/workflow-router.d.ts +2 -2
  17. package/dist/composition/owned-ui.js +17 -13
  18. package/dist/composition/settings-route-host.js +1 -0
  19. package/dist/contracts/owned-ui/session-presenters.d.ts +1 -1
  20. package/dist/contracts/owned-ui/session-workflows.d.ts +3 -1
  21. package/dist/contracts/owned-ui/shell-components.d.ts +12 -0
  22. package/dist/contracts/presentation/component.d.ts +7 -0
  23. package/dist/contracts/presentation/index.d.ts +2 -0
  24. package/dist/contracts/presentation/index.js +4 -0
  25. package/dist/contracts/presentation/progress-palette.generated.d.ts +25 -1
  26. package/dist/contracts/presentation/progress-palette.generated.js +2 -135
  27. package/dist/contracts/presentation/terminal.d.ts +4 -0
  28. package/dist/contracts/presentation/theme.d.ts +2 -0
  29. package/dist/features/owned-ui/project-trust-dialog.d.ts +1 -1
  30. package/dist/features/owned-ui/project-trust-dialog.js +5 -5
  31. package/dist/features/owned-ui/project-trust-prompt.js +2 -2
  32. package/dist/features/owned-ui/reference-screen-app.d.ts +2 -0
  33. package/dist/features/owned-ui/reference-screen-app.js +4 -1
  34. package/dist/features/owned-ui/settings-app.js +6 -6
  35. package/dist/features/owned-ui/settings-model-dialog.js +3 -3
  36. package/dist/foundation/lifecycle/user-exports.d.ts +5 -0
  37. package/dist/foundation/lifecycle/user-exports.js +37 -0
  38. package/dist/foundation/startup/startup-descriptor.js +2 -2
  39. package/dist/integrations/pi/components/deferred-selector.d.ts +6 -0
  40. package/dist/integrations/pi/components/deferred-selector.js +40 -0
  41. package/dist/integrations/pi/components/login-dialog-loader.d.ts +3 -0
  42. package/dist/integrations/pi/components/login-dialog-loader.js +57 -0
  43. package/dist/integrations/pi/components/login-links.d.ts +6 -0
  44. package/dist/integrations/pi/components/login-links.js +31 -0
  45. package/dist/integrations/pi/components/login-text.d.ts +7 -0
  46. package/dist/integrations/pi/components/login-text.js +17 -0
  47. package/dist/integrations/pi/components/modal-frame.d.ts +5 -0
  48. package/dist/integrations/pi/components/modal-frame.js +30 -2
  49. package/dist/integrations/pi/components/models-dialog-loader.d.ts +11 -0
  50. package/dist/integrations/pi/components/models-dialog-loader.js +45 -0
  51. package/dist/integrations/pi/components/models-dialog.d.ts +2 -0
  52. package/dist/integrations/pi/components/models-dialog.js +13 -7
  53. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  54. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  55. package/dist/integrations/pi/components/shell-editor-autocomplete.js +21 -15
  56. package/dist/integrations/pi/components/shell-footer-status.js +3 -2
  57. package/dist/integrations/pi/components/shell-presenters-info.js +1 -1
  58. package/dist/integrations/pi/components/shell-presenters-transcript.js +3 -1
  59. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +13 -9
  60. package/dist/integrations/pi/components/shell-selectors-dialogs.js +38 -43
  61. package/dist/integrations/pi/components/shell-session-info-reference.d.ts +1 -1
  62. package/dist/integrations/pi/components/shell-session-info-reference.js +49 -46
  63. package/dist/integrations/pi/components/theme.d.ts +4 -0
  64. package/dist/integrations/pi/components/theme.js +9 -0
  65. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +8 -7
  66. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +13 -7
  67. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  68. package/dist/integrations/pi/components/upstream/components/auth-url.d.ts +16 -0
  69. package/dist/integrations/pi/components/upstream/components/auth-url.js +49 -0
  70. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  71. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  72. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  73. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  74. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  75. package/dist/integrations/pi/components/upstream/components/extension-selector.d.ts +1 -0
  76. package/dist/integrations/pi/components/upstream/components/extension-selector.js +30 -12
  77. package/dist/integrations/pi/components/upstream/components/login-dialog.d.ts +3 -0
  78. package/dist/integrations/pi/components/upstream/components/login-dialog.js +216 -0
  79. package/dist/integrations/pi/components/upstream/components/mermaid.d.ts +2 -1
  80. package/dist/integrations/pi/components/upstream/components/mermaid.js +26 -21
  81. package/dist/integrations/pi/components/upstream/components/oauth-selector.d.ts +19 -0
  82. package/dist/integrations/pi/components/upstream/components/oauth-selector.js +123 -0
  83. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  84. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  85. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  86. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  87. package/dist/integrations/pi/components/upstream/components/session-footer.js +17 -19
  88. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  89. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  90. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +6 -2
  91. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +27 -3
  92. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  93. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  94. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  95. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  96. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  97. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  98. package/dist/integrations/pi/components/upstream/components/tool-execution.js +1 -1
  99. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  100. package/dist/integrations/pi/components/upstream/components/tree-selector.js +7 -6
  101. package/dist/integrations/pi/components/upstream/components/trust-selector.d.ts +2 -0
  102. package/dist/integrations/pi/components/upstream/components/trust-selector.js +30 -13
  103. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  104. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  105. package/dist/integrations/pi/components/upstream/history/editor-core.js +2 -2
  106. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  107. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  108. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  109. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  110. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  111. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  112. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  113. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  114. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  115. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  116. package/dist/integrations/pi/engine/adapter.d.ts +1 -1
  117. package/dist/integrations/pi/engine/adapter.js +3 -3
  118. package/dist/integrations/pi/engine/host.d.ts +1 -1
  119. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  120. package/dist/integrations/pi/engine/workflow-export.d.ts +4 -0
  121. package/dist/integrations/pi/engine/workflow-export.js +25 -0
  122. package/dist/integrations/pi/engine/workflow-runner.d.ts +1 -0
  123. package/dist/integrations/pi/engine/workflow-runner.js +6 -10
  124. package/dist/integrations/pi/engine/workflow-support.d.ts +2 -1
  125. package/dist/integrations/pi/engine/workflow-support.js +28 -25
  126. package/dist/integrations/pi/engine/workflows.d.ts +6 -1
  127. package/dist/integrations/pi/engine/workflows.js +1 -1
  128. package/dist/integrations/pi/session-presenters/components.d.ts +1 -1
  129. package/dist/integrations/pi/session-presenters/components.js +8 -8
  130. package/dist/integrations/pi/session-presenters/presentation.js +2 -1
  131. package/dist/integrations/pi/session-presenters/presenters.js +24 -12
  132. package/dist/integrations/pi/startup-public.js +289 -240
  133. package/dist/integrations/pi/startup-public.manifest.json +579 -363
  134. package/dist/integrations/pi/tui-runtime/adapter.js +3 -1
  135. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  136. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  137. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +5 -2
  138. package/dist/integrations/pi/tui-runtime/overlay-geometry.js +12 -2
  139. package/dist/runtime-payload-inventory.json +8044 -6930
  140. package/dist/ui/components/dialog-panel.d.ts +4 -3
  141. package/dist/ui/components/dialog-panel.js +9 -13
  142. package/dist/ui/components/index.d.ts +1 -1
  143. package/dist/ui/components/index.js +1 -1
  144. package/dist/ui/components/list-block.d.ts +2 -2
  145. package/dist/ui/components/list-block.js +4 -3
  146. package/dist/ui/components/spans.d.ts +7 -0
  147. package/dist/ui/components/spans.js +63 -1
  148. package/dist/ui/settings/early-accent.d.ts +24 -0
  149. package/docs/architecture/prompt-suggestions.md +1 -1
  150. package/docs/architecture/ui-reference-provenance.md +2 -1
  151. package/docs/features/prompt-history.md +1 -1
  152. package/docs/features/session-exports.md +15 -0
  153. package/docs/local-worktree-cleanup.md +24 -0
  154. package/package.json +6 -6
@@ -6,12 +6,12 @@ const BOLD = "\u001b[1m";
6
6
  const RESET_BOLD = "\u001b[22m";
7
7
  const EXPLANATION = "This allows to load project settings and resources, install missing project packages, and execute project extensions.";
8
8
  /** Fixed startup-safe rendering only; project-derived presentation is forbidden before trust resolves. */
9
- export function renderProjectTrustDialog(cwd, options, selected, terminalWidth, terminalRows, accent, border) {
9
+ export function renderProjectTrustDialog(cwd, options, selected, terminalWidth, terminalRows, accent, border, selectedBg) {
10
10
  const width = Math.max(1, terminalWidth);
11
11
  const rule = `${border}${"─".repeat(width)}${RESET_FG}`;
12
- const title = ` ${BOLD}${accent}Trust project folder?${RESET_FG}${RESET_BOLD}`;
12
+ const title = ` ${BOLD}${accent}Trust Project Folder?${RESET_FG}${RESET_BOLD}`;
13
13
  const path = ` ${MUTED}${sanitize(cwd)}${RESET_FG}`;
14
- const choices = options.map((label, index) => choice(label, selected === index, accent));
14
+ const choices = options.map((label, index) => choice(label, selected === index, accent, selectedBg));
15
15
  const closeKey = displayShortcutKeyLabel(DIALOG_CLOSE_SHORTCUT_HINT.key);
16
16
  const hint = ` ${DIM}↑↓${MUTED} navigate ${DIM}Enter${MUTED} select ${DIM}${closeKey}${MUTED} ${DIALOG_CLOSE_SHORTCUT_HINT.action}${RESET_FG}`;
17
17
  const explanation = wrap(EXPLANATION, Math.max(1, width - 2)).map(line => ` ${line}`);
@@ -33,8 +33,8 @@ export function renderProjectTrustDialog(cwd, options, selected, terminalWidth,
33
33
  return choiceWindow(choices, selected, 2);
34
34
  return [choices[selected] ?? choices[0] ?? ""];
35
35
  }
36
- function choice(label, selected, accent) {
37
- return selected ? ` ${accent}→ ${label}${RESET_FG}` : ` ${label}`;
36
+ function choice(label, selected, accent, selectedBg) {
37
+ return selected ? ` ${selectedBg}${accent}→ ${RESET_FG}${label}\u001b[49m` : ` ${label}`;
38
38
  }
39
39
  function choiceWindow(choices, selected, capacity) {
40
40
  const start = Math.min(Math.max(0, selected - Math.floor(capacity / 2)), Math.max(0, choices.length - capacity));
@@ -67,7 +67,7 @@ export function createConsoleProjectTrustPrompt(options = {}) {
67
67
  const render = () => {
68
68
  const width = Math.max(renderBareDialog ? 1 : 20, output.columns ?? 80);
69
69
  const rows = Math.max(1, output.rows ?? 24);
70
- const lines = (renderBareDialog?.(cwd, choices.map(choice => choice.label), selected, width, rows, palette.accent, palette.border) ?? [
70
+ const lines = (renderBareDialog?.(cwd, choices.map(choice => choice.label), selected, width, rows, palette.accent, palette.border, palette.selectedBg) ?? [
71
71
  `${BOLD}${ACCENT}Trust project folder?${RESET_FG}${RESET_BOLD}`,
72
72
  cwd,
73
73
  "",
@@ -205,5 +205,5 @@ function clipAnsiSafe(line, width) {
205
205
  index += 1;
206
206
  visible += 1;
207
207
  }
208
- return `${output}${RESET_FG}${RESET_BOLD}`;
208
+ return `${output}${RESET_FG}${RESET_BOLD}${line.includes("\u001b[48;") ? "\u001b[49m" : ""}`;
209
209
  }
@@ -23,6 +23,8 @@ export interface ReferenceScreenOptions {
23
23
  readonly id: string;
24
24
  readonly title: string;
25
25
  readonly document: ReferenceDocumentProvider;
26
+ /** In-place documents put their shortcut row inside the bottom rule like other dock dialogs. */
27
+ readonly presentation?: "screen" | "dialog";
26
28
  /** The reader's scroll settings, read on every frame so a live change is honored. */
27
29
  readonly scrollSettings: () => OwnedUiViewportSettings;
28
30
  /** Enables owned text selection for document routes that explicitly opt in. */
@@ -47,6 +47,7 @@ const KEYS = {
47
47
  export class ReferenceScreenApp {
48
48
  id;
49
49
  #title;
50
+ #dialog;
50
51
  #document;
51
52
  #scrollSettings;
52
53
  #selection;
@@ -66,6 +67,7 @@ export class ReferenceScreenApp {
66
67
  constructor(options) {
67
68
  this.id = options.id;
68
69
  this.#title = options.title;
70
+ this.#dialog = options.presentation === "dialog";
69
71
  this.#document = options.document;
70
72
  this.#scrollSettings = options.scrollSettings;
71
73
  this.#selection = options.selection === undefined ? undefined : new ReferenceDocumentSelection(options.selection);
@@ -158,7 +160,8 @@ export class ReferenceScreenApp {
158
160
  // Compatibility: the v2 reference screen frames its document between two border-coloured rules.
159
161
  const rule = theme.fg("border", "─".repeat(rect.width));
160
162
  // Invariant: dialog chrome shares the title's one-cell inset; document content keeps its own layout.
161
- const frame = [rule, ...withRail, rule, padToWidth(hint, rect.width)];
163
+ const footer = padToWidth(hint, rect.width);
164
+ const frame = this.#dialog ? [rule, ...withRail, footer, rule] : [rule, ...withRail, rule, footer];
162
165
  // Invariant: a rectangle too small for the chrome still gets exactly its rows, top first.
163
166
  return frame.slice(0, rect.height).concat(Array(Math.max(0, rect.height - frame.length)).fill(""));
164
167
  }
@@ -1,4 +1,4 @@
1
- import { DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
1
+ import { DIALOG_CLOSE_SHORTCUT_HINT, wrapChoiceIndex } from "../../contracts/presentation/index.js";
2
2
  import { GLOBAL_SCOPE, LineInput, PLAIN_THEME, ShortcutRegistry, assertNoShortcutConflicts, blockJumpTarget, configureDialogChoiceCapacity, configureDialogTextRows, configureDialogWidth, numericValues, RAIL_COLUMNS, renderCenteredDialogOverlay, renderDialogPanel, renderEmptyState, renderGroupHeader, renderListRow, renderNote, renderShortcutHintsWithClose, menuRowAt, regionAt, renderValueMenu, withScrollbarRail, stepperEnds, steppedValue, truncateToWidth, valueColumnFor, valueMenuFrame, blockRowSpan, displayWidth, humanizeLabel, humanizeTitle, indexOfKey, layoutList, moveSelection, rowKey, scrollForSelection, scrollbarGeometry, scrollbarWheelRows, selectableIndexes, } from "../../ui/components/index.js";
3
3
  import { SettingsScrollRail } from "./settings-scroll-rail.js";
4
4
  import { SettingsEdits, replaceRecord, sameRecord } from "./settings-edits.js";
@@ -240,7 +240,7 @@ export class SettingsApp {
240
240
  }
241
241
  case "move-up":
242
242
  case "move-down":
243
- this.#select(rows, moveSelection(rows, selected, action === "move-down" ? 1 : -1));
243
+ this.#select(rows, moveSelection(rows, selected, action === "move-down" ? 1 : -1, true));
244
244
  return { consumed: true };
245
245
  case "page-up":
246
246
  case "page-down":
@@ -472,10 +472,10 @@ export class SettingsApp {
472
472
  this.#structured = null;
473
473
  return { consumed: true };
474
474
  case "part-previous":
475
- open.index = Math.max(0, open.index - 1);
475
+ open.index = wrapChoiceIndex(open.index, -1, open.flags.length);
476
476
  return { consumed: true };
477
477
  case "part-next":
478
- open.index = Math.min(open.flags.length - 1, open.index + 1);
478
+ open.index = wrapChoiceIndex(open.index, 1, open.flags.length);
479
479
  return { consumed: true };
480
480
  case "part-change":
481
481
  this.#toggleFlag(open.index);
@@ -529,7 +529,7 @@ export class SettingsApp {
529
529
  // Compatibility: the keyboard starts from the value in effect rather than from the top.
530
530
  menu.index = menu.index < 0
531
531
  ? menu.current
532
- : Math.min(menu.choices.length - 1, Math.max(0, menu.index + (key === "down" ? 1 : -1)));
532
+ : wrapChoiceIndex(menu.index, key === "down" ? 1 : -1, menu.choices.length);
533
533
  }
534
534
  else if (key === "enter") {
535
535
  const value = menu.choices[menu.index];
@@ -577,7 +577,7 @@ export class SettingsApp {
577
577
  this.#jump(rows, target);
578
578
  return { consumed: true };
579
579
  }
580
- this.#select(rows, moveSelection(rows, selected, forward ? 1 : -1));
580
+ this.#select(rows, moveSelection(rows, selected, forward ? 1 : -1, true));
581
581
  return { consumed: true };
582
582
  }
583
583
  const before = input.getText();
@@ -1,4 +1,4 @@
1
- import { DIALOG_BACK_SHORTCUT_HINT, DIALOG_CLOSE_SHORTCUT_HINT } from "../../contracts/presentation/index.js";
1
+ import { DIALOG_BACK_SHORTCUT_HINT, DIALOG_CLOSE_SHORTCUT_HINT, wrapChoiceIndex } from "../../contracts/presentation/index.js";
2
2
  import { LineInput, ShortcutRegistry, assertNoShortcutConflicts, configureDialogChoiceCapacity, configureDialogTextRows, handleLineInputKey, humanizeLabel, renderSteppedDialogPanel, } from "../../ui/components/index.js";
3
3
  import { changeFailure, structuredValue } from "./settings-edits.js";
4
4
  import { SETTINGS_APP_ID } from "./settings-route.js";
@@ -28,7 +28,7 @@ export function modelDialogKey(open, data, key, actions) {
28
28
  return { consumed: true };
29
29
  }
30
30
  if (action === "previous" || action === "next") {
31
- open.index = Math.min(Math.max(0, flags.length - 1), Math.max(0, open.index + (action === "next" ? 1 : -1)));
31
+ open.index = wrapChoiceIndex(open.index, action === "next" ? 1 : -1, flags.length);
32
32
  return { consumed: true };
33
33
  }
34
34
  if (action === "select") {
@@ -61,7 +61,7 @@ export function modelDialogKey(open, data, key, actions) {
61
61
  return { consumed: true };
62
62
  }
63
63
  if (action === "previous" || action === "next") {
64
- open.index = Math.min(Math.max(0, choices.length - 1), Math.max(0, open.index + (action === "next" ? 1 : -1)));
64
+ open.index = wrapChoiceIndex(open.index, action === "next" ? 1 : -1, choices.length);
65
65
  return { consumed: true };
66
66
  }
67
67
  if (action === "select") {
@@ -0,0 +1,5 @@
1
+ /** Exclusively reserve a default export; failure cleanup may remove only this same regular file. */
2
+ export declare function reserveUserExport(sessionId: string, format: "html" | "jsonl", home?: string): Promise<{
3
+ readonly path: string;
4
+ failed(): Promise<void>;
5
+ }>;
@@ -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,6 @@
1
+ import type { PiShellSkillsSelectorOptions } from "./skills-dialog.js";
2
+ import type { OwnedUiShellComponentPort } from "../../../contracts/owned-ui/index.js";
3
+ /** Defers optional selector code while retaining focus, early input and cancellation. */
4
+ export declare function deferredSelector(load: () => Promise<OwnedUiShellComponentPort>, cancel: () => void): OwnedUiShellComponentPort;
5
+ /** Keeps Skills outside first-prompt loading. */
6
+ export declare function createPiShellSkillsSelector(options: PiShellSkillsSelectorOptions): OwnedUiShellComponentPort;
@@ -0,0 +1,40 @@
1
+ /** Defers optional selector code while retaining focus, early input and cancellation. */
2
+ export function deferredSelector(load, cancel) {
3
+ let delegate;
4
+ let closed = false;
5
+ let focused = false;
6
+ let failure;
7
+ const pending = [];
8
+ const ready = load().then(component => {
9
+ if (closed) {
10
+ component.dispose?.();
11
+ return;
12
+ }
13
+ delegate = component;
14
+ delegate.setFocused?.(focused);
15
+ for (const key of pending.splice(0)) {
16
+ if (!closed)
17
+ delegate.handleInput?.(key);
18
+ }
19
+ }).catch(() => { if (!closed)
20
+ failure = "Selector unavailable — Esc close"; });
21
+ return {
22
+ ready,
23
+ render: width => delegate?.render(width) ?? (failure === undefined ? [] : [failure.slice(0, width)]),
24
+ invalidate: () => delegate?.invalidate(),
25
+ handleInput: data => {
26
+ if (delegate !== undefined)
27
+ delegate.handleInput?.(data);
28
+ else if (data === "\u001b" || data === "\u0003")
29
+ cancel();
30
+ else if (pending.length < 32)
31
+ pending.push(data);
32
+ },
33
+ setFocused: value => { focused = value; delegate?.setFocused?.(value); },
34
+ dispose: () => { closed = true; pending.length = 0; delegate?.dispose?.(); },
35
+ };
36
+ }
37
+ /** Keeps Skills outside first-prompt loading. */
38
+ export function createPiShellSkillsSelector(options) {
39
+ return deferredSelector(() => import("./skills-dialog.js").then(module => module.createPiShellSkillsSelector(options)), options.onCancel);
40
+ }
@@ -0,0 +1,3 @@
1
+ import type { OwnedUiShellLoginDialogPort as Login } from "../../../contracts/owned-ui/index.js";
2
+ /** Queues optional login calls. */
3
+ export declare function deferredLoginDialog(...args: Parameters<typeof import("./upstream/components/login-dialog.js").createThemedLoginDialog>): Login;
@@ -0,0 +1,57 @@
1
+ /** Queues optional login calls. */
2
+ export function deferredLoginDialog(...args) {
3
+ const [runtime, , complete] = args;
4
+ let dialog, closed = false, focused = false, failed = false;
5
+ const pending = [];
6
+ const rejects = new Set();
7
+ const rejectAll = (message) => { for (const reject of rejects)
8
+ reject(new Error(message)); rejects.clear(); pending.length = 0; };
9
+ const ready = import("./upstream/components/login-dialog.js").then(module => {
10
+ if (closed)
11
+ return;
12
+ dialog = module.createThemedLoginDialog(...args);
13
+ dialog.setFocused?.(focused);
14
+ for (const action of pending.splice(0)) {
15
+ if (!closed)
16
+ action(dialog);
17
+ }
18
+ }).catch(() => { failed = true; rejectAll("Login unavailable"); }).finally(() => { if (!closed)
19
+ runtime.requestRender(); });
20
+ const call = (action) => { if (!closed && !failed) {
21
+ if (dialog)
22
+ action(dialog);
23
+ else
24
+ pending.push(action);
25
+ } };
26
+ const dispose = () => { closed = true; dialog?.dispose?.(); rejectAll("Login cancelled"); };
27
+ const answer = (action) => new Promise((resolve, reject) => {
28
+ if (closed || failed) {
29
+ reject(new Error("Login unavailable"));
30
+ return;
31
+ }
32
+ rejects.add(reject);
33
+ call(target => { rejects.delete(reject); void action(target).then(resolve, reject); });
34
+ });
35
+ return {
36
+ ready, dispose,
37
+ render: width => dialog?.render(width) ?? (failed ? ["Login unavailable — Esc close".slice(0, width)] : []),
38
+ invalidate: () => dialog?.invalidate(),
39
+ setFocused: value => { focused = value; dialog?.setFocused?.(value); },
40
+ handleInput: data => {
41
+ if (!dialog && (data === "\u001b" || data === "\u0003")) {
42
+ dispose();
43
+ complete(false, "Login cancelled");
44
+ }
45
+ else
46
+ call(target => target.handleInput?.(data));
47
+ },
48
+ showAuth: (url, instructions) => call(target => target.showAuth(url, instructions)),
49
+ showDeviceCode: info => call(target => target.showDeviceCode(info)),
50
+ showManualInput: message => answer(target => target.showManualInput(message)),
51
+ showPrompt: (message, placeholder) => answer(target => target.showPrompt(message, placeholder)),
52
+ showDetails: lines => call(target => target.showDetails(lines)),
53
+ showInfo: (message, links) => call(target => target.showInfo(message, links)),
54
+ showWaiting: message => call(target => target.showWaiting(message)),
55
+ showProgress: message => call(target => target.showProgress(message)),
56
+ };
57
+ }
@@ -0,0 +1,6 @@
1
+ /** Login effects accept web URLs only; controls are encoded before any hyperlink or launcher sees them. */
2
+ export declare function loginWebUrl(value: string): string | undefined;
3
+ /** Keeps untrusted non-web instructions printable without granting them a hyperlink target. */
4
+ export declare function loginLink(value: string, label?: string): string;
5
+ /** Best-effort browser request with argv boundaries, no shell, and no dependency on the project cwd. */
6
+ export declare function openLoginBrowser(value: string): void;
@@ -0,0 +1,31 @@
1
+ import { spawn } from "node:child_process";
2
+ import { hyperlink, replaceControlCharacters } from "@earendil-works/pi-tui";
3
+ /** Login effects accept web URLs only; controls are encoded before any hyperlink or launcher sees them. */
4
+ export function loginWebUrl(value) {
5
+ try {
6
+ const url = new URL(value);
7
+ return ["http:", "https:"].includes(url.protocol) ? url.href : undefined;
8
+ }
9
+ catch {
10
+ return undefined;
11
+ }
12
+ }
13
+ /** Keeps untrusted non-web instructions printable without granting them a hyperlink target. */
14
+ export function loginLink(value, label) {
15
+ const url = loginWebUrl(value);
16
+ const text = replaceControlCharacters(label ?? url ?? value);
17
+ return url === undefined ? text : hyperlink(text, url);
18
+ }
19
+ /** Best-effort browser request with argv boundaries, no shell, and no dependency on the project cwd. */
20
+ export function openLoginBrowser(value) {
21
+ const url = loginWebUrl(value);
22
+ if (url === undefined)
23
+ return;
24
+ const platform = process.platform;
25
+ const executable = platform === "win32" ? "rundll32" : platform === "darwin" ? "open" : "xdg-open";
26
+ const args = platform === "win32" ? ["url.dll,FileProtocolHandler", url] : [url];
27
+ try {
28
+ spawn(executable, args, { stdio: "ignore", detached: true, cwd: "/" }).on("error", () => { }).unref();
29
+ }
30
+ catch { /* Compatibility: a failed browser launch never interrupts provider authentication. */ }
31
+ }
@@ -0,0 +1,7 @@
1
+ import { type Component } from "@earendil-works/pi-tui";
2
+ type LoginTextRole = "accent" | "muted" | "warning" | "success" | "error" | "dim" | "text";
3
+ /** Preserves authored line breaks without replaying control payloads. */
4
+ export declare function loginPlainText(value: string): string;
5
+ /** A zero-inset login row that resolves its semantic role against the live theme on every frame. */
6
+ export declare function loginText(text: string | (() => string), role?: LoginTextRole, bold?: boolean): Component;
7
+ export {};
@@ -0,0 +1,17 @@
1
+ import { Text, replaceControlCharacters } from "@earendil-works/pi-tui";
2
+ import { piTheme } from "./theme.js";
3
+ /** Preserves authored line breaks without replaying control payloads. */
4
+ export function loginPlainText(value) {
5
+ return value.split("\n").map(line => replaceControlCharacters(line)).join("\n");
6
+ }
7
+ /** A zero-inset login row that resolves its semantic role against the live theme on every frame. */
8
+ export function loginText(text, role = "muted", bold = false) {
9
+ return {
10
+ render(width) {
11
+ const theme = piTheme();
12
+ const value = typeof text === "string" ? loginPlainText(text) : text();
13
+ return new Text(theme.fg(role, bold ? theme.bold(value) : value), 0, 0).render(width);
14
+ },
15
+ invalidate() { },
16
+ };
17
+ }
@@ -1,5 +1,10 @@
1
1
  import { Container, Input, type Component, type Focusable } from "@earendil-works/pi-tui";
2
+ import type { OwnedUiShellComponentPort } from "../../../contracts/owned-ui/index.js";
2
3
  export declare const PI_MODAL_CONTENT_PADDING_X = 1;
4
+ /** Applies standard primary/metadata roles without changing a pinned row's text or clipping. */
5
+ export declare function renderPiModalSelectedPrimary(row: string, width: number, labels: readonly string[]): string;
6
+ /** Standardizes internal legacy model/message choices without changing their controller or metadata. */
7
+ export declare function standardInternalChoicePort(port: OwnedUiShellComponentPort, metadata?: boolean): OwnedUiShellComponentPort;
3
8
  /** Paints the standard Pi Input prompt as a live-themed dialog-search marker. */
4
9
  export declare function renderPiModalSearchInput(input: Input, width: number): string[];
5
10
  /** Replaces one explicitly identified direct Input child with the standard search presentation. */
@@ -1,7 +1,35 @@
1
- import { Container, Input, Spacer, Text, } from "@earendil-works/pi-tui";
1
+ import { Container, Input, Spacer, Text, sliceByColumn, stripTerminalSequences, visibleWidth, } from "@earendil-works/pi-tui";
2
2
  import { DIALOG_CONTENT_PADDING_X, dialogFrameGeometry, insetDialogContent } from "../../../contracts/presentation/index.js";
3
- import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints } from "./theme.js";
3
+ import { DIALOG_CLOSE_SHORTCUT_HINT, piTheme, renderPiModalShortcutHints, renderPiModalListRow } from "./theme.js";
4
4
  export const PI_MODAL_CONTENT_PADDING_X = DIALOG_CONTENT_PADDING_X;
5
+ /** Applies standard primary/metadata roles without changing a pinned row's text or clipping. */
6
+ export function renderPiModalSelectedPrimary(row, width, labels) {
7
+ const plain = stripTerminalSequences(row).trimEnd();
8
+ if (!plain.startsWith(" → "))
9
+ return row;
10
+ const primary = labels.filter(label => plain.slice(3).startsWith(label))
11
+ .sort((left, right) => visibleWidth(right) - visibleWidth(left))[0];
12
+ const length = primary === undefined ? Math.max(0, visibleWidth(plain) - 3) : visibleWidth(primary);
13
+ const label = stripTerminalSequences(sliceByColumn(row, 3, length));
14
+ const suffix = sliceByColumn(row, 3 + length, Math.max(0, visibleWidth(plain) - 3 - length));
15
+ const theme = piTheme();
16
+ return ` ${renderPiModalListRow(`${theme.fg("accent", "→ ")}${theme.fg("text", label)}${theme.fg("muted", stripTerminalSequences(suffix))}`, Math.max(0, width - 1), true)}`;
17
+ }
18
+ /** Standardizes internal legacy model/message choices without changing their controller or metadata. */
19
+ export function standardInternalChoicePort(port, metadata = false) {
20
+ return { ...port, render: width => port.render(width).map(row => {
21
+ const match = /^(\s*)[→›] (.*)$/u.exec(stripTerminalSequences(row).trimEnd());
22
+ if (match === null)
23
+ return row;
24
+ const indent = visibleWidth(match[1]);
25
+ const marker = metadata ? sliceByColumn(row, indent + 2, 2) : "";
26
+ const label = metadata ? match[2].slice(2).split(/ \[| \(/u, 1)[0] : match[2];
27
+ const suffixStart = indent + 2 + (metadata ? 2 : 0) + visibleWidth(label);
28
+ const suffix = metadata ? sliceByColumn(row, suffixStart, Math.max(0, visibleWidth(row) - suffixStart)).trimEnd() : "";
29
+ const theme = piTheme();
30
+ return `${match[1]}${renderPiModalListRow(`${theme.fg("accent", "→ ")}${marker}${theme.fg("text", label)}${suffix}`, Math.max(0, width - indent), true)}`;
31
+ }) };
32
+ }
5
33
  /** Paints the standard Pi Input prompt as a live-themed dialog-search marker. */
6
34
  export function renderPiModalSearchInput(input, width) {
7
35
  return input.render(width).map(row => {
@@ -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.