@quandev104/pi-style 0.2.2 → 0.2.3

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.
@@ -1,3 +1,352 @@
1
+ // extension-src/pi-style/features/messages/image-input.ts
2
+ import { readFile as readFile2, stat as stat2 } from "fs/promises";
3
+ import { tmpdir as tmpdir2 } from "os";
4
+
5
+ // extension-src/pi-style/shared/clipboard-path.ts
6
+ import { tmpdir } from "os";
7
+ var UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
8
+ var CLIPBOARD_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "gif"];
9
+ function clipboardImageMarker(index) {
10
+ return `[Image #${index}]`;
11
+ }
12
+ function escapeRegExp(text) {
13
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14
+ }
15
+ var clipboardPathRegexCache = /* @__PURE__ */ new Map();
16
+ var CLIPBOARD_PATH_REGEX_CACHE_MAX = 16;
17
+ function clipboardPathRegex(tmpRoot = tmpdir()) {
18
+ const cached = clipboardPathRegexCache.get(tmpRoot);
19
+ if (cached) return cached;
20
+ const exts = CLIPBOARD_IMAGE_EXTENSIONS.join("|");
21
+ const regex = new RegExp(
22
+ `(?<![a-zA-Z0-9])(${escapeRegExp(tmpRoot)}/pi-clipboard-${UUID_PATTERN}\\.(${exts}))(?![a-zA-Z0-9])`,
23
+ "g"
24
+ );
25
+ if (clipboardPathRegexCache.size >= CLIPBOARD_PATH_REGEX_CACHE_MAX) {
26
+ const oldest = clipboardPathRegexCache.keys().next().value;
27
+ if (oldest !== void 0) clipboardPathRegexCache.delete(oldest);
28
+ }
29
+ clipboardPathRegexCache.set(tmpRoot, regex);
30
+ return regex;
31
+ }
32
+ function isSingleClipboardImagePath(text, tmpRoot) {
33
+ if (!text || /\s/.test(text)) return false;
34
+ const match = text.match(clipboardPathRegex(tmpRoot ?? tmpdir()));
35
+ return match !== null && match.length === 1 && match[0] === text;
36
+ }
37
+ function extractClipboardImageTokens(text, tmpRoot) {
38
+ const tokens = /* @__PURE__ */ new Set();
39
+ for (const match of text.matchAll(clipboardPathRegex(tmpRoot ?? tmpdir()))) {
40
+ const token = match[1];
41
+ if (token) tokens.add(token);
42
+ }
43
+ return [...tokens];
44
+ }
45
+ var MARKER_AT_END_PATTERN = /\[Image #([0-9]+)\]( ?)$/;
46
+ function clipboardMarkerAtEnd(text) {
47
+ const match = MARKER_AT_END_PATTERN.exec(text);
48
+ if (!match) return void 0;
49
+ return { index: Number(match[1]), length: match[0].length };
50
+ }
51
+
52
+ // extension-src/pi-style/shared/clipboard-presence.ts
53
+ import { realpathSync } from "fs";
54
+ import { createRequire } from "module";
55
+ import { dirname } from "path";
56
+ var cachedModule;
57
+ function clipboardResolutionRoots() {
58
+ const roots = [];
59
+ const argvMain = process.argv[1];
60
+ if (typeof argvMain === "string" && argvMain.startsWith("/")) {
61
+ try {
62
+ roots.push(dirname(realpathSync(argvMain)));
63
+ } catch {
64
+ roots.push(dirname(argvMain));
65
+ }
66
+ }
67
+ roots.push(import.meta.url);
68
+ return roots;
69
+ }
70
+ function loadClipboardModule() {
71
+ if (cachedModule !== void 0) return cachedModule;
72
+ if (process.env.TERMUX_VERSION) {
73
+ cachedModule = null;
74
+ return cachedModule;
75
+ }
76
+ for (const root of clipboardResolutionRoots()) {
77
+ try {
78
+ const require2 = createRequire(root);
79
+ const resolved = require2("@mariozechner/clipboard");
80
+ if (resolved && typeof resolved.hasImage === "function") {
81
+ cachedModule = resolved;
82
+ return cachedModule;
83
+ }
84
+ } catch {
85
+ }
86
+ }
87
+ cachedModule = null;
88
+ return cachedModule;
89
+ }
90
+ function clipboardHasImageSync() {
91
+ const clipboard = loadClipboardModule();
92
+ if (!clipboard || typeof clipboard.hasImage !== "function") return null;
93
+ try {
94
+ return clipboard.hasImage() === true;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+ async function readClipboardImageBinary(options = {}) {
100
+ const clipboard = loadClipboardModule();
101
+ if (!clipboard || typeof clipboard.hasImage !== "function" || typeof clipboard.getImageBinary !== "function") {
102
+ return null;
103
+ }
104
+ try {
105
+ if (!options.skipPresenceProbe && !clipboard.hasImage()) return null;
106
+ const imageData = await clipboard.getImageBinary();
107
+ if (!imageData || imageData.length === 0) return null;
108
+ const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
109
+ return bytes.length > 0 ? { bytes } : null;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ // extension-src/pi-style/shared/pending-images.ts
116
+ import { readFile, stat } from "fs/promises";
117
+ var MAX_ATTACHABLE_BYTES = 20 * 1024 * 1024;
118
+ var MIME_BY_EXTENSION = Object.freeze({
119
+ png: "image/png",
120
+ jpg: "image/jpeg",
121
+ jpeg: "image/jpeg",
122
+ webp: "image/webp",
123
+ gif: "image/gif"
124
+ });
125
+ var pendingImages = /* @__PURE__ */ new Map();
126
+ var nextPendingIndex = 1;
127
+ function resetPendingImageRegistry() {
128
+ pendingImages.clear();
129
+ nextPendingIndex = 1;
130
+ }
131
+ function allocatePendingImage() {
132
+ const index = nextPendingIndex++;
133
+ let resolveFill = () => {
134
+ };
135
+ const entry = {
136
+ fill: new Promise((resolve3) => {
137
+ resolveFill = resolve3;
138
+ }),
139
+ settled: false
140
+ };
141
+ pendingImages.set(index, entry);
142
+ entry.resolveFill = resolveFill;
143
+ return { index, marker: `${clipboardImageMarker(index)} ` };
144
+ }
145
+ async function fillPendingImageFromPath(index, path, deps = {}) {
146
+ const entry = pendingImages.get(index);
147
+ if (!entry || entry.settled) return;
148
+ const extension = path.split(".").pop() ?? "";
149
+ const mimeType = MIME_BY_EXTENSION[extension];
150
+ if (!mimeType) {
151
+ settle(entry);
152
+ return;
153
+ }
154
+ const readFileImpl = deps.readFile ?? ((p) => readFile(p));
155
+ const statSizeImpl = deps.statSize ?? (async (p) => {
156
+ try {
157
+ return (await stat(p)).size;
158
+ } catch {
159
+ return void 0;
160
+ }
161
+ });
162
+ try {
163
+ const size = await statSizeImpl(path);
164
+ if (size === void 0 || size <= 0 || size > MAX_ATTACHABLE_BYTES) {
165
+ settle(entry);
166
+ return;
167
+ }
168
+ const bytes = await readFileImpl(path);
169
+ if (bytes.length === 0 || bytes.length > MAX_ATTACHABLE_BYTES) {
170
+ settle(entry);
171
+ return;
172
+ }
173
+ entry.data = Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64");
174
+ entry.mimeType = mimeType;
175
+ } catch {
176
+ }
177
+ settle(entry);
178
+ }
179
+ function fillPendingImageBytes(index, data, mimeType) {
180
+ const entry = pendingImages.get(index);
181
+ if (!entry || entry.settled) return;
182
+ if (data.length > 0) {
183
+ entry.data = data;
184
+ entry.mimeType = mimeType;
185
+ }
186
+ settle(entry);
187
+ }
188
+ function discardPendingImage(index) {
189
+ const entry = pendingImages.get(index);
190
+ if (!entry) return;
191
+ settle(entry);
192
+ pendingImages.delete(index);
193
+ }
194
+ function isPendingMarkerIndex(index) {
195
+ return pendingImages.has(index);
196
+ }
197
+ function discardPendingMarkerIndex(index) {
198
+ discardPendingImage(index);
199
+ }
200
+ function isPendingImageFilled(index) {
201
+ const entry = pendingImages.get(index);
202
+ return entry !== void 0 && entry.data !== void 0 && entry.mimeType !== void 0;
203
+ }
204
+ function settle(entry) {
205
+ entry.settled = true;
206
+ entry.resolveFill?.();
207
+ }
208
+ async function resolvePendingImagesForSubmit(text) {
209
+ if (pendingImages.size === 0) return void 0;
210
+ const markerRe = /\[Image #([0-9]+)\]/g;
211
+ const found = [];
212
+ for (const match of text.matchAll(markerRe)) {
213
+ const index = Number(match[1]);
214
+ if (Number.isInteger(index) && pendingImages.has(index)) found.push(index);
215
+ }
216
+ const matched = new Set(found);
217
+ await Promise.all(
218
+ [...pendingImages.entries()].filter(([index]) => matched.has(index)).map(([, entry]) => entry.fill)
219
+ );
220
+ const images = [];
221
+ for (const index of [...matched].sort((a, b) => a - b)) {
222
+ const entry = pendingImages.get(index);
223
+ if (entry?.data && entry.mimeType) {
224
+ images.push({ type: "image", data: entry.data, mimeType: entry.mimeType });
225
+ }
226
+ }
227
+ for (const index of [...pendingImages.keys()]) discardPendingImage(index);
228
+ return images.length > 0 ? { images } : void 0;
229
+ }
230
+
231
+ // extension-src/pi-style/features/messages/render-config.ts
232
+ var sessionMessagesConfig = {
233
+ showImagePreviews: true,
234
+ clipboardImages: true,
235
+ previewMaxWidth: 30
236
+ };
237
+ function setMessagesRenderConfig(config) {
238
+ sessionMessagesConfig = { ...sessionMessagesConfig, ...config };
239
+ }
240
+ function getMessagesRenderConfig() {
241
+ return sessionMessagesConfig;
242
+ }
243
+
244
+ // extension-src/pi-style/features/messages/image-input.ts
245
+ var IMAGE_TOKEN = "[image]";
246
+ var MAX_ATTACHABLE_BYTES2 = 20 * 1024 * 1024;
247
+ var MIME_BY_EXTENSION2 = Object.freeze({
248
+ png: "image/png",
249
+ jpg: "image/jpeg",
250
+ jpeg: "image/jpeg",
251
+ webp: "image/webp",
252
+ gif: "image/gif"
253
+ });
254
+ function createClipboardImagePasteSurface(deps = {}) {
255
+ const hasImage = deps.hasImage ?? clipboardHasImageSync;
256
+ const readBinary = deps.readBinary ?? (() => readClipboardImageBinary({ skipPresenceProbe: true }));
257
+ return {
258
+ enabled: () => getMessagesRenderConfig().clipboardImages,
259
+ clipboardHasImage: () => {
260
+ try {
261
+ return hasImage();
262
+ } catch {
263
+ return null;
264
+ }
265
+ },
266
+ markerFromClipboard() {
267
+ const { index, marker: marker2 } = allocatePendingImage();
268
+ void (async () => {
269
+ try {
270
+ const image = await readBinary();
271
+ if (image) {
272
+ fillPendingImageBytes(
273
+ index,
274
+ Buffer.from(image.bytes.buffer, image.bytes.byteOffset, image.bytes.byteLength).toString("base64"),
275
+ "image/png"
276
+ );
277
+ return;
278
+ }
279
+ } catch {
280
+ }
281
+ discardPendingMarkerIndex(index);
282
+ })();
283
+ return marker2;
284
+ },
285
+ async markerFromArtifact(path) {
286
+ const { index, marker: marker2 } = allocatePendingImage();
287
+ await fillPendingImageFromPath(index, path);
288
+ return isPendingImageFilled(index) ? marker2 : path;
289
+ },
290
+ isMarkerIndexRegistered: (index) => isPendingMarkerIndex(index),
291
+ discardMarkerIndex: (index) => discardPendingMarkerIndex(index)
292
+ };
293
+ }
294
+ async function resolvePendingImageMarkers(text) {
295
+ if (!getMessagesRenderConfig().clipboardImages) return void 0;
296
+ return resolvePendingImagesForSubmit(text);
297
+ }
298
+ async function transformClipboardImages(text, deps = {}) {
299
+ if (!getMessagesRenderConfig().clipboardImages) return void 0;
300
+ const tokens = extractClipboardImageTokens(text, deps.tmpRoot);
301
+ if (tokens.length === 0) return void 0;
302
+ const attached = [];
303
+ const rewritten = /* @__PURE__ */ new Map();
304
+ for (const token of tokens) {
305
+ const image = await readAttachable(token, deps);
306
+ if (!image) continue;
307
+ attached.push({ type: "image", data: image.data, mimeType: image.mimeType });
308
+ rewritten.set(token, IMAGE_TOKEN);
309
+ }
310
+ if (attached.length === 0) return void 0;
311
+ return { text: rewriteTokens(text, rewritten, deps.tmpRoot), images: attached };
312
+ }
313
+ async function readAttachable(path, deps) {
314
+ const extension = path.split(".").pop() ?? "";
315
+ const mimeType = MIME_BY_EXTENSION2[extension];
316
+ if (!mimeType) return void 0;
317
+ const readFileImpl = deps?.readFile ?? ((p) => readFile2(p));
318
+ const statSizeImpl = deps?.statSize ?? (async (p) => {
319
+ try {
320
+ return (await stat2(p)).size;
321
+ } catch {
322
+ return void 0;
323
+ }
324
+ });
325
+ try {
326
+ const size = await statSizeImpl(path);
327
+ if (size === void 0 || size <= 0 || size > MAX_ATTACHABLE_BYTES2) return void 0;
328
+ const bytes = await readFileImpl(path);
329
+ if (bytes.length === 0 || bytes.length > MAX_ATTACHABLE_BYTES2) return void 0;
330
+ return { data: Buffer.from(bytes).toString("base64"), mimeType };
331
+ } catch {
332
+ return void 0;
333
+ }
334
+ }
335
+ function rewriteTokens(text, rewritten, tmpRoot) {
336
+ if (rewritten.size === 0) return text;
337
+ return text.replace(
338
+ clipboardPathRegex(tmpRoot ?? tmpdir2()),
339
+ (match, token) => rewritten.has(token) ? IMAGE_TOKEN : match
340
+ );
341
+ }
342
+
343
+ // extension-src/pi-style/features/messages/image-preview.ts
344
+ import {
345
+ getCapabilities,
346
+ getImageDimensions,
347
+ Image
348
+ } from "@earendil-works/pi-tui";
349
+
1
350
  // extension-src/pi-style/shared/ansi.ts
2
351
  import { visibleWidth as tuiVisibleWidth } from "@earendil-works/pi-tui";
3
352
  function isFinal(byte) {
@@ -214,6 +563,147 @@ function fgHex(theme, hex, text) {
214
563
  return `${escapes.prefix}${text}${escapes.suffix}`;
215
564
  }
216
565
 
566
+ // extension-src/pi-style/features/messages/image-preview.ts
567
+ var IMAGE_PREVIEW_ENTRY_TYPE = "pi-style-image-preview";
568
+ var IMAGE_PREVIEW_EXPANDED_MAX_WIDTH_CELLS = 60;
569
+ var IMAGE_PREVIEW_MAX_WIDTH_CELLS = 60;
570
+ var GRID_GAP = 2;
571
+ var GRID_MIN_COLUMN = 14;
572
+ function isPreviewableImage(value) {
573
+ if (!value || typeof value !== "object") return false;
574
+ const data = value.data;
575
+ const mimeType = value.mimeType;
576
+ return typeof data === "string" && data.length > 0 && typeof mimeType === "string" && mimeType.length > 0;
577
+ }
578
+ function stageImagePreviewData(images) {
579
+ if (!getMessagesRenderConfig().showImagePreviews) return void 0;
580
+ const valid = images.filter(isPreviewableImage).map((image) => ({ data: image.data, mimeType: image.mimeType }));
581
+ return valid.length > 0 ? { images: valid } : void 0;
582
+ }
583
+ function flushImagePreviewEntry(pi, data) {
584
+ if (!getMessagesRenderConfig().showImagePreviews) return;
585
+ pi.appendEntry(IMAGE_PREVIEW_ENTRY_TYPE, data);
586
+ }
587
+ function previewImagesFromEntryData(data) {
588
+ if (!data || typeof data !== "object") return void 0;
589
+ const images = data.images;
590
+ if (!Array.isArray(images) || images.length === 0) return void 0;
591
+ if (!images.every(isPreviewableImage)) return void 0;
592
+ return images;
593
+ }
594
+ function imageLabel(theme, index, dims) {
595
+ const dimsPart = dims ? ` \xB7 ${dims.widthPx}\xD7${dims.heightPx}` : "";
596
+ return theme.fg("dim", `#${index}${dimsPart}`);
597
+ }
598
+ var __dimensionParsesForTest = { count: 0 };
599
+ function parseImageDimensions(image) {
600
+ __dimensionParsesForTest.count++;
601
+ const fromPrefix = getImageDimensions(image.data.slice(0, 256), image.mimeType);
602
+ return fromPrefix ?? getImageDimensions(image.data, image.mimeType) ?? void 0;
603
+ }
604
+ var previewCache = /* @__PURE__ */ new WeakMap();
605
+ function renderImagePreviewEntry(entry, options, theme) {
606
+ if (!getMessagesRenderConfig().showImagePreviews) return void 0;
607
+ const images = previewImagesFromEntryData(entry?.data);
608
+ if (!images) return void 0;
609
+ const fg = theme?.fg;
610
+ if (typeof fg !== "function") return void 0;
611
+ const expanded = options?.expanded === true;
612
+ try {
613
+ const themed = { fg: fg.bind(theme) };
614
+ let cached = previewCache.get(entry);
615
+ if (!cached) {
616
+ const dims = images.map(parseImageDimensions);
617
+ const components2 = images.map(
618
+ (image, index) => new Image(
619
+ image.data,
620
+ image.mimeType,
621
+ { fallbackColor: (text) => themed.fg("toolOutput", text) },
622
+ {},
623
+ dims[index]
624
+ )
625
+ );
626
+ const labels2 = images.map((_image, index) => imageLabel(themed, index + 1, dims[index]));
627
+ cached = { images, components: components2, labels: labels2 };
628
+ previewCache.set(entry, cached);
629
+ }
630
+ const { components, labels } = cached;
631
+ let memo;
632
+ return {
633
+ invalidate() {
634
+ memo = void 0;
635
+ for (const component of components) component.invalidate();
636
+ },
637
+ render(width) {
638
+ const config = getMessagesRenderConfig();
639
+ const kitty = getCapabilities().images === "kitty";
640
+ const key = `${width}|${expanded}|${config.previewMaxWidth}|${config.showImagePreviews}|${kitty}`;
641
+ if (memo && memo.key === key) return memo.lines;
642
+ const maxWidth = expanded ? IMAGE_PREVIEW_EXPANDED_MAX_WIDTH_CELLS : Math.min(config.previewMaxWidth, IMAGE_PREVIEW_MAX_WIDTH_CELLS);
643
+ let lines;
644
+ if (width <= 0) lines = [];
645
+ else if (images.length === 1 || !kitty) lines = renderStacked(width, maxWidth, labels, components);
646
+ else lines = renderGrid(width, maxWidth, labels, components);
647
+ memo = { key, lines };
648
+ return lines;
649
+ }
650
+ };
651
+ } catch {
652
+ return void 0;
653
+ }
654
+ }
655
+ function renderStacked(width, maxWidth, labels, components) {
656
+ const lines = [];
657
+ for (const [index, component] of components.entries()) {
658
+ if (index > 0) lines.push("");
659
+ lines.push(labels[index] ?? "");
660
+ lines.push(...component.render(Math.min(width, Math.max(1, maxWidth + 2))));
661
+ }
662
+ return lines;
663
+ }
664
+ function stripTrailingSpaces(line) {
665
+ if (!line.endsWith(" ")) return line;
666
+ let end = line.length;
667
+ while (end > 0 && line.charCodeAt(end - 1) === 32) end--;
668
+ return line.slice(0, end);
669
+ }
670
+ function renderGrid(width, maxWidth, labels, components) {
671
+ const usable = Math.max(1, width - 2);
672
+ const maxColumns = Math.floor((usable + GRID_GAP) / (GRID_MIN_COLUMN + GRID_GAP));
673
+ const columns = Math.max(1, Math.min(components.length, maxColumns, 3));
674
+ if (columns < 2) return renderStacked(width, maxWidth, labels, components);
675
+ const columnWidth = Math.min(maxWidth, Math.floor((usable - (columns - 1) * GRID_GAP) / columns));
676
+ if (columnWidth < GRID_MIN_COLUMN) return renderStacked(width, maxWidth, labels, components);
677
+ const lines = [];
678
+ for (let start = 0; start < components.length; start += columns) {
679
+ const group = components.slice(start, start + columns);
680
+ const groupLabels = labels.slice(start, start + columns);
681
+ if (start > 0) lines.push("");
682
+ const rendered = group.map((component) => component.render(columnWidth));
683
+ const labelRow = groupLabels.map((label, _index) => {
684
+ const pad = Math.max(0, columnWidth - visibleWidth(stripAnsi(label)));
685
+ return label + " ".repeat(pad);
686
+ }).join(" ".repeat(GRID_GAP)).trimEnd();
687
+ lines.push(labelRow);
688
+ const rows = Math.max(...rendered.map((column) => column.length));
689
+ for (let row = 0; row < rows; row++) {
690
+ let line = "";
691
+ for (let column = 0; column < rendered.length; column++) {
692
+ if (column > 0) {
693
+ const startCol = column * (columnWidth + GRID_GAP);
694
+ line += `\x1B[${startCol + 1}G`;
695
+ }
696
+ line += rendered[column]?.[row] ?? "";
697
+ }
698
+ lines.push(stripTrailingSpaces(line));
699
+ }
700
+ }
701
+ return lines;
702
+ }
703
+ function registerImagePreviewSurface(pi) {
704
+ pi.registerEntryRenderer(IMAGE_PREVIEW_ENTRY_TYPE, renderImagePreviewEntry);
705
+ }
706
+
217
707
  // extension-src/pi-style/shared/box.ts
218
708
  import { homedir as homedir2 } from "os";
219
709
  import { relative, resolve as resolve2 } from "path";
@@ -511,9 +1001,9 @@ function boxedResultRenderBudget(rawLineBudget = DEFAULT_COLLAPSED_RENDER_LINES)
511
1001
  // extension-src/pi-style/shared/theme-extras.ts
512
1002
  import { existsSync, readdirSync, readFileSync } from "fs";
513
1003
  import { homedir } from "os";
514
- import { dirname, join, resolve } from "path";
1004
+ import { dirname as dirname2, join, resolve } from "path";
515
1005
  import { fileURLToPath } from "url";
516
- var extensionDir = dirname(fileURLToPath(import.meta.url));
1006
+ var extensionDir = dirname2(fileURLToPath(import.meta.url));
517
1007
  var THEME_EXTRA_DEFAULTS = Object.freeze({
518
1008
  assistantPrefix: "\u2022",
519
1009
  assistantPrefixColor: "",
@@ -4212,16 +4702,16 @@ function parseGitShowStat(text) {
4212
4702
  }
4213
4703
  break;
4214
4704
  }
4215
- const stat = parseGitDiffStat(lines.slice(i).join("\n"));
4216
- if (!stat) return null;
4705
+ const stat3 = parseGitDiffStat(lines.slice(i).join("\n"));
4706
+ if (!stat3) return null;
4217
4707
  return {
4218
4708
  kind: "show-stat",
4219
4709
  hash,
4220
4710
  subject,
4221
- files: stat.files,
4222
- ...stat.filesChanged !== void 0 ? { filesChanged: stat.filesChanged } : {},
4223
- ...stat.insertions !== void 0 ? { insertions: stat.insertions } : {},
4224
- ...stat.deletions !== void 0 ? { deletions: stat.deletions } : {}
4711
+ files: stat3.files,
4712
+ ...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
4713
+ ...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
4714
+ ...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
4225
4715
  };
4226
4716
  }
4227
4717
  var DIFF_GIT_HEADER = /^diff --git a\/(.*) b\/(.*)$/;
@@ -4548,17 +5038,17 @@ function parseGitPull(text) {
4548
5038
  return null;
4549
5039
  }
4550
5040
  if (!range || ffIndex < 0) return null;
4551
- const stat = parseGitDiffStat(lines.slice(ffIndex + 1).join("\n"));
4552
- if (!stat) return null;
5041
+ const stat3 = parseGitDiffStat(lines.slice(ffIndex + 1).join("\n"));
5042
+ if (!stat3) return null;
4553
5043
  return {
4554
5044
  kind: "action",
4555
5045
  command: "pull",
4556
- files: stat.files,
5046
+ files: stat3.files,
4557
5047
  status: "Fast-forward",
4558
5048
  range,
4559
- ...stat.filesChanged !== void 0 ? { filesChanged: stat.filesChanged } : {},
4560
- ...stat.insertions !== void 0 ? { insertions: stat.insertions } : {},
4561
- ...stat.deletions !== void 0 ? { deletions: stat.deletions } : {}
5049
+ ...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
5050
+ ...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
5051
+ ...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
4562
5052
  };
4563
5053
  }
4564
5054
  function parseGitFetch(text) {
@@ -4700,30 +5190,30 @@ function parseGitMerge(text) {
4700
5190
  }
4701
5191
  const marker2 = body[idx] ?? "";
4702
5192
  if (range && marker2 === "Fast-forward") {
4703
- const stat = parseGitDiffStatTolerant(body.slice(idx + 1).join("\n"));
4704
- if (!stat) return null;
5193
+ const stat3 = parseGitDiffStatTolerant(body.slice(idx + 1).join("\n"));
5194
+ if (!stat3) return null;
4705
5195
  return {
4706
5196
  kind: "action",
4707
5197
  command: "merge",
4708
- files: stat.files,
5198
+ files: stat3.files,
4709
5199
  status: "Fast-forward",
4710
5200
  range,
4711
- ...stat.filesChanged !== void 0 ? { filesChanged: stat.filesChanged } : {},
4712
- ...stat.insertions !== void 0 ? { insertions: stat.insertions } : {},
4713
- ...stat.deletions !== void 0 ? { deletions: stat.deletions } : {}
5201
+ ...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
5202
+ ...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
5203
+ ...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
4714
5204
  };
4715
5205
  }
4716
5206
  if (MERGE_MADE.exec(marker2)) {
4717
- const stat = parseGitDiffStatTolerant(body.slice(idx + 1).join("\n"));
4718
- if (!stat) return null;
5207
+ const stat3 = parseGitDiffStatTolerant(body.slice(idx + 1).join("\n"));
5208
+ if (!stat3) return null;
4719
5209
  return {
4720
5210
  kind: "action",
4721
5211
  command: "merge",
4722
- files: stat.files,
5212
+ files: stat3.files,
4723
5213
  status: marker2,
4724
- ...stat.filesChanged !== void 0 ? { filesChanged: stat.filesChanged } : {},
4725
- ...stat.insertions !== void 0 ? { insertions: stat.insertions } : {},
4726
- ...stat.deletions !== void 0 ? { deletions: stat.deletions } : {}
5214
+ ...stat3.filesChanged !== void 0 ? { filesChanged: stat3.filesChanged } : {},
5215
+ ...stat3.insertions !== void 0 ? { insertions: stat3.insertions } : {},
5216
+ ...stat3.deletions !== void 0 ? { deletions: stat3.deletions } : {}
4727
5217
  };
4728
5218
  }
4729
5219
  return null;
@@ -7095,7 +7585,7 @@ var DEFAULT_CONFIG = Object.freeze({
7095
7585
  startup: Object.freeze({ mode: "compact", showResources: false, alwaysExpanded: false }),
7096
7586
  statusLine: Object.freeze({
7097
7587
  enabled: true,
7098
- separator: "powerline-thin",
7588
+ separator: "|",
7099
7589
  layout: Object.freeze({
7100
7590
  left: ["path", "git", "context_bar", "cost"],
7101
7591
  right: ["model_effort"],
@@ -7112,7 +7602,10 @@ var DEFAULT_CONFIG = Object.freeze({
7112
7602
  assistantPrefix: true,
7113
7603
  specialBlocks: true,
7114
7604
  hideThinkingLabel: true,
7115
- hideInterimText: true
7605
+ hideInterimText: true,
7606
+ showImagePreviews: true,
7607
+ clipboardImages: true,
7608
+ previewMaxWidth: 30
7116
7609
  }),
7117
7610
  tools: Object.freeze({
7118
7611
  enabled: true,
@@ -7229,7 +7722,10 @@ function normalizeConfig(input, defaults = DEFAULT_CONFIG) {
7229
7722
  assistantPrefix: bool(messages.assistantPrefix, defaults.messages.assistantPrefix),
7230
7723
  specialBlocks: bool(messages.specialBlocks, defaults.messages.specialBlocks),
7231
7724
  hideThinkingLabel: bool(messages.hideThinkingLabel, defaults.messages.hideThinkingLabel),
7232
- hideInterimText: bool(messages.hideInterimText, defaults.messages.hideInterimText)
7725
+ hideInterimText: bool(messages.hideInterimText, defaults.messages.hideInterimText),
7726
+ showImagePreviews: bool(messages.showImagePreviews, defaults.messages.showImagePreviews),
7727
+ clipboardImages: bool(messages.clipboardImages, defaults.messages.clipboardImages),
7728
+ previewMaxWidth: boundedInt(messages.previewMaxWidth, defaults.messages.previewMaxWidth, 8, 60)
7233
7729
  }),
7234
7730
  tools: Object.freeze({
7235
7731
  enabled: bool(tools.enabled, defaults.tools.enabled),
@@ -7283,6 +7779,8 @@ var BOOL_PATHS = /* @__PURE__ */ new Set([
7283
7779
  "messages.specialBlocks",
7284
7780
  "messages.hideThinkingLabel",
7285
7781
  "messages.hideInterimText",
7782
+ "messages.showImagePreviews",
7783
+ "messages.clipboardImages",
7286
7784
  "tools.enabled",
7287
7785
  "tools.showElapsed",
7288
7786
  "tools.dimOutput",
@@ -7328,6 +7826,8 @@ function validLeaf(path, value) {
7328
7826
  return typeof value === "string";
7329
7827
  if (path === "tools.maxCollapsedLines" || path === "tools.maxExpandedLines")
7330
7828
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
7829
+ if (path === "messages.previewMaxWidth")
7830
+ return typeof value === "number" && Number.isFinite(value) && value >= 8 && value <= 60;
7331
7831
  if (path === "statusLine.bottomMargin" || path === "statusLine.contextBarWidth")
7332
7832
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
7333
7833
  if (STRING_ARRAY_PATHS.has(path)) return Array.isArray(value) && value.every((item) => typeof item === "string");
@@ -7751,6 +8251,9 @@ var allowedPaths = /* @__PURE__ */ new Set([
7751
8251
  "messages.specialBlocks",
7752
8252
  "messages.hideThinkingLabel",
7753
8253
  "messages.hideInterimText",
8254
+ "messages.showImagePreviews",
8255
+ "messages.clipboardImages",
8256
+ "messages.previewMaxWidth",
7754
8257
  "tools.enabled",
7755
8258
  "tools.style",
7756
8259
  "tools.maxCollapsedLines",
@@ -7783,6 +8286,9 @@ function validatePathValue(path, value) {
7783
8286
  "messages.specialBlocks",
7784
8287
  "messages.hideThinkingLabel",
7785
8288
  "messages.hideInterimText",
8289
+ "messages.showImagePreviews",
8290
+ "messages.clipboardImages",
8291
+ "messages.previewMaxWidth",
7786
8292
  "tools.enabled",
7787
8293
  "tools.showElapsed",
7788
8294
  "tools.dimOutput",
@@ -7805,6 +8311,8 @@ function validatePathValue(path, value) {
7805
8311
  return typeof value === "string";
7806
8312
  if (path === "tools.maxCollapsedLines" || path === "tools.maxExpandedLines")
7807
8313
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
8314
+ if (path === "messages.previewMaxWidth")
8315
+ return typeof value === "number" && Number.isFinite(value) && value >= 8 && value <= 60;
7808
8316
  if (path === "statusLine.bottomMargin" || path === "statusLine.contextBarWidth")
7809
8317
  return typeof value === "number" && Number.isFinite(value) && value >= 0;
7810
8318
  if (path.endsWith(".colors") || path.endsWith(".glyphs"))
@@ -7937,8 +8445,8 @@ function runCommand(args, host, app, storage) {
7937
8445
  }
7938
8446
 
7939
8447
  // extension-src/pi-style/pi/config-host.ts
7940
- import { mkdir, readFile, rename, unlink, writeFile } from "fs/promises";
7941
- import { dirname as dirname2, join as join2 } from "path";
8448
+ import { mkdir, readFile as readFile3, rename, unlink, writeFile } from "fs/promises";
8449
+ import { dirname as dirname3, join as join2 } from "path";
7942
8450
  import { CONFIG_DIR_NAME, getAgentDir } from "@earendil-works/pi-coding-agent";
7943
8451
  var locks = /* @__PURE__ */ new Map();
7944
8452
  function serialize(path, operation) {
@@ -7951,14 +8459,14 @@ function serialize(path, operation) {
7951
8459
  }
7952
8460
  function createPiConfigFilePort(fs = {}) {
7953
8461
  const fsMkdir = fs.mkdir ?? mkdir;
7954
- const fsReadFile = fs.readFile ?? readFile;
8462
+ const fsReadFile = fs.readFile ?? readFile3;
7955
8463
  const fsRename = fs.rename ?? rename;
7956
8464
  const fsUnlink = fs.unlink ?? unlink;
7957
8465
  const fsWriteFile = fs.writeFile ?? writeFile;
7958
8466
  return {
7959
8467
  read: (path) => fsReadFile(path, "utf8"),
7960
8468
  writeAtomic: async (path, content) => serialize(path, async () => {
7961
- await fsMkdir(dirname2(path), { recursive: true });
8469
+ await fsMkdir(dirname3(path), { recursive: true });
7962
8470
  const temporary = `${path}.pi-style-${process.pid}-${Date.now()}.tmp`;
7963
8471
  try {
7964
8472
  await fsWriteFile(temporary, content, { mode: 384 });
@@ -8147,12 +8655,17 @@ function createBuiltinSegments() {
8147
8655
  const percent = contextPercent(snapshot.context ?? {});
8148
8656
  if (percent === void 0) return { visible: false, content: "" };
8149
8657
  const token = contextBarToken(percent);
8150
- const window = snapshot.context?.windowTokens;
8151
- const label = `ctx${window !== void 0 ? ` (${formatTokens(window)})` : ""}:`;
8152
8658
  const width = options.context_bar?.width ?? CONTEXT_BAR_WIDTH;
8659
+ const pipe = theme.apply("separator", "|");
8660
+ const bar = `${theme.apply("muted", "[")}${theme.apply(token, contextBar(percent, width))}${theme.apply("muted", "]")}`;
8661
+ const parts = [bar, `${theme.apply(token, `${Math.round(percent)}%`)}${theme.apply("muted", " used")}`];
8662
+ const current = snapshot.context?.currentTokens;
8663
+ const window = snapshot.context?.windowTokens;
8664
+ if (current !== void 0 && window !== void 0)
8665
+ parts.push(theme.apply("muted", `${formatTokens(current)}/${formatTokens(window)}`));
8153
8666
  return {
8154
8667
  visible: true,
8155
- content: `${theme.apply("muted", label)} ${theme.apply(token, contextBar(percent, width))} ${theme.apply(token, `${Math.round(percent)}%`)}`,
8668
+ content: parts.join(` ${pipe} `),
8156
8669
  compactContent: theme.apply(token, `${Math.round(percent)}%`)
8157
8670
  };
8158
8671
  }),
@@ -8235,10 +8748,7 @@ function contextBarToken(percent) {
8235
8748
  return "success";
8236
8749
  }
8237
8750
  function formatTokens(value) {
8238
- if (value >= 1e6) {
8239
- const millions = value / 1e6;
8240
- return `${Number.isInteger(millions) ? millions : millions.toFixed(1)}M`;
8241
- }
8751
+ if (value >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
8242
8752
  if (value >= 1e3) {
8243
8753
  const thousands = value / 1e3;
8244
8754
  return `${Number.isInteger(thousands) ? thousands : thousands.toFixed(1)}K`;
@@ -8546,6 +9056,10 @@ var StyledEditor = class extends CustomEditor {
8546
9056
  semanticDirty = false;
8547
9057
  disposed = false;
8548
9058
  renderPlanCache;
9059
+ clipboardImagePaste;
9060
+ /** Keybinding matcher (the factory's KeybindingsManager) for keystroke-level
9061
+ * interception: the paste keybinding and backspace checks below. */
9062
+ editorKeybindings;
8549
9063
  constructor(tui, theme, keybindings, options) {
8550
9064
  super(tui, theme, keybindings);
8551
9065
  this.config = options.config;
@@ -8553,6 +9067,8 @@ var StyledEditor = class extends CustomEditor {
8553
9067
  this.piTheme = theme;
8554
9068
  this.fullTheme = options.fullTheme;
8555
9069
  this.onSnapshot = options.onSnapshot;
9070
+ this.clipboardImagePaste = options.clipboardImagePaste;
9071
+ this.editorKeybindings = keybindings;
8556
9072
  this.semantic = semanticTheme(theme, options.config);
8557
9073
  this.setPaddingX(0);
8558
9074
  }
@@ -8569,9 +9085,88 @@ var StyledEditor = class extends CustomEditor {
8569
9085
  }
8570
9086
  handleInput(data) {
8571
9087
  if (this.disposed) return;
9088
+ if (this.handleClipboardImagePasteKeystroke(data)) return;
9089
+ if (this.handleAtomicMarkerBackspace(data)) return;
8572
9090
  super.handleInput(data);
8573
9091
  this.onSnapshot({ ...this.snapshot });
8574
9092
  }
9093
+ /**
9094
+ * Instant marker (ADR 0009): when the built-in paste keybinding fires, the
9095
+ * clipboard holds an image (sync native probe), and the surface is enabled,
9096
+ * the keystroke is owned here — an `[Image #N] ` marker is inserted
9097
+ * immediately (zero-latency feedback) and the bytes fill the registry
9098
+ * entry asynchronously. Pi's own paste handler never runs for this
9099
+ * keystroke, so no temp artifact is written. Text pastes (no image) and
9100
+ * probe-unavailable hosts fall through to the native path untouched.
9101
+ */
9102
+ handleClipboardImagePasteKeystroke(data) {
9103
+ const surface = this.clipboardImagePaste;
9104
+ if (!surface?.enabled()) return false;
9105
+ if (!this.editorKeybindings.matches(data, "app.clipboard.pasteImage")) return false;
9106
+ if (surface.clipboardHasImage() !== true) return false;
9107
+ super.insertTextAtCursor(surface.markerFromClipboard());
9108
+ this.onSnapshot({ ...this.snapshot });
9109
+ return true;
9110
+ }
9111
+ /**
9112
+ * Atomic marker backspace (ADR 0009): backspacing directly after a
9113
+ * registered `[Image #N] ` marker deletes the whole marker as one unit —
9114
+ * the image is discarded (image-paste semantics). Direct state surgery
9115
+ * (O(1)) mirroring pi-tui's handleBackspace mutation protocol: no setText
9116
+ * round-trip (it clears the pastes registry, and undo snapshots store that
9117
+ * Map by reference — silently breaking `[paste #N]` atomicity on undo) and
9118
+ * no per-column left-arrow inputs (O(K·L) through the keybinding chain and
9119
+ * visual line maps). No undo snapshot is pushed: the discarded image
9120
+ * cannot be restored, so the deletion is intentionally non-undoable.
9121
+ */
9122
+ handleAtomicMarkerBackspace(data) {
9123
+ const surface = this.clipboardImagePaste;
9124
+ if (!surface?.enabled()) return false;
9125
+ const isBackspace = this.editorKeybindings.matches(data, "tui.editor.deleteCharBackward") || data === "\x7F";
9126
+ if (!isBackspace) return false;
9127
+ const lines = this.getLines();
9128
+ const cursor = this.getCursor();
9129
+ const current = lines[cursor.line];
9130
+ if (current === void 0) return false;
9131
+ const before = current.slice(0, cursor.col);
9132
+ const hit = clipboardMarkerAtEnd(before);
9133
+ if (!hit || !surface.isMarkerIndexRegistered(hit.index)) return false;
9134
+ const editor = this;
9135
+ const targetCol = cursor.col - hit.length;
9136
+ editor.exitHistoryBrowsing();
9137
+ editor.lastAction = null;
9138
+ editor.cancelAutocomplete();
9139
+ editor.state.lines[cursor.line] = current.slice(0, targetCol) + current.slice(cursor.col);
9140
+ editor.setCursorCol(targetCol);
9141
+ editor.onChange?.(this.getText());
9142
+ surface.discardMarkerIndex(hit.index);
9143
+ this.invalidate();
9144
+ this.onSnapshot({ ...this.snapshot });
9145
+ return true;
9146
+ }
9147
+ /**
9148
+ * Artifact-path fallback (ADR 0009): when the keystroke was not owned
9149
+ * (probe unavailable, native editor path, config toggled) Pi's built-in
9150
+ * paste still inserts a `<tmpdir>/pi-clipboard-<uuid>.<ext>` path through
9151
+ * this method. Convert it to a marker when the artifact reads successfully;
9152
+ * every failure inserts the original text verbatim (exact native behavior).
9153
+ */
9154
+ insertTextAtCursor(text) {
9155
+ const surface = this.clipboardImagePaste;
9156
+ if (!surface?.enabled() || !isSingleClipboardImagePath(text)) {
9157
+ super.insertTextAtCursor(text);
9158
+ return;
9159
+ }
9160
+ void (async () => {
9161
+ let replacement = text;
9162
+ try {
9163
+ replacement = await surface.markerFromArtifact(text);
9164
+ } catch {
9165
+ }
9166
+ super.insertTextAtCursor(replacement);
9167
+ this.tui.requestRender();
9168
+ })();
9169
+ }
8575
9170
  invalidate() {
8576
9171
  super.invalidate();
8577
9172
  if (this.semanticDirty) {
@@ -8787,6 +9382,7 @@ function installEditor(options) {
8787
9382
  snapshot,
8788
9383
  theme,
8789
9384
  ...options.host.theme ? { fullTheme: options.host.theme } : {},
9385
+ ...options.clipboardImagePaste ? { clipboardImagePaste: options.clipboardImagePaste } : {},
8790
9386
  onSnapshot: (next) => {
8791
9387
  if (!disposed && options.isCurrent?.() !== false) snapshot = next;
8792
9388
  }
@@ -9363,7 +9959,7 @@ function renderGroup(items, separator, padding) {
9363
9959
  }
9364
9960
  function renderStatus(layout, snapshot, width, options) {
9365
9961
  if (width <= 0) return { primary: "", lines: [], visibleSegments: [] };
9366
- const separator = options.separator ?? "\u2502";
9962
+ const separator = options.separator ?? "|";
9367
9963
  const padding = options.padding ?? " ";
9368
9964
  const normalized = uniqueLayout(layout);
9369
9965
  const context = { snapshot, theme: options.theme, options: options.options ?? {}, width };
@@ -10180,7 +10776,10 @@ function createPiStyleRuntime(host, generation2, requestRender = host.requestRen
10180
10776
  config: currentConfig,
10181
10777
  generation: generation2,
10182
10778
  initialSnapshot: currentSnapshot,
10183
- isCurrent: () => !disposed
10779
+ isCurrent: () => !disposed,
10780
+ // Clipboard image paste surface (ADR 0009): instant `[Image #N] `
10781
+ // markers at keystroke time, artifact fallback, atomic backspace.
10782
+ clipboardImagePaste: createClipboardImagePasteSurface()
10184
10783
  });
10185
10784
  if (editor) {
10186
10785
  disposables.add(editor);
@@ -10878,7 +11477,7 @@ function isTierCAuthorized(input) {
10878
11477
 
10879
11478
  // extension-src/pi-style/pi/compatibility-probe.ts
10880
11479
  import { readFileSync as readFileSync2 } from "fs";
10881
- import { dirname as dirname3, join as join3 } from "path";
11480
+ import { dirname as dirname4, join as join3 } from "path";
10882
11481
  import { fileURLToPath as fileURLToPath2 } from "url";
10883
11482
  import {
10884
11483
  AssistantMessageComponent,
@@ -11675,19 +12274,19 @@ function detectPiVersion(resolution = {}) {
11675
12274
  );
11676
12275
  }
11677
12276
  });
11678
- const readFile2 = resolution.readFile ?? ((path) => readFileSync2(path, "utf8"));
12277
+ const readFile4 = resolution.readFile ?? ((path) => readFileSync2(path, "utf8"));
11679
12278
  try {
11680
12279
  const entry = resolvePackageEntry("@earendil-works/pi-coding-agent");
11681
- let directory = dirname3(entry);
12280
+ let directory = dirname4(entry);
11682
12281
  for (; ; ) {
11683
12282
  const packagePath = join3(directory, "package.json");
11684
12283
  try {
11685
- const packageJson = JSON.parse(readFile2(packagePath));
12284
+ const packageJson = JSON.parse(readFile4(packagePath));
11686
12285
  if (packageJson.name === "@earendil-works/pi-coding-agent" && typeof packageJson.version === "string")
11687
12286
  return { version: packageJson.version };
11688
12287
  } catch {
11689
12288
  }
11690
- const parent = dirname3(directory);
12289
+ const parent = dirname4(directory);
11691
12290
  if (parent === directory) break;
11692
12291
  directory = parent;
11693
12292
  }
@@ -12350,6 +12949,11 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
12350
12949
  };
12351
12950
  const applyMessagesConfig = (config) => {
12352
12951
  sessionUi?.setHiddenThinkingLabel?.(config.messages.hideThinkingLabel ? "" : void 0);
12952
+ setMessagesRenderConfig({
12953
+ showImagePreviews: config.messages.showImagePreviews,
12954
+ clipboardImages: config.messages.clipboardImages,
12955
+ previewMaxWidth: config.messages.previewMaxWidth
12956
+ });
12353
12957
  };
12354
12958
  const applyAutoTheme = (config, ctx) => {
12355
12959
  const target = config.theme.autoApply;
@@ -12409,6 +13013,7 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
12409
13013
  resetBatchRegistry();
12410
13014
  resetGrepRegistry();
12411
13015
  resetBashTreeRegistry();
13016
+ resetPendingImageRegistry();
12412
13017
  resetTurnRegistry();
12413
13018
  rebuildTurnRegistryFromEntries(ctx.sessionManager.getEntries());
12414
13019
  stopAllElapsedTickers();
@@ -12474,6 +13079,7 @@ function createPiStyleSessionCoordinator(pi, hooks = {}) {
12474
13079
  resetBatchRegistry();
12475
13080
  resetGrepRegistry();
12476
13081
  resetBashTreeRegistry();
13082
+ resetPendingImageRegistry();
12477
13083
  resetTurnRegistry();
12478
13084
  stopAllElapsedTickers();
12479
13085
  app.sessionShutdown();
@@ -12677,6 +13283,12 @@ function piStyleExtension(pi) {
12677
13283
  pi.registerFlag("pi-style-ascii", { type: "boolean", description: "Use ASCII pi-style markers" });
12678
13284
  const coordinator = createPiStyleSessionCoordinator(pi, compatibilityTestHooks);
12679
13285
  registerPiStyleCommand(pi, coordinator.app);
13286
+ registerImagePreviewSurface(pi);
13287
+ let stagedImagePreview;
13288
+ pi.on("before_agent_start", (event) => {
13289
+ const staged = stageImagePreviewData(event.images ?? []);
13290
+ if (staged) stagedImagePreview = staged;
13291
+ });
12680
13292
  pi.on("session_start", async (event, ctx) => {
12681
13293
  resetUsageFromSessionCache(ctx.sessionManager);
12682
13294
  if (pi.getFlag("pi-style-readonly-tools") === true) {
@@ -12688,7 +13300,7 @@ function piStyleExtension(pi) {
12688
13300
  coordinator.app.runtime.current?.dismissStartup();
12689
13301
  beginAgentRun();
12690
13302
  });
12691
- pi.on("input", (event, _ctx) => {
13303
+ pi.on("input", async (event, _ctx) => {
12692
13304
  coordinator.app.runtime.current?.dismissStartup();
12693
13305
  if (event.source === "interactive") {
12694
13306
  const trimmed = event.text.trimStart();
@@ -12697,6 +13309,15 @@ function piStyleExtension(pi) {
12697
13309
  if (trimmed.slice(bangLength).trim() === "") return { action: "handled" };
12698
13310
  }
12699
13311
  }
13312
+ const markerResult = await resolvePendingImageMarkers(event.text);
13313
+ const pathResult = event.source === "interactive" ? await transformClipboardImages(event.text) : void 0;
13314
+ if (markerResult || pathResult) {
13315
+ return {
13316
+ action: "transform",
13317
+ text: pathResult?.text ?? event.text,
13318
+ images: [...markerResult?.images ?? [], ...pathResult?.images ?? []]
13319
+ };
13320
+ }
12700
13321
  return void 0;
12701
13322
  });
12702
13323
  pi.on("tool_execution_start", () => coordinator.app.runtime.current?.dismissStartup());
@@ -12716,8 +13337,12 @@ function piStyleExtension(pi) {
12716
13337
  "session_info_changed",
12717
13338
  (event) => coordinator.app.update({ sessionName: event.name }, "coalesced", { refreshExtensionStatuses: true })
12718
13339
  );
12719
- pi.on("message_start", () => {
13340
+ pi.on("message_start", (event) => {
12720
13341
  closeActiveBatch();
13342
+ if (stagedImagePreview && event.message?.role === "assistant") {
13343
+ flushImagePreviewEntry(pi, stagedImagePreview);
13344
+ stagedImagePreview = void 0;
13345
+ }
12721
13346
  });
12722
13347
  pi.on("message_update", () => coordinator.app.update({}, "coalesced"));
12723
13348
  pi.on(