Files
ttstd-infinite-canvas/canvas-agent/src/dreamina-cli-runtime.ts

2000 lines
92 KiB
TypeScript

import crypto from "node:crypto";
import { watch as watchFileSystem } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import {
dreaminaCliInputSchema,
type DreaminaCliInput,
type DreaminaGenerationInput,
} from "./dreamina-cli-contract.js";
import { DreaminaCliArbiter, type DreaminaCliSessionSnapshot } from "./dreamina-cli-arbiter.js";
import { dreaminaTaskFromRuntimeJournal, type DreaminaTaskContext, type DreaminaTaskState } from "./dreamina-task-contract.js";
import {
discoverDreaminaExecutable,
DreaminaCliError,
runDreaminaProcess,
sanitizeDreaminaDiagnostic,
type DreaminaProcessRequest,
type DreaminaProcessResult,
} from "./dreamina-cli-process.js";
import {
acquireStateLock,
persistRuntimeDiskState,
readRuntimeDiskState,
recordKey,
recoverStateReplacement,
stateInvalid,
stateRank,
type RuntimeDiskState,
type RuntimeRecord,
type StateLockLease,
} from "./dreamina-cli-state.js";
import { scavengeStaleStagingDirectories, stageReferences } from "./dreamina-cli-staging.js";
import {
DreaminaTaskReconciler,
type DreaminaReconcileApplied,
type DreaminaReconcileObservation,
} from "./dreamina-task-reconciler.js";
import { DreaminaProviderArtifactStore } from "./dreamina-provider-artifacts.js";
import { DreaminaTaskProjector } from "./dreamina-task-projection.js";
import { DreaminaTaskStore } from "./dreamina-task-store.js";
import {
collectDreaminaGenerationResult,
LocalDreaminaGenerationError,
unknownGenerationResult,
type DreaminaGenerationResult,
} from "./dreamina-generation.js";
export { acquireStateLock } from "./dreamina-cli-state.js";
type Installation = { installed: true; executable: string } | { installed: false };
const DEFAULT_SUBMISSION_RESERVATION_LEASE_MS = 30_000;
const DEFAULT_SUBMISSION_RESERVATION_HEARTBEAT_MS = 5_000;
export type DreaminaRuntimeResult = { state: "accepted"; submitId: string };
type DreaminaRuntimeQueryResult = { state: "query"; result: unknown };
export type DreaminaRuntimeRunOptions = {
signal?: AbortSignal;
requestFingerprint?: string;
clientOperationId?: string;
taskContext?: DreaminaTaskContext;
};
export type DreaminaPublicGenerationTask = {
id: string;
clientOperationId?: string;
context?: DreaminaTaskContext;
provider: "dreamina-cli";
mode: "image" | "video";
operation: DreaminaGenerationInput["operation"];
model: string;
status: "queued" | "running" | "succeeded" | "failed" | "cancelled";
stage: "queued" | "submitting" | "submitted" | "generating" | "succeeded" | "failed" | "cancelled" | "submission_unknown";
progress?: number;
receiptRecorded: boolean;
errorCode?: string;
officialStatus?: "pending" | "processing" | "completed" | "failed" | "cancelled";
createdAt: string;
updatedAt: string;
result?: DreaminaGenerationResult;
};
export type DreaminaCliRuntimeOptions = {
ownerId: string;
stateFile: string;
referenceRoots?: () => readonly string[];
ensureReady: (signal?: AbortSignal) => Promise<DreaminaCliSessionSnapshot | void>;
env?: Record<string, string | undefined>;
discover?: (signal?: AbortSignal) => Promise<Installation>;
runProcess?: (request: DreaminaProcessRequest) => Promise<DreaminaProcessResult>;
now?: () => Date;
generationRoot?: string;
sleep?: (delayMs: number) => Promise<void>;
maxPollAttempts?: number;
pollIntervalMs?: number;
maxActiveTasks?: number;
arbiter?: DreaminaCliArbiter;
reservationLeaseMs?: number;
reservationHeartbeatMs?: number;
reconciler?: DreaminaTaskReconciler;
watchStateFile?: (listener: (filename: string | null) => void) => { close(): void };
scheduleDurableReload?: (callback: () => void, delayMs: number) => { cancel(): void };
durableReloadIntervalMs?: number;
onQueueHeartbeatWait?: (event: "started" | "settled", key: string) => void;
taskStore?: DreaminaTaskStore;
taskProjector?: DreaminaTaskProjector;
artifactStore?: DreaminaProviderArtifactStore;
};
type InFlightSubmission = {
requestHash: string;
promise: Promise<DreaminaRuntimeResult>;
controller: AbortController;
waiters: number;
settled: boolean;
};
type PreparedAsyncTask = {
key: string;
requestHash: string;
input?: DreaminaGenerationInput;
cleanup: () => Promise<void>;
cleanupId: string;
controller: AbortController;
completion: Promise<DreaminaGenerationResult>;
resolve: (result: DreaminaGenerationResult) => void;
reject: (error: unknown) => void;
result?: DreaminaGenerationResult;
error?: unknown;
background?: Promise<void>;
starting?: Promise<DreaminaPublicGenerationTask>;
polling: boolean;
settled: boolean;
};
type EnqueueInFlight = { requestHash: string; promise: Promise<DreaminaPublicGenerationTask> };
type SubmissionReservation = { reservationId: string; reservationOwnerId: string };
function encodeTaskListCursor(updatedAt: string, id: string) {
return Buffer.from(JSON.stringify([updatedAt, id]), "utf8").toString("base64url");
}
function decodeTaskListCursor(cursor: string) {
try {
if (!/^[A-Za-z0-9_-]{1,512}$/.test(cursor)) throw new Error("invalid cursor");
const value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")) as unknown;
if (!Array.isArray(value) || value.length !== 2 || typeof value[0] !== "string" || typeof value[1] !== "string") throw new Error("invalid cursor");
if (!Number.isFinite(Date.parse(value[0])) || !/^[A-Za-z0-9._:-]{16,120}$/.test(value[1])) throw new Error("invalid cursor");
return { updatedAt: value[0], id: value[1] };
} catch {
throw new DreaminaCliError("dreamina_request_invalid", "Dreamina 请求参数无效", 400);
}
}
export class DreaminaCliRuntime {
private readonly ownerId: string;
private readonly stateFile: string;
private readonly referenceRoots: () => readonly string[];
private readonly ensureReady: (signal?: AbortSignal) => Promise<DreaminaCliSessionSnapshot | void>;
private readonly env: Record<string, string | undefined>;
private readonly discover: (signal?: AbortSignal) => Promise<Installation>;
private readonly runProcess: (request: DreaminaProcessRequest) => Promise<DreaminaProcessResult>;
private readonly now: () => Date;
private readonly generationRoot: string;
private readonly pollIntervalMs: number;
private readonly maxActiveTasks: number;
private readonly reservationOwnerId = crypto.randomUUID();
private readonly reservationLeaseMs: number;
private readonly reservationHeartbeatMs: number;
private readonly records = new Map<string, RuntimeRecord>();
private readonly inFlight = new Map<string, InFlightSubmission>();
private readonly asyncTasks = new Map<string, PreparedAsyncTask>();
private readonly enqueueInFlight = new Map<string, EnqueueInFlight>();
private readonly queue: string[] = [];
private readonly activeTasks = new Set<string>();
private readonly queueHeartbeats = new Map<string, () => Promise<void>>();
private readonly deferredCleanups = new Map<string, () => Promise<void>>();
private nextQueueTicket = 1;
private readonly arbiter: DreaminaCliArbiter;
private readonly reconciler: DreaminaTaskReconciler;
private readonly taskStore: DreaminaTaskStore;
private readonly taskProjector: DreaminaTaskProjector;
private readonly artifactStore: DreaminaProviderArtifactStore;
private readonly watchStateFile: NonNullable<DreaminaCliRuntimeOptions["watchStateFile"]>;
private readonly scheduleDurableReload: NonNullable<DreaminaCliRuntimeOptions["scheduleDurableReload"]>;
private readonly durableReloadIntervalMs: number;
private readonly onQueueHeartbeatWait?: NonNullable<DreaminaCliRuntimeOptions["onQueueHeartbeatWait"]>;
private readonly queryShutdown = new AbortController();
private loadPromise?: Promise<void>;
private enqueueSerial: Promise<void> = Promise.resolve();
private recoveryStarted = false;
private disposed = false;
constructor(options: DreaminaCliRuntimeOptions) {
if (!/^[A-Za-z0-9._-]{16,120}$/.test(options.ownerId)) throw new Error("Dreamina owner is invalid");
this.ownerId = options.ownerId;
this.stateFile = options.stateFile;
this.referenceRoots = options.referenceRoots ?? (() => []);
this.ensureReady = options.ensureReady;
this.env = options.env ?? process.env;
this.discover = options.discover ?? ((signal) => discoverDreaminaExecutable(this.env, signal));
this.runProcess = options.runProcess ?? runDreaminaProcess;
this.now = options.now ?? (() => new Date());
this.generationRoot = path.resolve(options.generationRoot ?? path.join(path.dirname(this.stateFile), "dreamina-generation-runs"));
this.pollIntervalMs = boundedInteger(options.pollIntervalMs, 0, 60_000, 2_000);
this.maxActiveTasks = boundedInteger(options.maxActiveTasks, 1, 32, 5);
this.reservationLeaseMs = boundedInteger(options.reservationLeaseMs, 50, 300_000, DEFAULT_SUBMISSION_RESERVATION_LEASE_MS);
this.reservationHeartbeatMs = options.reservationHeartbeatMs === 0
? 0
: boundedInteger(options.reservationHeartbeatMs, 10, this.reservationLeaseMs, Math.min(DEFAULT_SUBMISSION_RESERVATION_HEARTBEAT_MS, Math.max(10, Math.floor(this.reservationLeaseMs / 3))));
this.arbiter = options.arbiter ?? new DreaminaCliArbiter({
stateFile: path.join(path.dirname(this.stateFile), "dreamina-cli-arbiter.json"),
});
this.taskStore = options.taskStore ?? new DreaminaTaskStore({
stateFile: path.join(path.dirname(this.stateFile), "dreamina-generation-task-store.json"),
now: this.now,
});
this.taskProjector = options.taskProjector ?? new DreaminaTaskProjector({
store: this.taskStore,
ownerId: this.ownerId,
journalFile: this.stateFile,
});
this.artifactStore = options.artifactStore ?? new DreaminaProviderArtifactStore({
root: path.join(path.dirname(this.stateFile), "dreamina-provider-artifacts"),
now: this.now,
});
this.watchStateFile = options.watchStateFile ?? ((listener) => {
const watcher = watchFileSystem(path.dirname(this.stateFile), { persistent: false }, (_event, filename) => {
listener(filename === null ? null : String(filename));
});
return { close: () => watcher.close() };
});
this.scheduleDurableReload = options.scheduleDurableReload ?? ((callback, delayMs) => {
let active = true;
const timer = setTimeout(() => {
if (!active) return;
active = false;
callback();
}, delayMs);
timer.unref();
return {
cancel() {
if (!active) return;
active = false;
clearTimeout(timer);
},
};
});
this.durableReloadIntervalMs = boundedInteger(options.durableReloadIntervalMs, 5, 5_000, 250);
this.onQueueHeartbeatWait = options.onQueueHeartbeatWait;
this.reconciler = options.reconciler ?? new DreaminaTaskReconciler({
ownerId: this.ownerId,
stateFile: this.stateFile,
now: this.now,
pollIntervalMs: Math.max(1, this.pollIntervalMs),
observe: (record, signal) => this.observeReconciliation(record, signal),
onApplied: (event) => this.handleReconcileApplied(event),
});
}
async start() {
await this.loadState();
await this.startRecoveryOnce();
}
enqueue(value: unknown, options: DreaminaRuntimeRunOptions = {}): Promise<DreaminaPublicGenerationTask> {
try {
throwIfCancelled(options.signal);
const input = parseInput(value);
if (input.operation === "query_result") throw generationRequestInvalid();
const requestHash = trustedRequestFingerprint(options.requestFingerprint) ?? hashRequest(input);
const key = recordKey(this.ownerId, input.idempotencyKey);
const pending = this.enqueueInFlight.get(key);
if (pending) {
if (pending.requestHash !== requestHash) return Promise.reject(idempotencyConflict());
return waitForAbort(pending.promise, options.signal);
}
const promise = this.enqueueSerial.then(() => this.enqueueNew(key, requestHash, input, options))
.finally(() => {
if (this.enqueueInFlight.get(key)?.promise === promise) this.enqueueInFlight.delete(key);
});
this.enqueueSerial = promise.then(() => undefined, () => undefined);
this.enqueueInFlight.set(key, { requestHash, promise });
return waitForAbort(promise, options.signal);
} catch (error) {
return Promise.reject(error);
}
}
async getTask(idempotencyKey: string, options: DreaminaRuntimeRunOptions = {}): Promise<DreaminaPublicGenerationTask> {
throwIfCancelled(options.signal);
validateIdempotencyKey(idempotencyKey);
await this.load(options.signal);
const key = recordKey(this.ownerId, idempotencyKey);
const record = this.records.get(key);
if (!record?.taskVersion || record.state === "deleted" || record.hidden) throw taskNotFound();
let task = this.asyncTasks.get(key);
if (record.state === "succeeded" && record.submitId && !task?.result) {
const durableResult = await this.readDurableResult(record).catch(() => undefined);
task = task ?? this.createRecoveredTask(key, record);
this.asyncTasks.set(key, task);
if (durableResult) this.resolvePreparedTask(task, durableResult);
else void this.reconciler.requestNow(record.idempotencyKey).catch(() => undefined);
}
return this.publicTask(record, task);
}
async refreshTask(idempotencyKey: string, options: DreaminaRuntimeRunOptions = {}): Promise<DreaminaPublicGenerationTask> {
throwIfCancelled(options.signal);
validateIdempotencyKey(idempotencyKey);
await this.loadState(options.signal);
const key = recordKey(this.ownerId, idempotencyKey);
const record = this.records.get(key);
if (!record?.taskVersion || record.state === "deleted" || record.hidden) throw taskNotFound();
if (!record.submitId || (record.state !== "accepted" && record.state !== "unknown" && record.state !== "succeeded")) {
if (isPublicTerminal(record.state)) return this.publicTask(record, this.asyncTasks.get(key));
throw submissionUnknown();
}
await this.reconciler.requestNow(record.idempotencyKey, options.signal);
await this.reconciler.start();
return this.publicTask(record, this.asyncTasks.get(key));
}
async listTasks(options: Pick<DreaminaRuntimeRunOptions, "signal"> = {}): Promise<DreaminaPublicGenerationTask[]> {
return (await this.listTaskPage({ limit: 100, signal: options.signal })).tasks;
}
async listTaskPage(options: {
limit?: number;
cursor?: string;
projectId?: string;
activeOnly?: boolean;
signal?: AbortSignal;
} = {}): Promise<{ tasks: DreaminaPublicGenerationTask[]; nextCursor?: string }> {
throwIfCancelled(options.signal);
await this.loadState(options.signal);
const limit = Number.isInteger(options.limit) ? Math.max(1, Math.min(100, options.limit as number)) : 50;
const projectId = options.projectId?.trim();
const cursor = options.cursor ? decodeTaskListCursor(options.cursor) : undefined;
const visible = [...this.records.entries()]
.filter(([, record]) => record.ownerId === this.ownerId && Boolean(record.taskVersion) && record.state !== "deleted" && !record.hidden)
.sort(([, left], [, right]) => right.updatedAt.localeCompare(left.updatedAt)
|| right.idempotencyKey.localeCompare(left.idempotencyKey))
.map(([key, record]) => {
const summary = { ...this.publicTask(record, this.asyncTasks.get(key)) };
delete summary.result;
return summary;
})
.filter((task) => !projectId || (task.context?.scope === "scoped" && task.context.projectId === projectId))
.filter((task) => !options.activeOnly || task.status === "queued" || task.status === "running")
.filter((task) => !cursor || task.updatedAt < cursor.updatedAt || (task.updatedAt === cursor.updatedAt && task.id < cursor.id));
const tasks = visible.slice(0, limit);
const last = tasks.at(-1);
return {
tasks,
...(last && visible.length > tasks.length ? { nextCursor: encodeTaskListCursor(last.updatedAt, last.id) } : {}),
};
}
async waitForTask(idempotencyKey: string, mode: "image" | "video", options: DreaminaRuntimeRunOptions = {}): Promise<DreaminaGenerationResult> {
throwIfCancelled(options.signal);
validateIdempotencyKey(idempotencyKey);
if (mode !== "image" && mode !== "video") throw generationRequestInvalid();
await this.load(options.signal);
// Startup recovery may yield while another Runtime commits a terminal result.
// Refresh the durable journal before this waiter classifies the task.
await this.loadState(options.signal);
const key = recordKey(this.ownerId, idempotencyKey);
const record = this.records.get(key);
if (!record?.taskVersion || record.mode !== mode) throw taskNotFound();
let task = this.asyncTasks.get(key);
if (record.state === "succeeded" && !task?.result) {
const durableResult = await this.readDurableResult(record).catch(() => undefined);
if (durableResult) {
task = task ?? this.createRecoveredTask(key, record);
this.asyncTasks.set(key, task);
this.resolvePreparedTask(task, durableResult);
}
}
if (!task && record.submitId && (record.state === "accepted" || record.state === "succeeded")) {
task = this.createRecoveredTask(key, record);
this.asyncTasks.set(key, task);
}
if (task?.result) return task.result;
if (record.state === "cancelled") throw cancelled();
if (record.state === "failed" || record.state === "unknown") throw taskFailed(record.errorCode);
if (!task || !record.submitId) throw taskFailed(record.errorCode);
await this.reconciler.requestNow(record.idempotencyKey, options.signal);
await this.reconciler.start();
const durableWaitController = new AbortController();
const durableWait = this.waitForDurableTerminal(key, mode, record.state, durableWaitController.signal);
try {
return await waitForAbort(Promise.race([task.completion, durableWait]), options.signal);
} finally {
durableWaitController.abort();
await durableWait.catch(() => undefined);
}
}
async cancelTask(idempotencyKey: string): Promise<DreaminaPublicGenerationTask> {
validateIdempotencyKey(idempotencyKey);
const key = recordKey(this.ownerId, idempotencyKey);
await this.stopQueueHeartbeat(key);
await this.load();
let finalRecord!: RuntimeRecord;
let pendingSubmission = false;
let cancelledNow = false;
await this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(key);
if (!record?.taskVersion) throw taskNotFound();
finalRecord = record;
if (isPublicTerminal(record.state)) return;
if (record.state === "pending") {
pendingSubmission = true;
return;
}
if (record.state === "accepted" && record.submitId) return;
const next: RuntimeRecord = {
...record,
state: "cancelled",
errorCode: "dreamina_cancelled",
updatedAt: this.now().toISOString(),
};
clearQueueLease(next);
this.records.set(key, next);
await this.persistUnlocked(lease);
finalRecord = next;
cancelledNow = true;
});
const task = this.asyncTasks.get(key);
if (pendingSubmission) {
task?.controller.abort();
throw submissionUnknown();
}
if (!cancelledNow) return this.publicTask(finalRecord, task);
const cancelledRecord = finalRecord;
task?.controller.abort();
const queuedIndex = this.queue.indexOf(key);
if (queuedIndex >= 0) this.queue.splice(queuedIndex, 1);
if (task && !task.settled) {
task.settled = true;
task.error = cancelled();
task.reject(task.error);
task.completion.catch(() => undefined);
await this.cleanupPreparedTask(task);
task.input = undefined;
}
this.releaseSlot(key);
return this.publicTask(cancelledRecord, task);
}
async deleteTask(idempotencyKey: string): Promise<{ deleted: true }> {
validateIdempotencyKey(idempotencyKey);
const key = recordKey(this.ownerId, idempotencyKey);
await this.stopQueueHeartbeat(key);
await this.loadState();
let task: PreparedAsyncTask | undefined;
let removeLocally = false;
await this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(key);
if (!record?.taskVersion || record.state === "deleted" || record.hidden) {
if (record?.state === "deleted" || record?.hidden) return;
throw taskNotFound();
}
task = this.asyncTasks.get(key);
removeLocally = record.state === "queued"
|| (record.state === "pending" && record.submissionPhase === "reserved");
const next: RuntimeRecord = {
...record,
...(removeLocally ? { state: "deleted" as const } : { hidden: true as const }),
updatedAt: this.now().toISOString(),
};
if (removeLocally) clearQueueLease(next);
this.records.set(key, next);
await this.persistUnlocked(lease);
});
if (!removeLocally) return { deleted: true };
task?.controller.abort();
const queuedIndex = this.queue.indexOf(key);
if (queuedIndex >= 0) this.queue.splice(queuedIndex, 1);
if (task && !task.settled) {
task.settled = true;
task.error = cancelled();
task.reject(task.error);
task.completion.catch(() => undefined);
await this.cleanupPreparedTask(task);
task.input = undefined;
}
this.releaseSlot(key);
this.asyncTasks.delete(key);
return { deleted: true };
}
async dispose() {
this.disposed = true;
this.queryShutdown.abort();
await Promise.allSettled([...this.queueHeartbeats.keys()].map((key) => this.stopQueueHeartbeat(key)));
await this.failOwnedQueuedTasksOnDispose();
await this.reconciler.dispose();
for (const task of this.asyncTasks.values()) task.controller.abort();
await Promise.allSettled([...this.asyncTasks.values()].map(async (task) => {
const pending: Promise<unknown>[] = [];
if (task.starting) pending.push(task.starting);
if (task.background) pending.push(task.background);
await Promise.allSettled(pending);
await this.cleanupPreparedTask(task);
task.input = undefined;
}));
await Promise.allSettled([...this.deferredCleanups.entries()].map(async ([key, cleanup]) => {
await cleanup();
this.deferredCleanups.delete(key);
}));
}
run(value: unknown, options: DreaminaRuntimeRunOptions = {}): Promise<DreaminaRuntimeResult> {
try {
throwIfCancelled(options.signal);
const input = parseInput(value);
if (input.operation === "query_result") throw generationRequestInvalid();
return this.generate(input, options.signal);
} catch (error) {
return Promise.reject(error);
}
}
async generateToResult(value: unknown, options: DreaminaRuntimeRunOptions = {}): Promise<DreaminaGenerationResult> {
throwIfCancelled(options.signal);
const input = parseInput(value);
if (input.operation === "query_result") throw generationRequestInvalid();
await this.enqueue(input, options);
return this.waitForTask(input.idempotencyKey, resultMode(input), options);
}
private async enqueueNew(
key: string,
requestHash: string,
input: DreaminaGenerationInput,
options: DreaminaRuntimeRunOptions,
) {
const signal = options.signal;
await this.load(signal);
const existing = this.records.get(key);
if (existing) {
if (existing.requestHash !== requestHash) throw idempotencyConflict();
if (!existing.taskVersion) throw submissionUnknown();
return this.publicTask(existing, this.asyncTasks.get(key));
}
const staged = await stageReferences(input, this.referenceRoots(), path.dirname(this.stateFile), signal);
const task = createPreparedTask(key, requestHash, staged.input, staged.cleanup);
try {
const createdAt = this.now().toISOString();
await this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const concurrent = this.records.get(key);
if (concurrent) {
if (concurrent.requestHash !== requestHash) throw idempotencyConflict();
throw submissionUnknown();
}
const queueTicket = this.nextQueueTicket;
if (!Number.isSafeInteger(queueTicket) || queueTicket < 1 || queueTicket >= Number.MAX_SAFE_INTEGER) throw stateInvalid();
this.nextQueueTicket = queueTicket + 1;
this.records.set(key, {
ownerId: this.ownerId,
idempotencyKey: input.idempotencyKey,
clientOperationId: options.clientOperationId ?? input.idempotencyKey,
context: options.taskContext ?? { scope: "legacy_unscoped" },
requestHash,
state: "queued",
queueOwnerId: this.reservationOwnerId,
queueExpiresAt: this.queueExpiry(),
queueTicket,
updatedAt: createdAt,
taskVersion: 1,
operation: input.operation,
mode: resultMode(input),
model: modelName(input),
createdAt,
});
await this.persistUnlocked(lease);
}, signal);
this.asyncTasks.set(key, task);
return await this.startTask(task, signal);
} catch (error) {
if (!this.asyncTasks.has(key)) {
const current = this.records.get(key);
if (current?.requestHash === requestHash && current.state === "queued") this.records.delete(key);
await this.cleanupPreparedTask(task);
task.input = undefined;
}
throw error;
}
}
private startTask(task: PreparedAsyncTask, signal?: AbortSignal): Promise<DreaminaPublicGenerationTask> {
const starting = this.startTaskNow(task, signal);
task.starting = starting;
void starting.finally(() => {
if (task.starting === starting) task.starting = undefined;
}).catch(() => undefined);
return starting;
}
private async startTaskNow(task: PreparedAsyncTask, signal?: AbortSignal): Promise<DreaminaPublicGenerationTask> {
if (this.disposed) throw cancelled();
const abort = () => task.controller.abort();
signal?.addEventListener("abort", abort, { once: true });
let reserved = false;
try {
if (signal?.aborted) task.controller.abort();
const reservation = await this.reserveSubmissionSlot(task, task.controller.signal);
reserved = Boolean(reservation);
if (!reservation) {
this.startQueueHeartbeat(task);
if (!this.queue.includes(task.key)) this.queue.push(task.key);
return this.publicTask(this.records.get(task.key)!, task);
}
await this.stopQueueHeartbeat(task.key);
this.activeTasks.add(task.key);
const stopHeartbeat = this.startReservationHeartbeat(task.key, task.requestHash, reservation);
const mode = resultMode(task.input!);
let accepted: { state: "accepted"; submitId: string };
try {
accepted = await this.submitPrepared(task, reservation, task.controller.signal);
} finally {
await stopHeartbeat();
}
const submitted = this.publicTask(this.records.get(task.key)!, task);
task.polling = true;
void this.waitForEnqueueDrain(task.controller.signal)
.then(() => this.reconciler.scheduleBackground(submitted.id, task.controller.signal))
.catch(() => undefined);
return submitted;
} catch (error) {
if (reserved && !(error instanceof DreaminaCliError
&& (error.code === "dreamina_cli_fenced" || error.code === "dreamina_account_session_changed"))) {
await this.failTask(task, errorCode(error));
this.releaseSlot(task.key);
}
throw error;
} finally {
signal?.removeEventListener("abort", abort);
}
}
private async reserveSubmissionSlot(task: PreparedAsyncTask, signal?: AbortSignal) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(task.key);
if (!record || record.requestHash !== task.requestHash) throw stateInvalid();
if (record.state !== "queued") throw submissionUnknown();
if (!this.hasQueuedGenerationPriority(record)) return undefined;
const reservation: SubmissionReservation = {
reservationId: crypto.randomUUID(),
reservationOwnerId: this.reservationOwnerId,
};
clearQueueLease(record);
Object.assign(record, {
state: "pending",
...reservation,
submissionPhase: "reserved" as const,
reservationExpiresAt: this.reservationExpiry(),
updatedAt: this.now().toISOString(),
});
await this.persistUnlocked(lease);
return reservation;
}, signal);
}
private reservationExpiry() {
return new Date(this.now().getTime() + this.reservationLeaseMs).toISOString();
}
private queueExpiry() {
return new Date(this.now().getTime() + this.reservationLeaseMs).toISOString();
}
private startQueueHeartbeat(task: PreparedAsyncTask) {
if (this.reservationHeartbeatMs === 0 || this.queueHeartbeats.has(task.key)) return;
let stopped = false;
const shutdown = new AbortController();
let refresh = Promise.resolve();
const renew = async () => {
if (stopped) return;
this.onQueueHeartbeatWait?.("started", task.key);
try {
await this.renewQueueLease(task.key, task.requestHash, shutdown.signal);
this.promoteQueueHead(task.key);
} catch {
stopped = true;
clearInterval(timer);
this.queueHeartbeats.delete(task.key);
} finally {
this.onQueueHeartbeatWait?.("settled", task.key);
}
};
const timer = setInterval(() => {
refresh = refresh.then(renew);
}, this.reservationHeartbeatMs);
timer.unref();
this.queueHeartbeats.set(task.key, async () => {
if (stopped) return;
stopped = true;
shutdown.abort();
clearInterval(timer);
await refresh;
this.queueHeartbeats.delete(task.key);
});
}
private async stopQueueHeartbeat(key: string) {
await this.queueHeartbeats.get(key)?.();
}
private async renewQueueLease(key: string, requestHash: string, signal: AbortSignal) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(key);
if (!record || record.requestHash !== requestHash || record.state !== "queued"
|| record.queueOwnerId !== this.reservationOwnerId || !liveQueueLease(record, this.now())) throw cliFenced();
record.queueExpiresAt = this.queueExpiry();
await this.persistUnlocked(lease);
}, signal);
}
private startReservationHeartbeat(key: string, requestHash: string, reservation: SubmissionReservation) {
if (this.reservationHeartbeatMs === 0) return async () => undefined;
let stopped = false;
let lost = false;
let refresh = Promise.resolve();
const renew = async () => {
if (stopped || lost) return;
try {
await this.renewReservation(key, requestHash, reservation);
} catch {
lost = true;
clearInterval(timer);
}
};
const timer = setInterval(() => {
refresh = refresh.then(renew);
}, this.reservationHeartbeatMs);
timer.unref();
return async () => {
if (stopped) return;
stopped = true;
clearInterval(timer);
await refresh;
};
}
private async renewReservation(key: string, requestHash: string, reservation: SubmissionReservation) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(key);
if (!matchesReservation(record, requestHash, reservation) || !liveReservation(record, this.now())) throw cliFenced();
record.reservationExpiresAt = this.reservationExpiry();
await this.persistUnlocked(lease);
});
}
private async submitPrepared(
task: PreparedAsyncTask,
reservation: SubmissionReservation,
signal?: AbortSignal,
): Promise<{ state: "accepted"; submitId: string }> {
let spawned = false;
let fenceEpoch: number | undefined;
try {
throwIfCancelled(signal);
const expectedSession = await this.ensureReady(signal);
const installation = await this.discover(signal);
if (!installation.installed) throw new DreaminaCliError("dreamina_missing", "Dreamina CLI is not installed", 404);
const invocation = await this.arbiter.acquire({ signal, expectedSession: expectedSession ?? undefined });
fenceEpoch = invocation.fenceEpoch;
let submitId: string;
try {
await this.commitInvocationFence(task, reservation, fenceEpoch, expectedSession ?? undefined, signal);
const result = await this.runProcess({
executable: installation.executable,
args: generationArguments(task.input!),
timeoutMs: 45_000,
completeOnJsonOutput: isSubmitReceiptOutput,
env: this.env,
signal,
onSpawn: () => { spawned = true; },
});
spawned = true;
const submitted = submittedReceipt(result);
if (!submitted.ok) throw submitFailure(submitted.code);
submitId = submitted.submitId;
await invocation.assertCurrent(expectedSession ?? undefined);
await this.commitAcceptedReceipt(
task,
reservation,
fenceEpoch,
submitId,
() => invocation.assertCurrent(expectedSession ?? undefined),
expectedSession ?? undefined,
signal,
);
} finally {
await invocation.release();
}
// Receipt persistence is the irreversible provider boundary. Local staging
// maintenance after this point must never rewrite provider acceptance to unknown.
const cleanupSucceeded = await this.cleanupPreparedTask(task);
task.input = undefined;
if (!cleanupSucceeded) await this.persistCleanupWarning(task, fenceEpoch).catch(() => undefined);
return { state: "accepted", submitId };
} catch (error) {
if (error instanceof DreaminaCliError && (error.code === "dreamina_cli_fenced" || error.code === "dreamina_account_session_changed")) {
throw error;
}
const code = submitFailureCode(error, spawned);
let durableCommitError: DreaminaCliError | undefined;
await this.commitSubmissionFailure(task, reservation, fenceEpoch, spawned, code).catch((commitError) => {
if (isDurableJournalCommitError(commitError)) durableCommitError = commitError;
});
await this.cleanupPreparedTask(task);
task.input = undefined;
if (durableCommitError) throw durableCommitError;
if (!spawned && error instanceof DreaminaCliError && error.code !== "dreamina_submission_unknown") {
this.asyncTasks.delete(task.key);
if (error.code === "dreamina_spawn_failed") throw submitFailure("dreamina_submit_spawn_failed");
throw error;
}
throw submitFailure(code);
}
}
private async commitInvocationFence(
task: PreparedAsyncTask,
reservation: SubmissionReservation,
fenceEpoch: number,
expectedSession?: DreaminaCliSessionSnapshot,
signal?: AbortSignal,
) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(task.key);
if (!matchesReservation(record, task.requestHash, reservation) || !liveReservation(record, this.now())) throw cliFenced();
if (record.fenceEpoch !== undefined && record.fenceEpoch > fenceEpoch) throw cliFenced();
record.fenceEpoch = fenceEpoch;
if (expectedSession?.accountBinding) {
if (record.accountBinding && record.accountBinding !== expectedSession.accountBinding) throw accountChanged();
record.accountBinding = expectedSession.accountBinding;
record.sessionEpoch = expectedSession.sessionEpoch;
}
record.submissionPhase = "spawn_permitted";
record.reservationExpiresAt = this.reservationExpiry();
await this.persistUnlocked(lease);
return record;
}, signal);
}
private async commitAcceptedReceipt(
task: PreparedAsyncTask,
reservation: SubmissionReservation,
fenceEpoch: number,
submitId: string,
assertInvocationCurrent: () => Promise<void>,
expectedSession?: DreaminaCliSessionSnapshot,
signal?: AbortSignal,
) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(task.key);
if (!matchesReservation(record, task.requestHash, reservation)
|| record.submissionPhase !== "spawn_permitted" || record.fenceEpoch !== fenceEpoch) throw cliFenced();
if (expectedSession?.accountBinding
&& (record.accountBinding !== expectedSession.accountBinding || record.sessionEpoch !== expectedSession.sessionEpoch)) throw accountChanged();
await assertInvocationCurrent();
Object.assign(record, { state: "accepted", submitId, updatedAt: this.now().toISOString() });
clearReservation(record);
await this.persistUnlocked(lease);
return record;
}, signal);
}
private async commitSubmissionFailure(
task: PreparedAsyncTask,
reservation: SubmissionReservation,
fenceEpoch: number | undefined,
spawned: boolean,
code: string,
) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(task.key);
if (!matchesReservation(record, task.requestHash, reservation)) throw cliFenced();
if (fenceEpoch !== undefined && record.fenceEpoch !== fenceEpoch) throw cliFenced();
if (!spawned) this.records.delete(task.key);
else {
Object.assign(record, { state: "unknown", errorCode: code, updatedAt: this.now().toISOString() });
clearReservation(record);
}
await this.persistUnlocked(lease);
});
}
private async cleanupPreparedTask(task: PreparedAsyncTask) {
try {
await task.cleanup();
this.deferredCleanups.delete(task.cleanupId);
return true;
} catch {
this.deferredCleanups.set(task.cleanupId, task.cleanup);
return false;
}
}
private async observeReconciliation(record: Readonly<RuntimeRecord>, signal?: AbortSignal): Promise<DreaminaReconcileObservation> {
if (!record.submitId) throw new DreaminaCliError("dreamina_submission_unknown", "Dreamina reconciliation requires a durable receipt", 409);
const queried = await this.query(record.submitId, signal, undefined, record.accountBinding);
const status = queryOfficialStatus(queried);
const observedAt = this.now().toISOString();
if (status !== "completed") return { status, observedAt };
try {
const mode = record.mode ?? "video";
const result = await this.materializeCompletedResultOnce(record.submitId, mode, signal, record.accountBinding);
const providerOutputs = await this.artifactStore.persistResult(result, this.artifactBinding(record, mode));
return { status, observedAt, result, providerOutputs };
} catch (error) {
return { status, observedAt, retryCode: reconciliationRetryCode(error) };
}
}
private async materializeCompletedResultOnce(
submitId: string,
mode: "image" | "video",
signal?: AbortSignal,
accountBinding?: string,
) {
await fs.mkdir(this.generationRoot, { recursive: true, mode: 0o700 });
const runRoot = await fs.mkdtemp(path.join(this.generationRoot, "run-"));
const outputRoot = path.join(runRoot, "output");
await fs.mkdir(outputRoot, { mode: 0o700 });
try {
const materialized = await this.query(submitId, signal, outputRoot, accountBinding);
if (queryState(materialized) !== "completed") {
throw new DreaminaCliError("dreamina_query_materialization_retry", "Dreamina result materialization is not ready", 502);
}
return await collectDreaminaGenerationResult(outputRoot, mode);
} finally {
await fs.rm(runRoot, { recursive: true, force: true });
}
}
private async handleReconcileApplied(event: DreaminaReconcileApplied) {
const key = recordKey(this.ownerId, event.record.idempotencyKey);
this.records.set(key, { ...event.record, ...(event.record.pollLease ? { pollLease: { ...event.record.pollLease } } : {}) });
const task = this.asyncTasks.get(key);
if (event.syncErrorCode === "dreamina_account_session_changed") {
if (task && !task.settled) {
task.polling = false;
task.settled = true;
task.error = accountChanged();
task.reject(task.error);
task.completion.catch(() => undefined);
if (this.asyncTasks.get(key) === task) this.asyncTasks.delete(key);
}
return;
}
if (event.record.state === "accepted") {
if (task) task.polling = true;
return;
}
if (event.record.state === "succeeded") {
this.releaseSlot(key);
if (!task) return;
const durableResult = await this.readDurableResult(event.record).catch(() => undefined);
task.polling = !durableResult;
if (durableResult) this.resolvePreparedTask(task, durableResult);
return;
}
if (event.record.state === "failed" || event.record.state === "cancelled") {
this.releaseSlot(key);
if (!task) return;
task.polling = false;
if (!task.settled) {
task.settled = true;
task.error = event.record.state === "cancelled" ? cancelled() : taskFailed(event.record.errorCode);
task.reject(task.error);
task.completion.catch(() => undefined);
}
}
}
private async persistCleanupWarning(task: PreparedAsyncTask, fenceEpoch: number) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(task.key);
if (!record || record.state !== "accepted" || record.fenceEpoch !== fenceEpoch) throw cliFenced();
Object.assign(record, { errorCode: "dreamina_reference_cleanup_failed", updatedAt: this.now().toISOString() });
await this.persistUnlocked(lease);
});
}
private async waitForEnqueueDrain(signal?: AbortSignal) {
let pending: Promise<void>;
do {
throwIfCancelled(signal);
pending = this.enqueueSerial;
await waitForAbort(pending, signal);
} while (this.enqueueInFlight.size > 0 || pending !== this.enqueueSerial);
}
private async failTask(task: PreparedAsyncTask, code: string) {
await this.stopQueueHeartbeat(task.key);
const record = this.records.get(task.key);
if (record && !isPublicTerminal(record.state)) {
const final = await this.transitionTask(task.key, (current) => {
const next: RuntimeRecord = { ...current, state: "failed", errorCode: code, updatedAt: this.now().toISOString() };
clearQueueLease(next);
return next;
});
if (final.state === "succeeded") {
task.polling = false;
if (this.asyncTasks.get(task.key) === task) this.asyncTasks.delete(task.key);
}
}
if (!task.settled) {
task.settled = true;
task.error ||= taskFailed(code);
task.reject(task.error);
task.completion.catch(() => undefined);
}
}
private async failOwnedQueuedTasksOnDispose() {
if (![...this.records.values()].some((record) => record.state === "queued"
&& record.queueOwnerId === this.reservationOwnerId)) return;
await this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
let changed = false;
for (const record of this.records.values()) {
if (record.state !== "queued" || record.queueOwnerId !== this.reservationOwnerId) continue;
record.state = "failed";
record.errorCode = "dreamina_interrupted_before_submission";
record.updatedAt = this.now().toISOString();
clearQueueLease(record);
changed = true;
}
if (changed) await this.persistUnlocked(lease);
});
}
private promoteQueueHead(expectedKey?: string) {
while (this.queue.length) {
const nextKey = this.queue[0]!;
if (expectedKey && nextKey !== expectedKey) return;
const next = this.asyncTasks.get(nextKey);
if (!next || this.records.get(nextKey)?.state !== "queued") {
this.queue.shift();
continue;
}
if (next.starting) return;
void this.startTask(next).then((result) => {
if (result.status !== "queued" && this.queue[0] === nextKey) this.queue.shift();
}).catch(() => {
if (this.records.get(nextKey)?.state !== "queued" && this.queue[0] === nextKey) this.queue.shift();
});
return;
}
}
private releaseSlot(key: string) {
if (!this.activeTasks.delete(key)) return;
this.promoteQueueHead();
}
private publicTask(record: RuntimeRecord, task?: PreparedAsyncTask): DreaminaPublicGenerationTask {
if (!record.taskVersion || !record.operation || !record.mode || !record.model || !record.createdAt) throw stateInvalid();
const state = publicState(record.state, dreaminaTaskFromRuntimeJournal(record));
if (record.state === "accepted" && record.officialStatus === "processing") {
state.stage = "generating";
state.progress = undefined;
}
return {
id: record.idempotencyKey,
...(record.clientOperationId ? { clientOperationId: record.clientOperationId } : {}),
...(record.context ? { context: record.context } : {}),
provider: "dreamina-cli",
mode: record.mode,
operation: record.operation as DreaminaGenerationInput["operation"],
model: record.model,
status: state.status,
stage: state.stage,
...(state.progress === undefined ? {} : { progress: state.progress }),
receiptRecorded: Boolean(record.submitId),
...(record.errorCode ? { errorCode: record.errorCode } : {}),
...(record.officialStatus ? { officialStatus: record.officialStatus } : {}),
createdAt: record.createdAt,
updatedAt: record.updatedAt,
...(task?.result ? { result: task.result } : {}),
};
}
private async transitionTask(key: string, update: (record: RuntimeRecord) => RuntimeRecord) {
return this.withStateLock(async (lease) => {
await this.reloadFromDisk(lease);
const record = this.records.get(key);
if (!record?.taskVersion) throw stateInvalid();
// The fresh record read under this lease is authoritative. A stale Runtime may
// observe accepted work, but it can never overwrite a terminal result written
// by another Runtime instance.
if (isPublicTerminal(record.state)) return record;
const next = update(record);
this.records.set(key, next);
await this.persistUnlocked(lease);
return next;
});
}
async resumeToResult(
idempotencyKey: string,
mode: "image" | "video",
options: DreaminaRuntimeRunOptions = {},
): Promise<DreaminaGenerationResult> {
throwIfCancelled(options.signal);
if (!/^[A-Za-z0-9._:-]{16,120}$/.test(idempotencyKey) || (mode !== "image" && mode !== "video")) throw generationRequestInvalid();
return this.waitForTask(idempotencyKey, mode, options);
}
private generate(input: DreaminaGenerationInput, signal?: AbortSignal, trustedFingerprint?: string) {
const key = recordKey(this.ownerId, input.idempotencyKey);
const requestHash = trustedFingerprint ?? hashRequest(input);
const active = this.inFlight.get(key);
if (active) {
if (active.requestHash !== requestHash) return Promise.reject(idempotencyConflict());
return subscribeToSubmission(active, signal);
}
const controller = new AbortController();
const submission = {
requestHash,
controller,
waiters: 0,
settled: false,
} as InFlightSubmission;
submission.promise = this.load(controller.signal).then(() => {
throwIfCancelled(controller.signal);
const existing = this.records.get(key);
if (existing) {
if (existing.requestHash !== requestHash) throw idempotencyConflict();
if (existing.state === "accepted" && existing.submitId) {
return { state: "accepted" as const, submitId: existing.submitId };
}
throw submissionUnknown();
}
return this.submitWithLock(key, requestHash, input, controller.signal);
})
.finally(() => {
submission.settled = true;
if (this.inFlight.get(key) === submission) this.inFlight.delete(key);
});
this.inFlight.set(key, submission);
return subscribeToSubmission(submission, signal);
}
private async submitWithLock(
key: string,
requestHash: string,
input: DreaminaGenerationInput,
signal?: AbortSignal,
) {
return this.withStateLock(async (lease) => {
throwIfCancelled(signal);
await this.reloadFromDisk(lease);
const existing = this.records.get(key);
if (existing) {
if (existing.requestHash !== requestHash) throw idempotencyConflict();
if (existing.state === "accepted" && existing.submitId) {
return { state: "accepted" as const, submitId: existing.submitId };
}
throw submissionUnknown();
}
if (!this.hasGenerationCapacity()) throw generationCapacityFull();
return await this.submit(key, requestHash, input, lease, signal);
}, signal);
}
private async submit(
key: string,
requestHash: string,
input: DreaminaGenerationInput,
lease: StateLockLease,
signal?: AbortSignal,
): Promise<DreaminaRuntimeResult> {
throwIfCancelled(signal);
await lease.assertOwned();
const staged = await stageReferences(input, this.referenceRoots(), path.dirname(this.stateFile), signal);
const cleanupId = crypto.randomUUID();
let receiptCommitted = false;
let primaryError: unknown;
try {
throwIfCancelled(signal);
const expectedSession = await this.ensureReady(signal);
throwIfCancelled(signal);
await lease.assertOwned();
const installation = await this.discover(signal);
throwIfCancelled(signal);
await lease.assertOwned();
if (!installation.installed) throw new DreaminaCliError("dreamina_missing", "未检测到 Dreamina CLI", 404);
const record: RuntimeRecord = {
ownerId: this.ownerId,
idempotencyKey: input.idempotencyKey,
requestHash,
state: "pending",
updatedAt: this.now().toISOString(),
};
this.records.set(key, record);
await this.persistUnlocked(lease);
let spawned = false;
try {
throwIfCancelled(signal);
await lease.assertOwned();
const invocation = await this.arbiter.acquire({ signal, expectedSession: expectedSession ?? undefined });
record.fenceEpoch = invocation.fenceEpoch;
if (expectedSession?.accountBinding) {
record.accountBinding = expectedSession.accountBinding;
record.sessionEpoch = expectedSession.sessionEpoch;
}
await this.persistUnlocked(lease);
let submitId: string;
try {
const result = await this.runProcess({
executable: installation.executable,
args: generationArguments(staged.input),
timeoutMs: 45_000,
completeOnJsonOutput: isSubmitReceiptOutput,
env: this.env,
signal,
onSpawn: () => { spawned = true; },
});
// A returned process result proves that process creation was attempted even
// when an injected runner did not call onSpawn.
spawned = true;
const submitted = submittedReceipt(result);
if (!submitted.ok) throw submitFailure(submitted.code);
submitId = submitted.submitId;
await invocation.assertCurrent(expectedSession ?? undefined);
Object.assign(record, {
state: "accepted",
submitId,
updatedAt: this.now().toISOString(),
});
await this.persistUnlocked(lease);
receiptCommitted = true;
} finally {
await invocation.release();
}
try {
await staged.cleanup();
this.deferredCleanups.delete(cleanupId);
} catch {
this.deferredCleanups.set(cleanupId, staged.cleanup);
Object.assign(record, {
errorCode: "dreamina_reference_cleanup_failed",
updatedAt: this.now().toISOString(),
});
try { await this.persistUnlocked(lease); } catch {
// Provider acceptance is already durable; a maintenance-warning write
// failure cannot turn this paid submit into an unknown/retryable result.
}
}
return { state: "accepted", submitId };
} catch (error) {
if (error instanceof DreaminaCliError && error.code === "dreamina_cli_fenced") throw error;
if (receiptCommitted && record.state === "accepted" && record.submitId) {
this.deferredCleanups.set(cleanupId, staged.cleanup);
return { state: "accepted", submitId: record.submitId };
}
if (!spawned && error instanceof DreaminaCliError && error.code === "dreamina_state_fenced") {
this.records.delete(key);
throw error;
}
if (!spawned && error instanceof DreaminaCliError && error.code !== "dreamina_submission_unknown") {
this.records.delete(key);
await this.persistUnlocked(lease);
throw error;
}
const code = submitFailureCode(error, spawned);
Object.assign(record, { state: "unknown", errorCode: code, updatedAt: this.now().toISOString() });
delete record.submitId;
try { await this.persistUnlocked(lease); } catch {
// The pending receipt was fsynced before spawn, so a lost lease or second
// persistence failure must stay non-retryable until recovery.
}
throw submitFailure(code);
}
} catch (error) {
primaryError = error;
throw error;
} finally {
if (!receiptCommitted) {
try {
await staged.cleanup();
this.deferredCleanups.delete(cleanupId);
} catch (cleanupError) {
this.deferredCleanups.set(cleanupId, staged.cleanup);
if (primaryError === undefined) throw cleanupError;
}
}
}
}
private async query(
submitId: string,
signal?: AbortSignal,
downloadDirectory?: string,
accountBinding?: string,
): Promise<DreaminaRuntimeQueryResult> {
const combined = combineAbortSignals(signal, this.queryShutdown.signal);
let invocation: Awaited<ReturnType<DreaminaCliArbiter["acquire"]>> | undefined;
try {
throwIfCancelled(combined.signal);
const expectedSession = await this.ensureReady(combined.signal);
if (accountBinding !== undefined) {
if (!expectedSession || expectedSession.accountBinding !== accountBinding) throw accountChanged();
} else if (expectedSession !== undefined) {
// A session-aware Runtime must not guess which account owns an unbound legacy receipt.
throw accountChanged();
}
throwIfCancelled(combined.signal);
const installation = await this.discover(combined.signal);
throwIfCancelled(combined.signal);
if (!installation.installed) throw new DreaminaCliError("dreamina_missing", "未检测到 Dreamina CLI", 404);
// The cross-process arbiter covers this one CLI invocation only; official work
// remains outside the lease after the process returns.
invocation = await this.arbiter.acquire({ signal: combined.signal, expectedSession: expectedSession ?? undefined });
throwIfCancelled(combined.signal);
const result = await this.runProcess({
executable: installation.executable,
args: [
"query_result",
`--submit_id=${submitId}`,
...(downloadDirectory ? [`--download_dir=${exactGenerationDirectory(this.generationRoot, downloadDirectory)}`] : []),
],
timeoutMs: 30_000,
completeOnJsonOutput: downloadDirectory ? undefined : isCompleteQueryOutput,
env: this.env,
signal: combined.signal,
});
await invocation.assertCurrent(expectedSession ?? undefined);
if (result.exitCode !== 0) throw new DreaminaCliError("dreamina_query_failed", "Dreamina 任务查询失败", 502);
return { state: "query", result: safeJsonResult(result.stdout) };
} finally {
await invocation?.release();
combined.dispose();
}
}
private waitForDurableTerminal(
key: string,
mode: "image" | "video",
initialState: RuntimeRecord["state"],
signal: AbortSignal,
): Promise<DreaminaGenerationResult> {
const combined = combineAbortSignals(signal, this.queryShutdown.signal);
return new Promise<DreaminaGenerationResult>((resolve, reject) => {
let settled = false;
let checking = false;
let checkAgain = false;
let abortPending = false;
let watcher: { close(): void } | undefined;
let fallback: { cancel(): void } | undefined;
const closeSignals = () => {
watcher?.close();
watcher = undefined;
fallback?.cancel();
fallback = undefined;
};
const finish = (action: () => void) => {
if (settled) return;
settled = true;
combined.signal.removeEventListener("abort", onAbort);
closeSignals();
combined.dispose();
action();
};
const onAbort = () => {
closeSignals();
if (checking) {
abortPending = true;
checkAgain = false;
return;
}
finish(() => reject(cancelled()));
};
const scheduleFallback = () => {
if (settled || abortPending || combined.signal.aborted || fallback) return;
fallback = this.scheduleDurableReload(() => {
fallback = undefined;
void check();
}, this.durableReloadIntervalMs);
};
const check = async () => {
if (settled) return;
if (checking) {
checkAgain = true;
return;
}
checking = true;
do {
checkAgain = false;
try {
await this.loadState();
if (combined.signal.aborted) {
abortPending = true;
break;
}
const record = this.records.get(key);
if (!record?.taskVersion || record.mode !== mode) {
finish(() => reject(taskNotFound()));
} else if (record.state === "succeeded") {
const durableResult = await this.readDurableResult(record).catch(() => undefined);
if (durableResult) finish(() => resolve(durableResult));
else if (initialState !== "succeeded") {
void this.reconciler.requestNow(record.idempotencyKey).catch(() => undefined);
}
} else if (record.state === "cancelled") {
finish(() => reject(cancelled()));
} else if (record.state === "failed" || record.state === "unknown" || record.state === "deleted") {
finish(() => reject(taskFailed(record.errorCode)));
}
} catch (error) {
if (combined.signal.aborted) abortPending = true;
else finish(() => reject(error));
}
} while (checkAgain && !settled && !abortPending);
checking = false;
if (abortPending && !settled) finish(() => reject(cancelled()));
else scheduleFallback();
};
const stateName = path.basename(this.stateFile);
try {
watcher = this.watchStateFile((filename) => {
if (filename !== null && filename !== stateName) return;
void check();
});
} catch {
// The durable reload timer remains authoritative when fs.watch is unavailable.
}
combined.signal.addEventListener("abort", onAbort, { once: true });
if (combined.signal.aborted) onAbort();
else {
scheduleFallback();
void check();
}
});
}
private load(signal?: AbortSignal) {
return this.loadState(signal).then(() => this.startRecoveryOnce());
}
private loadState(signal?: AbortSignal) {
return waitForAbort(this.withStateLock((lease) => this.reloadFromDisk(lease)), signal);
}
private async startRecoveryOnce() {
if (this.recoveryStarted) return this.reconciler.start();
this.recoveryStarted = true;
for (const [key, record] of this.records) {
if (!record.taskVersion || !record.submitId || (record.state !== "accepted" && record.state !== "succeeded")) continue;
const task = this.createRecoveredTask(key, record);
this.asyncTasks.set(key, task);
task.polling = true;
if (record.state === "accepted") this.activeTasks.add(key);
}
await this.artifactStore.scavenge([...this.records.values()].flatMap((record) => record.providerOutputs ?? []));
await this.reconciler.start();
}
private async reloadFromDisk(lease: StateLockLease) {
await lease.assertOwned();
await recoverStateReplacement(this.stateFile, this.ownerId, lease);
await lease.assertOwned();
await scavengeStaleStagingDirectories(path.dirname(this.stateFile), lease);
await lease.assertOwned();
const disk = await readRuntimeDiskState(this.stateFile, this.ownerId);
if (!disk) {
if (this.records.size === 0) return;
throw stateInvalid();
}
let recovered = false;
const next = new Map<string, RuntimeRecord>();
for (const value of disk.records) {
const record = { ...value };
const key = recordKey(record.ownerId, record.idempotencyKey);
if (record.state === "pending" && !liveReservation(record, this.now())) {
if (record.submissionPhase === "reserved" && record.reservationId && record.reservationOwnerId) {
record.state = "failed";
record.errorCode = "dreamina_interrupted_before_submission";
} else {
record.state = "unknown";
if (record.taskVersion) record.errorCode = "dreamina_submission_unknown";
}
record.updatedAt = this.now().toISOString();
clearReservation(record);
recovered = true;
} else if (record.taskVersion && record.state === "queued" && !liveQueueLease(record, this.now())) {
record.state = "failed";
record.errorCode = "dreamina_interrupted_before_submission";
record.updatedAt = this.now().toISOString();
clearQueueLease(record);
recovered = true;
}
next.set(key, record);
}
for (const [key, current] of this.records) {
const incoming = next.get(key);
if (!incoming || incoming.requestHash !== current.requestHash || stateRank(incoming.state) < stateRank(current.state)) {
throw stateInvalid();
}
}
this.records.clear();
for (const [key, record] of next) this.records.set(key, record);
this.nextQueueTicket = Math.max(
disk.nextQueueTicket ?? 1,
...disk.records.map((record) => (record.queueTicket ?? 0) + 1),
);
if (recovered) await this.persistUnlocked(lease);
}
private hasGenerationCapacity() {
return [...this.records.values()].filter(reservesOfficialSlot).length < this.maxActiveTasks;
}
private hasQueuedGenerationPriority(record: RuntimeRecord) {
const available = this.maxActiveTasks - [...this.records.values()].filter(reservesOfficialSlot).length;
if (available <= 0) return false;
return [...this.records.values()]
.filter((candidate) => liveQueueLease(candidate, this.now()))
.sort(compareDurableQueueOrder)
.slice(0, available)
.some((candidate) => candidate === record);
}
private async persistUnlocked(lease: StateLockLease) {
const payload: RuntimeDiskState = {
version: 1,
records: [...this.records.values()].sort((left, right) => left.idempotencyKey.localeCompare(right.idempotencyKey)),
nextQueueTicket: this.nextQueueTicket,
};
await persistRuntimeDiskState(this.stateFile, this.ownerId, payload, lease);
}
private async withStateLock<T>(action: (lease: StateLockLease) => Promise<T>, signal?: AbortSignal) {
const release = await acquireStateLock(this.stateFile, signal);
try { return await action(release); } finally { await release(); }
}
private artifactBinding(record: Readonly<RuntimeRecord>, mode: "image" | "video") {
return {
ownerId: record.ownerId,
idempotencyKey: record.idempotencyKey,
...(record.accountBinding ? { accountBinding: record.accountBinding } : {}),
...(record.fenceEpoch ? { fenceEpoch: record.fenceEpoch } : {}),
mode,
};
}
private async readDurableResult(record: Readonly<RuntimeRecord>) {
if (!record.taskVersion || !record.mode || !record.providerOutputs?.length) return undefined;
const projected = await this.taskProjector.projectJournalVersion(
record.idempotencyKey,
record.journalVersion ?? 1,
);
if (!projected || projected.visibility === "deleted"
|| projected.projectedJournalVersion < (record.journalVersion ?? 1)
|| projected.outputs.length !== record.providerOutputs.length) return undefined;
const outputs = projected.outputs.map((output) => {
if ((output.mediaType !== "image" && output.mediaType !== "video") || !output.providerArtifactRef) {
throw stateInvalid();
}
return {
outputIndex: output.outputIndex,
mediaType: output.mediaType,
providerArtifactRef: output.providerArtifactRef,
};
});
return this.artifactStore.readResult(outputs, this.artifactBinding(record, record.mode));
}
private resolvePreparedTask(task: PreparedAsyncTask, result: DreaminaGenerationResult) {
task.result = result;
task.polling = false;
if (task.settled) return;
task.settled = true;
task.resolve(result);
}
private createRecoveredTask(key: string, record: RuntimeRecord) {
return createPreparedTask(key, record.requestHash, undefined, async () => undefined);
}
}
function parseInput(value: unknown): DreaminaCliInput {
const result = dreaminaCliInputSchema.safeParse(value);
if (!result.success) throw new DreaminaCliError("dreamina_request_invalid", "Dreamina 请求参数无效", 400);
return result.data;
}
function resultMode(input: DreaminaGenerationInput): "image" | "video" {
return input.operation === "text2image" || input.operation === "image2image" || input.operation === "image_upscale" ? "image" : "video";
}
function modelName(input: DreaminaGenerationInput) {
if ("modelVersion" in input && input.modelVersion) return input.modelVersion;
if (input.operation === "text2image" || input.operation === "image2image") return "5.0";
if (input.operation === "image_upscale") return "image-upscale";
if (input.operation === "text2video") return "seedance2.0fast";
return "seedance2.0_vip";
}
function createPreparedTask(
key: string,
requestHash: string,
input: DreaminaGenerationInput | undefined,
cleanup: () => Promise<void>,
): PreparedAsyncTask {
let resolve!: (result: DreaminaGenerationResult) => void;
let reject!: (error: unknown) => void;
const completion = new Promise<DreaminaGenerationResult>((onResolve, onReject) => {
resolve = onResolve;
reject = onReject;
});
completion.catch(() => undefined);
return {
key,
requestHash,
input,
cleanup,
cleanupId: crypto.randomUUID(),
controller: new AbortController(),
completion,
resolve,
reject,
settled: false,
polling: false,
};
}
function publicState(
journalState: RuntimeRecord["state"],
state: DreaminaTaskState,
): Pick<DreaminaPublicGenerationTask, "status" | "stage" | "progress"> {
// Task 1 freezes product semantics without changing the legacy public DTO. Old Runtime
// records can encode local sync failure or stopped waiting as failed/cancelled; Task 4
// removes that transport-era presentation after shared reconciliation owns the lifecycle.
if (journalState === "failed") return { status: "failed", stage: "failed" };
if (journalState === "cancelled") return { status: "cancelled", stage: "cancelled" };
if (state.lifecycle === "QUEUED_LOCAL") return { status: "queued", stage: "queued", progress: 0 };
if (state.lifecycle === "SUBMITTING") return { status: "running", stage: "submitting" };
if (state.lifecycle === "SUBMISSION_UNCERTAIN") return { status: "failed", stage: "submission_unknown" };
if (state.lifecycle === "ACCEPTED") return { status: "running", stage: "submitted", progress: 10 };
if (state.lifecycle === "RUNNING") return { status: "running", stage: "generating" };
if (state.terminalOutcome === "SUCCEEDED") return { status: "succeeded", stage: "succeeded", progress: 100 };
return { status: "failed", stage: "failed" };
}
function isPublicTerminal(state: RuntimeRecord["state"]) {
return state === "succeeded" || state === "failed" || state === "cancelled" || state === "unknown" || state === "deleted";
}
function reservesOfficialSlot(record: RuntimeRecord) {
return record.state === "pending" || record.state === "unknown" || record.state === "accepted";
}
function liveQueueLease(record: RuntimeRecord, now: Date) {
return record.state === "queued"
&& typeof record.queueOwnerId === "string"
&& typeof record.queueExpiresAt === "string"
&& Date.parse(record.queueExpiresAt) > now.getTime();
}
function clearQueueLease(record: RuntimeRecord) {
delete record.queueOwnerId;
delete record.queueExpiresAt;
delete record.queueTicket;
}
function compareDurableQueueOrder(left: RuntimeRecord, right: RuntimeRecord) {
if (left.queueTicket !== undefined && right.queueTicket !== undefined) return left.queueTicket - right.queueTicket;
if (left.queueTicket === undefined && right.queueTicket !== undefined) return -1;
if (left.queueTicket !== undefined && right.queueTicket === undefined) return 1;
const created = Date.parse(left.createdAt ?? left.updatedAt) - Date.parse(right.createdAt ?? right.updatedAt);
return created || left.idempotencyKey.localeCompare(right.idempotencyKey);
}
function liveReservation(record: RuntimeRecord, now: Date) {
return record.state === "pending"
&& typeof record.reservationId === "string"
&& typeof record.reservationOwnerId === "string"
&& (record.submissionPhase === "reserved" || record.submissionPhase === "spawn_permitted")
&& typeof record.reservationExpiresAt === "string"
&& Date.parse(record.reservationExpiresAt) > now.getTime();
}
function matchesReservation(
record: RuntimeRecord | undefined,
requestHash: string,
reservation: SubmissionReservation,
): record is RuntimeRecord & {
state: "pending";
reservationId: string;
reservationOwnerId: string;
} {
return Boolean(record
&& record.requestHash === requestHash
&& record.state === "pending"
&& record.reservationId === reservation.reservationId
&& record.reservationOwnerId === reservation.reservationOwnerId);
}
function clearReservation(record: RuntimeRecord) {
delete record.reservationId;
delete record.reservationOwnerId;
delete record.reservationExpiresAt;
delete record.submissionPhase;
}
function validateIdempotencyKey(value: string) {
if (!/^[A-Za-z0-9._:-]{8,160}$/.test(value)) throw generationRequestInvalid();
}
function taskNotFound() {
return new DreaminaCliError("dreamina_task_not_found", "Dreamina generation task was not found", 404);
}
function taskFailed(code?: string) {
return new DreaminaCliError(code ?? "dreamina_generation_failed", "Dreamina generation did not complete", 502);
}
function errorCode(error: unknown) {
if (error instanceof DreaminaCliError || error instanceof LocalDreaminaGenerationError) return error.code;
return "dreamina_generation_failed";
}
function isDurableJournalCommitError(error: unknown) {
return error instanceof DreaminaCliError && [
"dreamina_cli_fenced",
"dreamina_state_fenced",
"dreamina_state_busy",
"dreamina_state_invalid",
].includes(error.code);
}
function queryState(value: DreaminaRuntimeQueryResult): "pending" | "completed" | "incomplete" {
if (value.state !== "query" || !value.result || typeof value.result !== "object" || Array.isArray(value.result)) return "pending";
const result = value.result as Record<string, unknown>;
const state = String(result.status ?? result.state ?? result.genStatus ?? result.code ?? "").toLowerCase();
if (/(?:fail|error|reject|cancel|blocked|denied)/.test(state)) return "incomplete";
if (/(?:success|complete|finished|done)/.test(state) || result.completed === true) return "completed";
return "pending";
}
function queryOfficialStatus(value: DreaminaRuntimeQueryResult): NonNullable<RuntimeRecord["officialStatus"]> {
if (value.state !== "query" || !value.result || typeof value.result !== "object" || Array.isArray(value.result)) throw queryResponseInvalid();
const result = value.result as Record<string, unknown>;
const status = result.genStatus ?? result.status ?? result.state ?? result.code;
if (status === "pending" || status === "processing" || status === "completed" || status === "failed" || status === "cancelled") return status;
throw queryResponseInvalid();
}
function exactGenerationDirectory(root: string, value: string) {
if (!path.isAbsolute(value) || value.includes("\0")) throw unknownGenerationResult();
const resolved = path.resolve(value);
const relative = path.relative(root, resolved);
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw unknownGenerationResult();
return resolved;
}
function boundedInteger(value: number | undefined, min: number, max: number, fallback: number) {
return Number.isInteger(value) && (value as number) >= min && (value as number) <= max ? value as number : fallback;
}
function generationRequestInvalid() {
return new LocalDreaminaGenerationError("local_generation_request_invalid", "本机生成请求无效", 400);
}
function trustedRequestFingerprint(value: string | undefined) {
if (value === undefined) return undefined;
if (!/^[a-f0-9]{64}$/.test(value)) throw new DreaminaCliError("dreamina_request_invalid", "Dreamina 请求指纹无效", 400);
return value;
}
export function generationArguments(input: DreaminaGenerationInput) {
const args: string[] = [input.operation];
if ("prompt" in input && input.prompt) args.push(`--prompt=${input.prompt}`);
if ("modelVersion" in input && input.modelVersion) args.push(`--model_version=${input.modelVersion}`);
if ("ratio" in input && input.ratio) args.push(`--ratio=${input.ratio}`);
if ("resolutionType" in input) args.push(`--resolution_type=${input.resolutionType}`);
if ("videoResolution" in input) args.push(`--video_resolution=${input.videoResolution}`);
if ("duration" in input && input.duration !== undefined) args.push(`--duration=${input.duration}`);
if ("generateNum" in input && input.generateNum !== undefined) args.push(`--generate_num=${input.generateNum}`);
if (input.operation === "image2image") args.push(`--images=${input.referenceImages.join(",")}`);
if (input.operation === "image_upscale" || input.operation === "image2video") args.push(`--image=${input.referenceImages[0]}`);
if (input.operation === "frames2video") {
args.push(`--first=${input.referenceImages[0]}`, `--last=${input.referenceImages[1]}`);
}
if (input.operation === "multiframe2video") {
args.push(`--images=${input.referenceImages.join(",")}`);
for (const value of input.transitionPrompts ?? []) args.push(`--transition-prompt=${value}`);
for (const value of input.transitionDurations ?? []) args.push(`--transition-duration=${value}`);
}
if (input.operation === "multimodal2video") {
for (const value of input.referenceImages ?? []) args.push(`--image=${value}`);
for (const value of input.referenceVideos ?? []) args.push(`--video=${value}`);
for (const value of input.referenceAudios ?? []) args.push(`--audio=${value}`);
}
return args;
}
function hashRequest(input: DreaminaGenerationInput) {
const entries = Object.entries(input)
.filter(([key]) => key !== "idempotencyKey")
.sort(([left], [right]) => left.localeCompare(right));
return crypto.createHash("sha256").update(JSON.stringify(Object.fromEntries(entries))).digest("hex");
}
function extractSubmitId(stdout: string) {
try {
const parsed = JSON.parse(stdout) as Record<string, unknown>;
const value = parsed.submit_id ?? parsed.submitId;
return typeof value === "string" && /^[A-Za-z0-9._:-]{8,160}$/.test(value) ? value : "";
} catch {
return "";
}
}
function submittedReceipt(result: DreaminaProcessResult): { ok: true; submitId: string } | { ok: false; code: string } {
if (result.exitCode !== 0) return { ok: false, code: "dreamina_submit_exit_nonzero" };
const submitId = extractSubmitId(result.stdout);
return submitId ? { ok: true, submitId } : { ok: false, code: "dreamina_submit_receipt_missing" };
}
function isSubmitReceiptOutput(value: unknown) {
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
const source = value as Record<string, unknown>;
if (Object.keys(source).some((key) => key !== "submit_id" && key !== "submitId")) return false;
const submitId = source.submit_id ?? source.submitId;
return typeof submitId === "string" && /^[A-Za-z0-9._:-]{8,160}$/.test(submitId);
}
function safeJsonResult(stdout: string): unknown {
let parsed: unknown;
try { parsed = JSON.parse(stdout); } catch { throw queryResponseInvalid(); }
return safeQueryResult(parsed);
}
function safeQueryResult(parsed: unknown): unknown {
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw queryResponseInvalid();
const source = parsed as Record<string, unknown>;
const result: Record<string, unknown> = {};
const submitId = safeQueryId(source.submit_id ?? source.submitId);
const status = safeQueryEnum(source.status, QUERY_STATUS_MAP);
const state = safeQueryEnum(source.state, QUERY_STATUS_MAP);
const genStatus = safeQueryEnum(source.gen_status, QUERY_STATUS_MAP);
const code = safeQueryEnum(source.code, QUERY_CODE_MAP);
const progress = typeof source.progress === "number"
&& Number.isFinite(source.progress)
&& source.progress >= 0
&& source.progress <= 100
? source.progress
: undefined;
const completed = typeof source.completed === "boolean"
? source.completed
: typeof source.success === "boolean" ? source.success : undefined;
const message = typeof source.message === "string" ? sanitizeDreaminaDiagnostic(source.message) : undefined;
if (submitId) result.submitId = submitId;
if (status) result.status = status;
if (state) result.state = state;
if (genStatus) result.genStatus = genStatus;
if (code) result.code = code;
if (progress !== undefined) result.progress = progress;
if (completed !== undefined) result.completed = completed;
if (message) result.message = message;
if (!Object.keys(result).length) throw queryResponseInvalid();
return result;
}
function isCompleteQueryOutput(value: unknown) {
try {
const result = safeQueryResult(value) as Record<string, unknown>;
return ["status", "state", "genStatus", "code", "completed"].some((key) => key in result);
} catch {
return false;
}
}
function safeQueryId(value: unknown) {
return typeof value === "string" && /^[A-Za-z0-9._:-]{8,160}$/.test(value) ? value : undefined;
}
const QUERY_STATUS_MAP = new Map([
["pending", "pending"], ["processing", "processing"], ["querying", "processing"], ["running", "processing"],
["completed", "completed"], ["success", "completed"], ["succeeded", "completed"],
["fail", "failed"], ["failed", "failed"], ["error", "failed"], ["cancelled", "cancelled"], ["canceled", "cancelled"],
]);
const QUERY_CODE_MAP = new Map([
["0", "success"], ["ok", "success"], ["success", "success"], ["pending", "pending"],
["processing", "processing"], ["failed", "failed"], ["cancelled", "cancelled"], ["canceled", "cancelled"],
]);
function safeQueryEnum(value: unknown, values: ReadonlyMap<string, string>) {
if (value === undefined) return undefined;
if (typeof value !== "string") throw queryResponseInvalid();
const result = values.get(value.toLowerCase());
if (!result) throw queryResponseInvalid();
return result;
}
function queryResponseInvalid() { return new DreaminaCliError("dreamina_query_response_invalid", "Dreamina 查询响应无法识别", 502); }
function idempotencyConflict() { return new DreaminaCliError("dreamina_idempotency_conflict", "同一幂等键不能用于不同 Dreamina 请求", 409); }
function submissionUnknown() { return new DreaminaCliError("dreamina_submission_unknown", "Dreamina 提交结果不确定,已禁止自动重试;请按 receipt 查询或人工确认", 409); }
function generationCapacityFull() { return new DreaminaCliError("dreamina_generation_capacity_full", "Dreamina 官方生成名额已满", 409); }
function submitFailure(code: string) { return new DreaminaCliError(code, "Dreamina 提交未完成,已禁止自动重试", code === "dreamina_submit_spawn_failed" ? 503 : 409); }
function submitFailureCode(error: unknown, spawned: boolean) {
if (!spawned && error instanceof DreaminaCliError && error.code === "dreamina_spawn_failed") return "dreamina_submit_spawn_failed";
if (error instanceof DreaminaCliError && error.code === "dreamina_command_timeout") return "dreamina_submit_timeout";
if (error instanceof DreaminaCliError && error.code === "dreamina_submit_exit_nonzero") return error.code;
if (error instanceof DreaminaCliError && error.code === "dreamina_submit_receipt_missing") return error.code;
return "dreamina_submission_unknown";
}
function cliFenced() { return new DreaminaCliError("dreamina_cli_fenced", "Dreamina CLI invocation lease 已失效", 409); }
function reconciliationRetryCode(error: unknown) {
if (isAccountSessionError(error)) return "dreamina_account_session_changed";
if (error instanceof DreaminaCliError && /^[a-z][a-z0-9_]{2,80}$/.test(error.code)) return error.code;
if (error instanceof LocalDreaminaGenerationError && /^[a-z][a-z0-9_]{2,80}$/.test(error.code)) {
return error.code === "local_generation_result_invalid" ? "dreamina_query_materialization_retry" : error.code;
}
return "dreamina_query_materialization_retry";
}
function accountChanged() { return new DreaminaCliError("dreamina_account_session_changed", "Dreamina 账号会话已变化", 409); }
function isAccountSessionError(error: unknown) {
return error instanceof DreaminaCliError && error.code === "dreamina_account_session_changed";
}
function cancelled() { return new DreaminaCliError("dreamina_cancelled", "Dreamina 操作已取消", 499); }
function throwIfCancelled(signal?: AbortSignal) { if (signal?.aborted) throw cancelled(); }
function combineAbortSignals(...signals: Array<AbortSignal | undefined>) {
const present = signals.filter((signal): signal is AbortSignal => Boolean(signal));
if (present.length === 1) return { signal: present[0], dispose: () => undefined };
const controller = new AbortController();
const onAbort = () => controller.abort();
for (const signal of present) signal.addEventListener("abort", onAbort, { once: true });
if (present.some((signal) => signal.aborted)) controller.abort();
return {
signal: controller.signal,
dispose: () => {
for (const signal of present) signal.removeEventListener("abort", onAbort);
},
};
}
function waitForAbort<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
if (!signal) return promise;
throwIfCancelled(signal);
return new Promise<T>((resolve, reject) => {
const onAbort = () => { signal.removeEventListener("abort", onAbort); reject(cancelled()); };
signal.addEventListener("abort", onAbort, { once: true });
promise.then(
(value) => { signal.removeEventListener("abort", onAbort); resolve(value); },
(error) => { signal.removeEventListener("abort", onAbort); reject(error); },
);
if (signal.aborted) onAbort();
});
}
function subscribeToSubmission(submission: InFlightSubmission, signal?: AbortSignal) {
throwIfCancelled(signal);
submission.waiters += 1;
return new Promise<DreaminaRuntimeResult>((resolve, reject) => {
let finished = false;
const finish = () => {
if (finished) return false;
finished = true;
signal?.removeEventListener("abort", onAbort);
submission.waiters -= 1;
if (submission.waiters === 0 && !submission.settled) submission.controller.abort();
return true;
};
const onAbort = () => {
if (finish()) reject(cancelled());
};
signal?.addEventListener("abort", onAbort, { once: true });
submission.promise.then(
(value) => { if (finish()) resolve(value); },
(error) => { if (finish()) reject(error); },
);
if (signal?.aborted) onAbort();
});
}