@timurproko/a1 0.1.8-dev.182 → 0.1.8-dev.218

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.
Files changed (143) hide show
  1. package/bin/cli.js +2 -2
  2. package/bin/sync-pi-tui-proxy.js +1 -1
  3. package/bin/ui.js +1 -1
  4. package/dist/cli/dispatch.js +3 -3
  5. package/dist/cli/packages.js +1 -1
  6. package/dist/cli/version-stats.js +3 -3
  7. package/dist/composition/owned-ui.js +1 -1
  8. package/dist/composition/settings-route-host.js +6 -4
  9. package/dist/features/launch/development-launch.js +1 -1
  10. package/dist/features/owned-ui/customization.d.ts +2 -0
  11. package/dist/features/owned-ui/customization.js +2 -0
  12. package/dist/features/owned-ui/diagnostics.d.ts +1 -0
  13. package/dist/features/owned-ui/diagnostics.js +1 -0
  14. package/dist/features/owned-ui/project-trust-prompt.d.ts +11 -6
  15. package/dist/features/owned-ui/project-trust-prompt.js +134 -17
  16. package/dist/features/owned-ui/settings-app.js +47 -50
  17. package/dist/features/workspace/reducer.d.ts +1 -0
  18. package/dist/features/workspace/reducer.js +1 -0
  19. package/dist/features/workspace/router.d.ts +1 -0
  20. package/dist/features/workspace/router.js +1 -0
  21. package/dist/features/workspace/store.d.ts +1 -0
  22. package/dist/features/workspace/store.js +1 -0
  23. package/dist/foundation/launch-guardian/main.js +1 -1
  24. package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
  25. package/dist/foundation/native-host-protocol/codec.js +2 -0
  26. package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
  27. package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
  28. package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
  29. package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
  30. package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
  31. package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
  32. package/dist/foundation/protocol/client.d.ts +1 -0
  33. package/dist/foundation/protocol/client.js +1 -0
  34. package/dist/foundation/protocol/messages.d.ts +1 -0
  35. package/dist/foundation/protocol/messages.js +1 -0
  36. package/dist/foundation/release/bootstrap.js +6 -6
  37. package/dist/foundation/release/cohort-state.d.ts +1 -0
  38. package/dist/foundation/release/cohort-state.js +1 -0
  39. package/dist/foundation/release/release-gc.js +1 -1
  40. package/dist/foundation/release/release.js +1 -1
  41. package/dist/foundation/release/update-transaction.d.ts +1 -0
  42. package/dist/foundation/release/update-transaction.js +1 -0
  43. package/dist/foundation/release/update.js +12 -12
  44. package/dist/foundation/storage/control-store.d.ts +1 -0
  45. package/dist/foundation/storage/control-store.js +1 -0
  46. package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
  47. package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
  48. package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
  49. package/dist/foundation/structured-agent-runtime/commands.js +1 -0
  50. package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
  51. package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
  52. package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
  53. package/dist/foundation/structured-agent-runtime/state.js +1 -0
  54. package/dist/foundation/supervision/main.js +1 -1
  55. package/dist/foundation/supervision/server.d.ts +1 -0
  56. package/dist/foundation/supervision/server.js +4 -6
  57. package/dist/integrations/pi/components/conformance.d.ts +1 -0
  58. package/dist/integrations/pi/components/conformance.js +1 -0
  59. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  60. package/dist/integrations/pi/components/shell-footer-status.js +8 -7
  61. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  62. package/dist/integrations/pi/engine/adapter.d.ts +7 -0
  63. package/dist/integrations/pi/engine/adapter.js +37 -35
  64. package/dist/integrations/pi/engine/conformance.d.ts +2 -0
  65. package/dist/integrations/pi/engine/conformance.js +2 -0
  66. package/dist/integrations/pi/engine/http-dispatcher.js +1 -1
  67. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
  68. package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
  69. package/dist/integrations/pi/engine/package-integration.js +1 -1
  70. package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
  71. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
  72. package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
  73. package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
  74. package/dist/integrations/pi/engine/session-integration.js +2 -1
  75. package/dist/integrations/pi/engine/settings-effects.d.ts +13 -2
  76. package/dist/integrations/pi/engine/settings-effects.js +62 -42
  77. package/dist/integrations/pi/engine/settings-integration.js +4 -4
  78. package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -0
  79. package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
  80. package/dist/integrations/pi/session-ui/index.d.ts +1 -0
  81. package/dist/integrations/pi/session-ui/index.js +1 -0
  82. package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
  83. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +30 -2
  84. package/dist/integrations/pi/session-ui/session-shell-root.js +133 -72
  85. package/dist/integrations/pi/session-ui/session-shell.d.ts +9 -2
  86. package/dist/integrations/pi/session-ui/session-shell.js +112 -37
  87. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  88. package/dist/integrations/pi/session-ui/session-viewport-controller.js +44 -11
  89. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  90. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  91. package/dist/integrations/pi/session-ui/system-clipboard.js +6 -6
  92. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
  93. package/dist/integrations/pi/tui-runtime/adapter.js +138 -8
  94. package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
  95. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
  96. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
  97. package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
  98. package/dist/integrations/pi/tui-runtime/index.js +2 -0
  99. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  100. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  101. package/dist/native/darwin-arm64/manifest.json +3 -3
  102. package/dist/native/darwin-arm64/process-guardian +0 -0
  103. package/dist/native/linux-x64/manifest.json +1 -1
  104. package/dist/native/win32-x64/manifest.json +2 -2
  105. package/dist/native/win32-x64/process-guardian.exe +0 -0
  106. package/dist/ui/apps/host.js +5 -9
  107. package/dist/ui/apps/registry.d.ts +1 -0
  108. package/dist/ui/apps/registry.js +1 -0
  109. package/dist/ui/components/dialog-panel.js +5 -3
  110. package/dist/ui/components/frame.d.ts +1 -0
  111. package/dist/ui/components/frame.js +1 -0
  112. package/dist/ui/components/index.d.ts +1 -0
  113. package/dist/ui/components/index.js +1 -0
  114. package/dist/ui/components/line-input.d.ts +1 -6
  115. package/dist/ui/components/line-input.js +4 -9
  116. package/dist/ui/components/list-view.d.ts +0 -6
  117. package/dist/ui/components/list-view.js +14 -7
  118. package/dist/ui/components/progress-status.d.ts +2 -0
  119. package/dist/ui/components/progress-status.js +4 -0
  120. package/dist/ui/components/scrollbar.js +1 -1
  121. package/dist/ui/components/shortcuts.d.ts +1 -0
  122. package/dist/ui/components/shortcuts.js +1 -0
  123. package/dist/ui/components/spans.d.ts +0 -5
  124. package/dist/ui/components/spans.js +3 -8
  125. package/dist/ui/components/surface.d.ts +0 -5
  126. package/dist/ui/components/surface.js +1 -5
  127. package/dist/ui/components/text-selection.d.ts +10 -1
  128. package/dist/ui/components/text-selection.js +96 -29
  129. package/dist/ui/components/text.js +3 -3
  130. package/dist/ui/components/transcript-viewport.d.ts +42 -0
  131. package/dist/ui/components/transcript-viewport.js +287 -39
  132. package/dist/ui/settings/sections.d.ts +0 -2
  133. package/dist/ui/settings/sections.js +10 -16
  134. package/dist/ui/settings/session.js +5 -4
  135. package/dist/ui/settings/store.d.ts +1 -0
  136. package/dist/ui/settings/store.js +2 -1
  137. package/docs/architecture/code-documentation.md +77 -0
  138. package/docs/architecture/project-structure.md +1 -1
  139. package/docs/architecture/ui-reference-provenance.md +23 -3
  140. package/docs/ci-release-runbook.md +48 -4
  141. package/docs/manual-owned-ui-checkpoint.md +28 -1
  142. package/docs/manual-terminal-colour-check.md +4 -3
  143. package/package.json +7 -2
