@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.
Files changed (171) hide show
  1. package/README.md +32 -0
  2. package/bin/cli.js +2 -2
  3. package/bin/sync-pi-tui-proxy.js +1 -1
  4. package/bin/ui.js +18 -3
  5. package/dist/cli/dispatch.js +3 -3
  6. package/dist/cli/packages.js +1 -1
  7. package/dist/cli/version-stats.js +3 -3
  8. package/dist/composition/owned-ui.d.ts +4 -1
  9. package/dist/composition/owned-ui.js +9 -6
  10. package/dist/composition/settings-route-host.js +6 -4
  11. package/dist/contracts/agent-engine/capability-ports.d.ts +2 -2
  12. package/dist/contracts/agent-engine/domain-validation.js +22 -4
  13. package/dist/contracts/agent-engine/domain.d.ts +17 -0
  14. package/dist/contracts/owned-ui/model.d.ts +8 -0
  15. package/dist/contracts/owned-ui/validation.js +11 -0
  16. package/dist/features/launch/development-launch.js +1 -1
  17. package/dist/features/owned-ui/customization.d.ts +2 -0
  18. package/dist/features/owned-ui/customization.js +2 -0
  19. package/dist/features/owned-ui/diagnostics.d.ts +1 -0
  20. package/dist/features/owned-ui/diagnostics.js +1 -0
  21. package/dist/features/owned-ui/index.d.ts +1 -0
  22. package/dist/features/owned-ui/index.js +1 -0
  23. package/dist/features/owned-ui/project-trust-prompt.d.ts +25 -0
  24. package/dist/features/owned-ui/project-trust-prompt.js +149 -0
  25. package/dist/features/owned-ui/settings-app.js +69 -44
  26. package/dist/features/workspace/reducer.d.ts +1 -0
  27. package/dist/features/workspace/reducer.js +1 -0
  28. package/dist/features/workspace/router.d.ts +1 -0
  29. package/dist/features/workspace/router.js +1 -0
  30. package/dist/features/workspace/store.d.ts +1 -0
  31. package/dist/features/workspace/store.js +1 -0
  32. package/dist/foundation/launch-guardian/main.js +1 -1
  33. package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
  34. package/dist/foundation/native-host-protocol/codec.js +2 -0
  35. package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
  36. package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
  37. package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
  38. package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
  39. package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
  40. package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
  41. package/dist/foundation/protocol/client.d.ts +1 -0
  42. package/dist/foundation/protocol/client.js +1 -0
  43. package/dist/foundation/protocol/messages.d.ts +1 -0
  44. package/dist/foundation/protocol/messages.js +1 -0
  45. package/dist/foundation/release/bootstrap.js +6 -6
  46. package/dist/foundation/release/cohort-state.d.ts +1 -0
  47. package/dist/foundation/release/cohort-state.js +1 -0
  48. package/dist/foundation/release/release-gc.js +1 -1
  49. package/dist/foundation/release/release.js +1 -1
  50. package/dist/foundation/release/update-transaction.d.ts +1 -0
  51. package/dist/foundation/release/update-transaction.js +1 -0
  52. package/dist/foundation/release/update.js +12 -12
  53. package/dist/foundation/storage/control-store.d.ts +1 -0
  54. package/dist/foundation/storage/control-store.js +1 -0
  55. package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
  56. package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
  57. package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
  58. package/dist/foundation/structured-agent-runtime/commands.js +1 -0
  59. package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
  60. package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
  61. package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
  62. package/dist/foundation/structured-agent-runtime/state.js +1 -0
  63. package/dist/foundation/supervision/main.js +1 -1
  64. package/dist/foundation/supervision/server.d.ts +1 -0
  65. package/dist/foundation/supervision/server.js +4 -6
  66. package/dist/integrations/pi/components/conformance.d.ts +1 -0
  67. package/dist/integrations/pi/components/conformance.js +1 -0
  68. package/dist/integrations/pi/components/shell-components.d.ts +1 -0
  69. package/dist/integrations/pi/components/shell-components.js +1 -0
  70. package/dist/integrations/pi/components/shell-editor-autocomplete.js +10 -0
  71. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  72. package/dist/integrations/pi/components/shell-footer-status.js +19 -13
  73. package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
  74. package/dist/integrations/pi/components/shell-presenters-info.js +78 -0
  75. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +3 -36
  76. package/dist/integrations/pi/components/shell-presenters-transcript.js +88 -128
  77. package/dist/integrations/pi/components/shell-shared-facade.d.ts +11 -1
  78. package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
  79. package/dist/integrations/pi/engine/adapter.d.ts +22 -4
  80. package/dist/integrations/pi/engine/adapter.js +220 -70
  81. package/dist/integrations/pi/engine/conformance.d.ts +2 -0
  82. package/dist/integrations/pi/engine/conformance.js +2 -0
  83. package/dist/integrations/pi/engine/http-dispatcher.d.ts +4 -0
  84. package/dist/integrations/pi/engine/http-dispatcher.js +25 -0
  85. package/dist/integrations/pi/engine/index.d.ts +3 -0
  86. package/dist/integrations/pi/engine/index.js +3 -0
  87. package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
  88. package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
  89. package/dist/integrations/pi/engine/package-integration.js +1 -1
  90. package/dist/integrations/pi/engine/project-trust-preflight.d.ts +22 -0
  91. package/dist/integrations/pi/engine/project-trust-preflight.js +50 -0
  92. package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
  93. package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
  94. package/dist/integrations/pi/engine/runtime-integration.d.ts +16 -1
  95. package/dist/integrations/pi/engine/runtime-integration.js +34 -4
  96. package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
  97. package/dist/integrations/pi/engine/session-integration.js +2 -1
  98. package/dist/integrations/pi/engine/settings-effects.d.ts +66 -0
  99. package/dist/integrations/pi/engine/settings-effects.js +249 -0
  100. package/dist/integrations/pi/engine/settings-integration.d.ts +14 -26
  101. package/dist/integrations/pi/engine/settings-integration.js +102 -108
  102. package/dist/integrations/pi/engine/workflow-controllers.d.ts +2 -1
  103. package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
  104. package/dist/integrations/pi/session-ui/clipboard-image.d.ts +11 -0
  105. package/dist/integrations/pi/session-ui/clipboard-image.js +29 -0
  106. package/dist/integrations/pi/session-ui/index.d.ts +1 -0
  107. package/dist/integrations/pi/session-ui/index.js +1 -0
  108. package/dist/integrations/pi/session-ui/prompt-chips.js +6 -2
  109. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +39 -4
  110. package/dist/integrations/pi/session-ui/session-shell-root.js +215 -80
  111. package/dist/integrations/pi/session-ui/session-shell.d.ts +10 -1
  112. package/dist/integrations/pi/session-ui/session-shell.js +256 -45
  113. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  114. package/dist/integrations/pi/session-ui/session-viewport-controller.js +76 -11
  115. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
  116. package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
  117. package/dist/integrations/pi/session-ui/system-clipboard.d.ts +9 -0
  118. package/dist/integrations/pi/session-ui/system-clipboard.js +38 -9
  119. package/dist/integrations/pi/tui-runtime/adapter.d.ts +6 -0
  120. package/dist/integrations/pi/tui-runtime/adapter.js +166 -8
  121. package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
  122. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
  123. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
  124. package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
  125. package/dist/integrations/pi/tui-runtime/index.js +2 -0
  126. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
  127. package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
  128. package/dist/native/darwin-arm64/manifest.json +1 -1
  129. package/dist/native/linux-x64/manifest.json +1 -1
  130. package/dist/native/win32-x64/manifest.json +2 -2
  131. package/dist/native/win32-x64/process-guardian.exe +0 -0
  132. package/dist/ui/apps/host.js +5 -9
  133. package/dist/ui/apps/registry.d.ts +1 -0
  134. package/dist/ui/apps/registry.js +1 -0
  135. package/dist/ui/components/dialog-panel.js +5 -3
  136. package/dist/ui/components/frame.d.ts +1 -0
  137. package/dist/ui/components/frame.js +1 -0
  138. package/dist/ui/components/index.d.ts +1 -0
  139. package/dist/ui/components/index.js +1 -0
  140. package/dist/ui/components/line-input.d.ts +1 -6
  141. package/dist/ui/components/line-input.js +4 -9
  142. package/dist/ui/components/list-view.d.ts +0 -6
  143. package/dist/ui/components/list-view.js +14 -7
  144. package/dist/ui/components/progress-status.d.ts +2 -0
  145. package/dist/ui/components/progress-status.js +4 -0
  146. package/dist/ui/components/scrollbar.js +1 -1
  147. package/dist/ui/components/shortcuts.d.ts +1 -0
  148. package/dist/ui/components/shortcuts.js +1 -0
  149. package/dist/ui/components/spans.d.ts +2 -5
  150. package/dist/ui/components/spans.js +28 -8
  151. package/dist/ui/components/surface.d.ts +0 -5
  152. package/dist/ui/components/surface.js +1 -5
  153. package/dist/ui/components/text-selection.d.ts +10 -1
  154. package/dist/ui/components/text-selection.js +96 -29
  155. package/dist/ui/components/text.js +3 -3
  156. package/dist/ui/components/transcript-viewport.d.ts +42 -0
  157. package/dist/ui/components/transcript-viewport.js +287 -39
  158. package/dist/ui/settings/sections.d.ts +6 -19
  159. package/dist/ui/settings/sections.js +17 -15
  160. package/dist/ui/settings/session.d.ts +8 -18
  161. package/dist/ui/settings/session.js +60 -52
  162. package/dist/ui/settings/store.d.ts +1 -0
  163. package/dist/ui/settings/store.js +2 -1
  164. package/docs/architecture/code-documentation.md +77 -0
  165. package/docs/architecture/project-structure.md +15 -1
  166. package/docs/architecture/ui-reference-provenance.md +23 -3
  167. package/docs/ci-release-runbook.md +98 -7
  168. package/docs/manual-owned-ui-checkpoint.md +28 -1
  169. package/docs/manual-terminal-colour-check.md +4 -3
  170. package/docs/repository-governance-live-acceptance.md +77 -0
  171. 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
- 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,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.#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; });
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
+ }
@@ -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";