UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,128 lines (1,111 loc) 47.7 kB
import { D as resolveExpiresAtMsFromDurationMs, g as isFutureDateTimestampMs, t as MAX_DATE_TIMESTAMP_MS } from "./number-coercion-CLj0HTDM.js"; import { c as isRecord } from "./record-coerce-DItp3I4t.js"; import { w as resolveStateDir } from "./paths-D2sRr1a_.js"; import { t as openNodeSqliteDatabase, u as runSqliteImmediateTransactionSync } from "./node-sqlite-BpQX3W0e.js"; import { a as getNodeSqliteKysely, i as executeSqliteQueryTakeFirstSync, o as iterateSqliteQuerySync } from "./kysely-sync-COmh4HWh.js"; import { t as migrateSqliteSchemaToStrict } from "./sqlite-strict-Cpho4I9M.js"; import { t as configureSqliteConnectionPragmas } from "./sqlite-wal-B4waQq_w.js"; import "./sqlite-runtime-BpLlraG0.js"; import "./number-runtime-Cy4drVnh.js"; import "./string-coerce-runtime-GQa0ehRA.js"; import "./plugin-state-runtime-CeL_xbVf.js"; import "./state-paths-2IWTvznc.js"; import fs from "node:fs"; import path from "node:path"; //#region extensions/workboard/src/store-constants.ts const POSITION_STEP = 1e3; const MAX_CARDS = 2e3; const MAX_ATTACHMENT_BYTES = 262144; const MAX_CARD_METADATA_BYTES = 24576; const DEFAULT_CLAIM_TTL_MS = 18e5; const DEFAULT_WORKBOARD_DISPATCH_OWNER = "workboard-dispatcher"; const READY_STRANDED_MS = 36e5; const RUNNING_HEARTBEAT_STALE_MS = 12e5; const BLOCKED_TOO_LONG_MS = 864e5; const CLAIM_RECLAIM_MS = 3e5; function isWorkboardClaimReclaimable(claim, now) { return Boolean(claim?.expiresAt && now - claim.expiresAt > CLAIM_RECLAIM_MS); } function workboardCardConsumesOwnerSlot(card, now) { const claim = card.metadata?.claim; const activeClaim = claim && isFutureDateTimestampMs(claim.expiresAt, { nowMs: now }); return !card.metadata?.archivedAt && !isWorkboardClaimReclaimable(claim, now) && (card.status === "running" || card.status !== "done" && activeClaim || card.execution?.status === "running"); } function workboardCardSlotOwner(card, now) { const claim = card.metadata?.claim; return (claim && (now === void 0 || isFutureDateTimestampMs(claim.expiresAt, { nowMs: now })) ? claim.ownerId : void 0) || card.agentId || "workboard-dispatcher"; } 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; } //#endregion //#region extensions/workboard/src/sqlite-store.ts const WORKBOARD_DB_RELATIVE_PATH = [ "plugins", "workboard", "workboard.sqlite" ]; const SCHEMA_VERSION = 3; const WORKBOARD_SQLITE_BUSY_TIMEOUT_MS = 5e3; const WORKBOARD_SQLITE_DIR_MODE = 448; const WORKBOARD_SQLITE_FILE_MODE = 384; function resolveWorkboardSqlitePath(env = process.env) { return path.join(resolveStateDir(env), ...WORKBOARD_DB_RELATIVE_PATH); } function jsonValue(value) { return value === void 0 ? null : JSON.stringify(value); } function parseJson(value) { if (typeof value !== "string" || !value) return; return JSON.parse(value); } function stringValue(row, key) { const value = row[key]; return typeof value === "string" && value.length > 0 ? value : void 0; } function numberValue(row, key) { const value = row[key]; if (typeof value === "number") return Number.isFinite(value) ? value : void 0; if (typeof value === "bigint") return Number(value); } function requiredString(row, key) { const value = stringValue(row, key); if (!value) throw new Error(`workboard sqlite row missing ${key}`); return value; } function requiredNumber(row, key) { const value = numberValue(row, key); if (value === void 0) throw new Error(`workboard sqlite row missing ${key}`); return value; } function optional(value) { return Object.keys(value).length > 0 ? value : void 0; } function asBlobContent(value) { return Buffer.from(value, "base64"); } function blobToBase64(value) { if (value instanceof Uint8Array) return Buffer.from(value).toString("base64"); if (typeof value === "string") return Buffer.from(value).toString("base64"); return ""; } function tableColumns(db, tableName) { return new Set(db.prepare(`PRAGMA table_info(${tableName})`).all().flatMap((row) => typeof row.name === "string" ? [row.name] : [])); } function ensureColumn(db, tableName, columnName, definition) { if (tableColumns(db, tableName).has(columnName)) return; db.exec(`ALTER TABLE ${tableName} ADD COLUMN ${definition}`); } const WORKBOARD_SCHEMA_SQL = ` CREATE TABLE IF NOT EXISTS workboard_schema_migrations ( id TEXT PRIMARY KEY, applied_at INTEGER NOT NULL ) STRICT; CREATE TABLE IF NOT EXISTS workboard_boards ( id TEXT PRIMARY KEY, name TEXT, description TEXT, icon TEXT, color TEXT, automation_job_id TEXT, default_workspace_json TEXT, orchestration_json TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, archived_at INTEGER ) STRICT; CREATE TABLE IF NOT EXISTS workboard_cards ( id TEXT PRIMARY KEY, board_id TEXT NOT NULL, title TEXT NOT NULL, notes TEXT, status TEXT NOT NULL, priority TEXT NOT NULL, agent_id TEXT, session_key TEXT, run_id TEXT, task_id TEXT, source_url TEXT, position REAL NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, started_at INTEGER, completed_at INTEGER, execution_id TEXT, execution_kind TEXT, execution_engine TEXT, execution_mode TEXT, execution_status TEXT, execution_model TEXT, execution_session_key TEXT, execution_run_id TEXT, execution_started_at INTEGER, execution_updated_at INTEGER, automation_json TEXT, claim_json TEXT, template_id TEXT, archived_at INTEGER, stale_json TEXT, lifecycle_status_source_updated_at INTEGER, failure_count INTEGER ) STRICT; CREATE INDEX IF NOT EXISTS workboard_cards_board_status_idx ON workboard_cards(board_id, status, position); CREATE INDEX IF NOT EXISTS workboard_cards_session_idx ON workboard_cards(session_key, run_id); CREATE TABLE IF NOT EXISTS workboard_card_labels ( card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, label TEXT NOT NULL, PRIMARY KEY(card_id, ordinal) ) STRICT; CREATE TABLE IF NOT EXISTS workboard_card_events ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, kind TEXT NOT NULL, at INTEGER NOT NULL, from_status TEXT, to_status TEXT, session_key TEXT, run_id TEXT ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_events_card_idx ON workboard_card_events(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_card_attempts ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, status TEXT NOT NULL, started_at INTEGER NOT NULL, ended_at INTEGER, engine TEXT, mode TEXT, model TEXT, session_key TEXT, run_id TEXT, error TEXT ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_attempts_card_idx ON workboard_card_attempts(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_card_comments ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, body TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_comments_card_idx ON workboard_card_comments(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_card_links ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, type TEXT NOT NULL, target_card_id TEXT, title TEXT, url TEXT, created_at INTEGER NOT NULL ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_links_card_idx ON workboard_card_links(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_card_proof ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, status TEXT NOT NULL, label TEXT, command TEXT, url TEXT, note TEXT, created_at INTEGER NOT NULL ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_proof_card_idx ON workboard_card_proof(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_card_artifacts ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, label TEXT, url TEXT, path TEXT, mime_type TEXT, created_at INTEGER NOT NULL ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_artifacts_card_idx ON workboard_card_artifacts(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_card_diagnostics ( card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, kind TEXT NOT NULL, severity TEXT NOT NULL, title TEXT NOT NULL, detail TEXT NOT NULL, first_seen_at INTEGER NOT NULL, last_seen_at INTEGER NOT NULL, count INTEGER NOT NULL, actions_json TEXT NOT NULL, PRIMARY KEY(card_id, ordinal) ) STRICT; CREATE TABLE IF NOT EXISTS workboard_card_notifications ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, kind TEXT NOT NULL, message TEXT NOT NULL, created_at INTEGER NOT NULL, sequence INTEGER, session_key TEXT, run_id TEXT ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_notifications_card_idx ON workboard_card_notifications(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_worker_logs ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, level TEXT NOT NULL, message TEXT NOT NULL, created_at INTEGER NOT NULL, session_key TEXT, run_id TEXT ) STRICT; CREATE INDEX IF NOT EXISTS workboard_worker_logs_card_idx ON workboard_worker_logs(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_worker_protocol ( card_id TEXT PRIMARY KEY REFERENCES workboard_cards(id) ON DELETE CASCADE, state TEXT NOT NULL, updated_at INTEGER NOT NULL, detail TEXT ) STRICT; CREATE TABLE IF NOT EXISTS workboard_card_attachments ( id TEXT PRIMARY KEY, card_id TEXT NOT NULL REFERENCES workboard_cards(id) ON DELETE CASCADE, ordinal INTEGER NOT NULL, file_name TEXT NOT NULL, byte_size INTEGER NOT NULL, mime_type TEXT, note TEXT, created_at INTEGER NOT NULL ) STRICT; CREATE INDEX IF NOT EXISTS workboard_card_attachments_card_idx ON workboard_card_attachments(card_id, ordinal); CREATE TABLE IF NOT EXISTS workboard_attachment_blobs ( attachment_id TEXT PRIMARY KEY, content BLOB NOT NULL ) STRICT; CREATE TABLE IF NOT EXISTS workboard_notification_subscriptions ( id TEXT PRIMARY KEY, board_id TEXT NOT NULL, card_id TEXT, session_key TEXT, run_id TEXT, target TEXT, event_kinds_json TEXT, last_event_at INTEGER, last_event_id TEXT, last_event_sequence INTEGER, delivered_event_ids_json TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) STRICT; `; function ensureWorkboardSchema(db) { db.exec(WORKBOARD_SCHEMA_SQL); ensureColumn(db, "workboard_boards", "automation_job_id", "automation_job_id TEXT"); ensureColumn(db, "workboard_cards", "lifecycle_status_source_updated_at", "lifecycle_status_source_updated_at INTEGER"); const migrationId = `schema-${SCHEMA_VERSION}`; if (!db.prepare("SELECT 1 AS found FROM workboard_schema_migrations WHERE id = ?").get(migrationId)) { migrateSqliteSchemaToStrict(db, WORKBOARD_SCHEMA_SQL, { databaseLabel: "workboard database" }); db.prepare("INSERT OR IGNORE INTO workboard_schema_migrations (id, applied_at) VALUES (?, ?)").run(migrationId, Date.now()); } } function chmodIfExists(targetPath, mode) { try { fs.chmodSync(targetPath, mode); } catch (err) { if (err.code !== "ENOENT") throw err; } } function hardenWorkboardDatabaseFiles(dbPath) { fs.chmodSync(path.dirname(dbPath), WORKBOARD_SQLITE_DIR_MODE); chmodIfExists(dbPath, WORKBOARD_SQLITE_FILE_MODE); chmodIfExists(`${dbPath}-wal`, WORKBOARD_SQLITE_FILE_MODE); chmodIfExists(`${dbPath}-shm`, WORKBOARD_SQLITE_FILE_MODE); chmodIfExists(`${dbPath}-journal`, WORKBOARD_SQLITE_FILE_MODE); } function createDatabase(dbPath) { fs.mkdirSync(path.dirname(dbPath), { recursive: true, mode: WORKBOARD_SQLITE_DIR_MODE }); chmodIfExists(path.dirname(dbPath), WORKBOARD_SQLITE_DIR_MODE); if (!fs.existsSync(dbPath)) fs.closeSync(fs.openSync(dbPath, "a", WORKBOARD_SQLITE_FILE_MODE)); const db = openNodeSqliteDatabase(dbPath); let maintenance; try { maintenance = configureSqliteConnectionPragmas(db, { busyTimeoutMs: WORKBOARD_SQLITE_BUSY_TIMEOUT_MS, checkpointIntervalMs: 0, databaseLabel: "workboard database", databasePath: dbPath, foreignKeys: true, synchronous: "NORMAL" }); ensureWorkboardSchema(db); hardenWorkboardDatabaseFiles(dbPath); return { db, maintenance }; } catch (error) { try { maintenance?.close(); } finally { db.close(); } throw error; } } const CARD_CHILD_TABLES = [ "workboard_card_labels", "workboard_card_events", "workboard_card_attempts", "workboard_card_comments", "workboard_card_links", "workboard_card_proof", "workboard_card_artifacts", "workboard_card_attachments", "workboard_worker_logs", "workboard_card_diagnostics", "workboard_card_notifications" ]; function groupByCardId(rows) { const grouped = /* @__PURE__ */ new Map(); for (const row of rows) { const cardId = stringValue(row, "card_id"); if (!cardId) continue; const bucket = grouped.get(cardId); if (bucket) bucket.push(row); else grouped.set(cardId, [row]); } return grouped; } function loadCardChildRows(db) { const byTable = /* @__PURE__ */ new Map(); for (const table of CARD_CHILD_TABLES) byTable.set(table, groupByCardId(db.prepare(`SELECT * FROM ${table} ORDER BY card_id ASC, ordinal ASC`).all())); const workerProtocol = /* @__PURE__ */ new Map(); for (const row of db.prepare("SELECT * FROM workboard_worker_protocol").all()) { const cardId = stringValue(row, "card_id"); if (cardId) workerProtocol.set(cardId, row); } return { byTable, workerProtocol }; } function childRows(db, table, cardId, preloaded) { const cached = preloaded?.byTable.get(table); if (cached) { const rows = cached.get(cardId) ?? []; cached.delete(cardId); return rows; } return db.prepare(`SELECT * FROM ${table} WHERE card_id = ? ORDER BY ordinal ASC`).all(cardId); } function workerProtocolRow(db, cardId, preloaded) { if (preloaded) { const row = preloaded.workerProtocol.get(cardId); preloaded.workerProtocol.delete(cardId); return row; } return db.prepare("SELECT * FROM workboard_worker_protocol WHERE card_id = ?").get(cardId); } function readLabels(db, cardId, preloaded) { return childRows(db, "workboard_card_labels", cardId, preloaded).flatMap((row) => { const label = stringValue(row, "label"); return label ? [label] : []; }); } function readEvents(db, cardId, preloaded) { const events = childRows(db, "workboard_card_events", cardId, preloaded).map((row) => { const event = { id: requiredString(row, "id"), kind: requiredString(row, "kind"), at: requiredNumber(row, "at") }; const fromStatus = stringValue(row, "from_status"); const toStatus = stringValue(row, "to_status"); const sessionKey = stringValue(row, "session_key"); const runId = stringValue(row, "run_id"); if (fromStatus) event.fromStatus = fromStatus; if (toStatus) event.toStatus = toStatus; if (sessionKey) event.sessionKey = sessionKey; if (runId) event.runId = runId; return event; }); return events.length > 0 ? events : void 0; } function readExecution(row) { const id = stringValue(row, "execution_id"); if (!id) return; return { id, kind: "agent-session", mode: requiredString(row, "execution_mode"), status: requiredString(row, "execution_status"), ...stringValue(row, "execution_engine") ? { engine: stringValue(row, "execution_engine") } : {}, ...stringValue(row, "execution_model") ? { model: stringValue(row, "execution_model") } : {}, ...stringValue(row, "execution_session_key") ? { sessionKey: stringValue(row, "execution_session_key") } : {}, ...stringValue(row, "execution_run_id") ? { runId: stringValue(row, "execution_run_id") } : {}, startedAt: requiredNumber(row, "execution_started_at"), updatedAt: requiredNumber(row, "execution_updated_at") }; } function readAttachment(row) { return { id: requiredString(row, "id"), cardId: requiredString(row, "card_id"), createdAt: requiredNumber(row, "created_at"), fileName: requiredString(row, "file_name"), byteSize: requiredNumber(row, "byte_size"), ...stringValue(row, "mime_type") ? { mimeType: stringValue(row, "mime_type") } : {}, ...stringValue(row, "note") ? { note: stringValue(row, "note") } : {} }; } function readMetadata(db, row, preloaded) { const cardId = requiredString(row, "id"); const attempts = childRows(db, "workboard_card_attempts", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), status: requiredString(child, "status"), startedAt: requiredNumber(child, "started_at") }; const endedAt = numberValue(child, "ended_at"); const engine = stringValue(child, "engine"); const mode = stringValue(child, "mode"); const model = stringValue(child, "model"); const sessionKey = stringValue(child, "session_key"); const runId = stringValue(child, "run_id"); const error = stringValue(child, "error"); if (endedAt !== void 0) entry.endedAt = endedAt; if (engine) entry.engine = engine; if (mode) entry.mode = mode; if (model) entry.model = model; if (sessionKey) entry.sessionKey = sessionKey; if (runId) entry.runId = runId; if (error) entry.error = error; return entry; }); const comments = childRows(db, "workboard_card_comments", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), body: requiredString(child, "body"), createdAt: requiredNumber(child, "created_at") }; const updatedAt = numberValue(child, "updated_at"); if (updatedAt !== void 0) entry.updatedAt = updatedAt; return entry; }); const links = childRows(db, "workboard_card_links", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), type: requiredString(child, "type"), createdAt: requiredNumber(child, "created_at") }; const targetCardId = stringValue(child, "target_card_id"); const title = stringValue(child, "title"); const url = stringValue(child, "url"); if (targetCardId) entry.targetCardId = targetCardId; if (title) entry.title = title; if (url) entry.url = url; return entry; }); const proof = childRows(db, "workboard_card_proof", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), status: requiredString(child, "status"), createdAt: requiredNumber(child, "created_at") }; const label = stringValue(child, "label"); const command = stringValue(child, "command"); const url = stringValue(child, "url"); const note = stringValue(child, "note"); if (label) entry.label = label; if (command) entry.command = command; if (url) entry.url = url; if (note) entry.note = note; return entry; }); const artifacts = childRows(db, "workboard_card_artifacts", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), createdAt: requiredNumber(child, "created_at") }; const label = stringValue(child, "label"); const url = stringValue(child, "url"); const artifactPath = stringValue(child, "path"); const mimeType = stringValue(child, "mime_type"); if (label) entry.label = label; if (url) entry.url = url; if (artifactPath) entry.path = artifactPath; if (mimeType) entry.mimeType = mimeType; return entry; }); const attachments = childRows(db, "workboard_card_attachments", cardId, preloaded).map(readAttachment); const workerLogs = childRows(db, "workboard_worker_logs", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), createdAt: requiredNumber(child, "created_at"), level: requiredString(child, "level"), message: requiredString(child, "message") }; const sessionKey = stringValue(child, "session_key"); const runId = stringValue(child, "run_id"); if (sessionKey) entry.sessionKey = sessionKey; if (runId) entry.runId = runId; return entry; }); const diagnostics = childRows(db, "workboard_card_diagnostics", cardId, preloaded).map((child) => ({ kind: requiredString(child, "kind"), severity: requiredString(child, "severity"), title: requiredString(child, "title"), detail: requiredString(child, "detail"), firstSeenAt: requiredNumber(child, "first_seen_at"), lastSeenAt: requiredNumber(child, "last_seen_at"), count: requiredNumber(child, "count"), actions: parseJson(child.actions_json) ?? [] })); const notifications = childRows(db, "workboard_card_notifications", cardId, preloaded).map((child) => { const entry = { id: requiredString(child, "id"), kind: requiredString(child, "kind"), createdAt: requiredNumber(child, "created_at"), message: requiredString(child, "message") }; const sequence = numberValue(child, "sequence"); const sessionKey = stringValue(child, "session_key"); const runId = stringValue(child, "run_id"); if (sequence !== void 0) entry.sequence = sequence; if (sessionKey) entry.sessionKey = sessionKey; if (runId) entry.runId = runId; return entry; }); const protocol = workerProtocolRow(db, cardId, preloaded); const automation = parseJson(row.automation_json); const claim = parseJson(row.claim_json); const stale = parseJson(row.stale_json); const lifecycleStatusSourceUpdatedAt = numberValue(row, "lifecycle_status_source_updated_at"); return optional({ ...attempts.length > 0 ? { attempts } : {}, ...comments.length > 0 ? { comments } : {}, ...links.length > 0 ? { links } : {}, ...proof.length > 0 ? { proof } : {}, ...artifacts.length > 0 ? { artifacts } : {}, ...attachments.length > 0 ? { attachments } : {}, ...workerLogs.length > 0 ? { workerLogs } : {}, ...protocol ? { workerProtocol: { state: requiredString(protocol, "state"), updatedAt: requiredNumber(protocol, "updated_at"), ...stringValue(protocol, "detail") ? { detail: stringValue(protocol, "detail") } : {} } } : {}, ...automation ? { automation } : {}, ...claim ? { claim } : {}, ...diagnostics.length > 0 ? { diagnostics } : {}, ...notifications.length > 0 ? { notifications } : {}, ...stringValue(row, "template_id") ? { templateId: stringValue(row, "template_id") } : {}, ...numberValue(row, "archived_at") !== void 0 ? { archivedAt: numberValue(row, "archived_at") } : {}, ...stale ? { stale } : {}, ...lifecycleStatusSourceUpdatedAt !== void 0 ? { lifecycleStatusSourceUpdatedAt } : {}, ...numberValue(row, "failure_count") !== void 0 ? { failureCount: numberValue(row, "failure_count") } : {} }); } function readCard(db, row, preloaded) { const card = { id: requiredString(row, "id"), title: requiredString(row, "title"), status: requiredString(row, "status"), priority: requiredString(row, "priority"), labels: readLabels(db, requiredString(row, "id"), preloaded), position: requiredNumber(row, "position"), createdAt: requiredNumber(row, "created_at"), updatedAt: requiredNumber(row, "updated_at") }; const metadata = readMetadata(db, row, preloaded); const events = readEvents(db, card.id, preloaded); return { ...card, ...stringValue(row, "notes") ? { notes: stringValue(row, "notes") } : {}, ...stringValue(row, "agent_id") ? { agentId: stringValue(row, "agent_id") } : {}, ...stringValue(row, "session_key") ? { sessionKey: stringValue(row, "session_key") } : {}, ...stringValue(row, "run_id") ? { runId: stringValue(row, "run_id") } : {}, ...stringValue(row, "task_id") ? { taskId: stringValue(row, "task_id") } : {}, ...stringValue(row, "source_url") ? { sourceUrl: stringValue(row, "source_url") } : {}, ...readExecution(row) ? { execution: readExecution(row) } : {}, ...numberValue(row, "started_at") !== void 0 ? { startedAt: numberValue(row, "started_at") } : {}, ...numberValue(row, "completed_at") !== void 0 ? { completedAt: numberValue(row, "completed_at") } : {}, ...events ? { events } : {}, ...metadata ? { metadata } : {} }; } function cardBoardId(card) { return card.metadata?.automation?.boardId ?? "default"; } function bindNull(value) { if (value === void 0 || value === null || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || value instanceof Uint8Array) return value ?? null; return JSON.stringify(value); } function insertChildren(db, table, cardId, entries, insert) { db.prepare(`DELETE FROM ${table} WHERE card_id = ?`).run(cardId); entries?.forEach(insert); } function insertCard(db, card) { const execution = card.execution; const metadata = card.metadata; db.prepare(` INSERT INTO workboard_cards ( id, board_id, title, notes, status, priority, agent_id, session_key, run_id, task_id, source_url, position, created_at, updated_at, started_at, completed_at, execution_id, execution_kind, execution_engine, execution_mode, execution_status, execution_model, execution_session_key, execution_run_id, execution_started_at, execution_updated_at, automation_json, claim_json, template_id, archived_at, stale_json, lifecycle_status_source_updated_at, failure_count ) VALUES ( @id, @board_id, @title, @notes, @status, @priority, @agent_id, @session_key, @run_id, @task_id, @source_url, @position, @created_at, @updated_at, @started_at, @completed_at, @execution_id, @execution_kind, @execution_engine, @execution_mode, @execution_status, @execution_model, @execution_session_key, @execution_run_id, @execution_started_at, @execution_updated_at, @automation_json, @claim_json, @template_id, @archived_at, @stale_json, @lifecycle_status_source_updated_at, @failure_count ) ON CONFLICT(id) DO UPDATE SET board_id = excluded.board_id, title = excluded.title, notes = excluded.notes, status = excluded.status, priority = excluded.priority, agent_id = excluded.agent_id, session_key = excluded.session_key, run_id = excluded.run_id, task_id = excluded.task_id, source_url = excluded.source_url, position = excluded.position, created_at = excluded.created_at, updated_at = excluded.updated_at, started_at = excluded.started_at, completed_at = excluded.completed_at, execution_id = excluded.execution_id, execution_kind = excluded.execution_kind, execution_engine = excluded.execution_engine, execution_mode = excluded.execution_mode, execution_status = excluded.execution_status, execution_model = excluded.execution_model, execution_session_key = excluded.execution_session_key, execution_run_id = excluded.execution_run_id, execution_started_at = excluded.execution_started_at, execution_updated_at = excluded.execution_updated_at, automation_json = excluded.automation_json, claim_json = excluded.claim_json, template_id = excluded.template_id, archived_at = excluded.archived_at, stale_json = excluded.stale_json, lifecycle_status_source_updated_at = excluded.lifecycle_status_source_updated_at, failure_count = excluded.failure_count `).run({ id: card.id, board_id: cardBoardId(card), title: card.title, notes: bindNull(card.notes), status: card.status, priority: card.priority, agent_id: bindNull(card.agentId), session_key: bindNull(card.sessionKey), run_id: bindNull(card.runId), task_id: bindNull(card.taskId), source_url: bindNull(card.sourceUrl), position: card.position, created_at: card.createdAt, updated_at: card.updatedAt, started_at: bindNull(card.startedAt), completed_at: bindNull(card.completedAt), execution_id: bindNull(execution?.id), execution_kind: bindNull(execution?.kind), execution_engine: bindNull(execution?.engine), execution_mode: bindNull(execution?.mode), execution_status: bindNull(execution?.status), execution_model: bindNull(execution?.model), execution_session_key: bindNull(execution?.sessionKey), execution_run_id: bindNull(execution?.runId), execution_started_at: bindNull(execution?.startedAt), execution_updated_at: bindNull(execution?.updatedAt), automation_json: jsonValue(metadata?.automation), claim_json: jsonValue(metadata?.claim), template_id: bindNull(metadata?.templateId), archived_at: bindNull(metadata?.archivedAt), stale_json: jsonValue(metadata?.stale), lifecycle_status_source_updated_at: bindNull(metadata?.lifecycleStatusSourceUpdatedAt), failure_count: bindNull(metadata?.failureCount) }); insertChildren(db, "workboard_card_labels", card.id, card.labels, (label, ordinal) => { db.prepare("INSERT INTO workboard_card_labels (card_id, ordinal, label) VALUES (?, ?, ?)").run(card.id, ordinal, label); }); insertChildren(db, "workboard_card_events", card.id, card.events, (event, ordinal) => { db.prepare(` INSERT INTO workboard_card_events (id, card_id, ordinal, kind, at, from_status, to_status, session_key, run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `).run(event.id, card.id, ordinal, event.kind, event.at, bindNull(event.fromStatus), bindNull(event.toStatus), bindNull(event.sessionKey), bindNull(event.runId)); }); insertChildren(db, "workboard_card_attempts", card.id, metadata?.attempts, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_attempts (id, card_id, ordinal, status, started_at, ended_at, engine, mode, model, session_key, run_id, error) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, entry.status, entry.startedAt, bindNull(entry.endedAt), bindNull(entry.engine), bindNull(entry.mode), bindNull(entry.model), bindNull(entry.sessionKey), bindNull(entry.runId), bindNull(entry.error)); }); insertChildren(db, "workboard_card_comments", card.id, metadata?.comments, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_comments (id, card_id, ordinal, body, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, entry.body, entry.createdAt, bindNull(entry.updatedAt)); }); insertChildren(db, "workboard_card_links", card.id, metadata?.links, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_links (id, card_id, ordinal, type, target_card_id, title, url, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, entry.type, bindNull(entry.targetCardId), bindNull(entry.title), bindNull(entry.url), entry.createdAt); }); insertChildren(db, "workboard_card_proof", card.id, metadata?.proof, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_proof (id, card_id, ordinal, status, label, command, url, note, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, entry.status, bindNull(entry.label), bindNull(entry.command), bindNull(entry.url), bindNull(entry.note), entry.createdAt); }); insertChildren(db, "workboard_card_artifacts", card.id, metadata?.artifacts, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_artifacts (id, card_id, ordinal, label, url, path, mime_type, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, bindNull(entry.label), bindNull(entry.url), bindNull(entry.path), bindNull(entry.mimeType), entry.createdAt); }); insertChildren(db, "workboard_card_attachments", card.id, metadata?.attachments, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_attachments (id, card_id, ordinal, file_name, byte_size, mime_type, note, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, entry.cardId, ordinal, entry.fileName, entry.byteSize, bindNull(entry.mimeType), bindNull(entry.note), entry.createdAt); }); insertChildren(db, "workboard_card_diagnostics", card.id, metadata?.diagnostics, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_diagnostics (card_id, ordinal, kind, severity, title, detail, first_seen_at, last_seen_at, count, actions_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run(card.id, ordinal, entry.kind, entry.severity, entry.title, entry.detail, entry.firstSeenAt, entry.lastSeenAt, entry.count, JSON.stringify(entry.actions)); }); insertChildren(db, "workboard_card_notifications", card.id, metadata?.notifications, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_card_notifications (id, card_id, ordinal, kind, message, created_at, sequence, session_key, run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, entry.kind, entry.message, entry.createdAt, bindNull(entry.sequence), bindNull(entry.sessionKey), bindNull(entry.runId)); }); insertChildren(db, "workboard_worker_logs", card.id, metadata?.workerLogs, (entry, ordinal) => { db.prepare(` INSERT INTO workboard_worker_logs (id, card_id, ordinal, level, message, created_at, session_key, run_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(entry.id, card.id, ordinal, entry.level, entry.message, entry.createdAt, bindNull(entry.sessionKey), bindNull(entry.runId)); }); db.prepare("DELETE FROM workboard_worker_protocol WHERE card_id = ?").run(card.id); if (metadata?.workerProtocol) db.prepare(` INSERT INTO workboard_worker_protocol (card_id, state, updated_at, detail) VALUES (?, ?, ?, ?) `).run(card.id, metadata.workerProtocol.state, metadata.workerProtocol.updatedAt, bindNull(metadata.workerProtocol.detail)); } var WorkboardSqliteCardStore = class { constructor(db) { this.db = db; } validatePayload(key, value) { if (value.version !== 1 || value.card.id !== key) throw new Error("invalid workboard card payload"); } async register(key, value) { this.validatePayload(key, value); runSqliteImmediateTransactionSync(this.db, () => insertCard(this.db, value.card)); } async registerIfAbsent(key, value) { this.validatePayload(key, value); return runSqliteImmediateTransactionSync(this.db, () => { if (this.db.prepare("SELECT 1 FROM workboard_cards WHERE id = ?").get(key)) return false; insertCard(this.db, value.card); return true; }); } async registerIfUpdatedAt(key, value, expectedUpdatedAt) { this.validatePayload(key, value); return runSqliteImmediateTransactionSync(this.db, () => { const current = this.db.prepare("SELECT updated_at FROM workboard_cards WHERE id = ?").get(key); if (!isRecord(current) || numberValue(current, "updated_at") !== expectedUpdatedAt) return false; insertCard(this.db, value.card); return true; }); } async claimIfOwnerAvailable(key, value, expectedUpdatedAt, ownerId, now) { this.validatePayload(key, value); return runSqliteImmediateTransactionSync(this.db, () => { const current = this.db.prepare("SELECT updated_at FROM workboard_cards WHERE id = ?").get(key); if (!isRecord(current) || numberValue(current, "updated_at") !== expectedUpdatedAt) return "conflict"; const rows = this.db.prepare("SELECT * FROM workboard_cards WHERE id <> ?").all(key); const preloaded = loadCardChildRows(this.db); for (const row of rows) { const card = readCard(this.db, row, preloaded); if (workboardCardConsumesOwnerSlot(card, now) && workboardCardSlotOwner(card) === ownerId) return "owner_busy"; } insertCard(this.db, value.card); return "updated"; }); } async deleteIfUpdatedAt(key, expectedUpdatedAt) { return runSqliteImmediateTransactionSync(this.db, () => { const current = this.db.prepare("SELECT updated_at FROM workboard_cards WHERE id = ?").get(key); if (!isRecord(current) || numberValue(current, "updated_at") !== expectedUpdatedAt) return false; this.deleteCard(key); return true; }); } async lookup(key) { const row = this.db.prepare("SELECT * FROM workboard_cards WHERE id = ?").get(key); return row ? { version: 1, card: readCard(this.db, row) } : void 0; } async delete(key) { return runSqliteImmediateTransactionSync(this.db, () => this.deleteCard(key)).changes > 0; } deleteCard(key) { this.db.prepare(` DELETE FROM workboard_attachment_blobs WHERE attachment_id IN ( SELECT id FROM workboard_card_attachments WHERE card_id = ? ) `).run(key); return this.db.prepare("DELETE FROM workboard_cards WHERE id = ?").run(key); } async entries() { const rows = this.db.prepare("SELECT * FROM workboard_cards ORDER BY created_at ASC, id ASC").all(); const preloaded = loadCardChildRows(this.db); return rows.map((row) => ({ key: requiredString(row, "id"), value: { version: 1, card: readCard(this.db, row, preloaded) } })); } async listBoardAggregates() { return this.db.prepare(` SELECT board_id, status, COUNT(*) AS total, SUM(CASE WHEN archived_at IS NOT NULL AND archived_at <> 0 THEN 1 ELSE 0 END) AS archived, MAX(updated_at) AS updated_at FROM workboard_cards GROUP BY board_id, status ORDER BY board_id ASC, status ASC `).all().map((row) => ({ boardId: requiredString(row, "board_id"), status: requiredString(row, "status"), total: requiredNumber(row, "total"), archived: requiredNumber(row, "archived"), updatedAt: requiredNumber(row, "updated_at") })); } }; function readBoard(row) { const defaultWorkspace = parseJson(row.default_workspace_json); const orchestration = parseJson(row.orchestration_json); return { version: 1, board: { id: requiredString(row, "id"), ...stringValue(row, "name") ? { name: stringValue(row, "name") } : {}, ...stringValue(row, "description") ? { description: stringValue(row, "description") } : {}, ...stringValue(row, "icon") ? { icon: stringValue(row, "icon") } : {}, ...stringValue(row, "color") ? { color: stringValue(row, "color") } : {}, ...stringValue(row, "automation_job_id") ? { automationJobId: stringValue(row, "automation_job_id") } : {}, ...defaultWorkspace ? { defaultWorkspace } : {}, ...orchestration ? { orchestration } : {}, createdAt: requiredNumber(row, "created_at"), updatedAt: requiredNumber(row, "updated_at"), ...numberValue(row, "archived_at") !== void 0 ? { archivedAt: numberValue(row, "archived_at") } : {} } }; } var WorkboardSqliteBoardStore = class { constructor(db) { this.db = db; this.rowsQuery = getNodeSqliteKysely(db).selectFrom("workboard_boards").selectAll(); } async register(key, value) { if (value.version !== 1 || value.board.id !== key) throw new Error("invalid workboard board payload"); const board = value.board; this.db.prepare(` INSERT INTO workboard_boards ( id, name, description, icon, color, automation_job_id, default_workspace_json, orchestration_json, created_at, updated_at, archived_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET name = excluded.name, description = excluded.description, icon = excluded.icon, color = excluded.color, automation_job_id = excluded.automation_job_id, default_workspace_json = excluded.default_workspace_json, orchestration_json = excluded.orchestration_json, created_at = excluded.created_at, updated_at = excluded.updated_at, archived_at = excluded.archived_at `).run(board.id, bindNull(board.name), bindNull(board.description), bindNull(board.icon), bindNull(board.color), bindNull(board.automationJobId), jsonValue(board.defaultWorkspace), jsonValue(board.orchestration), board.createdAt, board.updatedAt, bindNull(board.archivedAt)); } async lookup(key) { const row = executeSqliteQueryTakeFirstSync(this.db, this.rowsQuery.where("id", "=", key)); return row ? readBoard(row) : void 0; } async delete(key) { return this.db.prepare("DELETE FROM workboard_boards WHERE id = ?").run(key).changes > 0; } async entries() { return Array.from(iterateSqliteQuerySync(this.db, this.rowsQuery.orderBy("id", "asc")), (row) => ({ key: requiredString(row, "id"), value: readBoard(row) })); } }; function readSubscription(row) { const eventKinds = parseJson(row.event_kinds_json); const deliveredEventIds = parseJson(row.delivered_event_ids_json); return { version: 1, subscription: { id: requiredString(row, "id"), boardId: requiredString(row, "board_id"), ...stringValue(row, "card_id") ? { cardId: stringValue(row, "card_id") } : {}, ...stringValue(row, "session_key") ? { sessionKey: stringValue(row, "session_key") } : {}, ...stringValue(row, "run_id") ? { runId: stringValue(row, "run_id") } : {}, ...stringValue(row, "target") ? { target: stringValue(row, "target") } : {}, ...eventKinds ? { eventKinds } : {}, ...numberValue(row, "last_event_at") !== void 0 ? { lastEventAt: numberValue(row, "last_event_at") } : {}, ...stringValue(row, "last_event_id") ? { lastEventId: stringValue(row, "last_event_id") } : {}, ...numberValue(row, "last_event_sequence") !== void 0 ? { lastEventSequence: numberValue(row, "last_event_sequence") } : {}, ...deliveredEventIds ? { deliveredEventIds } : {}, createdAt: requiredNumber(row, "created_at"), updatedAt: requiredNumber(row, "updated_at") } }; } var WorkboardSqliteSubscriptionStore = class { constructor(db) { this.db = db; this.rowsQuery = getNodeSqliteKysely(db).selectFrom("workboard_notification_subscriptions").selectAll(); } async register(key, value) { if (value.version !== 1 || value.subscription.id !== key) throw new Error("invalid workboard notification subscription payload"); const subscription = value.subscription; this.db.prepare(` INSERT INTO workboard_notification_subscriptions ( id, board_id, card_id, session_key, run_id, target, event_kinds_json, last_event_at, last_event_id, last_event_sequence, delivered_event_ids_json, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET board_id = excluded.board_id, card_id = excluded.card_id, session_key = excluded.session_key, run_id = excluded.run_id, target = excluded.target, event_kinds_json = excluded.event_kinds_json, last_event_at = excluded.last_event_at, last_event_id = excluded.last_event_id, last_event_sequence = excluded.last_event_sequence, delivered_event_ids_json = excluded.delivered_event_ids_json, created_at = excluded.created_at, updated_at = excluded.updated_at `).run(subscription.id, subscription.boardId, bindNull(subscription.cardId), bindNull(subscription.sessionKey), bindNull(subscription.runId), bindNull(subscription.target), jsonValue(subscription.eventKinds), bindNull(subscription.lastEventAt), bindNull(subscription.lastEventId), bindNull(subscription.lastEventSequence), jsonValue(subscription.deliveredEventIds), subscription.createdAt, subscription.updatedAt); } async lookup(key) { const row = executeSqliteQueryTakeFirstSync(this.db, this.rowsQuery.where("id", "=", key)); return row ? readSubscription(row) : void 0; } async delete(key) { return this.db.prepare("DELETE FROM workboard_notification_subscriptions WHERE id = ?").run(key).changes > 0; } async entries() { return Array.from(iterateSqliteQuerySync(this.db, this.rowsQuery.orderBy("created_at", "asc").orderBy("id", "asc")), (row) => ({ key: requiredString(row, "id"), value: readSubscription(row) })); } }; function readPersistedAttachment(row) { return { version: 1, attachment: readAttachment(row), contentBase64: blobToBase64(row.content) }; } var WorkboardSqliteAttachmentStore = class { constructor(db) { this.db = db; this.rowsQuery = getNodeSqliteKysely(db).selectFrom("workboard_card_attachments as a").innerJoin("workboard_attachment_blobs as b", "b.attachment_id", "a.id").selectAll("a").select("b.content"); } async register(key, value) { if (value.version !== 1 || value.attachment.id !== key) throw new Error("invalid workboard attachment payload"); const attachment = value.attachment; this.db.prepare(` INSERT INTO workboard_attachment_blobs (attachment_id, content) VALUES (?, ?) ON CONFLICT(attachment_id) DO UPDATE SET content = excluded.content `).run(attachment.id, asBlobContent(value.contentBase64)); } async lookup(key) { const row = executeSqliteQueryTakeFirstSync(this.db, this.rowsQuery.where("a.id", "=", key)); return row ? readPersistedAttachment(row) : void 0; } async delete(key) { return runSqliteImmediateTransactionSync(this.db, () => { this.db.prepare("DELETE FROM workboard_attachment_blobs WHERE attachment_id = ?").run(key); return this.db.prepare("DELETE FROM workboard_card_attachments WHERE id = ?").run(key); }).changes > 0; } async entries() { return Array.from(iterateSqliteQuerySync(this.db, this.rowsQuery.orderBy("a.created_at", "asc").orderBy("a.id", "asc")), (row) => ({ key: requiredString(row, "id"), value: readPersistedAttachment(row) })); } }; function createWorkboardSqliteStores(options = {}) { const { db, maintenance } = createDatabase(options.dbPath ?? resolveWorkboardSqlitePath(options.env)); return { cards: new WorkboardSqliteCardStore(db), boards: new WorkboardSqliteBoardStore(db), subscriptions: new WorkboardSqliteSubscriptionStore(db), attachments: new WorkboardSqliteAttachmentStore(db), dataVersion: () => requiredNumber(db.prepare("PRAGMA data_version").get(), "data_version"), close: () => { maintenance.close(); db.close(); } }; } //#endregion export { DEFAULT_WORKBOARD_DISPATCH_OWNER as a, MAX_CARD_METADATA_BYTES as c, RUNNING_HEARTBEAT_STALE_MS as d, addWorkboardDurationMs as f, workboardCardSlotOwner as g, workboardCardConsumesOwnerSlot as h, DEFAULT_CLAIM_TTL_MS as i, POSITION_STEP as l, secondsToDurationMs as m, resolveWorkboardSqlitePath as n, MAX_ATTACHMENT_BYTES as o, isWorkboardClaimReclaimable as p, BLOCKED_TOO_LONG_MS as r, MAX_CARDS as s, createWorkboardSqliteStores as t, READY_STRANDED_MS as u };