|
@@ -1,13 +1,33 @@
|
|
|
import { spawn } from "node:child_process";
|
|
import { spawn } from "node:child_process";
|
|
|
-import { readFile, readdir } from "node:fs/promises";
|
|
|
|
|
-import { dirname, join, resolve } from "node:path";
|
|
|
|
|
|
|
+import { copyFile, readFile, readdir, writeFile } from "node:fs/promises";
|
|
|
|
|
+import { basename, dirname, join, resolve } from "node:path";
|
|
|
import { fileURLToPath } from "node:url";
|
|
import { fileURLToPath } from "node:url";
|
|
|
import { inspectLatestClaudeInvocation } from "../../providers/claude-attachment-client.mjs";
|
|
import { inspectLatestClaudeInvocation } from "../../providers/claude-attachment-client.mjs";
|
|
|
import { createRun, pathsForRun } from "../../core/run-paths.mjs";
|
|
import { createRun, pathsForRun } from "../../core/run-paths.mjs";
|
|
|
|
|
+import { runDesignedAsset } from "../designed-asset/designed-asset-runner.mjs";
|
|
|
|
|
+import { compileProductionPlan } from "./production-plan-compiler.mjs";
|
|
|
|
|
+import {
|
|
|
|
|
+ compileReferenceAssetPlan,
|
|
|
|
|
+ compileReferenceFirstSetPlan,
|
|
|
|
|
+} from "./reference-asset-plan-compiler.mjs";
|
|
|
|
|
+import { planReferenceFirstCreative } from "./reference-first-creative-planner.mjs";
|
|
|
|
|
+import { writeReferenceFirstCopyPlan } from "./reference-first-copy-plan.mjs";
|
|
|
|
|
+import { detectReferenceFirstLayoutRegions } from "./reference-first-layout-region-detector.mjs";
|
|
|
|
|
+import { runReferenceFirstDesignPostprocessor } from "./reference-first-design-postprocessor.mjs";
|
|
|
|
|
+import { compileProductionOutcome } from "./production-outcome-compiler.mjs";
|
|
|
|
|
+import { runProductionDesignBridge } from "./production-design-bridge.mjs";
|
|
|
|
|
+import { verifyProductionSetQuality } from "./set-quality-verifier.mjs";
|
|
|
|
|
+import { sha256 } from "../../core/run-state-integrity.mjs";
|
|
|
|
|
+import { observeGeneratedReferences, observeOriginalVisual } from "./reference-first-visual-observer.mjs";
|
|
|
|
|
+import { assembleClaudeAuthoredPromptPlan, contractSha256, validateClaudeAuthoredSetPlan, validateClaudeReferenceAuthoring, V3_WORKFLOW } from "./reference-first-v3-contracts.mjs";
|
|
|
|
|
+import { writeSettingBoardCopyPlan } from "./setting-board-copy-plan.mjs";
|
|
|
|
|
+import { renderAnnotatedSettingBoard } from "./setting-board-annotated-renderer.mjs";
|
|
|
|
|
|
|
|
const scriptsDirectory = dirname(fileURLToPath(import.meta.url));
|
|
const scriptsDirectory = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
+const packageRoot = resolve(scriptsDirectory, "..", "..", "..", "..");
|
|
|
const productVisionFile = resolve(scriptsDirectory, "product-vision-client.mjs");
|
|
const productVisionFile = resolve(scriptsDirectory, "product-vision-client.mjs");
|
|
|
const jimengClientFile = resolve(scriptsDirectory, "..", "..", "providers", "jimeng-v4-client.mjs");
|
|
const jimengClientFile = resolve(scriptsDirectory, "..", "..", "providers", "jimeng-v4-client.mjs");
|
|
|
|
|
+const validatePlanFile = resolve(scriptsDirectory, "validate-plan.mjs");
|
|
|
const args = process.argv.slice(2);
|
|
const args = process.argv.slice(2);
|
|
|
const command = args[0];
|
|
const command = args[0];
|
|
|
const option = (name, fallback = "") => {
|
|
const option = (name, fallback = "") => {
|
|
@@ -15,6 +35,214 @@ const option = (name, fallback = "") => {
|
|
|
return index >= 0 ? args[index + 1] : fallback;
|
|
return index >= 0 ? args[index + 1] : fallback;
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+function inferredWorkspaceRoot() {
|
|
|
|
|
+ const packageParent = dirname(packageRoot);
|
|
|
|
|
+ const container = dirname(packageParent);
|
|
|
|
|
+ if (
|
|
|
|
|
+ basename(packageParent).toLowerCase() === "skills"
|
|
|
|
|
+ && new Set([".claude", ".codex"]).has(basename(container).toLowerCase())
|
|
|
|
|
+ ) {
|
|
|
|
|
+ return dirname(container);
|
|
|
|
|
+ }
|
|
|
|
|
+ return packageParent;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function resolveAttachmentCwd({ explicitCwd = "", env = process.env } = {}) {
|
|
|
|
|
+ return resolve(
|
|
|
|
|
+ explicitCwd
|
|
|
|
|
+ || env.CLAUDE_PROJECT_DIR
|
|
|
|
|
+ || env.FMODE_IMAGE_SET_PROJECT_ROOT
|
|
|
|
|
+ || inferredWorkspaceRoot(),
|
|
|
|
|
+ );
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function resolveStartInvocation({
|
|
|
|
|
+ explicitImage = "",
|
|
|
|
|
+ preferCurrentAttachment = false,
|
|
|
|
|
+ cwd,
|
|
|
|
|
+ claudeConfigDirectory = "",
|
|
|
|
|
+ sessionId = "",
|
|
|
|
|
+ inspectInvocation = inspectLatestClaudeInvocation,
|
|
|
|
|
+} = {}) {
|
|
|
|
|
+ if (explicitImage && !preferCurrentAttachment) return null;
|
|
|
|
|
+ let invocation;
|
|
|
|
|
+ try {
|
|
|
|
|
+ invocation = await inspectInvocation({ cwd, claudeConfigDirectory, sessionId });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (explicitImage && new Set(["CLAUDE_ATTACHMENT_NOT_FOUND", "CLAUDE_SESSION_NOT_FOUND", "CLAUDE_ATTACHMENT_EXPIRED"]).has(error?.code)) return null;
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (invocation.image_count === 0 && explicitImage) return null;
|
|
|
|
|
+ if (invocation.image_count !== 1) {
|
|
|
|
|
+ throw new Error(`CURRENT_REQUEST_IMAGE_COUNT_INVALID: expected one image, found ${invocation.image_count}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ return { ...invocation, explicit_image_ignored: Boolean(explicitImage) };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildInputProvenance({ invocation = null, explicitImage = "", vision = null } = {}) {
|
|
|
|
|
+ return invocation
|
|
|
|
|
+ ? {
|
|
|
|
|
+ mode: "current_vscode_claude_attachment",
|
|
|
|
|
+ prompt_id: invocation.prompt_id,
|
|
|
|
|
+ session_id: invocation.session_id,
|
|
|
|
|
+ image_count: invocation.image_count,
|
|
|
|
|
+ invocation_mode: invocation.invocation_mode,
|
|
|
|
|
+ explicit_image_ignored: Boolean(invocation.explicit_image_ignored),
|
|
|
|
|
+ sha256: vision?.reference?.attachment?.sha256 || null,
|
|
|
|
|
+ }
|
|
|
|
|
+ : {
|
|
|
|
|
+ mode: "explicit_user_path_or_url",
|
|
|
|
|
+ image_count: explicitImage ? 1 : 0,
|
|
|
|
|
+ explicit_image_ignored: false,
|
|
|
|
|
+ sha256: null,
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildVisionArguments({ invocation = null, explicitImage = "", outputFile, attachmentCwd, claudeConfigDirectory = "", sessionId = "" } = {}) {
|
|
|
|
|
+ if (invocation) {
|
|
|
|
|
+ return [
|
|
|
|
|
+ "--from-claude-attachment",
|
|
|
|
|
+ "--output", outputFile,
|
|
|
|
|
+ "--attachment-cwd", attachmentCwd,
|
|
|
|
|
+ ...(claudeConfigDirectory ? ["--claude-config-dir", claudeConfigDirectory] : []),
|
|
|
|
|
+ ...(sessionId ? ["--session-id", sessionId] : []),
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!explicitImage) throw new Error("START_IMAGE_SOURCE_REQUIRED");
|
|
|
|
|
+ return ["--image", explicitImage, "--output", outputFile];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildStartResult({
|
|
|
|
|
+ vision,
|
|
|
|
|
+ outputLevel,
|
|
|
|
|
+ paths,
|
|
|
|
|
+ designedAsset = null,
|
|
|
|
|
+ inputProvenance = null,
|
|
|
|
|
+ productionPlan = null,
|
|
|
|
|
+ planValidation = null,
|
|
|
|
|
+ preflightRecord = null,
|
|
|
|
|
+ executionAuthorization = null,
|
|
|
|
|
+}) {
|
|
|
|
|
+ if (vision?.status !== "complete") {
|
|
|
|
|
+ return {
|
|
|
|
|
+ status: "blocked",
|
|
|
|
|
+ output_level: outputLevel,
|
|
|
|
|
+ paths,
|
|
|
|
|
+ vision,
|
|
|
|
|
+ ...(inputProvenance ? { input_provenance: inputProvenance } : {}),
|
|
|
|
|
+ ...(designedAsset ? { designed_asset: designedAsset } : {}),
|
|
|
|
|
+ next_action: "stop_and_report_error",
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ if (outputLevel === "designed_asset") {
|
|
|
|
|
+ const complete = designedAsset?.status === "complete";
|
|
|
|
|
+ return {
|
|
|
|
|
+ status: complete ? "complete" : "blocked",
|
|
|
|
|
+ output_level: outputLevel,
|
|
|
|
|
+ paths,
|
|
|
|
|
+ vision,
|
|
|
|
|
+ ...(inputProvenance ? { input_provenance: inputProvenance } : {}),
|
|
|
|
|
+ ...(designedAsset ? { designed_asset: designedAsset } : {}),
|
|
|
|
|
+ next_action: complete
|
|
|
|
|
+ ? "stop_designed_asset_complete"
|
|
|
|
|
+ : designedAsset?.next_action || "stop_and_report_designed_asset_error",
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ if (productionPlan || planValidation) {
|
|
|
|
|
+ const valid = planValidation?.valid === true && productionPlan?.images?.length === 7;
|
|
|
|
|
+ const paidGenerationAuthorized = valid && executionAuthorization?.authorized === true;
|
|
|
|
|
+ return {
|
|
|
|
|
+ status: valid ? "waiting_for_confirmation" : "blocked",
|
|
|
|
|
+ output_level: outputLevel,
|
|
|
|
|
+ paths,
|
|
|
|
|
+ vision,
|
|
|
|
|
+ ...(inputProvenance ? { input_provenance: inputProvenance } : {}),
|
|
|
|
|
+ production: {
|
|
|
|
|
+ mode: "seven_image_vertical_slice",
|
|
|
|
|
+ plan_file: paths.prompt_plan,
|
|
|
|
|
+ schema_version: productionPlan?.schema_version || "0.6",
|
|
|
|
|
+ page_count: productionPlan?.images?.length || 0,
|
|
|
|
|
+ validation: planValidation || { valid: false, errors: ["production plan was not compiled"] },
|
|
|
|
|
+ estimated_cost_cny: 1.54,
|
|
|
|
|
+ max_cost_required_cny: 1.54,
|
|
|
|
|
+ paid_generation_authorized: paidGenerationAuthorized,
|
|
|
|
|
+ authorization_source: paidGenerationAuthorized ? executionAuthorization.source : null,
|
|
|
|
|
+ authorization_max_cost_cny: paidGenerationAuthorized ? executionAuthorization.max_cost_cny : null,
|
|
|
|
|
+ preflight_plan_sha256: preflightRecord?.plan_sha256 || null,
|
|
|
|
|
+ paid_external_calls: 0,
|
|
|
|
|
+ },
|
|
|
|
|
+ files: valid ? [paths.prompt_plan] : [],
|
|
|
|
|
+ next_action: valid
|
|
|
|
|
+ ? paidGenerationAuthorized
|
|
|
|
|
+ ? "execute_same_run_under_recorded_authorization"
|
|
|
|
|
+ : "review_plan_and_explicitly_authorize_execute"
|
|
|
|
|
+ : "stop_and_report_plan_validation_error",
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ status: "ready_for_plan",
|
|
|
|
|
+ output_level: outputLevel,
|
|
|
|
|
+ paths,
|
|
|
|
|
+ vision,
|
|
|
|
|
+ ...(inputProvenance ? { input_provenance: inputProvenance } : {}),
|
|
|
|
|
+ next_action: "write_prompt_plan_then_execute_same_run",
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function normalizeExecutionAuthorization(productionBrief = {}, planSha256 = "") {
|
|
|
|
|
+ const input = productionBrief?.authorization;
|
|
|
|
|
+ if (!input || input.mode !== "preauthorized") return null;
|
|
|
|
|
+ if (input.source !== "current_user_prompt") {
|
|
|
|
|
+ throw new Error("EXECUTION_AUTHORIZATION_SOURCE_INVALID: preauthorization must come from the current user prompt");
|
|
|
|
|
+ }
|
|
|
|
|
+ const maxCost = Number(input.maxCost ?? 1.54);
|
|
|
|
|
+ if (!Number.isFinite(maxCost) || maxCost < 1.54) {
|
|
|
|
|
+ throw new Error("EXECUTION_AUTHORIZATION_COST_LIMIT_INSUFFICIENT: expected at least 1.54");
|
|
|
|
|
+ }
|
|
|
|
|
+ return {
|
|
|
|
|
+ schema_version: "0.1",
|
|
|
|
|
+ authorized: true,
|
|
|
|
|
+ mode: "preauthorized",
|
|
|
|
|
+ source: input.source,
|
|
|
|
|
+ max_cost_cny: maxCost,
|
|
|
|
|
+ plan_sha256: planSha256,
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function readOptionalJson(filename) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ return JSON.parse(await readFile(filename, "utf8"));
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ if (error?.code === "ENOENT") return null;
|
|
|
|
|
+ throw error;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function assertPreflightPlanUnchanged(paths) {
|
|
|
|
|
+ const preflight = await readOptionalJson(paths.preflight_record);
|
|
|
|
|
+ if (!preflight) return null;
|
|
|
|
|
+ const currentPlanText = await readFile(paths.prompt_plan, "utf8");
|
|
|
|
|
+ if (preflight.run_id !== paths.run_id || preflight.plan_sha256 !== sha256(currentPlanText)) {
|
|
|
|
|
+ throw new Error("PROMPT_PLAN_CHANGED_AFTER_PREFLIGHT: compile and validate a new run instead of editing prompt-plan.json");
|
|
|
|
|
+ }
|
|
|
|
|
+ return preflight;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function isExecutorBlocked(summary) {
|
|
|
|
|
+ return new Set(["blocked", "error"]).has(summary?.status);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export function buildDesignedAssetFailure(error) {
|
|
|
|
|
+ const code = error?.code || (/^[A-Z][A-Z0-9_]+/.exec(error?.message || "")?.[0]) || "DESIGNED_ASSET_COMPOSITION_FAILED";
|
|
|
|
|
+ return {
|
|
|
|
|
+ status: "blocked",
|
|
|
|
|
+ error: { code, message: error?.message || String(error) },
|
|
|
|
|
+ next_action: code === "DESIGNED_ASSET_PRODUCT_NOT_VISIBLE"
|
|
|
|
|
+ ? "attach_one_clear_product_image_and_start_again"
|
|
|
|
|
+ : "keep_visual_base_and_review_reference_background",
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function runNode(scriptFile, childArgs) {
|
|
function runNode(scriptFile, childArgs) {
|
|
|
return new Promise((resolvePromise, reject) => {
|
|
return new Promise((resolvePromise, reject) => {
|
|
|
const child = spawn(process.execPath, [scriptFile, ...childArgs], {
|
|
const child = spawn(process.execPath, [scriptFile, ...childArgs], {
|
|
@@ -23,12 +251,16 @@ function runNode(scriptFile, childArgs) {
|
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
|
});
|
|
});
|
|
|
let stdout = "";
|
|
let stdout = "";
|
|
|
|
|
+ let stderr = "";
|
|
|
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
|
- child.stderr.on("data", (chunk) => { process.stderr.write(chunk); });
|
|
|
|
|
|
|
+ child.stderr.on("data", (chunk) => { stderr += chunk; process.stderr.write(chunk); });
|
|
|
child.on("error", reject);
|
|
child.on("error", reject);
|
|
|
child.on("close", (code) => {
|
|
child.on("close", (code) => {
|
|
|
if (code !== 0) {
|
|
if (code !== 0) {
|
|
|
- reject(new Error(`${scriptFile} exited with code ${code}`));
|
|
|
|
|
|
|
+ const error = new Error(`${scriptFile} exited with code ${code}`);
|
|
|
|
|
+ error.stdout = stdout;
|
|
|
|
|
+ error.stderr = stderr;
|
|
|
|
|
+ reject(error);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
resolvePromise(stdout);
|
|
resolvePromise(stdout);
|
|
@@ -36,6 +268,394 @@ function runNode(scriptFile, childArgs) {
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+function addCostRanges(...summaries) {
|
|
|
|
|
+ return summaries.reduce((total, summary) => ({
|
|
|
|
|
+ min: Number((total.min + Number(summary?.cost_range_cny?.min || summary?.confirmed_minimum_cost_cny || 0)).toFixed(2)),
|
|
|
|
|
+ max: Number((total.max + Number(summary?.cost_range_cny?.max
|
|
|
|
|
+ || (Number(summary?.confirmed_minimum_cost_cny || 0) + Number(summary?.potential_additional_cost_cny || 0)))).toFixed(2)),
|
|
|
|
|
+ }), { min: 0, max: 0 });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function auxiliaryModelUsage(...stages) {
|
|
|
|
|
+ const calls = stages.reduce((sum, stage) => sum + Number(stage?.model_calls || 0), 0);
|
|
|
|
|
+ const unitCost = Number(process.env.COMMERCE_VISION_COST_PER_CALL_CNY);
|
|
|
|
|
+ return {
|
|
|
|
|
+ calls,
|
|
|
|
|
+ usage: stages.map((stage) => stage?.usage || null),
|
|
|
|
|
+ cost_cny: Number.isFinite(unitCost) && unitCost >= 0 ? Number((calls * unitCost).toFixed(4)) : null,
|
|
|
|
|
+ pricing_status: Number.isFinite(unitCost) && unitCost >= 0 ? "configured_estimate" : "usage_recorded_price_not_configured",
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function writeReferenceFirstResult(paths, result) {
|
|
|
|
|
+ await writeFile(paths.reference_first_result, `${JSON.stringify(result, null, 2)}\n`, "utf8");
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+async function writeV3State(paths, state) {
|
|
|
|
|
+ const record = { schema_version: "1.0", workflow: V3_WORKFLOW, run_id: paths.run_id, ...state, updated_at: new Date().toISOString() };
|
|
|
|
|
+ await writeFile(paths.v3_stage_state, `${JSON.stringify(record, null, 2)}\n`, "utf8");
|
|
|
|
|
+ return record;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function generatedInputs(summary, localImages) {
|
|
|
|
|
+ const remote = Array.isArray(summary?.remote_images) ? summary.remote_images.filter(Boolean) : [];
|
|
|
|
|
+ return remote.length === localImages.length ? remote : localImages;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function providerLocalImages(record) {
|
|
|
|
|
+ const direct = Array.isArray(record?.local_images) ? record.local_images.filter(Boolean) : [];
|
|
|
|
|
+ if (direct.length) return direct;
|
|
|
|
|
+ return Array.isArray(record?.results) ? record.results.filter((item) => item.status === "complete").flatMap((item) => item.local_images || []).filter(Boolean) : [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function providerRemoteImages(record) {
|
|
|
|
|
+ const direct = Array.isArray(record?.remote_images) ? record.remote_images.filter(Boolean) : [];
|
|
|
|
|
+ if (direct.length) return direct;
|
|
|
|
|
+ return Array.isArray(record?.results) ? record.results.filter((item) => item.status === "complete").flatMap((item) => item.images || []).filter(Boolean) : [];
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function startClaudeAuthoredV3({ paths, originalReference, productionBrief = {}, referenceCapture = null, inputProvenance = null, observeOriginal = observeOriginalVisual } = {}) {
|
|
|
|
|
+ if (!paths?.run_id || !originalReference) throw new Error("CLAUDE_AUTHORED_V3_START_INPUT_REQUIRED");
|
|
|
|
|
+ const observationStage = await observeOriginal({ runId: paths.run_id, source: originalReference, outputFile: paths.original_visual_observation });
|
|
|
|
|
+ const observationSha = contractSha256(observationStage.observation);
|
|
|
|
|
+ const request = {
|
|
|
|
|
+ schema_version: "1.0", workflow: V3_WORKFLOW, run_id: paths.run_id, original_reference: originalReference,
|
|
|
|
|
+ production_brief: productionBrief, expected_paid_images: 9, estimated_cost_cny: 1.98,
|
|
|
|
|
+ direct_generation_authorization: { authorized: true, source: "current_user_prompt", max_cost_cny: 1.98 },
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeFile(paths.reference_first_request, `${JSON.stringify(request, null, 2)}\n`, "utf8");
|
|
|
|
|
+ if (referenceCapture) await writeFile(paths.reference_capture, `${JSON.stringify(referenceCapture, null, 2)}\n`, "utf8");
|
|
|
|
|
+ await writeV3State(paths, { stage: "ready_for_reference_authoring", original_observation_sha256: observationSha, paid_image_calls: 0, auxiliary_model_calls: observationStage.model_calls });
|
|
|
|
|
+ const result = {
|
|
|
|
|
+ status: "ready_for_reference_authoring", output_level: "visual_base", workflow: V3_WORKFLOW, paths,
|
|
|
|
|
+ original_visual_observation: observationStage.observation, original_visual_observation_sha256: observationSha,
|
|
|
|
|
+ reference_capture: referenceCapture, input_provenance: inputProvenance,
|
|
|
|
|
+ authorization: request.direct_generation_authorization,
|
|
|
|
|
+ cost: { confirmed_image_cost_cny: 0, next_stage_max_image_cost_cny: 0.44, total_authorized_max_cny: 1.98 },
|
|
|
|
|
+ files: [paths.original_visual_observation], next_action: "claude_author_reference_assets_then_generate_references",
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeReferenceFirstResult(paths, result);
|
|
|
|
|
+ return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function generateClaudeAuthoredReferences({ paths, referenceAuthoring, executeNode = runNode, observeReferences = observeGeneratedReferences, writeBoardCopy = writeSettingBoardCopyPlan, renderBoard = renderAnnotatedSettingBoard } = {}) {
|
|
|
|
|
+ const request = await readOptionalJson(paths?.reference_first_request);
|
|
|
|
|
+ const originalObservation = await readOptionalJson(paths?.original_visual_observation);
|
|
|
|
|
+ if (request?.workflow !== V3_WORKFLOW || !originalObservation) throw new Error("CLAUDE_AUTHORED_V3_START_REQUIRED");
|
|
|
|
|
+ const observationSha = contractSha256(originalObservation);
|
|
|
|
|
+ const authored = validateClaudeReferenceAuthoring(referenceAuthoring, { runId: paths.run_id, observationSha256: observationSha });
|
|
|
|
|
+ await writeFile(paths.claude_reference_authoring, `${JSON.stringify(authored, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const authoringSha = contractSha256(authored);
|
|
|
|
|
+ const referencePlan = compileReferenceAssetPlan({ runId: paths.run_id, originalReference: request.original_reference, settingBoardPrompt: authored.setting_board_visual_prompt, authoredBy: "claude", sourceAuthoringSha256: authoringSha });
|
|
|
|
|
+ await writeFile(paths.reference_asset_plan, `${JSON.stringify(referencePlan, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const referenceStdout = await executeNode(jimengClientFile, [paths.reference_asset_plan, "--execute", "--asset-map", paths.asset_map, "--max-cost", "0.44", "--output", paths.reference_asset_result]);
|
|
|
|
|
+ const referenceSummary = JSON.parse(referenceStdout);
|
|
|
|
|
+ const localImages = Array.isArray(referenceSummary.local_images) ? referenceSummary.local_images.filter(Boolean) : [];
|
|
|
|
|
+ if (referenceSummary.status !== "complete" || localImages.length !== 2) {
|
|
|
|
|
+ const costRange = addCostRanges(referenceSummary);
|
|
|
|
|
+ await writeV3State(paths, { stage: "reference_generation_partial", authoring_sha256: authoringSha, paid_image_calls: localImages.length, cost_range_cny: costRange });
|
|
|
|
|
+ return writeReferenceFirstResult(paths, { status: "partial", workflow: V3_WORKFLOW, output_level: "visual_base", paths, reference_assets: referenceSummary, files: localImages, local_images: localImages, cost: { stage_cost_range_cny: costRange, next_stage_max_image_cost_cny: Number(((2 - localImages.length) * 0.22).toFixed(2)) }, next_action: "resume_same_run_after_reference_generation_technical_failure" });
|
|
|
|
|
+ }
|
|
|
|
|
+ await copyFile(localImages[1], paths.setting_board_visual);
|
|
|
|
|
+ const copyPlan = await writeBoardCopy(authored.setting_board_spec, paths.setting_board_copy_plan);
|
|
|
|
|
+ const boardReport = await renderBoard({ sourceVisual: paths.setting_board_visual, copyPlan, outputFile: paths.setting_board_annotated, reportFile: paths.setting_board_layout_report });
|
|
|
|
|
+ const inputs = generatedInputs(referenceSummary, localImages);
|
|
|
|
|
+ const referenceObservationStage = await observeReferences({ runId: paths.run_id, sources: inputs, outputFile: paths.reference_visual_observation });
|
|
|
|
|
+ const referenceObservationSha = contractSha256(referenceObservationStage.observation);
|
|
|
|
|
+ await writeV3State(paths, { stage: "ready_for_set_authoring", authoring_sha256: authoringSha, reference_observation_sha256: referenceObservationSha, paid_image_calls: 2, confirmed_image_cost_cny: 0.44, auxiliary_model_calls: referenceObservationStage.model_calls });
|
|
|
|
|
+ const result = {
|
|
|
|
|
+ status: "ready_for_set_authoring", workflow: V3_WORKFLOW, output_level: "visual_base", paths,
|
|
|
|
|
+ reference_assets: { ...referenceSummary, local_images: localImages, generation_inputs: inputs },
|
|
|
|
|
+ reference_visual_observation: referenceObservationStage.observation, reference_visual_observation_sha256: referenceObservationSha,
|
|
|
|
|
+ setting_board: { visual_file: paths.setting_board_visual, annotated_file: paths.setting_board_annotated, copy_plan_file: paths.setting_board_copy_plan, layout_report_file: paths.setting_board_layout_report, report: boardReport },
|
|
|
|
|
+ cost: { confirmed_image_cost_cny: 0.44, next_stage_max_image_cost_cny: 1.54, total_authorized_max_cny: 1.98 },
|
|
|
|
|
+ files: [localImages[0], paths.setting_board_annotated, paths.reference_visual_observation], local_images: [localImages[0], paths.setting_board_annotated],
|
|
|
|
|
+ original_reference_used_for_set: false, next_action: "claude_author_seven_page_plan_then_generate_set",
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeReferenceFirstResult(paths, result); return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function v3CopyPlan(plan) {
|
|
|
|
|
+ return { schema_version: "1.0", run_id: plan.run_id, inferred_product_facts_forbidden: true, pages: plan.pages.map((page) => ({ slot_id: page.id, role: page.role, overlay_required: page.overlay_required, copy: page.copy, source_refs: page.source_refs })) };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+function v3Blueprint(plan) {
|
|
|
|
|
+ return { schema_version: "3.0", run_id: plan.run_id, platform_strategy: { resolved_profile: plan.platform.resolved_profile }, pages: plan.pages.map((page) => ({ id: page.id, role: page.role, template_id: page.template_id })) };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function generateClaudeAuthoredSet({ paths, setPlan, executeNode = runNode, detectLayout = detectReferenceFirstLayoutRegions, postprocessDesign = runReferenceFirstDesignPostprocessor } = {}) {
|
|
|
|
|
+ const request = await readOptionalJson(paths?.reference_first_request); const referenceObservation = await readOptionalJson(paths?.reference_visual_observation); const referenceResult = await readOptionalJson(paths?.reference_asset_result);
|
|
|
|
|
+ if (request?.workflow !== V3_WORKFLOW || !referenceObservation || !referenceResult) throw new Error("CLAUDE_AUTHORED_V3_REFERENCES_REQUIRED");
|
|
|
|
|
+ const referenceObservationSha = contractSha256(referenceObservation);
|
|
|
|
|
+ const authored = validateClaudeAuthoredSetPlan(setPlan, { runId: paths.run_id, referenceObservationSha256: referenceObservationSha });
|
|
|
|
|
+ await writeFile(paths.claude_authored_set_plan, `${JSON.stringify(authored, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const referenceLocal = providerLocalImages(referenceResult);
|
|
|
|
|
+ const referenceRemote = providerRemoteImages(referenceResult);
|
|
|
|
|
+ const referenceInputs = referenceRemote.length === 2 ? referenceRemote : referenceLocal;
|
|
|
|
|
+ if (referenceInputs.length !== 2) throw new Error("CLAUDE_AUTHORED_V3_TWO_REFERENCE_ASSETS_REQUIRED");
|
|
|
|
|
+ const promptPlan = assembleClaudeAuthoredPromptPlan({ plan: authored, generatedReferences: referenceInputs, aspectRatio: request.production_brief?.aspectRatio || "1:1" });
|
|
|
|
|
+ await writeFile(paths.prompt_plan, `${JSON.stringify(promptPlan, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const stdout = await executeNode(jimengClientFile, [paths.prompt_plan, "--execute", "--asset-map", paths.asset_map, "--max-cost", "1.54", "--output", paths.jimeng_result]);
|
|
|
|
|
+ const setSummary = JSON.parse(stdout); const setImages = Array.isArray(setSummary.local_images) ? setSummary.local_images.filter(Boolean) : [];
|
|
|
|
|
+ const costRange = addCostRanges({ cost_range_cny: { min: 0.44, max: 0.44 } }, setSummary);
|
|
|
|
|
+ if (setSummary.status !== "complete" || setImages.length !== 7) {
|
|
|
|
|
+ await writeV3State(paths, { stage: "set_generation_partial", set_plan_sha256: contractSha256(authored), paid_image_calls: 2 + setImages.length, cost_range_cny: costRange });
|
|
|
|
|
+ return writeReferenceFirstResult(paths, { status: "partial", workflow: V3_WORKFLOW, output_level: "visual_base", paths, set_generation: setSummary, visual_base_images: setImages, files: [referenceLocal[0], paths.setting_board_annotated, ...setImages].filter(Boolean), local_images: setImages, cost: { total_cost_range_cny: costRange, next_stage_max_image_cost_cny: Number(((7 - setImages.length) * 0.22).toFixed(2)) }, original_reference_used_for_set: false, next_action: "resume_same_run_after_set_generation_technical_failure" });
|
|
|
|
|
+ }
|
|
|
|
|
+ const copyPlan = v3CopyPlan(authored); await writeFile(paths.copy_plan, `${JSON.stringify(copyPlan, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const remoteSet = generatedInputs(setSummary, setImages); let layoutDetection; let designedSet;
|
|
|
|
|
+ try {
|
|
|
|
|
+ layoutDetection = await detectLayout({ runId: paths.run_id, imageUrls: remoteSet, outputFile: paths.layout_regions });
|
|
|
|
|
+ designedSet = await postprocessDesign({ runDirectory: paths.run_directory, outputDirectory: join(paths.run_directory, "平台设计成品V3"), baseImages: setImages, blueprint: v3Blueprint(authored), copyPlan, layoutRegions: layoutDetection.regions, aspectRatio: request.production_brief?.aspectRatio || "1:1", layoutReportFile: paths.layout_report });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ await writeV3State(paths, { stage: "design_postprocess_partial", set_plan_sha256: contractSha256(authored), paid_image_calls: 9, confirmed_image_cost_cny: 1.98, technical_error: error?.message || String(error) });
|
|
|
|
|
+ return writeReferenceFirstResult(paths, { status: "partial", workflow: V3_WORKFLOW, output_level: "visual_base", paths, set_generation: setSummary, visual_base_images: setImages, layout_geometry: { status: "blocked", error: error?.message || String(error) }, files: [referenceLocal[0], paths.setting_board_annotated, ...setImages].filter(Boolean), local_images: setImages, cost: { confirmed_image_cost_cny: 1.98, next_stage_max_image_cost_cny: 0 }, original_reference_used_for_set: false, next_action: "resume_design_postprocess_without_regenerating_paid_images" });
|
|
|
|
|
+ }
|
|
|
|
|
+ const designedImages = designedSet.files.map((item) => item.file);
|
|
|
|
|
+ await writeV3State(paths, { stage: "complete", set_plan_sha256: contractSha256(authored), paid_image_calls: 9, confirmed_image_cost_cny: 1.98, auxiliary_model_calls: layoutDetection.model_calls });
|
|
|
|
|
+ const result = {
|
|
|
|
|
+ status: "complete", workflow: V3_WORKFLOW, output_level: "platform_designed_image_set_v3", paths,
|
|
|
|
|
+ reference_assets: { ...referenceResult, local_images: referenceLocal }, setting_board: { visual_file: paths.setting_board_visual, annotated_file: paths.setting_board_annotated },
|
|
|
|
|
+ claude_authored_set_plan_file: paths.claude_authored_set_plan, prompt_plan_file: paths.prompt_plan,
|
|
|
|
|
+ set_generation: { ...setSummary, local_images: setImages }, visual_base_images: setImages,
|
|
|
|
|
+ layout_geometry: { status: "complete", regions_file: paths.layout_regions, model_calls: layoutDetection.model_calls, reused: layoutDetection.reused, usage: layoutDetection.usage }, designed_set: designedSet,
|
|
|
|
|
+ local_images: [referenceLocal[0], paths.setting_board_annotated, ...designedImages], files: [referenceLocal[0], paths.setting_board_annotated, ...designedImages],
|
|
|
|
|
+ summary: { completed: 9, expected_total: 9, reference_assets_completed: 2, set_images_completed: 7, designed_images_completed: 7, cost_range_cny: costRange },
|
|
|
|
|
+ cost: { confirmed_image_cost_cny: 1.98, next_stage_max_image_cost_cny: 0 }, content_review_enabled: false, quality_gate_enabled: false, structural_contract_validation_enabled: true, original_reference_used_for_set: false,
|
|
|
|
|
+ next_action: "deliver_three_view_annotated_setting_board_and_platform_designed_seven_images",
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeReferenceFirstResult(paths, result); return result;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export async function runReferenceFirstPipeline({
|
|
|
|
|
+ paths,
|
|
|
|
|
+ originalReference,
|
|
|
|
|
+ productionBrief = {},
|
|
|
|
|
+ executeNode = runNode,
|
|
|
|
|
+ planCreative = planReferenceFirstCreative,
|
|
|
|
|
+ writeCopyPlan = writeReferenceFirstCopyPlan,
|
|
|
|
|
+ detectLayout = detectReferenceFirstLayoutRegions,
|
|
|
|
|
+ postprocessDesign = runReferenceFirstDesignPostprocessor,
|
|
|
|
|
+} = {}) {
|
|
|
|
|
+ if (!paths?.run_id || !originalReference) throw new Error("REFERENCE_FIRST_PIPELINE_INPUT_REQUIRED");
|
|
|
|
|
+ const requestRecord = {
|
|
|
|
|
+ schema_version: "0.1",
|
|
|
|
|
+ workflow: "reference_first_platform_v2",
|
|
|
|
|
+ run_id: paths.run_id,
|
|
|
|
|
+ original_reference: originalReference,
|
|
|
|
|
+ production_brief: productionBrief,
|
|
|
|
|
+ expected_paid_images: 9,
|
|
|
|
|
+ estimated_cost_cny: 1.98,
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeFile(paths.reference_first_request, `${JSON.stringify(requestRecord, null, 2)}\n`, "utf8");
|
|
|
|
|
+
|
|
|
|
|
+ const referencePlan = compileReferenceAssetPlan({
|
|
|
|
|
+ runId: paths.run_id,
|
|
|
|
|
+ originalReference,
|
|
|
|
|
+ });
|
|
|
|
|
+ await writeFile(paths.reference_asset_plan, `${JSON.stringify(referencePlan, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const referenceStdout = await executeNode(jimengClientFile, [
|
|
|
|
|
+ paths.reference_asset_plan,
|
|
|
|
|
+ "--execute",
|
|
|
|
|
+ "--asset-map", paths.asset_map,
|
|
|
|
|
+ "--max-cost", "0.44",
|
|
|
|
|
+ "--output", paths.reference_asset_result,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ const referenceSummary = JSON.parse(referenceStdout);
|
|
|
|
|
+ const generatedReferences = Array.isArray(referenceSummary.local_images)
|
|
|
|
|
+ ? referenceSummary.local_images.filter(Boolean)
|
|
|
|
|
+ : [];
|
|
|
|
|
+ if (referenceSummary.status !== "complete" || generatedReferences.length !== 2) {
|
|
|
|
|
+ const costRange = addCostRanges(referenceSummary);
|
|
|
|
|
+ return writeReferenceFirstResult(paths, {
|
|
|
|
|
+ status: "partial",
|
|
|
|
|
+ output_level: "visual_base",
|
|
|
|
|
+ workflow: "reference_first_platform_v2",
|
|
|
|
|
+ paths,
|
|
|
|
|
+ reference_assets: referenceSummary,
|
|
|
|
|
+ local_images: generatedReferences,
|
|
|
|
|
+ files: generatedReferences,
|
|
|
|
|
+ summary: {
|
|
|
|
|
+ completed: generatedReferences.length,
|
|
|
|
|
+ failed: Math.max(0, 2 - generatedReferences.length),
|
|
|
|
|
+ expected_total: 9,
|
|
|
|
|
+ cost_range_cny: costRange,
|
|
|
|
|
+ },
|
|
|
|
|
+ content_review_enabled: false,
|
|
|
|
|
+ next_action: "resume_same_run_after_reference_asset_technical_failure",
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const generatedReferenceInputs = Array.isArray(referenceSummary.remote_images)
|
|
|
|
|
+ && referenceSummary.remote_images.filter(Boolean).length === 2
|
|
|
|
|
+ ? referenceSummary.remote_images.filter(Boolean)
|
|
|
|
|
+ : generatedReferences;
|
|
|
|
|
+ let creativePlanning;
|
|
|
|
|
+ try {
|
|
|
|
|
+ creativePlanning = await planCreative({
|
|
|
|
|
+ runId: paths.run_id,
|
|
|
|
|
+ generatedReferences: generatedReferenceInputs,
|
|
|
|
|
+ brief: productionBrief,
|
|
|
|
|
+ outputFile: paths.set_blueprint,
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const costRange = addCostRanges(referenceSummary);
|
|
|
|
|
+ return writeReferenceFirstResult(paths, {
|
|
|
|
|
+ status: "partial",
|
|
|
|
|
+ output_level: "visual_base",
|
|
|
|
|
+ workflow: "reference_first_platform_v2",
|
|
|
|
|
+ paths,
|
|
|
|
|
+ reference_assets: referenceSummary,
|
|
|
|
|
+ creative_planning: { status: "blocked", error: error?.message || String(error), model_calls: Number(error?.model_calls || 0), usage: error?.usage || null },
|
|
|
|
|
+ local_images: generatedReferences,
|
|
|
|
|
+ files: generatedReferences,
|
|
|
|
|
+ summary: {
|
|
|
|
|
+ completed: generatedReferences.length,
|
|
|
|
|
+ failed: 0,
|
|
|
|
|
+ expected_total: 9,
|
|
|
|
|
+ reference_assets_completed: generatedReferences.length,
|
|
|
|
|
+ set_images_completed: 0,
|
|
|
|
|
+ cost_range_cny: costRange,
|
|
|
|
|
+ },
|
|
|
|
|
+ content_review_enabled: false,
|
|
|
|
|
+ validation_enabled: false,
|
|
|
|
|
+ original_reference_used_for_set: false,
|
|
|
|
|
+ next_action: "configure_creative_planner_then_resume_same_run_without_regenerating_reference_assets",
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ const setPlan = compileReferenceFirstSetPlan({
|
|
|
|
|
+ runId: paths.run_id,
|
|
|
|
|
+ generatedReferences: generatedReferenceInputs,
|
|
|
|
|
+ brief: productionBrief,
|
|
|
|
|
+ blueprint: creativePlanning.blueprint,
|
|
|
|
|
+ });
|
|
|
|
|
+ await writeFile(paths.prompt_plan, `${JSON.stringify(setPlan, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const setStdout = await executeNode(jimengClientFile, [
|
|
|
|
|
+ paths.prompt_plan,
|
|
|
|
|
+ "--execute",
|
|
|
|
|
+ "--asset-map", paths.asset_map,
|
|
|
|
|
+ "--max-cost", "1.54",
|
|
|
|
|
+ "--output", paths.jimeng_result,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ const setSummary = JSON.parse(setStdout);
|
|
|
|
|
+ const setImages = Array.isArray(setSummary.local_images) ? setSummary.local_images.filter(Boolean) : [];
|
|
|
|
|
+ const generatedSetComplete = setSummary.status === "complete" && setImages.length === 7;
|
|
|
|
|
+ const costRange = addCostRanges(referenceSummary, setSummary);
|
|
|
|
|
+ if (!generatedSetComplete) {
|
|
|
|
|
+ const localImages = [...generatedReferences, ...setImages];
|
|
|
|
|
+ return writeReferenceFirstResult(paths, {
|
|
|
|
|
+ status: "partial", output_level: "visual_base", workflow: "reference_first_platform_v2", paths,
|
|
|
|
|
+ reference_assets: { ...referenceSummary, plan_file: paths.reference_asset_plan, result_file: paths.reference_asset_result, local_images: generatedReferences },
|
|
|
|
|
+ creative_planning: { status: "complete", blueprint_file: paths.set_blueprint, reused: creativePlanning.reused, model_calls: creativePlanning.model_calls, usage: creativePlanning.usage },
|
|
|
|
|
+ set_generation: { ...setSummary, plan_file: paths.prompt_plan, result_file: paths.jimeng_result, local_images: setImages },
|
|
|
|
|
+ visual_base_images: setImages, local_images: localImages, files: localImages,
|
|
|
|
|
+ summary: { completed: localImages.length, failed: Math.max(0, 9 - localImages.length), expected_total: 9, reference_assets_completed: generatedReferences.length, set_images_completed: setImages.length, cost_range_cny: costRange },
|
|
|
|
|
+ estimated_cost_cny: 1.98, confirmed_minimum_cost_cny: costRange.min,
|
|
|
|
|
+ potential_additional_cost_cny: Number((costRange.max - costRange.min).toFixed(2)),
|
|
|
|
|
+ content_review_enabled: false, validation_enabled: false, original_reference_used_for_set: false,
|
|
|
|
|
+ next_action: "resume_same_run_after_generation_technical_failure",
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const copyPlan = await writeCopyPlan({ brief: productionBrief, blueprint: creativePlanning.blueprint, outputFile: paths.copy_plan });
|
|
|
|
|
+ const remoteSetImages = Array.isArray(setSummary.remote_images) && setSummary.remote_images.filter(Boolean).length === 7
|
|
|
|
|
+ ? setSummary.remote_images.filter(Boolean)
|
|
|
|
|
+ : Array.isArray(setSummary.results)
|
|
|
|
|
+ ? setSummary.results.flatMap((item) => Array.isArray(item.images) ? item.images.slice(0, 1) : []).filter(Boolean)
|
|
|
|
|
+ : [];
|
|
|
|
|
+ let layoutDetection;
|
|
|
|
|
+ let designedSet;
|
|
|
|
|
+ try {
|
|
|
|
|
+ layoutDetection = await detectLayout({
|
|
|
|
|
+ runId: paths.run_id, imageUrls: remoteSetImages, outputFile: paths.layout_regions,
|
|
|
|
|
+ });
|
|
|
|
|
+ designedSet = await postprocessDesign({
|
|
|
|
|
+ runDirectory: paths.run_directory,
|
|
|
|
|
+ outputDirectory: join(paths.run_directory, "平台设计成品V2"),
|
|
|
|
|
+ baseImages: setImages,
|
|
|
|
|
+ blueprint: creativePlanning.blueprint,
|
|
|
|
|
+ copyPlan,
|
|
|
|
|
+ layoutRegions: layoutDetection.regions,
|
|
|
|
|
+ aspectRatio: productionBrief.aspectRatio || "1:1",
|
|
|
|
|
+ layoutReportFile: paths.layout_report,
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ const localImages = [...generatedReferences, ...setImages];
|
|
|
|
|
+ return writeReferenceFirstResult(paths, {
|
|
|
|
|
+ status: "partial", output_level: "visual_base", workflow: "reference_first_platform_v2", paths,
|
|
|
|
|
+ reference_assets: { ...referenceSummary, plan_file: paths.reference_asset_plan, result_file: paths.reference_asset_result, local_images: generatedReferences },
|
|
|
|
|
+ creative_planning: { status: "complete", blueprint_file: paths.set_blueprint, reused: creativePlanning.reused, model_calls: creativePlanning.model_calls, usage: creativePlanning.usage },
|
|
|
|
|
+ set_generation: { ...setSummary, plan_file: paths.prompt_plan, result_file: paths.jimeng_result, local_images: setImages },
|
|
|
|
|
+ copy_plan_file: paths.copy_plan,
|
|
|
|
|
+ layout_geometry: { status: "blocked", error: error?.message || String(error), regions_file: paths.layout_regions, model_calls: Number(error?.model_calls || 0), usage: error?.usage || null },
|
|
|
|
|
+ design_status: "blocked", visual_base_images: setImages, local_images: localImages, files: localImages,
|
|
|
|
|
+ summary: { completed: 9, failed: 0, expected_total: 9, reference_assets_completed: 2, set_images_completed: 7, designed_images_completed: 0, cost_range_cny: costRange },
|
|
|
|
|
+ estimated_cost_cny: 1.98, confirmed_minimum_cost_cny: costRange.min,
|
|
|
|
|
+ potential_additional_cost_cny: Number((costRange.max - costRange.min).toFixed(2)),
|
|
|
|
|
+ content_review_enabled: false, validation_enabled: false, original_reference_used_for_set: false,
|
|
|
|
|
+ next_action: "configure_layout_geometry_then_resume_same_run_without_regenerating_paid_images",
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ const designedImages = designedSet.files.map((item) => item.file);
|
|
|
|
|
+ const localImages = [...generatedReferences, ...designedImages];
|
|
|
|
|
+ const complete = designedImages.length === 7;
|
|
|
|
|
+ return writeReferenceFirstResult(paths, {
|
|
|
|
|
+ status: complete ? "complete" : "partial",
|
|
|
|
|
+ output_level: "visual_base",
|
|
|
|
|
+ workflow: "reference_first_platform_v2",
|
|
|
|
|
+ paths,
|
|
|
|
|
+ reference_assets: {
|
|
|
|
|
+ ...referenceSummary,
|
|
|
|
|
+ plan_file: paths.reference_asset_plan,
|
|
|
|
|
+ result_file: paths.reference_asset_result,
|
|
|
|
|
+ local_images: generatedReferences,
|
|
|
|
|
+ },
|
|
|
|
|
+ creative_planning: {
|
|
|
|
|
+ status: "complete",
|
|
|
|
|
+ blueprint_file: paths.set_blueprint,
|
|
|
|
|
+ reused: creativePlanning.reused,
|
|
|
|
|
+ model_calls: creativePlanning.model_calls,
|
|
|
|
|
+ usage: creativePlanning.usage,
|
|
|
|
|
+ },
|
|
|
|
|
+ set_generation: {
|
|
|
|
|
+ ...setSummary,
|
|
|
|
|
+ plan_file: paths.prompt_plan,
|
|
|
|
|
+ result_file: paths.jimeng_result,
|
|
|
|
|
+ local_images: setImages,
|
|
|
|
|
+ },
|
|
|
|
|
+ visual_base_images: setImages,
|
|
|
|
|
+ copy_plan_file: paths.copy_plan,
|
|
|
|
|
+ layout_geometry: {
|
|
|
|
|
+ status: "complete", regions_file: paths.layout_regions,
|
|
|
|
|
+ reused: layoutDetection.reused, model_calls: layoutDetection.model_calls, usage: layoutDetection.usage,
|
|
|
|
|
+ },
|
|
|
|
|
+ auxiliary_model_usage: auxiliaryModelUsage(creativePlanning, layoutDetection),
|
|
|
|
|
+ designed_set: designedSet,
|
|
|
|
|
+ designed_production_files: designedSet.files,
|
|
|
|
|
+ layout_report_file: paths.layout_report,
|
|
|
|
|
+ local_images: localImages,
|
|
|
|
|
+ files: localImages,
|
|
|
|
|
+ summary: {
|
|
|
|
|
+ completed: localImages.length,
|
|
|
|
|
+ failed: Math.max(0, 9 - localImages.length),
|
|
|
|
|
+ expected_total: 9,
|
|
|
|
|
+ reference_assets_completed: generatedReferences.length,
|
|
|
|
|
+ set_images_completed: setImages.length,
|
|
|
|
|
+ designed_images_completed: designedImages.length,
|
|
|
|
|
+ cost_range_cny: costRange,
|
|
|
|
|
+ },
|
|
|
|
|
+ estimated_cost_cny: 1.98,
|
|
|
|
|
+ confirmed_minimum_cost_cny: costRange.min,
|
|
|
|
|
+ potential_additional_cost_cny: Number((costRange.max - costRange.min).toFixed(2)),
|
|
|
|
|
+ content_review_enabled: false,
|
|
|
|
|
+ validation_enabled: false,
|
|
|
|
|
+ original_reference_used_for_set: false,
|
|
|
|
|
+ layout_geometry_enabled: true,
|
|
|
|
|
+ next_action: complete ? "deliver_reference_assets_and_platform_designed_seven_images" : "resume_same_run_after_technical_failure",
|
|
|
|
|
+ });
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
export async function auditDelivery(paths, result) {
|
|
export async function auditDelivery(paths, result) {
|
|
|
const tracked = new Set(
|
|
const tracked = new Set(
|
|
|
(result.results ?? [])
|
|
(result.results ?? [])
|
|
@@ -57,49 +677,262 @@ export async function auditDelivery(paths, result) {
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function withDeliveryAudit(paths, summary) {
|
|
|
|
|
|
|
+export async function withDeliveryAudit(paths, summary) {
|
|
|
const state = JSON.parse(await readFile(paths.jimeng_result, "utf8"));
|
|
const state = JSON.parse(await readFile(paths.jimeng_result, "utf8"));
|
|
|
const deliveryIntegrity = await auditDelivery(paths, state);
|
|
const deliveryIntegrity = await auditDelivery(paths, state);
|
|
|
|
|
+ const executionAuthorization = await readOptionalJson(paths.execution_authorization);
|
|
|
|
|
+ const promptPlan = await readOptionalJson(paths.prompt_plan);
|
|
|
|
|
+ const setQualityReview = await verifyProductionSetQuality({ runState: state, promptPlan: promptPlan || {} });
|
|
|
|
|
+ const baseOutcome = compileProductionOutcome({
|
|
|
|
|
+ runState: state,
|
|
|
|
|
+ deliveryIntegrity,
|
|
|
|
|
+ executionAuthorization,
|
|
|
|
|
+ setQualityReview,
|
|
|
|
|
+ });
|
|
|
|
|
+ let designedProduction = {
|
|
|
|
|
+ status: "blocked",
|
|
|
|
|
+ reason: baseOutcome.status !== "complete"
|
|
|
|
|
+ ? "base_result_incomplete"
|
|
|
|
|
+ : baseOutcome.quality_status !== "quality_passed"
|
|
|
|
|
+ ? "set_quality_gate_not_passed"
|
|
|
|
|
+ : "design_bridge_not_run",
|
|
|
|
|
+ quality_status: baseOutcome.quality_status,
|
|
|
|
|
+ required_base_pages: 7,
|
|
|
|
|
+ paid_external_calls: 0,
|
|
|
|
|
+ files: [],
|
|
|
|
|
+ };
|
|
|
|
|
+ if (baseOutcome.status === "complete"
|
|
|
|
|
+ && baseOutcome.quality_status === "quality_passed"
|
|
|
|
|
+ && deliveryIntegrity.status === "verified") {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const vision = await readOptionalJson(paths.vision_result);
|
|
|
|
|
+ designedProduction = await runProductionDesignBridge({
|
|
|
|
|
+ runState: state,
|
|
|
|
|
+ runDirectory: paths.run_directory,
|
|
|
|
|
+ outputDirectory: join(paths.run_directory, "designed-production"),
|
|
|
|
|
+ reference: vision?.reference?.source || vision?.reference?.public_url,
|
|
|
|
|
+ productUnderstanding: vision?.product_understanding || {},
|
|
|
|
|
+ promptPlan: promptPlan || {},
|
|
|
|
|
+ setQualityReview,
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ designedProduction = {
|
|
|
|
|
+ status: "blocked",
|
|
|
|
|
+ reason: "design_bridge_failed",
|
|
|
|
|
+ error: { code: error?.code || "PRODUCTION_DESIGN_BRIDGE_FAILED", message: error?.message || String(error) },
|
|
|
|
|
+ required_base_pages: 7,
|
|
|
|
|
+ paid_external_calls: 0,
|
|
|
|
|
+ files: [],
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ const productionOutcome = { ...baseOutcome, designed_production: designedProduction };
|
|
|
|
|
+ await writeFile(paths.production_outcome, `${JSON.stringify(productionOutcome, null, 2)}\n`, "utf8");
|
|
|
if (deliveryIntegrity.status === "verified") {
|
|
if (deliveryIntegrity.status === "verified") {
|
|
|
- return { ...summary, delivery_integrity: deliveryIntegrity };
|
|
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...summary,
|
|
|
|
|
+ delivery_integrity: deliveryIntegrity,
|
|
|
|
|
+ production_outcome: productionOutcome,
|
|
|
|
|
+ production_outcome_file: paths.production_outcome,
|
|
|
|
|
+ designed_production: designedProduction,
|
|
|
|
|
+ designed_production_files: designedProduction.files || [],
|
|
|
|
|
+ };
|
|
|
}
|
|
}
|
|
|
return {
|
|
return {
|
|
|
...summary,
|
|
...summary,
|
|
|
status: "blocked",
|
|
status: "blocked",
|
|
|
delivery_integrity: deliveryIntegrity,
|
|
delivery_integrity: deliveryIntegrity,
|
|
|
|
|
+ production_outcome: productionOutcome,
|
|
|
|
|
+ production_outcome_file: paths.production_outcome,
|
|
|
|
|
+ designed_production: designedProduction,
|
|
|
|
|
+ designed_production_files: designedProduction.files || [],
|
|
|
next_action: "stop_and_report_delivery_integrity_error",
|
|
next_action: "stop_and_report_delivery_integrity_error",
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
async function runCli() {
|
|
async function runCli() {
|
|
|
if (command === "start") {
|
|
if (command === "start") {
|
|
|
|
|
+ const outputLevel = option("--output-level", "visual_base");
|
|
|
|
|
+ if (!new Set(["visual_base", "designed_asset"]).has(outputLevel)) {
|
|
|
|
|
+ throw new Error("OUTPUT_LEVEL_INVALID: expected visual_base or designed_asset");
|
|
|
|
|
+ }
|
|
|
const explicitImage = option("--image");
|
|
const explicitImage = option("--image");
|
|
|
- const invocation = await inspectLatestClaudeInvocation({
|
|
|
|
|
- cwd: option("--cwd", process.cwd()),
|
|
|
|
|
- claudeConfigDirectory: option("--claude-config-dir", process.env.CLAUDE_CONFIG_DIR),
|
|
|
|
|
- sessionId: option("--session-id", process.env.CLAUDE_CODE_SESSION_ID || ""),
|
|
|
|
|
|
|
+ const preferCurrentAttachment = args.includes("--prefer-current-attachment");
|
|
|
|
|
+ const attachmentCwd = resolveAttachmentCwd({ explicitCwd: option("--cwd") });
|
|
|
|
|
+ const claudeConfigDirectory = option("--claude-config-dir", process.env.CLAUDE_CONFIG_DIR);
|
|
|
|
|
+ const sessionId = option("--session-id", process.env.CLAUDE_CODE_SESSION_ID || "");
|
|
|
|
|
+ const invocation = await resolveStartInvocation({
|
|
|
|
|
+ explicitImage,
|
|
|
|
|
+ preferCurrentAttachment,
|
|
|
|
|
+ cwd: attachmentCwd,
|
|
|
|
|
+ claudeConfigDirectory,
|
|
|
|
|
+ sessionId,
|
|
|
});
|
|
});
|
|
|
- if (!explicitImage && invocation.image_count !== 1) {
|
|
|
|
|
- throw new Error(`CURRENT_REQUEST_IMAGE_COUNT_INVALID: expected one image, found ${invocation.image_count}`);
|
|
|
|
|
|
|
+ const paths = await createRun(invocation
|
|
|
|
|
+ ? { requestKey: `${invocation.session_id}:${invocation.prompt_id}` }
|
|
|
|
|
+ : {});
|
|
|
|
|
+ const productionBriefJson = option("--production-brief-json");
|
|
|
|
|
+ const visionArgs = buildVisionArguments({
|
|
|
|
|
+ invocation,
|
|
|
|
|
+ explicitImage,
|
|
|
|
|
+ outputFile: paths.vision_result,
|
|
|
|
|
+ attachmentCwd,
|
|
|
|
|
+ claudeConfigDirectory,
|
|
|
|
|
+ sessionId,
|
|
|
|
|
+ });
|
|
|
|
|
+ if (outputLevel === "visual_base" && productionBriefJson) {
|
|
|
|
|
+ const captureStdout = await runNode(productVisionFile, [...visionArgs, "--dry-run"]);
|
|
|
|
|
+ const referenceCapture = JSON.parse(captureStdout);
|
|
|
|
|
+ const inputProvenance = buildInputProvenance({ invocation, explicitImage, vision: referenceCapture });
|
|
|
|
|
+ const originalReference = referenceCapture?.reference?.source || referenceCapture?.reference?.public_url;
|
|
|
|
|
+ if (referenceCapture?.status !== "dry-run" || !originalReference) {
|
|
|
|
|
+ console.log(JSON.stringify({
|
|
|
|
|
+ status: "blocked",
|
|
|
|
|
+ output_level: outputLevel,
|
|
|
|
|
+ workflow: "reference_first_platform_v2",
|
|
|
|
|
+ paths,
|
|
|
|
|
+ reference_capture: referenceCapture,
|
|
|
|
|
+ input_provenance: inputProvenance,
|
|
|
|
|
+ error: { code: "REFERENCE_CAPTURE_FAILED", message: "当前商品图片未能进入参考资产优先流程" },
|
|
|
|
|
+ next_action: "attach_one_product_image_and_start_again",
|
|
|
|
|
+ }, null, 2));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const productionBrief = JSON.parse(productionBriefJson);
|
|
|
|
|
+ if (productionBrief.orchestrationMode !== "legacy_internal_planner") {
|
|
|
|
|
+ const result = await startClaudeAuthoredV3({ paths, originalReference, productionBrief, referenceCapture, inputProvenance });
|
|
|
|
|
+ console.log(JSON.stringify(result, null, 2));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const pipelineResult = await runReferenceFirstPipeline({ paths, originalReference, productionBrief });
|
|
|
|
|
+ const result = {
|
|
|
|
|
+ ...pipelineResult,
|
|
|
|
|
+ reference_capture: referenceCapture,
|
|
|
|
|
+ input_provenance: inputProvenance,
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeReferenceFirstResult(paths, result);
|
|
|
|
|
+ console.log(JSON.stringify(result, null, 2));
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
- const paths = await createRun({ requestKey: `${invocation.session_id}:${invocation.prompt_id}` });
|
|
|
|
|
- const visionStdout = await runNode(productVisionFile, explicitImage
|
|
|
|
|
- ? ["--image", explicitImage, "--output", paths.vision_result]
|
|
|
|
|
- : ["--from-claude-attachment", "--output", paths.vision_result]);
|
|
|
|
|
|
|
+ const visionStdout = await runNode(productVisionFile, visionArgs);
|
|
|
const vision = JSON.parse(visionStdout);
|
|
const vision = JSON.parse(visionStdout);
|
|
|
- console.log(JSON.stringify({
|
|
|
|
|
- status: vision.status === "complete" ? "ready_for_plan" : "blocked",
|
|
|
|
|
- paths,
|
|
|
|
|
|
|
+ const inputProvenance = buildInputProvenance({ invocation, explicitImage, vision });
|
|
|
|
|
+ let designedAsset = null;
|
|
|
|
|
+ let productionPlan = null;
|
|
|
|
|
+ let planValidation = null;
|
|
|
|
|
+ let preflightRecord = null;
|
|
|
|
|
+ let executionAuthorization = null;
|
|
|
|
|
+ if (outputLevel === "designed_asset" && vision.status === "complete") {
|
|
|
|
|
+ try {
|
|
|
|
|
+ designedAsset = await runDesignedAsset({
|
|
|
|
|
+ reference: vision?.reference?.source || explicitImage,
|
|
|
|
|
+ outputDirectory: join(paths.run_directory, "designed-assets"),
|
|
|
|
|
+ productUnderstanding: vision?.product_understanding || {},
|
|
|
|
|
+ externalCalls: vision?.external_calls || {},
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ designedAsset = buildDesignedAssetFailure(error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if (outputLevel === "visual_base" && productionBriefJson && vision.status === "complete") {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const productionBrief = JSON.parse(productionBriefJson);
|
|
|
|
|
+ productionPlan = compileProductionPlan({
|
|
|
|
|
+ runId: paths.run_id,
|
|
|
|
|
+ referenceUrl: vision?.reference?.public_url,
|
|
|
|
|
+ productUnderstanding: vision?.product_understanding,
|
|
|
|
|
+ brief: productionBrief,
|
|
|
|
|
+ });
|
|
|
|
|
+ await writeFile(paths.prompt_plan, `${JSON.stringify(productionPlan, null, 2)}\n`, "utf8");
|
|
|
|
|
+ const validationStdout = await runNode(validatePlanFile, [paths.prompt_plan]);
|
|
|
|
|
+ planValidation = JSON.parse(validationStdout);
|
|
|
|
|
+ if (planValidation?.valid === true && productionPlan?.images?.length === 7) {
|
|
|
|
|
+ const planText = await readFile(paths.prompt_plan, "utf8");
|
|
|
|
|
+ preflightRecord = {
|
|
|
|
|
+ schema_version: "0.1",
|
|
|
|
|
+ run_id: paths.run_id,
|
|
|
|
|
+ plan_sha256: sha256(planText),
|
|
|
|
|
+ validation: { valid: true, image_count: 7 },
|
|
|
|
|
+ paid_external_calls: 0,
|
|
|
|
|
+ created_at: new Date().toISOString(),
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeFile(paths.preflight_record, `${JSON.stringify(preflightRecord, null, 2)}\n`, "utf8");
|
|
|
|
|
+ executionAuthorization = normalizeExecutionAuthorization(productionBrief, preflightRecord.plan_sha256);
|
|
|
|
|
+ if (executionAuthorization) {
|
|
|
|
|
+ executionAuthorization = {
|
|
|
|
|
+ ...executionAuthorization,
|
|
|
|
|
+ run_id: paths.run_id,
|
|
|
|
|
+ recorded_at: new Date().toISOString(),
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeFile(paths.execution_authorization, `${JSON.stringify(executionAuthorization, null, 2)}\n`, "utf8");
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ let detail = null;
|
|
|
|
|
+ try { detail = JSON.parse(String(error?.stderr || "")); } catch { detail = null; }
|
|
|
|
|
+ planValidation = {
|
|
|
|
|
+ valid: false,
|
|
|
|
|
+ errors: detail?.errors || [error?.message || "PRODUCTION_PLAN_COMPILATION_FAILED"],
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ console.log(JSON.stringify(buildStartResult({
|
|
|
vision,
|
|
vision,
|
|
|
- next_action: vision.status === "complete"
|
|
|
|
|
- ? "write_prompt_plan_then_execute_same_run"
|
|
|
|
|
- : "stop_and_report_error",
|
|
|
|
|
- }, null, 2));
|
|
|
|
|
|
|
+ outputLevel,
|
|
|
|
|
+ paths,
|
|
|
|
|
+ designedAsset,
|
|
|
|
|
+ inputProvenance,
|
|
|
|
|
+ productionPlan,
|
|
|
|
|
+ planValidation,
|
|
|
|
|
+ preflightRecord,
|
|
|
|
|
+ executionAuthorization,
|
|
|
|
|
+ }), null, 2));
|
|
|
|
|
+} else if (command === "generate_references") {
|
|
|
|
|
+ const runId = option("--run-id"); if (!runId) throw new Error("RUN_ID_REQUIRED");
|
|
|
|
|
+ const authoringJson = option("--reference-authoring-json"); if (!authoringJson) throw new Error("REFERENCE_AUTHORING_JSON_REQUIRED");
|
|
|
|
|
+ console.log(JSON.stringify(await generateClaudeAuthoredReferences({ paths: pathsForRun(runId), referenceAuthoring: JSON.parse(authoringJson) }), null, 2));
|
|
|
|
|
+} else if (command === "generate_set") {
|
|
|
|
|
+ const runId = option("--run-id"); if (!runId) throw new Error("RUN_ID_REQUIRED");
|
|
|
|
|
+ const setPlanJson = option("--set-plan-json"); if (!setPlanJson) throw new Error("SET_PLAN_JSON_REQUIRED");
|
|
|
|
|
+ console.log(JSON.stringify(await generateClaudeAuthoredSet({ paths: pathsForRun(runId), setPlan: JSON.parse(setPlanJson) }), null, 2));
|
|
|
} else if (command === "execute" || command === "resume") {
|
|
} else if (command === "execute" || command === "resume") {
|
|
|
const runId = option("--run-id");
|
|
const runId = option("--run-id");
|
|
|
if (!runId) throw new Error("RUN_ID_REQUIRED");
|
|
if (!runId) throw new Error("RUN_ID_REQUIRED");
|
|
|
const paths = pathsForRun(runId);
|
|
const paths = pathsForRun(runId);
|
|
|
|
|
+ const prototypeRequest = await readOptionalJson(paths.reference_first_request);
|
|
|
|
|
+ if (command === "resume" && prototypeRequest?.workflow === V3_WORKFLOW) {
|
|
|
|
|
+ const stage = await readOptionalJson(paths.v3_stage_state);
|
|
|
|
|
+ if (stage?.stage === "reference_generation_partial") {
|
|
|
|
|
+ console.log(JSON.stringify(await generateClaudeAuthoredReferences({ paths, referenceAuthoring: await readOptionalJson(paths.claude_reference_authoring) }), null, 2)); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (new Set(["set_generation_partial", "design_postprocess_partial"]).has(stage?.stage)) {
|
|
|
|
|
+ console.log(JSON.stringify(await generateClaudeAuthoredSet({ paths, setPlan: await readOptionalJson(paths.claude_authored_set_plan) }), null, 2)); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const current = await readOptionalJson(paths.reference_first_result);
|
|
|
|
|
+ console.log(JSON.stringify({ ...current, resumed: false, reason: "waiting_for_claude_authored_next_stage_input", next_action: current?.next_action || "provide_next_stage_contract" }, null, 2)); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (command === "resume" && new Set(["reference_first_prototype", "reference_first_platform_v2"]).has(prototypeRequest?.workflow)) {
|
|
|
|
|
+ const previousResult = await readOptionalJson(paths.reference_first_result);
|
|
|
|
|
+ const resumed = await runReferenceFirstPipeline({
|
|
|
|
|
+ paths,
|
|
|
|
|
+ originalReference: prototypeRequest.original_reference,
|
|
|
|
|
+ productionBrief: prototypeRequest.production_brief || {},
|
|
|
|
|
+ });
|
|
|
|
|
+ const result = {
|
|
|
|
|
+ ...resumed,
|
|
|
|
|
+ ...(previousResult?.reference_capture ? { reference_capture: previousResult.reference_capture } : {}),
|
|
|
|
|
+ ...(previousResult?.input_provenance ? { input_provenance: previousResult.input_provenance } : {}),
|
|
|
|
|
+ };
|
|
|
|
|
+ await writeReferenceFirstResult(paths, result);
|
|
|
|
|
+ console.log(JSON.stringify(result, null, 2));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
const maxCost = option("--max-cost", "1.54");
|
|
const maxCost = option("--max-cost", "1.54");
|
|
|
|
|
+ await assertPreflightPlanUnchanged(paths);
|
|
|
|
|
+ const executionAuthorization = await readOptionalJson(paths.execution_authorization);
|
|
|
|
|
+ if (executionAuthorization?.authorized === true && Number(maxCost) > Number(executionAuthorization.max_cost_cny)) {
|
|
|
|
|
+ throw new Error("EXECUTION_AUTHORIZATION_LIMIT_EXCEEDED");
|
|
|
|
|
+ }
|
|
|
const stdout = await runNode(jimengClientFile, [
|
|
const stdout = await runNode(jimengClientFile, [
|
|
|
paths.prompt_plan,
|
|
paths.prompt_plan,
|
|
|
"--execute",
|
|
"--execute",
|
|
@@ -109,15 +942,28 @@ if (command === "start") {
|
|
|
"--output", paths.jimeng_result,
|
|
"--output", paths.jimeng_result,
|
|
|
]);
|
|
]);
|
|
|
const summary = JSON.parse(stdout);
|
|
const summary = JSON.parse(stdout);
|
|
|
|
|
+ if (isExecutorBlocked(summary)) {
|
|
|
|
|
+ console.log(JSON.stringify(summary, null, 2));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
console.log(JSON.stringify(await withDeliveryAudit(paths, summary), null, 2));
|
|
console.log(JSON.stringify(await withDeliveryAudit(paths, summary), null, 2));
|
|
|
} else if (command === "status") {
|
|
} else if (command === "status") {
|
|
|
const runId = option("--run-id");
|
|
const runId = option("--run-id");
|
|
|
if (!runId) throw new Error("RUN_ID_REQUIRED");
|
|
if (!runId) throw new Error("RUN_ID_REQUIRED");
|
|
|
const paths = pathsForRun(runId);
|
|
const paths = pathsForRun(runId);
|
|
|
|
|
+ const prototypeResult = await readOptionalJson(paths.reference_first_result);
|
|
|
|
|
+ if (prototypeResult?.workflow === V3_WORKFLOW) {
|
|
|
|
|
+ const stage = await readOptionalJson(paths.v3_stage_state);
|
|
|
|
|
+ console.log(JSON.stringify({ ...prototypeResult, stage_state: stage }, null, 2)); return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (new Set(["reference_first_prototype", "reference_first_platform_v2"]).has(prototypeResult?.workflow)) {
|
|
|
|
|
+ console.log(JSON.stringify(prototypeResult, null, 2));
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
const result = JSON.parse(await readFile(paths.jimeng_result, "utf8"));
|
|
const result = JSON.parse(await readFile(paths.jimeng_result, "utf8"));
|
|
|
console.log(JSON.stringify(await withDeliveryAudit(paths, { paths, result }), null, 2));
|
|
console.log(JSON.stringify(await withDeliveryAudit(paths, { paths, result }), null, 2));
|
|
|
} else {
|
|
} else {
|
|
|
- throw new Error("Usage: node image-set-runner.mjs start [--image <path-or-url>] | execute --run-id <run_id> [--max-cost 1.54] | resume --run-id <run_id> | status --run-id <run_id>");
|
|
|
|
|
|
|
+ throw new Error("Usage: node image-set-runner.mjs start [--image <path-or-url>] | generate_references --run-id <run_id> --reference-authoring-json <json> | generate_set --run-id <run_id> --set-plan-json <json> | resume --run-id <run_id> | status --run-id <run_id>");
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|