@timurproko/a1 0.1.8-dev.332 → 0.1.8-dev.368

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 (110) hide show
  1. package/README.md +40 -11
  2. package/dist/composition/owned-ui.d.ts +2 -0
  3. package/dist/composition/owned-ui.js +20 -3
  4. package/dist/contracts/owned-ui/index.d.ts +2 -0
  5. package/dist/contracts/owned-ui/index.js +2 -0
  6. package/dist/contracts/owned-ui/model.d.ts +38 -3
  7. package/dist/contracts/owned-ui/prompt-history.d.ts +18 -0
  8. package/dist/contracts/owned-ui/prompt-suggestions.d.ts +1 -1
  9. package/dist/contracts/owned-ui/prompt-suggestions.js +4 -1
  10. package/dist/contracts/owned-ui/suggestion-diagnostics.d.ts +22 -0
  11. package/dist/contracts/owned-ui/suggestion-diagnostics.js +9 -0
  12. package/dist/contracts/owned-ui/transcript-lifecycle.d.ts +5 -0
  13. package/dist/contracts/owned-ui/transcript-lifecycle.js +33 -0
  14. package/dist/contracts/owned-ui/validation.js +45 -2
  15. package/dist/contracts/presentation/index.d.ts +8 -0
  16. package/dist/features/owned-ui/settings-app.js +1 -1
  17. package/dist/features/prompt-history/image-sidecar.d.ts +32 -0
  18. package/dist/features/prompt-history/image-sidecar.js +130 -0
  19. package/dist/features/prompt-history/index.d.ts +2 -0
  20. package/dist/features/prompt-history/index.js +1 -0
  21. package/dist/features/prompt-history/paths.d.ts +1 -0
  22. package/dist/features/prompt-history/paths.js +2 -1
  23. package/dist/features/prompt-history/store.d.ts +1 -1
  24. package/dist/features/prompt-history/store.js +46 -2
  25. package/dist/features/prompt-history/worker.js +1 -1
  26. package/dist/features/prompt-suggestions/diagnostics.d.ts +18 -0
  27. package/dist/features/prompt-suggestions/diagnostics.js +82 -0
  28. package/dist/features/prompt-suggestions/index.d.ts +1 -0
  29. package/dist/features/prompt-suggestions/index.js +1 -0
  30. package/dist/foundation/release/bootstrap.d.ts +2 -0
  31. package/dist/foundation/release/bootstrap.js +54 -18
  32. package/dist/foundation/release/index.d.ts +1 -0
  33. package/dist/foundation/release/index.js +1 -0
  34. package/dist/foundation/release/update-launch.d.ts +6 -0
  35. package/dist/foundation/release/update-launch.js +17 -0
  36. package/dist/foundation/release/update.d.ts +2 -0
  37. package/dist/foundation/release/update.js +20 -21
  38. package/dist/foundation/supervision/main.js +5 -2
  39. package/dist/foundation/supervision/server.js +40 -13
  40. package/dist/integrations/pi/components/owned-editor-ux.d.ts +9 -1
  41. package/dist/integrations/pi/components/owned-editor-ux.js +11 -8
  42. package/dist/integrations/pi/components/prompt-input-port.d.ts +18 -0
  43. package/dist/integrations/pi/components/prompt-input-port.js +1 -0
  44. package/dist/integrations/pi/components/shell-editor-autocomplete.js +66 -11
  45. package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
  46. package/dist/integrations/pi/components/shell-footer-status.js +9 -5
  47. package/dist/integrations/pi/components/shell-presenters-info.js +7 -2
  48. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +6 -2
  49. package/dist/integrations/pi/components/shell-presenters-transcript.js +81 -49
  50. package/dist/integrations/pi/components/shell-shared-facade.d.ts +17 -4
  51. package/dist/integrations/pi/components/shell-shared-facade.js +2 -1
  52. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +3 -1
  53. package/dist/integrations/pi/components/submitted-prompt-adapter.js +20 -4
  54. package/dist/integrations/pi/components/tool-result-adapter.d.ts +6 -0
  55. package/dist/integrations/pi/components/tool-result-adapter.js +27 -0
  56. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -0
  57. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +5 -0
  58. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +7 -2
  59. package/dist/integrations/pi/components/upstream/components/owned-editor.js +41 -33
  60. package/dist/integrations/pi/components/upstream/components/session-footer.d.ts +2 -1
  61. package/dist/integrations/pi/components/upstream/components/session-footer.js +12 -7
  62. package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
  63. package/dist/integrations/pi/engine/adapter.d.ts +2 -1
  64. package/dist/integrations/pi/engine/adapter.js +181 -84
  65. package/dist/integrations/pi/engine/tool-rendering.d.ts +13 -0
  66. package/dist/integrations/pi/engine/tool-rendering.js +122 -0
  67. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +30 -0
  68. package/dist/integrations/pi/session-ui/prompt-chips.js +128 -1
  69. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +8 -0
  70. package/dist/integrations/pi/session-ui/prompt-history-controller.js +30 -2
  71. package/dist/integrations/pi/session-ui/prompt-suggestion-controller.d.ts +11 -3
  72. package/dist/integrations/pi/session-ui/prompt-suggestion-controller.js +126 -51
  73. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +24 -2
  74. package/dist/integrations/pi/session-ui/session-shell-root.js +108 -38
  75. package/dist/integrations/pi/session-ui/session-shell.js +51 -31
  76. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +7 -1
  77. package/dist/integrations/pi/session-ui/session-viewport-controller.js +106 -16
  78. package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
  79. package/dist/integrations/pi/tui-runtime/adapter.js +35 -11
  80. package/dist/integrations/pi/tui-runtime/contracts.d.ts +4 -1
  81. package/dist/integrations/pi/tui-runtime/index.d.ts +1 -0
  82. package/dist/integrations/pi/tui-runtime/index.js +1 -0
  83. package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +13 -0
  84. package/dist/integrations/pi/tui-runtime/mouse-report-input.js +59 -0
  85. package/dist/integrations/pi/tui-runtime/overlay-geometry.d.ts +32 -0
  86. package/dist/integrations/pi/tui-runtime/overlay-geometry.js +159 -0
  87. package/dist/native/darwin-arm64/manifest.json +1 -1
  88. package/dist/native/linux-x64/manifest.json +1 -1
  89. package/dist/native/win32-x64/manifest.json +2 -2
  90. package/dist/native/win32-x64/process-guardian.exe +0 -0
  91. package/dist/product-identity.d.ts +1 -1
  92. package/dist/product-identity.js +1 -1
  93. package/dist/product-identity.json +1 -0
  94. package/dist/ui/components/index.d.ts +1 -0
  95. package/dist/ui/components/index.js +1 -0
  96. package/dist/ui/components/line-input.d.ts +2 -3
  97. package/dist/ui/components/line-input.js +4 -20
  98. package/dist/ui/components/mouse.d.ts +1 -1
  99. package/dist/ui/components/mouse.js +4 -3
  100. package/dist/ui/components/prompt-input.d.ts +32 -0
  101. package/dist/ui/components/prompt-input.js +54 -0
  102. package/docs/architecture/boundaries.md +1 -0
  103. package/docs/architecture/history-editor-provenance.md +5 -1
  104. package/docs/architecture/project-structure.md +1 -0
  105. package/docs/architecture/prompt-suggestions.md +68 -0
  106. package/docs/architecture/toolchain.md +1 -1
  107. package/docs/architecture/ui-reference-provenance.md +23 -0
  108. package/docs/ci-release-runbook.md +45 -7
  109. package/docs/features/modal-content-interaction.md +37 -0
  110. package/package.json +1 -1
