@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
@@ -1,9 +1,10 @@
1
- import { backgroundSgrSpan, composeSubmittedPromptRows, displayWidth, formatSubmittedPromptTime, heldNativeHyperlinkStyle, hyperlinkSgrSpan, nativeHyperlinkStyle, overlaySpan, submittedPromptLayout, stripAnsi, } from "../../../ui/components/index.js";
1
+ import { backgroundSgrSpan, composeSubmittedPromptRows, displayWidth, formatSubmittedPromptTime, heldNativeHyperlinkStyle, hyperlinkSgrSpan, nativeHyperlinkStyle, overlaySpan, progressStatusText, submittedPromptLayout, stripAnsi, } from "../../../ui/components/index.js";
2
2
  import { PINNED_PI_HIDDEN_COMMAND_NAMES, PINNED_PI_WORKFLOW_COMMAND_NAMES, } from "../engine/index.js";
3
3
  import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, createPiShellCollapsedChangelog, createPiShellDaxnuts, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellEditor, createPiShellExtensionSelector, createPiShellFooter, createPiShellHeader, createPiShellHotkeys, createPiShellLoadedResources, createPiShellLoginDialog, createPiShellModelSelector, createPiShellOperationLoader, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionInfo, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellStatus, createPiShellTranscriptComponent, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, onPiThemeChange, piShellVisibleWidth, piTheme, renderPiShellPackageUpdateNotice, renderPiShellStartupDiagnostic, renderPiShellStatusText, renderPiShellTranscriptBlock, } from "../components/index.js";
4
- import { PiTuiRuntimeAdapter, } from "../tui-runtime/index.js";
4
+ import { PiTuiRuntimeAdapter, classifyPiTuiInput, } from "../tui-runtime/index.js";
5
5
  import { PromptChipStore } from "./prompt-chips.js";
6
6
  import { SessionViewportController } from "./session-viewport-controller.js";
