@blockrun/franklin 3.25.0 → 3.25.1

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 (2) hide show
  1. package/dist/ui/app.js +45 -28
  2. package/package.json +1 -1
package/dist/ui/app.js CHANGED
@@ -445,37 +445,54 @@ function PromptTextInput({ value, onChange, onSubmit, placeholder = '', focus =
445
445
  const buffered = pasteBufferRef.current;
446
446
  pasteBufferRef.current = '';
447
447
  pasteActiveRef.current = false;
448
- // Image-paste detection: terminals deliver Cmd+V as a bracketed paste,
449
- // but image bytes don't ride that stream Terminal/iTerm2 just fire an
450
- // empty (or whitespace-only) bracketed paste, while the actual image
451
- // sits in the system clipboard. So when the buffered text is empty we
452
- // probe the clipboard before falling through to plain text handling.
453
- // (No race vs. real paste content: pasted text always populates the
454
- // buffer before END arrives, so non-empty buffer = certainly text.)
455
- if (buffered.trim().length === 0) {
456
- // Clipboard probe + optional resize are async; the input handler
457
- // returns now and updateValue happens once the Promise resolves.
458
- // Capture the cursor offset so the block goes where the user pasted,
459
- // even if they moved the cursor in the meantime.
460
- const insertAt = currentCursorOffset;
461
- tryReadClipboardImage().then((img) => {
462
- let injected;
463
- if (img && 'path' in img)
464
- injected = encodeImageBlock(img.path);
465
- else if (img && 'error' in img)
466
- injected = `[Image rejected: ${img.error}] `;
467
- else
468
- return; // no image on clipboard — nothing to do
448
+ // Image-paste detection. Original heuristic (3.25.0) only probed the
449
+ // clipboard when the bracketed-paste buffer was empty, assuming Cmd+V
450
+ // on an image-only clipboard always arrived as an empty paste. That
451
+ // assumption holds for macOS Terminal/iTerm2 but NOT for several Linux
452
+ // terminals some send a filename, a `file://` URI, or a short binary
453
+ // header alongside the image, which made the gate skip the probe and
454
+ // the image silently dropped (user-reported on Kali: the clipboard
455
+ // image was readable by other tools but Franklin couldn't paste it).
456
+ // Verified in a Lima Ubuntu VM with xclip non-empty buffer triggered
457
+ // the skip.
458
+ //
459
+ // Fix: ALWAYS probe the clipboard on a paste-end. If an image is there,
460
+ // it wins; the bracketed-paste buffer text is treated as terminal noise
461
+ // and dropped. If no image, fall through to the normal text-paste path
462
+ // (collapse-to-block or inline) so text pastes are unaffected. The
463
+ // probe is async (osascript / xclip / wl-paste shell-out, 30-100 ms),
464
+ // so the handler returns immediately and updateValue happens when the
465
+ // Promise resolves.
466
+ const insertAt = currentCursorOffset;
467
+ tryReadClipboardImage().then((img) => {
468
+ if (img && 'path' in img) {
469
+ // Image wins — drop the bracketed-paste buffer (likely a stub the
470
+ // terminal sent for the image).
471
+ const injected = encodeImageBlock(img.path);
469
472
  const cur = valueRef.current;
470
473
  const at = Math.min(insertAt, cur.length);
471
474
  updateValue(cur.slice(0, at) + injected + cur.slice(at), at + injected.length);
472
- }).catch(() => { });
473
- return;
474
- }
475
- const lineCount = buffered.split('\n').length;
476
- text = lineCount >= PASTE_COLLAPSE_LINE_THRESHOLD
477
- ? encodePasteBlock(buffered)
478
- : buffered;
475
+ return;
476
+ }
477
+ if (img && 'error' in img) {
478
+ const injected = `[Image rejected: ${img.error}] `;
479
+ const cur = valueRef.current;
480
+ const at = Math.min(insertAt, cur.length);
481
+ updateValue(cur.slice(0, at) + injected + cur.slice(at), at + injected.length);
482
+ return;
483
+ }
484
+ // No image on the clipboard — treat as a normal text paste.
485
+ if (buffered.length === 0)
486
+ return;
487
+ const lineCount = buffered.split('\n').length;
488
+ const textToInsert = lineCount >= PASTE_COLLAPSE_LINE_THRESHOLD
489
+ ? encodePasteBlock(buffered)
490
+ : buffered;
491
+ const cur = valueRef.current;
492
+ const at = Math.min(insertAt, cur.length);
493
+ updateValue(cur.slice(0, at) + textToInsert + cur.slice(at), at + textToInsert.length);
494
+ }).catch(() => { });
495
+ return;
479
496
  }
480
497
  if (!text) {
481
498
  if (hasPasteEnd)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.25.0",
3
+ "version": "3.25.1",
4
4
  "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {