@12ui/design 0.2.67 → 0.2.68

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 (60) hide show
  1. package/SKILL.md +1 -1
  2. package/open-design.json +2 -2
  3. package/package.json +1 -1
  4. package/runtime/bootstrap.mjs +131 -0
  5. package/runtime/codex-conversion/README.md +75 -0
  6. package/runtime/codex-conversion/bin/convert.mjs +21 -0
  7. package/runtime/codex-conversion/bin/export.mjs +4 -0
  8. package/runtime/codex-conversion/bin/finish.mjs +11 -0
  9. package/runtime/codex-conversion/bin/improve.mjs +12 -0
  10. package/runtime/codex-conversion/bin/install-skill.mjs +36 -0
  11. package/runtime/codex-conversion/bin/recover-image.mjs +11 -0
  12. package/runtime/codex-conversion/bin/recovery/prepare.mjs +68 -0
  13. package/runtime/codex-conversion/bin/recovery/run.mjs +37 -0
  14. package/runtime/codex-conversion/npm-shrinkwrap.json +1199 -0
  15. package/runtime/codex-conversion/package.json +45 -0
  16. package/runtime/codex-conversion/src/codex/arguments.mjs +14 -0
  17. package/runtime/codex-conversion/src/codex/completion.mjs +14 -0
  18. package/runtime/codex-conversion/src/codex/events.mjs +41 -0
  19. package/runtime/codex-conversion/src/codex/home.mjs +37 -0
  20. package/runtime/codex-conversion/src/codex/invoke.mjs +79 -0
  21. package/runtime/codex-conversion/src/contract-repair.mjs +44 -0
  22. package/runtime/codex-conversion/src/contracts/compile.mjs +53 -0
  23. package/runtime/codex-conversion/src/contracts/contracts.mjs +19 -0
  24. package/runtime/codex-conversion/src/contracts/geometry.mjs +38 -0
  25. package/runtime/codex-conversion/src/contracts/grid-frame.mjs +22 -0
  26. package/runtime/codex-conversion/src/contracts/groups.mjs +14 -0
  27. package/runtime/codex-conversion/src/contracts/initial.mjs +9 -0
  28. package/runtime/codex-conversion/src/contracts/normalize.mjs +46 -0
  29. package/runtime/codex-conversion/src/exports/browser.mjs +26 -0
  30. package/runtime/codex-conversion/src/exports/editable.mjs +34 -0
  31. package/runtime/codex-conversion/src/exports/files.mjs +7 -0
  32. package/runtime/codex-conversion/src/exports/index.mjs +24 -0
  33. package/runtime/codex-conversion/src/exports/react.mjs +44 -0
  34. package/runtime/codex-conversion/src/exports/viewport.mjs +17 -0
  35. package/runtime/codex-conversion/src/finish/index.mjs +58 -0
  36. package/runtime/codex-conversion/src/finish/patch.mjs +14 -0
  37. package/runtime/codex-conversion/src/images/execute.mjs +52 -0
  38. package/runtime/codex-conversion/src/images/frame-normalization.mjs +10 -0
  39. package/runtime/codex-conversion/src/images/geometry.mjs +9 -0
  40. package/runtime/codex-conversion/src/images/local-operation.mjs +17 -0
  41. package/runtime/codex-conversion/src/images/local.mjs +18 -0
  42. package/runtime/codex-conversion/src/images/native-collect.mjs +43 -0
  43. package/runtime/codex-conversion/src/images/native-files.mjs +11 -0
  44. package/runtime/codex-conversion/src/images/native-invoke.mjs +49 -0
  45. package/runtime/codex-conversion/src/images/native-records.mjs +57 -0
  46. package/runtime/codex-conversion/src/images/native.mjs +41 -0
  47. package/runtime/codex-conversion/src/images/prompt.mjs +14 -0
  48. package/runtime/codex-conversion/src/images/provider.mjs +22 -0
  49. package/runtime/codex-conversion/src/output.mjs +28 -0
  50. package/runtime/codex-conversion/src/project/invoke.mjs +35 -0
  51. package/runtime/codex-conversion/src/project/prompt.mjs +13 -0
  52. package/runtime/codex-conversion/src/project/run.mjs +83 -0
  53. package/runtime/codex-conversion/src/project/source.mjs +21 -0
  54. package/runtime/codex-conversion/src/references.mjs +114 -0
  55. package/runtime/codex-conversion/src/run-store-io.mjs +38 -0
  56. package/runtime/codex-conversion/src/run-store.mjs +114 -0
  57. package/runtime/codex-conversion/src/run.mjs +108 -0
  58. package/runtime/codex-conversion/src/runtime-pin.mjs +12 -0
  59. package/runtime/codex-conversion/templates/initial.schema.json +90 -0
  60. package/runtime/codex-conversion/templates/initial.txt +22 -0
