Add durable Live variant planning

This commit is contained in:
Paul Bakaus
2026-07-13 11:22:26 -07:00
parent 49bff8da75
commit eedccaedb0
6 changed files with 180 additions and 10 deletions
+90 -2
View File
@@ -8,6 +8,35 @@ import {
import { createLiveSessionStore } from './session-store.mjs';
export const CODEX_WORKER_OWNER = 'impeccable-live-codex-worker-v1';
const VARIANT_PLAN_SCHEMA = Object.freeze({
type: 'object',
properties: {
identityLock: {
type: 'array',
minItems: 1,
maxItems: 8,
items: { type: 'string', minLength: 1, maxLength: 240 },
},
directions: {
type: 'array',
minItems: 1,
maxItems: 6,
items: {
type: 'object',
properties: {
variantId: { type: 'integer', minimum: 1, maximum: 6 },
name: { type: 'string', minLength: 1, maxLength: 80 },
axis: { type: 'string', minLength: 1, maxLength: 120 },
intent: { type: 'string', minLength: 1, maxLength: 300 },
},
required: ['variantId', 'name', 'axis', 'intent'],
additionalProperties: false,
},
},
},
required: ['identityLock', 'directions'],
additionalProperties: false,
});
export const CODEX_WORKER_OUTPUT_SCHEMA = Object.freeze({
type: 'object',
properties: {
@@ -29,6 +58,17 @@ export const CODEX_WORKER_OUTPUT_SCHEMA = Object.freeze({
additionalProperties: false,
});
export function codexWorkerOutputSchemaForPhase(phase, expectedVariants = 3) {
const requirePlan = Number(expectedVariants) > 1 && (phase === 'first' || phase === 'atomic');
return {
...CODEX_WORKER_OUTPUT_SCHEMA,
properties: requirePlan
? { ...CODEX_WORKER_OUTPUT_SCHEMA.properties, plan: VARIANT_PLAN_SCHEMA }
: CODEX_WORKER_OUTPUT_SCHEMA.properties,
required: requirePlan ? ['files', 'plan'] : ['files'],
};
}
export function resolveCodexWorkerConfig({ env = process.env, liveConfig = {} } = {}) {
const configured = liveConfig.experimentalCodexWorker || liveConfig.codexWorker || {};
const envEnabled = parseBoolean(env.IMPECCABLE_LIVE_CODEX_WORKER);
@@ -87,6 +127,7 @@ export function buildGenerationTurnInput({
phase,
prepared,
artifact,
variantPlan,
product,
design,
actionReference,
@@ -96,24 +137,35 @@ export function buildGenerationTurnInput({
const count = Number(event.count || 3);
const first = phase === 'first';
const component = Boolean(prepared.previewMode);
const actionRules = event.action === 'bolder' && count > 1
? [
'For /bolder, keep variant 1 low-risk: preserve the selected roots high-level layout and create impact through controlled hierarchy, proportion, or rhythm. Reserve root recomposition for variant 2 or 3.',
'At least one later direction must recompose the selected root or materially change the spatial relationship among its children. The set must not merely restyle the same descendant three ways.',
'Color alone is not a sufficient primary axis for /bolder; pair any palette shift with a meaningful hierarchy, proportion, rhythm, or composition change.',
]
: [];
const phaseRules = first
? [
'Produce only variant 1 now so it can be reviewed immediately.',
'Variant 1 must be the strongest low-risk, independently shippable interpretation of the request; reserve more experimental directions for later variants.',
`Before authoring, define the shared identity lock and exactly ${count} distinct, meaningful design axes. Return them in plan.directions ordered by variantId so the final phase can complete the same coherent set.`,
'Defer tunable parameters: params must be absent or empty for this phase.',
]
: phase === 'final'
? [
`Complete variants 2 through ${count} and the final parameter manifest.`,
'Variant 1 is already visible and immutable. Do not return or alter its file, markup, or CSS.',
'Follow the durable variant plan below. Preserve its identity lock and implement each remaining named axis instead of improvising a new set.',
]
: [
`Produce the complete set of ${count} variants and final parameters atomically.`,
`Before authoring, define the shared identity lock and exactly ${count} distinct, meaningful design axes and return them in plan.directions ordered by variantId.`,
];
return [
`LIVE GENERATION PHASE: ${phase}`,
...phaseRules,
...actionRules,
component
? `Return staged component files relative to componentDir. Allowed variant extension: .${artifact.componentExtension}. The supervisor updates manifest.json.`
: `Return exactly one file whose path is ${JSON.stringify(prepared.artifactFile)} and whose content is the complete staged source artifact.`,
@@ -124,6 +176,9 @@ export function buildGenerationTurnInput({
'<event>',
JSON.stringify(sanitizeEvent(event), null, 2),
'</event>',
'<variant_plan>',
JSON.stringify(variantPlan || null, null, 2),
'</variant_plan>',
'',
'<product_context>',
String(product || ''),
@@ -221,6 +276,9 @@ export function applyCodexWorkerOutput({
totalBytes += Buffer.byteLength(file.content);
}
if (totalBytes > maxBytes) throw workerError('worker_output_too_large');
const requirePlan = Number(expectedVariants) > 1 && (phase === 'first' || phase === 'atomic');
if (requirePlan && !parsed.plan) throw workerError('worker_output_plan_missing');
const plan = parsed.plan ? normalizeVariantPlan(parsed.plan, expectedVariants) : null;
if (!prepared.previewMode) {
if (parsed.files.length !== 1 || parsed.files[0].path !== prepared.artifactFile) {
@@ -229,7 +287,7 @@ export function applyCodexWorkerOutput({
const artifactPath = resolveInside(cwd, prepared.artifactFile);
if (!artifactPath) throw workerError('artifact_path_outside_project');
fs.writeFileSync(artifactPath, parsed.files[0].content, 'utf-8');
return { files: [prepared.artifactFile] };
return { files: [prepared.artifactFile], plan };
}
const componentDir = resolveInside(cwd, prepared.componentDir);
@@ -267,7 +325,37 @@ export function applyCodexWorkerOutput({
}
manifest.arrivedVariants = phase === 'first' ? 1 : expectedVariants;
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
return { files: [...seen] };
return { files: [...seen], plan };
}
function normalizeVariantPlan(plan, expectedVariants) {
if (!plan || typeof plan !== 'object' || Array.isArray(plan)) {
throw workerError('worker_output_plan_invalid');
}
const identityLock = Array.isArray(plan.identityLock)
? plan.identityLock.map((item) => String(item || '').trim()).filter(Boolean)
: [];
const directions = Array.isArray(plan.directions) ? plan.directions : [];
if (identityLock.length < 1 || identityLock.length > 8 || directions.length !== Number(expectedVariants)) {
throw workerError('worker_output_plan_invalid');
}
const normalizedDirections = directions.map((direction) => ({
variantId: Number(direction?.variantId),
name: String(direction?.name || '').trim(),
axis: String(direction?.axis || '').trim(),
intent: String(direction?.intent || '').trim(),
}));
const expectedIds = Array.from({ length: Number(expectedVariants) }, (_, index) => index + 1);
const sortedIds = normalizedDirections.map((direction) => direction.variantId).sort((a, b) => a - b);
if (normalizedDirections.some((direction) => (
!Number.isInteger(direction.variantId)
|| !direction.name
|| !direction.axis
|| !direction.intent
)) || sortedIds.some((id, index) => id !== expectedIds[index])) {
throw workerError('worker_output_plan_invalid');
}
return { identityLock, directions: normalizedDirections };
}
export function prepareCodexWorkerPhase({ id, sourceFile, cwd = process.cwd() }) {