feat(fs): add a minimal read_image tool over the attachment and fs seams

The model reads a PNG/JPEG/WebP/GIF file, the bytes commit through the
durable attachment lifecycle, and the tool result carries the real
ImageBlock so the image enters context from the next request onward.
FileSystem gains a bounded readBytes primitive (local + E2B providers);
registration is conditional on the attachment store, and a strict
execution gate refuses routes that do not declare image input, so a
text route's durable history stays free of image blocks. llm-replay
models may declare inputModalities, letting keyless ACP snapshots pin
both the sha256-referenced success and the verbatim refusal.

Supersedes the withdrawn route-scoped design of PR #598; the decision
record is .agents/notes/implemented/feature/2026-08-10-minimal-read-image-tool.md.
This commit is contained in:
creatixchu
2026-08-10 15:09:07 +08:00
parent 3764ce62a5
commit 1861a3fc7c
65 changed files with 1973 additions and 67 deletions
+26
View File
@@ -38,6 +38,8 @@ const WORKSPACE_CONTEXT_CONFIG = fileURLToPath(new URL('../workspace-context.cor
const ADVANCED_CONFIG = fileURLToPath(new URL('../advanced.cordis.yml', import.meta.url))
const FS_CONFIG = fileURLToPath(new URL('../fs.cordis.yml', import.meta.url))
const SESSION_QUERY_CONFIG = fileURLToPath(new URL('../session-query.cordis.yml', import.meta.url))
const IMAGE_CONFIG = fileURLToPath(new URL('../image.cordis.yml', import.meta.url))
const IMAGE_TEXT_ROUTE_CONFIG = fileURLToPath(new URL('../image-text-route.cordis.yml', import.meta.url))
const PTY_CONFIG = fileURLToPath(new URL('../pty.cordis.yml', import.meta.url))
const DEPTH_TWO_CONFIG = fileURLToPath(new URL('../depth-two.cordis.yml', import.meta.url))
const CHILD_QUESTION_CONFIG = fileURLToPath(new URL('../child-question.cordis.yml', import.meta.url))
@@ -153,6 +155,30 @@ const SCENARIOS: Scenario[] = [
configPath: SESSION_QUERY_CONFIG,
posixOnly: true,
},
// Authored keyless replays through the assembled app: the replay catalog
// declares flash image-capable (success) or text-only (refusal), and the
// real read_image tool executes against the workspace fixture and the real
// attachment store. Both boot the same composed header (the tool registers
// with the attachment store, independent of route), so they share one class.
{
name: 'read-image',
hasModelTurn: true,
recorded: false,
pinsHeader: true,
headerClass: 'image',
// The overlay adds no prompt section (read_image carries no guidance), so
// the composed system prompt is byte-identical to the default class; only
// the tool-schema sidecar is class-specific.
systemPromptSource: 'text-turn',
configPath: IMAGE_CONFIG,
},
{
name: 'read-image-text-route',
hasModelTurn: true,
recorded: false,
headerClass: 'image',
configPath: IMAGE_TEXT_ROUTE_CONFIG,
},
{
name: 'pty-tools',
hasModelTurn: true,