7
+ /** Composes backend session state into the owned transcript, viewport, editor, and dock presentation. */
7
8
  export class OwnedUiSessionShellRoot {
8
9
  editor;
9
10
  header;
@@ -12,7 +13,7 @@ export class OwnedUiSessionShellRoot {
12
13
  #transcript = new Map();
13
14
  #blocksById = new Map();
14
15
  #renderedRows = new Map();
15
- /** Flattened transcript rows reused by wheel, rail, jump, and selection frames. */
16
+ // Performance: document layouts are shared by wheel, rail, jump, and selection frames.
16
17
  #documentLayouts = new Map();
17
18
  #themeUnsubscribe;
18
19
  #transcriptOrder = [];
@@ -26,6 +27,8 @@ export class OwnedUiSessionShellRoot {
26
27
  #customViewport;
27
28
  #submittedPromptComposer;
28
29
  #viewportController;
30
+ #viewportTheme;
31
+ #onViewportFrame;
29
32
  #componentRuntime;
30
33
  #toolsExpanded = false;
31
34
  #thinkingVisible = true;
@@ -38,6 +41,13 @@ export class OwnedUiSessionShellRoot {
38
41
  #workflowStatusMessages = new Map();
39
42
  #lastWorkflowStatusId;
40
43
  #inputSurface;
44
+ #inputSurfaceCoordination = "editor";
45
+ #dockInputReuseEnabled;
46
+ #dockInputCandidate = false;
47
+ #visibleViewportSnapshot;
48
+ #fullViewportCompositions = 0;
49
+ #dockOnlyViewportCompositions = 0;
50
+ #transcriptBlockRenders = 0;
41
51
  #extensionHeader = null;
42
52
  #extensionFooter = null;
43
53
  #extensionWidgets = new Map();
@@ -61,9 +71,11 @@ export class OwnedUiSessionShellRoot {
61
71
  this.#extensionRenderers = extensionRenderers;
62
72
  this.#imageAssets = imageAssets;
63
73
  this.#componentRuntime = handlers;
74
+ this.#onViewportFrame = handlers.onViewportFrame;
75
+ this.#dockInputReuseEnabled = handlers.enableDockInputReuse ?? true;
64
76
  this.header = createPiShellHeader(startup);
65
77
  this.resources = createPiShellLoadedResources(startup.resources ?? [], startup.expanded ?? false);
66
- this.#status = createPiShellStatus(view, handlers);
78
+ this.#status = createPiShellStatus(view, progressStatusText, handlers);
67
79
  this.#footer = createPiShellFooter(this.#viewWithExtensionStatuses(view), cwd);
68
80
  this.#queued = createPiQueuedInputStatus(view.editor.queuedSubmissions, this.#customViewport ? "custom-viewport" : "pinned");
69
81
  this.editor = createPiShellEditor({
@@ -78,7 +90,7 @@ export class OwnedUiSessionShellRoot {
78
90
  if (ranges.length === 0)
79
91
  return row;
80
92
  const linkResetAndTail = `\u001b]8;;\u001b\\\u001b[24m${" ".repeat(Math.max(0, width - piShellVisibleWidth(row)))}`;
81
- // VS15 is zero-column and default-ignorable. It breaks Windows Terminal's
93
+ // Platform: VS15 is zero-column and default-ignorable. It breaks Windows Terminal's
82
94
  // plain-text URL detector only in the held-button paint; semantic text stays exact.
83
95
  const paintRow = this.#viewportController.editorPointerSelecting
84
96
  ? row.replaceAll("https://", "https:\uFE0E//").replaceAll("http://", "http:\uFE0E//")
@@ -86,7 +98,7 @@ export class OwnedUiSessionShellRoot {
86
98
  const decorated = this.#viewportController.editorPointerSelecting
87
99
  ? ranges.reduce((result, range) => backgroundSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), "\u001b[4:4m", "\u001b[24m", piShellVisibleWidth), paintRow)
88
100
  : ranges.reduce((result, range) => hyperlinkSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), range.target, piShellVisibleWidth), row);
89
- // Windows Terminal can retain stale native dotted-link cells when a mutable
101
+ // Platform: Windows Terminal can retain stale native dotted-link cells when a mutable
90
102
  // prompt replaces a longer URL. Explicit non-link spaces overwrite that tail.
91
103
  return `${decorated}${linkResetAndTail}`;
92
104
  },
@@ -101,6 +113,16 @@ export class OwnedUiSessionShellRoot {
101
113
  requestRender: force => this.#componentRuntime.requestRender(force),
102
114
  hasEditorLinks: () => this.#promptChips.hyperlinkRanges(this.editor.getText()).length > 0,
103
115
  });
116
+ // Performance: stable painter identities let the neutral viewport retain row-level
117
+ // selection variants while each callback still resolves the live Pi theme.
118
+ this.#viewportTheme = {
119
+ track: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("dim", text)}`,
120
+ thumb: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("accent", text)}`,
121
+ sticky: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", withoutTerminalBackground(text))),
122
+ quietSticky: text => `\u001b[2m${text.replace(/\u001b\[(?:0|22)m/g, "$&\u001b[2m")}\u001b[22m`,
123
+ bottomControl: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", text)),
124
+ selection: (line, from, to) => backgroundSgrSpan(line, from, to, "\u001b[48;2;38;79;120m", "\u001b[49m"),
125
+ };
104
126
  this.editor.setThinkingLevel(view.thinkingLevel);
105
127
  this.#inputSurface = this.editor;
106
128
  for (const block of view.transcript) {
@@ -108,7 +130,7 @@ export class OwnedUiSessionShellRoot {
108
130
  this.editor.addToHistory(block.text);
109
131
  }
110
132
  this.#syncTranscript(view.transcript);
111
- // Colours come from the active theme, so rendered rows outlive their revision only
133
+ // Invariant: colours come from the active theme, so rendered rows outlive their revision only
112
134
  // until the theme under them changes.
113
135
  this.#themeUnsubscribe = onPiThemeChange(() => {
114
136
  this.#renderedRows.clear();
@@ -190,7 +212,7 @@ export class OwnedUiSessionShellRoot {
190
212
  else if (component.revision !== block.revision) {
191
213
  component.update(block);
192
214
  }
193
- // One chunk touches one block, and the updated component tracks its own dirtiness.
215
+ // Performance: one chunk touches one block, and the updated component tracks its own dirtiness.
194
216
  // Invalidating the whole shell here would re-wrap the entire transcript per chunk.
195
217
  this.#renderedRows.delete(block.id);
196
218
  this.#documentLayouts.clear();
@@ -202,62 +224,79 @@ export class OwnedUiSessionShellRoot {
202
224
  }
203
225
  const height = Math.max(0, this.#componentRuntime.getRows());
204
226
  const dock = this.#renderDockLayout(width);
205
- const availableWithoutTransient = Math.max(0, height - Math.min(height, dock.rowsWithoutTransient.length));
206
- // Ordinary transcript content uses the full terminal width. The rail is an
227
+ // Invariant: ordinary transcript content uses the full terminal width. The rail is an
207
228
  // overlay, not a lost wrapping cell. Submitted prompts alone retain the
208
229
  // intentional final gutter after their right-aligned timestamp.
209
230
  const documentWidth = width;
210
- let document = this.#renderDocumentLayout(documentWidth);
211
- // Keep queued steering/follow-up messages and Working together in Pi's
212
- // semantic order while content fits. Once the transcript overflows, move
213
- // the complete transient group into its tail so every row naturally
214
- // scrolls away and the detached viewport can use the full space above the
215
- // stable editor/footer dock.
216
- const pinTransient = document.rows.length + dock.transientRows.length <= availableWithoutTransient;
217
- const dockRows = pinTransient ? dock.rows : dock.rowsWithoutTransient;
231
+ const document = this.#renderDocumentLayout(documentWidth);
232
+ // Invariant: prompt-adjacent transient surfaces always belong to the dock.
233
+ // Changing from fitting to overflowing content reallocates transcript rows
234
+ // only; it never moves queued or Working rows into selectable history.
235
+ const dockRows = dock.rows;
218
236
  const selectableDocumentRowCount = document.rows.length;
219
- if (!pinTransient)
220
- document = { ...document, rows: [...document.rows, ...dock.transientRows] };
221
237
  const dockStartRow = height - dockRows.length + 1;
222
- const editorOffset = pinTransient ? dock.editorOffsetWithTransient : dock.editorOffsetWithoutTransient;
238
+ const editorOffset = dock.editorOffset;
223
239
  this.#viewportController.setEditorPointerFrame(this.usesDefaultInputSurface()
224
240
  ? {
225
241
  rowStart: dockStartRow + editorOffset,
226
242
  rowEnd: dockStartRow + editorOffset + dock.inputRows - 1,
227
243
  }
228
244
  : undefined);
229
- return this.#viewportController.compose({
230
- documentRows: document.rows,
231
- ...(this.#viewportController.transcriptPointerSelecting
232
- ? { paintDocumentRow: heldNativeHyperlinkStyle }
233
- : {}),
234
- // Overflowing queued and Working rows scroll with the transcript but
235
- // remain status chrome: selection and copying stop at the real document tail.
236
- selectableDocumentRowCount,
237
- dockRows,
238
- promptAnchors: document.promptAnchors,
245
+ const snapshot = this.#visibleViewportSnapshot;
246
+ const dockInputCandidate = this.#dockInputCandidate;
247
+ this.#dockInputCandidate = false;
248
+ let frame = dockInputCandidate
249
+ && snapshot !== undefined
250
+ && snapshot.width === width
251
+ && snapshot.height === height
252
+ && snapshot.documentRows === document.rows
253
+ && snapshot.promptAnchors === document.promptAnchors
254
+ && snapshot.dockLength === dockRows.length
255
+ && snapshot.inputSurface === this.#inputSurface
256
+ && snapshot.viewportRevision === this.#viewportController.presentationRevision
257
+ && snapshot.selectionRevision === this.#viewportController.selectionRevision
258
+ ? this.#viewportController.composeDockOnly(dockRows, width, height)
259
+ : null;
260
+ if (frame === null) {
261
+ frame = this.#viewportController.compose({
262
+ documentRows: document.rows,
263
+ ...(this.#viewportController.transcriptPointerSelecting
264
+ ? { paintDocumentRow: heldNativeHyperlinkStyle }
265
+ : {}),
266
+ // Invariant: selection and copying stop at the real document tail; queued
267
+ // and Working rows remain dock-owned status chrome at every fit boundary.
268
+ selectableDocumentRowCount,
269
+ dockRows,
270
+ promptAnchors: document.promptAnchors,
271
+ width,
272
+ height,
273
+ // Invariant: the control belongs immediately above the complete dock,
274
+ // never on top of a queued or Working row.
275
+ bottomControlRow: Math.max(0, height - Math.min(height, dockRows.length) - 1),
276
+ theme: this.#viewportTheme,
277
+ });
278
+ this.#fullViewportCompositions += 1;
279
+ }
280
+ else
281
+ this.#dockOnlyViewportCompositions += 1;
282
+ this.#visibleViewportSnapshot = {
239
283
  width,
240
284
  height,
241
- // Anchor above the stable editor/widget/footer group. Queued input,
242
- // working states, and transient notifications may grow above it without
243
- // moving the floating control up and down the terminal.
244
- bottomControlRow: Math.max(0, height - Math.min(height, dock.stableBottomRows) - 1),
245
- theme: {
246
- // Match the v2 rail: a continuously dim track and an accent thumb at
247
- // rest, thickening on hover/drag through the presentation glyph. Reset
248
- // inherited text decorations so dim transcript rows cannot dull the
249
- // thumb; the row background deliberately remains intact.
250
- track: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("dim", text)}`,
251
- thumb: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("accent", text)}`,
252
- sticky: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", withoutTerminalBackground(text))),
253
- quietSticky: text => `\u001b[2m${text.replace(/\u001b\[(?:0|22)m/g, "$&\u001b[2m")}\u001b[22m`,
254
- bottomControl: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", text)),
255
- // Selection paints only the familiar dark-blue background. Source
256
- // foreground roles (including links, warnings, muted text, and bold
257
- // intensity) remain exactly as rendered instead of being inverted.
258
- selection: (line, from, to) => backgroundSgrSpan(line, from, to, "\u001b[48;2;38;79;120m", "\u001b[49m"),
259
- },
260
- }).rows;
285
+ documentRows: document.rows,
286
+ promptAnchors: document.promptAnchors,
287
+ dockLength: dockRows.length,
288
+ inputSurface: this.#inputSurface,
289
+ viewportRevision: this.#viewportController.presentationRevision,
290
+ selectionRevision: this.#viewportController.selectionRevision,
291
+ };
292
+ this.#onViewportFrame?.(frame);
293
+ return frame.rows;
294
+ }
295
+ viewportFrameDescriptor() {
296
+ return this.#viewportController.frame?.descriptor ?? null;
297
+ }
298
+ hasActiveSelection() {
299
+ return this.#viewportController.hasSelection || this.editor.hasSelection();
261
300
  }
