@vox-ai-app/storage 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +109 -0
- package/package.json +10 -4
- package/src/db.js +6 -107
- package/src/index.js +9 -3
- package/src/migrations/001_initial_schema.js +217 -0
- package/src/migrations/002_task_activity_types.js +45 -0
- package/src/migrations/runner.js +31 -0
- package/src/repos/mcp-servers.js +81 -0
- package/src/repos/messages.js +219 -0
- package/src/repos/patterns.js +54 -0
- package/src/repos/schedules.js +58 -0
- package/src/repos/settings.js +38 -0
- package/src/repos/tasks.js +195 -0
- package/src/repos/tool-secrets.js +26 -0
- package/src/repos/tools.js +104 -0
- package/src/repos/vectors.js +105 -0
- package/src/config.js +0 -62
- package/src/messages.js +0 -213
- package/src/tasks.js +0 -219
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @vox-ai-app/storage
|
|
2
|
+
|
|
3
|
+
Local persistence for Vox: conversations, messages, tasks, settings, tool registry, MCP servers, schedules, secrets, patterns, and vector embeddings. Built on SQLite via `better-sqlite3` with WAL mode and automatic migrations.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @vox-ai-app/storage
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Exports
|
|
12
|
+
|
|
13
|
+
| Export | Contents |
|
|
14
|
+
| ---------------------------------- | ------------------------------- |
|
|
15
|
+
| `@vox-ai-app/storage` | All exports |
|
|
16
|
+
| `@vox-ai-app/storage/db` | Database lifecycle (open/close) |
|
|
17
|
+
| `@vox-ai-app/storage/messages` | Conversations and messages |
|
|
18
|
+
| `@vox-ai-app/storage/tasks` | Task and task activity storage |
|
|
19
|
+
| `@vox-ai-app/storage/tools` | Custom tool definitions |
|
|
20
|
+
| `@vox-ai-app/storage/settings` | Key-value settings persistence |
|
|
21
|
+
| `@vox-ai-app/storage/mcp-servers` | MCP server configurations |
|
|
22
|
+
| `@vox-ai-app/storage/schedules` | Scheduled job persistence |
|
|
23
|
+
| `@vox-ai-app/storage/tool-secrets` | Encrypted tool secret storage |
|
|
24
|
+
| `@vox-ai-app/storage/patterns` | Conversation pattern storage |
|
|
25
|
+
| `@vox-ai-app/storage/vectors` | Vector embedding storage |
|
|
26
|
+
|
|
27
|
+
## Database
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { openDb, closeDb } from '@vox-ai-app/storage/db'
|
|
31
|
+
|
|
32
|
+
const db = openDb('/path/to/storage.db')
|
|
33
|
+
closeDb('/path/to/storage.db')
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The database uses WAL journal mode and foreign keys. Schema is managed via migrations in `src/migrations/`.
|
|
37
|
+
|
|
38
|
+
## Messages
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
import {
|
|
42
|
+
ensureConversation,
|
|
43
|
+
appendMessage,
|
|
44
|
+
getMessages,
|
|
45
|
+
getMessagesBeforeId,
|
|
46
|
+
clearMessages,
|
|
47
|
+
saveSummaryCheckpoint,
|
|
48
|
+
loadSummaryCheckpoint
|
|
49
|
+
} from '@vox-ai-app/storage/messages'
|
|
50
|
+
|
|
51
|
+
ensureConversation(db, 'main')
|
|
52
|
+
appendMessage(db, 'user', 'Hello', 'main')
|
|
53
|
+
appendMessage(db, 'assistant', 'Hi there!', 'main')
|
|
54
|
+
|
|
55
|
+
const messages = getMessages(db, 'main', 50)
|
|
56
|
+
const older = getMessagesBeforeId(db, messages[0].id, 'main', 20)
|
|
57
|
+
|
|
58
|
+
saveSummaryCheckpoint(db, 'summary text', 42, 'main')
|
|
59
|
+
const { summary, checkpointId } = loadSummaryCheckpoint(db, 'main')
|
|
60
|
+
|
|
61
|
+
clearMessages(db, 'main')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Tasks
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
import {
|
|
68
|
+
upsertTask,
|
|
69
|
+
getTask,
|
|
70
|
+
loadTasks,
|
|
71
|
+
appendTaskActivity,
|
|
72
|
+
loadTaskActivity
|
|
73
|
+
} from '@vox-ai-app/storage/tasks'
|
|
74
|
+
|
|
75
|
+
upsertTask(db, {
|
|
76
|
+
taskId: 'abc-123',
|
|
77
|
+
instructions: 'Summarize the document',
|
|
78
|
+
status: 'running'
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const task = getTask(db, 'abc-123')
|
|
82
|
+
const allTasks = loadTasks(db)
|
|
83
|
+
|
|
84
|
+
appendTaskActivity(db, {
|
|
85
|
+
id: 'act-1',
|
|
86
|
+
taskId: 'abc-123',
|
|
87
|
+
type: 'tool_call',
|
|
88
|
+
name: 'read_local_file',
|
|
89
|
+
timestamp: new Date().toISOString(),
|
|
90
|
+
data: { path: '~/doc.md' }
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const activity = loadTaskActivity(db, 'abc-123')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Settings
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
import { getSetting, setSetting, getAllSettings, deleteSetting } from '@vox-ai-app/storage/settings'
|
|
100
|
+
|
|
101
|
+
setSetting(db, 'theme', 'dark')
|
|
102
|
+
const theme = getSetting(db, 'theme')
|
|
103
|
+
const all = getAllSettings(db)
|
|
104
|
+
deleteSetting(db, 'theme')
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vox-ai-app/storage",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Local message and config persistence for Vox",
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
"exports": {
|
|
10
10
|
".": "./src/index.js",
|
|
11
11
|
"./db": "./src/db.js",
|
|
12
|
-
"./messages": "./src/messages.js",
|
|
13
|
-
"./
|
|
14
|
-
"./
|
|
12
|
+
"./messages": "./src/repos/messages.js",
|
|
13
|
+
"./tasks": "./src/repos/tasks.js",
|
|
14
|
+
"./tools": "./src/repos/tools.js",
|
|
15
|
+
"./settings": "./src/repos/settings.js",
|
|
16
|
+
"./mcp-servers": "./src/repos/mcp-servers.js",
|
|
17
|
+
"./schedules": "./src/repos/schedules.js",
|
|
18
|
+
"./tool-secrets": "./src/repos/tool-secrets.js",
|
|
19
|
+
"./patterns": "./src/repos/patterns.js",
|
|
20
|
+
"./vectors": "./src/repos/vectors.js"
|
|
15
21
|
},
|
|
16
22
|
"publishConfig": {
|
|
17
23
|
"access": "public",
|
package/src/db.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import Database from 'better-sqlite3'
|
|
2
2
|
import { mkdirSync } from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
|
+
import { runMigrations } from './migrations/runner.js'
|
|
5
|
+
import * as initialSchema from './migrations/001_initial_schema.js'
|
|
6
|
+
import * as taskActivityTypes from './migrations/002_task_activity_types.js'
|
|
4
7
|
|
|
5
8
|
const dbs = new Map()
|
|
6
9
|
|
|
10
|
+
const migrations = [initialSchema, taskActivityTypes]
|
|
11
|
+
|
|
7
12
|
function resolveDbPath(dbPath) {
|
|
8
13
|
const normalized = String(dbPath || '').trim()
|
|
9
14
|
if (!normalized) {
|
|
@@ -11,116 +16,10 @@ function resolveDbPath(dbPath) {
|
|
|
11
16
|
}
|
|
12
17
|
return path.resolve(normalized)
|
|
13
18
|
}
|
|
14
|
-
|
|
15
19
|
function prepareDb(db) {
|
|
16
20
|
db.pragma('journal_mode = WAL')
|
|
17
21
|
db.pragma('foreign_keys = ON')
|
|
18
|
-
|
|
19
|
-
db.exec(`
|
|
20
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
21
|
-
id TEXT PRIMARY KEY,
|
|
22
|
-
created_at TEXT NOT NULL,
|
|
23
|
-
updated_at TEXT NOT NULL
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
CREATE TABLE IF NOT EXISTS messages (
|
|
27
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
28
|
-
conversation_id TEXT NOT NULL,
|
|
29
|
-
role TEXT NOT NULL,
|
|
30
|
-
content TEXT NOT NULL,
|
|
31
|
-
created_at TEXT NOT NULL,
|
|
32
|
-
FOREIGN KEY (conversation_id) REFERENCES conversations(id) ON DELETE CASCADE
|
|
33
|
-
);
|
|
34
|
-
|
|
35
|
-
CREATE INDEX IF NOT EXISTS idx_messages_conversation_id_id
|
|
36
|
-
ON messages (conversation_id, id);
|
|
37
|
-
|
|
38
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
39
|
-
task_id TEXT PRIMARY KEY,
|
|
40
|
-
instructions TEXT NOT NULL DEFAULT '',
|
|
41
|
-
context TEXT NOT NULL DEFAULT '',
|
|
42
|
-
status TEXT NOT NULL DEFAULT 'queued',
|
|
43
|
-
created_at TEXT NOT NULL,
|
|
44
|
-
updated_at TEXT NOT NULL,
|
|
45
|
-
current_plan TEXT NOT NULL DEFAULT '',
|
|
46
|
-
message TEXT NOT NULL DEFAULT '',
|
|
47
|
-
result TEXT,
|
|
48
|
-
completed_at TEXT NOT NULL DEFAULT '',
|
|
49
|
-
failed_at TEXT NOT NULL DEFAULT ''
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_created_at
|
|
53
|
-
ON tasks (created_at DESC, task_id DESC);
|
|
54
|
-
|
|
55
|
-
CREATE TABLE IF NOT EXISTS task_activity (
|
|
56
|
-
id TEXT PRIMARY KEY,
|
|
57
|
-
task_id TEXT NOT NULL,
|
|
58
|
-
type TEXT NOT NULL,
|
|
59
|
-
name TEXT,
|
|
60
|
-
raw_result TEXT,
|
|
61
|
-
timestamp TEXT NOT NULL,
|
|
62
|
-
data TEXT NOT NULL DEFAULT '{}',
|
|
63
|
-
FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
CREATE INDEX IF NOT EXISTS idx_task_activity_timestamp
|
|
67
|
-
ON task_activity (timestamp ASC, id ASC);
|
|
68
|
-
|
|
69
|
-
CREATE INDEX IF NOT EXISTS idx_task_activity_task_id
|
|
70
|
-
ON task_activity (task_id, timestamp ASC, id ASC);
|
|
71
|
-
`)
|
|
72
|
-
|
|
73
|
-
try {
|
|
74
|
-
db.exec(`ALTER TABLE tasks ADD COLUMN reported INTEGER NOT NULL DEFAULT 0`)
|
|
75
|
-
} catch {
|
|
76
|
-
/* */
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
db.exec(`ALTER TABLE conversations ADD COLUMN context_summary TEXT`)
|
|
81
|
-
} catch {
|
|
82
|
-
/* */
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
db.exec(`ALTER TABLE conversations ADD COLUMN context_checkpoint_id INTEGER`)
|
|
87
|
-
} catch {
|
|
88
|
-
/* */
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
db.exec(`
|
|
93
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
94
|
-
task_id UNINDEXED,
|
|
95
|
-
instructions,
|
|
96
|
-
result,
|
|
97
|
-
tokenize = 'unicode61'
|
|
98
|
-
)
|
|
99
|
-
`)
|
|
100
|
-
} catch {
|
|
101
|
-
/* table may already exist */
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
db.exec(`
|
|
106
|
-
CREATE TABLE IF NOT EXISTS knowledge_patterns (
|
|
107
|
-
id TEXT PRIMARY KEY,
|
|
108
|
-
trigger TEXT NOT NULL,
|
|
109
|
-
solution TEXT NOT NULL,
|
|
110
|
-
created_at TEXT NOT NULL
|
|
111
|
-
)
|
|
112
|
-
`)
|
|
113
|
-
db.exec(`
|
|
114
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS patterns_fts USING fts5(
|
|
115
|
-
pattern_id UNINDEXED,
|
|
116
|
-
trigger,
|
|
117
|
-
solution,
|
|
118
|
-
tokenize = 'unicode61'
|
|
119
|
-
)
|
|
120
|
-
`)
|
|
121
|
-
} catch {
|
|
122
|
-
/* tables may already exist */
|
|
123
|
-
}
|
|
22
|
+
runMigrations(db, migrations)
|
|
124
23
|
}
|
|
125
24
|
|
|
126
25
|
export function openDb(dbPath) {
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
export * from './db.js'
|
|
2
|
-
export * from './messages.js'
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
2
|
+
export * from './repos/messages.js'
|
|
3
|
+
export * from './repos/tasks.js'
|
|
4
|
+
export * from './repos/tools.js'
|
|
5
|
+
export * from './repos/settings.js'
|
|
6
|
+
export * from './repos/mcp-servers.js'
|
|
7
|
+
export * from './repos/schedules.js'
|
|
8
|
+
export * from './repos/tool-secrets.js'
|
|
9
|
+
export * from './repos/patterns.js'
|
|
10
|
+
export * from './repos/vectors.js'
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
export const name = '001_initial_schema'
|
|
2
|
+
|
|
3
|
+
export function up(db) {
|
|
4
|
+
db.exec(`
|
|
5
|
+
CREATE TABLE conversations (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
title TEXT,
|
|
8
|
+
user_info TEXT NOT NULL DEFAULT '{}',
|
|
9
|
+
context_summary TEXT,
|
|
10
|
+
context_checkpoint_id TEXT,
|
|
11
|
+
created_at TEXT NOT NULL,
|
|
12
|
+
updated_at TEXT NOT NULL
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
CREATE TABLE messages (
|
|
16
|
+
sort_order INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
17
|
+
id TEXT NOT NULL UNIQUE,
|
|
18
|
+
conversation_id TEXT NOT NULL REFERENCES conversations(id) ON DELETE CASCADE,
|
|
19
|
+
role TEXT NOT NULL CHECK(role IN ('user','assistant','system','tool','tool_result')),
|
|
20
|
+
content TEXT NOT NULL,
|
|
21
|
+
tokens INTEGER,
|
|
22
|
+
created_at TEXT NOT NULL,
|
|
23
|
+
updated_at TEXT NOT NULL
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX idx_messages_conversation
|
|
27
|
+
ON messages(conversation_id, sort_order);
|
|
28
|
+
|
|
29
|
+
CREATE TABLE tasks (
|
|
30
|
+
id TEXT PRIMARY KEY,
|
|
31
|
+
instructions TEXT NOT NULL DEFAULT '',
|
|
32
|
+
context TEXT NOT NULL DEFAULT '',
|
|
33
|
+
status TEXT NOT NULL DEFAULT 'queued'
|
|
34
|
+
CHECK(status IN ('queued','running','completed','incomplete','failed','aborted')),
|
|
35
|
+
current_plan TEXT NOT NULL DEFAULT '',
|
|
36
|
+
result TEXT,
|
|
37
|
+
error TEXT,
|
|
38
|
+
abort_reason TEXT,
|
|
39
|
+
provider TEXT,
|
|
40
|
+
model TEXT,
|
|
41
|
+
context_injected INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
created_at TEXT NOT NULL,
|
|
43
|
+
updated_at TEXT NOT NULL,
|
|
44
|
+
completed_at TEXT
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE INDEX idx_tasks_created
|
|
48
|
+
ON tasks(created_at DESC, id DESC);
|
|
49
|
+
|
|
50
|
+
CREATE INDEX idx_tasks_status
|
|
51
|
+
ON tasks(status, created_at DESC);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE task_activity (
|
|
54
|
+
id TEXT PRIMARY KEY,
|
|
55
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
56
|
+
type TEXT NOT NULL CHECK(type IN ('tool','spawn','success','error','info','step')),
|
|
57
|
+
name TEXT,
|
|
58
|
+
result TEXT,
|
|
59
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
60
|
+
created_at TEXT NOT NULL
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
CREATE INDEX idx_task_activity_task
|
|
64
|
+
ON task_activity(task_id, created_at ASC, id ASC);
|
|
65
|
+
|
|
66
|
+
CREATE VIRTUAL TABLE tasks_fts USING fts5(
|
|
67
|
+
task_id UNINDEXED,
|
|
68
|
+
instructions,
|
|
69
|
+
result,
|
|
70
|
+
tokenize = 'unicode61'
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
CREATE TRIGGER tasks_fts_insert AFTER INSERT ON tasks BEGIN
|
|
74
|
+
INSERT INTO tasks_fts(task_id, instructions, result)
|
|
75
|
+
VALUES (NEW.id, NEW.instructions, COALESCE(NEW.result, ''));
|
|
76
|
+
END;
|
|
77
|
+
|
|
78
|
+
CREATE TRIGGER tasks_fts_update AFTER UPDATE OF instructions, result ON tasks BEGIN
|
|
79
|
+
DELETE FROM tasks_fts WHERE task_id = OLD.id;
|
|
80
|
+
INSERT INTO tasks_fts(task_id, instructions, result)
|
|
81
|
+
VALUES (NEW.id, NEW.instructions, COALESCE(NEW.result, ''));
|
|
82
|
+
END;
|
|
83
|
+
|
|
84
|
+
CREATE TRIGGER tasks_fts_delete AFTER DELETE ON tasks BEGIN
|
|
85
|
+
DELETE FROM tasks_fts WHERE task_id = OLD.id;
|
|
86
|
+
END;
|
|
87
|
+
|
|
88
|
+
CREATE VIRTUAL TABLE messages_fts USING fts5(
|
|
89
|
+
message_id UNINDEXED,
|
|
90
|
+
role UNINDEXED,
|
|
91
|
+
content,
|
|
92
|
+
tokenize = 'unicode61'
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
CREATE TRIGGER messages_fts_insert AFTER INSERT ON messages BEGIN
|
|
96
|
+
INSERT INTO messages_fts(message_id, role, content)
|
|
97
|
+
VALUES (NEW.id, NEW.role, NEW.content);
|
|
98
|
+
END;
|
|
99
|
+
|
|
100
|
+
CREATE TRIGGER messages_fts_update AFTER UPDATE OF content ON messages BEGIN
|
|
101
|
+
DELETE FROM messages_fts WHERE message_id = OLD.id;
|
|
102
|
+
INSERT INTO messages_fts(message_id, role, content)
|
|
103
|
+
VALUES (NEW.id, NEW.role, NEW.content);
|
|
104
|
+
END;
|
|
105
|
+
|
|
106
|
+
CREATE TRIGGER messages_fts_delete AFTER DELETE ON messages BEGIN
|
|
107
|
+
DELETE FROM messages_fts WHERE message_id = OLD.id;
|
|
108
|
+
END;
|
|
109
|
+
|
|
110
|
+
CREATE TABLE tools (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
name TEXT NOT NULL UNIQUE CHECK(length(name) BETWEEN 1 AND 64),
|
|
113
|
+
description TEXT NOT NULL DEFAULT '',
|
|
114
|
+
parameters TEXT NOT NULL DEFAULT '{"type":"object","properties":{}}',
|
|
115
|
+
source_type TEXT NOT NULL CHECK(source_type IN ('js_function','http_webhook','desktop')),
|
|
116
|
+
source_code TEXT,
|
|
117
|
+
webhook_url TEXT,
|
|
118
|
+
webhook_headers TEXT NOT NULL DEFAULT '{}',
|
|
119
|
+
is_enabled INTEGER NOT NULL DEFAULT 1,
|
|
120
|
+
tags TEXT NOT NULL DEFAULT '[]',
|
|
121
|
+
version INTEGER NOT NULL DEFAULT 1,
|
|
122
|
+
created_at TEXT NOT NULL,
|
|
123
|
+
updated_at TEXT NOT NULL
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
CREATE INDEX idx_tools_enabled
|
|
127
|
+
ON tools(is_enabled) WHERE is_enabled = 1;
|
|
128
|
+
|
|
129
|
+
CREATE TABLE tool_secrets (
|
|
130
|
+
id TEXT PRIMARY KEY,
|
|
131
|
+
tool_id TEXT NOT NULL REFERENCES tools(id) ON DELETE CASCADE,
|
|
132
|
+
key TEXT NOT NULL,
|
|
133
|
+
encrypted_value TEXT NOT NULL,
|
|
134
|
+
created_at TEXT NOT NULL,
|
|
135
|
+
updated_at TEXT NOT NULL,
|
|
136
|
+
UNIQUE(tool_id, key)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
CREATE INDEX idx_tool_secrets_tool
|
|
140
|
+
ON tool_secrets(tool_id);
|
|
141
|
+
|
|
142
|
+
CREATE TABLE mcp_servers (
|
|
143
|
+
id TEXT PRIMARY KEY,
|
|
144
|
+
name TEXT NOT NULL,
|
|
145
|
+
transport TEXT NOT NULL CHECK(transport IN ('stdio','sse','http')),
|
|
146
|
+
command TEXT,
|
|
147
|
+
args TEXT NOT NULL DEFAULT '[]',
|
|
148
|
+
url TEXT,
|
|
149
|
+
env TEXT NOT NULL DEFAULT '{}',
|
|
150
|
+
is_enabled INTEGER NOT NULL DEFAULT 1,
|
|
151
|
+
created_at TEXT NOT NULL,
|
|
152
|
+
updated_at TEXT NOT NULL
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
CREATE TABLE settings (
|
|
156
|
+
key TEXT PRIMARY KEY,
|
|
157
|
+
value TEXT NOT NULL,
|
|
158
|
+
updated_at TEXT NOT NULL
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
CREATE TABLE schedules (
|
|
162
|
+
id TEXT PRIMARY KEY,
|
|
163
|
+
cron_expr TEXT NOT NULL,
|
|
164
|
+
timezone TEXT,
|
|
165
|
+
prompt TEXT NOT NULL,
|
|
166
|
+
channel TEXT,
|
|
167
|
+
is_enabled INTEGER NOT NULL DEFAULT 1,
|
|
168
|
+
once INTEGER NOT NULL DEFAULT 0,
|
|
169
|
+
created_at TEXT NOT NULL,
|
|
170
|
+
updated_at TEXT NOT NULL
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
CREATE TABLE vectors (
|
|
174
|
+
id TEXT NOT NULL,
|
|
175
|
+
collection TEXT NOT NULL,
|
|
176
|
+
embedding BLOB NOT NULL,
|
|
177
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
178
|
+
created_at TEXT NOT NULL,
|
|
179
|
+
updated_at TEXT NOT NULL,
|
|
180
|
+
PRIMARY KEY (id, collection)
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
CREATE INDEX idx_vectors_collection
|
|
184
|
+
ON vectors(collection);
|
|
185
|
+
|
|
186
|
+
CREATE TABLE knowledge_patterns (
|
|
187
|
+
id TEXT PRIMARY KEY,
|
|
188
|
+
task_id TEXT REFERENCES tasks(id) ON DELETE SET NULL,
|
|
189
|
+
trigger TEXT NOT NULL,
|
|
190
|
+
solution TEXT NOT NULL,
|
|
191
|
+
created_at TEXT NOT NULL,
|
|
192
|
+
updated_at TEXT NOT NULL
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
CREATE VIRTUAL TABLE patterns_fts USING fts5(
|
|
196
|
+
pattern_id UNINDEXED,
|
|
197
|
+
trigger,
|
|
198
|
+
solution,
|
|
199
|
+
tokenize = 'unicode61'
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
CREATE TRIGGER patterns_fts_insert AFTER INSERT ON knowledge_patterns BEGIN
|
|
203
|
+
INSERT INTO patterns_fts(pattern_id, trigger, solution)
|
|
204
|
+
VALUES (NEW.id, NEW.trigger, NEW.solution);
|
|
205
|
+
END;
|
|
206
|
+
|
|
207
|
+
CREATE TRIGGER patterns_fts_update AFTER UPDATE OF trigger, solution ON knowledge_patterns BEGIN
|
|
208
|
+
DELETE FROM patterns_fts WHERE pattern_id = OLD.id;
|
|
209
|
+
INSERT INTO patterns_fts(pattern_id, trigger, solution)
|
|
210
|
+
VALUES (NEW.id, NEW.trigger, NEW.solution);
|
|
211
|
+
END;
|
|
212
|
+
|
|
213
|
+
CREATE TRIGGER patterns_fts_delete AFTER DELETE ON knowledge_patterns BEGIN
|
|
214
|
+
DELETE FROM patterns_fts WHERE pattern_id = OLD.id;
|
|
215
|
+
END;
|
|
216
|
+
`)
|
|
217
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export const name = '002_task_activity_types'
|
|
2
|
+
|
|
3
|
+
export function up(db) {
|
|
4
|
+
db.exec(`
|
|
5
|
+
CREATE TABLE task_activity_new (
|
|
6
|
+
id TEXT PRIMARY KEY,
|
|
7
|
+
task_id TEXT NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
|
8
|
+
type TEXT NOT NULL CHECK(type IN (
|
|
9
|
+
'tool_call','tool_result','text','thought',
|
|
10
|
+
'journal','spawn','error'
|
|
11
|
+
)),
|
|
12
|
+
name TEXT,
|
|
13
|
+
args TEXT,
|
|
14
|
+
result TEXT,
|
|
15
|
+
plan TEXT,
|
|
16
|
+
data TEXT NOT NULL DEFAULT '{}',
|
|
17
|
+
created_at TEXT NOT NULL
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
INSERT INTO task_activity_new (id, task_id, type, name, args, result, plan, data, created_at)
|
|
21
|
+
SELECT
|
|
22
|
+
id,
|
|
23
|
+
task_id,
|
|
24
|
+
CASE type
|
|
25
|
+
WHEN 'tool' THEN 'tool_call'
|
|
26
|
+
WHEN 'step' THEN 'journal'
|
|
27
|
+
WHEN 'info' THEN 'text'
|
|
28
|
+
WHEN 'success' THEN 'text'
|
|
29
|
+
ELSE type
|
|
30
|
+
END,
|
|
31
|
+
name,
|
|
32
|
+
NULL,
|
|
33
|
+
result,
|
|
34
|
+
NULL,
|
|
35
|
+
data,
|
|
36
|
+
created_at
|
|
37
|
+
FROM task_activity;
|
|
38
|
+
|
|
39
|
+
DROP TABLE task_activity;
|
|
40
|
+
ALTER TABLE task_activity_new RENAME TO task_activity;
|
|
41
|
+
|
|
42
|
+
CREATE INDEX idx_task_activity_task
|
|
43
|
+
ON task_activity(task_id, created_at ASC, id ASC);
|
|
44
|
+
`)
|
|
45
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function runMigrations(db, migrations) {
|
|
2
|
+
db.exec(`
|
|
3
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
4
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
5
|
+
name TEXT NOT NULL UNIQUE,
|
|
6
|
+
applied_at TEXT NOT NULL
|
|
7
|
+
)
|
|
8
|
+
`)
|
|
9
|
+
|
|
10
|
+
const applied = new Set(
|
|
11
|
+
db
|
|
12
|
+
.prepare('SELECT name FROM _migrations')
|
|
13
|
+
.all()
|
|
14
|
+
.map((r) => r.name)
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
const pending = migrations
|
|
18
|
+
.slice()
|
|
19
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
20
|
+
.filter((m) => !applied.has(m.name))
|
|
21
|
+
|
|
22
|
+
for (const migration of pending) {
|
|
23
|
+
db.transaction(() => {
|
|
24
|
+
migration.up(db)
|
|
25
|
+
db.prepare('INSERT INTO _migrations (name, applied_at) VALUES (?, ?)').run(
|
|
26
|
+
migration.name,
|
|
27
|
+
new Date().toISOString()
|
|
28
|
+
)
|
|
29
|
+
})()
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto'
|
|
2
|
+
|
|
3
|
+
function mapServer(row) {
|
|
4
|
+
if (!row) return null
|
|
5
|
+
return {
|
|
6
|
+
id: row.id,
|
|
7
|
+
name: row.name,
|
|
8
|
+
transport: row.transport,
|
|
9
|
+
command: row.command || null,
|
|
10
|
+
args: JSON.parse(row.args),
|
|
11
|
+
url: row.url || null,
|
|
12
|
+
env: JSON.parse(row.env),
|
|
13
|
+
isEnabled: !!row.is_enabled,
|
|
14
|
+
createdAt: row.created_at,
|
|
15
|
+
updatedAt: row.updated_at
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function createMcpServer(db, server) {
|
|
20
|
+
const id = server.id || randomUUID()
|
|
21
|
+
const now = new Date().toISOString()
|
|
22
|
+
|
|
23
|
+
db.prepare(
|
|
24
|
+
`INSERT INTO mcp_servers (id, name, transport, command, args, url, env, is_enabled, created_at, updated_at)
|
|
25
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
26
|
+
).run(
|
|
27
|
+
id,
|
|
28
|
+
String(server.name),
|
|
29
|
+
String(server.transport || 'stdio'),
|
|
30
|
+
server.command || null,
|
|
31
|
+
JSON.stringify(server.args || []),
|
|
32
|
+
server.url || null,
|
|
33
|
+
JSON.stringify(server.env || {}),
|
|
34
|
+
server.isEnabled === false ? 0 : 1,
|
|
35
|
+
now,
|
|
36
|
+
now
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
return getMcpServer(db, id)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function updateMcpServer(db, id, updates) {
|
|
43
|
+
const existing = getMcpServer(db, id)
|
|
44
|
+
if (!existing) return null
|
|
45
|
+
|
|
46
|
+
const now = new Date().toISOString()
|
|
47
|
+
const merged = { ...existing, ...updates }
|
|
48
|
+
|
|
49
|
+
db.prepare(
|
|
50
|
+
`UPDATE mcp_servers SET name = ?, transport = ?, command = ?, args = ?, url = ?,
|
|
51
|
+
env = ?, is_enabled = ?, updated_at = ?
|
|
52
|
+
WHERE id = ?`
|
|
53
|
+
).run(
|
|
54
|
+
String(merged.name),
|
|
55
|
+
String(merged.transport || 'stdio'),
|
|
56
|
+
merged.command || null,
|
|
57
|
+
JSON.stringify(merged.args || []),
|
|
58
|
+
merged.url || null,
|
|
59
|
+
JSON.stringify(merged.env || {}),
|
|
60
|
+
merged.isEnabled === false ? 0 : 1,
|
|
61
|
+
now,
|
|
62
|
+
id
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return getMcpServer(db, id)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function deleteMcpServer(db, id) {
|
|
69
|
+
return db.prepare(`DELETE FROM mcp_servers WHERE id = ?`).run(id)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getMcpServer(db, id) {
|
|
73
|
+
return mapServer(db.prepare(`SELECT * FROM mcp_servers WHERE id = ?`).get(id))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function listMcpServers(db, enabledOnly = false) {
|
|
77
|
+
if (enabledOnly) {
|
|
78
|
+
return db.prepare(`SELECT * FROM mcp_servers WHERE is_enabled = 1`).all().map(mapServer)
|
|
79
|
+
}
|
|
80
|
+
return db.prepare(`SELECT * FROM mcp_servers ORDER BY name ASC`).all().map(mapServer)
|
|
81
|
+
}
|