@timurproko/a1 0.1.8-dev.572 → 0.1.8-dev.573

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 (49) hide show
  1. package/dist/app/session-shell/prompt-chips.js +20 -28
  2. package/dist/app/session-shell/session-shell-root.d.ts +2 -0
  3. package/dist/app/session-shell/session-shell-root.js +54 -14
  4. package/dist/app/session-shell/session-shell.js +30 -2
  5. package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
  6. package/dist/app/session-shell/session-viewport-controller.js +5 -1
  7. package/dist/contracts/owned-ui/index.d.ts +2 -0
  8. package/dist/contracts/owned-ui/index.js +1 -0
  9. package/dist/contracts/owned-ui/prompt-chips.d.ts +15 -0
  10. package/dist/contracts/owned-ui/prompt-chips.js +59 -0
  11. package/dist/contracts/owned-ui/validation.js +1 -1
  12. package/dist/integrations/pi/components/index.d.ts +1 -0
  13. package/dist/integrations/pi/components/index.js +1 -0
  14. package/dist/integrations/pi/components/modal-frame.d.ts +41 -0
  15. package/dist/integrations/pi/components/modal-frame.js +110 -0
  16. package/dist/integrations/pi/components/models-dialog.js +19 -5
  17. package/dist/integrations/pi/components/shell-selectors-dialogs.js +38 -2
  18. package/dist/integrations/pi/components/skills-dialog.js +3 -3
  19. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +1 -1
  20. package/dist/integrations/pi/components/submitted-prompt-adapter.js +5 -2
  21. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -1
  22. package/dist/integrations/pi/components/upstream/components/extension-editor.js +7 -6
  23. package/dist/integrations/pi/components/upstream/components/extension-input.js +9 -8
  24. package/dist/integrations/pi/components/upstream/components/extension-selector.js +10 -9
  25. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  26. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
  27. package/dist/integrations/pi/components/upstream/components/session-selector.js +13 -6
  28. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
  29. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +15 -12
  30. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  31. package/dist/integrations/pi/components/upstream/components/tree-selector.js +13 -6
  32. package/dist/integrations/pi/components/upstream/components/trust-selector.js +9 -9
  33. package/dist/integrations/pi/engine/compaction-progress.d.ts +2 -6
  34. package/dist/integrations/pi/engine/compaction-progress.js +10 -10
  35. package/dist/integrations/pi/engine/settings-bridge.js +1 -1
  36. package/dist/integrations/pi/engine/settings-port.js +1 -1
  37. package/dist/integrations/pi/tui-runtime/adapter.js +6 -2
  38. package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -0
  39. package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +6 -0
  40. package/dist/integrations/pi/tui-runtime/mouse-report-input.js +34 -0
  41. package/dist/native/darwin-arm64/manifest.json +1 -1
  42. package/dist/native/linux-x64/manifest.json +1 -1
  43. package/dist/native/win32-x64/manifest.json +2 -2
  44. package/dist/native/win32-x64/process-guardian.exe +0 -0
  45. package/dist/ui/components/dialog-panel.js +10 -6
  46. package/dist/ui/components/mouse.d.ts +2 -2
  47. package/dist/ui/components/mouse.js +2 -2
  48. package/dist/ui/components/transcript-viewport.js +35 -13
  49. package/package.json +1 -1
@@ -3,15 +3,17 @@
3
3
  * packages/coding-agent/src/modes/interactive/components/thinking-selector.ts.
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
- * 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 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.
6
+ * shell, styling the title with the established bold semantic accent treatment, placing it directly
7
+ * below the top rule through the shared compact padded modal frame and its muted hint directly below
8
+ * it, deduplicating levels, and rendering aligned muted descriptions after adjacent active and
9
+ * bracketed default markers. All list and border colors use the owned theme and its explicit color
10
+ * mode, and the footer uses the shared bare-A1 modal shortcut row. The comparison profile retains the
11
+ * public pinned component.
11
12
  * Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading.
12
13
  */
