@timurproko/a1 0.1.8-dev.290 → 0.1.8-dev.322

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 (112) hide show
  1. package/README.md +21 -0
  2. package/bin/guardian.js +4 -3
  3. package/bin/ui.js +4 -2
  4. package/bin/update-recovery.js +3 -1
  5. package/dist/composition/owned-ui.js +13 -1
  6. package/dist/contracts/owned-ui/index.d.ts +1 -0
  7. package/dist/contracts/owned-ui/index.js +1 -0
  8. package/dist/contracts/owned-ui/prompt-history.d.ts +37 -0
  9. package/dist/contracts/owned-ui/prompt-history.js +23 -0
  10. package/dist/features/launch/profile-paths.d.ts +2 -0
  11. package/dist/features/launch/profile-paths.js +11 -0
  12. package/dist/features/prompt-history/index.d.ts +2 -0
  13. package/dist/features/prompt-history/index.js +2 -0
  14. package/dist/features/prompt-history/paths.d.ts +4 -0
  15. package/dist/features/prompt-history/paths.js +16 -0
  16. package/dist/features/prompt-history/service.d.ts +24 -0
  17. package/dist/features/prompt-history/service.js +260 -0
  18. package/dist/features/prompt-history/store.d.ts +15 -0
  19. package/dist/features/prompt-history/store.js +166 -0
  20. package/dist/features/prompt-history/worker.d.ts +9 -0
  21. package/dist/features/prompt-history/worker.js +42 -0
  22. package/dist/foundation/launch-context/index.d.ts +29 -0
  23. package/dist/foundation/launch-context/index.js +83 -0
  24. package/dist/foundation/launch-guardian/main.js +4 -3
  25. package/dist/foundation/release/bootstrap.d.ts +1 -1
  26. package/dist/foundation/release/bootstrap.js +18 -12
  27. package/dist/foundation/release/cohort-state.d.ts +1 -0
  28. package/dist/foundation/release/cohort-state.js +4 -0
  29. package/dist/foundation/release/release-gc.js +2 -2
  30. package/dist/foundation/release/release-store.js +10 -3
  31. package/dist/foundation/release/release.d.ts +3 -1
  32. package/dist/foundation/release/release.js +7 -3
  33. package/dist/foundation/release/restart-certification.d.ts +1 -0
  34. package/dist/foundation/release/restart-certification.js +5 -0
  35. package/dist/foundation/release/update-recovery.d.ts +2 -0
  36. package/dist/foundation/release/update-recovery.js +4 -0
  37. package/dist/foundation/release/update.js +5 -0
  38. package/dist/foundation/release/warmup.d.ts +1 -1
  39. package/dist/foundation/release/warmup.js +5 -4
  40. package/dist/foundation/startup/startup-runtime.js +13 -11
  41. package/dist/foundation/supervision/main.js +8 -7
  42. package/dist/integrations/pi/components/editor-interaction.d.ts +38 -0
  43. package/dist/integrations/pi/components/editor-interaction.js +1 -0
  44. package/dist/integrations/pi/components/history-editor-loader.d.ts +5 -0
  45. package/dist/integrations/pi/components/history-editor-loader.js +5 -0
  46. package/dist/integrations/pi/components/index.d.ts +1 -0
  47. package/dist/integrations/pi/components/index.js +1 -0
  48. package/dist/integrations/pi/components/owned-editor-ux.d.ts +2 -1
  49. package/dist/integrations/pi/components/owned-editor-ux.js +95 -22
  50. package/dist/integrations/pi/components/shell-editor-autocomplete.js +12 -1
  51. package/dist/integrations/pi/components/shell-presenters-info.d.ts +9 -2
  52. package/dist/integrations/pi/components/shell-presenters-info.js +35 -4
  53. package/dist/integrations/pi/components/shell-selectors-dialogs.d.ts +3 -2
  54. package/dist/integrations/pi/components/shell-selectors-dialogs.js +8 -5
  55. package/dist/integrations/pi/components/shell-shared-facade.d.ts +15 -2
  56. package/dist/integrations/pi/components/upstream/assets/earendil-image.json +12 -0
  57. package/dist/integrations/pi/components/upstream/components/earendil-announcement.js +25 -2
  58. package/dist/integrations/pi/components/upstream/components/owned-editor.d.ts +13 -9
  59. package/dist/integrations/pi/components/upstream/components/owned-editor.js +137 -133
  60. package/dist/integrations/pi/components/upstream/history/editor-core.d.ts +280 -0
  61. package/dist/integrations/pi/components/upstream/history/editor-core.js +2062 -0
  62. package/dist/integrations/pi/components/upstream/history/kill-ring.d.ts +32 -0
  63. package/dist/integrations/pi/components/upstream/history/kill-ring.js +48 -0
  64. package/dist/integrations/pi/components/upstream/history/printable-key.d.ts +1 -0
  65. package/dist/integrations/pi/components/upstream/history/printable-key.js +40 -0
  66. package/dist/integrations/pi/components/upstream/history/text-helpers.d.ts +14 -0
  67. package/dist/integrations/pi/components/upstream/history/text-helpers.js +28 -0
  68. package/dist/integrations/pi/components/upstream/history/undo-stack.d.ts +22 -0
  69. package/dist/integrations/pi/components/upstream/history/undo-stack.js +36 -0
  70. package/dist/integrations/pi/components/upstream/history/word-navigation.d.ts +24 -0
  71. package/dist/integrations/pi/components/upstream/history/word-navigation.js +100 -0
  72. package/dist/integrations/pi/engine/adapter.d.ts +13 -1
  73. package/dist/integrations/pi/engine/adapter.js +86 -25
  74. package/dist/integrations/pi/engine/changelog.d.ts +2 -0
  75. package/dist/integrations/pi/engine/changelog.js +63 -0
  76. package/dist/integrations/pi/engine/resources/changelog.json +12 -0
  77. package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -1
  78. package/dist/integrations/pi/engine/workflows.d.ts +1 -1
  79. package/dist/integrations/pi/session-ui/prompt-chips.d.ts +4 -1
  80. package/dist/integrations/pi/session-ui/prompt-chips.js +40 -13
  81. package/dist/integrations/pi/session-ui/prompt-history-controller.d.ts +30 -0
  82. package/dist/integrations/pi/session-ui/prompt-history-controller.js +147 -0
  83. package/dist/integrations/pi/session-ui/session-shell-root.d.ts +33 -0
  84. package/dist/integrations/pi/session-ui/session-shell-root.js +68 -74
  85. package/dist/integrations/pi/session-ui/session-shell.d.ts +1 -1
  86. package/dist/integrations/pi/session-ui/session-shell.js +98 -12
  87. package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
  88. package/dist/integrations/pi/session-ui/session-viewport-controller.js +21 -15
  89. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +7 -0
  90. package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +7 -6
  91. package/dist/native/darwin-arm64/manifest.json +1 -1
  92. package/dist/native/linux-x64/manifest.json +1 -1
  93. package/dist/native/win32-x64/manifest.json +2 -2
  94. package/dist/native/win32-x64/process-guardian.exe +0 -0
  95. package/dist/product-identity.d.ts +2 -2
  96. package/dist/product-identity.js +3 -36
  97. package/dist/product-identity.json +1 -22
  98. package/dist/ui/components/transcript-viewport.d.ts +8 -0
  99. package/dist/ui/components/transcript-viewport.js +23 -2
  100. package/dist/ui/components/visible-hyperlinks.d.ts +7 -0
  101. package/dist/ui/components/visible-hyperlinks.js +1 -0
  102. package/dist/ui/settings/declarations.js +19 -0
  103. package/dist/ui/settings/sections.js +11 -1
  104. package/dist/ui/settings/session.js +13 -4
  105. package/docs/architecture/boundaries.md +2 -0
  106. package/docs/architecture/history-editor-provenance.md +96 -0
  107. package/docs/architecture/internal-naming.md +89 -0
  108. package/docs/architecture/project-structure.md +1 -0
  109. package/docs/architecture/resource-and-data-policy.md +27 -1
  110. package/docs/architecture/toolchain.md +15 -1
  111. package/docs/features/prompt-history.md +149 -0
  112. package/package.json +8 -3
