@timurproko/a1 0.2.7-dev.808 → 0.2.7-dev.810

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 (141) hide show
  1. package/README.md +4 -2
  2. package/dist/app/session-shell/dialog-coordinator.d.ts +42 -0
  3. package/dist/app/session-shell/dialog-coordinator.js +133 -0
  4. package/dist/app/session-shell/dock-chrome.d.ts +34 -0
  5. package/dist/app/session-shell/dock-chrome.js +109 -0
  6. package/dist/app/session-shell/exit-notice.d.ts +1 -1
  7. package/dist/app/session-shell/exit-notice.js +1 -1
  8. package/dist/app/session-shell/extension-surface.d.ts +30 -0
  9. package/dist/app/session-shell/extension-surface.js +105 -0
  10. package/dist/app/session-shell/input-surface-switch.d.ts +36 -0
  11. package/dist/app/session-shell/input-surface-switch.js +85 -0
  12. package/dist/app/session-shell/login-interaction.d.ts +27 -0
  13. package/dist/app/session-shell/login-interaction.js +91 -0
  14. package/dist/app/session-shell/prompt-submission.d.ts +48 -0
  15. package/dist/app/session-shell/prompt-submission.js +276 -0
  16. package/dist/app/session-shell/resident-tab-coordinator.d.ts +41 -0
  17. package/dist/app/session-shell/resident-tab-coordinator.js +120 -0
  18. package/dist/app/session-shell/response-copy-transport.d.ts +13 -0
  19. package/dist/app/session-shell/response-copy-transport.js +16 -7
  20. package/dist/app/session-shell/session-concern.d.ts +22 -0
  21. package/dist/app/session-shell/session-concern.js +29 -0
  22. package/dist/app/session-shell/session-lifecycle.d.ts +29 -0
  23. package/dist/app/session-shell/session-lifecycle.js +49 -0
  24. package/dist/app/session-shell/session-shell-root.d.ts +1 -1
  25. package/dist/app/session-shell/session-shell-root.js +182 -931
  26. package/dist/app/session-shell/session-shell.d.ts +2 -8
  27. package/dist/app/session-shell/session-shell.js +202 -901
  28. package/dist/app/session-shell/settings-bindings.d.ts +17 -0
  29. package/dist/app/session-shell/settings-bindings.js +91 -0
  30. package/dist/app/session-shell/shell-editor-factory.d.ts +27 -0
  31. package/dist/app/session-shell/shell-editor-factory.js +102 -0
  32. package/dist/app/session-shell/shell-host.d.ts +6 -0
  33. package/dist/app/session-shell/shell-host.js +8 -0
  34. package/dist/app/session-shell/shell-layout.d.ts +64 -0
  35. package/dist/app/session-shell/shell-layout.js +18 -0
  36. package/dist/app/session-shell/suggestion-surface.d.ts +25 -0
  37. package/dist/app/session-shell/suggestion-surface.js +63 -0
  38. package/dist/app/session-shell/transcript-document.d.ts +78 -0
  39. package/dist/app/session-shell/transcript-document.js +365 -0
  40. package/dist/app/session-shell/viewport-layout.d.ts +31 -0
  41. package/dist/app/session-shell/viewport-layout.js +160 -0
  42. package/dist/app/session-shell/workflow-output.d.ts +33 -0
  43. package/dist/app/session-shell/workflow-output.js +174 -0
  44. package/dist/app/session-shell/workflow-router.d.ts +25 -0
  45. package/dist/app/session-shell/workflow-router.js +39 -0
  46. package/dist/composition/owned-ui.d.ts +4 -25
  47. package/dist/composition/owned-ui.js +26 -270
  48. package/dist/composition/resident-tab-bridge.d.ts +16 -0
  49. package/dist/composition/resident-tab-bridge.js +159 -0
  50. package/dist/composition/session-backend.d.ts +19 -0
  51. package/dist/composition/session-backend.js +1 -0
  52. package/dist/composition/session-runtime-registry.d.ts +20 -0
  53. package/dist/composition/session-runtime-registry.js +85 -0
  54. package/dist/composition/startup-route.d.ts +32 -0
  55. package/dist/composition/startup-route.js +72 -0
  56. package/dist/foundation/release/progress-palette.generated.d.ts +2 -2
  57. package/dist/foundation/release/progress-palette.generated.js +2 -2
  58. package/dist/foundation/startup/startup-descriptor.js +2 -2
  59. package/dist/integrations/pi/components/resources/builtin-themes.d.ts +2 -2
  60. package/dist/integrations/pi/components/resources/builtin-themes.js +2 -2
  61. package/dist/integrations/pi/components/shell-extension-ui.js +79 -60
  62. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +1 -1
  63. package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +1 -1
  64. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +2 -2
  65. package/dist/integrations/pi/components/upstream/components/countdown-timer.d.ts +1 -1
  66. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +1 -1
  67. package/dist/integrations/pi/components/upstream/components/extension-editor.d.ts +1 -1
  68. package/dist/integrations/pi/components/upstream/components/extension-editor.js +1 -1
  69. package/dist/integrations/pi/components/upstream/components/extension-input.js +1 -1
  70. package/dist/integrations/pi/components/upstream/components/extension-selector.js +1 -1
  71. package/dist/integrations/pi/components/upstream/components/mermaid.js +1 -1
  72. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +1 -1
  73. package/dist/integrations/pi/components/upstream/components/owned-editor.js +1 -1
  74. package/dist/integrations/pi/components/upstream/components/pi-logo.js +1 -1
  75. package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
  76. package/dist/integrations/pi/components/upstream/components/session-footer.js +1 -1
  77. package/dist/integrations/pi/components/upstream/components/session-selector-search.js +1 -1
  78. package/dist/integrations/pi/components/upstream/components/session-selector.js +1 -1
  79. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.d.ts +1 -1
  80. package/dist/integrations/pi/components/upstream/components/skill-invocation-message.js +1 -1
  81. package/dist/integrations/pi/components/upstream/components/status-indicator.d.ts +1 -1
  82. package/dist/integrations/pi/components/upstream/components/status-indicator.js +1 -1
  83. package/dist/integrations/pi/components/upstream/components/themed-text.d.ts +1 -1
  84. package/dist/integrations/pi/components/upstream/components/themed-text.js +1 -1
  85. package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +1 -1
  86. package/dist/integrations/pi/components/upstream/components/thinking-selector.js +1 -1
  87. package/dist/integrations/pi/components/upstream/components/tool-execution.d.ts +2 -4
  88. package/dist/integrations/pi/components/upstream/components/tool-execution.js +77 -98
  89. package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
  90. package/dist/integrations/pi/components/upstream/components/tree-selector.js +1 -1
  91. package/dist/integrations/pi/components/upstream/components/trust-selector.js +1 -1
  92. package/dist/integrations/pi/components/upstream/external-editor.js +1 -1
  93. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +1 -1
  94. package/dist/integrations/pi/components/upstream/history/editor-core.js +1 -1
  95. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +1 -1
  96. package/dist/integrations/pi/components/upstream/history/kill-ring.js +1 -1
  97. package/dist/integrations/pi/components/upstream/history/printable-key.js +1 -1
  98. package/dist/integrations/pi/components/upstream/history/text-helpers.js +1 -1
  99. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +1 -1
  100. package/dist/integrations/pi/components/upstream/history/undo-stack.js +1 -1
  101. package/dist/integrations/pi/components/upstream/history/word-navigation.js +1 -1
  102. package/dist/integrations/pi/components/upstream/model-search.d.ts +1 -1
  103. package/dist/integrations/pi/components/upstream/program-status-reporter.d.ts +1 -1
  104. package/dist/integrations/pi/components/upstream/theme/theme-controller.d.ts +1 -1
  105. package/dist/integrations/pi/components/upstream/theme/theme.js +1 -1
  106. package/dist/integrations/pi/engine/adapter.d.ts +4 -77
  107. package/dist/integrations/pi/engine/adapter.js +85 -393
  108. package/dist/integrations/pi/engine/command-dispatch.d.ts +13 -0
  109. package/dist/integrations/pi/engine/command-dispatch.js +57 -0
  110. package/dist/integrations/pi/engine/host.js +2 -2
  111. package/dist/integrations/pi/engine/ports.d.ts +56 -0
  112. package/dist/integrations/pi/engine/ports.js +89 -0
  113. package/dist/integrations/pi/engine/resources/changelog.json +4 -4
  114. package/dist/integrations/pi/engine/view-state.d.ts +42 -0
  115. package/dist/integrations/pi/engine/view-state.js +126 -0
  116. package/dist/integrations/pi/startup-public.js +164 -150
  117. package/dist/integrations/pi/startup-public.manifest.json +351 -310
  118. package/dist/integrations/pi/tui-runtime/adapter.js +4 -0
  119. package/dist/integrations/pi/tui-runtime/contracts.d.ts +5 -0
  120. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +1 -1
  121. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +1 -1
  122. package/dist/native/darwin-arm64/manifest.json +1 -1
  123. package/dist/native/darwin-arm64/terminal-host.manifest.json +3 -3
  124. package/dist/native/darwin-arm64/terminal-host.node +0 -0
  125. package/dist/native/darwin-x64/manifest.json +1 -1
  126. package/dist/native/darwin-x64/terminal-host.manifest.json +3 -3
  127. package/dist/native/darwin-x64/terminal-host.node +0 -0
  128. package/dist/native/linux-x64/manifest.json +1 -1
  129. package/dist/native/linux-x64/terminal-host.manifest.json +3 -3
  130. package/dist/native/linux-x64/terminal-host.node +0 -0
  131. package/dist/native/win32-x64/manifest.json +2 -2
  132. package/dist/native/win32-x64/process-guardian.node +0 -0
  133. package/dist/native/win32-x64/terminal-host.manifest.json +3 -3
  134. package/dist/native/win32-x64/terminal-host.node +0 -0
  135. package/dist/runtime-payload-inventory.json +5181 -5369
  136. package/dist/ui/components/transcript-viewport.js +10 -19
  137. package/docs/architecture/boundaries.md +14 -0
  138. package/docs/architecture/resident-tabs.md +8 -8
  139. package/docs/architecture/response-copy-delivery.md +1 -1
  140. package/docs/architecture/toolchain.md +2 -2
  141. package/package.json +5 -5
