@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
|
@@ -53,10 +53,12 @@ export class OwnedUiSessionShell {
|
|
|
53
53
|
#lastEscapeTime = 0;
|
|
54
54
|
#activeLoginDialog;
|
|
55
55
|
#sessionGeneration;
|
|
56
|
+
#sessionBindingGeneration;
|
|
56
57
|
#suggestionModelKey;
|
|
57
58
|
constructor(options) {
|
|
58
59
|
this.backend = options.backend;
|
|
59
60
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
61
|
+
this.#sessionBindingGeneration = this.backend.sessionBindingGeneration;
|
|
60
62
|
this.#suggestionModelKey = modelKey(this.backend.view());
|
|
61
63
|
this.#cwd = options.cwd;
|
|
62
64
|
this.#routeHost = options.routeHost ?? null;
|
|
@@ -73,7 +75,7 @@ export class OwnedUiSessionShell {
|
|
|
73
75
|
getColumns: () => runtime?.viewport().columns ?? options.terminal?.columns ?? 80,
|
|
74
76
|
getRows: () => runtime?.viewport().rows ?? options.terminal?.rows ?? 24,
|
|
75
77
|
requestRender: force => runtime?.requestRender(force),
|
|
76
|
-
requestHyperlinkCleanup:
|
|
78
|
+
requestHyperlinkCleanup: rows => damageTerminal?.requestHyperlinkCleanup(rows),
|
|
77
79
|
onViewportFrame: frame => damageTerminal?.arm(frame.descriptor, {
|
|
78
80
|
overlayActive: runtime?.hasOverlay() ?? false,
|
|
79
81
|
selectionActive: this.root.hasActiveSelection(),
|
|
@@ -139,6 +141,7 @@ export class OwnedUiSessionShell {
|
|
|
139
141
|
}, this.backend.agentDir, {
|
|
140
142
|
getMessageRenderer: customType => this.backend.pinnedMessageRenderer(customType),
|
|
141
143
|
getToolDefinition: toolName => this.backend.pinnedToolDefinition(toolName),
|
|
144
|
+
getShortcuts: bindings => this.backend.pinnedShortcutDescriptions(bindings),
|
|
142
145
|
}, options.sessionLayout, {
|
|
143
146
|
resolve: assetId => this.backend.resolveTranscriptImage(assetId),
|
|
144
147
|
});
|
|
@@ -237,6 +240,17 @@ export class OwnedUiSessionShell {
|
|
|
237
240
|
? this.runtime.addPreInputListener(data => {
|
|
238
241
|
if (options.inputPresentation?.coordination === false)
|
|
239
242
|
this.#streamPresentation.noteImmediatePresentation();
|
|
243
|
+
// Compatibility: Pi's fullscreen renderer also intercepts plain Home/End.
|
|
244
|
+
// Deliver them to the focused owned input before that outer scroll handler;
|
|
245
|
+
// overlays retain Pi's normal dispatch, and comparison profiles never enter here.
|
|
246
|
+
if (!this.runtime.hasOverlay() && (this.root.editor.matchesTerminalKey(data, "home")
|
|
247
|
+
|| this.root.editor.matchesTerminalKey(data, "end"))) {
|
|
248
|
+
if (this.root.usesDefaultInputSurface())
|
|
249
|
+
this.root.handleViewportPreInput(data, true);
|
|
250
|
+
this.root.handleInput(data);
|
|
251
|
+
this.runtime.requestRender();
|
|
252
|
+
return { consume: true };
|
|
253
|
+
}
|
|
240
254
|
// Invariant: an overlay or editor-replacement screen owns its entire pointer
|
|
241
255
|
// surface. Letting the transcript pre-router inspect those reports
|
|
242
256
|
// steals settings value menus and numeric +/- controls before the
|
|
@@ -322,7 +336,6 @@ export class OwnedUiSessionShell {
|
|
|
322
336
|
fallback: this.view().transcript.flatMap(block => block.kind === "user" ? [block.text] : []),
|
|
323
337
|
active: () => this.root.usesDefaultInputSurface(),
|
|
324
338
|
render: () => this.runtime.requestRender(),
|
|
325
|
-
failure: message => this.root.addExtensionNotification(message, "warning"),
|
|
326
339
|
});
|
|
327
340
|
}
|
|
328
341
|
this.#extensionBridge = createPiExtensionUiBridge({
|
|
@@ -793,8 +806,7 @@ export class OwnedUiSessionShell {
|
|
|
793
806
|
showLoginMethodSelector(providerReference) {
|
|
794
807
|
const method = this.backend.pinnedLoginMethodOptions(providerReference);
|
|
795
808
|
if (method.options.length === 0) {
|
|
796
|
-
this.
|
|
797
|
-
this.runtime.requestRender();
|
|
809
|
+
this.showLoginProviderSelector(undefined, providerReference);
|
|
798
810
|
return;
|
|
799
811
|
}
|
|
800
812
|
if (method.options.length === 1) {
|
|
@@ -836,6 +848,20 @@ export class OwnedUiSessionShell {
|
|
|
836
848
|
initialFilterMode: context.filterMode,
|
|
837
849
|
...(initialSelectedId === undefined ? {} : { initialSelectedId }),
|
|
838
850
|
onLabelChange: context.appendLabelChange,
|
|
851
|
+
onCopy: text => {
|
|
852
|
+
if (!text) {
|
|
853
|
+
this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: "Selected entry has no text to copy" });
|
|
854
|
+
this.runtime.requestRender();
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
void this.backend.copyWorkflowText(text).then(() => {
|
|
858
|
+
this.root.appendWorkflowStatus("Copied selected message to clipboard");
|
|
859
|
+
this.runtime.requestRender();
|
|
860
|
+
}).catch(error => {
|
|
861
|
+
this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
|
|
862
|
+
this.runtime.requestRender();
|
|
863
|
+
});
|
|
864
|
+
},
|
|
839
865
|
onCancel: close,
|
|
840
866
|
onSelect: entryId => {
|
|
841
867
|
close();
|
|
@@ -863,10 +889,10 @@ export class OwnedUiSessionShell {
|
|
|
863
889
|
this.root.setInputSurface(component);
|
|
864
890
|
this.runtime.requestRender();
|
|
865
891
|
}
|
|
866
|
-
showLoginProviderSelector(authType) {
|
|
892
|
+
showLoginProviderSelector(authType, initialSearchInput) {
|
|
867
893
|
const options = this.backend.pinnedLoginOptions(authType);
|
|
868
894
|
if (options.length === 0) {
|
|
869
|
-
this.root.appendWorkflowStatus(authType === "oauth" ? "No subscription providers available." : "No API key providers available.");
|
|
895
|
+
this.root.appendWorkflowStatus(authType === "oauth" ? "No subscription providers available." : authType === "api_key" ? "No API key providers available." : "No login providers available.");
|
|
870
896
|
this.runtime.requestRender();
|
|
871
897
|
return;
|
|
872
898
|
}
|
|
@@ -879,8 +905,9 @@ export class OwnedUiSessionShell {
|
|
|
879
905
|
void this.runWorkflow({ command: "login", argument: "", selection: id });
|
|
880
906
|
}, () => {
|
|
881
907
|
close();
|
|
882
|
-
|
|
883
|
-
|
|
908
|
+
if (authType !== undefined)
|
|
909
|
+
this.showLoginAuthTypeSelector();
|
|
910
|
+
}, initialSearchInput);
|
|
884
911
|
this.root.setInputSurface(component);
|
|
885
912
|
this.runtime.requestRender();
|
|
886
913
|
}
|
|
@@ -961,6 +988,24 @@ export class OwnedUiSessionShell {
|
|
|
961
988
|
return this.runWorkflow({ command: "quit", argument: "" });
|
|
962
989
|
}
|
|
963
990
|
async runWorkflow(request) {
|
|
991
|
+
if (request.command === "login" && request.selection !== undefined) {
|
|
992
|
+
const setup = this.backend.pinnedAmbientAuthentication(request.selection);
|
|
993
|
+
if (setup) {
|
|
994
|
+
const close = () => {
|
|
995
|
+
this.root.setInputSurface(null);
|
|
996
|
+
this.runtime.requestRender();
|
|
997
|
+
};
|
|
998
|
+
const dialog = createPiShellLoginDialog({
|
|
999
|
+
getColumns: () => this.runtime.viewport().columns,
|
|
1000
|
+
getRows: () => this.runtime.viewport().rows,
|
|
1001
|
+
requestRender: () => this.runtime.requestRender(),
|
|
1002
|
+
}, setup.providerId, close, setup.providerName, setup.title);
|
|
1003
|
+
dialog.showInfo(setup.message, [], true);
|
|
1004
|
+
this.root.setInputSurface(dialog);
|
|
1005
|
+
this.runtime.requestRender();
|
|
1006
|
+
return { outcome: "completed", diagnostic: null };
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
964
1009
|
if (request.command === "scoped-models" && request.selection === undefined && request.confirmed === undefined) {
|
|
965
1010
|
this.showScopedModelsSelector();
|
|
966
1011
|
return { outcome: "completed", diagnostic: null };
|
|
@@ -1001,8 +1046,13 @@ export class OwnedUiSessionShell {
|
|
|
1001
1046
|
return { outcome: "completed", diagnostic: null };
|
|
1002
1047
|
}
|
|
1003
1048
|
if (request.command === "reload") {
|
|
1049
|
+
const blocked = this.backend.reloadBlockedResult();
|
|
1050
|
+
if (blocked) {
|
|
1051
|
+
this.root.appendWorkflowResult(blocked);
|
|
1052
|
+
this.runtime.requestRender();
|
|
1053
|
+
return workflowAdapterResult(blocked);
|
|
1054
|
+
}
|
|
1004
1055
|
this.root.resetExtensionUi();
|
|
1005
|
-
this.root.resetWorkflowPresentation();
|
|
1006
1056
|
}
|
|
1007
1057
|
const shareSurface = request.command === "share"
|
|
1008
1058
|
? createPiShellOperationLoader({
|
|
@@ -1053,6 +1103,8 @@ export class OwnedUiSessionShell {
|
|
|
1053
1103
|
return { outcome: "failed", diagnostic: `Owned controller missing for ${request.command}` };
|
|
1054
1104
|
}
|
|
1055
1105
|
if (request.command === "reload" && result.outcome === "completed") {
|
|
1106
|
+
this.root.resetWorkflowPresentation();
|
|
1107
|
+
this.root.editor.reloadKeybindings();
|
|
1056
1108
|
this.root.editor.setAutocompleteCommands(this.backend.workflowAutocompleteCommands());
|
|
1057
1109
|
}
|
|
1058
1110
|
this.root.appendWorkflowResult(result);
|
|
@@ -1211,9 +1263,7 @@ export class OwnedUiSessionShell {
|
|
|
1211
1263
|
attempt(() => this.#extensionBridge.dispose());
|
|
1212
1264
|
// Invariant: terminal restoration precedes any potentially stalled backend teardown.
|
|
1213
1265
|
await this.runtime.dispose().catch(error => failures.push(error));
|
|
1214
|
-
|
|
1215
|
-
if (!historySaved)
|
|
1216
|
-
this.runtime.writeAfterStop("Prompt history could not finish saving before exit.\n");
|
|
1266
|
+
await historyCleanup.catch(() => false); // Security: background durability outcomes never enter terminal output.
|
|
1217
1267
|
await boundedCleanup(() => pasteCleanup).catch(error => failures.push(error));
|
|
1218
1268
|
await boundedCleanup(() => this.backend.unbindExtensionUi()).catch(error => failures.push(error));
|
|
1219
1269
|
if (failures.length > 0)
|
|
@@ -1251,7 +1301,11 @@ export class OwnedUiSessionShell {
|
|
|
1251
1301
|
this.root.resetPendingPastes();
|
|
1252
1302
|
this.#promptSuggestions?.invalidate();
|
|
1253
1303
|
this.#sessionGeneration = this.backend.sessionGeneration;
|
|
1254
|
-
|
|
1304
|
+
// Invariant: delivery recovery invalidates callbacks, not same-session local recall or its draft.
|
|
1305
|
+
if (this.#sessionBindingGeneration !== this.backend.sessionBindingGeneration) {
|
|
1306
|
+
this.#sessionBindingGeneration = this.backend.sessionBindingGeneration;
|
|
1307
|
+
this.#promptHistory?.reset(view.transcript.flatMap(block => block.kind === "user" ? [block.text] : []));
|
|
1308
|
+
}
|
|
1255
1309
|
this.#activeLoginDialog = undefined;
|
|
1256
1310
|
this.#extensionBridge.reset();
|
|
1257
1311
|
// Invariant: a replaced session takes its transient viewport and owned-route state with it.
|
|
@@ -5,8 +5,8 @@ export interface SessionViewportControllerOptions {
|
|
|
5
5
|
readonly enabled: boolean;
|
|
6
6
|
readonly editor: PiShellEditorPort;
|
|
7
7
|
readonly requestRender: (force?: boolean) => void;
|
|
8
|
-
readonly requestHyperlinkCleanup?: () => void;
|
|
9
|
-
/** Whether mutable editor URL chips need
|
|
8
|
+
readonly requestHyperlinkCleanup?: (rows?: readonly number[]) => void;
|
|
9
|
+
/** Whether mutable editor URL chips need targeted link-cell cleanup on deletion. */
|
|
10
10
|
readonly hasEditorLinks?: () => boolean;
|
|
11
11
|
}
|
|
12
12
|
export interface SessionViewportInputResult {
|
|
@@ -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;
|
|
@@ -60,6 +60,10 @@ export class SessionViewportController {
|
|
|
60
60
|
get presentationRevision() {
|
|
61
61
|
return this.#presentationRevision;
|
|
62
62
|
}
|
|
63
|
+
/** Latest reported coordinates; exposed without rendering for frame provenance and evidence. */
|
|
64
|
+
get pointerPosition() {
|
|
65
|
+
return this.#pointerPosition;
|
|
66
|
+
}
|
|
63
67
|
get frame() {
|
|
64
68
|
return this.#viewport.frame;
|
|
65
69
|
}
|
|
@@ -85,8 +89,8 @@ export class SessionViewportController {
|
|
|
85
89
|
if (this.#hoveredHyperlinkKey !== undefined && next !== this.#hoveredHyperlinkKey) {
|
|
86
90
|
// Platform: Windows Terminal can leave its solid native-hover underline behind
|
|
87
91
|
// when a following viewport moves the link away from a stationary pointer.
|
|
88
|
-
this.#requestHyperlinkCleanup();
|
|
89
|
-
this.#requestRender(
|
|
92
|
+
this.#requestHyperlinkCleanup([this.#pointerPosition.row]);
|
|
93
|
+
this.#requestRender();
|
|
90
94
|
}
|
|
91
95
|
this.#hoveredHyperlinkKey = next;
|
|
92
96
|
}
|
|
@@ -157,7 +161,24 @@ export class SessionViewportController {
|
|
|
157
161
|
// Platform: URL chip deletion must overwrite terminal link cells in the same frame.
|
|
158
162
|
if (EDITOR_LINK_DELETION_INPUTS.has(data) && this.#hasEditorLinks()) {
|
|
159
163
|
this.#requestHyperlinkCleanup();
|
|
160
|
-
this.#requestRender(
|
|
164
|
+
this.#requestRender();
|
|
165
|
+
}
|
|
166
|
+
// Invariant: content shortcuts never reach the prompt, even before the first frame
|
|
167
|
+
// or when scrolling is a no-op. Plain Home/End remain editor line navigation.
|
|
168
|
+
if (allowWheel && this.#editor.matchesTerminalKey(data, "ctrl+home")) {
|
|
169
|
+
if (this.#viewport.scrollTo(0, now)) {
|
|
170
|
+
this.#scheduleActivityExpiry();
|
|
171
|
+
this.#requestRender();
|
|
172
|
+
}
|
|
173
|
+
return { data: "", consumed: true };
|
|
174
|
+
}
|
|
175
|
+
if (allowWheel && this.#editor.matchesTerminalKey(data, "ctrl+end")) {
|
|
176
|
+
if (!this.#viewport.followingEnd) {
|
|
177
|
+
this.#viewport.scrollToEnd(now);
|
|
178
|
+
this.#scheduleActivityExpiry();
|
|
179
|
+
this.#requestRender();
|
|
180
|
+
}
|
|
181
|
+
return { data: "", consumed: true };
|
|
161
182
|
}
|
|
162
183
|
if (this.#viewport.frame === null) {
|
|
163
184
|
// Invariant: a not-yet-painted A1 screen is not Pi's selection surface.
|
|
@@ -183,21 +204,6 @@ export class SessionViewportController {
|
|
|
183
204
|
}
|
|
184
205
|
}
|
|
185
206
|
}
|
|
186
|
-
if (allowWheel && this.#editor.matchesTerminalKey(data, "home")) {
|
|
187
|
-
if (this.#viewport.scrollTo(0, now)) {
|
|
188
|
-
this.#scheduleActivityExpiry();
|
|
189
|
-
this.#requestRender();
|
|
190
|
-
}
|
|
191
|
-
return { data: "", consumed: true };
|
|
192
|
-
}
|
|
193
|
-
if (allowWheel && this.#editor.matchesTerminalKey(data, "end")) {
|
|
194
|
-
if (!this.#viewport.followingEnd) {
|
|
195
|
-
this.#viewport.scrollToEnd(now);
|
|
196
|
-
this.#scheduleActivityExpiry();
|
|
197
|
-
this.#requestRender();
|
|
198
|
-
}
|
|
199
|
-
return { data: "", consumed: true };
|
|
200
|
-
}
|
|
201
207
|
if (allowWheel && (SHIFT_UP_INPUTS.has(data) || SHIFT_DOWN_INPUTS.has(data))) {
|
|
202
208
|
const scrolled = SHIFT_UP_INPUTS.has(data)
|
|
203
209
|
? this.#viewport.scrollToPreviousPrompt(now)
|
|
@@ -236,8 +242,7 @@ export class SessionViewportController {
|
|
|
236
242
|
const nextHyperlink = this.#hyperlinkKeyAt(frame, event.column, event.row);
|
|
237
243
|
if (this.#hoveredHyperlinkKey !== undefined && nextHyperlink !== this.#hoveredHyperlinkKey) {
|
|
238
244
|
// Platform: overwrite Windows Terminal's cached native-hover underline exactly once.
|
|
239
|
-
this.#requestHyperlinkCleanup();
|
|
240
|
-
forceRepaint = true;
|
|
245
|
+
this.#requestHyperlinkCleanup(previousPointer === undefined ? undefined : [previousPointer.row]);
|
|
241
246
|
repaint = true;
|
|
242
247
|
}
|
|
243
248
|
this.#hoveredHyperlinkKey = nextHyperlink;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Normalize clipboard text and classify its compact label using the pinned Pi 0.84.2 paste policy. */
|
|
2
|
+
export function prepareTextPaste(source) {
|
|
3
|
+
// Compatibility: extended-key terminals can encode literal controls inside bracketed paste.
|
|
4
|
+
const decoded = source.replace(/\x1b\[(\d+);5u/g, (sequence, value) => {
|
|
5
|
+
const code = Number(value);
|
|
6
|
+
if (code >= 65 && code <= 90)
|
|
7
|
+
return String.fromCharCode(code - 64);
|
|
8
|
+
if (code >= 97 && code <= 122)
|
|
9
|
+
return String.fromCharCode(code - 96);
|
|
10
|
+
return sequence;
|
|
11
|
+
});
|
|
12
|
+
const text = decoded.replace(/\r\n?/g, "\n").replace(/\t/g, " ").replace(/[\x00-\x09\x0b-\x1f]/g, "");
|
|
13
|
+
const lines = text.split("\n").length;
|
|
14
|
+
const label = lines > 10 ? `+${lines} lines` : text.length > 1000 ? `${text.length} chars` : undefined;
|
|
15
|
+
return { text, ...(label === undefined ? {} : { label }) };
|
|
16
|
+
}
|
|
@@ -12,6 +12,12 @@ export interface PiTuiDamageFrameDescriptor {
|
|
|
12
12
|
readonly rowStart: number;
|
|
13
13
|
readonly rowEnd: number;
|
|
14
14
|
} | null;
|
|
15
|
+
/**
|
|
16
|
+
* Visible rows owned by the block that is currently streaming. A live block repaints
|
|
17
|
+
* every row it owns when it reflows, so the allowed damage follows the declared tail
|
|
18
|
+
* instead of assuming a one-row tail.
|
|
19
|
+
*/
|
|
20
|
+
readonly liveTailRows?: number;
|
|
15
21
|
readonly verticalShiftRows: number;
|
|
16
22
|
readonly safeVerticalShift: boolean;
|
|
17
23
|
readonly cause: string;
|
|
@@ -34,6 +40,7 @@ export interface DamageAwareTerminalOptions {
|
|
|
34
40
|
/** The shell supplies visible-row analysis; the terminal layer owns no UI parser. */
|
|
35
41
|
readonly inspectHyperlinks: (content: string) => {
|
|
36
42
|
readonly ranges: readonly unknown[];
|
|
43
|
+
readonly hasExplicitLink: boolean;
|
|
37
44
|
readonly signature: string;
|
|
38
45
|
readonly replaySafe: boolean;
|
|
39
46
|
readonly width: number;
|
|
@@ -56,8 +63,8 @@ export declare class DamageAwareTerminalAdapter implements PiTuiTerminalPort {
|
|
|
56
63
|
get kittyProtocolActive(): boolean;
|
|
57
64
|
get lastDecision(): PiTuiDamageDecision;
|
|
58
65
|
get hyperlinkCleanupPending(): boolean;
|
|
59
|
-
/** Latches
|
|
60
|
-
requestHyperlinkCleanup(): void;
|
|
66
|
+
/** Latches the former link rows, including rows whose replacement contains no link. */
|
|
67
|
+
requestHyperlinkCleanup(rows?: readonly number[]): void;
|
|
61
68
|
arm(descriptor: PiTuiDamageFrameDescriptor, safety: PiTuiDamageFrameSafety): void;
|
|
62
69
|
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
63
70
|
stop(): void;
|
|
@@ -15,6 +15,8 @@ export class DamageAwareTerminalAdapter {
|
|
|
15
15
|
#rows = new Map();
|
|
16
16
|
#links = new Map();
|
|
17
17
|
#cleanupRevision = 0;
|
|
18
|
+
#cleanupRows = new Set();
|
|
19
|
+
#cleanupNeedsCompleteFrame = false;
|
|
18
20
|
#cleanedRevision = 0;
|
|
19
21
|
#recoveryRevision = 0;
|
|
20
22
|
#epoch = 0;
|
|
@@ -38,8 +40,23 @@ export class DamageAwareTerminalAdapter {
|
|
|
38
40
|
get kittyProtocolActive() { return this.inner.kittyProtocolActive; }
|
|
39
41
|
get lastDecision() { return this.#decision; }
|
|
40
42
|
get hyperlinkCleanupPending() { return this.#cleanupRevision > this.#cleanedRevision; }
|
|
41
|
-
/** Latches
|
|
42
|
-
requestHyperlinkCleanup() {
|
|
43
|
+
/** Latches the former link rows, including rows whose replacement contains no link. */
|
|
44
|
+
requestHyperlinkCleanup(rows) {
|
|
45
|
+
this.#cleanupRevision += 1;
|
|
46
|
+
if (rows !== undefined) {
|
|
47
|
+
for (const row of rows) {
|
|
48
|
+
if (Number.isSafeInteger(row) && row >= 1 && row <= this.rows)
|
|
49
|
+
this.#cleanupRows.add(row);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Invariant: no known pointer is not proof of no host-hover decoration. Unscoped
|
|
54
|
+
// removal/invalidation repairs every previously linked row, not every screen row.
|
|
55
|
+
for (const [row, state] of this.#links)
|
|
56
|
+
if (state.ranges.length > 0)
|
|
57
|
+
this.#cleanupRows.add(row);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
43
60
|
arm(descriptor, safety) {
|
|
44
61
|
this.#armed = { descriptor, safety };
|
|
45
62
|
}
|
|
@@ -56,6 +73,8 @@ export class DamageAwareTerminalAdapter {
|
|
|
56
73
|
this.#epoch += 1;
|
|
57
74
|
this.#armed = undefined;
|
|
58
75
|
this.#cleanupRevision = this.#cleanedRevision = this.#recoveryRevision = 0;
|
|
76
|
+
this.#cleanupRows.clear();
|
|
77
|
+
this.#cleanupNeedsCompleteFrame = false;
|
|
59
78
|
this.#lastConsumedFrameId = 0;
|
|
60
79
|
this.#invalidateRows();
|
|
61
80
|
this.inner.stop();
|
|
@@ -77,25 +96,19 @@ export class DamageAwareTerminalAdapter {
|
|
|
77
96
|
this.#invalidatePresentation();
|
|
78
97
|
if (unknownPaint && !this.hyperlinkCleanupPending && this.options.inspectHyperlinks(data).ranges.length > 0) {
|
|
79
98
|
this.requestHyperlinkCleanup();
|
|
99
|
+
this.#cleanupNeedsCompleteFrame = true;
|
|
80
100
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const next = this.options.inspectHyperlinks(row.content);
|
|
88
|
-
if (previous.ranges.length > 0 && previous.signature !== next.signature) {
|
|
89
|
-
this.requestHyperlinkCleanup();
|
|
90
|
-
break;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
101
|
+
// Invariant: a deliberate same-geometry reset discards presented link state. Ordinary streamed
|
|
102
|
+
// row changes are not cleanup requests: candidate-looking text is not terminal state.
|
|
103
|
+
if (sameGeometry && parsed?.structuralPrefix === "\u001b[2J"
|
|
104
|
+
&& hasCompleteScreenRows(parsed.rows, this.rows) && !this.hyperlinkCleanupPending
|
|
105
|
+
&& !this.#hasSameHyperlinkState(parsed.rows))
|
|
106
|
+
this.requestHyperlinkCleanup();
|
|
94
107
|
const decision = this.#decide(armed, parsed);
|
|
108
|
+
const coveredRevision = this.#cleanupRevision;
|
|
95
109
|
const cleanup = this.hyperlinkCleanupPending && parsed !== null
|
|
96
110
|
&& (armed === undefined || (armed.descriptor.width === this.columns && armed.descriptor.height === this.rows))
|
|
97
|
-
? this.#
|
|
98
|
-
const coveredRevision = this.#cleanupRevision;
|
|
111
|
+
? this.#boundedCleanupFrame(parsed) : null;
|
|
99
112
|
const epoch = this.#epoch;
|
|
100
113
|
let output = data;
|
|
101
114
|
if (cleanup !== null) {
|
|
@@ -107,8 +120,9 @@ export class DamageAwareTerminalAdapter {
|
|
|
107
120
|
}
|
|
108
121
|
else {
|
|
109
122
|
this.#decision = decision;
|
|
110
|
-
if (decision.reason === "suppressed-redundant-clear" && parsed !== null)
|
|
111
|
-
output = buildClearlessWrite(parsed);
|
|
123
|
+
if (decision.reason === "suppressed-redundant-clear" && parsed !== null) {
|
|
124
|
+
output = buildClearlessWrite(parsed, this.#rows);
|
|
125
|
+
}
|
|
112
126
|
else if (decision.transformed && armed !== undefined && parsed !== null) {
|
|
113
127
|
output = buildDamageWrite(parsed, armed.descriptor, new Set(decision.paintedRows), this.#rows);
|
|
114
128
|
}
|
|
@@ -117,11 +131,16 @@ export class DamageAwareTerminalAdapter {
|
|
|
117
131
|
if (epoch !== this.#epoch
|
|
118
132
|
|| (armed !== undefined && this.#lastConsumedFrameId !== armed.descriptor.frameId))
|
|
119
133
|
return;
|
|
120
|
-
// Invariant: cache invalidation follows forwarded bytes, not a clear
|
|
121
|
-
//
|
|
134
|
+
// Invariant: cache invalidation follows forwarded bytes, not a suppressed clear.
|
|
135
|
+
// A bounded cleanup preserves prior rows and publishes all changed or invalidated rows.
|
|
122
136
|
this.#rememberFrame(armed?.descriptor, cleanup ?? parsed, output);
|
|
123
|
-
if (cleanup !== null)
|
|
137
|
+
if (cleanup !== null) {
|
|
124
138
|
this.#cleanedRevision = Math.max(this.#cleanedRevision, coveredRevision);
|
|
139
|
+
if (this.#cleanupRevision === coveredRevision) {
|
|
140
|
+
this.#cleanupRows.clear();
|
|
141
|
+
this.#cleanupNeedsCompleteFrame = false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
125
144
|
this.#scheduleCleanupRecovery();
|
|
126
145
|
}
|
|
127
146
|
moveBy(lines) { this.inner.moveBy(lines); }
|
|
@@ -175,7 +194,7 @@ export class DamageAwareTerminalAdapter {
|
|
|
175
194
|
return { ...base, reason: "grammar-mismatch" };
|
|
176
195
|
if (parsed.structuralPrefix.length > 0
|
|
177
196
|
|| hasUnsafeTerminalContent(parsed, this.options.inspectHyperlinks, descriptor.transcript)
|
|
178
|
-
|| this.#
|
|
197
|
+
|| this.#hasExplicitLinkRisk(descriptor.transcript) || this.#hasUnsafeRows(descriptor.transcript)) {
|
|
179
198
|
return { ...base, reason: "unsafe-terminal-content" };
|
|
180
199
|
}
|
|
181
200
|
const { rowStart, rowEnd } = descriptor.transcript;
|
|
@@ -204,10 +223,11 @@ export class DamageAwareTerminalAdapter {
|
|
|
204
223
|
if (descriptor.dock !== null && row.row >= descriptor.dock.rowStart && row.row <= descriptor.dock.rowEnd)
|
|
205
224
|
painted.add(row.row);
|
|
206
225
|
}
|
|
207
|
-
// Rationale:
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
|
|
226
|
+
// Rationale: a followed frame may repaint the live tail it declares, expose the shifted
|
|
227
|
+
// suffix, and restyle one sticky boundary row. A frame that declares no tail keeps the
|
|
228
|
+
// historical one-row allowance. Anything beyond that reaches settled rows and fails closed.
|
|
229
|
+
const liveTailRows = Math.max(1, descriptor.liveTailRows ?? 1);
|
|
230
|
+
if (painted.size > descriptor.verticalShiftRows + liveTailRows + 1 + countDockRows(parsed.rows, descriptor.dock)) {
|
|
211
231
|
return { ...base, reason: "excessive-real-damage" };
|
|
212
232
|
}
|
|
213
233
|
return {
|
|
@@ -248,11 +268,9 @@ export class DamageAwareTerminalAdapter {
|
|
|
248
268
|
return next?.replaySafe === true && (previous?.signature ?? "") === next.signature;
|
|
249
269
|
});
|
|
250
270
|
}
|
|
251
|
-
#
|
|
271
|
+
#hasExplicitLinkRisk(region) {
|
|
252
272
|
for (const [row, state] of this.#links) {
|
|
253
|
-
if (
|
|
254
|
-
continue;
|
|
255
|
-
if (state.ranges.length > 0)
|
|
273
|
+
if (row >= region.rowStart && row <= region.rowEnd && state.hasExplicitLink)
|
|
256
274
|
return true;
|
|
257
275
|
}
|
|
258
276
|
return false;
|
|
@@ -264,14 +282,33 @@ export class DamageAwareTerminalAdapter {
|
|
|
264
282
|
}
|
|
265
283
|
return false;
|
|
266
284
|
}
|
|
267
|
-
#
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (desired.size !== this.rows)
|
|
285
|
+
#boundedCleanupFrame(parsed) {
|
|
286
|
+
const sameGeometry = this.#cacheWidth === this.columns && this.#cacheHeight === this.rows;
|
|
287
|
+
const complete = hasCompleteScreenRows(parsed.rows, this.rows);
|
|
288
|
+
if (this.#cleanupNeedsCompleteFrame && !complete)
|
|
272
289
|
return null;
|
|
290
|
+
const suppressReset = sameGeometry && !this.#cleanupNeedsCompleteFrame
|
|
291
|
+
&& parsed.structuralPrefix === "\u001b[2J" && complete;
|
|
292
|
+
const structuralPrefix = suppressReset ? "" : parsed.structuralPrefix;
|
|
293
|
+
const desired = structuralPrefix.length === 0 ? new Map(this.#rows) : new Map();
|
|
294
|
+
const dirty = new Set([...this.#cleanupRows].filter(row => row <= this.rows));
|
|
295
|
+
for (const row of parsed.rows) {
|
|
296
|
+
if (row.row > this.rows)
|
|
297
|
+
return null;
|
|
298
|
+
desired.set(row.row, row.content);
|
|
299
|
+
if (structuralPrefix.length > 0 || this.#cleanupNeedsCompleteFrame
|
|
300
|
+
|| this.#rows.get(row.row) !== row.content)
|
|
301
|
+
dirty.add(row.row);
|
|
302
|
+
}
|
|
303
|
+
// Invariant: validate all incoming rows before dropping an apparent redundant reset. Unknown
|
|
304
|
+
// content must pass through unchanged, never become a partially transformed frame.
|
|
305
|
+
for (const row of parsed.rows) {
|
|
306
|
+
const state = row.content === this.#rows.get(row.row) ? this.#links.get(row.row) : this.options.inspectHyperlinks(row.content);
|
|
307
|
+
if (state?.replaySafe !== true || state.width > this.columns)
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
273
310
|
const rows = [];
|
|
274
|
-
for (
|
|
311
|
+
for (const row of [...dirty].sort((left, right) => left - right)) {
|
|
275
312
|
const content = desired.get(row);
|
|
276
313
|
if (content === undefined)
|
|
277
314
|
return null;
|
|
@@ -280,7 +317,8 @@ export class DamageAwareTerminalAdapter {
|
|
|
280
317
|
return null;
|
|
281
318
|
rows.push({ row, content, segment: `\u001b[${row};1H\u001b[2K${content}` });
|
|
282
319
|
}
|
|
283
|
-
|
|
320
|
+
// Invariant: only preserve a structural clear supplied by Pi; cleanup never manufactures one.
|
|
321
|
+
return { structuralPrefix, rows, cursorSuffix: parsed.cursorSuffix };
|
|
284
322
|
}
|
|
285
323
|
#scheduleCleanupRecovery() {
|
|
286
324
|
if (!this.hyperlinkCleanupPending || this.#recoveryRevision === this.#cleanupRevision
|
|
@@ -296,8 +334,13 @@ export class DamageAwareTerminalAdapter {
|
|
|
296
334
|
});
|
|
297
335
|
}
|
|
298
336
|
#invalidatePresentation() {
|
|
299
|
-
if (this.#
|
|
337
|
+
if ([...this.#links.values()].some(state => state.ranges.length > 0) && !this.hyperlinkCleanupPending) {
|
|
300
338
|
this.requestHyperlinkCleanup();
|
|
339
|
+
}
|
|
340
|
+
// Invariant: after unknown paint or geometry loss, exact former row locations are no longer
|
|
341
|
+
// authoritative. Wait for a complete safe frame instead of guessing a partial repair.
|
|
342
|
+
if (this.hyperlinkCleanupPending)
|
|
343
|
+
this.#cleanupNeedsCompleteFrame = true;
|
|
301
344
|
this.#invalidateRows();
|
|
302
345
|
}
|
|
303
346
|
#invalidateRows() {
|
|
@@ -345,7 +388,7 @@ function hasUnsafeTerminalContent(parsed, inspect, linkRegion) {
|
|
|
345
388
|
if (linkRegion !== undefined && (row.row < linkRegion.rowStart || row.row > linkRegion.rowEnd))
|
|
346
389
|
return false;
|
|
347
390
|
const state = inspect(row.content);
|
|
348
|
-
return !state.replaySafe || state.
|
|
391
|
+
return !state.replaySafe || state.hasExplicitLink;
|
|
349
392
|
});
|
|
350
393
|
}
|
|
351
394
|
function buildDamageWrite(parsed, descriptor, paintedRows, previousRows) {
|
|
@@ -363,12 +406,13 @@ function buildDamageWrite(parsed, descriptor, paintedRows, previousRows) {
|
|
|
363
406
|
}).join("");
|
|
364
407
|
return `${BEGIN_SYNCHRONIZED_OUTPUT}${regionShift}${rowPaint}${parsed.cursorSuffix}`;
|
|
365
408
|
}
|
|
366
|
-
/** Keeps
|
|
409
|
+
/** Keeps required cleanup rows and current content in one synchronized transaction. */
|
|
367
410
|
function buildCompleteWrite(parsed) {
|
|
368
411
|
return `${BEGIN_SYNCHRONIZED_OUTPUT}${parsed.structuralPrefix}${parsed.rows.map(row => row.segment).join("")}${parsed.cursorSuffix}`;
|
|
369
412
|
}
|
|
370
|
-
function buildClearlessWrite(parsed) {
|
|
371
|
-
|
|
413
|
+
function buildClearlessWrite(parsed, previousRows) {
|
|
414
|
+
const changed = parsed.rows.filter(row => previousRows.get(row.row) !== row.content);
|
|
415
|
+
return `${BEGIN_SYNCHRONIZED_OUTPUT}${changed.map(row => row.segment).join("")}${parsed.cursorSuffix}`;
|
|
372
416
|
}
|
|
373
417
|
function hasCompleteScreenRows(rows, height) {
|
|
374
418
|
return rows.length === height && rows.every((row, index) => row.row === index + 1);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-12T13:59:03.140Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-12T13:58:54.058Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-12T13:59:34.009Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "15e00144629345d7f00b110494775b80c65e6e575e6758d88b533c25b57d35d5",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const ENVIRONMENT_KEYS: readonly ["
|
|
1
|
+
declare const ENVIRONMENT_KEYS: readonly ["configDir", "dataDir", "databasePath", "developmentInstanceId", "developmentRoot", "endpoint", "paneId", "probeTrace", "processGuardianPath", "profileHome", "runtimeDir", "startupTrace", "terminalSessionId"];
|
|
2
2
|
declare const FILESYSTEM_KEYS: readonly ["slug", "windowsDirectory", "unixDirectory", "temporaryPrefix"];
|
|
3
3
|
declare const STATE_KEYS: readonly ["windowsControlDirectory", "unixControlDirectory", "developmentDirectory", "piAgentProfile", "piVanillaProfile"];
|
|
4
4
|
declare const ENDPOINT_KEYS: readonly ["windowsPipeStem", "unixSocketFilename", "metadataFilename", "supervisorLogFilename", "databaseFilename"];
|