@timurproko/a1 0.1.8-dev.137 → 0.1.8-dev.151
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 +2 -4
- package/dist/cli/capabilities.d.ts +2 -11
- package/dist/cli/capabilities.js +1 -1
- package/dist/cli/dispatch.js +3 -6
- package/dist/composition/owned-ui.js +26 -5
- package/dist/composition/settings-route-host.d.ts +1 -1
- package/dist/composition/settings-route-host.js +1 -1
- package/dist/contracts/owned-ui/model.d.ts +28 -0
- package/dist/contracts/owned-ui/validation.js +14 -0
- package/dist/features/launch/development-launch.js +11 -3
- package/dist/features/launch/index.d.ts +0 -1
- package/dist/features/launch/index.js +0 -1
- package/dist/features/launch/intent.d.ts +3 -2
- package/dist/features/launch/intent.js +1 -2
- package/dist/features/launch/prepare-launch.d.ts +0 -2
- package/dist/features/launch/prepare-launch.js +2 -9
- package/dist/features/launch/profile-paths.d.ts +1 -2
- package/dist/features/launch/profile-paths.js +2 -8
- package/dist/features/launch/runtime-selection.d.ts +2 -5
- package/dist/features/owned-ui/settings-app.js +38 -14
- package/dist/foundation/lifecycle/model.d.ts +1 -1
- package/dist/foundation/lifecycle/model.js +1 -1
- package/dist/foundation/release/bootstrap.d.ts +1 -3
- package/dist/foundation/release/bootstrap.js +1 -1
- package/dist/foundation/storage/control-store.js +37 -3
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +55 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +778 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +44 -6
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +14 -5
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -2
- package/dist/integrations/pi/components/shell-presenters-transcript.js +9 -8
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +41 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +3 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +13 -0
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +27 -0
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +10 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +36 -3
- package/dist/integrations/pi/engine/adapter.js +21 -3
- package/dist/integrations/pi/engine/session-integration.d.ts +2 -1
- package/dist/integrations/pi/engine/session-integration.js +17 -3
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +23 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +242 -0
- package/dist/integrations/pi/session-ui/route-host.d.ts +5 -0
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.d.ts +34 -5
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell-root.js +294 -45
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.d.ts +1 -1
- package/dist/integrations/pi/{owned-ui → session-ui}/session-shell.js +167 -24
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +39 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +369 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +11 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +101 -0
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -1
- package/dist/integrations/pi/tui-runtime/adapter.js +51 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -6
- 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/product-identity.d.ts +2 -2
- package/dist/product-identity.js +3 -4
- package/dist/product-identity.json +3 -5
- package/dist/ui/apps/contracts.d.ts +23 -1
- package/dist/ui/apps/host.js +9 -1
- package/dist/ui/components/index.d.ts +3 -0
- package/dist/ui/components/index.js +3 -0
- package/dist/ui/components/mouse.d.ts +10 -0
- package/dist/ui/components/mouse.js +23 -0
- package/dist/ui/components/prompt-row.d.ts +14 -0
- package/dist/ui/components/prompt-row.js +20 -0
- package/dist/ui/components/scrollbar.d.ts +26 -0
- package/dist/ui/components/scrollbar.js +35 -0
- package/dist/ui/components/spans.d.ts +21 -0
- package/dist/ui/components/spans.js +178 -0
- package/dist/ui/components/submitted-prompt.d.ts +23 -0
- package/dist/ui/components/submitted-prompt.js +50 -0
- package/dist/ui/components/text-selection.d.ts +53 -0
- package/dist/ui/components/text-selection.js +168 -0
- package/dist/ui/components/text.d.ts +14 -0
- package/dist/ui/components/text.js +40 -0
- package/dist/ui/components/transcript-viewport.d.ts +104 -0
- package/dist/ui/components/transcript-viewport.js +381 -0
- package/dist/ui/settings/declarations.d.ts +8 -1
- package/dist/ui/settings/declarations.js +33 -5
- package/dist/ui/settings/migrations.d.ts +1 -1
- package/dist/ui/settings/migrations.js +21 -2
- package/dist/ui/settings/sections.js +15 -8
- package/dist/ui/settings/session.d.ts +2 -0
- package/dist/ui/settings/session.js +7 -5
- package/docs/architecture/boundaries.md +3 -3
- package/docs/architecture/project-structure.md +2 -2
- package/docs/architecture/resource-and-data-policy.md +1 -1
- package/docs/architecture/toolchain.md +5 -5
- package/docs/architecture/ui-reference-provenance.md +11 -2
- package/docs/features/launch-profiles.md +7 -14
- package/docs/manual-launch-instance-acceptance.md +12 -35
- package/docs/manual-owned-ui-checkpoint.md +2 -3
- package/docs/manual-pi-boundary-candidate.md +4 -5
- package/package.json +4 -2
- package/dist/features/launch/profiles.d.ts +0 -12
- package/dist/features/launch/profiles.js +0 -26
- package/dist/integrations/pi/owned-ui/route-host.d.ts +0 -29
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.d.ts +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/index.js +0 -0
- /package/dist/integrations/pi/{owned-ui → session-ui}/route-host.js +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { backgroundSgrSpan, composeSubmittedPromptRows, displayWidth, formatSubmittedPromptTime, heldNativeHyperlinkStyle, hyperlinkSgrSpan, nativeHyperlinkStyle, overlaySpan, submittedPromptLayout, stripAnsi, } from "../../../ui/components/index.js";
|
|
2
2
|
import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../engine/index.js";
|
|
3
|
-
import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellStatus, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, onPiThemeChange, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
|
|
3
|
+
import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellStatus, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, onPiThemeChange, piShellVisibleWidth, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
|
|
4
4
|
import { PiTuiRuntimeAdapter, } from "../tui-runtime/index.js";
|
|
5
|
+
import { PromptChipStore } from "./prompt-chips.js";
|
|
6
|
+
import { SessionViewportController } from "./session-viewport-controller.js";
|
|
5
7
|
export class OwnedUiSessionShellRoot {
|
|
6
8
|
editor;
|
|
7
9
|
header;
|
|
@@ -10,6 +12,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
10
12
|
#transcript = new Map();
|
|
11
13
|
#blocksById = new Map();
|
|
12
14
|
#renderedRows = new Map();
|
|
15
|
+
/** Flattened transcript rows reused by wheel, rail, jump, and selection frames. */
|
|
16
|
+
#documentLayouts = new Map();
|
|
13
17
|
#themeUnsubscribe;
|
|
14
18
|
#transcriptOrder = [];
|
|
15
19
|
#view;
|
|
@@ -17,6 +21,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
17
21
|
#footer;
|
|
18
22
|
#queued;
|
|
19
23
|
#extensionRenderers;
|
|
24
|
+
#promptChips = new PromptChipStore();
|
|
25
|
+
#customViewport;
|
|
26
|
+
#submittedPromptComposer;
|
|
27
|
+
#viewportController;
|
|
20
28
|
#componentRuntime;
|
|
21
29
|
#toolsExpanded = false;
|
|
22
30
|
#thinkingVisible = true;
|
|
@@ -34,8 +42,16 @@ export class OwnedUiSessionShellRoot {
|
|
|
34
42
|
constructor(view, cwd, handlers, startup = {}, agentDir, extensionRenderers = {
|
|
35
43
|
getMessageRenderer: () => undefined,
|
|
36
44
|
getToolDefinition: () => undefined,
|
|
37
|
-
}) {
|
|
45
|
+
}, sessionLayout = "pinned") {
|
|
38
46
|
this.#view = view;
|
|
47
|
+
this.#customViewport = sessionLayout === "custom-viewport";
|
|
48
|
+
this.#submittedPromptComposer = this.#customViewport
|
|
49
|
+
? {
|
|
50
|
+
layout: submittedPromptLayout,
|
|
51
|
+
compose: (rows, width, source, style) => composeSubmittedPromptRows(rows, width, source, style)
|
|
52
|
+
.map(row => nativeHyperlinkStyle(row, nativeTranscriptLinkColor)),
|
|
53
|
+
}
|
|
54
|
+
: undefined;
|
|
39
55
|
this.#cwd = cwd;
|
|
40
56
|
this.#extensionRenderers = extensionRenderers;
|
|
41
57
|
this.#componentRuntime = handlers;
|
|
@@ -43,13 +59,42 @@ export class OwnedUiSessionShellRoot {
|
|
|
43
59
|
this.resources = createPiShellLoadedResources(startup.resources ?? [], startup.expanded ?? false);
|
|
44
60
|
this.#status = createPiShellStatus(view, handlers);
|
|
45
61
|
this.#footer = createPiShellFooter(this.#viewWithExtensionStatuses(view), cwd);
|
|
46
|
-
this.#queued = createPiQueuedInputStatus(view.editor.queuedSubmissions);
|
|
62
|
+
this.#queued = createPiQueuedInputStatus(view.editor.queuedSubmissions, this.#customViewport ? "custom-viewport" : "pinned");
|
|
47
63
|
this.editor = createPiShellEditor({
|
|
48
64
|
...handlers,
|
|
65
|
+
keybindingProfile: this.#customViewport ? "a1" : "pi",
|
|
66
|
+
paintEditorSelection: (line, from, to, atomic) => backgroundSgrSpan(line, from, to, atomic ? "\u001b[7m" : "\u001b[27m\u001b[48;2;38;79;120m", atomic ? "\u001b[27m" : "\u001b[49m", piShellVisibleWidth),
|
|
67
|
+
transformPastedContent: content => this.#promptChips.transformPastedContent(content),
|
|
68
|
+
editorAtomicRanges: line => this.#promptChips.atomicRanges(line),
|
|
69
|
+
decorateEditorRow: (row, width) => {
|
|
70
|
+
const plain = stripAnsi(row);
|
|
71
|
+
const ranges = this.#promptChips.hyperlinkRanges(plain);
|
|
72
|
+
if (ranges.length === 0)
|
|
73
|
+
return row;
|
|
74
|
+
const linkResetAndTail = `\u001b]8;;\u001b\\\u001b[24m${" ".repeat(Math.max(0, width - piShellVisibleWidth(row)))}`;
|
|
75
|
+
// VS15 is zero-column and default-ignorable. It breaks Windows Terminal's
|
|
76
|
+
// plain-text URL detector only in the held-button paint; semantic text stays exact.
|
|
77
|
+
const paintRow = this.#viewportController.editorPointerSelecting
|
|
78
|
+
? row.replaceAll("https://", "https:\uFE0E//").replaceAll("http://", "http:\uFE0E//")
|
|
79
|
+
: row;
|
|
80
|
+
const decorated = this.#viewportController.editorPointerSelecting
|
|
81
|
+
? ranges.reduce((result, range) => backgroundSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), "\u001b[4:4m", "\u001b[24m", piShellVisibleWidth), paintRow)
|
|
82
|
+
: ranges.reduce((result, range) => hyperlinkSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), range.target, piShellVisibleWidth), row);
|
|
83
|
+
// Windows Terminal can retain stale native dotted-link cells when a mutable
|
|
84
|
+
// prompt replaces a longer URL. Explicit non-link spaces overwrite that tail.
|
|
85
|
+
return `${decorated}${linkResetAndTail}`;
|
|
86
|
+
},
|
|
87
|
+
expandCopiedEditorText: text => this.#promptChips.expandCopiedText(text),
|
|
49
88
|
cwd,
|
|
50
89
|
...(agentDir === undefined ? {} : { agentDir }),
|
|
51
90
|
onToolsExpand: () => this.#setToolsExpanded(!this.#toolsExpanded),
|
|
52
91
|
});
|
|
92
|
+
this.#viewportController = new SessionViewportController({
|
|
93
|
+
enabled: this.#customViewport,
|
|
94
|
+
editor: this.editor,
|
|
95
|
+
requestRender: force => this.#componentRuntime.requestRender(force),
|
|
96
|
+
hasEditorLinks: () => this.#promptChips.hyperlinkRanges(this.editor.getText()).length > 0,
|
|
97
|
+
});
|
|
53
98
|
this.editor.setThinkingLevel(view.thinkingLevel);
|
|
54
99
|
this.#inputSurface = this.editor;
|
|
55
100
|
for (const block of view.transcript) {
|
|
@@ -59,9 +104,19 @@ export class OwnedUiSessionShellRoot {
|
|
|
59
104
|
this.#syncTranscript(view.transcript);
|
|
60
105
|
// Colours come from the active theme, so rendered rows outlive their revision only
|
|
61
106
|
// until the theme under them changes.
|
|
62
|
-
this.#themeUnsubscribe = onPiThemeChange(() =>
|
|
107
|
+
this.#themeUnsubscribe = onPiThemeChange(() => {
|
|
108
|
+
this.#renderedRows.clear();
|
|
109
|
+
this.#documentLayouts.clear();
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
preparePromptSubmission(text) {
|
|
113
|
+
return this.#promptChips.prepareSubmission(text);
|
|
63
114
|
}
|
|
64
115
|
update(view) {
|
|
116
|
+
if (view.diagnostics.length !== this.#view.diagnostics.length
|
|
117
|
+
|| view.diagnostics.some((diagnostic, index) => diagnostic.sequence !== this.#view.diagnostics[index]?.sequence)) {
|
|
118
|
+
this.#documentLayouts.clear();
|
|
119
|
+
}
|
|
65
120
|
this.#view = view;
|
|
66
121
|
this.#status.update(view);
|
|
67
122
|
this.#footer.update(this.#viewWithExtensionStatuses(view));
|
|
@@ -80,7 +135,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
80
135
|
this.#blocksById.set(block.id, block);
|
|
81
136
|
const component = this.#transcript.get(block.id);
|
|
82
137
|
if (component === undefined) {
|
|
83
|
-
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers);
|
|
138
|
+
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer);
|
|
84
139
|
created.setExpanded(this.#toolsExpanded);
|
|
85
140
|
this.#transcript.set(block.id, created);
|
|
86
141
|
this.#transcriptOrder.push(block.id);
|
|
@@ -91,18 +146,112 @@ export class OwnedUiSessionShellRoot {
|
|
|
91
146
|
// One chunk touches one block, and the updated component tracks its own dirtiness.
|
|
92
147
|
// Invalidating the whole shell here would re-wrap the entire transcript per chunk.
|
|
93
148
|
this.#renderedRows.delete(block.id);
|
|
149
|
+
this.#documentLayouts.clear();
|
|
94
150
|
}
|
|
95
151
|
render(width) {
|
|
152
|
+
if (!this.#customViewport) {
|
|
153
|
+
this.#viewportController.setEditorPointerFrame(undefined);
|
|
154
|
+
return [...this.#renderDocument(width), ...this.#renderDock(width)];
|
|
155
|
+
}
|
|
156
|
+
const height = Math.max(0, this.#componentRuntime.getRows());
|
|
157
|
+
const dock = this.#renderDockLayout(width);
|
|
158
|
+
const availableWithoutTransient = Math.max(0, height - Math.min(height, dock.rowsWithoutTransient.length));
|
|
159
|
+
// Ordinary transcript content uses the full terminal width. The rail is an
|
|
160
|
+
// overlay, not a lost wrapping cell. Submitted prompts alone retain the
|
|
161
|
+
// intentional final gutter after their right-aligned timestamp.
|
|
162
|
+
const documentWidth = width;
|
|
163
|
+
let document = this.#renderDocumentLayout(documentWidth);
|
|
164
|
+
// Keep queued steering/follow-up messages and Working together in Pi's
|
|
165
|
+
// semantic order while content fits. Once the transcript overflows, move
|
|
166
|
+
// the complete transient group into its tail so every row naturally
|
|
167
|
+
// scrolls away and the detached viewport can use the full space above the
|
|
168
|
+
// stable editor/footer dock.
|
|
169
|
+
const pinTransient = document.rows.length + dock.transientRows.length <= availableWithoutTransient;
|
|
170
|
+
const dockRows = pinTransient ? dock.rows : dock.rowsWithoutTransient;
|
|
171
|
+
const selectableDocumentRowCount = document.rows.length;
|
|
172
|
+
if (!pinTransient)
|
|
173
|
+
document = { ...document, rows: [...document.rows, ...dock.transientRows] };
|
|
174
|
+
const dockStartRow = height - dockRows.length + 1;
|
|
175
|
+
const editorOffset = pinTransient ? dock.editorOffsetWithTransient : dock.editorOffsetWithoutTransient;
|
|
176
|
+
this.#viewportController.setEditorPointerFrame(this.usesDefaultInputSurface()
|
|
177
|
+
? {
|
|
178
|
+
rowStart: dockStartRow + editorOffset,
|
|
179
|
+
rowEnd: dockStartRow + editorOffset + dock.inputRows - 1,
|
|
180
|
+
}
|
|
181
|
+
: undefined);
|
|
182
|
+
return this.#viewportController.compose({
|
|
183
|
+
documentRows: document.rows,
|
|
184
|
+
...(this.#viewportController.transcriptPointerSelecting
|
|
185
|
+
? { paintDocumentRow: heldNativeHyperlinkStyle }
|
|
186
|
+
: {}),
|
|
187
|
+
// Overflowing queued and Working rows scroll with the transcript but
|
|
188
|
+
// remain status chrome: selection and copying stop at the real document tail.
|
|
189
|
+
selectableDocumentRowCount,
|
|
190
|
+
dockRows,
|
|
191
|
+
promptAnchors: document.promptAnchors,
|
|
192
|
+
width,
|
|
193
|
+
height,
|
|
194
|
+
// Anchor above the stable editor/widget/footer group. Queued input,
|
|
195
|
+
// working states, and transient notifications may grow above it without
|
|
196
|
+
// moving the floating control up and down the terminal.
|
|
197
|
+
bottomControlRow: Math.max(0, height - Math.min(height, dock.stableBottomRows) - 1),
|
|
198
|
+
theme: {
|
|
199
|
+
// Match the v2 rail: a continuously dim track and an accent thumb at
|
|
200
|
+
// rest, thickening on hover/drag through the presentation glyph. Reset
|
|
201
|
+
// inherited text decorations so dim transcript rows cannot dull the
|
|
202
|
+
// thumb; the row background deliberately remains intact.
|
|
203
|
+
track: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("dim", text)}`,
|
|
204
|
+
thumb: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("accent", text)}`,
|
|
205
|
+
sticky: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", withoutTerminalBackground(text))),
|
|
206
|
+
quietSticky: text => `\u001b[2m${text.replace(/\u001b\[(?:0|22)m/g, "$&\u001b[2m")}\u001b[22m`,
|
|
207
|
+
bottomControl: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", text)),
|
|
208
|
+
// Selection paints only the familiar dark-blue background. Source
|
|
209
|
+
// foreground roles (including links, warnings, muted text, and bold
|
|
210
|
+
// intensity) remain exactly as rendered instead of being inverted.
|
|
211
|
+
selection: (line, from, to) => backgroundSgrSpan(line, from, to, "\u001b[48;2;38;79;120m", "\u001b[49m"),
|
|
212
|
+
},
|
|
213
|
+
}).rows;
|
|
214
|
+
}
|
|
215
|
+
setViewportConfig(config) {
|
|
216
|
+
if (this.#viewportController.setConfig(config))
|
|
217
|
+
this.#documentLayouts.clear();
|
|
218
|
+
}
|
|
219
|
+
resumeViewportFollowing() {
|
|
220
|
+
this.#viewportController.resumeFollowing();
|
|
221
|
+
}
|
|
222
|
+
noteCompletedAssistantMessage() {
|
|
223
|
+
this.#viewportController.noteCompletedAssistantMessage();
|
|
224
|
+
}
|
|
225
|
+
resetViewport() {
|
|
226
|
+
this.#viewportController.reset();
|
|
227
|
+
}
|
|
228
|
+
clearViewportPointerState() {
|
|
229
|
+
this.#viewportController.clearPointerState();
|
|
230
|
+
}
|
|
231
|
+
handleViewportPreInput(data, allowWheel = true, now = Date.now()) {
|
|
232
|
+
return this.#viewportController.handlePreInput(data, allowWheel, now);
|
|
233
|
+
}
|
|
234
|
+
#renderDock(width) {
|
|
235
|
+
return this.#renderDockLayout(width).rows;
|
|
236
|
+
}
|
|
237
|
+
#renderDockLayout(width) {
|
|
96
238
|
const queued = this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
239
|
+
const statusRows = this.#renderStatus(width);
|
|
240
|
+
const transientRows = [...queued, ...statusRows];
|
|
241
|
+
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
242
|
+
const input = this.#inputSurface.render(width);
|
|
243
|
+
const belowWidgets = this.#renderWidgets("belowEditor", width);
|
|
244
|
+
const footer = this.#renderFooter(width);
|
|
245
|
+
const rowsWithoutTransient = [...aboveWidgets, ...input, ...belowWidgets, ...footer];
|
|
246
|
+
return {
|
|
247
|
+
rows: [...transientRows, ...rowsWithoutTransient],
|
|
248
|
+
rowsWithoutTransient,
|
|
249
|
+
transientRows,
|
|
250
|
+
stableBottomRows: rowsWithoutTransient.length,
|
|
251
|
+
editorOffsetWithTransient: transientRows.length + aboveWidgets.length,
|
|
252
|
+
editorOffsetWithoutTransient: aboveWidgets.length,
|
|
253
|
+
inputRows: input.length,
|
|
254
|
+
};
|
|
106
255
|
}
|
|
107
256
|
layoutRoot() {
|
|
108
257
|
const document = layoutPort(width => this.#renderDocument(width), () => this.invalidate());
|
|
@@ -154,23 +303,66 @@ export class OwnedUiSessionShellRoot {
|
|
|
154
303
|
};
|
|
155
304
|
}
|
|
156
305
|
#renderDocument(width) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return
|
|
165
|
-
});
|
|
306
|
+
return this.#renderDocumentLayout(width).rows;
|
|
307
|
+
}
|
|
308
|
+
#renderDocumentLayout(width) {
|
|
309
|
+
// Extension headers may animate independently of transcript revisions, so
|
|
310
|
+
// only the stable built-in document participates in this frame cache.
|
|
311
|
+
const cached = this.#extensionHeader === null ? this.#documentLayouts.get(width) : undefined;
|
|
312
|
+
if (cached !== undefined)
|
|
313
|
+
return cached;
|
|
166
314
|
const diagnostics = this.#view.diagnostics;
|
|
167
315
|
const startupRows = diagnostics
|
|
168
316
|
.filter(diagnostic => diagnostic.code === "engine-startup")
|
|
169
317
|
.flatMap(diagnostic => renderPiShellStartupDiagnostic(diagnostic, width));
|
|
170
|
-
const
|
|
318
|
+
const resourceRows = this.#customViewport ? [] : [...this.resources.render(width)];
|
|
319
|
+
if (resourceRows.at(-1) === "")
|
|
320
|
+
resourceRows.pop();
|
|
321
|
+
const headerRows = this.#extensionHeader !== null
|
|
322
|
+
? this.#extensionHeader.render(width)
|
|
323
|
+
: this.#customViewport ? [] : this.header.render(width);
|
|
324
|
+
const rows = [
|
|
325
|
+
...startupRows,
|
|
326
|
+
...headerRows,
|
|
327
|
+
...resourceRows,
|
|
328
|
+
];
|
|
329
|
+
const promptAnchors = [];
|
|
330
|
+
for (let index = 0; index < this.#transcriptOrder.length; index += 1) {
|
|
331
|
+
const id = this.#transcriptOrder[index];
|
|
332
|
+
const block = this.#blocksById.get(id);
|
|
333
|
+
if (!this.#thinkingVisible && block?.kind === "thinking")
|
|
334
|
+
continue;
|
|
335
|
+
const blockWidth = this.#customViewport
|
|
336
|
+
&& block?.kind === "user"
|
|
337
|
+
&& this.#viewportController.config.scrollbarAppearance !== "hidden"
|
|
338
|
+
&& width > 1
|
|
339
|
+
? width - 1
|
|
340
|
+
: width;
|
|
341
|
+
const blockRows = this.#blockRows(id, block, blockWidth);
|
|
342
|
+
if (block?.kind === "user") {
|
|
343
|
+
// The first natural prompt gets one breathing row at the document top.
|
|
344
|
+
// Once scrolling advances, the prompt itself reaches row zero and then
|
|
345
|
+
// becomes sticky there, so the spacer is never pinned with it.
|
|
346
|
+
if (this.#customViewport && index === 0)
|
|
347
|
+
rows.push("");
|
|
348
|
+
else if (index > 0)
|
|
349
|
+
rows.push("");
|
|
350
|
+
}
|
|
351
|
+
const firstRow = rows.length;
|
|
352
|
+
rows.push(...blockRows);
|
|
353
|
+
if (block?.kind === "user" && blockRows[0] !== undefined) {
|
|
354
|
+
promptAnchors.push({
|
|
355
|
+
id: block.id,
|
|
356
|
+
firstRow,
|
|
357
|
+
lastRow: Math.max(firstRow, rows.length - 1),
|
|
358
|
+
sourceRow: pinnedPromptSourceRow(block, blockRows[0], blockWidth),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
rows.push(...diagnostics
|
|
171
363
|
.filter(diagnostic => diagnostic.code === "package-updates")
|
|
172
|
-
.flatMap(diagnostic => renderPiShellPackageUpdateNotice(diagnostic.message.split("\n").filter(line => line.startsWith("- ")).map(line => line.slice(2)), width));
|
|
173
|
-
|
|
364
|
+
.flatMap(diagnostic => renderPiShellPackageUpdateNotice(diagnostic.message.split("\n").filter(line => line.startsWith("- ")).map(line => line.slice(2)), width)));
|
|
365
|
+
rows.push(...diagnostics
|
|
174
366
|
.filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "package-updates")
|
|
175
367
|
.slice(-3)
|
|
176
368
|
.flatMap(diagnostic => renderPiShellTranscriptBlock({
|
|
@@ -181,18 +373,15 @@ export class OwnedUiSessionShellRoot {
|
|
|
181
373
|
title: diagnostic.code,
|
|
182
374
|
text: diagnostic.message,
|
|
183
375
|
payload: {},
|
|
184
|
-
}, width, this.#cwd));
|
|
185
|
-
const
|
|
186
|
-
if (
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
...packageUpdateRows,
|
|
194
|
-
...diagnosticRows,
|
|
195
|
-
];
|
|
376
|
+
}, width, this.#cwd)));
|
|
377
|
+
const layout = { rows: Object.freeze(rows), promptAnchors: Object.freeze(promptAnchors) };
|
|
378
|
+
if (this.#extensionHeader === null) {
|
|
379
|
+
this.#documentLayouts.set(width, layout);
|
|
380
|
+
// The custom viewport commonly probes full width and reserved-rail width.
|
|
381
|
+
while (this.#documentLayouts.size > 2)
|
|
382
|
+
this.#documentLayouts.delete(this.#documentLayouts.keys().next().value);
|
|
383
|
+
}
|
|
384
|
+
return layout;
|
|
196
385
|
}
|
|
197
386
|
/**
|
|
198
387
|
* Rows for one transcript block. A finalized block renders once for a given revision
|
|
@@ -204,13 +393,28 @@ export class OwnedUiSessionShellRoot {
|
|
|
204
393
|
const component = this.#transcript.get(id);
|
|
205
394
|
if (component === undefined)
|
|
206
395
|
return [];
|
|
396
|
+
const render = () => {
|
|
397
|
+
const rows = component.render(width);
|
|
398
|
+
if (!this.#customViewport || block?.kind === "user")
|
|
399
|
+
return rows;
|
|
400
|
+
return rows.map(row => nativeHyperlinkStyle(row, nativeTranscriptLinkColor));
|
|
401
|
+
};
|
|
207
402
|
if (block === undefined || block.status !== "finalized")
|
|
208
|
-
return
|
|
209
|
-
|
|
210
|
-
|
|
403
|
+
return render();
|
|
404
|
+
let byWidth = this.#renderedRows.get(id);
|
|
405
|
+
const cached = byWidth?.get(width);
|
|
406
|
+
if (cached?.revision === block.revision)
|
|
211
407
|
return cached.rows;
|
|
212
|
-
const rows =
|
|
213
|
-
|
|
408
|
+
const rows = render();
|
|
409
|
+
if (byWidth === undefined) {
|
|
410
|
+
byWidth = new Map();
|
|
411
|
+
this.#renderedRows.set(id, byWidth);
|
|
412
|
+
}
|
|
413
|
+
byWidth.set(width, { revision: block.revision, rows });
|
|
414
|
+
// Bare A1 probes full width before reserving the overflowing rail column;
|
|
415
|
+
// retain both widths without turning resize history into an unbounded cache.
|
|
416
|
+
while (byWidth.size > 2)
|
|
417
|
+
byWidth.delete(byWidth.keys().next().value);
|
|
214
418
|
return rows;
|
|
215
419
|
}
|
|
216
420
|
transcriptComponent(id) {
|
|
@@ -222,6 +426,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
222
426
|
&& this.#transcriptOrder.at(-1) === previousId
|
|
223
427
|
&& this.#workflowStatusAnchors.get(previousId) === this.#view.transcript.length) {
|
|
224
428
|
this.#workflowStatusMessages.set(previousId, message);
|
|
429
|
+
this.#documentLayouts.clear();
|
|
225
430
|
this.invalidate();
|
|
226
431
|
return;
|
|
227
432
|
}
|
|
@@ -313,6 +518,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
313
518
|
}
|
|
314
519
|
toggleThinkingVisibility() {
|
|
315
520
|
this.#thinkingVisible = !this.#thinkingVisible;
|
|
521
|
+
this.#documentLayouts.clear();
|
|
316
522
|
this.invalidate();
|
|
317
523
|
}
|
|
318
524
|
get toolsExpanded() {
|
|
@@ -335,6 +541,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
335
541
|
if (this.#extensionHeader !== component)
|
|
336
542
|
this.#extensionHeader?.dispose?.();
|
|
337
543
|
this.#extensionHeader = component;
|
|
544
|
+
this.#documentLayouts.clear();
|
|
338
545
|
this.invalidate();
|
|
339
546
|
}
|
|
340
547
|
setExtensionFooter(component) {
|
|
@@ -366,6 +573,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
366
573
|
this.#workflowStatusMessages.clear();
|
|
367
574
|
this.#transcriptOrder = this.#transcriptOrder.filter(id => !id.startsWith("workflow-status-"));
|
|
368
575
|
this.#lastWorkflowStatusId = undefined;
|
|
576
|
+
this.#documentLayouts.clear();
|
|
369
577
|
this.invalidate();
|
|
370
578
|
}
|
|
371
579
|
resetExtensionUi() {
|
|
@@ -382,6 +590,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
382
590
|
this.#footer.update(this.#viewWithExtensionStatuses(this.#view));
|
|
383
591
|
// An extension renderer may have drawn transcript blocks that are now unrendered by it.
|
|
384
592
|
this.#renderedRows.clear();
|
|
593
|
+
this.#documentLayouts.clear();
|
|
385
594
|
this.invalidate();
|
|
386
595
|
}
|
|
387
596
|
addExtensionNotification(message, type) {
|
|
@@ -401,6 +610,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
401
610
|
onBranchChange: () => () => { },
|
|
402
611
|
};
|
|
403
612
|
}
|
|
613
|
+
usesDefaultInputSurface() {
|
|
614
|
+
return this.#inputSurface === this.editor;
|
|
615
|
+
}
|
|
404
616
|
setInputSurface(component, disposePrevious = true) {
|
|
405
617
|
const next = component ?? this.editor;
|
|
406
618
|
if (next === this.#inputSurface)
|
|
@@ -432,6 +644,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
432
644
|
this.#inputSurface.setFocused?.(focused);
|
|
433
645
|
}
|
|
434
646
|
dispose() {
|
|
647
|
+
this.clearViewportPointerState();
|
|
435
648
|
this.#themeUnsubscribe();
|
|
436
649
|
this.header.dispose?.();
|
|
437
650
|
this.resources.dispose?.();
|
|
@@ -439,6 +652,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
439
652
|
component.dispose?.();
|
|
440
653
|
this.#transcript.clear();
|
|
441
654
|
this.#renderedRows.clear();
|
|
655
|
+
this.#documentLayouts.clear();
|
|
442
656
|
if (this.#inputSurface !== this.editor)
|
|
443
657
|
this.#inputSurface.dispose?.();
|
|
444
658
|
this.#extensionHeader?.dispose?.();
|
|
@@ -452,6 +666,13 @@ export class OwnedUiSessionShellRoot {
|
|
|
452
666
|
this.#queued.dispose?.();
|
|
453
667
|
}
|
|
454
668
|
#syncTranscript(blocks) {
|
|
669
|
+
const previousIds = this.#transcriptOrder.filter(id => !id.startsWith("workflow-status-"));
|
|
670
|
+
const transcriptChanged = previousIds.length !== blocks.length || blocks.some((block, index) => {
|
|
671
|
+
const previous = this.#blocksById.get(block.id);
|
|
672
|
+
return previousIds[index] !== block.id || previous?.revision !== block.revision;
|
|
673
|
+
});
|
|
674
|
+
if (transcriptChanged)
|
|
675
|
+
this.#documentLayouts.clear();
|
|
455
676
|
this.#blocksById.clear();
|
|
456
677
|
for (const block of blocks)
|
|
457
678
|
this.#blocksById.set(block.id, block);
|
|
@@ -466,7 +687,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
466
687
|
for (const block of blocks) {
|
|
467
688
|
const component = this.#transcript.get(block.id);
|
|
468
689
|
if (component === undefined) {
|
|
469
|
-
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers);
|
|
690
|
+
const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer);
|
|
470
691
|
created.setExpanded(this.#toolsExpanded);
|
|
471
692
|
this.#transcript.set(block.id, created);
|
|
472
693
|
}
|
|
@@ -508,6 +729,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
508
729
|
this.#transcript.set(id, component);
|
|
509
730
|
this.#workflowStatusAnchors.set(id, this.#view.transcript.length);
|
|
510
731
|
this.#transcriptOrder.push(id);
|
|
732
|
+
this.#documentLayouts.clear();
|
|
511
733
|
this.invalidate();
|
|
512
734
|
return id;
|
|
513
735
|
}
|
|
@@ -555,9 +777,36 @@ export class OwnedUiSessionShellRoot {
|
|
|
555
777
|
component.setExpanded(expanded);
|
|
556
778
|
// Expansion changes what a block draws without changing its revision.
|
|
557
779
|
this.#renderedRows.clear();
|
|
780
|
+
this.#documentLayouts.clear();
|
|
558
781
|
this.invalidate();
|
|
559
782
|
}
|
|
560
783
|
}
|
|
784
|
+
const SCROLLBAR_CELL_RESET = "\u001b[22;23;24;25;27;28;29;39;54;55m";
|
|
785
|
+
const TERMINAL_BACKGROUND = /\u001b\[(?:4[0-9]|10[0-7]|48(?:[;:][0-9;:]*)?)m/g;
|
|
786
|
+
/** Web URLs use link blue; file targets retain A1's cyan interactive accent. */
|
|
787
|
+
function nativeTranscriptLinkColor(text, target) {
|
|
788
|
+
return piTheme().fg(/^file:/iu.test(target) ? "accent" : "mdLink", text);
|
|
789
|
+
}
|
|
790
|
+
/** Repaint a source prompt with viewport chrome while preserving text, links, and foreground roles. */
|
|
791
|
+
function withoutTerminalBackground(text) {
|
|
792
|
+
return text.replace(TERMINAL_BACKGROUND, "");
|
|
793
|
+
}
|
|
794
|
+
/** A pinned timestamp is content now, not secondary transcript metadata. */
|
|
795
|
+
function pinnedPromptSourceRow(block, sourceRow, width) {
|
|
796
|
+
if (typeof block.payload !== "object" || block.payload === null)
|
|
797
|
+
return sourceRow;
|
|
798
|
+
const value = block.payload.timestamp;
|
|
799
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
800
|
+
return sourceRow;
|
|
801
|
+
const timestamp = formatSubmittedPromptTime(value);
|
|
802
|
+
if (timestamp === null || submittedPromptLayout(width, value).timestamp === null)
|
|
803
|
+
return sourceRow;
|
|
804
|
+
const rowWidth = displayWidth(sourceRow);
|
|
805
|
+
const timestampWidth = displayWidth(timestamp);
|
|
806
|
+
if (rowWidth < timestampWidth)
|
|
807
|
+
return sourceRow;
|
|
808
|
+
return overlaySpan(sourceRow, rowWidth - timestampWidth, rowWidth, piTheme().fg("userMessageText", timestamp));
|
|
809
|
+
}
|
|
561
810
|
function layoutPort(render, invalidate, handleInput) {
|
|
562
811
|
return {
|
|
563
812
|
render,
|
|
@@ -16,7 +16,7 @@ export declare class OwnedUiSessionShell {
|
|
|
16
16
|
waitUntilStopped(): Promise<void>;
|
|
17
17
|
submit(text: string): Promise<AdapterCommandResult>;
|
|
18
18
|
clearOrExit(now?: number): Promise<AdapterCommandResult>;
|
|
19
|
-
interrupt(): Promise<AdapterCommandResult>;
|
|
19
|
+
interrupt(now?: number): Promise<AdapterCommandResult>;
|
|
20
20
|
abort(): Promise<AdapterCommandResult>;
|
|
21
21
|
retry(): Promise<AdapterCommandResult>;
|
|
22
22
|
compact(): Promise<AdapterCommandResult>;
|