@timurproko/a1 0.1.8-dev.260 → 0.1.8-dev.271
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/bin/update-recovery.js +272 -0
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/release-gc.js +2 -0
- package/dist/foundation/release/update-recovery.d.ts +74 -0
- package/dist/foundation/release/update-recovery.js +352 -0
- package/dist/foundation/release/update-transaction.d.ts +10 -0
- package/dist/foundation/release/update-transaction.js +12 -1
- package/dist/foundation/release/update.d.ts +19 -1
- package/dist/foundation/release/update.js +67 -10
- package/dist/integrations/pi/components/owned-editor-ux.js +61 -8
- package/dist/integrations/pi/components/path-word-ranges.d.ts +2 -0
- package/dist/integrations/pi/components/path-word-ranges.js +37 -0
- package/dist/integrations/pi/engine/conformance.d.ts +1 -1
- package/dist/integrations/pi/engine/conformance.js +9 -1
- package/dist/integrations/pi/engine/runtime-integration.js +10 -1
- package/dist/integrations/pi/engine/windows-filesystem-hygiene.d.ts +21 -0
- package/dist/integrations/pi/engine/windows-filesystem-hygiene.js +55 -0
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +29 -20
- 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/runtime-payload-inventory.json +1 -0
- package/dist/ui/components/transcript-viewport.d.ts +8 -0
- package/dist/ui/components/transcript-viewport.js +30 -6
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CURSOR_MARKER, decodeKittyPrintable, visibleWidth, } from "#pi-tui";
|
|
2
|
+
import { promptPathWordRanges } from "./path-word-ranges.js";
|
|
2
3
|
export class OwnedEditorUxInterception {
|
|
3
4
|
interceptors;
|
|
4
5
|
fallback;
|
|
@@ -59,12 +60,13 @@ class PromptSelectionInterceptor {
|
|
|
59
60
|
#lastClick;
|
|
60
61
|
#redoStack = [];
|
|
61
62
|
#selectionRevision = 0;
|
|
63
|
+
#wordDirection;
|
|
62
64
|
#geometry;
|
|
63
65
|
constructor(editor, keybindings, options) {
|
|
64
66
|
this.editor = editor;
|
|
65
67
|
this.keybindings = keybindings;
|
|
66
68
|
this.options = options;
|
|
67
|
-
installAtomicSegmentation(editor, options.atomicRanges);
|
|
69
|
+
installAtomicSegmentation(editor, options.atomicRanges, () => this.#wordDirection);
|
|
68
70
|
}
|
|
69
71
|
handleInput(data, next) {
|
|
70
72
|
if (this.keybindings.matches(data, "owned.editor.selectAll")) {
|
|
@@ -167,24 +169,30 @@ class PromptSelectionInterceptor {
|
|
|
167
169
|
if (this.keybindings.matches(data, "tui.editor.cursorWordLeft")) {
|
|
168
170
|
const before = this.#cursor();
|
|
169
171
|
const startedWithAtomicFocus = this.#atomicFocus() !== undefined;
|
|
170
|
-
next
|
|
172
|
+
this.#delegateWord(-1, next);
|
|
171
173
|
let after = this.#cursor();
|
|
172
174
|
const landedAtomic = this.#atomicRangeAt(after);
|
|
173
175
|
if (!startedWithAtomicFocus && landedAtomic?.start === after.col && after.col > 0) {
|
|
174
176
|
const line = editorState(this.editor).lines[after.line] ?? "";
|
|
175
177
|
const previous = [...GRAPHEMES.segment(line.slice(0, after.col))].at(-1);
|
|
176
178
|
if (previous !== undefined && !/^\s+$/u.test(previous.segment)) {
|
|
177
|
-
next
|
|
179
|
+
this.#delegateWord(-1, next);
|
|
178
180
|
after = this.#cursor();
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
|
-
|
|
183
|
+
const line = editorState(this.editor).lines[after.line] ?? "";
|
|
184
|
+
const landedPath = promptPathWordRanges(line).some(range => range.start === after.col);
|
|
185
|
+
if (!samePosition(before, after) && !landedPath)
|
|
182
186
|
this.#moveOntoPreviousSeparator();
|
|
183
187
|
this.#requestRender();
|
|
184
188
|
return;
|
|
185
189
|
}
|
|
186
190
|
const beforeText = this.editor.getText();
|
|
187
|
-
|
|
191
|
+
const wordDirection = this.#wordDirectionFor(data);
|
|
192
|
+
if (wordDirection === undefined)
|
|
193
|
+
next();
|
|
194
|
+
else
|
|
195
|
+
this.#delegateWord(wordDirection, next);
|
|
188
196
|
if (this.editor.getText() !== beforeText)
|
|
189
197
|
this.#redoStack = [];
|
|
190
198
|
}
|
|
@@ -584,11 +592,28 @@ class PromptSelectionInterceptor {
|
|
|
584
592
|
#snapshot() {
|
|
585
593
|
return { text: this.editor.getText(), cursor: this.#cursor() };
|
|
586
594
|
}
|
|
595
|
+
#wordDirectionFor(data) {
|
|
596
|
+
if (this.keybindings.matches(data, "tui.editor.deleteWordBackward"))
|
|
597
|
+
return -1;
|
|
598
|
+
if (this.keybindings.matches(data, "tui.editor.deleteWordForward")
|
|
599
|
+
|| this.keybindings.matches(data, "tui.editor.cursorWordRight"))
|
|
600
|
+
return 1;
|
|
601
|
+
return undefined;
|
|
602
|
+
}
|
|
603
|
+
#delegateWord(direction, next) {
|
|
604
|
+
this.#wordDirection = direction;
|
|
605
|
+
try {
|
|
606
|
+
next();
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
this.#wordDirection = undefined;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
587
612
|
#requestRender() {
|
|
588
613
|
this.options.requestRender();
|
|
589
614
|
}
|
|
590
615
|
}
|
|
591
|
-
function installAtomicSegmentation(editor, rangesForText) {
|
|
616
|
+
function installAtomicSegmentation(editor, rangesForText, wordDirection) {
|
|
592
617
|
if (Reflect.get(editor, ATOMIC_SEGMENTATION) === true)
|
|
593
618
|
return;
|
|
594
619
|
const originalValue = Reflect.get(editor, "segment");
|
|
@@ -597,7 +622,14 @@ function installAtomicSegmentation(editor, rangesForText) {
|
|
|
597
622
|
const original = originalValue.bind(editor);
|
|
598
623
|
Reflect.set(editor, "segment", (text, mode) => {
|
|
599
624
|
const segments = [...original(text, mode)].filter(isEditorSegment);
|
|
600
|
-
const ranges = rangesForText(text);
|
|
625
|
+
const ranges = rangesForText(text).map(range => ({ ...range, wordLike: false }));
|
|
626
|
+
if (mode === "word") {
|
|
627
|
+
for (const range of contextualPathRanges(editor, text, wordDirection())) {
|
|
628
|
+
if (!ranges.some(existing => rangesOverlap(existing, range)))
|
|
629
|
+
ranges.push({ ...range, wordLike: true });
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
ranges.sort((left, right) => left.start - right.start);
|
|
601
633
|
if (ranges.length === 0)
|
|
602
634
|
return segments;
|
|
603
635
|
const merged = [];
|
|
@@ -608,10 +640,12 @@ function installAtomicSegmentation(editor, rangesForText) {
|
|
|
608
640
|
const range = ranges[rangeIndex];
|
|
609
641
|
if (range !== undefined && segment.index >= range.start && segment.index < range.end) {
|
|
610
642
|
if (segment.index === range.start) {
|
|
643
|
+
const source = text.slice(range.start, range.end);
|
|
611
644
|
merged.push({
|
|
612
|
-
segment:
|
|
645
|
+
segment: range.wordLike ? "w".repeat(source.length) : source.replaceAll(" ", ATOMIC_SPACE_SENTINEL),
|
|
613
646
|
index: range.start,
|
|
614
647
|
input: text,
|
|
648
|
+
...(range.wordLike ? { isWordLike: true } : {}),
|
|
615
649
|
});
|
|
616
650
|
}
|
|
617
651
|
continue;
|
|
@@ -622,6 +656,25 @@ function installAtomicSegmentation(editor, rangesForText) {
|
|
|
622
656
|
});
|
|
623
657
|
Reflect.set(editor, ATOMIC_SEGMENTATION, true);
|
|
624
658
|
}
|
|
659
|
+
function contextualPathRanges(editor, text, direction) {
|
|
660
|
+
if (direction === undefined)
|
|
661
|
+
return promptPathWordRanges(text);
|
|
662
|
+
const state = editorState(editor);
|
|
663
|
+
const line = state.lines[state.cursorLine] ?? "";
|
|
664
|
+
const offset = direction < 0 ? 0 : state.cursorCol;
|
|
665
|
+
if (text !== (direction < 0 ? line.slice(0, state.cursorCol) : line.slice(state.cursorCol))) {
|
|
666
|
+
return promptPathWordRanges(text);
|
|
667
|
+
}
|
|
668
|
+
const end = offset + text.length;
|
|
669
|
+
return promptPathWordRanges(line).flatMap(range => {
|
|
670
|
+
const start = Math.max(range.start, offset);
|
|
671
|
+
const finish = Math.min(range.end, end);
|
|
672
|
+
return finish <= start ? [] : [{ start: start - offset, end: finish - offset }];
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
function rangesOverlap(left, right) {
|
|
676
|
+
return left.start < right.end && right.start < left.end;
|
|
677
|
+
}
|
|
625
678
|
function isEditorSegment(value) {
|
|
626
679
|
if (typeof value !== "object" || value === null)
|
|
627
680
|
return false;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const WHITESPACE = /\s/u;
|
|
2
|
+
const DRIVE_ROOT = /^[A-Za-z]:[\\/]/u;
|
|
3
|
+
const UNC_ROOT = /^(?:\\\\|\/\/)[^\\/\s]+[\\/][^\\/\s]+/u;
|
|
4
|
+
const POSIX_ROOT = /^\//u;
|
|
5
|
+
const EXPLICIT_RELATIVE_ROOT = /^(?:\.{1,2}|~)[\\/]/u;
|
|
6
|
+
export function promptPathWordRanges(line) {
|
|
7
|
+
const ranges = [];
|
|
8
|
+
let index = 0;
|
|
9
|
+
while (index < line.length) {
|
|
10
|
+
while (index < line.length && WHITESPACE.test(line[index] ?? ""))
|
|
11
|
+
index += 1;
|
|
12
|
+
if (index >= line.length)
|
|
13
|
+
break;
|
|
14
|
+
const start = index;
|
|
15
|
+
const quote = line[index] === '"' || line[index] === "'" ? line[index] : undefined;
|
|
16
|
+
if (quote !== undefined) {
|
|
17
|
+
const closing = line.indexOf(quote, index + 1);
|
|
18
|
+
if (closing >= 0 && (closing + 1 === line.length || WHITESPACE.test(line[closing + 1] ?? ""))) {
|
|
19
|
+
if (isExplicitPath(line.slice(index + 1, closing)))
|
|
20
|
+
ranges.push({ start, end: closing + 1 });
|
|
21
|
+
index = closing + 1;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
while (index < line.length && !WHITESPACE.test(line[index] ?? ""))
|
|
26
|
+
index += 1;
|
|
27
|
+
if (isExplicitPath(line.slice(start, index)))
|
|
28
|
+
ranges.push({ start, end: index });
|
|
29
|
+
}
|
|
30
|
+
return ranges;
|
|
31
|
+
}
|
|
32
|
+
function isExplicitPath(value) {
|
|
33
|
+
return DRIVE_ROOT.test(value)
|
|
34
|
+
|| UNC_ROOT.test(value)
|
|
35
|
+
|| POSIX_ROOT.test(value)
|
|
36
|
+
|| EXPLICIT_RELATIVE_ROOT.test(value);
|
|
37
|
+
}
|
|
@@ -9,7 +9,7 @@ export declare const REQUIRED_PI_CAPABILITY_OPERATIONS: Readonly<{
|
|
|
9
9
|
readonly "commands-events": readonly ["prompt", "steer", "followUp", "abort", "compact", "setModel", "setThinkingLevel", "subscribe", "dispose"];
|
|
10
10
|
readonly "models-authentication": readonly ["models.list", "models.refresh", "auth.status", "auth.login", "auth.logout", "auth.cancel"];
|
|
11
11
|
readonly settings: readonly ["settings.read", "settings.write", "settings.flush"];
|
|
12
|
-
readonly "resources-extensions": readonly ["resources.discover", "extensions.bind", "extensions.reload", "renderers.invoke"];
|
|
12
|
+
readonly "resources-extensions": readonly ["resources.discover", "extensions.inline", "extensions.bind", "extensions.reload", "renderers.invoke"];
|
|
13
13
|
readonly workflows: readonly ["workflow.route", "workflow.validate", "workflow.diagnostics"];
|
|
14
14
|
readonly disposal: readonly ["subscription.dispose", "session.dispose", "services.cleanup"];
|
|
15
15
|
}>;
|
|
@@ -4,13 +4,14 @@ import { join } from "node:path";
|
|
|
4
4
|
import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, ModelRuntime, SessionManager, VERSION, } from "@earendil-works/pi-coding-agent";
|
|
5
5
|
import { OWNED_UI_CONTRACT_VERSION } from "../../../contracts/owned-ui/index.js";
|
|
6
6
|
import { PRODUCT_IDENTITY } from "../../../product-identity.js";
|
|
7
|
+
import { createWindowsNulCleanupExtension } from "./windows-filesystem-hygiene.js";
|
|
7
8
|
export const REQUIRED_PI_CAPABILITY_OPERATIONS = Object.freeze({
|
|
8
9
|
"public-exports": ["services.create", "session.create", "runtime.create"],
|
|
9
10
|
"session-lifecycle": ["session.new", "session.resume", "session.rebind", "session.dispose"],
|
|
10
11
|
"commands-events": ["prompt", "steer", "followUp", "abort", "compact", "setModel", "setThinkingLevel", "subscribe", "dispose"],
|
|
11
12
|
"models-authentication": ["models.list", "models.refresh", "auth.status", "auth.login", "auth.logout", "auth.cancel"],
|
|
12
13
|
settings: ["settings.read", "settings.write", "settings.flush"],
|
|
13
|
-
"resources-extensions": ["resources.discover", "extensions.bind", "extensions.reload", "renderers.invoke"],
|
|
14
|
+
"resources-extensions": ["resources.discover", "extensions.inline", "extensions.bind", "extensions.reload", "renderers.invoke"],
|
|
14
15
|
workflows: ["workflow.route", "workflow.validate", "workflow.diagnostics"],
|
|
15
16
|
disposal: ["subscription.dispose", "session.dispose", "services.cleanup"],
|
|
16
17
|
});
|
|
@@ -59,11 +60,18 @@ export async function runPiUpgradeConformance() {
|
|
|
59
60
|
refreshOnCreate: false,
|
|
60
61
|
allowModelNetwork: false,
|
|
61
62
|
});
|
|
63
|
+
const cleanupExtension = createWindowsNulCleanupExtension({ platform: "win32" });
|
|
64
|
+
if (cleanupExtension === null)
|
|
65
|
+
throw new Error("Windows NUL cleanup extension is unavailable");
|
|
62
66
|
services = await createAgentSessionServices({
|
|
63
67
|
cwd: root,
|
|
64
68
|
agentDir: join(root, "agent"),
|
|
65
69
|
modelRuntime,
|
|
70
|
+
resourceLoaderOptions: { extensionFactories: [cleanupExtension] },
|
|
66
71
|
});
|
|
72
|
+
if (!services.resourceLoader.getExtensions().extensions.some(extension => extension.path === "<inline:windows-nul-file-cleanup>")) {
|
|
73
|
+
throw new Error("named inline extension factory was not loaded");
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
catch (error) {
|
|
69
77
|
throw new PiUpgradeConformanceError("services", error);
|
|
@@ -2,6 +2,7 @@ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentS
|
|
|
2
2
|
import { resolvePiProjectTrustPreflight, } from "./project-trust-preflight.js";
|
|
3
3
|
import { openSelectedPiSession, resolveSessionArgumentPath } from "./session-selection.js";
|
|
4
4
|
import { markStartupPhase } from "../../../foundation/startup/index.js";
|
|
5
|
+
import { createWindowsNulCleanupExtension } from "./windows-filesystem-hygiene.js";
|
|
5
6
|
/**
|
|
6
7
|
* Mirrors pinned Pi's CLI startup: resolve the `models` patterns from settings
|
|
7
8
|
* into a scoped model list, keep the resolver's warnings (e.g. "No models match
|
|
@@ -44,7 +45,15 @@ export async function createPiRuntimeServicesAfterTrust(options) {
|
|
|
44
45
|
});
|
|
45
46
|
const settingsManager = createSettingsManager(options.cwd, options.agentDir, trust.trusted);
|
|
46
47
|
await markStartupPhase(process.env, "settings-loaded");
|
|
47
|
-
const
|
|
48
|
+
const cleanupExtension = createWindowsNulCleanupExtension();
|
|
49
|
+
const services = await createServices({
|
|
50
|
+
cwd: options.cwd,
|
|
51
|
+
agentDir: options.agentDir,
|
|
52
|
+
settingsManager,
|
|
53
|
+
...(cleanupExtension === null ? {} : {
|
|
54
|
+
resourceLoaderOptions: { extensionFactories: [cleanupExtension] },
|
|
55
|
+
}),
|
|
56
|
+
});
|
|
48
57
|
await markStartupPhase(process.env, "pi-services");
|
|
49
58
|
await markStartupPhase(process.env, "resource-discovery");
|
|
50
59
|
return { services, trust };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type InlineExtension } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
interface FileMetadata {
|
|
3
|
+
isFile(): boolean;
|
|
4
|
+
isSymbolicLink(): boolean;
|
|
5
|
+
}
|
|
6
|
+
interface CleanupFileSystem {
|
|
7
|
+
lstat(path: string): Promise<FileMetadata>;
|
|
8
|
+
unlink(path: string): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
interface CleanupPaths {
|
|
11
|
+
dirname(path: string): string;
|
|
12
|
+
join(...paths: string[]): string;
|
|
13
|
+
resolve(...paths: string[]): string;
|
|
14
|
+
}
|
|
15
|
+
export interface WindowsNulCleanupOptions {
|
|
16
|
+
readonly platform?: NodeJS.Platform;
|
|
17
|
+
readonly fileSystem?: CleanupFileSystem;
|
|
18
|
+
readonly paths?: CleanupPaths;
|
|
19
|
+
}
|
|
20
|
+
export declare function createWindowsNulCleanupExtension(options?: WindowsNulCleanupOptions): InlineExtension | null;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isBashToolResult, isEditToolResult, isWriteToolResult, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { lstat, unlink } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
const DEFAULT_FILE_SYSTEM = { lstat, unlink };
|
|
5
|
+
const DEFAULT_PATHS = { dirname, join, resolve };
|
|
6
|
+
const SIMPLE_CD = /(?:^|[;&|\n])\s*cd\s+(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([^\s;&|]+))/;
|
|
7
|
+
export function createWindowsNulCleanupExtension(options = {}) {
|
|
8
|
+
if ((options.platform ?? process.platform) !== "win32")
|
|
9
|
+
return null;
|
|
10
|
+
const fileSystem = options.fileSystem ?? DEFAULT_FILE_SYSTEM;
|
|
11
|
+
const paths = options.paths ?? DEFAULT_PATHS;
|
|
12
|
+
return {
|
|
13
|
+
name: "windows-nul-file-cleanup",
|
|
14
|
+
factory: pi => registerNulCleanup(pi, fileSystem, paths),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function registerNulCleanup(pi, fileSystem, paths) {
|
|
18
|
+
const cleanDirectory = async (directory) => {
|
|
19
|
+
const candidate = paths.join(directory, "nul");
|
|
20
|
+
try {
|
|
21
|
+
const metadata = await fileSystem.lstat(candidate);
|
|
22
|
+
if (metadata.isFile() && !metadata.isSymbolicLink())
|
|
23
|
+
await fileSystem.unlink(candidate);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
30
|
+
await cleanDirectory(ctx.cwd);
|
|
31
|
+
});
|
|
32
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
33
|
+
const directories = new Set([ctx.cwd]);
|
|
34
|
+
if (isBashToolResult(event)) {
|
|
35
|
+
const command = stringInput(event.input, "command");
|
|
36
|
+
const target = command === null ? null : simpleCdTarget(command);
|
|
37
|
+
if (target !== null)
|
|
38
|
+
directories.add(paths.resolve(ctx.cwd, target));
|
|
39
|
+
}
|
|
40
|
+
if (isWriteToolResult(event) || isEditToolResult(event)) {
|
|
41
|
+
const target = stringInput(event.input, "path");
|
|
42
|
+
if (target !== null)
|
|
43
|
+
directories.add(paths.dirname(paths.resolve(ctx.cwd, target)));
|
|
44
|
+
}
|
|
45
|
+
await Promise.all(Array.from(directories, cleanDirectory));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function simpleCdTarget(command) {
|
|
49
|
+
const match = SIMPLE_CD.exec(command);
|
|
50
|
+
return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
|
|
51
|
+
}
|
|
52
|
+
function stringInput(input, key) {
|
|
53
|
+
const value = input[key];
|
|
54
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
55
|
+
}
|
|
@@ -92,7 +92,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
92
92
|
applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
|
|
93
93
|
render(width: number): readonly string[];
|
|
94
94
|
viewportFrameDescriptor(): TranscriptViewportFrameDescriptor | null;
|
|
95
|
-
/** Visible
|
|
95
|
+
/** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
|
|
96
96
|
viewportTransientTailRowCount(): number;
|
|
97
97
|
hasActiveSelection(): boolean;
|
|
98
98
|
setViewportConfig(config: OwnedUiViewportSettings): void;
|
|
@@ -45,7 +45,6 @@ export class OwnedUiSessionShellRoot {
|
|
|
45
45
|
#dockInputReuseEnabled;
|
|
46
46
|
#dockInputCandidate = false;
|
|
47
47
|
#dockInputSnapshot;
|
|
48
|
-
#dockInputStatusSignature;
|
|
49
48
|
#visibleViewportSnapshot;
|
|
50
49
|
#fullViewportCompositions = 0;
|
|
51
50
|
#dockOnlyViewportCompositions = 0;
|
|
@@ -232,14 +231,18 @@ export class OwnedUiSessionShellRoot {
|
|
|
232
231
|
// intentional final gutter after their right-aligned timestamp.
|
|
233
232
|
const documentWidth = width;
|
|
234
233
|
const document = this.#renderDocumentLayout(documentWidth);
|
|
234
|
+
const steeringRows = this.#renderQueued(width);
|
|
235
235
|
const statusRows = this.#renderStatus(width);
|
|
236
|
-
const
|
|
236
|
+
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
237
237
|
const snapshot = this.#visibleViewportSnapshot;
|
|
238
238
|
const dockInputCandidate = this.#dockInputCandidate;
|
|
239
239
|
const dockInputSnapshot = dockInputCandidate ? this.#dockInputSnapshot : undefined;
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
const dockInputTransientSignature = dockInputCandidate
|
|
241
|
+
? dockInputSnapshot?.transientSignature ?? transientSignature
|
|
242
|
+
: transientSignature;
|
|
243
|
+
// Invariant: pending Steering and live Working rows share one non-selectable
|
|
244
|
+
// viewport tail; only non-working status, widgets, input, and footer stay docked.
|
|
245
|
+
const scrollRows = [...document.rows, ...steeringRows, ...statusRows];
|
|
243
246
|
const dockRows = dock.rows;
|
|
244
247
|
const selectableDocumentRowCount = document.rows.length;
|
|
245
248
|
const dockStartRow = height - dockRows.length + 1;
|
|
@@ -256,7 +259,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
256
259
|
&& snapshot.width === width
|
|
257
260
|
&& snapshot.height === height
|
|
258
261
|
&& snapshot.documentRows === (dockInputSnapshot?.documentRows ?? scrollRows)
|
|
259
|
-
&& snapshot.
|
|
262
|
+
&& snapshot.transientSignature === dockInputTransientSignature
|
|
260
263
|
&& snapshot.promptAnchors === (dockInputSnapshot?.promptAnchors ?? document.promptAnchors)
|
|
261
264
|
&& snapshot.dockLength === dockRows.length
|
|
262
265
|
&& snapshot.inputSurface === (dockInputSnapshot?.inputSurface ?? this.#inputSurface)
|
|
@@ -264,9 +267,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
264
267
|
&& snapshot.selectionRevision === (dockInputSnapshot?.selectionRevision ?? this.#viewportController.selectionRevision)
|
|
265
268
|
? this.#viewportController.composeDockOnly(dockRows, width, height)
|
|
266
269
|
: null;
|
|
267
|
-
// Performance: a
|
|
268
|
-
//
|
|
269
|
-
if (frame !== null &&
|
|
270
|
+
// Performance: a spinner tick or queue update can race the input-triggered
|
|
271
|
+
// render. Recompute the complete transient signature before reusing rows.
|
|
272
|
+
if (frame !== null && dockInputTransientSignature !== transientSignature)
|
|
270
273
|
frame = null;
|
|
271
274
|
if (frame === null) {
|
|
272
275
|
frame = this.#viewportController.compose({
|
|
@@ -274,15 +277,16 @@ export class OwnedUiSessionShellRoot {
|
|
|
274
277
|
...(this.#viewportController.transcriptPointerSelecting
|
|
275
278
|
? { paintDocumentRow: heldNativeHyperlinkStyle }
|
|
276
279
|
: {}),
|
|
277
|
-
// Invariant: selection and copying stop at the real document tail;
|
|
278
|
-
//
|
|
280
|
+
// Invariant: selection and copying stop at the real document tail; Steering,
|
|
281
|
+
// fitting alignment, and live Working remain transient presentation chrome.
|
|
279
282
|
selectableDocumentRowCount,
|
|
283
|
+
bottomAlignedTailRowCount: statusRows.length,
|
|
280
284
|
dockRows,
|
|
281
285
|
promptAnchors: document.promptAnchors,
|
|
282
286
|
width,
|
|
283
287
|
height,
|
|
284
|
-
// Invariant: the control belongs immediately above the complete dock
|
|
285
|
-
//
|
|
288
|
+
// Invariant: the control belongs immediately above the complete dock and
|
|
289
|
+
// floats over transient viewport content rather than consuming a dock row.
|
|
286
290
|
bottomControlRow: Math.max(0, height - Math.min(height, dockRows.length) - 1),
|
|
287
291
|
theme: this.#viewportTheme,
|
|
288
292
|
});
|
|
@@ -294,8 +298,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
294
298
|
width,
|
|
295
299
|
height,
|
|
296
300
|
documentRows: scrollRows,
|
|
297
|
-
|
|
298
|
-
dockInputStatusSignature,
|
|
301
|
+
transientSignature,
|
|
299
302
|
promptAnchors: document.promptAnchors,
|
|
300
303
|
dockLength: dockRows.length,
|
|
301
304
|
inputSurface: this.#inputSurface,
|
|
@@ -308,7 +311,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
308
311
|
viewportFrameDescriptor() {
|
|
309
312
|
return this.#viewportController.frame?.descriptor ?? null;
|
|
310
313
|
}
|
|
311
|
-
/** Visible
|
|
314
|
+
/** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
|
|
312
315
|
viewportTransientTailRowCount() {
|
|
313
316
|
return this.#viewportController.frame?.hits.transientTail.length ?? 0;
|
|
314
317
|
}
|
|
@@ -338,7 +341,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
338
341
|
return this.#renderDockLayout(width).rows;
|
|
339
342
|
}
|
|
340
343
|
#renderDockLayout(width) {
|
|
341
|
-
const queued = this.#
|
|
344
|
+
const queued = this.#customViewport ? [] : this.#renderQueued(width);
|
|
342
345
|
const statusRows = this.#customViewport ? this.#status.renderDock(width) : this.#renderStatus(width);
|
|
343
346
|
const transientRows = [...queued, ...statusRows];
|
|
344
347
|
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
@@ -352,6 +355,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
352
355
|
inputRows: input.length,
|
|
353
356
|
};
|
|
354
357
|
}
|
|
358
|
+
#renderQueued(width) {
|
|
359
|
+
return this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
|
|
360
|
+
}
|
|
355
361
|
layoutRoot() {
|
|
356
362
|
const document = layoutPort(width => this.#renderDocument(width), () => this.invalidate());
|
|
357
363
|
const queued = layoutPort(width => this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width), () => this.#queued.invalidate());
|
|
@@ -781,14 +787,15 @@ export class OwnedUiSessionShellRoot {
|
|
|
781
787
|
}
|
|
782
788
|
#captureDockInputSnapshot() {
|
|
783
789
|
const width = Math.max(1, this.#componentRuntime.getColumns());
|
|
790
|
+
const steeringRows = this.#renderQueued(width);
|
|
784
791
|
const statusRows = this.#renderStatus(width);
|
|
785
|
-
|
|
792
|
+
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
786
793
|
const snapshot = this.#visibleViewportSnapshot;
|
|
787
794
|
this.#dockInputSnapshot = snapshot === undefined
|
|
788
795
|
? undefined
|
|
789
796
|
: {
|
|
790
797
|
documentRows: snapshot.documentRows,
|
|
791
|
-
|
|
798
|
+
transientSignature,
|
|
792
799
|
promptAnchors: snapshot.promptAnchors,
|
|
793
800
|
inputSurface: this.#inputSurface,
|
|
794
801
|
viewportRevision: this.#viewportController.presentationRevision,
|
|
@@ -824,7 +831,6 @@ export class OwnedUiSessionShellRoot {
|
|
|
824
831
|
this.#visibleViewportSnapshot = undefined;
|
|
825
832
|
this.#dockInputCandidate = false;
|
|
826
833
|
this.#dockInputSnapshot = undefined;
|
|
827
|
-
this.#dockInputStatusSignature = undefined;
|
|
828
834
|
if (this.#inputSurface !== this.editor)
|
|
829
835
|
this.#inputSurface.dispose?.();
|
|
830
836
|
this.#extensionHeader?.dispose?.();
|
|
@@ -1156,3 +1162,6 @@ function isPackageExtensionSource(sourceInfo) {
|
|
|
1156
1162
|
const source = sourceInfo?.source ?? "";
|
|
1157
1163
|
return source.startsWith("npm:") || source.startsWith("git:");
|
|
1158
1164
|
}
|
|
1165
|
+
function transientRowsSignature(steeringRows, statusRows) {
|
|
1166
|
+
return `${steeringRows.length}\u0000${steeringRows.join("\u0000")}\u0001${statusRows.length}\u0000${statusRows.join("\u0000")}`;
|
|
1167
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-06T15:05:45.656Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "dc03605e5780e2aeebb4ecafd62868dc673e22ddd38b024a369aff704ff136dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-06T15:05:42.977Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-06T15:06:12.448Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "7cca17597d89ae0dad9cd14114acc0795f688d54f6580b897463fba11af479ca",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -24,6 +24,8 @@ export interface TranscriptViewportFrameInput {
|
|
|
24
24
|
readonly paintDocumentRow?: (row: string) => string;
|
|
25
25
|
/** Leading document rows that participate in pointer selection and copying. */
|
|
26
26
|
readonly selectableDocumentRowCount?: number;
|
|
27
|
+
/** Final transient rows to bottom-align with unused viewport space while content fits. */
|
|
28
|
+
readonly bottomAlignedTailRowCount?: number;
|
|
27
29
|
readonly dockRows: readonly string[];
|
|
28
30
|
readonly promptAnchors: readonly TranscriptPromptAnchor[];
|
|
29
31
|
readonly width: number;
|
|
@@ -86,6 +88,12 @@ export interface TranscriptViewportFrameDescriptor {
|
|
|
86
88
|
};
|
|
87
89
|
readonly previousFollowingEnd: boolean | null;
|
|
88
90
|
readonly followingEnd: boolean;
|
|
91
|
+
/** Complete non-selectable suffix, including pending steering, alignment, and status rows. */
|
|
92
|
+
readonly transientRowCount: number;
|
|
93
|
+
/** Flexible rows inserted before the bottom-aligned live status while content fits. */
|
|
94
|
+
readonly transientAlignmentGapRows: number;
|
|
95
|
+
/** Live status rows at the end of the transient suffix. */
|
|
96
|
+
readonly bottomAlignedTailRowCount: number;
|
|
89
97
|
readonly verticalShiftRows: number;
|
|
90
98
|
readonly safeVerticalShift: boolean;
|
|
91
99
|
/** Monotonic interaction revision used to reject stale selection evidence. */
|