@timurproko/a1 0.1.8-dev.536 → 0.1.8-dev.546

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 (82) hide show
  1. package/bin/cli.js +4 -0
  2. package/bin/update-recovery.js +25 -3
  3. package/dist/app/session-shell/session-shell-root.js +15 -5
  4. package/dist/cli/dispatch.d.ts +5 -0
  5. package/dist/cli/dispatch.js +26 -0
  6. package/dist/cli/session-context.d.ts +9 -0
  7. package/dist/cli/session-context.js +29 -0
  8. package/dist/composition/owned-ui.js +6 -2
  9. package/dist/contracts/owned-ui/model.d.ts +5 -0
  10. package/dist/contracts/owned-ui/validation.js +6 -0
  11. package/dist/contracts/presentation/index.d.ts +13 -0
  12. package/dist/contracts/presentation/index.js +30 -0
  13. package/dist/features/launch/index.d.ts +2 -0
  14. package/dist/features/launch/index.js +1 -0
  15. package/dist/features/launch/session-context.d.ts +8 -0
  16. package/dist/features/launch/session-context.js +8 -0
  17. package/dist/features/owned-ui/project-trust-prompt.js +1 -1
  18. package/dist/features/owned-ui/reference-screen-app.js +7 -8
  19. package/dist/features/owned-ui/settings-app.js +9 -5
  20. package/dist/foundation/lifecycle/index.d.ts +2 -0
  21. package/dist/foundation/lifecycle/index.js +1 -0
  22. package/dist/foundation/lifecycle/session-repository-context.d.ts +18 -0
  23. package/dist/foundation/lifecycle/session-repository-context.js +173 -0
  24. package/dist/foundation/release/index.d.ts +2 -2
  25. package/dist/foundation/release/index.js +1 -1
  26. package/dist/foundation/release/update-recovery.d.ts +7 -1
  27. package/dist/foundation/release/update-recovery.js +39 -9
  28. package/dist/foundation/release/update.d.ts +7 -0
  29. package/dist/foundation/release/update.js +115 -24
  30. package/dist/integrations/pi/components/models-dialog.js +9 -9
  31. package/dist/integrations/pi/components/shell-editor-autocomplete.js +27 -3
  32. package/dist/integrations/pi/components/shell-extension-ui.js +4 -2
  33. package/dist/integrations/pi/components/shell-footer-status.js +1 -1
  34. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -2
  35. package/dist/integrations/pi/components/skills-command.d.ts +2 -0
  36. package/dist/integrations/pi/components/skills-command.js +11 -10
  37. package/dist/integrations/pi/components/skills-dialog.js +8 -3
  38. package/dist/integrations/pi/components/theme.d.ts +4 -0
  39. package/dist/integrations/pi/components/theme.js +10 -0
  40. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +4 -4
  41. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -2
  42. package/dist/integrations/pi/components/upstream/components/extension-editor.js +9 -10
  43. package/dist/integrations/pi/components/upstream/components/extension-input.d.ts +17 -0
  44. package/dist/integrations/pi/components/upstream/components/extension-input.js +72 -0
  45. package/dist/integrations/pi/components/upstream/components/extension-selector.d.ts +15 -0
  46. package/dist/integrations/pi/components/upstream/components/extension-selector.js +92 -0
  47. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
  48. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +16 -15
  49. package/dist/integrations/pi/components/upstream/components/session-footer.js +25 -9
  50. package/dist/integrations/pi/components/upstream/components/session-selector.js +25 -16
  51. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +6 -4
  52. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +22 -12
  53. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +3 -2
  54. package/dist/integrations/pi/components/upstream/components/tree-selector.js +14 -12
  55. package/dist/integrations/pi/components/upstream/components/trust-selector.js +11 -5
  56. package/dist/integrations/pi/engine/adapter.d.ts +2 -1
  57. package/dist/integrations/pi/engine/adapter.js +2 -0
  58. package/dist/integrations/pi/engine/message-values.js +1 -0
  59. package/dist/integrations/pi/engine/session-runtime.d.ts +11 -2
  60. package/dist/integrations/pi/engine/session-runtime.js +136 -33
  61. package/dist/integrations/pi/engine/transcript-projection.js +35 -0
  62. package/dist/integrations/pi/startup-public.manifest.json +2 -0
  63. package/dist/native/darwin-arm64/manifest.json +1 -1
  64. package/dist/native/linux-x64/manifest.json +1 -1
  65. package/dist/native/win32-x64/manifest.json +2 -2
  66. package/dist/native/win32-x64/process-guardian.exe +0 -0
  67. package/dist/product-identity.d.ts +1 -1
  68. package/dist/product-identity.js +1 -1
  69. package/dist/product-identity.json +2 -1
  70. package/dist/ui/components/dialog-panel.d.ts +2 -1
  71. package/dist/ui/components/dialog-panel.js +6 -1
  72. package/dist/ui/components/index.d.ts +2 -0
  73. package/dist/ui/components/index.js +1 -0
  74. package/dist/ui/components/shortcut-hints.d.ts +7 -0
  75. package/dist/ui/components/shortcut-hints.js +12 -0
  76. package/dist/ui/components/shortcuts.d.ts +2 -0
  77. package/dist/ui/components/shortcuts.js +5 -2
  78. package/dist/ui/components/transcript-viewport.d.ts +1 -1
  79. package/docs/ci-release-runbook.md +9 -7
  80. package/docs/openspec-archive-automation.md +6 -0
  81. package/docs/validation.md +5 -2
  82. package/package.json +1 -1
