Update: 将子项目从 submodule 转为完整内容
- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用 - 添加所有子项目的完整源代码 - 保留原始 .git 为 .git.bak 备份
This commit is contained in:
@@ -0,0 +1,975 @@
|
||||
-- Migration 001: Baseline schema for nomifun-backend.
|
||||
--
|
||||
-- This file is the 2026-06-13 "primary-key redesign" baseline (see
|
||||
-- docs/superpowers/specs/2026-06-13-primary-key-redesign-design.md). It
|
||||
-- supersedes and squashes the former 001(seq baseline)/002(attachments)/
|
||||
-- 003(channel_per_pet). The system has never shipped, so the baseline only
|
||||
-- serves BRAND-NEW databases: final-state schema + seed data, NO backfill /
|
||||
-- normalization. Any pre-baseline database is renamed `*.pre-baseline.bak`
|
||||
-- and recreated by database.rs during the pre-launch window.
|
||||
--
|
||||
-- ID model (single id per entity, NO seq dual-track; display == primary key):
|
||||
-- * Cross-device entities (id leaves this machine via remote protocol /
|
||||
-- ACP transcript / external IM / cross-device coordination key) use the
|
||||
-- string global id `{prefix}_{uuidv7}` minted by generate_prefixed_id:
|
||||
-- messages(msg_) cron_jobs(cron_) agent_metadata(agent_builtin_/agent_)
|
||||
-- providers(prov_) assistants assistant_plugins assistant_users(achu_)
|
||||
-- assistant_sessions knowledge_bases(kb_) teams(team_) team_agents(slot_)
|
||||
-- team_tasks(task_) attachments(att_) pets(pet_, filesystem) device_id.
|
||||
-- * Local-only entities use INTEGER PRIMARY KEY AUTOINCREMENT (monotonic,
|
||||
-- ordered, never reused): conversations, requirements, terminal_sessions,
|
||||
-- conversation_artifacts, mcp_servers, remote_agents, webhooks, mailbox,
|
||||
-- knowledge_bindings(binding_id). The user-facing conversation/requirement/
|
||||
-- terminal ids render as `#N`; see
|
||||
-- docs/superpowers/specs/2026-06-14-numeric-session-requirement-id-design.md.
|
||||
-- * Natural keys unchanged: users.id, client_preferences.key,
|
||||
-- oauth_tokens.server_url, requirement_tags.tag, tag_settings.tag,
|
||||
-- assistant_pairing_codes.code, system_settings(id=1).
|
||||
--
|
||||
-- INVARIANT: a cross-device INTEGER id (conversation/requirement/terminal)
|
||||
-- enters a remote payload only as the client-provided correlation tag (safe;
|
||||
-- the real remote routing key is the peer-minted sessionKey). FK column type
|
||||
-- == referenced table PK type. agent-address columns (teams.lead_agent_id,
|
||||
-- mailbox.to_agent_id/from_agent_id, team_tasks.owner) are NOT foreign keys:
|
||||
-- they hold a slot_id OR the 'user'/'lead' sentinels, so a FK would reject
|
||||
-- valid rows.
|
||||
--
|
||||
-- Builtin agent seed ids use the stable slug scheme `agent_builtin_{backend}`.
|
||||
-- Runtime lookup goes through find_builtin_by_backend, never id literals.
|
||||
--
|
||||
-- Requires PRAGMA foreign_keys = ON on every connection for the ON DELETE
|
||||
-- cascades below to fire (set in database.rs connect options).
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Core tables
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
email TEXT UNIQUE,
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_path TEXT,
|
||||
jwt_secret TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
last_login INTEGER
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
|
||||
-- Singleton settings row. Deliberately NOT seeded: the settings repository
|
||||
-- treats a missing row as "defaults" (get_settings returns Option) and lazily
|
||||
-- creates it on first upsert.
|
||||
CREATE TABLE IF NOT EXISTS system_settings (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
language TEXT NOT NULL DEFAULT 'en-US',
|
||||
notification_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
cron_notification_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
command_queue_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
save_upload_to_workspace INTEGER NOT NULL DEFAULT 0,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS client_preferences (
|
||||
key TEXT PRIMARY KEY NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Cross-device: provider_id is embedded as a value-object snapshot in
|
||||
-- conversation.model / pet config / idmm.sidecar and travels with them.
|
||||
CREATE TABLE IF NOT EXISTS providers (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- prov_{uuidv7}
|
||||
platform TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
base_url TEXT NOT NULL,
|
||||
api_key_encrypted TEXT NOT NULL,
|
||||
models TEXT NOT NULL DEFAULT '[]',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
capabilities TEXT NOT NULL DEFAULT '[]',
|
||||
context_limit INTEGER,
|
||||
model_protocols TEXT,
|
||||
model_enabled TEXT,
|
||||
model_health TEXT,
|
||||
bedrock_config TEXT,
|
||||
is_full_url INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_providers_platform ON providers(platform);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Conversations & Messages
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- cron_job_id: the cron job that created this conversation (was the JSON
|
||||
-- key extra.cronJobId + an expression index; now a real nullable FK column).
|
||||
-- This forms a ring with cron_jobs.conversation_id: writers must INSERT the
|
||||
-- cron row with conversation_id=NULL first, then the conversation, then
|
||||
-- backfill both (see spec §9.A).
|
||||
CREATE TABLE IF NOT EXISTS conversations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
extra TEXT NOT NULL DEFAULT '{}',
|
||||
model TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK(status IN ('pending', 'running', 'finished')),
|
||||
source TEXT,
|
||||
channel_chat_id TEXT,
|
||||
pinned INTEGER NOT NULL DEFAULT 0,
|
||||
pinned_at INTEGER,
|
||||
cron_job_id TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (cron_job_id) REFERENCES cron_jobs(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_user_id ON conversations(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_updated_at ON conversations(updated_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_type ON conversations(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_user_updated ON conversations(user_id, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_source ON conversations(source);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_source_updated ON conversations(source, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_source_chat ON conversations(source, channel_chat_id, updated_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversations_cron_job_id ON conversations(cron_job_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- msg_{uuidv7}
|
||||
conversation_id INTEGER NOT NULL,
|
||||
msg_id TEXT,
|
||||
type TEXT NOT NULL,
|
||||
content TEXT NOT NULL DEFAULT '{}',
|
||||
position TEXT CHECK(position IN ('left', 'right', 'center', 'pop')),
|
||||
status TEXT CHECK(status IN ('finish', 'pending', 'error', 'work')),
|
||||
hidden INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id ON messages(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_created_at ON messages(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_type ON messages(type);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_msg_id ON messages(msg_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_conv_created ON messages(conversation_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_conv_created_desc ON messages(conversation_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_type_created ON messages(type, created_at DESC);
|
||||
|
||||
-- Local-only INTEGER surrogate id. Idempotency moved off the old composite
|
||||
-- text id onto a partial unique index: skill_suggest is unique per
|
||||
-- (conversation, cron_job); cron_trigger has NO unique (one row per fire).
|
||||
CREATE TABLE IF NOT EXISTS conversation_artifacts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
conversation_id INTEGER NOT NULL,
|
||||
cron_job_id TEXT,
|
||||
kind TEXT NOT NULL
|
||||
CHECK(kind IN ('cron_trigger', 'skill_suggest')),
|
||||
status TEXT NOT NULL DEFAULT 'active'
|
||||
CHECK(status IN ('active', 'pending', 'dismissed', 'saved')),
|
||||
payload TEXT NOT NULL DEFAULT '{}',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (cron_job_id) REFERENCES cron_jobs(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_artifacts_conversation_id ON conversation_artifacts(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_artifacts_created_at ON conversation_artifacts(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_artifacts_conversation_created ON conversation_artifacts(conversation_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_artifacts_cron_job ON conversation_artifacts(cron_job_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_artifacts_kind_status ON conversation_artifacts(kind, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_conversation_artifacts_skill_suggest
|
||||
ON conversation_artifacts(conversation_id, cron_job_id) WHERE kind = 'skill_suggest';
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- ACP Sessions
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS acp_session (
|
||||
conversation_id INTEGER PRIMARY KEY,
|
||||
agent_backend TEXT NOT NULL,
|
||||
agent_source TEXT NOT NULL,
|
||||
-- Nullable: an ACP conversation can be created before a concrete catalog
|
||||
-- agent is picked (legacy clients post only `backend`, or nothing at all).
|
||||
-- A non-NULL value is RESTRICT-bound to agent_metadata; NULL means
|
||||
-- "no agent chosen yet" and is exempt from FK enforcement (SQLite does not
|
||||
-- check FKs on NULL child columns). Replaces the old empty-string sentinel,
|
||||
-- which the new RESTRICT FK would reject.
|
||||
agent_id TEXT,
|
||||
session_id TEXT,
|
||||
session_status TEXT NOT NULL DEFAULT 'idle',
|
||||
session_config TEXT NOT NULL DEFAULT '{}',
|
||||
last_active_at INTEGER,
|
||||
suspended_at INTEGER,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (agent_id) REFERENCES agent_metadata(id) ON DELETE RESTRICT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_acp_session_status ON acp_session(session_status);
|
||||
CREATE INDEX IF NOT EXISTS idx_acp_session_suspended ON acp_session(session_status, suspended_at) WHERE session_status = 'suspended';
|
||||
CREATE INDEX IF NOT EXISTS idx_acp_session_agent_id ON acp_session(agent_id);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Agent Metadata
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS agent_metadata (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
icon TEXT,
|
||||
name TEXT NOT NULL,
|
||||
name_i18n TEXT,
|
||||
description TEXT,
|
||||
description_i18n TEXT,
|
||||
backend TEXT,
|
||||
agent_type TEXT NOT NULL,
|
||||
agent_source TEXT NOT NULL,
|
||||
agent_source_info TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
command TEXT,
|
||||
args TEXT,
|
||||
env TEXT,
|
||||
native_skills_dirs TEXT,
|
||||
behavior_policy TEXT,
|
||||
yolo_id TEXT,
|
||||
agent_capabilities TEXT,
|
||||
auth_methods TEXT,
|
||||
config_options TEXT,
|
||||
available_modes TEXT,
|
||||
available_models TEXT,
|
||||
available_commands TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 1000,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_metadata_backend ON agent_metadata(backend);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_metadata_agent_type ON agent_metadata(agent_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_agent_metadata_sort_order ON agent_metadata(sort_order);
|
||||
|
||||
-- Seed agent_metadata with builtin agents.
|
||||
--
|
||||
-- Values are the post-001/003/004/010/012 final state of the legacy migration
|
||||
-- chain: bun package pins from 004, ACP handshake captures (agent_capabilities
|
||||
-- / auth_methods) from 003, command/binary_name fixes for Qoder/Vibe/Kiro from
|
||||
-- 003, and the internal agent display name "Nomi" from 012. Agents without a
|
||||
-- 003 handshake capture (Claude, Codex, Gemini, OpenCode, Cursor, Hermes,
|
||||
-- Snow, and the non-ACP rows) keep NULL capabilities until first spawn.
|
||||
INSERT OR IGNORE INTO agent_metadata
|
||||
(id, icon, name, backend, agent_type, agent_source, agent_source_info,
|
||||
enabled, command, args, env, native_skills_dirs, behavior_policy, yolo_id,
|
||||
agent_capabilities, auth_methods,
|
||||
sort_order, created_at, updated_at)
|
||||
VALUES
|
||||
-- ACP builtin agents
|
||||
('agent_builtin_claude', '/api/assets/logos/ai-major/claude.svg', 'Claude Code',
|
||||
'claude', 'acp', 'builtin', '{"binary_name":"claude","bridge_binary":"bun"}',
|
||||
1, 'bun', '["x","--bun","@agentclientprotocol/claude-agent-acp@0.33.1"]', '[]',
|
||||
'[".claude/skills"]',
|
||||
'{"supports_side_question":true,"self_identity_sticky":true,"session_load_via_meta_field":true,"supports_team":true}',
|
||||
'bypassPermissions',
|
||||
NULL, NULL,
|
||||
3100,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_codex', '/api/assets/logos/tools/coding/codex.svg', 'Codex CLI',
|
||||
'codex', 'acp', 'builtin', '{"binary_name":"codex","bridge_binary":"bun"}',
|
||||
1, 'bun', '["x","--bun","@zed-industries/codex-acp@0.14.0"]', '[]',
|
||||
'[".codex/skills"]',
|
||||
'{"supports_side_question":false,"supports_team":true}',
|
||||
'full-access',
|
||||
NULL, NULL,
|
||||
3110,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_gemini', '/api/assets/logos/ai-major/gemini.svg', 'Gemini CLI',
|
||||
'gemini', 'acp', 'builtin', '{"binary_name":"gemini"}',
|
||||
1, 'gemini', '["--experimental-acp"]', '[]',
|
||||
'[".gemini/skills"]',
|
||||
'{"supports_side_question":false,"supports_team":true}',
|
||||
'yolo',
|
||||
NULL, NULL,
|
||||
3120,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_qwen', '/api/assets/logos/ai-china/qwen.svg', 'Qwen',
|
||||
'qwen', 'acp', 'builtin', '{"binary_name":"qwen"}',
|
||||
1, 'qwen', '["--acp"]', '[]',
|
||||
'[".qwen/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"prompt_capabilities":{"image":true,"audio":true,"embedded_context":true},"session_capabilities":{"list":{},"resume":{}},"mcp_capabilities":{"sse":true,"http":true}}',
|
||||
'[{"id":"openai","name":"Use OpenAI API key","description":"Requires setting the `OPENAI_API_KEY` environment variable","_meta":{"type":"terminal","args":["--auth-type=openai"]}},{"id":"qwen-oauth","name":"Qwen OAuth","description":"Qwen OAuth (free tier discontinued 2026-04-15)","_meta":{"type":"terminal","args":["--auth-type=qwen-oauth"]}}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_codebuddy', '/api/assets/logos/tools/coding/codebuddy.svg', 'CodeBuddy',
|
||||
'codebuddy', 'acp', 'builtin', '{"binary_name":"codebuddy","bridge_binary":"bun"}',
|
||||
1, 'bun', '["x","--bun","@tencent-ai/codebuddy-code@2.97.0","--acp"]', '[]',
|
||||
'[".codebuddy/skills"]',
|
||||
'{"supports_side_question":false,"supports_team":true}',
|
||||
'bypassPermissions',
|
||||
'{"prompt_capabilities":{"image":true,"embedded_context":true},"mcp_capabilities":{"http":true,"sse":true},"load_session":true,"delegate_tools_support":true}',
|
||||
'[{"id":"iOA","name":"Login with iOA","description":null},{"id":"external","name":"Login with Google/Github","description":null},{"id":"internal","name":"Login with WeChat","description":null},{"id":"selfhosted","name":"Login with Enterprise Domain","description":null}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_droid', '/api/assets/logos/brand/droid.svg', 'Droid',
|
||||
'droid', 'acp', 'builtin', '{"binary_name":"droid"}',
|
||||
1, 'droid', '["exec","--output-format","acp"]', '[]',
|
||||
'[".factory/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"session_capabilities":{"list":{},"resume":{}},"prompt_capabilities":{"image":true,"embedded_context":true},"_meta":{"terminal_output":true,"terminal-auth":true}}',
|
||||
'[{"id":"device-pairing","name":"Login","description":"Authenticate with Factory using a device pairing code in your browser."},{"id":"factory-api-key","name":"Factory API Key","description":"Authenticate using a Factory API key set in the FACTORY_API_KEY environment variable."}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_goose', '/api/assets/logos/tools/goose.svg', 'Goose',
|
||||
'goose', 'acp', 'builtin', '{"binary_name":"goose"}',
|
||||
1, 'goose', '["acp"]', '[]',
|
||||
'[".goose/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"prompt_capabilities":{"image":true,"audio":false,"embedded_context":true},"mcp_capabilities":{"http":true,"sse":false},"session_capabilities":{"list":{},"close":{}},"auth":{}}',
|
||||
'[{"id":"goose-provider","name":"Configure Provider","description":"Run `goose configure` to set up your AI provider and API key"}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_auggie', '/api/assets/logos/brand/auggie.svg', 'Auggie',
|
||||
'auggie', 'acp', 'builtin', '{"binary_name":"auggie"}',
|
||||
1, 'auggie', '["--acp"]', '[]',
|
||||
NULL,
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"prompt_capabilities":{"image":true},"session_capabilities":{"list":{}}}',
|
||||
'[]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_kimi', '/api/assets/logos/ai-china/kimi.svg', 'Kimi',
|
||||
'kimi', 'acp', 'builtin', '{"binary_name":"kimi"}',
|
||||
1, 'kimi', '["acp"]', '[]',
|
||||
'[".kimi/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"mcp_capabilities":{"http":true,"sse":false},"prompt_capabilities":{"audio":false,"embedded_context":true,"image":true},"session_capabilities":{"list":{},"resume":{}}}',
|
||||
'[{"_meta":{"terminal-auth":{"command":"kimi","args":["login"],"label":"Kimi Code Login","env":{},"type":"terminal"}},"description":"Run `kimi login` command in the terminal, then follow the instructions to finish login.","id":"login","name":"Login with Kimi account"}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_opencode', '/api/assets/logos/tools/coding/opencode-light.svg', 'OpenCode',
|
||||
'opencode', 'acp', 'builtin', '{"binary_name":"opencode"}',
|
||||
1, 'opencode', '["acp"]', '[]',
|
||||
'[".opencode/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'build',
|
||||
NULL, NULL,
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_copilot', '/api/assets/logos/tools/github.svg', 'Copilot',
|
||||
'copilot', 'acp', 'builtin', '{"binary_name":"copilot"}',
|
||||
1, 'copilot', '["--acp","--stdio"]', '[]',
|
||||
NULL,
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"mcp_capabilities":{"http":true,"sse":true},"prompt_capabilities":{"image":true,"audio":false,"embedded_context":true},"session_capabilities":{"list":{}}}',
|
||||
'[{"id":"copilot-login","name":"Log in with Copilot CLI","description":"Run `copilot login` in the terminal","_meta":{"terminal-auth":{"command":"copilot","args":["login"],"label":"Copilot Login"}}}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_qoder', '/api/assets/logos/tools/coding/qoder.png', 'Qoder',
|
||||
'qoder', 'acp', 'builtin', '{"binary_name":"qodercli"}',
|
||||
1, 'qodercli', '["--acp"]', '[]',
|
||||
NULL,
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"session_capabilities":{"list":{}},"prompt_capabilities":{"image":true,"audio":true,"embedded_context":true},"mcp_capabilities":{"http":true,"sse":true}}',
|
||||
'[{"id":"qodercli-login","name":"Use qodercli login","description":"Use your existing qodercli login for this agent. If needed, sign in from qodercli first."},{"type":"env_var","id":"qoder-personal-access-token","name":"Use QODER_PERSONAL_ACCESS_TOKEN","description":"Requires `QODER_PERSONAL_ACCESS_TOKEN` in the agent environment.","vars":[{"name":"QODER_PERSONAL_ACCESS_TOKEN"}]}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_vibe', '/api/assets/logos/ai-major/mistral.svg', 'Vibe',
|
||||
'vibe', 'acp', 'builtin', '{"binary_name":"vibe-acp"}',
|
||||
1, 'vibe-acp', '[]', '[]',
|
||||
'[".vibe/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"prompt_capabilities":{"audio":false,"embedded_context":true,"image":false},"session_capabilities":{"close":{},"fork":{},"list":{}}}',
|
||||
'[]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_cursor', '/api/assets/logos/tools/coding/cursor.png', 'Cursor',
|
||||
'cursor', 'acp', 'builtin', '{"binary_name":"agent"}',
|
||||
1, 'agent', '["acp"]', '[]',
|
||||
'[".cursor/skills"]',
|
||||
'{"supports_side_question":false}',
|
||||
'agent',
|
||||
NULL, NULL,
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_kiro', NULL, 'Kiro',
|
||||
'kiro', 'acp', 'builtin', '{"binary_name":"kiro-cli"}',
|
||||
1, 'kiro-cli', '["acp"]', '[]',
|
||||
NULL,
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
'{"load_session":true,"prompt_capabilities":{"image":true,"audio":false,"embedded_context":false},"mcp_capabilities":{"http":true,"sse":false},"session_capabilities":{}}',
|
||||
'[{"id":"kiro-login","name":"Kiro Login","description":"Run ''kiro-cli login'' in terminal to authenticate. See https://kiro.dev/docs/cli/authentication/"}]',
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_hermes', '/api/assets/logos/brand/hermes.svg', 'Hermes',
|
||||
'hermes', 'acp', 'builtin', '{"binary_name":"hermes"}',
|
||||
1, 'hermes', '["acp"]', '[]',
|
||||
NULL,
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
NULL, NULL,
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_snow', '/api/assets/logos/tools/coding/snow.png', 'Snow',
|
||||
'snow', 'acp', 'builtin', '{"binary_name":"snow"}',
|
||||
1, 'snow', '["--acp"]', '[]',
|
||||
NULL,
|
||||
'{"supports_side_question":false}',
|
||||
'yolo',
|
||||
NULL, NULL,
|
||||
3130,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
-- Non-ACP builtins
|
||||
('agent_builtin_nanobot', '/api/assets/logos/tools/nanobot.svg', 'Nanobot',
|
||||
NULL, 'nanobot', 'builtin', '{"binary_name":"nanobot"}',
|
||||
1, 'nanobot', '["--experimental-acp"]', '[]',
|
||||
NULL,
|
||||
'{}',
|
||||
'yolo',
|
||||
NULL, NULL,
|
||||
3990,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
('agent_builtin_openclaw', '/api/assets/logos/tools/openclaw.svg', 'OpenClaw',
|
||||
NULL, 'openclaw-gateway', 'builtin', '{"binary_name":"openclaw"}',
|
||||
1, 'openclaw', '[]', '[]',
|
||||
NULL,
|
||||
'{}',
|
||||
'yolo',
|
||||
NULL, NULL,
|
||||
3900,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000),
|
||||
|
||||
-- Internal
|
||||
('agent_builtin_nomi', '/api/assets/logos/brand/nomi.svg', 'Nomi',
|
||||
NULL, 'nomi', 'internal', '{}',
|
||||
1, NULL, '[]', '[]',
|
||||
'[".nomi/skills"]',
|
||||
'{"supports_team":true}',
|
||||
'yolo',
|
||||
NULL, NULL,
|
||||
100,
|
||||
unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Remote Agents & MCP
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Local-only INTEGER id; the cross-device identity is device_id (a derived
|
||||
-- key), not this row id.
|
||||
CREATE TABLE IF NOT EXISTS remote_agents (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
protocol TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
auth_type TEXT NOT NULL,
|
||||
auth_token TEXT,
|
||||
allow_insecure INTEGER NOT NULL DEFAULT 0,
|
||||
avatar TEXT,
|
||||
description TEXT,
|
||||
device_id TEXT,
|
||||
device_public_key TEXT,
|
||||
device_private_key TEXT,
|
||||
device_token TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'unknown',
|
||||
last_connected_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_remote_agents_status ON remote_agents(status);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mcp_servers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
transport_type TEXT NOT NULL,
|
||||
transport_config TEXT NOT NULL,
|
||||
tools TEXT,
|
||||
last_test_status TEXT NOT NULL DEFAULT 'disconnected',
|
||||
last_connected INTEGER,
|
||||
original_json TEXT,
|
||||
builtin INTEGER NOT NULL DEFAULT 0,
|
||||
deleted_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_mcp_servers_name ON mcp_servers(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_mcp_servers_enabled ON mcp_servers(enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_mcp_servers_deleted_at ON mcp_servers(deleted_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS oauth_tokens (
|
||||
server_url TEXT PRIMARY KEY NOT NULL,
|
||||
access_token TEXT NOT NULL,
|
||||
refresh_token TEXT,
|
||||
token_type TEXT NOT NULL DEFAULT 'bearer',
|
||||
expires_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Assistants (channel / IM-facing: cross-device TEXT ids)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assistants (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
avatar TEXT,
|
||||
preset_agent_type TEXT NOT NULL DEFAULT 'gemini',
|
||||
enabled_skills TEXT,
|
||||
custom_skill_names TEXT,
|
||||
disabled_builtin_skills TEXT,
|
||||
prompts TEXT,
|
||||
models TEXT,
|
||||
name_i18n TEXT,
|
||||
description_i18n TEXT,
|
||||
prompts_i18n TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistants_updated_at ON assistants(updated_at DESC);
|
||||
|
||||
-- assistant_id may reference a builtin assistant defined in JSON (not a row
|
||||
-- in `assistants`), so NO foreign key: orphans are GC'd by the assistant
|
||||
-- service's delete_orphans(valid_ids).
|
||||
CREATE TABLE IF NOT EXISTS assistant_overrides (
|
||||
assistant_id TEXT PRIMARY KEY,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
preset_agent_type TEXT,
|
||||
last_used_at INTEGER,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Multi-row channel plugins: one row per connected bot. `pet_id` binds the
|
||||
-- bot to one pet (filesystem entity, no FK). `bot_key` is the platform-level
|
||||
-- bot identity; the partial unique index guarantees one bot binds to at most
|
||||
-- one pet. (Merged from former migration 003; legacy backfill dropped.)
|
||||
CREATE TABLE IF NOT EXISTS assistant_plugins (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
config TEXT NOT NULL,
|
||||
status TEXT,
|
||||
last_connected INTEGER,
|
||||
pet_id TEXT,
|
||||
bot_key TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_assistant_plugins_type_bot_key
|
||||
ON assistant_plugins(type, bot_key) WHERE bot_key IS NOT NULL;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assistant_users (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- achu_{uuidv7}
|
||||
platform_user_id TEXT NOT NULL,
|
||||
platform_type TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
authorized_at INTEGER NOT NULL,
|
||||
last_active INTEGER,
|
||||
session_id TEXT,
|
||||
UNIQUE (platform_user_id, platform_type)
|
||||
);
|
||||
|
||||
-- channel_id: which bot plugin owns this session (so two bots sharing a chat
|
||||
-- get isolated sessions). Merged from former migration 003.
|
||||
CREATE TABLE IF NOT EXISTS assistant_sessions (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
user_id TEXT NOT NULL,
|
||||
agent_type TEXT NOT NULL,
|
||||
conversation_id INTEGER,
|
||||
workspace TEXT,
|
||||
chat_id TEXT,
|
||||
channel_id TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_activity INTEGER NOT NULL,
|
||||
FOREIGN KEY (user_id) REFERENCES assistant_users(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (channel_id) REFERENCES assistant_plugins(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_sessions_user_id ON assistant_sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_sessions_user_chat ON assistant_sessions(user_id, chat_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_sessions_channel ON assistant_sessions(channel_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assistant_pairing_codes (
|
||||
code TEXT PRIMARY KEY NOT NULL,
|
||||
platform_user_id TEXT NOT NULL,
|
||||
platform_type TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
requested_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'approved', 'rejected', 'expired'))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_pairing_codes_status ON assistant_pairing_codes(status);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Teams (cross-device TEXT ids: team_/slot_/task_ travel via guide MCP
|
||||
-- result + team wake prompts + MCP env, recorded in ACP transcripts)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- lead_agent_id is an agent-address (a slot_id, or the 'lead'/'user'
|
||||
-- sentinel), NOT a foreign key.
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- team_{uuidv7}
|
||||
user_id TEXT NOT NULL DEFAULT 'system_default_user',
|
||||
name TEXT NOT NULL,
|
||||
workspace TEXT NOT NULL DEFAULT '',
|
||||
workspace_mode TEXT NOT NULL DEFAULT 'shared',
|
||||
lead_agent_id TEXT,
|
||||
session_mode TEXT,
|
||||
agents_version TEXT NOT NULL DEFAULT '1.0.0',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_user_id ON teams(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_teams_updated_at ON teams(updated_at);
|
||||
|
||||
-- Columnized from the former teams.agents JSON array. slot_id stays a string
|
||||
-- PK because it is transmitted in the MCP env (TEAM_AGENT_SLOT_ID) and remote
|
||||
-- protocol. conversation_id FK CASCADE: create flow inserts the slot's
|
||||
-- conversation before the slot row (see spec §9.A).
|
||||
CREATE TABLE IF NOT EXISTS team_agents (
|
||||
slot_id TEXT PRIMARY KEY NOT NULL, -- slot_{uuidv7}
|
||||
team_id TEXT NOT NULL,
|
||||
name TEXT NOT NULL DEFAULT '',
|
||||
role TEXT NOT NULL DEFAULT 'teammate',
|
||||
conversation_id INTEGER,
|
||||
backend TEXT NOT NULL DEFAULT '',
|
||||
model TEXT NOT NULL DEFAULT '',
|
||||
custom_agent_id TEXT,
|
||||
status TEXT,
|
||||
conversation_type TEXT,
|
||||
cli_path TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_team_agents_team ON team_agents(team_id, sort_order);
|
||||
CREATE INDEX IF NOT EXISTS idx_team_agents_conversation ON team_agents(conversation_id);
|
||||
|
||||
-- to_agent_id / from_agent_id are agent-addresses (slot_id or 'user'/'lead'
|
||||
-- sentinel), NOT foreign keys.
|
||||
CREATE TABLE IF NOT EXISTS mailbox (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_id TEXT NOT NULL,
|
||||
to_agent_id TEXT NOT NULL,
|
||||
from_agent_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL CHECK (type IN ('message', 'idle_notification', 'shutdown_request')),
|
||||
content TEXT NOT NULL,
|
||||
summary TEXT,
|
||||
files TEXT,
|
||||
read INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_mailbox_team_to_read ON mailbox(team_id, to_agent_id, read);
|
||||
CREATE INDEX IF NOT EXISTS idx_mailbox_team_id ON mailbox(team_id);
|
||||
|
||||
-- owner is an agent-address (slot_id or sentinel), NOT a foreign key.
|
||||
-- blocked_by/blocks JSON arrays are columnized into team_task_deps.
|
||||
CREATE TABLE IF NOT EXISTS team_tasks (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- task_{uuidv7}
|
||||
team_id TEXT NOT NULL,
|
||||
subject TEXT NOT NULL,
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'in_progress', 'completed', 'deleted')),
|
||||
owner TEXT,
|
||||
metadata TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_team_tasks_team_id ON team_tasks(team_id);
|
||||
|
||||
-- Single-directed dependency edge (replaces the bidirectional blocked_by/
|
||||
-- blocks JSON arrays). "who blocks X" = WHERE blocked_task_id=X; "what X
|
||||
-- blocks" = WHERE blocker_task_id=X.
|
||||
CREATE TABLE IF NOT EXISTS team_task_deps (
|
||||
blocker_task_id TEXT NOT NULL,
|
||||
blocked_task_id TEXT NOT NULL,
|
||||
PRIMARY KEY (blocker_task_id, blocked_task_id),
|
||||
CHECK (blocker_task_id <> blocked_task_id),
|
||||
FOREIGN KEY (blocker_task_id) REFERENCES team_tasks(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (blocked_task_id) REFERENCES team_tasks(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_team_task_deps_blocked ON team_task_deps(blocked_task_id);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Cron Jobs
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- conversation_id is now NULLABLE with a FK (was NOT NULL, no FK): a
|
||||
-- new_conversation job has no target until first fire. terminal_session_id
|
||||
-- FK SET NULL (terminal lazily created).
|
||||
CREATE TABLE IF NOT EXISTS cron_jobs (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- cron_{uuidv7}
|
||||
name TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
schedule_kind TEXT NOT NULL CHECK(schedule_kind IN ('at', 'every', 'cron')),
|
||||
schedule_value TEXT NOT NULL,
|
||||
schedule_tz TEXT,
|
||||
schedule_description TEXT,
|
||||
payload_message TEXT NOT NULL,
|
||||
execution_mode TEXT NOT NULL DEFAULT 'existing'
|
||||
CHECK(execution_mode IN ('existing', 'new_conversation')),
|
||||
agent_config TEXT,
|
||||
conversation_id INTEGER,
|
||||
conversation_title TEXT,
|
||||
agent_type TEXT NOT NULL,
|
||||
created_by TEXT NOT NULL CHECK(created_by IN ('user', 'agent')),
|
||||
skill_content TEXT,
|
||||
description TEXT,
|
||||
target_kind TEXT NOT NULL DEFAULT 'agent',
|
||||
terminal_mode TEXT,
|
||||
terminal_session_id INTEGER,
|
||||
terminal_command TEXT,
|
||||
terminal_args TEXT,
|
||||
terminal_script TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
next_run_at INTEGER,
|
||||
last_run_at INTEGER,
|
||||
last_status TEXT CHECK(last_status IN ('ok', 'error', 'skipped', 'missed')),
|
||||
last_error TEXT,
|
||||
run_count INTEGER NOT NULL DEFAULT 0,
|
||||
retry_count INTEGER NOT NULL DEFAULT 0,
|
||||
max_retries INTEGER NOT NULL DEFAULT 3,
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE SET NULL,
|
||||
FOREIGN KEY (terminal_session_id) REFERENCES terminal_sessions(id) ON DELETE SET NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_cron_jobs_conversation ON cron_jobs(conversation_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_cron_jobs_next_run ON cron_jobs(next_run_at) WHERE enabled = 1;
|
||||
CREATE INDEX IF NOT EXISTS idx_cron_jobs_agent_type ON cron_jobs(agent_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_cron_jobs_terminal_session ON cron_jobs(terminal_session_id);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Terminal sessions
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- PTY-backed interactive sessions. Scrollback kept in-memory (not persisted).
|
||||
-- autowork: JSON {enabled, tag, max_requirements} — AutoWork config, nullable.
|
||||
-- idmm: JSON blob — IDMM per-terminal stall-supervision config, nullable.
|
||||
CREATE TABLE IF NOT EXISTS terminal_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
cwd TEXT NOT NULL,
|
||||
command TEXT NOT NULL,
|
||||
args TEXT NOT NULL DEFAULT '[]',
|
||||
env TEXT,
|
||||
backend TEXT,
|
||||
mode TEXT,
|
||||
cols INTEGER NOT NULL DEFAULT 80,
|
||||
rows INTEGER NOT NULL DEFAULT 24,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
last_status TEXT NOT NULL DEFAULT 'running'
|
||||
CHECK(last_status IN ('running', 'exited', 'error')),
|
||||
exit_code INTEGER,
|
||||
user_id TEXT NOT NULL,
|
||||
pinned INTEGER NOT NULL DEFAULT 0,
|
||||
pinned_at INTEGER,
|
||||
autowork TEXT,
|
||||
idmm TEXT,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_terminal_sessions_user ON terminal_sessions(user_id);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Requirements Platform
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- owner_session_id records the executing session and is a dual-domain
|
||||
-- address (a conv_* conversation id OR a term_* terminal id), discriminated
|
||||
-- by owner_kind. No FK (single column cannot reference two tables); when a
|
||||
-- conversation/terminal is deleted the service clears the matching owner
|
||||
-- (clear_owner_for_session, spec §9.B). The owner token replaces the former
|
||||
-- redundant claimed_by column.
|
||||
CREATE TABLE IF NOT EXISTS requirements (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT NOT NULL DEFAULT '',
|
||||
tag TEXT NOT NULL,
|
||||
order_key TEXT NOT NULL DEFAULT '',
|
||||
sort_seq TEXT NOT NULL DEFAULT '', -- normalized sortable form of order_key (NOT a display seq)
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
priority INTEGER NOT NULL DEFAULT 0,
|
||||
completion_note TEXT,
|
||||
owner_session_id INTEGER, -- conversation OR terminal id; no FK
|
||||
owner_kind TEXT CHECK(owner_kind IS NULL OR owner_kind IN ('conversation', 'terminal')),
|
||||
claimed_at INTEGER,
|
||||
lease_expires_at INTEGER,
|
||||
started_at INTEGER,
|
||||
completed_at INTEGER,
|
||||
attempt_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_by TEXT NOT NULL DEFAULT 'user',
|
||||
extra TEXT NOT NULL DEFAULT '{}',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
CHECK ((owner_session_id IS NULL) = (owner_kind IS NULL))
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_requirements_tag_status ON requirements(tag, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_requirements_tag_order ON requirements(tag, sort_seq);
|
||||
CREATE INDEX IF NOT EXISTS idx_requirements_owner ON requirements(owner_session_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_requirements_status ON requirements(status);
|
||||
|
||||
-- AutoWork tag-level pause. paused_req_id FK SET NULL: the triggering
|
||||
-- requirement may be deleted while the pause stays.
|
||||
CREATE TABLE IF NOT EXISTS requirement_tags (
|
||||
tag TEXT PRIMARY KEY,
|
||||
paused INTEGER NOT NULL DEFAULT 0,
|
||||
paused_reason TEXT,
|
||||
paused_req_id INTEGER,
|
||||
paused_at INTEGER,
|
||||
FOREIGN KEY (paused_req_id) REFERENCES requirements(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Webhooks + per-tag settings (AutoWork completion notifications)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS webhooks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
platform TEXT NOT NULL DEFAULT 'lark',
|
||||
url TEXT NOT NULL,
|
||||
secret TEXT,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tag_settings (
|
||||
tag TEXT PRIMARY KEY,
|
||||
webhook_id INTEGER,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (webhook_id) REFERENCES webhooks(id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Knowledge Base platform
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Cross-device: kb id is an agent-facing gateway tool argument/result, so it
|
||||
-- enters the master agent's ACP transcript -> string global id.
|
||||
CREATE TABLE IF NOT EXISTS knowledge_bases (
|
||||
id TEXT PRIMARY KEY, -- kb_{uuidv7}
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
root_path TEXT NOT NULL,
|
||||
managed INTEGER NOT NULL DEFAULT 1,
|
||||
extra TEXT NOT NULL DEFAULT '{}',
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Per-target mount binding. The former composite PK (target_kind,target_id)
|
||||
-- and JSON kb_ids array are redesigned into a surrogate binding_id +
|
||||
-- type-discriminated nullable target columns (CHECK exactly-one) +
|
||||
-- knowledge_binding_bases junction. target_kind set is owned by the
|
||||
-- nomifun-knowledge service (BINDING_KINDS = workpath/conversation/terminal/pet).
|
||||
-- workpath: normalized workspace path key (not an entity, no FK)
|
||||
-- conversation/terminal: real TEXT FK CASCADE (binding dies with the session)
|
||||
-- pet: pet_{} filesystem entity (no FK; pet service cleans on delete)
|
||||
-- writeback_mode: 'staged' confines agent writes to {kb}/_inbox/{conversation_id}/
|
||||
CREATE TABLE IF NOT EXISTS knowledge_bindings (
|
||||
binding_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
target_kind TEXT NOT NULL,
|
||||
target_workpath TEXT,
|
||||
target_conv_id INTEGER,
|
||||
target_term_id INTEGER,
|
||||
target_pet_id TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
writeback INTEGER NOT NULL DEFAULT 0,
|
||||
writeback_mode TEXT NOT NULL DEFAULT 'staged'
|
||||
CHECK(writeback_mode IN ('staged', 'direct')),
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (target_conv_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (target_term_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE,
|
||||
CHECK (
|
||||
(target_kind = 'workpath' AND target_workpath IS NOT NULL
|
||||
AND target_conv_id IS NULL AND target_term_id IS NULL AND target_pet_id IS NULL)
|
||||
OR (target_kind = 'conversation' AND target_conv_id IS NOT NULL
|
||||
AND target_workpath IS NULL AND target_term_id IS NULL AND target_pet_id IS NULL)
|
||||
OR (target_kind = 'terminal' AND target_term_id IS NOT NULL
|
||||
AND target_workpath IS NULL AND target_conv_id IS NULL AND target_pet_id IS NULL)
|
||||
OR (target_kind = 'pet' AND target_pet_id IS NOT NULL
|
||||
AND target_workpath IS NULL AND target_conv_id IS NULL AND target_term_id IS NULL)
|
||||
)
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_workpath ON knowledge_bindings(target_workpath) WHERE target_workpath IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_conv ON knowledge_bindings(target_conv_id) WHERE target_conv_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_term ON knowledge_bindings(target_term_id) WHERE target_term_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_pet ON knowledge_bindings(target_pet_id) WHERE target_pet_id IS NOT NULL;
|
||||
|
||||
-- Columnized from the former knowledge_bindings.kb_ids JSON array.
|
||||
CREATE TABLE IF NOT EXISTS knowledge_binding_bases (
|
||||
binding_id INTEGER NOT NULL,
|
||||
kb_id TEXT NOT NULL,
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (binding_id, kb_id),
|
||||
FOREIGN KEY (binding_id) REFERENCES knowledge_bindings(binding_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (kb_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_kb_binding_bases_kb ON knowledge_binding_bases(kb_id);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Attachments (requirement images; was migration 002)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- Cross-device: att id rides the requirement DTO into the master agent's ACP
|
||||
-- transcript (nomi_requirement_update result) -> string global id. The former
|
||||
-- generic (kind, target_id) polymorphism is collapsed to a real
|
||||
-- requirement_id FK (only the requirement kind was ever used).
|
||||
CREATE TABLE IF NOT EXISTS attachments (
|
||||
id TEXT PRIMARY KEY, -- att_{uuidv7}
|
||||
requirement_id INTEGER NOT NULL,
|
||||
file_name TEXT NOT NULL, -- original display name (deduped per requirement)
|
||||
rel_path TEXT NOT NULL, -- relative to data_dir
|
||||
mime TEXT NOT NULL,
|
||||
size_bytes INTEGER NOT NULL,
|
||||
created_by TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (requirement_id) REFERENCES requirements(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_attachments_requirement ON attachments(requirement_id);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Conversation <-> MCP server selection (was conversations.extra.selected_mcp_server_ids)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS conversation_mcp_servers (
|
||||
conversation_id INTEGER NOT NULL,
|
||||
mcp_server_id INTEGER NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (conversation_id, mcp_server_id),
|
||||
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (mcp_server_id) REFERENCES mcp_servers(id) ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversation_mcp_servers_mcp ON conversation_mcp_servers(mcp_server_id);
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
-- Migration 002: write-back "eagerness" (回写意识) for knowledge bindings.
|
||||
--
|
||||
-- Adds a SECOND, orthogonal write-back axis to `knowledge_bindings`. The
|
||||
-- existing `writeback_mode` controls WHERE agent writes land (`staged` inbox
|
||||
-- vs `direct` body); this new `writeback_eagerness` controls HOW EAGERLY the
|
||||
-- agent writes at all, while write-back is enabled:
|
||||
-- * 'conservative' — the historical, restrained default: only persist
|
||||
-- knowledge the model judges clearly worth keeping.
|
||||
-- * 'aggressive' — capture anything plausibly relevant to a mounted base
|
||||
-- without much hesitation; the user prunes later.
|
||||
-- Both are purely prompt-contract wording (rendered by
|
||||
-- nomifun-knowledge::context); the column only persists the user's pick.
|
||||
--
|
||||
-- Additive on purpose: editing 001_baseline would change its checksum and
|
||||
-- trip the pre-baseline rebuild path (database.rs), wiping existing dev DBs.
|
||||
-- SQLite allows ADD COLUMN with a NOT NULL DEFAULT + CHECK; the default
|
||||
-- satisfies the CHECK for every pre-existing row.
|
||||
ALTER TABLE knowledge_bindings
|
||||
ADD COLUMN writeback_eagerness TEXT NOT NULL DEFAULT 'conservative'
|
||||
CHECK(writeback_eagerness IN ('conservative', 'aggressive'));
|
||||
@@ -0,0 +1,77 @@
|
||||
-- 002_companion_rename.sql
|
||||
-- 「pet」域整体更名为「companion」(数字伙伴)。001_baseline 已冻结(sqlx 校验和),
|
||||
-- 故所有列/值的前向更名集中在本迁移:新库 = 001 建 pet_* 列后由本迁移改为 companion_*;
|
||||
-- 既有库 = 本迁移把存量数据迁到 companion 名。代码侧标识符已全部改为 companion。
|
||||
|
||||
-- 1) assistant_plugins.pet_id -> companion_id(该列无索引/约束依赖,直接改名)。
|
||||
ALTER TABLE assistant_plugins RENAME COLUMN pet_id TO companion_id;
|
||||
|
||||
-- 2) knowledge_bindings:target_pet_id -> target_companion_id 且 kind 'pet' -> 'companion'。
|
||||
-- CHECK 约束内嵌字面量 'pet' 与列名,SQLite 无法 ALTER CHECK,需整表重建。
|
||||
-- knowledge_binding_bases 通过 ON DELETE CASCADE 挂在本表下:重建期先备份、后恢复,
|
||||
-- binding_id 全程保留以维持外键引用;对 foreign_keys 开/关两态都正确(DELETE+INSERT 幂等)。
|
||||
CREATE TEMP TABLE _kbb_backup AS SELECT * FROM knowledge_binding_bases;
|
||||
|
||||
CREATE TABLE knowledge_bindings_new (
|
||||
binding_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
target_kind TEXT NOT NULL,
|
||||
target_workpath TEXT,
|
||||
target_conv_id INTEGER,
|
||||
target_term_id INTEGER,
|
||||
target_companion_id TEXT,
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
writeback INTEGER NOT NULL DEFAULT 0,
|
||||
writeback_mode TEXT NOT NULL DEFAULT 'staged'
|
||||
CHECK(writeback_mode IN ('staged', 'direct')),
|
||||
writeback_eagerness TEXT NOT NULL DEFAULT 'conservative'
|
||||
CHECK(writeback_eagerness IN ('conservative', 'aggressive')),
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (target_conv_id) REFERENCES conversations(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (target_term_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE,
|
||||
CHECK (
|
||||
(target_kind = 'workpath' AND target_workpath IS NOT NULL
|
||||
AND target_conv_id IS NULL AND target_term_id IS NULL AND target_companion_id IS NULL)
|
||||
OR (target_kind = 'conversation' AND target_conv_id IS NOT NULL
|
||||
AND target_workpath IS NULL AND target_term_id IS NULL AND target_companion_id IS NULL)
|
||||
OR (target_kind = 'terminal' AND target_term_id IS NOT NULL
|
||||
AND target_workpath IS NULL AND target_conv_id IS NULL AND target_companion_id IS NULL)
|
||||
OR (target_kind = 'companion' AND target_companion_id IS NOT NULL
|
||||
AND target_workpath IS NULL AND target_conv_id IS NULL AND target_term_id IS NULL)
|
||||
)
|
||||
);
|
||||
|
||||
INSERT INTO knowledge_bindings_new
|
||||
(binding_id, target_kind, target_workpath, target_conv_id, target_term_id, target_companion_id, enabled, writeback, writeback_mode, writeback_eagerness, updated_at)
|
||||
SELECT binding_id,
|
||||
CASE WHEN target_kind = 'pet' THEN 'companion' ELSE target_kind END,
|
||||
target_workpath, target_conv_id, target_term_id, target_pet_id,
|
||||
enabled, writeback, writeback_mode, writeback_eagerness, updated_at
|
||||
FROM knowledge_bindings;
|
||||
|
||||
DROP TABLE knowledge_bindings;
|
||||
ALTER TABLE knowledge_bindings_new RENAME TO knowledge_bindings;
|
||||
|
||||
-- 恢复子表(CASCADE 可能已清空,或 FK 关闭时仍在):清后按备份重灌,保证幂等无重复。
|
||||
DELETE FROM knowledge_binding_bases;
|
||||
INSERT INTO knowledge_binding_bases SELECT * FROM _kbb_backup;
|
||||
DROP TABLE _kbb_backup;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_workpath ON knowledge_bindings(target_workpath) WHERE target_workpath IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_conv ON knowledge_bindings(target_conv_id) WHERE target_conv_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_term ON knowledge_bindings(target_term_id) WHERE target_term_id IS NOT NULL;
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_kb_binding_companion ON knowledge_bindings(target_companion_id) WHERE target_companion_id IS NOT NULL;
|
||||
|
||||
-- 3) conversations.extra:petCompanion -> companionSession(保留 JSON 布尔型,后端 as_bool 依赖),
|
||||
-- petId -> companionId。companion 会话两键恒同时存在;先迁真值行,再清理任何遗留旧键。
|
||||
UPDATE conversations
|
||||
SET extra = json_remove(
|
||||
json_set(
|
||||
json_set(extra, '$.companionId', json_extract(extra, '$.petId')),
|
||||
'$.companionSession', json('true')
|
||||
),
|
||||
'$.petId', '$.petCompanion')
|
||||
WHERE json_valid(extra) AND json_extract(extra, '$.petCompanion') = 1;
|
||||
|
||||
UPDATE conversations
|
||||
SET extra = json_remove(extra, '$.petCompanion', '$.petId')
|
||||
WHERE json_valid(extra) AND (json_extract(extra, '$.petCompanion') IS NOT NULL OR json_extract(extra, '$.petId') IS NOT NULL);
|
||||
@@ -0,0 +1,50 @@
|
||||
-- Migration 004: Add branding configuration table
|
||||
--
|
||||
-- Stores brand customization including:
|
||||
-- - Logo paths (light/dark variants)
|
||||
-- - Theme colors (primary, secondary, accent)
|
||||
-- - Custom CSS variables
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Branding configuration table
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS branding_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
-- Logo configuration
|
||||
logo_light TEXT, -- Path to light logo
|
||||
logo_dark TEXT, -- Path to dark logo
|
||||
logo_favicon TEXT, -- Path to favicon
|
||||
-- Theme colors (hex format)
|
||||
primary_color TEXT NOT NULL DEFAULT '#3B82F6',
|
||||
secondary_color TEXT NOT NULL DEFAULT '#64748B',
|
||||
accent_color TEXT NOT NULL DEFAULT '#10B981',
|
||||
-- Background colors
|
||||
background_light TEXT NOT NULL DEFAULT '#FFFFFF',
|
||||
background_dark TEXT NOT NULL DEFAULT '#0F172A',
|
||||
surface_light TEXT NOT NULL DEFAULT '#F8FAFC',
|
||||
surface_dark TEXT NOT NULL DEFAULT '#1E293B',
|
||||
-- Text colors
|
||||
text_primary_light TEXT NOT NULL DEFAULT '#0F172A',
|
||||
text_primary_dark TEXT NOT NULL DEFAULT '#F8FAFC',
|
||||
text_secondary_light TEXT NOT NULL DEFAULT '#475569',
|
||||
text_secondary_dark TEXT NOT NULL DEFAULT '#94A3B8',
|
||||
-- Border colors
|
||||
border_light TEXT NOT NULL DEFAULT '#E2E8F0',
|
||||
border_dark TEXT NOT NULL DEFAULT '#334155',
|
||||
-- Active preset name (for quick reference)
|
||||
active_preset TEXT NOT NULL DEFAULT 'default',
|
||||
-- Custom CSS that will be injected
|
||||
custom_css TEXT NOT NULL DEFAULT '',
|
||||
-- Timestamps
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Seed default branding configuration
|
||||
INSERT OR IGNORE INTO branding_config (id, created_at, updated_at)
|
||||
VALUES (1, unixepoch('now', 'subsec') * 1000, unixepoch('now', 'subsec') * 1000);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- End of migration
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,57 @@
|
||||
-- Migration 005: Add audit logging table
|
||||
--
|
||||
-- Records sensitive operations for security and compliance:
|
||||
-- - Authentication events (login, logout, failed attempts)
|
||||
-- - Configuration changes
|
||||
-- - User management operations
|
||||
-- - Data access and exports
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Audit log table
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
-- Event classification
|
||||
action TEXT NOT NULL,
|
||||
category TEXT NOT NULL
|
||||
CHECK(category IN (
|
||||
'auth',
|
||||
'user_management',
|
||||
'branding',
|
||||
'system_config',
|
||||
'data_access',
|
||||
'data_export',
|
||||
'permission_change',
|
||||
'other'
|
||||
)),
|
||||
-- Actor information
|
||||
user_id TEXT,
|
||||
username TEXT,
|
||||
ip_address TEXT,
|
||||
user_agent TEXT,
|
||||
-- Event details
|
||||
resource_type TEXT,
|
||||
resource_id TEXT,
|
||||
details TEXT NOT NULL DEFAULT '{}', -- JSON with action-specific data
|
||||
-- Outcome
|
||||
status TEXT NOT NULL
|
||||
CHECK(status IN ('success', 'failure', 'denied'))
|
||||
DEFAULT 'success',
|
||||
-- Timestamp (ms since epoch)
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Indexes for common query patterns
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_created_at ON audit_log(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_action ON audit_log(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_category ON audit_log(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_status ON audit_log(status);
|
||||
-- Composite index for filtered queries
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_category_created ON audit_log(category, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_user_created ON audit_log(user_id, created_at DESC);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- End of migration
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,98 @@
|
||||
-- Migration 006: Add domain configuration table
|
||||
--
|
||||
-- Stores domain-specific settings and configurations:
|
||||
-- - Government: departments, approval workflows
|
||||
-- - Enterprise: departments, team structures
|
||||
-- - Education: schools, faculties, semesters
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Domain configuration table
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS domain_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
-- Domain type (mirrors system_config.domain_type)
|
||||
domain_type TEXT NOT NULL DEFAULT 'enterprise'
|
||||
CHECK(domain_type IN ('government', 'enterprise', 'education')),
|
||||
-- Domain-specific settings (JSON)
|
||||
settings TEXT NOT NULL DEFAULT '{}',
|
||||
-- Government-specific
|
||||
government_settings TEXT NOT NULL DEFAULT '{}',
|
||||
-- Enterprise-specific
|
||||
enterprise_settings TEXT NOT NULL DEFAULT '{}',
|
||||
-- Education-specific
|
||||
education_settings TEXT NOT NULL DEFAULT '{}',
|
||||
-- Enabled features (JSON array)
|
||||
enabled_features TEXT NOT NULL DEFAULT '[]',
|
||||
-- Department/division list (JSON array)
|
||||
departments TEXT NOT NULL DEFAULT '[]',
|
||||
-- Custom domain parameters (JSON)
|
||||
custom_params TEXT NOT NULL DEFAULT '{}',
|
||||
-- Timestamps
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Seed default domain configuration
|
||||
INSERT OR IGNORE INTO domain_config (id, created_at, updated_at)
|
||||
VALUES (1, unixepoch('now', 'subsec') * 1000, unixepoch('now', 'subsec') * 1000);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Domain presets (reference data)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS domain_presets (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
domain_type TEXT NOT NULL
|
||||
CHECK(domain_type IN ('government', 'enterprise', 'education')),
|
||||
description TEXT,
|
||||
-- Default settings for this preset
|
||||
settings TEXT NOT NULL DEFAULT '{}',
|
||||
-- Preset configurations
|
||||
config TEXT NOT NULL DEFAULT '{}',
|
||||
-- Sort order
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
-- Timestamps
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Seed government presets
|
||||
INSERT OR IGNORE INTO domain_presets (id, name, domain_type, description, settings, config, sort_order, created_at)
|
||||
VALUES
|
||||
('gov_office', '政务办公', 'government', '政务办公场景,适合政府机关日常办公',
|
||||
'{"approval_workflow": true, "document_classification": true, "multi_department": true}',
|
||||
'{"departments": ["办公室", "人事处", "财务处", "业务处"], "features": ["公文管理", "审批流程", "档案管理"]}',
|
||||
10, unixepoch('now', 'subsec') * 1000),
|
||||
('gov_service', '政务服务', 'government', '政务服务场景,适合对外服务窗口',
|
||||
'{"queue_management": true, "service_rating": true, "id_verification": true}',
|
||||
'{"departments": ["综合窗口", "专项服务", "投诉受理"], "features": ["排队叫号", "服务评价", "身份核验"]}',
|
||||
20, unixepoch('now', 'subsec') * 1000);
|
||||
|
||||
-- Seed enterprise presets
|
||||
INSERT OR IGNORE INTO domain_presets (id, name, domain_type, description, settings, config, sort_order, created_at)
|
||||
VALUES
|
||||
('ent_corporate', '企业办公', 'enterprise', '企业通用办公场景',
|
||||
'{"team_structure": true, "project_tracking": true, "knowledge_base": true}',
|
||||
'{"departments": ["技术部", "市场部", "运营部", "财务部"], "features": ["项目管理", "知识库", "团队协作"]}',
|
||||
10, unixepoch('now', 'subsec') * 1000),
|
||||
('ent_sales', '销售团队', 'enterprise', '销售团队专用场景',
|
||||
'{"crm_integration": true, "lead_tracking": true, "sales_reporting": true}',
|
||||
'{"departments": ["销售一部", "销售二部", "客服部"], "features": ["客户管理", "销售报表", "线索追踪"]}',
|
||||
20, unixepoch('now', 'subsec') * 1000);
|
||||
|
||||
-- Seed education presets
|
||||
INSERT OR IGNORE INTO domain_presets (id, name, domain_type, description, settings, config, sort_order, created_at)
|
||||
VALUES
|
||||
('edu_university', '高等院校', 'education', '高等院校教学科研场景',
|
||||
'{"semester_management": true, "course_management": true, "research_support": true}',
|
||||
'{"departments": ["计算机系", "数学系", "物理系", "外语系"], "features": ["课程管理", "科研助手", "论文写作"]}',
|
||||
10, unixepoch('now', 'subsec') * 1000),
|
||||
('edu_school', '中小学', 'education', '中小学教育场景',
|
||||
'{"grade_management": true, "homework_tracking": true, "parent_communication": true}',
|
||||
'{"departments": ["语文组", "数学组", "英语组", "综合组"], "features": ["作业管理", "家长通知", "成绩分析"]}',
|
||||
20, unixepoch('now', 'subsec') * 1000);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- End of migration
|
||||
------------------------------------------------------------------------
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
-- Align built-in ACP agent launch metadata with the current product-owned CLI names.
|
||||
-- Existing databases created before this migration keep their rows, so update
|
||||
-- only the known stale command/binary pairs.
|
||||
|
||||
UPDATE agent_metadata
|
||||
SET
|
||||
agent_source_info = '{"binary_name":"agent"}',
|
||||
command = 'agent',
|
||||
args = '["acp"]',
|
||||
updated_at = unixepoch('now','subsec') * 1000
|
||||
WHERE id = 'agent_builtin_cursor'
|
||||
AND agent_source = 'builtin'
|
||||
AND (
|
||||
agent_source_info = '{"binary_name":"cursor"}'
|
||||
OR command = 'cursor'
|
||||
);
|
||||
|
||||
UPDATE agent_metadata
|
||||
SET
|
||||
agent_source_info = '{"binary_name":"kiro-cli"}',
|
||||
command = 'kiro-cli',
|
||||
args = '["acp"]',
|
||||
updated_at = unixepoch('now','subsec') * 1000
|
||||
WHERE id = 'agent_builtin_kiro'
|
||||
AND agent_source = 'builtin'
|
||||
AND (
|
||||
agent_source_info = '{"binary_name":"kiro-cli-chat"}'
|
||||
OR command = 'kiro-cli-chat'
|
||||
);
|
||||
@@ -0,0 +1,24 @@
|
||||
-- IDMM 决策记录:把原本仅存于内存(100 条环形缓冲、重启即丢、前端零渲染)的
|
||||
-- 介入审计落独立表,使决策"看得见、可追溯"。激进淘汰(数据不重要,只留一点):
|
||||
-- 每 target 仅留最近 30 条 + 全局 TTL 48h + 全局硬上限兜底;target_id 多态
|
||||
-- (会话 TEXT / 终端 INTEGER),不设双 FK,删会话/终端时由应用层级联清理。
|
||||
CREATE TABLE IF NOT EXISTS idmm_interventions (
|
||||
id TEXT PRIMARY KEY NOT NULL, -- idmmrec_{uuidv7}
|
||||
target_kind TEXT NOT NULL, -- 'conversation' | 'terminal'
|
||||
target_id TEXT NOT NULL,
|
||||
watch TEXT NOT NULL, -- 'fault'(provider/agent 故障)| 'decision'(其余),由信号种类推导
|
||||
at INTEGER NOT NULL, -- epoch ms
|
||||
signal TEXT NOT NULL, -- stall_class: provider_error/idle/decision/open_question/scheduled
|
||||
tier_used TEXT NOT NULL, -- rule | sidecar | rule_fallback
|
||||
category TEXT, -- option/open_question/permission/fault
|
||||
action TEXT NOT NULL, -- retry/answer_choice/send_text/confirm/wait/stop
|
||||
detail TEXT, -- 选了什么/答了什么(截断 ≤2000 字符)
|
||||
reason TEXT, -- 思路(模型 reason 或规则解释;非选项分支的描述串也落这里)
|
||||
confidence REAL, -- 模型置信度(规则档 NULL)
|
||||
bypass_model TEXT, -- provider/model(规则档 NULL)
|
||||
outcome TEXT NOT NULL -- 规范枚举 applied/resolved/failed/halted/skipped(Phase1 发 applied|halted)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_idmm_interventions_target
|
||||
ON idmm_interventions(target_kind, target_id, at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_idmm_interventions_at
|
||||
ON idmm_interventions(at);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- 009_assistant_tags.sql
|
||||
-- Two-dimension tagging for assistants (audience / scenario).
|
||||
-- Per-assistant tag keys live as JSON arrays on the assistants table
|
||||
-- (mirrors enabled_skills). The vocabulary's user-created entries live in
|
||||
-- assistant_tags; built-in seed tags ship in tags.json (no rows here).
|
||||
|
||||
ALTER TABLE assistants ADD COLUMN audience_tags TEXT;
|
||||
ALTER TABLE assistants ADD COLUMN scenario_tags TEXT;
|
||||
|
||||
-- User-created tag vocabulary only. Built-in seed tags are served from the
|
||||
-- embedded tags.json manifest and merged at the service layer, so they are
|
||||
-- NOT rows here. `dimension` is 'audience' | 'scenario'. No FK to assistants
|
||||
-- (assistants reference tags by key in their JSON arrays; deletion cleanup is
|
||||
-- done by the service).
|
||||
CREATE TABLE IF NOT EXISTS assistant_tags (
|
||||
key TEXT PRIMARY KEY,
|
||||
dimension TEXT NOT NULL CHECK (dimension IN ('audience', 'scenario')),
|
||||
label TEXT NOT NULL,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_tags_dimension ON assistant_tags(dimension, sort_order);
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Migration 009: external-channel write re-enable toggle for knowledge bindings.
|
||||
--
|
||||
-- P1/P2 hard-disable knowledge write-back for external IM channel master-agent
|
||||
-- sessions (discord/slack/lark/…) by default: an unattended bot writing to a
|
||||
-- shared knowledge base is a standing risk. This column lets the user opt a
|
||||
-- specific binding back in. When enabled, channel writes are still forced to
|
||||
-- STAGED placement (review inbox) — never direct — so the human review gate
|
||||
-- remains the safety net (enforced in `resolve_write_policy`).
|
||||
--
|
||||
-- Additive on purpose: editing 001_baseline would change its checksum and trip
|
||||
-- the pre-baseline rebuild path (database.rs), wiping existing dev DBs. SQLite
|
||||
-- allows ADD COLUMN with a NOT NULL DEFAULT; the default (0 = disabled)
|
||||
-- preserves the prior behavior for every pre-existing row.
|
||||
ALTER TABLE knowledge_bindings
|
||||
ADD COLUMN channel_write_enabled INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,20 @@
|
||||
-- Migration 010: encrypted credentials for source connectors (feishu/notion/…).
|
||||
--
|
||||
-- Stores per-connector credentials as an opaque AES-256-GCM ciphertext blob
|
||||
-- (`payload_encrypted`); the service layer holds the encryption key and the
|
||||
-- JSON payload shape (e.g. `{ "app_id": ..., "app_secret": ... }`), exactly
|
||||
-- like the providers table's `api_key_encrypted`. Multiple credentials of the
|
||||
-- same kind are allowed (different tenants/accounts), so the key is a surrogate
|
||||
-- id, not the kind.
|
||||
--
|
||||
-- Additive: never touches 001_baseline (checksum stability).
|
||||
CREATE TABLE IF NOT EXISTS connector_credentials (
|
||||
id TEXT PRIMARY KEY,
|
||||
kind TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
payload_encrypted TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_connector_credentials_kind ON connector_credentials(kind);
|
||||
@@ -0,0 +1,14 @@
|
||||
-- 010_skill_tags.sql
|
||||
-- Per-skill tag assignments. Skills are filesystem folders (not DB rows),
|
||||
-- keyed by their unique `name`. Tags are decoupled from skill files so ANY
|
||||
-- source (builtin/custom/extension/external) is taggable. Built-in seed
|
||||
-- assignments ship in skill-tags.json and are merged at the route layer;
|
||||
-- this table holds user assignments/overrides only. JSON-array TEXT columns
|
||||
-- mirror the assistants' audience_tags/scenario_tags storage. Tag keys
|
||||
-- reference the shared vocabulary (assistant_tags + tags.json).
|
||||
CREATE TABLE IF NOT EXISTS skill_tags (
|
||||
skill_name TEXT PRIMARY KEY,
|
||||
audience_tags TEXT,
|
||||
scenario_tags TEXT,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
-- 013_knowledge_tags.sql
|
||||
-- User-defined tags for knowledge bases. The `tags` column on knowledge_bases
|
||||
-- stores a JSON array of tag keys assigned to that base (NULL = no tags).
|
||||
-- The `knowledge_tags` table holds the tag definitions (palette).
|
||||
|
||||
ALTER TABLE knowledge_bases ADD COLUMN tags TEXT; -- JSON array text; NULL = no tags
|
||||
|
||||
CREATE TABLE knowledge_tags (
|
||||
key TEXT PRIMARY KEY,
|
||||
label TEXT NOT NULL,
|
||||
color TEXT,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Per-tag notification event filter. Comma-separated subset of done/failed/needs_review.
|
||||
-- Default keeps current behavior (all three fire).
|
||||
ALTER TABLE tag_settings ADD COLUMN notify_events TEXT NOT NULL DEFAULT 'done,failed,needs_review';
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Single-row table holding the SHA-256 hash of the long-lived instance API
|
||||
-- token used by the Remote capability front door (`/mcp`). All-or-nothing
|
||||
-- trust: one token per instance, hash-only at rest, revocable by deleting the
|
||||
-- row. CHECK(id = 1) enforces the singleton invariant.
|
||||
CREATE TABLE IF NOT EXISTS instance_api_token (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
token_hash TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Per-companion access tokens for the Remote capability front door.
|
||||
-- Replaces the singleton `instance_api_token` (015): every external connection
|
||||
-- binds to exactly one companion. Only the SHA-256 hash is stored; the plaintext
|
||||
-- is shown once at mint time and never persisted.
|
||||
CREATE TABLE IF NOT EXISTS companion_access_token (
|
||||
companion_id TEXT PRIMARY KEY,
|
||||
token_hash TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Retire the singleton instance token. Safe whether or not 015 was ever applied
|
||||
-- on this DB (fresh branch DB: 015 creates it, 016 drops it; already-migrated DB:
|
||||
-- 016 drops the existing table). No dead table remains.
|
||||
DROP TABLE IF EXISTS instance_api_token;
|
||||
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS cron_job_runs (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
job_id TEXT NOT NULL,
|
||||
executed_at_ms INTEGER NOT NULL,
|
||||
status TEXT NOT NULL CHECK(status IN ('ok', 'error', 'skipped', 'missed')),
|
||||
created_at_ms INTEGER NOT NULL,
|
||||
FOREIGN KEY (job_id) REFERENCES cron_jobs(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_cron_job_runs_job_time
|
||||
ON cron_job_runs(job_id, executed_at_ms DESC, created_at_ms DESC);
|
||||
@@ -0,0 +1,30 @@
|
||||
-- Migration 002: Add user role support
|
||||
--
|
||||
-- Single-tenant architecture: all tables retain tenant_id as extension point,
|
||||
-- but the first deployed system operates in single-tenant mode.
|
||||
--
|
||||
-- This migration adds role-based access control to the users table.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Extend users table with role field
|
||||
------------------------------------------------------------------------
|
||||
|
||||
ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'user'
|
||||
CHECK(role IN ('admin', 'user'));
|
||||
|
||||
-- Create index for role-based queries
|
||||
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Seed data: upgrade existing users to admin if they are the first user
|
||||
------------------------------------------------------------------------
|
||||
|
||||
-- The first registered user (by created_at) becomes admin
|
||||
-- This handles the case where users existed before this migration
|
||||
UPDATE users
|
||||
SET role = 'admin'
|
||||
WHERE id = (SELECT id FROM users ORDER BY created_at ASC LIMIT 1);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- End of migration
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,36 @@
|
||||
-- Migration 003: Add system configuration table
|
||||
--
|
||||
-- Stores organization-level configuration including:
|
||||
-- - Organization name
|
||||
-- - Domain type (government/enterprise/education)
|
||||
-- - Initialization state
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- System configuration table
|
||||
------------------------------------------------------------------------
|
||||
|
||||
CREATE TABLE IF NOT EXISTS system_config (
|
||||
id INTEGER PRIMARY KEY CHECK (id = 1),
|
||||
organization_name TEXT NOT NULL DEFAULT 'My Organization',
|
||||
domain_type TEXT NOT NULL DEFAULT 'enterprise'
|
||||
CHECK(domain_type IN ('government', 'enterprise', 'education')),
|
||||
initialized INTEGER NOT NULL DEFAULT 0,
|
||||
init_completed_at INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
-- Seed default configuration row
|
||||
INSERT OR IGNORE INTO system_config (id, created_at, updated_at)
|
||||
VALUES (1, unixepoch('now', 'subsec') * 1000, unixepoch('now', 'subsec') * 1000);
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Extend system_settings for additional preferences
|
||||
------------------------------------------------------------------------
|
||||
|
||||
ALTER TABLE system_settings ADD COLUMN theme_mode TEXT NOT NULL DEFAULT 'light'
|
||||
CHECK(theme_mode IN ('light', 'dark', 'auto'));
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- End of migration
|
||||
------------------------------------------------------------------------
|
||||
@@ -0,0 +1,72 @@
|
||||
-- 004_channel_scoped_pairing.sql
|
||||
-- 配对/授权域从全局 (platform_user_id, platform_type) 收敛到 per-bot:
|
||||
-- 两张表加 channel_id + FK→assistant_plugins(id) ON DELETE CASCADE,
|
||||
-- 唯一约束改为含 channel_id。回填到对应平台"最可能在用"的那一行。
|
||||
|
||||
-- ── 1) assistant_users 重建(子表 assistant_sessions 需备份/恢复)──
|
||||
CREATE TEMP TABLE _sessions_backup AS SELECT * FROM assistant_sessions;
|
||||
|
||||
CREATE TABLE assistant_users_new (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
platform_user_id TEXT NOT NULL,
|
||||
platform_type TEXT NOT NULL,
|
||||
channel_id TEXT,
|
||||
display_name TEXT,
|
||||
authorized_at INTEGER NOT NULL,
|
||||
last_active INTEGER,
|
||||
session_id TEXT,
|
||||
UNIQUE (platform_user_id, platform_type, channel_id),
|
||||
FOREIGN KEY (channel_id) REFERENCES assistant_plugins(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO assistant_users_new
|
||||
(id, platform_user_id, platform_type, channel_id, display_name, authorized_at, last_active, session_id)
|
||||
SELECT u.id, u.platform_user_id, u.platform_type,
|
||||
(SELECT p.id FROM assistant_plugins p
|
||||
WHERE p.type = u.platform_type
|
||||
ORDER BY (p.companion_id IS NOT NULL) DESC, p.created_at ASC
|
||||
LIMIT 1),
|
||||
u.display_name, u.authorized_at, u.last_active, u.session_id
|
||||
FROM assistant_users u;
|
||||
|
||||
DROP TABLE assistant_users;
|
||||
ALTER TABLE assistant_users_new RENAME TO assistant_users;
|
||||
|
||||
DELETE FROM assistant_sessions;
|
||||
INSERT INTO assistant_sessions SELECT * FROM _sessions_backup;
|
||||
DROP TABLE _sessions_backup;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_sessions_user_id ON assistant_sessions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_sessions_user_chat ON assistant_sessions(user_id, chat_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_sessions_channel ON assistant_sessions(channel_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assistant_users_channel ON assistant_users(channel_id);
|
||||
|
||||
-- ── 2) assistant_pairing_codes 重建(无子表)──
|
||||
CREATE TABLE assistant_pairing_codes_new (
|
||||
code TEXT PRIMARY KEY NOT NULL,
|
||||
platform_user_id TEXT NOT NULL,
|
||||
platform_type TEXT NOT NULL,
|
||||
channel_id TEXT,
|
||||
display_name TEXT,
|
||||
requested_at INTEGER NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
status TEXT NOT NULL DEFAULT 'pending'
|
||||
CHECK (status IN ('pending', 'approved', 'rejected', 'expired')),
|
||||
FOREIGN KEY (channel_id) REFERENCES assistant_plugins(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO assistant_pairing_codes_new
|
||||
(code, platform_user_id, platform_type, channel_id, display_name, requested_at, expires_at, status)
|
||||
SELECT c.code, c.platform_user_id, c.platform_type,
|
||||
(SELECT p.id FROM assistant_plugins p
|
||||
WHERE p.type = c.platform_type
|
||||
ORDER BY (p.companion_id IS NOT NULL) DESC, p.created_at ASC
|
||||
LIMIT 1),
|
||||
c.display_name, c.requested_at, c.expires_at, c.status
|
||||
FROM assistant_pairing_codes c;
|
||||
|
||||
DROP TABLE assistant_pairing_codes;
|
||||
ALTER TABLE assistant_pairing_codes_new RENAME TO assistant_pairing_codes;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_pairing_codes_status ON assistant_pairing_codes(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_pairing_codes_channel ON assistant_pairing_codes(channel_id);
|
||||
@@ -0,0 +1,15 @@
|
||||
-- 005_terminal_scrollback.sql
|
||||
-- 终端 scrollback 跨重启持久化:把原本仅存于内存(256KB 有界缓冲)的输出历史
|
||||
-- 落到独立表,使应用重启后仍能回放历史显示(配合 boot 对账把幽灵 running
|
||||
-- 行改成 exited,前端即出现 relaunch 入口并回放这段历史)。
|
||||
--
|
||||
-- 用独立表而非给 terminal_sessions 加列:list 查询天然不拉这块大数据(保持
|
||||
-- 列表轻量),且 ON DELETE CASCADE 随会话删除自动清理。
|
||||
-- 写入由后端去抖驱动(仅脏会话、~5s 一次 + 进程退出时),绝不每输出块写。
|
||||
|
||||
CREATE TABLE IF NOT EXISTS terminal_scrollback (
|
||||
session_id INTEGER PRIMARY KEY NOT NULL,
|
||||
data BLOB NOT NULL,
|
||||
updated_at INTEGER NOT NULL,
|
||||
FOREIGN KEY (session_id) REFERENCES terminal_sessions(id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Keyset (cursor) pagination for conversation message history.
|
||||
--
|
||||
-- An ever-growing single conversation — notably a desktop companion's ONE
|
||||
-- session, which now also absorbs every IM-channel turn — is loaded in
|
||||
-- newest-first windows via a (created_at, id) keyset cursor instead of one
|
||||
-- giant fetch:
|
||||
--
|
||||
-- SELECT * FROM messages
|
||||
-- WHERE conversation_id = ?
|
||||
-- AND type NOT IN ('cron_trigger','skill_suggest')
|
||||
-- AND (created_at < ? OR (created_at = ? AND id < ?))
|
||||
-- ORDER BY created_at DESC, id DESC
|
||||
-- LIMIT ?
|
||||
--
|
||||
-- The existing idx_messages_conv_created / idx_messages_conv_created_desc cover
|
||||
-- (conversation_id, created_at) but NOT the `id` tiebreaker, so the cursor's
|
||||
-- (created_at, id) comparison and the matching ORDER BY degrade to a sort. This
|
||||
-- composite covers both, keeping deep "load older" pages index-only and stable
|
||||
-- under concurrent streaming appends. `id` is msg_{uuidv7} (time-ordered), so it
|
||||
-- is a sound, monotonic tiebreaker for rows sharing a created_at millisecond.
|
||||
CREATE INDEX IF NOT EXISTS idx_messages_conv_created_id
|
||||
ON messages (conversation_id, created_at DESC, id DESC);
|
||||
Reference in New Issue
Block a user