@@ -17,5 +17,5 @@ export declare class PiWorkflowControllerPort implements AgentWorkflowPort {
17
17
  listWorkflows(): Promise<readonly AgentWorkflowDescriptor[]>;
18
18
  executeWorkflow(workflowId: string, input: AgentJsonValue, signal?: AbortSignal): Promise<AgentJsonValue>;
19
19
  }
20
- export declare const PI_BUILTIN_WORKFLOW_ROUTES: readonly ("name" | "share" | "model" | "login" | "logout" | "settings" | "reload" | "compact" | "new" | "resume" | "quit" | "session" | "scoped-models" | "export" | "import" | "copy" | "changelog" | "hotkeys" | "fork" | "clone" | "tree" | "trust" | "debug" | "arminsayshi" | "dementedelves")[];
20
+ export declare const PI_BUILTIN_WORKFLOW_ROUTES: readonly ("name" | "share" | "model" | "login" | "logout" | "settings" | "reload" | "compact" | "new" | "resume" | "quit" | "session" | "tree" | "scoped-models" | "export" | "import" | "copy" | "changelog" | "hotkeys" | "fork" | "clone" | "trust" | "debug" | "arminsayshi" | "dementedelves")[];
21
21
  export declare const PI_WORKFLOW_CAPABILITIES: readonly WorkflowCapability[];