@@ -28,7 +28,7 @@ export async function readSystemClipboardContent() {
28
28
  return { kind: "image", ...image };
29
29
  }
30
30
  catch {
31
- // Fall through to text and platform readers.
31
+ // Compatibility: fall through to text and platform readers.
32
32
  }
33
33
  }
34
34
  const text = await readSystemClipboardText();
@@ -48,7 +48,7 @@ export async function readSystemClipboardImage(reader) {
48
48
  }
49
49
  }
50
50
  catch {
51
- // Older or partially available native bindings may still expose base64.
51
+ // Compatibility: older or partially available native bindings may still expose base64.
52
52
  }
53
53
  }
54
54
  if (reader.getImageBase64 !== undefined) {
@@ -59,13 +59,13 @@ export async function readSystemClipboardImage(reader) {
59
59
  });
60
60
  }
61
61
  catch {
62
- // Fall through to the caller's text path.
62
+ // Compatibility: fall through to the caller's text path.
63
63
  }
64
64
  }
65
65
  return null;
66
66
  }
67
67
  export async function readSystemClipboardText() {
68
- // Ctrl+C/Ctrl+X and Ctrl+V can arrive in adjacent input turns. Serialize the
68
+ // Concurrency: Ctrl+C/Ctrl+X and Ctrl+V can arrive in adjacent input turns. Serialize the
69
69
  // read behind our native write so paste never observes the previous value.
70
70
  await pendingClipboardWrite;
71
71
  if (process.platform === "win32" || process.platform === "darwin") {
@@ -77,7 +77,7 @@ export async function readSystemClipboardText() {
77
77
  return text;
78
78
  }
79
79
  catch {
80
- // Fall through to the platform command.
80
+ // Compatibility: fall through to the platform command.
81
81
  }
82
82
  }
83
83
  }
@@ -103,7 +103,7 @@ export function writeSystemClipboardText(text) {
103
103
  return;
104
104
  }
105
105
  catch {
106
- // The caller still emits its terminal clipboard fallback.
106
+ // Compatibility: the caller still emits its terminal clipboard fallback.
107
107
  }
108
108
  }
109
109
  };
