@timurproko/a1 0.1.8-dev.332 → 0.1.8-dev.368

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 (110) hide show
  1. package/README.md +40 -11
  2. package/dist/composition/owned-ui.d.ts +2 -0
  3. package/dist/composition/owned-ui.js +20 -3
  4. package/dist/contracts/owned-ui/index.d.ts +2 -0
  5. package/dist/contracts/owned-ui/index.js +2 -0
  6. package/dist/contracts/owned-ui/model.d.ts +38 -3
  7. package/dist/contracts/owned-ui/prompt-history.d.ts +18 -0
  8. package/dist/contracts/owned-ui/prompt-suggestions.d.ts +1 -1
  9. package/dist/contracts/owned-ui/prompt-suggestions.js +4 -1
  10. package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +22 -0
  11. package/dist/contracts/owned-ui/suggestion-diagnostics.js +9 -0
  12. package/dist/contracts/owned-ui/transcript-lifecycle.d.ts +5 -0
  13. package/dist/contracts/owned-ui/transcript-lifecycle.js +33 -0
  14. package/dist/contracts/owned-ui/validation.js +45 -2
  15. package/dist/contracts/presentation/index.d.ts +8 -0
  16. package/dist/features/owned-ui/settings-app.js +1 -1
  17. package/dist/features/prompt-history/image-sidecar.d.ts +32 -0
  18. package/dist/features/prompt-history/image-sidecar.js +130 -0
  19. package/dist/features/prompt-history/index.d.ts +2 -0
  20. package/dist/features/prompt-history/index.js +1 -0
  21. package/dist/features/prompt-history/paths.d.ts +1 -0
  22. package/dist/features/prompt-history/paths.js +2 -1
  23. package/dist/features/prompt-history/store.d.ts +1 -1
  24. package/dist/features/prompt-history/store.js +46 -2
  25. package/dist/features/prompt-history/worker.js +1 -1
  26. package/dist/features/prompt-suggestions/diagnostics.d.ts +18 -0
  27. package/dist/features/prompt-suggestions/diagnostics.js +82 -0
  28. package/dist/features/prompt-suggestions/index.d.ts +1 -0
  29. package/dist/features/prompt-suggestions/index.js +1 -0
  30. package/dist/foundation/release/bootstrap.d.ts +2 -0
  31. package/dist/foundation/release/bootstrap.js +54 -18
  32. package/dist/foundation/release/index.d.ts +1 -0
  33. package/dist/foundation/release/index.js +1 -0
  34. package/dist/foundation/release/update-launch.d.ts +6 -0
  35. package/dist/foundation/release/update-launch.js +17 -0
  36. package/dist/foundation/release/update.d.ts +2 -0
  37. package/dist/foundation/release/update.js +20 -21
  38. package/dist/foundation/supervision/main.js +5 -2
  39. package/dist/foundation/supervision/server.js +40 -13
  40. package/dist/integrations/pi/components/owned-editor-ux.d.ts +9 -1
  41. package/dist/integrations/pi/components/owned-editor-ux.js +11 -8
  42. package/dist/integrations/pi/components/prompt-input-port.d.ts +18 -0
  43. package/dist/integrations/pi/components/prompt-input-port.js +1 -0
  44. package/dist/integrations/pi/components/shell-editor-autocomplete.js +66 -11
  45. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  46. package/dist/integrations/pi/components/shell-footer-status.js +9 -5
  47. package/dist/integrations/pi/components/shell-presenters-info.js +7 -2
  48. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +6 -2
  49. package/dist/integrations/pi/components/shell-presenters-transcript.js +81 -49
  50. package/dist/integrations/pi/components/shell-shared-facade.d.ts +17 -4
  51. package/dist/integrations/pi/components/shell-shared-facade.js +2 -1
  52. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +3 -1
  53. package/dist/integrations/pi/components/submitted-prompt-adapter.js +20 -4
  54. package/dist/integrations/pi/components/tool-result-adapter.d.ts +6 -0
  55. package/dist/integrations/pi/components/tool-result-adapter.js +27 -0
  56. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -0
  57. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +5 -0
  58. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -2
  59. package/dist/integrations/pi/components/upstream/components/owned-editor.js +41 -33
  60. package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
  61. package/dist/integrations/pi/components/upstream/components/session-footer.js +12 -7
  62. package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
  63. package/dist/integrations/pi/engine/adapter.d.ts +2 -1
  64. package/dist/integrations/pi/engine/adapter.js +181 -84
  65. package/dist/integrations/pi/engine/tool-rendering.d.ts +13 -0
  66. package/dist/integrations/pi/engine/tool-rendering.js +122 -0
  67. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +30 -0
  68. package/dist/integrations/pi/session-ui/prompt-chips.js +128 -1
  69. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +8 -0
  70. package/dist/integrations/pi/session-ui/prompt-history-controller.js +30 -2
  71. package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +11 -3
  72. package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +126 -51
  73. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +24 -2
  74. package/dist/integrations/pi/session-ui/session-shell-root.js +108 -38
  75. package/dist/integrations/pi/session-ui/session-shell.js +51 -31
  76. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +7 -1
  77. package/dist/integrations/pi/session-ui/session-viewport-controller.js +106 -16
  78. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
  79. package/dist/integrations/pi/tui-runtime/adapter.js +35 -11
  80. package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -1
  81. package/dist/integrations/pi/tui-runtime/index.d.ts +1 -0
  82. package/dist/integrations/pi/tui-runtime/index.js +1 -0
  83. package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +13 -0
  84. package/dist/integrations/pi/tui-runtime/mouse-report-input.js +59 -0
  85. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +32 -0
  86. package/dist/integrations/pi/tui-runtime/overlay-geometry.js +159 -0
  87. package/dist/native/darwin-arm64/manifest.json +1 -1
  88. package/dist/native/linux-x64/manifest.json +1 -1
  89. package/dist/native/win32-x64/manifest.json +2 -2
  90. package/dist/native/win32-x64/process-guardian.exe +0 -0
  91. package/dist/product-identity.d.ts +1 -1
  92. package/dist/product-identity.js +1 -1
  93. package/dist/product-identity.json +1 -0
  94. package/dist/ui/components/index.d.ts +1 -0
  95. package/dist/ui/components/index.js +1 -0
  96. package/dist/ui/components/line-input.d.ts +2 -3
  97. package/dist/ui/components/line-input.js +4 -20
  98. package/dist/ui/components/mouse.d.ts +1 -1
  99. package/dist/ui/components/mouse.js +4 -3
  100. package/dist/ui/components/prompt-input.d.ts +32 -0
  101. package/dist/ui/components/prompt-input.js +54 -0
  102. package/docs/architecture/boundaries.md +1 -0
  103. package/docs/architecture/history-editor-provenance.md +5 -1
  104. package/docs/architecture/project-structure.md +1 -0
  105. package/docs/architecture/prompt-suggestions.md +68 -0
  106. package/docs/architecture/toolchain.md +1 -1
  107. package/docs/architecture/ui-reference-provenance.md +23 -0
  108. package/docs/ci-release-runbook.md +45 -7
  109. package/docs/features/modal-content-interaction.md +37 -0
  110. package/package.json +1 -1
