@timurproko/a1 0.1.8-dev.563 → 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/README.md +13 -11
- 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 +60 -17
- package/dist/app/session-shell/session-shell.js +35 -1
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
- package/dist/app/session-shell/session-viewport-controller.js +52 -30
- package/dist/cli/dispatch.d.ts +3 -1
- package/dist/cli/dispatch.js +56 -56
- package/dist/cli/package-messages.d.ts +4 -3
- package/dist/cli/package-messages.js +23 -11
- 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-footer-status.d.ts +4 -3
- package/dist/integrations/pi/components/shell-footer-status.js +53 -15
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +38 -2
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
- 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/adjacent/core/keybindings.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +3 -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 +3 -7
- package/dist/integrations/pi/engine/compaction-progress.js +11 -10
- package/dist/integrations/pi/engine/repository-pr.d.ts +3 -3
- package/dist/integrations/pi/engine/repository-pr.js +6 -6
- package/dist/integrations/pi/engine/session-runtime.js +5 -4
- 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.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +11 -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 +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/dialog-panel.js +10 -6
- package/dist/ui/components/index.d.ts +2 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/mouse.d.ts +2 -2
- package/dist/ui/components/mouse.js +2 -2
- package/dist/ui/components/progress-status.d.ts +9 -2
- package/dist/ui/components/progress-status.js +24 -3
- package/dist/ui/components/transcript-viewport.d.ts +9 -3
- package/dist/ui/components/transcript-viewport.js +116 -136
- package/docs/architecture/ui-reference-provenance.md +8 -5
- package/docs/openspec-archive-automation.md +2 -2
- package/docs/validation.md +1 -1
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Modifications: Source-synchronized session selector port: preserve threaded/current/all scope,
|
|
5
5
|
* search, sort, named/path filters, rename, delete confirmation, active-session protection, loading
|
|
6
6
|
* progress, cancellation, focus, and disposal while remapping public helpers, owned keybindings/theme,
|
|
7
|
-
* canonical path handling, and the shared bare-A1 modal shortcut row.
|
|
7
|
+
* canonical path handling, and the shared bare-A1 modal shortcut row and compact padded modal frame.
|
|
8
8
|
* Deviations: owned-modal-shortcut-hints.
|
|
9
9
|
*/
|
|
10
10
|
import { spawnSync } from "node:child_process";
|
|
@@ -14,6 +14,7 @@ import * as os from "node:os";
|
|
|
14
14
|
import { Container, getKeybindings, Input, Spacer, Text, truncateToWidth, visibleWidth, } from "@earendil-works/pi-tui";
|
|
15
15
|
import { KeybindingsManager } from "../adjacent/core/keybindings.js";
|
|
16
16
|
import { DynamicBorder } from "../../../startup-public.js";
|
|
17
|
+
import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
|
|
17
18
|
import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
|
|
18
19
|
const theme = new Proxy({}, {
|
|
19
20
|
get(_target, property) {
|
|
@@ -665,15 +666,21 @@ export class SessionSelectorComponent extends Container {
|
|
|
665
666
|
buildBaseLayout(content, options) {
|
|
666
667
|
this.clear();
|
|
667
668
|
this.addChild(new Spacer(1));
|
|
668
|
-
|
|
669
|
+
const showHeader = options?.showHeader ?? true;
|
|
670
|
+
const modalHeader = showHeader
|
|
671
|
+
? addPiModalHeader(this, new DynamicBorder((s) => theme.fg("accent", s)), this.header)
|
|
672
|
+
: undefined;
|
|
673
|
+
if (!showHeader)
|
|
674
|
+
this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
669
675
|
this.addChild(new Spacer(1));
|
|
670
|
-
if (options?.showHeader ?? true) {
|
|
671
|
-
this.addChild(this.header);
|
|
672
|
-
this.addChild(new Spacer(1));
|
|
673
|
-
}
|
|
674
676
|
this.addChild(content);
|
|
675
677
|
this.addChild(new Spacer(1));
|
|
676
678
|
this.addChild(new DynamicBorder((s) => theme.fg("accent", s)));
|
|
679
|
+
const frame = { topIndex: 1, bottomIndex: this.children.length - 1 };
|
|
680
|
+
if (modalHeader === undefined)
|
|
681
|
+
adoptPiModalFrame(this, frame);
|
|
682
|
+
else
|
|
683
|
+
adoptPiModalFrame(this, { ...frame, header: modalHeader });
|
|
677
684
|
}
|
|
678
685
|
constructor(currentSessionsLoader, allSessionsLoader, onSelect, onCancel, onExit, requestRender, options, currentSessionFilePath) {
|
|
679
686
|
super();
|
|
@@ -3,11 +3,12 @@
|
|
|
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, type Focusable, SelectList } from "@earendil-works/pi-tui";
|
|
@@ -16,7 +17,7 @@ export type ThinkingSelectorLevel = "off" | "minimal" | "low" | "medium" | "high
|
|
|
16
17
|
export declare class OwnedThinkingSelectorComponent extends Container implements Focusable {
|
|
17
18
|
private searchInput;
|
|
18
19
|
private selectList;
|
|
19
|
-
private
|
|
20
|
+
private selectListContainer;
|
|
20
21
|
private allItems;
|
|
21
22
|
private onSelect;
|
|
22
23
|
private onCancel;
|
|
@@ -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
|
}
|
|
@@ -10,7 +10,7 @@ export interface CompactionProgressSession {
|
|
|
10
10
|
} | undefined;
|
|
11
11
|
}
|
|
12
12
|
export interface CompactionProgressObserver {
|
|
13
|
-
/** Marks `compaction_start`: resets the
|
|
13
|
+
/** Marks `compaction_start`: resets the estimate and immediately reports zero percent. */
|
|
14
14
|
begin(): void;
|
|
15
15
|
/** Marks `compaction_end`: later stream chunks are ignored. */
|
|
16
16
|
end(): void;
|
|
@@ -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;
|
|
@@ -60,6 +60,7 @@ export function observeCompactionProgress(session, onProgress) {
|
|
|
60
60
|
reported = null;
|
|
61
61
|
expected = latestCompactionSummaryLength(session) ?? DEFAULT_EXPECTED_COMPACTION_SUMMARY_CHARS;
|
|
62
62
|
active = true;
|
|
63
|
+
report();
|
|
63
64
|
},
|
|
64
65
|
end() {
|
|
65
66
|
active = false;
|
|
@@ -3,6 +3,6 @@ export interface PiPullRequestIdentity {
|
|
|
3
3
|
readonly url: string;
|
|
4
4
|
}
|
|
5
5
|
export type PiPullRequestProbe = (cwd: string, branch: string, signal: AbortSignal) => Promise<PiPullRequestIdentity | null>;
|
|
6
|
-
/** Read
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
6
|
+
/** Read an exact branch's open or merged PR. */
|
|
7
|
+
export declare function readPullRequest(cwd: string, branch: string, signal: AbortSignal, environment?: NodeJS.ProcessEnv): Promise<PiPullRequestIdentity | null>;
|
|
8
|
+
export declare function parsePullRequest(stdout: string, branch: string): PiPullRequestIdentity | null;
|
|
@@ -2,19 +2,19 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
3
3
|
const GH_TIMEOUT_MS = 5_000;
|
|
4
4
|
const GH_MAX_BUFFER_BYTES = 64 * 1024;
|
|
5
|
-
/** Read
|
|
6
|
-
export async function
|
|
5
|
+
/** Read an exact branch's open or merged PR. */
|
|
6
|
+
export async function readPullRequest(cwd, branch, signal, environment = process.env) {
|
|
7
7
|
if (branch.length === 0 || signal.aborted)
|
|
8
8
|
return null;
|
|
9
9
|
const preview = environment[PRODUCT_IDENTITY.environment.prFooterPreview];
|
|
10
10
|
if (preview !== undefined)
|
|
11
|
-
return
|
|
11
|
+
return parsePullRequest(preview, branch);
|
|
12
12
|
const stdout = await executeGh(cwd, signal);
|
|
13
13
|
if (stdout === null)
|
|
14
14
|
return null;
|
|
15
|
-
return
|
|
15
|
+
return parsePullRequest(stdout, branch);
|
|
16
16
|
}
|
|
17
|
-
export function
|
|
17
|
+
export function parsePullRequest(stdout, branch) {
|
|
18
18
|
let value;
|
|
19
19
|
try {
|
|
20
20
|
value = JSON.parse(stdout);
|
|
@@ -25,7 +25,7 @@ export function parseOpenPullRequest(stdout, branch) {
|
|
|
25
25
|
if (!isRecord(value)
|
|
26
26
|
|| !Number.isSafeInteger(value.number)
|
|
27
27
|
|| value.number <= 0
|
|
28
|
-
|| value.state !== "OPEN"
|
|
28
|
+
|| (value.state !== "OPEN" && value.state !== "MERGED")
|
|
29
29
|
|| value.headRefName !== branch
|
|
30
30
|
|| typeof value.url !== "string")
|
|
31
31
|
return null;
|
|
@@ -6,7 +6,7 @@ import { observeCompactionProgress } from "./compaction-progress.js";
|
|
|
6
6
|
import { createPiRuntimeIntegration } from "./runtime-integration.js";
|
|
7
7
|
import { PiSessionCommandIntegration } from "./session-integration.js";
|
|
8
8
|
import { DefaultPackageManager, VERSION } from "../startup-public.js";
|
|
9
|
-
import {
|
|
9
|
+
import { readPullRequest, } from "./repository-pr.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
11
|
const PULL_REQUEST_REFRESH_MS = 60_000;
|
|
12
12
|
const REPOSITORY_CONTEXT_POLL_MS = 5_000;
|
|
@@ -56,7 +56,7 @@ export class PiEngineRuntime {
|
|
|
56
56
|
?? (options.createRuntime
|
|
57
57
|
? async () => []
|
|
58
58
|
: settingsManager => checkDefaultPiPackageUpdates(this.#cwd, options.agentDir, settingsManager));
|
|
59
|
-
this.#pullRequestProbe = options.pullRequestProbe ??
|
|
59
|
+
this.#pullRequestProbe = options.pullRequestProbe ?? readPullRequest;
|
|
60
60
|
this.#pullRequestRefreshMs = options.pullRequestRefreshMs ?? PULL_REQUEST_REFRESH_MS;
|
|
61
61
|
this.#repositoryContextPollMs = options.repositoryContextPollMs ?? REPOSITORY_CONTEXT_POLL_MS;
|
|
62
62
|
this.#repositoryContextReader = options.repositoryContextReader ?? (async () => null);
|
|
@@ -393,6 +393,8 @@ function samePullRequest(left, right) {
|
|
|
393
393
|
return left?.number === right?.number && left?.url === right?.url;
|
|
394
394
|
}
|
|
395
395
|
async function readGitBranch(cwd, signal) {
|
|
396
|
+
if (!existsSync(cwd))
|
|
397
|
+
return null;
|
|
396
398
|
try {
|
|
397
399
|
const { stdout } = await execFileAsync("git", ["branch", "--show-current"], {
|
|
398
400
|
cwd,
|
|
@@ -402,8 +404,7 @@ async function readGitBranch(cwd, signal) {
|
|
|
402
404
|
signal,
|
|
403
405
|
encoding: "utf8",
|
|
404
406
|
});
|
|
405
|
-
|
|
406
|
-
return branch.length > 0 ? branch : null;
|
|
407
|
+
return stdout.trim() || null;
|
|
407
408
|
}
|
|
408
409
|
catch {
|
|
409
410
|
return null;
|
|
@@ -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
|
}
|
|
@@ -47,6 +47,8 @@ export declare class PiTuiRuntimeAdapter {
|
|
|
47
47
|
getClearOnShrink(): boolean;
|
|
48
48
|
setTerminalProgress(active: boolean): void;
|
|
49
49
|
setTitle(title: string): void;
|
|
50
|
+
/** Shows a bounded fullscreen acknowledgement without changing root layout geometry. */
|
|
51
|
+
showFlash(message: string, durationMs?: number): void;
|
|
50
52
|
writeAfterStop(text: string): void;
|
|
51
53
|
invalidate(): void;
|
|
52
54
|
requestRender(force?: boolean): void;
|
|
@@ -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
|
|
@@ -392,6 +394,11 @@ export class PiTuiRuntimeAdapter {
|
|
|
392
394
|
setTitle(title) {
|
|
393
395
|
this.#terminal.setTitle(title);
|
|
394
396
|
}
|
|
397
|
+
/** Shows a bounded fullscreen acknowledgement without changing root layout geometry. */
|
|
398
|
+
showFlash(message, durationMs) {
|
|
399
|
+
this.#assertRunning("flash");
|
|
400
|
+
this.#requireFullscreen().flash(message, durationMs);
|
|
401
|
+
}
|
|
395
402
|
writeAfterStop(text) {
|
|
396
403
|
if (this.#state !== "stopped")
|
|
397
404
|
throw new Error("terminal output requires a stopped Pi TUI runtime");
|
|
@@ -484,7 +491,9 @@ export class PiTuiRuntimeAdapter {
|
|
|
484
491
|
else if (result?.consume === true)
|
|
485
492
|
current = "";
|
|
486
493
|
}
|
|
487
|
-
|
|
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;
|
|
488
497
|
}
|
|
489
498
|
#focusedOverlayInputCoordination() {
|
|
490
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,11 +5,11 @@
|
|
|
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": "
|
|
12
|
-
"size":
|
|
11
|
+
"sha256": "c7d92e5f9cec0d36ec4482b25e518464acb4e1bbc5bdd6701f1e04ada7775950",
|
|
12
|
+
"size": 178176
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
15
15
|
"language": "Rust",
|
|
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
|
}
|