@12ui/design 0.2.73 → 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 (142) 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/mcp-slate-command.d.ts.map +1 -1
  29. package/dist/mcp-slate-command.js +6 -1
  30. package/dist/mcp-slate-command.js.map +1 -1
  31. package/dist/mcp-slate-tool-defs.js +3 -3
  32. package/dist/mcp-slate-tool-defs.js.map +1 -1
  33. package/dist/mcp-slate-ui-copy.d.ts +30 -19
  34. package/dist/mcp-slate-ui-copy.d.ts.map +1 -1
  35. package/dist/mcp-slate-ui-copy.js +51 -23
  36. package/dist/mcp-slate-ui-copy.js.map +1 -1
  37. package/dist/mcp-slate-ui-script-dialog.d.ts +15 -14
  38. package/dist/mcp-slate-ui-script-dialog.d.ts.map +1 -1
  39. package/dist/mcp-slate-ui-script-dialog.js +61 -72
  40. package/dist/mcp-slate-ui-script-dialog.js.map +1 -1
  41. package/dist/mcp-slate-ui-script-inline.d.ts.map +1 -1
  42. package/dist/mcp-slate-ui-script-inline.js +9 -6
  43. package/dist/mcp-slate-ui-script-inline.js.map +1 -1
  44. package/dist/mcp-slate-ui-script-inspiration.d.ts +3 -2
  45. package/dist/mcp-slate-ui-script-inspiration.d.ts.map +1 -1
  46. package/dist/mcp-slate-ui-script-inspiration.js +29 -9
  47. package/dist/mcp-slate-ui-script-inspiration.js.map +1 -1
  48. package/dist/mcp-slate-ui-script-more.d.ts +16 -14
  49. package/dist/mcp-slate-ui-script-more.d.ts.map +1 -1
  50. package/dist/mcp-slate-ui-script-more.js +120 -56
  51. package/dist/mcp-slate-ui-script-more.js.map +1 -1
  52. package/dist/mcp-slate-ui-script-remote.d.ts +2 -2
  53. package/dist/mcp-slate-ui-script-remote.js +2 -2
  54. package/dist/mcp-slate-ui-script-scribble.js +1 -1
  55. package/dist/mcp-slate-ui-script-stage.d.ts +3 -2
  56. package/dist/mcp-slate-ui-script-stage.d.ts.map +1 -1
  57. package/dist/mcp-slate-ui-script-stage.js +7 -8
  58. package/dist/mcp-slate-ui-script-stage.js.map +1 -1
  59. package/dist/mcp-slate-ui-script-state.js +3 -3
  60. package/dist/mcp-slate-ui-script.d.ts +1 -2
  61. package/dist/mcp-slate-ui-script.d.ts.map +1 -1
  62. package/dist/mcp-slate-ui-script.js +6 -4
  63. package/dist/mcp-slate-ui-script.js.map +1 -1
  64. package/dist/mcp-slate-ui-styles-dialog.d.ts +12 -9
  65. package/dist/mcp-slate-ui-styles-dialog.d.ts.map +1 -1
  66. package/dist/mcp-slate-ui-styles-dialog.js +48 -26
  67. package/dist/mcp-slate-ui-styles-dialog.js.map +1 -1
  68. package/dist/mcp-slate-ui-styles-stage.d.ts +15 -8
  69. package/dist/mcp-slate-ui-styles-stage.d.ts.map +1 -1
  70. package/dist/mcp-slate-ui-styles-stage.js +32 -18
  71. package/dist/mcp-slate-ui-styles-stage.js.map +1 -1
  72. package/dist/mcp-slate-ui-styles.d.ts +5 -0
  73. package/dist/mcp-slate-ui-styles.d.ts.map +1 -1
  74. package/dist/mcp-slate-ui-styles.js +21 -6
  75. package/dist/mcp-slate-ui-styles.js.map +1 -1
  76. package/dist/mcp-slate-ui.d.ts.map +1 -1
  77. package/dist/mcp-slate-ui.js +4 -3
  78. package/dist/mcp-slate-ui.js.map +1 -1
  79. package/dist/slate-actions.d.ts +7 -2
  80. package/dist/slate-actions.d.ts.map +1 -1
  81. package/dist/slate-actions.js +14 -8
  82. package/dist/slate-actions.js.map +1 -1
  83. package/dist/slate-handoff-options.d.ts +1 -1
  84. package/dist/slate-handoff-options.d.ts.map +1 -1
  85. package/dist/slate-handoff-options.js +8 -18
  86. package/dist/slate-handoff-options.js.map +1 -1
  87. package/dist/slate-image-executor.js +1 -1
  88. package/dist/slate-image-executor.js.map +1 -1
  89. package/dist/slate-inspiration.d.ts +1 -1
  90. package/dist/slate-inspiration.js +1 -1
  91. package/dist/slate-local-image.d.ts +3 -6
  92. package/dist/slate-local-image.d.ts.map +1 -1
  93. package/dist/slate-local-image.js +2 -18
  94. package/dist/slate-local-image.js.map +1 -1
  95. package/dist/slate-view-contract.d.ts +2 -3
  96. package/dist/slate-view-contract.d.ts.map +1 -1
  97. package/dist/slate-workspace-parse.d.ts.map +1 -1
  98. package/dist/slate-workspace-parse.js +6 -1
  99. package/dist/slate-workspace-parse.js.map +1 -1
  100. package/dist/workspace-draw-client.d.ts +69 -0
  101. package/dist/workspace-draw-client.d.ts.map +1 -0
  102. package/dist/workspace-draw-client.js +193 -0
  103. package/dist/workspace-draw-client.js.map +1 -0
  104. package/dist/workspace-draw-codex.d.ts +49 -0
  105. package/dist/workspace-draw-codex.d.ts.map +1 -0
  106. package/dist/workspace-draw-codex.js +89 -0
  107. package/dist/workspace-draw-codex.js.map +1 -0
  108. package/dist/workspace-draw-engine.d.ts +39 -0
  109. package/dist/workspace-draw-engine.d.ts.map +1 -0
  110. package/dist/workspace-draw-engine.js +72 -0
  111. package/dist/workspace-draw-engine.js.map +1 -0
  112. package/dist/workspace-draw-messages.d.ts +45 -0
  113. package/dist/workspace-draw-messages.d.ts.map +1 -0
  114. package/dist/workspace-draw-messages.js +48 -0
  115. package/dist/workspace-draw-messages.js.map +1 -0
  116. package/dist/workspace-draw-run.d.ts +43 -0
  117. package/dist/workspace-draw-run.d.ts.map +1 -0
  118. package/dist/workspace-draw-run.js +328 -0
  119. package/dist/workspace-draw-run.js.map +1 -0
  120. package/dist/workspace-draw-store.d.ts +23 -0
  121. package/dist/workspace-draw-store.d.ts.map +1 -0
  122. package/dist/workspace-draw-store.js +65 -0
  123. package/dist/workspace-draw-store.js.map +1 -0
  124. package/dist/workspace-draw-types.d.ts +121 -0
  125. package/dist/workspace-draw-types.d.ts.map +1 -0
  126. package/dist/workspace-draw-types.js +21 -0
  127. package/dist/workspace-draw-types.js.map +1 -0
  128. package/open-design.json +2 -2
  129. package/package.json +1 -1
  130. package/runtime/codex-conversion/src/codex/home.mjs +43 -14
  131. package/runtime/codex-conversion/src/images/native-files.mjs +20 -0
  132. package/runtime/codex-conversion/src/images/native-invoke.mjs +5 -2
  133. package/runtime/codex-conversion/src/images/native-records.mjs +75 -3
  134. package/runtime/codex-conversion/src/images/native.mjs +7 -4
  135. package/dist/mcp-slate-ui-script-thumbtools.d.ts +0 -23
  136. package/dist/mcp-slate-ui-script-thumbtools.d.ts.map +0 -1
  137. package/dist/mcp-slate-ui-script-thumbtools.js +0 -103
  138. package/dist/mcp-slate-ui-script-thumbtools.js.map +0 -1
  139. package/dist/mcp-slate-ui-styles-thumbtools.d.ts +0 -10
  140. package/dist/mcp-slate-ui-styles-thumbtools.d.ts.map +0 -1
  141. package/dist/mcp-slate-ui-styles-thumbtools.js +0 -25
  142. package/dist/mcp-slate-ui-styles-thumbtools.js.map +0 -1
