@12ui/design 0.2.74 → 0.2.75

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 (68) hide show
  1. package/SKILL.md +1 -1
  2. package/dist/cli-codex-readiness.d.ts +19 -6
  3. package/dist/cli-codex-readiness.d.ts.map +1 -1
  4. package/dist/cli-codex-readiness.js +36 -16
  5. package/dist/cli-codex-readiness.js.map +1 -1
  6. package/dist/cli-help/design.d.ts +9 -2
  7. package/dist/cli-help/design.d.ts.map +1 -1
  8. package/dist/cli-help/design.js +17 -2
  9. package/dist/cli-help/design.js.map +1 -1
  10. package/dist/cli-help/index.d.ts +9 -2
  11. package/dist/cli-help/index.d.ts.map +1 -1
  12. package/dist/cli-run.d.ts.map +1 -1
  13. package/dist/cli-run.js +6 -0
  14. package/dist/cli-run.js.map +1 -1
  15. package/dist/cli-usage.d.ts.map +1 -1
  16. package/dist/cli-usage.js +1 -0
  17. package/dist/cli-usage.js.map +1 -1
  18. package/dist/cli-workspace-command.js +1 -1
  19. package/dist/cli-workspace-command.js.map +1 -1
  20. package/dist/cli-workspace-draw.d.ts +23 -0
  21. package/dist/cli-workspace-draw.d.ts.map +1 -0
  22. package/dist/cli-workspace-draw.js +97 -0
  23. package/dist/cli-workspace-draw.js.map +1 -0
  24. package/dist/codex-binary.d.ts +20 -0
  25. package/dist/codex-binary.d.ts.map +1 -0
  26. package/dist/codex-binary.js +46 -0
  27. package/dist/codex-binary.js.map +1 -0
  28. package/dist/slate-image-executor.js +1 -1
  29. package/dist/slate-image-executor.js.map +1 -1
  30. package/dist/slate-local-image.d.ts +3 -6
  31. package/dist/slate-local-image.d.ts.map +1 -1
  32. package/dist/slate-local-image.js +2 -18
  33. package/dist/slate-local-image.js.map +1 -1
  34. package/dist/workspace-draw-client.d.ts +69 -0
  35. package/dist/workspace-draw-client.d.ts.map +1 -0
  36. package/dist/workspace-draw-client.js +193 -0
  37. package/dist/workspace-draw-client.js.map +1 -0
  38. package/dist/workspace-draw-codex.d.ts +49 -0
  39. package/dist/workspace-draw-codex.d.ts.map +1 -0
  40. package/dist/workspace-draw-codex.js +89 -0
  41. package/dist/workspace-draw-codex.js.map +1 -0
  42. package/dist/workspace-draw-engine.d.ts +39 -0
  43. package/dist/workspace-draw-engine.d.ts.map +1 -0
  44. package/dist/workspace-draw-engine.js +72 -0
  45. package/dist/workspace-draw-engine.js.map +1 -0
  46. package/dist/workspace-draw-messages.d.ts +45 -0
  47. package/dist/workspace-draw-messages.d.ts.map +1 -0
  48. package/dist/workspace-draw-messages.js +48 -0
  49. package/dist/workspace-draw-messages.js.map +1 -0
  50. package/dist/workspace-draw-run.d.ts +43 -0
  51. package/dist/workspace-draw-run.d.ts.map +1 -0
  52. package/dist/workspace-draw-run.js +328 -0
  53. package/dist/workspace-draw-run.js.map +1 -0
  54. package/dist/workspace-draw-store.d.ts +23 -0
  55. package/dist/workspace-draw-store.d.ts.map +1 -0
  56. package/dist/workspace-draw-store.js +65 -0
  57. package/dist/workspace-draw-store.js.map +1 -0
  58. package/dist/workspace-draw-types.d.ts +121 -0
  59. package/dist/workspace-draw-types.d.ts.map +1 -0
  60. package/dist/workspace-draw-types.js +21 -0
  61. package/dist/workspace-draw-types.js.map +1 -0
  62. package/open-design.json +2 -2
  63. package/package.json +1 -1
  64. package/runtime/codex-conversion/src/codex/home.mjs +43 -14
  65. package/runtime/codex-conversion/src/images/native-files.mjs +20 -0
  66. package/runtime/codex-conversion/src/images/native-invoke.mjs +5 -2
  67. package/runtime/codex-conversion/src/images/native-records.mjs +75 -3
  68. package/runtime/codex-conversion/src/images/native.mjs +7 -4
