@timurproko/a1 0.1.8-dev.299 → 0.1.8-dev.322
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 +16 -4
- package/bin/guardian.js +4 -3
- package/bin/ui.js +4 -2
- package/bin/update-recovery.js +3 -1
- package/dist/composition/owned-ui.js +2 -1
- package/dist/features/launch/profile-paths.d.ts +2 -0
- package/dist/features/launch/profile-paths.js +11 -0
- package/dist/foundation/launch-context/index.d.ts +29 -0
- package/dist/foundation/launch-context/index.js +83 -0
- package/dist/foundation/launch-guardian/main.js +4 -3
- package/dist/foundation/release/bootstrap.d.ts +1 -1
- package/dist/foundation/release/bootstrap.js +18 -12
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +4 -0
- package/dist/foundation/release/release-gc.js +2 -2
- package/dist/foundation/release/release-store.js +10 -3
- package/dist/foundation/release/release.d.ts +3 -1
- package/dist/foundation/release/release.js +7 -3
- package/dist/foundation/release/restart-certification.d.ts +1 -0
- package/dist/foundation/release/restart-certification.js +5 -0
- package/dist/foundation/release/update-recovery.d.ts +2 -0
- package/dist/foundation/release/update-recovery.js +4 -0
- package/dist/foundation/release/update.js +5 -0
- package/dist/foundation/release/warmup.d.ts +1 -1
- package/dist/foundation/release/warmup.js +5 -4
- package/dist/foundation/startup/startup-runtime.js +13 -11
- package/dist/foundation/supervision/main.js +8 -7
- package/dist/integrations/pi/components/owned-editor-ux.d.ts +1 -0
- package/dist/integrations/pi/components/owned-editor-ux.js +54 -3
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +3 -0
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +9 -2
- package/dist/integrations/pi/components/shell-presenters-info.js +35 -4
- package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +3 -2
- package/dist/integrations/pi/components/shell-selectors-dialogs.js +8 -5
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -2
- package/dist/integrations/pi/components/upstream/assets/earendil-image.json +12 -0
- package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +25 -2
- package/dist/integrations/pi/engine/adapter.d.ts +13 -1
- package/dist/integrations/pi/engine/adapter.js +86 -25
- package/dist/integrations/pi/engine/changelog.d.ts +2 -0
- package/dist/integrations/pi/engine/changelog.js +63 -0
- package/dist/integrations/pi/engine/resources/changelog.json +12 -0
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
- package/dist/integrations/pi/engine/workflows.d.ts +1 -1
- package/dist/integrations/pi/session-ui/prompt-chips.d.ts +3 -1
- package/dist/integrations/pi/session-ui/prompt-chips.js +37 -13
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +25 -0
- package/dist/integrations/pi/session-ui/session-shell-root.js +65 -74
- package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell.js +58 -7
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +21 -15
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +7 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +7 -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 +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/product-identity.d.ts +1 -1
- package/dist/product-identity.js +2 -35
- package/dist/product-identity.json +0 -22
- package/dist/ui/components/transcript-viewport.d.ts +8 -0
- package/dist/ui/components/transcript-viewport.js +23 -2
- package/dist/ui/components/visible-hyperlinks.d.ts +7 -0
- package/dist/ui/components/visible-hyperlinks.js +1 -0
- package/dist/ui/settings/declarations.js +1 -0
- package/dist/ui/settings/sections.js +11 -1
- package/docs/architecture/boundaries.md +2 -1
- package/docs/architecture/internal-naming.md +89 -0
- package/docs/architecture/resource-and-data-policy.md +9 -4
- package/docs/architecture/toolchain.md +15 -1
- package/docs/features/prompt-history.md +39 -11
- package/package.json +8 -3
|
@@ -17,5 +17,5 @@ export declare class PiWorkflowControllerPort implements AgentWorkflowPort {
|
|
|
17
17
|
listWorkflows(): Promise<readonly AgentWorkflowDescriptor[]>;
|
|
18
18
|
executeWorkflow(workflowId: string, input: AgentJsonValue, signal?: AbortSignal): Promise<AgentJsonValue>;
|
|
19
19
|
}
|
|
20
|
-
export declare const PI_BUILTIN_WORKFLOW_ROUTES: readonly ("name" | "share" | "model" | "login" | "logout" | "settings" | "reload" | "compact" | "new" | "resume" | "quit" | "session" | "scoped-models" | "export" | "import" | "copy" | "changelog" | "hotkeys" | "fork" | "clone" | "
|
|
20
|
+
export declare const PI_BUILTIN_WORKFLOW_ROUTES: readonly ("name" | "share" | "model" | "login" | "logout" | "settings" | "reload" | "compact" | "new" | "resume" | "quit" | "session" | "tree" | "scoped-models" | "export" | "import" | "copy" | "changelog" | "hotkeys" | "fork" | "clone" | "trust" | "debug" | "arminsayshi" | "dementedelves")[];
|
|
21
21
|
export declare const PI_WORKFLOW_CAPABILITIES: readonly WorkflowCapability[];
|
|
@@ -178,7 +178,7 @@ export interface PiWorkflowHost {
|
|
|
178
178
|
readonly stdout: string;
|
|
179
179
|
readonly stderr: string;
|
|
180
180
|
}>;
|
|
181
|
-
readChangelog(): Promise<string>;
|
|
181
|
+
readChangelog(sinceVersion?: string): Promise<string>;
|
|
182
182
|
}
|
|
183
183
|
export interface PiBashWorkflowResult {
|
|
184
184
|
readonly command: string;
|
|
@@ -11,7 +11,7 @@ export interface PreparedPrompt {
|
|
|
11
11
|
/** Owns semantic chips and bounded pending paste references; cancels background work on reset or disposal. */
|
|
12
12
|
export declare class PromptChipStore {
|
|
13
13
|
#private;
|
|
14
|
-
beginPaste(currentText: string, read: (signal: AbortSignal) => Promise<PiShellClipboardContent | null>, onError: (error: unknown) => void): {
|
|
14
|
+
beginPaste(currentText: string, read: (signal: AbortSignal) => Promise<PiShellClipboardContent | null>, onError: (error: unknown) => void, onImage?: () => void): {
|
|
15
15
|
marker: string;
|
|
16
16
|
result: Promise<string>;
|
|
17
17
|
};
|
|
@@ -21,6 +21,8 @@ export declare class PromptChipStore {
|
|
|
21
21
|
resetPastes(text: string): string;
|
|
22
22
|
dispose(): Promise<void>;
|
|
23
23
|
transformPastedContent(content: PiShellClipboardContent, currentText?: string): string;
|
|
24
|
+
/** Hide provisional clipboard identities until the read identifies an actual image. */
|
|
25
|
+
hiddenRanges(line: string): readonly PiShellEditorTextRange[];
|
|
24
26
|
atomicRanges(line: string): readonly PiShellEditorTextRange[];
|
|
25
27
|
hyperlinkRanges(text: string): readonly {
|
|
26
28
|
start: number;
|
|
@@ -15,27 +15,32 @@ export class PromptChipStore {
|
|
|
15
15
|
#pending = new Map();
|
|
16
16
|
#preparation = new ImagePreparationClient();
|
|
17
17
|
#stopping = new Set();
|
|
18
|
-
beginPaste(currentText, read, onError) {
|
|
19
|
-
const
|
|
18
|
+
beginPaste(currentText, read, onError, onImage = () => { }) {
|
|
19
|
+
const id = randomBytes(5).toString("hex");
|
|
20
|
+
// Invariant: reserve paste identity immediately, but only display a screenshot after identifying an image.
|
|
21
|
+
const marker = `[📷 screenshot-${id}]`;
|
|
20
22
|
const job = this.#preparation.start(async (signal) => {
|
|
21
23
|
const content = await read(signal);
|
|
22
24
|
entry.kind = content?.kind === "image" ? "image" : "text";
|
|
23
|
-
if (content?.kind === "image"
|
|
24
|
-
|
|
25
|
+
if (content?.kind === "image") {
|
|
26
|
+
onImage();
|
|
27
|
+
if (this.#imageCount(currentText) >= 8)
|
|
28
|
+
throw new ImageAttachmentError("image-count");
|
|
29
|
+
}
|
|
25
30
|
return content;
|
|
26
31
|
});
|
|
27
32
|
const entry = { marker, job, references: 0, kind: "unknown", completion: job.result.then(content => {
|
|
28
33
|
if (content === null)
|
|
29
34
|
return "";
|
|
30
35
|
if (content.kind === "image") {
|
|
31
|
-
return this.#addPreparedImage(content);
|
|
36
|
+
return this.#addPreparedImage(content, id);
|
|
32
37
|
}
|
|
33
38
|
return this.transformPastedContent(content);
|
|
34
39
|
}).catch(error => {
|
|
35
40
|
entry.error = error instanceof ImageAttachmentError ? error : new ImageAttachmentError("image-codec");
|
|
36
41
|
if (entry.error.code !== "image-canceled" && entry.references === 0)
|
|
37
42
|
onError(entry.error);
|
|
38
|
-
return marker.replace("
|
|
43
|
+
return marker.replace("screenshot-", "failed-");
|
|
39
44
|
}).then(replacement => {
|
|
40
45
|
entry.replacement = replacement;
|
|
41
46
|
return replacement;
|
|
@@ -95,12 +100,17 @@ export class PromptChipStore {
|
|
|
95
100
|
}
|
|
96
101
|
async dispose() { await Promise.all([this.#preparation.dispose(), ...this.#stopping]); }
|
|
97
102
|
#imageCount(text) {
|
|
98
|
-
const
|
|
99
|
-
|
|
103
|
+
const tags = new Set([...this.#chips.values()].filter(chip => chip.kind === "image" && text.includes(chip.tag)).map(chip => chip.tag));
|
|
104
|
+
for (const entry of this.#pending.values()) {
|
|
105
|
+
if (entry.kind !== "text" && (text.includes(entry.marker) || text.includes(entry.marker.replace("screenshot-", "failed-")))) {
|
|
106
|
+
// Invariant: a ready screenshot can retain its pending label but still occupies only one slot.
|
|
107
|
+
tags.add(entry.replacement ?? entry.marker);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return tags.size;
|
|
100
111
|
}
|
|
101
|
-
#addPreparedImage(image) {
|
|
102
|
-
const
|
|
103
|
-
const tag = `[📷 screenshot-${randomBytes(5).toString("hex")}${image.transformed ? "-resized" : ""}.${suffix}]`;
|
|
112
|
+
#addPreparedImage(image, id) {
|
|
113
|
+
const tag = `[📷 screenshot-${id}${image.transformed ? "-resized" : ""}]`;
|
|
104
114
|
const attachment = Object.freeze({ type: "image", data: image.data, mimeType: image.mimeType });
|
|
105
115
|
assertPromptImages([attachment]);
|
|
106
116
|
this.#chips.set(tag, { kind: "image", tag, image: attachment });
|
|
@@ -114,7 +124,7 @@ export class PromptChipStore {
|
|
|
114
124
|
return "";
|
|
115
125
|
assertPromptImages([...this.prepareSubmission(currentText).images, { type: "image", ...image }]);
|
|
116
126
|
const id = randomBytes(5).toString("hex");
|
|
117
|
-
const tag = `[📷 screenshot-${id}
|
|
127
|
+
const tag = `[📷 screenshot-${id}]`;
|
|
118
128
|
this.#chips.set(tag, {
|
|
119
129
|
kind: "image",
|
|
120
130
|
tag,
|
|
@@ -142,6 +152,20 @@ export class PromptChipStore {
|
|
|
142
152
|
return this.#recordUnique({ kind: "file", tag: `[${icon} ${label}]`, path: item.fullPath });
|
|
143
153
|
}).join("");
|
|
144
154
|
}
|
|
155
|
+
/** Hide provisional clipboard identities until the read identifies an actual image. */
|
|
156
|
+
hiddenRanges(line) {
|
|
157
|
+
const ranges = [];
|
|
158
|
+
for (const entry of this.#pending.values()) {
|
|
159
|
+
if (entry.kind === "image")
|
|
160
|
+
continue;
|
|
161
|
+
let from = 0;
|
|
162
|
+
while ((from = line.indexOf(entry.marker, from)) >= 0) {
|
|
163
|
+
ranges.push({ start: from, end: from + entry.marker.length });
|
|
164
|
+
from += entry.marker.length;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return ranges.sort((left, right) => left.start - right.start);
|
|
168
|
+
}
|
|
145
169
|
atomicRanges(line) {
|
|
146
170
|
return [...line.matchAll(CHIP_PATTERN)].map(match => ({
|
|
147
171
|
start: match.index,
|
|
@@ -181,7 +205,7 @@ export class PromptChipStore {
|
|
|
181
205
|
#replaceResolvable(text, includeImages) {
|
|
182
206
|
let expanded = text;
|
|
183
207
|
for (const entry of this.#pending.values()) {
|
|
184
|
-
if (!expanded.includes(entry.marker) && !expanded.includes(entry.marker.replace("
|
|
208
|
+
if (!expanded.includes(entry.marker) && !expanded.includes(entry.marker.replace("screenshot-", "failed-")))
|
|
185
209
|
continue;
|
|
186
210
|
if (includeImages && entry.error !== undefined)
|
|
187
211
|
throw entry.error;
|
|
@@ -116,6 +116,31 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
116
116
|
applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
|
|
117
117
|
render(width: number): readonly string[];
|
|
118
118
|
viewportFrameDescriptor(): TranscriptViewportFrameDescriptor | null;
|
|
119
|
+
/** Read-only, payload-free evidence; observing a pending frame must never render or repair it. */
|
|
120
|
+
viewportPresentationEvidence(): {
|
|
121
|
+
candidate: boolean;
|
|
122
|
+
candidateRevision: number | null;
|
|
123
|
+
currentRevision: number;
|
|
124
|
+
composedRevision: number | null;
|
|
125
|
+
currentPointer: {
|
|
126
|
+
readonly column: number;
|
|
127
|
+
readonly row: number;
|
|
128
|
+
} | null;
|
|
129
|
+
composedPointer: {
|
|
130
|
+
readonly column: number;
|
|
131
|
+
readonly row: number;
|
|
132
|
+
} | null;
|
|
133
|
+
frameId: number | null;
|
|
134
|
+
bottom: {
|
|
135
|
+
readonly row: number;
|
|
136
|
+
readonly columnStart: number;
|
|
137
|
+
readonly columnEnd: number;
|
|
138
|
+
} | null;
|
|
139
|
+
followingEnd: boolean;
|
|
140
|
+
scrollTop: number;
|
|
141
|
+
maxScroll: number;
|
|
142
|
+
cause: "detached" | "initial" | "steady" | "dock-input" | "follow-shift" | "geometry-change" | null;
|
|
143
|
+
};
|
|
119
144
|
/** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
|
|
120
145
|
viewportTransientTailRowCount(): number;
|
|
121
146
|
hasActiveSelection(): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PROMPT_GLYPH, backgroundSgrSpan, caretCell, composeSubmittedPromptRows, displayWidth, faint, formatSubmittedPromptTime, heldNativeHyperlinkStyle, hyperlinkSgrSpan, nativeHyperlinkStyle, overlaySpan, progressStatusText, 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, createPiShellCollapsedChangelog, 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";
|
|
3
|
+
import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, 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, renderPiShellCommandMessage, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
|
|
4
4
|
import { PiTuiRuntimeAdapter, classifyPiTuiInput, } from "../tui-runtime/index.js";
|
|
5
5
|
import { PromptChipStore } from "./prompt-chips.js";
|
|
6
6
|
import { SessionViewportController } from "./session-viewport-controller.js";
|
|
@@ -45,7 +45,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
45
45
|
#inputSurfaceCoordination = "editor";
|
|
46
46
|
#dockInputReuseEnabled;
|
|
47
47
|
#dockInputCandidate = false;
|
|
48
|
-
|
|
48
|
+
// Invariant: receipt-time eligibility is evidence, never authorization for composition-time reuse.
|
|
49
|
+
#dockInputRevision;
|
|
49
50
|
#visibleViewportSnapshot;
|
|
50
51
|
#fullViewportCompositions = 0;
|
|
51
52
|
#dockOnlyViewportCompositions = 0;
|
|
@@ -94,8 +95,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
94
95
|
return "";
|
|
95
96
|
}
|
|
96
97
|
},
|
|
97
|
-
...(this.#customViewport ? { beginClipboardPaste: () => this.#promptChips.beginPaste(this.editor.getText(), signal => handlers.readClipboardContent?.(signal) ?? Promise.resolve(null), error => handlers.onPasteRejected?.(error)) } : {}),
|
|
98
|
+
...(this.#customViewport ? { beginClipboardPaste: () => this.#promptChips.beginPaste(this.editor.getText(), signal => handlers.readClipboardContent?.(signal) ?? Promise.resolve(null), error => handlers.onPasteRejected?.(error), () => handlers.requestRender()) } : {}),
|
|
98
99
|
editorAtomicRanges: line => this.#promptChips.atomicRanges(line),
|
|
100
|
+
editorHiddenRanges: line => this.#promptChips.hiddenRanges(line),
|
|
99
101
|
decorateEditorRow: (row, width) => {
|
|
100
102
|
const plain = stripAnsi(row);
|
|
101
103
|
const ranges = this.#promptChips.hyperlinkRanges(plain);
|
|
@@ -289,13 +291,12 @@ export class OwnedUiSessionShellRoot {
|
|
|
289
291
|
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
290
292
|
const snapshot = this.#visibleViewportSnapshot;
|
|
291
293
|
const dockInputCandidate = this.#dockInputCandidate;
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const scrollRows = [...document.rows, ...steeringRows, ...statusRows];
|
|
294
|
+
// Concurrency: input may change after a keyboard receipt but before its paint.
|
|
295
|
+
// Capture only the live inputs this composition will actually represent.
|
|
296
|
+
const viewportRevision = this.#viewportController.presentationRevision;
|
|
297
|
+
const selectionRevision = this.#viewportController.selectionRevision;
|
|
298
|
+
const pointerPosition = this.#viewportController.pointerPosition;
|
|
299
|
+
const inputSurface = this.#inputSurface;
|
|
299
300
|
const dockRows = dock.rows;
|
|
300
301
|
const selectableDocumentRowCount = document.rows.length;
|
|
301
302
|
const dockStartRow = height - dockRows.length + 1;
|
|
@@ -311,28 +312,27 @@ export class OwnedUiSessionShellRoot {
|
|
|
311
312
|
&& snapshot !== undefined
|
|
312
313
|
&& snapshot.width === width
|
|
313
314
|
&& snapshot.height === height
|
|
314
|
-
&& snapshot.documentRows ===
|
|
315
|
-
&& snapshot.transientSignature ===
|
|
316
|
-
&& snapshot.promptAnchors ===
|
|
315
|
+
&& snapshot.documentRows === document.rows
|
|
316
|
+
&& snapshot.transientSignature === transientSignature
|
|
317
|
+
&& snapshot.promptAnchors === document.promptAnchors
|
|
317
318
|
&& snapshot.dockLength === dockRows.length
|
|
318
|
-
&& snapshot.inputSurface ===
|
|
319
|
-
&& snapshot.viewportRevision ===
|
|
320
|
-
&& snapshot.selectionRevision ===
|
|
319
|
+
&& snapshot.inputSurface === inputSurface
|
|
320
|
+
&& snapshot.viewportRevision === viewportRevision
|
|
321
|
+
&& snapshot.selectionRevision === selectionRevision
|
|
321
322
|
? this.#viewportController.composeDockOnly(dockRows, width, height)
|
|
322
323
|
: null;
|
|
323
|
-
// Performance: a spinner tick or queue update can race the input-triggered
|
|
324
|
-
// render. Recompute the complete transient signature before reusing rows.
|
|
325
|
-
if (frame !== null && dockInputTransientSignature !== transientSignature)
|
|
326
|
-
frame = null;
|
|
327
324
|
if (frame === null) {
|
|
328
325
|
frame = this.#viewportController.compose({
|
|
329
|
-
|
|
326
|
+
// Performance: do not copy the complete document on unchanged dock-only input.
|
|
327
|
+
// Steering and Working retain their one non-selectable viewport-tail ownership.
|
|
328
|
+
documentRows: [...document.rows, ...steeringRows, ...statusRows],
|
|
330
329
|
...(this.#viewportController.transcriptPointerSelecting
|
|
331
330
|
? { paintDocumentRow: heldNativeHyperlinkStyle }
|
|
332
331
|
: {}),
|
|
333
332
|
// Invariant: selection and copying stop at the real document tail; Steering,
|
|
334
333
|
// fitting alignment, and live Working remain transient presentation chrome.
|
|
335
334
|
selectableDocumentRowCount,
|
|
335
|
+
...(document.liveTailStartRow === undefined ? {} : { liveTailStartRow: document.liveTailStartRow }),
|
|
336
336
|
bottomAlignedTailRowCount: statusRows.length,
|
|
337
337
|
dockRows,
|
|
338
338
|
promptAnchors: document.promptAnchors,
|
|
@@ -350,13 +350,16 @@ export class OwnedUiSessionShellRoot {
|
|
|
350
350
|
this.#visibleViewportSnapshot = {
|
|
351
351
|
width,
|
|
352
352
|
height,
|
|
353
|
-
documentRows:
|
|
353
|
+
documentRows: document.rows,
|
|
354
354
|
transientSignature,
|
|
355
355
|
promptAnchors: document.promptAnchors,
|
|
356
356
|
dockLength: dockRows.length,
|
|
357
|
-
inputSurface
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
inputSurface,
|
|
358
|
+
// Invariant: a callback during composition cannot bless older rows with its
|
|
359
|
+
// newer revision. Its ordinary pending render will publish the newer state.
|
|
360
|
+
viewportRevision,
|
|
361
|
+
selectionRevision: frame.descriptor.selectionRevision,
|
|
362
|
+
pointerPosition,
|
|
360
363
|
};
|
|
361
364
|
this.#onViewportFrame?.(frame);
|
|
362
365
|
return frame.rows;
|
|
@@ -364,6 +367,25 @@ export class OwnedUiSessionShellRoot {
|
|
|
364
367
|
viewportFrameDescriptor() {
|
|
365
368
|
return this.#viewportController.frame?.descriptor ?? null;
|
|
366
369
|
}
|
|
370
|
+
/** Read-only, payload-free evidence; observing a pending frame must never render or repair it. */
|
|
371
|
+
viewportPresentationEvidence() {
|
|
372
|
+
const frame = this.#viewportController.frame;
|
|
373
|
+
const snapshot = this.#visibleViewportSnapshot;
|
|
374
|
+
return {
|
|
375
|
+
candidate: this.#dockInputCandidate,
|
|
376
|
+
candidateRevision: this.#dockInputCandidate ? this.#dockInputRevision ?? null : null,
|
|
377
|
+
currentRevision: this.#viewportController.presentationRevision,
|
|
378
|
+
composedRevision: snapshot?.viewportRevision ?? null,
|
|
379
|
+
currentPointer: this.#viewportController.pointerPosition ?? null,
|
|
380
|
+
composedPointer: snapshot?.pointerPosition ?? null,
|
|
381
|
+
frameId: frame?.descriptor.frameId ?? null,
|
|
382
|
+
bottom: frame?.hits.bottom ?? null,
|
|
383
|
+
followingEnd: frame?.followingEnd ?? true,
|
|
384
|
+
scrollTop: frame?.scrollTop ?? 0,
|
|
385
|
+
maxScroll: frame?.maxScroll ?? 0,
|
|
386
|
+
cause: frame?.descriptor.cause ?? null,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
367
389
|
/** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
|
|
368
390
|
viewportTransientTailRowCount() {
|
|
369
391
|
return this.#viewportController.frame?.hits.transientTail.length ?? 0;
|
|
@@ -485,6 +507,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
485
507
|
...resourceRows,
|
|
486
508
|
];
|
|
487
509
|
const promptAnchors = [];
|
|
510
|
+
// Rationale: a block that is still streaming re-renders every row it owns, so the frame
|
|
511
|
+
// reports where its rows begin. Damage-aware painting uses that to tell a live block's
|
|
512
|
+
// own reflow apart from a repaint that reaches settled rows.
|
|
513
|
+
let liveTailStartRow;
|
|
488
514
|
for (let index = 0; index < this.#transcriptOrder.length; index += 1) {
|
|
489
515
|
const id = this.#transcriptOrder[index];
|
|
490
516
|
const block = this.#blocksById.get(id);
|
|
@@ -507,6 +533,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
507
533
|
rows.push("");
|
|
508
534
|
}
|
|
509
535
|
const firstRow = rows.length;
|
|
536
|
+
if (liveTailStartRow === undefined && block !== undefined && block.status !== "finalized") {
|
|
537
|
+
liveTailStartRow = firstRow;
|
|
538
|
+
}
|
|
510
539
|
rows.push(...blockRows);
|
|
511
540
|
if (block?.kind === "user" && blockRows[0] !== undefined) {
|
|
512
541
|
promptAnchors.push({
|
|
@@ -538,7 +567,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
538
567
|
text: diagnostic.message,
|
|
539
568
|
payload: {},
|
|
540
569
|
}, width, this.#cwd)));
|
|
541
|
-
const layout = { rows: Object.freeze(rows), promptAnchors: Object.freeze(promptAnchors) };
|
|
570
|
+
const layout = { rows: Object.freeze(rows), promptAnchors: Object.freeze(promptAnchors), liveTailStartRow };
|
|
542
571
|
if (this.#extensionHeader === null) {
|
|
543
572
|
this.#documentLayouts.set(width, layout);
|
|
544
573
|
// Performance: the custom viewport commonly probes full width and reserved-rail width.
|
|
@@ -606,7 +635,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
606
635
|
}
|
|
607
636
|
const id = this.#appendAnchoredWorkflowComponent(width => [
|
|
608
637
|
"",
|
|
609
|
-
...renderPiShellStatusText(this.#workflowStatusMessages.get(id) ?? message, width
|
|
638
|
+
...renderPiShellStatusText(this.#workflowStatusMessages.get(id) ?? message, width),
|
|
610
639
|
]);
|
|
611
640
|
this.#workflowStatusMessages.set(id, message);
|
|
612
641
|
this.#lastWorkflowStatusId = id;
|
|
@@ -617,10 +646,8 @@ export class OwnedUiSessionShellRoot {
|
|
|
617
646
|
return;
|
|
618
647
|
}
|
|
619
648
|
this.#lastWorkflowStatusId = undefined;
|
|
620
|
-
const
|
|
621
|
-
|
|
622
|
-
const text = prefix === undefined ? message.message : `${prefix}: ${message.message}`;
|
|
623
|
-
this.#appendAnchoredWorkflowComponent(() => ["", ` ${piTheme().fg(color, text)}`]);
|
|
649
|
+
const presentation = { kind: message.kind, message: message.message };
|
|
650
|
+
this.#appendAnchoredWorkflowComponent(width => renderPiShellCommandMessage(presentation, width, this.#outputPad));
|
|
624
651
|
}
|
|
625
652
|
appendWorkflowResult(result) {
|
|
626
653
|
if (result.messages !== undefined) {
|
|
@@ -642,7 +669,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
642
669
|
}
|
|
643
670
|
if (result.command === "hotkeys" && result.outcome === "completed") {
|
|
644
671
|
this.#lastWorkflowStatusId = undefined;
|
|
645
|
-
const hotkeys = createPiShellHotkeys();
|
|
672
|
+
const hotkeys = createPiShellHotkeys(this.editor.keybindingConfig(), bindings => this.#extensionRenderers.getShortcuts?.(bindings) ?? [], this.#customViewport ? "a1" : "pi");
|
|
646
673
|
this.#appendAnchoredWorkflowComponent(width => hotkeys.render(width), () => hotkeys.dispose?.());
|
|
647
674
|
return;
|
|
648
675
|
}
|
|
@@ -661,26 +688,10 @@ export class OwnedUiSessionShellRoot {
|
|
|
661
688
|
}
|
|
662
689
|
if (result.outcome === "completed" && (result.command === "quit" || result.command === "compact"))
|
|
663
690
|
return;
|
|
664
|
-
if (result.command === "new"
|
|
665
|
-
this.appendWorkflowMessage({ kind: "accent", message: result.message });
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
if (result.command === "name" && result.outcome === "completed") {
|
|
669
|
-
this.#lastWorkflowStatusId = undefined;
|
|
670
|
-
this.#appendAnchoredWorkflowComponent(() => [
|
|
671
|
-
...(result.detail ? ["", ` ${piTheme().fg("warning", `Warning: ${result.detail}`)}`] : []),
|
|
672
|
-
"",
|
|
673
|
-
` ${piTheme().fg("dim", result.message)}`,
|
|
674
|
-
]);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
if (result.command === "debug" && result.outcome === "completed") {
|
|
691
|
+
if (result.outcome === "completed" && (result.command === "new" || result.command === "name" || result.command === "debug")) {
|
|
678
692
|
this.#lastWorkflowStatusId = undefined;
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
` ${piTheme().fg("accent", result.message)}`,
|
|
682
|
-
...(result.detail ? [` ${piTheme().fg("muted", result.detail)}`] : []),
|
|
683
|
-
]);
|
|
693
|
+
const presentation = { kind: result.command, message: result.message, ...(result.detail === undefined ? {} : { detail: result.detail }) };
|
|
694
|
+
this.#appendAnchoredWorkflowComponent(width => renderPiShellCommandMessage(presentation, width, this.#outputPad));
|
|
684
695
|
return;
|
|
685
696
|
}
|
|
686
697
|
if (result.command === "arminsayshi" && result.outcome === "completed") {
|
|
@@ -785,9 +796,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
785
796
|
this.appendWorkflowStatus(message);
|
|
786
797
|
return;
|
|
787
798
|
}
|
|
788
|
-
this
|
|
789
|
-
const prefix = type === "warning" ? "Warning" : "Error";
|
|
790
|
-
this.#appendAnchoredWorkflowComponent(() => ["", ` ${piTheme().fg(type, `${prefix}: ${message}`)}`]);
|
|
799
|
+
this.appendWorkflowMessage({ kind: type, message });
|
|
791
800
|
}
|
|
792
801
|
extensionFooterData() {
|
|
793
802
|
return {
|
|
@@ -829,8 +838,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
829
838
|
handleInput(data) {
|
|
830
839
|
this.#dockInputCandidate = this.#customViewport && this.#dockInputReuseEnabled
|
|
831
840
|
&& classifyPiTuiInput(data, this.#inputSurfaceCoordination) === "safe";
|
|
832
|
-
|
|
833
|
-
this.#captureDockInputSnapshot();
|
|
841
|
+
this.#dockInputRevision = this.#dockInputCandidate ? this.#viewportController.presentationRevision : undefined;
|
|
834
842
|
try {
|
|
835
843
|
this.#inputSurface.handleInput?.(data);
|
|
836
844
|
}
|
|
@@ -839,23 +847,6 @@ export class OwnedUiSessionShellRoot {
|
|
|
839
847
|
throw error;
|
|
840
848
|
}
|
|
841
849
|
}
|
|
842
|
-
#captureDockInputSnapshot() {
|
|
843
|
-
const width = Math.max(1, this.#componentRuntime.getColumns());
|
|
844
|
-
const steeringRows = this.#renderQueued(width);
|
|
845
|
-
const statusRows = this.#renderStatus(width);
|
|
846
|
-
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
847
|
-
const snapshot = this.#visibleViewportSnapshot;
|
|
848
|
-
this.#dockInputSnapshot = snapshot === undefined
|
|
849
|
-
? undefined
|
|
850
|
-
: {
|
|
851
|
-
documentRows: snapshot.documentRows,
|
|
852
|
-
transientSignature,
|
|
853
|
-
promptAnchors: snapshot.promptAnchors,
|
|
854
|
-
inputSurface: this.#inputSurface,
|
|
855
|
-
viewportRevision: this.#viewportController.presentationRevision,
|
|
856
|
-
selectionRevision: this.#viewportController.selectionRevision,
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
850
|
invalidate() {
|
|
860
851
|
this.header.invalidate();
|
|
861
852
|
this.resources.invalidate();
|
|
@@ -884,7 +875,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
884
875
|
this.#documentLayouts.clear();
|
|
885
876
|
this.#visibleViewportSnapshot = undefined;
|
|
886
877
|
this.#dockInputCandidate = false;
|
|
887
|
-
this.#
|
|
878
|
+
this.#dockInputRevision = undefined;
|
|
888
879
|
if (this.#inputSurface !== this.editor)
|
|
889
880
|
this.#inputSurface.dispose?.();
|
|
890
881
|
this.#extensionHeader?.dispose?.();
|
|
@@ -37,7 +37,7 @@ export declare class OwnedUiSessionShell {
|
|
|
37
37
|
showLoginMethodSelector(providerReference: string): void;
|
|
38
38
|
showTreeSelector(initialSelectedId?: string): void;
|
|
39
39
|
showLoginAuthTypeSelector(): void;
|
|
40
|
-
showLoginProviderSelector(authType
|
|
40
|
+
showLoginProviderSelector(authType?: "oauth" | "api_key", initialSearchInput?: string): void;
|
|
41
41
|
showSessionSelector(): void;
|
|
42
42
|
showSettingsSelector(): void;
|
|
43
43
|
shutdown(): Promise<AdapterCommandResult>;
|
|
@@ -139,6 +139,7 @@ export class OwnedUiSessionShell {
|
|
|
139
139
|
}, this.backend.agentDir, {
|
|
140
140
|
getMessageRenderer: customType => this.backend.pinnedMessageRenderer(customType),
|
|
141
141
|
getToolDefinition: toolName => this.backend.pinnedToolDefinition(toolName),
|
|
142
|
+
getShortcuts: bindings => this.backend.pinnedShortcutDescriptions(bindings),
|
|
142
143
|
}, options.sessionLayout, {
|
|
143
144
|
resolve: assetId => this.backend.resolveTranscriptImage(assetId),
|
|
144
145
|
});
|
|
@@ -237,6 +238,17 @@ export class OwnedUiSessionShell {
|
|
|
237
238
|
? this.runtime.addPreInputListener(data => {
|
|
238
239
|
if (options.inputPresentation?.coordination === false)
|
|
239
240
|
this.#streamPresentation.noteImmediatePresentation();
|
|
241
|
+
// Compatibility: Pi's fullscreen renderer also intercepts plain Home/End.
|
|
242
|
+
// Deliver them to the focused owned input before that outer scroll handler;
|
|
243
|
+
// overlays retain Pi's normal dispatch, and comparison profiles never enter here.
|
|
244
|
+
if (!this.runtime.hasOverlay() && (this.root.editor.matchesTerminalKey(data, "home")
|
|
245
|
+
|| this.root.editor.matchesTerminalKey(data, "end"))) {
|
|
246
|
+
if (this.root.usesDefaultInputSurface())
|
|
247
|
+
this.root.handleViewportPreInput(data, true);
|
|
248
|
+
this.root.handleInput(data);
|
|
249
|
+
this.runtime.requestRender();
|
|
250
|
+
return { consume: true };
|
|
251
|
+
}
|
|
240
252
|
// Invariant: an overlay or editor-replacement screen owns its entire pointer
|
|
241
253
|
// surface. Letting the transcript pre-router inspect those reports
|
|
242
254
|
// steals settings value menus and numeric +/- controls before the
|
|
@@ -793,8 +805,7 @@ export class OwnedUiSessionShell {
|
|
|
793
805
|
showLoginMethodSelector(providerReference) {
|
|
794
806
|
const method = this.backend.pinnedLoginMethodOptions(providerReference);
|
|
795
807
|
if (method.options.length === 0) {
|
|
796
|
-
this.
|
|
797
|
-
this.runtime.requestRender();
|
|
808
|
+
this.showLoginProviderSelector(undefined, providerReference);
|
|
798
809
|
return;
|
|
799
810
|
}
|
|
800
811
|
if (method.options.length === 1) {
|
|
@@ -836,6 +847,20 @@ export class OwnedUiSessionShell {
|
|
|
836
847
|
initialFilterMode: context.filterMode,
|
|
837
848
|
...(initialSelectedId === undefined ? {} : { initialSelectedId }),
|
|
838
849
|
onLabelChange: context.appendLabelChange,
|
|
850
|
+
onCopy: text => {
|
|
851
|
+
if (!text) {
|
|
852
|
+
this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: "Selected entry has no text to copy" });
|
|
853
|
+
this.runtime.requestRender();
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
void this.backend.copyWorkflowText(text).then(() => {
|
|
857
|
+
this.root.appendWorkflowStatus("Copied selected message to clipboard");
|
|
858
|
+
this.runtime.requestRender();
|
|
859
|
+
}).catch(error => {
|
|
860
|
+
this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
|
|
861
|
+
this.runtime.requestRender();
|
|
862
|
+
});
|
|
863
|
+
},
|
|
839
864
|
onCancel: close,
|
|
840
865
|
onSelect: entryId => {
|
|
841
866
|
close();
|
|
@@ -863,10 +888,10 @@ export class OwnedUiSessionShell {
|
|
|
863
888
|
this.root.setInputSurface(component);
|
|
864
889
|
this.runtime.requestRender();
|
|
865
890
|
}
|
|
866
|
-
showLoginProviderSelector(authType) {
|
|
891
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
867
892
|
const options = this.backend.pinnedLoginOptions(authType);
|
|
868
893
|
if (options.length === 0) {
|
|
869
|
-
this.root.appendWorkflowStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
|
|
894
|
+
this.root.appendWorkflowStatus(authType === "oauth" ? "No subscription providers available." : authType === "api_key" ? "No API key providers available." : "No login providers available.");
|
|
870
895
|
this.runtime.requestRender();
|
|
871
896
|
return;
|
|
872
897
|
}
|
|
@@ -879,8 +904,9 @@ export class OwnedUiSessionShell {
|
|
|
879
904
|
void this.runWorkflow({ command: "login", argument: "", selection: id });
|
|
880
905
|
}, () => {
|
|
881
906
|
close();
|
|
882
|
-
|
|
883
|
-
|
|
907
|
+
if (authType !== undefined)
|
|
908
|
+
this.showLoginAuthTypeSelector();
|
|
909
|
+
}, initialSearchInput);
|
|
884
910
|
this.root.setInputSurface(component);
|
|
885
911
|
this.runtime.requestRender();
|
|
886
912
|
}
|
|
@@ -961,6 +987,24 @@ export class OwnedUiSessionShell {
|
|
|
961
987
|
return this.runWorkflow({ command: "quit", argument: "" });
|
|
962
988
|
}
|
|
963
989
|
async runWorkflow(request) {
|
|
990
|
+
if (request.command === "login" && request.selection !== undefined) {
|
|
991
|
+
const setup = this.backend.pinnedAmbientAuthentication(request.selection);
|
|
992
|
+
if (setup) {
|
|
993
|
+
const close = () => {
|
|
994
|
+
this.root.setInputSurface(null);
|
|
995
|
+
this.runtime.requestRender();
|
|
996
|
+
};
|
|
997
|
+
const dialog = createPiShellLoginDialog({
|
|
998
|
+
getColumns: () => this.runtime.viewport().columns,
|
|
999
|
+
getRows: () => this.runtime.viewport().rows,
|
|
1000
|
+
requestRender: () => this.runtime.requestRender(),
|
|
1001
|
+
}, setup.providerId, close, setup.providerName, setup.title);
|
|
1002
|
+
dialog.showInfo(setup.message, [], true);
|
|
1003
|
+
this.root.setInputSurface(dialog);
|
|
1004
|
+
this.runtime.requestRender();
|
|
1005
|
+
return { outcome: "completed", diagnostic: null };
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
964
1008
|
if (request.command === "scoped-models" && request.selection === undefined && request.confirmed === undefined) {
|
|
965
1009
|
this.showScopedModelsSelector();
|
|
966
1010
|
return { outcome: "completed", diagnostic: null };
|
|
@@ -1001,8 +1045,13 @@ export class OwnedUiSessionShell {
|
|
|
1001
1045
|
return { outcome: "completed", diagnostic: null };
|
|
1002
1046
|
}
|
|
1003
1047
|
if (request.command === "reload") {
|
|
1048
|
+
const blocked = this.backend.reloadBlockedResult();
|
|
1049
|
+
if (blocked) {
|
|
1050
|
+
this.root.appendWorkflowResult(blocked);
|
|
1051
|
+
this.runtime.requestRender();
|
|
1052
|
+
return workflowAdapterResult(blocked);
|
|
1053
|
+
}
|
|
1004
1054
|
this.root.resetExtensionUi();
|
|
1005
|
-
this.root.resetWorkflowPresentation();
|
|
1006
1055
|
}
|
|
1007
1056
|
const shareSurface = request.command === "share"
|
|
1008
1057
|
? createPiShellOperationLoader({
|
|
@@ -1053,6 +1102,8 @@ export class OwnedUiSessionShell {
|
|
|
1053
1102
|
return { outcome: "failed", diagnostic: `Owned controller missing for ${request.command}` };
|
|
1054
1103
|
}
|
|
1055
1104
|
if (request.command === "reload" && result.outcome === "completed") {
|
|
1105
|
+
this.root.resetWorkflowPresentation();
|
|
1106
|
+
this.root.editor.reloadKeybindings();
|
|
1056
1107
|
this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
|
|
1057
1108
|
}
|
|
1058
1109
|
this.root.appendWorkflowResult(result);
|
|
@@ -28,6 +28,11 @@ export declare class SessionViewportController {
|
|
|
28
28
|
get hasSelection(): boolean;
|
|
29
29
|
get selectionRevision(): number;
|
|
30
30
|
get presentationRevision(): number;
|
|
31
|
+
/** Latest reported coordinates; exposed without rendering for frame provenance and evidence. */
|
|
32
|
+
get pointerPosition(): {
|
|
33
|
+
readonly column: number;
|
|
34
|
+
readonly row: number;
|
|
35
|
+
} | undefined;
|
|
31
36
|
get frame(): TranscriptViewportFrame | null;
|
|
32
37
|
setEditorPointerFrame(frame: {
|
|
33
38
|
readonly rowStart: number;
|