UNPKG

agents

Version:

A home for your AI agents

1,656 lines 70.5 kB
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
import { t as LifecycleCapability } from "./capability-B4WbF81e.js";
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
import { isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isPlatformFailure } from "./retries.js";
import { SqlError } from "./sql-error.js";
import { nanoid } from "nanoid";
//#region src/tasks/errors.ts
/**
* Error classes for the Tasks capability. Each carries a stable `name` so
* hosts and tests can classify failures without depending on message text.
*/
/**
* Thrown by a step callback to fail its run immediately, skipping any
* remaining retry attempts.
*
* Errors named `"NonRetryableError"` from other sources (for example
* `cloudflare:workflows`) are honored the same way.
*
* @experimental The API surface may change before stabilizing.
*/
var NonRetryableError = class extends Error {
	constructor(message, options) {
		super(message, options);
		this.name = "NonRetryableError";
	}
};
/** True when an error should skip remaining step retry attempts. */
function isNonRetryableError(error) {
	return error instanceof NonRetryableError || error instanceof Error && error.name === "NonRetryableError";
}
/**
* Thrown before executing user code when one replay uses the same step name
* twice. Step names are durable journal keys and must be unique within a run.
*
* @experimental The API surface may change before stabilizing.
*/
var DuplicateTaskStepError = class extends Error {
	constructor(stepName) {
		super(`Step name "${stepName}" was already used in this run. Step names are durable journal keys; suffix loop steps with a stable index, e.g. "${stepName}:0".`);
		this.name = "DuplicateTaskStepError";
		this.stepName = stepName;
	}
};
/**
* Thrown when a replay observes a journal that this handler code cannot have
* written — a known step under a different kind, for example. The run fails
* rather than guessing; changing a definition's step layout for in-flight
* runs requires versioning the definition name.
*
* @experimental The API surface may change before stabilizing.
*/
var TaskReplayDivergedError = class extends Error {
	constructor(stepName, detail) {
		super(`Replay diverged from the journal at step "${stepName}": ${detail}. Version the definition name (e.g. "name@v2") instead of changing the step layout of in-flight runs.`);
		this.name = "TaskReplayDivergedError";
		this.stepName = stepName;
	}
};
/**
* Recorded against a run whose persisted definition name is no longer
* registered after a deployment. The run fails visibly; it is never silently
* deleted and never replayed against a different handler.
*
* @experimental The API surface may change before stabilizing.
*/
var MissingTaskDefinitionError = class extends Error {
	constructor(definition) {
		super(`No Task definition named "${definition}" is registered. A deployment removed or renamed it while this run was active. Re-register the definition (or a versioned successor with the same name) to let the run finish.`);
		this.name = "MissingTaskDefinitionError";
		this.definition = definition;
	}
};
/**
* Thrown when a Task input, step result, metadata value, or final result is
* not JSON-serializable or exceeds the serialized size limit.
*
* @experimental The API surface may change before stabilizing.
*/
var TaskSerializationError = class extends Error {
	constructor(context, detail) {
		super(`Cannot serialize ${context}: ${detail}`);
		this.name = "TaskSerializationError";
	}
};
//#endregion
//#region src/tasks/serialization.ts
/**
* Value serialization for the Tasks capability.
*
* Task inputs, step results, metadata, and final results persist as JSON
* text in SQLite. `undefined` (and a `void` handler result) is represented
* as SQL `NULL` rather than a JSON envelope, so the JSON column space stays
* plain: `"null"` is JSON `null`, column `NULL` is `undefined`.
*/
/** Default ceiling for one serialized value (1 MiB). */
const MAX_SERIALIZED_BYTES = 1048576;
const utf8 = new TextEncoder();
/**
* Serialize one Task value for storage.
*
* @param value - The value to persist.
* @param context - What is being serialized, for error messages
* (e.g. `input for definition "report"`, `result of step "fetch"`).
* @returns JSON text, or `null` when the value is `undefined`.
* @throws TaskSerializationError when the value is not JSON-serializable or
* its serialized form exceeds {@link MAX_SERIALIZED_BYTES}.
*/
function serializeTaskValue(value, context) {
	if (value === void 0) return null;
	let json;
	try {
		json = JSON.stringify(value);
	} catch (error) {
		throw new TaskSerializationError(context, error instanceof Error ? error.message : String(error));
	}
	if (json === void 0) throw new TaskSerializationError(context, `value of type ${typeof value} has no JSON representation`);
	const bytes = utf8.encode(json).byteLength;
	if (bytes > 1048576) throw new TaskSerializationError(context, `serialized size ${bytes} bytes exceeds the ${MAX_SERIALIZED_BYTES}-byte limit`);
	return json;
}
/**
* Restore a value serialized by {@link serializeTaskValue}.
*
* @param stored - The stored column value.
* @returns The original value; column `NULL` restores `undefined`.
*/
function deserializeTaskValue(stored) {
	if (stored === null) return void 0;
	return JSON.parse(stored);
}
//#endregion
//#region src/tasks/store.ts
/**
* Storage layer for the Tasks capability: owns the `cf_agents_task_runs` and
* `cf_agents_task_steps` tables — DDL, row access, generation-fenced writes,
* and the snapshot projection. The engine in `tasks.ts` holds the state
* machine; every byte that touches SQLite goes through here.
*/
var _storage = /* @__PURE__ */ new WeakMap();
/** @internal SQL-backed store for one Tasks capability instance. */
var TaskStore = class {
	constructor(storage) {
		_classPrivateFieldInitSpec(this, _storage, void 0);
		_classPrivateFieldSet2(_storage, this, storage);
	}
	sql(strings, ...values) {
		const query = strings.reduce((result, part, index) => result + part + (index < values.length ? "?" : ""), "");
		try {
			return [..._classPrivateFieldGet2(_storage, this).sql.exec(query, ...values)];
		} catch (cause) {
			throw new SqlError(query, cause);
		}
	}
	write(query, params) {
		try {
			return _classPrivateFieldGet2(_storage, this).sql.exec(query, ...params).rowsWritten;
		} catch (cause) {
			throw new SqlError(query, cause);
		}
	}
	/**
	* Run one generation-fenced run mutation. Returns false when the fence
	* rejected it because another attempt superseded this one.
	*/
	fencedWrite(runId, generation, query, leadingParams) {
		try {
			return _classPrivateFieldGet2(_storage, this).sql.exec(query, ...leadingParams, runId, generation).rowsWritten > 0;
		} catch (cause) {
			throw new SqlError(query, cause);
		}
	}
	getRun(runId) {
		return this.sql`
      SELECT * FROM cf_agents_task_runs WHERE run_id = ${runId}
    `[0];
	}
	getRunByKey(idempotencyKey) {
		return this.sql`
      SELECT * FROM cf_agents_task_runs WHERE idempotency_key = ${idempotencyKey}
    `[0];
	}
	deleteRun(runId) {
		this.sql`DELETE FROM cf_agents_task_steps WHERE run_id = ${runId}`;
		this.sql`DELETE FROM cf_agents_task_runs WHERE run_id = ${runId}`;
	}
	ensureTables() {
		const rawSql = (query) => {
			try {
				_classPrivateFieldGet2(_storage, this).sql.exec(query);
			} catch (cause) {
				throw new SqlError(query, cause);
			}
		};
		rawSql(`
      CREATE TABLE IF NOT EXISTS cf_agents_task_runs (
        run_id TEXT PRIMARY KEY,
        definition TEXT NOT NULL,
        input TEXT,
        state TEXT NOT NULL CHECK (state IN (
          'pending', 'running', 'waiting',
          'completed', 'failed', 'cancelled'
        )),
        result TEXT,
        error_name TEXT,
        error_message TEXT,
        status_message TEXT,
        metadata TEXT,
        idempotency_key TEXT UNIQUE,
        retain INTEGER NOT NULL DEFAULT 1,
        attempt INTEGER NOT NULL DEFAULT 0,
        generation TEXT,
        next_at INTEGER,
        wait_reason TEXT,
        cancel_requested INTEGER NOT NULL DEFAULT 0,
        cancel_reason TEXT,
        created_at INTEGER NOT NULL,
        started_at INTEGER,
        updated_at INTEGER NOT NULL,
        settled_at INTEGER
      ) WITHOUT ROWID`);
		rawSql(`
      CREATE INDEX IF NOT EXISTS cf_agents_task_runs_definition
      ON cf_agents_task_runs (definition, created_at)
    `);
		rawSql(`
      CREATE TABLE IF NOT EXISTS cf_agents_task_steps (
        run_id TEXT NOT NULL,
        step_name TEXT NOT NULL,
        kind TEXT NOT NULL CHECK (kind IN ('do', 'sleep')),
        state TEXT NOT NULL CHECK (state IN (
          'running', 'waiting', 'completed', 'failed'
        )),
        result TEXT,
        error_name TEXT,
        error_message TEXT,
        attempt INTEGER NOT NULL DEFAULT 0,
        next_at INTEGER,
        created_at INTEGER NOT NULL,
        started_at INTEGER,
        updated_at INTEGER NOT NULL,
        completed_at INTEGER,
        PRIMARY KEY (run_id, step_name)
      ) WITHOUT ROWID`);
	}
	rowToSnapshot(row) {
		const metadata = row.metadata !== null ? JSON.parse(row.metadata) : void 0;
		const base = {
			runId: row.run_id,
			definition: row.definition,
			createdAt: row.created_at,
			...metadata !== void 0 ? { metadata } : {}
		};
		switch (row.state) {
			case "pending": return {
				...base,
				state: "pending"
			};
			case "running": return {
				...base,
				state: "running",
				attempt: row.attempt,
				startedAt: row.started_at ?? row.created_at,
				...row.status_message !== null ? { statusMessage: row.status_message } : {}
			};
			case "waiting": return {
				...base,
				state: "waiting",
				reason: row.wait_reason ?? "sleep",
				wakeAt: row.next_at ?? row.updated_at,
				...row.status_message !== null ? { statusMessage: row.status_message } : {}
			};
			case "completed": return {
				...base,
				state: "completed",
				result: deserializeTaskValue(row.result),
				settledAt: row.settled_at ?? row.updated_at
			};
			case "failed": return {
				...base,
				state: "failed",
				error: {
					name: row.error_name ?? "Error",
					message: row.error_message ?? "Task run failed"
				},
				settledAt: row.settled_at ?? row.updated_at
			};
			case "cancelled": return {
				...base,
				state: "cancelled",
				...row.cancel_reason !== null ? { reason: row.cancel_reason } : {},
				settledAt: row.settled_at ?? row.updated_at
			};
		}
	}
};
//#endregion
//#region src/tasks/duration.ts
const UNIT_MILLISECONDS = {
	second: 1e3,
	minute: 60 * 1e3,
	hour: 3600 * 1e3,
	day: 1440 * 60 * 1e3,
	week: 10080 * 60 * 1e3
};
const DURATION_PATTERN = /^(\d+(?:\.\d+)?)\s+(second|minute|hour|day|week)s?$/;
/**
* Parse a duration into whole milliseconds.
*
* @param duration - Milliseconds, or a duration string such as `"10 seconds"`.
* @param context - Name of the option being parsed, used in error messages.
* @returns The duration in milliseconds, floored to an integer.
* @throws Error when the duration is negative, not finite, or unparseable.
*/
function parseTaskDuration(duration, context) {
	if (typeof duration === "number") {
		if (!Number.isFinite(duration) || duration < 0) throw new Error(`Invalid ${context}: expected a non-negative number of milliseconds, got ${duration}`);
		return Math.floor(duration);
	}
	const match = DURATION_PATTERN.exec(duration.trim());
	if (!match) throw new Error(`Invalid ${context}: expected milliseconds or a duration like "10 seconds", got ${JSON.stringify(duration)}`);
	const amount = Number(match[1]);
	const unit = match[2];
	return Math.floor(amount * UNIT_MILLISECONDS[unit]);
}
//#endregion
//#region src/tasks/replay.ts
/**
* Replay step engine for the Tasks capability.
*
* `ReplayStep` implements the `TaskStep` surface one handler attempt
* receives. It owns replay semantics — journal hits, journal misses, retry
* policy, durable sleeps, the status live gate, and duplicate/divergence
* detection — while all SQL stays behind the narrow {@link TaskStepEngine}
* port implemented by the `Tasks` capability, the single owner of the
* schema.
*/
/** Longest computed retry delay: backoff growth never exceeds one day. */
const MAX_RETRY_DELAY_MS = 1440 * 60 * 1e3;
/** Steps per run ceiling; crossing it fails the run instead of degrading. */
const MAX_STEPS_PER_RUN = 1e4;
/**
* Thrown by the engine to end one execution attempt while its run waits for
* a durable deadline (sleep or retry). Not an `Error` subclass so a step
* callback's `catch (error)` around unrelated work is less likely to swallow
* it; the capability re-checks with {@link isTaskSuspension}.
*/
var TaskSuspension = class {
	constructor(wakeAt, reason) {
		this.wakeAt = wakeAt;
		this.reason = reason;
	}
};
/** True when a thrown value is the engine's suspension signal. */
function isTaskSuspension(value) {
	return value instanceof TaskSuspension;
}
/**
* Thrown by the engine when a step boundary observes the run's cancellation
* request. The capability settles the run as cancelled.
*/
var TaskCancellation = class {
	constructor(reason) {
		this.reason = reason;
	}
};
/** True when a thrown value is the engine's cancellation signal. */
function isTaskCancellation(value) {
	return value instanceof TaskCancellation;
}
/**
* Thrown by engine writes when another execution attempt has superseded this
* one. The stale attempt unwinds without settling anything; every durable
* write it might still try is generation-fenced.
*/
var AttemptSupersededError = class extends Error {
	constructor(runId) {
		super(`Task attempt superseded: run "${runId}" is no longer claimed by this execution attempt`);
		this.name = "AttemptSupersededError";
	}
};
/** Compute the delay before the next attempt after `failedAttempt` failed. */
function computeRetryDelayMs(policy, failedAttempt) {
	const base = policy.retryDelayMs;
	let delay;
	switch (policy.backoff) {
		case "constant":
			delay = base;
			break;
		case "linear":
			delay = base * failedAttempt;
			break;
		case "exponential":
			delay = base * 2 ** (failedAttempt - 1);
			break;
	}
	return Math.min(delay, MAX_RETRY_DELAY_MS);
}
/** Resolve one `step.do()` config against the capability defaults. */
function resolveStepPolicy(defaults, config) {
	const limit = config?.retries?.limit ?? defaults.retryLimit;
	if (!Number.isInteger(limit) || limit < 1) throw new Error(`Invalid step retries.limit: expected an integer >= 1, got ${limit}`);
	return {
		retryLimit: limit,
		retryDelayMs: config?.retries?.delay !== void 0 ? parseTaskDuration(config.retries.delay, "step retries.delay") : defaults.retryDelayMs,
		backoff: config?.retries?.backoff ?? defaults.backoff,
		timeoutMs: config?.timeout !== void 0 ? parseTaskDuration(config.timeout, "step timeout") : defaults.timeoutMs
	};
}
var _engine = /* @__PURE__ */ new WeakMap();
var _usedNames = /* @__PURE__ */ new WeakMap();
var _live = /* @__PURE__ */ new WeakMap();
var _ReplayStep_brand = /* @__PURE__ */ new WeakSet();
/**
* The `TaskStep` implementation for one execution attempt.
*
* Attempt 1 starts live. A later attempt starts silent and becomes live at
* the frontier of new ground — the first journal miss, or a step still
* waiting or running — so replayed `status()` calls from completed ground
* are suppressed instead of re-published as new progress.
*/
var ReplayStep = class {
	constructor(engine, options) {
		_classPrivateMethodInitSpec(this, _ReplayStep_brand);
		_classPrivateFieldInitSpec(this, _engine, void 0);
		_classPrivateFieldInitSpec(this, _usedNames, /* @__PURE__ */ new Set());
		_classPrivateFieldInitSpec(this, _live, void 0);
		_classPrivateFieldSet2(_engine, this, engine);
		_classPrivateFieldSet2(_live, this, options.startsLive);
		this.interrupted = options.interrupted ?? null;
	}
	async do(name, configOrCallback, maybeCallback) {
		const config = typeof configOrCallback === "function" ? void 0 : configOrCallback;
		const callback = typeof configOrCallback === "function" ? configOrCallback : maybeCallback;
		if (typeof callback !== "function") throw new Error(`step.do("${name}") requires a callback`);
		const policy = resolveStepPolicy(_classPrivateFieldGet2(_engine, this).defaults, config);
		_assertClassBrand(_ReplayStep_brand, this, _enterStep).call(this, name);
		const row = _classPrivateFieldGet2(_engine, this).readStep(name);
		if (row === void 0) {
			_classPrivateFieldSet2(_live, this, true);
			if (_classPrivateFieldGet2(_engine, this).countSteps() >= 1e4) throw new Error(`Run exceeded ${MAX_STEPS_PER_RUN} steps; split the work across multiple Task runs`);
			_classPrivateFieldGet2(_engine, this).insertStep(name, "do", null);
			return _assertClassBrand(_ReplayStep_brand, this, _executeAttempt).call(this, name, 1, policy, callback);
		}
		if (row.kind !== "do") throw new TaskReplayDivergedError(name, `journaled as a ${row.kind} step but replayed as a do step`);
		switch (row.state) {
			case "completed": return deserializeTaskValue(row.result);
			case "failed": throw restoreStepError(row);
			case "waiting": {
				_classPrivateFieldSet2(_live, this, true);
				const wakeAt = row.next_at ?? Date.now();
				if (Date.now() < wakeAt) throw new TaskSuspension(wakeAt, "retry");
				const attempt = _classPrivateFieldGet2(_engine, this).claimStepAttempt(name);
				_classPrivateFieldGet2(_engine, this).emit("task:step:retry", {
					step: name,
					attempt
				});
				return _assertClassBrand(_ReplayStep_brand, this, _executeAttempt).call(this, name, attempt, policy, callback);
			}
			case "running": {
				_classPrivateFieldSet2(_live, this, true);
				const attempt = _classPrivateFieldGet2(_engine, this).claimStepAttempt(name);
				return _assertClassBrand(_ReplayStep_brand, this, _executeAttempt).call(this, name, attempt, policy, callback);
			}
		}
	}
	async sleep(name, duration) {
		const durationMs = parseTaskDuration(duration, "sleep duration");
		return _assertClassBrand(_ReplayStep_brand, this, _sleepAt).call(this, name, () => Date.now() + durationMs);
	}
	async sleepUntil(name, when) {
		const wakeAt = when instanceof Date ? when.getTime() : when;
		if (!Number.isFinite(wakeAt)) throw new Error(`Invalid sleepUntil time for step "${name}": ${String(when)}`);
		return _assertClassBrand(_ReplayStep_brand, this, _sleepAt).call(this, name, () => wakeAt);
	}
	async status(message) {
		if (!_classPrivateFieldGet2(_live, this)) return;
		_classPrivateFieldGet2(_engine, this).writeStatus(String(message));
	}
	idempotencyKey(name) {
		return _classPrivateFieldGet2(_engine, this).stepIdempotencyKey(name);
	}
};
/** Validate a step boundary: name rules, duplicates, cancellation. */
function _enterStep(name) {
	if (typeof name !== "string" || name.length === 0) throw new Error("Step names must be non-empty strings");
	if (name.length > 256) throw new Error(`Step name exceeds 256 characters: "${name.slice(0, 40)}…"`);
	if (name.startsWith("__cf")) throw new Error(`Step names must not use the reserved "__cf" prefix`);
	if (_classPrivateFieldGet2(_usedNames, this).has(name)) throw new DuplicateTaskStepError(name);
	_classPrivateFieldGet2(_usedNames, this).add(name);
	const cancellation = _classPrivateFieldGet2(_engine, this).cancellationRequested();
	if (cancellation) throw new TaskCancellation(cancellation.reason);
}
/** First persist wins: the recorded wake time is authoritative. */
async function _sleepAt(name, wakeTime) {
	_assertClassBrand(_ReplayStep_brand, this, _enterStep).call(this, name);
	const row = _classPrivateFieldGet2(_engine, this).readStep(name);
	if (row === void 0) {
		_classPrivateFieldSet2(_live, this, true);
		const wakeAt = wakeTime();
		if (wakeAt <= Date.now()) {
			_classPrivateFieldGet2(_engine, this).insertCompletedSleep(name);
			return;
		}
		_classPrivateFieldGet2(_engine, this).insertStep(name, "sleep", wakeAt);
		throw new TaskSuspension(wakeAt, "sleep");
	}
	if (row.kind !== "sleep") throw new TaskReplayDivergedError(name, `journaled as a ${row.kind} step but replayed as a sleep step`);
	if (row.state === "completed") return;
	_classPrivateFieldSet2(_live, this, true);
	const wakeAt = row.next_at ?? 0;
	if (Date.now() < wakeAt) throw new TaskSuspension(wakeAt, "sleep");
	_classPrivateFieldGet2(_engine, this).completeStep(name, void 0);
}
/** Execute one claimed attempt of a `do` step under timeout and retries. */
async function _executeAttempt(name, attempt, policy, callback) {
	_classPrivateFieldGet2(_engine, this).refreshClaim();
	_classPrivateFieldGet2(_engine, this).emit("task:step:started", {
		step: name,
		attempt
	});
	const timeout = new AbortController();
	const onRunAbort = () => timeout.abort(_classPrivateFieldGet2(_engine, this).attemptSignal.reason);
	_classPrivateFieldGet2(_engine, this).attemptSignal.addEventListener("abort", onRunAbort, { once: true });
	const timer = setTimeout(() => {
		timeout.abort(/* @__PURE__ */ new Error(`Step "${name}" attempt ${attempt} timed out after ${policy.timeoutMs}ms`));
	}, policy.timeoutMs);
	try {
		const result = await _assertClassBrand(_ReplayStep_brand, this, _raceTimeout).call(this, Promise.resolve(callback({
			attempt,
			idempotencyKey: _classPrivateFieldGet2(_engine, this).stepIdempotencyKey(name),
			signal: timeout.signal
		})), timeout.signal);
		_classPrivateFieldGet2(_engine, this).completeStep(name, result);
		_classPrivateFieldGet2(_engine, this).emit("task:step:completed", {
			step: name,
			attempt
		});
		return result;
	} catch (error) {
		if (error instanceof AttemptSupersededError) throw error;
		const cancellation = _classPrivateFieldGet2(_engine, this).cancellationRequested();
		if (cancellation) throw new TaskCancellation(cancellation.reason);
		if (isDurableObjectCodeUpdateReset(error) || isDurableObjectMemoryLimitReset(error)) throw error;
		if (isPlatformFailure(error) && attempt >= policy.retryLimit) throw error;
		if (isNonRetryableError(error) || error instanceof TaskSerializationError || attempt >= policy.retryLimit) {
			_classPrivateFieldGet2(_engine, this).failStep(name, toErrorSummary(error));
			throw error;
		}
		const wakeAt = Date.now() + computeRetryDelayMs(policy, attempt);
		_classPrivateFieldGet2(_engine, this).waitStep(name, wakeAt);
		throw new TaskSuspension(wakeAt, "retry");
	} finally {
		clearTimeout(timer);
		_classPrivateFieldGet2(_engine, this).attemptSignal.removeEventListener("abort", onRunAbort);
	}
}
/**
* Settle with the callback or its timeout, whichever finishes first. A
* callback that ignores its abort signal cannot wedge the attempt; its
* late settlement is discarded and generation fencing rejects late writes.
*/
function _raceTimeout(pending, signal) {
	if (signal.aborted) return Promise.reject(signal.reason);
	return new Promise((resolve, reject) => {
		const onAbort = () => reject(signal.reason);
		signal.addEventListener("abort", onAbort, { once: true });
		pending.then((value) => {
			signal.removeEventListener("abort", onAbort);
			resolve(value);
		}, (error) => {
			signal.removeEventListener("abort", onAbort);
			reject(error);
		});
	});
}
/** Rebuild a persisted terminal step error for rethrow. */
function restoreStepError(row) {
	const error = new Error(row.error_message ?? "Step failed");
	error.name = row.error_name ?? "Error";
	return error;
}
/** Safe name/message projection of an arbitrary thrown value. */
function toErrorSummary(error) {
	if (error instanceof Error) return {
		name: error.name,
		message: error.message
	};
	return {
		name: "Error",
		message: String(error)
	};
}
//#endregion
//#region src/tasks/engine-port.ts
/**
* The step-engine port: the storage-side operations `ReplayStep` drives —
* journal reads and writes, generation fencing, claim refresh, progress —
* bound to one claimed attempt. `tasks.ts` owns the state machine; this
* module owns nothing but the port's construction.
*/
/** @internal Build the engine port for one claimed attempt. */
function createTaskStepEngine(deps) {
	const { runId, generation } = deps;
	let lastClaimWriteAt = deps.claimedAtMs;
	let lastStatusMessage;
	const assertCurrent = () => {
		const row = deps.store.getRun(runId);
		if (!row || row.generation !== generation) throw new AttemptSupersededError(runId);
	};
	return {
		readStep: (name) => {
			return deps.store.sql`
          SELECT * FROM cf_agents_task_steps
          WHERE run_id = ${runId} AND step_name = ${name}
        `[0];
		},
		countSteps: () => {
			return deps.store.sql`
          SELECT COUNT(*) AS count FROM cf_agents_task_steps WHERE run_id = ${runId}
        `[0]?.count ?? 0;
		},
		insertStep: (name, kind, wakeAt) => {
			assertCurrent();
			const now = Date.now();
			deps.store.sql`
          INSERT INTO cf_agents_task_steps
            (run_id, step_name, kind, state, attempt, next_at, created_at,
             started_at, updated_at)
          VALUES
            (${runId}, ${name}, ${kind},
             ${kind === "do" ? "running" : wakeAt === null ? "running" : "waiting"},
             ${kind === "do" ? 1 : 0}, ${wakeAt},
             ${now}, ${kind === "do" ? now : null}, ${now})
        `;
		},
		insertCompletedSleep: (name) => {
			assertCurrent();
			const now = Date.now();
			deps.store.sql`
          INSERT INTO cf_agents_task_steps
            (run_id, step_name, kind, state, attempt, next_at, created_at,
             completed_at, updated_at)
          VALUES
            (${runId}, ${name}, 'sleep', 'completed', 0, NULL, ${now},
             ${now}, ${now})
        `;
		},
		claimStepAttempt: (name) => {
			assertCurrent();
			const now = Date.now();
			deps.store.sql`
          UPDATE cf_agents_task_steps
          SET state = 'running', attempt = attempt + 1, next_at = NULL,
              started_at = ${now}, updated_at = ${now}
          WHERE run_id = ${runId} AND step_name = ${name}
        `;
			return deps.store.sql`
          SELECT attempt FROM cf_agents_task_steps
          WHERE run_id = ${runId} AND step_name = ${name}
        `[0]?.attempt ?? 1;
		},
		completeStep: (name, result) => {
			assertCurrent();
			const resultJson = serializeTaskValue(result, `result of step "${name}" in run "${runId}"`);
			const now = Date.now();
			deps.store.sql`
          UPDATE cf_agents_task_steps
          SET state = 'completed', result = ${resultJson}, next_at = NULL,
              completed_at = ${now}, updated_at = ${now}
          WHERE run_id = ${runId} AND step_name = ${name}
        `;
		},
		failStep: (name, error) => {
			assertCurrent();
			const now = Date.now();
			deps.store.sql`
          UPDATE cf_agents_task_steps
          SET state = 'failed', error_name = ${error.name},
              error_message = ${error.message}, next_at = NULL, updated_at = ${now}
          WHERE run_id = ${runId} AND step_name = ${name}
        `;
		},
		waitStep: (name, wakeAt) => {
			assertCurrent();
			const now = Date.now();
			deps.store.sql`
          UPDATE cf_agents_task_steps
          SET state = 'waiting', next_at = ${wakeAt}, updated_at = ${now}
          WHERE run_id = ${runId} AND step_name = ${name}
        `;
		},
		refreshClaim: () => {
			const now = Date.now();
			if (now - lastClaimWriteAt < deps.claimRefreshAfterMs) return;
			if (deps.store.fencedWrite(runId, generation, `UPDATE cf_agents_task_runs SET next_at = ?, updated_at = ?
           WHERE run_id = ? AND generation = ? AND state = 'running'`, [now + deps.claimTimeoutMs(), now])) lastClaimWriteAt = now;
		},
		writeStatus: (message) => {
			if (message === lastStatusMessage) return;
			if (deps.store.fencedWrite(runId, generation, `UPDATE cf_agents_task_runs SET status_message = ?, updated_at = ?
           WHERE run_id = ? AND generation = ? AND state = 'running'`, [message, Date.now()])) lastStatusMessage = message;
		},
		cancellationRequested: () => {
			const row = deps.store.getRun(runId);
			if (!row || row.cancel_requested !== 1) return null;
			return { reason: row.cancel_reason ?? void 0 };
		},
		attemptSignal: deps.signal,
		emit: deps.emit,
		stepIdempotencyKey: (name) => `${runId}:${name}`,
		defaults: deps.defaults
	};
}
//#endregion
//#region src/tasks/tasks.ts
/**
* Durable replayable execution for Lifecycle Objects. `Tasks` owns the
* `cf_agents_task_runs` and `cf_agents_task_steps` tables, the definitions registry, run
* acceptance, generation-fenced claiming, and due-run processing.
*
* Tasks consumes only the standard capability services: storage, the job
* queue, the host invocation boundary, events, and routing. A run's storage
* and step journal always live where it was accepted; only its deadline
* mirrors as one Lifecycle queue job, routed to the root Lifecycle when
* accepted on a routed sub-agent, since only the root owns the physical
* alarm. Definition handlers run through Lifecycle's host invocation
* boundary. Interrupted work replays: completed steps return journaled
* results and handlers resume from durable evidence.
*/
const taskDefinitionResolvers = /* @__PURE__ */ new WeakMap();
/**
* @internal Supply a composition-root fallback for definition names outside
* the declared map. Frameworks use this to attach internal definitions (for
* example a future Agent compatibility layer) without occupying the host's
* constructor map; resolved handlers still run inside the Lifecycle host
* boundary. The resolver must return the same definition for a name on
* every Durable Object wake, or that name's in-flight runs cannot resume.
*/
function setTaskDefinitionResolver(tasks, resolver) {
	taskDefinitionResolvers.set(tasks, resolver);
}
const taskRoutedMemoryLimitHandlers = /* @__PURE__ */ new WeakMap();
/**
* @internal Supply a composition-root bridge from a routed run's sealed
* strike to the owning host's own `onAlarmMemoryLimit` hook. A root's own
* local runs already reach that hook through Lifecycle's alarm dispatch on
* the same Durable Object; a routed run's owner is a different instance,
* whose Lifecycle never observes the root's alarm directly.
*/
function setTaskRoutedMemoryLimitHandler(tasks, handler) {
	taskRoutedMemoryLimitHandlers.set(tasks, handler);
}
const FIBER_SCHEMA_VERSION_KEY = "cf_agents:tasks_schema_version";
const CURRENT_FIBER_SCHEMA_VERSION = 1;
const DEFAULT_STEP_POLICY = {
	retryLimit: 5,
	retryDelayMs: 1e3,
	backoff: "exponential",
	timeoutMs: 300 * 1e3
};
/**
* Slack added to the default step timeout to form the claim deadline — the
* durable recovery backstop that wakes the object when a claimed attempt's
* isolate disappears.
*/
const CLAIM_SLACK_MS = 3e4;
const DEFAULT_LIST_LIMIT = 100;
const MAX_DEFINITION_NAME_LENGTH = 256;
/**
* Queue-job id prefix for run wakes. Run IDs are caller-selectable, so the
* job id namespaces them instead of exposing them verbatim to the shared
* job id space.
*/
const WAKE_JOB_PREFIX = "task:";
/** Normal Task deadline dispatch. */
const WAKE_JOB_FN = "wake";
/**
* A platform failure that escapes an attempt (ReplayStep rethrows once the
* step's own retry budget is spent) leaves the run claimed with a future
* `next_at`. An in-driver retry would not re-run the step — `#executeRun`
* returns at its claim guard — it would only read the claim back as a clean
* `{ rescheduleAt }`, hiding the failure from the alarm boundary. One
* attempt keeps JobDriver's platform-failure contract: the wake rejects, the
* job row is preserved, and the platform re-runs the alarm on a fresh
* invocation while the claim deadline stays the durable wake.
*/
const WAKE_JOB_RETRY = { maxAttempts: 1 };
/**
* How long one queue-driven attempt may hold the serial dispatch loop
* before detaching. Correctness never depends on the inline await — the
* claim backstop owns the durable wake — so this only trades a prompt
* inline settle for queue liveness.
*/
const DISPATCH_BUDGET_MS = 5e3;
const TERMINAL_STATES = /* @__PURE__ */ new Set([
	"completed",
	"failed",
	"cancelled"
]);
function isTaskWakeJobPayload(value) {
	return typeof value === "object" && value !== null && typeof value.runId === "string";
}
var _definitions = /* @__PURE__ */ new WeakMap();
var _registered = /* @__PURE__ */ new WeakMap();
var _active = /* @__PURE__ */ new WeakMap();
var _storeInstance = /* @__PURE__ */ new WeakMap();
var _stepDefaults = /* @__PURE__ */ new WeakMap();
var _onError = /* @__PURE__ */ new WeakMap();
var _Tasks_brand = /* @__PURE__ */ new WeakSet();
/**
* Durable replayable execution for a Lifecycle Object.
*
* Declare named definitions in the constructor and install the instance with
* `Lifecycle.use()`. The constructor map is the registry: it is rebuilt on
* every Durable Object wake, so in-flight runs always resolve their
* persisted definition names. Each definition's handler replays from the
* beginning on every execution attempt; completed steps return journaled
* results, sleeps consult persisted deadlines, and interrupted work
* continues from the first unfinished step after process loss.
*
* @experimental The API surface may change before stabilizing.
*/
var Tasks = class extends LifecycleCapability {
	/**
	* Create a Tasks capability.
	*
	* @param options - Named definitions plus default step retry/timeout
	* policy and alarm batching. Declaring `definitions` types {@link run} and
	* {@link handle} against the map — names and inputs are checked where the
	* handlers are declared and where runs start. Names outside the map are
	* rejected unless a composition-root resolver supplies them.
	*/
	constructor(options = {}) {
		super("tasks");
		_classPrivateMethodInitSpec(this, _Tasks_brand);
		_classPrivateFieldInitSpec(this, _definitions, void 0);
		_classPrivateFieldInitSpec(this, _registered, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _active, /* @__PURE__ */ new Map());
		_classPrivateFieldInitSpec(this, _storeInstance, void 0);
		_classPrivateFieldInitSpec(this, _stepDefaults, void 0);
		_classPrivateFieldInitSpec(this, _onError, void 0);
		_classPrivateFieldSet2(_definitions, this, options.definitions ?? {});
		_classPrivateFieldSet2(_stepDefaults, this, {
			retryLimit: options.retries?.limit ?? DEFAULT_STEP_POLICY.retryLimit,
			retryDelayMs: options.retries?.delay !== void 0 ? parseTaskDuration(options.retries.delay, "retries.delay") : DEFAULT_STEP_POLICY.retryDelayMs,
			backoff: options.retries?.backoff ?? DEFAULT_STEP_POLICY.backoff,
			timeoutMs: options.stepTimeout !== void 0 ? parseTaskDuration(options.stepTimeout, "stepTimeout") : DEFAULT_STEP_POLICY.timeoutMs
		});
		_classPrivateFieldSet2(_onError, this, options.onError);
	}
	/**
	* @internal Framework aperture: register one reserved (`__cf`-prefixed)
	* Task definition directly on this instance, bypassing the constructor's
	* `definitions` map so a host's own subclass layers can each declare their
	* own `definitions` / `taskDefinitions` field without colliding with — or
	* being silently clobbered by — a framework's internal names. Call once per
	* name from the owning host's own constructor, unconditionally, so the
	* definition is rebuilt identically on every Durable Object wake: an
	* in-flight run resolves the same handler for its persisted definition name
	* every time, or it cannot resume.
	*
	* Throws if `name` does not carry the reserved `__cf` prefix — this is not
	* a general-purpose registration path; declare ordinary definitions in the
	* constructor's `definitions` map instead — or if `name` is already
	* registered, which is always a real conflict: this method runs exactly
	* once per name per Tasks construction.
	*/
	register(name, definition) {
		if (typeof name !== "string" || name.length === 0) throw new Error("Task definition names must be non-empty strings");
		if (name.length > MAX_DEFINITION_NAME_LENGTH) throw new Error(`Task definition name exceeds ${MAX_DEFINITION_NAME_LENGTH} characters`);
		if (!name.startsWith("__cf")) throw new Error(`register() requires a "__cf"-prefixed reserved definition name, got "${name}"`);
		if (Object.hasOwn(_classPrivateFieldGet2(_definitions, this), name) || _classPrivateFieldGet2(_registered, this).has(name)) throw new Error(`Task definition "${name}" is already registered on this Tasks capability`);
		_classPrivateFieldGet2(_registered, this).set(name, definition);
	}
	/**
	* Durably accept one run of a declared definition and return a receipt
	* without waiting for terminal state. The same `idempotencyKey` or `runId`
	* joins the existing run (`accepted: false`) instead of creating a second.
	*/
	async run(definition, input, options) {
		_assertClassBrand(_Tasks_brand, this, _validateDefinitionName).call(this, definition);
		return _assertClassBrand(_Tasks_brand, this, _accept).call(this, definition, input, options);
	}
	/**
	* A typed handle scoped to one declared definition: its `run`, `get`,
	* `getByIdempotencyKey`, and `cancel` see only that definition's runs. The
	* handle is a pure lens over this capability — it holds no state and may
	* be created at any time.
	*/
	handle(definition) {
		_assertClassBrand(_Tasks_brand, this, _validateDefinitionName).call(this, definition);
		return {
			name: definition,
			run: (input, options) => this.run(definition, input, options),
			get: (runId) => _assertClassBrand(_Tasks_brand, this, _snapshot).call(this, runId, definition),
			getByIdempotencyKey: (idempotencyKey) => _assertClassBrand(_Tasks_brand, this, _snapshotByKey).call(this, idempotencyKey, definition),
			cancel: (runId, reason) => _assertClassBrand(_Tasks_brand, this, _cancelScoped).call(this, runId, definition, reason)
		};
	}
	/** Migrate storage and reconcile run deadlines during Lifecycle startup. */
	async onStart() {
		const storage = this.lifecycle.storage;
		if ((await storage.get(FIBER_SCHEMA_VERSION_KEY) ?? 0) < CURRENT_FIBER_SCHEMA_VERSION) {
			_get_store.call(_assertClassBrand(_Tasks_brand, this)).ensureTables();
			await storage.put(FIBER_SCHEMA_VERSION_KEY, CURRENT_FIBER_SCHEMA_VERSION);
		}
		_assertClassBrand(_Tasks_brand, this, _reconcile).call(this);
		await _assertClassBrand(_Tasks_brand, this, _syncAllWakes).call(this);
	}
	/** Drive one due run's wake dispatched by the Lifecycle event loop. */
	async onJob(context) {
		const timing = isTaskWakeJobPayload(context.job.payload) ? context.job.payload : void 0;
		const runId = timing?.runId ?? context.job.id.slice(5);
		if (timing?.owner_path) {
			const target = {
				key: timing.owner_path_key ?? timing.owner_path,
				data: timing.owner_path
			};
			const call = this.lifecycle.routes.to(target, {
				type: "dispatch",
				runId
			});
			let budgetTimer;
			const budget = new Promise((resolve) => {
				budgetTimer = setTimeout(() => resolve("budget"), DISPATCH_BUDGET_MS);
			});
			try {
				const winner = await Promise.race([call.then((outcome) => ({ outcome })), budget]);
				if (winner === "budget") {
					this.lifecycle.trackAlarmWork(call);
					return;
				}
				return winner.outcome;
			} catch (error) {
				if (isPlatformFailure(error)) throw error;
				console.error(`error dispatching routed Task run "${runId}"`, error);
				return "yield";
			} finally {
				clearTimeout(budgetTimer);
			}
		}
		return _assertClassBrand(_Tasks_brand, this, _dispatchRun).call(this, runId);
	}
	/**
	* Alarm memory-limit breaker policy (#1825) for the run whose wake struck.
	*
	* The run row is the durable source of truth: startup reconciliation
	* re-derives due-now wakes from it, so the breaker's queue-row backoff
	* and purge alone cannot contain a run whose attempt deterministically
	* exhausts memory — a fresh isolate would resurrect it immediately. On a
	* strike the run's claim is stripped and its deadline pushed to the
	* backoff wake: the row keeps its state, so a struck `running` row still
	* reads as an interrupted attempt (`step.interrupted`) when it is
	* reclaimed, while reconciliation leaves the claimless row alone instead
	* of flooring its deadline to now. When the breaker seals, the run
	* terminally fails with an observable `task:failed` outcome.
	*/
	async onMemoryLimit(context) {
		const job = context.executing;
		if (job?.capability !== this.capabilityId) return;
		const timing = isTaskWakeJobPayload(job.payload) ? job.payload : void 0;
		const runId = timing?.runId ?? job.id.slice(5);
		if (timing?.owner_path) {
			try {
				await this.lifecycle.routes.to({
					key: timing.owner_path_key ?? timing.owner_path,
					data: timing.owner_path
				}, {
					type: "memoryLimit",
					runId,
					context
				});
			} catch (error) {
				console.error(`Failed to route memory-limit policy for Task run "${runId}"`, error);
			}
			return;
		}
		await _assertClassBrand(_Tasks_brand, this, _applyMemoryLimit).call(this, runId, context);
	}
	/**
	* @internal Framework aperture: durably accept one run — reserved
	* (`__cf`-prefixed) definition names included, which the public `run()`
	* refuses so users cannot start framework runs — and drive its first
	* attempt in the caller's invocation, resolving when that attempt reaches
	* its next durable boundary. The receipt's run may already be terminal
	* when this resolves; callers that need the outcome read it from their own
	* channel (the run handler settles it) or from the snapshot.
	*/
	async __DO_NOT_USE_WILL_BREAK__runAttached(definition, input, options) {
		const receipt = await _assertClassBrand(_Tasks_brand, this, _acceptReserved).call(this, definition, input, options, "attached");
		if (receipt.accepted) await _assertClassBrand(_Tasks_brand, this, _executeRun).call(this, receipt.runId);
		return receipt;
	}
	/**
	* @internal Framework aperture: durably accept one run — reserved names
	* included — and leave its first attempt to the durable queue wake instead
	* of warm-starting it in the caller's invocation. Chat recovery uses this
	* so a continuation always runs under an alarm, where `trackAlarmWork`
	* keeps its model turn inside the memory-limit breaker domain.
	*/
	async __DO_NOT_USE_WILL_BREAK__enqueue(definition, input, options) {
		return _assertClassBrand(_Tasks_brand, this, _acceptReserved).call(this, definition, input, options, "queued");
	}
	/** Handle Tasks protocol messages routed by another Lifecycle. */
	async onRoute(context) {
		const message = context.payload;
		switch (message.type) {
			case "syncWake": {
				const owner = context.source;
				if (!owner) throw new Error("Routed Tasks message missing source");
				return _assertClassBrand(_Tasks_brand, this, _syncRoutedWake).call(this, owner, message.runId, message.next);
			}
			case "dispatch": return _assertClassBrand(_Tasks_brand, this, _dispatchRoutedRun).call(this, message.runId);
			case "memoryLimit": {
				await _assertClassBrand(_Tasks_brand, this, _applyMemoryLimit).call(this, message.runId, message.context);
				const handler = taskRoutedMemoryLimitHandlers.get(this);
				if (handler) await this.lifecycle.runInHostContext(() => handler(message.context));
				return true;
			}
			default: throw new Error("Unknown routed Tasks message");
		}
	}
	/**
	* @internal Framework aperture: bulk-cancel this root's routed wake
	* mirrors for every run owned by a deleted facet subtree. The runs and
	* their step journals live on the deleted facets' own storage and are
	* wiped with them; only this root's mirror job needs an explicit cancel,
	* or it stays due forever, retrying a dispatch to a facet that is gone.
	*/
	async __DO_NOT_USE_WILL_BREAK__cleanupRoutePrefix(prefix) {
		for (const job of this.lifecycle.jobs.list()) {
			const timing = isTaskWakeJobPayload(job.payload) ? job.payload : void 0;
			const ownerKey = timing?.owner_path_key ?? timing?.owner_path;
			if (!timing?.owner_path || ownerKey === null || ownerKey === void 0) continue;
			if (ownerKey !== prefix && !ownerKey.startsWith(`${prefix}/`)) continue;
			await this.lifecycle.jobs.cancel(job.id);
		}
	}
	/** Read one run by ID across all definitions. */
	async get(runId) {
		return _assertClassBrand(_Tasks_brand, this, _snapshot).call(this, runId);
	}
	/** Read one run by idempotency key across all definitions. */
	async getByIdempotencyKey(idempotencyKey) {
		return _assertClassBrand(_Tasks_brand, this, _snapshotByKey).call(this, idempotencyKey);
	}
	/** List runs, newest first. */
	async list(options = {}) {
		await this.lifecycle.ready();
		let query = "SELECT * FROM cf_agents_task_runs WHERE 1 = 1";
		const params = [];
		if (options.definition !== void 0) {
			query += " AND definition = ?";
			params.push(options.definition);
		}
		const states = Array.isArray(options.status) ? options.status : options.status !== void 0 ? [options.status] : [];
		if (states.length > 0) {
			query += ` AND state IN (${states.map(() => "?").join(", ")})`;
			params.push(...states);
		}
		query += " ORDER BY created_at DESC, run_id DESC LIMIT ?";
		params.push(options.limit ?? DEFAULT_LIST_LIMIT);
		let rows;
		try {
			rows = this.lifecycle.storage.sql.exec(query, ...params).toArray();
		} catch (cause) {
			throw new SqlError(query, cause);
		}
		return rows.map((row) => _get_store.call(_assertClassBrand(_Tasks_brand, this)).rowToSnapshot(row));
	}
	/**
	* Request cooperative cancellation of one run.
	*
	* A live attempt is aborted and settles as cancelled at its next step
	* boundary; a parked run settles immediately.
	*
	* @returns True when a non-terminal run accepted the request.
	*/
	async cancel(runId, reason) {
		await this.lifecycle.ready();
		const row = _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId);
		if (!row || TERMINAL_STATES.has(row.state)) return false;
		const active = _classPrivateFieldGet2(_active, this).get(runId);
		if (active) {
			const now = Date.now();
			_get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
        UPDATE cf_agents_task_runs
        SET cancel_requested = 1, cancel_reason = ${reason ?? null},
            next_at = ${now}, updated_at = ${now}
        WHERE run_id = ${runId}
      `;
			active.controller.abort(new TaskCancellation(reason));
			await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, runId);
			return true;
		}
		await _assertClassBrand(_Tasks_brand, this, _settleCancelled).call(this, runId, null, reason);
		return true;
	}
	/**
	* Delete retained terminal runs and their step journals.
	*
	* @returns The number of runs deleted.
	*/
	async delete(options = {}) {
		await this.lifecycle.ready();
		const states = options.status ?? [
			"completed",
			"failed",
			"cancelled"
		];
		if (states.length === 0) return 0;
		let query = `SELECT run_id, definition FROM cf_agents_task_runs WHERE state IN (${states.map(() => "?").join(", ")})`;
		const params = [...states];
		if (options.settledBefore) {
			query += " AND settled_at < ?";
			params.push(options.settledBefore.getTime());
		}
		query += " ORDER BY settled_at ASC LIMIT ?";
		params.push(options.limit ?? DEFAULT_LIST_LIMIT);
		let rows;
		try {
			rows = this.lifecycle.storage.sql.exec(query, ...params).toArray();
		} catch (cause) {
			throw new SqlError(query, cause);
		}
		for (const row of rows) {
			_get_store.call(_assertClassBrand(_Tasks_brand, this)).deleteRun(row.run_id);
			_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:deleted", {
				runId: row.run_id,
				definition: row.definition
			});
		}
		return rows.length;
	}
};
function _claimTimeoutMs() {
	return _classPrivateFieldGet2(_stepDefaults, this).timeoutMs + CLAIM_SLACK_MS;
}
/** The SQL store over this Lifecycle's storage (see `store.ts`). */
function _get_store() {
	_classPrivateFieldGet2(_storeInstance, this) ?? _classPrivateFieldSet2(_storeInstance, this, new TaskStore(this.lifecycle.storage));
	return _classPrivateFieldGet2(_storeInstance, this);
}
/** Resolve a name to its declared or composition-root-supplied handler. */
function _resolveDefinition(name) {
	return _classPrivateFieldGet2(_definitions, this)[name] ?? _classPrivateFieldGet2(_registered, this).get(name) ?? taskDefinitionResolvers.get(this)?.(name);
}
/** True when a name resolves to a runnable definition. */
function _hasDefinition(name) {
	return _assertClassBrand(_Tasks_brand, this, _resolveDefinition).call(this, name) !== void 0;
}
function _validateDefinitionName(name) {
	if (typeof name !== "string" || name.length === 0) throw new Error("Task definition names must be non-empty strings");
	if (name.length > MAX_DEFINITION_NAME_LENGTH) throw new Error(`Task definition name exceeds ${MAX_DEFINITION_NAME_LENGTH} characters`);
	if (name.startsWith("__cf")) throw new Error(`Task definition names must not use the reserved "__cf" prefix`);
	if (!_assertClassBrand(_Tasks_brand, this, _hasDefinition).call(this, name)) throw new Error(`Unknown Task definition "${name}": not declared on this Tasks`);
}
/** Cancel through a handle: another definition's run is not visible. */
async function _cancelScoped(runId, definition, reason) {
	await this.lifecycle.ready();
	const row = _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId);
	if (!row || row.definition !== definition) return false;
	return this.cancel(runId, reason);
}
/** Push a live attempt's durable claim deadline forward one claim window. */
function _refreshClaim(runId) {
	_get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      UPDATE cf_agents_task_runs
      SET next_at = ${Date.now() + _assertClassBrand(_Tasks_brand, this, _claimTimeoutMs).call(this)}, updated_at = ${Date.now()}
      WHERE run_id = ${runId} AND state = 'running'
    `;
}
/**
* Drive one local due run to its next durable boundary, bounded by the
* dispatch budget, and return the wake outcome for this capability's own
* queue job.
*/
async function _dispatchRun(runId) {
	const active = _classPrivateFieldGet2(_active, this).get(runId);
	if (active) {
		_assertClassBrand(_Tasks_brand, this, _refreshClaim).call(this, runId);
		this.lifecycle.trackAlarmWork(active.promise);
		return _assertClassBrand(_Tasks_brand, this, _wakeOutcome).call(this, runId);
	}
	let budgetTimer;
	const budget = new Promise((resolve) => {
		budgetTimer = setTimeout(() => resolve("budget"), DISPATCH_BUDGET_MS);
	});
	const runAttempt = _assertClassBrand(_Tasks_brand, this, _executeRun).call(this, runId);
	const attempt = runAttempt.then(() => "settled");
	try {
		if (await Promise.race([attempt, budget]) === "budget") {
			this.lifecycle.trackAlarmWork(_classPrivateFieldGet2(_active, this).get(runId)?.promise ?? runAttempt);
			return _assertClassBrand(_Tasks_brand, this, _wakeOutcome).call(this, runId);
		}
	} finally {
		clearTimeout(budgetTimer);
	}
	return _assertClassBrand(_Tasks_brand, this, _wakeOutcome).call(this, runId);
}
/**
* Drive one routed dispatch to completion on this owning facet. There is
* no local budget to race here: the root that sent this message races
* its own await of the call instead, so a full await is safe regardless
* of how long the attempt takes — the call keeps running on this facet
* either way. An already-active attempt only needs its claim refreshed:
* it is already tracked against whichever alarm's breaker domain
* originally dispatched it (a root's pending routed call, or this
* facet's own local alarm).
*/
async function _dispatchRoutedRun(runId) {
	if (_classPrivateFieldGet2(_active, this).get(runId)) {
		_assertClassBrand(_Tasks_brand, this, _refreshClaim).call(this, runId);
		return _assertClassBrand(_Tasks_brand, this, _wakeOutcome).call(this, runId);
	}
	await _assertClassBrand(_Tasks_brand, this, _executeRun).call(this, runId);
	return _assertClassBrand(_Tasks_brand, this, _wakeOutcome).call(this, runId);
}
/**
* Apply the alarm memory-limit breaker policy (#1825) to one run, local to
* whichever Lifecycle owns its storage — the root for an unrouted run, or
* the owning facet when {@link onMemoryLimit} forwarded a routed strike.
*/
async function _applyMemoryLimit(runId, context) {
	if (context.sealed) {
		await _assertClassBrand(_Tasks_brand, this, _settleFailed).call(this, runId, null, {
			name: "TaskMemoryLimitSealed",
			message: "Sealed by the alarm memory-limit circuit breaker (#1825) after consecutive Durable Object memory-limit resets."
		});
		return;
	}
	if (context.nextTime === void 0) return;
	const now = Date.now();
	_get_store.call(_assertClassBrand(_Tasks_brand, this)).write(`UPDATE cf_agents_task_runs
       SET generation = NULL,
           next_at = CASE
             WHEN next_at IS NULL OR next_at < ? THEN ?
             ELSE next_at
           END,
           updated_at = ?
       WHERE run_id = ?
         AND state IN ('pending', 'waiting', 'running')`, [
		context.nextTime,
		context.nextTime,
		now,
		runId
	]);
	await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, runId);
}
/** Accept a run of any resolvable definition, reserved names included. */
async function _acceptReserved(definition, input, options, startMode) {
	if (!_assertClassBrand(_Tasks_brand, this, _hasDefinition).call(this, definition)) throw new Error(`Unknown Task definition "${definition}": not declared on this Tasks`);
	return _assertClassBrand(_Tasks_brand, this, _accept).call(this, definition, input, options, startMode);
}
/**
* The queue outcome for one run's wake job, derived from the run row's
* authoritative `next_at` after dispatch. A same-id `#syncWake` push made
* mid-drive supersedes this return at the queue (newer pushes win over
* drive results), but both are computed from the same row, so the row is
* the single source of truth for whether — and when — the run wakes
* again either way.
*/
function _wakeOutcome(runId) {
	const next = _get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      SELECT next_at FROM cf_agents_task_runs
      WHERE run_id = ${runId}
        AND state IN ('pending', 'waiting', 'running')
    `[0]?.next_at;
	return typeof next === "number" ? { rescheduleAt: next } : void 0;
}
/**
* Mirror one run's authoritative deadline into the Lifecycle job queue:
* a non-terminal run with a `next_at` gets one job (id = `task:` plus the
* run id, so a retime is a same-id replace); anything else cancels the
* mirror. The prefix keeps caller-selected run IDs inside Tasks' own job
* namespace. Every durable mutation of a run's deadline or state funnels
* through here.
*
* @returns False when the queue already carried exactly this wake and
* nothing was written — a same-values upsert is still a billed row write.
*/
async function _syncWake(runId) {
	const next = _get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      SELECT next_at FROM cf_agents_task_runs
      WHERE run_id = ${runId}
        AND state IN ('pending', 'waiting', 'running')
    `[0]?.next_at ?? null;
	if (this.lifecycle.routes.source) return await this.lifecycle.routes.toRoot({
		type: "syncWake",
		runId,
		next
	});
	const jobId = `${WAKE_JOB_PREFIX}${runId}`;
	if (next === null) {
		await this.lifecycle.jobs.cancel(jobId);
		return true;
	}
	const existing = this.lifecycle.jobs.get(jobId);
	if (existing?.fn === WAKE_JOB_FN && existing.time === next && existing.retry?.maxAttempts === WAKE_JOB_RETRY.maxAttempts) return false;
	await this.lifecycle.jobs.push({
		id: jobId,
		fn: WAKE_JOB_FN,
		time: next,
		payload: { runId },
		retry: WAKE_JOB_RETRY
	});
	return true;
}
/** Mirror a routed facet's run deadline into this root's job queue. */
async function _syncRoutedWake(owner, runId, next) {
	const jobId = `${WAKE_JOB_PREFIX}${owner.key}:${runId}`;
	if (next === null) {
		await this.lifecycle.jobs.cancel(jobId);
		return true;
	}
	const existing = this.lifecycle.jobs.get(jobId);
	if (existing?.fn === WAKE_JOB_FN && existing.time === next && existing.retry?.maxAttempts === WAKE_JOB_RETRY.maxAttempts) return false;
	await this.lifecycle.jobs.push({
		id: jobId,
		fn: WAKE_JOB_FN,
		time: next,
		payload: {
			runId,
			owner_path: owner.data,
			owner_path_key: owner.key
		},
		retry: WAKE_JOB_RETRY
	});
	return true;
}
/** Mirror every non-terminal run into the queue (startup reconcile). */
async function _syncAllWakes() {
	const rows = _get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      SELECT run_id FROM cf_agents_task_runs
      WHERE state IN ('pending', 'waiting', 'running')
        AND next_at IS NOT NULL
    `;
	let pushed = false;
	for (const { run_id } of rows) if (await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, run_id)) pushed = true;
	if (rows.length > 0 && !pushed) await this.lifecycle.jobs.rearm();
}
async function _accept(definition, input, options = {}, startMode = "warm") {
	await this.lifecycle.ready();
	if (options.runId !== void 0 && options.runId.length === 0) throw new Error("runId must be a non-empty string when provided");
	if (options.idempotencyKey !== void 0 && options.idempotencyKey.length === 0) throw new Error("idempotencyKey must be a non-empty string when provided");
	const inputJson = serializeTaskValue(input, `input for Task definition "${definition}"`);
	const metadataJson = serializeTaskValue(options.metadata, `metadata for Task definition "${definition}"`);
	const existing = (options.runId !== void 0 ? _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(options.runId) : void 0) ?? (options.idempotencyKey !== void 0 ? _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRunByKey(options.idempotencyKey) : void 0);
	if (existing) {
		if (existing.definition !== definition) throw new Error(`Task run "${existing.run_id}" already belongs to definition "${existing.definition}"; refusing to reuse its ${options.runId !== void 0 ? "run ID" : "idempotency key"} for "${definition}"`);
		if (options.idempotencyKey !== void 0 && existing.idempotency_key !== options.idempotencyKey) throw new Error(`Task run "${existing.run_id}" carries idempotency key ${existing.idempotency_key === null ? "none" : `"${existing.idempotency_key}"`}; refusing to join it with conflicting key "${options.idempotencyKey}"`);
		await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, existing.run_id);
		return {
			runId: existing.run_id,
			definition,
			accepted: false,
			state: existing.state,
			createdAt: existing.created_at
		};
	}
	const runId = options.runId ?? `task_${nanoid()}`;
	const now = Date.now();
	_get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      INSERT INTO cf_agents_task_runs
        (run_id, definition, input, state, metadata, idempotency_key, retain,
         attempt, next_at, cancel_requested, created_at, updated_at)
      VALUES
        (${runId}, ${definition}, ${inputJson}, 'pending', ${metadataJson},
         ${options.idempotencyKey ?? null}, ${options.retain === false ? 0 : 1},
         0, ${now}, 0, ${now}, ${now})
    `;
	await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, runId);
	_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:accepted", {
		runId,
		definition,
		accepted: true
	});
	if (startMode === "warm" && this.lifecycle.status() !== "starting") _assertClassBrand(_Tasks_brand, this, _executeRun).call(this, runId).catch(() => {});
	return {
		runId,
		definition,
		accepted: true,
		state: "pending",
		createdAt: now
	};
}
/** Claim and drive one due run to its next durable boundary. */
async function _executeRun(runId) {
	if (_classPrivateFieldGet2(_active, this).has(runId)) return;
	const row = _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId);
	if (!row || TERMINAL_STATES.has(row.state)) return;
	const now = Date.now();
	if (row.cancel_requested === 1) {
		await _assertClassBrand(_Tasks_brand, this, _settleCancelled).call(this, runId, null, row.cancel_reason ?? void 0);
		return;
	}
	if (row.next_at !== null && row.next_at > now) return;
	const handler = _assertClassBrand(_Tasks_brand, this, _resolveDefinition).call(this, row.definition);
	if (!handler) {
		const error = new MissingTaskDefinitionError(row.definition);
		console.error(error.message);
		await _assertClassBrand(_Tasks_brand, this, _settleFailed).call(this, runId, null, toErrorSummary(error));
		await _assertClassBrand(_Tasks_brand, this, _observeError).call(this, error);
		return;
	}
	const interrupted = row.state === "running" ? _assertClassBrand(_Tasks_brand, this, _interruptedStep).call(this, runId) : null;
	if (row.state === "running") _assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:attempt:interrupted", {
		runId,
		definition: row.definition,
		attempt: row.attempt,
		step: interrupted?.name ?? null
	});
	const generation = nanoid();
	const attempt = row.attempt + 1;
	_get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      UPDATE cf_agents_task_runs
      SET state = 'running', attempt = ${attempt}, generation = ${generation},
          started_at = coalesce(started_at, ${now}),
          next_at = ${now + _assertClassBrand(_Tasks_brand, this, _claimTimeoutMs).call(this)}, wait_reason = NULL,
          updated_at = ${now}
      WHERE run_id = ${runId}
        AND state IN ('pending', 'waiting', 'running')
    `;
	await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, runId);
	const controller = new AbortController();
	_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:attempt:started", {
		runId,
		definition: row.definition,
		attempt
	});
	const promise = _assertClassBrand(_Tasks_brand, this, _runAttempt).call(this, row, handler, generation, attempt, controller, interrupted, now);
	_classPrivateFieldGet2(_active, this).set(runId, {
		generation,
		controller,
		promise
	});
	try {
		await promise;
	} finally {
		_classPrivateFieldGet2(_active, this).delete(runId);
	}
}
/** Run one claimed attempt and persist its outcome, generation-fenced. */
async function _runAttempt(row, handler, generation, attempt, controller, interrupted, claimedAtMs) {
	const runId = row.run_id;
	const input = deserializeTaskValue(row.input);
	const step = new ReplayStep(_assertClassBrand(_Tasks_brand, this, _createEngine).call(this, runId, row.definition, generation, controller, claimedAtMs), {
		startsLive: attempt === 1,
		interrupted
	});
	try {
		const resultJson = serializeTaskValue(await this.lifecycle.runInHostContext(() => handler(input, step)), `result of Task definition "${row.definition}"`);
		if (_get_store.call(_assertClassBrand(_Tasks_brand, this)).fencedWrite(runId, generation, `UPDATE cf_agents_task_runs
         SET state = 'completed', result = ?, generation = NULL, next_at = NULL,
             settled_at = ?, updated_at = ?
         WHERE run_id = ? AND generation = ? AND state = 'running'`, [
			resultJson,
			Date.now(),
			Date.now()
		])) {
			_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:completed", {
				runId,
				definition: row.definition
			});
			await _assertClassBrand(_Tasks_brand, this, _finishTerminalSettlement).call(this, runId, row);
		}
	} catch (thrown) {
		await _assertClassBrand(_Tasks_brand, this, _settleThrown).call(this, row, generation, thrown);
	}
}
/** Persist a non-completed attempt outcome. */
async function _settleThrown(row, generation, thrown) {
	const runId = row.run_id;
	if (thrown instanceof AttemptSupersededError) return;
	if (isPlatformFailure(thrown)) throw thrown;
	if (isTaskCancellation(thrown)) {
		await _assertClassBrand(_Tasks_brand, this, _settleCancelled).call(this, runId, generation, thrown.reason);
		return;
	}
	if (isTaskSuspension(thrown)) {
		const current = _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId);
		if (current?.cancel_requested === 1) {
			await _assertClassBrand(_Tasks_brand, this, _settleCancelled).call(this, runId, generation, current.cancel_reason ?? void 0);
			return;
		}
		if (_get_store.call(_assertClassBrand(_Tasks_brand, this)).fencedWrite(runId, generation, `UPDATE cf_agents_task_runs
         SET state = 'waiting', wait_reason = ?, next_at = ?, generation = NULL,
             updated_at = ?
         WHERE run_id = ? AND generation = ? AND state = 'running'`, [
			thrown.reason,
			thrown.wakeAt,
			Date.now()
		])) {
			_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:waiting", {
				runId,
				definition: row.definition,
				reason: thrown.reason,
				wakeAt: thrown.wakeAt
			});
			await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, runId);
		}
		return;
	}
	const summary = toErrorSummary(thrown);
	if (await _assertClassBrand(_Tasks_brand, this, _settleFailed).call(this, runId, generation, summary)) console.error(`Task run "${runId}" (definition "${row.definition}") failed: ${summary.name}: ${summary.message}`);
	await _assertClassBrand(_Tasks_brand, this, _observeError).call(this, thrown);
}
async function _observeError(error) {
	if (!_classPrivateFieldGet2(_onError, this)) return;
	try {
		await this.lifecycle.runInHostContext(() => _classPrivateFieldGet2(_onError, this)?.call(this, error));
	} catch {}
}
function _createEngine(runId, definition, generation, controller, claimedAtMs) {
	return createTaskStepEngine({
		store: _get_store.call(_assertClassBrand(_Tasks_brand, this)),
		runId,
		generation,
		signal: controller.signal,
		claimTimeoutMs: () => _assertClassBrand(_Tasks_brand, this, _claimTimeoutMs).call(this),
		claimedAtMs,
		claimRefreshAfterMs: CLAIM_SLACK_MS / 2,
		defaults: _classPrivateFieldGet2(_stepDefaults, this),
		emit: (type, payload) => _assertClassBrand(_Tasks_brand, this, _emit).call(this, type, {
			runId,
			definition,
			...payload
		})
	});
}
/** The step a lost attempt left mid-execution — replay-entry evidence. */
function _interruptedStep(runId) {
	const rows = _get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      SELECT step_name, attempt FROM cf_agents_task_steps
      WHERE run_id = ${runId} AND state = 'running'
      ORDER BY started_at DESC
      LIMIT 1
    `;
	return rows[0] ? {
		name: rows[0].step_name,
		attempt: rows[0].attempt
	} : null;
}
/**
* Settle one run as cancelled and sync its queue mirror. Fenced when a
* generation is supplied.
*/
async function _settleCancelled(runId, generation, reason) {
	const now = Date.now();
	let settled;
	if (generation !== null) settled = _get_store.call(_assertClassBrand(_Tasks_brand, this)).fencedWrite(runId, generation, `UPDATE cf_agents_task_runs
         SET state = 'cancelled', cancel_requested = 1, cancel_reason = ?,
             generation = NULL, next_at = NULL, settled_at = ?, updated_at = ?
         WHERE run_id = ? AND generation = ?
           AND state = 'running'`, [
		reason ?? null,
		now,
		now
	]);
	else settled = _get_store.call(_assertClassBrand(_Tasks_brand, this)).write(`UPDATE cf_agents_task_runs
         SET state = 'cancelled', cancel_requested = 1, cancel_reason = ?,
             generation = NULL, next_at = NULL, settled_at = ?, updated_at = ?
         WHERE run_id = ?
           AND state IN ('pending', 'waiting', 'running')`, [
		reason ?? null,
		now,
		now,
		runId
	]) > 0;
	const row = settled ? _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId) : void 0;
	if (row) {
		_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:cancelled", {
			runId,
			definition: row.definition,
			reason: reason ?? null
		});
		await _assertClassBrand(_Tasks_brand, this, _finishTerminalSettlement).call(this, runId, row);
	}
}
/**
* Settle one run as failed and sync its queue mirror. Fenced when a
* generation is supplied.
*/
async function _settleFailed(runId, generation, error) {
	const now = Date.now();
	let settled;
	if (generation !== null) settled = _get_store.call(_assertClassBrand(_Tasks_brand, this)).fencedWrite(runId, generation, `UPDATE cf_agents_task_runs
         SET state = 'failed', error_name = ?, error_message = ?,
             generation = NULL, next_at = NULL, settled_at = ?, updated_at = ?
         WHERE run_id = ? AND generation = ?
           AND state = 'running'`, [
		error.name,
		error.message,
		now,
		now
	]);
	else settled = _get_store.call(_assertClassBrand(_Tasks_brand, this)).write(`UPDATE cf_agents_task_runs
         SET state = 'failed', error_name = ?, error_message = ?,
             generation = NULL, next_at = NULL, settled_at = ?, updated_at = ?
         WHERE run_id = ?
           AND state IN ('pending', 'waiting', 'running')`, [
		error.name,
		error.message,
		now,
		now,
		runId
	]) > 0;
	const row = settled ? _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId) : void 0;
	if (row) {
		_assertClassBrand(_Tasks_brand, this, _emit).call(this, "task:failed", {
			runId,
			definition: row.definition,
			error: error.name
		});
		await _assertClassBrand(_Tasks_brand, this, _finishTerminalSettlement).call(this, runId, row);
	}
	return settled;
}
/** Apply terminal retention policy, then remove the run's wake mirror. */
async function _finishTerminalSettlement(runId, row) {
	if (row?.retain === 0) _get_store.call(_assertClassBrand(_Tasks_brand, this)).deleteRun(runId);
	await _assertClassBrand(_Tasks_brand, this, _syncWake).call(this, runId);
}
/** Make deadlines sane after a fresh isolate: interrupted work wakes now. */
function _reconcile() {
	const now = Date.now();
	_get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      UPDATE cf_agents_task_runs SET next_at = ${now}, updated_at = ${now}
      WHERE state = 'running' AND generation IS NOT NULL
    `;
	_get_store.call(_assertClassBrand(_Tasks_brand, this)).sql`
      UPDATE cf_agents_task_runs SET next_at = ${now}, updated_at = ${now}
      WHERE state IN ('pending', 'waiting') AND next_at IS NULL
    `;
}
async function _snapshot(runId, definition) {
	await this.lifecycle.ready();
	const row = _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRun(runId);
	if (!row) return null;
	if (definition !== void 0 && row.definition !== definition) return null;
	return _get_store.call(_assertClassBrand(_Tasks_brand, this)).rowToSnapshot(row);
}
async function _snapshotByKey(idempotencyKey, definition) {
	await this.lifecycle.ready();
	const row = _get_store.call(_assertClassBrand(_Tasks_brand, this)).getRunByKey(idempotencyKey);
	if (!row) return null;
	if (definition !== void 0 && row.definition !== definition) return null;
	return _get_store.call(_assertClassBrand(_Tasks_brand, this)).rowToSnapshot(row);
}
function _emit(type, payload) {
	this.lifecycle.events.emit(type, payload);
}
//#endregion
export { DuplicateTaskStepError as a, TaskReplayDivergedError as c, MAX_SERIALIZED_BYTES as i, TaskSerializationError as l, setTaskDefinitionResolver as n, MissingTaskDefinitionError as o, setTaskRoutedMemoryLimitHandler as r, NonRetryableError as s, Tasks as t };

//# sourceMappingURL=tasks-D4nLqVSI.js.map