@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,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
- import { createPiExtensionUiBridge, createPiQueuedInputStatus, createPiShellArmin, createPiShellAuthProviderSelector, createPiShellChangelog, 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";
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, 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 = [];
@@ -21,18 +22,32 @@ export class OwnedUiSessionShellRoot {
21
22
  #footer;
22
23
  #queued;
23
24
  #extensionRenderers;
25
+ #imageAssets;
24
26
  #promptChips = new PromptChipStore();
25
27
  #customViewport;
26
28
  #submittedPromptComposer;
27
29
  #viewportController;
30
+ #viewportTheme;
31
+ #onViewportFrame;
28
32
  #componentRuntime;
29
33
  #toolsExpanded = false;
30
34
  #thinkingVisible = true;
35
+ #mermaidRenderingMode = "off";
36
+ #showImages = true;
37
+ #imageWidthCells = 80;
38
+ #outputPad = 1;
31
39
  #workflowTranscriptSequence = 0;
32
40
  #workflowStatusAnchors = new Map();
33
41
  #workflowStatusMessages = new Map();
34
42
  #lastWorkflowStatusId;
35
43
  #inputSurface;
44
+ #inputSurfaceCoordination = "editor";
45
+ #dockInputReuseEnabled;
46
+ #dockInputCandidate = false;
47
+ #visibleViewportSnapshot;
48
+ #fullViewportCompositions = 0;
49
+ #dockOnlyViewportCompositions = 0;
50
+ #transcriptBlockRenders = 0;
36
51
  #extensionHeader = null;
37
52
  #extensionFooter = null;
38
53
  #extensionWidgets = new Map();
@@ -42,7 +57,7 @@ export class OwnedUiSessionShellRoot {
42
57
  constructor(view, cwd, handlers, startup = {}, agentDir, extensionRenderers = {
43
58
  getMessageRenderer: () => undefined,
44
59
  getToolDefinition: () => undefined,
45
- }, sessionLayout = "pinned") {
60
+ }, sessionLayout = "pinned", imageAssets) {
46
61
  this.#view = view;
47
62
  this.#customViewport = sessionLayout === "custom-viewport";
48
63
  this.#submittedPromptComposer = this.#customViewport
@@ -54,10 +69,13 @@ export class OwnedUiSessionShellRoot {
54
69
  : undefined;
55
70
  this.#cwd = cwd;
56
71
  this.#extensionRenderers = extensionRenderers;
72
+ this.#imageAssets = imageAssets;
57
73
  this.#componentRuntime = handlers;
74
+ this.#onViewportFrame = handlers.onViewportFrame;
75
+ this.#dockInputReuseEnabled = handlers.enableDockInputReuse ?? true;
58
76
  this.header = createPiShellHeader(startup);
59
77
  this.resources = createPiShellLoadedResources(startup.resources ?? [], startup.expanded ?? false);
60
- this.#status = createPiShellStatus(view, handlers);
78
+ this.#status = createPiShellStatus(view, progressStatusText, handlers);
61
79
  this.#footer = createPiShellFooter(this.#viewWithExtensionStatuses(view), cwd);
62
80
  this.#queued = createPiQueuedInputStatus(view.editor.queuedSubmissions, this.#customViewport ? "custom-viewport" : "pinned");
63
81
  this.editor = createPiShellEditor({
@@ -72,7 +90,7 @@ export class OwnedUiSessionShellRoot {
72
90
  if (ranges.length === 0)
73
91
  return row;
74
92
  const linkResetAndTail = `\u001b]8;;\u001b\\\u001b[24m${" ".repeat(Math.max(0, width - piShellVisibleWidth(row)))}`;
75
- // 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
76
94
  // plain-text URL detector only in the held-button paint; semantic text stays exact.
77
95
  const paintRow = this.#viewportController.editorPointerSelecting
78
96
  ? row.replaceAll("https://", "https:\uFE0E//").replaceAll("http://", "http:\uFE0E//")
@@ -80,7 +98,7 @@ export class OwnedUiSessionShellRoot {
80
98
  const decorated = this.#viewportController.editorPointerSelecting
81
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)
82
100
  : ranges.reduce((result, range) => hyperlinkSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), range.target, piShellVisibleWidth), row);
83
- // 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
84
102
  // prompt replaces a longer URL. Explicit non-link spaces overwrite that tail.
85
103
  return `${decorated}${linkResetAndTail}`;
86
104
  },
@@ -95,6 +113,16 @@ export class OwnedUiSessionShellRoot {
95
113
  requestRender: force => this.#componentRuntime.requestRender(force),
96
114
  hasEditorLinks: () => this.#promptChips.hyperlinkRanges(this.editor.getText()).length > 0,
97
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
+ };
98
126
  this.editor.setThinkingLevel(view.thinkingLevel);
99
127
  this.#inputSurface = this.editor;
100
128
  for (const block of view.transcript) {
@@ -102,13 +130,54 @@ export class OwnedUiSessionShellRoot {
102
130
  this.editor.addToHistory(block.text);
103
131
  }
104
132
  this.#syncTranscript(view.transcript);
105
- // 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
106
134
  // until the theme under them changes.
107
135
  this.#themeUnsubscribe = onPiThemeChange(() => {
108
136
  this.#renderedRows.clear();
109
137
  this.#documentLayouts.clear();
110
138
  });
111
139
  }
140
+ setEditorPaddingX(padding) {
141
+ this.editor.setPaddingX(padding);
142
+ this.#documentLayouts.clear();
143
+ }
144
+ setAutocompleteMaxVisible(maxVisible) {
145
+ this.editor.setAutocompleteMaxVisible(maxVisible);
146
+ }
147
+ setOutputPad(padding) {
148
+ if (this.#outputPad === padding)
149
+ return;
150
+ this.#outputPad = padding;
151
+ this.#status.setOutputPad(padding);
152
+ for (const component of this.#transcript.values())
153
+ component.setOutputPad(padding);
154
+ this.#invalidateTranscriptPresentation();
155
+ }
156
+ setHideThinkingBlock(hidden) {
157
+ if (this.#thinkingVisible === !hidden)
158
+ return;
159
+ this.#thinkingVisible = !hidden;
160
+ for (const component of this.#transcript.values())
161
+ component.setHideThinkingBlock(hidden);
162
+ this.#invalidateTranscriptPresentation();
163
+ }
164
+ setMermaidRenderingMode(mode) {
165
+ if (this.#mermaidRenderingMode === mode)
166
+ return;
167
+ this.#mermaidRenderingMode = mode;
168
+ for (const component of this.#transcript.values())
169
+ component.setMermaidRenderingMode(mode);
170
+ this.#invalidateTranscriptPresentation();
171
+ }
172
+ setImagePresentation(showImages, imageWidthCells) {
173
+ if (this.#showImages === showImages && this.#imageWidthCells === imageWidthCells)
174
+ return;
175
+ this.#showImages = showImages;
176
+ this.#imageWidthCells = imageWidthCells;
177
+ for (const component of this.#transcript.values())
178
+ component.setImagePresentation(showImages, imageWidthCells);
179
+ this.#invalidateTranscriptPresentation();
180
+ }
112
181
  preparePromptSubmission(text) {
113
182
  return this.#promptChips.prepareSubmission(text);
114
183
  }
@@ -135,7 +204,7 @@ export class OwnedUiSessionShellRoot {
135
204
  this.#blocksById.set(block.id, block);
136
205
  const component = this.#transcript.get(block.id);
137
206
  if (component === undefined) {
138
- const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer);
207
+ const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer, this.#outputPad, !this.#thinkingVisible, this.#mermaidRenderingMode, this.#showImages, this.#imageWidthCells, this.#imageAssets);
139
208
  created.setExpanded(this.#toolsExpanded);
140
209
  this.#transcript.set(block.id, created);
141
210
  this.#transcriptOrder.push(block.id);
@@ -143,7 +212,7 @@ export class OwnedUiSessionShellRoot {
143
212
  else if (component.revision !== block.revision) {
144
213
  component.update(block);
145
214
  }
146
- // 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.
147
216
  // Invalidating the whole shell here would re-wrap the entire transcript per chunk.
148
217
  this.#renderedRows.delete(block.id);
149
218
  this.#documentLayouts.clear();
@@ -155,62 +224,79 @@ export class OwnedUiSessionShellRoot {
155
224
  }
156
225
  const height = Math.max(0, this.#componentRuntime.getRows());
157
226
  const dock = this.#renderDockLayout(width);
158
- const availableWithoutTransient = Math.max(0, height - Math.min(height, dock.rowsWithoutTransient.length));
159
- // 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
160
228
  // overlay, not a lost wrapping cell. Submitted prompts alone retain the
161
229
  // intentional final gutter after their right-aligned timestamp.
162
230
  const documentWidth = width;
163
- let document = this.#renderDocumentLayout(documentWidth);
164
- // Keep queued steering/follow-up messages and Working together in Pi's
165
- // semantic order while content fits. Once the transcript overflows, move
166
- // the complete transient group into its tail so every row naturally
167
- // scrolls away and the detached viewport can use the full space above the
168
- // stable editor/footer dock.
169
- const pinTransient = document.rows.length + dock.transientRows.length <= availableWithoutTransient;
170
- 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;
171
236
  const selectableDocumentRowCount = document.rows.length;
172
- if (!pinTransient)
173
- document = { ...document, rows: [...document.rows, ...dock.transientRows] };
174
237
  const dockStartRow = height - dockRows.length + 1;
175
- const editorOffset = pinTransient ? dock.editorOffsetWithTransient : dock.editorOffsetWithoutTransient;
238
+ const editorOffset = dock.editorOffset;
176
239
  this.#viewportController.setEditorPointerFrame(this.usesDefaultInputSurface()
177
240
  ? {
178
241
  rowStart: dockStartRow + editorOffset,
179
242
  rowEnd: dockStartRow + editorOffset + dock.inputRows - 1,
180
243
  }
181
244
  : undefined);
182
- return this.#viewportController.compose({
183
- documentRows: document.rows,
184
- ...(this.#viewportController.transcriptPointerSelecting
185
- ? { paintDocumentRow: heldNativeHyperlinkStyle }
186
- : {}),
187
- // Overflowing queued and Working rows scroll with the transcript but
188
- // remain status chrome: selection and copying stop at the real document tail.
189
- selectableDocumentRowCount,
190
- dockRows,
191
- 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 = {
192
283
  width,
193
284
  height,
194
- // Anchor above the stable editor/widget/footer group. Queued input,
195
- // working states, and transient notifications may grow above it without
196
- // moving the floating control up and down the terminal.
197
- bottomControlRow: Math.max(0, height - Math.min(height, dock.stableBottomRows) - 1),
198
- theme: {
199
- // Match the v2 rail: a continuously dim track and an accent thumb at
200
- // rest, thickening on hover/drag through the presentation glyph. Reset
201
- // inherited text decorations so dim transcript rows cannot dull the
202
- // thumb; the row background deliberately remains intact.
203
- track: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("dim", text)}`,
204
- thumb: text => `${SCROLLBAR_CELL_RESET}${piTheme().fg("accent", text)}`,
205
- sticky: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", withoutTerminalBackground(text))),
206
- quietSticky: text => `\u001b[2m${text.replace(/\u001b\[(?:0|22)m/g, "$&\u001b[2m")}\u001b[22m`,
207
- bottomControl: (text, hovered) => piTheme().bg(hovered ? "selectedBg" : "toolPendingBg", piTheme().fg("text", text)),
208
- // Selection paints only the familiar dark-blue background. Source
209
- // foreground roles (including links, warnings, muted text, and bold
210
- // intensity) remain exactly as rendered instead of being inverted.
211
- selection: (line, from, to) => backgroundSgrSpan(line, from, to, "\u001b[48;2;38;79;120m", "\u001b[49m"),
212
- },
213
- }).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();
214
300
  }
215
301
  setViewportConfig(config) {
216
302
  if (this.#viewportController.setConfig(config))
@@ -245,11 +331,7 @@ export class OwnedUiSessionShellRoot {
245
331
  const rowsWithoutTransient = [...aboveWidgets, ...input, ...belowWidgets, ...footer];
246
332
  return {
247
333
  rows: [...transientRows, ...rowsWithoutTransient],
248
- rowsWithoutTransient,
249
- transientRows,
250
- stableBottomRows: rowsWithoutTransient.length,
251
- editorOffsetWithTransient: transientRows.length + aboveWidgets.length,
252
- editorOffsetWithoutTransient: aboveWidgets.length,
334
+ editorOffset: transientRows.length + aboveWidgets.length,
253
335
  inputRows: input.length,
254
336
  };
255
337
  }
@@ -306,7 +388,7 @@ export class OwnedUiSessionShellRoot {
306
388
  return this.#renderDocumentLayout(width).rows;
307
389
  }
308
390
  #renderDocumentLayout(width) {
309
- // Extension headers may animate independently of transcript revisions, so
391
+ // Performance: extension headers may animate independently of transcript revisions, so
310
392
  // only the stable built-in document participates in this frame cache.
311
393
  const cached = this.#extensionHeader === null ? this.#documentLayouts.get(width) : undefined;
312
394
  if (cached !== undefined)
@@ -340,7 +422,7 @@ export class OwnedUiSessionShellRoot {
340
422
  : width;
341
423
  const blockRows = this.#blockRows(id, block, blockWidth);
342
424
  if (block?.kind === "user") {
343
- // 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.
344
426
  // Once scrolling advances, the prompt itself reaches row zero and then
345
427
  // becomes sticky there, so the spacer is never pinned with it.
346
428
  if (this.#customViewport && index === 0)
@@ -363,7 +445,13 @@ export class OwnedUiSessionShellRoot {
363
445
  .filter(diagnostic => diagnostic.code === "package-updates")
364
446
  .flatMap(diagnostic => renderPiShellPackageUpdateNotice(diagnostic.message.split("\n").filter(line => line.startsWith("- ")).map(line => line.slice(2)), width)));
365
447
  rows.push(...diagnostics
366
- .filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "package-updates")
448
+ .filter(diagnostic => diagnostic.code === "changelog-collapsed" || diagnostic.code === "changelog-expanded")
449
+ .flatMap(diagnostic => diagnostic.code === "changelog-collapsed"
450
+ ? createPiShellCollapsedChangelog().render(width)
451
+ : createPiShellChangelog(diagnostic.message).render(width)));
452
+ rows.push(...diagnostics
453
+ .filter(diagnostic => diagnostic.code !== "engine-startup" && diagnostic.code !== "package-updates"
454
+ && diagnostic.code !== "changelog-collapsed" && diagnostic.code !== "changelog-expanded")
367
455
  .slice(-3)
368
456
  .flatMap(diagnostic => renderPiShellTranscriptBlock({
369
457
  id: `diagnostic-${diagnostic.sequence}`,
@@ -377,23 +465,19 @@ export class OwnedUiSessionShellRoot {
377
465
  const layout = { rows: Object.freeze(rows), promptAnchors: Object.freeze(promptAnchors) };
378
466
  if (this.#extensionHeader === null) {
379
467
  this.#documentLayouts.set(width, layout);
380
- // The custom viewport commonly probes full width and reserved-rail width.
468
+ // Performance: the custom viewport commonly probes full width and reserved-rail width.
381
469
  while (this.#documentLayouts.size > 2)
382
470
  this.#documentLayouts.delete(this.#documentLayouts.keys().next().value);
383
471
  }
384
472
  return layout;
385
473
  }
386
- /**
387
- * Rows for one transcript block. A finalized block renders once for a given revision
388
- * and width and is reused after that, so a frame costs what changed rather than what
389
- * the session has accumulated. A live block, and anything the shell drives itself, is
390
- * rendered every time because its content is still moving.
391
- */
474
+ // Performance: finalized blocks cache by revision and width; live blocks always render.
392
475
  #blockRows(id, block, width) {
393
476
  const component = this.#transcript.get(id);
394
477
  if (component === undefined)
395
478
  return [];
396
479
  const render = () => {
480
+ this.#transcriptBlockRenders += 1;
397
481
  const rows = component.render(width);
398
482
  if (!this.#customViewport || block?.kind === "user")
399
483
  return rows;
@@ -411,7 +495,7 @@ export class OwnedUiSessionShellRoot {
411
495
  this.#renderedRows.set(id, byWidth);
412
496
  }
413
497
  byWidth.set(width, { revision: block.revision, rows });
414
- // Bare A1 probes full width before reserving the overflowing rail column;
498
+ // Invariant: bare A1 probes full width before reserving the overflowing rail column;
415
499
  // retain both widths without turning resize history into an unbounded cache.
416
500
  while (byWidth.size > 2)
417
501
  byWidth.delete(byWidth.keys().next().value);
@@ -420,6 +504,20 @@ export class OwnedUiSessionShellRoot {
420
504
  transcriptComponent(id) {
421
505
  return this.#transcript.get(id);
422
506
  }
507
+ exitTranscript(width) {
508
+ const rows = [];
509
+ for (const id of this.#transcriptOrder) {
510
+ const block = this.#blocksById.get(id);
511
+ if (block === undefined || (!this.#thinkingVisible && block.kind === "thinking"))
512
+ continue;
513
+ if (rows.length > 0 && block.kind === "user")
514
+ rows.push("");
515
+ rows.push(...this.#blockRows(id, block, width).map(sanitizeExitTranscriptRow));
516
+ }
517
+ while (rows.at(-1) === "")
518
+ rows.pop();
519
+ return rows.join("\n");
520
+ }
423
521
  appendWorkflowStatus(message) {
424
522
  const previousId = this.#lastWorkflowStatusId;
425
523
  if (previousId !== undefined
@@ -432,7 +530,7 @@ export class OwnedUiSessionShellRoot {
432
530
  }
433
531
  const id = this.#appendAnchoredWorkflowComponent(width => [
434
532
  "",
435
- ...renderPiShellStatusText(this.#workflowStatusMessages.get(id) ?? message, width),
533
+ ...renderPiShellStatusText(this.#workflowStatusMessages.get(id) ?? message, width, this.#outputPad),
436
534
  ]);
437
535
  this.#workflowStatusMessages.set(id, message);
438
536
  this.#lastWorkflowStatusId = id;
@@ -517,9 +615,7 @@ export class OwnedUiSessionShellRoot {
517
615
  this.#appendAnchoredWorkflowComponent(width => ["", ...daxnuts.render(width)], () => daxnuts.dispose?.());
518
616
  }
519
617
  toggleThinkingVisibility() {
520
- this.#thinkingVisible = !this.#thinkingVisible;
521
- this.#documentLayouts.clear();
522
- this.invalidate();
618
+ this.setHideThinkingBlock(this.#thinkingVisible);
523
619
  }
524
620
  get toolsExpanded() {
525
621
  return this.#toolsExpanded;
@@ -588,7 +684,7 @@ export class OwnedUiSessionShellRoot {
588
684
  this.#extensionWorkingMessage = undefined;
589
685
  this.#status.setWorkingOverride(undefined);
590
686
  this.#footer.update(this.#viewWithExtensionStatuses(this.#view));
591
- // 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.
592
688
  this.#renderedRows.clear();
593
689
  this.#documentLayouts.clear();
594
690
  this.invalidate();
@@ -613,19 +709,41 @@ export class OwnedUiSessionShellRoot {
613
709
  usesDefaultInputSurface() {
614
710
  return this.#inputSurface === this.editor;
615
711
  }
616
- 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") {
617
722
  const next = component ?? this.editor;
618
- if (next === this.#inputSurface)
723
+ const nextCoordination = next === this.editor ? "editor" : coordination;
724
+ if (next === this.#inputSurface) {
725
+ this.#inputSurfaceCoordination = nextCoordination;
619
726
  return;
727
+ }
728
+ this.#dockInputCandidate = false;
620
729
  this.#inputSurface.setFocused?.(false);
621
730
  if (disposePrevious && this.#inputSurface !== this.editor)
622
731
  this.#inputSurface.dispose?.();
623
732
  this.#inputSurface = next;
733
+ this.#inputSurfaceCoordination = nextCoordination;
624
734
  this.#inputSurface.setFocused?.(true);
625
735
  this.invalidate();
626
736
  }
627
737
  handleInput(data) {
628
- 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
+ }
629
747
  }
630
748
  invalidate() {
631
749
  this.header.invalidate();
@@ -653,6 +771,8 @@ export class OwnedUiSessionShellRoot {
653
771
  this.#transcript.clear();
654
772
  this.#renderedRows.clear();
655
773
  this.#documentLayouts.clear();
774
+ this.#visibleViewportSnapshot = undefined;
775
+ this.#dockInputCandidate = false;
656
776
  if (this.#inputSurface !== this.editor)
657
777
  this.#inputSurface.dispose?.();
658
778
  this.#extensionHeader?.dispose?.();
@@ -665,6 +785,11 @@ export class OwnedUiSessionShellRoot {
665
785
  this.#footer.dispose?.();
666
786
  this.#queued.dispose?.();
667
787
  }
788
+ #invalidateTranscriptPresentation() {
789
+ this.#renderedRows.clear();
790
+ this.#documentLayouts.clear();
791
+ this.invalidate();
792
+ }
668
793
  #syncTranscript(blocks) {
669
794
  const previousIds = this.#transcriptOrder.filter(id => !id.startsWith("workflow-status-"));
670
795
  const transcriptChanged = previousIds.length !== blocks.length || blocks.some((block, index) => {
@@ -687,7 +812,7 @@ export class OwnedUiSessionShellRoot {
687
812
  for (const block of blocks) {
688
813
  const component = this.#transcript.get(block.id);
689
814
  if (component === undefined) {
690
- const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer);
815
+ const created = createPiShellTranscriptComponent(block, this.#cwd, this.#extensionRenderers, this.#submittedPromptComposer, this.#outputPad, !this.#thinkingVisible, this.#mermaidRenderingMode, this.#showImages, this.#imageWidthCells, this.#imageAssets);
691
816
  created.setExpanded(this.#toolsExpanded);
692
817
  this.#transcript.set(block.id, created);
693
818
  }
@@ -724,6 +849,10 @@ export class OwnedUiSessionShellRoot {
724
849
  invalidate() { },
725
850
  update() { },
726
851
  setExpanded() { },
852
+ setOutputPad() { },
853
+ setHideThinkingBlock() { },
854
+ setMermaidRenderingMode() { },
855
+ setImagePresentation() { },
727
856
  ...(dispose === undefined ? {} : { dispose }),
728
857
  };
729
858
  this.#transcript.set(id, component);
@@ -775,13 +904,19 @@ export class OwnedUiSessionShellRoot {
775
904
  this.resources.setExpanded(expanded);
776
905
  for (const component of this.#transcript.values())
777
906
  component.setExpanded(expanded);
778
- // Expansion changes what a block draws without changing its revision.
907
+ // Invariant: expansion changes what a block draws without changing its revision.
779
908
  this.#renderedRows.clear();
780
909
  this.#documentLayouts.clear();
781
910
  this.invalidate();
782
911
  }
783
912
  }
784
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
+ }
785
920
  const TERMINAL_BACKGROUND = /\u001b\[(?:4[0-9]|10[0-7]|48(?:[;:][0-9;:]*)?)m/g;
786
921
  /** Web URLs use link blue; file targets retain A1's cyan interactive accent. */
787
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,3 +46,10 @@ export declare class OwnedUiSessionShell {
44
46
  showScopedModelsSelector(): void;
45
47
  dispose(): Promise<void>;
46
48
  }
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;
55
+ export declare function quoteCommandArgument(value: string): string;