@timurproko/a1 0.2.2-dev.611 → 0.2.3-dev.651

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 (67) hide show
  1. package/README.md +16 -8
  2. package/bin/pinned-pi-public.js +11 -4
  3. package/dist/app/session-shell/index.d.ts +1 -1
  4. package/dist/app/session-shell/paste-executor.js +6 -1
  5. package/dist/app/session-shell/prompt-chips.d.ts +8 -0
  6. package/dist/app/session-shell/prompt-chips.js +58 -13
  7. package/dist/app/session-shell/session-shell-root.d.ts +21 -3
  8. package/dist/app/session-shell/session-shell-root.js +110 -17
  9. package/dist/app/session-shell/session-shell.d.ts +1 -1
  10. package/dist/app/session-shell/session-shell.js +76 -31
  11. package/dist/app/session-shell/session-viewport-controller.d.ts +13 -0
  12. package/dist/app/session-shell/session-viewport-controller.js +28 -1
  13. package/dist/cli/version-stats.js +4 -7
  14. package/dist/composition/owned-ui.d.ts +7 -0
  15. package/dist/composition/owned-ui.js +74 -6
  16. package/dist/contracts/owned-ui/image-attachments.d.ts +7 -4
  17. package/dist/contracts/owned-ui/image-attachments.js +20 -7
  18. package/dist/contracts/owned-ui/index.d.ts +1 -1
  19. package/dist/contracts/owned-ui/index.js +1 -1
  20. package/dist/contracts/owned-ui/prompt-chips.d.ts +2 -2
  21. package/dist/contracts/owned-ui/prompt-chips.js +9 -6
  22. package/dist/features/owned-ui/project-trust-prompt.js +39 -3
  23. package/dist/features/owned-ui/release-note-state.d.ts +13 -0
  24. package/dist/features/owned-ui/release-note-state.js +109 -0
  25. package/dist/features/owned-ui/release-notes.d.ts +10 -0
  26. package/dist/features/owned-ui/release-notes.js +73 -0
  27. package/dist/features/owned-ui/resources/release-notes.json +9 -0
  28. package/dist/foundation/release/latest-release.d.ts +55 -0
  29. package/dist/foundation/release/latest-release.js +166 -0
  30. package/dist/foundation/release/update.js +9 -0
  31. package/dist/integrations/pi/components/index.d.ts +1 -1
  32. package/dist/integrations/pi/components/index.js +1 -1
  33. package/dist/integrations/pi/components/prompt-chip-presentation.d.ts +3 -0
  34. package/dist/integrations/pi/components/prompt-chip-presentation.js +14 -0
  35. package/dist/integrations/pi/components/shell-footer-status.js +30 -9
  36. package/dist/integrations/pi/components/shell-presenters-info.d.ts +36 -0
  37. package/dist/integrations/pi/components/shell-presenters-info.js +77 -1
  38. package/dist/integrations/pi/components/shell-presenters-transcript.d.ts +0 -6
  39. package/dist/integrations/pi/components/shell-presenters-transcript.js +0 -19
  40. package/dist/integrations/pi/components/shell-shared-facade.d.ts +2 -1
  41. package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +1 -1
  42. package/dist/integrations/pi/components/submitted-prompt-adapter.js +13 -4
  43. package/dist/integrations/pi/engine/adapter.d.ts +12 -0
  44. package/dist/integrations/pi/engine/adapter.js +13 -0
  45. package/dist/integrations/pi/engine/session-runtime.d.ts +1 -0
  46. package/dist/integrations/pi/engine/session-runtime.js +2 -1
  47. package/dist/native/darwin-arm64/manifest.json +1 -1
  48. package/dist/native/linux-x64/manifest.json +1 -1
  49. package/dist/native/win32-x64/manifest.json +2 -2
  50. package/dist/native/win32-x64/process-guardian.exe +0 -0
  51. package/dist/product-identity.d.ts +1 -1
  52. package/dist/product-identity.js +1 -1
  53. package/dist/product-identity.json +1 -0
  54. package/dist/ui/settings/declarations.d.ts +25 -1
  55. package/dist/ui/settings/declarations.js +19 -1
  56. package/dist/ui/settings/migrations.js +14 -0
  57. package/docs/architecture/internal-naming.md +2 -2
  58. package/docs/architecture/resource-and-data-policy.md +11 -0
  59. package/docs/architecture/toolchain.md +1 -1
  60. package/docs/architecture/ui-reference-provenance.md +1 -0
  61. package/docs/ci-release-runbook.md +159 -70
  62. package/docs/local-worktree-cleanup.md +21 -4
  63. package/docs/manual-owned-ui-checkpoint.md +1 -0
  64. package/docs/openspec-archive-automation.md +1 -1
  65. package/docs/releases/0.2.2.md +47 -0
  66. package/docs/validation.md +13 -3
  67. package/package.json +3 -3
