Skip to content

Commit 32a38f1

Browse files
committed
fix: keep codex cli images in workspace
1 parent feecc53 commit 32a38f1

8 files changed

Lines changed: 120 additions & 53 deletions

extensions/openai/cli-backend.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function buildOpenAICodexCliBackend(): CliBackendPlugin {
5555
systemPromptWhen: "first",
5656
imageArg: "--image",
5757
imageMode: "repeat",
58+
imagePathScope: "workspace",
5859
reliability: {
5960
watchdog: {
6061
fresh: { ...CLI_FRESH_WATCHDOG_DEFAULTS },

src/agents/cli-backends.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,7 @@ beforeEach(() => {
264264
systemPromptFileConfigArg: "-c",
265265
systemPromptFileConfigKey: "model_instructions_file",
266266
systemPromptWhen: "first",
267+
imagePathScope: "workspace",
267268
reliability: {
268269
watchdog: {
269270
fresh: {
@@ -779,6 +780,7 @@ describe("resolveCliBackendConfig google-gemini-cli defaults", () => {
779780
expect(resolved?.config.systemPromptFileConfigArg).toBe("-c");
780781
expect(resolved?.config.systemPromptFileConfigKey).toBe("model_instructions_file");
781782
expect(resolved?.config.systemPromptWhen).toBe("first");
783+
expect(resolved?.config.imagePathScope).toBe("workspace");
782784
});
783785
});
784786

src/agents/cli-runner.helpers.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,11 +275,15 @@ describe("writeCliImages", () => {
275275
baseArgs: ["exec", "--json"],
276276
modelId: "gpt-5.4",
277277
imagePaths: prepared.imagePaths,
278+
promptArg: "describe the attached image",
278279
useResume: false,
279280
});
280281

281282
const imageArgIndex = argv.indexOf("--image");
283+
const promptIndex = argv.indexOf("describe the attached image");
282284
expect(imageArgIndex).toBeGreaterThanOrEqual(0);
285+
expect(promptIndex).toBeGreaterThanOrEqual(0);
286+
expect(imageArgIndex).toBeGreaterThan(promptIndex);
283287
expect(argv[imageArgIndex + 1]).toContain("openclaw-cli-images");
284288
expect(argv[imageArgIndex + 1]).not.toBe(sourceImage);
285289

src/agents/cli-runner/helpers.ts

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,18 @@ export function buildCliArgs(params: {
391391
args.push(params.backend.sessionArg, params.sessionId);
392392
}
393393
}
394+
if (params.promptArg !== undefined) {
395+
let replacedPromptPlaceholder = false;
396+
for (let i = 0; i < args.length; i += 1) {
397+
if (args[i] === "{prompt}") {
398+
args[i] = params.promptArg;
399+
replacedPromptPlaceholder = true;
400+
}
401+
}
402+
if (!replacedPromptPlaceholder) {
403+
args.push(params.promptArg);
404+
}
405+
}
394406
if (params.imagePaths && params.imagePaths.length > 0) {
395407
const mode = params.backend.imageMode ?? "repeat";
396408
const imageArg = params.backend.imageArg;
@@ -404,18 +416,5 @@ export function buildCliArgs(params: {
404416
}
405417
}
406418
}
407-
if (params.promptArg !== undefined) {
408-
let replacedPromptPlaceholder = false;
409-
for (let i = 0; i < args.length; i += 1) {
410-
if (args[i] === "{prompt}") {
411-
args[i] = params.promptArg;
412-
replacedPromptPlaceholder = true;
413-
}
414-
}
415-
if (replacedPromptPlaceholder) {
416-
return args;
417-
}
418-
args.push(params.promptArg);
419-
}
420419
return args;
421420
}

src/gateway/gateway-cli-backend.live-probe-helpers.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ export async function verifyCliBackendImageProbe(params: {
267267
// still receives a local file path, but now via the runner code we
268268
// actually want to validate instead of an ad hoc prompt-only shortcut.
269269
message:
270-
"Read the large word printed at the bottom of the attached image. " +
271-
"Reply with that word in lowercase and nothing else.",
270+
"What animal is drawn in the attached image? Reply with only the lowercase animal name.",
272271
attachments: [
273272
{
274273
mimeType: "image/png",

src/gateway/gateway-cli-backend.live.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,13 @@ describeLive("gateway live (cli backend)", () => {
205205
clearEnv: filteredCliClearEnv.length > 0 ? filteredCliClearEnv : undefined,
206206
env: Object.keys(preservedCliEnv).length > 0 ? preservedCliEnv : undefined,
207207
systemPromptWhen: providerDefaults?.systemPromptWhen ?? "never",
208-
...(cliImageArg ? { imageArg: cliImageArg, imageMode: cliImageMode } : {}),
208+
...(cliImageArg
209+
? {
210+
imageArg: cliImageArg,
211+
imageMode: cliImageMode,
212+
imagePathScope: providerDefaults?.imagePathScope,
213+
}
214+
: {}),
209215
},
210216
},
211217
sandbox: { mode: "off" },
@@ -355,11 +361,15 @@ describeLive("gateway live (cli backend)", () => {
355361
}
356362

357363
if (enableCliImageProbe) {
358-
logCliBackendLiveStep("image-probe:start", { sessionKey });
364+
const imageSessionKey =
365+
providerId === "codex-cli"
366+
? `agent:dev:live-cli-backend-image:${randomUUID()}`
367+
: sessionKey;
368+
logCliBackendLiveStep("image-probe:start", { sessionKey: imageSessionKey });
359369
await verifyCliBackendImageProbe({
360370
client,
361371
providerId,
362-
sessionKey,
372+
sessionKey: imageSessionKey,
363373
tempDir,
364374
bootstrapWorkspace,
365375
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { describe, expect, it } from "vitest";
2+
import { renderCatFacePngBase64 } from "./live-image-probe.js";
3+
4+
describe("live image probe", () => {
5+
it("leaves room for the unclipped bottom CAT label", () => {
6+
const png = Buffer.from(renderCatFacePngBase64(), "base64");
7+
8+
expect(png.toString("ascii", 1, 4)).toBe("PNG");
9+
expect(png.readUInt32BE(16)).toBe(256);
10+
expect(png.readUInt32BE(20)).toBeGreaterThanOrEqual(274);
11+
});
12+
});

src/gateway/live-image-probe.ts

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,49 @@ function fillTriangle(params: {
200200
}
201201
}
202202

203+
function drawBlockCatLabel(params: {
204+
buf: Buffer;
205+
width: number;
206+
height: number;
207+
x: number;
208+
y: number;
209+
color: { r: number; g: number; b: number; a?: number };
210+
}) {
211+
const t = 12;
212+
const h = 78;
213+
const w = 58;
214+
const gap = 20;
215+
const cX = params.x;
216+
const aX = cX + w + gap;
217+
const tX = aX + w + gap;
218+
219+
fillRect({ ...params, x: cX, y: params.y, w, h: t, color: params.color });
220+
fillRect({ ...params, x: cX, y: params.y, w: t, h, color: params.color });
221+
fillRect({ ...params, x: cX, y: params.y + h - t, w, h: t, color: params.color });
222+
223+
fillRect({ ...params, x: aX, y: params.y, w, h: t, color: params.color });
224+
fillRect({ ...params, x: aX, y: params.y, w: t, h, color: params.color });
225+
fillRect({ ...params, x: aX + w - t, y: params.y, w: t, h, color: params.color });
226+
fillRect({
227+
...params,
228+
x: aX,
229+
y: params.y + Math.floor((h - t) / 2),
230+
w,
231+
h: t,
232+
color: params.color,
233+
});
234+
235+
fillRect({ ...params, x: tX, y: params.y, w, h: t, color: params.color });
236+
fillRect({
237+
...params,
238+
x: tX + Math.floor((w - t) / 2),
239+
y: params.y,
240+
w: t,
241+
h,
242+
color: params.color,
243+
});
244+
}
245+
203246
export function renderCatNoncePngBase64(nonce: string): string {
204247
const top = "CAT";
205248
const bottom = nonce.toUpperCase();
@@ -242,7 +285,7 @@ export function renderCatNoncePngBase64(nonce: string): string {
242285

243286
export function renderCatFacePngBase64(): string {
244287
const width = 256;
245-
const height = 256;
288+
const height = 288;
246289
const buf = Buffer.alloc(width * height * 4, 255);
247290
const outline = { r: 40, g: 40, b: 40 };
248291
const innerEar = { r: 245, g: 182, b: 193 };
@@ -253,54 +296,54 @@ export function renderCatFacePngBase64(): string {
253296
buf,
254297
width,
255298
height,
256-
a: { x: 62, y: 86 },
257-
b: { x: 106, y: 18 },
258-
c: { x: 136, y: 104 },
299+
a: { x: 62, y: 74 },
300+
b: { x: 106, y: 12 },
301+
c: { x: 134, y: 88 },
259302
color: outline,
260303
});
261304
fillTriangle({
262305
buf,
263306
width,
264307
height,
265-
a: { x: 194, y: 86 },
266-
b: { x: 150, y: 18 },
267-
c: { x: 120, y: 104 },
308+
a: { x: 194, y: 74 },
309+
b: { x: 150, y: 12 },
310+
c: { x: 122, y: 88 },
268311
color: outline,
269312
});
270313
fillTriangle({
271314
buf,
272315
width,
273316
height,
274-
a: { x: 78, y: 82 },
275-
b: { x: 106, y: 38 },
276-
c: { x: 122, y: 92 },
317+
a: { x: 80, y: 70 },
318+
b: { x: 106, y: 34 },
319+
c: { x: 122, y: 80 },
277320
color: innerEar,
278321
});
279322
fillTriangle({
280323
buf,
281324
width,
282325
height,
283-
a: { x: 178, y: 82 },
284-
b: { x: 150, y: 38 },
285-
c: { x: 134, y: 92 },
326+
a: { x: 176, y: 70 },
327+
b: { x: 150, y: 34 },
328+
c: { x: 134, y: 80 },
286329
color: innerEar,
287330
});
288331
fillEllipse({
289332
buf,
290333
width,
291334
height,
292335
cx: 128,
293-
cy: 142,
294-
rx: 82,
295-
ry: 78,
336+
cy: 112,
337+
rx: 78,
338+
ry: 66,
296339
color: outline,
297340
});
298341
fillEllipse({
299342
buf,
300343
width,
301344
height,
302345
cx: 98,
303-
cy: 126,
346+
cy: 100,
304347
rx: 9,
305348
ry: 12,
306349
color: { r: 255, g: 255, b: 255 },
@@ -310,7 +353,7 @@ export function renderCatFacePngBase64(): string {
310353
width,
311354
height,
312355
cx: 158,
313-
cy: 126,
356+
cy: 100,
314357
rx: 9,
315358
ry: 12,
316359
color: { r: 255, g: 255, b: 255 },
@@ -320,34 +363,31 @@ export function renderCatFacePngBase64(): string {
320363
width,
321364
height,
322365
cx: 128,
323-
cy: 158,
366+
cy: 130,
324367
rx: 22,
325-
ry: 18,
368+
ry: 17,
326369
color: { r: 255, g: 255, b: 255 },
327370
});
328371
fillTriangle({
329372
buf,
330373
width,
331374
height,
332-
a: { x: 128, y: 150 },
333-
b: { x: 118, y: 164 },
334-
c: { x: 138, y: 164 },
375+
a: { x: 128, y: 122 },
376+
b: { x: 118, y: 136 },
377+
c: { x: 138, y: 136 },
335378
color: nose,
336379
});
337-
fillRect({ buf, width, height, x: 127, y: 164, w: 2, h: 16, color: whisker });
338-
fillRect({ buf, width, height, x: 74, y: 161, w: 42, h: 2, color: whisker });
339-
fillRect({ buf, width, height, x: 140, y: 161, w: 42, h: 2, color: whisker });
340-
fillRect({ buf, width, height, x: 76, y: 173, w: 38, h: 2, color: whisker });
341-
fillRect({ buf, width, height, x: 142, y: 173, w: 38, h: 2, color: whisker });
342-
fillRect({ buf, width, height, x: 85, y: 185, w: 30, h: 2, color: whisker });
343-
fillRect({ buf, width, height, x: 141, y: 185, w: 30, h: 2, color: whisker });
344-
drawText({
380+
fillRect({ buf, width, height, x: 127, y: 136, w: 2, h: 15, color: whisker });
381+
fillRect({ buf, width, height, x: 74, y: 134, w: 42, h: 2, color: whisker });
382+
fillRect({ buf, width, height, x: 140, y: 134, w: 42, h: 2, color: whisker });
383+
fillRect({ buf, width, height, x: 80, y: 146, w: 34, h: 2, color: whisker });
384+
fillRect({ buf, width, height, x: 142, y: 146, w: 34, h: 2, color: whisker });
385+
drawBlockCatLabel({
345386
buf,
346387
width,
347-
x: Math.floor((width - measureTextWidthPx("CAT", 10)) / 2),
348-
y: 212,
349-
text: "CAT",
350-
scale: 10,
388+
height,
389+
x: 21,
390+
y: 190,
351391
color: outline,
352392
});
353393

0 commit comments

Comments
 (0)