@12ui/design 0.2.77 → 0.2.79
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.
- package/SKILL.md +1 -1
- package/dist/cli-workspace-draw.d.ts +1 -1
- package/dist/cli-workspace-draw.d.ts.map +1 -1
- package/dist/cli-workspace-draw.js +3 -2
- package/dist/cli-workspace-draw.js.map +1 -1
- package/dist/mcp-slate-ui-copy.d.ts +6 -6
- package/dist/mcp-slate-ui-copy.d.ts.map +1 -1
- package/dist/mcp-slate-ui-copy.js +6 -5
- package/dist/mcp-slate-ui-copy.js.map +1 -1
- package/dist/mcp-slate-ui-locales/de-DE.js +2 -2
- package/dist/mcp-slate-ui-locales/de-DE.js.map +1 -1
- package/dist/mcp-slate-ui-locales/es-419.js +2 -2
- package/dist/mcp-slate-ui-locales/es-419.js.map +1 -1
- package/dist/mcp-slate-ui-locales/es-ES.js +2 -2
- package/dist/mcp-slate-ui-locales/es-ES.js.map +1 -1
- package/dist/mcp-slate-ui-locales/fr-FR.js +2 -2
- package/dist/mcp-slate-ui-locales/fr-FR.js.map +1 -1
- package/dist/mcp-slate-ui-locales/hi-IN.js +2 -2
- package/dist/mcp-slate-ui-locales/hi-IN.js.map +1 -1
- package/dist/mcp-slate-ui-locales/it-IT.js +2 -2
- package/dist/mcp-slate-ui-locales/it-IT.js.map +1 -1
- package/dist/mcp-slate-ui-locales/ja-JP.js +2 -2
- package/dist/mcp-slate-ui-locales/ja-JP.js.map +1 -1
- package/dist/mcp-slate-ui-locales/ko-KR.js +2 -2
- package/dist/mcp-slate-ui-locales/ko-KR.js.map +1 -1
- package/dist/mcp-slate-ui-locales/pt-BR.js +2 -2
- package/dist/mcp-slate-ui-locales/pt-BR.js.map +1 -1
- package/dist/mcp-slate-ui-locales/zh-CN.js +2 -2
- package/dist/mcp-slate-ui-locales/zh-CN.js.map +1 -1
- package/dist/mcp-slate-ui-locales/zh-TW.js +2 -2
- package/dist/mcp-slate-ui-locales/zh-TW.js.map +1 -1
- package/dist/mcp-slate-ui-script-more.d.ts +9 -5
- package/dist/mcp-slate-ui-script-more.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-more.js +15 -7
- package/dist/mcp-slate-ui-script-more.js.map +1 -1
- package/dist/mcp-slate-ui-script-remote.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-remote.js +10 -2
- package/dist/mcp-slate-ui-script-remote.js.map +1 -1
- package/dist/mcp-slate-ui-script-stage.d.ts +10 -9
- package/dist/mcp-slate-ui-script-stage.d.ts.map +1 -1
- package/dist/mcp-slate-ui-script-stage.js +18 -16
- package/dist/mcp-slate-ui-script-stage.js.map +1 -1
- package/dist/mcp-slate-ui-styles-dialog.d.ts +3 -2
- package/dist/mcp-slate-ui-styles-dialog.d.ts.map +1 -1
- package/dist/mcp-slate-ui-styles-dialog.js +14 -6
- package/dist/mcp-slate-ui-styles-dialog.js.map +1 -1
- package/dist/mcp-slate-ui-styles-stage.d.ts +9 -6
- package/dist/mcp-slate-ui-styles-stage.d.ts.map +1 -1
- package/dist/mcp-slate-ui-styles-stage.js +28 -21
- package/dist/mcp-slate-ui-styles-stage.js.map +1 -1
- package/dist/slate-image-executor.d.ts +10 -2
- package/dist/slate-image-executor.d.ts.map +1 -1
- package/dist/slate-image-executor.js +55 -12
- package/dist/slate-image-executor.js.map +1 -1
- package/dist/slate-round-prompt.d.ts +15 -3
- package/dist/slate-round-prompt.d.ts.map +1 -1
- package/dist/slate-round-prompt.js +14 -5
- package/dist/slate-round-prompt.js.map +1 -1
- package/dist/slate-workspace-types.d.ts +1 -1
- package/dist/slate-workspace-types.d.ts.map +1 -1
- package/dist/workspace-draw-client.d.ts +16 -4
- package/dist/workspace-draw-client.d.ts.map +1 -1
- package/dist/workspace-draw-client.js +28 -4
- package/dist/workspace-draw-client.js.map +1 -1
- package/dist/workspace-draw-messages.d.ts +8 -1
- package/dist/workspace-draw-messages.d.ts.map +1 -1
- package/dist/workspace-draw-messages.js +7 -2
- package/dist/workspace-draw-messages.js.map +1 -1
- package/dist/workspace-draw-run.d.ts.map +1 -1
- package/dist/workspace-draw-run.js +28 -8
- package/dist/workspace-draw-run.js.map +1 -1
- package/dist/workspace-draw-types.d.ts +1 -0
- package/dist/workspace-draw-types.d.ts.map +1 -1
- package/dist/workspace-draw-types.js +1 -0
- package/dist/workspace-draw-types.js.map +1 -1
- package/dist/workspace-runner-children.d.ts +11 -0
- package/dist/workspace-runner-children.d.ts.map +1 -1
- package/dist/workspace-runner-children.js +27 -0
- package/dist/workspace-runner-children.js.map +1 -1
- package/dist/workspace-runner-client.d.ts +2 -0
- package/dist/workspace-runner-client.d.ts.map +1 -1
- package/dist/workspace-runner-client.js +11 -2
- package/dist/workspace-runner-client.js.map +1 -1
- package/dist/workspace-runner-main.d.ts.map +1 -1
- package/dist/workspace-runner-main.js +38 -10
- package/dist/workspace-runner-main.js.map +1 -1
- package/open-design.json +2 -2
- package/package.json +1 -1
- package/runtime/codex-conversion/src/images/native-frame.mjs +103 -0
- package/runtime/codex-conversion/src/images/native.mjs +11 -3
- package/runtime/codex-conversion/src/run-store.mjs +21 -1
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import sharp from 'sharp';
|
|
2
|
+
|
|
3
|
+
// How a native image that came back in another shape than its frame meets the frame without distortion (one module
|
|
4
|
+
// for the policy, CLAUDE.md item 9). The Design workspace's options use it (`aspect-preserving-lanczos3`); conversion
|
|
5
|
+
// keeps the exact stretch, whose geometry must map onto its source frame.
|
|
6
|
+
//
|
|
7
|
+
// Never stretch: the image is scaled by one factor on both axes. Cutting it to the frame's shape (cover) loses what
|
|
8
|
+
// the cut strips hold, so it is chosen only with proof that they hold nothing: every pixel of every strip within
|
|
9
|
+
// `BLANK_DELTA` of its strip's median colour on every channel. One ink pixel is no proof (CLAUDE.md item 8): a share of
|
|
10
|
+
// stray pixels let a 69 px strip holding 12 pixels of a heart button's faint outline count as blank (0.2.78 gate,
|
|
11
|
+
// 1606x979 into 1536x1024). Without that proof the whole image is kept (contain) and the frame's spare side is filled
|
|
12
|
+
// with the colour of the image's own edge there, so nothing drawn is lost.
|
|
13
|
+
|
|
14
|
+
/** A channel difference from a strip's median colour beyond which a pixel is ink. */
|
|
15
|
+
export const BLANK_DELTA = 24;
|
|
16
|
+
/** How many pixels deep an edge is sampled for the colour of the spare side. */
|
|
17
|
+
const EDGE_DEPTH = 2;
|
|
18
|
+
|
|
19
|
+
const rgbOf = async (image, region) => {
|
|
20
|
+
const {data, info} = await sharp(image).extract(region).removeAlpha().raw().toBuffer({resolveWithObject: true});
|
|
21
|
+
return {data, channels: info.channels, count: info.width * info.height};
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** The per-channel median colour of the pixels in `region`. */
|
|
25
|
+
const medianColour = ({data, channels, count}) => {
|
|
26
|
+
const colour = [];
|
|
27
|
+
for (let channel = 0; channel < 3; channel += 1) {
|
|
28
|
+
const histogram = new Uint32Array(256);
|
|
29
|
+
for (let index = 0; index < count; index += 1) histogram[data[index * channels + Math.min(channel, channels - 1)]] += 1;
|
|
30
|
+
let seen = 0, value = 0;
|
|
31
|
+
while (value < 255 && seen + histogram[value] <= count / 2) { seen += histogram[value]; value += 1; }
|
|
32
|
+
colour.push(value);
|
|
33
|
+
}
|
|
34
|
+
return colour;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Whether a strip holds nothing: every pixel within `BLANK_DELTA` of its median colour. Any ink pixel vetoes the cut. */
|
|
38
|
+
export const stripIsBlank = async (image, region) => {
|
|
39
|
+
if (region.width <= 0 || region.height <= 0) return true;
|
|
40
|
+
const pixels = await rgbOf(image, region);
|
|
41
|
+
const [r, g, b] = medianColour(pixels);
|
|
42
|
+
for (let index = 0; index < pixels.count; index += 1) {
|
|
43
|
+
const at = index * pixels.channels;
|
|
44
|
+
const c = (offset) => pixels.data[at + Math.min(offset, pixels.channels - 1)];
|
|
45
|
+
if (Math.abs(c(0) - r) > BLANK_DELTA || Math.abs(c(1) - g) > BLANK_DELTA || Math.abs(c(2) - b) > BLANK_DELTA) return false;
|
|
46
|
+
}
|
|
47
|
+
return true;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const hex = (colour) => `#${colour.map((value) => value.toString(16).padStart(2, '0')).join('')}`;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The cover window: the largest part of the image in the frame's shape, centred, and the strips outside it (in the
|
|
54
|
+
* image's own pixels). An image already in the frame's shape to within a pixel has no strips.
|
|
55
|
+
*/
|
|
56
|
+
export const coverWindow = (actual, expected) => {
|
|
57
|
+
const {width: aw, height: ah} = actual, {width: fw, height: fh} = expected;
|
|
58
|
+
if (aw * fh > ah * fw) {
|
|
59
|
+
const kw = Math.min(aw, Math.round(ah * fw / fh)), left = Math.floor((aw - kw) / 2);
|
|
60
|
+
return {window: {left, top: 0, width: kw, height: ah}, strips: [{left: 0, top: 0, width: left, height: ah}, {left: left + kw, top: 0, width: aw - left - kw, height: ah}], axis: 'x'};
|
|
61
|
+
}
|
|
62
|
+
const kh = Math.min(ah, Math.round(aw * fh / fw)), top = Math.floor((ah - kh) / 2);
|
|
63
|
+
return {window: {left: 0, top, width: aw, height: kh}, strips: [{left: 0, top: 0, width: aw, height: top}, {left: 0, top: top + kh, width: aw, height: ah - top - kh}], axis: 'y'};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Fit `original` (PNG bytes) to `expected` without distortion: the uniform scale when its shape is the frame's (to
|
|
68
|
+
* within a pixel), the cover crop when every cut strip is blank, else the contain fit padded with the edge colours.
|
|
69
|
+
* Returns the PNG and the record of what was done.
|
|
70
|
+
*/
|
|
71
|
+
export async function fitToFrame(original, expected) {
|
|
72
|
+
const meta = await sharp(original).metadata();
|
|
73
|
+
const actual = {width: meta.width, height: meta.height};
|
|
74
|
+
const {window, strips, axis} = coverWindow(actual, expected);
|
|
75
|
+
/** @type {import('sharp').ResizeOptions} */
|
|
76
|
+
const resize = {fit: 'fill', kernel: 'lanczos3'};
|
|
77
|
+
if (strips.every((strip) => strip.width <= 0 || strip.height <= 0)) {
|
|
78
|
+
const png = await sharp(original).resize(expected.width, expected.height, resize).png().toBuffer();
|
|
79
|
+
return {png, normalization: {name: 'uniform-scale', kernel: 'lanczos3', output: expected, scale: expected.width / actual.width}};
|
|
80
|
+
}
|
|
81
|
+
const blank = [];
|
|
82
|
+
for (const strip of strips) blank.push(await stripIsBlank(original, strip));
|
|
83
|
+
if (blank.every(Boolean)) {
|
|
84
|
+
const png = await sharp(original).extract(window).resize(expected.width, expected.height, resize).png().toBuffer();
|
|
85
|
+
return {png, normalization: {name: 'cover-crop', kernel: 'lanczos3', output: expected, scale: expected.width / window.width, crop: window}};
|
|
86
|
+
}
|
|
87
|
+
// Contain: the whole image at one scale, the spare side filled with the colour of the image's own edge beside it.
|
|
88
|
+
const scale = Math.min(expected.width / actual.width, expected.height / actual.height);
|
|
89
|
+
const width = Math.min(expected.width, Math.max(1, Math.round(actual.width * scale)));
|
|
90
|
+
const height = Math.min(expected.height, Math.max(1, Math.round(actual.height * scale)));
|
|
91
|
+
const spare = axis === 'y' ? expected.width - width : expected.height - height;
|
|
92
|
+
const before = Math.floor(spare / 2), after = spare - before;
|
|
93
|
+
const edges = axis === 'y'
|
|
94
|
+
? [{left: 0, top: 0, width: Math.min(EDGE_DEPTH, actual.width), height: actual.height}, {left: Math.max(0, actual.width - EDGE_DEPTH), top: 0, width: Math.min(EDGE_DEPTH, actual.width), height: actual.height}]
|
|
95
|
+
: [{left: 0, top: 0, width: actual.width, height: Math.min(EDGE_DEPTH, actual.height)}, {left: 0, top: Math.max(0, actual.height - EDGE_DEPTH), width: actual.width, height: Math.min(EDGE_DEPTH, actual.height)}];
|
|
96
|
+
const [first, second] = await Promise.all(edges.map(async (edge) => medianColour(await rgbOf(original, edge))));
|
|
97
|
+
const fill = (colour) => ({r: colour[0], g: colour[1], b: colour[2], alpha: 1});
|
|
98
|
+
const pad = axis === 'y' ? {left: before, right: after, top: 0, bottom: 0} : {left: 0, right: 0, top: before, bottom: after};
|
|
99
|
+
let png = await sharp(original).resize(width, height, resize).png().toBuffer();
|
|
100
|
+
if (before > 0) png = await sharp(png).extend(axis === 'y' ? {left: before, background: fill(first)} : {top: before, background: fill(first)}).png().toBuffer();
|
|
101
|
+
if (after > 0) png = await sharp(png).extend(axis === 'y' ? {right: after, background: fill(second)} : {bottom: after, background: fill(second)}).png().toBuffer();
|
|
102
|
+
return {png, normalization: {name: 'contain-pad', kernel: 'lanczos3', output: expected, scale, pad, colours: [hex(first), hex(second)]}};
|
|
103
|
+
}
|
|
@@ -7,16 +7,24 @@ import {invokeNative} from './native-invoke.mjs';
|
|
|
7
7
|
import {collectNative,packNativeReceipt} from './native-collect.mjs';
|
|
8
8
|
import {filesUnder,inputFileNames} from './native-files.mjs';
|
|
9
9
|
import {sha256,pngDimensions,parseNativeRecords} from './native-records.mjs';
|
|
10
|
+
import {fitToFrame} from './native-frame.mjs';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/** How a native image meets its frame: conversion's exact stretch (the default), or the aspect-preserving fit (native-frame.mjs). */
|
|
13
|
+
export const NATIVE_NORMALIZATIONS=Object.freeze({stretch:'exact-stretch-lanczos3',preserveAspect:'aspect-preserving-lanczos3'});
|
|
14
|
+
/** @typedef {typeof NATIVE_NORMALIZATIONS[keyof typeof NATIVE_NORMALIZATIONS]} NativeNormalization */
|
|
15
|
+
/** @param {Buffer} bytes @param {{normalization?: NativeNormalization}} [options] */
|
|
16
|
+
export async function decodeNativeResponse(bytes,{normalization=NATIVE_NORMALIZATIONS.stretch}={}) {
|
|
17
|
+
if(!Object.values(NATIVE_NORMALIZATIONS).includes(normalization)) throw Error('Unknown native normalization: '+normalization);
|
|
12
18
|
const receipt=JSON.parse(bytes.toString('utf8'));
|
|
13
19
|
if(!['exact-size-pass','size-mismatch'].includes(receipt.state)||receipt.outputs.length!==1) throw Error('Native image call requires inspection: '+receipt.state);
|
|
14
20
|
if(receipt.model!=='gpt-6-astra'||receipt.effort!=='medium')throw Error('Native coordinator model/effort does not match requested execution');
|
|
15
21
|
const original=Buffer.from(receipt.outputs[0].base64,'base64');
|
|
16
22
|
if(sha256(original)!==receipt.outputs[0].sha256) throw Error('Native original custody mismatch');
|
|
17
23
|
const actual=pngDimensions(original), expected=receipt.geometry.raw.expected;
|
|
18
|
-
const
|
|
19
|
-
|
|
24
|
+
const fitted=normalization===NATIVE_NORMALIZATIONS.preserveAspect
|
|
25
|
+
?await fitToFrame(original,expected)
|
|
26
|
+
:{png:await sharp(original).resize(expected.width,expected.height,{fit:'fill',kernel:'lanczos3'}).png().toBuffer(),normalization:{name:'exact-stretch',kernel:'lanczos3',output:expected,scaleX:expected.width/actual.width,scaleY:expected.height/actual.height}};
|
|
27
|
+
return {png:fitted.png,usage:{codexTokens:receipt.tokenUsage??[],imageUsage:null},revisedPrompt:receipt.generations[0].revisedPrompt??null,native:{originalPath:receipt.outputs[0].path,originalSha256:sha256(original),originalDimensions:actual,normalization:fitted.normalization,model:receipt.model,effort:receipt.effort,imageModel:null,invocationCount:receipt.invocationCount}};
|
|
20
28
|
}
|
|
21
29
|
/** `processOptions` reaches the call's `codex exec` child: `interruptOnSignal`, `onSpawn`, `onExit` (native-invoke.mjs). */
|
|
22
30
|
export async function executeNativeCall({request,input,callId,store,codexBinary='codex',codexHome,timeoutMs=1800000,processOptions={}}) {
|
|
@@ -3,7 +3,7 @@ import { hostname } from 'node:os';
|
|
|
3
3
|
import { join, dirname } from 'node:path';
|
|
4
4
|
import { randomUUID } from 'node:crypto';
|
|
5
5
|
import { canonical, hash, readJson, writeJson, writeBytes, syncDirectory } from './run-store-io.mjs';
|
|
6
|
-
export { hash, readJson, writeJson } from './run-store-io.mjs';
|
|
6
|
+
export { canonical, hash, readJson, writeJson } from './run-store-io.mjs';
|
|
7
7
|
/** @typedef {{bytes: Buffer, metadata?: any}} Response */
|
|
8
8
|
/** @param {string} message @param {string} code */
|
|
9
9
|
function failure(message, code) { return Object.assign(new Error(message), { code }); }
|
|
@@ -77,6 +77,26 @@ export function createStore(runDir) {
|
|
|
77
77
|
},
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* The response a call id already received, read back with the request it was received for, when its custody is
|
|
82
|
+
* intact (the checks `runCall` makes before it reuses one); null when the id received nothing. It only reads: a caller
|
|
83
|
+
* whose own request differs (`REQUEST_CHANGED`) decides whether that paid response serves it, and `runCall` keeps the
|
|
84
|
+
* id's identity as it is.
|
|
85
|
+
* @param {string} runDir @param {string} id
|
|
86
|
+
* @returns {Promise<{callDir: string, request: any, requestHash: string, bytes: Buffer, metadata: any} | null>}
|
|
87
|
+
*/
|
|
88
|
+
export async function readReceived(runDir, id) {
|
|
89
|
+
const callDir = join(runDir, 'calls', hash(id));
|
|
90
|
+
const state = await readJson(join(callDir, 'state.json'));
|
|
91
|
+
if (!state || state.id !== id || state.status === 'prepared') return null;
|
|
92
|
+
const [request, custody] = await Promise.all([readJson(join(callDir, 'request.json')), readJson(join(callDir, 'custody.json'))]);
|
|
93
|
+
let bytes = null;
|
|
94
|
+
try { bytes = await readFile(join(callDir, 'response.bin')); }
|
|
95
|
+
catch (error) { if (/** @type {NodeJS.ErrnoException} */ (error).code !== 'ENOENT') throw error; }
|
|
96
|
+
if (!bytes || !request || hash(canonical(request)) !== state.requestHash) return null;
|
|
97
|
+
if (custody?.requestHash !== state.requestHash || custody.length !== bytes.length || custody.responseHash !== hash(bytes)) return null;
|
|
98
|
+
return { callDir, request, requestHash: state.requestHash, bytes, metadata: custody.metadata };
|
|
99
|
+
}
|
|
80
100
|
/** @template T @param {string} runDir @param {() => Promise<T>} callback @returns {Promise<T>} */
|
|
81
101
|
export async function withRunLock(runDir, callback) {
|
|
82
102
|
await mkdir(runDir, { recursive: true });
|