@@ -0,0 +1,17 @@
1
+ import type { OwnedUiSessionSettingsPort, OwnedUiSessionSettingsSnapshot } from "../../contracts/owned-ui/index.js";
2
+ import type { OwnedUiPresenterTerminal } from "./shell-host.js";
3
+ import type { OwnedUiSessionShellRoot } from "./session-shell-root.js";
4
+ /** What the session's live setting effects reach: terminal modes, root presentation, and the presenter's progress flag. */
5
+ export interface SessionSettingsTargets {
6
+ readonly terminal: Pick<OwnedUiPresenterTerminal, "setHardwareCursor" | "setClearOnShrink" | "setWheelScrollLines">;
7
+ readonly root: Pick<OwnedUiSessionShellRoot, "setEditorPaddingX" | "setAutocompleteMaxVisible" | "setOutputPad" | "setHideThinkingBlock" | "setMermaidRenderingMode" | "setImagePresentation" | "setFullscreenCopyOnSelect">;
8
+ setTerminalProgress(enabled: boolean): void;
9
+ }
10
+ /**
11
+ * Applies the snapshot's presentation settings, then binds every live setting effect the session
12
+ * owns, one owner at a time. The returned function releases the owners in reverse; a failed bind
13
+ * releases the owners already bound before it throws.
14
+ */
15
+ export declare function bindSessionSettings(settings: Pick<OwnedUiSessionSettingsPort, "bindOwner" | "productMode">, snapshot: OwnedUiSessionSettingsSnapshot, targets: SessionSettingsTargets, options: {
16
+ readonly customViewport: boolean;
17
+ }): () => void;
@@ -0,0 +1,91 @@
1
+ const isBoolean = (value) => typeof value === "boolean";
2
+ const isNumber = (value) => typeof value === "number";
3
+ const isOutputPad = (value) => value === 0 || value === 1;
4
+ const isMermaidMode = (value) => value === "off" || value === "final" || value === "streaming";
5
+ const isImageWidth = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 1;
6
+ const isWheelDistance = (value) => value === "auto" || typeof value === "number" && Number.isSafeInteger(value) && value >= 1 && value <= 100;
7
+ function snapshotValue(snapshot, key) {
8
+ const values = snapshot;
9
+ return values[key];
10
+ }
11
+ /** One validated setting; `seeded` also installs the snapshot's value at bind time. */
12
+ function setting(owner, key, invalid, accepts, apply, seeded = true) {
13
+ return {
14
+ owner,
15
+ key,
16
+ live: value => {
17
+ if (!accepts(value))
18
+ throw new TypeError(invalid);
19
+ apply(value);
20
+ },
21
+ ...(seeded ? {
22
+ seed: (snapshot) => {
23
+ const value = snapshotValue(snapshot, key);
24
+ if (accepts(value))
25
+ apply(value);
26
+ },
27
+ } : {}),
28
+ };
29
+ }
30
+ function settingBindings(targets, snapshot, customViewport, bare) {
31
+ const { root, terminal } = targets;
32
+ let showImages = snapshot.showImages;
33
+ let imageWidthCells = snapshot.imageWidthCells;
34
+ const presentImages = () => root.setImagePresentation(showImages, imageWidthCells);
35
+ return [
36
+ // Invariant: bare A1 prints only the resume hint at exit, so the pinned exit-output
37
+ // choice is hidden there and cannot be bound; the comparison profile binds and honors it.
38
+ ...(bare ? [] : [{ owner: "shutdown", key: "fullscreenExitOutput", live: () => { } }]),
39
+ setting("terminal", "showHardwareCursor", "Hardware cursor setting is invalid", isBoolean, value => terminal.setHardwareCursor(value)),
40
+ setting("terminal", "clearOnShrink", "Clear-on-shrink setting is invalid", isBoolean, value => terminal.setClearOnShrink(value)),
41
+ setting("terminal", "showTerminalProgress", "Terminal progress setting is invalid", isBoolean, value => targets.setTerminalProgress(value)),
42
+ setting("shell", "editorPaddingX", "Editor padding is invalid", isNumber, value => root.setEditorPaddingX(value)),
43
+ setting("shell", "autocompleteMaxVisible", "Autocomplete maximum is invalid", isNumber, value => root.setAutocompleteMaxVisible(value)),
44
+ setting("shell", "outputPad", "Output padding is invalid", isOutputPad, value => root.setOutputPad(value)),
45
+ setting("shell", "hideThinkingBlock", "Thinking-block visibility is invalid", isBoolean, value => root.setHideThinkingBlock(value)),
46
+ setting("shell", "mermaidRenderingMode", "Mermaid mode is invalid", isMermaidMode, value => root.setMermaidRenderingMode(value)),
47
+ setting("shell", "showImages", "Image visibility is invalid", isBoolean, value => { showImages = value; presentImages(); }, false),
48
+ setting("shell", "imageWidthCells", "Image width is invalid", isImageWidth, value => { imageWidthCells = value; presentImages(); }, false),
49
+ { owner: null, key: "imageWidthCells", live: () => { }, seed: presentImages },
50
+ // Invariant: the custom viewport owns copy-on-select; the pinned comparison profile keeps Pi's wheel distance.
51
+ customViewport
52
+ ? setting("shell", "fullscreenCopyOnSelect", "Fullscreen copy-on-select setting is invalid", isBoolean, value => root.setFullscreenCopyOnSelect(value))
53
+ : setting("shell", "fullscreenWheelScrollLines", "Fullscreen wheel-scroll distance is invalid", isWheelDistance, value => terminal.setWheelScrollLines(value), false),
54
+ ...(customViewport ? [] : [{
55
+ owner: null,
56
+ key: "fullscreenCopyOnSelect",
57
+ live: () => { },
58
+ seed: (initial) => root.setFullscreenCopyOnSelect(initial.fullscreenCopyOnSelect),
59
+ }]),
60
+ ];
61
+ }
62
+ /**
63
+ * Applies the snapshot's presentation settings, then binds every live setting effect the session
64
+ * owns, one owner at a time. The returned function releases the owners in reverse; a failed bind
65
+ * releases the owners already bound before it throws.
66
+ */
67
+ export function bindSessionSettings(settings, snapshot, targets, options) {
68
+ const bindings = settingBindings(targets, snapshot, options.customViewport, settings.productMode === "bare");
69
+ for (const binding of bindings)
70
+ binding.seed?.(snapshot);
71
+ const owners = [...new Set(bindings.flatMap(binding => binding.owner === null ? [] : [binding.owner]))];
72
+ const releases = [];
73
+ const release = () => {
74
+ for (const unbind of releases.splice(0).reverse())
75
+ unbind();
76
+ };
77
+ try {
78
+ for (const owner of owners) {
79
+ const handlers = {};
80
+ for (const binding of bindings.filter(candidate => candidate.owner === owner)) {
81
+ handlers[binding.key] = { apply: value => binding.live(value) };
82
+ }
83
+ releases.push(settings.bindOwner(owner, handlers));
84
+ }
85
+ }
86
+ catch (error) {
87
+ release();
88
+ throw error;
89
+ }
90
+ return release;
91
+ }
@@ -0,0 +1,27 @@
1
+ import { type PiShellEditorOptions, type PiShellEditorPort } from "../../integrations/pi/components/shell-shared-facade.js";
2
+ import type { PasteEvent, PasteSource } from "./paste-protocol.js";
3
+ import type { PromptChipStore } from "./prompt-chips.js";
4
+ /** The shell callbacks the editor is wired to; the editor receives the whole object as its own options. */
5
+ export type ShellEditorHandlers = Omit<PiShellEditorOptions, "onChange" | "onToolsExpand" | "keybindingProfile"> & {
6
+ readonly requestRender: (force?: boolean) => void;
7
+ readonly onEditorChange?: (text: string) => void;
8
+ readonly captureClipboardPaste?: () => PasteSource;
9
+ readonly pasteDiagnostics?: (event: PasteEvent) => void;
10
+ };
11
+ export interface ShellEditorPorts {
12
+ readonly handlers: ShellEditorHandlers;
13
+ readonly customViewport: boolean;
14
+ readonly cwd: string;
15
+ readonly agentDir?: string;
16
+ readonly promptChips: PromptChipStore;
17
+ /** True while a pointer drag selects editor text, which paints links as plain underlines. */
18
+ readonly editorPointerSelecting: () => boolean;
19
+ /** The next payload-free paste diagnostic id, shared with the viewport's own paste diagnostics. */
20
+ readonly nextPasteDiagnosticRequest: () => number;
21
+ readonly toggleToolsExpanded: () => void;
22
+ }
23
+ /**
24
+ * Builds the session's prompt editor: chip-aware paste, selection, and hyperlink decoration over
25
+ * the Pi editor, plus bare A1's prompt presentation and terminal paste diagnostics.
26
+ */
27
+ export declare function createShellEditor(ports: ShellEditorPorts): PiShellEditorPort;
@@ -0,0 +1,102 @@
1
+ import { caretCell } from "../../ui/components/line-input.js";
2
+ import { PromptInput } from "../../ui/components/prompt-input.js";
3
+ import { backgroundSgrSpan, hyperlinkSgrSpan } from "../../ui/components/spans.js";
4
+ import { faint, stripAnsi } from "../../ui/components/text.js";
5
+ import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
6
+ import { piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
7
+ import { piShellTruncateToWidth, piShellVisibleWidth, } from "../../integrations/pi/components/shell-shared-facade.js";
8
+ import { EditorHyperlinkBudget } from "./editor-hyperlink-budget.js";
9
+ import { canPreparePasteInline } from "./paste-text-preparation.js";
10
+ /**
11
+ * Builds the session's prompt editor: chip-aware paste, selection, and hyperlink decoration over
12
+ * the Pi editor, plus bare A1's prompt presentation and terminal paste diagnostics.
13
+ */
14
+ export function createShellEditor(ports) {
15
+ const { handlers, customViewport, promptChips } = ports;
16
+ let terminalPasteDiagnosticRequest;
17
+ const editor = createPiShellEditor({
18
+ ...handlers,
19
+ keybindingProfile: customViewport ? "a1" : "pi",
20
+ ...(handlers.keybindings === undefined ? {} : { keybindings: handlers.keybindings }),
21
+ paintEditorSelection: (line, from, to, atomic) => backgroundSgrSpan(line, from, to, atomic ? "\u001b[7m" : "\u001b[27m\u001b[48;2;38;79;120m", atomic ? "\u001b[27m" : "\u001b[49m", piShellVisibleWidth),
22
+ transformPastedContent: content => {
23
+ try {
24
+ return promptChips.transformPastedContent(content, editor.getText());
25
+ }
26
+ catch (error) {
27
+ handlers.onPasteRejected?.(error);
28
+ return "";
29
+ }
30
+ },
31
+ ...(customViewport ? {
32
+ beginClipboardPaste: () => promptChips.beginPaste(editor.getText(), handlers.captureClipboardPaste?.() ?? { kind: "provided", read: signal => handlers.readClipboardContent?.(signal) ?? Promise.resolve(null) }, error => handlers.onPasteRejected?.(error), () => handlers.requestRender()),
33
+ onPasteInput: (bytes, phase) => {
34
+ try {
35
+ if (phase === "framing")
36
+ terminalPasteDiagnosticRequest = ports.nextPasteDiagnosticRequest();
37
+ const request = terminalPasteDiagnosticRequest ?? ports.nextPasteDiagnosticRequest();
38
+ handlers.pasteDiagnostics?.({ request, phase, bytes, atMs: performance.now(), pending: 0, transport: "terminal",
39
+ ...(phase === "settled" ? { outcome: "ready" } : {}) });
40
+ if (phase === "settled")
41
+ terminalPasteDiagnosticRequest = undefined;
42
+ }
43
+ catch { /* Invariant: payload-free diagnostics cannot interfere with the input path. */ }
44
+ },
45
+ deferTextPaste: text => !canPreparePasteInline(text),
46
+ beginTextPaste: text => promptChips.beginPaste(editor.getText(), { kind: "text", text }, error => handlers.onPasteRejected?.(error)),
47
+ } : {}),
48
+ editorAtomicRanges: line => promptChips.atomicRanges(line),
49
+ editorHiddenRanges: line => promptChips.hiddenRanges(line),
50
+ decorateEditorRow: editorRowDecorator(promptChips, ports.editorPointerSelecting),
51
+ expandCopiedEditorText: text => promptChips.expandCopiedText(text),
52
+ cwd: ports.cwd,
53
+ ...(ports.agentDir === undefined ? {} : { agentDir: ports.agentDir }),
54
+ onToolsExpand: () => ports.toggleToolsExpanded(),
55
+ ...(customViewport ? {
56
+ promptPresentation: {
57
+ input: new PromptInput({ fg: (token, text) => piTheme().fg(token, text) }, {
58
+ measure: piShellVisibleWidth,
59
+ truncate: piShellTruncateToWidth,
60
+ }),
61
+ styleSuggestion: faint,
62
+ styleSuggestionCaret: caretCell,
63
+ },
64
+ } : {}),
65
+ onChange: text => {
66
+ handlers.onEditorChange?.(text);
67
+ // Concurrency: Pi clears before onSubmit; capture waiting references before pruning removed chips.
68
+ queueMicrotask(() => promptChips.reconcileDraft(editor.getText()));
69
+ },
70
+ ...(handlers.onPromptSuggestionAccepted === undefined ? {} : { onPromptSuggestionAccepted: handlers.onPromptSuggestionAccepted }),
71
+ });
72
+ return editor;
73
+ }
74
+ /**
75
+ * Dims unsent chips and turns the draft's URLs into terminal hyperlinks, each target once per
76
+ * decoration pass; while a pointer drag selects, links paint as plain underlines instead.
77
+ */
78
+ function editorRowDecorator(promptChips, editorPointerSelecting) {
79
+ const editorHyperlinks = new EditorHyperlinkBudget();
80
+ return (row, width, rowIndex) => {
81
+ if (rowIndex === 0)
82
+ editorHyperlinks.reset();
83
+ const plain = stripAnsi(row);
84
+ const dimmed = promptChips.unsentRanges(plain).reduce((result, range) => backgroundSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), "\u001b[2m", "\u001b[22m", piShellVisibleWidth), row);
85
+ const ranges = promptChips.hyperlinkRanges(plain);
86
+ if (ranges.length === 0 || !editorHyperlinks.takeCleanup())
87
+ return dimmed;
88
+ const linkResetAndTail = `\u001b]8;;\u001b\\\u001b[24m${" ".repeat(Math.max(0, width - piShellVisibleWidth(dimmed)))}`;
89
+ // Platform: VS15 is zero-column and default-ignorable. It breaks Windows Terminal's
90
+ // plain-text URL detector only in the held-button paint; semantic text stays exact.
91
+ const selecting = editorPointerSelecting();
92
+ const paintRow = selecting
93
+ ? dimmed.replaceAll("https://", "https:︎//").replaceAll("http://", "http:︎//")
94
+ : dimmed;
95
+ const decorated = selecting
96
+ ? 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)
97
+ : ranges.filter(range => editorHyperlinks.take(range.target)).reduce((result, range) => hyperlinkSgrSpan(result, piShellVisibleWidth(plain.slice(0, range.start)), piShellVisibleWidth(plain.slice(0, range.end)), range.target, piShellVisibleWidth), dimmed);
98
+ // Platform: Windows Terminal can retain stale native dotted-link cells when a mutable
99
+ // prompt replaces a longer URL. Explicit non-link spaces overwrite that tail.
100
+ return `${decorated}${linkResetAndTail}`;
101
+ };
102
+ }
@@ -78,6 +78,12 @@ export interface OwnedUiTerminalHostOptions {
78
78
  readonly backgroundSettings?: OwnedUiBackgroundSettingsPort;
79
79
  readonly quitOutro?: OwnedUiShellPresentationOptions["quitOutro"];
80
80
  }