262
301
  setViewportConfig(config) {
263
302
  if (this.#viewportController.setConfig(config))
@@ -292,11 +331,7 @@ export class OwnedUiSessionShellRoot {
292
331
  const rowsWithoutTransient = [...aboveWidgets, ...input, ...belowWidgets, ...footer];
293
332
  return {
294
333
  rows: [...transientRows, ...rowsWithoutTransient],
295
- rowsWithoutTransient,
296
- transientRows,
297
- stableBottomRows: rowsWithoutTransient.length,
298
- editorOffsetWithTransient: transientRows.length + aboveWidgets.length,
299
- editorOffsetWithoutTransient: aboveWidgets.length,
334
+ editorOffset: transientRows.length + aboveWidgets.length,
300
335
  inputRows: input.length,
301
336
  };
302
337
  }
@@ -353,7 +388,7 @@ export class OwnedUiSessionShellRoot {
353
388
  return this.#renderDocumentLayout(width).rows;
354
389
  }
355
390
  #renderDocumentLayout(width) {
356
- // Extension headers may animate independently of transcript revisions, so
391
+ // Performance: extension headers may animate independently of transcript revisions, so
357
392
  // only the stable built-in document participates in this frame cache.
358
393
  const cached = this.#extensionHeader === null ? this.#documentLayouts.get(width) : undefined;
359
394
  if (cached !== undefined)
@@ -387,7 +422,7 @@ export class OwnedUiSessionShellRoot {
387
422
  : width;
388
423
  const blockRows = this.#blockRows(id, block, blockWidth);
389
424
  if (block?.kind === "user") {
390
- // The first natural prompt gets one breathing row at the document top.
425
+ // Compatibility: the first natural prompt gets one breathing row at the document top.
391
426
  // Once scrolling advances, the prompt itself reaches row zero and then
392
427
  // becomes sticky there, so the spacer is never pinned with it.
393
428
  if (this.#customViewport && index === 0)
@@ -430,23 +465,19 @@ export class OwnedUiSessionShellRoot {
430
465
  const layout = { rows: Object.freeze(rows), promptAnchors: Object.freeze(promptAnchors) };
431
466
  if (this.#extensionHeader === null) {
432
467
  this.#documentLayouts.set(width, layout);
433
- // The custom viewport commonly probes full width and reserved-rail width.
468
+ // Performance: the custom viewport commonly probes full width and reserved-rail width.
434
469
  while (this.#documentLayouts.size > 2)
435
470
  this.#documentLayouts.delete(this.#documentLayouts.keys().next().value);
436
471
  }
437
472
  return layout;
438
473
  }
439
- /**
440
- * Rows for one transcript block. A finalized block renders once for a given revision
441
- * and width and is reused after that, so a frame costs what changed rather than what
442
- * the session has accumulated. A live block, and anything the shell drives itself, is
443
- * rendered every time because its content is still moving.
444
- */
474
+ // Performance: finalized blocks cache by revision and width; live blocks always render.
445
475
  #blockRows(id, block, width) {
446
476
  const component = this.#transcript.get(id);
447
477
  if (component === undefined)
448
478
  return [];
449
479
  const render = () => {
480
+ this.#transcriptBlockRenders += 1;
450
481
  const rows = component.render(width);
451
482
  if (!this.#customViewport || block?.kind === "user")
452
483
  return rows;
@@ -464,7 +495,7 @@ export class OwnedUiSessionShellRoot {
464
495
  this.#renderedRows.set(id, byWidth);
465
496
  }
466
497
  byWidth.set(width, { revision: block.revision, rows });
467
- // Bare A1 probes full width before reserving the overflowing rail column;
498
+ // Invariant: bare A1 probes full width before reserving the overflowing rail column;
468
499
  // retain both widths without turning resize history into an unbounded cache.
469
500
  while (byWidth.size > 2)
470
501
  byWidth.delete(byWidth.keys().next().value);
@@ -481,7 +512,7 @@ export class OwnedUiSessionShellRoot {
481
512
  continue;
482
513
  if (rows.length > 0 && block.kind === "user")
483
514
  rows.push("");
484
- rows.push(...this.#blockRows(id, block, width).map(row => stripAnsi(row).trimEnd()));
515
+ rows.push(...this.#blockRows(id, block, width).map(sanitizeExitTranscriptRow));
485
516
  }
486
517
  while (rows.at(-1) === "")
487
518
  rows.pop();
@@ -653,7 +684,7 @@ export class OwnedUiSessionShellRoot {
653
684
  this.#extensionWorkingMessage = undefined;
654
685
  this.#status.setWorkingOverride(undefined);
655
686
  this.#footer.update(this.#viewWithExtensionStatuses(this.#view));
656
- // An extension renderer may have drawn transcript blocks that are now unrendered by it.
687
+ // Invariant: an extension renderer may have drawn transcript blocks that are now unrendered by it.
657
688
  this.#renderedRows.clear();
658
689
  this.#documentLayouts.clear();
659
690
  this.invalidate();
@@ -678,19 +709,41 @@ export class OwnedUiSessionShellRoot {
678
709
  usesDefaultInputSurface() {
679
710
  return this.#inputSurface === this.editor;
680
711
  }
681
- setInputSurface(component, disposePrevious = true) {
712
+ inputCoordinationSurface() {
713
+ return this.#inputSurfaceCoordination;
714
+ }
715
+ viewportCompositionEvidence() {
716
+ return { full: this.#fullViewportCompositions, dockOnly: this.#dockOnlyViewportCompositions };
717
+ }
718
+ transcriptRenderCount() {
719
+ return this.#transcriptBlockRenders;
720
+ }
721
+ setInputSurface(component, disposePrevious = true, coordination = "owned") {
682
722
  const next = component ?? this.editor;
683
- if (next === this.#inputSurface)
723
+ const nextCoordination = next === this.editor ? "editor" : coordination;
724
+ if (next === this.#inputSurface) {
725
+ this.#inputSurfaceCoordination = nextCoordination;
684
726
  return;
727
+ }
728
+ this.#dockInputCandidate = false;
685
729
  this.#inputSurface.setFocused?.(false);
686
730
  if (disposePrevious && this.#inputSurface !== this.editor)
687
731
  this.#inputSurface.dispose?.();
688
732
  this.#inputSurface = next;
733
+ this.#inputSurfaceCoordination = nextCoordination;
689
734
  this.#inputSurface.setFocused?.(true);
690
735
  this.invalidate();
691
736
  }
692
737
  handleInput(data) {
693
- this.#inputSurface.handleInput?.(data);
738
+ this.#dockInputCandidate = this.#customViewport && this.#dockInputReuseEnabled
739
+ && classifyPiTuiInput(data, this.#inputSurfaceCoordination) === "safe";
740
+ try {
741
+ this.#inputSurface.handleInput?.(data);
742
+ }
743
+ catch (error) {
744
+ this.#dockInputCandidate = false;
745
+ throw error;
746
+ }
694
747
  }
695
748
  invalidate() {
696
749
  this.header.invalidate();
@@ -718,6 +771,8 @@ export class OwnedUiSessionShellRoot {
718
771
  this.#transcript.clear();
719
772
  this.#renderedRows.clear();
720
773
  this.#documentLayouts.clear();
774
+ this.#visibleViewportSnapshot = undefined;
775
+ this.#dockInputCandidate = false;
721
776
  if (this.#inputSurface !== this.editor)
722
777
  this.#inputSurface.dispose?.();
723
778
  this.#extensionHeader?.dispose?.();
@@ -849,13 +904,19 @@ export class OwnedUiSessionShellRoot {
849
904
  this.resources.setExpanded(expanded);
850
905
  for (const component of this.#transcript.values())
851
906
  component.setExpanded(expanded);
852
- // Expansion changes what a block draws without changing its revision.
907
+ // Invariant: expansion changes what a block draws without changing its revision.
853
908
  this.#renderedRows.clear();
854
909
  this.#documentLayouts.clear();
855
910
  this.invalidate();
856
911
  }
857
912
  }
858
913
  const SCROLLBAR_CELL_RESET = "\u001b[22;23;24;25;27;28;29;39;54;55m";
914
+ const KITTY_IMAGE_CONTROL = /\u001b_G[\s\S]*?\u001b\\/g;
915
+ const ITERM_IMAGE_CONTROL = /\u001b]1337;File=[^\u0007]*(?:\u0007|\u001b\\)/g;
916
+ /** Keep semantic SGR/OSC styling while excluding non-replayable image payloads. */
917
+ function sanitizeExitTranscriptRow(row) {
918
+ return row.replace(KITTY_IMAGE_CONTROL, "").replace(ITERM_IMAGE_CONTROL, "");
919
+ }
859
920
  const TERMINAL_BACKGROUND = /\u001b\[(?:4[0-9]|10[0-7]|48(?:[;:][0-9;:]*)?)m/g;
860
921
  /** Web URLs use link blue; file targets retain A1's cyan interactive accent. */
861
922
  function nativeTranscriptLinkColor(text, target) {
@@ -1,9 +1,10 @@
1
1
  import type { OwnedUiSessionViewModel, OwnedUiThinkingLevel } from "../../../contracts/owned-ui/index.js";
2
2
  import { type AdapterCommandResult, type PiWorkflowRequest } from "../engine/index.js";
3
3
  import { type PiShellSelectorOption } from "../components/index.js";
4
- import { PiTuiRuntimeAdapter, type PiTuiOverlayHandle } from "../tui-runtime/index.js";
4
+ import { PiTuiRuntimeAdapter, type PiTuiDamageDecision, type PiTuiOverlayHandle } from "../tui-runtime/index.js";
5
5
  import { OwnedUiSessionShellRoot, type OwnedUiBackendPort, type OwnedUiSessionShellOptions } from "./session-shell-root.js";
6
6
  export { OwnedUiSessionShellRoot, type OwnedUiSessionShellOptions } from "./session-shell-root.js";
7
+ /** Coordinates backend, owned presentation, and Pi TUI lifecycles for one interactive session. */
7
8
  export declare class OwnedUiSessionShell {
8
9
  #private;
9
10
  readonly backend: OwnedUiBackendPort;
@@ -11,6 +12,7 @@ export declare class OwnedUiSessionShell {
11
12
  readonly runtime: PiTuiRuntimeAdapter;
12
13
  constructor(options: OwnedUiSessionShellOptions);
13
14
  view(): OwnedUiSessionViewModel;
15
+ damagePresentationDecision(): PiTuiDamageDecision | null;
14
16
  start(): void;
15
17
  onView(listener: (view: OwnedUiSessionViewModel) => void): () => void;
16
18
  waitUntilStopped(): Promise<void>;
@@ -44,5 +46,10 @@ export declare class OwnedUiSessionShell {
44
46
  showScopedModelsSelector(): void;
45
47
  dispose(): Promise<void>;
46
48
  }
47
- export declare function formatSessionResumeCommand(sessionFile: string): string;
49
+ export interface SessionResumeCommandMetadata {
50
+ readonly sessionId: string;
51
+ readonly sessionDir: string;
52
+ readonly usesDefaultSessionDir: boolean;
53
+ }
54
+ export declare function formatSessionResumeCommand(metadata: SessionResumeCommandMetadata): string;
48
55
  export declare function quoteCommandArgument(value: string): string;