workplane 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../db/src/schema/sqlite.ts","../../core/src/config.ts","../../core/src/env.ts","../../db/src/migration.ts","../../db/src/client.ts","../../db/src/pg-store.ts","../../db/src/schema/pg.ts","../../db/src/sqlite-store.ts","../../db/src/baseline-migrations.ts","../../db/src/migrate.ts"],"sourcesContent":["import { isNull, sql } from \"drizzle-orm\";\nimport { index, integer, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\n\nconst nowDefault = sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;\n\nexport const tasks = sqliteTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n requires: text(\"requires\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = sqliteTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: text(\"last_heartbeat_at\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n});\n\nexport const runs = sqliteTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: text(\"started_at\"),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = sqliteTable(\"run_logs\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: text(\"timestamp\").notNull().default(nowDefault),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = sqliteTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = sqliteTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: text(\"inputs\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n enabled: integer(\"enabled\", { mode: \"boolean\" }).notNull().default(true),\n lastRunAt: text(\"last_run_at\"),\n nextRunAt: text(\"next_run_at\"),\n createdBy: text(\"created_by\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = sqliteTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = sqliteTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = sqliteTable(\"run_input_events\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: integer(\"sequence\").notNull(),\n kind: text(\"kind\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n deliveredAt: text(\"delivered_at\"),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { z } from \"zod\";\nimport { loadLocalEnv } from \"./env.js\";\n\nloadLocalEnv();\n\nconst serverEnvSchema = z.object({\n DATABASE_URL: z.string().optional().default(\"sqlite://~/.workplane/workplane.db\"),\n WORKPLANE_SERVER_PORT: z\n .string()\n .optional()\n .default(\"8787\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_OPERATOR_TOKEN: z.string().optional(),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nconst nodeEnvSchema = z.object({\n WORKPLANE_SERVER_URL: z.string().url().optional().default(\"http://localhost:8787\"),\n WORKPLANE_NODE_NAME: z.string().optional().default(\"local-node-1\"),\n WORKPLANE_NODE_ID: z.string().optional(),\n WORKPLANE_NODE_CAPABILITIES: z\n .string()\n .optional()\n .default(\"shell,git,node,typescript,aider,ollama,codex,claude-code\"),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_POLL_INTERVAL_MS: z\n .string()\n .optional()\n .default(\"3000\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nexport interface ServerConfig {\n databaseUrl: string;\n port: number;\n nodeToken?: string;\n operatorToken?: string;\n envAllowlist: string[];\n}\n\nexport interface NodeConfig {\n serverUrl: string;\n nodeName: string;\n nodeId?: string;\n nodeCapabilities: string[];\n nodeToken?: string;\n pollIntervalMs: number;\n envAllowlist: string[];\n}\n\nexport function loadServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const parsed = serverEnvSchema.parse(env);\n\n return {\n databaseUrl: parsed.DATABASE_URL,\n port: parsed.WORKPLANE_SERVER_PORT,\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n operatorToken: parsed.WORKPLANE_OPERATOR_TOKEN,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function loadNodeConfig(env: NodeJS.ProcessEnv = process.env): NodeConfig {\n const parsed = nodeEnvSchema.parse(env);\n\n return {\n serverUrl: parsed.WORKPLANE_SERVER_URL,\n nodeName: parsed.WORKPLANE_NODE_NAME,\n nodeId: parsed.WORKPLANE_NODE_ID,\n nodeCapabilities: parseCsv(parsed.WORKPLANE_NODE_CAPABILITIES),\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n pollIntervalMs: parsed.WORKPLANE_POLL_INTERVAL_MS,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function parseCsv(value: string): string[] {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n","import { config as dotenvConfig } from \"dotenv\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nlet envLoaded = false;\n\n// Load order (highest priority first, all with override:false so shell env vars win):\n// 1. .env.local — project-local overrides (gitignored)\n// 2. ~/.workplane/.env — user global config written by workplane-setup\n// 3. .env — project defaults (committed)\nexport function loadLocalEnv(): void {\n if (envLoaded) {\n return;\n }\n\n dotenvConfig({ path: \".env.local\" });\n dotenvConfig({ path: join(homedir(), \".workplane\", \".env\") });\n dotenvConfig({ path: \".env\" });\n\n envLoaded = true;\n}\n","// Side-effect-free module — safe to import from both the migrate CLI and setup wizard.\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getDrizzle, getPool } from \"./client.js\";\nimport { baselineLegacyPostgresMigrations, baselineLegacySqliteMigrations } from \"./baseline-migrations.js\";\n\nconst distDir = dirname(fileURLToPath(import.meta.url));\n\nasync function migratePostgres(databaseUrl: string): Promise<void> {\n const adminUrl = new URL(databaseUrl);\n const dbName = adminUrl.pathname.replace(/^\\//, \"\");\n if (!dbName) throw new Error(\"DATABASE_URL must include a database name\");\n\n adminUrl.pathname = \"/postgres\";\n const adminPool = getPool(adminUrl.toString());\n try {\n const { rowCount } = await adminPool.query(\n \"SELECT 1 FROM pg_database WHERE datname = $1\",\n [dbName],\n );\n if (!rowCount) {\n await adminPool.query(`CREATE DATABASE \"${dbName.replaceAll('\"', '\"\"')}\"`);\n process.stdout.write(`Created database: ${dbName}\\n`);\n }\n } catch (err) {\n const e = err as { code?: string };\n if (e.code === \"42501\") {\n throw new Error(\n `Database \"${dbName}\" does not exist and the current role lacks CREATE DATABASE permissions.`,\n );\n }\n throw err;\n } finally {\n await adminPool.end();\n }\n\n const pool = getPool(databaseUrl);\n const migrationsFolder = resolve(distDir, \"migrations/pg\");\n await baselineLegacyPostgresMigrations(pool, migrationsFolder);\n const { migrate } = await import(\"drizzle-orm/node-postgres/migrator\");\n await migrate(getDrizzle(pool), { migrationsFolder });\n await pool.end();\n process.stdout.write(\"Postgres migrations applied.\\n\");\n}\n\nasync function migrateSqlite(databaseUrl: string): Promise<void> {\n const { default: Database } = await import(\"better-sqlite3\");\n const { drizzle } = await import(\"drizzle-orm/better-sqlite3\");\n const { migrate } = await import(\"drizzle-orm/better-sqlite3/migrator\");\n const sqliteSchema = await import(\"./schema/sqlite.js\");\n\n const rawPath = databaseUrl.startsWith(\"sqlite://\")\n ? databaseUrl.slice(\"sqlite://\".length)\n : databaseUrl;\n const filePath = !rawPath\n ? join(homedir(), \".workplane\", \"workplane.db\")\n : rawPath.startsWith(\"~/\") ? join(homedir(), rawPath.slice(2)) : rawPath;\n\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const migrationsFolder = resolve(distDir, \"migrations/sqlite\");\n baselineLegacySqliteMigrations(sqlite, migrationsFolder);\n const db = drizzle(sqlite, { schema: sqliteSchema });\n migrate(db, { migrationsFolder });\n sqlite.close();\n process.stdout.write(`SQLite migrations applied: ${filePath}\\n`);\n}\n\nexport async function runMigration(databaseUrl: string): Promise<void> {\n if (databaseUrl.startsWith(\"postgres://\") || databaseUrl.startsWith(\"postgresql://\")) {\n await migratePostgres(databaseUrl);\n } else {\n await migrateSqlite(databaseUrl);\n }\n}\n","import { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { drizzle as drizzlePg } from \"drizzle-orm/node-postgres\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { drizzle as drizzleSqlite } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport { Pool } from \"pg\";\nimport { PgStore } from \"./pg-store.js\";\nimport { SqliteStore } from \"./sqlite-store.js\";\nimport * as pgSchema from \"./schema/pg.js\";\nimport * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\n\nexport type DrizzleDb = NodePgDatabase<typeof pgSchema>;\n\nexport interface StoreHandle {\n store: WorkplaneStore;\n close: () => Promise<void>;\n}\n\nconst pools = new Map<string, Pool>();\n\nexport function getPool(databaseUrl: string): Pool {\n const existing = pools.get(databaseUrl);\n if (existing) return existing;\n const pool = new Pool({ connectionString: databaseUrl });\n pools.set(databaseUrl, pool);\n return pool;\n}\n\nexport function getDrizzle(pool: Pool): DrizzleDb {\n return drizzlePg(pool, { schema: pgSchema });\n}\n\nfunction parseSqlitePath(url: string): string {\n const path = url.startsWith(\"sqlite://\") ? url.slice(\"sqlite://\".length) : url;\n if (!path) return join(homedir(), \".workplane\", \"workplane.db\");\n if (path.startsWith(\"~/\")) return join(homedir(), path.slice(2));\n return path;\n}\n\nexport function createStore(databaseUrl?: string): StoreHandle {\n const url = databaseUrl ?? \"\";\n\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n const pool = getPool(url);\n return {\n store: new PgStore(getDrizzle(pool)),\n close: () => pool.end(),\n };\n }\n\n const filePath = parseSqlitePath(url);\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const db = drizzleSqlite(sqlite, { schema: sqliteSchema });\n return {\n store: new SqliteStore(db),\n close: async () => { sqlite.close(); },\n };\n}\n","import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql } from \"drizzle-orm\";\nimport { makeId } from \"../../core/src/ids.js\";\nimport type { DrizzleDb } from \"./client.js\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/pg.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"../../types/src/index.js\";\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt?.toISOString() ?? null,\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt?.toISOString() ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp.toISOString(),\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt.toISOString(),\n deliveredAt: row.deliveredAt?.toISOString() ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt?.toISOString() ?? null,\n nextRunAt: row.nextRunAt?.toISOString() ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt.toISOString(),\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction mapRawTask(row: Record<string, unknown>): TaskRecord {\n return {\n id: String(row.id),\n kind: String(row.kind),\n adapter: String(row.adapter),\n payload: (row.payload as Record<string, unknown>) ?? {},\n requires: (row.requires as string[]) ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: new Date(String(row.created_at)).toISOString(),\n updatedAt: new Date(String(row.updated_at)).toISOString(),\n };\n}\n\nfunction mapRawRun(row: Record<string, unknown>): RunRecord {\n return {\n id: String(row.id),\n taskId: String(row.task_id),\n nodeId: String(row.node_id),\n attempt: Number(row.attempt),\n status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ? new Date(String(row.started_at)).toISOString() : null,\n endedAt: row.ended_at ? new Date(String(row.ended_at)).toISOString() : null,\n error: row.error ? String(row.error) : null,\n };\n}\n\nexport class PgStore implements WorkplaneStore {\n constructor(private readonly db: DrizzleDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const taskId = makeId(\"task\");\n const [row] = await this.db.insert(tasks).values({\n id: taskId,\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db\n .select()\n .from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt))\n .limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conditions = [];\n if (filters?.taskId) conditions.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conditions.push(eq(runs.status, filters.status));\n\n const rows = await this.db\n .select()\n .from(runs)\n .where(conditions.length ? and(...conditions) : undefined)\n .orderBy(desc(runs.createdAt))\n .limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: sql`now()`, updatedAt: sql`now()` })\n .where(eq(nodes.id, existing.id))\n .returning();\n return toNodeRecord(updated);\n }\n\n const nodeId = preferredId ?? makeId(\"node\");\n const [inserted] = await this.db.insert(nodes).values({\n id: nodeId, name, capabilities, status: \"online\", lastHeartbeatAt: sql`now()`,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs)\n .innerJoin(tasks, eq(tasks.id, runs.taskId))\n .where(eq(runs.id, runId));\n return row ?? null;\n }\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n return await this.db.transaction(async (tx) => {\n await tx.update(nodes)\n .set({\n capabilities,\n status: \"online\",\n lastHeartbeatAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(eq(nodes.id, nodeId));\n\n const [taskRow] = await tx\n .select()\n .from(tasks)\n .where(and(eq(tasks.status, \"queued\"), arrayContains(tasks.requires, capabilities)))\n .orderBy(asc(tasks.createdAt))\n .limit(1)\n .for(\"update\", { skipLocked: true });\n\n if (!taskRow) return null;\n\n const taskId = taskRow.id;\n\n const attemptResult = await tx.execute(sql`\n SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ${taskId}\n `);\n const attempt = Number((attemptResult.rows[0] as Record<string, unknown>).max_attempt) + 1;\n\n const runId = makeId(\"run\");\n\n const runResult = await tx.execute(sql`\n INSERT INTO runs (id, task_id, node_id, attempt, status, started_at)\n VALUES (${runId}, ${taskId}, ${nodeId}, ${attempt}, 'assigned', now())\n RETURNING *\n `);\n\n await tx.execute(sql`\n UPDATE tasks SET status = 'assigned', updated_at = now() WHERE id = ${taskId}\n `);\n\n return {\n task: toTaskRecord(taskRow),\n run: mapRawRun(runResult.rows[0] as Record<string, unknown>),\n };\n });\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n return await this.db.transaction(async (tx) => {\n const currentResult = await tx.execute(sql`\n SELECT r.*, t.status AS task_status\n FROM runs r JOIN tasks t ON t.id = r.task_id\n WHERE r.id = ${runId}\n FOR UPDATE\n `);\n\n if (currentResult.rows.length === 0) return null;\n\n const currentRow = currentResult.rows[0] as Record<string, unknown>;\n const currentTaskStatus = String(currentRow.task_status);\n\n if (currentTaskStatus === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(currentRow);\n }\n\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n const [updatedRun] = await tx.update(runs)\n .set(isTerminal\n ? { status, error: error ?? null, endedAt: sql`now()`, updatedAt: sql`now()` }\n : { status, error: error ?? null, updatedAt: sql`now()` })\n .where(eq(runs.id, runId))\n .returning();\n\n if (!updatedRun) return null;\n\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (currentTaskStatus !== \"cancelled\" || status === \"cancelled\") {\n await tx.update(tasks)\n .set({ status: nextTaskStatus, updatedAt: sql`now()` })\n .where(eq(tasks.id, updatedRun.taskId));\n }\n\n return toRunRecord(updatedRun);\n });\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId,\n stepName: log.stepName ?? null,\n stream: log.stream,\n message: log.message,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string): Promise<RunLogRecord[]> {\n const rows = await this.db\n .select()\n .from(runLogs)\n .where(eq(runLogs.runId, runId))\n .orderBy(asc(runLogs.timestamp));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const artifactId = makeId(\"artifact\");\n const [row] = await this.db.insert(artifacts).values({\n id: artifactId,\n runId,\n type: input.type,\n name: input.name,\n path: input.path,\n metadata: input.metadata ?? null,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db\n .select()\n .from(artifacts)\n .where(eq(artifacts.runId, runId))\n .orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: sql`now()` })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\")))\n .returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n return await this.db.transaction(async (tx) => {\n const taskResult = await tx.execute(sql`\n SELECT * FROM tasks WHERE id = ${taskId} FOR UPDATE\n `);\n\n if (taskResult.rows.length === 0) return null;\n\n const taskRow = taskResult.rows[0] as Record<string, unknown>;\n const taskStatus = String(taskRow.status);\n\n if (taskStatus === \"succeeded\" || taskStatus === \"failed\" || taskStatus === \"cancelled\") {\n return null;\n }\n\n if (taskStatus === \"assigned\" || taskStatus === \"running\") {\n const runResult = await tx.execute(sql`\n SELECT * FROM runs WHERE task_id = ${taskId} ORDER BY attempt DESC LIMIT 1 FOR UPDATE\n `);\n\n if (runResult.rows.length > 0) {\n const runRow = runResult.rows[0] as Record<string, unknown>;\n const runStatus = String(runRow.status);\n if (runStatus !== \"succeeded\" && runStatus !== \"failed\" && runStatus !== \"cancelled\") {\n await tx.execute(sql`\n UPDATE runs SET status = 'cancelled', ended_at = now(), updated_at = now(),\n error = COALESCE(error, 'task cancelled')\n WHERE id = ${runRow.id}\n `);\n }\n }\n }\n\n const [updatedTask] = await tx.update(tasks)\n .set({ status: \"cancelled\", updatedAt: sql`now()` })\n .where(eq(tasks.id, taskId))\n .returning();\n\n return toTaskRecord(updatedTask);\n });\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n return await this.db.transaction(async (tx) => {\n const seqResult = await tx.execute(sql`\n SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ${runId}\n `);\n const sequence = Number((seqResult.rows[0] as Record<string, unknown>).next_seq);\n\n const [inserted] = await tx.insert(runInputEvents)\n .values({ runId, sequence, kind: input.kind, payload: input.payload })\n .returning();\n\n return toInputEventRecord(inserted);\n });\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db\n .select()\n .from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: sql`now()` })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ? new Date(input.nextRunAt) : null,\n createdBy: input.createdBy ?? null,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = {};\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt ? new Date(input.nextRunAt) : null;\n patch.updatedAt = new Date();\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n return await this.db.transaction(async (tx) => {\n await tx.update(workplanRuns)\n .set({ scheduleId: null })\n .where(eq(workplanRuns.scheduleId, scheduleId));\n\n const deleted = await tx.delete(workplanSchedules)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning({ id: workplanSchedules.id });\n return deleted.length > 0;\n });\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const asOfDate = new Date(asOf);\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n isNotNull(workplanSchedules.nextRunAt),\n lte(workplanSchedules.nextRunAt, asOfDate),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({\n lastRunAt: new Date(lastRunAt),\n nextRunAt: new Date(nextRunAt),\n updatedAt: sql`now()`,\n })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (input.idempotencyKey && code === \"23505\") {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ? new Date(endedAt) : status !== \"running\" ? new Date() : null,\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { isNull, sql } from \"drizzle-orm\";\nimport { bigint, bigserial, boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from \"drizzle-orm/pg-core\";\n\nexport const tasks = pgTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n requires: text(\"requires\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = pgTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: timestamp(\"last_heartbeat_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport const runs = pgTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: timestamp(\"started_at\", { withTimezone: true }),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = pgTable(\"run_logs\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: timestamp(\"timestamp\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = pgTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = pgTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: jsonb(\"inputs\").$type<Record<string, unknown>>().notNull().default({}),\n enabled: boolean(\"enabled\").notNull().default(true),\n lastRunAt: timestamp(\"last_run_at\", { withTimezone: true }),\n nextRunAt: timestamp(\"next_run_at\", { withTimezone: true }),\n createdBy: text(\"created_by\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = pgTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = pgTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = pgTable(\"run_input_events\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: bigint(\"sequence\", { mode: \"number\" }).notNull(),\n kind: text(\"kind\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n deliveredAt: timestamp(\"delivered_at\", { withTimezone: true }),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { and, asc, desc, eq, gt, lte, sql } from \"drizzle-orm\";\nimport type { BetterSQLite3Database } from \"drizzle-orm/better-sqlite3\";\nimport { makeId } from \"../../core/src/ids.js\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/sqlite.js\";\nimport type * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"../../types/src/index.js\";\n\ntype SqliteDb = BetterSQLite3Database<typeof sqliteSchema>;\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\n// Drizzle-typed mappers for simple CRUD methods (camelCase, text timestamps)\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt ?? null,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp,\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt,\n deliveredAt: row.deliveredAt ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt ?? null,\n nextRunAt: row.nextRunAt ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\n// Raw-row mappers for transaction methods that use $client directly.\n// better-sqlite3 rejects async transaction callbacks so we use the sync\n// $client API with prepared statements for the 4 methods that need atomicity.\n\ninterface RawTaskRow {\n id: string; kind: string; adapter: string;\n payload: string; requires: string; status: string;\n created_at: string; updated_at: string;\n}\n\ninterface RawRunRow {\n id: string; task_id: string; node_id: string; attempt: number; status: string;\n started_at: string | null; ended_at: string | null; error: string | null;\n created_at: string; updated_at: string;\n}\n\nfunction mapRawTask(row: RawTaskRow): TaskRecord {\n return {\n id: row.id, kind: row.kind, adapter: row.adapter,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n requires: JSON.parse(row.requires) as string[],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.created_at, updatedAt: row.updated_at,\n };\n}\n\nfunction mapRawRun(row: RawRunRow): RunRecord {\n return {\n id: row.id, taskId: row.task_id, nodeId: row.node_id,\n attempt: row.attempt, status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ?? null, endedAt: row.ended_at ?? null,\n error: row.error ?? null,\n };\n}\n\nexport class SqliteStore implements WorkplaneStore {\n constructor(private readonly db: SqliteDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(tasks).values({\n id: makeId(\"task\"),\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db.select().from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt)).limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conds = [];\n if (filters?.taskId) conds.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conds.push(eq(runs.status, filters.status));\n const rows = await this.db.select().from(runs)\n .where(conds.length ? and(...conds) : undefined)\n .orderBy(desc(runs.createdAt)).limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const now = nowIso();\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, existing.id)).returning();\n return toNodeRecord(updated);\n }\n const [inserted] = await this.db.insert(nodes).values({\n id: preferredId ?? makeId(\"node\"), name, capabilities,\n status: \"online\", lastHeartbeatAt: now, createdAt: now, updatedAt: now,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs).innerJoin(tasks, eq(tasks.id, runs.taskId)).where(eq(runs.id, runId));\n return row ? { runStatus: row.runStatus, taskStatus: row.taskStatus ?? \"\" } : null;\n }\n\n // Transaction methods use this.db.$client directly because better-sqlite3\n // rejects async callbacks — all operations inside must be synchronous.\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n const now = nowIso();\n await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, nodeId));\n\n const raw = this.db.$client;\n return raw.transaction(() => {\n const capSet = new Set(capabilities);\n const queued = raw.prepare(\n \"SELECT * FROM tasks WHERE status = 'queued' ORDER BY created_at ASC\",\n ).all() as RawTaskRow[];\n\n const task = queued.find((t) =>\n (JSON.parse(t.requires) as string[]).every((r) => capSet.has(r)),\n );\n if (!task) return null;\n\n const { max_attempt } = raw.prepare(\n \"SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ?\",\n ).get(task.id) as { max_attempt: number };\n\n const runId = makeId(\"run\");\n raw.prepare(\n \"INSERT INTO runs (id, task_id, node_id, attempt, status, started_at, created_at, updated_at) VALUES (?, ?, ?, ?, 'assigned', ?, ?, ?)\",\n ).run(runId, task.id, nodeId, max_attempt + 1, now, now, now);\n\n raw.prepare(\"UPDATE tasks SET status = 'assigned', updated_at = ? WHERE id = ?\").run(now, task.id);\n\n return {\n task: mapRawTask(task),\n run: mapRawRun(raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow),\n };\n })();\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const current = raw.prepare(\n \"SELECT r.*, t.status AS task_status FROM runs r JOIN tasks t ON t.id = r.task_id WHERE r.id = ?\",\n ).get(runId) as (RawRunRow & { task_status: string }) | undefined;\n if (!current) return null;\n\n if (current.task_status === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(current);\n }\n\n const now = nowIso();\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n if (isTerminal) {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, ended_at = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, now, runId);\n } else {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, runId);\n }\n\n const updatedRun = raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow;\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (current.task_status !== \"cancelled\" || status === \"cancelled\") {\n raw.prepare(\"UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?\").run(\n nextTaskStatus, now, updatedRun.task_id,\n );\n }\n return mapRawRun(updatedRun);\n })();\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n const now = nowIso();\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId, stepName: log.stepName ?? null,\n stream: log.stream, message: log.message, timestamp: now,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string): Promise<RunLogRecord[]> {\n const rows = await this.db.select().from(runLogs)\n .where(eq(runLogs.runId, runId)).orderBy(asc(runLogs.timestamp));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(artifacts).values({\n id: makeId(\"artifact\"), runId, type: input.type, name: input.name,\n path: input.path, metadata: input.metadata ?? null, createdAt: now,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db.select().from(artifacts)\n .where(eq(artifacts.runId, runId)).orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: nowIso() })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\"))).returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const taskRow = raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow | undefined;\n if (!taskRow) return null;\n\n const terminal = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n if (terminal.has(taskRow.status)) return null;\n\n const now = nowIso();\n if (taskRow.status === \"assigned\" || taskRow.status === \"running\") {\n const latestRun = raw.prepare(\n \"SELECT * FROM runs WHERE task_id = ? ORDER BY attempt DESC LIMIT 1\",\n ).get(taskId) as RawRunRow | undefined;\n\n if (latestRun && !terminal.has(latestRun.status)) {\n raw.prepare(\n \"UPDATE runs SET status = 'cancelled', ended_at = ?, updated_at = ?, error = COALESCE(error, 'task cancelled') WHERE id = ?\",\n ).run(now, now, latestRun.id);\n }\n }\n\n raw.prepare(\"UPDATE tasks SET status = 'cancelled', updated_at = ? WHERE id = ?\").run(now, taskId);\n return mapRawTask(raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow);\n })();\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n const raw = this.db.$client;\n const now = nowIso();\n const event = raw.transaction(() => {\n const { next_seq } = raw.prepare(\n \"SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ?\",\n ).get(runId) as { next_seq: number };\n\n raw.prepare(\n \"INSERT INTO run_input_events (run_id, sequence, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n ).run(runId, next_seq, input.kind, JSON.stringify(input.payload), now);\n\n return raw.prepare(\n \"SELECT * FROM run_input_events WHERE run_id = ? AND sequence = ?\",\n ).get(runId, next_seq) as {\n id: number; run_id: string; sequence: number; kind: string;\n payload: string; created_at: string; delivered_at: string | null;\n };\n })();\n\n return {\n id: event.id, runId: event.run_id, sequence: event.sequence,\n kind: event.kind as RunInputEvent[\"kind\"],\n payload: JSON.parse(event.payload) as Record<string, unknown>,\n createdAt: event.created_at, deliveredAt: event.delivered_at ?? null,\n };\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db.select().from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: nowIso() })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ?? null,\n createdBy: input.createdBy ?? null,\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = { updatedAt: nowIso() };\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt;\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n raw.prepare(\"UPDATE workplan_runs SET schedule_id = NULL WHERE schedule_id = ?\").run(scheduleId);\n const result = raw.prepare(\"DELETE FROM workplan_schedules WHERE id = ?\").run(scheduleId);\n return (result.changes ?? 0) > 0;\n })();\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n sql`${workplanSchedules.nextRunAt} IS NOT NULL`,\n lte(workplanSchedules.nextRunAt, asOf),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({ lastRunAt, nextRunAt, updatedAt: nowIso() })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n const now = nowIso();\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n createdAt: now,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n if (input.idempotencyKey && err instanceof Error && err.message.includes(\"UNIQUE constraint failed\")) {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ?? (status !== \"running\" ? nowIso() : null),\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const now = nowIso();\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n createdAt: now,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type pg from \"pg\";\n\ninterface JournalEntry {\n idx: number;\n when: number;\n tag: string;\n}\n\ninterface Journal {\n entries: JournalEntry[];\n}\n\nfunction readJournal(migrationsFolder: string): Journal {\n return JSON.parse(\n readFileSync(resolve(migrationsFolder, \"meta/_journal.json\"), \"utf8\"),\n ) as Journal;\n}\n\nfunction migrationHash(migrationsFolder: string, tag: string): string {\n const sql = readFileSync(resolve(migrationsFolder, `${tag}.sql`), \"utf8\");\n return createHash(\"sha256\").update(sql).digest(\"hex\");\n}\n\nfunction baselineEntries(migrationsFolder: string, upToIdx: number): Array<{ hash: string; createdAt: number; tag: string }> {\n const journal = readJournal(migrationsFolder);\n return journal.entries\n .filter((entry) => entry.idx <= upToIdx)\n .map((entry) => ({\n hash: migrationHash(migrationsFolder, entry.tag),\n createdAt: entry.when,\n tag: entry.tag,\n }));\n}\n\nasync function tableExistsPg(pool: pg.Pool, tableName: string): Promise<boolean> {\n const result = await pool.query<{ exists: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = $1\n ) AS exists`,\n [tableName],\n );\n return Boolean(result.rows[0]?.exists);\n}\n\nasync function getLastMigrationCreatedAtPg(pool: pg.Pool): Promise<number | null> {\n await pool.query('CREATE SCHEMA IF NOT EXISTS drizzle');\n await pool.query(`\n CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (\n id SERIAL PRIMARY KEY,\n hash text NOT NULL,\n created_at bigint\n )\n `);\n\n const result = await pool.query<{ created_at: string | null }>(\n \"SELECT created_at FROM drizzle.__drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n );\n const value = result.rows[0]?.created_at;\n return value === null || value === undefined ? null : Number(value);\n}\n\nasync function insertBaselinePg(\n pool: pg.Pool,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): Promise<string[]> {\n const inserted: string[] = [];\n const lastCreatedAt = await getLastMigrationCreatedAtPg(pool);\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n\n const existing = await pool.query<{ id: number }>(\n \"SELECT id FROM drizzle.__drizzle_migrations WHERE created_at = $1 LIMIT 1\",\n [entry.createdAt],\n );\n if (existing.rowCount) {\n continue;\n }\n\n await pool.query(\n 'INSERT INTO drizzle.__drizzle_migrations (\"hash\", \"created_at\") VALUES ($1, $2)',\n [entry.hash, entry.createdAt],\n );\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport async function baselineLegacyPostgresMigrations(\n pool: pg.Pool,\n migrationsFolder: string,\n): Promise<void> {\n const hasTasks = await tableExistsPg(pool, \"tasks\");\n if (!hasTasks) {\n return;\n }\n\n const hasWorkplanSchedules = await tableExistsPg(pool, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = await insertBaselinePg(pool, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing Postgres schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n\nfunction tableExistsSqlite(db: import(\"better-sqlite3\").Database, tableName: string): boolean {\n const row = db.prepare(\n \"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1\",\n ).get(tableName);\n return Boolean(row);\n}\n\nfunction getLastMigrationCreatedAtSqlite(db: import(\"better-sqlite3\").Database): number | null {\n db.exec(`\n CREATE TABLE IF NOT EXISTS __drizzle_migrations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n hash text NOT NULL,\n created_at numeric\n )\n `);\n\n const row = db.prepare(\n \"SELECT created_at FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n ).get() as { created_at: number } | undefined;\n\n return row?.created_at ?? null;\n}\n\nfunction insertBaselineSqlite(\n db: import(\"better-sqlite3\").Database,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): string[] {\n const inserted: string[] = [];\n const lastCreatedAt = getLastMigrationCreatedAtSqlite(db);\n\n const insert = db.prepare(\n 'INSERT INTO __drizzle_migrations (\"hash\", \"created_at\") VALUES (?, ?)',\n );\n const exists = db.prepare(\n \"SELECT 1 FROM __drizzle_migrations WHERE created_at = ? LIMIT 1\",\n );\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n if (exists.get(entry.createdAt)) {\n continue;\n }\n insert.run(entry.hash, entry.createdAt);\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport function baselineLegacySqliteMigrations(\n db: import(\"better-sqlite3\").Database,\n migrationsFolder: string,\n): void {\n if (!tableExistsSqlite(db, \"tasks\")) {\n return;\n }\n\n const hasWorkplanSchedules = tableExistsSqlite(db, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = insertBaselineSqlite(db, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing SQLite schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n","import { loadServerConfig } from \"../../core/src/config.js\";\nimport { runMigration } from \"./migration.js\";\n\nasync function main(): Promise<void> {\n const config = loadServerConfig();\n await runMigration(config.databaseUrl);\n}\n\nmain().catch((error) => {\n process.stderr.write(`${String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":["artifacts","nodes","runInputEvents","runLogs","runs","tasks","workplanRuns","workplanSchedules","workplanStepResults","isNull","sql","index","integer","text","uniqueIndex","mkdirSync","homedir","dirname","join","resolve","homedir","join","sql","and","asc","desc","eq","gt","lte","sql","sql","dirname","resolve","Database","join","homedir","mkdirSync"]}
1
+ {"version":3,"sources":["../../../libs/db/src/schema/sqlite.ts","../../../libs/core/src/config.ts","../../../libs/core/src/env.ts","../../../libs/db/src/migration.ts","../../../libs/db/src/client.ts","../../../libs/db/src/pg-store.ts","../../../libs/db/src/schema/pg.ts","../../../libs/db/src/sqlite-store.ts","../../../libs/db/src/baseline-migrations.ts","../../../libs/db/src/migrate.ts"],"sourcesContent":["import { isNull, sql } from \"drizzle-orm\";\nimport { index, integer, sqliteTable, text, uniqueIndex } from \"drizzle-orm/sqlite-core\";\n\nconst nowDefault = sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`;\n\nexport const tasks = sqliteTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n requires: text(\"requires\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = sqliteTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\", { mode: \"json\" }).$type<string[]>().notNull().default([]),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: text(\"last_heartbeat_at\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n});\n\nexport const runs = sqliteTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: text(\"started_at\"),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = sqliteTable(\"run_logs\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: text(\"timestamp\").notNull().default(nowDefault),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = sqliteTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = sqliteTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: text(\"inputs\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n enabled: integer(\"enabled\", { mode: \"boolean\" }).notNull().default(true),\n lastRunAt: text(\"last_run_at\"),\n nextRunAt: text(\"next_run_at\"),\n createdBy: text(\"created_by\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n updatedAt: text(\"updated_at\").notNull().default(nowDefault),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = sqliteTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n endedAt: text(\"ended_at\"),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = sqliteTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: text(\"metadata\", { mode: \"json\" }).$type<Record<string, unknown>>(),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = sqliteTable(\"run_input_events\", {\n id: integer(\"id\").primaryKey({ autoIncrement: true }),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: integer(\"sequence\").notNull(),\n kind: text(\"kind\").notNull(),\n payload: text(\"payload\", { mode: \"json\" }).$type<Record<string, unknown>>().notNull().default({}),\n createdAt: text(\"created_at\").notNull().default(nowDefault),\n deliveredAt: text(\"delivered_at\"),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { z } from \"zod\";\nimport { loadLocalEnv } from \"./env.js\";\n\nloadLocalEnv();\n\nconst serverEnvSchema = z.object({\n DATABASE_URL: z.string().optional().default(\"sqlite://~/.workplane/workplane.db\"),\n WORKPLANE_SERVER_PORT: z\n .string()\n .optional()\n .default(\"8787\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_OPERATOR_TOKEN: z.string().optional(),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nconst nodeEnvSchema = z.object({\n WORKPLANE_SERVER_URL: z.string().url().optional().default(\"http://localhost:8787\"),\n WORKPLANE_NODE_NAME: z.string().optional().default(\"local-node-1\"),\n WORKPLANE_NODE_ID: z.string().optional(),\n WORKPLANE_NODE_CAPABILITIES: z\n .string()\n .optional()\n .default(\"shell,git,node,typescript,aider,ollama,codex,claude-code\"),\n WORKPLANE_NODE_TOKEN: z.string().optional(),\n WORKPLANE_POLL_INTERVAL_MS: z\n .string()\n .optional()\n .default(\"3000\")\n .transform((value) => Number.parseInt(value, 10)),\n WORKPLANE_ENV_ALLOWLIST: z.string().optional(),\n});\n\nexport interface ServerConfig {\n databaseUrl: string;\n port: number;\n nodeToken?: string;\n operatorToken?: string;\n envAllowlist: string[];\n}\n\nexport interface NodeConfig {\n serverUrl: string;\n nodeName: string;\n nodeId?: string;\n nodeCapabilities: string[];\n nodeToken?: string;\n pollIntervalMs: number;\n envAllowlist: string[];\n}\n\nexport function loadServerConfig(env: NodeJS.ProcessEnv = process.env): ServerConfig {\n const parsed = serverEnvSchema.parse(env);\n\n return {\n databaseUrl: parsed.DATABASE_URL,\n port: parsed.WORKPLANE_SERVER_PORT,\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n operatorToken: parsed.WORKPLANE_OPERATOR_TOKEN,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function loadNodeConfig(env: NodeJS.ProcessEnv = process.env): NodeConfig {\n const parsed = nodeEnvSchema.parse(env);\n\n return {\n serverUrl: parsed.WORKPLANE_SERVER_URL,\n nodeName: parsed.WORKPLANE_NODE_NAME,\n nodeId: parsed.WORKPLANE_NODE_ID,\n nodeCapabilities: parseCsv(parsed.WORKPLANE_NODE_CAPABILITIES),\n nodeToken: parsed.WORKPLANE_NODE_TOKEN,\n pollIntervalMs: parsed.WORKPLANE_POLL_INTERVAL_MS,\n envAllowlist: parseCsv(parsed.WORKPLANE_ENV_ALLOWLIST ?? \"\"),\n };\n}\n\nexport function parseCsv(value: string): string[] {\n return value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean);\n}\n","import { config as dotenvConfig } from \"dotenv\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nlet envLoaded = false;\n\n// Load order (highest priority first, all with override:false so shell env vars win):\n// 1. .env.local — project-local overrides (gitignored)\n// 2. ~/.workplane/.env — user global config written by workplane-setup\n// 3. .env — project defaults (committed)\nexport function loadLocalEnv(): void {\n if (envLoaded) {\n return;\n }\n\n dotenvConfig({ path: \".env.local\" });\n dotenvConfig({ path: join(homedir(), \".workplane\", \".env\") });\n dotenvConfig({ path: \".env\" });\n\n envLoaded = true;\n}\n","// Side-effect-free module — safe to import from both the migrate CLI and setup wizard.\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { getDrizzle, getPool } from \"./client.js\";\nimport { baselineLegacyPostgresMigrations, baselineLegacySqliteMigrations } from \"./baseline-migrations.js\";\n\nconst distDir = dirname(fileURLToPath(import.meta.url));\n\nasync function migratePostgres(databaseUrl: string): Promise<void> {\n const adminUrl = new URL(databaseUrl);\n const dbName = adminUrl.pathname.replace(/^\\//, \"\");\n if (!dbName) throw new Error(\"DATABASE_URL must include a database name\");\n\n adminUrl.pathname = \"/postgres\";\n const adminPool = getPool(adminUrl.toString());\n try {\n const { rowCount } = await adminPool.query(\n \"SELECT 1 FROM pg_database WHERE datname = $1\",\n [dbName],\n );\n if (!rowCount) {\n await adminPool.query(`CREATE DATABASE \"${dbName.replaceAll('\"', '\"\"')}\"`);\n process.stdout.write(`Created database: ${dbName}\\n`);\n }\n } catch (err) {\n const e = err as { code?: string };\n if (e.code === \"42501\") {\n throw new Error(\n `Database \"${dbName}\" does not exist and the current role lacks CREATE DATABASE permissions.`,\n );\n }\n throw err;\n } finally {\n await adminPool.end();\n }\n\n const pool = getPool(databaseUrl);\n const migrationsFolder = resolve(distDir, \"migrations/pg\");\n await baselineLegacyPostgresMigrations(pool, migrationsFolder);\n const { migrate } = await import(\"drizzle-orm/node-postgres/migrator\");\n await migrate(getDrizzle(pool), { migrationsFolder });\n await pool.end();\n process.stdout.write(\"Postgres migrations applied.\\n\");\n}\n\nasync function migrateSqlite(databaseUrl: string): Promise<void> {\n const { default: Database } = await import(\"better-sqlite3\");\n const { drizzle } = await import(\"drizzle-orm/better-sqlite3\");\n const { migrate } = await import(\"drizzle-orm/better-sqlite3/migrator\");\n const sqliteSchema = await import(\"./schema/sqlite.js\");\n\n const rawPath = databaseUrl.startsWith(\"sqlite://\")\n ? databaseUrl.slice(\"sqlite://\".length)\n : databaseUrl;\n const filePath = !rawPath\n ? join(homedir(), \".workplane\", \"workplane.db\")\n : rawPath.startsWith(\"~/\") ? join(homedir(), rawPath.slice(2)) : rawPath;\n\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const migrationsFolder = resolve(distDir, \"migrations/sqlite\");\n baselineLegacySqliteMigrations(sqlite, migrationsFolder);\n const db = drizzle(sqlite, { schema: sqliteSchema });\n migrate(db, { migrationsFolder });\n sqlite.close();\n process.stdout.write(`SQLite migrations applied: ${filePath}\\n`);\n}\n\nexport async function runMigration(databaseUrl: string): Promise<void> {\n if (databaseUrl.startsWith(\"postgres://\") || databaseUrl.startsWith(\"postgresql://\")) {\n await migratePostgres(databaseUrl);\n } else {\n await migrateSqlite(databaseUrl);\n }\n}\n","import { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { drizzle as drizzlePg } from \"drizzle-orm/node-postgres\";\nimport type { NodePgDatabase } from \"drizzle-orm/node-postgres\";\nimport { drizzle as drizzleSqlite } from \"drizzle-orm/better-sqlite3\";\nimport Database from \"better-sqlite3\";\nimport { Pool } from \"pg\";\nimport { PgStore } from \"./pg-store.js\";\nimport { SqliteStore } from \"./sqlite-store.js\";\nimport * as pgSchema from \"./schema/pg.js\";\nimport * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\n\nexport type DrizzleDb = NodePgDatabase<typeof pgSchema>;\n\nexport interface StoreHandle {\n store: WorkplaneStore;\n close: () => Promise<void>;\n}\n\nconst pools = new Map<string, Pool>();\n\nexport function getPool(databaseUrl: string): Pool {\n const existing = pools.get(databaseUrl);\n if (existing) return existing;\n const pool = new Pool({ connectionString: databaseUrl });\n pools.set(databaseUrl, pool);\n return pool;\n}\n\nexport function getDrizzle(pool: Pool): DrizzleDb {\n return drizzlePg(pool, { schema: pgSchema });\n}\n\nfunction parseSqlitePath(url: string): string {\n const path = url.startsWith(\"sqlite://\") ? url.slice(\"sqlite://\".length) : url;\n if (!path) return join(homedir(), \".workplane\", \"workplane.db\");\n if (path.startsWith(\"~/\")) return join(homedir(), path.slice(2));\n return path;\n}\n\nexport function createStore(databaseUrl?: string): StoreHandle {\n const url = databaseUrl ?? \"\";\n\n if (url.startsWith(\"postgres://\") || url.startsWith(\"postgresql://\")) {\n const pool = getPool(url);\n return {\n store: new PgStore(getDrizzle(pool)),\n close: () => pool.end(),\n };\n }\n\n const filePath = parseSqlitePath(url);\n mkdirSync(dirname(filePath), { recursive: true });\n const sqlite = new Database(filePath);\n sqlite.pragma(\"journal_mode = WAL\");\n sqlite.pragma(\"synchronous = NORMAL\");\n const db = drizzleSqlite(sqlite, { schema: sqliteSchema });\n return {\n store: new SqliteStore(db),\n close: async () => { sqlite.close(); },\n };\n}\n","import { and, arrayContains, asc, desc, eq, gt, isNotNull, lte, sql } from \"drizzle-orm\";\nimport { makeId } from \"@workplane/core\";\nimport type { DrizzleDb } from \"./client.js\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/pg.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"@workplane/types\";\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt?.toISOString() ?? null,\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt?.toISOString() ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp.toISOString(),\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt.toISOString(),\n deliveredAt: row.deliveredAt?.toISOString() ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt?.toISOString() ?? null,\n nextRunAt: row.nextRunAt?.toISOString() ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt.toISOString(),\n updatedAt: row.updatedAt.toISOString(),\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt.toISOString(),\n endedAt: row.endedAt?.toISOString() ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt.toISOString(),\n };\n}\n\nfunction mapRawTask(row: Record<string, unknown>): TaskRecord {\n return {\n id: String(row.id),\n kind: String(row.kind),\n adapter: String(row.adapter),\n payload: (row.payload as Record<string, unknown>) ?? {},\n requires: (row.requires as string[]) ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: new Date(String(row.created_at)).toISOString(),\n updatedAt: new Date(String(row.updated_at)).toISOString(),\n };\n}\n\nfunction mapRawRun(row: Record<string, unknown>): RunRecord {\n return {\n id: String(row.id),\n taskId: String(row.task_id),\n nodeId: String(row.node_id),\n attempt: Number(row.attempt),\n status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ? new Date(String(row.started_at)).toISOString() : null,\n endedAt: row.ended_at ? new Date(String(row.ended_at)).toISOString() : null,\n error: row.error ? String(row.error) : null,\n };\n}\n\nexport class PgStore implements WorkplaneStore {\n constructor(private readonly db: DrizzleDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const taskId = makeId(\"task\");\n const [row] = await this.db.insert(tasks).values({\n id: taskId,\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db\n .select()\n .from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt))\n .limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conditions = [];\n if (filters?.taskId) conditions.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conditions.push(eq(runs.status, filters.status));\n\n const rows = await this.db\n .select()\n .from(runs)\n .where(conditions.length ? and(...conditions) : undefined)\n .orderBy(desc(runs.createdAt))\n .limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: sql`now()`, updatedAt: sql`now()` })\n .where(eq(nodes.id, existing.id))\n .returning();\n return toNodeRecord(updated);\n }\n\n const nodeId = preferredId ?? makeId(\"node\");\n const [inserted] = await this.db.insert(nodes).values({\n id: nodeId, name, capabilities, status: \"online\", lastHeartbeatAt: sql`now()`,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async listNodes(): Promise<NodeRecord[]> {\n const rows = await this.db.select().from(nodes).orderBy(asc(nodes.name));\n return rows.map(toNodeRecord);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs)\n .innerJoin(tasks, eq(tasks.id, runs.taskId))\n .where(eq(runs.id, runId));\n return row ?? null;\n }\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n return await this.db.transaction(async (tx) => {\n await tx.update(nodes)\n .set({\n capabilities,\n status: \"online\",\n lastHeartbeatAt: sql`now()`,\n updatedAt: sql`now()`,\n })\n .where(eq(nodes.id, nodeId));\n\n const [taskRow] = await tx\n .select()\n .from(tasks)\n .where(and(eq(tasks.status, \"queued\"), arrayContains(tasks.requires, capabilities)))\n .orderBy(asc(tasks.createdAt))\n .limit(1)\n .for(\"update\", { skipLocked: true });\n\n if (!taskRow) return null;\n\n const taskId = taskRow.id;\n\n const attemptResult = await tx.execute(sql`\n SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ${taskId}\n `);\n const attempt = Number((attemptResult.rows[0] as Record<string, unknown>).max_attempt) + 1;\n\n const runId = makeId(\"run\");\n\n const runResult = await tx.execute(sql`\n INSERT INTO runs (id, task_id, node_id, attempt, status, started_at)\n VALUES (${runId}, ${taskId}, ${nodeId}, ${attempt}, 'assigned', now())\n RETURNING *\n `);\n\n await tx.execute(sql`\n UPDATE tasks SET status = 'assigned', updated_at = now() WHERE id = ${taskId}\n `);\n\n return {\n task: toTaskRecord(taskRow),\n run: mapRawRun(runResult.rows[0] as Record<string, unknown>),\n };\n });\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n return await this.db.transaction(async (tx) => {\n const currentResult = await tx.execute(sql`\n SELECT r.*, t.status AS task_status\n FROM runs r JOIN tasks t ON t.id = r.task_id\n WHERE r.id = ${runId}\n FOR UPDATE\n `);\n\n if (currentResult.rows.length === 0) return null;\n\n const currentRow = currentResult.rows[0] as Record<string, unknown>;\n const currentTaskStatus = String(currentRow.task_status);\n\n if (currentTaskStatus === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(currentRow);\n }\n\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n const [updatedRun] = await tx.update(runs)\n .set(isTerminal\n ? { status, error: error ?? null, endedAt: sql`now()`, updatedAt: sql`now()` }\n : { status, error: error ?? null, updatedAt: sql`now()` })\n .where(eq(runs.id, runId))\n .returning();\n\n if (!updatedRun) return null;\n\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (currentTaskStatus !== \"cancelled\" || status === \"cancelled\") {\n await tx.update(tasks)\n .set({ status: nextTaskStatus, updatedAt: sql`now()` })\n .where(eq(tasks.id, updatedRun.taskId));\n }\n\n return toRunRecord(updatedRun);\n });\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId,\n stepName: log.stepName ?? null,\n stream: log.stream,\n message: log.message,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string, afterId?: number): Promise<RunLogRecord[]> {\n const rows = await this.db\n .select()\n .from(runLogs)\n .where(afterId === undefined ? eq(runLogs.runId, runId) : and(eq(runLogs.runId, runId), gt(runLogs.id, afterId)))\n .orderBy(asc(runLogs.timestamp), asc(runLogs.id));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const artifactId = makeId(\"artifact\");\n const [row] = await this.db.insert(artifacts).values({\n id: artifactId,\n runId,\n type: input.type,\n name: input.name,\n path: input.path,\n metadata: input.metadata ?? null,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db\n .select()\n .from(artifacts)\n .where(eq(artifacts.runId, runId))\n .orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: sql`now()` })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\")))\n .returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n return await this.db.transaction(async (tx) => {\n const taskResult = await tx.execute(sql`\n SELECT * FROM tasks WHERE id = ${taskId} FOR UPDATE\n `);\n\n if (taskResult.rows.length === 0) return null;\n\n const taskRow = taskResult.rows[0] as Record<string, unknown>;\n const taskStatus = String(taskRow.status);\n\n if (taskStatus === \"succeeded\" || taskStatus === \"failed\" || taskStatus === \"cancelled\") {\n return null;\n }\n\n if (taskStatus === \"assigned\" || taskStatus === \"running\") {\n const runResult = await tx.execute(sql`\n SELECT * FROM runs WHERE task_id = ${taskId} ORDER BY attempt DESC LIMIT 1 FOR UPDATE\n `);\n\n if (runResult.rows.length > 0) {\n const runRow = runResult.rows[0] as Record<string, unknown>;\n const runStatus = String(runRow.status);\n if (runStatus !== \"succeeded\" && runStatus !== \"failed\" && runStatus !== \"cancelled\") {\n await tx.execute(sql`\n UPDATE runs SET status = 'cancelled', ended_at = now(), updated_at = now(),\n error = COALESCE(error, 'task cancelled')\n WHERE id = ${runRow.id}\n `);\n }\n }\n }\n\n const [updatedTask] = await tx.update(tasks)\n .set({ status: \"cancelled\", updatedAt: sql`now()` })\n .where(eq(tasks.id, taskId))\n .returning();\n\n return toTaskRecord(updatedTask);\n });\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n return await this.db.transaction(async (tx) => {\n const seqResult = await tx.execute(sql`\n SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ${runId}\n `);\n const sequence = Number((seqResult.rows[0] as Record<string, unknown>).next_seq);\n\n const [inserted] = await tx.insert(runInputEvents)\n .values({ runId, sequence, kind: input.kind, payload: input.payload })\n .returning();\n\n return toInputEventRecord(inserted);\n });\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db\n .select()\n .from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: sql`now()` })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ? new Date(input.nextRunAt) : null,\n createdBy: input.createdBy ?? null,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = {};\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt ? new Date(input.nextRunAt) : null;\n patch.updatedAt = new Date();\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n return await this.db.transaction(async (tx) => {\n await tx.update(workplanRuns)\n .set({ scheduleId: null })\n .where(eq(workplanRuns.scheduleId, scheduleId));\n\n const deleted = await tx.delete(workplanSchedules)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning({ id: workplanSchedules.id });\n return deleted.length > 0;\n });\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const asOfDate = new Date(asOf);\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n isNotNull(workplanSchedules.nextRunAt),\n lte(workplanSchedules.nextRunAt, asOfDate),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({\n lastRunAt: new Date(lastRunAt),\n nextRunAt: new Date(nextRunAt),\n updatedAt: sql`now()`,\n })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (input.idempotencyKey && code === \"23505\") {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ? new Date(endedAt) : status !== \"running\" ? new Date() : null,\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { isNull, sql } from \"drizzle-orm\";\nimport { bigint, bigserial, boolean, index, integer, jsonb, pgTable, text, timestamp, uniqueIndex } from \"drizzle-orm/pg-core\";\n\nexport const tasks = pgTable(\"tasks\", {\n id: text(\"id\").primaryKey(),\n kind: text(\"kind\").notNull(),\n adapter: text(\"adapter\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n requires: text(\"requires\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n statusCreatedAtIdx: index(\"idx_tasks_status_created_at\").on(table.status, table.createdAt),\n}));\n\nexport const nodes = pgTable(\"nodes\", {\n id: text(\"id\").primaryKey(),\n name: text(\"name\").notNull(),\n capabilities: text(\"capabilities\").array().notNull().default(sql`'{}'::text[]`),\n status: text(\"status\").notNull().default(\"online\"),\n lastHeartbeatAt: timestamp(\"last_heartbeat_at\", { withTimezone: true }),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n});\n\nexport const runs = pgTable(\"runs\", {\n id: text(\"id\").primaryKey(),\n taskId: text(\"task_id\").notNull().references(() => tasks.id),\n nodeId: text(\"node_id\").notNull().references(() => nodes.id),\n attempt: integer(\"attempt\").notNull(),\n status: text(\"status\").notNull(),\n startedAt: timestamp(\"started_at\", { withTimezone: true }),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n taskIdIdx: index(\"idx_runs_task_id\").on(table.taskId),\n nodeIdIdx: index(\"idx_runs_node_id\").on(table.nodeId),\n}));\n\nexport const runLogs = pgTable(\"run_logs\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n stepName: text(\"step_name\"),\n stream: text(\"stream\").notNull(),\n message: text(\"message\").notNull(),\n timestamp: timestamp(\"timestamp\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdTimestampIdx: index(\"idx_run_logs_run_id_timestamp\").on(table.runId, table.timestamp),\n}));\n\nexport const artifacts = pgTable(\"artifacts\", {\n id: text(\"id\").primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n type: text(\"type\").notNull(),\n name: text(\"name\").notNull(),\n path: text(\"path\").notNull(),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdIdx: index(\"idx_artifacts_run_id\").on(table.runId),\n}));\n\nexport const workplanSchedules = pgTable(\"workplan_schedules\", {\n id: text(\"id\").primaryKey(),\n planId: text(\"plan_id\").notNull(),\n name: text(\"name\").notNull(),\n cronExpression: text(\"cron_expression\").notNull(),\n timezone: text(\"timezone\").notNull(),\n inputs: jsonb(\"inputs\").$type<Record<string, unknown>>().notNull().default({}),\n enabled: boolean(\"enabled\").notNull().default(true),\n lastRunAt: timestamp(\"last_run_at\", { withTimezone: true }),\n nextRunAt: timestamp(\"next_run_at\", { withTimezone: true }),\n createdBy: text(\"created_by\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp(\"updated_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n dueIdx: index(\"idx_workplan_schedules_due\").on(table.nextRunAt),\n}));\n\nexport const workplanRuns = pgTable(\"workplan_runs\", {\n id: text(\"id\").primaryKey(),\n scheduleId: text(\"schedule_id\").references(() => workplanSchedules.id),\n planId: text(\"plan_id\").notNull(),\n planName: text(\"plan_name\").notNull(),\n status: text(\"status\").notNull().default(\"running\"),\n idempotencyKey: text(\"idempotency_key\"),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n endedAt: timestamp(\"ended_at\", { withTimezone: true }),\n error: text(\"error\"),\n}, (table) => ({\n scheduleIdx: index(\"idx_workplan_runs_schedule_id\").on(table.scheduleId),\n idempotencyUnique: uniqueIndex(\"idx_workplan_runs_idempotency\").on(table.idempotencyKey),\n}));\n\nexport const workplanStepResults = pgTable(\"workplan_step_results\", {\n id: text(\"id\").primaryKey(),\n workplanRunId: text(\"workplan_run_id\").notNull().references(() => workplanRuns.id),\n stepId: text(\"step_id\").notNull(),\n stepName: text(\"step_name\").notNull(),\n output: text(\"output\"),\n exitCode: integer(\"exit_code\"),\n durationMs: integer(\"duration_ms\"),\n metadata: jsonb(\"metadata\").$type<Record<string, unknown>>(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n}, (table) => ({\n runIdx: index(\"idx_workplan_step_results_run_id\").on(table.workplanRunId),\n}));\n\nexport const runInputEvents = pgTable(\"run_input_events\", {\n id: bigserial(\"id\", { mode: \"number\" }).primaryKey(),\n runId: text(\"run_id\").notNull().references(() => runs.id),\n sequence: bigint(\"sequence\", { mode: \"number\" }).notNull(),\n kind: text(\"kind\").notNull(),\n payload: jsonb(\"payload\").$type<Record<string, unknown>>().notNull(),\n createdAt: timestamp(\"created_at\", { withTimezone: true }).notNull().defaultNow(),\n deliveredAt: timestamp(\"delivered_at\", { withTimezone: true }),\n}, (table) => ({\n runSequenceUniqueIdx: uniqueIndex(\"idx_run_input_events_run_sequence\").on(table.runId, table.sequence),\n undeliveredIdx: index(\"idx_run_input_events_undelivered\").on(table.runId, table.sequence).where(isNull(table.deliveredAt)),\n}));\n","import { and, asc, desc, eq, gt, lte, sql } from \"drizzle-orm\";\nimport type { BetterSQLite3Database } from \"drizzle-orm/better-sqlite3\";\nimport type BetterSqlite3Client from \"better-sqlite3\";\nimport { makeId } from \"@workplane/core\";\nimport { artifacts, nodes, runInputEvents, runLogs, runs, tasks, workplanRuns, workplanSchedules, workplanStepResults } from \"./schema/sqlite.js\";\nimport type * as sqliteSchema from \"./schema/sqlite.js\";\nimport type { WorkplaneStore } from \"./store-interface.js\";\nimport type {\n AppendInputEventInput,\n ArtifactInput,\n ArtifactRecord,\n CreateTaskInput,\n CreateWorkplanRunInput,\n CreateWorkplanScheduleInput,\n NodePollResult,\n NodeRecord,\n RunInputEvent,\n RunLogInput,\n RunLogRecord,\n RunRecord,\n TaskRecord,\n UpdateWorkplanScheduleInput,\n WorkplanRunRecord,\n WorkplanScheduleRecord,\n WorkplanStepResultInput,\n WorkplanStepResultRecord,\n} from \"@workplane/types\";\n\ntype SqliteDb = BetterSQLite3Database<typeof sqliteSchema> & { $client: BetterSqlite3Client.Database };\n\nfunction nowIso(): string {\n return new Date().toISOString();\n}\n\n// Drizzle-typed mappers for simple CRUD methods (camelCase, text timestamps)\n\nfunction toTaskRecord(row: typeof tasks.$inferSelect): TaskRecord {\n return {\n id: row.id,\n kind: row.kind,\n adapter: row.adapter,\n payload: row.payload,\n requires: row.requires ?? [],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toRunRecord(row: typeof runs.$inferSelect): RunRecord {\n return {\n id: row.id,\n taskId: row.taskId,\n nodeId: row.nodeId,\n attempt: row.attempt,\n status: row.status as RunRecord[\"status\"],\n startedAt: row.startedAt ?? null,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toNodeRecord(row: typeof nodes.$inferSelect): NodeRecord {\n return {\n id: row.id,\n name: row.name,\n capabilities: row.capabilities ?? [],\n status: row.status as NodeRecord[\"status\"],\n lastHeartbeatAt: row.lastHeartbeatAt ?? null,\n };\n}\n\nfunction toLogRecord(row: typeof runLogs.$inferSelect): RunLogRecord {\n return {\n id: row.id,\n runId: row.runId,\n stepName: row.stepName ?? null,\n stream: row.stream as RunLogRecord[\"stream\"],\n message: row.message,\n timestamp: row.timestamp,\n };\n}\n\nfunction toArtifactRecord(row: typeof artifacts.$inferSelect): ArtifactRecord {\n return {\n id: row.id,\n runId: row.runId,\n type: row.type,\n name: row.name,\n path: row.path,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\nfunction toInputEventRecord(row: typeof runInputEvents.$inferSelect): RunInputEvent {\n return {\n id: row.id,\n runId: row.runId,\n sequence: row.sequence,\n kind: row.kind as RunInputEvent[\"kind\"],\n payload: row.payload,\n createdAt: row.createdAt,\n deliveredAt: row.deliveredAt ?? null,\n };\n}\n\nfunction toScheduleRecord(row: typeof workplanSchedules.$inferSelect): WorkplanScheduleRecord {\n return {\n id: row.id,\n planId: row.planId,\n name: row.name,\n cronExpression: row.cronExpression,\n timezone: row.timezone,\n inputs: row.inputs ?? {},\n enabled: row.enabled,\n lastRunAt: row.lastRunAt ?? null,\n nextRunAt: row.nextRunAt ?? null,\n createdBy: row.createdBy ?? null,\n createdAt: row.createdAt,\n updatedAt: row.updatedAt,\n };\n}\n\nfunction toWorkplanRunRecord(row: typeof workplanRuns.$inferSelect): WorkplanRunRecord {\n return {\n id: row.id,\n scheduleId: row.scheduleId ?? null,\n planId: row.planId,\n planName: row.planName,\n status: row.status as WorkplanRunRecord[\"status\"],\n idempotencyKey: row.idempotencyKey ?? null,\n createdAt: row.createdAt,\n endedAt: row.endedAt ?? null,\n error: row.error ?? null,\n };\n}\n\nfunction toWorkplanStepResultRecord(row: typeof workplanStepResults.$inferSelect): WorkplanStepResultRecord {\n return {\n id: row.id,\n workplanRunId: row.workplanRunId,\n stepId: row.stepId,\n stepName: row.stepName,\n output: row.output ?? null,\n exitCode: row.exitCode ?? null,\n durationMs: row.durationMs ?? null,\n metadata: row.metadata ?? null,\n createdAt: row.createdAt,\n };\n}\n\n// Raw-row mappers for transaction methods that use $client directly.\n// better-sqlite3 rejects async transaction callbacks so we use the sync\n// $client API with prepared statements for the 4 methods that need atomicity.\n\ninterface RawTaskRow {\n id: string; kind: string; adapter: string;\n payload: string; requires: string; status: string;\n created_at: string; updated_at: string;\n}\n\ninterface RawRunRow {\n id: string; task_id: string; node_id: string; attempt: number; status: string;\n started_at: string | null; ended_at: string | null; error: string | null;\n created_at: string; updated_at: string;\n}\n\nfunction mapRawTask(row: RawTaskRow): TaskRecord {\n return {\n id: row.id, kind: row.kind, adapter: row.adapter,\n payload: JSON.parse(row.payload) as Record<string, unknown>,\n requires: JSON.parse(row.requires) as string[],\n status: row.status as TaskRecord[\"status\"],\n createdAt: row.created_at, updatedAt: row.updated_at,\n };\n}\n\nfunction mapRawRun(row: RawRunRow): RunRecord {\n return {\n id: row.id, taskId: row.task_id, nodeId: row.node_id,\n attempt: row.attempt, status: row.status as RunRecord[\"status\"],\n startedAt: row.started_at ?? null, endedAt: row.ended_at ?? null,\n error: row.error ?? null,\n };\n}\n\nexport class SqliteStore implements WorkplaneStore {\n constructor(private readonly db: SqliteDb) {}\n\n async createTask(input: CreateTaskInput): Promise<TaskRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(tasks).values({\n id: makeId(\"task\"),\n kind: input.kind,\n adapter: input.adapter,\n payload: input.payload,\n requires: input.requires ?? [],\n status: \"queued\",\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toTaskRecord(row);\n }\n\n async listTasks(status?: TaskRecord[\"status\"]): Promise<TaskRecord[]> {\n const rows = await this.db.select().from(tasks)\n .where(status ? eq(tasks.status, status) : undefined)\n .orderBy(desc(tasks.createdAt)).limit(100);\n return rows.map(toTaskRecord);\n }\n\n async getTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.select().from(tasks).where(eq(tasks.id, taskId));\n return row ? toTaskRecord(row) : null;\n }\n\n async listRuns(filters?: { taskId?: string; status?: RunRecord[\"status\"] }): Promise<RunRecord[]> {\n const conds = [];\n if (filters?.taskId) conds.push(eq(runs.taskId, filters.taskId));\n if (filters?.status) conds.push(eq(runs.status, filters.status));\n const rows = await this.db.select().from(runs)\n .where(conds.length ? and(...conds) : undefined)\n .orderBy(desc(runs.createdAt)).limit(100);\n return rows.map(toRunRecord);\n }\n\n async getRun(runId: string): Promise<RunRecord | null> {\n const [row] = await this.db.select().from(runs).where(eq(runs.id, runId));\n return row ? toRunRecord(row) : null;\n }\n\n async registerNode(name: string, capabilities: string[], preferredId?: string): Promise<NodeRecord> {\n const now = nowIso();\n const [existing] = await this.db.select().from(nodes).where(eq(nodes.name, name));\n if (existing) {\n const [updated] = await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, existing.id)).returning();\n return toNodeRecord(updated);\n }\n const [inserted] = await this.db.insert(nodes).values({\n id: preferredId ?? makeId(\"node\"), name, capabilities,\n status: \"online\", lastHeartbeatAt: now, createdAt: now, updatedAt: now,\n }).returning();\n return toNodeRecord(inserted);\n }\n\n async listNodes(): Promise<NodeRecord[]> {\n const rows = await this.db.select().from(nodes).orderBy(asc(nodes.name));\n return rows.map(toNodeRecord);\n }\n\n async getRunCancellationState(runId: string): Promise<{ runStatus: string; taskStatus: string } | null> {\n const [row] = await this.db\n .select({ runStatus: runs.status, taskStatus: tasks.status })\n .from(runs).innerJoin(tasks, eq(tasks.id, runs.taskId)).where(eq(runs.id, runId));\n return row ? { runStatus: row.runStatus, taskStatus: row.taskStatus ?? \"\" } : null;\n }\n\n // Transaction methods use this.db.$client directly because better-sqlite3\n // rejects async callbacks — all operations inside must be synchronous.\n\n async pollNode(nodeId: string, capabilities: string[]): Promise<NodePollResult | null> {\n const now = nowIso();\n await this.db.update(nodes)\n .set({ capabilities, status: \"online\", lastHeartbeatAt: now, updatedAt: now })\n .where(eq(nodes.id, nodeId));\n\n const raw = this.db.$client;\n return raw.transaction(() => {\n const capSet = new Set(capabilities);\n const queued = raw.prepare(\n \"SELECT * FROM tasks WHERE status = 'queued' ORDER BY created_at ASC\",\n ).all() as RawTaskRow[];\n\n const task = queued.find((t) =>\n (JSON.parse(t.requires) as string[]).every((r) => capSet.has(r)),\n );\n if (!task) return null;\n\n const { max_attempt } = raw.prepare(\n \"SELECT COALESCE(MAX(attempt), 0) AS max_attempt FROM runs WHERE task_id = ?\",\n ).get(task.id) as { max_attempt: number };\n\n const runId = makeId(\"run\");\n raw.prepare(\n \"INSERT INTO runs (id, task_id, node_id, attempt, status, started_at, created_at, updated_at) VALUES (?, ?, ?, ?, 'assigned', ?, ?, ?)\",\n ).run(runId, task.id, nodeId, max_attempt + 1, now, now, now);\n\n raw.prepare(\"UPDATE tasks SET status = 'assigned', updated_at = ? WHERE id = ?\").run(now, task.id);\n\n return {\n task: mapRawTask(task),\n run: mapRawRun(raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow),\n };\n })();\n }\n\n async updateRunStatus(runId: string, status: RunRecord[\"status\"], error?: string): Promise<RunRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const current = raw.prepare(\n \"SELECT r.*, t.status AS task_status FROM runs r JOIN tasks t ON t.id = r.task_id WHERE r.id = ?\",\n ).get(runId) as (RawRunRow & { task_status: string }) | undefined;\n if (!current) return null;\n\n if (current.task_status === \"cancelled\" && status !== \"cancelled\") {\n return mapRawRun(current);\n }\n\n const now = nowIso();\n const isTerminal = status === \"succeeded\" || status === \"failed\" || status === \"cancelled\";\n if (isTerminal) {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, ended_at = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, now, runId);\n } else {\n raw.prepare(\n \"UPDATE runs SET status = ?, error = ?, updated_at = ? WHERE id = ?\",\n ).run(status, error ?? null, now, runId);\n }\n\n const updatedRun = raw.prepare(\"SELECT * FROM runs WHERE id = ?\").get(runId) as RawRunRow;\n const nextTaskStatus = isTerminal ? status : \"running\";\n if (current.task_status !== \"cancelled\" || status === \"cancelled\") {\n raw.prepare(\"UPDATE tasks SET status = ?, updated_at = ? WHERE id = ?\").run(\n nextTaskStatus, now, updatedRun.task_id,\n );\n }\n return mapRawRun(updatedRun);\n })();\n }\n\n async appendRunLogs(runId: string, logs: RunLogInput[]): Promise<number> {\n if (logs.length === 0) return 0;\n const now = nowIso();\n await this.db.insert(runLogs).values(\n logs.map((log) => ({\n runId, stepName: log.stepName ?? null,\n stream: log.stream, message: log.message, timestamp: now,\n })),\n );\n return logs.length;\n }\n\n async getRunLogs(runId: string, afterId?: number): Promise<RunLogRecord[]> {\n const rows = await this.db.select().from(runLogs)\n .where(afterId === undefined ? eq(runLogs.runId, runId) : and(eq(runLogs.runId, runId), gt(runLogs.id, afterId)))\n .orderBy(asc(runLogs.timestamp), asc(runLogs.id));\n return rows.map(toLogRecord);\n }\n\n async createArtifact(runId: string, input: ArtifactInput): Promise<ArtifactRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(artifacts).values({\n id: makeId(\"artifact\"), runId, type: input.type, name: input.name,\n path: input.path, metadata: input.metadata ?? null, createdAt: now,\n }).returning();\n return toArtifactRecord(row);\n }\n\n async listRunArtifacts(runId: string): Promise<ArtifactRecord[]> {\n const rows = await this.db.select().from(artifacts)\n .where(eq(artifacts.runId, runId)).orderBy(asc(artifacts.createdAt));\n return rows.map(toArtifactRecord);\n }\n\n async retryTask(taskId: string): Promise<TaskRecord | null> {\n const [row] = await this.db.update(tasks)\n .set({ status: \"queued\", updatedAt: nowIso() })\n .where(and(eq(tasks.id, taskId), eq(tasks.status, \"failed\"))).returning();\n return row ? toTaskRecord(row) : null;\n }\n\n async cancelTask(taskId: string): Promise<TaskRecord | null> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n const taskRow = raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow | undefined;\n if (!taskRow) return null;\n\n const terminal = new Set([\"succeeded\", \"failed\", \"cancelled\"]);\n if (terminal.has(taskRow.status)) return null;\n\n const now = nowIso();\n if (taskRow.status === \"assigned\" || taskRow.status === \"running\") {\n const latestRun = raw.prepare(\n \"SELECT * FROM runs WHERE task_id = ? ORDER BY attempt DESC LIMIT 1\",\n ).get(taskId) as RawRunRow | undefined;\n\n if (latestRun && !terminal.has(latestRun.status)) {\n raw.prepare(\n \"UPDATE runs SET status = 'cancelled', ended_at = ?, updated_at = ?, error = COALESCE(error, 'task cancelled') WHERE id = ?\",\n ).run(now, now, latestRun.id);\n }\n }\n\n raw.prepare(\"UPDATE tasks SET status = 'cancelled', updated_at = ? WHERE id = ?\").run(now, taskId);\n return mapRawTask(raw.prepare(\"SELECT * FROM tasks WHERE id = ?\").get(taskId) as RawTaskRow);\n })();\n }\n\n async appendInputEvent(runId: string, input: AppendInputEventInput): Promise<RunInputEvent> {\n const raw = this.db.$client;\n const now = nowIso();\n const event = raw.transaction(() => {\n const { next_seq } = raw.prepare(\n \"SELECT COALESCE(MAX(sequence), 0) + 1 AS next_seq FROM run_input_events WHERE run_id = ?\",\n ).get(runId) as { next_seq: number };\n\n raw.prepare(\n \"INSERT INTO run_input_events (run_id, sequence, kind, payload, created_at) VALUES (?, ?, ?, ?, ?)\",\n ).run(runId, next_seq, input.kind, JSON.stringify(input.payload), now);\n\n return raw.prepare(\n \"SELECT * FROM run_input_events WHERE run_id = ? AND sequence = ?\",\n ).get(runId, next_seq) as {\n id: number; run_id: string; sequence: number; kind: string;\n payload: string; created_at: string; delivered_at: string | null;\n };\n })();\n\n return {\n id: event.id, runId: event.run_id, sequence: event.sequence,\n kind: event.kind as RunInputEvent[\"kind\"],\n payload: JSON.parse(event.payload) as Record<string, unknown>,\n createdAt: event.created_at, deliveredAt: event.delivered_at ?? null,\n };\n }\n\n async getInputEvents(runId: string, afterSequence: number): Promise<RunInputEvent[]> {\n const rows = await this.db.select().from(runInputEvents)\n .where(and(eq(runInputEvents.runId, runId), gt(runInputEvents.sequence, afterSequence)))\n .orderBy(asc(runInputEvents.sequence));\n return rows.map(toInputEventRecord);\n }\n\n async markInputDelivered(runId: string, sequence: number): Promise<void> {\n await this.db.update(runInputEvents)\n .set({ deliveredAt: nowIso() })\n .where(and(eq(runInputEvents.runId, runId), eq(runInputEvents.sequence, sequence)));\n }\n\n async createWorkplanSchedule(input: CreateWorkplanScheduleInput): Promise<WorkplanScheduleRecord> {\n const now = nowIso();\n const [row] = await this.db.insert(workplanSchedules).values({\n id: makeId(\"sched\"),\n planId: input.planId,\n name: input.name,\n cronExpression: input.cronExpression,\n timezone: input.timezone,\n inputs: input.inputs ?? {},\n enabled: input.enabled ?? true,\n nextRunAt: input.enabled === false ? null : input.nextRunAt ?? null,\n createdBy: input.createdBy ?? null,\n createdAt: now,\n updatedAt: now,\n }).returning();\n return toScheduleRecord(row);\n }\n\n async listWorkplanSchedules(enabled?: boolean): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(enabled === undefined ? undefined : eq(workplanSchedules.enabled, enabled))\n .orderBy(desc(workplanSchedules.createdAt));\n return rows.map(toScheduleRecord);\n }\n\n async getWorkplanSchedule(scheduleId: string): Promise<WorkplanScheduleRecord | null> {\n const [row] = await this.db.select().from(workplanSchedules).where(eq(workplanSchedules.id, scheduleId));\n return row ? toScheduleRecord(row) : null;\n }\n\n async updateWorkplanSchedule(\n scheduleId: string,\n input: UpdateWorkplanScheduleInput,\n ): Promise<WorkplanScheduleRecord | null> {\n const patch: Partial<typeof workplanSchedules.$inferInsert> = { updatedAt: nowIso() };\n if (input.name !== undefined) patch.name = input.name;\n if (input.cronExpression !== undefined) patch.cronExpression = input.cronExpression;\n if (input.timezone !== undefined) patch.timezone = input.timezone;\n if (input.inputs !== undefined) patch.inputs = input.inputs;\n if (input.enabled !== undefined) patch.enabled = input.enabled;\n if (input.nextRunAt !== undefined) patch.nextRunAt = input.nextRunAt;\n\n const [row] = await this.db.update(workplanSchedules)\n .set(patch)\n .where(eq(workplanSchedules.id, scheduleId))\n .returning();\n return row ? toScheduleRecord(row) : null;\n }\n\n async deleteWorkplanSchedule(scheduleId: string): Promise<boolean> {\n const raw = this.db.$client;\n return raw.transaction(() => {\n raw.prepare(\"UPDATE workplan_runs SET schedule_id = NULL WHERE schedule_id = ?\").run(scheduleId);\n const result = raw.prepare(\"DELETE FROM workplan_schedules WHERE id = ?\").run(scheduleId);\n return (result.changes ?? 0) > 0;\n })();\n }\n\n async listDueWorkplanSchedules(asOf: string): Promise<WorkplanScheduleRecord[]> {\n const rows = await this.db.select().from(workplanSchedules)\n .where(and(\n eq(workplanSchedules.enabled, true),\n sql`${workplanSchedules.nextRunAt} IS NOT NULL`,\n lte(workplanSchedules.nextRunAt, asOf),\n ))\n .orderBy(asc(workplanSchedules.nextRunAt));\n return rows.map(toScheduleRecord);\n }\n\n async markWorkplanScheduleRan(scheduleId: string, lastRunAt: string, nextRunAt: string): Promise<void> {\n await this.db.update(workplanSchedules)\n .set({ lastRunAt, nextRunAt, updatedAt: nowIso() })\n .where(eq(workplanSchedules.id, scheduleId));\n }\n\n async tryCreateWorkplanRun(input: CreateWorkplanRunInput): Promise<WorkplanRunRecord | null> {\n const now = nowIso();\n try {\n const [row] = await this.db.insert(workplanRuns).values({\n id: makeId(\"wprun\"),\n scheduleId: input.scheduleId ?? null,\n planId: input.planId,\n planName: input.planName,\n status: \"running\",\n idempotencyKey: input.idempotencyKey ?? null,\n createdAt: now,\n }).returning();\n return toWorkplanRunRecord(row);\n } catch (err) {\n if (input.idempotencyKey && err instanceof Error && err.message.includes(\"UNIQUE constraint failed\")) {\n return null;\n }\n throw err;\n }\n }\n\n async updateWorkplanRun(\n runId: string,\n status: WorkplanRunRecord[\"status\"],\n error?: string,\n endedAt?: string,\n ): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.update(workplanRuns)\n .set({\n status,\n error: error ?? null,\n endedAt: endedAt ?? (status !== \"running\" ? nowIso() : null),\n })\n .where(eq(workplanRuns.id, runId))\n .returning();\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async getWorkplanRun(runId: string): Promise<WorkplanRunRecord | null> {\n const [row] = await this.db.select().from(workplanRuns).where(eq(workplanRuns.id, runId));\n return row ? toWorkplanRunRecord(row) : null;\n }\n\n async listWorkplanRuns(filters?: { scheduleId?: string }): Promise<WorkplanRunRecord[]> {\n const rows = await this.db.select().from(workplanRuns)\n .where(filters?.scheduleId ? eq(workplanRuns.scheduleId, filters.scheduleId) : undefined)\n .orderBy(desc(workplanRuns.createdAt))\n .limit(100);\n return rows.map(toWorkplanRunRecord);\n }\n\n async appendWorkplanStepResults(\n runId: string,\n steps: WorkplanStepResultInput[],\n ): Promise<WorkplanStepResultRecord[]> {\n if (steps.length === 0) return [];\n const now = nowIso();\n const rows = await this.db.insert(workplanStepResults).values(\n steps.map((step) => ({\n id: makeId(\"wpstep\"),\n workplanRunId: runId,\n stepId: step.stepId,\n stepName: step.stepName,\n output: step.output ?? null,\n exitCode: step.exitCode ?? null,\n durationMs: step.durationMs ?? null,\n metadata: step.metadata ?? null,\n createdAt: now,\n })),\n ).returning();\n return rows.map(toWorkplanStepResultRecord);\n }\n\n async listWorkplanStepResults(runId: string): Promise<WorkplanStepResultRecord[]> {\n const rows = await this.db.select().from(workplanStepResults)\n .where(eq(workplanStepResults.workplanRunId, runId))\n .orderBy(asc(workplanStepResults.createdAt));\n return rows.map(toWorkplanStepResultRecord);\n }\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport type pg from \"pg\";\n\ninterface JournalEntry {\n idx: number;\n when: number;\n tag: string;\n}\n\ninterface Journal {\n entries: JournalEntry[];\n}\n\nfunction readJournal(migrationsFolder: string): Journal {\n return JSON.parse(\n readFileSync(resolve(migrationsFolder, \"meta/_journal.json\"), \"utf8\"),\n ) as Journal;\n}\n\nfunction migrationHash(migrationsFolder: string, tag: string): string {\n const sql = readFileSync(resolve(migrationsFolder, `${tag}.sql`), \"utf8\");\n return createHash(\"sha256\").update(sql).digest(\"hex\");\n}\n\nfunction baselineEntries(migrationsFolder: string, upToIdx: number): Array<{ hash: string; createdAt: number; tag: string }> {\n const journal = readJournal(migrationsFolder);\n return journal.entries\n .filter((entry) => entry.idx <= upToIdx)\n .map((entry) => ({\n hash: migrationHash(migrationsFolder, entry.tag),\n createdAt: entry.when,\n tag: entry.tag,\n }));\n}\n\nasync function tableExistsPg(pool: pg.Pool, tableName: string): Promise<boolean> {\n const result = await pool.query<{ exists: boolean }>(\n `SELECT EXISTS (\n SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = $1\n ) AS exists`,\n [tableName],\n );\n return Boolean(result.rows[0]?.exists);\n}\n\nasync function getLastMigrationCreatedAtPg(pool: pg.Pool): Promise<number | null> {\n await pool.query('CREATE SCHEMA IF NOT EXISTS drizzle');\n await pool.query(`\n CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (\n id SERIAL PRIMARY KEY,\n hash text NOT NULL,\n created_at bigint\n )\n `);\n\n const result = await pool.query<{ created_at: string | null }>(\n \"SELECT created_at FROM drizzle.__drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n );\n const value = result.rows[0]?.created_at;\n return value === null || value === undefined ? null : Number(value);\n}\n\nasync function insertBaselinePg(\n pool: pg.Pool,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): Promise<string[]> {\n const inserted: string[] = [];\n const lastCreatedAt = await getLastMigrationCreatedAtPg(pool);\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n\n const existing = await pool.query<{ id: number }>(\n \"SELECT id FROM drizzle.__drizzle_migrations WHERE created_at = $1 LIMIT 1\",\n [entry.createdAt],\n );\n if (existing.rowCount) {\n continue;\n }\n\n await pool.query(\n 'INSERT INTO drizzle.__drizzle_migrations (\"hash\", \"created_at\") VALUES ($1, $2)',\n [entry.hash, entry.createdAt],\n );\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport async function baselineLegacyPostgresMigrations(\n pool: pg.Pool,\n migrationsFolder: string,\n): Promise<void> {\n const hasTasks = await tableExistsPg(pool, \"tasks\");\n if (!hasTasks) {\n return;\n }\n\n const hasWorkplanSchedules = await tableExistsPg(pool, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = await insertBaselinePg(pool, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing Postgres schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n\nfunction tableExistsSqlite(db: import(\"better-sqlite3\").Database, tableName: string): boolean {\n const row = db.prepare(\n \"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1\",\n ).get(tableName);\n return Boolean(row);\n}\n\nfunction getLastMigrationCreatedAtSqlite(db: import(\"better-sqlite3\").Database): number | null {\n db.exec(`\n CREATE TABLE IF NOT EXISTS __drizzle_migrations (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n hash text NOT NULL,\n created_at numeric\n )\n `);\n\n const row = db.prepare(\n \"SELECT created_at FROM __drizzle_migrations ORDER BY created_at DESC LIMIT 1\",\n ).get() as { created_at: number } | undefined;\n\n return row?.created_at ?? null;\n}\n\nfunction insertBaselineSqlite(\n db: import(\"better-sqlite3\").Database,\n entries: Array<{ hash: string; createdAt: number; tag: string }>,\n): string[] {\n const inserted: string[] = [];\n const lastCreatedAt = getLastMigrationCreatedAtSqlite(db);\n\n const insert = db.prepare(\n 'INSERT INTO __drizzle_migrations (\"hash\", \"created_at\") VALUES (?, ?)',\n );\n const exists = db.prepare(\n \"SELECT 1 FROM __drizzle_migrations WHERE created_at = ? LIMIT 1\",\n );\n\n for (const entry of entries) {\n if (lastCreatedAt !== null && lastCreatedAt >= entry.createdAt) {\n continue;\n }\n if (exists.get(entry.createdAt)) {\n continue;\n }\n insert.run(entry.hash, entry.createdAt);\n inserted.push(entry.tag);\n }\n\n return inserted;\n}\n\nexport function baselineLegacySqliteMigrations(\n db: import(\"better-sqlite3\").Database,\n migrationsFolder: string,\n): void {\n if (!tableExistsSqlite(db, \"tasks\")) {\n return;\n }\n\n const hasWorkplanSchedules = tableExistsSqlite(db, \"workplan_schedules\");\n const upToIdx = hasWorkplanSchedules ? 1 : 0;\n const inserted = insertBaselineSqlite(db, baselineEntries(migrationsFolder, upToIdx));\n\n if (inserted.length > 0) {\n process.stdout.write(\n `Baselined existing SQLite schema for migration(s): ${inserted.join(\", \")}\\n`,\n );\n }\n}\n","import { loadServerConfig } from \"@workplane/core\";\nimport { runMigration } from \"./migration.js\";\n\nasync function main(): Promise<void> {\n const config = loadServerConfig();\n await runMigration(config.databaseUrl);\n}\n\nmain().catch((error) => {\n process.stderr.write(`${String(error)}\\n`);\n process.exit(1);\n});\n"],"mappings":"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","names":["artifacts","nodes","runInputEvents","runLogs","runs","tasks","workplanRuns","workplanSchedules","workplanStepResults","isNull","sql","index","integer","text","uniqueIndex","mkdirSync","homedir","dirname","join","resolve","homedir","join","sql","and","asc","desc","eq","gt","lte","sql","sql","dirname","resolve","Database","join","homedir","mkdirSync"]}
package/dist/node.js CHANGED
@@ -4,10 +4,18 @@
4
4
  import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
