@timurproko/a1 0.1.8-dev.544 → 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 (75) hide show
  1. package/bin/cli.js +4 -0
  2. package/dist/app/session-shell/session-shell-root.js +15 -5
  3. package/dist/cli/dispatch.d.ts +5 -0
  4. package/dist/cli/dispatch.js +26 -0
  5. package/dist/cli/session-context.d.ts +9 -0
  6. package/dist/cli/session-context.js +29 -0
  7. package/dist/composition/owned-ui.js +6 -2
  8. package/dist/contracts/owned-ui/model.d.ts +5 -0
  9. package/dist/contracts/owned-ui/validation.js +6 -0
  10. package/dist/contracts/presentation/index.d.ts +13 -0
  11. package/dist/contracts/presentation/index.js +30 -0
  12. package/dist/features/launch/index.d.ts +2 -0
  13. package/dist/features/launch/index.js +1 -0
  14. package/dist/features/launch/session-context.d.ts +8 -0
  15. package/dist/features/launch/session-context.js +8 -0
  16. package/dist/features/owned-ui/project-trust-prompt.js +1 -1
  17. package/dist/features/owned-ui/reference-screen-app.js +7 -8
  18. package/dist/features/owned-ui/settings-app.js +9 -5
  19. package/dist/foundation/lifecycle/index.d.ts +2 -0
  20. package/dist/foundation/lifecycle/index.js +1 -0
  21. package/dist/foundation/lifecycle/session-repository-context.d.ts +18 -0
  22. package/dist/foundation/lifecycle/session-repository-context.js +173 -0
  23. package/dist/integrations/pi/components/models-dialog.js +9 -9
  24. package/dist/integrations/pi/components/shell-editor-autocomplete.js +27 -3
  25. package/dist/integrations/pi/components/shell-extension-ui.js +4 -2
  26. package/dist/integrations/pi/components/shell-footer-status.js +1 -1
  27. package/dist/integrations/pi/components/shell-presenters-transcript.js +4 -2
  28. package/dist/integrations/pi/components/skills-command.d.ts +2 -0
  29. package/dist/integrations/pi/components/skills-command.js +11 -10
  30. package/dist/integrations/pi/components/skills-dialog.js +8 -3
  31. package/dist/integrations/pi/components/theme.d.ts +4 -0
  32. package/dist/integrations/pi/components/theme.js +10 -0
  33. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +4 -4
  34. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -2
  35. package/dist/integrations/pi/components/upstream/components/extension-editor.js +9 -10
  36. package/dist/integrations/pi/components/upstream/components/extension-input.d.ts +17 -0
  37. package/dist/integrations/pi/components/upstream/components/extension-input.js +72 -0
  38. package/dist/integrations/pi/components/upstream/components/extension-selector.d.ts +15 -0
  39. package/dist/integrations/pi/components/upstream/components/extension-selector.js +92 -0
  40. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +3 -2
  41. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +16 -15
  42. package/dist/integrations/pi/components/upstream/components/session-footer.js +25 -9
  43. package/dist/integrations/pi/components/upstream/components/session-selector.js +25 -16
  44. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +6 -4
  45. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +22 -12
  46. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +3 -2
  47. package/dist/integrations/pi/components/upstream/components/tree-selector.js +14 -12
  48. package/dist/integrations/pi/components/upstream/components/trust-selector.js +11 -5
  49. package/dist/integrations/pi/engine/adapter.d.ts +2 -1
  50. package/dist/integrations/pi/engine/adapter.js +2 -0
  51. package/dist/integrations/pi/engine/message-values.js +1 -0
  52. package/dist/integrations/pi/engine/session-runtime.d.ts +11 -2
  53. package/dist/integrations/pi/engine/session-runtime.js +136 -33
  54. package/dist/integrations/pi/engine/transcript-projection.js +35 -0
  55. package/dist/integrations/pi/startup-public.manifest.json +2 -0
  56. package/dist/native/darwin-arm64/manifest.json +1 -1
  57. package/dist/native/linux-x64/manifest.json +1 -1
  58. package/dist/native/win32-x64/manifest.json +2 -2
  59. package/dist/native/win32-x64/process-guardian.exe +0 -0
  60. package/dist/product-identity.d.ts +1 -1
  61. package/dist/product-identity.js +1 -1
  62. package/dist/product-identity.json +2 -1
  63. package/dist/ui/components/dialog-panel.d.ts +2 -1
  64. package/dist/ui/components/dialog-panel.js +6 -1
  65. package/dist/ui/components/index.d.ts +2 -0
  66. package/dist/ui/components/index.js +1 -0
  67. package/dist/ui/components/shortcut-hints.d.ts +7 -0
  68. package/dist/ui/components/shortcut-hints.js +12 -0
  69. package/dist/ui/components/shortcuts.d.ts +2 -0
  70. package/dist/ui/components/shortcuts.js +5 -2
  71. package/dist/ui/components/transcript-viewport.d.ts +1 -1
  72. package/docs/ci-release-runbook.md +9 -7
  73. package/docs/openspec-archive-automation.md +6 -0
  74. package/docs/validation.md +5 -2
  75. package/package.json +1 -1
