Files
orca/src/shared/image-path-paste.ts
T
NeilandWooseong Kim 2b716f601d fix(mobile): attach Codex images when agent identity is missing (#31370)
Use the shared desktop image-path formatter for unidentified mobile terminals and identified Codex, preserving owned agent identity, bracketed paste, upload routing and acknowledged-input ordering.

Adapted from community PR #30310 by Wooseong Kim (@innocarpe). Fixes #30298.

Validation includes exact-head CI and aggregate gate, native macOS and Windows consumer checks, actual paired Windows uploads with byte-integrity checks, and combined mobile/desktop tests, types, lint and bundle checks.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
2026-10-21 15:31:55 -07:00

76 lines
2.7 KiB
TypeScript

import { IMAGE_FILE_EXTENSIONS } from './image-file-extensions'
import { shellEscapePath } from './shell-path-quoting'
type TerminalTargetShell = 'posix' | 'windows'
// Why: dropped image files should be handed to terminal TUIs (Claude Code,
// Codex, etc.) as image attachments, which those tools detect from a
// *bracketed paste* of the file path — exactly how clipboard screenshot paste
// already works in Orca (see terminal-clipboard-paste.ts + issue #2842).
const IMAGE_DROP_EXTENSIONS = new Set(IMAGE_FILE_EXTENSIONS)
// Why: an ASCII space is unsafe because Codex shlex-splits a raw spaced path into several tokens.
const POSIX_RAW_IMAGE_DROP_UNSAFE_RE = /[ "'`$;&|<>(){}[\]*?!#\\]/
const WINDOWS_RAW_IMAGE_DROP_UNSAFE_RE = /[ "'`$;&|<>(){}[\]*?!#^%]/
/**
* Returns true when `path` looks like a local/remote image file based on its
* extension. Handles POSIX (`/`) and Windows (`\`) separators and is
* case-insensitive. The extension must be part of the basename, so directory
* components with dots (e.g. `/home/jane.doe/photo`) are not misclassified.
*/
export function isImageDropPath(path: string): boolean {
const lastDot = path.lastIndexOf('.')
if (lastDot === -1) {
return false
}
const lastSeparator = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
if (lastDot < lastSeparator) {
return false
}
return IMAGE_DROP_EXTENSIONS.has(path.slice(lastDot).toLowerCase())
}
export function canPasteImageDropPathRaw(path: string, targetShell: TerminalTargetShell): boolean {
if (hasControlByte(path)) {
return false
}
const unsafeRe =
targetShell === 'windows' ? WINDOWS_RAW_IMAGE_DROP_UNSAFE_RE : POSIX_RAW_IMAGE_DROP_UNSAFE_RE
return !unsafeRe.test(path)
}
/**
* Text to bracketed-paste for a dropped image, or null when a paste would alter
* the path (control bytes are rewritten by the paste frame).
*/
export function formatImageDropPasteText(
path: string,
targetShell: TerminalTargetShell
): string | null {
if (hasControlByte(path)) {
return null
}
if (canPasteImageDropPathRaw(path, targetShell)) {
return path
}
// Why: Claude Code strips only one outer quote pair, so a POSIX `'\''` splice
// leaks quotes into the path; backslash escapes round-trip in Claude, Codex, and shells.
return targetShell === 'windows'
? shellEscapePath(path, 'windows')
: backslashEscapePosixPath(path)
}
function backslashEscapePosixPath(path: string): string {
return path.replace(/[^a-zA-Z0-9_./@:\-\u0080-\uffff]/g, '\\$&')
}
function hasControlByte(path: string): boolean {
for (let i = 0; i < path.length; i += 1) {
const code = path.charCodeAt(i)
if (code < 0x20 || code === 0x7f) {
return true
}
}
return false
}