package/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  ## Install
4
4
 
5
5
  ```sh
6
- npx -y @timurproko/a1-install # release
7
- npx -y @timurproko/a1-install --develop # develop
8
- npx -y @timurproko/a1-install --develop 107 # preview number
9
- npx -y @timurproko/a1-install --develop 0.1.8-dev.107 # exact preview
6
+ npm x -y -- @timurproko/a1-install # release
7
+ npm x -y -- @timurproko/a1-install --develop # develop
8
+ npm x -y -- @timurproko/a1-install --develop 107 # preview number
9
+ npm x -y -- @timurproko/a1-install --develop 0.1.8-dev.107 # exact preview
10
10
  ```
11
11
 
12
12
  ## Use
@@ -14,7 +14,7 @@ npx -y @timurproko/a1-install --develop 0.1.8-dev.107 # exact preview
14
14
  ```sh
15
15
  a1 # launch A1 (profile: ~/.a1/agent)
16
16
  a1 help # show all commands
17
- a1 version # show the version
17
+ a1 version # show the current and latest versions
18
18
  a1 pi # vanilla Pi oracle: ~/.pi/agent
19
19
  ```
20
20
 
@@ -22,12 +22,14 @@ Update:
22
22
 
23
23
  ```sh
24
24
  a1 update # install the release
25
- a1 update --models # refresh A1's model catalogs
26
25
  a1 update --develop # install develop
27
26
  a1 update --develop 107 # install preview 107
28
27
  a1 update --develop 0.1.8-dev.107 # install that exact preview
28
+ a1 update --models # refresh A1's model catalogs
29
29
  ```
30
30
 
31
+ On interactive startup A1 checks, at most once a day, whether a newer release exists on its own channel and shows an `Update Available` notice with the command to run. It never installs anything. Turn it off with the `Update check` setting (Generic section), or set `A1_SKIP_VERSION_CHECK=1`; it is also skipped when `PI_OFFLINE` or `CI` is set, or when output is not a terminal. The check reads the public npm registry, or `npm_config_registry` when set.
32
+
31
33
  ## Extensions
32
34
 
33
35
  ```sh
@@ -46,13 +48,13 @@ a1 update npm:pi-mcp-adapter # update one
46
48
  ## Develop
47
49
 
48
50
  ```sh
49
- npm run doctor # report Node, npm, git, Rust, and dependency readiness
50
51
  npm ci # install exact locked dependencies
51
52
  npm run build # compile TypeScript and the process guardian into dist
52
53
  npm start # build and launch a source `a1`
53
54
  npm run start:pi # build and launch a source `a1 pi`
54
55
  npm run test:fast # typecheck + fast suite (alias: npm test)
55
56
  npm run test:full # complete non-physical suite
57
+ npm run doctor # report Node, npm, git, Rust, and dependency readiness
56
58
  ```
57
59
 
58
60
  ## Publish
@@ -69,5 +71,11 @@ npm run develop # request the preview publish
69
71
  npm run release -- patch # 0.1.8-dev -> 0.1.8
70
72
  npm run release -- minor # 0.1.8-dev -> 0.2.0
71
73
  npm run release -- major # 0.1.8-dev -> 1.0.0
72
- npm run release -- 0.4.0 # an exact stable version
74
+ npm run release -- 0.4.0 # prepare an exact stable version
73
75
  ```
76
+
77
+ The command creates the draft, starts validation of its source, prints the progress
78
+ link, and waits. When validation passes it prints the draft link: edit the Pi-style
79
+ `## [version] - YYYY-MM-DD` changelog, then choose
80
+ GitHub's native **Publish release** button: that publishes both packages to npm. If
81
+ publication fails before npm, the Release returns to draft so you can publish it again.
@@ -2,16 +2,23 @@ import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, posix, resolve, sep } from "node:path";
3
3
  import { fileURLToPath, pathToFileURL } from "node:url";
4
4
 
5
+ /** @type {string | undefined} */
6
+ let configuredRoot;
7
+
5
8
  /**
6
9
  * Bind the generated facade to the package reached through Pi's public entry.
7
10
  * @param {string | URL} entryUrl
8
11
  */
9
12
  export function configurePinnedPiPublicPackageEntry(entryUrl) {
10
- const root = dirname(dirname(fileURLToPath(entryUrl)));
13
+ const root = resolve(dirname(dirname(fileURLToPath(entryUrl))));
11
14
  const manifest = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
12
15
  if (manifest.name !== "@earendil-works/pi-coding-agent" || typeof manifest.version !== "string") {
13
16
  throw new Error("pinned Pi public package identity is invalid");
14
17
  }
18
+ if (configuredRoot !== undefined && configuredRoot !== root) {
19
+ throw new Error("pinned Pi public package identity cannot change after configuration");
20
+ }
21
+ configuredRoot = root;
15
22
  process.env.PI_PACKAGE_DIR = root;
16
23
  return { root, version: manifest.version };
17
24
  }
@@ -55,9 +62,9 @@ export function resolvePinnedPiImport(specifier) {
55
62
  }
56
63
 
57
64
  function pinnedRoot() {
58
- const root = process.env.PI_PACKAGE_DIR;
59
- if (!root || !existsSync(resolve(root, "package.json"))) throw new Error("pinned Pi public package directory is not configured");
60
- return resolve(root);
65
+ if (configuredRoot === undefined) throw new Error("pinned Pi public package directory is not configured");
66
+ if (!existsSync(resolve(configuredRoot, "package.json"))) throw new Error("pinned Pi public package directory is unavailable");
67
+ return configuredRoot;
61
68
  }
62
69
 
63
70
  /**
@@ -2,6 +2,6 @@ export { ClipboardDiagnosticCapture } from "./clipboard-diagnostics.js";
2
2
  export { ContextualPromptSuggestionController, samePromptSuggestionIdentity } from "./prompt-suggestion-controller.js";
3
3
  export type { ContextualPromptSuggestionControllerOptions, ContextualPromptSuggestionSurface } from "./prompt-suggestion-controller.js";
4
4
  export { OwnedUiSessionShell, OwnedUiSessionShellRoot, formatSessionResumeCommand, quoteCommandArgument } from "./session-shell.js";
5
- export type { OwnedUiSessionShellOptions, OwnedUiShellDiagnosticOptions, OwnedUiShellEngineOptions, OwnedUiShellHistoryOptions, OwnedUiShellPresentationOptions, OwnedUiShellSkillsOptions, OwnedUiShellSuggestionOptions, SessionResumeCommandMetadata, } from "./session-shell.js";
5
+ export type { OwnedUiSessionShellOptions, OwnedUiShellDiagnosticOptions, OwnedUiShellEngineOptions, OwnedUiShellHistoryOptions, OwnedUiShellPresentationOptions, OwnedUiShellPromptImagesOptions, OwnedUiShellSkillsOptions, OwnedUiShellSuggestionOptions, SessionResumeCommandMetadata, } from "./session-shell.js";
6
6
  export { STREAM_PRESENTATION_INTERVAL_MS, StreamPresentationCoalescer } from "./stream-presentation-coalescer.js";
7
7
  export type { StreamPresentationScheduler } from "./stream-presentation-coalescer.js";
@@ -181,7 +181,12 @@ export function startPasteExecutor(content, signal, phase = () => { }, helper, p
181
181
  fail();
182
182
  }
183
183
  catch (error) {
184
- fail(error instanceof ImageAttachmentError ? error.code : "paste-unavailable");
184
+ if (error instanceof ImageAttachmentError) {
185
+ failure = error;
186
+ cancel();
187
+ }
188
+ else
189
+ fail("paste-unavailable");
185
190
  }
186
191
  });
187
192
  child.once("error", () => fail());
@@ -17,6 +17,7 @@ export declare class PromptChipStore {
17
17
  readonly isolated?: boolean;
18
18
  readonly onEvent?: (event: PasteEvent) => void;
19
19
  readonly preparation?: Omit<PastePreparationClientOptions, "onEvent">;
20
+ readonly imageLimit?: () => number;
20
21
  });
21
22
  /** Forks the spare paste helper so the session's first paste does not pay for helper startup. */
22
23
  warm(): void;
@@ -31,10 +32,17 @@ export declare class PromptChipStore {
31
32
  reconcileDraft(text: string): void;
32
33
  resetPastes(text: string): string;
33
34
  dispose(): Promise<void>;
35
+ imageLimitState(text: string): {
36
+ readonly count: number;
37
+ readonly limit: number;
38
+ readonly corrected: boolean;
39
+ };
34
40
  transformPastedContent(content: PiShellClipboardContent, currentText?: string): string;
35
41
  /** Hide provisional clipboard identities until the read identifies an actual image. */
36
42
  hiddenRanges(line: string): readonly PiShellEditorTextRange[];
37
43
  atomicRanges(line: string): readonly PiShellEditorTextRange[];
44
+ unsentRanges(line: string): readonly PiShellEditorTextRange[];
45
+ omitUnsentImages(text: string): string;
38
46
  hyperlinkRanges(text: string): readonly {
39
47
  start: number;
40
48
  end: number;
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import path from "node:path";
3
3
  import { canonicalizeClipboardImage } from "./clipboard-image.js";
4
- import { assertImageEncodedSize, assertPromptImages, canonicalPromptChipMatches, ImageAttachmentError, replaceCanonicalPromptChips, } from "../../contracts/owned-ui/index.js";
4
+ import { assertImageEncodedSize, assertPromptImages, canonicalPromptChipMatches, DEFAULT_PROMPT_IMAGE_LIMIT, ImageAttachmentError, replaceCanonicalPromptChips, } from "../../contracts/owned-ui/index.js";
5
5
  import { ImagePreparationClient } from "./image-preparation-client.js";
6
6
  import { preparePasteText } from "./paste-text-preparation.js";
7
7
  import { pathChipTag } from "./path-chip-presentation.js";
@@ -13,18 +13,29 @@ function imageChipIdentifier(tag) {
13
13
  const match = IMAGE_CHIP_IDENTIFIER_PATTERN.exec(tag);
14
14
  return match === null ? null : match[1] ?? null;
15
15
  }
16
+ function rejectedImageMarker(marker, error) {
17
+ return error.code === "image-count" ? marker : marker.replace("screenshot-", "failed-");
18
+ }
19
+ function referencesPasteMarker(text, entry) {
20
+ return text.includes(entry.marker)
21
+ || (entry.error !== undefined && entry.replacement !== undefined && text.includes(entry.replacement));
22
+ }
16
23
  /** Owns semantic chips and bounded pending paste references; cancels background work on reset or disposal. */
17
24
  export class PromptChipStore {
18
25
  #chips = new Map();
19
26
  // Invariant: clearing the editor never recycles a recoverable text chip's identity.
20
27
  #textCounter = 0;
21
28
  #pending = new Map();
29
+ // Invariant: count-rejected images keep the standard chip label, so retain their identities after draft cleanup.
30
+ #countRejectedImages = new Set();
22
31
  #preparation = new ImagePreparationClient();
23
32
  #stopping = new Set();
24
33
  #isolated;
25
34
  #provisionalOwners = new Map();
26
35
  #ownedChipTags = new Map();
36
+ #imageLimit;
27
37
  constructor(options = {}) {
38
+ this.#imageLimit = options.imageLimit ?? (() => DEFAULT_PROMPT_IMAGE_LIMIT);
28
39
  this.#isolated = options.isolated
29
40
  ? new PastePreparationClient({ ...options.preparation, ...(options.onEvent === undefined ? {} : { onEvent: options.onEvent }) })
30
41
  : undefined;
@@ -44,8 +55,9 @@ export class PromptChipStore {
44
55
  entry.kind = content?.kind === "image" ? "image" : "text";
45
56
  if (content?.kind === "image") {
46
57
  onImage();
47
- if (this.#imageCount(currentText) >= 8)
48
- throw new ImageAttachmentError("image-count");
58
+ const imageLimit = this.#imageLimit();
59
+ if (this.#imageCount(currentText) >= imageLimit)
60
+ throw new ImageAttachmentError("image-count", imageLimit);
49
61
  }
50
62
  return content;
51
63
  });
@@ -60,9 +72,11 @@ export class PromptChipStore {
60
72
  entry.error = error instanceof ImageAttachmentError ? error : new ImageAttachmentError("image-codec");
61
73
  if (entry.error.code !== "image-canceled" && entry.references === 0)
62
74
  onError(entry.error);
63
- return marker.replace("screenshot-", "failed-");
75
+ return rejectedImageMarker(marker, entry.error);
64
76
  }).then(replacement => {
65
77
  entry.replacement = replacement;
78
+ if (entry.error?.code === "image-count")
79
+ this.#countRejectedImages.add(replacement);
66
80
  return replacement;
67
81
  }) };
68
82
  this.#pending.set(marker, entry);
@@ -86,8 +100,9 @@ export class PromptChipStore {
86
100
  }, () => {
87
101
  entry.kind = "image";
88
102
  onImage();
89
- if (this.#imageCount(currentText) >= 8)
90
- throw new ImageAttachmentError("image-count");
103
+ const imageLimit = this.#imageLimit();
104
+ if (this.#imageCount(currentText) >= imageLimit)
105
+ throw new ImageAttachmentError("image-count", imageLimit);
91
106
  }, () => { entry.kind = "text"; });
92
107
  entry = { marker, job, references: 0, kind: "unknown", completion: job.result.catch(error => {
93
108
  this.#finishChipOwnership(owner, false);
@@ -97,8 +112,13 @@ export class PromptChipStore {
97
112
  entry.kind = "image";
98
113
  if (entry.error.code !== "image-canceled" && entry.references === 0)
99
114
  onError(entry.error);
100
- return entry.kind === "image" ? marker.replace("screenshot-", "failed-") : "";
101
- }).then(replacement => { entry.replacement = replacement; return replacement; }) };
115
+ return entry.kind === "image" ? rejectedImageMarker(marker, entry.error) : "";
116
+ }).then(replacement => {
117
+ entry.replacement = replacement;
118
+ if (entry.error?.code === "image-count")
119
+ this.#countRejectedImages.add(replacement);
120
+ return replacement;
121
+ }) };
102
122
  entry.onComplete = () => {
103
123
  this.#finishChipOwnership(owner, job.isCurrent() && entry.error === undefined);
104
124
  job.complete();
@@ -152,7 +172,7 @@ export class PromptChipStore {
152
172
  }
153
173
  reconcileDraft(text) {
154
174
  for (const entry of this.#pending.values()) {
155
- if (entry.references === 0 && !text.includes(entry.marker) && !text.includes(entry.marker.replace("screenshot-", "failed-"))) {
175
+ if (entry.references === 0 && !referencesPasteMarker(text, entry)) {
156
176
  entry.job.cancel();
157
177
  if (entry.replacement !== undefined) {
158
178
  entry.onComplete?.();
@@ -180,13 +200,21 @@ export class PromptChipStore {
180
200
  await Promise.all([this.#preparation.dispose(), this.#isolated?.dispose(), ...this.#stopping]);
181
201
  this.#chips.clear();
182
202
  this.#pending.clear();
203
+ this.#countRejectedImages.clear();
183
204
  this.#provisionalOwners.clear();
184
205
  this.#ownedChipTags.clear();
185
206
  }
207
+ imageLimitState(text) {
208
+ const limit = this.#imageLimit();
209
+ const rejectedCountMarker = canonicalPromptChipMatches(text).some(match => this.#countRejectedImages.has(match.text))
210
+ || [...this.#pending.values()].some(entry => entry.error?.code === "image-count" && referencesPasteMarker(text, entry));
211
+ const count = this.#imageCount(text);
212
+ return { count, limit, corrected: count <= limit && !rejectedCountMarker };
213
+ }
186
214
  #imageCount(text) {
187
215
  const tags = new Set([...this.#chips.values()].filter(chip => chip.kind === "image" && text.includes(chip.tag)).map(chip => chip.tag));
188
216
  for (const entry of this.#pending.values()) {
189
- if (entry.kind !== "text" && (text.includes(entry.marker) || text.includes(entry.marker.replace("screenshot-", "failed-")))) {
217
+ if (entry.kind !== "text" && entry.error?.code !== "image-count" && referencesPasteMarker(text, entry)) {
190
218
  // Invariant: a ready screenshot can retain its pending label but still occupies only one slot.
191
219
  tags.add(entry.replacement ?? entry.marker);
192
220
  }
@@ -207,7 +235,7 @@ export class PromptChipStore {
207
235
  const image = canonicalizeClipboardImage(content);
208
236
  if (image === null)
209
237
  return "";
210
- assertPromptImages([...this.prepareSubmission(currentText).images, { type: "image", ...image }]);
238
+ assertPromptImages([...this.prepareSubmission(currentText).images, { type: "image", ...image }], this.#imageLimit());
211
239
  const id = randomBytes(5).toString("hex");
212
240
  const tag = `[📷 screenshot-${id}]`;
213
241
  this.#chips.set(tag, {
@@ -262,6 +290,15 @@ export class PromptChipStore {
262
290
  .filter(match => !match.text.startsWith("[paste #") || this.#chips.has(match.text))
263
291
  .map(match => ({ start: match.start, end: match.end }));
264
292
  }
293
+ unsentRanges(line) {
294
+ return canonicalPromptChipMatches(line)
295
+ .filter(match => this.#countRejectedImages.has(match.text))
296
+ .map(match => ({ start: match.start, end: match.end }));
297
+ }
298
+ omitUnsentImages(text) {
299
+ return replaceCanonicalPromptChips(text, match => this.#countRejectedImages.has(match.text)
300
+ || this.#pendingEntry(match.text)?.error?.code === "image-count" ? "" : match.text);
301
+ }
265
302
  hyperlinkRanges(text) {
266
303
  const ranges = [];
267
304
  for (const chip of this.#chips.values()) {
@@ -292,7 +329,7 @@ export class PromptChipStore {
292
329
  * chips carrying the original attachment bytes.
293
330
  */
294
331
  prepareHistoryText(text) {
295
- return this.#replaceResolvable(text, false, false).text.trim();
332
+ return this.#replaceResolvable(this.omitUnsentImages(text), false, false).text.trim();
296
333
  }
297
334
  /**
298
335
  * Extract the image chip attachments referenced by an editor draft in occurrence order,
@@ -396,6 +433,7 @@ export class PromptChipStore {
396
433
  }
397
434
  prepareSubmission(text) {
398
435
  const expanded = this.#replaceResolvable(text, true);
436
+ assertPromptImages(expanded.images, this.#imageLimit());
399
437
  return { text: expanded.text, images: expanded.images };
400
438
  }
401
439
  #replaceResolvable(text, includeImages, omitImages = false) {
@@ -419,9 +457,13 @@ export class PromptChipStore {
419
457
  // Invariant: scan only draft tokens (and resolved reservation tokens), never emitted payloads.
420
458
  const expanded = replaceCanonicalPromptChips(text, match => {
421
459
  const tag = match.text;
422
- const entry = this.#pending.get(tag) ?? this.#pending.get(tag.replace("failed-", "screenshot-"));
460
+ if (this.#countRejectedImages.has(tag))
461
+ return "";
462
+ const entry = this.#pendingEntry(tag);
423
463
  if (entry === undefined)
424
464
  return resolveChip(tag);
465
+ if (entry.error?.code === "image-count")
466
+ return "";
425
467
  if (includeImages && entry.error !== undefined)
426
468
  throw entry.error;
427
469
  if (includeImages && entry.replacement === undefined)
@@ -430,6 +472,9 @@ export class PromptChipStore {
430
472
  });
431
473
  return { text: expanded, images };
432
474
  }
475
+ #pendingEntry(tag) {
476
+ return this.#pending.get(tag) ?? [...this.#pending.values()].find(entry => entry.error !== undefined && entry.replacement === tag);
477
+ }
433
478
  #claimChip(tag, owner, created) {
434
479
  if (owner === undefined) {
435
480
  this.#provisionalOwners.delete(tag);
@@ -1,6 +1,7 @@
1
+ import { ImageAttachmentError } from "../../contracts/owned-ui/index.js";
1
2
  import type { OwnedUiPromptSuggestionGeneratorPort, OwnedUiSessionViewModel, OwnedUiViewportSettings, OwnedUiViewportSettingsPort, OwnedUiQuitOutroSettingsPort, SuggestionDecision, SuggestionDiagnosticObserver } from "../../contracts/owned-ui/index.js";
2
3
  import type { PiTuiPointerSurface } from "../../integrations/pi/tui-runtime/contracts.js";
3
- import type { UiRouteHost } from "../../ui/apps/contracts.js";
4
+ import type { UiRouteHost, UiRouteInput } from "../../ui/apps/contracts.js";
4
5
  import type { TranscriptViewportFrame, TranscriptViewportFrameDescriptor } from "../../ui/components/transcript-viewport.js";
5
6
  import type { PiEngineAdapter } from "../../integrations/pi/engine/adapter.js";
6
7
  import type { PiWorkflowMessage, PiWorkflowResult } from "../../integrations/pi/engine/workflows.js";
@@ -35,6 +36,12 @@ export interface OwnedUiShellEngineOptions {
35
36
  * every other route continues to the pinned workflow table unchanged.
36
37
  */
37
38
  readonly routeHost?: UiRouteHost;
39
+ /** One product-owned route presented after the first usable frame and any startup modal. */
40
+ readonly startupRoute?: {
41
+ readonly route: string;
42
+ readonly input?: UiRouteInput;
43
+ readonly onClosed?: () => void | Promise<void>;
44
+ };
38
45
  /** Bare A1 selects the owned bounded viewport; comparison profiles stay pinned. */
39
46
  readonly sessionLayout?: "pinned" | "custom-viewport";
40
47
  }
@@ -110,6 +117,11 @@ export interface OwnedUiShellDiagnosticOptions {
110
117
  /** Test seams for isolated paste preparation: an in-process starter, or the forked helper's entry. */
111
118
  readonly pastePreparation?: Omit<PastePreparationClientOptions, "onEvent" | "spareIdleMs">;
112
119
  }
120
+ /** Live prompt-image admission policy supplied only by the bare-A1 profile. */
121
+ export interface OwnedUiShellPromptImagesOptions {
122
+ readonly limit: () => number;
123
+ readonly onChange: (listener: () => void) => () => void;
124
+ }
113
125
  /** The skills presentation choice: collapse the per-skill commands into one skills command or expand them. */
114
126
  export interface OwnedUiShellSkillsOptions {
115
127
  readonly presentation: () => "collapse" | "expand";
@@ -121,6 +133,8 @@ export interface OwnedUiSessionShellOptions {
121
133
  readonly presentation?: OwnedUiShellPresentationOptions;
122
134
  readonly history?: OwnedUiShellHistoryOptions;
123
135
  readonly suggestions?: OwnedUiShellSuggestionOptions;
136
+ /** Supplied only to bare A1; absent retains the fixed eight-image comparison policy. */
137
+ readonly promptImages?: OwnedUiShellPromptImagesOptions;
124
138
  /** Supplied only to the bare-A1 composition; absent keeps the pinned per-skill command list. */
125
139
  readonly skills?: OwnedUiShellSkillsOptions;
126
140
  readonly diagnostics?: OwnedUiShellDiagnosticOptions;
@@ -160,6 +174,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
160
174
  readonly captureClipboardPaste?: () => PasteSource;
161
175
  readonly pasteDiagnostics?: (event: PasteEvent) => void;
162
176
  readonly pastePreparation?: Omit<PastePreparationClientOptions, "onEvent">;
177
+ readonly promptImageLimit?: () => number;
163
178
  readonly skillsPresentation?: () => "collapse" | "expand";
164
179
  }, startup?: PiShellHeaderOptions, agentDir?: string, extensionRenderers?: PiShellExtensionRendererResolver, sessionLayout?: "pinned" | "custom-viewport", imageAssets?: PiShellImageAssetResolver);
165
180
  setEditorPaddingX(padding: number): void;
@@ -169,6 +184,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
169
184
  setMermaidRenderingMode(mode: "off" | "final" | "streaming"): void;
170
185
  setImagePresentation(showImages: boolean, imageWidthCells: number): void;
171
186
  prepareHistoryText(text: string): string;
187
+ omitUnsentPromptImages(text: string): string;
172
188
  rehydrateHistoryText(text: string, resolveImage: (id: string) => import("../../contracts/owned-ui/index.js").PromptHistoryImageSidecarAttachment | null): string;
173
189
  imageChipAttachmentsFromEditor(text: string): {
174
190
  readonly id: string;
@@ -180,6 +196,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
180
196
  };
181
197
  }[];
182
198
  preparePromptSubmission(text: string): PreparedPrompt;
199
+ reconcilePromptImageLimitNotice(text?: string): boolean;
183
200
  hasPendingPastes(text: string): boolean;
184
201
  waitForPromptPastes(text: string, signal: AbortSignal): Promise<PreparedPrompt>;
185
202
  resetPendingPastes(): void;
@@ -244,8 +261,8 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
244
261
  transcriptComponent(id: string): PiShellTranscriptComponentPort | undefined;
245
262
  exitTranscript(width: number): string;
246
263
  appendWorkflowStatus(message: string): void;
247
- appendWorkflowMessage(message: PiWorkflowMessage): void;
248
- appendWorkflowResult(result: PiWorkflowResult): void;
264
+ appendWorkflowMessage(message: PiWorkflowMessage, errorCode?: ImageAttachmentError["code"]): void;
265
+ appendWorkflowResult(result: PiWorkflowResult, errorCode?: ImageAttachmentError["code"]): void;
249
266
  appendDaxnuts(): void;
250
267
  toggleThinkingVisibility(): void;
251
268
  get toolsExpanded(): boolean;
@@ -255,6 +272,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
255
272
  setExtensionFooter(component: PiShellComponentPort | null): void;
256
273
  setExtensionStatus(key: string, text: string | undefined): void;
257
274
  setExtensionWorking(message: string | undefined, visible?: boolean): void;
275
+ beginImageSubmissionStatus(): () => void;
258
276
  hotkeysPresentation(): PiShellHotkeysPresentation;
259
277
  resetWorkflowPresentation(): void;
260
278
  resetExtensionUi(): void;