5
5
  import { join as join7, resolve } from "path";
6
6
 
7
- // ../core/src/config.ts
7
+ // ../../libs/core/src/auth.ts
8
+ function authHeaders(token) {
9
+ if (!token) {
10
+ return {};
11
+ }
12
+ return { authorization: `Bearer ${token}` };
13
+ }
14
+
15
+ // ../../libs/core/src/config.ts
8
16
  import { z } from "zod";
9
17
 
10
- // ../core/src/env.ts
18
+ // ../../libs/core/src/env.ts
11
19
  import { config as dotenvConfig } from "dotenv";
12
20
  import { homedir } from "os";
13
21
  import { join } from "path";
@@ -22,7 +30,7 @@ function loadLocalEnv() {
22
30
  envLoaded = true;
23
31
  }
24
32
 
25
- // ../core/src/config.ts
33
+ // ../../libs/core/src/config.ts
26
34
  loadLocalEnv();
27
35
  var serverEnvSchema = z.object({
28
36
  DATABASE_URL: z.string().optional().default("sqlite://~/.workplane/workplane.db"),
@@ -66,36 +74,7 @@ function parseCsv(value) {
66
74
  return value.split(",").map((entry) => entry.trim()).filter(Boolean);
67
75
  }
68
76
 
69
- // ../core/src/auth.ts
70
- function authHeaders(token) {
71
- if (!token) {
72
- return {};
73
- }
74
- return { authorization: `Bearer ${token}` };
75
- }
76
-
77
- // ../core/src/http-client.ts
78
- function buildWorkplaneHeaders(options = {}) {
79
- const token = options.nodeToken ?? options.operatorToken;
80
- return {
81
- "content-type": "application/json",
82
- ...authHeaders(token)
83
- };
84
- }
85
- async function workplaneFetch(serverUrl, path, options = {}) {
86
- const response = await fetch(`${serverUrl}${path}`, {
87
- method: options.method ?? "GET",
88
- headers: buildWorkplaneHeaders(options),
89
- body: options.body ? JSON.stringify(options.body) : void 0
90
- });
91
- if (!response.ok) {
92
- const text = await response.text();
93
- throw new Error(`${response.status} ${response.statusText} from ${path}: ${text}`);
94
- }
95
- return await response.json();
96
- }
97
-
98
- // ../core/src/git.ts
77
+ // ../../libs/core/src/git.ts
99
78
  import { join as join2 } from "path";
100
79
  function repoPath(workspacePath) {
101
80
  return join2(workspacePath, "repo");
@@ -127,7 +106,28 @@ async function captureGitDiff(context, adapterName, artifactName = "changes.diff
127
106
  return diffPath;
128
107
  }
129
108
 
130
- // ../adapter-sdk/src/index.ts
109
+ // ../../libs/core/src/http-client.ts
110
+ function buildWorkplaneHeaders(options = {}) {
111
+ const token = options.nodeToken ?? options.operatorToken;
112
+ return {
113
+ "content-type": "application/json",
114
+ ...authHeaders(token)
115
+ };
116
+ }
117
+ async function workplaneFetch(serverUrl, path, options = {}) {
118
+ const response = await fetch(`${serverUrl}${path}`, {
119
+ method: options.method ?? "GET",
120
+ headers: buildWorkplaneHeaders(options),
121
+ body: options.body ? JSON.stringify(options.body) : void 0
122
+ });
123
+ if (!response.ok) {
124
+ const text = await response.text();
125
+ throw new Error(`${response.status} ${response.statusText} from ${path}: ${text}`);
126
+ }
127
+ return await response.json();
128
+ }
129
+
130
+ // ../../libs/adapter-sdk/src/index.ts
131
131
  import { spawn } from "child_process";
132
132
  import { mkdir } from "fs/promises";
133
133
  import { join as join3 } from "path";
@@ -307,7 +307,7 @@ function createPtyExec(context) {
307
307
  return { exec, kill, writeStdin, ptyResize };
308
308
  }
309
309
 
310
- // ../adapter-shell/src/index.ts
310
+ // ../../libs/adapter-shell/src/index.ts
311
311
  import { join as join4 } from "path";
312
312
  import { writeFile } from "fs/promises";
313
313
  var shellAdapter = {
@@ -344,7 +344,7 @@ var shellAdapter = {
344
344
  }
345
345
  };
346
346
 
347
- // ../adapter-aider/src/index.ts
347
+ // ../../libs/adapter-aider/src/index.ts
348
348
  var aiderAdapter = {
349
349
  name: "aider",
350
350
  kind: "agent.run",
@@ -370,7 +370,7 @@ var aiderAdapter = {
370
370
  }
371
371
  };
372
372
 
373
- // ../adapter-ollama/src/index.ts
373
+ // ../../libs/adapter-ollama/src/index.ts
374
374
  import { writeFile as writeFile2 } from "fs/promises";
375
375
  import { join as join5 } from "path";
376
376
  var ollamaAdapter = {
@@ -398,7 +398,7 @@ var ollamaAdapter = {
398
398
  }
399
399
  };
400
400
 
401
- // ../adapter-harness/src/index.ts
401
+ // ../../libs/adapter-harness/src/index.ts
402
402
  import { join as join6 } from "path";
403
403
  function resolveBinary(binary) {
404
404
  return typeof binary === "function" ? binary() : binary;
@@ -454,7 +454,7 @@ ${testResult.stderr}`, "utf8");
454
454
  };
455
455
  }
456
456
 
457
- // ../adapter-codex/src/index.ts
457
+ // ../../libs/adapter-codex/src/index.ts
458
458
  var codexAdapter = createHarnessAdapter({
459
459
  name: "codex",
460
460
  kind: "agent.run",
@@ -478,7 +478,7 @@ var codexAdapter = createHarnessAdapter({
478
478
  }
479
479
  });
480
480
 
481
- // ../adapter-claude-code/src/index.ts
481
+ // ../../libs/adapter-claude-code/src/index.ts
482
482
  var claudeCodeAdapter = createHarnessAdapter({
483
483
  name: "claude-code",
484
484
  kind: "agent.run",