81
+ /** The size and render request a Pi component reads from the terminal it is shown on, read on every call. */
82
+ export declare function componentRuntime(terminal: Pick<OwnedUiPresenterTerminal, "viewport" | "requestRender">): {
83
+ getColumns(): number;
84
+ getRows(): number;
85
+ requestRender(): void;
86
+ };
81
87
  /** The terminal a session's shell options call for: its layout, TUI mode, cursor, and presentation seams. */
82
88
  export declare function sessionTerminalHostOptions(options: OwnedUiSessionShellOptions): OwnedUiTerminalHostOptions;
83
89
  /**
@@ -7,6 +7,14 @@ import { DamageAwareTerminalAdapter, } from "../../integrations/pi/tui-runtime/d
7
7
  import { PiTuiRuntimeAdapter, legacyKeyInput } from "../../integrations/pi/tui-runtime/adapter.js";
8
8
  import { classifyPiTuiInput } from "../../integrations/pi/tui-runtime/input-presentation-coordinator.js";
9
9
  import { pinnedLayoutRoot, } from "./session-shell-root.js";
10
+ /** The size and render request a Pi component reads from the terminal it is shown on, read on every call. */
11
+ export function componentRuntime(terminal) {
12
+ return {
13
+ getColumns: () => terminal.viewport().columns,
14
+ getRows: () => terminal.viewport().rows,
15
+ requestRender: () => terminal.requestRender(),
16
+ };
17
+ }
10
18
  /** The terminal a session's shell options call for: its layout, TUI mode, cursor, and presentation seams. */