@@ -0,0 +1,65 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { resolveCliInstallLayout } from './cli-persistence.js';
5
+ import { acquireLock, RUNNER_HEARTBEAT_INTERVAL_MS, staleWhenHeartbeatLapsed } from './slate-lock.js';
6
+ import { RECORD_SCHEMA } from './workspace-draw-types.js';
7
+ /**
8
+ * Where one request's draws live on this machine: `<data root>/workspace-draw/<opId>/`, holding the op lock, the
9
+ * codex call store (`calls/`) and the run record (`run.json`). Nothing here is created before the sandbox gate has
10
+ * passed and the command is about to draw.
11
+ */
12
+ export const opRootOf = (dataRoot, opId) => path.join(dataRoot, 'workspace-draw', opId);
13
+ export const defaultDataRoot = (env) => resolveCliInstallLayout(env).dataRoot;
14
+ /**
15
+ * The op lock: one live run per op on this machine. A holder is alive while its process is and its heartbeat (the
16
+ * lock file's mtime, touched every few seconds) is fresh; a dead or silent holder's lock is taken over. Null when a
17
+ * live run holds it.
18
+ */
19
+ export const takeOpLock = async (opRoot) => {
20
+ await mkdir(opRoot, { recursive: true });
21
+ let lock;
22
+ try {
23
+ lock = await acquireLock({ file: path.join(opRoot, 'lock'), policy: staleWhenHeartbeatLapsed, waitMs: 0 });
24
+ }
25
+ catch (error) {
26
+ if (error.code === 'workspace_busy')
27
+ return null;
28
+ throw error;
29
+ }
30
+ const heartbeat = setInterval(() => { void lock.touch().catch(() => undefined); }, RUNNER_HEARTBEAT_INTERVAL_MS);
31
+ heartbeat.unref();
32
+ return {
33
+ release: async () => {
34
+ clearInterval(heartbeat);
35
+ await lock.release();
36
+ },
37
+ };
38
+ };
39
+ export const recordPathOf = (opRoot) => path.join(opRoot, 'run.json');
40
+ /** The record an earlier run left, if it is one this command wrote. */
41
+ export const readPreviousRecord = async (opRoot) => {
42
+ try {
43
+ const parsed = JSON.parse(await readFile(recordPathOf(opRoot), 'utf8'));
44
+ return parsed?.schema === RECORD_SCHEMA ? parsed : null;
45
+ }
46
+ catch (error) {
47
+ if (error.code === 'ENOENT' || error instanceof SyntaxError)
48
+ return null;
49
+ throw error;
50
+ }
51
+ };
52
+ /** Replace the record atomically: a reader never sees half a file. */
53
+ export const writeRecord = async (opRoot, record) => {
54
+ const file = recordPathOf(opRoot);
55
+ const temporary = `${file}.${randomUUID()}.tmp`;
56
+ try {
57
+ await writeFile(temporary, `${JSON.stringify(record, null, 2)}\n`, { flag: 'wx' });
58
+ await rename(temporary, file);
59
+ }
60
+ finally {
61
+ await rm(temporary, { force: true });
62
+ }
63
+ return file;
64
+ };
65
+ //# sourceMappingURL=workspace-draw-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-draw-store.js","sourceRoot":"","sources":["../src/workspace-draw-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * The shapes `12ui workspace draw` reads and writes: the frozen draw plan design.12ui.com serves (interface I1,
3
+ * `GET /api/v1/slate/ops/{opId}/plan`), and the command's own outcome codes and run record (interface I2).
4
+ */
5
+ export type PlanImageName = 'parent' | 'annotated' | `ref-${number}`;
6
+ export type SlatePlanImage = {
7
+ name: PlanImageName;
8
+ role: 'parent' | 'annotated' | 'reference';
9
+ referenceId: string | null;
10
+ versionId: string | null;
11
+ };
12
+ export type SlatePlanOption = {
13
+ key: string;
14
+ callId: string;
15
+ prompt: string;
16
+ promptSha256: string;
17
+ images: SlatePlanImage[];
18
+ frame: {
19
+ width: number;
20
+ height: number;
21
+ };
22
+ noReferenceReason: 'search_rate_limited' | 'search_unavailable' | 'too_few_fresh' | 'no_query' | null;
23
+ state: 'pending' | 'filled' | 'failed';
24
+ versionId: string | null;
25
+ label: string | null;
26
+ failure: string | null;
27
+ };
28
+ export type SlateDrawPlan = {
29
+ schema: '12ui.slate.plan/1';
30
+ workspace: string;
31
+ opId: string;
32
+ kind: 'round' | 'edit';
33
+ mode: 'root' | 'new' | 'like' | 'edit';
34
+ promptRevision: string;
35
+ planDigest: string;
36
+ state: 'running' | 'succeeded' | 'failed' | 'interrupted';
37
+ options: SlatePlanOption[];
38
+ };
39
+ export declare const PLAN_SCHEMA = "12ui.slate.plan/1";
40
+ /** The `code` field of the command's JSON line, and the exit code each one carries (I2). */
41
+ export declare const DRAW_EXIT_CODES: {
42
+ readonly done: 0;
43
+ readonly replayed: 0;
44
+ readonly partial: 1;
45
+ readonly failed: 1;
46
+ readonly token_invalid: 2;
47
+ readonly unknown_op: 2;
48
+ readonly op_in_progress: 2;
49
+ readonly usage: 2;
50
+ readonly needs_escalation: 3;
51
+ readonly no_engine: 4;
52
+ readonly cli_outdated: 5;
53
+ };
54
+ export type DrawCode = keyof typeof DRAW_EXIT_CODES;
55
+ /** The one engine of this release (decision D2-B): drawing on 12ui needs a 12ui sign-in and is not part of the command. */
56
+ export type DrawEngine = 'codex';
57
+ export type DrawImageRecord = {
58
+ name: string;
59
+ referenceId: string | null;
60
+ versionId: string | null;
61
+ sha256: string;
62
+ mimeType: string;
63
+ };
64
+ export type DrawOptionCodex = {
65
+ callDir: string;
66
+ tempHome: string;
67
+ tempHomeRemoved: boolean;
68
+ rollouts: string[];
69
+ imageGenCalls: number;
70
+ promptVerbatim: boolean | null;
71
+ revisedPrompt: string | null;
72
+ };
73
+ export type DrawOptionRecord = {
74
+ key: string;
75
+ callId: string;
76
+ /** Null when Codex could not draw and nothing was drawn (the option's failure was pushed). */
77
+ engine: DrawEngine | null;
78
+ prompt: string;
79
+ promptSha256: string;
80
+ images: DrawImageRecord[];
81
+ frame: {
82
+ width: number;
83
+ height: number;
84
+ };
85
+ startedAt: string;
86
+ finishedAt: string;
87
+ state: 'filled' | 'failed' | 'replayed';
88
+ failure: string | null;
89
+ versionId: string | null;
90
+ label: string | null;
91
+ codex?: DrawOptionCodex;
92
+ };
93
+ export type DrawRecordCodex = {
94
+ binary: string;
95
+ version: string;
96
+ home: string;
97
+ threadId: string | null;
98
+ sameHomeRollout: string | null;
99
+ };
100
+ export type DrawRecord = {
101
+ schema: '12ui.workspace.draw-record/1';
102
+ opId: string;
103
+ origin: string;
104
+ cliVersion: string;
105
+ /** Null when Codex could not draw here: the run spent nothing and pushed each pending option's failure. */
106
+ engine: DrawEngine | null;
107
+ engineReason: string;
108
+ codex: DrawRecordCodex | null;
109
+ plan: {
110
+ planDigest: string;
111
+ promptRevision: string;
112
+ kind: string;
113
+ mode: string;
114
+ };
115
+ startedAt: string;
116
+ finishedAt: string;
117
+ wallMs: number;
118
+ options: DrawOptionRecord[];
119
+ };
120
+ export declare const RECORD_SCHEMA = "12ui.workspace.draw-record/1";
121
+ //# sourceMappingURL=workspace-draw-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-draw-types.d.ts","sourceRoot":"","sources":["../src/workspace-draw-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The shapes `12ui workspace draw` reads and writes: the frozen draw plan design.12ui.com serves (interface I1,
3
+ * `GET /api/v1/slate/ops/{opId}/plan`), and the command's own outcome codes and run record (interface I2).
4
+ */
5
+ export const PLAN_SCHEMA = '12ui.slate.plan/1';
6
+ /** The `code` field of the command's JSON line, and the exit code each one carries (I2). */
7
+ export const DRAW_EXIT_CODES = {
8
+ done: 0,
9
+ replayed: 0,
10
+ partial: 1,
11
+ failed: 1,
12
+ token_invalid: 2,
13
+ unknown_op: 2,
14
+ op_in_progress: 2,
15
+ usage: 2,
16
+ needs_escalation: 3,
17
+ no_engine: 4,
18
+ cli_outdated: 5,
19
+ };
20
+ export const RECORD_SCHEMA = '12ui.workspace.draw-record/1';
21
+ //# sourceMappingURL=workspace-draw-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-draw-types.js","sourceRoot":"","sources":["../src/workspace-draw-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqCH,MAAM,CAAC,MAAM,WAAW,GAAG,mBAAmB,CAAC;AAE/C,4FAA4F;AAC5F,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,IAAI,EAAE,CAAC;IACP,QAAQ,EAAE,CAAC;IACX,OAAO,EAAE,CAAC;IACV,MAAM,EAAE,CAAC;IACT,aAAa,EAAE,CAAC;IAChB,UAAU,EAAE,CAAC;IACb,cAAc,EAAE,CAAC;IACjB,KAAK,EAAE,CAAC;IACR,gBAAgB,EAAE,CAAC;IACnB,SAAS,EAAE,CAAC;IACZ,YAAY,EAAE,CAAC;CACP,CAAC;AAsDX,MAAM,CAAC,MAAM,aAAa,GAAG,8BAA8B,CAAC"}
package/open-design.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "specVersion": "1.0.0",
4
4
  "name": "12ui-design",
