@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.
- package/dist/app/session-shell/prompt-chips.js +20 -28
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +54 -14
- package/dist/app/session-shell/session-shell.js +30 -2
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
- package/dist/app/session-shell/session-viewport-controller.js +5 -1
- package/dist/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/prompt-chips.d.ts +15 -0
- package/dist/contracts/owned-ui/prompt-chips.js +59 -0
- package/dist/contracts/owned-ui/validation.js +1 -1
- package/dist/integrations/pi/components/index.d.ts +1 -0
- package/dist/integrations/pi/components/index.js +1 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +41 -0
- package/dist/integrations/pi/components/modal-frame.js +110 -0
- package/dist/integrations/pi/components/models-dialog.js +19 -5
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +38 -2
- package/dist/integrations/pi/components/skills-dialog.js +3 -3
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +1 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +5 -2
- package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +7 -6
- package/dist/integrations/pi/components/upstream/components/extension-input.js +9 -8
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +10 -9
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
- package/dist/integrations/pi/components/upstream/components/session-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +15 -12
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +9 -9
- package/dist/integrations/pi/engine/compaction-progress.d.ts +2 -6
- package/dist/integrations/pi/engine/compaction-progress.js +10 -10
- package/dist/integrations/pi/engine/settings-bridge.js +1 -1
- package/dist/integrations/pi/engine/settings-port.js +1 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +6 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +34 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/dialog-panel.js +10 -6
- package/dist/ui/components/mouse.d.ts +2 -2
- package/dist/ui/components/mouse.js +2 -2
- package/dist/ui/components/transcript-viewport.js +35 -13
- 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
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
//
|
|
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
|
|
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
|
-
|
|
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
|
|
33
|
-
this.addChild(new
|
|
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)}`),
|
|
38
|
-
this.addChild(new Text(piTheme().fg("muted", `Current session: ${options.projectTrusted ? "trusted" : "untrusted"}`),
|
|
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
|
-
]),
|
|
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}`,
|
|
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
|
|
22
|
-
*
|
|
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
|
-
/**
|
|
4
|
-
const
|
|
3
|
+
/** Character-based estimates stay below terminal stream completion. */
|
|
4
|
+
const MAX_STREAMING_PERCENT = 99;
|
|
5
|
+
const COMPLETE_PERCENT = 100;
|
|
5
6
|
/**
|
|
6
|
-
* Observes
|
|
7
|
-
*
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
8
|
+
"builtAt": "2026-09-24T07:18:02.282Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "c7d92e5f9cec0d36ec4482b25e518464acb4e1bbc5bdd6701f1e04ada7775950",
|
|
12
12
|
"size": 178176
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -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
|
|
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",
|
|
31
|
-
: renderShortcutHints(state.hint, theme,
|
|
32
|
-
const plainHint = typeof state.hint === "string" ?
|
|
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
|
-
|
|
41
|
+
contentRow(theme.fg("dim", ` ${description}`), ` ${description}`),
|
|
38
42
|
"",
|
|
39
|
-
|
|
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
|
-
|
|
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:
|
|
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,
|
|
118
|
-
: clamp(column, 1,
|
|
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:
|
|
315
|
-
//
|
|
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
|
|
352
|
-
|
|
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
|
|
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, () =>
|
|
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
|
|
407
|
-
//
|
|
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,
|
|
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