@@ -178,7 +178,7 @@ export interface PiWorkflowHost {
178
178
  readonly stdout: string;
179
179
  readonly stderr: string;
180
180
  }>;
181
- readChangelog(): Promise<string>;
181
+ readChangelog(sinceVersion?: string): Promise<string>;
182
182
  }
183
183
  export interface PiBashWorkflowResult {
184
184
  readonly command: string;
@@ -11,7 +11,7 @@ export interface PreparedPrompt {
11
11
  /** Owns semantic chips and bounded pending paste references; cancels background work on reset or disposal. */
12
12
  export declare class PromptChipStore {
13
13
  #private;
14
- beginPaste(currentText: string, read: (signal: AbortSignal) => Promise<PiShellClipboardContent | null>, onError: (error: unknown) => void): {
14
+ beginPaste(currentText: string, read: (signal: AbortSignal) => Promise<PiShellClipboardContent | null>, onError: (error: unknown) => void, onImage?: () => void): {
15
15
  marker: string;
16
16
  result: Promise<string>;
17
17
  };
@@ -21,6 +21,8 @@ export declare class PromptChipStore {
21
21
  resetPastes(text: string): string;
22
22
  dispose(): Promise<void>;
23
23
  transformPastedContent(content: PiShellClipboardContent, currentText?: string): string;
24
+ /** Hide provisional clipboard identities until the read identifies an actual image. */
25
+ hiddenRanges(line: string): readonly PiShellEditorTextRange[];
24
26
  atomicRanges(line: string): readonly PiShellEditorTextRange[];
25
27
  hyperlinkRanges(text: string): readonly {
26
28
  start: number;
@@ -28,5 +30,6 @@ export declare class PromptChipStore {
28
30
  target: string;
29
31
  }[];
30
32
  expandCopiedText(text: string): string;
33
+ prepareHistoryText(text: string): string;
31
34
  prepareSubmission(text: string): PreparedPrompt;
32
35
  }
@@ -15,27 +15,32 @@ export class PromptChipStore {
15
15
  #pending = new Map();
16
16
  #preparation = new ImagePreparationClient();
17
17
  #stopping = new Set();
18
- beginPaste(currentText, read, onError) {
19
- const marker = `[📷 preparing-${randomBytes(5).toString("hex")}]`;
18
+ beginPaste(currentText, read, onError, onImage = () => { }) {
19
+ const id = randomBytes(5).toString("hex");
20
+ // Invariant: reserve paste identity immediately, but only display a screenshot after identifying an image.
21
+ const marker = `[📷 screenshot-${id}]`;
20
22
  const job = this.#preparation.start(async (signal) => {
21
23
  const content = await read(signal);
22
24
  entry.kind = content?.kind === "image" ? "image" : "text";
23
- if (content?.kind === "image" && this.#imageCount(currentText) >= 8)
24
- throw new ImageAttachmentError("image-count");
25
+ if (content?.kind === "image") {
26
+ onImage();
27
+ if (this.#imageCount(currentText) >= 8)
28
+ throw new ImageAttachmentError("image-count");
29
+ }
25
30
  return content;
26
31
  });
27
32
  const entry = { marker, job, references: 0, kind: "unknown", completion: job.result.then(content => {
28
33
  if (content === null)
29
34
  return "";
30
35
  if (content.kind === "image") {
31
- return this.#addPreparedImage(content);
36
+ return this.#addPreparedImage(content, id);
32
37
  }
33
38
  return this.transformPastedContent(content);
34
39
  }).catch(error => {
35
40
  entry.error = error instanceof ImageAttachmentError ? error : new ImageAttachmentError("image-codec");
36
41
  if (entry.error.code !== "image-canceled" && entry.references === 0)
37
42
  onError(entry.error);
38
- return marker.replace("preparing-", "failed-");
43
+ return marker.replace("screenshot-", "failed-");
39
44
  }).then(replacement => {
40
45
  entry.replacement = replacement;
41
46
  return replacement;
@@ -95,12 +100,17 @@ export class PromptChipStore {
95
100
  }
96
101
  async dispose() { await Promise.all([this.#preparation.dispose(), ...this.#stopping]); }
97
102
  #imageCount(text) {
98
- const regular = [...this.#chips.values()].filter(chip => chip.kind === "image" && text.includes(chip.tag)).length;
99
- return regular + [...this.#pending.values()].filter(entry => entry.kind !== "text" && (text.includes(entry.marker) || text.includes(entry.marker.replace("preparing-", "failed-")))).length;
103
+ const tags = new Set([...this.#chips.values()].filter(chip => chip.kind === "image" && text.includes(chip.tag)).map(chip => chip.tag));
104
+ for (const entry of this.#pending.values()) {
105
+ if (entry.kind !== "text" && (text.includes(entry.marker) || text.includes(entry.marker.replace("screenshot-", "failed-")))) {
106
+ // Invariant: a ready screenshot can retain its pending label but still occupies only one slot.
107
+ tags.add(entry.replacement ?? entry.marker);
108
+ }
109
+ }
110
+ return tags.size;
100
111
  }
101
- #addPreparedImage(image) {
102
- const suffix = image.mimeType === "image/jpeg" ? "jpg" : image.mimeType.split("/")[1] ?? "png";
103
- const tag = `[📷 screenshot-${randomBytes(5).toString("hex")}${image.transformed ? "-resized" : ""}.${suffix}]`;
112
+ #addPreparedImage(image, id) {
113
+ const tag = `[📷 screenshot-${id}${image.transformed ? "-resized" : ""}]`;
104
114
  const attachment = Object.freeze({ type: "image", data: image.data, mimeType: image.mimeType });
105
115
  assertPromptImages([attachment]);
106
116
  this.#chips.set(tag, { kind: "image", tag, image: attachment });
@@ -114,7 +124,7 @@ export class PromptChipStore {
114
124
  return "";
115
125
  assertPromptImages([...this.prepareSubmission(currentText).images, { type: "image", ...image }]);
116
126
  const id = randomBytes(5).toString("hex");
117
- const tag = `[📷 screenshot-${id}.png]`;
127
+ const tag = `[📷 screenshot-${id}]`;
118
128
  this.#chips.set(tag, {
119
129
  kind: "image",
120
130
  tag,
@@ -142,6 +152,20 @@ export class PromptChipStore {
142
152
  return this.#recordUnique({ kind: "file", tag: `[${icon} ${label}]`, path: item.fullPath });
143
153
  }).join("");
144
154
  }
155
+ /** Hide provisional clipboard identities until the read identifies an actual image. */
156
+ hiddenRanges(line) {
157
+ const ranges = [];
158
+ for (const entry of this.#pending.values()) {
159
+ if (entry.kind === "image")
160
+ continue;
161
+ let from = 0;
162
+ while ((from = line.indexOf(entry.marker, from)) >= 0) {
163
+ ranges.push({ start: from, end: from + entry.marker.length });
164
+ from += entry.marker.length;
165
+ }
166
+ }
167
+ return ranges.sort((left, right) => left.start - right.start);
168
+ }
145
169
  atomicRanges(line) {
146
170
  return [...line.matchAll(CHIP_PATTERN)].map(match => ({
147
171
  start: match.index,
@@ -171,6 +195,9 @@ export class PromptChipStore {
171
195
  expandCopiedText(text) {
172
196
  return this.#replaceResolvable(text, false).text;
173
197
  }
198
+ prepareHistoryText(text) {
199
+ return this.#replaceResolvable(text, false).text.replace(CHIP_PATTERN, tag => this.#chips.get(tag)?.kind === "image" ? "" : tag).trim();
200
+ }
174
201
  prepareSubmission(text) {
175
202
  const expanded = this.#replaceResolvable(text, true);
176
203
  return { text: expanded.text, images: expanded.images };
@@ -178,7 +205,7 @@ export class PromptChipStore {
178
205
  #replaceResolvable(text, includeImages) {
179
206
  let expanded = text;
180
207
  for (const entry of this.#pending.values()) {
181
- if (!expanded.includes(entry.marker) && !expanded.includes(entry.marker.replace("preparing-", "failed-")))
208
+ if (!expanded.includes(entry.marker) && !expanded.includes(entry.marker.replace("screenshot-", "failed-")))
182
209
  continue;
183
210
  if (includeImages && entry.error !== undefined)
184
211
  throw entry.error;
@@ -0,0 +1,30 @@
1
+ import { type PromptHistoryKind, type PromptHistoryPort } from "../../../contracts/owned-ui/index.js";
2
+ import type { PiShellEditorPort } from "../components/index.js";
3
+ /** Merges bounded recall snapshots; the editor alone owns browsing, draft, cursor, and undo state. */
4
+ export declare class PromptHistoryController {
5
+ #private;
6
+ readonly options: {
7
+ editor: PiShellEditorPort;
8
+ store: PromptHistoryPort;
9
+ limit: number;
10
+ fallback: readonly string[];
11
+ active(): boolean;
12
+ render(): void;
13
+ failure(message: string): void;
14
+ };
15
+ constructor(options: {
16
+ editor: PiShellEditorPort;
17
+ store: PromptHistoryPort;
18
+ limit: number;
19
+ fallback: readonly string[];
20
+ active(): boolean;
21
+ render(): void;
22
+ failure(message: string): void;
23
+ });
24
+ start(): void;
25
+ capture(text: string, kind: PromptHistoryKind, cwd: string, sessionId: string): void;
26
+ rememberRecovery(text: string): void;
27
+ synchronize(): void;
28
+ reset(fallback: readonly string[]): void;
29
+ close(): Promise<boolean>;
30
+ }
@@ -0,0 +1,147 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { PROMPT_HISTORY_MAX_TEXT_BYTES } from "../../../contracts/owned-ui/index.js";
3
+ /** Merges bounded recall snapshots; the editor alone owns browsing, draft, cursor, and undo state. */
4
+ export class PromptHistoryController {
5
+ options;
6
+ #local = new Map();
7
+ #unsubscribe;
8
+ #snapshot;
9
+ #fallback;
10
+ #disposed = false;
11
+ #generation = 0;
12
+ #unsubscribeSnapshot;
13
+ #unsubscribeFailure;
14
+ #closeFailed = false;
15
+ #closePromise;
16
+ constructor(options) {
17
+ this.options = options;
18
+ if (options.editor.recall === undefined)
19
+ throw new Error("The selected editor does not expose typed history");
20
+ this.#snapshot = { revision: 0, limit: options.limit, entries: [] };
21
+ this.#fallback = boundedTexts([...options.fallback].reverse(), options.limit);
22
+ this.#unsubscribeSnapshot = this.#subscribeSnapshot();
23
+ this.#unsubscribeFailure = options.store.onFailure(code => {
24
+ if (this.#disposed)
25
+ this.#closeFailed = true;
26
+ else
27
+ options.failure(`Prompt history ${code}; current-session recall remains available.`);
28
+ });
29
+ this.#unsubscribe = [
30
+ options.editor.recall.observe(() => {
31
+ if (this.#disposed)
32
+ return;
33
+ if (options.editor.recall.position().index === -1)
34
+ this.synchronize();
35
+ options.store.refresh();
36
+ }),
37
+ ];
38
+ this.synchronize();
39
+ }
40
+ start() { this.options.store.start(); }
41
+ capture(text, kind, cwd, sessionId) {
42
+ const canonical = text.trim();
43
+ if (!canonical || this.#disposed)
44
+ return;
45
+ const id = randomUUID();
46
+ const generation = this.#generation;
47
+ this.#local.set(id, { text: canonical, committed: false });
48
+ this.#boundLocal();
49
+ this.synchronize();
50
+ void this.options.store.record({ id, text: canonical, timestamp: Date.now(), kind, cwd, sessionId }).then(outcome => {
51
+ if (this.#disposed || generation !== this.#generation)
52
+ return;
53
+ const local = this.#local.get(id);
54
+ if (local === undefined || outcome !== "committed")
55
+ return;
56
+ local.committed = true;
57
+ if (this.#snapshot.entries.some(entry => entry.submissionId === id))
58
+ this.#local.delete(id);
59
+ this.synchronize();
60
+ }).catch(() => {
61
+ if (!this.#disposed)
62
+ this.options.failure("Prompt history unavailable; current-session recall remains available.");
63
+ });
64
+ }
65
+ rememberRecovery(text) {
66
+ if (this.#disposed || !text.trim())
67
+ return;
68
+ const canonical = text.trim();
69
+ if ([...this.#local.values()].some(item => item.text === canonical)
70
+ || this.#snapshot.entries.some(item => item.text === canonical))
71
+ return;
72
+ this.#local.set(randomUUID(), { text: canonical, committed: false });
73
+ this.#boundLocal();
74
+ this.synchronize();
75
+ }
76
+ synchronize() {
77
+ if (this.#disposed || !this.options.active())
78
+ return;
79
+ const values = [...this.#local.values()].reverse().map(item => item.text)
80
+ .concat(this.#snapshot.entries.map(item => item.text), this.#fallback);
81
+ this.options.editor.recall.replace(boundedTexts(values, this.#snapshot.limit));
82
+ this.options.render();
83
+ }
84
+ reset(fallback) {
85
+ this.#generation++;
86
+ this.#unsubscribeSnapshot();
87
+ this.#unsubscribeSnapshot = this.#subscribeSnapshot();
88
+ this.#fallback = boundedTexts([...fallback].reverse(), this.options.limit);
89
+ this.#local.clear();
90
+ this.options.editor.recall.reset();
91
+ this.synchronize();
92
+ this.options.store.refresh();
93
+ }
94
+ close() {
95
+ if (this.#closePromise !== undefined)
96
+ return this.#closePromise;
97
+ this.#disposed = true;
98
+ this.#unsubscribeSnapshot();
99
+ for (const unsubscribe of this.#unsubscribe)
100
+ unsubscribe();
101
+ this.#local.clear();
102
+ this.#fallback = [];
103
+ this.#closePromise = this.options.store.close().then(() => !this.#closeFailed, () => false)
104
+ .finally(() => this.#unsubscribeFailure());
105
+ return this.#closePromise;
106
+ }
107
+ #subscribeSnapshot() {
108
+ const generation = this.#generation;
109
+ return this.options.store.onSnapshot(snapshot => {
110
+ if (this.#disposed || generation !== this.#generation || snapshot.revision < this.#snapshot.revision)
111
+ return;
112
+ this.#snapshot = snapshot;
113
+ for (const [id, entry] of this.#local) {
114
+ if (entry.committed)
115
+ this.#local.delete(id);
116
+ }
117
+ this.synchronize();
118
+ });
119
+ }
120
+ #boundLocal() {
121
+ let bytes = [...this.#local.values()].reduce((sum, item) => sum + Buffer.byteLength(item.text), 0);
122
+ while (this.#local.size > this.options.limit || bytes > PROMPT_HISTORY_MAX_TEXT_BYTES) {
123
+ const id = this.#local.keys().next().value;
124
+ bytes -= Buffer.byteLength(this.#local.get(id).text);
125
+ this.#local.delete(id);
126
+ }
127
+ }
128
+ }
129
+ function boundedTexts(values, limit) {
130
+ const result = [];
131
+ const seen = new Set();
132
+ let bytes = 0;
133
+ for (const value of values) {
134
+ const text = value.trim();
135
+ if (!text || seen.has(text))
136
+ continue;
137
+ const size = Buffer.byteLength(text);
138
+ if (bytes + size > PROMPT_HISTORY_MAX_TEXT_BYTES)
139
+ continue;
140
+ result.push(text);
141
+ seen.add(text);
142
+ bytes += size;
143
+ if (result.length >= limit)
144
+ break;
145
+ }
146
+ return result;
147
+ }
@@ -18,6 +18,11 @@ export interface OwnedUiClipboardPort {
18
18
  writeText?(text: string): Promise<void>;
19
19
  }
20
20
  export interface OwnedUiSessionShellOptions {
21
+ readonly promptHistory?: {
22
+ readonly store: import("../../../contracts/owned-ui/index.js").PromptHistoryPort;
23
+ readonly limit: number;
24
+ readonly editor: import("../components/index.js").HistoryEditorConstructor;
25
+ };
21
26
  readonly backend: OwnedUiBackendPort;
22
27
  readonly cwd: string;
23
28
  readonly terminal?: OwnedUiTerminalPort;
@@ -67,6 +72,8 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
67
72
  readonly onViewportFrame?: (frame: TranscriptViewportFrame) => void;
68
73
  readonly requestHyperlinkCleanup?: () => void;
69
74
  readonly enableDockInputReuse?: boolean;
75
+ readonly persistentHistory?: boolean;
76
+ readonly historyEditor?: import("../components/index.js").HistoryEditorConstructor;
70
77
  readonly onSubmit: (text: string) => void;
71
78
  readonly onPasteRejected?: (error: unknown) => void;
72
79
  readonly onInterrupt: () => void;
@@ -91,6 +98,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
91
98
  setHideThinkingBlock(hidden: boolean): void;
92
99
  setMermaidRenderingMode(mode: "off" | "final" | "streaming"): void;
93
100
  setImagePresentation(showImages: boolean, imageWidthCells: number): void;
101
+ prepareHistoryText(text: string): string;
94
102
  preparePromptSubmission(text: string): PreparedPrompt;
95
103
  hasPendingPastes(text: string): boolean;
96
104
  waitForPromptPastes(text: string, signal: AbortSignal): Promise<PreparedPrompt>;
@@ -108,6 +116,31 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
108
116
  applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
109
117
  render(width: number): readonly string[];
110
118
  viewportFrameDescriptor(): TranscriptViewportFrameDescriptor | null;
119
+ /** Read-only, payload-free evidence; observing a pending frame must never render or repair it. */
120
+ viewportPresentationEvidence(): {
121
+ candidate: boolean;
122
+ candidateRevision: number | null;
123
+ currentRevision: number;
124
+ composedRevision: number | null;
125
+ currentPointer: {
126
+ readonly column: number;
127
+ readonly row: number;
128
+ } | null;
129
+ composedPointer: {
130
+ readonly column: number;
131
+ readonly row: number;
132
+ } | null;
133
+ frameId: number | null;
134
+ bottom: {
135
+ readonly row: number;
136
+ readonly columnStart: number;
137
+ readonly columnEnd: number;
138
+ } | null;
139
+ followingEnd: boolean;
140
+ scrollTop: number;
141
+ maxScroll: number;
142
+ cause: "detached" | "initial" | "steady" | "dock-input" | "follow-shift" | "geometry-change" | null;
143
+ };
111
144
  /** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
112
145
  viewportTransientTailRowCount(): number;
113
146
  hasActiveSelection(): boolean;