@@ -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);
@@ -4,14 +4,15 @@
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, fuzzyFilter, getKeybindings, Input, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
13
14
  import { DynamicBorder } from "../../../startup-public.js";
14
- import { piTheme } from "../theme/theme.js";
15
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
15
16
  const THINKING_SELECT_LIST_LAYOUT = {
16
17
  minPrimaryColumnWidth: 12,
17
18
  maxPrimaryColumnWidth: 32,
@@ -35,6 +36,7 @@ export class OwnedThinkingSelectorComponent extends Container {
35
36
  onCancel;
36
37
  onSelectAsDefault;
37
38
  currentLevel;
39
+ defaultThinkingLevel;
38
40
  _focused = false;
39
41
  get focused() {
40
42
  return this._focused;
@@ -49,10 +51,11 @@ export class OwnedThinkingSelectorComponent extends Container {
49
51
  this.onCancel = onCancel;
50
52
  this.onSelectAsDefault = onSelectAsDefault;
51
53
  this.currentLevel = currentLevel;
54
+ this.defaultThinkingLevel = defaultThinkingLevel;
52
55
  this.allItems = [...new Set(availableLevels)].map((level) => ({
53
56
  value: level,
54
57
  label: level,
55
- description: level === defaultThinkingLevel ? `${LEVEL_DESCRIPTIONS[level]} · default` : LEVEL_DESCRIPTIONS[level],
58
+ description: LEVEL_DESCRIPTIONS[level],
56
59
  }));
57
60
  this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
58
61
  this.addChild(new Spacer(1));
@@ -67,7 +70,11 @@ export class OwnedThinkingSelectorComponent extends Container {
67
70
  this.selectListChildIndex = this.children.length;
68
71
  this.addChild(this.selectList);
69
72
  this.addChild(new Spacer(1));
70
- this.addChild(new Text(piTheme().fg("dim", ` ${this.keyDisplayText("tui.select.confirm")} to select · ${this.keyDisplayText("app.thinking.save")} to set as default · ${this.keyDisplayText("tui.select.cancel")} to cancel`), 0, 0));
73
+ this.addChild(new Text(renderPiModalShortcutHints([
74
+ { key: this.keyDisplayText("tui.select.confirm"), action: "to select" },
75
+ { key: this.keyDisplayText("app.thinking.save"), action: "to set as default" },
76
+ { key: this.keyDisplayText("tui.select.cancel"), action: "to cancel" },
77
+ ], 2), 0, 0));
71
78
  this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
72
79
  }
73
80
  keyDisplayText(keybinding) {
@@ -79,17 +86,20 @@ export class OwnedThinkingSelectorComponent extends Container {
79
86
  .join("/");
80
87
  }
81
88
  buildSelectList(items, preselect) {
89
+ const markerWidth = (item) => (item.value === this.currentLevel ? 2 : 0) + (item.value === this.defaultThinkingLevel ? 10 : 0);
82
90
  const primaryWidth = this.allItems.reduce((widest, item) => {
83
91
  const level = item.label ?? item.value;
84
- return Math.max(widest, level.length + (item.value === this.currentLevel ? 2 : 0));
92
+ return Math.max(widest, level.length + markerWidth(item));
85
93
  }, 0);
86
94
  const themedItems = items.map((item) => {
87
95
  const level = item.label ?? item.value;
88
- const current = item.value === this.currentLevel;
89
- const currentMarker = current ? ` ${piTheme().fg("success", "✓")}` : "";
90
- const separator = " ".repeat(Math.max(1, primaryWidth - level.length - (current ? 2 : 0) + 1));
96
+ const currentMarker = item.value === this.currentLevel ? ` ${piTheme().fg("success", "✓")}` : "";
97
+ const defaultMarker = item.value === this.defaultThinkingLevel
98
+ ? ` ${piTheme().fg("muted", "[default]")}`
99
+ : "";
100
+ const separator = " ".repeat(Math.max(1, primaryWidth - level.length - markerWidth(item) + 1));
91
101
  const description = item.description ? piTheme().fg("muted", item.description) : "";
92
- return { value: item.value, label: `${level}${currentMarker}${separator}${description}` };
102
+ return { value: item.value, label: `${level}${currentMarker}${defaultMarker}${separator}${description}` };
93
103
  });
94
104
  // Invariant: the bare selector uses the owned theme's explicit color mode, not upstream's host detection.
95
105
  const list = new SelectList(themedItems, Math.max(1, themedItems.length), {
@@ -3,8 +3,9 @@
3
3
  * packages/coding-agent/src/modes/interactive/components/tree-selector.ts.
4
4
  * Modifications: Source-synchronized tree selector port: preserve filtering, folding, labels, copying,
5
5
  * tree navigation, key hints, focus, and viewport behavior while remapping public types/components
6
- * plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton.
7
- * Deviations: none.
6
+ * plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton;
7
+ * bare A1 uses the shared semantic modal shortcut row.
8
+ * Deviations: owned-modal-shortcut-hints.
8
9
  */
9
10
  import { type Component, Container, type Focusable } from "@earendil-works/pi-tui";
10
11
  import { type SessionTreeNode } from "@earendil-works/pi-coding-agent";
@@ -3,13 +3,14 @@
3
3
  * packages/coding-agent/src/modes/interactive/components/tree-selector.ts.
4
4
  * Modifications: Source-synchronized tree selector port: preserve filtering, folding, labels, copying,
5
5
  * tree navigation, key hints, focus, and viewport behavior while remapping public types/components
6
- * plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton.
7
- * Deviations: none.
6
+ * plus owned keybindings/theme helpers required to avoid the pinned package nested pi-tui singleton;
7
+ * bare A1 uses the shared semantic modal shortcut row.
8
+ * Deviations: owned-modal-shortcut-hints.
8
9
  */
9
10
  import { Container, Input, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
10
11
  import { DynamicBorder } from "../../../startup-public.js";
11
12
  import { KeybindingsManager } from "../adjacent/core/keybindings.js";
12
- import { piTheme } from "../theme/theme.js";
13
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
13
14
  const theme = new Proxy({}, {
14
15
  get(_target, property) {
15
16
  const active = piTheme();
@@ -24,8 +25,8 @@ function formatKeyText(key) {
24
25
  .map((value) => value.split("+").map((part) => process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part).join("+"))
25
26
  .join("/");
26
27
  }
27
- function keyHint(keybinding, description) {
28
- return theme.fg("dim", formatKeyText(treeKeybindings.getKeys(keybinding).join("/"))) + theme.fg("muted", ` ${description}`);
28
+ function shortcutHint(keybinding, action) {
29
+ return { key: formatKeyText(treeKeybindings.getKeys(keybinding).join("/")), action };
29
30
  }
30
31
  const TREE_GUTTER_WIDTH = 2;
31
32
  const MIN_VISIBLE_ANCHOR_CONTENT_WIDTH = 4;
@@ -1075,14 +1076,12 @@ class TreeHelp {
1075
1076
  invalidate() { }
1076
1077
  render(width) {
1077
1078
  const items = TREE_HELP_ITEMS.map(({ keys, label, labelFirst }) => {
1078
- const text = formatHelpKeys(keys);
1079
- if (!text)
1080
- return label;
1081
- return labelFirst ? `${label} ${text}` : `${text} ${label}`;
1079
+ const key = formatHelpKeys(keys);
1080
+ return renderPiModalShortcutHints([key ? { key, action: label, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action: label }]);
1082
1081
  });
1083
1082
  const availableWidth = Math.max(1, width);
1084
1083
  const indent = " ";
1085
- const separator = " · ";
1084
+ const separator = " ";
1086
1085
  const lines = [];
1087
1086
  let currentLine = "";
1088
1087
  for (const item of items) {
@@ -1101,7 +1100,7 @@ class TreeHelp {
1101
1100
  if (currentLine) {
1102
1101
  lines.push(...wrapTextWithAnsi(currentLine.trimEnd(), availableWidth));
1103
1102
  }
1104
- return lines.map((line) => theme.fg("muted", line));
1103
+ return lines;
1105
1104
  }
1106
1105
  }
1107
1106
  const TREE_HELP_ITEMS = [
@@ -1184,7 +1183,10 @@ class LabelInput {
1184
1183
  const availableWidth = width - indent.length;
1185
1184
  lines.push(truncateToWidth(`${indent}${theme.fg("muted", "Label (empty to remove):")}`, width));
1186
1185
  lines.push(...this.input.render(availableWidth).map((line) => truncateToWidth(`${indent}${line}`, width)));
1187
- lines.push(truncateToWidth(`${indent}${keyHint("tui.select.confirm", "save")} ${keyHint("tui.select.cancel", "cancel")}`, width));
1186
+ lines.push(truncateToWidth(renderPiModalShortcutHints([
1187
+ shortcutHint("tui.select.confirm", "save"),
1188
+ shortcutHint("tui.select.cancel", "cancel"),
1189
+ ], indent.length), width));
1188
1190
  return lines;
1189
1191
  }
1190
1192
  handleInput(keyData) {
@@ -2,12 +2,13 @@
2
2
  * Provenance: @earendil-works/pi-coding-agent 0.87.0 (MIT), commit 16787ad5b2dc748047f314ca1bfe7708f30f54f3,
3
3
  * packages/coding-agent/src/modes/interactive/components/trust-selector.ts.
4
4
  * Modifications: Mechanical source-synchronized trust selector port with injected public
5
- * ProjectTrustStore-derived options and remapped owned theme imports.
6
- * Deviations: none.
5
+ * ProjectTrustStore-derived options, remapped owned theme imports, and the shared bare-A1 modal
6
+ * shortcut row.
7
+ * Deviations: owned-modal-shortcut-hints.
7
8
  */
8
- import { DynamicBorder, keyHint, rawKeyHint } from "../../../startup-public.js";
9
+ import { DynamicBorder } from "../../../startup-public.js";
9
10
  import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
10
- import { piTheme } from "../theme/theme.js";
11
+ import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
11
12
  function formatDecision(trustPath, decision) {
12
13
  if (decision === null)
13
14
  return "none";
@@ -39,7 +40,12 @@ export class TrustSelectorComponent extends Container {
39
40
  this.listContainer = new Container();
40
41
  this.addChild(this.listContainer);
41
42
  this.addChild(new Spacer(1));
42
- this.addChild(new Text(`${rawKeyHint("↑↓", "navigate")} ${keyHint("tui.select.confirm", "save")} ${keyHint("tui.select.cancel", "cancel")}`, 1, 0));
43
+ const keybindings = getKeybindings();
44
+ this.addChild(new Text(renderPiModalShortcutHints([
45
+ { key: "↑↓", action: "navigate" },
46
+ { key: keybindings.getKeys("tui.select.confirm").join("/"), action: "save" },
47
+ { key: keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
48
+ ]), 1, 0));
43
49
  this.addChild(new Spacer(1));
44
50
  this.addChild(new DynamicBorder());
45
51
  this.updateList(options.savedDecision);
@@ -9,7 +9,7 @@ import type { AgentSettingOwner } from "../../../contracts/agent-engine/index.js
9
9
  import { type AdapterCommandResult } from "./command-dispatch.js";
10
10
  import { type OwnedPiVisualExtensionSupport } from "./extension-ui-binding.js";
11
11
  import { type OwnedPiExtensionResourceSummary, type OwnedPiResourceSummary } from "./resource-catalog.js";
12
- import { type PiEnginePackageUpdateProbe, type PiEngineRuntimeFactory } from "./session-runtime.js";
12
+ import { type PiEnginePackageUpdateProbe, type PiEngineRuntimeFactory, type PiRepositoryContextReader } from "./session-runtime.js";
13
13
  import { PiWorkflowContexts } from "./workflow-contexts.js";
14
14
  export type { OwnedPiExtensionResourceSummary, OwnedPiExtensionSourceSummary, OwnedPiResourceSummary } from "./resource-catalog.js";
15
15
  export type { PiEngineRuntimeFactory, PiEngineRuntimeFactoryInput } from "./session-runtime.js";
@@ -41,6 +41,7 @@ export interface PiEngineAdapterOptions {
41
41
  * mode. Returns display names of packages with updates available.
42
42
  */
43
43
  readonly checkPackageUpdates?: PiEnginePackageUpdateProbe;
44
+ readonly repositoryContextReader?: PiRepositoryContextReader;
44
45
  }
45
46
  /** Owns the pinned Pi session lifecycle and translates its events into the neutral agent-engine contract. */
46
47
  export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGeneratorPort {
@@ -133,6 +133,7 @@ export class PiEngineAdapter {
133
133
  projectTrustPrompt: options.projectTrustPrompt,
134
134
  createRuntime: options.createRuntime,
135
135
  checkPackageUpdates: options.checkPackageUpdates,
136
+ ...(options.repositoryContextReader === undefined ? {} : { repositoryContextReader: options.repositoryContextReader }),
136
137
  host: this.#workflowHost,
137
138
  }, {
138
139
  disposed: () => this.#disposed,
@@ -428,6 +429,7 @@ export class PiEngineAdapter {
428
429
  usage: this.#usageCache ??= readUsageView(this.#engine.session, this.#engine.runtime, this.#activeModel),
429
430
  footer: {
430
431
  branch: this.#engine.gitBranch,
432
+ repositoryPath: this.#engine.repositoryCwd,
431
433
  pullRequest: this.#engine.pullRequest,
432
434
  sessionName: this.#engine.session?.sessionManager?.getSessionName() ?? null,
433
435
  availableProviderCount: new Set(this.#engine.runtime?.services.modelRuntime.getAvailableSnapshot?.().map(model => model.provider).filter(provider => provider !== undefined) ?? []).size,
@@ -29,6 +29,7 @@ export function sameBlockContent(left, right) {
29
29
  && left.status === right.status
30
30
  && left.title === right.title
31
31
  && left.text === right.text
32
+ && sameValue(left.userPresentation, right.userPresentation)
32
33
  && sameValue(left.toolState, right.toolState)
33
34
  && sameValue(left.toolRendering, right.toolRendering)
34
35
  && sameValue(left.payload, right.payload)
@@ -16,6 +16,12 @@ export interface PiEngineRuntimeFactoryInput {
16
16
  }
17
17
  export type PiEngineRuntimeFactory = (input: PiEngineRuntimeFactoryInput) => Promise<AgentSessionRuntime>;
18
18
  export type PiEnginePackageUpdateProbe = (settingsManager: AgentSessionServices["settingsManager"]) => Promise<readonly string[]>;
19
+ export interface PiRepositoryContextSelection {
20
+ readonly cwd: string;
21
+ readonly branch: string;
22
+ }
23
+ export type PiRepositoryContextReader = (sessionId: string, sessionFile: string, signal: AbortSignal) => Promise<PiRepositoryContextSelection | null>;
24
+ export type PiGitBranchReader = (cwd: string, signal: AbortSignal) => Promise<string | null>;
19
25
  export interface PiEngineRuntimeOptions {
20
26
  readonly cwd: string;
21
27
  readonly agentDir: string;
@@ -28,12 +34,14 @@ export interface PiEngineRuntimeOptions {
28
34
  readonly checkPackageUpdates: PiEnginePackageUpdateProbe | undefined;
29
35
  readonly pullRequestProbe?: PiPullRequestProbe;
30
36
  readonly pullRequestRefreshMs?: number;
31
- readonly gitBranchReader?: (cwd: string) => Promise<string | null>;
37
+ readonly repositoryContextPollMs?: number;
38
+ readonly repositoryContextReader?: PiRepositoryContextReader;
39
+ readonly gitBranchReader?: PiGitBranchReader;
32
40
  readonly host: PiWorkflowHost;
33
41
  }
34
42
  export interface PiEngineRuntimePorts {
35
43
  disposed(): boolean;
36
- /** The runtime exists and its cwd and git branch are known; nothing is bound yet. */
44
+ /** The runtime exists and its startup cwd is known; repository metadata resolves after binding. */
37
45
  started(runtime: AgentSessionRuntime): void;
38
46
  /** True while a runtime-initiated rebind must be ignored: delivery overload, stopped admission, or disposal. */
39
47
  rebindBlocked(): boolean;
@@ -65,6 +73,7 @@ export declare class PiEngineRuntime {
65
73
  /** Actual session replacements, excluding delivery-only invalidation of callbacks. */
66
74
  get bindingGeneration(): number;
67
75
  get cwd(): string;
76
+ get repositoryCwd(): string;
68
77
  get gitBranch(): string | null;
69
78
  get pullRequest(): PiPullRequestIdentity | null;
70
79
  get sessionCommands(): PiSessionCommandIntegration | undefined;