13
14
  import { Container, fuzzyFilter, getKeybindings, Input, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
14
15
  import { DynamicBorder } from "../../../startup-public.js";
16
+ import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
15
17
  import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
16
18
  const THINKING_SELECT_LIST_LAYOUT = {
17
19
  minPrimaryColumnWidth: 12,
@@ -30,7 +32,7 @@ const LEVEL_DESCRIPTIONS = {
30
32
  export class OwnedThinkingSelectorComponent extends Container {
31
33
  searchInput;
32
34
  selectList;
33
- selectListChildIndex;
35
+ selectListContainer;
34
36
  allItems;
35
37
  onSelect;
36
38
  onCancel;
@@ -57,9 +59,7 @@ export class OwnedThinkingSelectorComponent extends Container {
57
59
  label: level,
58
60
  description: LEVEL_DESCRIPTIONS[level],
59
61
  }));
60
- this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
61
- this.addChild(new Spacer(1));
62
- this.addChild(new Text(piTheme().fg("accent", piTheme().bold("Thinking Level")), 0, 0));
62
+ const header = addPiModalHeader(this, new DynamicBorder((text) => piTheme().fg("border", text)), new Text(piTheme().fg("accent", piTheme().bold("Thinking Level")), 0, 0));
63
63
  this.addChild(new Text(piTheme().fg("muted", `${cycleKeyDisplay} cycles thinking levels in-session`), 0, 0));
64
64
  this.addChild(new Spacer(1));
65
65
  this.searchInput = new Input();
@@ -67,8 +67,9 @@ export class OwnedThinkingSelectorComponent extends Container {
67
67
  this.addChild(this.searchInput);
68
68
  this.addChild(new Spacer(1));
69
69
  this.selectList = this.buildSelectList(this.allItems, currentLevel);
70
- this.selectListChildIndex = this.children.length;
71
- this.addChild(this.selectList);
70
+ this.selectListContainer = new Container();
71
+ this.selectListContainer.addChild(this.selectList);
72
+ this.addChild(this.selectListContainer);
72
73
  this.addChild(new Spacer(1));
73
74
  this.addChild(new Text(renderPiModalShortcutHints([
74
75
  { key: this.keyDisplayText("tui.select.confirm"), action: "to select" },
@@ -76,6 +77,7 @@ export class OwnedThinkingSelectorComponent extends Container {
76
77
  { key: this.keyDisplayText("tui.select.cancel"), action: "to cancel" },
77
78
  ]), 0, 0));
78
79
  this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
80
+ adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
79
81
  }
80
82
  keyDisplayText(keybinding) {
81
83
  return getKeybindings().getKeys(keybinding)
@@ -122,7 +124,8 @@ export class OwnedThinkingSelectorComponent extends Container {
122
124
  : this.allItems;
123
125
  const selectedValue = this.selectList.getSelectedItem()?.value;
124
126
  const newList = this.buildSelectList(filtered, selectedValue);
125
- this.children[this.selectListChildIndex] = newList;
127
+ this.selectListContainer.clear();
128
+ this.selectListContainer.addChild(newList);
126
129
  this.selectList = newList;
127
130
  }
128
131
  handleInput(keyData) {
@@ -4,7 +4,7 @@
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
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.
7
+ * bare A1 uses the shared semantic modal shortcut row and compact padded modal frame.
8
8
  * Deviations: owned-modal-shortcut-hints.
9
9
  */
10
10
  import { type Component, Container, type Focusable } from "@earendil-works/pi-tui";
@@ -4,12 +4,13 @@
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
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.
7
+ * bare A1 uses the shared semantic modal shortcut row and compact padded modal frame.
8
8
  * Deviations: owned-modal-shortcut-hints.
9
9
  */
10
10
  import { Container, Input, Spacer, sliceByColumn, Text, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "@earendil-works/pi-tui";
11
11
  import { DynamicBorder } from "../../../startup-public.js";
12
12
  import { KeybindingsManager } from "../adjacent/core/keybindings.js";
13
+ import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
13
14
  import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
14
15
  const theme = new Proxy({}, {
15
16
  get(_target, property) {
@@ -1080,8 +1081,8 @@ class TreeHelp {
1080
1081
  return renderPiModalShortcutHints([key ? { key, action: label, ...(labelFirst === undefined ? {} : { actionFirst: labelFirst }) } : { action: label }]);
1081
1082
  });
1082
1083
  const availableWidth = Math.max(1, width);
1083
- // Match the title's Text padding plus its two-cell authored inset.
1084
- const indent = " ";
1084
+ // The frame supplies the shared outer cell; help content needs no duplicate chrome inset.
1085
+ const indent = "";
1085
1086
  const separator = " ";
1086
1087
  const lines = [];
1087
1088
  let currentLine = "";
@@ -1239,16 +1240,22 @@ export class TreeSelectorComponent extends Container {
1239
1240
  this.treeContainer.addChild(this.treeList);
1240
1241
  this.labelInputContainer = new Container();
1241
1242
  this.addChild(new Spacer(1));
1242
- this.addChild(new DynamicBorder());
1243
- this.addChild(new Text(theme.bold(" Session Tree"), 1, 0));
1243
+ const header = addPiModalHeader(this, new DynamicBorder(), new Text(theme.bold("Session Tree"), 0, 0));
1244
1244
  this.addChild(new TreeHelp());
1245
1245
  this.addChild(new SearchLine(this.treeList));
1246
- this.addChild(new DynamicBorder());
1246
+ const separator = new DynamicBorder();
1247
+ this.addChild(separator);
1247
1248
  this.addChild(new Spacer(1));
1248
1249
  this.addChild(this.treeContainer);
1249
1250
  this.addChild(this.labelInputContainer);
1250
1251
  this.addChild(new Spacer(1));
1251
1252
  this.addChild(new DynamicBorder());
1253
+ adoptPiModalFrame(this, {
1254
+ topIndex: 1,
1255
+ bottomIndex: this.children.length - 1,
1256
+ header,
1257
+ fullWidthContent: [separator],
1258
+ });
1252
1259
  if (tree.length === 0) {
1253
1260
  setTimeout(() => onCancel(), 100);
1254
1261
  }
@@ -3,11 +3,12 @@
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
5
  * ProjectTrustStore-derived options, remapped owned theme imports, and the shared bare-A1 modal
6
- * shortcut row.
6
+ * shortcut row and compact padded modal frame.
7
7
  * Deviations: owned-modal-shortcut-hints.
8
8
  */
9
9
  import { DynamicBorder } from "../../../startup-public.js";
10
10
  import { Container, getKeybindings, Spacer, Text } from "@earendil-works/pi-tui";
11
+ import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
11
12
  import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
12
13
  function formatDecision(trustPath, decision) {
13
14
  if (decision === null)
@@ -29,13 +30,11 @@ export class TrustSelectorComponent extends Container {
29
30
  && options.savedDecision?.decision === option.trusted
30
31
  && options.savedDecision.path === option.savedPath;
31
32
  this.selectedIndex = Math.max(0, this.trustOptions.findIndex(isSaved));
32
- this.addChild(new DynamicBorder());
33
- this.addChild(new Spacer(1));
34
- this.addChild(new Text(piTheme().fg("accent", piTheme().bold("Project trust")), 1, 0));
35
- this.addChild(new Text(piTheme().fg("muted", options.cwd), 1, 0));
33
+ const header = addPiModalHeader(this, new DynamicBorder(), new Text(piTheme().fg("accent", piTheme().bold("Project trust")), 0, 0));
34
+ this.addChild(new Text(piTheme().fg("muted", options.cwd), 0, 0));
36
35
  this.addChild(new Spacer(1));
37
- this.addChild(new Text(piTheme().fg("muted", `Saved decision: ${formatDecision(this.trustOptions[0]?.savedPath, options.savedDecision)}`), 1, 0));
38
- this.addChild(new Text(piTheme().fg("muted", `Current session: ${options.projectTrusted ? "trusted" : "untrusted"}`), 1, 0));
36
+ this.addChild(new Text(piTheme().fg("muted", `Saved decision: ${formatDecision(this.trustOptions[0]?.savedPath, options.savedDecision)}`), 0, 0));
37
+ this.addChild(new Text(piTheme().fg("muted", `Current session: ${options.projectTrusted ? "trusted" : "untrusted"}`), 0, 0));
39
38
  this.addChild(new Spacer(1));
40
39
  this.listContainer = new Container();
41
40
  this.addChild(this.listContainer);
@@ -45,9 +44,10 @@ export class TrustSelectorComponent extends Container {
45
44
  { key: "↑↓", action: "navigate" },
46
45
  { key: keybindings.getKeys("tui.select.confirm").join("/"), action: "save" },
47
46
  { key: keybindings.getKeys("tui.select.cancel").join("/"), action: "cancel" },
48
- ]), 1, 0));
47
+ ]), 0, 0));
49
48
  this.addChild(new Spacer(1));
50
49
  this.addChild(new DynamicBorder());
50
+ adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
51
51
  this.updateList(options.savedDecision);
52
52
  this.handleInput = data => {
53
53
  const kb = getKeybindings();
@@ -81,7 +81,7 @@ export class TrustSelectorComponent extends Container {
81
81
  const prefix = selected ? piTheme().fg("accent", "→ ") : " ";
82
82
  const label = selected ? piTheme().fg("accent", option.label) : piTheme().fg("text", option.label);
83
83
  const currentMarker = current ? piTheme().fg("accent", "✓ ") : " ";
84
- this.listContainer.addChild(new Text(`${prefix}${currentMarker}${label}`, 1, 0));
84
+ this.listContainer.addChild(new Text(`${prefix}${currentMarker}${label}`, 0, 0));
85
85
  }
86
86
  }
87
87
  }
@@ -18,11 +18,7 @@ export interface CompactionProgressObserver {
18
18
  dispose(): void;
19
19
  }
20
20
  /**
21
- * Observes the summarization stream of a compaction through the session agent's public stream
22
- * function and reports an estimated integer percent. Pi never iterates that stream itself (it
23
- * reads only its final result), so the observer iterates it in the background between
24
- * `begin()` and `end()` and returns the same stream object to Pi. Outside that window the
25
- * wrapper is a pass-through. Returns null when the agent exposes no callable stream function,
26
- * in which case compaction proceeds without progress.
21
+ * Observes summary-stream progress without changing the stream returned to Pi. Returns null
22
+ * when the session exposes no callable public stream function.
27
23
  */
28
24
  export declare function observeCompactionProgress(session: CompactionProgressSession, onProgress: (percent: number) => void): CompactionProgressObserver | null;
@@ -1,14 +1,11 @@
1
1
  /** Expected summary size for a first compaction, when no previous summary on the branch can serve as the estimate. */
2
2
  export const DEFAULT_EXPECTED_COMPACTION_SUMMARY_CHARS = 4000;
3
- /** The percent shown while a compaction is still running never reaches this value. */
4
- const MAX_RUNNING_PERCENT = 99;
3
+ /** Character-based estimates stay below terminal stream completion. */
4
+ const MAX_STREAMING_PERCENT = 99;
5
+ const COMPLETE_PERCENT = 100;
5
6
  /**
6
- * Observes the summarization stream of a compaction through the session agent's public stream
7
- * function and reports an estimated integer percent. Pi never iterates that stream itself (it
8
- * reads only its final result), so the observer iterates it in the background between
9
- * `begin()` and `end()` and returns the same stream object to Pi. Outside that window the
10
- * wrapper is a pass-through. Returns null when the agent exposes no callable stream function,
11
- * in which case compaction proceeds without progress.
7
+ * Observes summary-stream progress without changing the stream returned to Pi. Returns null
8
+ * when the session exposes no callable public stream function.
12
9
  */
13
10
  export function observeCompactionProgress(session, onProgress) {
14
11
  const agent = session.agent;
@@ -21,8 +18,7 @@ export function observeCompactionProgress(session, onProgress) {
21
18
  let streamed = 0;
22
19
  let expected = DEFAULT_EXPECTED_COMPACTION_SUMMARY_CHARS;
23
20
  let reported = null;
24
- const report = () => {
25
- const percent = Math.min(MAX_RUNNING_PERCENT, Math.floor((100 * streamed) / expected));
21
+ const report = (percent = Math.min(MAX_STREAMING_PERCENT, Math.floor((100 * streamed) / expected))) => {
26
22
  if (percent === reported)
27
23
  return;
28
24
  reported = percent;
@@ -45,6 +41,10 @@ export function observeCompactionProgress(session, onProgress) {
45
41
  report();
46
42
  }
47
43
  }
44
+ // Invariant: normal exhaustion is the only observed completion boundary. The real
45
+ // compaction_end event owns work-state exit; errors and stale generations never claim 100%.
46
+ if (observed === generation && active && !disposed)
47
+ report(COMPLETE_PERCENT);
48
48
  }
49
49
  catch {
50
50
  return;
@@ -35,7 +35,7 @@ export const PI_SETTING_EFFECTS = Object.freeze({
35
35
  tuiMode: hiddenEffect("next-session", "shell", "pinned regular/fullscreen selector and terminal lifecycle", "pi-terminal-operation-parity"),
36
36
  fullscreenExitOutput: hiddenEffect("current-exit", "shutdown", "pinned styled transcript and compact dim resume hint", "pinned-fullscreen-exit-parity"),
37
37
  fullscreenScrollbar: hiddenEffect("live", "shell", "pinned fullscreen scrollbar reservation", "pi-terminal-operation-parity"),
38
- fullscreenCopyOnSelect: hiddenEffect("live", "shell", "pinned fullscreen copy-on-select toggle", "pi-terminal-operation-parity"),
38
+ fullscreenCopyOnSelect: effect("live", "shell", "terminal-status", "pinned fullscreen copy-on-select toggle and copy acknowledgement", "pi-terminal-operation-parity"),
39
39
  warnings: effect("live", "agent", "transcript-notice", "pinned warning rows by warning part", "pinned-transcript-lifecycle-parity"),
40
40
  });
41
41
  function effect(application, owner, visualClass, pinnedSurface, evidence) {
@@ -219,7 +219,7 @@ export class PiEngineSettings {
219
219
  tuiMode: setting(settings?.getTuiMode, "regular"),
220
220
  fullscreenExitOutput: setting(settings?.getFullscreenExitOutput, "transcript"),
221
221
  fullscreenScrollbar: setting(settings?.getFullscreenScrollbar, "auto"),
222
- fullscreenCopyOnSelect: setting(settings?.getFullscreenCopyOnSelect, false),
222
+ fullscreenCopyOnSelect: setting(settings?.getFullscreenCopyOnSelect, true),
223
223
  warnings: setting(settings?.getWarnings, { anthropicExtraUsage: true }),
224
224
  };
225
225
  }
@@ -1,5 +1,5 @@
1
1
  import { HStack, ProcessTerminal, ScrollView, TuiAltScreen, TuiMainScreen, VStack, visibleWidth, } from "@earendil-works/pi-tui";
2
- import { MouseReportInput } from "./mouse-report-input.js";
2
+ import { MouseReportInput, stripSgrMouseReports } from "./mouse-report-input.js";
3
3
  import { GeometryObservedAltScreen, OverlayGeometryTracker } from "./overlay-geometry.js";
4
4
  import { boundedCleanup, EMERGENCY_TERMINAL_RESET } from "../../../foundation/terminal-cleanup/terminal-reset.js";
5
5
  import { InputPresentationCoordinator, } from "./input-presentation-coordinator.js";
@@ -108,6 +108,7 @@ export class PiTuiRuntimeAdapter {
108
108
  #overlayInputCoordination = new Map();
109
109
  #inputListeners = new Map();
110
110
  #preInputListeners = new Set();
111
+ #consumeUnhandledMouse;
111
112
  #inputCoordinator;
112
113
  #inputDiagnostics;
113
114
  #diagnosticNow;
@@ -121,6 +122,7 @@ export class PiTuiRuntimeAdapter {
121
122
  this.#root = options.root;
122
123
  this.#overlayGeometry = options.onOverlayGeometry === undefined ? undefined : new OverlayGeometryTracker(options.onOverlayGeometry);
123
124
  this.#terminal = options.terminal ?? new ProcessTerminal();
125
+ this.#consumeUnhandledMouse = options.consumeUnhandledMouse ?? false;
124
126
  this.#inputDiagnostics = options.inputDiagnostics;
125
127
  this.#diagnosticNow = options.inputDiagnostics?.now ?? (() => performance.now());
126
128
  // Invariant: the pinned renderer's frames pass through this gate, while writeControl and
@@ -489,7 +491,9 @@ export class PiTuiRuntimeAdapter {
489
491
  else if (result?.consume === true)
490
492
  current = "";
491
493
  }
492
- return current;
494
+ // Invariant: bare A1 owns every fullscreen pointer path. Listeners keep first
495
+ // refusal, then this boundary prevents Pi's reverse-video fallback selection.
496
+ return this.#consumeUnhandledMouse ? stripSgrMouseReports(current) : current;
493
497
  }
494
498
  #focusedOverlayInputCoordination() {
495
499
  for (const [bridge, coordination] of this.#overlayInputCoordination) {
@@ -143,6 +143,8 @@ export interface PiTuiRuntimeAdapterOptions {
143
143
  };
144
144
  readonly hardwareCursor?: boolean;
145
145
  readonly mouse?: boolean;
146
+ /** Consume residual SGR mouse reports after every pre-input listener has run. */
147
+ readonly consumeUnhandledMouse?: boolean;
146
148
  readonly wheelScrollLines?: number;
147
149
  readonly openUrl?: (url: string) => void;
148
150
  readonly onRightClickPaste?: () => void;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Removes complete SGR mouse reports outside bracketed paste while preserving
3
+ * every adjacent byte in order. Incomplete reports remain available to the
4
+ * caller's framing boundary rather than being guessed at here.
5
+ */
6
+ export declare function stripSgrMouseReports(data: string): string;
1
7
  /**
2
8
  * Keeps mouse reports and adjacent keyboard input in physical delivery order.
3
9
  * ProcessTerminal already frames escape/paste sequences; this also handles hosts
@@ -1,3 +1,37 @@
1
+ const BRACKETED_PASTE_START = "\u001b[200~";
2
+ const BRACKETED_PASTE_END = "\u001b[201~";
3
+ const SGR_MOUSE_REPORT_PATTERN = /\u001b\[<\d+;\d+;\d+[Mm]/g;
4
+ /**
5
+ * Removes complete SGR mouse reports outside bracketed paste while preserving
6
+ * every adjacent byte in order. Incomplete reports remain available to the
7
+ * caller's framing boundary rather than being guessed at here.
8
+ */
9
+ export function stripSgrMouseReports(data) {
10
+ if (!data.includes("\u001b[<"))
11
+ return data;
12
+ let output = "";
13
+ let from = 0;
14
+ while (from < data.length) {
15
+ const pasteStart = data.indexOf(BRACKETED_PASTE_START, from);
16
+ SGR_MOUSE_REPORT_PATTERN.lastIndex = from;
17
+ const report = SGR_MOUSE_REPORT_PATTERN.exec(data);
18
+ if (pasteStart >= 0 && (report === null || pasteStart < report.index)) {
19
+ output += data.slice(from, pasteStart);
20
+ const pasteEnd = data.indexOf(BRACKETED_PASTE_END, pasteStart + BRACKETED_PASTE_START.length);
21
+ if (pasteEnd < 0)
22
+ return output + data.slice(pasteStart);
23
+ const afterPaste = pasteEnd + BRACKETED_PASTE_END.length;
24
+ output += data.slice(pasteStart, afterPaste);
25
+ from = afterPaste;
26
+ continue;
27
+ }
28
+ if (report === null)
29
+ return output + data.slice(from);
30
+ output += data.slice(from, report.index);
31
+ from = report.index + report[0].length;
32
+ }
33
+ return output;
34
+ }
1
35
  /**
2
36
  * Keeps mouse reports and adjacent keyboard input in physical delivery order.
3
37
  * ProcessTerminal already frames escape/paste sequences; this also handles hosts
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-23T23:03:13.628Z",
8
+ "builtAt": "2026-09-24T07:17:37.739Z",
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-23T23:03:11.352Z",
8
+ "builtAt": "2026-09-24T07:17:22.316Z",
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-23T23:03:54.826Z",
8
+ "builtAt": "2026-09-24T07:18:02.282Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "6c21b828ef2eb284a783609ad28dee4744f81cf0d53a4de1e38bddba4da08079",
11
+ "sha256": "c7d92e5f9cec0d36ec4482b25e518464acb4e1bbc5bdd6701f1e04ada7775950",
12
12
  "size": 178176
13
13
  },
14
14
  "provenance": {
@@ -13,6 +13,10 @@ export function dialogValueColumn(rows) {
13
13
  export function renderDialogPanel(state, width, theme) {
14
14
  const labelColumn = Math.min(LABEL_COLUMN_CAP, Math.max(0, ...state.rows.map(row => displayWidth(row.label))));
15
15
  const rule = theme.fg("border", "─".repeat(Math.max(0, width)));
16
+ const contentPadding = Math.min(1, Math.max(0, width));
17
+ const contentWidth = Math.max(0, width - contentPadding);
18
+ const inset = " ".repeat(contentPadding);
19
+ const contentRow = (painted, raw) => `${inset}${pad(truncateToWidth(painted, contentWidth), contentWidth, raw)}`;
16
20
  const rows = state.rows.map((row, index) => {
17
21
  const selected = index === state.index;
18
22
  const padded = `${row.label}${" ".repeat(Math.max(0, labelColumn - displayWidth(row.label)))}`;
@@ -23,20 +27,20 @@ export function renderDialogPanel(state, width, theme) {
23
27
  const painted = selected
24
28
  ? `${theme.fg("accent", cursor)}${theme.fg("accent", padded)} ${theme.fg("accent", row.value)}`
25
29
  : `${cursor}${padded} ${theme.fg("muted", row.value)}`;
26
- return pad(truncateToWidth(painted, width), width, raw);
30
+ return contentRow(painted, raw);
27
31
  });
28
32
  const description = state.rows[state.index]?.description ?? "";
29
33
  const hint = typeof state.hint === "string"
30
- ? theme.fg("dim", ` ${state.hint}`)
31
- : renderShortcutHints(state.hint, theme, 1);
32
- const plainHint = typeof state.hint === "string" ? ` ${state.hint}` : shortcutHintsText(state.hint, 1);
34
+ ? theme.fg("dim", state.hint)
35
+ : renderShortcutHints(state.hint, theme, 0);
36
+ const plainHint = typeof state.hint === "string" ? state.hint : shortcutHintsText(state.hint, 0);
33
37
  return [
34
38
  rule,
35
39
  ...rows,
36
40
  "",
37
- pad(truncateToWidth(theme.fg("dim", ` ${description}`), width), width, ` ${description}`),
41
+ contentRow(theme.fg("dim", ` ${description}`), ` ${description}`),
38
42
  "",
39
- pad(truncateToWidth(hint, width), width, plainHint),
43
+ contentRow(hint, plainHint),
40
44
  rule,
41
45
  ];
42
46
  }
@@ -4,8 +4,8 @@ import type { PaneMouseEvent } from "./pane.js";
4
4
  * terminal to send reports; nothing else in A1 turns this on, and the sequences
5
5
  * are paired so the terminal is always left as it was found.
6
6
  */
7
- export declare const MOUSE_TRACKING_ON = "\u001B[?1006h\u001B[?1000h\u001B[?1003h";
8
- export declare const MOUSE_TRACKING_OFF = "\u001B[?1003l\u001B[?1000l\u001B[?1006l";
7
+ export declare const MOUSE_TRACKING_ON = "\u001B[?1006h\u001B[?1000h\u001B[?1003h\u001B[?1004h";
8
+ export declare const MOUSE_TRACKING_OFF = "\u001B[?1004l\u001B[?1003l\u001B[?1000l\u001B[?1006l";
9
9
  export interface ParsedMouseInput {
10
10
  readonly events: readonly PaneMouseEvent[];
11
11
  /** Input with every mouse report removed, for ordinary key handling. */
@@ -3,8 +3,8 @@
3
3
  * terminal to send reports; nothing else in A1 turns this on, and the sequences
4
4
  * are paired so the terminal is always left as it was found.
5
5
  */
6
- export const MOUSE_TRACKING_ON = "\u001b[?1006h\u001b[?1000h\u001b[?1003h";
7
- export const MOUSE_TRACKING_OFF = "\u001b[?1003l\u001b[?1000l\u001b[?1006l";
6
+ export const MOUSE_TRACKING_ON = "\u001b[?1006h\u001b[?1000h\u001b[?1003h\u001b[?1004h";
7
+ export const MOUSE_TRACKING_OFF = "\u001b[?1004l\u001b[?1003l\u001b[?1000l\u001b[?1006l";
8
8
  const SGR_PATTERN = /\u001b\[<(\d+);(\d+);(\d+)([Mm])/g;
9
9
  /** Extracts SGR mouse reports, leaving any keyboard input untouched. */
10
10
  export function parseMouseInput(data) {
@@ -4,6 +4,9 @@ import { backgroundSgrSpan, overlaySpan } from "./spans.js";
4
4
  import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
5
5
  import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
6
6
  const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
7
+ // Invariant: the scrollbar gutter is presentation chrome: no source link, emphasis,
8
+ // selection, or background may cross from the content cell beside it.
9
+ const GUTTER_STYLE_RESET = CONTROL_STYLE_RESET;
7
10
  const IDENTITY_ROW = (row) => row;
8
11
  const DEFAULT_CONFIG = {
9
12
  scrollbarAppearance: "auto",
@@ -81,13 +84,16 @@ export class TranscriptViewport {
81
84
  get hasSelection() { return orderedTextSelection(this.#selection) !== undefined; }
82
85
  pressSelection(column, frameRow, now = Date.now()) {
83
86
  const frameHeight = this.#frame?.rows.length ?? 0;
84
- if (frameRow < 1 || frameRow > frameHeight || column < 1 || column > this.#selectionWidth)
87
+ const selectionWidth = frameRow <= (this.#frame?.hits.viewportHeight ?? 0)
88
+ ? this.#contentWidth
89
+ : this.#selectionWidth;
90
+ if (frameRow < 1 || frameRow > frameHeight || column < 1 || column > selectionWidth)
85
91
  return false;
86
92
  const line = frameRow - 1;
87
93
  const pressed = pressTextSelection({
88
94
  line,
89
95
  column,
90
- contentWidth: this.#selectionWidth,
96
+ contentWidth: selectionWidth,
91
97
  lineText: this.#selectionRows[line] ?? "",
92
98
  lineContent: usefulTextLineContent(this.#selectionRows[line] ?? ""),
93
99
  ...(this.#selectionClick === undefined ? {} : { previousClick: this.#selectionClick }),
@@ -113,9 +119,12 @@ export class TranscriptViewport {
113
119
  else if (autoScroll && frameRow <= 1 && this.#scrollTop > 0)
114
120
  this.scrollBy(-1, now);
115
121
  const line = clamp(frameRow - 1, 0, this.#selectionRows.length - 1);
122
+ const selectionWidth = line < (this.#frame?.hits.viewportHeight ?? 0)
123
+ ? this.#contentWidth
124
+ : this.#selectionWidth;
116
125
  const targetColumn = selection.fullRow
117
- ? textSelectionLineExtendColumn(selection, line, this.#selectionWidth)
118
- : clamp(column, 1, this.#selectionWidth);
126
+ ? textSelectionLineExtendColumn(selection, line, selectionWidth)
127
+ : clamp(column, 1, selectionWidth);
119
128
  const point = selection.fullRow
120
129
  ? { line, column: targetColumn }
121
130
  : textSelectionPointAt(line, targetColumn, this.#selectionRows[line] ?? "");
@@ -311,8 +320,8 @@ export class TranscriptViewport {
311
320
  this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
312
321
  this.#promptAnchors = input.promptAnchors;
313
322
  this.#contentWidth = contentWidth;
314
- // Invariant: controls reserve the rail column, but source text beneath it
315
- // remains reachable by a drag already owned by transcript selection.
323
+ // Invariant: dock rows remain selectable at full width; scrollable source rows
324
+ // use contentWidth and leave the gutter outside semantic selection.
316
325
  this.#selectionWidth = width;
317
326
  const paintDocumentRow = input.paintDocumentRow ?? IDENTITY_ROW;
318
327
  const cacheLimit = Math.max(32, viewportHeight * 6);
@@ -348,8 +357,12 @@ export class TranscriptViewport {
348
357
  visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
349
358
  visibleSource[0] = sourceRow;
350
359
  }
351
- // Invariant: selection uses exact visible source rows; paint-only hyperlink guards stay rendered-only.
352
- this.#selectionRows = [...visibleSource, ...dock].slice(0, height);
360
+ // Invariant: selection uses exact visible source rows within the rendered
361
+ // content boundary; paint-only guards and the rail gutter stay presentation-only.
362
+ this.#selectionRows = [
363
+ ...visibleSource.map(row => truncateToWidth(row, contentWidth)),
364
+ ...dock,
365
+ ].slice(0, height);
353
366
  const selectionPainterId = this.#functionId(theme.selection);
354
367
  // Concurrency: a painter may synchronously route input in tests or host integrations.
355
368
  // Label this frame with the exact selection snapshot it began composing.
@@ -394,17 +407,20 @@ export class TranscriptViewport {
394
407
  this.#updateCopyableSelection();
395
408
  for (let row = 0; row < frameRows.length; row += 1) {
396
409
  const painted = frameRows[row] ?? "";
397
- const range = selectionRangeForLine(orderedSelection, row, this.#selectionRows.length, width);
410
+ const rowSelectionWidth = row < viewportHeight ? contentWidth : width;
411
+ const range = selectionRangeForLine(orderedSelection, row, this.#selectionRows.length, rowSelectionWidth);
398
412
  const padded = row < viewportHeight || range !== null;
399
- const base = cachedString(this.#baseRowCache, `${width}\u0000${padded}\u0000${painted}`, cacheLimit, () => padded ? padRowPreservingBackground(painted, width) : painted);
413
+ const base = cachedString(this.#baseRowCache, `${width}\u0000${contentWidth}\u0000${row < viewportHeight}\u0000${padded}\u0000${painted}`, cacheLimit, () => row < viewportHeight
414
+ ? padViewportRowWithGutter(painted, contentWidth, width)
415
+ : padded ? padRowPreservingBackground(painted, width) : painted);
400
416
  let rowRecomputed = row < viewportHeight && paintRecomputedRows.has(row) || !base.reused;
401
417
  const rangeKey = range === null ? "-" : `${range.from}:${range.to}`;
402
418
  const selected = range === null
403
419
  ? { value: base.value, reused: true }
404
420
  : cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value, range.from, range.to));
405
421
  rowRecomputed ||= !selected.reused;
406
- // Invariant: paint the rail after selection. Full-row selection reaches the terminal
407
- // edge, while the foreground thumb/track remains visible above that background.
422
+ // Invariant: paint the rail after selection, inside its neutral gutter.
423
+ // Source and full-row selection end at the preceding content cell.
408
424
  let railCell = "";
409
425
  if (row < viewportHeight && presentation.visible && geometry !== null && row > 0) {
410
426
  const trackRow = row - 1;
@@ -412,7 +428,7 @@ export class TranscriptViewport {
412
428
  const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
413
429
  railCell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
414
430
  }
415
- const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, railCell, { inheritStartStyle: true }));
431
+ const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, `${GUTTER_STYLE_RESET}${railCell}`));
416
432
  rowRecomputed ||= !final.reused;
417
433
  frameRows[row] = final.value;
418
434
  (rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
@@ -626,6 +642,12 @@ function governingPrompt(anchors, scrollTop) {
626
642
  }
627
643
  return result;
628
644
  }
645
+ function padViewportRowWithGutter(line, contentWidth, frameWidth) {
646
+ const boundedContentWidth = Math.min(Math.max(1, contentWidth), frameWidth);
647
+ const content = padRowPreservingBackground(line, boundedContentWidth);
648
+ const gutterWidth = frameWidth - boundedContentWidth;
649
+ return gutterWidth === 0 ? content : `${content}${GUTTER_STYLE_RESET}${" ".repeat(gutterWidth)}`;
650
+ }
629
651
  function padRowPreservingBackground(line, width) {
630
652
  const shown = displayWidth(line) > width ? truncateToWidth(line, width) : line;
631
653
  const padding = Math.max(0, width - displayWidth(shown));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.1.8-dev.572",
3
+ "version": "0.1.8-dev.573",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",