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);
|
||||
Reference in New Issue
Block a user