11
19
  export function sessionTerminalHostOptions(options) {
12
20
  const { backend, sessionLayout } = options.engine;
@@ -0,0 +1,64 @@
1
+ import type { OwnedUiSessionViewModel } from "../../contracts/owned-ui/index.js";
2
+ import type { TranscriptViewportFrame, TranscriptViewportFrameInput, TranscriptViewportTheme } from "../../ui/components/transcript-viewport.js";
3
+ import type { PiShellEditorPort } from "../../integrations/pi/components/shell-shared-facade.js";
4
+ import type { DockChrome } from "./dock-chrome.js";
5
+ import type { InputSurfaceSwitch } from "./input-surface-switch.js";
6
+ import type { PromptChipStore } from "./prompt-chips.js";
7
+ import type { SessionViewportController } from "./session-viewport-controller.js";
8
+ import type { TranscriptDocumentLayout } from "./transcript-document.js";
9
+ /** The dock's rows and where its input and footer sit inside them. */
10
+ export interface DockLayout {
11
+ readonly rows: readonly string[];
12
+ readonly inputOffset: number;
13
+ readonly editorOffset: number;
14
+ readonly inputRows: number;
15
+ readonly footerRows: number;
16
+ /** Exact status, warning, error, command-result, and notice rows; queued and structural rows stay excluded. */
17
+ readonly copyableMessageRows: NonNullable<TranscriptViewportFrameInput["copyableDockRows"]>;
18
+ }
19
+ /** What a layout reads from the shell root to compose one frame. */
20
+ export interface ShellLayoutPorts {
21
+ readonly viewport: SessionViewportController;
22
+ readonly input: InputSurfaceSwitch;
23
+ readonly editor: Pick<PiShellEditorPort, "getText">;
24
+ readonly promptChips: Pick<PromptChipStore, "atomicRanges">;
25
+ readonly dockChrome: Pick<DockChrome, "renderCopyAcknowledgement" | "renderReleaseBanner" | "locateReleaseClose">;
26
+ readonly theme: TranscriptViewportTheme;
27
+ readonly onFrame?: (frame: TranscriptViewportFrame) => void;
28
+ rows(): number;
29
+ dock(width: number): DockLayout;
30
+ document(width: number): TranscriptDocumentLayout;
31
+ queued(width: number): readonly string[];
32
+ liveStatus(width: number): readonly string[];
33
+ diagnostics(): OwnedUiSessionViewModel["diagnostics"];
34
+ }
35
+ /**
36
+ * How one session's root composes a frame. The root picks one layout at construction and never
37
+ * consults the layout mode again on the render path.
38
+ */
39
+ export interface ShellLayout {
40
+ render(width: number): readonly string[];
41
+ /** Visible content changed outside a render, so the next frame may not reuse the last one. */
42
+ invalidate(): void;
43
+ /** What the last composed frame represented, for payload-free evidence; undefined before one exists. */
44
+ composed(): {
45
+ readonly viewportRevision: number;
46
+ readonly pointerPosition: TranscriptViewportFrameInput["pointerPosition"];
47
+ } | undefined;
48
+ compositions(): {
49
+ readonly full: number;
50
+ readonly dockOnly: number;
51
+ };
52
+ }
53
+ /** The pinned comparison profile: the whole document above the dock; the terminal host scrolls it. */
54
+ export declare class PinnedLayout implements ShellLayout {
55
+ #private;
56
+ constructor(ports: ShellLayoutPorts);
57
+ render(width: number): readonly string[];
58
+ invalidate(): void;
59
+ composed(): undefined;
60
+ compositions(): {
61
+ readonly full: number;
62
+ readonly dockOnly: number;
63
+ };
64
+ }
@@ -0,0 +1,18 @@
1
+ /** The pinned comparison profile: the whole document above the dock; the terminal host scrolls it. */
2
+ export class PinnedLayout {
3
+ #ports;
4
+ constructor(ports) {
5
+ this.#ports = ports;
6
+ }
7
+ render(width) {
8
+ this.#ports.viewport.setEditorPointerFrame(undefined);
9
+ return [...this.#ports.document(width).rows, ...this.#ports.dock(width).rows];
10
+ }
11
+ invalidate() { }
12
+ composed() {
13
+ return undefined;
14
+ }
15
+ compositions() {
16
+ return { full: 0, dockOnly: 0 };
17
+ }
18
+ }
@@ -0,0 +1,25 @@
1
+ import type { OwnedUiEvent, OwnedUiPromptSuggestionIdentity, OwnedUiSessionViewModel, SuggestionDecision } from "../../contracts/owned-ui/index.js";
2
+ import type { ContextualPromptSuggestionController, ContextualPromptSuggestionSurface } from "./prompt-suggestion-controller.js";
3
+ import type { OwnedUiSessionShellRoot } from "./session-shell-root.js";
4
+ type ResponseEvent = Extract<OwnedUiEvent, {
5
+ readonly type: "assistant-message-completed" | "agent-run-settled";
6
+ }>;
7
+ /** The provider/model key a suggestion identity and the session view are compared by; empty without a model. */
8
+ export declare function suggestionModelKey(view: Pick<OwnedUiSessionViewModel, "activeModel">): string;
9
+ /**
10
+ * Why a completed response cannot seed a suggestion, checked in order; `prepareBlockReason` is asked
11
+ * only when the response itself qualifies.
12
+ */
13
+ export declare function responseSuggestionDecision(event: ResponseEvent, prepareBlockReason: () => SuggestionDecision): SuggestionDecision;
14
+ /** Hands one completed or settled response to the controller: skipped without a model, otherwise considered or settled. */
15
+ export declare function routeResponseToSuggestions(controller: Pick<ContextualPromptSuggestionController, "skip" | "consider" | "settle"> | null, event: ResponseEvent, prepareBlockReason: () => SuggestionDecision): void;
16
+ export interface SuggestionPresentationState {
17
+ readonly disposed: boolean;
18
+ readonly view: Pick<OwnedUiSessionViewModel, "sessionId" | "activeModel">;
19
+ readonly sessionGeneration: number;
20
+ }
21
+ /** Why a prepared suggestion cannot be shown now: a released shell, a different session or model, or the root's own reason. */
22
+ export declare function suggestionPresentationBlockReason(identity: OwnedUiPromptSuggestionIdentity, state: SuggestionPresentationState, rootBlockReason: () => SuggestionDecision): SuggestionDecision;
23
+ /** The controller's view of the shell: ghost text in the root's editor, gated by `blockReason`. */
24
+ export declare function promptSuggestionSurface(root: Pick<OwnedUiSessionShellRoot, "canPresentPromptSuggestion" | "setPromptSuggestion">, blockReason: (identity: OwnedUiPromptSuggestionIdentity) => SuggestionDecision, requestRender: () => void): ContextualPromptSuggestionSurface;
25
+ export {};
@@ -0,0 +1,63 @@
1
+ /** The provider/model key a suggestion identity and the session view are compared by; empty without a model. */
2
+ export function suggestionModelKey(view) {
3
+ return view.activeModel === null ? "" : `${view.activeModel.providerId}/${view.activeModel.modelId}`;
4
+ }
5
+ /**
6
+ * Why a completed response cannot seed a suggestion, checked in order; `prepareBlockReason` is asked
7
+ * only when the response itself qualifies.
8
+ */
9
+ export function responseSuggestionDecision(event, prepareBlockReason) {
10
+ if (!event.successful)
11
+ return "failed-response";
12
+ if (event.toolContinuation || event.stopReason === "toolUse")
13
+ return "tool-continuation";
14
+ if (event.stopReason !== "stop")
15
+ return "incomplete-response";
16
+ if (event.assistantMessageCount < 2)
17
+ return "early-conversation";
18
+ return prepareBlockReason();
19
+ }
20
+ /** Hands one completed or settled response to the controller: skipped without a model, otherwise considered or settled. */
21
+ export function routeResponseToSuggestions(controller, event, prepareBlockReason) {
22
+ const identity = {
23
+ sessionId: event.sessionId,
24
+ sessionGeneration: event.sessionGeneration,
25
+ runSequence: event.runSequence,
26
+ responseSequence: event.responseSequence,
27
+ model: event.model,
28
+ };
29
+ if (event.model === null) {
30
+ controller?.skip(identity, "no-model");
31
+ return;
32
+ }
33
+ const known = { ...identity, model: event.model };
34
+ const decision = responseSuggestionDecision(event, prepareBlockReason);
35
+ if (event.type === "assistant-message-completed")
36
+ controller?.consider(known, decision);
37
+ else
38
+ controller?.settle(known, decision);
39
+ }
40
+ /** Why a prepared suggestion cannot be shown now: a released shell, a different session or model, or the root's own reason. */
41
+ export function suggestionPresentationBlockReason(identity, state, rootBlockReason) {
42
+ if (state.disposed)
43
+ return "disposed";
44
+ if (identity.sessionId !== state.view.sessionId || identity.sessionGeneration !== state.sessionGeneration
45
+ || suggestionModelKey(state.view) !== `${identity.model.providerId}/${identity.model.modelId}`)
46
+ return "stale-identity";
47
+ return rootBlockReason();
48
+ }
49
+ /** The controller's view of the shell: ghost text in the root's editor, gated by `blockReason`. */
50
+ export function promptSuggestionSurface(root, blockReason, requestRender) {
51
+ return {
52
+ canPresent: identity => blockReason(identity) === null,
53
+ presentationBlockReason: blockReason,
54
+ present: text => {
55
+ if (!root.canPresentPromptSuggestion())
56
+ return false;
57
+ root.setPromptSuggestion(text);
58
+ return true;
59
+ },
60
+ clear: () => root.setPromptSuggestion(null),
61
+ requestRender,
62
+ };
63
+ }
@@ -0,0 +1,78 @@
1
+ import type { OwnedUiSessionViewModel, OwnedUiViewportSettings } from "../../contracts/owned-ui/index.js";
2
+ import type { TranscriptPromptAnchor } from "../../ui/components/transcript-viewport.js";
3
+ import type { PiShellTranscriptComponentPort } from "../../integrations/pi/components/shell-shared-facade.js";
4
+ type TranscriptBlock = OwnedUiSessionViewModel["transcript"][number];
5
+ export interface TranscriptDocumentLayout {
6
+ readonly rows: readonly string[];
7
+ readonly promptAnchors: readonly TranscriptPromptAnchor[];
8
+ readonly liveTailStartRow: number | undefined;
9
+ }
10
+ /**
11
+ * Why the document's caches must drop. Layout-only reasons keep each block's rendered rows; the
12
+ * others also drop every block's rows because what a block draws has changed without its revision.
13
+ */
14
+ export type TranscriptInvalidation = "editor-padding" | "diagnostics" | "workflow-status" | "extension-header" | "viewport-config" | "extension-renderers" | "block-presentation" | "tools-expanded" | "repaint";
15
+ /** What the document reads from the shell around it on every uncached layout. */
16
+ export interface TranscriptDocumentChrome {
17
+ diagnostics(): OwnedUiSessionViewModel["diagnostics"];
18
+ /** The extension header's rows when one is set, else the built-in header's rows for this layout. */
19
+ headerRows(width: number): readonly string[];
20
+ /** The loaded-resources listing the pinned layout shows above the transcript. */
21
+ resourceRows(width: number): readonly string[];
22
+ /** An extension header may animate, so a document under one is never cached. */
23
+ hasExtensionHeader(): boolean;
24
+ thinkingVisible(): boolean;
25
+ timeFormat(): OwnedUiViewportSettings["timeFormat"];
26
+ }
27
+ export interface TranscriptDocumentOptions {
28
+ readonly customViewport: boolean;
29
+ readonly cwd: string;
30
+ /** Creates the component for a newly seen block; its change callback should call `blockChanged`. */
31
+ mount(block: TranscriptBlock): PiShellTranscriptComponentPort;
32
+ readonly chrome: TranscriptDocumentChrome;
33
+ }
34
+ /**
35
+ * The only owner of the transcript's blocks, their order (including anchored workflow output), the
36
+ * per-block rendered rows, and the per-width document layouts. Every cache drop names its reason.
37
+ */
38
+ export declare class TranscriptDocument {
39
+ #private;
40
+ constructor(options: TranscriptDocumentOptions);
41
+ /** Block component renders since construction, for rendering evidence. */
42
+ get renderCount(): number;
43
+ get lastId(): string | undefined;
44
+ component(id: string): PiShellTranscriptComponentPort | undefined;
45
+ components(): IterableIterator<PiShellTranscriptComponentPort>;
46
+ anchorOf(id: string): number | undefined;
47
+ /**
48
+ * Applies one block: its component is created or updated in place and the order grows
49
+ * only when the block is new. A streamed chunk costs one component update rather than a
50
+ * walk of the whole transcript.
51
+ */
52
+ apply(block: TranscriptBlock): void;
53
+ /** Replaces the whole transcript with the view's blocks, keeping anchored workflow output in place. */
54
+ sync(blocks: OwnedUiSessionViewModel["transcript"]): void;
55
+ /** Appends workflow output anchored after the transcript's first `anchor` blocks. */
56
+ appendAnchored(id: string, component: PiShellTranscriptComponentPort, anchor: number): void;
57
+ /** Disposes every anchored workflow output and drops it from the order. */
58
+ removeAnchored(): void;
59
+ /** One block's component drew something new: drop its rows and every layout that contains them. */
60
+ blockChanged(id: string): void;
61
+ invalidate(reason: TranscriptInvalidation): void;
62
+ /** A new session binding owns new mounts even when it reuses semantic invocation ids. */
63
+ reset(): void;
64
+ /** Disposes every component; anchors and blocks are left for a shell that is going away. */
65
+ dispose(): void;
66
+ layout(width: number): TranscriptDocumentLayout;
67
+ /** The transcript as plain rows for the parent terminal after a fullscreen leave. */
68
+ exitTranscript(width: number): string;
69
+ }
70
+ /** The release, command, and optional changelog carried by the adapter's `release-update` diagnostic. */
71
+ export declare function parseReleaseUpdateDiagnostic(message: string): {
72
+ version: string;
73
+ command: string;
74
+ changelogUrl: string | null;
75
+ } | null;
76
+ /** Web URLs use link blue; file targets retain A1's cyan interactive accent. */
77
+ export declare function nativeTranscriptLinkColor(text: string, target: string): string;
78
+ export {};