mirror of
https://github.com/stablyai/orca.git
synced 2026-11-02 18:17:36 +00:00
* [comment audit] Align legacy failure-read counts with cached display * [comment audit] Condense shared provider lifecycle and browser recovery comments [skip ci] * [comment audit] Condense shared generation, path and preview comments [skip ci] * [comment audit] Condense host, file and Git contract rationale [skip ci] * [comment audit] Tighten Git lifecycle and provider contract comments [skip ci] * [comment audit] Clarify shared host authority and launch isolation [skip ci] * [comment audit] simplify local access mobile and native chat contracts [skip ci] * [comment audit] simplify native chat presentation and state contracts [skip ci] * [comment audit] simplify native runtime and provider contracts [skip ci] * [comment audit] simplify migration and orchestration contracts [skip ci] * [comment audit] simplify plugin trust and process capture rationale [skip ci] * [comment audit] simplify terminal delivery and remote compatibility rationale [skip ci] * [comment audit] clarify remote recovery and launch wire rationale [skip ci] * [comment audit] shorten shared RPC compatibility and runtime evidence comments [skip ci] * [comment audit] simplify secure storage shell startup and sidebar rationale [skip ci] * [comment audit] clarify sidebar, skill and SSH invariants [skip ci] * [comment audit] condense structured chat delivery and timing rationale [skip ci] * [comment audit] condense terminal replay and telemetry invariants [skip ci] * [comment audit] clarify launch permission and terminal delivery rationale [skip ci] * [comment audit] clarify workspace ownership and layout contracts [skip ci] * [comment audit] wait for fitted and painted SSH terminal links * [comment audit] clarify layout persistence and session ownership [skip ci] * [comment audit] clarify host routing and workspace lifecycle contracts [skip ci] * [comment audit] clarify startup persistence and app lifetime comments [skip ci] * [comment audit] clarify recovery and activity boundaries [skip ci] * [comment audit] condense activity and artifact invariants [skip ci] * [comment audit] condense automation authority contracts [skip ci] * [comment audit] simplify automation scheduling and history contracts [skip ci] * [comment audit] clarify browser chrome and annotation lifecycles [skip ci] * [comment audit] clarify browser routing and retained guest ownership [skip ci] * [comment audit] clarify browser recovery and document ownership comments [skip ci] * [comment audit] restore hosted runners required by native IME fixtures * [comment audit] shorten preview, palette and dashboard invariants [skip ci] * [comment audit] clarify terminal preview, dashboard and retention comments [skip ci] * [comment audit] clarify diff-note recovery and editor lifecycle comments [skip ci] * [comment audit] shorten rich-text and combined-diff ownership comments [skip ci] * [comment audit] shorten autosave, draft recovery and model ownership comments [skip ci] * [comment audit] shorten save-queue, notebook and Markdown constraints [skip ci] * [comment audit] shorten Monaco, PDF and rich Markdown safeguards [skip ci] * [comment audit] clarify rich Markdown source and selection safeguards [skip ci] * [comment audit] clarify editor loading and emulator stream lifetimes [skip ci] * [comment audit] trim emulator and feature tour narration [skip ci] * [comment audit] clarify floating workspace and GitHub cache ownership [skip ci] * [comment audit] trim provider controls and Linear filter explanations [skip ci] * [comment audit] clarify pairing and chat resume lifetimes [skip ci] * [comment audit] clarify chat delivery and transcript lifetimes [skip ci] * [comment audit] clarify session options and host-owned status [skip ci] * [comment audit] clarify draft durability and live-status precedence [skip ci] * [comment audit] Trim chat send and model ownership explanations [skip ci] * [comment audit] Shorten transcript ownership and send recovery comments [skip ci] * [comment audit] Simplify native chat composer and transcript hook comments [skip ci] * [comment audit] Shorten native chat scroll and structured session explanations [skip ci] * [comment audit] Trim workspace picker and onboarding commentary [skip ci] * [comment audit] Simplify pet rendering, review cache and quick open comments [skip ci] * [comment audit] Trim sidebar and session history explanations [skip ci] * [comment audit] Condense session resume and review checks comments [skip ci] * [comment audit] Shorten checks state and file explorer refresh comments [skip ci] * [comment audit] Trim plugin bridge and source-control action rationale [skip ci] * [comment audit] Trim source-control listing and review rationale [skip ci] * [comment audit] Shorten sidebar lifecycle and explorer safeguards [skip ci] * [comment audit] Condense Settings account and appearance rationale [skip ci] * [comment audit] Trim pairing and repository Settings explanations [skip ci] * [comment audit] Condense Settings search SSH and terminal safeguards [skip ci] * [comment audit] Shorten Settings voice and provider ownership comments [skip ci] * [comment audit] Condense Settings draft persistence and host-state rationale [skip ci] * [comment audit] Trim Settings hooks and sidebar setup rationale [skip ci] * [comment audit] Condense sidebar deletion and board-filter safeguards [skip ci] * [comment audit] Condense worktree cards, host ownership and Markdown contracts [skip ci] * [comment audit] Condense sidebar drag, folder and filtering contracts [skip ci] * [comment audit] Condense sidebar parity, sleep and host ownership contracts [skip ci] * [comment audit] Prune focus-read baseline after comment cleanup * [comment audit] Clarify sidebar visibility, board drops and agent evidence [skip ci] * [comment audit] Tighten sidebar listing, drag and reveal contracts [skip ci] * [comment audit] Clarify viewport geometry and skill update safeguards [skip ci] * [comment audit] Tighten skill inventory, usage and host status comments [skip ci] * [comment audit] Clarify usage ownership and resource kill safeguards [skip ci] * [comment audit] Tighten tab selection, rename and search contracts [skip ci] * [comment audit] Tighten tab-strip geometry and drag ownership comments [skip ci] * [comment audit] Tighten Tasks recovery, pagination and mutation comments [skip ci] * [comment audit] Tighten provider loading and terminal recovery comments [skip ci] * [comment audit] Tighten completion, cadence and PTY ownership comments [skip ci] * [comment audit] Clarify terminal restore and parked-pane boundaries [skip ci] * [comment audit] Tighten PTY startup and replay explanations [skip ci] * [comment audit] Clarify PTY identity and delivery ownership [skip ci] * [comment audit] Shorten remote reconnect and input replay rationale [skip ci] * [comment audit] Condense terminal parking clipboard and file link comments [skip ci] * [comment audit] Clarify IME ownership and trim trace history [skip ci] * [comment audit] Shorten pane recovery and parked watcher explanations [skip ci] * [comment audit] Condense snapshot replay and visibility recovery rationale [skip ci] * [comment audit] Shorten terminal parking and keyboard ownership rationale [skip ci] * [comment audit] Trim renderer lifecycle and menu rationale [skip ci] * [comment audit] Condense workspace cleanup ownership and settlement notes [skip ci] * [comment audit] Clarify renderer hook evidence and source ownership [skip ci] * [comment audit] Tighten IPC ownership and reconnect explanations [skip ci] * [comment audit] Trim renderer focus, paste and attention comments [skip ci] * [comment audit] Shorten scroll, locale and agent lifecycle comments [skip ci] * [comment audit] Trim agent launch, resume and ownership commentary [skip ci] * [comment audit] Shorten account, browser and editor comments [skip ci] * [comment audit] Clarify host-mirror waits, previews and input ownership [skip ci] * [comment audit] Condense launch recovery and editor tokenization comments [skip ci] * [comment audit] Condense workspace selection and terminal pane rationale [skip ci] * [comment audit] Condense terminal fitting and rendering recovery comments [skip ci] * [comment audit] Condense terminal input and host routing explanations [skip ci] * [comment audit] Condense ownership and sleeping-session recovery rationale [skip ci] * [comment audit] Condense structured launch and terminal link explanations [skip ci] * [comment audit] Condense terminal ownership and workspace file routing rationale [skip ci] * [comment audit] Condense session persistence and workspace activation rationale [skip ci] * [comment audit] Condense workspace routing and browser ownership rationale [skip ci] * [comment audit] Condense mirror recovery and layout authority rationale [skip ci] * [comment audit] Condense runtime transport and file safety rationale [skip ci] * [comment audit] Condense runtime graph and structured session rationale [skip ci] * [comment audit] Condense browser creation and remote tab recovery rationale [skip ci] * [comment audit] Condense snapshot authority and publisher recovery rationale [skip ci] * [comment audit] Condense frame, startup and provider cache rationale [skip ci] * [comment audit] Condense host twin and agent lifecycle rationale [skip ci] * [comment audit] Condense browser authority and editor cleanup rationale [skip ci] * [comment audit] Condense editor buffer identity and review cache rationale [skip ci] * [comment audit] Condense provider lifetime and repository ownership rationale [skip ci] * [comment audit] Condense reconnect, hydration and terminal retirement rationale [skip ci] * [comment audit] Condense recovery, reconciliation and persistence rationale [skip ci] * [comment audit] Condense cleanup ownership and listing admission rationale [skip ci] * [comment audit] Condense teardown ordering and terminal ownership rationale [skip ci] * [comment audit] Condense recovery ledger and web host-boundary rationale [skip ci] * [comment audit] Condense web reconnect and credential lifecycle rationale [skip ci] * [comment audit] Condense cloud rollout and assignment safety rationale [skip ci] * [comment audit] Condense relay session and database recovery rationale [skip ci] * [comment audit] Condense relay placement and protocol compatibility rationale [skip ci] * [comment audit] Condense build and packaging constraints [skip ci] * [comment audit] Condense CI gates and validation isolation rationale [skip ci] * [comment audit] Condense catalog identity and browser validation rationale [skip ci] * [comment audit] Condense mobile browser and frame-budget rationale [skip ci] * [comment audit] Shorten mobile route and browser test explanations [skip ci] * [comment audit] Condense editor and session seam test rationale [skip ci] * [comment audit] Trim session and terminal closure history [skip ci] * [comment audit] Shorten terminal browser and census explanations [skip ci] * [comment audit] Preserve root-only E2E diagnostic baseline * [comment audit] Condense mobile seam and HTML preview test rationale [skip ci] * [comment audit] Condense preview instrumentation and render harness rationale [skip ci] * [comment audit] Condense bundle contracts and native-build constraints [skip ci] * [comment audit] Shorten native packaging and CI routing rationale [skip ci] * [comment audit] Condense release tooling and benchmark constraints [skip ci] * [comment audit] Condense packaging and platform gate rationale [skip ci] * [comment audit] Shorten mobile route and history rationale [skip ci] * [comment audit] Condense mobile browser and component rationale [skip ci] * [comment audit] Shorten drawer and workspace-form rationale [skip ci] * [comment audit] Trim mobile review and editor-document explanations [skip ci] * [comment audit] Condense editor lifecycle and Markdown explanations [skip ci] * [comment audit] Trim mobile file, diagnostics and dictation commentary [skip ci] * [comment audit] Simplify mobile host-list and workspace-view explanations [skip ci] * [comment audit] Clarify mobile shell lifetime and bridge delivery constraints [skip ci] * [comment audit] Condense bridge stream, native request and audio rationale [skip ci] * [comment audit] Reduce bridge schema and page-client commentary [skip ci] * [comment audit] Tighten mobile page bridge contracts [skip ci] * [comment audit] Clarify mobile cache and session constraints [skip ci] * [comment audit] Condense mobile route and lifecycle rationale [skip ci] * [comment audit] Tighten mobile shell hook timing rationale [skip ci] * [comment audit] Clarify route handoff and notification compatibility [skip ci] * [comment audit] Clarify push registration and dictation cleanup constraints [skip ci] * [comment audit] Condense keyboard geometry and native media ownership rationale [skip ci] * [comment audit] Tighten mobile chat scroll and composer concurrency rationale [skip ci] * [comment audit] Condense mobile reply compatibility and upload ownership rationale [skip ci] * [comment audit] Condense optimistic echo and scroll ownership rationale [skip ci] * [comment audit] Tighten stream replay and queued session ownership rationale [skip ci] * [comment audit] Condense mobile composer and reply-schema constraints [skip ci] * [comment audit] Clarify mobile transcript recovery and session ownership [skip ci] * [comment audit] Clarify mobile queue and source-control compatibility [skip ci] * [comment audit] Condense storage and task launch constraints [skip ci] * [comment audit] Clarify task schemas and terminal document ownership [skip ci] * [comment audit] Tighten terminal input and viewport rationale [skip ci] * [comment audit] Shorten terminal and RPC recorder contracts [skip ci] * [comment audit] Condense recorder engine and connection contracts [skip ci] * [comment audit] Shorten host credential and relay recovery comments [skip ci] * [comment audit] Tighten pairing recovery and bundle transport rationale [skip ci] * [comment audit] Clarify RPC delivery and workspace catalog boundaries [skip ci] * [comment audit] Condense catalog and relay entry contracts [skip ci] * [comment audit] Shorten shutdown and SSH relay delivery contracts [skip ci] * [comment audit] Condense WSL relay and E2E regression rationale [skip ci] * [comment audit] Shorten cross-version harness and renderer fixture explanations [skip ci] * [comment audit] Condense paired-host and E2E lifecycle rationale [skip ci] * [comment audit] Shorten layout oracle and regression explanations [skip ci] * [comment audit] Condense paired recovery and ownership regression contracts [skip ci] * [comment audit] Shorten restart, sidebar and SSH lifecycle contracts [skip ci] * [comment audit] Condense terminal rendering and IME regression rationale [skip ci] * [comment audit] Shorten terminal recovery and benchmark contracts [skip ci] * [comment audit] Condense tooling and Windows lifecycle proof rationale [skip ci] * [comment audit] Condense workflow routing and release rationale [skip ci] * [comment audit] Condense mobile security and installer contracts [skip ci] * [comment audit] Shorten native rationale and stylesheet workarounds [skip ci] * [comment audit] Shorten Windows visibility probe rationale [skip ci] * [comment audit] Condense embedded release and signing rationale [skip ci] * [comment audit] Retain current main test behavior in final reconciliation [skip ci] * [comment audit] Complete final comment review across current source inventory * [comment audit] Handle consolidated audit reports before final CI * [comment audit] Preserve credential storage census detail [skip ci] * [comment audit] Scale broad changed E2E selections with verified timing shards * [comment audit] Align E2E assertions with browser close and settled session metadata * [comment audit] Match existing E2Es to current resource and windowing contracts * [comment audit] Observe the shared server outage notice in reconnect coverage * [comment audit] Make existing renderer probes measure settled tab and PTY work * [comment audit] Align palette E2E selector with task search * [comment audit] Refresh remaining palette search selectors * [comment audit] Keep selector correction within selected audit specs * [comment audit] Await emoji fixture settings inside renderer evaluation * [comment audit] Cancel fixture fit retries between terminal tests --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
349 lines
12 KiB
TypeScript
349 lines
12 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
||
import type { NativeChatMessage } from './native-chat-types'
|
||
import { imageReferenceToken } from './native-chat-image-reference'
|
||
import {
|
||
imageReferencesToPromptMarkers,
|
||
promptMarkersToImageReferences,
|
||
isImageSourceUserTurn,
|
||
normalizeImageTranscriptMessages,
|
||
normalizeNativeChatUserText,
|
||
normalizedNativeChatUserMessageText,
|
||
stripImagePromptMarker,
|
||
stripSentImagePromptMarkers
|
||
} from './native-chat-image-transcript-markers'
|
||
import { formatNativeChatFileReference } from './agent-image-paste'
|
||
|
||
function userText(id: string, text: string): NativeChatMessage {
|
||
return {
|
||
id,
|
||
role: 'user',
|
||
blocks: [{ type: 'text', text }],
|
||
timestamp: 1,
|
||
source: 'transcript'
|
||
}
|
||
}
|
||
|
||
describe('normalizeImageTranscriptMessages', () => {
|
||
it('merges the paired [Image: source]/[Image #1] turns into one image-ref turn', () => {
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('a', '[Image: source: /tmp/orca-paste-1-2.png]'),
|
||
userText('b', '[Image #1] describe this')
|
||
])
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]!.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/tmp/orca-paste-1-2.png' },
|
||
{ type: 'text', text: 'describe this' }
|
||
])
|
||
})
|
||
|
||
it('merges a source turn into a prompt with a trailing image marker', () => {
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('a', '[Image: source: /tmp/orca-paste-1-2.png]'),
|
||
userText('b', 'describe this[Image #1]')
|
||
])
|
||
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]!.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/tmp/orca-paste-1-2.png' },
|
||
{ type: 'text', text: 'describe this' }
|
||
])
|
||
})
|
||
|
||
it('folds and strips markers in later text blocks', () => {
|
||
const prompt: NativeChatMessage = {
|
||
...userText('prompt', 'unused'),
|
||
blocks: [
|
||
{ type: 'text', text: 'describe' },
|
||
{ type: 'image-ref', path: '/tmp/existing.png' },
|
||
{ type: 'text', text: '[Image #1] this' }
|
||
]
|
||
}
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('source', '[Image: source: /tmp/a.png]'),
|
||
prompt
|
||
])
|
||
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]?.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/tmp/a.png' },
|
||
{ type: 'text', text: 'describe' },
|
||
{ type: 'image-ref', path: '/tmp/existing.png' },
|
||
{ type: 'text', text: 'this' }
|
||
])
|
||
})
|
||
|
||
it.each([
|
||
['[Image #1] describe this', 'describe this'],
|
||
['[Image #1]\t describe this', 'describe this'],
|
||
[' \t[Image #1] describe this', 'describe this'],
|
||
['describe this [Image #1]', 'describe this'],
|
||
['describe this \t[Image #1]', 'describe this'],
|
||
['describe this [Image #1]\t ', 'describe this'],
|
||
['describe [Image #1] this', 'describe this'],
|
||
['describe [Image #1]\t this', 'describe \t this'],
|
||
['describe[Image #1]\t this', 'describe\t this'],
|
||
['describe\n[Image #1]\nthis', 'describe\n\nthis'],
|
||
['com[Image #1]pare this', 'compare this'],
|
||
['[Image #1] [Image #2]', ''],
|
||
['literal [Image #x] text', 'literal [Image #x] text']
|
||
])('strips image prompt markers anywhere in text', (text, expected) => {
|
||
expect(stripImagePromptMarker(text)).toBe(expected)
|
||
})
|
||
|
||
it('returns long marker-free whitespace without regex backtracking', () => {
|
||
const text = ' '.repeat(50_000)
|
||
expect(stripImagePromptMarker(text)).toBe(text)
|
||
})
|
||
|
||
it('shares marker-aware text matching across multiple text blocks', () => {
|
||
const message: NativeChatMessage = {
|
||
...userText('prompt', 'unused'),
|
||
blocks: [
|
||
{ type: 'text', text: 'look' },
|
||
{ type: 'image-ref', path: '/tmp/a.png' },
|
||
{ type: 'text', text: '[Image #1] here' }
|
||
]
|
||
}
|
||
|
||
expect(normalizeNativeChatUserText(' look [Image #1] here ')).toBe('look here')
|
||
expect(normalizedNativeChatUserMessageText(message)).toBe('look here')
|
||
})
|
||
|
||
it('recognizes only sole-text image-source user turns', () => {
|
||
const source = userText('source', '[Image: source: /tmp/a.png]')
|
||
expect(isImageSourceUserTurn(source)).toBe(true)
|
||
expect(isImageSourceUserTurn({ ...source, role: 'assistant' })).toBe(false)
|
||
expect(
|
||
isImageSourceUserTurn({
|
||
...source,
|
||
blocks: [...source.blocks, { type: 'text', text: 'caption' }]
|
||
})
|
||
).toBe(false)
|
||
})
|
||
|
||
it('converts a lone [Image: source] turn (no prompt) into an image-ref instead of raw text', () => {
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('a', '[Image: source: /Users/me/Pictures/hero-image-2.png]')
|
||
])
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]!.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/Users/me/Pictures/hero-image-2.png' }
|
||
])
|
||
})
|
||
|
||
it('folds every source and strips every prompt marker for a multi-image send', () => {
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('a', '[Image: source: /tmp/a.png]'),
|
||
userText('b', '[Image: source: /tmp/b.png]'),
|
||
userText('c', '[Image: source: /tmp/c.png]'),
|
||
userText('prompt', '[Image #1] [Image #2] [Image #3] compare these')
|
||
])
|
||
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]).toMatchObject({ id: 'prompt' })
|
||
expect(out[0]!.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/tmp/a.png' },
|
||
{ type: 'image-ref', path: '/tmp/b.png' },
|
||
{ type: 'image-ref', path: '/tmp/c.png' },
|
||
{ type: 'text', text: 'compare these' }
|
||
])
|
||
})
|
||
|
||
it('keeps all image refs when a multi-image send has no caption', () => {
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('a', '[Image: source: /tmp/a.png]'),
|
||
userText('b', '[Image: source: /tmp/b.png]'),
|
||
userText('prompt', '[Image #1] [Image #2]')
|
||
])
|
||
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]!.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/tmp/a.png' },
|
||
{ type: 'image-ref', path: '/tmp/b.png' }
|
||
])
|
||
})
|
||
|
||
it('preserves adjacent standalone image turns without a prompt marker', () => {
|
||
const out = normalizeImageTranscriptMessages([
|
||
userText('a', '[Image: source: /tmp/a.png]'),
|
||
userText('b', '[Image: source: /tmp/b.png]')
|
||
])
|
||
|
||
expect(out).toHaveLength(2)
|
||
expect(out.map((message) => message.id)).toEqual(['a', 'b'])
|
||
expect(out.map((message) => message.blocks)).toEqual([
|
||
[{ type: 'image-ref', path: '/tmp/a.png' }],
|
||
[{ type: 'image-ref', path: '/tmp/b.png' }]
|
||
])
|
||
})
|
||
|
||
it('folds a multi-block companion turn into one turn carrying every ref', () => {
|
||
// Claude emits one companion message with multiple source blocks; single-block matching missed multi-image pastes.
|
||
const out = normalizeImageTranscriptMessages([
|
||
{
|
||
id: 'companion',
|
||
role: 'user',
|
||
blocks: [
|
||
{ type: 'text', text: '[Image: source: /tmp/a.png]' },
|
||
{ type: 'text', text: '[Image: source: /tmp/b.png]' }
|
||
],
|
||
timestamp: 1,
|
||
source: 'transcript'
|
||
}
|
||
])
|
||
|
||
expect(out).toHaveLength(1)
|
||
expect(out[0]!.blocks).toEqual([
|
||
{ type: 'image-ref', path: '/tmp/a.png' },
|
||
{ type: 'image-ref', path: '/tmp/b.png' }
|
||
])
|
||
})
|
||
|
||
it('does not treat a turn mixing prose with a marker as an image-source turn', () => {
|
||
const messages = [
|
||
{
|
||
id: 'mixed',
|
||
role: 'user' as const,
|
||
blocks: [
|
||
{ type: 'text' as const, text: '[Image: source: /tmp/a.png]' },
|
||
{ type: 'text' as const, text: 'and here is what I think' }
|
||
],
|
||
timestamp: 1,
|
||
source: 'transcript' as const
|
||
}
|
||
]
|
||
const out = normalizeImageTranscriptMessages(messages)
|
||
|
||
expect(out[0]!.blocks.some((block) => block.type === 'image-ref')).toBe(false)
|
||
})
|
||
|
||
it('leaves ordinary user text untouched', () => {
|
||
const message = userText('a', 'how about this')
|
||
const messages = [message]
|
||
const out = normalizeImageTranscriptMessages(messages)
|
||
expect(out).toBe(messages)
|
||
expect(out[0]).toBe(message)
|
||
expect(out[0]!.blocks).toBe(message.blocks)
|
||
})
|
||
|
||
it('removes a whitespace-only first text block', () => {
|
||
const out = normalizeImageTranscriptMessages([userText('a', ' ')])
|
||
|
||
expect(out[0]?.blocks).toEqual([])
|
||
})
|
||
|
||
it('preserves unaffected rows when another row needs normalization', () => {
|
||
const before = userText('before', 'keep this row')
|
||
const marker = userText('marker', '[Image: source: /tmp/image.png]')
|
||
const after = userText('after', 'keep this row too')
|
||
const messages = [before, marker, after]
|
||
|
||
const out = normalizeImageTranscriptMessages(messages)
|
||
|
||
expect(out).not.toBe(messages)
|
||
expect(out[0]).toBe(before)
|
||
expect(out[2]).toBe(after)
|
||
})
|
||
|
||
it('leaves assistant messages untouched', () => {
|
||
const assistant: NativeChatMessage = {
|
||
id: 'a',
|
||
role: 'assistant',
|
||
blocks: [{ type: 'text', text: '[Image: source: /tmp/x.png]' }],
|
||
timestamp: 1,
|
||
source: 'transcript'
|
||
}
|
||
expect(normalizeImageTranscriptMessages([assistant])).toEqual([assistant])
|
||
})
|
||
})
|
||
|
||
describe('normalizeNativeChatUserText control bytes', () => {
|
||
it('drops the Ctrl+U a TUI pasted in front of the prompt', () => {
|
||
expect(normalizeNativeChatUserText('\u0015run the tests')).toBe('run the tests')
|
||
})
|
||
|
||
it('drops control bytes that landed inside the prompt', () => {
|
||
expect(normalizeNativeChatUserText('run\u0015 the\u0000 tests')).toBe('run the tests')
|
||
})
|
||
|
||
it('still finds the image marker behind a leading control byte', () => {
|
||
expect(normalizeNativeChatUserText('\u0015[Image #1] describe this')).toBe('describe this')
|
||
})
|
||
|
||
// Remove a bracketed-paste wrapper as one sequence so its printable tail cannot survive.
|
||
it('drops a bracketed-paste wrapper, not just its ESC introducer', () => {
|
||
expect(normalizeNativeChatUserText('\u001b[200~/tmp/orca-paste-1.png\u001b[201~')).toBe(
|
||
'/tmp/orca-paste-1.png'
|
||
)
|
||
})
|
||
|
||
it('preserves bracketed-paste-looking text without an ESC introducer', () => {
|
||
expect(normalizeNativeChatUserText('[200~literal[201~')).toBe('[200~literal[201~')
|
||
})
|
||
|
||
it('leaves tabs, newlines and carriage returns to the whitespace collapse', () => {
|
||
expect(normalizeNativeChatUserText('run\tthe\r\ntests')).toBe('run the tests')
|
||
})
|
||
})
|
||
|
||
describe('composer image chips at send and back', () => {
|
||
const chip = imageReferenceToken
|
||
|
||
it('numbers each chip by its image’s place among the images sent', () => {
|
||
expect(
|
||
imageReferencesToPromptMarkers(`after ${chip('b')} before ${chip('a')} again ${chip('b')}`, [
|
||
'a',
|
||
'b'
|
||
])
|
||
).toBe('after [Image #2] before [Image #1] again [Image #2]')
|
||
})
|
||
|
||
it('drops a chip whose image is not sent, and counts an image with no chip', () => {
|
||
expect(imageReferencesToPromptMarkers(`${chip('gone')}see ${chip('c')}`, ['a', 'c'])).toBe(
|
||
'see [Image #2]'
|
||
)
|
||
})
|
||
|
||
it('leaves typed text alone, even a typed marker or a path holding one', () => {
|
||
const text = 'compare [Image #2] with @/tmp/[Image #2].md and [Image](orca-image:bad id)'
|
||
expect(imageReferencesToPromptMarkers(text, ['a', 'b'])).toBe(text)
|
||
})
|
||
|
||
it('turns a returned message’s markers back into chips for its own images', () => {
|
||
expect(
|
||
promptMarkersToImageReferences('[Image #2] then [Image #1], not [Image #3]', ['r-1', 'r-3'])
|
||
).toBe(`${chip('r-3')} then ${chip('r-1')}, not [Image #3]`)
|
||
})
|
||
|
||
it('strips only the markers its images have, leaving a typed one past them', () => {
|
||
expect(stripSentImagePromptMarkers('[Image #1] then [Image #2], not [Image #3]', 2)).toBe(
|
||
'then , not [Image #3]'
|
||
)
|
||
expect(stripSentImagePromptMarkers('Explain the literal token [Image #1]', 0)).toBe(
|
||
'Explain the literal token [Image #1]'
|
||
)
|
||
})
|
||
|
||
it('leaves a quoted file path that reads like a marker as typed, back or stripped', () => {
|
||
const files = [
|
||
formatNativeChatFileReference('/repo/[Image #1].md'),
|
||
formatNativeChatFileReference('/repo/"q" [Image #1].md'),
|
||
formatNativeChatFileReference('/repo/"q" \'s\' [Image #1].md')
|
||
]
|
||
for (const file of files) {
|
||
expect(promptMarkersToImageReferences(`[Image #1] in ${file}`, ['r-1'])).toBe(
|
||
`${chip('r-1')} in ${file}`
|
||
)
|
||
expect(stripSentImagePromptMarkers(`[Image #1] in ${file} [Image #1]`, 1)).toBe(`in ${file}`)
|
||
}
|
||
})
|
||
|
||
it('sends a returned message with the numbers it had', () => {
|
||
const sent = 'compare [Image #1] with [Image #2]'
|
||
const ids = ['returned-m-1', 'returned-m-2']
|
||
expect(imageReferencesToPromptMarkers(promptMarkersToImageReferences(sent, ids), ids)).toBe(
|
||
sent
|
||
)
|
||
})
|
||
})
|