@@ -1,12 +1,14 @@
1
1
  import { AssistantMessageComponent, BashExecutionComponent, CompactionSummaryMessageComponent, CustomMessageComponent, DynamicBorder, getMarkdownTheme, parseSkillBlock, ToolExecutionComponent, UserMessageComponent, } from "@earendil-works/pi-coding-agent";
2
2
  import { SkillInvocationMessageComponent } from "./upstream/components/skill-invocation-message.js";
3
+ import { piToolArguments as toolArguments, updatePiToolResult as updateToolResult } from "./tool-result-adapter.js";
3
4
  import { createMermaidMarkdownTransformer } from "./upstream/components/mermaid.js";
4
5
  import { Container, Image, Spacer, Text, } from "#pi-tui";
5
6
  import { PRODUCT_TEXT } from "../../../product-identity.js";
6
- import { createPiSubmittedPromptComponent, } from "./submitted-prompt-adapter.js";
7
+ import { createPiSubmittedPromptComponent, isPiPromptStyleCompaction, } from "./submitted-prompt-adapter.js";
8
+ export { isPiPromptStyleCompaction } from "./submitted-prompt-adapter.js";
7
9
  import { PINNED_PI_LAYOUT, piTheme, } from "./theme.js";
8
10
  import { componentPort, createTuiFacade, ensureTheme, isRecord, } from "./shell-shared-facade.js";
9
- export function createPiShellTranscriptComponent(initial, cwd, extensions, submittedPrompt, initialOutputPad = PINNED_PI_LAYOUT.outputPad, initialHideThinkingBlock = false, initialMermaidRenderingMode = "off", initialShowImages = true, initialImageWidthCells = 80, imageAssets) {
11
+ export function createPiShellTranscriptComponent(initial, cwd, extensions, submittedPrompt, initialOutputPad = PINNED_PI_LAYOUT.outputPad, initialHideThinkingBlock = false, initialMermaidRenderingMode = "off", initialShowImages = true, initialImageWidthCells = 80, imageAssets, presentation) {
10
12
  ensureTheme();
11
13
  let block = initial;
12
14
  let expanded = false;
@@ -15,27 +17,65 @@ export function createPiShellTranscriptComponent(initial, cwd, extensions, submi
15
17
  let mermaidRenderingMode = initialMermaidRenderingMode;
16
18
  let showImages = initialShowImages;
17
19
  let imageWidthCells = initialImageWidthCells;
18
- const rebuild = () => withTranscriptImages(transcriptComponent(block, cwd, expanded, extensions, submittedPrompt, outputPad, hideThinkingBlock, mermaidRenderingMode, showImages, imageWidthCells), block, imageAssets, showImages, imageWidthCells);
20
+ let disposed = false;
21
+ let mount = 0;
22
+ let updating = false;
23
+ let dirty = false;
24
+ let presentationRevision = 0;
25
+ const mutate = (action) => {
26
+ const previous = updating;
27
+ updating = true;
28
+ try {
29
+ return action();
30
+ }
31
+ finally {
32
+ updating = previous;
33
+ }
34
+ };
35
+ const rebuild = () => {
36
+ const token = ++mount;
37
+ const requestRender = () => {
38
+ if (disposed || updating || token !== mount || dirty)
39
+ return;
40
+ dirty = true;
41
+ presentationRevision++;
42
+ presentation?.changed();
43
+ };
44
+ return mutate(() => withTranscriptImages(transcriptComponent(block, cwd, expanded, extensions, submittedPrompt, outputPad, hideThinkingBlock, mermaidRenderingMode, showImages, imageWidthCells, imageAssets, createTuiFacade({ getColumns: presentation?.getColumns ?? (() => 80),
45
+ getRows: presentation?.getRows ?? (() => 24), requestRender })), block, imageAssets, showImages, imageWidthCells));
46
+ };
19
47
  let component = rebuild();
20
48
  return {
21
49
  get id() { return block.id; },
22
50
  get revision() { return block.revision; },
23
- render: width => renderWithOutputPad(component, block.kind, width, outputPad),
24
- invalidate: () => component.invalidate(),
51
+ get presentationRevision() { return presentationRevision; },
52
+ render: width => {
53
+ dirty = false;
54
+ const rows = mutate(() => renderWithOutputPad(component, block.kind, width, outputPad));
55
+ const warning = block.toolRendering?.unavailable;
56
+ return warning === undefined ? rows : [...rows, ...new Text(piTheme().fg("warning", warning), outputPad, 0).render(width)];
57
+ },
58
+ invalidate: () => mutate(() => component.invalidate()),
59
+ dispose: () => { disposed = true; mount++; },
25
60
  update(next) {
26
61
  if (next.id !== block.id)
27
62
  throw new TypeError("Pi transcript component identity cannot change");
28
63
  const previous = block;
29
64
  block = next;
30
- if (!updateTranscriptComponent(component, previous, next, expanded))
65
+ if (!mutate(() => updateTranscriptComponent(component, previous, next, expanded, imageAssets)))
31
66
  component = rebuild();
32
67
  },
33
68
  setExpanded(next) {
34
69
  if (expanded === next)
35
70
  return;
36
71
  expanded = next;
72
+ if (submittedPrompt !== undefined && isPiPromptStyleCompaction(block))
73
+ return;
37
74
  if ("setExpanded" in component && typeof component.setExpanded === "function")
38
- component.setExpanded(expanded);
75
+ mutate(() => {
76
+ if ("setExpanded" in component && typeof component.setExpanded === "function")
77
+ component.setExpanded(expanded);
78
+ });
39
79
  else
40
80
  component = rebuild();
41
81
  },
@@ -61,8 +101,8 @@ export function createPiShellTranscriptComponent(initial, cwd, extensions, submi
61
101
  showImages = show;
62
102
  imageWidthCells = width;
63
103
  if (component instanceof ToolExecutionComponent) {
64
- component.setShowImages(show);
65
- component.setImageWidthCells(width);
104
+ const tool = component;
105
+ mutate(() => { tool.setShowImages(show); tool.setImageWidthCells(width); });
66
106
  }
67
107
  else
68
108
  component = rebuild();
@@ -70,6 +110,9 @@ export function createPiShellTranscriptComponent(initial, cwd, extensions, submi
70
110
  };
71
111
  }
72
112
  function withTranscriptImages(component, block, assets, showImages, imageWidthCells) {
113
+ // Compatibility: Pi owns tool images and renderer state, including references introduced after the call header.
114
+ if (component instanceof ToolExecutionComponent)
115
+ return component;
73
116
  const references = block.imageReferences ?? [];
74
117
  if (references.length === 0)
75
118
  return component;
@@ -135,7 +178,7 @@ export function renderPiShellPackageUpdateNotice(packages, width) {
135
178
  container.addChild(new DynamicBorder(text => theme.fg("warning", text)));
136
179
  return container.render(width);
137
180
  }
138
- function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt, outputPad, hideThinkingBlock, mermaidRenderingMode, showImages, imageWidthCells) {
181
+ function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt, outputPad, hideThinkingBlock, mermaidRenderingMode, showImages, imageWidthCells, imageAssets, tui) {
139
182
  switch (block.kind) {
140
183
  case "user": {
141
184
  const skill = parseSkillBlock(block.text);
@@ -156,11 +199,15 @@ function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt,
156
199
  return assistantComponent(block, outputPad, hideThinkingBlock, mermaidRenderingMode);
157
200
  case "tool-call":
158
201
  case "tool-result": {
159
- const component = toolComponent(block, cwd, extensions, showImages, imageWidthCells);
202
+ const component = toolComponent(block, cwd, extensions, showImages, imageWidthCells, imageAssets, tui);
160
203
  component.setExpanded(expanded);
161
204
  return component;
162
205
  }
163
206
  case "compaction": {
207
+ if (submittedPrompt !== undefined && isPiPromptStyleCompaction(block)) {
208
+ const tokens = numericPayload(block, "tokensBefore").toLocaleString("en-US");
209
+ return createPiSubmittedPromptComponent(block, submittedPrompt, `Compacted from ${tokens} tokens`);
210
+ }
164
211
  const component = new CompactionSummaryMessageComponent({
165
212
  role: "compactionSummary",
166
213
  summary: block.text,
@@ -179,10 +226,10 @@ function transcriptComponent(block, cwd, expanded, extensions, submittedPrompt,
179
226
  case "custom":
180
227
  return customMessageComponent(block, expanded, extensions, outputPad);
181
228
  case "bash":
182
- return bashExecutionComponent(block, cwd, expanded);
229
+ return bashExecutionComponent(block, cwd, expanded, tui);
183
230
  }
184
231
  }
185
- function updateTranscriptComponent(component, previous, next, expanded) {
232
+ function updateTranscriptComponent(component, previous, next, expanded, imageAssets) {
186
233
  if (component instanceof AssistantMessageComponent && next.kind === previous.kind
187
234
  && (next.kind === "assistant" || next.kind === "thinking")) {
188
235
  component.updateContent(validatedAssistantMessage(next), next.status === "live");
@@ -191,22 +238,10 @@ function updateTranscriptComponent(component, previous, next, expanded) {
191
238
  if (component instanceof ToolExecutionComponent
192
239
  && (next.kind === "tool-call" || next.kind === "tool-result")
193
240
  && (previous.kind === "tool-call" || previous.kind === "tool-result")) {
194
- const payload = blockPayload(next);
195
- component.updateArgs(toolArguments(payload));
196
- if (next.status === "live")
197
- component.markExecutionStarted();
198
- if (next.status === "finalized" || payload.argsComplete === true)
199
- component.setArgsComplete();
200
- if (payload.partialResult === true) {
201
- component.updateResult({ content: [{ type: "text", text: next.text }], isError: false }, true);
202
- }
203
- else if (next.kind === "tool-result") {
204
- component.updateResult({
205
- content: [{ type: "text", text: next.text }],
206
- isError: payload.isError === true,
207
- }, next.status === "live");
208
- }
209
- component.setExpanded(expanded);
241
+ if (toolArguments(previous) !== toolArguments(next))
242
+ component.updateArgs(toolArguments(next));
243
+ applyToolState(component, next, previous);
244
+ updateToolResult(component, next, imageAssets);
210
245
  return true;
211
246
  }
212
247
  if (component instanceof BashExecutionComponent && next.kind === "bash" && previous.kind === "bash") {
@@ -296,26 +331,26 @@ function validatedMessageRenderer(value) {
296
331
  function isPiMessageRenderer(value) {
297
332
  return typeof value === "function";
298
333
  }
299
- function toolComponent(block, cwd, extensions, showImages, imageWidthCells) {
334
+ function toolComponent(block, cwd, extensions, showImages, imageWidthCells, imageAssets, tui) {
300
335
  const payload = blockPayload(block);
301
336
  const toolCallId = stringPayload(payload, "toolCallId") ?? block.id;
302
337
  const toolName = stringPayload(payload, "toolName") ?? block.title ?? "tool";
303
- const argumentsPayload = toolArguments(payload);
304
- const component = new ToolExecutionComponent(toolName, toolCallId, argumentsPayload, { showImages, imageWidthCells }, validatedToolDefinition(extensions?.getToolDefinition(toolName)), createTuiFacade({ getColumns: () => 80, getRows: () => 24, requestRender() { }, onSubmit() { } }), cwd);
305
- if (block.status === "live")
338
+ const argumentsPayload = toolArguments(block);
339
+ const component = new ToolExecutionComponent(toolName, toolCallId, argumentsPayload, { showImages, imageWidthCells }, validatedToolDefinition(extensions?.getToolDefinition(toolName)), tui ?? createTuiFacade({ getColumns: () => 80, getRows: () => 24, requestRender() { } }), cwd);
340
+ applyToolState(component, block);
341
+ updateToolResult(component, block, imageAssets);
342
+ return component;
343
+ }
344
+ /** The public renderer's argument-complete and execution-started flags are independent. */
345
+ function applyToolState(component, block, previous) {
346
+ const started = (value) => value.toolState?.execution === "running"
347
+ || value.toolState === undefined && value.status === "live";
348
+ const complete = (value) => value.toolState?.argsComplete
349
+ ?? (value.status === "finalized" || blockPayload(value).argsComplete === true);
350
+ if (started(block) && (previous === undefined || !started(previous)))
306
351
  component.markExecutionStarted();
307
- if (block.status === "finalized" || payload.argsComplete === true)
352
+ if (complete(block) && (previous === undefined || !complete(previous)))
308
353
  component.setArgsComplete();
309
- if (payload.partialResult === true) {
310
- component.updateResult({ content: [{ type: "text", text: block.text }], isError: false }, true);
311
- }
312
- else if (block.kind === "tool-result") {
313
- component.updateResult({
314
- content: [{ type: "text", text: block.text }],
315
- isError: payload.isError === true,
316
- });
317
- }
318
- return component;
319
354
  }
320
355
  function customMessageComponent(block, expanded, extensions, outputPad) {
321
356
  const payload = blockPayload(block);
@@ -332,9 +367,9 @@ function customMessageComponent(block, expanded, extensions, outputPad) {
332
367
  component.setExpanded(expanded);
333
368
  return component;
334
369
  }
335
- function bashExecutionComponent(block, cwd, expanded) {
370
+ function bashExecutionComponent(block, cwd, expanded, tui) {
336
371
  const payload = blockPayload(block);
337
- const component = new BashExecutionComponent(stringPayload(payload, "command") ?? block.title ?? "", createTuiFacade({ getColumns: () => 80, getRows: () => 24, requestRender() { } }), payload.excludeFromContext === true);
372
+ const component = new BashExecutionComponent(stringPayload(payload, "command") ?? block.title ?? "", tui ?? createTuiFacade({ getColumns: () => 80, getRows: () => 24, requestRender() { } }), payload.excludeFromContext === true);
338
373
  if (block.text)
339
374
  component.appendOutput(block.text);
340
375
  if (block.status === "finalized")
@@ -358,9 +393,6 @@ function completeBashComponent(component, block) {
358
393
  maxBytes: 50 * 1024,
359
394
  } : undefined, stringPayload(payload, "fullOutputPath"));
360
395
  }
361
- function toolArguments(payload) {
362
- return isRecord(payload.arguments) && "json" in payload.arguments ? payload.arguments.json : payload.arguments ?? {};
363
- }
364
396
  function transcriptText(block) {
365
397
  if (block.kind === "retry")
366
398
  return piTheme().fg("warning", `Retry: ${block.text}`);
@@ -1,10 +1,12 @@
1
1
  import { visibleWidth, type AutocompleteProvider, type Component, type KeybindingsConfig, type TUI } from "#pi-tui";
2
- import type { OwnedUiImageAttachment, OwnedUiSessionViewModel, OwnedUiThinkingLevel, OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
2
+ import type { OwnedUiImageAttachment, OwnedUiSessionViewModel, OwnedUiThinkingLevel, OwnedUiTranscriptBlock, SuggestionDecision } from "../../../contracts/owned-ui/index.js";
3
3
  import type { HistoryEditorConstructor } from "./history-editor-loader.js";
4
+ import type { PiShellPromptInputPresentation } from "./prompt-input-port.js";
4
5
  import type { EditorRecallPort } from "./editor-interaction.js";
5
6
  import type { PresentationComponentPort } from "../../../contracts/presentation/index.js";
6
7
  /** Terminal-cell width authority used by Pi-rendered component rows. */
7
8
  export declare const piShellVisibleWidth: typeof visibleWidth;
9
+ export declare const piShellTruncateToWidth: (text: string, width: number) => string;
8
10
  export interface PiShellComponentPort extends PresentationComponentPort {
9
11
  }
10
12
  export type PiShellClipboardContent = {
@@ -23,10 +25,18 @@ export interface PiShellEditorPointerEvent {
23
25
  readonly kind: "press" | "motion" | "release";
24
26
  readonly button: number;
25
27
  readonly column: number;
26
- /** One-based row relative to the top of the editor component. */
28
+ /** One-based row relative to the top border of the editor body, excluding its menu. */
27
29
  readonly row: number;
28
30
  }
31
+ export interface PiShellEditorBodyGeometry {
32
+ /** Zero-based body offset in the most recently rendered component rows. */
33
+ readonly rowOffset: number;
34
+ /** Border-inclusive body height, excluding autocomplete. */
35
+ readonly rowCount: number;
36
+ }
29
37
  export interface PiShellEditorPort extends PiShellComponentPort {
38
+ /** Present only for the owned above-prompt autocomplete presentation. */
39
+ bodyGeometry?(): PiShellEditorBodyGeometry;
30
40
  readonly recall?: EditorRecallPort;
31
41
  readonly historyReplacement?: typeof import("../../../contracts/owned-ui/index.js").PROMPT_HISTORY_EDITOR_REPLACEMENT;
32
42
  /** Restores this editor's profile after another Pi component changed the global manager. */
@@ -34,7 +44,7 @@ export interface PiShellEditorPort extends PiShellComponentPort {
34
44
  keybindingConfig(): KeybindingsConfig;
35
45
  reloadKeybindings(): void;
36
46
  /** Uses Pi's terminal decoder rather than assuming one terminal escape spelling. */
37
- matchesTerminalKey(data: string, key: "home" | "end" | "ctrl+home" | "ctrl+end" | "ctrl+v"): boolean;
47
+ matchesTerminalKey(data: string, key: "home" | "end" | "ctrl+home" | "ctrl+end" | "alt+home" | "ctrl+v"): boolean;
38
48
  getText(): string;
39
49
  setText(text: string): void;
40
50
  insertText(text: string): void;
@@ -49,6 +59,7 @@ export interface PiShellEditorPort extends PiShellComponentPort {
49
59
  setThinkingLevel(level: OwnedUiThinkingLevel): void;
50
60
  setPromptSuggestion(text: string | null): void;
51
61
  canPresentPromptSuggestion(): boolean;
62
+ promptSuggestionBlockReason?(): SuggestionDecision;
52
63
  hasSelection(): boolean;
53
64
  ownsPointer(): boolean;
54
65
  handlePointer(event: PiShellEditorPointerEvent): boolean;
@@ -104,6 +115,7 @@ export interface PiShellExtensionRendererResolver {
104
115
  export interface PiShellTranscriptComponentPort extends PiShellComponentPort {
105
116
  readonly id: string;
106
117
  readonly revision: number;
118
+ readonly presentationRevision?: number;
107
119
  update(block: OwnedUiTranscriptBlock): void;
108
120
  setExpanded(expanded: boolean): void;
109
121
  setOutputPad(padding: 0 | 1): void;
@@ -116,6 +128,7 @@ export interface PiShellStartupNotice {
116
128
  readonly message: string;
117
129
  }
118
130
  export interface PiShellHeaderOptions {
131
+ readonly getKeybindings?: () => KeybindingsConfig;
119
132
  readonly quiet?: boolean;
120
133
  readonly expanded?: boolean;
121
134
  readonly notices?: readonly PiShellStartupNotice[];
@@ -163,7 +176,7 @@ export interface PiShellEditorOptions {
163
176
  readonly agentDir?: string;
164
177
  readonly autocompleteCommands?: readonly PiShellAutocompleteCommand[];
165
178
  readonly promptPresentation?: {
166
- readonly prefix: string;
179
+ readonly input: PiShellPromptInputPresentation;
167
180
  readonly styleSuggestion: (text: string) => string;
168
181
  readonly styleSuggestionCaret: (text: string) => string;
169
182
  };
@@ -1,8 +1,9 @@
1
- import { setKeybindings, visibleWidth, } from "#pi-tui";
1
+ import { setKeybindings, visibleWidth, truncateToWidth, } from "#pi-tui";
2
2
  import { KeybindingsManager, } from "./upstream/adjacent/core/keybindings.js";
3
3
  import { ensurePiTheme, } from "./theme.js";
4
4
  /** Terminal-cell width authority used by Pi-rendered component rows. */
5
5
  export const piShellVisibleWidth = visibleWidth;
6
+ export const piShellTruncateToWidth = (text, width) => truncateToWidth(text, width, "");
6
7
  // Provenance: pinned from packages/coding-agent/src/core/slash-commands.ts at 914cf14.
7
8
  export function createTuiFacade(options) {
8
9
  const children = [];
@@ -9,5 +9,7 @@ export interface PiShellSubmittedPromptComposer {
9
9
  readonly timestamp?: (text: string) => string;
10
10
  }) => readonly string[];
11
11
  }
12
+ /** Branch summaries share the block kind but do not participate in prompt navigation. */
13
+ export declare function isPiPromptStyleCompaction(block: OwnedUiTranscriptBlock): boolean;
12
14
  /** Pi owns Markdown and theme adaptation; the injected composer owns neutral row geometry. */
13
- export declare function createPiSubmittedPromptComponent(block: OwnedUiTranscriptBlock, composer: PiShellSubmittedPromptComposer): Component;
15
+ export declare function createPiSubmittedPromptComponent(block: OwnedUiTranscriptBlock, composer: PiShellSubmittedPromptComposer, heading?: string): Component;
@@ -1,19 +1,35 @@
1
1
  import { getMarkdownTheme } from "@earendil-works/pi-coding-agent";
2
- import { Box, Markdown } from "#pi-tui";
2
+ import { Box, Markdown, Text } from "#pi-tui";
3
3
  import { piTheme } from "./theme.js";
4
+ /** Branch summaries share the block kind but do not participate in prompt navigation. */
5
+ export function isPiPromptStyleCompaction(block) {
6
+ return block.kind === "compaction" && block.status === "finalized"
7
+ && typeof block.payload === "object" && block.payload !== null
8
+ && block.payload.role === "compactionSummary";
9
+ }
4
10
  /** Pi owns Markdown and theme adaptation; the injected composer owns neutral row geometry. */
5
- export function createPiSubmittedPromptComponent(block, composer) {
11
+ export function createPiSubmittedPromptComponent(block, composer, heading) {
6
12
  const timestamp = numericTimestamp(block);
13
+ // Invariant: presentation-only chrome stays outside the summary's Markdown parser and stored text.
14
+ const header = heading === undefined ? undefined : new Text(heading, 0, 0);
7
15
  const markdown = new Markdown(block.text, 0, 0, getMarkdownTheme(), { color: content => piTheme().fg("userMessageText", content) }, { preserveOrderedListMarkers: true, preserveBackslashEscapes: true });
8
16
  const content = {
9
17
  render(width) {
10
18
  const layout = composer.layout(width, timestamp);
11
- return [...composer.compose(markdown.render(layout.contentWidth), width, timestamp, {
19
+ const bodyRows = markdown.render(layout.contentWidth);
20
+ const rows = header === undefined ? bodyRows : [
21
+ ...header.render(layout.contentWidth).map(row => piTheme().fg("userMessageText", row)),
22
+ ...(bodyRows.length === 0 ? [] : ["", ...bodyRows]),
23
+ ];
24
+ return [...composer.compose(rows, width, timestamp, {
12
25
  prefix: text => piTheme().fg("muted", text),
13
26
  timestamp: text => piTheme().fg("dim", text),
14
27
  })];
15
28
  },
16
- invalidate: () => markdown.invalidate(),
29
+ invalidate() {
30
+ header?.invalidate();
31
+ markdown.invalidate();
32
+ },
17
33
  };
18
34
  const box = new Box(0, 0, line => piTheme().bg("userMessageBg", line));
19
35
  box.addChild(content);
@@ -0,0 +1,6 @@
1
+ import type { ToolExecutionComponent } from "@earendil-works/pi-coding-agent";
2
+ import type { OwnedUiTranscriptBlock } from "../../../contracts/owned-ui/index.js";
3
+ import { type PiShellImageAssetResolver } from "./shell-shared-facade.js";
4
+ /** Reconstruct public Pi inputs once at the component boundary, never from diagnostic summaries. */
5
+ export declare function updatePiToolResult(component: ToolExecutionComponent, block: OwnedUiTranscriptBlock, assets?: PiShellImageAssetResolver): void;
6
+ export declare function piToolArguments(block: OwnedUiTranscriptBlock): unknown;
@@ -0,0 +1,27 @@
1
+ import { isRecord } from "./shell-shared-facade.js";
2
+ /** Reconstruct public Pi inputs once at the component boundary, never from diagnostic summaries. */
3
+ export function updatePiToolResult(component, block, assets) {
4
+ const payload = isRecord(block.payload) ? block.payload : {};
5
+ if (block.kind !== "tool-result" && payload.partialResult !== true)
6
+ return;
7
+ const image = (index) => {
8
+ const reference = block.imageReferences?.[index];
9
+ const asset = reference === undefined ? null : assets?.resolve(reference.assetId);
10
+ return asset == null
11
+ ? { type: "text", text: `[Image unavailable: ${reference?.mimeType ?? "unknown"}]` }
12
+ : { type: "image", data: asset.data, mimeType: asset.mimeType };
13
+ };
14
+ const result = block.toolRendering?.result;
15
+ const content = result === undefined
16
+ ? [{ type: "text", text: block.text }, ...(block.imageReferences ?? []).map((_, index) => image(index))]
17
+ : result.content.map(part => part.type === "text"
18
+ ? { type: "text", text: block.text.slice(part.start, part.end) } : image(part.imageIndex));
19
+ component.updateResult({ content, details: result?.details, isError: payload.isError === true }, block.status === "live");
20
+ }
21
+ export function piToolArguments(block) {
22
+ if (block.toolRendering !== undefined)
23
+ return block.toolRendering.arguments;
24
+ // Compatibility: existing owned blocks and component consumers may lack rendering metadata.
25
+ const payload = isRecord(block.payload) ? block.payload : {};
26
+ return isRecord(payload.arguments) && "json" in payload.arguments ? payload.arguments.json : payload.arguments ?? {};
27
+ }
@@ -421,6 +421,7 @@ export declare class KeybindingsManager extends TuiKeybindingsManager {
421
421
  private configPath;
422
422
  constructor(userBindings?: KeybindingsConfig, configPath?: string, definitions?: KeybindingDefinitions);
423
423
  static create(agentDir?: string): KeybindingsManager;
424
+ static fromOwnedBindings(bindings?: KeybindingsConfig): KeybindingsManager;
424
425
  static createForOwnedInput(agentDir?: string): KeybindingsManager;
425
426
  private static createWithDefinitions;
426
427
  reload(): void;
@@ -150,6 +150,8 @@ export const KEYBINDINGS = {
150
150
  /** Pi actions with bare-A1 aliases; the comparison profile keeps KEYBINDINGS unchanged. */
151
151
  const OWNED_INPUT_KEYBINDINGS = {
152
152
  ...KEYBINDINGS,
153
+ "app.thinking.cycle": { ...KEYBINDINGS["app.thinking.cycle"], defaultKeys: "ctrl+l" },
154
+ "app.model.select": { ...KEYBINDINGS["app.model.select"], defaultKeys: [] },
153
155
  "tui.editor.deleteWordBackward": {
154
156
  ...KEYBINDINGS["tui.editor.deleteWordBackward"],
155
157
  defaultKeys: [...KEYBINDINGS["tui.editor.deleteWordBackward"].defaultKeys, "ctrl+backspace"],
@@ -304,6 +306,9 @@ export class KeybindingsManager extends TuiKeybindingsManager {
304
306
  static create(agentDir = getAgentDir()) {
305
307
  return KeybindingsManager.createWithDefinitions(agentDir, KEYBINDINGS);
306
308
  }
309
+ static fromOwnedBindings(bindings = {}) {
310
+ return new KeybindingsManager(bindings, undefined, OWNED_INPUT_KEYBINDINGS);
311
+ }
307
312
  static createForOwnedInput(agentDir = getAgentDir()) {
308
313
  return KeybindingsManager.createWithDefinitions(agentDir, OWNED_INPUT_KEYBINDINGS);
309
314
  }
@@ -2,18 +2,21 @@
2
2
  * Adapted from @earendil-works/pi-coding-agent 0.84.2
3
3
  * packages/coding-agent/src/modes/interactive/components/custom-editor.ts (MIT).
4
4
  * Modifications: A1-owned class name, synchronized keybinding contract, and a semantic
5
- * bare-A1 prompt-prefix/contextual-suggestion presentation branch.
5
+ * injected bare-A1 input-frame/contextual-suggestion presentation with explicit body geometry.
6
6
  */
7
7
  import { type EditorOptions, type EditorTheme, type TUI } from "#pi-tui";
8
8
  import type { AppKeybinding, KeybindingsManager } from "../adjacent/core/keybindings.js";
9
9
  import type { EditorSurface } from "../../editor-interaction.js";
10
+ import type { PiShellPromptInputPresentation } from "../../prompt-input-port.js";
10
11
  export interface OwnedEditorOptions extends EditorOptions {
11
12
  readonly persistentHistory?: boolean;
12
13
  readonly styleHistoryLabel?: (text: string) => string;
13
- readonly promptPrefix?: string;
14
+ readonly inputPresentation?: PiShellPromptInputPresentation;
14
15
  readonly styleSuggestion?: (text: string) => string;
15
16
  readonly styleSuggestionCaret?: (text: string) => string;
16
17
  readonly terminalRows?: () => number;
18
+ /** Reuses the editor's established atomic-aware visual layout when available. */
19
+ readonly getVisualLineCount?: (width: number) => number | undefined;
17
20
  }
18
21
  export interface ShellEditorInstance extends EditorSurface {
19
22
  readonly actionHandlers: Map<AppKeybinding, () => void>;
@@ -22,6 +25,8 @@ export interface ShellEditorInstance extends EditorSurface {
22
25
  onPasteImage?: () => void;
23
26
  onExtensionShortcut?: (data: string) => boolean;
24
27
  onPromptSuggestionAccepted?: (text: string) => void;
28
+ /** Border-inclusive body height from the most recent render, excluding autocomplete. */
29
+ getRenderedBodyRowCount(): number;
25
30
  setPromptSuggestion(text: string | null): void;
26
31
  canPresentPromptSuggestion(): boolean;
27
32
  onAction(action: AppKeybinding, handler: () => void): void;
@@ -2,9 +2,9 @@
2
2
  * Adapted from @earendil-works/pi-coding-agent 0.84.2
3
3
  * packages/coding-agent/src/modes/interactive/components/custom-editor.ts (MIT).
4
4
  * Modifications: A1-owned class name, synchronized keybinding contract, and a semantic
5
- * bare-A1 prompt-prefix/contextual-suggestion presentation branch.
5
+ * injected bare-A1 input-frame/contextual-suggestion presentation with explicit body geometry.
6
6
  */
7
- import { CURSOR_MARKER, Editor, truncateToWidth, visibleWidth, wrapTextWithAnsi, } from "#pi-tui";
7
+ import { CURSOR_MARKER, Editor, visibleWidth, wrapTextWithAnsi, } from "#pi-tui";
8
8
  const PROMPT_GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
9
9
  export function createOwnedEditorClass(Base) {
10
10
  return class extends Base {
@@ -16,23 +16,27 @@ export function createOwnedEditorClass(Base) {
16
16
  onExtensionShortcut;
17
17
  onPromptSuggestionAccepted;
18
18
  #promptSuggestion = null;
19
- #promptPrefix;
19
+ #inputPresentation;
20
20
  #styleSuggestion;
21
21
  #styleSuggestionCaret;
22
22
  #terminalRows;
23
+ #getVisualLineCount;
24
+ #renderedBodyRowCount = 0;
23
25
  constructor(tui, theme, keybindings, options = {}) {
24
26
  super(tui, theme, options);
25
27
  this.keybindings = keybindings;
26
- this.#promptPrefix = options.promptPrefix ?? "";
28
+ this.#inputPresentation = options.inputPresentation;
27
29
  this.#styleSuggestion = options.styleSuggestion ?? (text => text);
28
30
  this.#styleSuggestionCaret = options.styleSuggestionCaret ?? (text => `\u001b[7m${text}\u001b[27m`);
29
31
  this.#terminalRows = options.terminalRows ?? (() => 24);
32
+ this.#getVisualLineCount = options.getVisualLineCount;
30
33
  }
34
+ getRenderedBodyRowCount() { return this.#renderedBodyRowCount; }
31
35
  setPromptSuggestion(text) {
32
36
  this.#promptSuggestion = text;
33
37
  }
34
38
  canPresentPromptSuggestion() {
35
- return this.#promptPrefix.length > 0
39
+ return this.#inputPresentation !== undefined
36
40
  && this.focused
37
41
  && !this.disableSubmit
38
42
  && this.getText().length === 0
@@ -44,10 +48,15 @@ export function createOwnedEditorClass(Base) {
44
48
  super.setText(text);
45
49
  }
46
50
  render(width) {
47
- if (this.#promptPrefix.length === 0)
48
- return super.render(width);
51
+ if (this.#inputPresentation === undefined) {
52
+ const rows = super.render(width);
53
+ this.#renderedBodyRowCount = this.#getVisualLineCount === undefined ? rows.length : this.#measureBodyRows(width);
54
+ return rows;
55
+ }
49
56
  if (this.#promptSuggestion !== null && this.canPresentPromptSuggestion()) {
50
- return this.#renderSuggestion(width);
57
+ const rows = this.#renderSuggestion(width);
58
+ this.#renderedBodyRowCount = rows.length;
59
+ return rows;
51
60
  }
52
61
  return this.#renderPrefixedEditor(width);
53
62
  }
@@ -105,16 +114,11 @@ export function createOwnedEditorClass(Base) {
105
114
  super.handleInput(data);
106
115
  }
107
116
  #renderSuggestion(width) {
108
- const prefixWidth = visibleWidth(this.#promptPrefix);
109
- const innerWidth = Math.max(1, width - prefixWidth);
110
- const maxPadding = Math.max(0, Math.floor((innerWidth - 1) / 2));
111
- const paddingX = Math.min(this.getPaddingX(), maxPadding);
112
- const contentWidth = Math.max(1, innerWidth - paddingX * 2);
113
- const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));
117
+ const presentation = this.#inputPresentation;
118
+ const { paddingX, contentWidth, layoutWidth } = presentation.geometry(width, this.getPaddingX());
114
119
  const chunks = wrapTextWithAnsi(this.#promptSuggestion ?? "", layoutWidth).map(text => ({ text }));
115
120
  const maxVisible = Math.max(5, Math.floor(this.#terminalRows() * 0.3));
116
121
  const visible = chunks.slice(0, maxVisible);
117
- const horizontal = this.borderColor("─".repeat(Math.max(0, width)));
118
122
  const leftPadding = " ".repeat(paddingX);
119
123
  const rightPadding = leftPadding;
120
124
  const rows = visible.map((chunk, index) => {
@@ -130,27 +134,31 @@ export function createOwnedEditorClass(Base) {
130
134
  }
131
135
  const plainWidth = visibleWidth(chunk.text);
132
136
  const padding = " ".repeat(Math.max(0, contentWidth - plainWidth));
133
- const prefix = index === 0 ? this.#promptPrefix : " ".repeat(prefixWidth);
134
- return truncateToWidth(`${prefix}${leftPadding}${content}${padding}${rightPadding}`, width);
137
+ return `${leftPadding}${content}${padding}${rightPadding}`;
135
138
  });
136
- return [horizontal, ...rows, horizontal];
139
+ return presentation.render(width, () => ({ rows }));
137
140
  }
138
141
  #renderPrefixedEditor(width) {
139
- const prefixWidth = visibleWidth(this.#promptPrefix);
140
- const innerWidth = Math.max(1, width - prefixWidth);
141
- const rows = super.render(innerWidth);
142
- const maxPadding = Math.max(0, Math.floor((innerWidth - 1) / 2));
143
- const paddingX = Math.min(this.getPaddingX(), maxPadding);
144
- const contentWidth = Math.max(1, innerWidth - paddingX * 2);
145
- const layoutWidth = Math.max(1, contentWidth - (paddingX ? 0 : 1));
146
- const layoutCount = this.getLines().flatMap(line => wrapTextWithAnsi(line, layoutWidth)).length || 1;
147
- const visibleCount = Math.min(layoutCount, Math.max(5, Math.floor(this.#terminalRows() * 0.3)));
148
- const bottomBorder = visibleCount + 1;
149
- return rows.map((row, index) => {
150
- if (index === 0 || index === bottomBorder)
151
- return `${row}${this.borderColor("─".repeat(prefixWidth))}`;
152
- return `${index === 1 ? this.#promptPrefix : " ".repeat(prefixWidth)}${row}`;
153
- });
142
+ return this.#inputPresentation.render(width, innerWidth => {
143
+ const rows = super.render(innerWidth);
144
+ this.#renderedBodyRowCount = this.#measureBodyRows(innerWidth);
145
+ const bottomBorder = this.#renderedBodyRowCount - 1;
146
+ return {
147
+ rows: rows.slice(1, bottomBorder),
148
+ topRule: rows[0],
149
+ bottomRule: rows[bottomBorder],
150
+ after: rows.slice(bottomBorder + 1),
151
+ };
152
+ }, true, this.getPaddingX());
153
+ }
154
+ #measureBodyRows(width) {
155
+ const padding = Math.min(this.getPaddingX(), Math.max(0, Math.floor((width - 1) / 2)));
156
+ const contentWidth = Math.max(1, width - padding * 2);
157
+ const layoutWidth = Math.max(1, contentWidth - (padding ? 0 : 1));
158
+ // Invariant: split by editor layout, never by styled border or completion text.
159
+ const lineCount = this.#getVisualLineCount?.(layoutWidth)
160
+ ?? this.getLines().reduce((count, line) => count + wrapTextWithAnsi(line, layoutWidth).length, 0);
161
+ return Math.min(Math.max(1, lineCount), Math.max(5, Math.floor(this.#terminalRows() * 0.3))) + 2;
154
162
  }
155
163
  };
156
164
  }
@@ -3,7 +3,8 @@ import type { OwnedUiSessionViewModel } from "../../../../../contracts/owned-ui/
3
3
  export declare class SessionFooter implements Component {
4
4
  private readonly getView;
5
5
  private readonly cwd;
6
- constructor(getView: () => OwnedUiSessionViewModel, cwd: string);
6
+ private readonly profile;
7
+ constructor(getView: () => OwnedUiSessionViewModel, cwd: string, profile?: "pi" | "a1");
7
8
  invalidate(): void;
8
9
  dispose(): void;
9
10
  render(width: number): string[];