scenri 0.6.9 → 0.6.11
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/CHANGELOG.md +22 -0
- package/dist/serve.js +178 -38
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.11](https://github.com/tonygorb/Scenri/compare/v0.6.10...v0.6.11) (2026-08-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **engine-codex:** say what a usage limit means instead of quoting the banner ([f769d81](https://github.com/tonygorb/Scenri/commit/f769d812675e8671c46959c6b2007808cb3fa97e))
|
|
9
|
+
* **engine-codex:** say what a usage limit means instead of quoting the banner ([edd2155](https://github.com/tonygorb/Scenri/commit/edd2155fe1bf6592376feef9ab9871e1c51ca3b1))
|
|
10
|
+
|
|
11
|
+
## [0.6.10](https://github.com/tonygorb/Scenri/compare/v0.6.9...v0.6.10) (2026-08-29)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **engine-codex:** ask for the tool's own grid point, not our nominal pixels ([a0cc518](https://github.com/tonygorb/Scenri/commit/a0cc518c2e79702f728bc7d00edddf874d3b2dc3))
|
|
17
|
+
* **engine-codex:** bind references by role-named files and give every take the same instruction ([2e645b2](https://github.com/tonygorb/Scenri/commit/2e645b2d34dc7006f23792cad04ad9170f3bae28))
|
|
18
|
+
* **engine-codex:** bind references by role-named files and give every take the same instruction ([416999d](https://github.com/tonygorb/Scenri/commit/416999d85ad984e8e5ee4df06e12a6edf09ed84a))
|
|
19
|
+
* **engine-codex:** never let the model resize; the frame rides as ratio language ([a5907a0](https://github.com/tonygorb/Scenri/commit/a5907a0a2a247eb5e369d3eae94884880cdb6cc3))
|
|
20
|
+
* **engine-codex:** never let the model resize; the frame rides as ratio language ([063316a](https://github.com/tonygorb/Scenri/commit/063316ad15fe01000d112cf240ad10a6e5707ccf))
|
|
21
|
+
* validate and orient engine images before storing; crop drifted frames to the asked ratio ([b88c039](https://github.com/tonygorb/Scenri/commit/b88c0392ff97a103301617ad23ddfcb500303e90))
|
|
22
|
+
* validate and orient engine images before storing; crop drifted frames to the asked ratio ([3cbd462](https://github.com/tonygorb/Scenri/commit/3cbd462df35b322cd55c1e3b6e8c47b767ee4880))
|
|
23
|
+
* widen the crop net to the drift real briefs actually produce ([491aa3c](https://github.com/tonygorb/Scenri/commit/491aa3c9f03e1511188a3fc71a2de817a9aff289))
|
|
24
|
+
|
|
3
25
|
## [0.6.9](https://github.com/tonygorb/Scenri/compare/v0.6.8...v0.6.9) (2026-08-29)
|
|
4
26
|
|
|
5
27
|
|
package/dist/serve.js
CHANGED
|
@@ -1310,6 +1310,28 @@ var EDIT_REFERENCE_ROLE_DIRECTIVE = {
|
|
|
1310
1310
|
reference: "a reference for composition, lighting and treatment only"
|
|
1311
1311
|
};
|
|
1312
1312
|
var ASPECT_TOLERANCE = 0.15;
|
|
1313
|
+
var NAMED_RATIOS = [
|
|
1314
|
+
["1:1", 1],
|
|
1315
|
+
["4:5", 4 / 5],
|
|
1316
|
+
["5:4", 5 / 4],
|
|
1317
|
+
["2:3", 2 / 3],
|
|
1318
|
+
["3:2", 3 / 2],
|
|
1319
|
+
["3:4", 3 / 4],
|
|
1320
|
+
["4:3", 4 / 3],
|
|
1321
|
+
["9:16", 9 / 16],
|
|
1322
|
+
["16:9", 16 / 9],
|
|
1323
|
+
["2:1", 2],
|
|
1324
|
+
["1:2", 0.5]
|
|
1325
|
+
];
|
|
1326
|
+
function ratioLabel(width, height) {
|
|
1327
|
+
const ratio = width / height;
|
|
1328
|
+
for (const [label, value] of NAMED_RATIOS) {
|
|
1329
|
+
if (Math.abs(ratio - value) / value < 0.02) return label;
|
|
1330
|
+
}
|
|
1331
|
+
const gcd = (a, b) => b ? gcd(b, a % b) : a;
|
|
1332
|
+
const d = gcd(width, height) || 1;
|
|
1333
|
+
return `${Math.round(width / d)}:${Math.round(height / d)}`;
|
|
1334
|
+
}
|
|
1313
1335
|
|
|
1314
1336
|
// ../core/src/index.ts
|
|
1315
1337
|
function defaultHome() {
|
|
@@ -2118,13 +2140,26 @@ function createRunner(opts = {}) {
|
|
|
2118
2140
|
child.on("exit", (code) => {
|
|
2119
2141
|
if (code === 0) {
|
|
2120
2142
|
finish("ok", resolve);
|
|
2121
|
-
|
|
2122
|
-
|
|
2143
|
+
return;
|
|
2144
|
+
}
|
|
2145
|
+
const limit = /You've hit your usage limit\.(?:[^\n]*?\btry again at ([^.\n]+)\.)?/.exec(stderr);
|
|
2146
|
+
if (limit) {
|
|
2147
|
+
const when = limit[1]?.trim();
|
|
2123
2148
|
finish(
|
|
2124
2149
|
`exit-${code ?? "unknown"}`,
|
|
2125
|
-
() => reject(
|
|
2150
|
+
() => reject(
|
|
2151
|
+
new Error(
|
|
2152
|
+
`Your Codex plan's usage limit is used up${when ? ` until ${when}` : ""}. Generation resumes on its own then, or add credits from your Codex account.`
|
|
2153
|
+
)
|
|
2154
|
+
)
|
|
2126
2155
|
);
|
|
2156
|
+
return;
|
|
2127
2157
|
}
|
|
2158
|
+
const snippet2 = stderr.trim().slice(0, 200);
|
|
2159
|
+
finish(
|
|
2160
|
+
`exit-${code ?? "unknown"}`,
|
|
2161
|
+
() => reject(new Error(`codex exited with code ${code ?? "unknown"}${snippet2 ? `: ${snippet2}` : ""}`))
|
|
2162
|
+
);
|
|
2128
2163
|
});
|
|
2129
2164
|
});
|
|
2130
2165
|
}
|
|
@@ -2479,6 +2514,27 @@ var CODEX_POOL = 2;
|
|
|
2479
2514
|
function codexNodeBudgetMs(count) {
|
|
2480
2515
|
return Math.ceil(Math.max(1, count) / CODEX_POOL) * DEFAULT_TIMEOUT_MS2 + 6e4;
|
|
2481
2516
|
}
|
|
2517
|
+
function orientationOf(width, height) {
|
|
2518
|
+
return width === height ? "square" : width > height ? "landscape" : "portrait";
|
|
2519
|
+
}
|
|
2520
|
+
var CODEX_PIXEL_BUDGET = 1572864;
|
|
2521
|
+
function codexNativeSize(width, height) {
|
|
2522
|
+
const ratio = width / height;
|
|
2523
|
+
if (!(ratio > 0) || !Number.isFinite(ratio)) return { width, height };
|
|
2524
|
+
return {
|
|
2525
|
+
width: Math.round(Math.sqrt(CODEX_PIXEL_BUDGET * ratio)),
|
|
2526
|
+
height: Math.round(Math.sqrt(CODEX_PIXEL_BUDGET / ratio))
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
function refFileNames(roles, count) {
|
|
2530
|
+
const perRole = /* @__PURE__ */ new Map();
|
|
2531
|
+
return Array.from({ length: count }, (_, i) => {
|
|
2532
|
+
const role = roles[i] ?? "reference";
|
|
2533
|
+
const n = (perRole.get(role) ?? 0) + 1;
|
|
2534
|
+
perRole.set(role, n);
|
|
2535
|
+
return `${role}-${n}.png`;
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2482
2538
|
function createCodexEngine(opts) {
|
|
2483
2539
|
const { saveImage } = opts;
|
|
2484
2540
|
const platform = opts.platform ?? process.platform;
|
|
@@ -2511,7 +2567,9 @@ function createCodexEngine(opts) {
|
|
|
2511
2567
|
}
|
|
2512
2568
|
const hashes = [];
|
|
2513
2569
|
for (const name of outFiles) {
|
|
2514
|
-
|
|
2570
|
+
const buf = await readFile(join(dir, name));
|
|
2571
|
+
if (buf.length === 0) throw new Error(`codex: ${name} is empty`);
|
|
2572
|
+
hashes.push(saveImage(buf));
|
|
2515
2573
|
}
|
|
2516
2574
|
return hashes;
|
|
2517
2575
|
}
|
|
@@ -2558,6 +2616,11 @@ function createCodexEngine(opts) {
|
|
|
2558
2616
|
* attached — and on an edit the first one attached is `input.png`, the
|
|
2559
2617
|
* shot being edited. A refine carrying a full identity payload was
|
|
2560
2618
|
* silently editing nothing at all.
|
|
2619
|
+
*
|
|
2620
|
+
* That same backwards walk is why every reference is bound to its role
|
|
2621
|
+
* by FILENAME (refFileNames) on both paths: the tool decides which
|
|
2622
|
+
* pictures it surfaces and in what order, so an ordinal "Attached
|
|
2623
|
+
* image N" binding pointed identity claims at the wrong picture.
|
|
2561
2624
|
*/
|
|
2562
2625
|
maxReferenceImages: 5,
|
|
2563
2626
|
/*
|
|
@@ -2589,8 +2652,9 @@ function createCodexEngine(opts) {
|
|
|
2589
2652
|
(_, i) => async () => withWorkDir(async (dir) => {
|
|
2590
2653
|
const args = execArgs(dir);
|
|
2591
2654
|
let refBytes = 0;
|
|
2655
|
+
const names = refFileNames(roles, refs.length);
|
|
2592
2656
|
for (const [idx, ref] of refs.entries()) {
|
|
2593
|
-
const dest = join(dir,
|
|
2657
|
+
const dest = join(dir, names[idx]);
|
|
2594
2658
|
await copyFile(ref, dest);
|
|
2595
2659
|
refBytes += (await stat(dest)).size;
|
|
2596
2660
|
args.splice(args.length - 1, 0, `--image=${dest}`);
|
|
@@ -2666,9 +2730,15 @@ function createCodexEngine(opts) {
|
|
|
2666
2730
|
await copyFile(editRefs[i], join(dir, name));
|
|
2667
2731
|
refLines.push(`${name} shows ${EDIT_REFERENCE_ROLE_DIRECTIVE[role]}`);
|
|
2668
2732
|
}
|
|
2669
|
-
const promptText = `Edit input.png using your image generation/editing tool: ${req.instruction}.` + (refLines.length ? ` ${refLines.join(". ")}.` : "") +
|
|
2670
|
-
//
|
|
2671
|
-
|
|
2733
|
+
const promptText = `Edit input.png using your image generation/editing tool: ${req.instruction}.` + (refLines.length ? ` ${refLines.join(". ")}.` : "") + // The old tail licensed "the commands needed to save and resize it"
|
|
2734
|
+
// and then asked for exactly WxH pixels - which the model honoured
|
|
2735
|
+
// with sips -z, a force-fit of BOTH axes. Every refine hop was a
|
|
2736
|
+
// cheap-kernel shell resample of freshly generated pixels, and when
|
|
2737
|
+
// the tool had drifted the shape it was a shear: the reported
|
|
2738
|
+
// crushed faces and the deep-chain mush. The server's own canvas
|
|
2739
|
+
// pass (enforceEditCanvas) owns size now, with one uniform lanczos
|
|
2740
|
+
// only when actually needed.
|
|
2741
|
+
(req.width && req.height ? ` Keep the edited frame at input.png's own ${ratioLabel(req.width, req.height)} shape, ${codexNativeSize(req.width, req.height).width}x${codexNativeSize(req.width, req.height).height}.` : "") + ` Do not browse the web or explore files. Save the tool's output in the current directory as out-1.png, byte-for-byte unchanged: you may run the commands needed to copy or move the file, but never resize, scale, stretch, pad, crop or re-encode it \u2014 deliver the tool's own pixels at the tool's own size. Nothing else.`;
|
|
2672
2742
|
const args = execArgs(dir);
|
|
2673
2743
|
for (const name of ["input.png", ...refLines.map((_, i) => `${editRoles[i] ?? "reference"}-${i + 1}.png`)]) {
|
|
2674
2744
|
args.splice(args.length - 1, 0, `--image=${join(dir, name)}`);
|
|
@@ -2692,16 +2762,29 @@ function createCodexEngine(opts) {
|
|
|
2692
2762
|
}
|
|
2693
2763
|
function buildPrompt2(req, index, roles) {
|
|
2694
2764
|
const roleDirective = REFERENCE_ROLE_DIRECTIVE;
|
|
2695
|
-
const
|
|
2696
|
-
|
|
2697
|
-
|
|
2765
|
+
const names = refFileNames(roles, roles.length);
|
|
2766
|
+
const refDirectives = roles.map((role, i) => `${names[i]} shows ${roleDirective[role]}.`).join(" ");
|
|
2767
|
+
const count = Math.max(1, req.count);
|
|
2768
|
+
const native = codexNativeSize(req.width, req.height);
|
|
2698
2769
|
return (
|
|
2699
2770
|
// "professional-grade", not "flawless": the audit of the waxy-presenter
|
|
2700
2771
|
// report traced part of the plastic, over-perfected rendering to that
|
|
2701
2772
|
// one unconditional word. Still "image", never "photograph" - this
|
|
2702
2773
|
// wrapper also generates graphic assets. Independently revertible on
|
|
2703
2774
|
// render evidence.
|
|
2704
|
-
`Generate one professional-grade image immediately using your image generation tool, ${
|
|
2775
|
+
`Generate one professional-grade image immediately using your image generation tool, composed as a ${native.width}x${native.height} frame (${ratioLabel(req.width, req.height)} ${orientationOf(req.width, req.height)}): ${req.prompt}.` + (refDirectives ? ` ${refDirectives}` : "") + // The save instruction bans what the old one licensed. "you may run the
|
|
2776
|
+
// commands needed to save and resize it" invited sips -z, which
|
|
2777
|
+
// force-fits BOTH axes: the model drew at one shape, sheared the pixels
|
|
2778
|
+
// to the requested one, and the aspect check passed BECAUSE of the shear
|
|
2779
|
+
// - the reported crushed faces. Copy/move stays licensed because the
|
|
2780
|
+
// win32 recovery path moves files out of generated_images.
|
|
2781
|
+
` Do not browse the web or explore files. Save the tool's output in the current directory as out-1.png, byte-for-byte unchanged: you may run the commands needed to copy or move the file, but never resize, scale, stretch, pad, crop or re-encode it \u2014 deliver the tool's own pixels at the tool's own size. Nothing else.` + // Every take in a batch gets the SAME-shaped clause. Take 1 used to get
|
|
2782
|
+
// nothing - so the first output was literally asked for the most
|
|
2783
|
+
// reference-faithful decode - and later takes were licensed to a
|
|
2784
|
+
// "different composition", which read as permission to drift from the
|
|
2785
|
+
// directives. Reported as: output #1 copies the scene reference, output
|
|
2786
|
+
// #2 mixes identities. A single generation stays byte-stable.
|
|
2787
|
+
(count > 1 ? ` (take ${index + 1} of ${count} \u2014 same brief, same identities and constraints, a naturally different moment and framing of the same shoot)` : "")
|
|
2705
2788
|
);
|
|
2706
2789
|
}
|
|
2707
2790
|
}
|
|
@@ -6442,28 +6525,6 @@ Continue: the same surface, the same light direction, the same colour temperatur
|
|
|
6442
6525
|
Constraints: change only the blurred margin; keep the sharp photograph unchanged in position, scale and content.
|
|
6443
6526
|
Avoid: new objects, products, people, text or watermarks.` + own;
|
|
6444
6527
|
}
|
|
6445
|
-
var NAMED_RATIOS = [
|
|
6446
|
-
["1:1", 1],
|
|
6447
|
-
["4:5", 4 / 5],
|
|
6448
|
-
["5:4", 5 / 4],
|
|
6449
|
-
["2:3", 2 / 3],
|
|
6450
|
-
["3:2", 3 / 2],
|
|
6451
|
-
["3:4", 3 / 4],
|
|
6452
|
-
["4:3", 4 / 3],
|
|
6453
|
-
["9:16", 9 / 16],
|
|
6454
|
-
["16:9", 16 / 9],
|
|
6455
|
-
["2:1", 2],
|
|
6456
|
-
["1:2", 0.5]
|
|
6457
|
-
];
|
|
6458
|
-
function ratioLabel(width, height) {
|
|
6459
|
-
const ratio = width / height;
|
|
6460
|
-
for (const [label, value] of NAMED_RATIOS) {
|
|
6461
|
-
if (Math.abs(ratio - value) / value < 0.02) return label;
|
|
6462
|
-
}
|
|
6463
|
-
const gcd = (a, b) => b ? gcd(b, a % b) : a;
|
|
6464
|
-
const d = gcd(width, height) || 1;
|
|
6465
|
-
return `${Math.round(width / d)}:${Math.round(height / d)}`;
|
|
6466
|
-
}
|
|
6467
6528
|
function reframeInstruction(plan, source, direction) {
|
|
6468
6529
|
const wider = plan.axis === "width";
|
|
6469
6530
|
const where = wider ? "to the left and to the right" : "above and below";
|
|
@@ -8164,6 +8225,31 @@ function registerImageRoutes(app, deps) {
|
|
|
8164
8225
|
|
|
8165
8226
|
// src/release/notes.data.ts
|
|
8166
8227
|
var RELEASES = [
|
|
8228
|
+
{
|
|
8229
|
+
version: "0.6.11",
|
|
8230
|
+
date: "2026-08-29",
|
|
8231
|
+
sections: [
|
|
8232
|
+
{
|
|
8233
|
+
heading: "Create",
|
|
8234
|
+
body: "Hitting your Codex plan's usage limit now says so in plain words, with the time it comes back, instead of a technical session listing."
|
|
8235
|
+
}
|
|
8236
|
+
]
|
|
8237
|
+
},
|
|
8238
|
+
{
|
|
8239
|
+
version: "0.6.10",
|
|
8240
|
+
date: "2026-08-29",
|
|
8241
|
+
title: "Pictures keep their shape.",
|
|
8242
|
+
sections: [
|
|
8243
|
+
{
|
|
8244
|
+
heading: "Create",
|
|
8245
|
+
body: "Images can no longer come back crushed or stretched: Scenri now works with the exact frame sizes the Codex image tool actually produces, and an answer that drifts off the requested shape is trimmed to it rather than distorted or refused. The wave of failed shots saying the engine could not produce the requested aspect ratio is gone with it, and refining an image over and over keeps its full sharpness at every step."
|
|
8246
|
+
},
|
|
8247
|
+
{
|
|
8248
|
+
heading: "Scenes",
|
|
8249
|
+
body: "When a scene built from photographs of one person is used with a chosen presenter, every output now shows the chosen presenter. Each reference image travels with a name that says what it is, so a scene photograph can lend its world and its styling without lending anyone a face, and asking for several variations gives every variation the same instructions rather than letting the first one copy the reference."
|
|
8250
|
+
}
|
|
8251
|
+
]
|
|
8252
|
+
},
|
|
8167
8253
|
{
|
|
8168
8254
|
version: "0.6.9",
|
|
8169
8255
|
date: "2026-08-29",
|
|
@@ -9316,10 +9402,51 @@ function buildServer(opts) {
|
|
|
9316
9402
|
const out = [];
|
|
9317
9403
|
for (const h of images) {
|
|
9318
9404
|
const buf = core.images.read(h);
|
|
9319
|
-
|
|
9405
|
+
const meta2 = await sharp7(buf).metadata().catch(() => null);
|
|
9406
|
+
if (!meta2?.width || !meta2.height) throw new Error("engine returned an undecodable image");
|
|
9407
|
+
const oriented = (meta2.orientation ?? 1) !== 1;
|
|
9408
|
+
out.push(
|
|
9409
|
+
buf.subarray(0, 8).equals(PNG_SIG) && !oriented ? h : core.images.save(await sharp7(buf).rotate().png().toBuffer())
|
|
9410
|
+
);
|
|
9320
9411
|
}
|
|
9321
9412
|
return out;
|
|
9322
9413
|
}
|
|
9414
|
+
const CROPPABLE_DRIFT = 0.35;
|
|
9415
|
+
function conformToCanvas(nodeId, want) {
|
|
9416
|
+
return async (images) => {
|
|
9417
|
+
const target = want.width / want.height;
|
|
9418
|
+
const out = [];
|
|
9419
|
+
for (const h of images) {
|
|
9420
|
+
const buf = core.images.read(h);
|
|
9421
|
+
const meta2 = await sharp7(buf).metadata();
|
|
9422
|
+
if (!meta2.width || !meta2.height) {
|
|
9423
|
+
out.push(h);
|
|
9424
|
+
continue;
|
|
9425
|
+
}
|
|
9426
|
+
const got = meta2.width / meta2.height;
|
|
9427
|
+
const drift = Math.abs(got - target) / target;
|
|
9428
|
+
if (drift <= SAME_SHAPE_TOL || drift > CROPPABLE_DRIFT) {
|
|
9429
|
+
out.push(h);
|
|
9430
|
+
continue;
|
|
9431
|
+
}
|
|
9432
|
+
const w = got > target ? Math.round(meta2.height * target) : meta2.width;
|
|
9433
|
+
const hpx = got > target ? meta2.height : Math.round(meta2.width / target);
|
|
9434
|
+
const cropped = await sharp7(buf).resize(w, hpx, { fit: "cover", position: "attention" }).png().toBuffer();
|
|
9435
|
+
app.log.info(
|
|
9436
|
+
{ nodeId, got: `${meta2.width}x${meta2.height}`, want: `${w}x${hpx}` },
|
|
9437
|
+
"canvas: cropped a drifted frame to the asked ratio"
|
|
9438
|
+
);
|
|
9439
|
+
out.push(core.images.save(cropped));
|
|
9440
|
+
try {
|
|
9441
|
+
const fresh = core.store.getNode(nodeId);
|
|
9442
|
+
const b = fresh?.brief ?? {};
|
|
9443
|
+
core.store.setBrief(nodeId, { ...b, croppedFrom: [meta2.width, meta2.height] });
|
|
9444
|
+
} catch {
|
|
9445
|
+
}
|
|
9446
|
+
}
|
|
9447
|
+
return out;
|
|
9448
|
+
};
|
|
9449
|
+
}
|
|
9323
9450
|
async function assertAspect(images, expect) {
|
|
9324
9451
|
const want = expect.width / expect.height;
|
|
9325
9452
|
for (const h of images) {
|
|
@@ -9363,7 +9490,13 @@ function buildServer(opts) {
|
|
|
9363
9490
|
const brief = node.brief ?? {};
|
|
9364
9491
|
const raw = result.raw;
|
|
9365
9492
|
const survivors = typeof raw?.requested === "number" && Array.isArray(raw.variantIndexes) ? { requested: raw.requested, variantIndexes: raw.variantIndexes } : {};
|
|
9366
|
-
|
|
9493
|
+
const asked = expect ? { requestedSize: [expect.width, expect.height] } : {};
|
|
9494
|
+
core.store.setBrief(nodeId, { ...brief, rendered: { sizes, ...survivors, ...asked } });
|
|
9495
|
+
if (node.kind === "generation" && engineId === "codex-cli" && expect && expect.width !== expect.height && sizes.some(([w, h]) => w === expect.width && h === expect.height))
|
|
9496
|
+
app.log.warn(
|
|
9497
|
+
{ nodeId },
|
|
9498
|
+
"codex delivered exactly the requested pixels; its image tool cannot pin size - suggests a forbidden shell resize"
|
|
9499
|
+
);
|
|
9367
9500
|
}
|
|
9368
9501
|
} catch {
|
|
9369
9502
|
}
|
|
@@ -9760,7 +9893,12 @@ function buildServer(opts) {
|
|
|
9760
9893
|
try {
|
|
9761
9894
|
const fresh = core.store.getNode(node.id);
|
|
9762
9895
|
const b = fresh?.brief ?? {};
|
|
9763
|
-
core.store.
|
|
9896
|
+
const parentBrief = core.store.getNode(resolvedParentId)?.brief ?? {};
|
|
9897
|
+
core.store.setBrief(node.id, {
|
|
9898
|
+
...b,
|
|
9899
|
+
resizedFrom: [got.width, got.height],
|
|
9900
|
+
resampledHops: (parentBrief.resampledHops ?? 0) + 1
|
|
9901
|
+
});
|
|
9764
9902
|
} catch {
|
|
9765
9903
|
}
|
|
9766
9904
|
} else out.push(h);
|
|
@@ -9805,9 +9943,11 @@ function buildServer(opts) {
|
|
|
9805
9943
|
out.push(outcome === "composited" ? core.images.save(image) : h);
|
|
9806
9944
|
}
|
|
9807
9945
|
staged = out;
|
|
9946
|
+
} else if (expectShape) {
|
|
9947
|
+
staged = await conformToCanvas(node.id, expectShape)(staged);
|
|
9808
9948
|
}
|
|
9809
9949
|
return enforceEditCanvas(staged);
|
|
9810
|
-
} : void 0;
|
|
9950
|
+
} : kind === "generation" && compiled2?.width && compiled2?.height ? conformToCanvas(node.id, { width: compiled2.width, height: compiled2.height }) : void 0;
|
|
9811
9951
|
const nodeBudgetMs = kind === "generation" && runEngine.capabilities().id === "codex-cli" ? codexNodeBudgetMs(Math.min(Math.max(1, Number(count)), 8)) : void 0;
|
|
9812
9952
|
void runNode(node.id, runEngine, estimate, work, expectShape, post, nodeBudgetMs).catch(
|
|
9813
9953
|
(err) => app.log.error({ err }, "node run failed")
|