UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

3,193 lines 135 kB
import { C as resolveExpiresAtMsFromDurationMs, m as isFutureDateTimestampMs, t as MAX_DATE_TIMESTAMP_MS } from "./number-coercion-CJQ8TR--.js";
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import "./error-runtime-C8vbtAJt.js";
import "./number-runtime-DBLVDypr.js";
import { t as createWorkboardSqliteStores } from "./sqlite-store-kIpvsc4c.js";
import { randomUUID } from "node:crypto";
//#region extensions/workboard/src/types.ts
const WORKBOARD_STATUSES = [
	"triage",
	"backlog",
	"todo",
	"scheduled",
	"ready",
	"running",
	"review",
	"blocked",
	"done"
];
const WORKBOARD_PRIORITIES = [
	"low",
	"normal",
	"high",
	"urgent"
];
const WORKBOARD_EXECUTION_ENGINES = ["codex", "claude"];
const WORKBOARD_EXECUTION_MODES = ["autonomous", "manual"];
const WORKBOARD_EXECUTION_STATUSES = [
	"idle",
	"running",
	"review",
	"blocked",
	"done"
];
const WORKBOARD_EVENT_KINDS = [
	"created",
	"edited",
	"moved",
	"linked",
	"specified",
	"decomposed",
	"claimed",
	"heartbeat",
	"execution_updated",
	"attempt_started",
	"attempt_updated",
	"comment_added",
	"link_added",
	"proof_added",
	"artifact_added",
	"attachment_added",
	"diagnostic",
	"notification",
	"dispatch",
	"orchestration",
	"protocol_violation",
	"archived",
	"unarchived",
	"stale"
];
const WORKBOARD_ATTEMPT_STATUSES = [
	"running",
	"succeeded",
	"failed",
	"blocked",
	"stopped"
];
const WORKBOARD_LINK_TYPES = [
	"parent",
	"child",
	"blocks",
	"blocked_by",
	"relates_to"
];
const WORKBOARD_PROOF_STATUSES = [
	"passed",
	"failed",
	"skipped",
	"unknown"
];
const WORKBOARD_TEMPLATE_IDS = [
	"bugfix",
	"docs",
	"release",
	"pr_review",
	"plugin"
];
const WORKBOARD_DIAGNOSTIC_KINDS = [
	"stranded_ready",
	"running_without_heartbeat",
	"blocked_too_long",
	"repeated_failures",
	"missing_proof",
	"orphaned_session"
];
const WORKBOARD_DIAGNOSTIC_SEVERITIES = [
	"warning",
	"error",
	"critical"
];
const WORKBOARD_NOTIFICATION_KINDS = [
	"completed",
	"failed",
	"stale"
];
//#endregion
//#region extensions/workboard/src/store.ts
const POSITION_STEP = 1e3;
const MAX_CARDS = 2e3;
const MAX_CARD_LINKS = 50;
const MAX_ATTACHMENT_ENTRIES = MAX_CARDS * 21;
const MAX_ATTACHMENT_BYTES = 256 * 1024;
const MAX_CARD_METADATA_BYTES = 24 * 1024;
const DEFAULT_CLAIM_TTL_MS = 1800 * 1e3;
const READY_STRANDED_MS = 3600 * 1e3;
const RUNNING_HEARTBEAT_STALE_MS = 1200 * 1e3;
const BLOCKED_TOO_LONG_MS = 1440 * 60 * 1e3;
const CLAIM_RECLAIM_MS = 300 * 1e3;
function secondsToDurationMs(seconds) {
	const ms = Math.trunc(seconds) * 1e3;
	return Number.isFinite(ms) ? Math.min(MAX_DATE_TIMESTAMP_MS, Math.max(1, ms)) : MAX_DATE_TIMESTAMP_MS;
}
function addWorkboardDurationMs(now, durationMs) {
	return resolveExpiresAtMsFromDurationMs(durationMs, { nowMs: now }) ?? 864e13;
}
function normalizeOptionalString(value) {
	return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function normalizeBoardId(value, fallback) {
	const raw = normalizeBoundedString(value, fallback, 80, "board id");
	if (!raw) return;
	const boardId = raw.toLowerCase();
	if (!/^[a-z0-9][a-z0-9._-]{0,79}$/.test(boardId)) throw new Error("board id must start with a letter or number and use letters, numbers, dots, dashes, or underscores.");
	return boardId;
}
function normalizeBoardIdRequired(value) {
	return normalizeBoardId(value) ?? "default";
}
function normalizeBoardMetadata(input, fallback, now = Date.now()) {
	const id = normalizeBoardId(input.id, fallback?.id) ?? "default";
	const name = normalizeBoundedString(input.name, fallback?.name, 120, "board name");
	const description = normalizeBoundedString(input.description, fallback?.description, 1e3, "board description");
	const icon = normalizeBoundedString(input.icon, fallback?.icon, 40, "board icon");
	const color = normalizeBoundedString(input.color, fallback?.color, 40, "board color");
	const defaultWorkspace = Object.hasOwn(input, "defaultWorkspace") ? normalizeWorkspace(input.defaultWorkspace, fallback?.defaultWorkspace) : fallback?.defaultWorkspace;
	const orchestration = Object.hasOwn(input, "orchestration") ? normalizeOrchestration(input.orchestration, fallback?.orchestration) : fallback?.orchestration;
	const archivedAt = Object.hasOwn(input, "archived") ? input.archived === false ? void 0 : now : fallback?.archivedAt;
	return {
		id,
		...name ? { name } : {},
		...description ? { description } : {},
		...icon ? { icon } : {},
		...color ? { color } : {},
		...defaultWorkspace ? { defaultWorkspace } : {},
		...orchestration ? { orchestration } : {},
		createdAt: fallback?.createdAt ?? now,
		updatedAt: now,
		...archivedAt ? { archivedAt } : {}
	};
}
function normalizeOrchestration(value, fallback) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return fallback;
	const record = value;
	const autoDecompose = typeof record.autoDecompose === "boolean" ? record.autoDecompose : fallback?.autoDecompose;
	const autoDecomposePerDispatch = typeof record.autoDecomposePerDispatch === "number" && Number.isFinite(record.autoDecomposePerDispatch) ? Math.max(1, Math.min(20, Math.trunc(record.autoDecomposePerDispatch))) : fallback?.autoDecomposePerDispatch;
	const defaultAssignee = normalizeBoundedString(record.defaultAssignee, fallback?.defaultAssignee, 120, "default assignee");
	const orchestratorProfile = normalizeBoundedString(record.orchestratorProfile, fallback?.orchestratorProfile, 120, "orchestrator profile");
	const next = {
		...autoDecompose !== void 0 ? { autoDecompose } : {},
		...autoDecomposePerDispatch ? { autoDecomposePerDispatch } : {},
		...defaultAssignee ? { defaultAssignee } : {},
		...orchestratorProfile ? { orchestratorProfile } : {}
	};
	return Object.keys(next).length ? next : void 0;
}
function normalizeNotificationKinds(value) {
	if (value == null) return;
	const entries = typeof value === "string" ? value.split(",") : Array.isArray(value) ? value : [];
	const kinds = [];
	for (const entry of entries) {
		const kind = typeof entry === "string" ? entry.trim() : "";
		if (!WORKBOARD_NOTIFICATION_KINDS.includes(kind)) throw new Error(`notification kind must be one of: ${WORKBOARD_NOTIFICATION_KINDS.join(", ")}.`);
		const notificationKind = kind;
		if (!kinds.includes(notificationKind)) kinds.push(notificationKind);
	}
	return kinds.length ? kinds : void 0;
}
function normalizeNotificationSubscription(input, fallback, now = Date.now()) {
	const boardId = normalizeBoardId(input.boardId, fallback?.boardId) ?? "default";
	const cardId = normalizeBoundedString(input.cardId, fallback?.cardId, 120, "card id");
	const sessionKey = normalizeBoundedString(input.sessionKey, fallback?.sessionKey, 240, "session key");
	const runId = normalizeBoundedString(input.runId, fallback?.runId, 160, "run id");
	const target = normalizeBoundedString(input.target, fallback?.target, 240, "notification target");
	if (!cardId && !sessionKey && !runId && !target) throw new Error("notification subscription needs cardId, sessionKey, runId, or target.");
	const eventKinds = normalizeNotificationKinds(input.eventKinds);
	return {
		id: fallback?.id ?? randomUUID(),
		boardId,
		...cardId ? { cardId } : {},
		...sessionKey ? { sessionKey } : {},
		...runId ? { runId } : {},
		...target ? { target } : {},
		...eventKinds ? { eventKinds } : {},
		...fallback?.lastEventAt ? { lastEventAt: fallback.lastEventAt } : {},
		...fallback?.lastEventId ? { lastEventId: fallback.lastEventId } : {},
		...fallback?.lastEventSequence ? { lastEventSequence: fallback.lastEventSequence } : {},
		...fallback?.deliveredEventIds?.length ? { deliveredEventIds: fallback.deliveredEventIds } : {},
		createdAt: fallback?.createdAt ?? now,
		updatedAt: now
	};
}
function normalizeTitle(value) {
	const title = normalizeOptionalString(value);
	if (!title) throw new Error("title is required.");
	if (title.length > 180) throw new Error("title must be 180 characters or fewer.");
	return title;
}
function normalizeNotes(value) {
	const notes = normalizeOptionalString(value);
	if (!notes) return;
	if (notes.length > 4e3) throw new Error("notes must be 4000 characters or fewer.");
	return notes;
}
function normalizeBoundedString(value, fallback, maxLength, fieldName) {
	const normalized = normalizeOptionalString(value);
	if (!normalized) return fallback;
	if (normalized.length > maxLength) throw new Error(`${fieldName} must be ${maxLength} characters or fewer.`);
	return normalized;
}
function normalizeStatus(value, fallback) {
	if (typeof value !== "string" || !value.trim()) return fallback;
	if (WORKBOARD_STATUSES.includes(value)) return value;
	throw new Error(`status must be one of: ${WORKBOARD_STATUSES.join(", ")}.`);
}
function normalizePriority(value, fallback) {
	if (typeof value !== "string" || !value.trim()) return fallback;
	if (WORKBOARD_PRIORITIES.includes(value)) return value;
	throw new Error(`priority must be one of: ${WORKBOARD_PRIORITIES.join(", ")}.`);
}
function normalizeLabels(value, fallback = []) {
	if (value == null) return fallback;
	const entries = typeof value === "string" ? value.split(",") : Array.isArray(value) ? value : void 0;
	if (!entries) throw new Error("labels must be an array or comma-separated string.");
	const labels = [];
	for (const entry of entries) {
		const label = normalizeOptionalString(entry);
		if (!label || labels.includes(label)) continue;
		if (label.length > 40) throw new Error("labels must be 40 characters or fewer.");
		labels.push(label);
		if (labels.length >= 12) break;
	}
	return labels;
}
function normalizeStringList(value, fieldName, maxLength = 80) {
	if (value == null) return [];
	const entries = typeof value === "string" ? value.split(",") : Array.isArray(value) ? value : void 0;
	if (!entries) throw new Error(`${fieldName} must be an array or comma-separated string.`);
	const values = [];
	for (const entry of entries) {
		if (Array.isArray(value) && typeof entry !== "string") throw new Error(`${fieldName} entries must be strings.`);
		const normalized = normalizeBoundedString(entry, void 0, maxLength, fieldName);
		if (normalized && !values.includes(normalized)) values.push(normalized);
		if (values.length > 20) throw new Error(`${fieldName} supports at most 20 entries.`);
	}
	return values;
}
function normalizePosition(value, fallback) {
	if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
	return Math.max(0, Math.trunc(value));
}
function normalizePositiveInteger$1(value, fieldName) {
	if (value == null || value === "") return;
	if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${fieldName} must be a number.`);
	return Math.max(1, Math.trunc(value));
}
function isAbsoluteWorkspacePath(value) {
	return value.startsWith("/") || /^[A-Za-z]:[\\/]/.test(value) || /^\\\\[^\\]+\\[^\\]+/.test(value);
}
function normalizeWorkspace(value, fallback) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return fallback;
	const record = value;
	const kind = record.kind === "scratch" || record.kind === "dir" || record.kind === "worktree" ? record.kind : fallback?.kind;
	if (!kind) throw new Error("workspace kind must be scratch, dir, or worktree.");
	const workspacePath = normalizeBoundedString(record.path, fallback?.path, 2e3, "workspace path");
	if (kind === "dir" && (!workspacePath || !isAbsoluteWorkspacePath(workspacePath))) throw new Error("dir workspace path must be absolute.");
	const branch = normalizeBoundedString(record.branch, fallback?.branch, 160, "workspace branch");
	return {
		kind,
		...workspacePath ? { path: workspacePath } : {},
		...branch ? { branch } : {}
	};
}
function normalizeAutomation(value, fallback = {}) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return Object.keys(fallback).length ? fallback : void 0;
	const record = value;
	const tenant = normalizeBoundedString(record.tenant, fallback.tenant, 80, "tenant");
	const boardId = Object.hasOwn(record, "boardId") ? normalizeBoardId(record.boardId, fallback.boardId) : fallback.boardId;
	const createdByCardId = normalizeBoundedString(record.createdByCardId, fallback.createdByCardId, 120, "created by card id");
	const idempotencyKey = normalizeBoundedString(record.idempotencyKey, fallback.idempotencyKey, 160, "idempotency key");
	const summary = normalizeBoundedString(record.summary, fallback.summary, 2e3, "summary");
	const skills = Object.hasOwn(record, "skills") ? normalizeStringList(record.skills, "skills") : fallback.skills;
	const createdCardIds = Object.hasOwn(record, "createdCardIds") ? normalizeStringList(record.createdCardIds, "created card ids", 120) : fallback.createdCardIds;
	const scheduledAt = Object.hasOwn(record, "scheduledAt") ? normalizeTimestamp(record.scheduledAt, 0) || void 0 : fallback.scheduledAt;
	const maxRuntimeSeconds = Object.hasOwn(record, "maxRuntimeSeconds") ? normalizePositiveInteger$1(record.maxRuntimeSeconds, "max runtime seconds") : fallback.maxRuntimeSeconds;
	const maxRetries = Object.hasOwn(record, "maxRetries") ? normalizePositiveInteger$1(record.maxRetries, "max retries") : fallback.maxRetries;
	const dispatchCount = Object.hasOwn(record, "dispatchCount") ? normalizeTimestamp(record.dispatchCount, 0) || void 0 : fallback.dispatchCount;
	const lastDispatchAt = Object.hasOwn(record, "lastDispatchAt") ? normalizeTimestamp(record.lastDispatchAt, 0) || void 0 : fallback.lastDispatchAt;
	const workspace = Object.hasOwn(record, "workspace") ? normalizeWorkspace(record.workspace, fallback.workspace) : fallback.workspace;
	const next = removeUndefinedAutomationFields({
		...tenant ? { tenant } : {},
		...boardId ? { boardId } : {},
		...createdByCardId ? { createdByCardId } : {},
		...idempotencyKey ? { idempotencyKey } : {},
		...skills?.length ? { skills } : {},
		...workspace ? { workspace } : {},
		...maxRuntimeSeconds ? { maxRuntimeSeconds } : {},
		...maxRetries ? { maxRetries } : {},
		...scheduledAt ? { scheduledAt } : {},
		...summary ? { summary } : {},
		...createdCardIds?.length ? { createdCardIds } : {},
		...dispatchCount ? { dispatchCount } : {},
		...lastDispatchAt ? { lastDispatchAt } : {}
	});
	return Object.keys(next).length ? next : void 0;
}
function deriveChildIdempotencyKey(parentKey, index) {
	if (!parentKey) return;
	const key = `${parentKey}:child:${index}`;
	return key.length <= 160 ? key : void 0;
}
function normalizeExecutionEngine(value, fallback) {
	if (typeof value === "string" && WORKBOARD_EXECUTION_ENGINES.includes(value)) return value;
	return fallback;
}
function normalizeExecutionMode(value, fallback) {
	if (typeof value === "string" && WORKBOARD_EXECUTION_MODES.includes(value)) return value;
	return fallback;
}
function normalizeExecutionStatus(value, fallback) {
	if (typeof value === "string" && WORKBOARD_EXECUTION_STATUSES.includes(value)) return value;
	return fallback;
}
function normalizeAttemptStatus(value, fallback) {
	if (typeof value === "string" && WORKBOARD_ATTEMPT_STATUSES.includes(value)) return value;
	return fallback;
}
function normalizeLinkType(value, fallback) {
	if (typeof value === "string" && WORKBOARD_LINK_TYPES.includes(value)) return value;
	return fallback;
}
function normalizeProofStatus(value, fallback) {
	if (typeof value === "string" && WORKBOARD_PROOF_STATUSES.includes(value)) return value;
	return fallback;
}
function normalizeTemplateId(value) {
	return typeof value === "string" && WORKBOARD_TEMPLATE_IDS.includes(value) ? value : void 0;
}
function normalizeTimestamp(value, fallback) {
	return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
}
function normalizeEvent(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const kind = WORKBOARD_EVENT_KINDS.includes(record.kind) ? record.kind : null;
	const at = normalizeTimestamp(record.at, 0);
	if (!id || !kind || !at) return null;
	const fromStatus = typeof record.fromStatus === "string" && WORKBOARD_STATUSES.includes(record.fromStatus) ? record.fromStatus : void 0;
	const toStatus = typeof record.toStatus === "string" && WORKBOARD_STATUSES.includes(record.toStatus) ? record.toStatus : void 0;
	const sessionKey = normalizeOptionalString(record.sessionKey);
	const runId = normalizeOptionalString(record.runId);
	return {
		id,
		kind,
		at,
		...fromStatus ? { fromStatus } : {},
		...toStatus ? { toStatus } : {},
		...sessionKey ? { sessionKey } : {},
		...runId ? { runId } : {}
	};
}
function normalizeEvents(value) {
	if (!Array.isArray(value)) return [];
	return value.map(normalizeEvent).filter((event) => event !== null).slice(-50);
}
function normalizeAttempt(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const startedAt = normalizeTimestamp(record.startedAt, 0);
	if (!id || !startedAt) return null;
	const endedAt = normalizeTimestamp(record.endedAt, 0);
	const sessionKey = normalizeOptionalString(record.sessionKey);
	const runId = normalizeOptionalString(record.runId);
	const error = normalizeBoundedString(record.error, void 0, 800, "attempt error");
	const model = normalizeBoundedString(record.model, void 0, 160, "attempt model");
	return {
		id,
		status: normalizeAttemptStatus(record.status, "running"),
		startedAt,
		...endedAt ? { endedAt } : {},
		...typeof record.engine === "string" && WORKBOARD_EXECUTION_ENGINES.includes(record.engine) ? { engine: record.engine } : {},
		...typeof record.mode === "string" && WORKBOARD_EXECUTION_MODES.includes(record.mode) ? { mode: record.mode } : {},
		...model ? { model } : {},
		...sessionKey ? { sessionKey } : {},
		...runId ? { runId } : {},
		...error ? { error } : {}
	};
}
function normalizeComment(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const body = normalizeBoundedString(record.body, void 0, 2e3, "comment body");
	const createdAt = normalizeTimestamp(record.createdAt, 0);
	if (!id || !body || !createdAt) return null;
	const updatedAt = normalizeTimestamp(record.updatedAt, 0);
	return {
		id,
		body,
		createdAt,
		...updatedAt ? { updatedAt } : {}
	};
}
function normalizeLink(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const createdAt = normalizeTimestamp(record.createdAt, 0);
	if (!id || !createdAt) return null;
	const targetCardId = normalizeBoundedString(record.targetCardId, void 0, 120, "link target");
	const title = normalizeBoundedString(record.title, void 0, 180, "link title");
	const url = normalizeBoundedString(record.url, void 0, 2e3, "link URL");
	if (!targetCardId && !url) return null;
	return {
		id,
		type: normalizeLinkType(record.type, "relates_to"),
		createdAt,
		...targetCardId ? { targetCardId } : {},
		...title ? { title } : {},
		...url ? { url } : {}
	};
}
function isDependencyLink(link) {
	return link.type === "parent" || link.type === "child";
}
function normalizeProof(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const createdAt = normalizeTimestamp(record.createdAt, 0);
	if (!id || !createdAt) return null;
	const label = normalizeBoundedString(record.label, void 0, 160, "proof label");
	const command = normalizeBoundedString(record.command, void 0, 1e3, "proof command");
	const url = normalizeBoundedString(record.url, void 0, 2e3, "proof URL");
	const note = normalizeBoundedString(record.note, void 0, 2e3, "proof note");
	return {
		id,
		status: normalizeProofStatus(record.status, "unknown"),
		createdAt,
		...label ? { label } : {},
		...command ? { command } : {},
		...url ? { url } : {},
		...note ? { note } : {}
	};
}
function normalizeArtifact(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id) ?? randomUUID();
	const createdAt = normalizeTimestamp(record.createdAt, Date.now());
	const label = normalizeBoundedString(record.label, void 0, 160, "artifact label");
	const url = normalizeBoundedString(record.url, void 0, 2e3, "artifact URL");
	const artifactPath = normalizeBoundedString(record.path, void 0, 2e3, "artifact path");
	const mimeType = normalizeBoundedString(record.mimeType, void 0, 160, "artifact MIME type");
	if (!url && !artifactPath) return null;
	return {
		id,
		createdAt,
		...label ? { label } : {},
		...url ? { url } : {},
		...artifactPath ? { path: artifactPath } : {},
		...mimeType ? { mimeType } : {}
	};
}
function normalizeAttachment(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const cardId = normalizeBoundedString(record.cardId, void 0, 120, "card id");
	const fileName = normalizeBoundedString(record.fileName, void 0, 240, "attachment file name");
	const createdAt = normalizeTimestamp(record.createdAt, 0);
	const byteSize = typeof record.byteSize === "number" && Number.isFinite(record.byteSize) ? Math.max(0, Math.trunc(record.byteSize)) : 0;
	if (!id || !cardId || !fileName || !createdAt || byteSize <= 0) return null;
	const mimeType = normalizeBoundedString(record.mimeType, void 0, 160, "attachment MIME type");
	const note = normalizeBoundedString(record.note, void 0, 400, "attachment note");
	return {
		id,
		cardId,
		createdAt,
		fileName,
		byteSize,
		...mimeType ? { mimeType } : {},
		...note ? { note } : {}
	};
}
function normalizeWorkerLog(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id);
	const message = normalizeBoundedString(record.message, void 0, 800, "worker log message");
	const createdAt = normalizeTimestamp(record.createdAt, 0);
	if (!id || !message || !createdAt) return null;
	const level = record.level === "warning" || record.level === "error" || record.level === "info" ? record.level : "info";
	const sessionKey = normalizeBoundedString(record.sessionKey, void 0, 240, "session key");
	const runId = normalizeBoundedString(record.runId, void 0, 160, "run id");
	return {
		id,
		level,
		message,
		createdAt,
		...sessionKey ? { sessionKey } : {},
		...runId ? { runId } : {}
	};
}
function normalizeWorkerProtocol(value, fallback) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return fallback;
	const record = value;
	const state = record.state === "idle" || record.state === "running" || record.state === "completed" || record.state === "blocked" || record.state === "violated" ? record.state : fallback?.state;
	if (!state) return;
	const updatedAt = normalizeTimestamp(record.updatedAt, fallback?.updatedAt ?? Date.now());
	const detail = normalizeBoundedString(record.detail, fallback?.detail, 800, "protocol detail");
	return {
		state,
		updatedAt,
		...detail ? { detail } : {}
	};
}
function normalizeAttachmentInput(cardId, input, now) {
	const fileName = normalizeBoundedString(input.fileName, void 0, 240, "attachment file name");
	if (!fileName) throw new Error("attachment fileName is required.");
	const contentBase64 = typeof input.contentBase64 === "string" && input.contentBase64 ? input.contentBase64 : void 0;
	if (!contentBase64) throw new Error("attachment contentBase64 is required.");
	if (!/^[A-Za-z0-9+/]*={0,2}$/.test(contentBase64) || contentBase64.length % 4 !== 0 || contentBase64.length > Math.ceil(MAX_ATTACHMENT_BYTES / 3) * 4) throw new Error("attachment contentBase64 must be canonical base64.");
	const decoded = Buffer.from(contentBase64, "base64");
	if (decoded.toString("base64") !== contentBase64) throw new Error("attachment contentBase64 must be canonical base64.");
	const byteSize = decoded.length;
	if (byteSize <= 0 || byteSize > MAX_ATTACHMENT_BYTES) throw new Error(`attachment must be between 1 and ${MAX_ATTACHMENT_BYTES} bytes.`);
	const mimeType = normalizeBoundedString(input.mimeType, void 0, 160, "attachment MIME type");
	const note = normalizeBoundedString(input.note, void 0, 400, "attachment note");
	return {
		attachment: {
			id: randomUUID(),
			cardId,
			createdAt: now,
			fileName,
			byteSize,
			...mimeType ? { mimeType } : {},
			...note ? { note } : {}
		},
		contentBase64
	};
}
function normalizeClaim(value, fallback) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return fallback;
	const record = value;
	const ownerId = normalizeBoundedString(record.ownerId, fallback?.ownerId, 120, "claim owner");
	const token = normalizeBoundedString(record.token, fallback?.token, 160, "claim token");
	const claimedAt = normalizeTimestamp(record.claimedAt, fallback?.claimedAt ?? Date.now());
	const lastHeartbeatAt = normalizeTimestamp(record.lastHeartbeatAt, fallback?.lastHeartbeatAt ?? claimedAt);
	const expiresAt = normalizeTimestamp(record.expiresAt, fallback?.expiresAt ?? 0);
	if (!ownerId || !token || !claimedAt || !lastHeartbeatAt) return;
	return {
		ownerId,
		token,
		claimedAt,
		lastHeartbeatAt,
		...expiresAt ? { expiresAt } : {}
	};
}
function normalizeDiagnosticAction(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const kind = record.kind === "claim" || record.kind === "unblock" || record.kind === "reassign" || record.kind === "add_proof" || record.kind === "open_session" ? record.kind : void 0;
	const label = normalizeBoundedString(record.label, void 0, 120, "diagnostic action label");
	return kind && label ? {
		kind,
		label
	} : null;
}
function normalizeDiagnostic(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const kind = WORKBOARD_DIAGNOSTIC_KINDS.includes(record.kind) ? record.kind : void 0;
	const severity = WORKBOARD_DIAGNOSTIC_SEVERITIES.includes(record.severity) ? record.severity : "warning";
	const title = normalizeBoundedString(record.title, void 0, 160, "diagnostic title");
	const detail = normalizeBoundedString(record.detail, void 0, 800, "diagnostic detail");
	const firstSeenAt = normalizeTimestamp(record.firstSeenAt, Date.now());
	const lastSeenAt = normalizeTimestamp(record.lastSeenAt, firstSeenAt);
	if (!kind || !title || !detail) return null;
	return {
		kind,
		severity,
		title,
		detail,
		firstSeenAt,
		lastSeenAt,
		count: typeof record.count === "number" && Number.isFinite(record.count) ? Math.max(1, Math.trunc(record.count)) : 1,
		actions: Array.isArray(record.actions) ? record.actions.map(normalizeDiagnosticAction).filter((action) => action !== null).slice(0, 4) : []
	};
}
function normalizeNotification(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return null;
	const record = value;
	const id = normalizeOptionalString(record.id) ?? randomUUID();
	const kind = WORKBOARD_NOTIFICATION_KINDS.includes(record.kind) ? record.kind : void 0;
	const createdAt = normalizeTimestamp(record.createdAt, Date.now());
	const sequence = normalizeTimestamp(record.sequence, 0) || void 0;
	const message = normalizeBoundedString(record.message, void 0, 240, "notification message");
	if (!kind || !message) return null;
	const sessionKey = normalizeBoundedString(record.sessionKey, void 0, 240, "session key");
	const runId = normalizeBoundedString(record.runId, void 0, 120, "run id");
	return {
		id,
		kind,
		createdAt,
		...sequence ? { sequence } : {},
		message,
		...sessionKey ? { sessionKey } : {},
		...runId ? { runId } : {}
	};
}
function normalizeProofInput(input, now) {
	const label = normalizeBoundedString(input.label, void 0, 160, "proof label");
	const command = normalizeBoundedString(input.command, void 0, 1e3, "proof command");
	const url = normalizeBoundedString(input.url, void 0, 2e3, "proof URL");
	const note = normalizeBoundedString(input.note, void 0, 2e3, "proof note");
	return {
		id: randomUUID(),
		status: normalizeProofStatus(input.status, "unknown"),
		createdAt: now,
		...label ? { label } : {},
		...command ? { command } : {},
		...url ? { url } : {},
		...note ? { note } : {}
	};
}
function normalizeMetadata(value, fallback = {}, options = {}) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return trimMetadataToBudget(fallback);
	const record = value;
	const stale = record.stale && typeof record.stale === "object" && !Array.isArray(record.stale) ? record.stale : null;
	const hasArchivedAt = Object.hasOwn(record, "archivedAt");
	const hasStale = Object.hasOwn(record, "stale");
	const hasLifecycleStatusSourceUpdatedAt = Object.hasOwn(record, "lifecycleStatusSourceUpdatedAt");
	const links = Array.isArray(record.links) ? record.links.map(normalizeLink).filter((link) => link !== null) : void 0;
	const normalizedLinks = links === void 0 ? fallback.links : options.allowDependencyLinks === false ? (() => {
		const dependencyLinks = (fallback.links ?? []).filter(isDependencyLink);
		const ordinaryCapacity = Math.max(0, MAX_CARD_LINKS - dependencyLinks.length);
		return [...dependencyLinks.slice(-50), ...ordinaryCapacity > 0 ? links.filter((link) => !isDependencyLink(link)).slice(-ordinaryCapacity) : []];
	})() : links.slice(-50);
	return trimMetadataToBudget({
		attempts: Array.isArray(record.attempts) ? record.attempts.map(normalizeAttempt).filter((attempt) => attempt !== null).slice(-30) : fallback.attempts,
		comments: Array.isArray(record.comments) ? record.comments.map(normalizeComment).filter((comment) => comment !== null).slice(-50) : fallback.comments,
		links: normalizedLinks,
		proof: Array.isArray(record.proof) ? record.proof.map(normalizeProof).filter((proof) => proof !== null).slice(-40) : fallback.proof,
		artifacts: Array.isArray(record.artifacts) ? record.artifacts.map(normalizeArtifact).filter((artifact) => artifact !== null).slice(-40) : fallback.artifacts,
		attachments: Array.isArray(record.attachments) ? record.attachments.map(normalizeAttachment).filter((attachment) => attachment !== null).slice(-20) : fallback.attachments,
		workerLogs: Array.isArray(record.workerLogs) ? record.workerLogs.map(normalizeWorkerLog).filter((log) => log !== null).slice(-40) : fallback.workerLogs,
		workerProtocol: Object.hasOwn(record, "workerProtocol") ? normalizeWorkerProtocol(record.workerProtocol, fallback.workerProtocol) : fallback.workerProtocol,
		automation: Object.hasOwn(record, "automation") ? normalizeAutomation(record.automation, fallback.automation) : fallback.automation,
		claim: Object.hasOwn(record, "claim") ? record.claim ? normalizeClaim(record.claim, fallback.claim) : void 0 : fallback.claim,
		diagnostics: Array.isArray(record.diagnostics) ? record.diagnostics.map(normalizeDiagnostic).filter((diagnosticLocal) => diagnosticLocal !== null).slice(-12) : fallback.diagnostics,
		notifications: Array.isArray(record.notifications) ? record.notifications.map(normalizeNotification).filter((notification) => notification !== null).slice(-20) : fallback.notifications,
		templateId: normalizeTemplateId(record.templateId) ?? fallback.templateId,
		archivedAt: hasArchivedAt ? normalizeTimestamp(record.archivedAt, 0) || void 0 : fallback.archivedAt,
		stale: hasStale ? stale ? {
			detectedAt: normalizeTimestamp(stale.detectedAt, Date.now()),
			lastSessionUpdatedAt: normalizeTimestamp(stale.lastSessionUpdatedAt, 0) || void 0,
			reason: normalizeBoundedString(stale.reason, fallback.stale?.reason, 240, "stale reason") ?? "Session has not reported recent activity."
		} : void 0 : fallback.stale,
		lifecycleStatusSourceUpdatedAt: hasLifecycleStatusSourceUpdatedAt ? normalizeTimestamp(record.lifecycleStatusSourceUpdatedAt, 0) : fallback.lifecycleStatusSourceUpdatedAt,
		failureCount: typeof record.failureCount === "number" && Number.isFinite(record.failureCount) ? Math.max(0, Math.trunc(record.failureCount)) : fallback.failureCount
	});
}
function normalizeExecution(value) {
	if (!value || typeof value !== "object" || Array.isArray(value)) return;
	const record = value;
	const now = Date.now();
	const model = normalizeOptionalString(record.model);
	const id = normalizeOptionalString(record.id) ?? randomUUID();
	if (!model) return;
	const startedAt = normalizeTimestamp(record.startedAt, now);
	const updatedAt = normalizeTimestamp(record.updatedAt, startedAt);
	const sessionKey = normalizeOptionalString(record.sessionKey);
	const runId = normalizeOptionalString(record.runId);
	return {
		id,
		kind: "agent-session",
		engine: normalizeExecutionEngine(record.engine, "codex"),
		mode: normalizeExecutionMode(record.mode, "autonomous"),
		status: normalizeExecutionStatus(record.status, "idle"),
		model,
		startedAt,
		updatedAt,
		...sessionKey ? { sessionKey } : {},
		...runId ? { runId } : {}
	};
}
function syncExecutionSessionKey(execution, sessionKey) {
	if (!execution) return;
	return removeUndefinedExecutionFields({
		...execution,
		sessionKey,
		updatedAt: Date.now()
	});
}
function removeUndefinedExecutionFields(execution) {
	const next = { ...execution };
	if (next.sessionKey === void 0) delete next.sessionKey;
	if (next.runId === void 0) delete next.runId;
	return next;
}
function removeUndefinedAutomationFields(automation) {
	const next = { ...automation };
	for (const key of [
		"tenant",
		"boardId",
		"createdByCardId",
		"idempotencyKey",
		"skills",
		"workspace",
		"maxRuntimeSeconds",
		"maxRetries",
		"scheduledAt",
		"summary",
		"createdCardIds",
		"dispatchCount",
		"lastDispatchAt"
	]) {
		const value = next[key];
		if (value === void 0 || Array.isArray(value) && value.length === 0 || typeof value === "object" && value !== null && Object.keys(value).length === 0) delete next[key];
	}
	return next;
}
function removeUndefinedMetadataFields(metadata) {
	const next = { ...metadata };
	for (const key of [
		"attempts",
		"comments",
		"links",
		"proof",
		"artifacts",
		"attachments",
		"workerLogs",
		"workerProtocol",
		"automation",
		"claim",
		"diagnostics",
		"notifications",
		"templateId",
		"archivedAt",
		"stale",
		"lifecycleStatusSourceUpdatedAt",
		"failureCount"
	]) {
		const value = next[key];
		if (value === void 0 || Array.isArray(value) && value.length === 0 || typeof value === "number" && value === 0 && key === "failureCount") delete next[key];
	}
	return next;
}
function clearDiagnostics(metadata, kinds) {
	if (!metadata?.diagnostics) return metadata ?? {};
	return {
		...metadata,
		diagnostics: metadata.diagnostics.filter((entry) => !kinds.includes(entry.kind))
	};
}
function metadataIsEmpty(metadata) {
	return !metadata || Object.keys(metadata).length === 0;
}
function metadataByteSize(metadata) {
	return Buffer.byteLength(JSON.stringify(metadata), "utf8");
}
function dropFirst(items) {
	if (!items?.length) return;
	const next = items.slice(1);
	return next.length ? next : void 0;
}
function dropFirstNonDependencyLink(items) {
	if (!items?.length) return;
	const index = items.findIndex((link) => !isDependencyLink(link));
	if (index < 0) return items.slice();
	const next = items.filter((_, itemIndex) => itemIndex !== index);
	return next.length ? next : void 0;
}
function appendLinkPreservingDependencies(links, link) {
	const next = [...links, link];
	if (next.length <= MAX_CARD_LINKS) return next;
	const dropIndex = next.findIndex((entry) => !isDependencyLink(entry));
	if (dropIndex < 0 || dropIndex === next.length - 1) throw new Error("card link limit reached.");
	return next.filter((_, index) => index !== dropIndex);
}
function trimMetadataToBudget(metadata) {
	let next = removeUndefinedMetadataFields(metadata);
	while (metadataByteSize(next) > MAX_CARD_METADATA_BYTES) {
		const currentSize = metadataByteSize(next);
		if (next.attempts?.length) next = removeUndefinedMetadataFields({
			...next,
			attempts: dropFirst(next.attempts)
		});
		else if (next.diagnostics?.length) next = removeUndefinedMetadataFields({
			...next,
			diagnostics: dropFirst(next.diagnostics)
		});
		else if (next.notifications?.length) next = removeUndefinedMetadataFields({
			...next,
			notifications: dropFirst(next.notifications)
		});
		else if (next.proof?.length) next = removeUndefinedMetadataFields({
			...next,
			proof: dropFirst(next.proof)
		});
		else if (next.artifacts?.length) next = removeUndefinedMetadataFields({
			...next,
			artifacts: dropFirst(next.artifacts)
		});
		else if (next.attachments?.length) next = removeUndefinedMetadataFields({
			...next,
			attachments: dropFirst(next.attachments)
		});
		else if (next.workerLogs?.length) next = removeUndefinedMetadataFields({
			...next,
			workerLogs: dropFirst(next.workerLogs)
		});
		else if (next.links?.length) {
			const links = dropFirstNonDependencyLink(next.links);
			if (links?.length === next.links.length) next = removeUndefinedMetadataFields({
				...next,
				comments: dropFirst(next.comments)
			});
			else next = removeUndefinedMetadataFields({
				...next,
				links
			});
		} else if (next.comments?.length) next = removeUndefinedMetadataFields({
			...next,
			comments: dropFirst(next.comments)
		});
		if (metadataByteSize(next) >= currentSize) break;
	}
	return next;
}
function compareCards(left, right) {
	if (left.status !== right.status) return WORKBOARD_STATUSES.indexOf(left.status) - WORKBOARD_STATUSES.indexOf(right.status);
	if (left.position !== right.position) return left.position - right.position;
	return left.createdAt - right.createdAt;
}
function cardSessionKey(card) {
	return card.sessionKey ?? card.execution?.sessionKey;
}
function cardRunId(card) {
	return card.runId ?? card.execution?.runId;
}
function executionAttemptStatus(execution) {
	if (execution.status === "running") return "running";
	if (execution.status === "blocked") return "blocked";
	if (execution.status === "done" || execution.status === "review") return "succeeded";
	return "stopped";
}
function syncExecutionAttemptMetadata(metadata, execution, now) {
	if (!execution) return metadata;
	const attemptStatus = executionAttemptStatus(execution);
	const attempts = [...metadata.attempts ?? []];
	const key = execution.runId ?? execution.sessionKey ?? execution.id;
	const existingIndex = attempts.findIndex((attempt) => execution.runId && attempt.runId === execution.runId || !execution.runId && attempt.id === key);
	const existingAttempt = existingIndex >= 0 ? attempts[existingIndex] : void 0;
	const nextAttempt = {
		id: existingAttempt?.id ?? key,
		status: attemptStatus,
		startedAt: existingAttempt?.startedAt ?? execution.startedAt,
		engine: execution.engine,
		mode: execution.mode,
		model: execution.model,
		...execution.sessionKey ? { sessionKey: execution.sessionKey } : {},
		...execution.runId ? { runId: execution.runId } : {},
		...attemptStatus !== "running" && { endedAt: execution.updatedAt || now },
		...attemptStatus !== "succeeded" && existingAttempt?.error ? { error: existingAttempt.error } : {}
	};
	if (existingIndex >= 0) attempts[existingIndex] = nextAttempt;
	else attempts.push(nextAttempt);
	const previousFailed = existingAttempt?.status === "blocked" || existingAttempt?.status === "failed";
	const failureCount = attemptStatus === "blocked" || attemptStatus === "failed" ? previousFailed ? metadata.failureCount : (metadata.failureCount ?? 0) + 1 : attemptStatus === "succeeded" ? 0 : metadata.failureCount;
	return removeUndefinedMetadataFields({
		...metadata,
		attempts: attempts.slice(-30),
		failureCount
	});
}
function appendEvent(card, event, at = Date.now()) {
	return [...normalizeEvents(card.events), {
		id: randomUUID(),
		at,
		...event
	}].slice(-50);
}
function latestMetadataIdChanged(existing, next) {
	const latestId = next?.at(-1)?.id;
	return Boolean(latestId && latestId !== existing?.at(-1)?.id);
}
function lifecycleStatusSourceUpdatedAtFromPatch(metadata) {
	if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return;
	if (!Object.hasOwn(metadata, "lifecycleStatusSourceUpdatedAt")) return;
	return normalizeTimestamp(metadata.lifecycleStatusSourceUpdatedAt, 0);
}
function latestStatusTransitionAt(card) {
	for (let index = (card.events?.length ?? 0) - 1; index >= 0; index -= 1) {
		const event = card.events?.[index];
		if ((event?.kind === "moved" || event?.kind === "created") && (event.kind === "created" && card.status !== "todo" || event.kind === "moved" && event.fromStatus !== event.toStatus) && event.toStatus === card.status && typeof event.at === "number" && Number.isFinite(event.at)) return event.at;
	}
}
function shouldSkipPersistedLifecycleStatusUpdate(existing, sourceUpdatedAt) {
	const lifecycleStatusSourceUpdatedAt = existing.metadata?.lifecycleStatusSourceUpdatedAt;
	if (lifecycleStatusSourceUpdatedAt !== void 0) return sourceUpdatedAt < lifecycleStatusSourceUpdatedAt;
	const statusTransitionAt = latestStatusTransitionAt(existing);
	return statusTransitionAt !== void 0 && sourceUpdatedAt < statusTransitionAt;
}
function updateEvent(existing, next) {
	if (existing.metadata?.workerProtocol?.state !== next.metadata?.workerProtocol?.state && next.metadata?.workerProtocol?.state === "violated") return { kind: "protocol_violation" };
	if (existing.status !== next.status || existing.position !== next.position) return {
		kind: "moved",
		fromStatus: existing.status,
		toStatus: next.status
	};
	if (cardSessionKey(existing) !== cardSessionKey(next)) return {
		kind: "linked",
		...cardSessionKey(next) ? { sessionKey: cardSessionKey(next) } : {}
	};
	if (existing.metadata?.claim?.token !== next.metadata?.claim?.token) return { kind: "claimed" };
	if (existing.metadata?.claim?.lastHeartbeatAt !== next.metadata?.claim?.lastHeartbeatAt) return { kind: "heartbeat" };
	if (existing.execution?.status !== next.execution?.status || existing.execution?.engine !== next.execution?.engine || cardRunId(existing) !== cardRunId(next)) {
		const existingAttempts = existing.metadata?.attempts ?? [];
		const nextAttempts = next.metadata?.attempts ?? [];
		const latestAttempt = nextAttempts.at(-1);
		if (nextAttempts.length > existingAttempts.length) return {
			kind: "attempt_started",
			...latestAttempt?.sessionKey ? { sessionKey: latestAttempt.sessionKey } : {},
			...latestAttempt?.runId ? { runId: latestAttempt.runId } : {}
		};
		const previousAttempt = latestAttempt ? existingAttempts.find((attempt) => attempt.id === latestAttempt.id) : void 0;
		if (latestAttempt && previousAttempt?.status !== latestAttempt.status) return {
			kind: "attempt_updated",
			...latestAttempt.sessionKey ? { sessionKey: latestAttempt.sessionKey } : {},
			...latestAttempt.runId ? { runId: latestAttempt.runId } : {}
		};
		return {
			kind: "execution_updated",
			...cardSessionKey(next) ? { sessionKey: cardSessionKey(next) } : {},
			...cardRunId(next) ? { runId: cardRunId(next) } : {}
		};
	}
	if ((existing.metadata?.comments?.length ?? 0) !== (next.metadata?.comments?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.comments, next.metadata?.comments)) return { kind: "comment_added" };
	if ((existing.metadata?.links?.length ?? 0) !== (next.metadata?.links?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.links, next.metadata?.links)) return { kind: "link_added" };
	if ((existing.metadata?.proof?.length ?? 0) !== (next.metadata?.proof?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.proof, next.metadata?.proof)) return { kind: "proof_added" };
	if ((existing.metadata?.artifacts?.length ?? 0) !== (next.metadata?.artifacts?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.artifacts, next.metadata?.artifacts)) return { kind: "artifact_added" };
	if ((existing.metadata?.attachments?.length ?? 0) !== (next.metadata?.attachments?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.attachments, next.metadata?.attachments)) return (next.metadata?.attachments?.length ?? 0) > (existing.metadata?.attachments?.length ?? 0) ? { kind: "attachment_added" } : { kind: "edited" };
	if (existing.metadata?.workerProtocol?.state !== next.metadata?.workerProtocol?.state) return { kind: "orchestration" };
	if ((existing.metadata?.workerLogs?.length ?? 0) !== (next.metadata?.workerLogs?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.workerLogs, next.metadata?.workerLogs)) return { kind: "orchestration" };
	if ((existing.metadata?.diagnostics?.length ?? 0) !== (next.metadata?.diagnostics?.length ?? 0)) return { kind: "diagnostic" };
	if ((existing.metadata?.notifications?.length ?? 0) !== (next.metadata?.notifications?.length ?? 0) || latestMetadataIdChanged(existing.metadata?.notifications, next.metadata?.notifications)) return { kind: "notification" };
	if (existing.metadata?.automation?.dispatchCount !== next.metadata?.automation?.dispatchCount || existing.metadata?.automation?.lastDispatchAt !== next.metadata?.automation?.lastDispatchAt) return { kind: "dispatch" };
	if (!existing.metadata?.archivedAt && next.metadata?.archivedAt) return { kind: "archived" };
	if (existing.metadata?.archivedAt && !next.metadata?.archivedAt) return { kind: "unarchived" };
	if (!existing.metadata?.stale && next.metadata?.stale) return { kind: "stale" };
	return { kind: "edited" };
}
function removeUndefinedCardFields(card) {
	const next = { ...card };
	for (const key of [
		"notes",
		"agentId",
		"sessionKey",
		"runId",
		"taskId",
		"sourceUrl",
		"execution",
		"startedAt",
		"completedAt",
		"metadata"
	]) if (next[key] === void 0) delete next[key];
	if (metadataIsEmpty(next.metadata)) delete next.metadata;
	return next;
}
function assertCanMutateClaimedCard(card, scope) {
	if (!scope) return;
	const claim = card.metadata?.claim;
	if (!claim) return;
	const ownerId = normalizeOptionalString(scope.ownerId);
	const token = normalizeOptionalString(scope.token);
	if (claim.ownerId === ownerId || token && claim.token === token) return;
	throw new Error(`card is claimed by ${claim.ownerId}.`);
}
function retryBudgetExhausted(card) {
	const maxRetries = card.metadata?.automation?.maxRetries;
	return Boolean(maxRetries && (card.metadata?.failureCount ?? 0) > maxRetries);
}
function diagnostic(params, now) {
	return {
		...params,
		firstSeenAt: now,
		lastSeenAt: now,
		count: 1
	};
}
function mergeDiagnostics(previous, next) {
	const byKind = new Map(previous?.map((entry) => [entry.kind, entry]));
	return next.map((entry) => {
		const prior = byKind.get(entry.kind);
		return prior ? {
			...entry,
			firstSeenAt: prior.firstSeenAt,
			count: prior.count + 1
		} : entry;
	});
}
function computeCardDiagnostics(card, now) {
	const diagnostics = [];
	const lastHeartbeatAt = (card.metadata?.claim)?.lastHeartbeatAt ?? card.execution?.updatedAt ?? card.updatedAt;
	if ((card.status === "todo" || card.status === "backlog" || card.status === "ready") && card.agentId && now - card.updatedAt > READY_STRANDED_MS) diagnostics.push(diagnostic({
		kind: "stranded_ready",
		severity: "warning",
		title: "Assigned card is waiting",
		detail: "The card has an assigned agent but has not been claimed recently.",
		actions: [{
			kind: "claim",
			label: "Claim card"
		}]
	}, now));
	if (card.status === "running" && now - lastHeartbeatAt > RUNNING_HEARTBEAT_STALE_MS) diagnostics.push(diagnostic({
		kind: "running_without_heartbeat",
		severity: "error",
		title: "Running card has no recent heartbeat",
		detail: "The linked run or claim has not reported recent activity.",
		actions: [{
			kind: "open_session",
			label: "Open session"
		}, {
			kind: "reassign",
			label: "Reassign card"
		}]
	}, now));
	if (card.status === "blocked" && now - card.updatedAt > BLOCKED_TOO_LONG_MS) diagnostics.push(diagnostic({
		kind: "blocked_too_long",
		severity: "warning",
		title: "Blocked card needs attention",
		detail: "The card has been blocked for more than a day.",
		actions: [{
			kind: "unblock",
			label: "Move to todo"
		}]
	}, now));
	if ((card.metadata?.failureCount ?? 0) >= 2) diagnostics.push(diagnostic({
		kind: "repeated_failures",
		severity: "error",
		title: "Repeated run failures",
		detail: "Multiple attempts failed or blocked on this card.",
		actions: [{
			kind: "reassign",
			label: "Reassign card"
		}]
	}, now));
	if (card.status === "done" && !(card.metadata?.proof?.length || card.metadata?.artifacts?.length || card.metadata?.attachments?.length)) diagnostics.push(diagnostic({
		kind: "missing_proof",
		severity: "warning",
		title: "Done card has no proof",
		detail: "The card is marked done without proof or an attached artifact.",
		actions: [{
			kind: "add_proof",
			label: "Add proof"
		}]
	}, now));
	if (card.sessionKey && !card.execution && card.status === "running") diagnostics.push(diagnostic({
		kind: "orphaned_session",
		severity: "warning",
		title: "Running card has only a loose session link",
		detail: "The card is running but has no execution record for lifecycle handoff.",
		actions: [{
			kind: "open_session",
			label: "Open session"
		}]
	}, now));
	return diagnostics;
}
function capText(value, max) {
	if (!value) return;
	return value.length <= max ? value : `${value.slice(0, Math.max(0, max - 1))}…`;
}
function cardBoardId$1(card) {
	return card.metadata?.automation?.boardId ?? "default";
}
function cardResultSummary(card) {
	return card.metadata?.automation?.summary ?? card.metadata?.comments?.findLast((comment) => comment.body.trim())?.body ?? card.metadata?.proof?.findLast((proof) => proof.note?.trim())?.note;
}
function buildWorkerContext(card, cards = []) {
	const lines = [
		`# Workboard card ${card.id}`,
		`Title: ${card.title}`,
		`Status: ${card.status}`,
		`Priority: ${card.priority}`,
		`Board: ${cardBoardId$1(card)}`,
		`Agent: ${card.agentId ?? "(default)"}`
	];
	if (card.notes) lines.push("", "## Notes", capText(card.notes, 4e3) ?? "");
	const attempts = card.metadata?.attempts?.slice(-8) ?? [];
	if (attempts.length) {
		lines.push("", "## Recent attempts");
		for (const attempt of attempts) lines.push(`- ${attempt.status} ${attempt.model ?? ""} ${attempt.error ? `error=${capText(attempt.error, 240)}` : ""}`.trim());
	}
	const comments = card.metadata?.comments?.slice(-12) ?? [];
	if (comments.length) {
		lines.push("", "## Recent comments");
		for (const comment of comments) lines.push(`- ${capText(comment.body, 400)}`);
	}
	const proof = card.metadata?.proof?.slice(-8) ?? [];
	if (proof.length) {
		lines.push("", "## Proof");
		for (const entry of proof) lines.push(`- ${entry.status}: ${capText(entry.label ?? entry.command ?? entry.url ?? entry.note, 400)}`);
	}
	const artifacts = card.metadata?.artifacts?.slice(-8) ?? [];
	if (artifacts.length) {
		lines.push("", "## Artifacts");
		for (const artifact of artifacts) lines.push(`- ${capText(artifact.label ?? artifact.url ?? artifact.path, 400)}`);
	}
	const attachments = card.metadata?.attachments?.slice(-8) ?? [];
	if (attachments.length) {
		lines.push("", "## Attachments");
		for (const attachment of attachments) {
			const detail = [
				attachment.fileName,
				`${attachment.byteSize} bytes`,
				attachment.mimeType,
				attachment.note
			].filter(Boolean).join(" · ");
			lines.push(`- ${capText(detail, 500)}`);
		}
	}
	if (card.metadata?.workerProtocol) {
		const protocol = card.metadata.workerProtocol;
		lines.push("", "## Worker protocol");
		lines.push(`${protocol.state}: ${capText(protocol.detail, 500) ?? "no detail"}`);
	}
	const workerLogs = card.metadata?.workerLogs?.slice(-8) ?? [];
	if (workerLogs.length) {
		lines.push("", "## Worker logs");
		for (const log of workerLogs) lines.push(`- ${log.level}: ${capText(log.message, 500)}`);
	}
	const links = card.metadata?.links?.slice(-8) ?? [];
	if (links.length) {
		lines.push("", "## Links");
		for (const link of links) lines.push(`- ${link.type}: ${link.title ?? link.url ?? link.targetCardId ?? ""}`);
	}
	const cardsById = new Map(cards.map((entry) => [entry.id, entry]));
	const parentResults = cardParentIds(card).map((parentId) => cardsById.get(parentId)).filter((parent) => parent !== void 0 && parent.status === "done").slice(-6);
	if (parentResults.length) {
		lines.push("", "## Parent results");
		for (const parent of parentResults) lines.push(`- ${parent.id} ${parent.title}: ${capText(cardResultSummary(parent), 500) ?? "done"}`);
	}
	const recentAgentWork = card.agentId && cards.length ? cards.filter((entry) => entry.id !== card.id && cardBoardId$1(entry) === cardBoardId$1(card) && entry.agentId === card.agentId && entry.status === "done").toSorted((a, b) => b.updatedAt - a.updatedAt).slice(0, 5) : [];
	if (recentAgentWork.length) {
		lines.push("", `## Recent done work by ${card.agentId}`);
		for (const entry of recentAgentWork) lines.push(`- ${entry.id} ${entry.title}: ${capText(cardResultSummary(entry), 300) ?? "done"}`);
	}
	const automation = card.metadata?.automation;
	if (automation) {
		lines.push("", "## Automation");
		if (automation.tenant) lines.push(`Tenant: ${automation.tenant}`);
		if (automation.boardId) lines.push(`Board: ${automation.boardId}`);
		if (automation.skills?.length) lines.push(`Skills: ${automation.skills.join(", ")}`);
		if (automation.workspace) lines.push(`Workspace: ${automation.workspace.kind}${automation.workspace.path ? ` ${automation.workspace.path}` : ""}`);
		if (automation.summary) lines.push(`Summary: ${capText(automation.summary, 400)}`);
	}
	const diagnostics = computeCardDiagnostics(card, Date.now());
	if (diagnostics.length) {
		lines.push("", "## Active diagnostics");
		for (const entry of diagnostics) lines.push(`- ${entry.severity}: ${entry.title}`);
	}
	return lines.join("\n");
}
function cardParentIds(card) {
	return (card.metadata?.links ?? []).filter((link) => link.type === "parent" && link.targetCardId).map((link) => link.targetCardId).filter((id, index, ids) => ids.indexOf(id) === index);
}
function cardChildIds(card) {
	return (card.metadata?.links ?? []).filter((link) => link.type === "child" && link.targetCardId).map((link) => link.targetCardId).filter((id, index, ids) => ids.indexOf(id) === index);
}
function latestRunningAttempt(card) {
	return card.metadata?.attempts?.findLast((attempt) => attempt.status === "running");
}
function isDependencyPromotableStatus(status) {
	return status === "backlog" || status === "triage" || status === "todo" || status === "scheduled" || status === "ready";
}
function isActiveDependencyTarget(card, options = {}) {
	return Boolean(card.metadata?.claim) || card.execution?.status === "running" || Boolean(latestRunningAttempt(card)) || !options.allowStatusOnly && (card.status === "running" || card.status === "review");
}
function closeRunningAttempts(attempts, now, status, reason) {
	if (!attempts?.some((attempt) => attempt.status === "running")) return attempts;
	return attempts.map((attempt) => attempt.status === "running" ? {
		...attempt,
		status,
		endedAt: now,
		...reason ? { error: reason } : {}
	} : attempt);
}
function notificationSequence(event) {
	return typeof event.sequence === "number" && Number.isFinite(event.sequence) ? Math.trunc(event.sequence) : void 0;
}
function compareNotifications(a, b) {
	if (a.createdAt !== b.createdAt) return a.createdAt - b.createdAt;
	const aSequence = notificationSequence(a);
	const bSequence = notificationSequence(b);
	if (aSequence !== void 0 && bSequence !== void 0) return aSequence - bSequence || a.id.localeCompare(b.id);
	if (aSequence !== void 0) return -1;
	if (bSequence !== void 0) return 1;
	return a.id.localeCompare(b.id);
}
var WorkboardStore = class WorkboardStore {
	constructor(store, stores = {}) {
		this.store = store;
		this.mutationQueue = Promise.resolve();
		this.lastNotificationSequence = 0;
		this.boardStore = stores.boards ?? store;
		this.subscriptionStore = stores.subscriptions ?? store;
		this.attachmentStore = stores.attachments ?? store;
	}
	async enqueueMutation(run) {
		const result = this.mutationQueue.then(run, run);
		this.mutationQueue = result.then(() => void 0, () => void 0);
		return await result;
	}
	async updateMetadata(id, mutate) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			return await this.updateCard(id, { metadata: mutate(existing) });
		});
	}
	async deleteDetachedAttachments(existing, next) {
		const nextIds = new Set(next.metadata?.attachments?.map((attachment) => attachment.id) ?? []);
		for (const attachment of existing.metadata?.attachments ?? []) if (!nextIds.has(attachment.id)) await this.attachmentStore.delete(attachment.id);
	}
	nextNotificationSequence(now) {
		const base = Math.max(0, Math.trunc(now)) * 1e3;
		this.lastNotificationSequence = Math.max(this.lastNotificationSequence + 1, base);
		return this.lastNotificationSequence;
	}
	async list(options = {}) {
		const boardId = normalizeBoardId(options.boardId);
		return (await this.store.entries()).map((entry) => entry.value).filter((entry) => entry?.version === 1 && Boolean(entry.card?.id)).map((entry) => entry.card).filter((card) => !boardId || cardBoardId$1(card) === boardId).toSorted(compareCards);
	}
	async listBoards() {
		const boards = /* @__PURE__ */ new Map();
		for (const entry of await this.boardStore.entries()) {
			if (entry.value?.version !== 1 || !entry.value.board?.id) continue;
			const board = entry.value.board;
			boards.set(board.id, {
				id: board.id,
				...board.name ? { name: board.name } : {},
				...board.description ? { description: board.description } : {},
				...board.icon ? { icon: board.icon } : {},
				...board.color ? { color: board.color } : {},
				...board.defaultWorkspace ? { defaultWorkspace: board.defaultWorkspace } : {},
				...board.orchestration ? { orchestration: board.orchestration } : {},
				total: 0,
				active: 0,
				archived: 0,
				byStatus: {},
				updatedAt: board.updatedAt,
				...board.archivedAt ? { archivedAt: board.archivedAt } : {}
			});
		}
		if (!boards.has("default")) boards.set("default", {
			id: "default",
			total: 0,
			active: 0,
			archived: 0,
			byStatus: {}
		});
		for (const card of await this.list()) {
			const boardId = cardBoardId$1(card);
			const summary = boards.get(boardId) ?? {
				id: boardId,
				total: 0,
				active: 0,
				archived: 0,
				byStatus: {}
			};
			summary.total += 1;
			if (card.metadata?.archivedAt) summary.archived += 1;
			else summary.active += 1;
			summary.byStatus[card.status] = (summary.byStatus[card.status] ?? 0) + 1;
			summary.updatedAt = Math.max(summary.updatedAt ?? 0, card.updatedAt);
			boards.set(boardId, summary);
		}
		return { boards: [...boards.values()].toSorted((a, b) => a.id === "default" ? -1 : b.id === "default" ? 1 : a.id.localeCompare(b.id)) };
	}
	async upsertBoard(input) {
		return await this.enqueueMutation(async () => {
			const id = normalizeBoardIdRequired(input.id);
			const existing = await this.boardStore.lookup(id);
			const board = normalizeBoardMetadata({
				...input,
				id
			}, existing?.board);
			await this.boardStore.register(id, {
				version: 1,
				board
			});
			return board;
		});
	}
	async archiveBoard(id, archived = true) {
		return await this.upsertBoard({
			id,
			archived
		});
	}
	async deleteBoard(id) {
		return await this.enqueueMutation(async () => {
			const boardId = normalizeBoardIdRequired(id);
			if (boardId === "default") throw new Error("default board cannot be deleted.");
			if ((await this.list({ boardId })).length > 0) throw new Error("board still has cards; archive it or move/delete the cards first.");
			for (const entry of await this.subscriptionStore.entries()) if (entry.value?.version === 1 && entry.value.subscription?.boardId === boardId) await this.subscriptionStore.delete(entry.key);
			return { deleted: await this.boardStore.delete(boardId) };
		});
	}
	async stats(input = {}, now = Date.now()) {
		const cards = await this.list(input);
		const boardId = normalizeBoardId(input.boardId) ?? "all";
		const byStatus = {};
		const byAgent = Object.create(null);
		let oldestReadyAt;
		let updatedAt;
		let archived = 0;
		for (const card of cards) {
			byStatus[card.status] = (byStatus[card.status] ?? 0) + 1;
			byAgent[card.agentId ?? "(default)"] = (byAgent[card.agentId ?? "(default)"] ?? 0) + 1;
			if (card.metadata?.archivedAt) archived += 1;
			if (card.status === "ready") oldestReadyAt = Math.min(oldestReadyAt ?? card.updatedAt, card.updatedAt);
			updatedAt = Math.max(updatedAt ?? 0, card.updatedAt);
		}
		return {
			id: boardId,
			total: cards.length,
			active: cards.length - archived,
			archived,
			byStatus,
			byAgent,
			...oldestReadyAt ? { oldestReadyAgeMs: Math.max(0, now - oldestReadyAt) } : {},
			...updatedAt ? { updatedAt } : {}
		};
	}
	async get(id) {
		const entry = await this.store.lookup(id.trim());
		return entry?.version === 1 ? entry.card : void 0;
	}
	async removeReferencesToCard(cardId) {
		for (const card of await this.list()) {
			const links = card.metadata?.links;
			if (!links?.some((link) => link.targetCardId === cardId)) continue;
			await this.updateCard(card.id, { metadata: {
				...card.metadata,
				links: links.filter((link) => link.targetCardId !== cardId)
			} });
		}
	}
	async create(input, scope) {
		return await this.enqueueMutation(async () => await this.createDirect(input, scope));
	}
	async createDirect(input, scope) {
		const now = Date.now();
		const requestedStatus = normalizeStatus(input.status, "todo");
		const cards = await this.list();
		const parents = normalizeStringList(input.parents, "parents", 120);
		const automation = normalizeAutomation({
			tenant: input.tenant,
			boardId: input.boardId,
			createdByCardId: input.createdByCardId,
			idempotencyKey: input.idempotencyKey,
			skills: input.skills,
			workspace: input.workspace,
			maxRuntimeSeconds: input.maxRuntimeSeconds,
			maxRetries: input.maxRetries,
			scheduledAt: input.scheduledAt
		});
		const heldBySchedule = Boolean(automation?.scheduledAt && automation.scheduledAt > now) && requestedStatus !== "blocked";
		let status = heldBySchedule ? "scheduled" : requestedStatus;
		let heldByDependencies = false;
		if (parents.length > 0 && (status === "running" || status === "review")) {
			status = "todo";
			heldByDependencies = true;
		}
		if (automation?.idempotencyKey) {
			const existing = cards.find((card) => card.metadata?.automation?.idempotencyKey === automation.idempotencyKey && card.metadata?.automation?.tenant === automation.tenant && cardBoardId$1(card) === (automation.boardId ?? "default"));
			if (existing) return existing;
		}
		const cardsById = new Map(cards.map((card) => [card.id, card]));
		const parentCards = parents.map((parentId) => {
			const parent = cardsById.get(parentId);
			if (!parent) throw new Error(`card not found: ${parentId}`);
			return parent;
		});
		const childAutomation = normalizeAutomation({
			...automation,
			createdByCardId: automation?.createdByCardId ?? (parents.length === 1 ? parents[0] : void 0)
		}, automation);
		const normalizedPosition = normalizePosition(input.position, NaN);
		const position = Number.isFinite(normalizedPosition) ? normalizedPosition : Math.max(0, ...cards.filter((card) => card.status === status).map((card) => card.position)) + POSITION_STEP;
		const notes = normalizeNotes(input.notes);
		const agentId = normalizeOptionalString(input.agentId);
		const sessionKey = normalizeOptionalString(input.sessionKey);
		const runId = normalizeOptionalString(input.runId);
		const taskId = normalizeOptionalString(input.taskId);
		const sourceUrl = normalizeOptionalString(input.sourceUrl);
		const normalizedExecution = normalizeExecution(input.execution);
		const execution = normalizedExecution?.status === "running" && (heldBySchedule || heldByDependencies) ? void 0 : normalizedExecution;
		const startedAt = input.startedAt === void 0 ? status === "running" ? now : void 0 : normalizeTimestamp(input.startedAt, 0) || void 0;
		const completedAt = input.completedAt === void 0 ? status === "done" ? now : void 0 : normalizeTimestamp(input.completedAt, 0) || void 0;
		const syncedMetadata = trimMetadataToBudget(syncExecutionAttemptMetadata(normalizeMetadata(input.metadata, {
			templateId: normalizeTemplateId(input.templateId),
			...childAutomation ? { automation: childAutomation } : {}
		}, { allowDependencyLinks: false }), execution, now));
		let card = {
			id: randomUUID(),
			title: normalizeTitle(input.title),
			status,
			priority: normalizePriority(input.priority, "normal"),
			labels: normalizeLabels(input.labels),
			position,
			createdAt: now,
			updatedAt: now,
			events: [{
				id: randomUUID(),
				kind: "created",
				at: now,
				toStatus: status,
				...sessionKey ? { sessionKey } : {},
				...runId ? { runId } : {}
			}],
			...notes ? { notes } : {},
			...agentId ? { agentId } : {},
			...sessionKey ? { sessionKey } : {},
			...runId ? { runId } : {},
			...taskId ? { taskId } : {},
			...sourceUrl ? { sourceUrl } : {},
			...execution ? { execution } : {},
			...startedAt ? { startedAt } : {},
			...completedAt ? { completedAt } : {},
			...!metadataIsEmpty(syncedMetadata) ? { metadata: syncedMetadata } : {}
		};
		await this.store.register(card.id, {
			version: 1,
			card
		});
		try {
			for (const parent of parentCards) card = await this.linkCardsDirect(parent.id, card.id, now, {
				allowStatusOnlyActiveChild: true,
				scope
			});
		} catch (error) {
			await this.store.delete(card.id);
			await this.removeReferencesToCard(card.id);
			throw error;
		}
		return card;
	}
	async update(id, patch) {
		return await this.enqueueMutation(async () => await this.updateCard(id, patch, {
			allowMetadataDependencyLinks: false,
			enforceStatusHolds: true
		}));
	}
	async updateCard(id, patch, options = {}) {
		const existing = await this.get(id);
		if (!existing) throw new Error(`card not found: ${id}`);
		const lifecycleStatusSourceUpdatedAt = lifecycleStatusSourceUpdatedAtFromPatch(patch.metadata);
		const existingLifecycleStatusSourceUpdatedAt = existing.metadata?.lifecycleStatusSourceUpdatedAt;
		const hasFreshLifecycleStatusSource = lifecycleStatusSourceUpdatedAt !== void 0 && lifecycleStatusSourceUpdatedAt !== existingLifecycleStatusSourceUpdatedAt;
		let effectivePatch = patch;
		if (patch.status !== void 0 && lifecycleStatusSourceUpdatedAt !== void 0 && shouldSkipPersistedLifecycleStatusUpdate(existing, lifecycleStatusSourceUpdatedAt)) {
			effectivePatch = {
				...patch,
				status: void 0
			};
			if (patch.metadata && typeof patch.metadata === "object" && !Array.isArray(patch.metadata)) {
				const { lifecycleStatusSourceUpdatedAt: _ignored, ...rest } = patch.metadata;
				effectivePatch.metadata = Object.keys(rest).length > 0 ? rest : void 0;
			}
			if (!Object.entries(effectivePatch).some(([key, value]) => key !== "status" && key !== "metadata" && value !== void 0) && effectivePatch.metadata === void 0) return existing;
		}
		const status = normalizeStatus(effectivePatch.status, existing.status);
		const now = Date.now();
		const startedAt = effectivePatch.startedAt === void 0 ? status === "running" ? existing.startedAt ?? now : existing.startedAt : normalizeTimestamp(effectivePatch.startedAt, 0) || void 0;
		const completedAt = effectivePatch.completedAt === void 0 ? status === "done" ? existing.completedAt ?? now : void 0 : normalizeTimestamp(effectivePatch.completedAt, 0) || void 0;
		const sessionKey = effectivePatch.sessionKey === void 0 ? existing.sessionKey : normalizeOptionalString(effectivePatch.sessionKey);
		const execution = effectivePatch.execution === void 0 ? effectivePatch.sessionKey === void 0 ? existing.execution : syncExecutionSessionKey(existing.execution, sessionKey) : normalizeExecution(effectivePatch.execution);
		let metadata = normalizeMetadata(effectivePatch.metadata, existing.metadata, { allowDependencyLinks: options.allowMetadataDependencyLinks !== false });
		if (status !== existing.status && !hasFreshLifecycleStatusSource) metadata = {
			...metadata,
			lifecycleStatusSourceUpdatedAt: void 0
		};
		const automationPatch = {};
		for (const key of [
			"tenant",
			"boardId",
			"createdByCardId",
			"idempotencyKey",
			"skills",
			"workspace",
			"maxRuntimeSeconds",
			"maxRetries",
			"scheduledAt"
		]) if (Object.hasOwn(effectivePatch, key) && effectivePatch[key] !== void 0) automationPatch[key] = effectivePatch[key];
		if (Object.keys(automationPatch).length > 0) metadata = trimMetadataToBudget({
			...metadata,
			automation: normalizeAutomation(automationPatch, metadata.automation)
		});
		const next = removeUndefinedCardFields({
			...existing,
			title: effectivePatch.title === void 0 ? existing.title : normalizeTitle(effectivePatch.title),
			notes: effectivePatch.notes === void 0 ? existing.notes : normalizeNotes(effectivePatch.notes),
			status,
			priority: effectivePatch.priority === void 0 ? existing.priority : normalizePriority(effectivePatch.priority, existing.priority),
			labels: effectivePatch.labels === void 0 ? existing.labels : normalizeLabels(effectivePatch.labels),
			agentId: effectivePatch.agentId === void 0 ? existing.agentId : normalizeOptionalString(effectivePatch.agentId),
			sessionKey,
			runId: effectivePatch.runId === void 0 ? existing.runId : normalizeOptionalString(effectivePatch.runId),
			taskId: effectivePatch.taskId === void 0 ? existing.taskId : normalizeOptionalString(effectivePatch.taskId),
			sourceUrl: effectivePatch.sourceUrl === void 0 ? existing.sourceUrl : normalizeOptionalString(effectivePatch.sourceUrl),
			execution,
			metadata: effectivePatch.templateId === void 0 ? metadata : {
				...metadata,
				templateId: normalizeTemplateId(effectivePatch.templateId)
			},
			position: effectivePatch.position === void 0 ? existing.position : normalizePosition(effectivePatch.position, existing.position),
			updatedAt: now,
			...startedAt ? { startedAt } : {},
			...completedAt ? { completedAt } : {}
		});
		next.metadata = trimMetadataToBudget(syncExecutionAttemptMetadata(next.metadata ?? {}, execution, now));
		next.events = appendEvent(next, updateEvent(existing, next), now);
		if (options.enforceStatusHolds && effectivePatch.status !== void 0) await this.assertActiveStatusAllowed(existing, next, now);
		if (status !== "done") delete next.completedAt;
		if (effectivePatch.startedAt !== void 0 && !startedAt) delete next.startedAt;
		if (effectivePatch.completedAt !== void 0 && !completedAt) delete next.completedAt;
		if (metadataIsEmpty(next.metadata)) delete next.metadata;
		await this.store.register(next.id, {
			version: 1,
			card: next
		});
		await this.deleteDetachedAttachments(existing, next);
		return next;
	}
	async assertActiveStatusAllowed(existing, next, now) {
		if (next.status !== "ready" && next.status !== "running" && next.status !== "review" && next.status !== "done") return;
		const parents = cardParentIds(next);
		const cards = parents.length > 0 ? new Map((await this.list()).map((card) => [card.id, card])) : void 0;
		if (parents.length > 0 && !parents.every((parentId) => cards?.get(parentId)?.status === "done")) throw new Error("card dependencies are not done.");
		if (next.status === "done") return;
		const scheduledAt = next.metadata?.automation?.scheduledAt;
		if (scheduledAt && scheduledAt > now || existing.status === "scheduled" && !scheduledAt) throw new Error("card is scheduled for later.");
	}
	async move(id, status, position) {
		return await this.update(id, {
			status,
			position
		});
	}
	async delete(id) {
		return await this.enqueueMutation(async () => await this.deleteDirect(id));
	}
	async deleteDirect(id) {
		const cardId = id.trim();
		if (!await this.store.delete(cardId)) return { deleted: false };
		for (const entry of await this.subscriptionStore.entries()) if (entry.value?.version === 1 && entry.value.subscription?.cardId === cardId) await this.subscriptionStore.delete(entry.key);
		for (const entry of await this.attachmentStore.entries()) if (entry.value?.version === 1 && entry.value.attachment?.cardId === cardId) await this.attachmentStore.delete(entry.key);
		await this.removeReferencesToCard(cardId);
		return { deleted: true };
	}
	async addComment(id, input, scope) {
		const now = Date.now();
		const body = normalizeBoundedString(input.body, void 0, 2e3, "comment body");
		if (!body) throw new Error("comment body is required.");
		const comment = {
			id: randomUUID(),
			body,
			createdAt: now
		};
		return await this.updateMetadata(id, (existing) => {
			assertCanMutateClaimedCard(existing, scope);
			return {
				...existing.metadata,
				comments: [...existing.metadata?.comments ?? [], comment].slice(-50)
			};
		});
	}
	async addLink(id, input) {
		const now = Date.now();
		const targetCardId = normalizeBoundedString(input.targetCardId, void 0, 120, "link target");
		const url = normalizeBoundedString(input.url, void 0, 2e3, "link URL");
		const title = normalizeBoundedString(input.title, void 0, 180, "link title");
		if (!targetCardId && !url) throw new Error("link targetCardId or url is required.");
		const type = normalizeLinkType(input.type, "relates_to");
		if (type === "parent" || type === "child") throw new Error("parent and child dependency links must use linkDependency.");
		const link = {
			id: randomUUID(),
			type,
			createdAt: now,
			...targetCardId ? { targetCardId } : {},
			...title ? { title } : {},
			...url ? { url } : {}
		};
		return await this.updateMetadata(id, (existing) => ({
			...existing.metadata,
			links: appendLinkPreservingDependencies(existing.metadata?.links ?? [], link)
		}));
	}
	async linkCards(parentId, childId, scope) {
		return await this.enqueueMutation(async () => await this.linkCardsDirect(parentId, childId, Date.now(), { scope }));
	}
	async linkCardsDirect(parentId, childId, now = Date.now(), options = {}) {
		if (parentId.trim() === childId.trim()) throw new Error("parent and child cards must differ.");
		const parent = await this.get(parentId);
		const child = await this.get(childId);
		if (!parent) throw new Error(`card not found: ${parentId}`);
		if (!child) throw new Error(`card not found: ${childId}`);
		assertCanMutateClaimedCard(parent, options.scope);
		assertCanMutateClaimedCard(child, options.scope);
		if (child.status === "done" || child.status === "blocked") {
			const cardsById = new Map((await this.list()).map((card) => [card.id, card]));
			if ([...cardParentIds(child), parent.id].filter((id, index, ids) => ids.indexOf(id) === index).some((id) => cardsById.get(id)?.status !== "done")) throw new Error("terminal child cards cannot gain incomplete parent dependencies.");
		}
		if (isActiveDependencyTarget(child, { allowStatusOnly: options.allowStatusOnlyActiveChild })) throw new Error("active child cards cannot gain parent dependencies.");
		if (await this.dependsOn(parent.id, child.id)) throw new Error("dependency link would create a cycle.");
		const parentLinks = parent.metadata?.links ?? [];
		const childLinks = child.metadata?.links ?? [];
		const nextParentLinks = parentLinks.some((link) => link.type === "child" && link.targetCardId === child.id) ? parentLinks : appendLinkPreservingDependencies(parentLinks, {
			id: randomUUID(),
			type: "child",
			targetCardId: child.id,
			createdAt: now
		});
		const nextChildLinks = childLinks.some((link) => link.type === "parent" && link.targetCardId === parent.id) ? childLinks : appendLinkPreservingDependencies(childLinks, {
			id: randomUUID(),
			type: "parent",
			targetCardId: parent.id,
			createdAt: now
		});
		await this.updateCard(parent.id, { metadata: {
			...parent.metadata,
			links: nextParentLinks
		} });
		const nextChild = await this.updateCard(child.id, { metadata: {
			...child.metadata,
			links: nextChildLinks
		} });
		return await this.promoteDependencyReady(nextChild.id);
	}
	async linkParents(childId, parentIds) {
		let child = await this.get(childId);
		if (!child) throw new Error(`card not found: ${childId}`);
		for (const parentId of parentIds) child = await this.linkCards(parentId, child.id);
		return child;
	}
	async dependencyTargetStatus(card, now) {
		const scheduledAt = card.metadata?.automation?.scheduledAt;
		const parents = cardParentIds(card);
		if (card.status === "scheduled" && !scheduledAt) return "scheduled";
		if (parents.length === 0) {
			if (scheduledAt && scheduledAt > now && isDependencyPromotableStatus(card.status)) return "scheduled";
			return card.status === "scheduled" ? "ready" : card.status;
		}
		const cards = new Map((await this.list()).map((entry) => [entry.id, entry]));
		const parentsDone = parents.every((parentId) => cards.get(parentId)?.status === "done");
		if (!parentsDone && scheduledAt && scheduledAt > now && isDependencyPromotableStatus(card.status)) return "scheduled";
		if (!parentsDone && isDependencyPromotableStatus(card.status)) return "todo";
		if (parentsDone && scheduledAt && scheduledAt > now && isDependencyPromotableStatus(card.status)) return "scheduled";
		return parentsDone && isDependencyPromotableStatus(card.status) ? "ready" : card.status;
	}
	async dependsOn(cardId, targetParentId) {
		const cards = new Map((await this.list()).map((entry) => [entry.id, entry]));
		const seen = /* @__PURE__ */ new Set();
		const visit = (id) => {
			if (id === targetParentId) return true;
			if (seen.has(id)) return false;
			seen.add(id);
			const card = cards.get(id);
			return Boolean(card && cardParentIds(card).some(visit));
		};
		return visit(cardId);
	}
	async recordDispatch(card, now) {
		const metadata = trimMetadataToBudget(normalizeMetadata({
			...card.metadata,
			automation: normalizeAutomation({
				...card.metadata?.automation,
				dispatchCount: (card.metadata?.automation?.dispatchCount ?? 0) + 1,
				lastDispatchAt: now
			}, card.metadata?.automation)
		}, card.metadata));
		const next = removeUndefinedCardFields({
			...card,
			...!metadataIsEmpty(metadata) ? { metadata } : { metadata: void 0 },
			events: appendEvent(card, { kind: "dispatch" }, now)
		});
		await this.store.register(card.id, {
			version: 1,
			card: next
		});
		return next;
	}
	async recordOrchestrationCandidate(card, now) {
		const metadata = trimMetadataToBudget({
			...card.metadata,
			workerLogs: [...card.metadata?.workerLogs ?? [], {
				id: randomUUID(),
				level: "info",
				message: "Auto orchestration marked this triage card for specification or decomposition.",
				createdAt: now
			}].slice(-40),
			workerProtocol: {
				state: "idle",
				updatedAt: now,
				detail: "Awaiting workboard_specify or workboard_decompose."
			}
		});
		const next = removeUndefinedCardFields({
			...card,
			...!metadataIsEmpty(metadata) ? { metadata } : { metadata: void 0 },
			events: appendEvent(card, { kind: "orchestration" }, now)
		});
		await this.store.register(card.id, {
			version: 1,
			card: next
		});
		return next;
	}
	async shouldAutoOrchestrate(card) {
		if (card.status !== "triage" || card.metadata?.archivedAt || card.metadata?.workerProtocol?.state === "idle") return false;
		const board = await this.boardStore.lookup(cardBoardId$1(card));
		return board?.version === 1 && board.board.orchestration?.autoDecompose === true;
	}
	async promoteDependencyReady(id, now = Date.now()) {
		const card = await this.get(id);
		if (!card) throw new Error(`card not found: ${id}`);
		const target = await this.dependencyTargetStatus(card, now);
		if (target === card.status) return card;
		return await this.updateCard(card.id, { status: target });
	}
	async promoteReady(now = Date.now()) {
		return await this.enqueueMutation(async () => {
			const promoted = [];
			for (const card of await this.list()) {
				const next = await this.promoteDependencyReady(card.id, now);
				if (next.status !== card.status) promoted.push(next);
			}
			return {
				cards: promoted,
				count: promoted.length
			};
		});
	}
	async addProof(id, input, scope) {
		const proof = normalizeProofInput(input, Date.now());
		return await this.updateMetadata(id, (existing) => {
			assertCanMutateClaimedCard(existing, scope);
			const metadata = clearDiagnostics(existing.metadata, ["missing_proof"]);
			return {
				...metadata,
				proof: [...metadata.proof ?? [], proof].slice(-40)
			};
		});
	}
	async addProofWithArtifact(id, proofInput, artifactInput, scope) {
		const now = Date.now();
		const proof = normalizeProofInput(proofInput, now);
		const artifact = normalizeArtifact({
			...artifactInput,
			createdAt: now
		});
		if (!artifact) throw new Error("artifact url or path is required.");
		return await this.updateMetadata(id, (existing) => {
			assertCanMutateClaimedCard(existing, scope);
			const metadata = clearDiagnostics(existing.metadata, ["missing_proof"]);
			return {
				...metadata,
				proof: [...metadata.proof ?? [], proof].slice(-40),
				artifacts: [...metadata.artifacts ?? [], artifact].slice(-40)
			};
		});
	}
	async addArtifact(id, input, scope) {
		const artifact = normalizeArtifact({
			...input,
			createdAt: Date.now()
		});
		if (!artifact) throw new Error("artifact url or path is required.");
		return await this.updateMetadata(id, (existing) => {
			assertCanMutateClaimedCard(existing, scope);
			const metadata = clearDiagnostics(existing.metadata, ["missing_proof"]);
			return {
				...metadata,
				artifacts: [...metadata.artifacts ?? [], artifact].slice(-40)
			};
		});
	}
	async addAttachment(id, input, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope);
			const { attachment, contentBase64 } = normalizeAttachmentInput(id, input, Date.now());
			await this.attachmentStore.register(attachment.id, {
				version: 1,
				attachment,
				contentBase64
			});
			try {
				const updated = await this.updateCard(id, { metadata: {
					...clearDiagnostics(existing.metadata, ["missing_proof"]),
					attachments: [...existing.metadata?.attachments ?? [], attachment].slice(-20)
				} });
				if (!updated.metadata?.attachments?.some((entry) => entry.id === attachment.id)) {
					await this.attachmentStore.delete(attachment.id);
					throw new Error("attachment metadata was trimmed before it could be indexed.");
				}
				return updated;
			} catch (error) {
				await this.attachmentStore.delete(attachment.id);
				throw error;
			}
		});
	}
	async listAttachments(id) {
		const card = await this.get(id);
		if (!card) throw new Error(`card not found: ${id}`);
		return {
			card,
			attachments: card.metadata?.attachments ?? []
		};
	}
	async getAttachment(id) {
		const attachmentId = id.trim();
		const entry = await this.attachmentStore.lookup(attachmentId);
		return entry?.version === 1 ? entry : void 0;
	}
	async deleteAttachment(cardId, attachmentId, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(cardId);
			if (!existing) throw new Error(`card not found: ${cardId}`);
			assertCanMutateClaimedCard(existing, scope);
			const attachments = existing.metadata?.attachments ?? [];
			if (!attachments.some((attachment) => attachment.id === attachmentId)) throw new Error(`attachment not found: ${attachmentId}`);
			await this.attachmentStore.delete(attachmentId);
			return await this.updateCard(cardId, { metadata: {
				...existing.metadata,
				attachments: attachments.filter((attachment) => attachment.id !== attachmentId)
			} });
		});
	}
	async addWorkerLog(id, input, scope) {
		const now = Date.now();
		const message = normalizeBoundedString(input.message, void 0, 800, "worker log message");
		if (!message) throw new Error("worker log message is required.");
		const level = input.level === "warning" || input.level === "error" || input.level === "info" ? input.level : "info";
		const sessionKey = normalizeBoundedString(input.sessionKey, void 0, 240, "session key");
		const runId = normalizeBoundedString(input.runId, void 0, 160, "run id");
		const log = {
			id: randomUUID(),
			level,
			message,
			createdAt: now,
			...sessionKey ? { sessionKey } : {},
			...runId ? { runId } : {}
		};
		return await this.updateMetadata(id, (existing) => {
			assertCanMutateClaimedCard(existing, scope);
			return {
				...existing.metadata,
				workerLogs: [...existing.metadata?.workerLogs ?? [], log].slice(-40)
			};
		});
	}
	async recordProtocolViolation(id, input = {}, scope) {
		return await this.enqueueMutation(async () => {
			const card = await this.get(id);
			if (!card) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(card, scope);
			const now = Date.now();
			const detail = normalizeBoundedString(input.detail, void 0, 800, "protocol violation detail") ?? "Worker stopped without completing or blocking the card.";
			const sessionKey = normalizeBoundedString(input.sessionKey, void 0, 240, "session key");
			const runId = normalizeBoundedString(input.runId, void 0, 160, "run id");
			const log = {
				id: randomUUID(),
				level: "error",
				message: detail,
				createdAt: now,
				...sessionKey ? { sessionKey } : {},
				...runId ? { runId } : {}
			};
			const execution = card.execution?.status === "running" ? {
				...card.execution,
				status: "blocked",
				updatedAt: now
			} : card.execution;
			const attempts = closeRunningAttempts(card.metadata?.attempts, now, "blocked", detail);
			const notification = {
				id: randomUUID(),
				kind: "failed",
				createdAt: now,
				sequence: this.nextNotificationSequence(now),
				message: capText(detail, 240) ?? "Worker protocol violation.",
				...sessionKey || cardSessionKey(card) ? { sessionKey: sessionKey ?? cardSessionKey(card) } : {},
				...runId || cardRunId(card) ? { runId: runId ?? cardRunId(card) } : {}
			};
			return await this.updateCard(card.id, {
				status: card.status === "done" ? card.status : "blocked",
				...execution ? { execution } : {},
				metadata: {
					...card.metadata,
					workerLogs: [...card.metadata?.workerLogs ?? [], log].slice(-40),
					workerProtocol: {
						state: "violated",
						updatedAt: now,
						detail
					},
					claim: void 0,
					...attempts ? { attempts } : {},
					failureCount: (card.metadata?.failureCount ?? 0) + 1,
					notifications: [...card.metadata?.notifications ?? [], notification].slice(-20)
				}
			});
		});
	}
	async claim(id, input) {
		const ownerId = normalizeBoundedString(input.ownerId, void 0, 120, "claim owner");
		if (!ownerId) throw new Error("claim ownerId is required.");
		const ttlSeconds = typeof input.ttlSeconds === "number" && Number.isFinite(input.ttlSeconds) ? Math.max(1, Math.trunc(input.ttlSeconds)) : void 0;
		const token = normalizeBoundedString(input.token, void 0, 160, "claim token") ?? randomUUID();
		return await this.enqueueMutation(async () => {
			const now = Date.now();
			const expiresAt = addWorkboardDurationMs(now, ttlSeconds ? secondsToDurationMs(ttlSeconds) : DEFAULT_CLAIM_TTL_MS);
			const guarded = await this.promoteDependencyReady(id, now);
			if (cardParentIds(guarded).length > 0 && guarded.status !== "ready") throw new Error("card dependencies are not done.");
			if (guarded.status === "scheduled") throw new Error("card is scheduled for later.");
			if (retryBudgetExhausted(guarded)) throw new Error("card exhausted its retry budget.");
			const existingClaim = guarded.metadata?.claim;
			if (existingClaim && isFutureDateTimestampMs(existingClaim.expiresAt, { nowMs: now })) throw new Error(`card already claimed by ${existingClaim.ownerId}.`);
			const metadata = clearDiagnostics(guarded.metadata, ["stranded_ready"]);
			const card = await this.updateCard(id, { metadata: {
				...metadata,
				claim: {
					ownerId,
					token,
					claimedAt: now,
					lastHeartbeatAt: now,
					expiresAt
				}
			} });
			return {
				card: await this.updateCard(card.id, {
					status: card.status === "backlog" || card.status === "todo" || card.status === "ready" ? "running" : card.status,
					agentId: card.agentId ?? ownerId
				}),
				token
			};
		});
	}
	async heartbeat(id, input) {
		const note = normalizeBoundedString(input.note, void 0, 400, "heartbeat note");
		return await this.updateMetadata(id, (existing) => {
			const claim = existing.metadata?.claim;
			if (!claim) throw new Error("card is not claimed.");
			const now = Math.max(Date.now(), claim.lastHeartbeatAt + 1);
			const token = normalizeOptionalString(input.token);
			const ownerId = normalizeOptionalString(input.ownerId);
			if (token && token !== claim.token) throw new Error("claim token does not match.");
			if (!token && ownerId && ownerId !== claim.ownerId) throw new Error("claim owner does not match.");
			const nextClaim = {
				...claim,
				lastHeartbeatAt: now,
				expiresAt: claim.expiresAt ? addWorkboardDurationMs(now, Math.max(1, claim.expiresAt > claim.claimedAt ? claim.expiresAt - claim.lastHeartbeatAt : DEFAULT_CLAIM_TTL_MS)) : void 0
			};
			const metadata = clearDiagnostics(existing.metadata, ["running_without_heartbeat"]);
			return {
				...metadata,
				claim: removeUndefinedMetadataFields({ claim: nextClaim }).claim,
				comments: note ? [...metadata.comments ?? [], {
					id: randomUUID(),
					body: note,
					createdAt: now
				}].slice(-50) : metadata.comments
			};
		});
	}
	async releaseClaim(id, input = {}) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			const status = input.status === void 0 ? existing.status : normalizeStatus(input.status, existing.status);
			const claim = existing.metadata?.claim;
			if (claim) {
				const token = normalizeOptionalString(input.token);
				const ownerId = normalizeOptionalString(input.ownerId);
				if (token && token !== claim.token) throw new Error("claim token does not match.");
				if (!token && ownerId && ownerId !== claim.ownerId) throw new Error("claim owner does not match.");
			}
			return await this.updateCard(id, {
				status,
				metadata: {
					...existing.metadata,
					claim: void 0
				}
			}, { enforceStatusHolds: input.status !== void 0 });
		});
	}
	async complete(id, input = {}, scope = input) {
		return await this.enqueueMutation(async () => await this.completeDirect(id, input, scope));
	}
	async completeDirect(id, input = {}, scope = input) {
		const existing = await this.get(id);
		if (!existing) throw new Error(`card not found: ${id}`);
		assertCanMutateClaimedCard(existing, scope === null ? void 0 : scope);
		const now = Date.now();
		const createdCardIds = normalizeStringList(input.createdCardIds, "created card ids", 120);
		const childIds = cardChildIds(existing);
		for (const createdCardId of createdCardIds) {
			const createdCard = await this.get(createdCardId);
			if (!createdCard) throw new Error(`created card not found: ${createdCardId}`);
			if (!(childIds.includes(createdCardId) && cardParentIds(createdCard).includes(existing.id))) throw new Error(`created card is not linked to this card: ${createdCardId}`);
		}
		const summary = normalizeBoundedString(input.summary, void 0, 2e3, "summary");
		const proofInput = input.proof && typeof input.proof === "object" && !Array.isArray(input.proof) ? input.proof : void 0;
		const proof = proofInput ? normalizeProofInput(proofInput, now) : void 0;
		const artifacts = Array.isArray(input.artifacts) ? input.artifacts.map((artifact) => normalizeArtifact({
			...artifact,
			createdAt: now
		})).filter((artifact) => artifact !== null).slice(-40) : [];
		const metadata = clearDiagnostics(existing.metadata, ["missing_proof"]);
		const notification = {
			id: randomUUID(),
			kind: "completed",
			createdAt: now,
			sequence: this.nextNotificationSequence(now),
			message: capText(summary, 240) ?? "Workboard card completed.",
			...cardSessionKey(existing) ? { sessionKey: cardSessionKey(existing) } : {},
			...cardRunId(existing) ? { runId: cardRunId(existing) } : {}
		};
		const execution = existing.execution?.status === "running" ? {
			...existing.execution,
			status: "done",
			updatedAt: now
		} : existing.execution;
		return await this.updateCard(id, {
			status: "done",
			...execution ? { execution } : {},
			metadata: {
				...metadata,
				claim: void 0,
				attempts: closeRunningAttempts(metadata.attempts, now, "succeeded"),
				failureCount: 0,
				automation: normalizeAutomation({
					...metadata.automation,
					summary,
					createdCardIds
				}, metadata.automation),
				comments: summary ? [...metadata.comments ?? [], {
					id: randomUUID(),
					body: summary,
					createdAt: now
				}].slice(-50) : metadata.comments,
				proof: proof ? [...metadata.proof ?? [], proof].slice(-40) : metadata.proof,
				artifacts: artifacts.length ? [...metadata.artifacts ?? [], ...artifacts].slice(-40) : metadata.artifacts,
				notifications: [...metadata.notifications ?? [], notification].slice(-20)
			}
		}, { enforceStatusHolds: true });
	}
	async block(id, input = {}, scope = input) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope === null ? void 0 : scope);
			const now = Date.now();
			const reason = normalizeBoundedString(input.reason, void 0, 2e3, "block reason") ?? "Workboard card blocked.";
			const metadata = existing.metadata ?? {};
			const notification = {
				id: randomUUID(),
				kind: "failed",
				createdAt: now,
				sequence: this.nextNotificationSequence(now),
				message: capText(reason, 240) ?? "Workboard card blocked.",
				...cardSessionKey(existing) ? { sessionKey: cardSessionKey(existing) } : {},
				...cardRunId(existing) ? { runId: cardRunId(existing) } : {}
			};
			const execution = existing.execution?.status === "running" ? {
				...existing.execution,
				status: "blocked",
				updatedAt: now
			} : existing.execution;
			return await this.updateCard(id, {
				status: "blocked",
				...execution ? { execution } : {},
				metadata: {
					...metadata,
					claim: void 0,
					attempts: closeRunningAttempts(metadata.attempts, now, "blocked", reason),
					failureCount: (metadata.failureCount ?? 0) + 1,
					comments: [...metadata.comments ?? [], {
						id: randomUUID(),
						body: reason,
						createdAt: now
					}].slice(-50),
					notifications: [...metadata.notifications ?? [], notification].slice(-20)
				}
			});
		});
	}
	async unblock(id, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope);
			const metadata = clearDiagnostics(existing.metadata, ["blocked_too_long"]);
			return await this.updateCard(id, {
				status: "todo",
				metadata: {
					...metadata,
					stale: null
				}
			});
		});
	}
	async promote(id, input = {}, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope === null ? void 0 : scope);
			const reason = normalizeBoundedString(input.reason, void 0, 1e3, "promote reason");
			const comments = reason ? [...existing.metadata?.comments ?? [], {
				id: randomUUID(),
				body: reason,
				createdAt: Date.now()
			}].slice(-50) : existing.metadata?.comments;
			return await this.updateCard(id, {
				status: "ready",
				metadata: {
					...clearDiagnostics(existing.metadata, ["stranded_ready", "blocked_too_long"]),
					comments,
					stale: null
				}
			}, { enforceStatusHolds: input.force !== true });
		});
	}
	async reassign(id, input = {}, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope === null ? void 0 : scope);
			const agentId = input.agentId === void 0 ? existing.agentId : normalizeOptionalString(input.agentId);
			const status = input.status === void 0 ? existing.status : normalizeStatus(input.status, existing.status);
			const reason = normalizeBoundedString(input.reason, void 0, 1e3, "reassign reason");
			const shouldResetFailures = input.resetFailures !== false;
			const baseMetadata = shouldResetFailures ? clearDiagnostics(existing.metadata, ["blocked_too_long", "repeated_failures"]) : existing.metadata;
			const metadata = {
				...baseMetadata,
				...shouldResetFailures ? { failureCount: 0 } : {},
				comments: reason ? [...baseMetadata?.comments ?? [], {
					id: randomUUID(),
					body: reason,
					createdAt: Date.now()
				}].slice(-50) : baseMetadata?.comments
			};
			return await this.updateCard(id, {
				agentId,
				status,
				metadata
			}, { enforceStatusHolds: true });
		});
	}
	async reclaim(id, input = {}, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope === null ? void 0 : scope);
			const now = Date.now();
			const reason = normalizeBoundedString(input.reason, void 0, 1e3, "reclaim reason") ?? "Workboard claim reclaimed.";
			const targetStatus = input.status === void 0 ? existing.status === "running" ? "ready" : existing.status : normalizeStatus(input.status, existing.status);
			const reclaimed = await this.updateCard(id, {
				status: targetStatus,
				execution: existing.execution?.status === "running" ? null : existing.execution,
				metadata: {
					...existing.metadata,
					claim: void 0,
					attempts: closeRunningAttempts(existing.metadata?.attempts, now, "stopped", reason),
					comments: [...existing.metadata?.comments ?? [], {
						id: randomUUID(),
						body: reason,
						createdAt: now
					}].slice(-50),
					stale: null
				}
			}, { enforceStatusHolds: true });
			return await this.promoteDependencyReady(reclaimed.id, now);
		});
	}
	async runs(id) {
		const card = await this.get(id);
		if (!card) throw new Error(`card not found: ${id}`);
		return {
			card,
			attempts: card.metadata?.attempts ?? []
		};
	}
	async specify(id, input = {}, scope) {
		return await this.enqueueMutation(async () => {
			const existing = await this.get(id);
			if (!existing) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(existing, scope === null ? void 0 : scope);
			if (existing.status !== "triage" && existing.status !== "backlog" && existing.status !== "todo") throw new Error("only triage, backlog, or todo cards can be specified.");
			if (normalizeStatus(input.status, "todo") !== "todo") throw new Error("specified cards must move to todo.");
			const now = Date.now();
			const summary = normalizeBoundedString(input.summary, void 0, 2e3, "spec summary");
			const metadata = {
				...existing.metadata,
				comments: summary ? [...existing.metadata?.comments ?? [], {
					id: randomUUID(),
					body: summary,
					createdAt: now
				}].slice(-50) : existing.metadata?.comments,
				automation: normalizeAutomation({
					...existing.metadata?.automation,
					summary: summary ?? existing.metadata?.automation?.summary
				}, existing.metadata?.automation)
			};
			const { summary: _summary, status: _status, ...cardPatch } = input;
			const updated = await this.updateCard(id, {
				...cardPatch,
				status: "todo",
				metadata
			}, { enforceStatusHolds: true });
			const specified = {
				...updated,
				events: appendEvent(updated, { kind: "specified" }, now)
			};
			await this.store.register(specified.id, {
				version: 1,
				card: specified
			});
			return specified;
		});
	}
	async decompose(id, input = {}, scope) {
		return await this.enqueueMutation(async () => {
			const parent = await this.get(id);
			if (!parent) throw new Error(`card not found: ${id}`);
			assertCanMutateClaimedCard(parent, scope === null ? void 0 : scope);
			const childrenInput = Array.isArray(input.children) ? input.children : [];
			if (childrenInput.length === 0) throw new Error("children are required.");
			if (childrenInput.length > 20) throw new Error("at most 20 children can be created at once.");
			const parentAutomation = parent.metadata?.automation;
			const existingCardIds = new Set((await this.list()).map((card) => card.id));
			const children = [];
			const reusedChildSnapshots = /* @__PURE__ */ new Map();
			try {
				for (const rawChild of childrenInput) {
					if (!rawChild || typeof rawChild !== "object" || Array.isArray(rawChild)) throw new Error("children must be objects.");
					const child = rawChild;
					const created = await this.createDirect({
						...child,
						parents: [parent.id],
						boardId: child.boardId ?? parentAutomation?.boardId,
						tenant: child.tenant ?? parentAutomation?.tenant,
						createdByCardId: parent.id,
						idempotencyKey: child.idempotencyKey ?? deriveChildIdempotencyKey(parentAutomation?.idempotencyKey, children.length + 1)
					}, scope === null ? void 0 : scope);
					if (existingCardIds.has(created.id) && !cardParentIds(created).includes(parent.id)) reusedChildSnapshots.set(created.id, created);
					children.push(cardParentIds(created).includes(parent.id) ? created : await this.linkCardsDirect(parent.id, created.id, Date.now(), {
						allowStatusOnlyActiveChild: true,
						scope: scope === null ? void 0 : scope
					}));
				}
				const summary = normalizeBoundedString(input.summary, void 0, 2e3, "decompose summary");
				const updatedParent = input.completeParent !== false ? await this.completeDirect(parent.id, {
					summary,
					createdCardIds: children.map((child) => child.id)
				}, scope) : await (async () => {
					const latestParent = await this.get(parent.id) ?? parent;
					return await this.updateCard(parent.id, {
						status: latestParent.status === "triage" || latestParent.status === "backlog" ? "todo" : latestParent.status,
						metadata: {
							...latestParent.metadata,
							automation: normalizeAutomation({
								...latestParent.metadata?.automation,
								summary,
								createdCardIds: children.map((child) => child.id)
							}, latestParent.metadata?.automation)
						}
					}, { enforceStatusHolds: true });
				})();
				const decomposedParent = {
					...updatedParent,
					events: appendEvent(updatedParent, { kind: "decomposed" })
				};
				await this.store.register(decomposedParent.id, {
					version: 1,
					card: decomposedParent
				});
				return {
					parent: decomposedParent,
					children
				};
			} catch (error) {
				for (const child of children.toReversed()) if (!existingCardIds.has(child.id)) await this.deleteDirect(child.id);
				for (const child of reusedChildSnapshots.values()) await this.store.register(child.id, {
					version: 1,
					card: child
				});
				await this.store.register(parent.id, {
					version: 1,
					card: parent
				});
				throw error;
			}
		});
	}
	async subscribeNotifications(input) {
		return await this.enqueueMutation(async () => {
			const subscription = normalizeNotificationSubscription(input);
			await this.subscriptionStore.register(subscription.id, {
				version: 1,
				subscription
			});
			return subscription;
		});
	}
	async listNotificationSubscriptions(input = {}) {
		const boardId = normalizeBoardId(input.boardId);
		const cardId = normalizeBoundedString(input.cardId, void 0, 120, "card id");
		return { subscriptions: (await this.subscriptionStore.entries()).map((entry) => entry.value).filter((entry) => entry?.version === 1 && Boolean(entry.subscription?.id)).map((entry) => entry.subscription).filter((subscription) => !boardId || subscription.boardId === boardId).filter((subscription) => !cardId || subscription.cardId === cardId).toSorted((a, b) => a.createdAt - b.createdAt || a.id.localeCompare(b.id)) };
	}
	async deleteNotificationSubscription(id) {
		return { deleted: await this.subscriptionStore.delete(id.trim()) };
	}
	async collectNotificationEvents(input = {}) {
		const subscriptionId = normalizeBoundedString(input.subscriptionId, void 0, 120, "subscription id");
		const boardId = normalizeBoardId(input.boardId);
		const cardId = normalizeBoundedString(input.cardId, void 0, 120, "card id");
		const limit = typeof input.limit === "number" && Number.isFinite(input.limit) ? Math.max(1, Math.min(200, Math.trunc(input.limit))) : 50;
		const subscriptionEntry = subscriptionId ? await this.subscriptionStore.lookup(subscriptionId) : void 0;
		if (subscriptionId && !subscriptionEntry?.subscription) throw new Error(`notification subscription not found: ${subscriptionId}`);
		const subscription = subscriptionEntry?.subscription;
		const effectiveCardId = subscription?.cardId ?? cardId;
		const effectiveBoardId = effectiveCardId ? void 0 : subscription?.boardId ?? boardId;
		const effectiveSessionKey = subscription?.sessionKey;
		const effectiveRunId = subscription?.runId;
		const events = [];
		for (const card of await this.list({ boardId: effectiveBoardId })) {
			if (effectiveCardId && card.id !== effectiveCardId) continue;
			const stale = card.metadata?.stale;
			const notifications = [...card.metadata?.notifications ?? [], ...stale ? [{
				id: `stale:${card.id}:${stale.detectedAt}`,
				kind: "stale",
				createdAt: stale.detectedAt,
				sequence: stale.detectedAt * 1e3,
				message: stale.reason,
				...cardSessionKey(card) ? { sessionKey: cardSessionKey(card) } : {},
				...cardRunId(card) ? { runId: cardRunId(card) } : {}
			}] : []];
			for (const event of notifications) {
				const eventSessionKey = event.sessionKey ?? cardSessionKey(card);
				const eventRunId = event.runId ?? cardRunId(card);
				if (effectiveSessionKey && eventSessionKey !== effectiveSessionKey) continue;
				if (effectiveRunId && eventRunId !== effectiveRunId) continue;
				if (subscription?.eventKinds?.length && !subscription.eventKinds.includes(event.kind)) continue;
				const eventSequence = notificationSequence(event);
				if (subscription?.lastEventSequence && eventSequence !== void 0) {
					if (eventSequence < subscription.lastEventSequence || eventSequence === subscription.lastEventSequence && event.id <= (subscription.lastEventId ?? "")) continue;
				} else if (subscription?.lastEventAt && (event.createdAt < subscription.lastEventAt || event.createdAt === subscription.lastEventAt && event.id <= (subscription.lastEventId ?? ""))) continue;
				events.push(event);
			}
		}
		const sorted = events.toSorted(compareNotifications).slice(0, limit);
		return {
			...subscription ? { subscription } : {},
			events: sorted
		};
	}
	async notificationEvents(input = {}) {
		return await this.collectNotificationEvents(input);
	}
	async advanceNotificationEvents(input = {}) {
		const subscriptionId = normalizeBoundedString(input.subscriptionId, void 0, 120, "subscription id");
		if (!subscriptionId) throw new Error("subscriptionId is required to advance notification events.");
		return await this.enqueueMutation(async () => {
			const result = await this.collectNotificationEvents({
				...input,
				subscriptionId
			});
			if (!result.subscription || !result.events.length) return result;
			const last = result.events.at(-1);
			const lastSequence = notificationSequence(last);
			const subscription = {
				...result.subscription,
				lastEventAt: last.createdAt,
				lastEventId: last.id,
				...lastSequence !== void 0 ? { lastEventSequence: lastSequence } : {},
				updatedAt: Date.now()
			};
			delete subscription.deliveredEventIds;
			if (lastSequence === void 0) delete subscription.lastEventSequence;
			await this.subscriptionStore.register(subscription.id, {
				version: 1,
				subscription
			});
			return {
				subscription,
				events: result.events
			};
		});
	}
	async dispatch(input = Date.now()) {
		const now = typeof input === "number" ? input : normalizeTimestamp(input.now, Date.now());
		const boardId = typeof input === "number" ? void 0 : normalizeBoardId(input.boardId);
		return await this.enqueueMutation(async () => {
			const promoted = [];
			const reclaimed = [];
			const blocked = [];
			const orchestrated = [];
			const orchestratedByBoard = /* @__PURE__ */ new Map();
			for (const card of await this.list({ boardId })) {
				let latest = await this.promoteDependencyReady(card.id, now);
				const wasPromoted = latest.status !== card.status;
				const claim = latest.metadata?.claim;
				const latestAttempt = latestRunningAttempt(latest);
				const maxRuntimeSeconds = latest.metadata?.automation?.maxRuntimeSeconds;
				const runtimeStartedAt = latestAttempt?.startedAt ?? claim?.claimedAt ?? latest.startedAt;
				const timedOut = Boolean(maxRuntimeSeconds && runtimeStartedAt) && now - runtimeStartedAt > secondsToDurationMs(maxRuntimeSeconds);
				const claimExpired = Boolean(claim?.expiresAt && now - claim.expiresAt > CLAIM_RECLAIM_MS);
				const retriesExhausted = retryBudgetExhausted(latest);
				if (latest.status === "running" && (timedOut || claimExpired)) {
					const reason = timedOut ? "Run exceeded the card max runtime." : "Claim expired without a recent heartbeat.";
					const execution = latest.execution?.status === "running" ? {
						...latest.execution,
						status: "blocked",
						updatedAt: now
					} : latest.execution;
					latest = await this.updateCard(latest.id, {
						status: "blocked",
						...execution ? { execution } : {},
						metadata: {
							...latest.metadata,
							claim: void 0,
							attempts: closeRunningAttempts(latest.metadata?.attempts, now, "blocked", reason),
							failureCount: (latest.metadata?.failureCount ?? 0) + 1,
							notifications: [...latest.metadata?.notifications ?? [], {
								id: randomUUID(),
								kind: "failed",
								createdAt: now,
								sequence: this.nextNotificationSequence(now),
								message: reason
							}].slice(-20)
						}
					});
					blocked.push(latest);
				} else if (claimExpired) {
					latest = await this.updateCard(latest.id, { metadata: {
						...latest.metadata,
						claim: void 0
					} });
					reclaimed.push(latest);
				}
				if (!latest.metadata?.claim && retriesExhausted && isDependencyPromotableStatus(latest.status)) {
					latest = await this.updateCard(latest.id, {
						status: "blocked",
						metadata: {
							...latest.metadata,
							notifications: [...latest.metadata?.notifications ?? [], {
								id: randomUUID(),
								kind: "failed",
								createdAt: now,
								sequence: this.nextNotificationSequence(now),
								message: "Card exhausted its retry budget."
							}].slice(-20)
						}
					});
					blocked.push(latest);
				}
				if (latest.status === "ready") latest = await this.recordDispatch(latest, now);
				if (await this.shouldAutoOrchestrate(latest)) {
					const latestBoardId = cardBoardId$1(latest);
					const cap = (await this.boardStore.lookup(latestBoardId))?.board.orchestration?.autoDecomposePerDispatch ?? 3;
					const boardCount = orchestratedByBoard.get(latestBoardId) ?? 0;
					if (boardCount < cap) {
						latest = await this.recordOrchestrationCandidate(latest, now);
						orchestrated.push(latest);
						orchestratedByBoard.set(latestBoardId, boardCount + 1);
					}
				}
				if (wasPromoted && latest.status !== "blocked") promoted.push(latest);
			}
			return {
				promoted,
				reclaimed,
				blocked,
				orchestrated,
				count: promoted.length + reclaimed.length + blocked.length + orchestrated.length
			};
		});
	}
	async bulkUpdate(input) {
		const ids = Array.isArray(input.ids) ? input.ids.filter((id) => typeof id === "string" && id.trim() !== "") : [];
		if (ids.length === 0) throw new Error("ids are required.");
		const patch = input.patch && typeof input.patch === "object" && !Array.isArray(input.patch) ? input.patch : {};
		const cards = [];
		for (const id of ids) {
			const updated = input.archived === void 0 ? await this.update(id, patch) : await this.archive(id, input.archived);
			cards.push(updated);
		}
		return { cards };
	}
	async archive(id, archived) {
		const shouldArchive = archived !== false;
		return await this.updateMetadata(id, (existing) => ({
			...existing.metadata,
			archivedAt: shouldArchive ? Date.now() : 0
		}));
	}
	async exportCards() {
		const cards = await this.list();
		return {
			cards,
			attachments: cards.flatMap((card) => card.metadata?.attachments ?? []),
			exportedAt: Date.now()
		};
	}
	async diagnostics(now = Date.now()) {
		const rows = (await this.list()).flatMap((card) => {
			const diagnostics = computeCardDiagnostics(card, now);
			return diagnostics.length ? [{
				card,
				diagnostics
			}] : [];
		});
		return {
			diagnostics: rows,
			count: rows.reduce((total, row) => total + row.diagnostics.length, 0)
		};
	}
	async refreshDiagnostics(now = Date.now()) {
		return await this.enqueueMutation(async () => {
			const cards = await this.list();
			const rows = [];
			for (const card of cards) {
				const latest = await this.get(card.id);
				if (!latest) continue;
				const diagnostics = mergeDiagnostics(latest.metadata?.diagnostics, computeCardDiagnostics(latest, now));
				if (diagnostics.length === 0 && !latest.metadata?.diagnostics?.length) continue;
				const metadata = trimMetadataToBudget({
					...latest.metadata,
					diagnostics
				});
				const next = removeUndefinedCardFields({
					...latest,
					metadata: metadataIsEmpty(metadata) ? void 0 : metadata
				});
				await this.store.register(next.id, {
					version: 1,
					card: next
				});
				if (diagnostics.length > 0) rows.push({
					card: next,
					diagnostics
				});
			}
			return {
				diagnostics: rows,
				count: rows.reduce((total, row) => total + row.diagnostics.length, 0)
			};
		});
	}
	async buildWorkerContext(id) {
		const card = await this.get(id);
		if (!card) throw new Error(`card not found: ${id}`);
		return buildWorkerContext(card, await this.list());
	}
	static open(openKeyedStore) {
		return new WorkboardStore(openKeyedStore({
			namespace: "workboard.cards",
			maxEntries: MAX_CARDS
		}), {
			boards: openKeyedStore({
				namespace: "workboard.boards",
				maxEntries: 200
			}),
			subscriptions: openKeyedStore({
				namespace: "workboard.notify",
				maxEntries: 2e3
			}),
			attachments: openKeyedStore({
				namespace: "workboard.attachments",
				maxEntries: MAX_ATTACHMENT_ENTRIES
			})
		});
	}
	static openSqlite() {
		const stores = createWorkboardSqliteStores();
		return new WorkboardStore(stores.cards, {
			boards: stores.boards,
			subscriptions: stores.subscriptions,
			attachments: stores.attachments
		});
	}
};
//#endregion
//#region extensions/workboard/src/dispatcher.ts
const DEFAULT_DISPATCH_MAX_STARTS = 3;
const DEFAULT_DISPATCH_OWNER = "workboard-dispatcher";
const DEFAULT_DISPATCH_MODEL = "default";
function normalizePositiveInteger(value, fallback) {
	return typeof value === "number" && Number.isFinite(value) ? Math.max(0, Math.trunc(value)) : fallback;
}
function cardBoardId(card) {
	return card.metadata?.automation?.boardId ?? "default";
}
function sanitizeSessionSegment(value, fallback) {
	return ((value ?? fallback).trim().replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || fallback).slice(0, 96);
}
function cardIsArchived(card) {
	return Boolean(card.metadata?.archivedAt);
}
function buildSessionKey(card) {
	const suffix = `subagent:workboard-${sanitizeSessionSegment(cardBoardId(card), "default")}-${sanitizeSessionSegment(card.id, "card")}`;
	return card.agentId ? `agent:${sanitizeSessionSegment(card.agentId, "agent")}:${suffix}` : suffix;
}
function buildExecution(params) {
	return {
		id: params.card.execution?.id ?? `${params.card.id}:codex`,
		kind: "agent-session",
		engine: "codex",
		mode: "autonomous",
		status: "running",
		model: params.model,
		sessionKey: params.sessionKey,
		runId: params.runId,
		startedAt: params.now,
		updatedAt: params.now
	};
}
function buildWorkerPrompt(params) {
	return [
		`Work on this OpenClaw Workboard card: ${params.card.title}`,
		"",
		"## Worker protocol",
		`Card id: ${params.card.id}`,
		`Claim ownerId: ${params.ownerId}`,
		`Claim token: ${params.token}`,
		"",
		"Heartbeat with workboard_heartbeat using the card id and token while working.",
		"When done, call workboard_complete with the card id, token, summary, and proof.",
		"If blocked, call workboard_block with the card id, token, and reason.",
		"",
		params.context
	].join("\n");
}
function sortReadyCards(a, b) {
	const priorityRank = {
		urgent: 0,
		high: 1,
		normal: 2,
		low: 3
	};
	return priorityRank[a.priority] - priorityRank[b.priority] || a.position - b.position || a.createdAt - b.createdAt;
}
function selectStartableCards(cards, limit, candidates = cards) {
	if (limit <= 0) return [];
	const runningByOwner = /* @__PURE__ */ new Map();
	for (const card of cards) {
		if (!(card.status === "running" || Boolean(card.metadata?.claim) || card.execution?.status === "running") || cardIsArchived(card)) continue;
		const owner = card.agentId ?? DEFAULT_DISPATCH_OWNER;
		runningByOwner.set(owner, (runningByOwner.get(owner) ?? 0) + 1);
	}
	const selected = [];
	for (const card of candidates.filter((entry) => entry.status === "ready" && !entry.metadata?.claim && !cardIsArchived(entry)).toSorted(sortReadyCards)) {
		const owner = card.agentId ?? DEFAULT_DISPATCH_OWNER;
		if ((runningByOwner.get(owner) ?? 0) > 0) continue;
		selected.push(card);
		runningByOwner.set(owner, 1);
		if (selected.length >= limit) break;
	}
	return selected;
}
async function dispatchAndStartWorkboardCards(params) {
	const now = params.options?.now ?? Date.now();
	const boardId = params.options?.boardId;
	const dispatch = await params.store.dispatch({
		now,
		boardId
	});
	const maxStarts = normalizePositiveInteger(params.options?.maxStarts, DEFAULT_DISPATCH_MAX_STARTS);
	const started = [];
	const startFailures = [];
	const model = params.options?.model?.trim() || DEFAULT_DISPATCH_MODEL;
	const cards = await params.store.list();
	const candidates = await params.store.list({ boardId });
	for (const card of selectStartableCards(cards, maxStarts, candidates)) {
		const ownerId = params.options?.ownerId?.trim() || card.agentId || DEFAULT_DISPATCH_OWNER;
		const sessionKey = buildSessionKey(card);
		let token = "";
		try {
			const claimed = await params.store.claim(card.id, {
				ownerId,
				ttlSeconds: card.metadata?.automation?.maxRuntimeSeconds
			});
			token = claimed.token;
			const context = await params.store.buildWorkerContext(card.id);
			const run = await params.subagent.run({
				sessionKey,
				message: buildWorkerPrompt({
					card: claimed.card,
					context,
					ownerId,
					token
				}),
				...params.options?.provider ? { provider: params.options.provider } : {},
				...params.options?.model ? { model: params.options.model } : {},
				lane: `workboard:${cardBoardId(card)}:${card.id}`,
				idempotencyKey: `workboard:${card.id}:${claimed.card.updatedAt}`,
				lightContext: true,
				deliver: false
			});
			const updated = await params.store.update(card.id, {
				sessionKey,
				runId: run.runId,
				execution: buildExecution({
					card: claimed.card,
					sessionKey,
					runId: run.runId,
					model,
					now
				})
			});
			await params.store.addWorkerLog(updated.id, {
				level: "info",
				message: `Dispatcher started subagent run ${run.runId}.`,
				sessionKey,
				runId: run.runId
			}, {
				ownerId,
				token
			});
			started.push({
				cardId: updated.id,
				title: updated.title,
				sessionKey,
				runId: run.runId
			});
		} catch (error) {
			const message = formatErrorMessage(error);
			startFailures.push({
				cardId: card.id,
				title: card.title,
				error: message
			});
			if (!token) continue;
			try {
				await params.store.block(card.id, {
					ownerId,
					token,
					reason: `Dispatcher could not start worker: ${message}`
				}, {
					ownerId,
					token
				});
			} catch {}
		}
	}
	return {
		...dispatch,
		started,
		startFailures,
		count: dispatch.count + started.length + startFailures.length
	};
}
//#endregion
//#region extensions/workboard/src/gateway.ts
const READ_SCOPE = "operator.read";
const WRITE_SCOPE = "operator.write";
function respondError(respond, error) {
	respond(false, void 0, {
		code: "workboard_error",
		message: formatErrorMessage(error)
	});
}
function readId(params) {
	const value = params.id;
	if (typeof value === "string" && value.trim()) return value.trim();
	throw new Error("id is required.");
}
function readPatch(params) {
	const patch = params.patch;
	if (patch && typeof patch === "object" && !Array.isArray(patch)) return patch;
	return params;
}
function assertNoCursorAdvance(params) {
	if (params.advance === true) throw new Error("notification cursor advancement requires workboard.notifications.advance.");
}
function redactClaimToken(card) {
	const claim = card.metadata?.claim;
	if (!claim) return card;
	return {
		...card,
		metadata: {
			...card.metadata,
			claim: {
				...claim,
				token: "[redacted]"
			}
		}
	};
}
function redactDiagnosticsRows(result) {
	return {
		...result,
		diagnostics: result.diagnostics.map((row) => ({
			...row,
			card: redactClaimToken(row.card)
		}))
	};
}
function registerWorkboardGatewayMethods(params) {
	const { api } = params;
	const store = params.store ?? WorkboardStore.openSqlite();
	api.registerGatewayMethod("workboard.cards.list", async ({ params: requestParams, respond }) => {
		try {
			respond(true, {
				cards: (await store.list({ boardId: requestParams.boardId })).map(redactClaimToken),
				statuses: WORKBOARD_STATUSES
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.cards.create", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.create(requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.update", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.update(readId(requestParams), readPatch(requestParams))) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.move", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.move(readId(requestParams), requestParams.status, requestParams.position)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.delete", async ({ params: requestParams, respond }) => {
		try {
			respond(true, await store.delete(readId(requestParams)));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.comment", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.addComment(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.link", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.addLink(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.linkDependency", async ({ params: requestParams, respond }) => {
		try {
			const parentId = requestParams.parentId;
			const childId = requestParams.childId;
			if (typeof parentId !== "string" || typeof childId !== "string") throw new Error("parentId and childId are required.");
			respond(true, { card: redactClaimToken(await store.linkCards(parentId, childId)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.proof", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.addProof(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.artifact", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.addArtifact(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.claim", async ({ params: requestParams, respond }) => {
		try {
			const claimed = await store.claim(readId(requestParams), requestParams);
			respond(true, {
				...claimed,
				card: redactClaimToken(claimed.card)
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.heartbeat", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.heartbeat(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.release", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.releaseClaim(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.promote", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.promote(readId(requestParams), requestParams, null)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.reassign", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.reassign(readId(requestParams), requestParams, null)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.reclaim", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.reclaim(readId(requestParams), requestParams, null)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.complete", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.complete(readId(requestParams), requestParams, null)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.block", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.block(readId(requestParams), requestParams, null)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.unblock", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.unblock(readId(requestParams))) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.bulk", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { cards: (await store.bulkUpdate(requestParams)).cards.map(redactClaimToken) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.diagnostics", async ({ respond }) => {
		try {
			respond(true, redactDiagnosticsRows(await store.diagnostics()));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.cards.diagnostics.refresh", async ({ respond }) => {
		try {
			respond(true, redactDiagnosticsRows(await store.refreshDiagnostics()));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.dispatch", async ({ params: requestParams, respond }) => {
		try {
			const boardId = requestParams && typeof requestParams === "object" && "boardId" in requestParams ? requestParams.boardId : void 0;
			const result = await dispatchAndStartWorkboardCards({
				store,
				subagent: api.runtime.subagent,
				options: { boardId: typeof boardId === "string" ? boardId : void 0 }
			});
			respond(true, {
				...result,
				promoted: result.promoted.map(redactClaimToken),
				reclaimed: result.reclaimed.map(redactClaimToken),
				blocked: result.blocked.map(redactClaimToken),
				orchestrated: result.orchestrated.map(redactClaimToken)
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.boards.list", async ({ respond }) => {
		try {
			respond(true, await store.listBoards());
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.boards.upsert", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { board: await store.upsertBoard(requestParams) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.boards.archive", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { board: await store.archiveBoard(requestParams.id, requestParams.archived) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.boards.delete", async ({ params: requestParams, respond }) => {
		try {
			respond(true, await store.deleteBoard(requestParams.id));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.stats", async ({ params: requestParams, respond }) => {
		try {
			respond(true, await store.stats({ boardId: requestParams.boardId }));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.cards.runs", async ({ params: requestParams, respond }) => {
		try {
			const result = await store.runs(readId(requestParams));
			respond(true, {
				...result,
				card: redactClaimToken(result.card)
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.cards.specify", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.specify(readId(requestParams), requestParams, null)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.decompose", async ({ params: requestParams, respond }) => {
		try {
			const result = await store.decompose(readId(requestParams), requestParams, null);
			respond(true, {
				parent: redactClaimToken(result.parent),
				children: result.children.map(redactClaimToken)
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.notifications.subscribe", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { subscription: await store.subscribeNotifications(requestParams) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.notifications.list", async ({ params: requestParams, respond }) => {
		try {
			respond(true, await store.listNotificationSubscriptions(requestParams));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.notifications.delete", async ({ params: requestParams, respond }) => {
		try {
			respond(true, await store.deleteNotificationSubscription(readId(requestParams)));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.notifications.events", async ({ params: requestParams, respond }) => {
		try {
			assertNoCursorAdvance(requestParams);
			respond(true, await store.notificationEvents(requestParams));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.notifications.advance", async ({ params: requestParams, respond }) => {
		try {
			respond(true, await store.advanceNotificationEvents(requestParams));
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.attachments.list", async ({ params: requestParams, respond }) => {
		try {
			const result = await store.listAttachments(readId(requestParams));
			respond(true, {
				...result,
				card: redactClaimToken(result.card)
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.cards.attachments.get", async ({ params: requestParams, respond }) => {
		try {
			const attachment = await store.getAttachment(readId(requestParams));
			if (!attachment) throw new Error(`attachment not found: ${readId(requestParams)}`);
			respond(true, attachment);
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
	api.registerGatewayMethod("workboard.cards.attachments.add", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.addAttachment(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.attachments.delete", async ({ params: requestParams, respond }) => {
		try {
			const attachmentId = requestParams.attachmentId;
			if (typeof attachmentId !== "string" || !attachmentId.trim()) throw new Error("attachmentId is required.");
			respond(true, { card: redactClaimToken(await store.deleteAttachment(readId(requestParams), attachmentId.trim())) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.workerLog", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.addWorkerLog(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.protocolViolation", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.recordProtocolViolation(readId(requestParams), requestParams)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.archive", async ({ params: requestParams, respond }) => {
		try {
			respond(true, { card: redactClaimToken(await store.archive(readId(requestParams), requestParams.archived)) });
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: WRITE_SCOPE });
	api.registerGatewayMethod("workboard.cards.export", async ({ respond }) => {
		try {
			const exported = await store.exportCards();
			respond(true, {
				...exported,
				cards: exported.cards.map(redactClaimToken)
			});
		} catch (error) {
			respondError(respond, error);
		}
	}, { scope: READ_SCOPE });
}
//#endregion
export { dispatchAndStartWorkboardCards as n, WorkboardStore as r, registerWorkboardGatewayMethods as t };