@@ -191,9 +191,11 @@ export function renderPiShellPackageUpdateNotice(packages, width) {
191
191
  function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt, outputPad, hideThinkingBlock, mermaidRenderingMode, showImages, imageWidthCells, imageAssets, tui) {
192
192
  switch (block.kind) {
193
193
  case "user": {
194
- const skill = parseSkillBlock(block.text);
194
+ const visibleBlock = submittedPrompt === undefined || block.userPresentation === undefined ? block
195
+ : { ...block, text: block.userPresentation.visibleText };
196
+ const skill = parseSkillBlock(visibleBlock.text);
195
197
  if (!skill)
196
- return submittedPrompt ? createPiSubmittedPromptComponent(block, submittedPrompt) : new UserMessageComponent(block.text);
198
+ return submittedPrompt ? createPiSubmittedPromptComponent(visibleBlock, submittedPrompt) : new UserMessageComponent(visibleBlock.text);
197
199
  const invocation = new SkillInvocationMessageComponent(skill, getMarkdownTheme());
198
200
  invocation.setExpanded(expanded);
199
201
  if (!skill.userMessage)
@@ -11,6 +11,8 @@ export interface PiShellSkillSummary {
11
11
  }
12
12
  export interface PiShellCollapsedSkillCommands {
13
13
  readonly commands: readonly PiShellAutocompleteCommand[];
14
+ /** The synthetic command replacing the withheld entries, when skill commands were present. */
15
+ readonly collapsedCommand?: PiShellAutocompleteCommand;
14
16
  /** The skills withheld from the top-level menu, in the engine's discovery order. */
15
17
  readonly skills: readonly PiShellSkillSummary[];
16
18
  }
@@ -27,6 +27,15 @@ export function collapseSkillCommands(commands) {
27
27
  if (skills.length === 0)
28
28
  return { commands, skills };
29
29
  const collapsed = [];
30
+ const collapsedCommand = {
31
+ name: SKILLS_COMMAND_NAME,
32
+ description: SKILLS_COMMAND_DESCRIPTION,
33
+ argumentOptions: skills.map(skill => ({
34
+ id: skill.name,
35
+ label: skill.name,
36
+ ...(skill.description.length === 0 ? {} : { description: skill.description }),
37
+ })),
38
+ };
30
39
  let inserted = false;
31
40
  for (const command of commands) {
32
41
  if (!command.name.startsWith(SKILL_COMMAND_PREFIX)) {
@@ -36,17 +45,9 @@ export function collapseSkillCommands(commands) {
36
45
  if (inserted)
37
46
  continue;
38
47
  inserted = true;
39
- collapsed.push({
40
- name: SKILLS_COMMAND_NAME,
41
- description: SKILLS_COMMAND_DESCRIPTION,
42
- argumentOptions: skills.map(skill => ({
43
- id: skill.name,
44
- label: skill.name,
45
- ...(skill.description.length === 0 ? {} : { description: skill.description }),
46
- })),
47
- });
48
+ collapsed.push(collapsedCommand);
48
49
  }
49
- return { commands: collapsed, skills };
50
+ return { commands: collapsed, collapsedCommand, skills };
50
51
  }
51
52
  /** Case-insensitive substring match on the name or description; a leading `skill:`/`skills:` in the query is ignored for the name. */
52
53
  export function skillMatchesQuery(skill, query) {
@@ -1,6 +1,6 @@
1
- import { DynamicBorder, keyHint, rawKeyHint } from "../startup-public.js";
1
+ import { DynamicBorder } from "../startup-public.js";
2
2
  import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
3
- import { PINNED_PI_LAYOUT, piTheme } from "./theme.js";
3
+ import { PINNED_PI_LAYOUT, piTheme, renderPiModalShortcutHints } from "./theme.js";
4
4
  import { componentPort, ensureTheme, piShellTruncateToWidth, piShellVisibleWidth } from "./shell-shared-facade.js";
5
5
  import { SKILL_COMMAND_PREFIX, skillMatchesQuery } from "./skills-command.js";
6
6
  /** A single line clipped at the viewport width: no wrap, no ellipsis, padded to the full width. */
@@ -44,7 +44,12 @@ class SkillsSelectorComponent extends Container {
44
44
  this.addChild(new Spacer(1));
45
45
  this.addChild(this.#listContainer);
46
46
  this.addChild(new Spacer(1));
47
- this.addChild(new Text(` ${rawKeyHint("↑↓", "navigate")} ${keyHint("tui.select.confirm", "select")} ${keyHint("tui.select.cancel", "cancel")}`, 0, 0));
47
+ const bindings = getKeybindings();
48
+ this.addChild(new Text(renderPiModalShortcutHints([
49
+ { key: "↑↓", action: "navigate" },
50
+ { key: bindings.getKeys("tui.select.confirm").join("/"), action: "select" },
51
+ { key: bindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
52
+ ], 2), 0, 0));
48
53
  this.addChild(new DynamicBorder());
49
54
  this.#updateList();
50
55
  }
@@ -1 +1,5 @@
1
+ import { type SemanticShortcutHint } from "../../../contracts/presentation/index.js";
1
2
  export * from "./upstream/theme/theme.js";
3
+ export type PiModalShortcutHint = SemanticShortcutHint;
4
+ /** The common bare-A1 modal footer: quiet display-cased keys, muted names, and whitespace-only gaps. */
5
+ export declare function renderPiModalShortcutHints(entries: readonly PiModalShortcutHint[], indent?: number): string;
@@ -1 +1,11 @@
1
+ import { renderSemanticShortcutHints, } from "../../../contracts/presentation/index.js";
2
+ import { piTheme } from "./upstream/theme/theme.js";
1
3
  export * from "./upstream/theme/theme.js";
4
+ /** The common bare-A1 modal footer: quiet display-cased keys, muted names, and whitespace-only gaps. */
5
+ export function renderPiModalShortcutHints(entries, indent = 0) {
6
+ const theme = piTheme();
7
+ return renderSemanticShortcutHints(entries, {
8
+ key: text => theme.fg("dim", text),
9
+ action: text => theme.fg("muted", text),
10
+ }, indent);
11
+ }
@@ -139,7 +139,7 @@ export declare const KEYBINDINGS: {
139
139
  readonly description: "Copy selection or last assistant message";
140
140
  };
141
141
  readonly "app.message.followUp": {
142
- readonly defaultKeys: "ctrl+q" | "alt+enter";
142
+ readonly defaultKeys: "alt+enter" | "ctrl+q";
143
143
  readonly description: "Queue follow-up message";
144
144
  };
145
145
  readonly "app.message.dequeue": {
@@ -147,7 +147,7 @@ export declare const KEYBINDINGS: {
147
147
  readonly description: "Restore queued messages";
148
148
  };
149
149
  readonly "app.clipboard.pasteImage": {
150
- readonly defaultKeys: "ctrl+v" | "alt+v";
150
+ readonly defaultKeys: "alt+v" | "ctrl+v";
151
151
  readonly description: "Paste image from clipboard (text fallback)";
152
152
  };
153
153
  readonly "app.session.new": {
@@ -167,11 +167,11 @@ export declare const KEYBINDINGS: {
167
167
  readonly description: "Resume a session";
168
168
  };
169
169
  readonly "app.tree.foldOrUp": {
170
- readonly defaultKeys: ("ctrl+left" | "alt+left")[];
170
+ readonly defaultKeys: ("alt+left" | "ctrl+left")[];
171
171
  readonly description: "Fold tree branch or move up";
172
172
  };
173
173
  readonly "app.tree.unfoldOrDown": {
174
- readonly defaultKeys: ("ctrl+right" | "alt+right")[];
174
+ readonly defaultKeys: ("alt+right" | "ctrl+right")[];
175
175
  readonly description: "Unfold tree branch or move down";
176
176
  };
177
177
  readonly "app.tree.editLabel": {
@@ -3,8 +3,8 @@
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,
6
- * cancellation, and external-editor lifecycle.
7
- * Deviations: none.
6
+ * cancellation, and external-editor lifecycle; bare A1 uses the shared semantic modal shortcut row.
7
+ * Deviations: owned-modal-shortcut-hints.
8
8
  */
9
9
  import { Container, type TUI } from "@earendil-works/pi-tui";
10
10
  import type { KeybindingsManager } from "../adjacent/core/keybindings.js";
@@ -3,16 +3,13 @@
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,
6
- * cancellation, and external-editor lifecycle.
7
- * Deviations: none.
6
+ * cancellation, and external-editor lifecycle; bare A1 uses the shared semantic modal shortcut row.
7
+ * Deviations: owned-modal-shortcut-hints.
8
8
  */
9
9
  import { Container, Editor, Spacer, Text, } from "@earendil-works/pi-tui";
10
10
  import { DynamicBorder, getSelectListTheme } from "../../../startup-public.js";
11
11
  import { editInExternalEditor } from "../external-editor.js";
12
- import { piTheme } from "../../theme.js";
13
- function keyHint(keybindings, keybinding, description) {
14
- return piTheme().fg("dim", keybindings.getKeys(keybinding).join("/")) + piTheme().fg("muted", ` ${description}`);
15
- }
12
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
16
13
  export class ExtensionEditorComponent extends Container {
17
14
  #editor;
18
15
  #tui;
@@ -54,10 +51,12 @@ export class ExtensionEditorComponent extends Container {
54
51
  this.#editor.onSubmit = onSubmit;
55
52
  this.addChild(this.#editor);
56
53
  this.addChild(new Spacer(1));
57
- const hint = keyHint(this.#keybindings, "tui.select.confirm", "submit")
58
- + " " + keyHint(this.#keybindings, "tui.input.newLine", "newline")
59
- + " " + keyHint(this.#keybindings, "tui.select.cancel", "cancel")
60
- + " " + keyHint(this.#keybindings, "app.editor.external", "external editor");
54
+ const hint = renderPiModalShortcutHints([
55
+ { key: this.#keybindings.getKeys("tui.select.confirm").join("/"), action: "submit" },
56
+ { key: this.#keybindings.getKeys("tui.input.newLine").join("/"), action: "newline" },
57
+ { key: this.#keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
58
+ { key: this.#keybindings.getKeys("app.editor.external").join("/"), action: "external editor" },
59
+ ]);
61
60
  this.addChild(new Text(hint, 1, 0));
62
61
  this.addChild(new Spacer(1));
63
62
  this.addChild(new DynamicBorder());
@@ -0,0 +1,17 @@
1
+ import { Container, type TUI } from "@earendil-works/pi-tui";
2
+ interface ExtensionInputOptions {
3
+ readonly tui?: TUI;
4
+ readonly timeout?: number;
5
+ readonly description?: string;
6
+ readonly initialValue?: string;
7
+ }
8
+ /** Bare-A1 extension text input using the common modal shortcut row. */
9
+ export declare class ExtensionInputComponent extends Container {
10
+ #private;
11
+ get focused(): boolean;
12
+ set focused(value: boolean);
13
+ constructor(title: string, _placeholder: string | undefined, onSubmit: (value: string) => void, onCancel: () => void, opts?: ExtensionInputOptions);
14
+ handleInput(keyData: string): void;
15
+ dispose(): void;
16
+ }
17
+ export {};
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Provenance: @earendil-works/pi-coding-agent 0.87.0 (MIT), commit 16787ad5b2dc748047f314ca1bfe7708f30f54f3,
3
+ * packages/coding-agent/src/modes/interactive/components/extension-input.ts.
4
+ * Modifications: Mechanical port: remap public imports, use ECMAScript private fields, and route the
5
+ * bare-A1 instruction row through the shared semantic shortcut renderer while preserving input,
6
+ * timeout, focus, submission, cancellation, and disposal behavior.
7
+ * Deviations: owned-modal-shortcut-hints.
8
+ */
9
+ import { DynamicBorder } from "../../../startup-public.js";
10
+ import { Container, getKeybindings, Input, Spacer, Text } from "@earendil-works/pi-tui";
11
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
12
+ import { CountdownTimer } from "./countdown-timer.js";
13
+ /** Bare-A1 extension text input using the common modal shortcut row. */
14
+ export class ExtensionInputComponent extends Container {
15
+ #input = new Input();
16
+ #onSubmit;
17
+ #onCancel;
18
+ #titleText;
19
+ #baseTitle;
20
+ #countdown;
21
+ #focused = false;
22
+ get focused() { return this.#focused; }
23
+ set focused(value) {
24
+ this.#focused = value;
25
+ this.#input.focused = value;
26
+ }
27
+ constructor(title, _placeholder, onSubmit, onCancel, opts) {
28
+ super();
29
+ this.#onSubmit = onSubmit;
30
+ this.#onCancel = onCancel;
31
+ this.#baseTitle = title;
32
+ const theme = piTheme();
33
+ this.addChild(new DynamicBorder());
34
+ this.addChild(new Spacer(1));
35
+ this.#titleText = new Text(theme.fg("accent", title), 1, 0);
36
+ this.addChild(this.#titleText);
37
+ if (opts?.description) {
38
+ this.addChild(new Spacer(1));
39
+ this.addChild(new Text(theme.fg("text", opts.description), 1, 0));
40
+ }
41
+ this.addChild(new Spacer(1));
42
+ this.#countdown = opts?.timeout !== undefined && opts.timeout > 0 && opts.tui !== undefined
43
+ ? new CountdownTimer(opts.timeout, opts.tui, seconds => {
44
+ const current = piTheme();
45
+ this.#titleText.setText(current.fg("accent", `${this.#baseTitle} (${seconds}s)`));
46
+ }, () => this.#onCancel())
47
+ : undefined;
48
+ if (opts?.initialValue !== undefined)
49
+ this.#input.setValue(opts.initialValue);
50
+ this.addChild(this.#input);
51
+ this.addChild(new Spacer(1));
52
+ const keys = getKeybindings();
53
+ this.addChild(new Text(renderPiModalShortcutHints([
54
+ { key: keys.getKeys("tui.select.confirm").join("/"), action: "submit" },
55
+ { key: keys.getKeys("tui.select.cancel").join("/"), action: "cancel" },
56
+ ]), 1, 0));
57
+ this.addChild(new Spacer(1));
58
+ this.addChild(new DynamicBorder());
59
+ }
60
+ handleInput(keyData) {
61
+ const keys = getKeybindings();
62
+ if (keys.matches(keyData, "tui.select.confirm") || keyData === "\n")
63
+ this.#onSubmit(this.#input.getValue());
64
+ else if (keys.matches(keyData, "tui.select.cancel"))
65
+ this.#onCancel();
66
+ else
67
+ this.#input.handleInput(keyData);
68
+ }
69
+ dispose() {
70
+ this.#countdown?.dispose();
71
+ }
72
+ }
@@ -0,0 +1,15 @@
1
+ import { Container, type TUI } from "@earendil-works/pi-tui";
2
+ interface ExtensionSelectorOptions {
3
+ readonly tui?: TUI;
4
+ readonly timeout?: number;
5
+ readonly description?: string;
6
+ readonly onToggleToolsExpanded?: () => void;
7
+ }
8
+ /** Bare-A1 extension selector using the common modal shortcut row. */
9
+ export declare class ExtensionSelectorComponent extends Container {
10
+ #private;
11
+ constructor(title: string, options: readonly string[], onSelect: (value: string) => void, onCancel: () => void, opts?: ExtensionSelectorOptions);
12
+ handleInput(keyData: string): void;
13
+ dispose(): void;
14
+ }
15
+ export {};
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Provenance: @earendil-works/pi-coding-agent 0.87.0 (MIT), commit 16787ad5b2dc748047f314ca1bfe7708f30f54f3,
3
+ * packages/coding-agent/src/modes/interactive/components/extension-selector.ts.
4
+ * Modifications: Mechanical port: remap public imports, use ECMAScript private fields, and route the
5
+ * bare-A1 instruction row through the shared semantic shortcut renderer while preserving options,
6
+ * timeout, navigation, selection, cancellation, and disposal behavior.
7
+ * Deviations: owned-modal-shortcut-hints.
8
+ */
9
+ import { DynamicBorder } from "../../../startup-public.js";
10
+ import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
11
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
12
+ import { CountdownTimer } from "./countdown-timer.js";
13
+ /** Bare-A1 extension selector using the common modal shortcut row. */
14
+ export class ExtensionSelectorComponent extends Container {
15
+ #options;
16
+ #selectedIndex = 0;
17
+ #listContainer = new Container();
18
+ #onSelect;
19
+ #onCancel;
20
+ #titleText;
21
+ #baseTitle;
22
+ #countdown;
23
+ #onToggleToolsExpanded;
24
+ constructor(title, options, onSelect, onCancel, opts) {
25
+ super();
26
+ this.#options = options;
27
+ this.#onSelect = onSelect;
28
+ this.#onCancel = onCancel;
29
+ this.#baseTitle = title;
30
+ this.#onToggleToolsExpanded = opts?.onToggleToolsExpanded;
31
+ const theme = piTheme();
32
+ this.addChild(new DynamicBorder());
33
+ this.addChild(new Spacer(1));
34
+ this.#titleText = new Text(theme.fg("accent", theme.bold(title)), 1, 0);
35
+ this.addChild(this.#titleText);
36
+ if (opts?.description) {
37
+ this.addChild(new Spacer(1));
38
+ this.addChild(new Text(theme.fg("text", opts.description), 1, 0));
39
+ }
40
+ this.addChild(new Spacer(1));
41
+ this.#countdown = opts?.timeout !== undefined && opts.timeout > 0 && opts.tui !== undefined
42
+ ? new CountdownTimer(opts.timeout, opts.tui, seconds => {
43
+ const current = piTheme();
44
+ this.#titleText.setText(current.fg("accent", current.bold(`${this.#baseTitle} (${seconds}s)`)));
45
+ }, () => this.#onCancel())
46
+ : undefined;
47
+ this.addChild(this.#listContainer);
48
+ this.addChild(new Spacer(1));
49
+ const keys = getKeybindings();
50
+ this.addChild(new Text(renderPiModalShortcutHints([
51
+ { key: "↑↓", action: "navigate" },
52
+ { key: keys.getKeys("tui.select.confirm").join("/"), action: "select" },
53
+ { key: keys.getKeys("tui.select.cancel").join("/"), action: "cancel" },
54
+ ]), 1, 0));
55
+ this.addChild(new Spacer(1));
56
+ this.addChild(new DynamicBorder());
57
+ this.#updateList();
58
+ }
59
+ #updateList() {
60
+ this.#listContainer.clear();
61
+ const theme = piTheme();
62
+ for (let index = 0; index < this.#options.length; index += 1) {
63
+ const option = this.#options[index];
64
+ this.#listContainer.addChild(new Text(index === this.#selectedIndex
65
+ ? theme.fg("accent", "→ ") + theme.fg("accent", option)
66
+ : ` ${theme.fg("text", option)}`, 1, 0));
67
+ }
68
+ }
69
+ handleInput(keyData) {
70
+ const keys = getKeybindings();
71
+ if (keys.matches(keyData, "app.tools.expand"))
72
+ this.#onToggleToolsExpanded?.();
73
+ else if (keys.matches(keyData, "tui.select.up") || keyData === "k") {
74
+ this.#selectedIndex = Math.max(0, this.#selectedIndex - 1);
75
+ this.#updateList();
76
+ }
77
+ else if (keys.matches(keyData, "tui.select.down") || keyData === "j") {
78
+ this.#selectedIndex = Math.min(this.#options.length - 1, this.#selectedIndex + 1);
79
+ this.#updateList();
80
+ }
81
+ else if (keys.matches(keyData, "tui.select.confirm") || keyData === "\n") {
82
+ const selected = this.#options[this.#selectedIndex];
83
+ if (selected !== undefined)
84
+ this.#onSelect(selected);
85
+ }
86
+ else if (keys.matches(keyData, "tui.select.cancel"))
87
+ this.#onCancel();
88
+ }
89
+ dispose() {
90
+ this.#countdown?.dispose();
91
+ }
92
+ }
@@ -4,8 +4,9 @@
4
4
  * Modifications: Source-synchronized scoped-model selector port: preserve session-only toggles,
5
5
  * search, bulk/provider/reorder actions, dirty state, Ctrl+S persistence, refresh status,
6
6
  * cancellation, and focus while remapping theme and public helper imports; local key labels preserve
7
- * pinned platform formatting before layout without changing binding identities.
8
- * Deviations: none.
7
+ * pinned platform formatting before layout without changing binding identities, and bare A1 uses the
8
+ * shared semantic modal shortcut row.
9
+ * Deviations: owned-modal-shortcut-hints.
9
10
  */
10
11
  interface ScopedModel {
11
12
  readonly provider: string;
@@ -1,7 +1,7 @@
1
1
  import { Container, fuzzyFilter, getKeybindings, Input, Key, matchesKey, Spacer, Text, } from "@earendil-works/pi-tui";
2
2
  import { DynamicBorder } from "../../../startup-public.js";
3
3
  import { getModelSearchText } from "../model-search.js";
4
- import { piTheme } from "../theme/theme.js";
4
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
5
5
  // Rationale: upstream's capitalized hint formatter is private to the package; the same mapping lives here.
6
6
  function keyDisplayText(keybinding) {
7
7
  return getKeybindings().getKeys(keybinding)
@@ -113,7 +113,9 @@ export class ScopedModelsSelectorComponent extends Container {
113
113
  this.addChild(new DynamicBorder());
114
114
  this.addChild(new Spacer(1));
115
115
  this.addChild(new Text(theme.fg("accent", theme.bold("Model Configuration")), 0, 0));
116
- this.addChild(new Text(theme.fg("muted", `Session-only. ${keyDisplayText("app.models.save")} to save to settings.`), 0, 0));
116
+ const saveKey = keyDisplayText("app.models.save");
117
+ this.addChild(new Text(theme.fg("muted", "Session-only.")
118
+ + (saveKey ? ` ${renderPiModalShortcutHints([{ key: saveKey, action: "to save to settings." }])}` : ""), 0, 0));
117
119
  this.addChild(new Spacer(1));
118
120
  // Search input
119
121
  this.searchInput = new Input();
@@ -167,19 +169,18 @@ export class ScopedModelsSelectorComponent extends Container {
167
169
  const allEnabled = this.enabledIds === null;
168
170
  const countText = allEnabled
169
171
  ? "all enabled"
170
- : `${enabledCount}/${this.allIds.length} enabled${unavailableCount ? ` · ${unavailableCount} unavailable` : ""}`;
171
- const parts = [
172
- `${keyDisplayText("tui.select.confirm")} toggle`,
173
- `${keyDisplayText("app.models.enableAll")} all`,
174
- `${keyDisplayText("app.models.clearAll")} clear`,
175
- `${keyDisplayText("app.models.toggleProvider")} provider`,
176
- `${keyDisplayText("app.models.reorderUp")}/${keyDisplayText("app.models.reorderDown")} reorder`,
177
- `${keyDisplayText("app.models.save")} save`,
178
- countText,
179
- ];
180
- return this.isDirty
181
- ? theme.fg("dim", ` ${parts.join(" · ")} `) + theme.fg("warning", "(unsaved)")
182
- : theme.fg("dim", ` ${parts.join(" · ")}`);
172
+ : `${enabledCount}/${this.allIds.length} enabled${unavailableCount ? `, ${unavailableCount} unavailable` : ""}`;
173
+ const reorderKeys = [keyDisplayText("app.models.reorderUp"), keyDisplayText("app.models.reorderDown")].filter(Boolean).join("/");
174
+ const hint = renderPiModalShortcutHints([
175
+ { key: keyDisplayText("tui.select.confirm"), action: "toggle" },
176
+ { key: keyDisplayText("app.models.enableAll"), action: "all" },
177
+ { key: keyDisplayText("app.models.clearAll"), action: "clear" },
178
+ { key: keyDisplayText("app.models.toggleProvider"), action: "provider" },
179
+ { key: reorderKeys, action: "reorder" },
180
+ { key: keyDisplayText("app.models.save"), action: "save" },
181
+ { action: countText },
182
+ ], 2);
183
+ return this.isDirty ? `${hint} ${theme.fg("warning", "(unsaved)")}` : hint;
183
184
  }
184
185
  refresh() {
185
186
  const query = this.searchInput.getValue();
@@ -2,9 +2,9 @@
2
2
  * Provenance: @earendil-works/pi-coding-agent 0.87.0 (MIT), commit 16787ad5b2dc748047f314ca1bfe7708f30f54f3,
3
3
  * packages/coding-agent/src/modes/interactive/components/footer.ts.
4
4
  * Modifications: Consumes neutral owned-UI view data instead of a fabricated concrete AgentSession; an
5
- * explicit bare-A1 profile colors the thinking-level name and adds its linked pull-request badge while
6
- * preserving the remaining footer layout and formatting, and replacement surfaces may suppress a
7
- * thinking level they already present.
5
+ * explicit bare-A1 profile colors the thinking-level name, renders its repository-context path, and
6
+ * reserves its linked pull-request badge before path truncation while preserving the remaining footer
7
+ * layout and formatting; replacement surfaces may suppress a thinking level they already present.
8
8
  * Deviations: owned-status-level-color, owned-pull-request-badge.
9
9
  */
10
10
  import { isAbsolute, relative, resolve, sep } from "node:path";
@@ -36,17 +36,33 @@ export class SessionFooter {
36
36
  const contextPercentValue = usage?.contextPercent ?? 0;
37
37
  const contextPercentSource = usage?.contextAvailable === false ? undefined : usage?.contextPercent;
38
38
  const contextPercent = contextPercentSource !== null ? contextPercentValue.toFixed(1) : "?";
39
- let pwd = formatCwd(this.cwd, process.env.HOME || process.env.USERPROFILE);
39
+ const repositoryPath = this.profile === "a1" ? view.status.footer?.repositoryPath ?? this.cwd : this.cwd;
40
+ let pwd = formatCwd(repositoryPath, process.env.HOME || process.env.USERPROFILE);
40
41
  const branch = view.status.footer?.branch;
41
42
  if (branch)
42
43
  pwd = `${pwd} (${branch})`;
43
44
  const sessionName = view.status.footer?.sessionName;
44
45
  const pullRequest = this.profile === "a1" ? view.status.footer?.pullRequest : null;
45
- const pathRow = pullRequest === undefined || pullRequest === null
46
- ? theme.fg("dim", sessionName ? `${pwd} • ${sessionName}` : pwd)
47
- : theme.fg("dim", `${pwd} PR `)
48
- + hyperlink(theme.fg("mdLink", `#${pullRequest.number}`), pullRequest.url)
49
- + (sessionName ? theme.fg("dim", ` • ${sessionName}`) : "");
46
+ let pathRow;
47
+ if (pullRequest === undefined || pullRequest === null) {
48
+ pathRow = theme.fg("dim", sessionName ? `${pwd} • ${sessionName}` : pwd);
49
+ }
50
+ else {
51
+ const badge = theme.fg("dim", " ")
52
+ + hyperlink(theme.fg("mdLink", `#${pullRequest.number}`), pullRequest.url);
53
+ const sessionSuffix = sessionName ? theme.fg("dim", ` • ${sessionName}`) : "";
54
+ const full = theme.fg("dim", pwd) + badge + sessionSuffix;
55
+ if (visibleWidth(full) <= width)
56
+ pathRow = full;
57
+ else if (visibleWidth(badge) <= width) {
58
+ const pathWidth = Math.max(0, width - visibleWidth(badge));
59
+ const path = pathWidth === 0 ? "" : truncateToWidth(theme.fg("dim", pwd), pathWidth, theme.fg("dim", "..."));
60
+ pathRow = path + badge;
61
+ }
62
+ else {
63
+ pathRow = truncateToWidth(badge, width, theme.fg("dim", "..."));
64
+ }
65
+ }
50
66
  const parts = [];
51
67
  if (input)
52
68
  parts.push(`↑${formatTokens(input)}`);
@@ -4,8 +4,8 @@
4
4
  * Modifications: Source-synchronized session selector port: preserve threaded/current/all scope,
5
5
  * search, sort, named/path filters, rename, delete confirmation, active-session protection, loading
6
6
  * progress, cancellation, focus, and disposal while remapping public helpers, owned keybindings/theme,
7
- * and canonical path handling.
8
- * Deviations: none.
7
+ * canonical path handling, and the shared bare-A1 modal shortcut row.
8
+ * Deviations: owned-modal-shortcut-hints.
9
9
  */
10
10
  import { spawnSync } from "node:child_process";
11
11
  import { existsSync, realpathSync } from "node:fs";
@@ -14,7 +14,7 @@ import * as os from "node:os";
14
14
  import { Container, getKeybindings, Input, Spacer, Text, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
15
15
  import { KeybindingsManager } from "../adjacent/core/keybindings.js";
16
16
  import { DynamicBorder } from "../../../startup-public.js";
17
- import { piTheme } from "../theme/theme.js";
17
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
18
18
  const theme = new Proxy({}, {
19
19
  get(_target, property) {
20
20
  const active = piTheme();
@@ -25,8 +25,8 @@ const theme = new Proxy({}, {
25
25
  function keyText(keybinding, keybindings) {
26
26
  return keybindings.getKeys(keybinding).join("/");
27
27
  }
28
- function keyHint(keybinding, description, keybindings) {
29
- return theme.fg("dim", keyText(keybinding, keybindings)) + theme.fg("muted", ` ${description}`);
28
+ function shortcutHint(keybinding, action, keybindings) {
29
+ return { key: keyText(keybinding, keybindings), action };
30
30
  }
31
31
  import { filterAndSortSessions, hasSessionName } from "./session-selector-search.js";
32
32
  function shortenPath(path) {
@@ -158,8 +158,11 @@ class SessionSelectorHeader {
158
158
  let hintLine1;
159
159
  let hintLine2;
160
160
  if (this.confirmingDeletePath !== null) {
161
- const confirmHint = `Delete session? ${keyHint("tui.select.confirm", "confirm", this.keybindings)} · ${keyHint("tui.select.cancel", "cancel", this.keybindings)}`;
162
- hintLine1 = theme.fg("error", truncateToWidth(confirmHint, width, "…"));
161
+ const confirmHint = theme.fg("error", "Delete session? ") + renderPiModalShortcutHints([
162
+ shortcutHint("tui.select.confirm", "confirm", this.keybindings),
163
+ shortcutHint("tui.select.cancel", "cancel", this.keybindings),
164
+ ]);
165
+ hintLine1 = truncateToWidth(confirmHint, width, "…");
163
166
  hintLine2 = "";
164
167
  }
165
168
  else if (this.statusMessage) {
@@ -169,18 +172,21 @@ class SessionSelectorHeader {
169
172
  }
170
173
  else {
171
174
  const pathState = this.showPath ? "(on)" : "(off)";
172
- const sep = theme.fg("muted", " · ");
173
- const hint1 = keyHint("tui.input.tab", "scope", this.keybindings) + sep + theme.fg("muted", 're:<pattern> regex · "phrase" exact');
175
+ const hint1 = renderPiModalShortcutHints([
176
+ shortcutHint("tui.input.tab", "scope", this.keybindings),
177
+ { action: "re:<pattern> regex" },
178
+ { action: '"phrase" exact' },
179
+ ]);
174
180
  const hint2Parts = [
175
- keyHint("app.session.toggleSort", "sort", this.keybindings),
176
- keyHint("app.session.toggleNamedFilter", "named", this.keybindings),
177
- keyHint("app.session.delete", "delete", this.keybindings),
178
- keyHint("app.session.togglePath", `path ${pathState}`, this.keybindings),
181
+ shortcutHint("app.session.toggleSort", "sort", this.keybindings),
182
+ shortcutHint("app.session.toggleNamedFilter", "named", this.keybindings),
183
+ shortcutHint("app.session.delete", "delete", this.keybindings),
184
+ shortcutHint("app.session.togglePath", `path ${pathState}`, this.keybindings),
179
185
  ];
180
186
  if (this.showRenameHint) {
181
- hint2Parts.push(keyHint("app.session.rename", "rename", this.keybindings));
187
+ hint2Parts.push(shortcutHint("app.session.rename", "rename", this.keybindings));
182
188
  }
183
- const hint2 = hint2Parts.join(sep);
189
+ const hint2 = renderPiModalShortcutHints(hint2Parts);
184
190
  hintLine1 = truncateToWidth(hint1, width, "…");
185
191
  hintLine2 = truncateToWidth(hint2, width, "…");
186
192
  }
@@ -776,7 +782,10 @@ export class SessionSelectorComponent extends Container {
776
782
  panel.addChild(new Spacer(1));
777
783
  panel.addChild(this.renameInput);
778
784
  panel.addChild(new Spacer(1));
779
- panel.addChild(new Text(theme.fg("muted", `${keyText("tui.select.confirm", this.keybindings)} to save · ${keyText("tui.select.cancel", this.keybindings)} to cancel`), 1, 0));
785
+ panel.addChild(new Text(renderPiModalShortcutHints([
786
+ shortcutHint("tui.select.confirm", "to save", this.keybindings),
787
+ shortcutHint("tui.select.cancel", "to cancel", this.keybindings),
788
+ ]), 1, 0));
780
789
  this.buildBaseLayout(panel, { showHeader: false });
781
790
  this.requestRender();
782
791
  }
@@ -4,10 +4,11 @@
4
4
  * Modifications: Preserve the searchable thinking-level selector, current/default semantics,
5
5
  * selection, save, cancellation, and focus while accepting the active bare-A1 cycle-key label from the
6
6
  * shell, styling the title with the established bold semantic accent treatment, placing its muted hint
7
- * directly below it, deduplicating levels, and rendering aligned muted descriptions after an adjacent
8
- * success-colored active marker. All list and border colors use the owned theme and its explicit color
9
- * mode. The comparison profile retains the public pinned component.
10
- * Deviations: owned-level-cycle-shortcut, owned-thinking-selector-heading.
7
+ * directly below it, deduplicating levels, and rendering aligned muted descriptions after adjacent
8
+ * active and bracketed default markers. All list and border colors use the owned theme and its
9
+ * explicit color mode, and the footer uses the shared bare-A1 modal shortcut row. The comparison
10
+ * profile retains the public pinned component.
11
+ * Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading.
11
12
  */
12
13
  import { Container, type Focusable, SelectList } from "@earendil-works/pi-tui";
13
14
  export type ThinkingSelectorLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
@@ -21,6 +22,7 @@ export declare class OwnedThinkingSelectorComponent extends Container implements
21
22
  private onCancel;
22
23
  private onSelectAsDefault;
23
24
  private currentLevel;
25
+ private defaultThinkingLevel;
24
26
  private _focused;
25
27
  get focused(): boolean;
26
28
  set focused(value: boolean);