@@ -0,0 +1,159 @@
1
+ import { TuiAltScreen, isFocusable } from "#pi-tui";
2
+ /** Observes public component renders in compositor order, without accessing renderer state. */
3
+ export class OverlayGeometryTracker {
4
+ publish;
5
+ #frame = [];
6
+ #pending;
7
+ #columns = 0;
8
+ #rows = 0;
9
+ ports = new WeakMap();
10
+ constructor(publish) {
11
+ this.publish = publish;
12
+ }
13
+ invalidate() {
14
+ if (this.#frame === null)
15
+ return;
16
+ this.#frame = null;
17
+ this.publish(null);
18
+ }
19
+ begin(columns, rows) {
20
+ this.#columns = columns;
21
+ this.#rows = rows;
22
+ this.#pending = [];
23
+ }
24
+ record(component, options, width, height) {
25
+ if (this.#pending === undefined)
26
+ return;
27
+ const region = overlayPointerSurface(component, options, width, height, this.#columns, this.#rows);
28
+ if (region !== undefined)
29
+ this.#pending.push(region);
30
+ }
31
+ end() {
32
+ const next = this.#pending ?? [];
33
+ this.#pending = undefined;
34
+ if (this.#frame !== null && samePointerSurfaces(this.#frame, next))
35
+ return;
36
+ this.#frame = next;
37
+ this.publish(next);
38
+ }
39
+ }
40
+ /** The protected composition hook brackets the actual visible, focus-ordered overlay renders. */
41
+ export class GeometryObservedAltScreen extends TuiAltScreen {
42
+ geometry;
43
+ #overlayComponents = new WeakMap();
44
+ get overlayFocused() {
45
+ return this.isOverlayFocused();
46
+ }
47
+ setFocus(component) {
48
+ super.setFocus(component === null ? null : this.#overlayComponents.get(component) ?? component);
49
+ }
50
+ showOverlay(component, options) {
51
+ // Compatibility: all overlay entries cross the public method, including the runtime's search
52
+ // dialog. Observe a wrapper, never replace a method on an installed component.
53
+ const geometry = this.geometry;
54
+ const port = geometry?.ports.get(component) ?? component;
55
+ const wrapper = {
56
+ get focused() { return isFocusable(component) && component.focused; },
57
+ set focused(value) { if (isFocusable(component))
58
+ component.focused = value; },
59
+ get wantsKeyRelease() { return component.wantsKeyRelease ?? false; },
60
+ render(width) {
61
+ const lines = component.render(width);
62
+ geometry?.record(port, options, width, lines.length);
63
+ return lines;
64
+ },
65
+ invalidate: () => component.invalidate(),
66
+ handleInput: data => component.handleInput?.(data),
67
+ };
68
+ this.#overlayComponents.set(component, wrapper);
69
+ geometry?.invalidate();
70
+ const handle = super.showOverlay(wrapper, options);
71
+ return {
72
+ hide: () => { geometry?.invalidate(); handle.hide(); this.#overlayComponents.delete(component); },
73
+ setHidden: hidden => {
74
+ if (hidden !== handle.isHidden())
75
+ geometry?.invalidate();
76
+ handle.setHidden(hidden);
77
+ },
78
+ isHidden: () => handle.isHidden(),
79
+ focus: () => { geometry?.invalidate(); handle.focus(); },
80
+ unfocus: target => {
81
+ geometry?.invalidate();
82
+ if (target === undefined)
83
+ handle.unfocus();
84
+ else
85
+ handle.unfocus({ target: target.target === null ? null : this.#overlayComponents.get(target.target) ?? target.target });
86
+ },
87
+ isFocused: () => handle.isFocused(),
88
+ };
89
+ }
90
+ hideOverlay() {
91
+ this.geometry?.invalidate();
92
+ super.hideOverlay();
93
+ }
94
+ compositeOverlays(lines, width, height) {
95
+ this.geometry?.begin(width, height);
96
+ const result = super.compositeOverlays(lines, width, height);
97
+ this.geometry?.end();
98
+ return result;
99
+ }
100
+ }
101
+ /** Compare only geometry and ownership, never animation content or transcript position. */
102
+ export function samePointerSurfaces(a, b) {
103
+ return a.length === b.length && a.every((surface, index) => {
104
+ const other = b[index];
105
+ return surface.component === other.component && surface.columnStart === other.columnStart
106
+ && surface.columnEnd === other.columnEnd && surface.rowStart === other.rowStart && surface.rowEnd === other.rowEnd;
107
+ });
108
+ }
109
+ /**
110
+ * Resolve the pinned public overlay options against the actual render width/height.
111
+ * The runtime still owns layout and painting. Independent terminal-cell tests certify
112
+ * this option-to-hit-region projection against its compositor, including clipping.
113
+ */
114
+ export function overlayPointerSurface(component, options, width, renderedHeight, columns, rows) {
115
+ const opt = options ?? {};
116
+ const margin = typeof opt.margin === "number"
117
+ ? { top: opt.margin, right: opt.margin, bottom: opt.margin, left: opt.margin }
118
+ : opt.margin ?? {};
119
+ const top = Math.max(0, margin.top ?? 0);
120
+ const right = Math.max(0, margin.right ?? 0);
121
+ const bottom = Math.max(0, margin.bottom ?? 0);
122
+ const left = Math.max(0, margin.left ?? 0);
123
+ const availableHeight = Math.max(1, rows - top - bottom);
124
+ const availableWidth = Math.max(1, columns - left - right);
125
+ const limit = size(opt.maxHeight, rows);
126
+ const height = limit === undefined ? renderedHeight : Math.min(renderedHeight, Math.floor(Math.max(1, Math.min(limit, availableHeight))));
127
+ const anchor = opt.anchor ?? "center";
128
+ const anchorRow = anchor.startsWith("top-") ? top
129
+ : anchor.startsWith("bottom-") ? top + availableHeight - height
130
+ : top + Math.floor((availableHeight - height) / 2);
131
+ const anchorColumn = anchor.endsWith("-left") || anchor === "left-center" ? left
132
+ : anchor.endsWith("-right") || anchor === "right-center" ? left + availableWidth - width
133
+ : left + Math.floor((availableWidth - width) / 2);
134
+ const row = Math.max(top, Math.min(position(opt.row, top, availableHeight, height, anchorRow) + (opt.offsetY ?? 0), rows - bottom - height));
135
+ const column = Math.max(left, Math.min(position(opt.col, left, availableWidth, width, anchorColumn) + (opt.offsetX ?? 0), columns - right - width));
136
+ if (height <= 0 || row >= rows || column >= columns)
137
+ return undefined;
138
+ return {
139
+ component,
140
+ rowStart: row + 1,
141
+ rowEnd: Math.min(rows, row + height),
142
+ columnStart: column + 1,
143
+ columnEnd: Math.min(columns, column + width),
144
+ };
145
+ }
146
+ function size(value, extent) {
147
+ if (typeof value !== "string")
148
+ return value;
149
+ const match = /^(\d+(?:\.\d+)?)%$/.exec(value);
150
+ return match === null ? undefined : Math.floor(extent * Number(match[1]) / 100);
151
+ }
152
+ function position(value, margin, available, extent, anchor) {
153
+ if (value === undefined)
154
+ return anchor;
155
+ if (typeof value === "number")
156
+ return value;
157
+ const percent = size(value, Math.max(0, available - extent));
158
+ return percent === undefined ? margin + Math.floor((available - extent) / 2) : margin + percent;
159
+ }
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-12T13:59:03.140Z",
8
+ "builtAt": "2026-09-13T13:11:55.967Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-12T13:58:54.058Z",
8
+ "builtAt": "2026-09-13T13:12:46.201Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-12T13:59:34.009Z",
8
+ "builtAt": "2026-09-13T13:12:19.326Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "15e00144629345d7f00b110494775b80c65e6e575e6758d88b533c25b57d35d5",
11
+ "sha256": "e6a7be98f08249e90a59466be1c132959a55a80830b000a660724da4d572887a",
12
12
  "size": 177664
13
13
  },
14
14
  "provenance": {
@@ -1,4 +1,4 @@
1
- declare const ENVIRONMENT_KEYS: readonly ["configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "terminalSessionId"];
1
+ declare const ENVIRONMENT_KEYS: readonly ["configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "suggestionDiagnostics", "terminalSessionId"];
2
2
  declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
3
3
  declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
4
4
  declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
@@ -1,7 +1,7 @@
1
1
  import rawIdentity from "./product-identity.json" with { type: "json" };
2
2
  const ENVIRONMENT_KEYS = [
3
3
  "configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint",
4
- "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "terminalSessionId",
4
+ "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "suggestionDiagnostics", "terminalSessionId",
5
5
  ];
6
6
  const FILESYSTEM_KEYS = ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
7
7
  const STATE_KEYS = ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
@@ -22,6 +22,7 @@
22
22
  "profileHome": "A1_PROFILE_HOME",
23
23
  "runtimeDir": "A1_RUNTIME_DIR",
24
24
  "startupTrace": "A1_STARTUP_TRACE",
25
+ "suggestionDiagnostics": "A1_SUGGESTION_DIAGNOSTICS",
25
26
  "terminalSessionId": "A1_TERMINAL_SESSION_ID"
26
27
  },
27
28
  "state": {
@@ -2,6 +2,7 @@ export * from "./dialog-panel.js";
2
2
  export * from "./frame.js";
3
3
  export * from "./label.js";
4
4
  export * from "./line-input.js";
5
+ export * from "./prompt-input.js";
5
6
  export * from "./list-block.js";
6
7
  export * from "./list-view.js";
7
8
  export * from "./mouse.js";
@@ -2,6 +2,7 @@ export * from "./dialog-panel.js";
2
2
  export * from "./frame.js";
3
3
  export * from "./label.js";
4
4
  export * from "./line-input.js";
5
+ export * from "./prompt-input.js";
5
6
  export * from "./list-block.js";
6
7
  export * from "./list-view.js";
7
8
  export * from "./mouse.js";
@@ -1,3 +1,4 @@
1
+ import type { UiTheme } from "./theme.js";
1
2
  export type LineInputOutcome = {
2
3
  readonly kind: "editing";
3
4
  } | {
@@ -47,9 +48,6 @@ export declare class LineInput {
47
48
  }
48
49
  /** Applies one key to the input and reports whether the caller should commit. */
49
50
  export declare function handleLineInputKey(input: LineInput, data: string): LineInputOutcome;
50
- /** Draws the reference prompt rule foreground-only so underlying cells survive. */
51
- export declare function promptRule(width: number): string;
52
- export declare const PROMPT_GLYPH = "\u001B[38;2;154;160;166m\u276F\u001B[39m ";
53
51
  /**
54
52
  * The caret the reference draws: the cell under it is reversed rather than given
55
53
  * a colour of its own, so it reads as a block in whatever theme is in use.
@@ -60,6 +58,7 @@ export interface InputRowOptions {
60
58
  readonly placeholder?: string;
61
59
  /** Rules above and below, in the prompt's own grey. Default true. */
62
60
  readonly ruled?: boolean;
61
+ readonly theme?: Pick<UiTheme, "fg">;
63
62
  }
64
63
  export interface InputRow {
65
64
  /** The rows to draw, already padded to the width. */
@@ -1,4 +1,5 @@
1
1
  import { displayWidth, faint, truncateToWidth } from "./text.js";
2
+ import { PromptInput } from "./prompt-input.js";
2
3
  const MAX_VALUE_LENGTH = 4_096;
3
4
  const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
4
5
  /** Character class for a word boundary: 0 space, 1 word, 2 anything else. */
@@ -198,11 +199,6 @@ export function handleLineInputKey(input, data) {
198
199
  input.insert(data);
199
200
  return { kind: "editing" };
200
201
  }
201
- /** Draws the reference prompt rule foreground-only so underlying cells survive. */
202
- export function promptRule(width) {
203
- return `\u001b[38;2;154;160;166m${"─".repeat(Math.max(0, width))}\u001b[39m`;
204
- }
205
- export const PROMPT_GLYPH = `\u001b[38;2;154;160;166m❯\u001b[39m `;
206
202
  /**
207
203
  * The caret the reference draws: the cell under it is reversed rather than given
208
204
  * a colour of its own, so it reads as a block in whatever theme is in use.
@@ -212,8 +208,8 @@ export function caretCell(text) {
212
208
  }
213
209
  /** The input row as the reference draws one, padded to exactly the width. */
214
210
  export function renderInputRow(input, width, options = {}) {
215
- const inner = Math.max(0, width - 2);
216
- const view = input.view(inner);
211
+ const presentation = new PromptInput(options.theme);
212
+ const view = input.view(Math.max(0, width - presentation.geometry(width).prefixWidth));
217
213
  const placeholder = options.placeholder ?? "";
218
214
  const empty = view.text.length === 0 && placeholder.length > 0;
219
215
  const before = view.text.slice(0, view.caretColumn);
@@ -224,17 +220,5 @@ export function renderInputRow(input, width, options = {}) {
224
220
  const body = empty
225
221
  ? `${caretCell(placeholder.slice(0, 1))}${faint(placeholder.slice(1))}`
226
222
  : `${before}${caretCell(under)}${after}`;
227
- const plain = empty
228
- ? placeholder
229
- : `${view.text}${view.caretColumn >= view.text.length ? " " : ""}`;
230
- const row = padVisible(truncateToWidth(`${PROMPT_GLYPH}${body}`, width), width, `❯ ${plain}`);
231
- if (options.ruled === false)
232
- return { lines: [row] };
233
- const rule = promptRule(width);
234
- return { lines: [rule, row, rule] };
235
- }
236
- /** Pads by visible width, so styling escapes do not shift the layout. */
237
- function padVisible(line, width, raw) {
238
- const visible = displayWidth(raw);
239
- return visible >= width ? line : line + " ".repeat(width - visible);
223
+ return { lines: presentation.render(width, () => ({ rows: [body] }), options.ruled) };
240
224
  }
@@ -22,4 +22,4 @@ export interface RoutedMouseInput {
22
22
  * Routes every SGR report independently, preserving keyboard bytes and mouse
23
23
  * reports the caller does not claim even when they share one physical chunk.
24
24
  */
25
- export declare function routeMouseInput(data: string, claim: (event: PaneMouseEvent) => boolean): RoutedMouseInput;
25
+ export declare function routeMouseInput(data: string, claim: (event: PaneMouseEvent, report: string) => boolean): RoutedMouseInput;
@@ -40,12 +40,13 @@ export function routeMouseInput(data, claim) {
40
40
  let output = "";
41
41
  let index = 0;
42
42
  let consumed = false;
43
- SGR_PATTERN.lastIndex = 0;
44
- for (let match = SGR_PATTERN.exec(data); match !== null; match = SGR_PATTERN.exec(data)) {
43
+ // Concurrency: reentrant routing may deliver a report to another component using this parser.
44
+ const pattern = new RegExp(SGR_PATTERN.source, "g");
45
+ for (let match = pattern.exec(data); match !== null; match = pattern.exec(data)) {
45
46
  output += data.slice(index, match.index);
46
47
  index = match.index + match[0].length;
47
48
  const event = toEvent(Number.parseInt(match[1] ?? "", 10), Number.parseInt(match[2] ?? "", 10), Number.parseInt(match[3] ?? "", 10), match[4] === "m");
48
- if (event !== null && claim(event))
49
+ if (event !== null && claim(event, match[0]))
49
50
  consumed = true;
50
51
  else
51
52
  output += match[0];
@@ -0,0 +1,32 @@
1
+ import { type UiTheme } from "./theme.js";
2
+ export declare const PROMPT_GLYPH = "\u276F ";
3
+ export declare const PROMPT_PREFIX_WIDTH = 2;
4
+ export declare function promptRuleText(text: string): string;
5
+ export declare function promptRule(width: number): string;
6
+ export declare function promptArrow(text: string, theme: Pick<UiTheme, "fg">): string;
7
+ export interface PromptInputBody {
8
+ readonly rows: readonly string[];
9
+ /** Semantic border fragments retain editor history and scroll annotations. */
10
+ readonly topRule?: string | undefined;
11
+ readonly bottomRule?: string | undefined;
12
+ readonly after?: readonly string[];
13
+ }
14
+ export interface PromptInputMetrics {
15
+ readonly measure: (text: string) => number;
16
+ readonly truncate: (text: string, width: number) => string;
17
+ }
18
+ /** Composes shared input chrome around controller-owned body rows without owning editing state. */
19
+ export declare class PromptInput {
20
+ readonly theme: Pick<UiTheme, "fg">;
21
+ readonly metrics: PromptInputMetrics;
22
+ constructor(theme?: Pick<UiTheme, "fg">, metrics?: PromptInputMetrics);
23
+ styleRule(text: string): string;
24
+ geometry(width: number, padding?: number): {
25
+ prefixWidth: number;
26
+ innerWidth: number;
27
+ paddingX: number;
28
+ contentWidth: number;
29
+ layoutWidth: number;
30
+ };
31
+ render(width: number, renderBody: (innerWidth: number) => PromptInputBody, ruled?: boolean, padding?: number): string[];
32
+ }
@@ -0,0 +1,54 @@
1
+ import { displayWidth, truncateToWidth } from "./text.js";
2
+ import { PLAIN_THEME } from "./theme.js";
3
+ export const PROMPT_GLYPH = "❯ ";
4
+ export const PROMPT_PREFIX_WIDTH = 2;
5
+ export function promptRuleText(text) {
6
+ return `\u001b[38;2;154;160;166m${text}\u001b[39m`;
7
+ }
8
+ export function promptRule(width) {
9
+ return promptRuleText("─".repeat(Math.max(0, width)));
10
+ }
11
+ export function promptArrow(text, theme) {
12
+ return theme.fg("muted", text);
13
+ }
14
+ /** Composes shared input chrome around controller-owned body rows without owning editing state. */
15
+ export class PromptInput {
16
+ theme;
17
+ metrics;
18
+ constructor(theme = PLAIN_THEME, metrics = { measure: displayWidth, truncate: truncateToWidth }) {
19
+ this.theme = theme;
20
+ this.metrics = metrics;
21
+ }
22
+ styleRule(text) { return promptRuleText(text); }
23
+ geometry(width, padding = 0) {
24
+ const prefixWidth = Math.min(PROMPT_PREFIX_WIDTH, Math.max(0, width));
25
+ // Compatibility: editor wrapping needs room for a two-cell grapheme plus its cursor cell.
26
+ // Lay out against a safe virtual width, then clip only at the shared frame boundary.
27
+ const innerWidth = Math.max(3 + padding * 2, width - prefixWidth);
28
+ const paddingX = Math.min(padding, Math.max(0, Math.floor((innerWidth - 1) / 2)));
29
+ const contentWidth = Math.max(1, innerWidth - paddingX * 2);
30
+ return { prefixWidth, innerWidth, paddingX, contentWidth, layoutWidth: Math.max(1, contentWidth - (paddingX ? 0 : 1)) };
31
+ }
32
+ render(width, renderBody, ruled = true, padding = 0) {
33
+ if (width <= 0)
34
+ return [];
35
+ const { prefixWidth, innerWidth } = this.geometry(width, padding);
36
+ const body = renderBody(innerWidth);
37
+ const fit = (row) => {
38
+ const clipped = this.metrics.truncate(row, width);
39
+ return clipped + " ".repeat(Math.max(0, width - this.metrics.measure(clipped)));
40
+ };
41
+ const rows = body.rows.map((row, index) => fit(`${index === 0
42
+ ? promptArrow(PROMPT_GLYPH, this.theme)
43
+ : " ".repeat(prefixWidth)}${row}`));
44
+ const rule = (fragment) => fragment === undefined
45
+ ? promptRule(width)
46
+ : fit(fragment + promptRule(Math.max(0, width - this.metrics.measure(fragment))));
47
+ return [
48
+ ...(ruled ? [rule(body.topRule)] : []),
49
+ ...rows,
50
+ ...(ruled ? [rule(body.bottomRule)] : []),
51
+ ...(body.after ?? []).map(row => fit(`${" ".repeat(prefixWidth)}${row}`)),
52
+ ];
53
+ }
54
+ }
@@ -30,6 +30,7 @@ The owned Pi-backed surface is not an arbitrary-CLI terminal multiplexer. A feat
30
30
  - `protocol`: additive control handshake, bounded line framing, authenticated launch-instance commands, typed stop intent, snapshots, and command results.
31
31
  - `launch`: resolves the effective profile home and the history-specific data root. Composition uses its public path resolver to select `~/.a1/data` for history unless `A1_DATA_DIR` is explicit; global control/release/runtime/cache paths stay unchanged.
32
32
  - `prompt-history`: narrowly typed user-input retention in separate profile-owned databases under `~/.a1/data/history` (or `<A1_DATA_DIR>/history`), with bounded worker queues and no supervisor/control-store payloads. It receives a resolved root rather than importing launch-feature internals; it never probes, imports, or deletes the former platform-default history. The editor's owned history state machine remains inside the Pi component boundary; the session UI only coordinates snapshots and classified submissions.
33
+ - `prompt-suggestions`: opt-in bounded diagnostic snapshots of neutral suggestion lifecycle metadata. Composition supplies an explicit local destination; this feature never imports Pi, generates suggestions, stores conversation text, or owns the editor. See [prompt suggestion diagnostics](prompt-suggestions.md).
33
34
  - `storage`: SQLite migrations, prior-boot reconciliation, and plural launch-instance persistence. Legacy foreground rows are historical migration input and never authorize current ownership.
34
35
  - `supervisor`: endpoint identity, plural cohort ownership, per-instance reconciliation, and aggregate release shutdown coordination. It owns no terminal surface.
35
36
  - `pi-engine-adapter`, `pi-component-adapter`, `pi-tui-runtime-adapter`, and `pi-session-ui-integration`: isolate pinned Pi engine and presentation knowledge behind neutral contracts; product features do not import them directly. The session UI render root assembles semantic document and dock rows, while its focused viewport controller owns follow state, pointer routing, selection, and interaction timers. Owned-app route lifecycle belongs to the neutral `ui-apps` owner and is only hosted by the Pi session UI.
@@ -39,7 +39,11 @@ snapshot installation and observation methods preserve input and undo state,
39
39
  freeze an active browse cycle, and change caret placement only in that mode.
40
40
  The editor renders position/overflow in its existing border. The position label
41
41
  uses an injected neutral status-text style (`dim`), while the surrounding rules
42
- retain the active input-border color, as clarified during manual review. Recall temporarily
42
+ retain the active input-border color, as clarified during manual review. The approved
43
+ `show-autocomplete-above-prompt` review refinement omits the literal `History` title:
44
+ `─── 1/100 ─…` keeps the same four-cell inset, count calculation, dim color, optional
45
+ scroll-overflow suffix, and clipping. Core and shell regressions verify numbering,
46
+ 100-entry recall, draft restoration, and the absence of the title. Recall temporarily
43
47
  separates the draft's live paste backing from recalled literal text, restoring it
44
48
  on return; ordinary pinned mode remains source-equivalent.
45
49
 
@@ -14,6 +14,7 @@ src/
14
14
  launch/ launch profiles, profile paths, and runtime selection
15
15
  owned-ui/ owned screens, settings application, diagnostics, and runtime lifecycle
16
16
  prompt-history/ profile-isolated prompt retention and bounded SQLite worker lifecycle
17
+ prompt-suggestions/ opt-in bounded metadata capture and local diagnostic snapshots
17
18
  workspace/ multi-agent presentation, reducer state, routing, and persistence orchestration
18
19
  contracts/
19
20
  agent-engine/ dependency-free agent engine, session, package, and capability ports
@@ -0,0 +1,68 @@
1
+ # Prompt suggestion reliability
2
+
3
+ Bare A1 predicts a short next user input with one isolated request using the selected model. A clear offered action consistent with recent user intent, such as `archive it` after accepted merged work, is preferred over abstaining merely because optional testing was also offered. Required validation, conflicting intent, and unresolved choices still allow no suggestion. There is no quoted-text extraction fallback.
4
+
5
+ ## Request and editor boundaries
6
+
7
+ The adapter resolves the lowest supported effort from the selected session model's available thinking levels, without changing the main thinking setting. Non-reasoning models omit reasoning controls; supported `off` also omits the optional reasoning argument, matching the model runtime's ordinary completion contract. Other supported efforts are supplied explicitly. Missing capabilities produce `unavailable`, not a guessed provider option. Provider mapping/clamping remains owned by the pinned model runtime; the diagnostic policy describes the applied request option, not inferred server-side computation.
8
+
9
+ The controller retains the 15-second deadline, does not retry or switch models, and retires a timeout before aborting. A provider that ignores abort cannot revive retired text. A timely candidate remains private until matching settlement. Ghost text is not editor content, approval, or a submission: Tab accepts it into the editor, and Enter must be pressed separately to submit it. Typing, session/model replacement, continuation, or disabling suggestions invalidates unaccepted work. `a1 pi` does not install this feature.
10
+
11
+ ## Opt-in local diagnostics
12
+
13
+ Set `A1_SUGGESTION_DIAGNOSTICS` to a **dedicated writable file** when launching bare A1. It is an explicit snapshot destination, not a log directory. The parent directory must already exist. The selected file is overwritten with the latest bounded snapshot; never select a session, source, credential, or other valuable file. Do not share one destination between running processes.
14
+
15
+ For a repository checkout in Git Bash:
16
+
17
+ ```sh
18
+ npm run build && A1_SUGGESTION_DIAGNOSTICS="$PWD/.artifacts/suggestion-diagnostics.json" ./scripts/dev
19
+ ```
20
+
21
+ Create `.artifacts` first if it does not exist. For an installed release, use the same environment assignment before `a1`. The variable follows the ordinary launch environment; it adds no interactive flag, setting, footer row, or transcript message. SDK composition can supply `suggestionDiagnosticsPath` explicitly. Comparison or settings-free compositions ignore both paths.
22
+
23
+ While that session is running, inspect the selected JSON file using a local editor or:
24
+
25
+ ```sh
26
+ node -e 'const fs=require("node:fs"); console.table(JSON.parse(fs.readFileSync(process.argv[1],"utf8")).records)' .artifacts/suggestion-diagnostics.json
27
+ ```
28
+
29
+ The `prompt-suggestion-diagnostics-v1` snapshot contains at most 128 metadata records and 64 KiB. Capture is off by default. Records include process-local session/run/response/request numbers, bounded provider/model identifiers, applied reasoning policy, elapsed milliseconds, event, and optional reason. They exclude conversation/candidate text, tool data, raw errors, credentials, and session/worktree paths. There is no upload. Older records are evicted, so a retained terminal event can outlive its evicted start event. `request: 0` means no request was started for that decision.
30
+
31
+ | Event | Meaning |
32
+ | --- | --- |
33
+ | `skipped` | No request started; inspect `reason`. |
34
+ | `started` | One generator request was initiated; reasoning may be `unavailable` for an injected generator without policy metadata. |
35
+ | `displayed` | A current candidate was shown after settlement. |
36
+ | `empty` | The provider returned no text; this does not reveal the model's private reason for abstaining. |
37
+ | `rejected` | Returned content violated the candidate contract, contained tool calls, was truncated, or had invalid formatting/voice. |
38
+ | `provider-failure` | Provider error/rejection; raw error details are intentionally omitted. |
39
+ | `unavailable` | Model/runtime/capabilities or request identity were unavailable at the generator boundary. |
40
+ | `timeout` | The controller's 15-second deadline expired. |
41
+ | `cancelled` | Work was invalidated or aborted, including typing or continuation. |
42
+ | `stale-result` | A result or settlement identity did not match its request. |
43
+ | `presentation-blocked` | A valid candidate could not be shown; inspect `reason`. |
44
+ | `late-result-discarded` | A retired request eventually resolved/rejected; this is not a second terminal outcome or request. |
45
+
46
+ Eligibility/presentation reasons include `disabled`, `disposed`, `early-conversation`, `no-model`, `failed-response`, `incomplete-response`, `tool-continuation`, `replacement-input`, `modal`, `draft`, `not-ready`, `not-focused`, `autocomplete`, `prompt-mode`, and `stale-identity`. `ineligible` and `presentation-unavailable` cover injected ports without finer metadata. A missing suggestion alone is never evidence of timeout or intentional model abstention.
47
+
48
+ Writes are asynchronous and coalesced: at most one write and one pending latest snapshot. Sink failures do not affect generation, input, or rendering and do not appear in the UI. An unwritable destination may therefore produce no file; check the destination locally rather than diagnosing the model from that absence. Disposal clears in-memory capture and pending writes; a write already in progress can finish. Exported local files remain until you remove them.
49
+
50
+ To disable capture, omit/unset `A1_SUGGESTION_DIAGNOSTICS` before the next launch and remove the dedicated snapshot if no longer needed. This does not disable suggestions; `/settings` -> Agent -> Prompt suggestions controls generation independently.
51
+
52
+ ## Verification and evidence limits
53
+
54
+ The focused tests cover the synthetic archive offer and required-testing/unresolved-choice counterexamples, unchanged primary thinking, typed outcomes, timeout/cancellation races, editor acceptance, and local privacy/bounds:
55
+
56
+ ```sh
57
+ npx vitest run test/integrations/pi/session-ui/prompt-suggestion-controller.test.ts test/integrations/pi/engine/adapter.test.ts test/features/prompt-suggestions/diagnostics.test.ts test/contracts/owned-ui/contracts.test.ts test/integrations/pi/session-ui/session-shell.test.ts
58
+ ```
59
+
60
+ A separate opt-in probe uses the profile's saved model/authentication with a scratch cwd, in-memory sessions/settings, disabled extensions/skills/prompts, and no tools. It performs five original-policy predictions, five revised-policy predictions, and one required-testing counterexample. This makes **11 provider requests** and can consume quota. Run only with explicit authorization:
61
+
62
+ ```sh
63
+ RUN_PROMPT_SUGGESTION_PROVIDER_TEST=1 PROMPT_SUGGESTION_AGENT_DIR="C:/path/to/agent-profile" npx vitest run test/integrations/pi/engine/prompt-suggestion-provider.integration.test.ts
64
+ ```
65
+
66
+ The probe reports outcome/latency summaries and archival-candidate counts, not raw text. It requires at least one revised archival continuation and rejects an archival continuation in the required-testing case. Five samples are a smoke comparison, not a statistical reliability guarantee. Missing credentials, model availability, or a successful real archival prediction leave provider acceptance pending; fake tests do not prove model quality.
67
+
68
+ For manual review, use a harmless synthetic conversation with two assistant responses: establish that an imaginary change was tested, accepted, and merged, then ask for a closeout offer containing `Say archive it` with optional extra testing. Do not ask the agent to perform real archival to test ghost text. Keep main thinking high and the editor untouched while waiting, inspect the outcome snapshot, check Tab/Enter separately, and repeat with typing cancellation and suggestions disabled. Verify required testing is not treated as optional. Real latency, model abstention, and existing context-reconstruction limitations can still leave the editor empty.
@@ -71,7 +71,7 @@ Presentation acceptance is the reader comparing `a1 pi` with pinned Pi. `node sc
71
71
 
72
72
  ## Publication
73
73
 
74
- One workflow publishes both channels: `.github/workflows/release.yml`. Pushes do not publish. Nightly development verification runs at `03:17 UTC`; `npm run develop` explicitly requests a numbered preview and `npm run release` explicitly requests stable publication after its version pull request merges. A preview is stamped as `-dev.<merged pull-request number>` and uses npm `next` only as an internal dist-tag. New candidates pack once and are validated on Windows, Linux, and macOS; a repeated nightly verifies the exact immutable registry tarball. All publication uses provenance from the `npm-publish` environment, a preview never changes `latest`, and the stable tag, GitHub Release, and `master` are written only after npm has the package. One global non-cancelling concurrency group serializes the final registry check.
74
+ One workflow publishes both channels: `.github/workflows/release.yml`. Pushes do not publish. Nightly development verification runs at `03:17 UTC`; `npm run develop` explicitly requests a numbered preview and `npm run release -- patch` explicitly requests stable publication after its version pull request is manually merged; it promotes a development version to its stable core. The following development-version pull request also requires manual merge. A preview is stamped as `-dev.<merged pull-request number>` and uses npm `next` only as an internal dist-tag. New candidates pack once and are validated on Windows, Linux, and macOS; a repeated nightly verifies the exact immutable registry tarball. All publication uses provenance from the `npm-publish` environment, a preview never changes `latest`, and the stable tag, GitHub Release, and `master` are written only after npm has the package. One global non-cancelling concurrency group serializes the final registry check.
75
75
 
76
76
  `docs/ci-release-runbook.md` is the operational reference.
77
77
 
@@ -47,6 +47,29 @@ its own `core` facade layer; A1 is a product, so the port adapts imports and kee
47
47
 
48
48
  ## Deliberate differences
49
49
 
50
+ - **Above-prompt autocomplete.** The accepted `show-autocomplete-above-prompt` change
51
+ moves the existing default-editor completion block above the prompt in bare A1.
52
+ `src/integrations/pi/components/upstream/components/owned-editor.ts` exposes its
53
+ border-inclusive body height using the existing atomic-aware layout boundary;
54
+ `shell-editor-autocomplete.ts` moves rows only after prefix/selection decoration
55
+ and adds one top line using the editor's current border-color function.
56
+ The accepted refinement (#343) restores this line instead of panel shading.
57
+ The user-approved review refinement in #344 moves the existing trailing counter
58
+ into that line without parentheses, at the history-label inset and in the same dim
59
+ color. The select-list `scrollInfo` callback identifies its final counter row;
60
+ private selection state and rendered-frame parsing are not used. Counter meaning
61
+ and visibility remain unchanged, and candidate ANSI styling/padding stay intact. The line is
62
+ included in the body offset, disappears with the menu, and is non-text chrome.
63
+ Theme/mode/resize tests verify matching line color and width; terminal background
64
+ replay verifies the menu has no added shading.
65
+ The session shell uses the resulting body offset for pointer routing. Neither
66
+ editor implementation, history mode, menu sizing, completion state, nor installed
67
+ Pi package changes. `a1 pi` remains below-prompt and byte-identical to the independently
68
+ run pinned editor in `test/integrations/pi/components/pinned-editor-input-parity.test.ts`.
69
+ Placement and whole-menu ANSI evidence is in `editor-autocomplete-placement.test.ts`;
70
+ shell checkpoint replay covers final cells, cursor rows, resizing, detached/streaming
71
+ transcripts, widgets, pointer selection, and extension-editor restoration. Physical
72
+ acceptance of the exact build remains required before merging the implementation.
50
73
  - **Owned in-session routes are overlays.** The A1 UI reference owns its surface and can switch screens; A1 renders in-session owned routes through the pinned Pi TUI as full-viewport overlays. The pre-resource trust selector is separate and uses a bounded alternate startup surface solely so every completion path can restore the untouched parent terminal before engine activation or a fail-closed diagnostic.
51
74
  - **Colour is a port, not an import.** The reference takes a Pi `Theme` directly. A1 defines
52
75
  `UiTheme` so the component layer never imports a Pi adapter and can be rendered plainly in