5
5
  "title": "12UI Design",
6
- "version": "0.2.74",
6
+ "version": "0.2.75",
7
7
  "description": "Explore corpus-grounded interface directions, choose one, and convert it into production-ready responsive or native artifacts with 12UI.",
8
8
  "author": {
9
9
  "name": "Just Every",
@@ -49,7 +49,7 @@
49
49
  "command": "npx",
50
50
  "args": [
51
51
  "-y",
52
- "@12ui/design@0.2.74",
52
+ "@12ui/design@0.2.75",
53
53
  "mcp"
54
54
  ]
55
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12ui/design",
3
- "version": "0.2.74",
3
+ "version": "0.2.75",
4
4
  "type": "module",
5
5
  "description": "The canonical 12ui CLI, SDK, MCP server, and visual interface design skills",
6
6
  "main": "dist/index.js",
@@ -1,7 +1,9 @@
1
- import { mkdtemp, symlink, copyFile, readFile, realpath, chmod } from 'node:fs/promises';
1
+ import { mkdtemp, symlink, copyFile, readFile, realpath, chmod, rm } from 'node:fs/promises';
2
2
  import { tmpdir } from 'node:os';
3
3
  import path from 'node:path';
4
- import { spawnSync } from 'node:child_process';
4
+ import { execFile } from 'node:child_process';
5
+
6
+ const TEMP_HOME_PREFIX = '12ui-codex-';
5
7
 
6
8
  /** The planner receives an allowlisted environment, never provider credentials. @param {string} home */
7
9
  export function subscriptionEnv(home) {
@@ -20,18 +22,45 @@ export async function prepareHome({ authHome, binary, runDir, reasoning = 'low'
20
22
  if (authRoot === runRoot || authRoot.startsWith(runRoot + path.sep)) throw new Error('authHome must be outside runDir');
21
23
  const authPath = await realpath(path.join(authRoot, 'auth.json'));
22
24
  if (authPath === runRoot || authPath.startsWith(runRoot + path.sep)) throw new Error('Credentials must be outside runDir');
23
- const home = await mkdtemp(path.join(tmpdir(), '12ui-codex-'));
24
- await chmod(home, 0o700);
25
- await symlink(authPath, path.join(home, 'auth.json'));
26
- try { await copyFile(path.join(authRoot, 'models_cache.json'), path.join(home, 'models_cache.json')); }
27
- catch (error) { throw new Error('Explicit authHome must contain models_cache.json', { cause: error }); }
28
- const cache = JSON.parse(await readFile(path.join(home, 'models_cache.json'), 'utf8'));
29
- if (!cache.models?.some(model => model.slug === 'gpt-6-astra' && model.supported_reasoning_levels?.some(level => level.effort === reasoning))) {
30
- throw new Error(`Auth model cache does not support gpt-6-astra ${reasoning}`);
31
- }
32
- const login = spawnSync(binary, ['login', 'status'], { env: subscriptionEnv(home), encoding: 'utf8', timeout: 30000 });
33
- if (login.status !== 0 || !`${login.stdout ?? ''}${login.stderr ?? ''}`.includes('Logged in using ChatGPT')) {
34
- throw new Error('ChatGPT authentication unavailable; API authentication is not supported for the planner');
25
+ const home = await mkdtemp(path.join(tmpdir(), TEMP_HOME_PREFIX));
26
+ try {
27
+ await chmod(home, 0o700);
28
+ await symlink(authPath, path.join(home, 'auth.json'));
29
+ try { await copyFile(path.join(authRoot, 'models_cache.json'), path.join(home, 'models_cache.json')); }
30
+ catch (error) { throw new Error('Explicit authHome must contain models_cache.json', { cause: error }); }
31
+ const cache = JSON.parse(await readFile(path.join(home, 'models_cache.json'), 'utf8'));
32
+ if (!cache.models?.some(model => model.slug === 'gpt-6-astra' && model.supported_reasoning_levels?.some(level => level.effort === reasoning))) {
33
+ throw new Error(`Auth model cache does not support gpt-6-astra ${reasoning}`);
34
+ }
35
+ // Asynchronous, so parallel calls prepare their homes side by side instead of queueing on a blocked event loop.
36
+ const login = await new Promise(resolve => {
37
+ execFile(binary, ['login', 'status'], { env: subscriptionEnv(home), encoding: 'utf8', timeout: 30000 },
38
+ (error, stdout, stderr) => resolve({ ok: !error, output: `${stdout ?? ''}${stderr ?? ''}` }));
39
+ });
40
+ if (!login.ok || !login.output.includes('Logged in using ChatGPT')) {
41
+ throw new Error('ChatGPT authentication unavailable; API authentication is not supported for the planner');
42
+ }
43
+ } catch (error) {
44
+ // Nothing was dispatched from this home, so nothing will ever read it.
45
+ await removeTempHome(home);
46
+ throw error;
35
47
  }
36
48
  return { home, authentication: 'chatgpt' };
37
49
  }
50
+
51
+
52
+ /**
53
+ * Remove one temp home this module made. Only a directory directly under the temp root whose name
54
+ * carries the temp-home prefix is ever removed, so a corrupted record cannot point this at a real home.
55
+ * @param {string | null | undefined} home
56
+ */
57
+ export async function removeTempHome(home) {
58
+ if (!home) return;
59
+ const resolved = path.resolve(home);
60
+ const roots = new Set([path.resolve(tmpdir())]);
61
+ try { roots.add(await realpath(tmpdir())); } catch { /* the temp root itself is gone: nothing to match */ }
62
+ if (!roots.has(path.dirname(resolved)) || !path.basename(resolved).startsWith(TEMP_HOME_PREFIX)) {
63
+ throw new Error(`Refusing to remove ${resolved}: not a 12ui temp Codex home`);
64
+ }
65
+ await rm(resolved, { recursive: true, force: true });
66
+ }
@@ -9,3 +9,23 @@ export async function filesUnder(directory) {
9
9
  }
10
10
  return result.sort();
11
11
  }
12
+
13
+ /**
14
+ * The file extension the bytes really are, so an attached input is named by its type
15
+ * (`reference.webp`, `reference-2.png`). Bytes of no recognised image type keep the
16
+ * historical `.png` name: they are not an image under any name, and the call reports that.
17
+ * @param {Uint8Array} bytes
18
+ */
19
+ export function sniffImageExtension(bytes) {
20
+ const b=Buffer.from(bytes.buffer,bytes.byteOffset,bytes.byteLength);
21
+ if(b.length>=8&&b.subarray(0,8).equals(Buffer.from('89504e470d0a1a0a','hex')))return 'png';
22
+ if(b.length>=3&&b[0]===0xff&&b[1]===0xd8&&b[2]===0xff)return 'jpg';
23
+ if(b.length>=12&&b.toString('latin1',0,4)==='RIFF'&&b.toString('latin1',8,12)==='WEBP')return 'webp';
24
+ if(b.length>=6&&/^GIF8[79]a$/.test(b.toString('latin1',0,6)))return 'gif';
25
+ return 'png';
26
+ }
27
+
28
+ /** Attach-order input names: the first is `reference.<ext>`, the rest `reference-<n>.<ext>`. @param {Uint8Array[]} inputs */
29
+ export function inputFileNames(inputs) {
30
+ return inputs.map((bytes,index)=>`${index===0?'reference':`reference-${index+1}`}.${sniffImageExtension(bytes)}`);
31
+ }
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import {spawn} from 'node:child_process';
4
4
  import {createWriteStream} from 'node:fs';
5
5
  import {finished} from 'node:stream/promises';
6
- import {subscriptionEnv} from '../codex/home.mjs';
6
+ import {subscriptionEnv,removeTempHome} from '../codex/home.mjs';
7
7
  import {filesUnder} from './native-files.mjs';
8
8
  import {collectNative,packNativeReceipt} from './native-collect.mjs';
9
9
 
@@ -45,5 +45,8 @@ export async function invokeNative(spec, {callDir, persistMetadata}) {
45
45
  await fs.writeFile(path.join(callDir, 'completion.json'), JSON.stringify(completed, null, 2) + '\n', {flag: 'wx'});
46
46
  const rolloutPaths = (await filesUnder(path.join(auth.home, 'sessions')).catch(error => error.code === 'ENOENT' ? [] : Promise.reject(error))).filter(file => file.endsWith('.jsonl'));
47
47
  const receipt = await collectNative({rolloutPaths, outputDir: path.join(callDir, 'native-custody'), expected: spec.expected});
48
- return {bytes: await packNativeReceipt(receipt), metadata: {...completed,nativeToolMs:receipt.toolMs,quotaBefore:receipt.quotaBefore,quotaAfter:receipt.quotaAfter,tokenUsage:receipt.tokenUsage}};
48
+ // The rollouts now live in the call's native-custody; the temp home (~66 MB) is removed. A call that dies
49
+ // before this line keeps its home, because recovery collects from it.
50
+ await removeTempHome(auth.home);
51
+ return {bytes: await packNativeReceipt(receipt), metadata: {...completed,nativeToolMs:receipt.toolMs,quotaBefore:receipt.quotaBefore,quotaAfter:receipt.quotaAfter,tokenUsage:receipt.tokenUsage,tempHomeRemoved:true}};
49
52
  }
@@ -20,10 +20,10 @@ export function parseNativeRecords(text) {
20
20
  if(['error','turn_aborted'].includes(p.type)) diagnostics.push({timestamp:record.timestamp,...p});
21
21
  if (p.rate_limits) quota.push({line: index + 1, timestamp: record.timestamp, ...p.rate_limits});
22
22
  if (p.type === 'custom_tool_call' && p.name === 'exec') {
23
- const count = (p.input?.match(/tools\.image_gen__imagegen\s*\(/g) ?? []).length;
24
- if (count) calls.set(p.call_id, {callId: p.call_id, count, line: index + 1, timestamp: record.timestamp});
23
+ const prompts = imagegenPromptLiterals(p.input ?? '');
24
+ if (prompts.length) calls.set(p.call_id, {callId: p.call_id, count: prompts.length, prompts, line: index + 1, timestamp: record.timestamp});
25
25
  }
26
- if (p.type === 'function_call' && /^(image_gen[._]imagegen|image_gen__imagegen)$/.test(p.name ?? '')) calls.set(p.call_id, {callId: p.call_id, count: 1, line: index + 1, timestamp: record.timestamp});
26
+ if (p.type === 'function_call' && /^(image_gen[._]imagegen|image_gen__imagegen)$/.test(p.name ?? '')) calls.set(p.call_id, {callId: p.call_id, count: 1, prompts: [functionCallPrompt(p.arguments)], line: index + 1, timestamp: record.timestamp});
27
27
  if (item?.kind === 'image_gen.generation' && p.type === 'item_completed') {
28
28
  const bytes = typeof item.result === 'string' ? Buffer.from(item.result, 'base64') : null;
29
29
  const entry = {id: item.id, status: item.status, failure: item.failure ?? null, savedPath: item.savedPath ?? null,
@@ -55,3 +55,75 @@ export function geometryTreatments(actual, expected) {
55
55
  selection: 'unselected-pending-multiple-case-registration-review',
56
56
  };
57
57
  }
58
+
59
+ const ESCAPES = {n: '\n', t: '\t', r: '\r', b: '\b', f: '\f', v: '\v', 0: '\0'};
60
+
61
+ /**
62
+ * Decode the JavaScript string literal that opens at `start` (a ', " or ` quote). Returns the value and the index
63
+ * after the closing quote, or null when it is not one plain literal (a template with ${...}, or unterminated).
64
+ * @param {string} source @param {number} start
65
+ */
66
+ function readStringLiteral(source, start) {
67
+ const quote = source[start];
68
+ if (!['"', "'", '`'].includes(quote)) return null;
69
+ let value = '';
70
+ for (let index = start + 1; index < source.length; index += 1) {
71
+ const char = source[index];
72
+ if (char === quote) return {value, end: index + 1};
73
+ if (quote === '`' && char === '$' && source[index + 1] === '{') return null;
74
+ if (char !== '\\') { value += char; continue; }
75
+ const next = source[index + 1];
76
+ if (next === undefined) return null;
77
+ if (next === 'u') {
78
+ const braced = source[index + 2] === '{' ? /^\{([0-9a-fA-F]+)\}/.exec(source.slice(index + 2)) : null;
79
+ const hex = braced ? braced[1] : /^[0-9a-fA-F]{4}/.exec(source.slice(index + 2))?.[0];
80
+ if (!hex) return null;
81
+ value += String.fromCodePoint(Number.parseInt(hex, 16));
82
+ index += 1 + (braced ? braced[0].length : 4);
83
+ } else if (next === 'x') {
84
+ const hex = /^[0-9a-fA-F]{2}/.exec(source.slice(index + 2))?.[0];
85
+ if (!hex) return null;
86
+ value += String.fromCharCode(Number.parseInt(hex, 16));
87
+ index += 3;
88
+ } else if (next === '\r' || next === '\n') {
89
+ // A line continuation adds nothing.
90
+ index += next === '\r' && source[index + 2] === '\n' ? 2 : 1;
91
+ } else {
92
+ value += Object.hasOwn(ESCAPES, next) ? ESCAPES[next] : next;
93
+ index += 1;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /**
100
+ * The `prompt` the nested driver passed to each `tools.image_gen__imagegen({...})` call in an exec input, one entry
101
+ * per call: the literal's value when it is one plain string literal, null when it is built some other way (a
102
+ * variable, a concatenation, a template with substitutions), which is then unknown rather than assumed.
103
+ * @param {string} input
104
+ * @returns {(string | null)[]}
105
+ */
106
+ export function imagegenPromptLiterals(input) {
107
+ const prompts = [];
108
+ const call = /tools\.image_gen__imagegen\s*\(/g;
109
+ let match;
110
+ while ((match = call.exec(input))) {
111
+ const after = input.slice(match.index + match[0].length);
112
+ const next = /tools\.image_gen__imagegen\s*\(/.exec(after);
113
+ const scope = next ? after.slice(0, next.index) : after;
114
+ const key = /(?:^|[{,\s])(?:prompt|"prompt"|'prompt')\s*:\s*/.exec(scope);
115
+ if (!key) { prompts.push(null); continue; }
116
+ const literal = readStringLiteral(scope, key.index + key[0].length);
117
+ const following = literal ? scope.slice(literal.end).trimStart()[0] : undefined;
118
+ prompts.push(literal && (following === ',' || following === '}') ? literal.value : null);
119
+ }
120
+ return prompts;
121
+ }
122
+
123
+ /** @param {unknown} argumentsText */
124
+ function functionCallPrompt(argumentsText) {
125
+ try {
126
+ const parsed = JSON.parse(String(argumentsText));
127
+ return typeof parsed?.prompt === 'string' ? parsed.prompt : null;
128
+ } catch { return null; }
129
+ }
@@ -2,10 +2,10 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
4
  import sharp from 'sharp';
5
- import {prepareHome} from '../codex/home.mjs';
5
+ import {prepareHome,removeTempHome} from '../codex/home.mjs';
6
6
  import {invokeNative} from './native-invoke.mjs';
7
7
  import {collectNative,packNativeReceipt} from './native-collect.mjs';
8
- import {filesUnder} from './native-files.mjs';
8
+ import {filesUnder,inputFileNames} from './native-files.mjs';
9
9
  import {sha256,pngDimensions,parseNativeRecords} from './native-records.mjs';
10
10
 
11
11
  export async function decodeNativeResponse(bytes) {
@@ -23,7 +23,8 @@ export async function executeNativeCall({request,input,callId,store,codexBinary=
23
23
  return store.runCall(callId,request,async({callDir,persistMetadata})=>{
24
24
  // One reference is the conversion case; a list attaches every image in order (design workspace edits and variations).
25
25
  const inputs=Array.isArray(input)?input:[input];
26
- const referencePaths=inputs.map((_bytes,index)=>path.join(callDir,index===0?'reference.png':`reference-${index+1}.png`));
26
+ // Each input is named by its sniffed type (reference.webp, reference-2.png), never a blanket .png.
27
+ const referencePaths=inputFileNames(inputs).map(name=>path.join(callDir,name));
27
28
  for(const [index,bytes] of inputs.entries())await fs.writeFile(referencePaths[index],bytes,{flag:'wx'});
28
29
  const referencePath=referencePaths[0];
29
30
  const inspect=referencePaths.length===1?`Use tools.view_image once to inspect ${referencePath}.`:`Use tools.view_image once for each of these files, in this order: ${referencePaths.join(', ')}.`;
@@ -40,6 +41,8 @@ export async function executeNativeCall({request,input,callId,store,codexBinary=
40
41
  if(audit.state!=='completed-awaiting-dimensions')return null;
41
42
  const receipt=await collectNative({rolloutPaths,outputDir:path.join(callDir,'native-custody'),expected:request.expected});
42
43
  if(!['exact-size-pass','size-mismatch'].includes(receipt.state))return null;
43
- return {bytes:await packNativeReceipt(receipt),metadata:{...metadata,nativeToolMs:receipt.toolMs,quotaBefore:receipt.quotaBefore,quotaAfter:receipt.quotaAfter,tokenUsage:receipt.tokenUsage,collectionRecovery:true}};
44
+ // Recovered and in custody: the temp home has nothing left that anything will read.
45
+ await removeTempHome(metadata.home);
46
+ return {bytes:await packNativeReceipt(receipt),metadata:{...metadata,nativeToolMs:receipt.toolMs,quotaBefore:receipt.quotaBefore,quotaAfter:receipt.quotaAfter,tokenUsage:receipt.tokenUsage,collectionRecovery:true,tempHomeRemoved:true}};
44
47
  }});
45
48
  }