@@ -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
  }
@@ -1,23 +0,0 @@
1
- /**
2
- * The two small controls on a design's thumbnail, in the card's strip and in
3
- * the workspace's selection: a plus (More like this) and an edit pencil
4
- * (Edit). They are siblings of the thumbnail button inside one cell (a button
5
- * cannot hold buttons), revealed by CSS on hover and on keyboard focus within
6
- * the cell, and on a device with no hover only on the selected thumbnail. A
7
- * click on either never reaches the thumbnail, so it never swaps the large
8
- * image by itself.
9
- *
10
- * The plus opens the Generate more panel for variations of that design
11
- * (openMoreLike, in mcp-slate-ui-script-more.ts). Edit selects that design and
12
- * opens the Edit dialog. The dialog lives in the workspace; the card has no
13
- * room for the drawing surface, so in the card Edit opens the workspace and the
14
- * dialog opens as soon as it shows (wantEdit). A host that cannot show the
15
- * workspace has no place for the dialog, so there Edit is hidden in the card.
16
- *
17
- * Not on the Generate more tile, and not on an option that has no picture yet.
18
- * Whether each control is on follows the view (syncThumbTools): the plus is off
19
- * while a round runs (the tile's rule), Edit while that design has an edit in
20
- * flight or its version cannot be edited.
21
- */
22
- export declare const SLATE_UI_THUMBTOOLS: string;
23
- //# sourceMappingURL=mcp-slate-ui-script-thumbtools.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-slate-ui-script-thumbtools.d.ts","sourceRoot":"","sources":["../src/mcp-slate-ui-script-thumbtools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,mBAAmB,QAgF/B,CAAC"}
@@ -1,103 +0,0 @@
1
- /**
2
- * The two small controls on a design's thumbnail, in the card's strip and in
3
- * the workspace's selection: a plus (More like this) and an edit pencil
4
- * (Edit). They are siblings of the thumbnail button inside one cell (a button
5
- * cannot hold buttons), revealed by CSS on hover and on keyboard focus within
6
- * the cell, and on a device with no hover only on the selected thumbnail. A
7
- * click on either never reaches the thumbnail, so it never swaps the large
8
- * image by itself.
9
- *
10
- * The plus opens the Generate more panel for variations of that design
11
- * (openMoreLike, in mcp-slate-ui-script-more.ts). Edit selects that design and
12
- * opens the Edit dialog. The dialog lives in the workspace; the card has no
13
- * room for the drawing surface, so in the card Edit opens the workspace and the
14
- * dialog opens as soon as it shows (wantEdit). A host that cannot show the
15
- * workspace has no place for the dialog, so there Edit is hidden in the card.
16
- *
17
- * Not on the Generate more tile, and not on an option that has no picture yet.
18
- * Whether each control is on follows the view (syncThumbTools): the plus is off
19
- * while a round runs (the tile's rule), Edit while that design has an edit in
20
- * flight or its version cannot be edited.
21
- */
22
- export const SLATE_UI_THUMBTOOLS = String.raw `
23
- var ICON_PENCIL = "M4 20l1-4L16.5 4.5a2.1 2.1 0 0 1 3 3L8 19l-4 1zM14 7l3 3";
24
-
25
- function thumbToolButton(action, label, path, c, onclick) {
26
- var b = button("", "thumbtool", function (e) {
27
- if (e && e.stopPropagation) e.stopPropagation();
28
- onclick(c.candidateId);
29
- });
30
- b.setAttribute("aria-label", label);
31
- b.title = label;
32
- b.setAttribute("data-action", action);
33
- b.setAttribute("data-candidate", c.candidateId);
34
- b.innerHTML = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="' + path + '"/></svg>';
35
- return b;
36
- }
37
-
38
- // One cell per option: the thumbnail button and, when there is a picture to work from, its two controls.
39
- function thumbCell(c, cls) {
40
- var cell = el("span", "thumbcell");
41
- var thumb = thumbButton(c, cls);
42
- if (thumb.classList.contains("selected")) cell.classList.add("selected");
43
- cell.appendChild(thumb);
44
- if (c.state === "ready" && latestVersion(c)) {
45
- var tools = el("span", "thumbtools");
46
- tools.appendChild(thumbToolButton("more", COPY.moreLikeThis, ICON_PLUS, c, openMoreLike));
47
- tools.appendChild(thumbToolButton("edit", COPY.edit, ICON_PENCIL, c, editFromThumb));
48
- cell.appendChild(tools);
49
- }
50
- return cell;
51
- }
52
-
53
- // Whether Edit has a dialog to open in this host: the workspace itself, or a card that can open the workspace.
54
- function canEditHere() { return isFullscreen() || canFullscreen(); }
55
-
56
- function thumbToolNodes() { return Array.prototype.slice.call(document.querySelectorAll(".thumbtool")); }
57
- function thumbToolFor(action, candidateId) {
58
- return thumbToolNodes().filter(function (t) {
59
- return t.getAttribute("data-action") === action && t.getAttribute("data-candidate") === candidateId && !t.disabled && !isHiddenNode(t);
60
- })[0];
61
- }
62
-
63
- function syncThumbTools() {
64
- if (!state.view) return;
65
- var busy = moreBusy();
66
- var edit = canEditHere();
67
- var likeId = state.more && state.more.mode === "like" ? state.more.fromCandidateId : null;
68
- thumbToolNodes().forEach(function (t) {
69
- var c = cand(t.getAttribute("data-candidate"));
70
- var v = latestVersion(c);
71
- if (t.getAttribute("data-action") === "more") {
72
- t.disabled = busy || !pickable(v);
73
- t.setAttribute("aria-expanded", likeId !== null && likeId === t.getAttribute("data-candidate") ? "true" : "false");
74
- } else {
75
- t.classList.toggle("hidden", !edit);
76
- t.disabled = !v || !v.editable || !!(c && c.busy);
77
- }
78
- });
79
- }
80
-
81
- // Edit: that design is selected, then the Edit dialog opens on it, exactly as the workspace's Edit button does.
82
- function editFromThumb(candidateId) {
83
- var c = cand(candidateId);
84
- var v = latestVersion(c);
85
- if (!c || !v || !v.editable || c.busy || state.dialog) return;
86
- selectCandidate(candidateId);
87
- if (isFullscreen()) { openDialog("edit", thumbToolFor("edit", candidateId) || stageEls.edit); return; }
88
- if (!canFullscreen()) return;
89
- state.wantEdit = v.versionId;
90
- request("ui/request-display-mode", { mode: "fullscreen" }).then(function (result) {
91
- if (result && result.mode && result.mode !== "fullscreen") state.wantEdit = null;
92
- }, function () { state.wantEdit = null; });
93
- }
94
- // The workspace has just shown: the dialog that was asked for in the card opens now, on the version that was asked for.
95
- function openWantedEdit() {
96
- var want = state.wantEdit;
97
- if (!want || !stageBuilt || state.dialog) return;
98
- state.wantEdit = null;
99
- var v = selectedVersion();
100
- if (v && v.versionId === want && v.editable) openDialog("edit", stageEls.edit);
101
- }
102
- `;
103
- //# sourceMappingURL=mcp-slate-ui-script-thumbtools.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-slate-ui-script-thumbtools.js","sourceRoot":"","sources":["../src/mcp-slate-ui-script-thumbtools.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgF5C,CAAC"}
@@ -1,10 +0,0 @@
1
- /**
2
- * The two controls on a design thumbnail (a plus and a pencil), and the cell
3
- * that holds them beside the thumbnail button. They sit in the top right corner
4
- * on a semi-opaque dark backdrop, so they stay legible over any picture, and
5
- * are shown only on hover or while keyboard focus is inside the cell. A device
6
- * with no hover shows them on the selected thumbnail only. A hidden control is
7
- * not clickable but stays focusable, and focus reveals it.
8
- */
9
- export declare const SLATE_UI_THUMBTOOLS_STYLES: string;
10
- //# sourceMappingURL=mcp-slate-ui-styles-thumbtools.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-slate-ui-styles-thumbtools.d.ts","sourceRoot":"","sources":["../src/mcp-slate-ui-styles-thumbtools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,eAAO,MAAM,0BAA0B,QAetC,CAAC"}
@@ -1,25 +0,0 @@
1
- /**
2
- * The two controls on a design thumbnail (a plus and a pencil), and the cell
3
- * that holds them beside the thumbnail button. They sit in the top right corner
4
- * on a semi-opaque dark backdrop, so they stay legible over any picture, and
5
- * are shown only on hover or while keyboard focus is inside the cell. A device
6
- * with no hover shows them on the selected thumbnail only. A hidden control is
7
- * not clickable but stays focusable, and focus reveals it.
8
- */
9
- export const SLATE_UI_THUMBTOOLS_STYLES = String.raw `
10
- .thumbcell { position: relative; flex: none; display: block; }
11
- .strip .thumbcell { width: 56px; height: 56px; }
12
- .option .thumbcell { width: 72px; height: 72px; }
13
- .thumbtools { position: absolute; top: 3px; right: 3px; display: flex; gap: 2px; opacity: 0; pointer-events: none; transition: opacity .12s; }
14
- .thumbcell:hover .thumbtools, .thumbcell:focus-within .thumbtools { opacity: 1; pointer-events: auto; }
15
- button.thumbtool { display: grid; place-items: center; flex: none; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 5px; background: rgba(20, 20, 20, .62); color: #fff; }
16
- button.thumbtool:hover:not(:disabled) { background: rgba(20, 20, 20, .85); }
17
- button.thumbtool:focus-visible { outline: 2px solid var(--ring); outline-offset: 0; }
18
- button.thumbtool:disabled { opacity: .5; }
19
- @media (hover: none) {
20
- .thumbtools { display: none; }
21
- .thumbcell.selected .thumbtools { display: flex; opacity: 1; pointer-events: auto; }
22
- }
23
- @media (prefers-reduced-motion: reduce) { .thumbtools { transition: none; } }
24
- `;
25
- //# sourceMappingURL=mcp-slate-ui-styles-thumbtools.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"mcp-slate-ui-styles-thumbtools.js","sourceRoot":"","sources":["../src/mcp-slate-ui-styles-thumbtools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;CAenD,CAAC"}