@timurproko/a1 0.1.8-dev.157 → 0.1.8-dev.214
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 +32 -0
- package/bin/cli.js +2 -2
- package/bin/sync-pi-tui-proxy.js +1 -1
- package/bin/ui.js +18 -3
- package/dist/cli/dispatch.js +3 -3
- package/dist/cli/packages.js +1 -1
- package/dist/cli/version-stats.js +3 -3
- package/dist/composition/owned-ui.d.ts +4 -1
- package/dist/composition/owned-ui.js +9 -6
- package/dist/composition/settings-route-host.js +6 -4
- package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
- package/dist/contracts/agent-engine/domain-validation.js +22 -4
- package/dist/contracts/agent-engine/domain.d.ts +17 -0
- package/dist/contracts/owned-ui/model.d.ts +8 -0
- package/dist/contracts/owned-ui/validation.js +11 -0
- package/dist/features/launch/development-launch.js +1 -1
- package/dist/features/owned-ui/customization.d.ts +2 -0
- package/dist/features/owned-ui/customization.js +2 -0
- package/dist/features/owned-ui/diagnostics.d.ts +1 -0
- package/dist/features/owned-ui/diagnostics.js +1 -0
- package/dist/features/owned-ui/index.d.ts +1 -0
- package/dist/features/owned-ui/index.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
- package/dist/features/owned-ui/project-trust-prompt.js +149 -0
- package/dist/features/owned-ui/settings-app.js +69 -44
- package/dist/features/workspace/reducer.d.ts +1 -0
- package/dist/features/workspace/reducer.js +1 -0
- package/dist/features/workspace/router.d.ts +1 -0
- package/dist/features/workspace/router.js +1 -0
- package/dist/features/workspace/store.d.ts +1 -0
- package/dist/features/workspace/store.js +1 -0
- package/dist/foundation/launch-guardian/main.js +1 -1
- package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
- package/dist/foundation/native-host-protocol/codec.js +2 -0
- package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
- package/dist/foundation/protocol/client.d.ts +1 -0
- package/dist/foundation/protocol/client.js +1 -0
- package/dist/foundation/protocol/messages.d.ts +1 -0
- package/dist/foundation/protocol/messages.js +1 -0
- package/dist/foundation/release/bootstrap.js +6 -6
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +1 -0
- package/dist/foundation/release/release-gc.js +1 -1
- package/dist/foundation/release/release.js +1 -1
- package/dist/foundation/release/update-transaction.d.ts +1 -0
- package/dist/foundation/release/update-transaction.js +1 -0
- package/dist/foundation/release/update.js +12 -12
- package/dist/foundation/storage/control-store.d.ts +1 -0
- package/dist/foundation/storage/control-store.js +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
- package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/commands.js +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
- package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/state.js +1 -0
- package/dist/foundation/supervision/main.js +1 -1
- package/dist/foundation/supervision/server.d.ts +1 -0
- package/dist/foundation/supervision/server.js +4 -6
- package/dist/integrations/pi/components/conformance.d.ts +1 -0
- package/dist/integrations/pi/components/conformance.js +1 -0
- package/dist/integrations/pi/components/shell-components.d.ts +1 -0
- package/dist/integrations/pi/components/shell-components.js +1 -0
- package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +19 -13
- package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
- package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
- package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
- package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +22 -4
- package/dist/integrations/pi/engine/adapter.js +220 -70
- package/dist/integrations/pi/engine/conformance.d.ts +2 -0
- package/dist/integrations/pi/engine/conformance.js +2 -0
- package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
- package/dist/integrations/pi/engine/index.d.ts +3 -0
- package/dist/integrations/pi/engine/index.js +3 -0
- package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
- package/dist/integrations/pi/engine/package-integration.js +1 -1
- package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
- package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
- package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
- package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
- package/dist/integrations/pi/engine/runtime-integration.js +34 -4
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/session-integration.js +2 -1
- package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
- package/dist/integrations/pi/engine/settings-effects.js +249 -0
- package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
- package/dist/integrations/pi/engine/settings-integration.js +102 -108
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
- package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
- package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
- package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +6 -2
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
- package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
- package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
- package/dist/integrations/pi/session-ui/session-shell.js +256 -45
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -11
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
- package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +166 -8
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
- package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/index.js +2 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
- 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/ui/apps/host.js +5 -9
- package/dist/ui/apps/registry.d.ts +1 -0
- package/dist/ui/apps/registry.js +1 -0
- package/dist/ui/components/dialog-panel.js +5 -3
- package/dist/ui/components/frame.d.ts +1 -0
- package/dist/ui/components/frame.js +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +1 -6
- package/dist/ui/components/line-input.js +4 -9
- package/dist/ui/components/list-view.d.ts +0 -6
- package/dist/ui/components/list-view.js +14 -7
- package/dist/ui/components/progress-status.d.ts +2 -0
- package/dist/ui/components/progress-status.js +4 -0
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/shortcuts.d.ts +1 -0
- package/dist/ui/components/shortcuts.js +1 -0
- package/dist/ui/components/spans.d.ts +2 -5
- package/dist/ui/components/spans.js +28 -8
- package/dist/ui/components/surface.d.ts +0 -5
- package/dist/ui/components/surface.js +1 -5
- package/dist/ui/components/text-selection.d.ts +10 -1
- package/dist/ui/components/text-selection.js +96 -29
- package/dist/ui/components/text.js +3 -3
- package/dist/ui/components/transcript-viewport.d.ts +42 -0
- package/dist/ui/components/transcript-viewport.js +287 -39
- package/dist/ui/settings/sections.d.ts +6 -19
- package/dist/ui/settings/sections.js +17 -15
- package/dist/ui/settings/session.d.ts +8 -18
- package/dist/ui/settings/session.js +60 -52
- package/dist/ui/settings/store.d.ts +1 -0
- package/dist/ui/settings/store.js +2 -1
- package/docs/architecture/code-documentation.md +77 -0
- package/docs/architecture/project-structure.md +15 -1
- package/docs/architecture/ui-reference-provenance.md +23 -3
- package/docs/ci-release-runbook.md +98 -7
- package/docs/manual-owned-ui-checkpoint.md +28 -1
- package/docs/manual-terminal-colour-check.md +4 -3
- package/docs/repository-governance-live-acceptance.md +77 -0
- package/package.json +11 -3
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { HStack, ProcessTerminal, ScrollView, TuiAltScreen, TuiMainScreen, VStack, visibleWidth, } from "#pi-tui";
|
|
2
|
+
import { InputPresentationCoordinator, } from "./input-presentation-coordinator.js";
|
|
3
|
+
/** Identifies the Pi TUI lifecycle stage that failed while preserving the original cause. */
|
|
2
4
|
export class PiTuiRuntimeError extends Error {
|
|
3
5
|
stage;
|
|
4
6
|
constructor(stage, cause) {
|
|
@@ -9,9 +11,11 @@ export class PiTuiRuntimeError extends Error {
|
|
|
9
11
|
}
|
|
10
12
|
class ComponentBridge {
|
|
11
13
|
port;
|
|
14
|
+
traceComposition;
|
|
12
15
|
#focused = false;
|
|
13
|
-
constructor(port) {
|
|
16
|
+
constructor(port, traceComposition) {
|
|
14
17
|
this.port = port;
|
|
18
|
+
this.traceComposition = traceComposition;
|
|
15
19
|
}
|
|
16
20
|
get focused() {
|
|
17
21
|
return this.#focused;
|
|
@@ -24,12 +28,18 @@ class ComponentBridge {
|
|
|
24
28
|
return this.port.wantsKeyRelease ?? false;
|
|
25
29
|
}
|
|
26
30
|
render(width) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
this.traceComposition?.("composition-start");
|
|
32
|
+
try {
|
|
33
|
+
const lines = [...this.port.render(width)];
|
|
34
|
+
const overflow = lines.findIndex(line => visibleWidth(line) > width);
|
|
35
|
+
if (overflow >= 0) {
|
|
36
|
+
throw new RangeError(`Pi TUI component row ${overflow} exceeds available width ${width}`);
|
|
37
|
+
}
|
|
38
|
+
return lines;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
this.traceComposition?.("composition-end");
|
|
31
42
|
}
|
|
32
|
-
return lines;
|
|
33
43
|
}
|
|
34
44
|
handleInput(data) {
|
|
35
45
|
this.port.handleInput?.(data);
|
|
@@ -79,6 +89,7 @@ class OverlayHandleBridge {
|
|
|
79
89
|
return !this.#disposed && this.handle.isFocused();
|
|
80
90
|
}
|
|
81
91
|
}
|
|
92
|
+
/** Bridges neutral presentation ports to one Pi TUI instance and owns terminal restoration on stop. */
|
|
82
93
|
export class PiTuiRuntimeAdapter {
|
|
83
94
|
#terminal;
|
|
84
95
|
#tuiTerminal;
|
|
@@ -92,18 +103,53 @@ export class PiTuiRuntimeAdapter {
|
|
|
92
103
|
#mountedComponents = new Set();
|
|
93
104
|
#scrollViews = new Map();
|
|
94
105
|
#overlayDisposers = new Set();
|
|
106
|
+
#overlayInputCoordination = new Map();
|
|
95
107
|
#inputListeners = new Map();
|
|
96
108
|
#preInputListeners = new Set();
|
|
109
|
+
#inputCoordinator;
|
|
110
|
+
#inputDiagnostics;
|
|
111
|
+
#diagnosticNow;
|
|
112
|
+
#inputSink;
|
|
97
113
|
#state = "idle";
|
|
98
114
|
#stopPromise;
|
|
99
115
|
#rootDisposed = false;
|
|
116
|
+
#terminalProgress = false;
|
|
100
117
|
constructor(options) {
|
|
101
118
|
this.#root = options.root;
|
|
102
119
|
this.#terminal = options.terminal ?? new ProcessTerminal();
|
|
103
|
-
this.#
|
|
120
|
+
this.#inputDiagnostics = options.inputDiagnostics;
|
|
121
|
+
this.#diagnosticNow = options.inputDiagnostics?.now ?? (() => performance.now());
|
|
122
|
+
const tracedTerminal = options.inputDiagnostics === undefined
|
|
123
|
+
? this.#terminal
|
|
124
|
+
: diagnosticTerminal(this.#terminal, phase => this.#traceRuntimePhase(phase));
|
|
125
|
+
const decoratedTerminal = options.decorateTerminal?.(tracedTerminal) ?? tracedTerminal;
|
|
126
|
+
const coordination = options.inputCoordination ?? (options.inputDiagnostics === undefined
|
|
127
|
+
? undefined
|
|
128
|
+
: { classify: () => "barrier" });
|
|
129
|
+
this.#inputCoordinator = coordination === undefined
|
|
130
|
+
? undefined
|
|
131
|
+
: new InputPresentationCoordinator({
|
|
132
|
+
classify: data => coordination.classify(data, this.#focusedOverlayInputCoordination()),
|
|
133
|
+
deliver: data => {
|
|
134
|
+
const routed = this.#routePreInput(data);
|
|
135
|
+
if (routed.length > 0)
|
|
136
|
+
this.#inputSink?.(routed);
|
|
137
|
+
},
|
|
138
|
+
...(coordination.onReceipt === undefined ? {} : { onReceipt: coordination.onReceipt }),
|
|
139
|
+
...(coordination.scheduler === undefined ? {} : { scheduler: coordination.scheduler }),
|
|
140
|
+
now: this.#diagnosticNow,
|
|
141
|
+
...(options.inputDiagnostics === undefined ? {} : {
|
|
142
|
+
onTrace: event => this.#traceInputCoordination(event),
|
|
143
|
+
}),
|
|
144
|
+
});
|
|
145
|
+
this.#tuiTerminal = this.#inputCoordinator === undefined
|
|
146
|
+
? preInputTerminal(decoratedTerminal, data => this.#routePreInput(data))
|
|
147
|
+
: coordinatedInputTerminal(decoratedTerminal, this.#inputCoordinator, sink => { this.#inputSink = sink; });
|
|
104
148
|
this.#layoutRoot = options.layoutRoot;
|
|
105
149
|
this.#logDirectory = options.logDirectory;
|
|
106
|
-
this.#rootBridge = new ComponentBridge(options.root
|
|
150
|
+
this.#rootBridge = new ComponentBridge(options.root, options.inputDiagnostics === undefined
|
|
151
|
+
? undefined
|
|
152
|
+
: phase => this.#traceRuntimePhase(phase));
|
|
107
153
|
this.#bridges.set(options.root, this.#rootBridge);
|
|
108
154
|
this.#mountedComponents.add(options.root);
|
|
109
155
|
try {
|
|
@@ -223,10 +269,12 @@ export class PiTuiRuntimeAdapter {
|
|
|
223
269
|
return;
|
|
224
270
|
hidden = true;
|
|
225
271
|
this.#overlayDisposers.delete(dispose);
|
|
272
|
+
this.#overlayInputCoordination.delete(bridge);
|
|
226
273
|
this.#bridges.delete(component);
|
|
227
274
|
component.dispose?.();
|
|
228
275
|
};
|
|
229
276
|
this.#overlayDisposers.add(dispose);
|
|
277
|
+
this.#overlayInputCoordination.set(bridge, options?.inputCoordination ?? "opaque");
|
|
230
278
|
const handle = this.#tui.showOverlay(bridge, toOverlayOptions(options));
|
|
231
279
|
return new OverlayHandleBridge(handle, target => this.#bridgeFor(target), dispose);
|
|
232
280
|
}
|
|
@@ -301,9 +349,26 @@ export class PiTuiRuntimeAdapter {
|
|
|
301
349
|
getHardwareCursor() {
|
|
302
350
|
return this.#tui.getShowHardwareCursor();
|
|
303
351
|
}
|
|
352
|
+
setClearOnShrink(enabled) {
|
|
353
|
+
this.#tui.setClearOnShrink(enabled);
|
|
354
|
+
}
|
|
355
|
+
getClearOnShrink() {
|
|
356
|
+
return this.#tui.getClearOnShrink();
|
|
357
|
+
}
|
|
358
|
+
setTerminalProgress(active) {
|
|
359
|
+
if (this.#terminalProgress === active)
|
|
360
|
+
return;
|
|
361
|
+
this.#terminal.setProgress(active);
|
|
362
|
+
this.#terminalProgress = active;
|
|
363
|
+
}
|
|
304
364
|
setTitle(title) {
|
|
305
365
|
this.#terminal.setTitle(title);
|
|
306
366
|
}
|
|
367
|
+
writeAfterStop(text) {
|
|
368
|
+
if (this.#state !== "stopped")
|
|
369
|
+
throw new Error("terminal output requires a stopped Pi TUI runtime");
|
|
370
|
+
this.#terminal.write(text);
|
|
371
|
+
}
|
|
307
372
|
invalidate() {
|
|
308
373
|
this.#tui.invalidate();
|
|
309
374
|
if (this.active)
|
|
@@ -322,6 +387,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
322
387
|
return this.#stopPromise;
|
|
323
388
|
if (this.#state === "idle") {
|
|
324
389
|
this.#state = "stopped";
|
|
390
|
+
this.#clearTerminalProgress();
|
|
325
391
|
this.#preInputListeners.clear();
|
|
326
392
|
this.#disposeRoot();
|
|
327
393
|
return Promise.resolve();
|
|
@@ -336,6 +402,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
336
402
|
}
|
|
337
403
|
async #stop(options) {
|
|
338
404
|
this.#state = "stopping";
|
|
405
|
+
this.#inputCoordinator?.flush();
|
|
339
406
|
let failure;
|
|
340
407
|
if (options.drainInput !== false) {
|
|
341
408
|
try {
|
|
@@ -354,6 +421,7 @@ export class PiTuiRuntimeAdapter {
|
|
|
354
421
|
this.#bestEffortTerminalRestore();
|
|
355
422
|
}
|
|
356
423
|
finally {
|
|
424
|
+
this.#clearTerminalProgress();
|
|
357
425
|
this.#disposeComponents();
|
|
358
426
|
}
|
|
359
427
|
this.#state = failure === undefined ? "stopped" : "failed";
|
|
@@ -378,6 +446,29 @@ export class PiTuiRuntimeAdapter {
|
|
|
378
446
|
}
|
|
379
447
|
return current;
|
|
380
448
|
}
|
|
449
|
+
#focusedOverlayInputCoordination() {
|
|
450
|
+
for (const [bridge, coordination] of this.#overlayInputCoordination) {
|
|
451
|
+
if (bridge.focused)
|
|
452
|
+
return coordination;
|
|
453
|
+
}
|
|
454
|
+
return undefined;
|
|
455
|
+
}
|
|
456
|
+
#traceInputCoordination(event) {
|
|
457
|
+
this.#inputDiagnostics?.onEvent(event);
|
|
458
|
+
}
|
|
459
|
+
#traceRuntimePhase(phase) {
|
|
460
|
+
if (this.#inputDiagnostics === undefined)
|
|
461
|
+
return;
|
|
462
|
+
const coordinator = this.#inputCoordinator;
|
|
463
|
+
this.#inputDiagnostics.onEvent({
|
|
464
|
+
phase,
|
|
465
|
+
revision: coordinator?.appliedRevision ?? 0,
|
|
466
|
+
atMs: this.#diagnosticNow(),
|
|
467
|
+
pendingDepth: coordinator?.pendingDepth ?? 0,
|
|
468
|
+
pendingPresentationDepth: coordinator?.pendingPresentation === true ? 1 : 0,
|
|
469
|
+
appliedRevision: coordinator?.appliedRevision ?? 0,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
381
472
|
#mountTui(tui) {
|
|
382
473
|
tui.addChild(this.#rootBridge);
|
|
383
474
|
if (tui instanceof TuiAltScreen && this.#layoutRoot !== undefined) {
|
|
@@ -459,7 +550,15 @@ export class PiTuiRuntimeAdapter {
|
|
|
459
550
|
this.#bestEffortTerminalRestore();
|
|
460
551
|
}
|
|
461
552
|
}
|
|
553
|
+
#clearTerminalProgress() {
|
|
554
|
+
try {
|
|
555
|
+
this.#terminal.setProgress(false);
|
|
556
|
+
}
|
|
557
|
+
catch { }
|
|
558
|
+
this.#terminalProgress = false;
|
|
559
|
+
}
|
|
462
560
|
#bestEffortTerminalRestore() {
|
|
561
|
+
this.#clearTerminalProgress();
|
|
463
562
|
try {
|
|
464
563
|
this.#terminal.showCursor();
|
|
465
564
|
}
|
|
@@ -478,15 +577,74 @@ export class PiTuiRuntimeAdapter {
|
|
|
478
577
|
this.#mountedComponents.clear();
|
|
479
578
|
}
|
|
480
579
|
#disposeComponents() {
|
|
580
|
+
this.#inputCoordinator?.dispose(false);
|
|
581
|
+
this.#inputSink = undefined;
|
|
481
582
|
for (const remove of this.#inputListeners.values())
|
|
482
583
|
remove();
|
|
483
584
|
this.#inputListeners.clear();
|
|
484
585
|
this.#preInputListeners.clear();
|
|
485
586
|
for (const dispose of [...this.#overlayDisposers])
|
|
486
587
|
dispose();
|
|
588
|
+
this.#overlayInputCoordination.clear();
|
|
487
589
|
this.#disposeRoot();
|
|
488
590
|
}
|
|
489
591
|
}
|
|
592
|
+
function coordinatedInputTerminal(terminal, coordinator, setSink) {
|
|
593
|
+
return {
|
|
594
|
+
get columns() { return terminal.columns; },
|
|
595
|
+
get rows() { return terminal.rows; },
|
|
596
|
+
get kittyProtocolActive() { return terminal.kittyProtocolActive; },
|
|
597
|
+
start(onInput, onResize) {
|
|
598
|
+
setSink(onInput);
|
|
599
|
+
terminal.start(data => coordinator.accept(data), () => {
|
|
600
|
+
coordinator.flush();
|
|
601
|
+
onResize();
|
|
602
|
+
});
|
|
603
|
+
},
|
|
604
|
+
stop() {
|
|
605
|
+
coordinator.flush();
|
|
606
|
+
setSink(undefined);
|
|
607
|
+
terminal.stop();
|
|
608
|
+
},
|
|
609
|
+
drainInput: (maxMs, idleMs) => terminal.drainInput(maxMs, idleMs),
|
|
610
|
+
write: data => terminal.write(data),
|
|
611
|
+
moveBy: lines => terminal.moveBy(lines),
|
|
612
|
+
hideCursor: () => terminal.hideCursor(),
|
|
613
|
+
showCursor: () => terminal.showCursor(),
|
|
614
|
+
clearLine: () => terminal.clearLine(),
|
|
615
|
+
clearFromCursor: () => terminal.clearFromCursor(),
|
|
616
|
+
clearScreen: () => terminal.clearScreen(),
|
|
617
|
+
setTitle: title => terminal.setTitle(title),
|
|
618
|
+
setProgress: active => terminal.setProgress(active),
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
function diagnosticTerminal(terminal, trace) {
|
|
622
|
+
return {
|
|
623
|
+
get columns() { return terminal.columns; },
|
|
624
|
+
get rows() { return terminal.rows; },
|
|
625
|
+
get kittyProtocolActive() { return terminal.kittyProtocolActive; },
|
|
626
|
+
start: (onInput, onResize) => terminal.start(onInput, onResize),
|
|
627
|
+
stop: () => terminal.stop(),
|
|
628
|
+
drainInput: (maxMs, idleMs) => terminal.drainInput(maxMs, idleMs),
|
|
629
|
+
write(data) {
|
|
630
|
+
trace("write-start");
|
|
631
|
+
try {
|
|
632
|
+
terminal.write(data);
|
|
633
|
+
}
|
|
634
|
+
finally {
|
|
635
|
+
trace("write-end");
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
moveBy: lines => terminal.moveBy(lines),
|
|
639
|
+
hideCursor: () => terminal.hideCursor(),
|
|
640
|
+
showCursor: () => terminal.showCursor(),
|
|
641
|
+
clearLine: () => terminal.clearLine(),
|
|
642
|
+
clearFromCursor: () => terminal.clearFromCursor(),
|
|
643
|
+
clearScreen: () => terminal.clearScreen(),
|
|
644
|
+
setTitle: title => terminal.setTitle(title),
|
|
645
|
+
setProgress: active => terminal.setProgress(active),
|
|
646
|
+
};
|
|
647
|
+
}
|
|
490
648
|
function preInputTerminal(terminal, route) {
|
|
491
649
|
return {
|
|
492
650
|
get columns() { return terminal.columns; },
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PresentationComponentPort } from "../../../contracts/presentation/index.js";
|
|
2
|
+
import type { PiTuiInputCoordinationDecision, PiTuiInputCoordinationScheduler, PiTuiInputSurfaceKind } from "./input-presentation-coordinator.js";
|
|
2
3
|
export type PiTuiRuntimeState = "idle" | "running" | "stopping" | "stopped" | "failed";
|
|
3
4
|
export interface PiTuiViewport {
|
|
4
5
|
readonly columns: number;
|
|
@@ -50,6 +51,8 @@ export interface PiTuiOverlayOptions {
|
|
|
50
51
|
readonly margin?: PiTuiOverlayMargin | number;
|
|
51
52
|
readonly visible?: (columns: number, rows: number) => boolean;
|
|
52
53
|
readonly nonCapturing?: boolean;
|
|
54
|
+
/** A1-owned input scheduling declaration; omitted extension overlays fail closed. */
|
|
55
|
+
readonly inputCoordination?: Exclude<PiTuiInputSurfaceKind, "editor">;
|
|
53
56
|
}
|
|
54
57
|
export interface PiTuiOverlayUnfocusOptions {
|
|
55
58
|
readonly target: PiTuiComponentPort | null;
|
|
@@ -98,11 +101,33 @@ export type PiTuiLayoutNode = {
|
|
|
98
101
|
readonly scrollbarStyle?: (text: string) => string;
|
|
99
102
|
readonly scrollbarHideDelayMs?: number;
|
|
100
103
|
};
|
|
104
|
+
export type PiTuiInputDiagnosticsPhase = "receipt" | "semantic-start" | "semantic-end" | "composition-start" | "composition-end" | "write-start" | "write-end";
|
|
105
|
+
export interface PiTuiInputDiagnosticsEvent {
|
|
106
|
+
readonly phase: PiTuiInputDiagnosticsPhase;
|
|
107
|
+
readonly revision: number;
|
|
108
|
+
readonly atMs: number;
|
|
109
|
+
readonly pendingDepth: number;
|
|
110
|
+
readonly pendingPresentationDepth: 0 | 1;
|
|
111
|
+
readonly appliedRevision: number;
|
|
112
|
+
}
|
|
101
113
|
export interface PiTuiRuntimeAdapterOptions {
|
|
102
114
|
readonly root: PiTuiComponentPort;
|
|
103
115
|
readonly mode?: "regular" | "fullscreen";
|
|
104
116
|
readonly layoutRoot?: PiTuiLayoutNode;
|
|
105
117
|
readonly terminal?: PiTuiTerminalPort;
|
|
118
|
+
/** A1-owned public-boundary decorator; comparison paths omit it. */
|
|
119
|
+
readonly decorateTerminal?: (terminal: PiTuiTerminalPort) => PiTuiTerminalPort;
|
|
120
|
+
/** Bare-custom-viewport scheduling policy; comparison paths omit it. */
|
|
121
|
+
readonly inputCoordination?: {
|
|
122
|
+
readonly classify: (data: string, focusedOverlay?: Exclude<PiTuiInputSurfaceKind, "editor">) => PiTuiInputCoordinationDecision;
|
|
123
|
+
readonly onReceipt?: () => void;
|
|
124
|
+
readonly scheduler?: PiTuiInputCoordinationScheduler;
|
|
125
|
+
};
|
|
126
|
+
/** Optional bounded evidence hook. Production composition omits it. */
|
|
127
|
+
readonly inputDiagnostics?: {
|
|
128
|
+
readonly onEvent: (event: PiTuiInputDiagnosticsEvent) => void;
|
|
129
|
+
readonly now?: () => number;
|
|
130
|
+
};
|
|
106
131
|
readonly hardwareCursor?: boolean;
|
|
107
132
|
readonly mouse?: boolean;
|
|
108
133
|
readonly wheelScrollLines?: number;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { PiTuiTerminalPort } from "./contracts.js";
|
|
2
|
+
export declare const PINNED_PI_TUI_DAMAGE_GRAMMAR = "@earendil-works/pi-tui@0.84.2:tui-alt-screen-one-write-v1";
|
|
3
|
+
export interface PiTuiDamageFrameDescriptor {
|
|
4
|
+
readonly frameId: number;
|
|
5
|
+
readonly width: number;
|
|
6
|
+
readonly height: number;
|
|
7
|
+
readonly transcript: {
|
|
8
|
+
readonly rowStart: number;
|
|
9
|
+
readonly rowEnd: number;
|
|
10
|
+
} | null;
|
|
11
|
+
readonly dock: {
|
|
12
|
+
readonly rowStart: number;
|
|
13
|
+
readonly rowEnd: number;
|
|
14
|
+
} | null;
|
|
15
|
+
readonly verticalShiftRows: number;
|
|
16
|
+
readonly safeVerticalShift: boolean;
|
|
17
|
+
readonly cause: string;
|
|
18
|
+
}
|
|
19
|
+
export interface PiTuiDamageFrameSafety {
|
|
20
|
+
readonly overlayActive: boolean;
|
|
21
|
+
readonly selectionActive: boolean;
|
|
22
|
+
readonly replacementSurfaceActive: boolean;
|
|
23
|
+
}
|
|
24
|
+
export type PiTuiDamageDecisionReason = "transformed" | "suppressed-redundant-clear" | "not-armed" | "unsafe-frame" | "stale-frame" | "unsupported-region-scroll" | "geometry-mismatch" | "grammar-mismatch" | "unsafe-terminal-content" | "incomplete-prior-frame" | "excessive-real-damage";
|
|
25
|
+
export interface PiTuiDamageDecision {
|
|
26
|
+
readonly frameId: number | null;
|
|
27
|
+
readonly transformed: boolean;
|
|
28
|
+
readonly reason: PiTuiDamageDecisionReason;
|
|
29
|
+
readonly shiftRows: number;
|
|
30
|
+
readonly paintedRows: readonly number[];
|
|
31
|
+
}
|
|
32
|
+
export interface DamageAwareTerminalOptions {
|
|
33
|
+
readonly regionalScroll: boolean;
|
|
34
|
+
readonly onResize?: () => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* A fail-closed adapter for one pinned Pi fullscreen write grammar. Semantic
|
|
38
|
+
* viewport metadata authorizes a shift; bytes are inspected only to validate
|
|
39
|
+
* and preserve Pi's complete row paint and cursor syntax.
|
|
40
|
+
*/
|
|
41
|
+
export declare class DamageAwareTerminalAdapter implements PiTuiTerminalPort {
|
|
42
|
+
#private;
|
|
43
|
+
readonly inner: PiTuiTerminalPort;
|
|
44
|
+
readonly options: DamageAwareTerminalOptions;
|
|
45
|
+
constructor(inner: PiTuiTerminalPort, options: DamageAwareTerminalOptions);
|
|
46
|
+
get columns(): number;
|
|
47
|
+
get rows(): number;
|
|
48
|
+
get kittyProtocolActive(): boolean;
|
|
49
|
+
get lastDecision(): PiTuiDamageDecision;
|
|
50
|
+
arm(descriptor: PiTuiDamageFrameDescriptor, safety: PiTuiDamageFrameSafety): void;
|
|
51
|
+
start(onInput: (data: string) => void, onResize: () => void): void;
|
|
52
|
+
stop(): void;
|
|
53
|
+
drainInput(maxMs?: number, idleMs?: number): Promise<void>;
|
|
54
|
+
write(data: string): void;
|
|
55
|
+
moveBy(lines: number): void;
|
|
56
|
+
hideCursor(): void;
|
|
57
|
+
showCursor(): void;
|
|
58
|
+
clearLine(): void;
|
|
59
|
+
clearFromCursor(): void;
|
|
60
|
+
clearScreen(): void;
|
|
61
|
+
setTitle(title: string): void;
|
|
62
|
+
setProgress(active: boolean): void;
|
|
63
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
export const PINNED_PI_TUI_DAMAGE_GRAMMAR = "@earendil-works/pi-tui@0.84.2:tui-alt-screen-one-write-v1";
|
|
2
|
+
const BEGIN_SYNCHRONIZED_OUTPUT = "\u001b[?2026h";
|
|
3
|
+
const END_SYNCHRONIZED_OUTPUT = "\u001b[?2026l";
|
|
4
|
+
const ROW_MARKER = /\u001b\[(\d+);1H\u001b\[2K/gu;
|
|
5
|
+
const CURSOR_SUFFIX = /\u001b\[(\d+);(\d+)H\u001b\[\?25[hl]\u001b\[\?2026l$/u;
|
|
6
|
+
const OSC8 = /\u001b\]8;[^;\u0007\u001b]*;([^\u0007\u001b]*)(?:\u0007|\u001b\\)/gu;
|
|
7
|
+
/**
|
|
8
|
+
* A fail-closed adapter for one pinned Pi fullscreen write grammar. Semantic
|
|
9
|
+
* viewport metadata authorizes a shift; bytes are inspected only to validate
|
|
10
|
+
* and preserve Pi's complete row paint and cursor syntax.
|
|
11
|
+
*/
|
|
12
|
+
export class DamageAwareTerminalAdapter {
|
|
13
|
+
inner;
|
|
14
|
+
options;
|
|
15
|
+
#rows = new Map();
|
|
16
|
+
#armed;
|
|
17
|
+
#lastConsumedFrameId = 0;
|
|
18
|
+
#cacheWidth = 0;
|
|
19
|
+
#cacheHeight = 0;
|
|
20
|
+
#decision = {
|
|
21
|
+
frameId: null,
|
|
22
|
+
transformed: false,
|
|
23
|
+
reason: "not-armed",
|
|
24
|
+
shiftRows: 0,
|
|
25
|
+
paintedRows: [],
|
|
26
|
+
};
|
|
27
|
+
constructor(inner, options) {
|
|
28
|
+
this.inner = inner;
|
|
29
|
+
this.options = options;
|
|
30
|
+
}
|
|
31
|
+
get columns() { return this.inner.columns; }
|
|
32
|
+
get rows() { return this.inner.rows; }
|
|
33
|
+
get kittyProtocolActive() { return this.inner.kittyProtocolActive; }
|
|
34
|
+
get lastDecision() { return this.#decision; }
|
|
35
|
+
arm(descriptor, safety) {
|
|
36
|
+
this.#armed = { descriptor, safety };
|
|
37
|
+
}
|
|
38
|
+
start(onInput, onResize) {
|
|
39
|
+
this.inner.start(onInput, () => {
|
|
40
|
+
this.#invalidateRows();
|
|
41
|
+
this.options.onResize?.();
|
|
42
|
+
onResize();
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
stop() {
|
|
46
|
+
this.#armed = undefined;
|
|
47
|
+
this.#invalidateRows();
|
|
48
|
+
this.inner.stop();
|
|
49
|
+
}
|
|
50
|
+
drainInput(maxMs, idleMs) { return this.inner.drainInput(maxMs, idleMs); }
|
|
51
|
+
write(data) {
|
|
52
|
+
const armed = this.#armed;
|
|
53
|
+
this.#armed = undefined;
|
|
54
|
+
const parsed = parsePinnedFullscreenWrite(data);
|
|
55
|
+
const decision = this.#decide(armed, parsed);
|
|
56
|
+
this.#decision = decision;
|
|
57
|
+
if (decision.reason === "suppressed-redundant-clear" && parsed !== null) {
|
|
58
|
+
this.inner.write(buildClearlessWrite(parsed));
|
|
59
|
+
}
|
|
60
|
+
else if (decision.transformed && armed !== undefined && parsed !== null) {
|
|
61
|
+
this.inner.write(buildDamageWrite(parsed, armed.descriptor, new Set(decision.paintedRows), this.#rows));
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.inner.write(data);
|
|
65
|
+
}
|
|
66
|
+
this.#rememberFrame(armed?.descriptor, parsed, data);
|
|
67
|
+
}
|
|
68
|
+
moveBy(lines) { this.inner.moveBy(lines); }
|
|
69
|
+
hideCursor() { this.inner.hideCursor(); }
|
|
70
|
+
showCursor() { this.inner.showCursor(); }
|
|
71
|
+
clearLine() { this.#invalidateRows(); this.inner.clearLine(); }
|
|
72
|
+
clearFromCursor() { this.#invalidateRows(); this.inner.clearFromCursor(); }
|
|
73
|
+
clearScreen() { this.#invalidateRows(); this.inner.clearScreen(); }
|
|
74
|
+
setTitle(title) { this.inner.setTitle(title); }
|
|
75
|
+
setProgress(active) { this.inner.setProgress(active); }
|
|
76
|
+
#decide(armed, parsed) {
|
|
77
|
+
if (armed === undefined) {
|
|
78
|
+
return { frameId: null, transformed: false, reason: "not-armed", shiftRows: 0, paintedRows: [] };
|
|
79
|
+
}
|
|
80
|
+
const descriptor = armed.descriptor;
|
|
81
|
+
const base = {
|
|
82
|
+
frameId: descriptor.frameId,
|
|
83
|
+
transformed: false,
|
|
84
|
+
shiftRows: descriptor.verticalShiftRows,
|
|
85
|
+
paintedRows: [],
|
|
86
|
+
};
|
|
87
|
+
if (descriptor.frameId <= this.#lastConsumedFrameId)
|
|
88
|
+
return { ...base, reason: "stale-frame" };
|
|
89
|
+
this.#lastConsumedFrameId = descriptor.frameId;
|
|
90
|
+
if (parsed !== null && parsed.structuralPrefix === "\u001b[2J"
|
|
91
|
+
&& this.#cacheWidth === descriptor.width && this.#cacheHeight === descriptor.height
|
|
92
|
+
&& hasCompleteScreenRows(parsed.rows, descriptor.height) && !hasUnsafeTerminalContent(parsed)) {
|
|
93
|
+
return {
|
|
94
|
+
frameId: descriptor.frameId,
|
|
95
|
+
transformed: true,
|
|
96
|
+
reason: "suppressed-redundant-clear",
|
|
97
|
+
shiftRows: 0,
|
|
98
|
+
paintedRows: parsed.rows.map(row => row.row),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!descriptor.safeVerticalShift || armed.safety.overlayActive || armed.safety.selectionActive
|
|
102
|
+
|| armed.safety.replacementSurfaceActive || descriptor.transcript === null) {
|
|
103
|
+
return { ...base, reason: "unsafe-frame" };
|
|
104
|
+
}
|
|
105
|
+
if (!this.options.regionalScroll)
|
|
106
|
+
return { ...base, reason: "unsupported-region-scroll" };
|
|
107
|
+
if (descriptor.width !== this.columns || descriptor.height !== this.rows
|
|
108
|
+
|| descriptor.verticalShiftRows < 1
|
|
109
|
+
|| descriptor.verticalShiftRows >= descriptor.transcript.rowEnd - descriptor.transcript.rowStart + 1) {
|
|
110
|
+
return { ...base, reason: "geometry-mismatch" };
|
|
111
|
+
}
|
|
112
|
+
if (parsed === null)
|
|
113
|
+
return { ...base, reason: "grammar-mismatch" };
|
|
114
|
+
if (parsed.structuralPrefix.length > 0 || hasUnsafeTerminalContent(parsed)) {
|
|
115
|
+
return { ...base, reason: "unsafe-terminal-content" };
|
|
116
|
+
}
|
|
117
|
+
const { rowStart, rowEnd } = descriptor.transcript;
|
|
118
|
+
const desired = new Map(parsed.rows.map(row => [row.row, row.content]));
|
|
119
|
+
for (let row = rowStart; row <= rowEnd; row += 1) {
|
|
120
|
+
if (!desired.has(row) && !this.#rows.has(row))
|
|
121
|
+
return { ...base, reason: "incomplete-prior-frame" };
|
|
122
|
+
if (row <= rowEnd - descriptor.verticalShiftRows && !this.#rows.has(row + descriptor.verticalShiftRows)) {
|
|
123
|
+
return { ...base, reason: "incomplete-prior-frame" };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const row of parsed.rows) {
|
|
127
|
+
const inTranscript = row.row >= rowStart && row.row <= rowEnd;
|
|
128
|
+
const inDock = descriptor.dock !== null && row.row >= descriptor.dock.rowStart && row.row <= descriptor.dock.rowEnd;
|
|
129
|
+
if (!inTranscript && !inDock)
|
|
130
|
+
return { ...base, reason: "grammar-mismatch" };
|
|
131
|
+
}
|
|
132
|
+
const painted = new Set();
|
|
133
|
+
for (let row = rowStart; row <= rowEnd; row += 1) {
|
|
134
|
+
const source = row + descriptor.verticalShiftRows;
|
|
135
|
+
const desiredContent = desired.get(row) ?? this.#rows.get(row);
|
|
136
|
+
if (source > rowEnd || desiredContent !== this.#rows.get(source))
|
|
137
|
+
painted.add(row);
|
|
138
|
+
}
|
|
139
|
+
for (const row of parsed.rows) {
|
|
140
|
+
if (descriptor.dock !== null && row.row >= descriptor.dock.rowStart && row.row <= descriptor.dock.rowEnd)
|
|
141
|
+
painted.add(row.row);
|
|
142
|
+
}
|
|
143
|
+
// Rationale: ordinary followed prose may change one active tail source row, expose
|
|
144
|
+
// the shifted suffix, and restyle one sticky boundary row. Larger changes indicate
|
|
145
|
+
// Markdown/theme/overlay reflow and fail closed.
|
|
146
|
+
if (painted.size > descriptor.verticalShiftRows + 2 + countDockRows(parsed.rows, descriptor.dock)) {
|
|
147
|
+
return { ...base, reason: "excessive-real-damage" };
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
frameId: descriptor.frameId,
|
|
151
|
+
transformed: true,
|
|
152
|
+
reason: "transformed",
|
|
153
|
+
shiftRows: descriptor.verticalShiftRows,
|
|
154
|
+
paintedRows: [...painted].sort((left, right) => left - right),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
#rememberFrame(descriptor, parsed, raw) {
|
|
158
|
+
if (raw.includes("\u001b[2J") || raw.includes("\u001b[?1049") || raw.includes("\u001b_G") || raw.includes("\u001bPq")) {
|
|
159
|
+
this.#invalidateRows();
|
|
160
|
+
}
|
|
161
|
+
if (parsed === null)
|
|
162
|
+
return;
|
|
163
|
+
if (descriptor !== undefined && (descriptor.width !== this.#cacheWidth || descriptor.height !== this.#cacheHeight)) {
|
|
164
|
+
this.#invalidateRows();
|
|
165
|
+
this.#cacheWidth = descriptor.width;
|
|
166
|
+
this.#cacheHeight = descriptor.height;
|
|
167
|
+
}
|
|
168
|
+
for (const row of parsed.rows)
|
|
169
|
+
this.#rows.set(row.row, row.content);
|
|
170
|
+
}
|
|
171
|
+
#invalidateRows() {
|
|
172
|
+
this.#rows.clear();
|
|
173
|
+
this.#cacheWidth = 0;
|
|
174
|
+
this.#cacheHeight = 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function parsePinnedFullscreenWrite(data) {
|
|
178
|
+
if (!data.startsWith(BEGIN_SYNCHRONIZED_OUTPUT) || !data.endsWith(END_SYNCHRONIZED_OUTPUT))
|
|
179
|
+
return null;
|
|
180
|
+
const suffix = data.match(CURSOR_SUFFIX);
|
|
181
|
+
if (suffix?.index === undefined)
|
|
182
|
+
return null;
|
|
183
|
+
const body = data.slice(BEGIN_SYNCHRONIZED_OUTPUT.length, suffix.index);
|
|
184
|
+
const matches = [...body.matchAll(ROW_MARKER)];
|
|
185
|
+
if (matches.length === 0)
|
|
186
|
+
return null;
|
|
187
|
+
const structuralPrefix = body.slice(0, matches[0].index);
|
|
188
|
+
if (structuralPrefix !== "" && structuralPrefix !== "\u001b[2J")
|
|
189
|
+
return null;
|
|
190
|
+
const rows = [];
|
|
191
|
+
let previousRow = 0;
|
|
192
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
193
|
+
const match = matches[index];
|
|
194
|
+
const row = Number.parseInt(match[1], 10);
|
|
195
|
+
if (!Number.isSafeInteger(row) || row < 1 || row <= previousRow)
|
|
196
|
+
return null;
|
|
197
|
+
previousRow = row;
|
|
198
|
+
const start = match.index;
|
|
199
|
+
const contentStart = start + match[0].length;
|
|
200
|
+
const end = matches[index + 1]?.index ?? body.length;
|
|
201
|
+
const content = body.slice(contentStart, end);
|
|
202
|
+
rows.push({ row, content, segment: body.slice(start, end) });
|
|
203
|
+
}
|
|
204
|
+
return { structuralPrefix, rows, cursorSuffix: data.slice(suffix.index) };
|
|
205
|
+
}
|
|
206
|
+
function hasUnsafeTerminalContent(parsed) {
|
|
207
|
+
const data = parsed.rows.map(row => row.content).join("");
|
|
208
|
+
if (data.includes("\u001b_G") || data.includes("\u001bPq") || data.includes("\u001b[2J"))
|
|
209
|
+
return true;
|
|
210
|
+
for (const match of data.matchAll(OSC8))
|
|
211
|
+
if ((match[1] ?? "").length > 0)
|
|
212
|
+
return true;
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
function buildDamageWrite(parsed, descriptor, paintedRows, previousRows) {
|
|
216
|
+
const transcript = descriptor.transcript;
|
|
217
|
+
const regionShift = `\u001b[${transcript.rowStart};${transcript.rowEnd}r`
|
|
218
|
+
+ `\u001b[${transcript.rowStart};1H`
|
|
219
|
+
+ `\u001b[${descriptor.verticalShiftRows}S`
|
|
220
|
+
+ "\u001b[r";
|
|
221
|
+
const parsedRows = new Map(parsed.rows.map(row => [row.row, row]));
|
|
222
|
+
const rowPaint = [...paintedRows].sort((left, right) => left - right).map(row => {
|
|
223
|
+
const parsedRow = parsedRows.get(row);
|
|
224
|
+
if (parsedRow !== undefined)
|
|
225
|
+
return parsedRow.segment;
|
|
226
|
+
return `\u001b[${row};1H\u001b[2K${previousRows.get(row) ?? ""}`;
|
|
227
|
+
}).join("");
|
|
228
|
+
return `${BEGIN_SYNCHRONIZED_OUTPUT}${regionShift}${rowPaint}${parsed.cursorSuffix}`;
|
|
229
|
+
}
|
|
230
|
+
function buildClearlessWrite(parsed) {
|
|
231
|
+
return `${BEGIN_SYNCHRONIZED_OUTPUT}${parsed.rows.map(row => row.segment).join("")}${parsed.cursorSuffix}`;
|
|
232
|
+
}
|
|
233
|
+
function hasCompleteScreenRows(rows, height) {
|
|
234
|
+
return rows.length === height && rows.every((row, index) => row.row === index + 1);
|
|
235
|
+
}
|
|
236
|
+
function countDockRows(rows, dock) {
|
|
237
|
+
if (dock === null)
|
|
238
|
+
return 0;
|
|
239
|
+
return rows.filter(row => row.row >= dock.rowStart && row.row <= dock.rowEnd).length;
|
|
240
|
+
}
|