@@ -1,9 +1,11 @@
1
1
  import type { PiTuiComponentPort, PiTuiInputListener, PiTuiOverlayHandle, PiTuiOverlayOptions, PiTuiPreInputListener, PiTuiRuntimeAdapterOptions, PiTuiRuntimeState, PiTuiScrollState, PiTuiStopOptions, PiTuiViewport } from "./contracts.js";
2
2
  export type PiTuiRuntimeErrorStage = "construction" | "start" | "input-drain" | "restoration";
3
+ /** Identifies the Pi TUI lifecycle stage that failed while preserving the original cause. */
3
4
  export declare class PiTuiRuntimeError extends Error {
4
5
  readonly stage: PiTuiRuntimeErrorStage;
5
6
  constructor(stage: PiTuiRuntimeErrorStage, cause: unknown);
6
7
  }
8
+ /** Bridges neutral presentation ports to one Pi TUI instance and owns terminal restoration on stop. */
7
9
  export declare class PiTuiRuntimeAdapter {
8
10
  #private;
9
11
  constructor(options: PiTuiRuntimeAdapterOptions);
@@ -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
- const lines = [...this.port.render(width)];
28
- const overflow = lines.findIndex(line => visibleWidth(line) > width);
29
- if (overflow >= 0) {
30
- throw new RangeError(`Pi TUI component row ${overflow} exceeds available width ${width}`);
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,8 +103,13 @@ 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;
@@ -101,10 +117,39 @@ export class PiTuiRuntimeAdapter {
101
117
  constructor(options) {
102
118
  this.#root = options.root;
103
119
  this.#terminal = options.terminal ?? new ProcessTerminal();
104
- this.#tuiTerminal = preInputTerminal(this.#terminal, data => this.#routePreInput(data));
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; });
105
148
  this.#layoutRoot = options.layoutRoot;
106
149
  this.#logDirectory = options.logDirectory;
107
- this.#rootBridge = new ComponentBridge(options.root);
150
+ this.#rootBridge = new ComponentBridge(options.root, options.inputDiagnostics === undefined
151
+ ? undefined
152
+ : phase => this.#traceRuntimePhase(phase));
108
153
  this.#bridges.set(options.root, this.#rootBridge);
109
154
  this.#mountedComponents.add(options.root);
110
155
  try {
@@ -224,10 +269,12 @@ export class PiTuiRuntimeAdapter {
224
269
  return;
225
270
  hidden = true;
226
271
  this.#overlayDisposers.delete(dispose);
272
+ this.#overlayInputCoordination.delete(bridge);
227
273
  this.#bridges.delete(component);
228
274
  component.dispose?.();
229
275
  };
230
276
  this.#overlayDisposers.add(dispose);
277
+ this.#overlayInputCoordination.set(bridge, options?.inputCoordination ?? "opaque");
231
278
  const handle = this.#tui.showOverlay(bridge, toOverlayOptions(options));
232
279
  return new OverlayHandleBridge(handle, target => this.#bridgeFor(target), dispose);
233
280
  }
@@ -355,6 +402,7 @@ export class PiTuiRuntimeAdapter {
355
402
  }
356
403
  async #stop(options) {
357
404
  this.#state = "stopping";
405
+ this.#inputCoordinator?.flush();
358
406
  let failure;
359
407
  if (options.drainInput !== false) {
360
408
  try {
@@ -398,6 +446,29 @@ export class PiTuiRuntimeAdapter {
398
446
  }
399
447
  return current;
400
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
+ }
401
472
  #mountTui(tui) {
402
473
  tui.addChild(this.#rootBridge);
403
474
  if (tui instanceof TuiAltScreen && this.#layoutRoot !== undefined) {
@@ -506,15 +577,74 @@ export class PiTuiRuntimeAdapter {
506
577
  this.#mountedComponents.clear();
507
578
  }
508
579
  #disposeComponents() {
580
+ this.#inputCoordinator?.dispose(false);
581
+ this.#inputSink = undefined;
509
582
  for (const remove of this.#inputListeners.values())
510
583
  remove();
511
584
  this.#inputListeners.clear();
512
585
  this.#preInputListeners.clear();
513
586
  for (const dispose of [...this.#overlayDisposers])
514
587
  dispose();
588
+ this.#overlayInputCoordination.clear();
515
589
  this.#disposeRoot();
516
590
  }
517
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
+ }
518
648
  function preInputTerminal(terminal, route) {
519
649
  return {
520
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
+ }
@@ -1,4 +1,6 @@
1
1
  export * from "./contracts.js";
2
+ export * from "./damage-aware-terminal.js";
3
+ export * from "./input-presentation-coordinator.js";
2
4
  export * from "./adapter.js";
3
5
  export * from "./conformance.js";
4
6
  export * from "./presentation-adapter.js";
@@ -1,4 +1,6 @@
1
1
  export * from "./contracts.js";
2
+ export * from "./damage-aware-terminal.js";
3
+ export * from "./input-presentation-coordinator.js";
2
4
  export * from "./adapter.js";
3
5
  export * from "./conformance.js";
4
6
  export * from "./presentation-adapter.js";