@timurproko/a1 0.1.8-dev.299 → 0.1.8-dev.332
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/cli/dispatch.js +1 -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/features/owned-ui/project-trust-prompt.js +1 -1
- package/dist/features/owned-ui/settings-app.js +1 -1
- package/dist/features/prompt-history/service.d.ts +31 -1
- package/dist/features/prompt-history/service.js +294 -129
- package/dist/features/prompt-history/store.d.ts +2 -1
- package/dist/features/prompt-history/store.js +17 -4
- package/dist/features/prompt-history/worker.js +6 -3
- package/dist/foundation/launch-context/index.d.ts +59 -0
- package/dist/foundation/launch-context/index.js +136 -0
- package/dist/foundation/launch-guardian/main.js +6 -4
- package/dist/foundation/release/bootstrap.d.ts +6 -1
- package/dist/foundation/release/bootstrap.js +59 -13
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +1 -0
- package/dist/foundation/release/dependency-certification-retention.d.ts +7 -0
- package/dist/foundation/release/dependency-certification-retention.js +88 -0
- package/dist/foundation/release/dependency-certification.d.ts +24 -0
- package/dist/foundation/release/dependency-certification.js +208 -0
- package/dist/foundation/release/dependency-layer.d.ts +3 -4
- package/dist/foundation/release/dependency-layer.js +6 -37
- package/dist/foundation/release/release-gc.js +74 -46
- package/dist/foundation/release/release-store.d.ts +2 -0
- package/dist/foundation/release/release-store.js +11 -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 +17 -1
- 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 +1 -1
- package/dist/foundation/release/warmup.d.ts +1 -1
- package/dist/foundation/release/warmup.js +18 -6
- 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 +2 -1
- package/dist/integrations/pi/components/owned-editor-ux.js +105 -30
- 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 +33 -1
- package/dist/integrations/pi/engine/adapter.js +309 -112
- package/dist/integrations/pi/engine/changelog.d.ts +2 -0
- package/dist/integrations/pi/engine/changelog.js +63 -0
- package/dist/integrations/pi/engine/pending-delivery.d.ts +26 -0
- package/dist/integrations/pi/engine/pending-delivery.js +149 -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 +74 -37
- package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +0 -2
- package/dist/integrations/pi/session-ui/prompt-history-controller.js +3 -8
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +26 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +70 -75
- package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell.js +67 -13
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +7 -2
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +25 -20
- package/dist/integrations/pi/session-ui/text-paste.d.ts +5 -0
- package/dist/integrations/pi/session-ui/text-paste.js +16 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +9 -2
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +87 -43
- 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 +1 -23
- 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 +91 -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/docs/manual-code-streaming-cleanup.md +88 -0
- package/package.json +8 -3
|
@@ -5,18 +5,20 @@ import { dirname, resolve } from "node:path";
|
|
|
5
5
|
import { homedir, platform } from "node:os";
|
|
6
6
|
import { performance } from "node:perf_hooks";
|
|
7
7
|
import { PRODUCT_IDENTITY } from "../../product-identity.js";
|
|
8
|
+
import { readLaunchContext } from "../launch-context/index.js";
|
|
8
9
|
/** Initialize opt-in startup tracing from a caller-provided evidence path. */
|
|
9
10
|
export function initializeStartupTrace(environment, profileId, startedAtMs = performance.timeOrigin + performance.now()) {
|
|
10
11
|
const configured = environment[PRODUCT_IDENTITY.environment.startupTrace];
|
|
11
12
|
if (!configured || parseContext(configured) !== null)
|
|
12
13
|
return;
|
|
14
|
+
const launch = readLaunchContext(environment);
|
|
13
15
|
const context = {
|
|
14
16
|
path: resolve(configured),
|
|
15
17
|
traceId: randomUUID(),
|
|
16
18
|
startedAtMs,
|
|
17
19
|
profileId,
|
|
18
|
-
releaseId:
|
|
19
|
-
dependencyLayerIds: parseLayerIds(
|
|
20
|
+
releaseId: launch.releaseId ?? null,
|
|
21
|
+
dependencyLayerIds: parseLayerIds(launch.releaseLayers),
|
|
20
22
|
};
|
|
21
23
|
environment[PRODUCT_IDENTITY.environment.startupTrace] = JSON.stringify(context);
|
|
22
24
|
}
|
|
@@ -25,17 +27,18 @@ export async function markStartupPhase(environment, phase) {
|
|
|
25
27
|
const context = parseContext(environment[PRODUCT_IDENTITY.environment.startupTrace]);
|
|
26
28
|
if (context === null)
|
|
27
29
|
return;
|
|
30
|
+
const elapsedMs = Math.max(0, performance.timeOrigin + performance.now() - context.startedAtMs);
|
|
31
|
+
const launch = readLaunchContext(environment);
|
|
32
|
+
const dependencyLayerIds = parseLayerIds(launch.releaseLayers);
|
|
28
33
|
const event = {
|
|
29
34
|
schema: PRODUCT_IDENTITY.evidence.startupTraceSchema,
|
|
30
35
|
traceId: context.traceId,
|
|
31
36
|
phase,
|
|
32
|
-
elapsedMs
|
|
37
|
+
elapsedMs,
|
|
33
38
|
processId: process.pid,
|
|
34
39
|
profileId: context.profileId,
|
|
35
|
-
releaseId:
|
|
36
|
-
dependencyLayerIds:
|
|
37
|
-
? parseLayerIds(environment[PRODUCT_IDENTITY.environment.releaseLayers])
|
|
38
|
-
: context.dependencyLayerIds,
|
|
40
|
+
releaseId: launch.releaseId ?? context.releaseId,
|
|
41
|
+
dependencyLayerIds: dependencyLayerIds.length > 0 ? dependencyLayerIds : context.dependencyLayerIds,
|
|
39
42
|
nodeVersion: process.version,
|
|
40
43
|
fileReadOperations: process.resourceUsage().fsRead,
|
|
41
44
|
};
|
|
@@ -43,9 +46,7 @@ export async function markStartupPhase(environment, phase) {
|
|
|
43
46
|
await appendFile(context.path, `${JSON.stringify(event)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
44
47
|
}
|
|
45
48
|
export function assertImmutableWarmupEnvironment(environment) {
|
|
46
|
-
|
|
47
|
-
throw new Error("warmup entry is private to verified update activation");
|
|
48
|
-
}
|
|
49
|
+
readLaunchContext(environment, "warmup");
|
|
49
50
|
}
|
|
50
51
|
/** Enable the compile cache directly from launch environment without loading profile services. */
|
|
51
52
|
export function enableEnvironmentCompileCache(environment) {
|
|
@@ -54,7 +55,8 @@ export function enableEnvironmentCompileCache(environment) {
|
|
|
54
55
|
?? (platform() === "win32"
|
|
55
56
|
? resolve(environment.LOCALAPPDATA ?? home, PRODUCT_IDENTITY.state.windowsControlDirectory)
|
|
56
57
|
: resolve(environment.XDG_DATA_HOME ?? resolve(home, ".local", "share"), PRODUCT_IDENTITY.state.unixControlDirectory)));
|
|
57
|
-
|
|
58
|
+
const launch = readLaunchContext(environment);
|
|
59
|
+
return enableStartupCompileCache(dataDir, launch.releaseId ?? null, parseLayerIds(launch.releaseLayers));
|
|
58
60
|
}
|
|
59
61
|
/** Enable Node's supported persistent compile cache in an immutable-identity namespace. */
|
|
60
62
|
export function enableStartupCompileCache(dataDir, releaseId, dependencyLayerIds) {
|
|
@@ -6,16 +6,18 @@ import { publishSupervisorStartupResult, supervisorStartupFailure, supervisorSta
|
|
|
6
6
|
import { ControlStore } from "../storage/index.js";
|
|
7
7
|
import { resolveCohortEndpoint, resolveProductPaths } from "./paths.js";
|
|
8
8
|
import { SupervisorServer } from "./server.js";
|
|
9
|
-
import {
|
|
9
|
+
import { PRODUCT_TEXT } from "../../product-identity.js";
|
|
10
|
+
import { readLaunchContext } from "../launch-context/index.js";
|
|
10
11
|
export async function runSupervisor(arguments_ = []) {
|
|
11
12
|
const productPaths = resolveProductPaths();
|
|
12
13
|
mkdirSync(productPaths.runtimeDir, { recursive: true, mode: 0o700 });
|
|
13
14
|
mkdirSync(productPaths.endpointsDir, { recursive: true, mode: 0o700 });
|
|
14
15
|
let paths = productPaths;
|
|
15
16
|
const log = (message) => appendFileSync(paths.supervisorLogPath, `${new Date().toISOString()} ${message}\n`);
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const
|
|
17
|
+
const context = readLaunchContext(process.env, "release");
|
|
18
|
+
const releaseRoot = context.releaseRoot;
|
|
19
|
+
const releaseId = context.releaseId;
|
|
20
|
+
const contentDigest = context.releaseDigest;
|
|
19
21
|
const attemptId = startupAttempt(arguments_);
|
|
20
22
|
const resultPath = attemptId ? supervisorStartupResultPath(productPaths.runtimeDir, attemptId) : null;
|
|
21
23
|
let stage = "release-environment";
|
|
@@ -24,9 +26,8 @@ export async function runSupervisor(arguments_ = []) {
|
|
|
24
26
|
if (!releaseRoot || !releaseId || !contentDigest)
|
|
25
27
|
throw new Error(PRODUCT_TEXT.diagnostic("supervisor must be launched from a verified immutable release"));
|
|
26
28
|
stage = "release-certification";
|
|
27
|
-
const release = await readCertifiedReleaseManifest({ releaseRoot, releaseId, contentDigest }, resolve(paths.dataDir, "releases")
|
|
28
|
-
//
|
|
29
|
-
// existed, even when its layer certification has already been upgraded by a retry.
|
|
29
|
+
const release = await readCertifiedReleaseManifest({ releaseRoot, releaseId, contentDigest }, resolve(paths.dataDir, "releases"));
|
|
30
|
+
// Invariant: readiness follows current-contract certification, never an old-updater upgrade path.
|
|
30
31
|
await recordParentCertifiedRelease(release, paths.dataDir);
|
|
31
32
|
stage = "immutable-root";
|
|
32
33
|
await assertImmutableExecutionRoot(release, paths.dataDir);
|
|
@@ -8,7 +8,7 @@ import type { PiShellClipboardContent, PiShellEditorTextRange } from "./shell-sh
|
|
|
8
8
|
* Pi editor port and gives future interactions one registration point.
|
|
9
9
|
*/
|
|
10
10
|
export interface OwnedEditorUxInterceptor {
|
|
11
|
-
handleInput(data: string, next: () => void): void;
|
|
11
|
+
handleInput(data: string, next: (data?: string) => void): void;
|
|
12
12
|
render(width: number, next: () => string[]): string[];
|
|
13
13
|
reset(): void;
|
|
14
14
|
handlePointer?(event: OwnedEditorPointerEvent): boolean;
|
|
@@ -52,6 +52,7 @@ export interface PromptSelectionUxOptions {
|
|
|
52
52
|
};
|
|
53
53
|
readonly transformPastedContent: (content: PiShellClipboardContent) => string;
|
|
54
54
|
readonly atomicRanges: (line: string) => readonly PiShellEditorTextRange[];
|
|
55
|
+
readonly hiddenRanges?: (line: string) => readonly PiShellEditorTextRange[];
|
|
55
56
|
readonly expandCopiedText: (text: string) => string;
|
|
56
57
|
readonly paintSelection: (line: string, from: number, to: number, atomic: boolean) => string;
|
|
57
58
|
readonly decorateRow: (row: string, width: number) => string;
|
|
@@ -8,15 +8,15 @@ export class OwnedEditorUxInterception {
|
|
|
8
8
|
this.fallback = fallback;
|
|
9
9
|
}
|
|
10
10
|
handleInput(data) {
|
|
11
|
-
const invoke = (index) => {
|
|
11
|
+
const invoke = (index, input) => {
|
|
12
12
|
const interceptor = this.interceptors[index];
|
|
13
13
|
if (interceptor === undefined) {
|
|
14
|
-
this.fallback.handleInput(
|
|
14
|
+
this.fallback.handleInput(input);
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
|
-
interceptor.handleInput(
|
|
17
|
+
interceptor.handleInput(input, (replacement = input) => invoke(index + 1, replacement));
|
|
18
18
|
};
|
|
19
|
-
invoke(0);
|
|
19
|
+
invoke(0, data);
|
|
20
20
|
}
|
|
21
21
|
render(width) {
|
|
22
22
|
const invoke = (index) => {
|
|
@@ -65,6 +65,7 @@ class PromptSelectionInterceptor {
|
|
|
65
65
|
#redoStack = [];
|
|
66
66
|
#selectionRevision = 0;
|
|
67
67
|
#pasteGeneration = 0;
|
|
68
|
+
#terminalPaste;
|
|
68
69
|
#wordDirection;
|
|
69
70
|
#geometry;
|
|
70
71
|
constructor(editor, keybindings, options) {
|
|
@@ -74,6 +75,8 @@ class PromptSelectionInterceptor {
|
|
|
74
75
|
installAtomicSegmentation(editor, options.atomicRanges, () => this.#wordDirection);
|
|
75
76
|
}
|
|
76
77
|
handleInput(data, next) {
|
|
78
|
+
if (this.#handleTerminalPaste(data, next))
|
|
79
|
+
return;
|
|
77
80
|
if (this.keybindings.matches(data, "owned.editor.selectAll")) {
|
|
78
81
|
this.#selectAll();
|
|
79
82
|
return;
|
|
@@ -105,23 +108,6 @@ class PromptSelectionInterceptor {
|
|
|
105
108
|
this.#redo();
|
|
106
109
|
return;
|
|
107
110
|
}
|
|
108
|
-
const terminalPaste = bracketedPasteContent(data);
|
|
109
|
-
if (terminalPaste !== undefined) {
|
|
110
|
-
if (terminalPaste.length === 0) {
|
|
111
|
-
this.pasteClipboard();
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
const transformed = this.options.transformPastedContent({ kind: "text", text: terminalPaste });
|
|
115
|
-
if (transformed !== terminalPaste) {
|
|
116
|
-
if (this.#orderedSelection() !== undefined)
|
|
117
|
-
this.#replaceSelection(transformed);
|
|
118
|
-
else
|
|
119
|
-
this.editor.insertTextAtCursor(transformed);
|
|
120
|
-
this.#redoStack = [];
|
|
121
|
-
this.#requestRender();
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
111
|
if (this.keybindings.matches(data, "tui.editor.undo")) {
|
|
126
112
|
const before = this.#snapshot();
|
|
127
113
|
this.#clearSelection(false);
|
|
@@ -202,6 +188,47 @@ class PromptSelectionInterceptor {
|
|
|
202
188
|
this.#redoStack = [];
|
|
203
189
|
}
|
|
204
190
|
render(width, next) {
|
|
191
|
+
const state = editorState(this.editor);
|
|
192
|
+
const hidden = state.lines.map(line => this.options.hiddenRanges?.(line) ?? []);
|
|
193
|
+
if (!hidden.some(ranges => ranges.length > 0))
|
|
194
|
+
return this.#renderVisible(width, next);
|
|
195
|
+
const original = { ...state, lines: [...state.lines] };
|
|
196
|
+
const selection = this.#selection;
|
|
197
|
+
const project = (position) => ({
|
|
198
|
+
line: position.line,
|
|
199
|
+
col: hideColumn(position.col, hidden[position.line] ?? []),
|
|
200
|
+
});
|
|
201
|
+
const lines = state.lines.map((line, index) => {
|
|
202
|
+
for (const range of [...(hidden[index] ?? [])].reverse())
|
|
203
|
+
line = line.slice(0, range.start) + line.slice(range.end);
|
|
204
|
+
return line;
|
|
205
|
+
});
|
|
206
|
+
// Rationale: project pending clipboard identities out before layout, not after painting.
|
|
207
|
+
// Keep the semantic draft/undo/submission markers intact and restore them even if rendering throws.
|
|
208
|
+
try {
|
|
209
|
+
this.#setRenderState(lines, project({ line: original.cursorLine, col: original.cursorCol }));
|
|
210
|
+
if (selection !== undefined)
|
|
211
|
+
this.#selection = { anchor: project(selection.anchor), head: project(selection.head) };
|
|
212
|
+
const rows = this.#renderVisible(width, next);
|
|
213
|
+
if (this.#geometry !== undefined) {
|
|
214
|
+
this.#geometry.hiddenRanges = hidden;
|
|
215
|
+
this.#geometry.lines = lines;
|
|
216
|
+
}
|
|
217
|
+
return rows;
|
|
218
|
+
}
|
|
219
|
+
finally {
|
|
220
|
+
this.#setRenderState(original.lines, { line: original.cursorLine, col: original.cursorCol });
|
|
221
|
+
this.#selection = selection;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
#setRenderState(lines, cursor) {
|
|
225
|
+
if (this.editor.interaction !== undefined)
|
|
226
|
+
this.editor.interaction.replaceLines(lines);
|
|
227
|
+
else
|
|
228
|
+
editorState(this.editor).lines = lines;
|
|
229
|
+
this.#setCursor(cursor);
|
|
230
|
+
}
|
|
231
|
+
#renderVisible(width, next) {
|
|
205
232
|
const rows = next().map(row => row.replaceAll(ATOMIC_SPACE_SENTINEL, " "));
|
|
206
233
|
const maxPadding = Math.max(0, Math.floor((width - 1) / 2));
|
|
207
234
|
const padding = Math.min(this.editor.getPaddingX(), maxPadding);
|
|
@@ -214,7 +241,7 @@ class PromptSelectionInterceptor {
|
|
|
214
241
|
const scrollOffset = this.editor.interaction?.scrollOffset() ?? numericProperty(this.editor, "scrollOffset");
|
|
215
242
|
const maxVisibleLines = Math.max(5, Math.floor(this.options.getRows() * 0.3));
|
|
216
243
|
const textRows = Math.max(0, Math.min(visualLines.length - scrollOffset, maxVisibleLines, rows.length - 2));
|
|
217
|
-
this.#geometry = { width, padding, layoutWidth, visualLines, scrollOffset, textRows };
|
|
244
|
+
this.#geometry = { width, padding, layoutWidth, visualLines, scrollOffset, textRows, hiddenRanges: [] };
|
|
218
245
|
if (this.#atomicFocus() !== undefined) {
|
|
219
246
|
for (let row = 0; row < rows.length; row += 1) {
|
|
220
247
|
rows[row] = (rows[row] ?? "").replaceAll(CURSOR_MARKER, "");
|
|
@@ -250,7 +277,10 @@ class PromptSelectionInterceptor {
|
|
|
250
277
|
this.#redoStack = [];
|
|
251
278
|
this.#selectionRevision += 1;
|
|
252
279
|
}
|
|
253
|
-
cancelPendingPastes() {
|
|
280
|
+
cancelPendingPastes() {
|
|
281
|
+
this.#pasteGeneration += 1;
|
|
282
|
+
this.#terminalPaste = undefined;
|
|
283
|
+
}
|
|
254
284
|
hasSelection() {
|
|
255
285
|
return this.#activeRange() !== undefined;
|
|
256
286
|
}
|
|
@@ -344,12 +374,12 @@ class PromptSelectionInterceptor {
|
|
|
344
374
|
const visual = geometry.visualLines[geometry.scrollOffset + textRow];
|
|
345
375
|
if (visual === undefined)
|
|
346
376
|
return undefined;
|
|
347
|
-
const line = editorState(this.editor).lines[visual.logicalLine] ?? "";
|
|
377
|
+
const line = (geometry.lines ?? editorState(this.editor).lines)[visual.logicalLine] ?? "";
|
|
348
378
|
const segment = line.slice(visual.startCol, visual.startCol + visual.length);
|
|
349
379
|
const displayColumn = Math.max(0, column - 1 - geometry.padding - (this.options.promptPrefixWidth ?? 0));
|
|
350
380
|
return {
|
|
351
381
|
line: visual.logicalLine,
|
|
352
|
-
col: visual.startCol + indexAtDisplayWidth(segment, displayColumn),
|
|
382
|
+
col: restoreColumn(visual.startCol + indexAtDisplayWidth(segment, displayColumn), geometry.hiddenRanges[visual.logicalLine] ?? []),
|
|
353
383
|
};
|
|
354
384
|
}
|
|
355
385
|
#selectWord(position) {
|
|
@@ -409,6 +439,46 @@ class PromptSelectionInterceptor {
|
|
|
409
439
|
this.#selectionRevision += 1;
|
|
410
440
|
this.#requestRender();
|
|
411
441
|
}
|
|
442
|
+
#handleTerminalPaste(data, next) {
|
|
443
|
+
const start = data.indexOf("\x1b[200~");
|
|
444
|
+
if (this.#terminalPaste === undefined) {
|
|
445
|
+
if (start < 0)
|
|
446
|
+
return false;
|
|
447
|
+
if (start > 0) {
|
|
448
|
+
const before = data.slice(0, start);
|
|
449
|
+
this.handleInput(before, (replacement = before) => next(replacement));
|
|
450
|
+
}
|
|
451
|
+
this.#terminalPaste = "";
|
|
452
|
+
data = data.slice(start + 6);
|
|
453
|
+
}
|
|
454
|
+
// Compatibility: the terminal's StdinBuffer assembles split opening delimiters;
|
|
455
|
+
// retain the body here too so editor-level chunks cannot allocate a native paste ID.
|
|
456
|
+
this.#terminalPaste += data;
|
|
457
|
+
const end = this.#terminalPaste.indexOf("\x1b[201~");
|
|
458
|
+
if (end < 0)
|
|
459
|
+
return true;
|
|
460
|
+
const text = this.#terminalPaste.slice(0, end);
|
|
461
|
+
const remaining = this.#terminalPaste.slice(end + 6);
|
|
462
|
+
this.#terminalPaste = undefined;
|
|
463
|
+
if (text.length === 0)
|
|
464
|
+
this.pasteClipboard();
|
|
465
|
+
else {
|
|
466
|
+
const transformed = this.options.transformPastedContent({ kind: "text", text });
|
|
467
|
+
if (transformed === text && this.#orderedSelection() === undefined)
|
|
468
|
+
next(`\x1b[200~${text}\x1b[201~`);
|
|
469
|
+
else {
|
|
470
|
+
if (this.#orderedSelection() !== undefined)
|
|
471
|
+
this.#replaceSelection(transformed);
|
|
472
|
+
else
|
|
473
|
+
this.editor.insertTextAtCursor(transformed);
|
|
474
|
+
this.#redoStack = [];
|
|
475
|
+
this.#requestRender();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
if (remaining.length > 0)
|
|
479
|
+
this.handleInput(remaining, (replacement = remaining) => next(replacement));
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
412
482
|
#pasteFromClipboard() {
|
|
413
483
|
if (this.options.beginClipboardPaste !== undefined) {
|
|
414
484
|
const generation = this.#pasteGeneration;
|
|
@@ -761,11 +831,6 @@ function isEditorSegment(value) {
|
|
|
761
831
|
const input = Reflect.get(value, "input");
|
|
762
832
|
return typeof segment === "string" && typeof index === "number" && typeof input === "string";
|
|
763
833
|
}
|
|
764
|
-
function bracketedPasteContent(data) {
|
|
765
|
-
const start = data.indexOf("\u001b[200~");
|
|
766
|
-
const end = data.indexOf("\u001b[201~", start + 6);
|
|
767
|
-
return start < 0 || end < 0 ? undefined : data.slice(start + 6, end);
|
|
768
|
-
}
|
|
769
834
|
function insertedText(data) {
|
|
770
835
|
if (!data || data.includes("\u001b[200~"))
|
|
771
836
|
return undefined;
|
|
@@ -804,6 +869,16 @@ function samePosition(left, right) {
|
|
|
804
869
|
function sameSnapshot(left, right) {
|
|
805
870
|
return left.text === right.text && samePosition(left.cursor, right.cursor);
|
|
806
871
|
}
|
|
872
|
+
function hideColumn(column, ranges) {
|
|
873
|
+
return column - ranges.reduce((removed, range) => removed + Math.max(0, Math.min(column, range.end) - range.start), 0);
|
|
874
|
+
}
|
|
875
|
+
function restoreColumn(column, ranges) {
|
|
876
|
+
let restored = column;
|
|
877
|
+
for (const range of ranges)
|
|
878
|
+
if (restored >= range.start)
|
|
879
|
+
restored += range.end - range.start;
|
|
880
|
+
return restored;
|
|
881
|
+
}
|
|
807
882
|
function editorState(editor) {
|
|
808
883
|
if (editor.interaction !== undefined)
|
|
809
884
|
return editor.interaction.snapshot();
|
|
@@ -64,6 +64,7 @@ export function createPiShellEditor(options) {
|
|
|
64
64
|
...(options.beginClipboardPaste === undefined ? {} : { beginClipboardPaste: options.beginClipboardPaste }),
|
|
65
65
|
transformPastedContent: options.transformPastedContent ?? (content => content.kind === "text" ? content.text : ""),
|
|
66
66
|
atomicRanges: options.editorAtomicRanges ?? (() => []),
|
|
67
|
+
hiddenRanges: options.editorHiddenRanges ?? (() => []),
|
|
67
68
|
expandCopiedText: options.expandCopiedEditorText ?? (text => text),
|
|
68
69
|
paintSelection: options.paintEditorSelection ?? (line => line),
|
|
69
70
|
decorateRow: options.decorateEditorRow ?? (row => row),
|
|
@@ -149,6 +150,8 @@ export function createPiShellEditor(options) {
|
|
|
149
150
|
return {
|
|
150
151
|
render: width => editorUx?.render(width) ?? editor.render(width),
|
|
151
152
|
activateKeybindings: () => setKeybindings(keybindings),
|
|
153
|
+
keybindingConfig: () => keybindings.getEffectiveConfig(),
|
|
154
|
+
reloadKeybindings: () => { keybindings.reload(); setKeybindings(keybindings); },
|
|
152
155
|
matchesTerminalKey: (data, key) => matchesKey(data, key),
|
|
153
156
|
handleInput: data => {
|
|
154
157
|
if (editorUx === undefined)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type KeybindingsConfig } from "#pi-tui";
|
|
2
|
+
import { type PiShellComponentPort, type PiShellExtensionRendererResolver } from "./shell-shared-facade.js";
|
|
2
3
|
export interface PiShellSessionInfoPresentation {
|
|
3
4
|
readonly sessionName?: string;
|
|
4
5
|
readonly stats: {
|
|
@@ -30,7 +31,13 @@ export interface PiShellSessionInfoPresentation {
|
|
|
30
31
|
}[];
|
|
31
32
|
}
|
|
32
33
|
export declare function renderPiShellStatusText(message: string, width: number, outputPad?: 0 | 1): readonly string[];
|
|
34
|
+
export interface PiShellCommandMessagePresentation {
|
|
35
|
+
readonly kind: "error" | "warning" | "accent" | "new" | "name" | "debug";
|
|
36
|
+
readonly message: string;
|
|
37
|
+
readonly detail?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function renderPiShellCommandMessage(presentation: PiShellCommandMessagePresentation, width: number, outputPad?: 0 | 1): readonly string[];
|
|
33
40
|
export declare function createPiShellSessionInfo(presentation: PiShellSessionInfoPresentation): PiShellComponentPort;
|
|
34
41
|
export declare function createPiShellCollapsedChangelog(): PiShellComponentPort;
|
|
35
42
|
export declare function createPiShellChangelog(markdown: string): PiShellComponentPort;
|
|
36
|
-
export declare function createPiShellHotkeys(): PiShellComponentPort;
|
|
43
|
+
export declare function createPiShellHotkeys(bindings?: KeybindingsConfig, getShortcuts?: NonNullable<PiShellExtensionRendererResolver["getShortcuts"]>, profile?: "pi" | "a1"): PiShellComponentPort;
|
|
@@ -7,6 +7,22 @@ export function renderPiShellStatusText(message, width, outputPad = PINNED_PI_LA
|
|
|
7
7
|
ensureTheme();
|
|
8
8
|
return new Text(piTheme().fg("dim", message), outputPad, 0).render(width);
|
|
9
9
|
}
|
|
10
|
+
export function renderPiShellCommandMessage(presentation, width, outputPad = PINNED_PI_LAYOUT.outputPad) {
|
|
11
|
+
ensureTheme();
|
|
12
|
+
const { kind, message, detail } = presentation;
|
|
13
|
+
const prefix = kind === "error" ? "Error: " : kind === "warning" ? "Warning: " : "";
|
|
14
|
+
const color = kind === "name" ? "dim" : kind === "error" || kind === "warning" ? kind : "accent";
|
|
15
|
+
const content = piTheme().fg(color, `${prefix}${message}`)
|
|
16
|
+
+ (kind === "debug" && detail ? `\n${piTheme().fg("muted", detail)}` : "");
|
|
17
|
+
// Compatibility: pinned errors honor outputPad; warnings and command notices retain
|
|
18
|
+
// one-cell padding. New-session/debug notices also own one vertical padding row.
|
|
19
|
+
const text = new Text(content, kind === "error" ? outputPad : 1, kind === "new" || kind === "debug" ? 1 : 0);
|
|
20
|
+
return [
|
|
21
|
+
...(kind === "name" && detail ? renderPiShellCommandMessage({ kind: "warning", message: detail }, width) : []),
|
|
22
|
+
...new Spacer(1).render(width),
|
|
23
|
+
...text.render(width),
|
|
24
|
+
];
|
|
25
|
+
}
|
|
10
26
|
export function createPiShellSessionInfo(presentation) {
|
|
11
27
|
ensureTheme();
|
|
12
28
|
const { stats, sessionName, cacheWaste, usageBreakdown } = presentation;
|
|
@@ -61,12 +77,27 @@ export function createPiShellChangelog(markdown) {
|
|
|
61
77
|
container.addChild(new DynamicBorder());
|
|
62
78
|
return componentPort(container);
|
|
63
79
|
}
|
|
64
|
-
|
|
80
|
+
function shortcutDisplay(key) {
|
|
81
|
+
// Platform: pinned Pi labels Alt as Option on macOS, including extension shortcuts.
|
|
82
|
+
return key.split("/").map(binding => binding.split("+").map(part => {
|
|
83
|
+
const label = process.platform === "darwin" && part.toLowerCase() === "alt" ? "option" : part;
|
|
84
|
+
return label.charAt(0).toUpperCase() + label.slice(1);
|
|
85
|
+
}).join("+")).join("/");
|
|
86
|
+
}
|
|
87
|
+
export function createPiShellHotkeys(bindings, getShortcuts = () => [], profile = "pi") {
|
|
65
88
|
ensureTheme();
|
|
66
|
-
const keys = new KeybindingsManager();
|
|
67
|
-
|
|
89
|
+
const keys = new KeybindingsManager(bindings);
|
|
90
|
+
// Compatibility: the owned viewport consumes these physical chords before editor actions.
|
|
91
|
+
const display = (action) => keys.getKeys(action)
|
|
92
|
+
.filter(key => profile !== "a1" || (key !== "ctrl+home" && key !== "ctrl+end"))
|
|
93
|
+
.map(shortcutDisplay).join("/");
|
|
68
94
|
const row = (actions, description) => `| ${actions.map(action => `\`${display(action)}\``).join(" / ")} | ${description} |`;
|
|
69
|
-
|
|
95
|
+
let markdown = ["**Navigation**", "| Key | Action |", "|-----|--------|", row(["tui.editor.cursorUp", "tui.editor.cursorDown", "tui.editor.cursorLeft", "tui.editor.cursorRight"], "Move cursor / browse history"), row(["tui.editor.cursorWordLeft", "tui.editor.cursorWordRight"], "Move by word"), row(["tui.editor.cursorLineStart"], profile === "a1" ? "Start of prompt line" : "Start of line"), row(["tui.editor.cursorLineEnd"], profile === "a1" ? "End of prompt line" : "End of line"), ...(profile === "a1" ? ["| `Ctrl+Home` | Start of content |", "| `Ctrl+End` | End of content / follow output |"] : []), row(["tui.editor.jumpForward"], "Jump forward to character"), row(["tui.editor.jumpBackward"], "Jump backward to character"), row(["tui.editor.pageUp", "tui.editor.pageDown"], "Scroll by page"), "", "**Editing**", "| Key | Action |", "|-----|--------|", row(["tui.input.submit"], "Send message"), row(["tui.input.newLine"], `New line${process.platform === "win32" ? " (Ctrl+Enter on Windows Terminal)" : ""}`), row(["tui.editor.deleteWordBackward"], "Delete word backwards"), row(["tui.editor.deleteWordForward"], "Delete word forwards"), row(["tui.editor.deleteToLineStart"], "Delete to start of line"), row(["tui.editor.deleteToLineEnd"], "Delete to end of line"), row(["tui.editor.yank"], "Paste the most-recently-deleted text"), row(["tui.editor.yankPop"], "Cycle through the deleted text after pasting"), row(["tui.editor.undo"], "Undo"), "", "**Other**", "| Key | Action |", "|-----|--------|", row(["tui.input.tab"], "Path completion / accept autocomplete"), row(["app.interrupt"], "Cancel autocomplete / abort streaming"), row(["app.clear"], "Clear editor (first) / exit (second)"), row(["app.exit"], "Exit (when editor is empty)"), row(["app.suspend"], "Suspend to background"), row(["app.thinking.cycle"], "Cycle thinking level"), row(["app.model.cycleForward", "app.model.cycleBackward"], "Cycle models"), row(["app.model.select"], "Open model selector"), row(["app.tools.expand"], "Toggle tool output expansion"), row(["app.thinking.toggle"], "Toggle thinking block visibility"), row(["app.editor.external"], "Edit message in external editor"), row(["app.message.copy"], "Copy last assistant message"), row(["app.message.followUp"], "Queue follow-up message"), row(["app.message.dequeue"], "Restore queued messages"), row(["app.clipboard.pasteImage"], "Paste image or text from clipboard"), "| `/` | Slash commands |", "| `!` | Run bash command |", "| `!!` | Run bash command (excluded from context) |"].join("\n");
|
|
96
|
+
const shortcuts = getShortcuts(bindings ?? keys.getEffectiveConfig());
|
|
97
|
+
if (shortcuts.length > 0) {
|
|
98
|
+
markdown += "\n\n**Extensions**\n| Key | Action |\n|-----|--------|\n";
|
|
99
|
+
markdown += shortcuts.map(shortcut => `| \`${shortcutDisplay(shortcut.key)}\` | ${shortcut.description} |`).join("\n");
|
|
100
|
+
}
|
|
70
101
|
const container = new Container();
|
|
71
102
|
container.addChild(new Spacer(1));
|
|
72
103
|
container.addChild(new DynamicBorder());
|
|
@@ -67,6 +67,7 @@ export declare function createPiShellTreeSelector(options: {
|
|
|
67
67
|
readonly onSelect: (id: string) => void;
|
|
68
68
|
readonly onCancel: () => void;
|
|
69
69
|
readonly onLabelChange: (entryId: string, label: string | undefined) => void;
|
|
70
|
+
readonly onCopy?: (text: string | undefined) => void;
|
|
70
71
|
readonly initialSelectedId?: string;
|
|
71
72
|
readonly initialFilterMode?: "default" | "no-tools" | "user-only" | "labeled-only" | "all";
|
|
72
73
|
}): PiShellComponentPort;
|
|
@@ -87,7 +88,7 @@ export interface PiShellLoginDialogPort extends PiShellComponentPort {
|
|
|
87
88
|
showWaiting(message: string): void;
|
|
88
89
|
showProgress(message: string): void;
|
|
89
90
|
}
|
|
90
|
-
export declare function createPiShellLoginDialog(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">, providerId: string, onComplete: (success: boolean, message?: string) => void, providerName?: string): PiShellLoginDialogPort;
|
|
91
|
+
export declare function createPiShellLoginDialog(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">, providerId: string, onComplete: (success: boolean, message?: string) => void, providerName?: string, title?: string): PiShellLoginDialogPort;
|
|
91
92
|
export declare function createPiShellArmin(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellComponentPort;
|
|
92
93
|
export declare function createPiShellDaxnuts(runtime: Pick<PiShellEditorOptions, "getColumns" | "getRows" | "requestRender">): PiShellComponentPort;
|
|
93
94
|
export declare function createPiShellEarendilAnnouncement(): PiShellComponentPort;
|
|
@@ -104,7 +105,7 @@ export interface PiShellAuthProviderOption extends PiShellSelectorOption {
|
|
|
104
105
|
readonly source?: string;
|
|
105
106
|
};
|
|
106
107
|
}
|
|
107
|
-
export declare function createPiShellAuthProviderSelector(mode: "login" | "logout", providers: readonly PiShellAuthProviderOption[], onSelect: (id: string) => void, onCancel: () => void): PiShellComponentPort;
|
|
108
|
+
export declare function createPiShellAuthProviderSelector(mode: "login" | "logout", providers: readonly PiShellAuthProviderOption[], onSelect: (id: string) => void, onCancel: () => void, initialSearchInput?: string): PiShellComponentPort;
|
|
108
109
|
export declare function createPiShellExtensionSelector(title: string, options: readonly string[], onSelect: (value: string) => void, onCancel: () => void): PiShellComponentPort;
|
|
109
110
|
export declare function createPiShellThinkingSelector(currentLevel: "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max", availableLevels: readonly ("off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max")[], onSelect: (level: string) => void, onCancel: () => void): PiShellComponentPort;
|
|
110
111
|
export declare function createPiShellThemeSelector(currentTheme: string, onSelect: (theme: string) => void, onCancel: () => void, onPreview: (theme: string) => void): PiShellComponentPort;
|
|
@@ -111,7 +111,10 @@ export function createPiShellSessionSelector(options) {
|
|
|
111
111
|
}
|
|
112
112
|
export function createPiShellTreeSelector(options) {
|
|
113
113
|
ensureTheme();
|
|
114
|
-
|
|
114
|
+
const selector = new TreeSelectorComponent([...options.tree], options.currentLeafId, options.terminalHeight, options.onSelect, options.onCancel, options.onLabelChange, options.initialSelectedId, options.initialFilterMode);
|
|
115
|
+
if (options.onCopy)
|
|
116
|
+
selector.onCopy = options.onCopy;
|
|
117
|
+
return componentPort(selector);
|
|
115
118
|
}
|
|
116
119
|
export function createPiShellUserMessageSelector(messages, onSelect, onCancel, initialSelectedId) {
|
|
117
120
|
ensureTheme();
|
|
@@ -119,9 +122,9 @@ export function createPiShellUserMessageSelector(messages, onSelect, onCancel, i
|
|
|
119
122
|
const list = selector.getMessageList();
|
|
120
123
|
return componentPort(selector, data => list.handleInput(data));
|
|
121
124
|
}
|
|
122
|
-
export function createPiShellLoginDialog(runtime, providerId, onComplete, providerName) {
|
|
125
|
+
export function createPiShellLoginDialog(runtime, providerId, onComplete, providerName, title) {
|
|
123
126
|
ensureTheme();
|
|
124
|
-
const dialog = new LoginDialogComponent(createTuiFacade(runtime), providerId, onComplete, providerName);
|
|
127
|
+
const dialog = new LoginDialogComponent(createTuiFacade(runtime), providerId, onComplete, providerName, title);
|
|
125
128
|
return {
|
|
126
129
|
...componentPort(dialog),
|
|
127
130
|
showAuth: (url, instructions) => dialog.showAuth(url, instructions),
|
|
@@ -162,7 +165,7 @@ export function createPiShellReloadBox() {
|
|
|
162
165
|
container.addChild(new DynamicBorder(borderColor));
|
|
163
166
|
return componentPort(container);
|
|
164
167
|
}
|
|
165
|
-
export function createPiShellAuthProviderSelector(mode, providers, onSelect, onCancel) {
|
|
168
|
+
export function createPiShellAuthProviderSelector(mode, providers, onSelect, onCancel, initialSearchInput) {
|
|
166
169
|
ensureTheme();
|
|
167
170
|
const selector = new OAuthSelectorComponent(mode, providers.map(provider => ({
|
|
168
171
|
id: provider.providerId,
|
|
@@ -173,7 +176,7 @@ export function createPiShellAuthProviderSelector(mode, providers, onSelect, onC
|
|
|
173
176
|
const selected = providers.find(provider => provider.providerId === providerId && provider.authType === authType);
|
|
174
177
|
if (selected)
|
|
175
178
|
onSelect(selected.id);
|
|
176
|
-
}, onCancel);
|
|
179
|
+
}, onCancel, initialSearchInput);
|
|
177
180
|
return componentPort(selector);
|
|
178
181
|
}
|
|
179
182
|
export function createPiShellExtensionSelector(title, options, onSelect, onCancel) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { visibleWidth, type AutocompleteProvider, type Component, type TUI } from "#pi-tui";
|
|
1
|
+
import { visibleWidth, type AutocompleteProvider, type Component, type KeybindingsConfig, type TUI } from "#pi-tui";
|
|
2
2
|
import type { OwnedUiImageAttachment, OwnedUiSessionViewModel, OwnedUiThinkingLevel, OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
|
|
3
3
|
import type { HistoryEditorConstructor } from "./history-editor-loader.js";
|
|
4
4
|
import type { EditorRecallPort } from "./editor-interaction.js";
|
|
@@ -31,8 +31,10 @@ export interface PiShellEditorPort extends PiShellComponentPort {
|
|
|
31
31
|
readonly historyReplacement?: typeof import("../../../contracts/owned-ui/index.js").PROMPT_HISTORY_EDITOR_REPLACEMENT;
|
|
32
32
|
/** Restores this editor's profile after another Pi component changed the global manager. */
|
|
33
33
|
activateKeybindings(): void;
|
|
34
|
+
keybindingConfig(): KeybindingsConfig;
|
|
35
|
+
reloadKeybindings(): void;
|
|
34
36
|
/** Uses Pi's terminal decoder rather than assuming one terminal escape spelling. */
|
|
35
|
-
matchesTerminalKey(data: string, key: "home" | "end" | "ctrl+v"): boolean;
|
|
37
|
+
matchesTerminalKey(data: string, key: "home" | "end" | "ctrl+home" | "ctrl+end" | "ctrl+v"): boolean;
|
|
36
38
|
getText(): string;
|
|
37
39
|
setText(text: string): void;
|
|
38
40
|
insertText(text: string): void;
|
|
@@ -94,6 +96,10 @@ export interface PiShellImageAssetResolver {
|
|
|
94
96
|
export interface PiShellExtensionRendererResolver {
|
|
95
97
|
getMessageRenderer(customType: string): unknown;
|
|
96
98
|
getToolDefinition(toolName: string): unknown;
|
|
99
|
+
getShortcuts?(bindings: KeybindingsConfig): readonly {
|
|
100
|
+
readonly key: string;
|
|
101
|
+
readonly description: string;
|
|
102
|
+
}[];
|
|
97
103
|
}
|
|
98
104
|
export interface PiShellTranscriptComponentPort extends PiShellComponentPort {
|
|
99
105
|
readonly id: string;
|
|
@@ -147,6 +153,7 @@ export interface PiShellEditorOptions {
|
|
|
147
153
|
readonly marker: string;
|
|
148
154
|
readonly result: Promise<string>;
|
|
149
155
|
};
|
|
156
|
+
readonly editorHiddenRanges?: (line: string) => readonly PiShellEditorTextRange[];
|
|
150
157
|
readonly transformPastedContent?: (content: PiShellClipboardContent) => string;
|
|
151
158
|
readonly editorAtomicRanges?: (line: string) => readonly PiShellEditorTextRange[];
|
|
152
159
|
readonly expandCopiedEditorText?: (text: string) => string;
|