mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 21:11:40 +00:00
refactor(state): inline canonical database schemas at build
This commit is contained in:
@@ -183,7 +183,7 @@ without exceptions outside doctor/import/export/debug boundaries.
|
||||
- [x] Keep Kysely generated types aligned after any schema change.
|
||||
Files: `src/state/openclaw-state-schema.sql`,
|
||||
`src/state/openclaw-agent-schema.sql`,
|
||||
`src/state/*generated*`.
|
||||
`src/state/*-db.generated.d.ts`.
|
||||
Proof: no schema change in this pass; `pnpm db:kysely:check`;
|
||||
`pnpm lint:kysely`.
|
||||
- [x] Re-run focused tests for touched stores, commands, and scripts.
|
||||
@@ -296,12 +296,12 @@ The branch already has a real shared SQLite base:
|
||||
macOS runtime locator, CI, and public install docs all agree.
|
||||
- `src/state/openclaw-state-db.ts` opens `openclaw.sqlite`, sets WAL,
|
||||
`synchronous=NORMAL`, `busy_timeout=30000`, `foreign_keys=ON`, and applies
|
||||
the generated schema module derived from
|
||||
the build-inlined schema bytes derived from
|
||||
`src/state/openclaw-state-schema.sql`.
|
||||
- Kysely table types and runtime schema modules are generated from disposable
|
||||
SQLite databases created from the committed `.sql` files; runtime code no
|
||||
longer keeps copy-pasted schema strings for global, per-agent, or proxy
|
||||
capture databases.
|
||||
- Kysely table types are generated from disposable SQLite databases created
|
||||
from the committed `.sql` files. Source runs read those canonical files;
|
||||
packaged builds inline the same bytes, so runtime code keeps no committed
|
||||
copy-pasted schema strings or packaged SQL assets.
|
||||
- Runtime stores derive selected and inserted row types from those generated
|
||||
Kysely `DB` interfaces instead of shadowing SQLite row shapes by hand. Raw SQL
|
||||
remains limited to schema application, pragmas, and migration-only DDL.
|
||||
|
||||
@@ -31,6 +31,7 @@ const TSDOWN_SOURCE_EXTENSIONS = [
|
||||
".json5",
|
||||
".mjs",
|
||||
".mts",
|
||||
".sql",
|
||||
".ts",
|
||||
".tsx",
|
||||
".yaml",
|
||||
|
||||
@@ -10,15 +10,11 @@ const SCHEMAS = [
|
||||
name: "openclaw-state",
|
||||
schema: "src/state/openclaw-state-schema.sql",
|
||||
outFile: "src/state/openclaw-state-db.generated.d.ts",
|
||||
schemaOutFile: "src/state/openclaw-state-schema.generated.ts",
|
||||
schemaExport: "OPENCLAW_STATE_SCHEMA_SQL",
|
||||
},
|
||||
{
|
||||
name: "openclaw-agent",
|
||||
schema: "src/state/openclaw-agent-schema.sql",
|
||||
outFile: "src/state/openclaw-agent-db.generated.d.ts",
|
||||
schemaOutFile: "src/state/openclaw-agent-schema.generated.ts",
|
||||
schemaExport: "OPENCLAW_AGENT_SCHEMA_SQL",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -118,39 +114,19 @@ function readUtf8(file) {
|
||||
return fs.readFileSync(file, "utf8");
|
||||
}
|
||||
|
||||
function generatedSchemaModule(schema) {
|
||||
const source = readUtf8(schema.schema).trimEnd();
|
||||
const literal = source.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
|
||||
return [
|
||||
"/**",
|
||||
" * This file was generated from the SQLite schema source.",
|
||||
" * Please do not edit it manually.",
|
||||
" */",
|
||||
"",
|
||||
`export const ${schema.schemaExport} = \`${literal}\\n\`;`,
|
||||
"",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function generate(schema) {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
try {
|
||||
db.exec(readUtf8(schema.schema));
|
||||
const typesSource = generateTypes(db);
|
||||
const schemaSource = generatedSchemaModule(schema);
|
||||
|
||||
if (verify) {
|
||||
if (typesSource !== readUtf8(schema.outFile)) {
|
||||
console.error(`${schema.outFile} is out of date. Run pnpm db:kysely:gen.`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
if (schemaSource !== readUtf8(schema.schemaOutFile)) {
|
||||
console.error(`${schema.schemaOutFile} is out of date. Run pnpm db:kysely:gen.`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(schema.outFile, typesSource);
|
||||
fs.writeFileSync(schema.schemaOutFile, schemaSource);
|
||||
}
|
||||
} finally {
|
||||
db.close();
|
||||
|
||||
@@ -7,10 +7,10 @@ import type { RuntimeEnv } from "../runtime.js";
|
||||
import { createLocalSqliteSnapshotProvider } from "../snapshot/local-repository.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "../state/openclaw-agent-db.js";
|
||||
import { resolveOpenClawAgentSqlitePath } from "../state/openclaw-agent-db.paths.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../state/openclaw-state-db.js";
|
||||
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
|
||||
import {
|
||||
backupSqliteCreateCommand,
|
||||
backupSqliteListCommand,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
clearOpenClawAgentDatabaseOpenFailure,
|
||||
migrateOpenClawAgentDatabaseForMaintenance,
|
||||
} from "../state/openclaw-agent-db.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
|
||||
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "../state/openclaw-state-db.js";
|
||||
import {
|
||||
createSessionSqliteMigrationFailureIssue,
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
OPENCLAW_STATE_SCHEMA_VERSION,
|
||||
} from "../state/openclaw-state-db.js";
|
||||
import { resolveOpenClawStateSqlitePath } from "../state/openclaw-state-db.paths.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
|
||||
import { runDoctorStateSqliteCompact } from "./doctor-state-sqlite-compact.js";
|
||||
|
||||
const tempDirs = useAutoCleanupTempDirTracker((cleanup) => {
|
||||
|
||||
@@ -34,7 +34,7 @@ import {
|
||||
OPENCLAW_AGENT_SCHEMA_VERSION,
|
||||
type OpenClawAgentDatabase,
|
||||
} from "../state/openclaw-agent-db.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
|
||||
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "../state/openclaw-state-db.js";
|
||||
import { VERSION } from "../version.js";
|
||||
import { repairGatewayAgentMediaMigrationStartupFailures } from "./gateway-boot-lifecycle.js";
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
// Covers bundling rules encoded in the root tsdown config.
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { bundledPluginRoot } from "openclaw/plugin-sdk/test-fixtures";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import tsdownConfig from "../../tsdown.config.ts";
|
||||
import tsdownConfig, {
|
||||
createStateSchemaInlinePlugin,
|
||||
STATE_SCHEMA_INLINE_PLUGIN_NAME,
|
||||
} from "../../tsdown.config.ts";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
|
||||
|
||||
type TsdownConfigEntry = {
|
||||
deps?: {
|
||||
@@ -12,6 +18,7 @@ type TsdownConfigEntry = {
|
||||
entry?: Record<string, string> | string[];
|
||||
inputOptions?: TsdownInputOptions;
|
||||
outDir?: string;
|
||||
plugins?: Array<{ name?: string }>;
|
||||
};
|
||||
|
||||
type TsdownLog = {
|
||||
@@ -87,6 +94,53 @@ function readAgentAuthDiscoverySource(): string {
|
||||
}
|
||||
|
||||
describe("tsdown config", () => {
|
||||
it.each([
|
||||
{
|
||||
exportName: "OPENCLAW_STATE_SCHEMA_SQL",
|
||||
modulePath: "src/state/openclaw-state-schema.ts",
|
||||
schemaPath: "src/state/openclaw-state-schema.sql",
|
||||
sourceValue: OPENCLAW_STATE_SCHEMA_SQL,
|
||||
},
|
||||
{
|
||||
exportName: "OPENCLAW_AGENT_SCHEMA_SQL",
|
||||
modulePath: "src/state/openclaw-agent-schema.ts",
|
||||
schemaPath: "src/state/openclaw-agent-schema.sql",
|
||||
sourceValue: OPENCLAW_AGENT_SCHEMA_SQL,
|
||||
},
|
||||
])("inlines canonical schema bytes for $modulePath", (schema) => {
|
||||
const rootDir = process.cwd();
|
||||
const watchedPaths: string[] = [];
|
||||
const plugin = createStateSchemaInlinePlugin(rootDir);
|
||||
const result = plugin.load.call(
|
||||
{ addWatchFile: (filePath: string) => watchedPaths.push(filePath) },
|
||||
path.resolve(rootDir, schema.modulePath),
|
||||
);
|
||||
const schemaPath = path.resolve(rootDir, schema.schemaPath);
|
||||
const canonicalSql = readFileSync(schemaPath, "utf8");
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
const match = result?.code.match(
|
||||
new RegExp(`^export const ${schema.exportName} = (.*);\\n$`, "su"),
|
||||
);
|
||||
expect(match?.[1]).toBeDefined();
|
||||
expect(JSON.parse(match?.[1] ?? "null")).toBe(canonicalSql);
|
||||
expect(schema.sourceValue).toBe(canonicalSql);
|
||||
expect(watchedPaths).toEqual([schemaPath]);
|
||||
});
|
||||
|
||||
it("installs schema inlining only on the unified runtime graph", () => {
|
||||
const unifiedGraph = requireUnifiedDistGraph();
|
||||
const inlinePlugins = asConfigArray(tsdownConfig).flatMap(
|
||||
(config) =>
|
||||
config.plugins?.filter((plugin) => plugin.name === STATE_SCHEMA_INLINE_PLUGIN_NAME) ?? [],
|
||||
);
|
||||
|
||||
expect(unifiedGraph.plugins).toContainEqual(
|
||||
expect.objectContaining({ name: STATE_SCHEMA_INLINE_PLUGIN_NAME }),
|
||||
);
|
||||
expect(inlinePlugins).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("keeps core, plugin runtime, plugin-sdk, bundled root plugins, and bundled hooks in one dist graph", () => {
|
||||
const distGraph = requireUnifiedDistGraph();
|
||||
|
||||
|
||||
@@ -8,9 +8,9 @@ import { requireNodeSqlite } from "../infra/node-sqlite.js";
|
||||
import { createPrivateSqliteDirectory } from "../infra/sqlite-private-directory.js";
|
||||
import { runExec } from "../process/exec.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "../state/openclaw-agent-db.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "../state/openclaw-agent-schema.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_VERSION } from "../state/openclaw-state-db.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "../state/openclaw-state-schema.js";
|
||||
import { hashSnapshotArtifact, readSnapshotManifest } from "./manifest.js";
|
||||
import {
|
||||
SNAPSHOT_MANIFEST_FILENAME,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
|
||||
const BOARD_SCHEMA_START = "CREATE TABLE IF NOT EXISTS board_tabs (";
|
||||
const BOARD_SCHEMA_END = "CREATE TABLE IF NOT EXISTS heartbeat_outcomes (";
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
readExistingAgentSchemaMeta,
|
||||
} from "./openclaw-agent-db-schema-helpers.js";
|
||||
import { ensureOpenClawAgentDatabaseSchema } from "./openclaw-agent-db-schema.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db.js";
|
||||
|
||||
/** Require exact agent ownership without requiring the latest schema. */
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
} from "./openclaw-agent-board-schema.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "./openclaw-agent-db-contract.js";
|
||||
import { OpenClawAgentDatabaseMediaMigrationRequiredError } from "./openclaw-agent-db-migration-required.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
import {
|
||||
AGENT_V14_ADDITIVE_SCHEMA_SQL,
|
||||
AGENT_V14_CORE_SCHEMA_SQL,
|
||||
|
||||
@@ -48,7 +48,7 @@ import {
|
||||
} from "./openclaw-agent-db-session-provenance.js";
|
||||
import type { DB as OpenClawAgentKyselyDatabase } from "./openclaw-agent-db.generated.js";
|
||||
import { resolveOpenClawAgentSqlitePath } from "./openclaw-agent-db.paths.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
import { OPENCLAW_SQLITE_BUSY_TIMEOUT_MS } from "./openclaw-state-db.js";
|
||||
|
||||
type OpenClawAgentMetadataDatabase = Pick<OpenClawAgentKyselyDatabase, "schema_meta">;
|
||||
|
||||
@@ -1,639 +0,0 @@
|
||||
/**
|
||||
* This file was generated from the SQLite schema source.
|
||||
* Please do not edit it manually.
|
||||
*/
|
||||
|
||||
export const OPENCLAW_AGENT_SCHEMA_SQL = `-- Session storage doctrine: session_nodes.entry_json is the canonical logical-session
|
||||
-- record. Promoted session_nodes columns are query indexes projected only by the
|
||||
-- session entry writer; session_windows and their children own transcript generations.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS schema_meta (
|
||||
meta_key TEXT NOT NULL PRIMARY KEY,
|
||||
role TEXT NOT NULL,
|
||||
schema_version INTEGER NOT NULL,
|
||||
agent_id TEXT,
|
||||
app_version TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS state_leases (
|
||||
scope TEXT NOT NULL,
|
||||
lease_key TEXT NOT NULL,
|
||||
owner TEXT NOT NULL,
|
||||
expires_at INTEGER,
|
||||
heartbeat_at INTEGER,
|
||||
payload_json TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (scope, lease_key)
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_state_leases_expiry
|
||||
ON state_leases(expires_at, scope, lease_key)
|
||||
WHERE expires_at IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_state_leases_owner
|
||||
ON state_leases(owner, updated_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_nodes (
|
||||
session_key TEXT NOT NULL PRIMARY KEY,
|
||||
current_session_id TEXT NOT NULL,
|
||||
entry_json TEXT NOT NULL,
|
||||
entry_valid INTEGER NOT NULL DEFAULT 0 CHECK (entry_valid IN (-1, 0, 1)),
|
||||
updated_at INTEGER NOT NULL,
|
||||
status TEXT CHECK (status IS NULL OR status IN ('running', 'done', 'failed', 'killed', 'timeout')),
|
||||
created_at INTEGER,
|
||||
created_via TEXT CHECK (created_via IS NULL OR created_via IN ('operator', 'spawn', 'channel', 'cron', 'talk', 'run', 'plugin', 'internal')),
|
||||
created_actor_type TEXT CHECK (created_actor_type IS NULL OR created_actor_type IN ('human', 'agent', 'system')),
|
||||
created_actor_id TEXT,
|
||||
parent_session_key TEXT,
|
||||
spawned_by TEXT,
|
||||
fork_source_session_key TEXT,
|
||||
fork_source_session_id TEXT,
|
||||
fork_source_entry_id TEXT,
|
||||
label TEXT,
|
||||
display_name TEXT,
|
||||
category TEXT,
|
||||
icon TEXT,
|
||||
pinned_at INTEGER,
|
||||
archived_at INTEGER,
|
||||
last_read_at INTEGER,
|
||||
last_interaction_at INTEGER,
|
||||
last_activity_at INTEGER
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_updated_at
|
||||
ON session_nodes(updated_at DESC, session_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_last_interaction_at
|
||||
ON session_nodes(last_interaction_at DESC, session_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_parent_session_key
|
||||
ON session_nodes(parent_session_key, session_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_spawned_by
|
||||
ON session_nodes(spawned_by, session_key);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_status
|
||||
ON session_nodes(status, session_key)
|
||||
WHERE status IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_archived_at
|
||||
ON session_nodes(archived_at, session_key)
|
||||
WHERE archived_at IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_current_session_id
|
||||
ON session_nodes(current_session_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_nodes_entry_valid_pending
|
||||
ON session_nodes(session_key)
|
||||
WHERE entry_valid = 0;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_key_contract (
|
||||
id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
|
||||
main_key TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
INSERT OR IGNORE INTO session_key_contract (id, main_key, updated_at) VALUES (1, 'main', 0);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS session_nodes_entry_valid_after_insert
|
||||
AFTER INSERT ON session_nodes
|
||||
BEGIN
|
||||
UPDATE session_nodes SET entry_valid = 0 WHERE session_key = NEW.session_key;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS session_nodes_entry_valid_after_entry_update
|
||||
AFTER UPDATE OF entry_json ON session_nodes
|
||||
BEGIN
|
||||
UPDATE session_nodes SET entry_valid = 0 WHERE session_key = NEW.session_key;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS session_nodes_entry_valid_after_identity_update
|
||||
AFTER UPDATE OF current_session_id, updated_at ON session_nodes
|
||||
BEGIN
|
||||
UPDATE session_nodes SET entry_valid = 0 WHERE session_key = NEW.session_key;
|
||||
END;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_windows (
|
||||
session_id TEXT NOT NULL PRIMARY KEY,
|
||||
session_key TEXT NOT NULL,
|
||||
previous_session_id TEXT,
|
||||
reason TEXT CHECK (reason IS NULL OR reason IN ('initial', 'reset', 'rollover', 'fork', 'rewind', 'switch', 'recovery', 'compaction')),
|
||||
session_scope TEXT NOT NULL DEFAULT 'conversation' CHECK (session_scope IN ('conversation', 'shared-main', 'group', 'channel')),
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
transcript_updated_at INTEGER DEFAULT NULL,
|
||||
transcript_observed_at INTEGER DEFAULT NULL,
|
||||
session_entry_provenance INTEGER NOT NULL DEFAULT 0 CHECK (session_entry_provenance IN (0, 1)),
|
||||
acp_owned INTEGER NOT NULL DEFAULT 0 CHECK (acp_owned IN (0, 1)),
|
||||
plugin_owner_id TEXT,
|
||||
hook_external_content_source TEXT CHECK (hook_external_content_source IS NULL OR hook_external_content_source IN ('gmail', 'webhook')),
|
||||
started_at INTEGER,
|
||||
ended_at INTEGER,
|
||||
status TEXT CHECK (status IS NULL OR status IN ('running', 'done', 'failed', 'killed', 'timeout')),
|
||||
chat_type TEXT CHECK (chat_type IS NULL OR chat_type IN ('direct', 'group', 'channel')),
|
||||
channel TEXT,
|
||||
account_id TEXT,
|
||||
primary_conversation_id TEXT,
|
||||
model_provider TEXT,
|
||||
model TEXT,
|
||||
agent_harness_id TEXT,
|
||||
parent_session_key TEXT,
|
||||
spawned_by TEXT,
|
||||
display_name TEXT,
|
||||
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE,
|
||||
FOREIGN KEY (primary_conversation_id) REFERENCES conversations(conversation_id) ON DELETE SET NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_windows_updated_at
|
||||
ON session_windows(updated_at DESC, session_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_windows_created_at
|
||||
ON session_windows(created_at DESC, session_id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_windows_conversation
|
||||
ON session_windows(primary_conversation_id, updated_at DESC, session_id)
|
||||
WHERE primary_conversation_id IS NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
conversation_id TEXT NOT NULL PRIMARY KEY,
|
||||
channel TEXT NOT NULL,
|
||||
account_id TEXT NOT NULL,
|
||||
kind TEXT NOT NULL CHECK (kind IN ('direct', 'group', 'channel')),
|
||||
peer_id TEXT NOT NULL,
|
||||
delivery_target TEXT NOT NULL,
|
||||
parent_conversation_id TEXT,
|
||||
thread_id TEXT,
|
||||
native_channel_id TEXT,
|
||||
native_direct_user_id TEXT,
|
||||
label TEXT,
|
||||
metadata_json TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_conversations_lookup
|
||||
ON conversations(channel, account_id, kind, peer_id, thread_id);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_conversations_identity
|
||||
ON conversations(
|
||||
channel,
|
||||
account_id,
|
||||
kind,
|
||||
peer_id,
|
||||
IFNULL(parent_conversation_id, ''),
|
||||
IFNULL(thread_id, '')
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_conversations_updated
|
||||
ON conversations(updated_at DESC, conversation_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conversation_deliveries (
|
||||
operation_id TEXT NOT NULL PRIMARY KEY,
|
||||
operation_kind TEXT NOT NULL CHECK (operation_kind IN ('send', 'turn')),
|
||||
conversation_id TEXT NOT NULL,
|
||||
source_session_key TEXT,
|
||||
message_hash TEXT NOT NULL,
|
||||
status TEXT NOT NULL CHECK (status IN ('created', 'queued', 'sent', 'suppressed', 'rejected', 'unknown', 'replied')),
|
||||
prepared_message_id TEXT,
|
||||
platform_message_id TEXT,
|
||||
queue_id TEXT,
|
||||
rejection_error TEXT,
|
||||
reply_message_id TEXT,
|
||||
reply_to_id TEXT,
|
||||
reply_thread_id TEXT,
|
||||
reply_text TEXT,
|
||||
reply_timestamp INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
CHECK (
|
||||
(status = 'rejected' AND rejection_error IS NOT NULL) OR
|
||||
(status != 'rejected' AND rejection_error IS NULL)
|
||||
),
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(conversation_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_conversation_deliveries_reply
|
||||
ON conversation_deliveries(conversation_id, platform_message_id, prepared_message_id)
|
||||
WHERE status IN ('queued', 'sent', 'replied');
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_conversation_deliveries_updated
|
||||
ON conversation_deliveries(updated_at DESC, operation_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_conversations (
|
||||
session_id TEXT NOT NULL,
|
||||
conversation_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL DEFAULT 'primary' CHECK (role IN ('primary', 'participant', 'related')),
|
||||
first_seen_at INTEGER NOT NULL,
|
||||
last_seen_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_id, conversation_id, role),
|
||||
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(conversation_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_conversations_conversation
|
||||
ON session_conversations(conversation_id, last_seen_at DESC, session_id);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_session_conversations_primary
|
||||
ON session_conversations(session_id)
|
||||
WHERE role = 'primary';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_members (
|
||||
session_key TEXT NOT NULL,
|
||||
identity_id TEXT NOT NULL,
|
||||
added_by TEXT NOT NULL,
|
||||
added_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_key, identity_id),
|
||||
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_members_identity
|
||||
ON session_members(identity_id, session_key);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_suggestions (
|
||||
id TEXT PRIMARY KEY,
|
||||
session_key TEXT NOT NULL,
|
||||
author_id TEXT NOT NULL,
|
||||
author_label TEXT,
|
||||
text TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
state TEXT NOT NULL CHECK (state IN ('pending', 'accepted', 'dismissed')),
|
||||
dispatch_token TEXT,
|
||||
dispatch_started_at INTEGER,
|
||||
dispatch_resolution TEXT CHECK (dispatch_resolution IN ('send', 'queue', 'edit', 'dismiss')),
|
||||
CHECK (
|
||||
(dispatch_token IS NULL AND dispatch_started_at IS NULL AND dispatch_resolution IS NULL)
|
||||
OR (dispatch_token IS NOT NULL AND dispatch_started_at IS NOT NULL AND dispatch_resolution IS NOT NULL)
|
||||
),
|
||||
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_suggestions_session_state_created
|
||||
ON session_suggestions(session_key, state, created_at, id);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_session_suggestions_author_created
|
||||
ON session_suggestions(author_id, created_at, id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS board_tabs (
|
||||
session_key TEXT NOT NULL,
|
||||
tab_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
position INTEGER NOT NULL CHECK (position >= 0),
|
||||
chat_dock TEXT NOT NULL DEFAULT 'right' CHECK (chat_dock IN ('left', 'right', 'bottom', 'hidden')),
|
||||
created_by TEXT NOT NULL CHECK (created_by IN ('user', 'agent')),
|
||||
revision INTEGER NOT NULL CHECK (revision >= 0),
|
||||
PRIMARY KEY (session_key, tab_id),
|
||||
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS board_widgets (
|
||||
session_key TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
tab_id TEXT NOT NULL,
|
||||
title TEXT,
|
||||
content_kind TEXT NOT NULL CHECK (content_kind IN ('html', 'mcp-app', 'plugin')),
|
||||
html BLOB,
|
||||
descriptor_json TEXT,
|
||||
sha256 TEXT NOT NULL,
|
||||
view_generation TEXT,
|
||||
revision INTEGER NOT NULL CHECK (revision >= 1),
|
||||
size_w INTEGER NOT NULL CHECK (size_w BETWEEN 1 AND 12),
|
||||
size_h INTEGER NOT NULL CHECK (size_h BETWEEN 1 AND 20),
|
||||
position INTEGER NOT NULL CHECK (position >= 0),
|
||||
manifest TEXT NOT NULL DEFAULT '{}',
|
||||
grant_state TEXT NOT NULL DEFAULT 'none' CHECK (grant_state IN ('none', 'pending', 'granted', 'rejected')),
|
||||
granted_sha TEXT,
|
||||
created_by TEXT NOT NULL CHECK (created_by IN ('user', 'agent')),
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_key, name),
|
||||
FOREIGN KEY (session_key, tab_id) REFERENCES board_tabs(session_key, tab_id) ON DELETE CASCADE,
|
||||
CHECK (
|
||||
(content_kind = 'html' AND html IS NOT NULL AND descriptor_json IS NULL AND view_generation IS NOT NULL) OR
|
||||
(content_kind = 'mcp-app' AND html IS NULL AND descriptor_json IS NOT NULL AND view_generation IS NULL) OR
|
||||
(content_kind = 'plugin' AND html IS NULL AND descriptor_json IS NOT NULL AND view_generation IS NULL)
|
||||
)
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_board_widgets_tab_position
|
||||
ON board_widgets(session_key, tab_id, position);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS heartbeat_outcomes (
|
||||
session_key TEXT NOT NULL PRIMARY KEY,
|
||||
run_session_key TEXT NOT NULL,
|
||||
outcome TEXT NOT NULL CHECK (outcome IN ('progress', 'done', 'blocked', 'needs_attention')),
|
||||
summary TEXT NOT NULL,
|
||||
response_reason TEXT,
|
||||
priority TEXT CHECK (priority IS NULL OR priority IN ('low', 'normal', 'high')),
|
||||
next_check TEXT,
|
||||
task_names_json TEXT,
|
||||
wake_source TEXT,
|
||||
wake_reason TEXT,
|
||||
occurred_at INTEGER NOT NULL,
|
||||
context_run_id TEXT,
|
||||
context_claimed_at INTEGER,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (session_key) REFERENCES session_nodes(session_key) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transcript_events (
|
||||
session_id TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL,
|
||||
event_json TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_id, seq),
|
||||
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transcript_rewrite_watermarks (
|
||||
session_id TEXT NOT NULL PRIMARY KEY,
|
||||
generation TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS trajectory_runtime_events (
|
||||
session_id TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL,
|
||||
run_id TEXT,
|
||||
event_json TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_id, seq),
|
||||
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_trajectory_runtime_run
|
||||
ON trajectory_runtime_events(session_id, run_id, seq)
|
||||
WHERE run_id IS NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS acp_parent_stream_events (
|
||||
session_id TEXT NOT NULL,
|
||||
run_id TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL,
|
||||
event_json TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_id, run_id, seq),
|
||||
FOREIGN KEY (session_id) REFERENCES "session_windows"(session_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_acp_parent_stream_run
|
||||
ON acp_parent_stream_events(run_id, seq);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS transcript_event_identities (
|
||||
session_id TEXT NOT NULL,
|
||||
event_id TEXT NOT NULL,
|
||||
seq INTEGER NOT NULL,
|
||||
event_type TEXT,
|
||||
parent_id TEXT,
|
||||
message_idempotency_key TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (session_id, event_id),
|
||||
FOREIGN KEY (session_id, seq) REFERENCES transcript_events(session_id, seq) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_message_idempotency
|
||||
ON transcript_event_identities(session_id, message_idempotency_key)
|
||||
WHERE message_idempotency_key IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_transcript_event_parent
|
||||
ON transcript_event_identities(session_id, parent_id)
|
||||
WHERE parent_id IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_transcript_event_sequence
|
||||
ON transcript_event_identities(session_id, event_type, seq DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cache_entries (
|
||||
scope TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value_json TEXT,
|
||||
blob BLOB,
|
||||
expires_at INTEGER,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (scope, key)
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_cache_expiry
|
||||
ON cache_entries(scope, expires_at, key)
|
||||
WHERE expires_at IS NOT NULL;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_cache_updated
|
||||
ON cache_entries(scope, updated_at DESC, key);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_profile_store (
|
||||
store_key TEXT NOT NULL PRIMARY KEY,
|
||||
store_json TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_profile_state (
|
||||
state_key TEXT NOT NULL PRIMARY KEY,
|
||||
state_json TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_index_meta (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_index_sources (
|
||||
id INTEGER PRIMARY KEY,
|
||||
path TEXT NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT 'memory',
|
||||
hash TEXT NOT NULL,
|
||||
mtime REAL NOT NULL,
|
||||
size INTEGER NOT NULL,
|
||||
UNIQUE (path, source)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_index_chunks (
|
||||
id TEXT PRIMARY KEY,
|
||||
path TEXT NOT NULL,
|
||||
source TEXT NOT NULL DEFAULT 'memory',
|
||||
start_line INTEGER NOT NULL,
|
||||
end_line INTEGER NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
text TEXT NOT NULL,
|
||||
embedding TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_index_chunk_recall_metadata (
|
||||
chunk_id TEXT PRIMARY KEY,
|
||||
importance INTEGER CHECK (importance IS NULL OR importance BETWEEN 1 AND 10),
|
||||
triggers TEXT,
|
||||
project_key TEXT,
|
||||
FOREIGN KEY (chunk_id) REFERENCES memory_index_chunks(id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_index_chunk_provenance (
|
||||
chunk_id TEXT PRIMARY KEY,
|
||||
origin_class TEXT NOT NULL CHECK (origin_class IN ('owner', 'agent', 'untrusted', 'system')),
|
||||
session_kind TEXT NOT NULL CHECK (session_kind IN ('interactive', 'cron', 'heartbeat', 'subagent', 'unknown')),
|
||||
observed_at INTEGER NOT NULL,
|
||||
supersedes_key TEXT,
|
||||
FOREIGN KEY (chunk_id) REFERENCES memory_index_chunks(id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_embedding_cache (
|
||||
provider TEXT NOT NULL,
|
||||
model TEXT NOT NULL,
|
||||
provider_key TEXT NOT NULL,
|
||||
hash TEXT NOT NULL,
|
||||
embedding TEXT NOT NULL,
|
||||
dims INTEGER,
|
||||
updated_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (provider, model, provider_key, hash)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS memory_index_state (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
revision INTEGER NOT NULL
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS standing_intents (
|
||||
intent_key INTEGER PRIMARY KEY,
|
||||
id TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL,
|
||||
trigger_keywords TEXT NOT NULL,
|
||||
trigger_embedding TEXT,
|
||||
channel_scope TEXT,
|
||||
sender_scope TEXT,
|
||||
creator_sender TEXT CHECK (creator_sender IS NULL OR length(trim(creator_sender)) > 0),
|
||||
status TEXT NOT NULL CHECK (status IN ('pending', 'armed', 'fired', 'done', 'cancelled', 'expired')),
|
||||
expires_at INTEGER NOT NULL,
|
||||
max_fires INTEGER NOT NULL CHECK (max_fires > 0),
|
||||
fire_count INTEGER NOT NULL DEFAULT 0 CHECK (fire_count >= 0),
|
||||
cooldown_seconds INTEGER NOT NULL DEFAULT 86400 CHECK (cooldown_seconds >= 0),
|
||||
last_fired_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
source_session_id TEXT
|
||||
) STRICT;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_standing_intents_lifecycle
|
||||
ON standing_intents(status, expires_at, last_fired_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_standing_intents_scope
|
||||
ON standing_intents(status, channel_scope, sender_scope);
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS standing_intents_fts USING fts5(
|
||||
trigger_keywords,
|
||||
content = 'standing_intents',
|
||||
content_rowid = 'intent_key',
|
||||
tokenize = 'unicode61 remove_diacritics 2'
|
||||
);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS standing_intents_fts_after_insert
|
||||
AFTER INSERT ON standing_intents
|
||||
BEGIN
|
||||
INSERT INTO standing_intents_fts(rowid, trigger_keywords)
|
||||
VALUES (new.intent_key, new.trigger_keywords);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS standing_intents_fts_after_delete
|
||||
AFTER DELETE ON standing_intents
|
||||
BEGIN
|
||||
INSERT INTO standing_intents_fts(standing_intents_fts, rowid, trigger_keywords)
|
||||
VALUES ('delete', old.intent_key, old.trigger_keywords);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS standing_intents_fts_after_update
|
||||
AFTER UPDATE OF trigger_keywords ON standing_intents
|
||||
BEGIN
|
||||
INSERT INTO standing_intents_fts(standing_intents_fts, rowid, trigger_keywords)
|
||||
VALUES ('delete', old.intent_key, old.trigger_keywords);
|
||||
INSERT INTO standing_intents_fts(rowid, trigger_keywords)
|
||||
VALUES (new.intent_key, new.trigger_keywords);
|
||||
END;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_transcript_index_state (
|
||||
session_id TEXT NOT NULL PRIMARY KEY,
|
||||
indexed_seq INTEGER NOT NULL,
|
||||
leaf_event_id TEXT,
|
||||
needs_rebuild INTEGER NOT NULL DEFAULT 0,
|
||||
active_event_count INTEGER NOT NULL DEFAULT 0,
|
||||
active_message_count INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (session_id) REFERENCES session_windows(session_id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session_transcript_active_events (
|
||||
session_id TEXT NOT NULL,
|
||||
active_position INTEGER NOT NULL CHECK (active_position >= 0),
|
||||
event_seq INTEGER NOT NULL,
|
||||
message_position INTEGER CHECK (message_position IS NULL OR message_position >= 0),
|
||||
PRIMARY KEY (session_id, active_position),
|
||||
FOREIGN KEY (session_id, event_seq) REFERENCES transcript_events(session_id, seq) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_active_event_seq
|
||||
ON session_transcript_active_events(session_id, event_seq);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_agent_transcript_active_messages
|
||||
ON session_transcript_active_events(session_id, message_position)
|
||||
WHERE message_position IS NOT NULL;
|
||||
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS session_transcript_fts USING fts5(
|
||||
text,
|
||||
session_id UNINDEXED,
|
||||
message_id UNINDEXED,
|
||||
role UNINDEXED,
|
||||
timestamp UNINDEXED,
|
||||
tokenize = 'unicode61 remove_diacritics 2'
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO memory_index_state (id, revision) VALUES (1, 0);
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_insert
|
||||
AFTER INSERT ON memory_index_sources
|
||||
BEGIN
|
||||
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_update
|
||||
AFTER UPDATE ON memory_index_sources
|
||||
BEGIN
|
||||
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS memory_index_sources_revision_after_delete
|
||||
AFTER DELETE ON memory_index_sources
|
||||
BEGIN
|
||||
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_insert
|
||||
AFTER INSERT ON memory_index_chunks
|
||||
BEGIN
|
||||
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_update
|
||||
AFTER UPDATE ON memory_index_chunks
|
||||
BEGIN
|
||||
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS memory_index_chunks_revision_after_delete
|
||||
AFTER DELETE ON memory_index_chunks
|
||||
BEGIN
|
||||
UPDATE memory_index_state SET revision = revision + 1 WHERE id = 1;
|
||||
END;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_embedding_cache_updated_at
|
||||
ON memory_embedding_cache(updated_at);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_index_sources_source
|
||||
ON memory_index_sources(source);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_path_source
|
||||
ON memory_index_chunks(path, source);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_path
|
||||
ON memory_index_chunks(path);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_memory_index_chunks_source
|
||||
ON memory_index_chunks(source);\n`;
|
||||
7
src/state/openclaw-agent-schema.ts
Normal file
7
src/state/openclaw-agent-schema.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Use the native builtin so unrelated node:fs mocks cannot replace this source input.
|
||||
// Production builds replace this module so packaged database opens need no asset file.
|
||||
export const OPENCLAW_AGENT_SCHEMA_SQL = process
|
||||
.getBuiltinModule("node:fs")
|
||||
.readFileSync(fileURLToPath(new URL("./openclaw-agent-schema.sql", import.meta.url)), "utf8");
|
||||
@@ -10,7 +10,7 @@ import { MEMORY_INDEX_CHUNK_RECALL_METADATA_TABLE } from "../../packages/memory-
|
||||
import { openNodeSqliteDatabase } from "../infra/node-sqlite.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_VERSION } from "./openclaw-agent-db-contract.js";
|
||||
import { ensureOpenClawAgentDatabaseSchema } from "./openclaw-agent-db-schema.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
import { ensureOpenClawAgentStandingIntentsSchema } from "./openclaw-agent-standing-intents-schema.js";
|
||||
|
||||
const STANDING_SCHEMA_START = "CREATE TABLE IF NOT EXISTS standing_intents (";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { runSqliteImmediateTransactionSync } from "../infra/sqlite-transaction.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
|
||||
export const STANDING_INTENTS_TABLE = "standing_intents";
|
||||
export const STANDING_INTENTS_FTS_TABLE = "standing_intents_fts";
|
||||
|
||||
@@ -5,12 +5,12 @@ import {
|
||||
assertOpenClawAgentDatabaseForMaintenance,
|
||||
OPENCLAW_AGENT_SCHEMA_VERSION,
|
||||
} from "./openclaw-agent-db.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.generated.js";
|
||||
import { OPENCLAW_AGENT_SCHEMA_SQL } from "./openclaw-agent-schema.js";
|
||||
import {
|
||||
assertOpenClawStateDatabaseForMaintenance,
|
||||
OPENCLAW_STATE_SCHEMA_VERSION,
|
||||
} from "./openclaw-state-db.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
|
||||
|
||||
describe("OpenClaw database maintenance schema validation", () => {
|
||||
it("accepts the current global and agent schemas", () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
type OpenClawStateDatabaseOptions,
|
||||
} from "./openclaw-state-db-contract.js";
|
||||
import { resolveOpenClawStateSqlitePath } from "./openclaw-state-db.paths.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
|
||||
|
||||
const OPENCLAW_STATE_MAINTENANCE_SCHEMA_COMPATIBILITY = {
|
||||
allowedMissingTables: LAZY_ADDITIVE_STATE_TABLES,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
assertCanonicalOperatorApprovalKinds,
|
||||
repairOperatorApprovalSchema,
|
||||
} from "./openclaw-state-db-operator-approval-migration.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
|
||||
|
||||
function canonicalOperatorApprovalCreateSql(): string {
|
||||
const marker = "CREATE TABLE IF NOT EXISTS operator_approvals (";
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import type { DatabaseSync } from "node:sqlite";
|
||||
import { runSqliteImmediateTransactionSync } from "../infra/sqlite-transaction.js";
|
||||
import { tableExists } from "./openclaw-state-db-schema-helpers.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
|
||||
|
||||
const COLUMNS = [
|
||||
"approval_id",
|
||||
|
||||
@@ -74,7 +74,7 @@ import {
|
||||
} from "./openclaw-state-db-schema-repair.js";
|
||||
import * as sessionWatchMigration from "./openclaw-state-db-session-watch-migration.js";
|
||||
import type { DB as OpenClawStateKyselyDatabase } from "./openclaw-state-db.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.generated.js";
|
||||
import { OPENCLAW_STATE_SCHEMA_SQL } from "./openclaw-state-schema.js";
|
||||
|
||||
export {
|
||||
OPENCLAW_DATABASE_SCHEMA_DOCS_URL,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
7
src/state/openclaw-state-schema.ts
Normal file
7
src/state/openclaw-state-schema.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
// Use the native builtin so unrelated node:fs mocks cannot replace this source input.
|
||||
// Production builds replace this module so packaged database opens need no asset file.
|
||||
export const OPENCLAW_STATE_SCHEMA_SQL = process
|
||||
.getBuiltinModule("node:fs")
|
||||
.readFileSync(fileURLToPath(new URL("./openclaw-state-schema.sql", import.meta.url)), "utf8");
|
||||
@@ -387,6 +387,14 @@ describe("resolveBuildAllSteps", () => {
|
||||
);
|
||||
}
|
||||
expect(unified.cache?.env).toContain("OPENCLAW_BUILD_PRIVATE_QA");
|
||||
expect(unified.cache?.inputs).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
path: "src",
|
||||
extensions: expect.arrayContaining([".sql"]),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(resolveBuildAllStepOnCacheHit(getBuildAllStep("copy-export-html-templates"))).toBeNull();
|
||||
});
|
||||
|
||||
|
||||
@@ -46,6 +46,43 @@ const env = {
|
||||
const OUTPUT_SOURCE_MAPS = process.env.OUTPUT_SOURCE_MAPS === "1";
|
||||
const RUN_NODE_SKIP_DTS_BUILD = process.env.OPENCLAW_RUN_NODE_SKIP_DTS_BUILD === "1";
|
||||
const TSDOWN_DECLARATIONS = !RUN_NODE_SKIP_DTS_BUILD;
|
||||
export const STATE_SCHEMA_INLINE_PLUGIN_NAME = "openclaw:inline-state-schemas";
|
||||
|
||||
const STATE_SCHEMA_MODULES = [
|
||||
{
|
||||
modulePath: "src/state/openclaw-state-schema.ts",
|
||||
schemaPath: "src/state/openclaw-state-schema.sql",
|
||||
exportName: "OPENCLAW_STATE_SCHEMA_SQL",
|
||||
},
|
||||
{
|
||||
modulePath: "src/state/openclaw-agent-schema.ts",
|
||||
schemaPath: "src/state/openclaw-agent-schema.sql",
|
||||
exportName: "OPENCLAW_AGENT_SCHEMA_SQL",
|
||||
},
|
||||
] as const;
|
||||
|
||||
/** Inline canonical schema bytes so packaged database opens need no SQL asset. */
|
||||
export function createStateSchemaInlinePlugin(rootDir: string = process.cwd()) {
|
||||
const schemasByModulePath = new Map(
|
||||
STATE_SCHEMA_MODULES.map((schema) => [path.resolve(rootDir, schema.modulePath), schema]),
|
||||
);
|
||||
|
||||
return {
|
||||
name: STATE_SCHEMA_INLINE_PLUGIN_NAME,
|
||||
load(this: { addWatchFile(id: string): void }, id: string) {
|
||||
const schema = schemasByModulePath.get(path.resolve(id));
|
||||
if (!schema) {
|
||||
return null;
|
||||
}
|
||||
const schemaPath = path.resolve(rootDir, schema.schemaPath);
|
||||
this.addWatchFile(schemaPath);
|
||||
return {
|
||||
code: `export const ${schema.exportName} = ${JSON.stringify(fs.readFileSync(schemaPath, "utf8"))};\n`,
|
||||
moduleType: "js" as const,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const SUPPRESSED_EVAL_WARNING_PATHS = [
|
||||
"@protobufjs/inquire/index.js",
|
||||
@@ -680,6 +717,7 @@ const configs = [
|
||||
// and bundled hooks in one graph so runtime singletons are emitted once.
|
||||
entry: unifiedDistEntries,
|
||||
deps: unifiedDeps,
|
||||
plugins: [createStateSchemaInlinePlugin()],
|
||||
},
|
||||
false,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user