@@ -0,0 +1,44 @@
1
+ import {parse} from 'parse5';
2
+ import {ownsEditableMarkup,editableMarkup} from './editable.mjs';
3
+ const quote=JSON.stringify;
4
+ const rename={charset:'charSet',contenteditable:'contentEditable',inputmode:'inputMode',class:'className',for:'htmlFor',tabindex:'tabIndex',readonly:'readOnly',maxlength:'maxLength',colspan:'colSpan',rowspan:'rowSpan',srcset:'srcSet',crossorigin:'crossOrigin',viewbox:'viewBox',preserveaspectratio:'preserveAspectRatio', 'xlink:href':'xlinkHref','xml:space':'xmlSpace'};
5
+ const bool=new Set(['readonly','disabled','checked','selected','multiple','required','autofocus','hidden','controls','loop','muted','open']);
6
+ const voids=new Set('area base br col embed hr img input link meta param source track wbr'.split(' '));
7
+ function attrName(n){return rename[n]??(n.startsWith('aria-')||n.startsWith('data-')?n:n.replace(/-([a-z])/g,(_,x)=>x.toUpperCase()));}
8
+ function styles(value){const declarations={}; for(const part of value.split(/;(?![^()]*\))/)){const i=part.indexOf(':');if(i<0)continue;const key=part.slice(0,i).trim();declarations[key.startsWith('--')?key:attrName(key)]=part.slice(i+1).trim();}return declarations;}
9
+ function textContent(node){return node.nodeName==='#text'?node.value:(node.childNodes??[]).map(textContent).join('');}
10
+ function selectValue(node){
11
+ const selected=[];
12
+ function visit(parent){for(const child of parent.childNodes??[]){
13
+ if(child.tagName==='option' && child.attrs.some(a=>a.name==='selected'))selected.push(child.attrs.find(a=>a.name==='value')?.value??textContent(child).replace(/[\t\n\f\r ]+/g,' ').trim());
14
+ else if(child.tagName==='optgroup')visit(child);
15
+ }}
16
+ visit(node);
17
+ return node.attrs.some(a=>a.name==='multiple')?selected:selected.at(-1);
18
+ }
19
+ function jsx(node,seams,editable){
20
+ if(node.nodeName==='#text')return `{${quote(node.value)}}`;
21
+ if(!node.tagName)return '';
22
+ if(node.tagName==='script'){seams.push('Source script omitted; implement application behavior explicitly.');return '';}
23
+ let attrs=(node.attrs??[]).map(({name,value,prefix})=>{if(prefix)name=prefix+':'+name;if(/^on/i.test(name)){seams.push(`Source ${name} handler omitted.`);return '';}
24
+ if(node.tagName==='option'&&name==='selected')return '';
25
+ if(['textarea','select'].includes(node.tagName)&&name==='value')return '';
26
+ if(name==='contenteditable'&&['true','false'].includes(value.toLowerCase()))return ` contentEditable={${value.toLowerCase()}}`;
27
+ if(name==='style')return ` style={${JSON.stringify(styles(value))}}`;
28
+ if(bool.has(name))return ` ${name==='checked'&&node.tagName==='input'?'defaultChecked':name==='autofocus'?'autoFocus':attrName(name)}={true}`;
29
+ if(name==='value'&&['input','textarea','select'].includes(node.tagName))name='defaultValue';
30
+ return ` ${attrName(name)}={${quote(value)}}`;}).join('');
31
+ if(!voids.has(node.tagName)&&node.tagName!=='textarea'&&ownsEditableMarkup(node)){const index=editable.push(editableMarkup(node,seams))-1;return `<${node.tagName}${attrs} dangerouslySetInnerHTML={editable${index}}/>`;}
32
+ if(node.tagName==='textarea')attrs+=` defaultValue={${quote(textContent(node))}}`;
33
+ if(node.tagName==='select'){const value=selectValue(node);if(value!==undefined)attrs+=` defaultValue={${quote(value)}}`;}
34
+ return voids.has(node.tagName)?`<${node.tagName}${attrs}/>`:`<${node.tagName}${attrs}>${node.tagName==='textarea'?'':(node.childNodes??[]).map(n=>jsx(n,seams,editable)).join('')}</${node.tagName}>`;
35
+ }
36
+ export async function toReact(html){
37
+ const doc=parse(html);const el=doc.childNodes.find(n=>'tagName' in n && n.tagName==='html');if(!el || !('childNodes' in el))throw Error('Missing HTML element');
38
+ const head=el.childNodes.find(n=>'tagName' in n && n.tagName==='head');const body=el.childNodes.find(n=>'tagName' in n && n.tagName==='body');const seams=[],editable=[];if(!head || !body || !('childNodes' in head) || !('childNodes' in body) || !('attrs' in body))throw Error('Missing document structure');
39
+ const bodyJsx=(body.childNodes??[]).map(n=>jsx(n,seams,editable)).join('');
40
+ // Page styles deliberately retain document selectors; packaging records the global scope.
41
+ const headJsx=(head.childNodes??[]).filter(n=>['style','link','title','meta'].includes('tagName' in n?n.tagName:'')).map(n=>jsx(n,seams,editable)).join('');
42
+ const bodyAttrs=Object.fromEntries((body.attrs??[]).map(a=>[a.name,a.value]));
43
+ return {code:`import React, {useLayoutEffect} from 'react';\n${editable.map((html,index)=>`const editable${index}={__html:${quote(html)}};`).join('\n')}\nexport default function ConvertedPage(){\nuseLayoutEffect(()=>{const attrs=${JSON.stringify(bodyAttrs)};const previous={};for(const [k,v] of Object.entries(attrs)){previous[k]=document.body.getAttribute(k);document.body.setAttribute(k,v);}return()=>{for(const [k,v] of Object.entries(previous)){if(v===null)document.body.removeAttribute(k);else document.body.setAttribute(k,v);}};},[]);\nreturn <>${headJsx}${bodyJsx}</>;\n}\n`,seams:[...new Set(seams)],cssScope:'document-global; mount one page per document; not an isolated embeddable widget'};
44
+ }
@@ -0,0 +1,17 @@
1
+ import {readFile} from 'node:fs/promises';
2
+ import {resolve} from 'node:path';
3
+
4
+ const positive = value => Number.isSafeInteger(value) && value > 0;
5
+ /** Explicit viewport dimensions win; native source geometry supplies omitted ones. */
6
+ export async function exportViewport(sourceDir, width, height) {
7
+ let manifest;
8
+ try { manifest = JSON.parse(await readFile(resolve(sourceDir, 'manifest.json'), 'utf8')); }
9
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
10
+ const source = manifest?.source;
11
+ const native = positive(source?.width) && positive(source?.height);
12
+ const resolvedWidth = width ?? (native ? source.width : 1440);
13
+ const resolvedHeight = height ?? (native ? source.height : 1000);
14
+ if (!positive(resolvedWidth) || !positive(resolvedHeight)) throw Error('Positive integer viewport required');
15
+ return {width: resolvedWidth, height: resolvedHeight, paginate: height !== undefined,
16
+ basis: native ? 'source dimensions with explicit overrides' : 'generic 1440x1000 viewport with explicit overrides'};
17
+ }
@@ -0,0 +1,58 @@
1
+ import {readFile, mkdir, writeFile, realpath} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {hash, writeJson} from '../run-store.mjs';
4
+ import {bindHtml} from '../references.mjs';
5
+ import {applyPatches} from './patch.mjs';
6
+
7
+ /** Deterministic derivative of a manifest-backed conversion; never calls a model. */
8
+ export async function finishOutput({sourceDir, patchFile, outputDir}) {
9
+ sourceDir = await realpath(sourceDir);
10
+ outputDir = path.resolve(outputDir);
11
+ outputDir = path.join(await realpath(path.dirname(outputDir)), path.basename(outputDir));
12
+ if (outputDir === sourceDir || outputDir.startsWith(sourceDir + path.sep)) throw Error('Output must be outside source directory');
13
+ const manifestBytes = await readFile(path.join(sourceDir, 'manifest.json'));
14
+ const manifest = JSON.parse(manifestBytes.toString('utf8'));
15
+ if (manifest.entrypoint !== 'index.html' || !Array.isArray(manifest.files)) throw Error('Expected conversion manifest with index.html');
16
+ const inputs = [], names = new Set();
17
+ for (const file of manifest.files) {
18
+ if (typeof file.path !== 'string' || path.isAbsolute(file.path) || file.path.split(/[\\/]/).some(p => !p || p === '.' || p === '..') || file.path === 'manifest.json' || names.has(file.path)) throw Error('Invalid manifest file path');
19
+ const resolved = await realpath(path.join(sourceDir, file.path));
20
+ if (!resolved.startsWith(sourceDir + path.sep)) throw Error('Manifest file escapes source');
21
+ const bytes = await readFile(resolved);
22
+ if (hash(bytes) !== file.sha256) throw Error(`Source hash mismatch: ${file.path}`);
23
+ names.add(file.path); inputs.push({file, bytes});
24
+ }
25
+ const original = inputs.find(item => item.file.path === 'index.html')?.bytes;
26
+ if (!original) throw Error('Manifest lacks index.html');
27
+ const raw = await readFile(patchFile), issues = [], recoveries = [];
28
+ let candidate = original, externalDependencies = manifest.externalDependencies ?? [];
29
+ try {
30
+ const proposed = applyPatches(original.toString('utf8'), JSON.parse(raw.toString('utf8')));
31
+ const assets = inputs.map(item => item.file.sourceOwnership).filter(Boolean);
32
+ const options = {supplementalNames: [...names].filter(name => name !== 'index.html')};
33
+ const checked = bindHtml(proposed, assets, options);
34
+ if (checked.supplemental.some(asset => !inputs.some(item => item.file.path === asset.name && hash(item.bytes) === hash(asset.bytes)))) throw Error('Patch introduces a new asset');
35
+ const prior = bindHtml(original.toString('utf8'), assets, options);
36
+ if (checked.externalDependencies.some(dep => !prior.externalDependencies.some(old => old.url === dep.url))) throw Error('Patch introduces an external dependency');
37
+ candidate = Buffer.from(checked.html); externalDependencies = checked.externalDependencies; recoveries.push(...checked.recoveries);
38
+ } catch (error) { issues.push(String(error.message ?? error)); }
39
+ const provenance = `finish-${hash(raw).slice(0, 12)}-${hash(original).slice(0, 12)}`;
40
+ if ([...names].some(name => name === provenance || name.startsWith(provenance + '/'))) throw Error('This patch provenance already exists; reuse the existing derivative');
41
+ await mkdir(outputDir); // Exclusive destination: never merge stale output.
42
+ const files = [];
43
+ for (const {file, bytes} of inputs) {
44
+ const content = file.path === 'index.html' ? candidate : bytes;
45
+ await mkdir(path.dirname(path.join(outputDir, file.path)), {recursive: true});
46
+ await writeFile(path.join(outputDir, file.path), content, {flag: 'wx'});
47
+ files.push({...file, sha256: hash(content)});
48
+ }
49
+ await mkdir(path.join(outputDir, provenance));
50
+ for (const [name, bytes] of [['original.html', original], ['patch.json', raw], ['original-manifest.json', manifestBytes]]) {
51
+ const relative = `${provenance}/${name}`;
52
+ await writeFile(path.join(outputDir, relative), bytes, {flag: 'wx'});
53
+ files.push({path: relative, sha256: hash(bytes)});
54
+ }
55
+ const result = {status: issues.length ? 'original-preserved' : 'complete', applied: !candidate.equals(original), issues, recoveries, modelCalls: 0, originalSha256: hash(original), candidateSha256: hash(candidate), provenance, entrypoint: 'index.html'};
56
+ await writeJson(path.join(outputDir, 'manifest.json'), {...manifest, files, externalDependencies, derivative: result});
57
+ return result;
58
+ }
@@ -0,0 +1,14 @@
1
+ /** Apply disjoint, unique anchors against the original, never partially. */
2
+ export function applyPatches(original, request) {
3
+ if (!Array.isArray(request?.patches)) throw Error('Expected patches array');
4
+ const spans = request.patches.map((patch, index) => {
5
+ if (typeof patch?.before !== 'string' || !patch.before || typeof patch.after !== 'string') throw Error(`Invalid patch ${index}`);
6
+ const start = original.indexOf(patch.before);
7
+ if (start < 0 || original.indexOf(patch.before, start + 1) >= 0) throw Error(`Patch ${index} anchor missing or ambiguous`);
8
+ return {start, end: start + patch.before.length, after: patch.after};
9
+ }).sort((a, b) => a.start - b.start);
10
+ for (let i = 1; i < spans.length; i++) if (spans[i].start < spans[i - 1].end) throw Error('Overlapping patch anchors');
11
+ let html = original;
12
+ for (const span of spans.reverse()) html = html.slice(0, span.start) + span.after + html.slice(span.end);
13
+ return html;
14
+ }
@@ -0,0 +1,52 @@
1
+ import {normalizeExternalFrame} from './frame-normalization.mjs';
2
+ import {executeLocalGroup} from './local.mjs';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import {createHash} from 'node:crypto';
6
+ import {prepareReference,cropProviderImage} from './geometry.mjs';
7
+ import {operationPrompt} from './prompt.mjs';
8
+ import {DEFAULT_IMAGE_ENDPOINT,requestImage,decodeImageResponse} from './provider.mjs';
9
+ import {executeNativeCall,decodeNativeResponse} from './native.mjs';
10
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
11
+ // Derived artifacts may be recreated freely from the immutable provider response.
12
+ async function save(file,bytes) { const temporary=`${file}.${process.pid}.tmp`; await fs.writeFile(temporary,bytes); await fs.rename(temporary,file); }
13
+ async function executeGroup({group,sourceBytes,runDir,store,env,progress,fetchImpl,imageBackend,codexBinary,codexHome,timeoutMs}) {
14
+ if(group.operation==='crop-source')return executeLocalGroup({group,sourceBytes,runDir,progress});
15
+ const input=await prepareReference(sourceBytes,group);
16
+ const request=imageBackend==='native'?{backend:'native',model:'gpt-6-astra',effort:'medium',imageModel:null,normalization:'exact-stretch-lanczos3',expected:group.frame.output,prompt:operationPrompt(group,'native'),inputSha256:sha(input)}:{endpoint:env.CODEX_CONVERSION_IMAGE_ENDPOINT??DEFAULT_IMAGE_ENDPOINT,model:'gpt-image-2.5-flare',quality:'high',size:`${(group.frame.providerOutput??group.frame.output).width}x${(group.frame.providerOutput??group.frame.output).height}`,background:group.background,prompt:operationPrompt(group),inputSha256:sha(input)};
17
+ const callId=`image-${group.id}`;
18
+ const result=imageBackend==='native'?await executeNativeCall({request,input,callId,store,codexBinary,codexHome,timeoutMs}):await store.runCall(callId,request,async({persistMetadata})=>requestImage({request,input,apiKey:env.OPENAI_API_KEY,idempotencyKey:sha(JSON.stringify({runDir:path.resolve(runDir),callId,request})),persistMetadata,fetchImpl}),{prepare:async()=>{if(!env.OPENAI_API_KEY?.trim())throw Error('OPENAI_API_KEY is required for external Flare image execution');}});
19
+ const {png:providerPng,usage,revisedPrompt,native}=imageBackend==='native'?await decodeNativeResponse(result.bytes):{...decodeImageResponse(result.bytes,result.metadata),native:undefined};
20
+ const {png,transform}=imageBackend==='external'?await normalizeExternalFrame(providerPng,group.frame):{png:providerPng,transform:null};
21
+ const crops=await cropProviderImage(png,group);
22
+ await save(path.join(runDir,`${group.id}-provider.png`),providerPng);
23
+ if(transform)await save(path.join(runDir,`${group.id}-frame.png`),png);
24
+ const assets=[];
25
+ for(const asset of crops) {
26
+ const crop=asset.bytes, relativePath=`${asset.id}.png`;
27
+ await save(path.join(runDir,relativePath),crop);
28
+ const receipt={id:asset.id,path:relativePath,providerSha256:sha(providerPng),sha256:sha(crop),...(transform?{transform}:{}),crop:asset.crop,delivery:asset.delivery,operation:'Exact frozen source-owner crop, no localization/resizing/repacking/repair'};
29
+ await save(path.join(runDir,`${asset.id}-delivery.json`),JSON.stringify(receipt,null,2)+'\n');
30
+ assets.push(receipt);
31
+ }
32
+ const receipt={groupId:group.id,callId,request,rawResponseSha256:sha(result.bytes),providerSha256:sha(providerPng),metadata:result.metadata,usage,revisedPrompt,...(native?{native}:{}),...(transform?{transform}:{}),reused:result.reused};
33
+ await save(path.join(runDir,`${group.id}-receipt.json`),JSON.stringify(receipt,null,2)+'\n');
34
+ progress?.({phase:'image-complete',groupId:group.id,reused:result.reused});
35
+ return {assets,receipt};
36
+ }
37
+ export async function executeImages({plan,sourcePath,runDir,store,env=process.env,progress,fetchImpl=fetch,imageBackend='external',codexBinary='codex',codexHome=undefined,timeoutMs=1800000,nativeModel='gpt-6-astra'}) {
38
+ if(!['external','native'].includes(imageBackend))throw Error('Unsupported image backend');
39
+ if(nativeModel!=='gpt-6-astra')throw Error('Unsupported native model');
40
+ await fs.mkdir(runDir,{recursive:true});
41
+ const sourceBytes=await fs.readFile(sourcePath);
42
+ const results=await Promise.allSettled(plan.groups.map(group=>executeGroup({group,sourceBytes,runDir,store,env,progress,fetchImpl,imageBackend,codexBinary,codexHome,timeoutMs})));
43
+ await save(path.join(runDir,'artwork-results.json'),JSON.stringify(results.map((r,i)=>r.status==='fulfilled'?{groupId:plan.groups[i].id,status:r.status,receipt:r.value.receipt}:{groupId:plan.groups[i].id,status:r.status,error:String(r.reason)}),null,2)+'\n');
44
+ const failures=results.filter(r=>r.status==='rejected');
45
+ if(failures.length) {
46
+ const error=new AggregateError(failures.map(r=>r.reason),'Artwork incomplete; saved calls can be resumed without fallback or paid retries');
47
+ if(failures.some(r=>r.reason?.code==='NEEDS_RECONCILIATION'))Object.assign(error,{code:'NEEDS_RECONCILIATION'});
48
+ throw error;
49
+ }
50
+ const completed=results.filter(r=>r.status==='fulfilled').map(r=>r.value);
51
+ return {assets:completed.flatMap(r=>r.assets),usage:completed.map(r=>({groupId:r.receipt.groupId,usage:r.receipt.usage,metadata:r.receipt.metadata,reused:r.receipt.reused}))};
52
+ }
@@ -0,0 +1,10 @@
1
+ import sharp from 'sharp';
2
+ import {createHash} from 'node:crypto';
3
+ const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
4
+ export async function normalizeExternalFrame(providerPng,frame){
5
+ if(!frame.providerOutput)return {png:providerPng,transform:null};
6
+ const actual=await sharp(providerPng).metadata();
7
+ if(actual.width!==frame.providerOutput.width||actual.height!==frame.providerOutput.height)throw Error('Provider grid dimensions differ from the frozen request');
8
+ const png=await sharp(providerPng).resize(frame.output.width,frame.output.height,{fit:'fill',kernel:'lanczos3'}).png().toBuffer();
9
+ return {png,transform:{...frame.normalization,input:frame.providerOutput,output:frame.output,scaleX:frame.output.width/actual.width,scaleY:frame.output.height/actual.height,providerSha256:hash(providerPng),sha256:hash(png)}};
10
+ }
@@ -0,0 +1,9 @@
1
+ import sharp from 'sharp';
2
+ export async function prepareReference(sourceBytes,group) {
3
+ return sharp(sourceBytes).extract(group.frame.input).png().toBuffer();
4
+ }
5
+ export async function cropProviderImage(png,group) {
6
+ const meta=await sharp(png).metadata();
7
+ if(meta.width!==group.frame.output.width||meta.height!==group.frame.output.height) throw Error(`Incorrect provider dimensions for ${group.id}; no resize`);
8
+ return Promise.all(group.members.map(async asset=>({id:asset.id,bytes:await sharp(png).extract(asset.frame.crop).png().toBuffer(),crop:asset.frame.crop,delivery:asset.delivery})));
9
+ }
@@ -0,0 +1,17 @@
1
+ import sharp from 'sharp';
2
+ import {createHash} from 'node:crypto';
3
+ const hash=b=>createHash('sha256').update(b).digest('hex');
4
+ const positive=n=>Number.isSafeInteger(n)&&n>0;
5
+ // Declared pixels only: no interpretation of colors, content, or ownership.
6
+ export async function executeLocalImageOperation({source,crop,output=undefined,mask=undefined,composite=[]}) {
7
+ const started=performance.now(), metadata=await sharp(source).metadata();
8
+ if(!crop||!['left','top','width','height'].every(k=>Number.isSafeInteger(crop[k]))||crop.left<0||crop.top<0||!positive(crop.width)||!positive(crop.height)||crop.left+crop.width>metadata.width||crop.top+crop.height>metadata.height)throw Error('Crop must be an integer rectangle inside the source');
9
+ let bytes=await sharp(source).extract(crop).png().toBuffer();
10
+ if(output){if(!positive(output.width)||!positive(output.height)||!['nearest','cubic','mitchell','lanczos2','lanczos3'].includes(output.kernel))throw Error('Explicit output dimensions and supported kernel required');bytes=await sharp(bytes).resize(output.width,output.height,{fit:'fill',kernel:output.kernel}).png().toBuffer();}
11
+ const size=await sharp(bytes).metadata();
12
+ /** @type {import('sharp').OverlayOptions[]} */ const steps=[];
13
+ if(mask){if(!mask.provenance)throw Error('Supplied mask provenance required');const m=await sharp(mask.input).metadata();if(m.width!==size.width||m.height!==size.height||!m.hasAlpha)throw Error('Supplied mask must have alpha and match output dimensions');steps.push({input:mask.input,blend:'dest-in'});}
14
+ for(const item of composite){if(item.blend!=='over'||!item.provenance||!Number.isSafeInteger(item.left)||!Number.isSafeInteger(item.top)||item.left<0||item.top<0)throw Error('Explicit over composition and provenance required');const m=await sharp(item.input).metadata();if(item.left+m.width>size.width||item.top+m.height>size.height)throw Error('Overlay outside output');steps.push({input:item.input,left:item.left,top:item.top,blend:'over'});}
15
+ if(steps.length)bytes=await sharp(bytes).ensureAlpha().composite(steps).png().toBuffer();
16
+ return {bytes,receipt:{operation:'declared-local-image',sourceSha256:hash(source),crop,output:output??{width:crop.width,height:crop.height,kernel:null},mask:mask?{sha256:hash(mask.input),provenance:mask.provenance}:null,composite:composite.map(x=>({sha256:hash(x.input),left:x.left,top:x.top,blend:x.blend,provenance:x.provenance})),sha256:hash(bytes),elapsedMs:performance.now()-started}};
17
+ }
@@ -0,0 +1,18 @@
1
+ import {writeFile} from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {executeLocalImageOperation} from './local-operation.mjs';
4
+ /** Executes only declared original opaque pixels; no provider dispatch. */
5
+ export async function executeLocalGroup({group,sourceBytes,runDir,progress}) {
6
+ const assets=[];
7
+ for(const asset of group.members){
8
+ if(asset.operation!=='crop-source'||asset.background!=='opaque'||asset.named_removals.length)throw Error('Invalid local source crop declaration');
9
+ const {bytes,receipt:operation}=await executeLocalImageOperation({source:sourceBytes,crop:asset.owner});
10
+ const relativePath=`${asset.id}.png`;
11
+ await writeFile(path.join(runDir,relativePath),bytes);
12
+ const receipt={id:asset.id,path:relativePath,sha256:operation.sha256,crop:asset.owner,delivery:asset.delivery,operation};
13
+ await writeFile(path.join(runDir,`${asset.id}-delivery.json`),JSON.stringify(receipt,null,2)+'\n');assets.push(receipt);
14
+ }
15
+ const receipt={groupId:group.id,backend:'local',operation:'crop-source',paidCall:false,assets:assets.map(a=>({id:a.id,sha256:a.sha256})),usage:null,reused:false};
16
+ await writeFile(path.join(runDir,`${group.id}-receipt.json`),JSON.stringify(receipt,null,2)+'\n');
17
+ progress?.({phase:'image-complete',groupId:group.id,backend:'local'});return {assets,receipt};
18
+ }
@@ -0,0 +1,43 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {parseNativeRecords, pngDimensions, geometryTreatments, sha256} from './native-records.mjs';
4
+
5
+ async function custody(file, bytes) {
6
+ try { await fs.writeFile(file, bytes, {flag: 'wx'}); }
7
+ catch (error) { if (error.code !== 'EEXIST' || sha256(await fs.readFile(file)) !== sha256(bytes)) throw error; }
8
+ }
9
+ // Collection is repeatable and never launches inference or changes previous bytes.
10
+ export async function collectNative({rolloutPaths, outputDir, expected}) {
11
+ await fs.mkdir(outputDir, {recursive: true});
12
+ const originals = [];
13
+ for (const [index, source] of rolloutPaths.entries()) {
14
+ const bytes = await fs.readFile(source), destination = path.join(outputDir, `rollout-${index}.jsonl`);
15
+ await custody(destination, bytes);
16
+ originals.push({source, path: destination, sha256: sha256(bytes)});
17
+ }
18
+ const audit = parseNativeRecords((await Promise.all(originals.map(f => fs.readFile(f.path, 'utf8')))).join('\n'));
19
+ const outputs = [];
20
+ for (const [index, generation] of audit.generations.entries()) {
21
+ if (!generation.bytes) continue;
22
+ const file = path.join(outputDir, `native-original-${index}.png`);
23
+ await custody(file, generation.bytes);
24
+ let savedPathMatches = null;
25
+ if (generation.savedPath) {
26
+ try { savedPathMatches = sha256(await fs.readFile(generation.savedPath)) === generation.outputSha256; }
27
+ catch (error) { if (error.code !== 'ENOENT') throw error; }
28
+ }
29
+ outputs.push({id: generation.id, path: file, sha256: generation.outputSha256, savedPathMatches, dimensions: pngDimensions(generation.bytes)});
30
+ }
31
+ const geometry = outputs.length === 1 ? geometryTreatments(outputs[0].dimensions, expected) : null;
32
+ const receipt = {...audit, generations: audit.generations.map(({bytes, ...rest}) => rest), originals, outputs, geometry,
33
+ state: outputs.some(output => output.savedPathMatches === false) ? 'custody-mismatch' : audit.state === 'completed-awaiting-dimensions' ? (geometry?.raw.status ?? 'unknown-missing-output') : audit.state,
34
+ accounting: 'Quota observations are account-wide snapshots, not per-image dollars or isolated consumption.',
35
+ recovery: 'Collect only. Never redispatch an existing claim, including unknown or failed outcomes.'};
36
+ await custody(path.join(outputDir, 'receipt.json'), JSON.stringify(receipt, null, 2) + '\n');
37
+ return receipt;
38
+ }
39
+
40
+ export async function packNativeReceipt(receipt) {
41
+ const originals=await Promise.all(receipt.outputs.map(async output=>({...output,base64:(await fs.readFile(output.path)).toString('base64')})));
42
+ return Buffer.from(JSON.stringify({...receipt,outputs:originals}));
43
+ }
@@ -0,0 +1,11 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ export async function filesUnder(directory) {
4
+ const result=[];
5
+ for(const entry of await fs.readdir(directory,{withFileTypes:true})) {
6
+ const file=path.join(directory,entry.name);
7
+ if(entry.isDirectory()) result.push(...await filesUnder(file));
8
+ else if(entry.isFile()) result.push(file);
9
+ }
10
+ return result.sort();
11
+ }
@@ -0,0 +1,49 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import {spawn} from 'node:child_process';
4
+ import {createWriteStream} from 'node:fs';
5
+ import {finished} from 'node:stream/promises';
6
+ import {subscriptionEnv} from '../codex/home.mjs';
7
+ import {filesUnder} from './native-files.mjs';
8
+ import {collectNative,packNativeReceipt} from './native-collect.mjs';
9
+
10
+ // Based on the successful C-N03 study invocation; separate from the no-tool planner.
11
+ export function nativeArgv(spec, finalPath) {
12
+ return ['-a', 'never', 'exec', '--ignore-user-config', '--ignore-rules', '--strict-config', '-C', spec.workDir,
13
+ '--skip-git-repo-check', '-s', 'danger-full-access', '-m', 'gpt-6-astra', '-c', 'model_reasoning_effort="medium"',
14
+ '-c', 'model_provider="openai"', '-c', 'forced_login_method="chatgpt"', '-c', 'project_doc_max_bytes=0',
15
+ '--enable', 'skip_host_skill_discovery', '--json', '--color', 'never', '--output-last-message', finalPath,
16
+ '--image', spec.referencePath, '-'];
17
+ }
18
+ export async function invokeNative(spec, {callDir, persistMetadata}) {
19
+ await fs.mkdir(spec.workDir, {recursive: true});
20
+ const auth = spec.auth;
21
+ const finalPath = path.join(callDir, 'final.txt'), argv = nativeArgv(spec, finalPath);
22
+ const metadata = {model: 'gpt-6-astra', effort: 'medium', authentication: auth.authentication, home: auth.home, argv, startedAt: new Date().toISOString()};
23
+ await persistMetadata(metadata); // Private runtime location needed for collection after interruption.
24
+ await fs.writeFile(path.join(callDir, 'invocation.json'), JSON.stringify(metadata, null, 2) + '\n', {flag: 'wx'});
25
+ const stdout = createWriteStream(path.join(callDir, 'events.jsonl'), {flags: 'wx'});
26
+ const stderr = createWriteStream(path.join(callDir, 'stderr.log'), {flags: 'wx'});
27
+ const prompt = spec.prompt;
28
+ let timedOut = false, killTimer;
29
+ const child = spawn(spec.binary, argv, {cwd: spec.workDir, env: subscriptionEnv(auth.home), detached: true, stdio: ['pipe', 'pipe', 'pipe']});
30
+ const stop = signal => { if (child.pid) try { process.kill(-child.pid, signal); } catch (error) { if (error.code !== 'ESRCH') throw error; } };
31
+ const terminate = () => { stop('SIGTERM'); killTimer = setTimeout(() => stop('SIGKILL'), 5000); };
32
+ child.stdout.pipe(stdout); child.stderr.pipe(stderr); child.stdin.on('error', () => {}); child.stdin.end(prompt);
33
+ const timer = setTimeout(() => { timedOut = true; terminate(); }, spec.timeoutMs);
34
+ const interrupted = () => terminate();
35
+ process.once('SIGINT', interrupted); process.once('SIGTERM', interrupted);
36
+ const result = await new Promise(resolve => { child.once('error', error => resolve({spawnError: String(error)})); child.once('close', (exitCode, signal) => resolve({exitCode, signal})); });
37
+ clearTimeout(timer); clearTimeout(killTimer); stop('SIGTERM');
38
+ process.removeListener('SIGINT', interrupted); process.removeListener('SIGTERM', interrupted);
39
+ await Promise.all([finished(stdout), finished(stderr)]);
40
+ const events=(await fs.readFile(path.join(callDir,'events.jsonl'),'utf8')).split('\n').filter(Boolean).flatMap(line=>{try{return [JSON.parse(line)];}catch{return [{type:'malformed-event'}];}});
41
+ const diagnostics=events.filter(event=>['error','turn.failed','malformed-event'].includes(event.type));
42
+ const codexUsage=events.filter(event=>event.type==='turn.completed').map(event=>event.usage);
43
+ const completed = {...metadata, diagnostics, codexUsage, ...result, timedOut, finishedAt: new Date().toISOString()};
44
+ await persistMetadata(completed);
45
+ await fs.writeFile(path.join(callDir, 'completion.json'), JSON.stringify(completed, null, 2) + '\n', {flag: 'wx'});
46
+ const rolloutPaths = (await filesUnder(path.join(auth.home, 'sessions')).catch(error => error.code === 'ENOENT' ? [] : Promise.reject(error))).filter(file => file.endsWith('.jsonl'));
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}};
49
+ }
@@ -0,0 +1,57 @@
1
+ import {createHash} from 'node:crypto';
2
+
3
+ export const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
4
+ export function pngDimensions(bytes) {
5
+ if (bytes.length < 24 || !bytes.subarray(0, 8).equals(Buffer.from('89504e470d0a1a0a', 'hex'))) throw Error('Native output is not a PNG');
6
+ return {width: bytes.readUInt32BE(16), height: bytes.readUInt32BE(20)};
7
+ }
8
+
9
+ // Preserve the source JSONL separately; this summary deliberately excludes base64.
10
+ export function parseNativeRecords(text) {
11
+ const generations = new Map(), calls = new Map(), quota = [], tokenUsage = [], diagnostics = [], malformed = [];
12
+ let model = null, effort = null;
13
+ for (const [index, line] of text.split('\n').entries()) {
14
+ if (!line.trim()) continue;
15
+ let record;
16
+ try { record = JSON.parse(line); } catch { malformed.push(index + 1); continue; }
17
+ const p = record.payload ?? {}, item = p.item;
18
+ if (record.type === 'turn_context') { model = p.model ?? model; effort = p.effort ?? p.reasoning_effort ?? effort; }
19
+ if(p.type === 'token_count' && p.info) tokenUsage.push({timestamp:record.timestamp,...p.info});
20
+ if(['error','turn_aborted'].includes(p.type)) diagnostics.push({timestamp:record.timestamp,...p});
21
+ if (p.rate_limits) quota.push({line: index + 1, timestamp: record.timestamp, ...p.rate_limits});
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});
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});
27
+ if (item?.kind === 'image_gen.generation' && p.type === 'item_completed') {
28
+ const bytes = typeof item.result === 'string' ? Buffer.from(item.result, 'base64') : null;
29
+ const entry = {id: item.id, status: item.status, failure: item.failure ?? null, savedPath: item.savedPath ?? null,
30
+ transparentBackground: item.transparentBackground, revisedPrompt: item.revisedPrompt,
31
+ line: index + 1, timestamp: record.timestamp, threadId: p.thread_id, turnId: p.turn_id,
32
+ outputSha256: bytes ? sha256(bytes) : null, bytes};
33
+ if (generations.has(item.id) && generations.get(item.id).outputSha256 !== entry.outputSha256) throw Error('Conflicting native completion for ' + item.id);
34
+ generations.set(item.id, entry);
35
+ }
36
+ }
37
+ const count = [...calls.values()].reduce((sum, call) => sum + call.count, 0);
38
+ const completed = [...generations.values()];
39
+ const success = completed.length === 1 && completed[0].status === 'completed' && !completed[0].failure && completed[0].bytes;
40
+ const startedAt = [...calls.values()].map(call => call.timestamp).filter(Boolean).sort()[0];
41
+ const finishedAt = completed.map(item => item.timestamp).filter(Boolean).sort().at(-1);
42
+ const quotaBefore = quota.filter(item => startedAt && item.timestamp <= startedAt).at(-1) ?? null;
43
+ const quotaAfter = quota.find(item => finishedAt && item.timestamp >= finishedAt) ?? null;
44
+ return {model, effort, toolStartedAt:startedAt??null,toolFinishedAt:finishedAt??null,toolMs:startedAt&&finishedAt?Date.parse(finishedAt)-Date.parse(startedAt):null, calls: [...calls.values()], invocationCount: count, generations: completed, quota, quotaBefore, quotaAfter, tokenUsage, diagnostics, malformed,
45
+ state: malformed.length ? 'unknown-malformed-log' : count !== 1 ? (count > 1 ? 'protocol-violation-multiple-calls' : 'unknown-no-auditable-call') : success ? 'completed-awaiting-dimensions' : completed.length ? 'native-failed' : 'unknown-no-completion'};
46
+ }
47
+
48
+ export function geometryTreatments(actual, expected) {
49
+ const x = expected.width / actual.width, y = expected.height / actual.height;
50
+ const scale = Math.max(x, y);
51
+ return {
52
+ raw: {status: actual.width === expected.width && actual.height === expected.height ? 'exact-size-pass' : 'size-mismatch', actual, expected},
53
+ stretch: {name: 'exact-stretch', scaleX: x, scaleY: y, anisotropy: Math.max(x / y, y / x) - 1},
54
+ cover: {name: 'aspect-preserving-center-cover', scale, offsetX: (expected.width - actual.width * scale) / 2, offsetY: (expected.height - actual.height * scale) / 2},
55
+ selection: 'unselected-pending-multiple-case-registration-review',
56
+ };
57
+ }
@@ -0,0 +1,41 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import sharp from 'sharp';
5
+ import {prepareHome} from '../codex/home.mjs';
6
+ import {invokeNative} from './native-invoke.mjs';
7
+ import {collectNative,packNativeReceipt} from './native-collect.mjs';
8
+ import {filesUnder} from './native-files.mjs';
9
+ import {sha256,pngDimensions,parseNativeRecords} from './native-records.mjs';
10
+
11
+ export async function decodeNativeResponse(bytes) {
12
+ const receipt=JSON.parse(bytes.toString('utf8'));
13
+ if(!['exact-size-pass','size-mismatch'].includes(receipt.state)||receipt.outputs.length!==1) throw Error('Native image call requires inspection: '+receipt.state);
14
+ if(receipt.model!=='gpt-6-astra'||receipt.effort!=='medium')throw Error('Native coordinator model/effort does not match requested execution');
15
+ const original=Buffer.from(receipt.outputs[0].base64,'base64');
16
+ if(sha256(original)!==receipt.outputs[0].sha256) throw Error('Native original custody mismatch');
17
+ const actual=pngDimensions(original), expected=receipt.geometry.raw.expected;
18
+ const png=await sharp(original).resize(expected.width,expected.height,{fit:'fill',kernel:'lanczos3'}).png().toBuffer();
19
+ return {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:{name:'exact-stretch',kernel:'lanczos3',output:expected,scaleX:expected.width/actual.width,scaleY:expected.height/actual.height},model:receipt.model,effort:receipt.effort,imageModel:null,invocationCount:receipt.invocationCount}};
20
+ }
21
+ export async function executeNativeCall({request,input,callId,store,codexBinary='codex',codexHome,timeoutMs=1800000}) {
22
+ let auth;
23
+ return store.runCall(callId,request,async({callDir,persistMetadata})=>{
24
+ const referencePath=path.join(callDir,'reference.png');
25
+ await fs.writeFile(referencePath,input,{flag:'wx'});
26
+ const prompt=`This is an asset-execution task. Use tools.view_image once to inspect ${referencePath}. Then invoke tools.image_gen__imagegen exactly once with referenced_image_paths=[${JSON.stringify(referencePath)}] and the following prompt copied verbatim. Do not use shell, web, apps, other image providers, or image-editing code. No retries, extra calls, HTML work, or visual repair. If unavailable or unsuccessful, report failure. Emit the native image result with generatedImage and finish. Do not print base64.\n\nIMAGE PROMPT:\n${request.prompt}\n`;
27
+ return invokeNative({auth,binary:codexBinary,workDir:path.join(callDir,'work'),referencePath,prompt,timeoutMs,expected:request.expected},{callDir,persistMetadata});
28
+ },{prepare:async({callDir})=>{
29
+ const authHome=codexHome??process.env.CODEX_HOME??path.join(os.homedir(),'.codex');
30
+ auth=await prepareHome({authHome,binary:codexBinary,runDir:callDir});
31
+ },recover:async({callDir,metadata})=>{
32
+ if(!metadata?.home)return null;
33
+ const rolloutPaths=(await filesUnder(path.join(metadata.home,'sessions')).catch(error=>{if(error.code==='ENOENT')return [];throw error;})).filter(file=>file.endsWith('.jsonl'));
34
+ if(!rolloutPaths.length)return null;
35
+ const audit=parseNativeRecords((await Promise.all(rolloutPaths.map(file=>fs.readFile(file,'utf8')))).join('\n'));
36
+ if(audit.state!=='completed-awaiting-dimensions')return null;
37
+ const receipt=await collectNative({rolloutPaths,outputDir:path.join(callDir,'native-custody'),expected:request.expected});
38
+ if(!['exact-size-pass','size-mismatch'].includes(receipt.state))return null;
39
+ return {bytes:await packNativeReceipt(receipt),metadata:{...metadata,nativeToolMs:receipt.toolMs,quotaBefore:receipt.quotaBefore,quotaAfter:receipt.quotaAfter,tokenUsage:receipt.tokenUsage,collectionRecovery:true}};
40
+ }});
41
+ }
@@ -0,0 +1,14 @@
1
+ export function operationPrompt(group,backend='external'){const {frame}=group;
2
+ if(frame.providerOutput&&backend==='external')return gridPrompt(group);
3
+ if(group.operation==='preserve-source')return `Upscale this complete original image to exactly ${frame.output.width} pixels wide by ${frame.output.height} pixels high. Preserve the original composition exactly: double every position and dimension, with no layout changes. Preserve ALL visible navigation text, captions, rules, gaps, and original materials and artwork, including their original crop, scale, texture, edges, and relative positions. Do not remove any content, redesign, rearrange, recenter, enlarge individual artwork, extend its framing, or add anything. Return the entire supplied image at exactly 2× resolution with faithfully clarified material detail. This is source-preserving upscaling of the whole reference, not a new layout.`;
4
+ const owners=group.members.map(a=>({rectangle:{left:a.owner.left-frame.input.left,top:a.owner.top-frame.input.top,width:a.owner.width,height:a.owner.height},desired_owner:a.desired_owner,remove_only:a.named_removals.map(r=>{const [x,y,w,h]=r.source_bbox_px,o=a.owner,left=Math.max(x,o.left),top=Math.max(y,o.top),right=Math.min(x+w,o.left+o.width),bottom=Math.min(y+h,o.top+o.height);return {text_or_element:r.text_or_element,rectangle:{left:left-frame.input.left,top:top-frame.input.top,width:right-left,height:bottom-top}};})}));
5
+ return `Return this complete original context at exactly ${frame.output.width}×${frame.output.height}, doubling every input coordinate. Preserve the original composition, artwork positions, extents, material detail and all neighboring content. Inside only the following INPUT-coordinate owner rectangles, deliver the desired underlying owner and remove only the explicitly named overlays in their listed INPUT-coordinate rectangles: ${JSON.stringify(owners)}. Rebuild the owner continuously where these overlays were removed. Declared removal facts have exclusive authority and override preservation instructions. Preserve only UNLISTED writing and details, including everything outside the owner rectangles. Do not recompose, recenter, enlarge, stretch or rearrange any element. Preserve the declared ${group.background} owner ground. No new readable content. The executor will take fixed source-coordinate crops; it cannot correct movement.`;
6
+ }
7
+
8
+ function gridPrompt(group){
9
+ const {frame}=group,size=frame.providerOutput;
10
+ const mapping=`Return the complete supplied original context at exactly ${size.width}×${size.height}. Map the entire ${frame.input.width}×${frame.input.height} input to that canvas proportionally on each axis, without padding, cropping or repositioning. These dimensions meet the provider grid; the executor will map the complete result to ${frame.output.width}×${frame.output.height} before taking exact source-owner crops.`;
11
+ if(group.operation==='preserve-source')return `${mapping} Preserve all visible writing, composition, artwork, texture and details. Clarify material detail without redesigning, adding, removing or rearranging content.`;
12
+ const owners=group.members.map(a=>({rectangle:{left:a.owner.left-frame.input.left,top:a.owner.top-frame.input.top,width:a.owner.width,height:a.owner.height},desired_owner:a.desired_owner,remove_only:a.named_removals.map(r=>{const [x,y,w,h]=r.source_bbox_px,o=a.owner,left=Math.max(x,o.left),top=Math.max(y,o.top),right=Math.min(x+w,o.left+o.width),bottom=Math.min(y+h,o.top+o.height);return {text_or_element:r.text_or_element,rectangle:{left:left-frame.input.left,top:top-frame.input.top,width:right-left,height:bottom-top}};})}));
13
+ return `${mapping} All following owner and removal rectangles use ORIGINAL INPUT coordinates. Inside only these owners, reconstruct the desired underlying material and remove only the explicitly named overlays: ${JSON.stringify(owners)}. Declared removal facts have exclusive authority. Preserve all unlisted content, all neighboring pixels and the declared ${group.background} ground. No new readable content, recentering, enlargement or rearrangement.`;
14
+ }
@@ -0,0 +1,22 @@
1
+ /** External Flare transport. Raw wire bytes are returned for durable custody before decoding. */
2
+ export const DEFAULT_IMAGE_ENDPOINT = 'https://api.openai.com/v1/images/edits';
3
+ export async function requestImage({request,input,apiKey,idempotencyKey,persistMetadata,fetchImpl=fetch}) {
4
+ if (!apiKey?.trim()) throw Error('OPENAI_API_KEY is required for external Flare image execution');
5
+ const started=performance.now(), requestStartedAt=new Date().toISOString();
6
+ const form=new FormData();
7
+ for (const [key,value] of Object.entries({model:request.model,prompt:request.prompt,quality:request.quality,n:'1',size:request.size,background:request.background,output_format:'png'})) form.set(key,value);
8
+ form.append('image[]',new Blob([input],{type:'image/png'}),'input-0.png');
9
+ const response=await fetchImpl(request.endpoint,{method:'POST',headers:{Authorization:`Bearer ${apiKey}`,'Idempotency-Key':idempotencyKey},body:form,signal:AbortSignal.timeout(600000)});
10
+ const metadata={status:response.status,headers:{requestId:response.headers.get('x-request-id'),processingMs:response.headers.get('openai-processing-ms')},requestStartedAt,responseHeadersAt:new Date().toISOString(),responseHeadersMs:Math.round(performance.now()-started)};
11
+ await persistMetadata(metadata);
12
+ const bytes=Buffer.from(await response.arrayBuffer());
13
+ return {bytes,metadata:{...metadata,bodyReceivedAt:new Date().toISOString(),bodyReceivedMs:Math.round(performance.now()-started)}};
14
+ }
15
+ export function decodeImageResponse(bytes,metadata) {
16
+ let payload;
17
+ try { payload=JSON.parse(bytes.toString('utf8')); } catch { throw Error('Saved image provider response is not JSON'); }
18
+ if(metadata.status<200||metadata.status>=300) throw Error(`Image provider returned HTTP ${metadata.status}; saved response requires inspection`);
19
+ const b64=payload?.data?.[0]?.b64_json;
20
+ if(typeof b64!=='string'||!b64||Buffer.from(b64,'base64').toString('base64')!==b64) throw Error('Saved image provider response contains no valid base64 image');
21
+ return {png:Buffer.from(b64,'base64'),usage:payload.usage??null,revisedPrompt:payload.data[0].revised_prompt??null};
22
+ }
@@ -0,0 +1,28 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { hash, writeJson } from './run-store.mjs';
4
+
5
+ /** @param {{runDir:string,normalized:any,assets:any[],source:any,plan:any}} options */
6
+ export async function publishOutput({ runDir, normalized, assets, source, plan }) {
7
+ const dir = path.join(runDir, 'output'); await mkdir(dir, { recursive: true });
8
+ const files = [];
9
+ for (const asset of assets) {
10
+ const bytes = await readFile(path.join(runDir, asset.path));
11
+ if (asset.sha256 && asset.sha256 !== hash(bytes)) throw new Error(`Asset identity changed: ${asset.id}`);
12
+ const name = `${asset.id}.png`;
13
+ await writeFile(path.join(dir, name), bytes);
14
+ files.push({ path: name, sha256: hash(bytes), width: asset.delivery.width, height: asset.delivery.height, sourceOwnership: plan.assets.find(item => item.id === asset.id) });
15
+ }
16
+ for (const asset of normalized.supplemental) {
17
+ await writeFile(path.join(dir, asset.name), asset.bytes);
18
+ files.push({ path: asset.name, sha256: hash(asset.bytes) });
19
+ }
20
+ await writeFile(path.join(dir, 'index.html'), normalized.html);
21
+ files.unshift({ path: 'index.html', sha256: hash(normalized.html) });
22
+ await writeJson(path.join(dir, 'manifest.json'), {
23
+ schemaVersion: 1, entrypoint: 'index.html', files, externalDependencies: normalized.externalDependencies,
24
+ source: { sha256: source.sha256, width: source.width, height: source.height },
25
+ format: 'html-css', nativeLayerDoc: false,
26
+ });
27
+ return { entrypoint: 'output/index.html', manifest: 'output/manifest.json' };
28
+ }
@@ -0,0 +1,35 @@
1
+ import {open,readFile} from 'node:fs/promises';
2
+ import {spawn} from 'node:child_process';
3
+ import path from 'node:path';
4
+ import {subscriptionEnv} from '../codex/home.mjs';
5
+ import {writeJson,hash} from '../run-store.mjs';
6
+ import {summarizeEvents} from '../codex/events.mjs';
7
+ import {projectArgv} from './prompt.mjs';
8
+ export async function invokeProject({config,runDir,home,binary,prompt}) {
9
+ const stdout=await open(path.join(runDir,'events.jsonl'),'wx'), stderr=await open(path.join(runDir,'stderr.log'),'wx');
10
+ const startedAt=new Date().toISOString(); let timedOut=false, interrupted=false, spawnError=null;
11
+ try {
12
+ const child=spawn(binary,projectArgv(config,runDir),{cwd:config.projectDir,env:subscriptionEnv(home),detached:true,stdio:['pipe',stdout.fd,stderr.fd]});
13
+ const stop=signal=>{if(child.pid)try{process.kill(-child.pid,signal);}catch(e){if(e.code!=='ESRCH')throw e;}};
14
+ let killTimer;
15
+ const terminate=()=>{stop('SIGTERM');killTimer=setTimeout(()=>stop('SIGKILL'),5000);};
16
+ const signal=()=>{interrupted=true;terminate();};
17
+ process.once('SIGINT',signal);process.once('SIGTERM',signal);
18
+ const timer=setTimeout(()=>{timedOut=true;terminate();},config.timeoutMs);
19
+ child.stdin.on('error',()=>{});child.stdin.end(prompt);
20
+ let exitCode=null,exitSignal=null;
21
+ try { await new Promise(resolve=>{child.once('error',e=>{spawnError={code:/** @type {NodeJS.ErrnoException} */ (e).code??null};});child.once('close',(code,sig)=>{exitCode=code;exitSignal=sig;resolve(undefined);});}); }
22
+ finally {clearTimeout(timer);clearTimeout(killTimer);stop('SIGTERM');process.removeListener('SIGINT',signal);process.removeListener('SIGTERM',signal);await stdout.sync();await stderr.sync();}
23
+ const final=await readFile(path.join(runDir,'final.txt')).catch(e=>{if(e.code==='ENOENT')return null;throw e;});
24
+ const execution={startedAt,finishedAt:new Date().toISOString(),exitCode,exitSignal,timedOut,interrupted,spawnError,finalSha256:final?hash(final):null};
25
+ await writeJson(path.join(runDir,'execution.json'),execution);
26
+ return execution;
27
+ }finally{await stdout.close();await stderr.close();}
28
+ }
29
+ export async function completion(runDir) {
30
+ const execution=await readFile(path.join(runDir,'execution.json'),'utf8').then(JSON.parse).catch(e=>{if(e.code==='ENOENT')return null;throw e;});
31
+ const events=await summarizeEvents(path.join(runDir,'events.jsonl')).catch(()=>null);
32
+ const final=await readFile(path.join(runDir,'final.txt')).catch(e=>{if(e.code==='ENOENT')return null;throw e;});
33
+ const complete=execution?.exitCode===0&&!execution.timedOut&&!execution.interrupted&&!execution.spawnError&&final?.length&&execution.finalSha256===hash(final)&&events?.usage.length>0&&!events.failed.length&&!events.errorsAfterCompletion.length&&!events.malformedJsonlLines.length;
34
+ return {status:complete?'complete':execution?'incomplete':'needs-reconciliation',execution,usage:events?.usage??[],diagnostics:events?.errors??[],threadId:events?.threadId??null,finalSha256:final?hash(final):null};
35
+ }
@@ -0,0 +1,13 @@
1
+ export function projectPrompt(c,runDir) {
2
+ return `Apply the FIRST attached image, the selected redesign target, to the actual working application in ${c.projectDir}. The SECOND image is its current source UI for orientation. Inspect its code and existing assets first, then realize the target through the actual framework components and styles in this one bounded coding turn. Actual code and live data are authoritative over both screenshots.
3
+ Preserve all content, controls, links, routes, state transitions, event handlers, API integration, real data and persistence. Do not replace the application with static HTML, mock behavior, fake data, a screenshot background, tracing or a full-page raster. Interpret layout, typography, colors, hierarchy and surfaces while retaining the application's meaning. Record any target content conflicting with real data.
4
+ Reuse existing artwork and the existing icon library. CSS and proper vector icons are appropriate UI implementation; do not approximate genuine photographic or illustrative artwork with CSS/vector drawings. If necessary new artwork is unavailable, write ${runDir}/asset-requests.json with target-image region coordinates, purpose and desired processing for the parent to execute separately. Continue delivering the best valid application around unresolved artwork and report it honestly. This runner does not process image requests.
5
+ Work only inside ${c.projectDir} and ${runDir}. Use an authentication/browser helper only by the exact command supplied below; it owns authentication. Do not read or print credentials, cookies, private auth state, secret files, raw runtime logs or database files. Do not commit, reset/reseed data, alter shared services or another project. Do not invoke 12ui, other agents, model APIs, image generation or paid services. No additional inference or automatic retry is authorized. Preserve project structure and scripts; keep changes modular and remove superseded styling.
6
+ Verify the real application at ${c.width}x${c.height} and a relevant narrow width, using the supplied build/typecheck and behavior checks. Save captures and verification output in ${runDir}. A command exit is execution evidence, not visual quality proof. Report actual checks, target differences, unresolved limitations and asset requests. Preserve meaningful changes and normalize concrete recoverable defects without another model call.
7
+ Automatic rule/skill discovery is disabled. The caller supplies all applicable project rules and exact runtime/build/browser/behavior context here:
8
+ ${c.instructions}
9
+ `;
10
+ }
11
+ export function projectArgv(c,runDir) {
12
+ return ['-a','never','exec','--ignore-user-config','--ignore-rules','--strict-config','-C',c.projectDir,'--skip-git-repo-check','-s','danger-full-access','-m','gpt-6-astra','-c','model_reasoning_effort="low"','-c','model_provider="openai"','-c','forced_login_method="chatgpt"','-c','project_doc_max_bytes=0','--enable','skip_host_skill_discovery','--json','--color','never','--output-last-message',`${runDir}/final.txt`,'--image',`${runDir}/target.image`,'--image',`${runDir}/source.image`,'-'];
13
+ }