@controlflow-ai/daemon 0.1.2 → 0.1.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 +54 -6
- package/package.json +3 -1
- package/src/agent-avatar.ts +30 -0
- package/src/agent-key.ts +28 -0
- package/src/agent-permissions.ts +359 -0
- package/src/agent-runtime.ts +795 -28
- package/src/agent-workspace.ts +183 -0
- package/src/app.ts +1970 -79
- package/src/args.ts +54 -7
- package/src/cli.ts +873 -14
- package/src/client.ts +472 -10
- package/src/coco.ts +9 -40
- package/src/codex.ts +33 -5
- package/src/config.ts +28 -4
- package/src/console.ts +230 -20
- package/src/daemon-client.ts +116 -3
- package/src/daemon.ts +936 -98
- package/src/db.ts +3128 -122
- package/src/delivery-ws.ts +269 -0
- package/src/format.ts +4 -1
- package/src/lark/cli.ts +3 -3
- package/src/lark/event-router.ts +60 -4
- package/src/lark/inbound-events.ts +156 -3
- package/src/lark/server-integration.ts +659 -111
- package/src/lark/ws-daemon.ts +136 -10
- package/src/local-api.ts +545 -15
- package/src/local-auth.ts +33 -1
- package/src/message-attachments.ts +71 -0
- package/src/messaging-cli.ts +741 -0
- package/src/messaging-status.ts +669 -0
- package/src/migrations/024_agents_model.ts +10 -0
- package/src/migrations/025_room_archive.ts +44 -0
- package/src/migrations/026_project_archive.ts +44 -0
- package/src/migrations/027_agent_permission_profiles.ts +16 -0
- package/src/migrations/028_lark_websocket_restart_state.ts +16 -0
- package/src/migrations/029_held_message_drafts.ts +32 -0
- package/src/migrations/030_agent_room_read_state.ts +25 -0
- package/src/migrations/031_room_tasks.ts +29 -0
- package/src/migrations/032_room_reminders.ts +29 -0
- package/src/migrations/033_room_saved_messages.ts +25 -0
- package/src/migrations/034_agent_activity_events.ts +27 -0
- package/src/migrations/035_agent_avatars.ts +17 -0
- package/src/migrations/036_project_agent_defaults.ts +21 -0
- package/src/migrations/037_message_attachments.ts +36 -0
- package/src/migrations/038_agent_activity_room_scope.ts +64 -0
- package/src/migrations/039_message_attachments_path.ts +34 -0
- package/src/migrations/040_message_attachments_file_schema.ts +80 -0
- package/src/migrations/041_room_system_events.ts +30 -0
- package/src/migrations/042_message_attachment_file_kind.ts +52 -0
- package/src/migrations/043_room_mode_skill_registry.ts +92 -0
- package/src/migrations/044_workflow_runtime.ts +69 -0
- package/src/migrations/045_skill_repository_ownership.ts +64 -0
- package/src/migrations.ts +69 -1
- package/src/neeko.ts +40 -4
- package/src/runtime-env.ts +179 -0
- package/src/runtime-registry.ts +83 -13
- package/src/server.ts +244 -4
- package/src/token-file.ts +13 -6
- package/src/types.ts +362 -0
- package/src/workflow-runtime.ts +275 -0
- package/src/web.ts +0 -904
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 25;
|
|
4
|
+
export const name = 'room_archive';
|
|
5
|
+
|
|
6
|
+
function hasColumn(db: Database, table: string, column: string): boolean {
|
|
7
|
+
const rows = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
8
|
+
return rows.some((row) => row.name === column);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function up(db: Database): void {
|
|
12
|
+
if (!hasColumn(db, 'chats', 'status')) {
|
|
13
|
+
db.exec(`ALTER TABLE chats ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived'))`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
db.exec(`
|
|
17
|
+
DROP VIEW IF EXISTS chat_stats;
|
|
18
|
+
CREATE VIEW chat_stats AS
|
|
19
|
+
SELECT
|
|
20
|
+
c.id,
|
|
21
|
+
c.name,
|
|
22
|
+
c.display_name,
|
|
23
|
+
c.kind,
|
|
24
|
+
c.server_id,
|
|
25
|
+
c.provider,
|
|
26
|
+
c.dm_type,
|
|
27
|
+
c.capabilities_json,
|
|
28
|
+
c.audit_visibility,
|
|
29
|
+
c.status,
|
|
30
|
+
c.project_id,
|
|
31
|
+
p.name AS project_name,
|
|
32
|
+
p.root_path AS project_root_path,
|
|
33
|
+
p.computer_id AS project_computer_id,
|
|
34
|
+
pc.name AS project_computer_name,
|
|
35
|
+
c.created_at,
|
|
36
|
+
COUNT(m.id) AS message_count,
|
|
37
|
+
MAX(m.created_at) AS last_message_at
|
|
38
|
+
FROM chats c
|
|
39
|
+
LEFT JOIN projects p ON p.id = c.project_id
|
|
40
|
+
LEFT JOIN computers pc ON pc.id = p.computer_id
|
|
41
|
+
LEFT JOIN messages m ON m.chat_id = c.id
|
|
42
|
+
GROUP BY c.id;
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 26;
|
|
4
|
+
export const name = 'project_archive';
|
|
5
|
+
|
|
6
|
+
function hasColumn(db: Database, table: string, column: string): boolean {
|
|
7
|
+
const rows = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
8
|
+
return rows.some((row) => row.name === column);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function up(db: Database): void {
|
|
12
|
+
if (!hasColumn(db, 'projects', 'status')) {
|
|
13
|
+
db.exec(`ALTER TABLE projects ADD COLUMN status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'archived'))`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
db.exec(`
|
|
17
|
+
DROP VIEW IF EXISTS chat_stats;
|
|
18
|
+
CREATE VIEW chat_stats AS
|
|
19
|
+
SELECT
|
|
20
|
+
c.id,
|
|
21
|
+
c.name,
|
|
22
|
+
c.display_name,
|
|
23
|
+
c.kind,
|
|
24
|
+
c.server_id,
|
|
25
|
+
c.provider,
|
|
26
|
+
c.dm_type,
|
|
27
|
+
c.capabilities_json,
|
|
28
|
+
c.audit_visibility,
|
|
29
|
+
c.status,
|
|
30
|
+
c.project_id,
|
|
31
|
+
p.name AS project_name,
|
|
32
|
+
p.root_path AS project_root_path,
|
|
33
|
+
p.computer_id AS project_computer_id,
|
|
34
|
+
pc.name AS project_computer_name,
|
|
35
|
+
c.created_at,
|
|
36
|
+
COUNT(m.id) AS message_count,
|
|
37
|
+
MAX(m.created_at) AS last_message_at
|
|
38
|
+
FROM chats c
|
|
39
|
+
LEFT JOIN projects p ON p.id = c.project_id
|
|
40
|
+
LEFT JOIN computers pc ON pc.id = p.computer_id
|
|
41
|
+
LEFT JOIN messages m ON m.chat_id = c.id
|
|
42
|
+
GROUP BY c.id;
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 27;
|
|
4
|
+
export const name = 'agent_permission_profiles';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS agent_permission_profiles (
|
|
9
|
+
agent_key TEXT PRIMARY KEY REFERENCES agents(agent_key) ON DELETE CASCADE,
|
|
10
|
+
filesystem_mode TEXT NOT NULL DEFAULT 'project-write' CHECK (filesystem_mode IN ('project-write', 'scoped-write', 'full-access')),
|
|
11
|
+
extra_writable_roots_json TEXT NOT NULL DEFAULT '[]',
|
|
12
|
+
created_at TEXT NOT NULL,
|
|
13
|
+
updated_at TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 28;
|
|
4
|
+
export const name = 'lark_websocket_restart_state';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS lark_websocket_restart_state (
|
|
9
|
+
app_id TEXT PRIMARY KEY,
|
|
10
|
+
restart_count INTEGER NOT NULL DEFAULT 0 CHECK (restart_count >= 0),
|
|
11
|
+
last_restart_at TEXT,
|
|
12
|
+
last_restart_reason TEXT,
|
|
13
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
14
|
+
);
|
|
15
|
+
`);
|
|
16
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 29;
|
|
4
|
+
export const name = 'held_message_drafts';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS held_message_drafts (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
chat_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
11
|
+
agent TEXT NOT NULL,
|
|
12
|
+
sender TEXT NOT NULL,
|
|
13
|
+
content TEXT NOT NULL,
|
|
14
|
+
mentions_json TEXT NOT NULL DEFAULT '[]',
|
|
15
|
+
base_message_id INTEGER NOT NULL,
|
|
16
|
+
latest_message_id_at_hold INTEGER NOT NULL,
|
|
17
|
+
status TEXT NOT NULL CHECK (status IN ('held', 'abandoned', 'sent_anyway')),
|
|
18
|
+
hold_reason TEXT NOT NULL,
|
|
19
|
+
hold_count INTEGER NOT NULL DEFAULT 1 CHECK (hold_count >= 1),
|
|
20
|
+
resolved_message_id INTEGER REFERENCES messages(id) ON DELETE SET NULL,
|
|
21
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
22
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
23
|
+
resolved_at TEXT
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_held_message_drafts_agent_status_created
|
|
27
|
+
ON held_message_drafts(agent, status, created_at);
|
|
28
|
+
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_held_message_drafts_chat_status
|
|
30
|
+
ON held_message_drafts(chat_id, status);
|
|
31
|
+
`);
|
|
32
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 30;
|
|
4
|
+
export const name = 'agent_room_read_state';
|
|
5
|
+
|
|
6
|
+
function hasColumn(db: Database, table: string, column: string): boolean {
|
|
7
|
+
const rows = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
8
|
+
return rows.some((row) => row.name === column);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function addColumnIfMissing(db: Database, table: string, column: string, ddl: string): void {
|
|
12
|
+
if (!hasColumn(db, table, column)) {
|
|
13
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${ddl}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function up(db: Database): void {
|
|
18
|
+
addColumnIfMissing(db, 'agent_room_subscriptions', 'last_read_message_id', 'last_read_message_id INTEGER NOT NULL DEFAULT 0');
|
|
19
|
+
addColumnIfMissing(db, 'agent_room_subscriptions', 'last_read_at', 'last_read_at TEXT');
|
|
20
|
+
|
|
21
|
+
db.exec(`
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_agent_room_subscriptions_read_state
|
|
23
|
+
ON agent_room_subscriptions(agent, room_id, last_read_message_id);
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 31;
|
|
4
|
+
export const name = 'room_tasks';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS room_tasks (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
room_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
11
|
+
task_number INTEGER NOT NULL,
|
|
12
|
+
title TEXT NOT NULL,
|
|
13
|
+
status TEXT NOT NULL DEFAULT 'todo' CHECK (status IN ('todo', 'in_progress', 'in_review', 'done', 'closed')),
|
|
14
|
+
assignee TEXT,
|
|
15
|
+
created_by TEXT,
|
|
16
|
+
source_message_id INTEGER REFERENCES messages(id) ON DELETE SET NULL,
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
18
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
19
|
+
completed_at TEXT,
|
|
20
|
+
UNIQUE(room_id, task_number)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_room_tasks_room_status
|
|
24
|
+
ON room_tasks(room_id, status, task_number);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_room_tasks_assignee_status
|
|
27
|
+
ON room_tasks(assignee, status, updated_at);
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 32;
|
|
4
|
+
export const name = 'room_reminders';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS room_reminders (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
room_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
11
|
+
source_message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
12
|
+
title TEXT NOT NULL,
|
|
13
|
+
status TEXT NOT NULL DEFAULT 'scheduled' CHECK (status IN ('scheduled', 'fired', 'canceled')),
|
|
14
|
+
created_by TEXT,
|
|
15
|
+
fire_at TEXT NOT NULL,
|
|
16
|
+
repeat TEXT,
|
|
17
|
+
fired_at TEXT,
|
|
18
|
+
canceled_at TEXT,
|
|
19
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
20
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_room_reminders_room_status_fire_at
|
|
24
|
+
ON room_reminders(room_id, status, fire_at);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_room_reminders_created_by_status_fire_at
|
|
27
|
+
ON room_reminders(created_by, status, fire_at);
|
|
28
|
+
`);
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 33;
|
|
4
|
+
export const name = 'room_saved_messages';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS room_saved_messages (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
room_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
11
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
12
|
+
saved_by TEXT NOT NULL,
|
|
13
|
+
note TEXT,
|
|
14
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
15
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
16
|
+
UNIQUE(message_id, saved_by)
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
CREATE INDEX IF NOT EXISTS idx_room_saved_messages_room_saved_by_created_at
|
|
20
|
+
ON room_saved_messages(room_id, saved_by, created_at);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_room_saved_messages_saved_by_created_at
|
|
23
|
+
ON room_saved_messages(saved_by, created_at);
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 34;
|
|
4
|
+
export const name = 'agent_activity_events';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS agent_activity_events (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
run_id TEXT NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,
|
|
11
|
+
session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
|
|
12
|
+
agent TEXT NOT NULL,
|
|
13
|
+
chat_id TEXT NOT NULL,
|
|
14
|
+
kind TEXT NOT NULL CHECK (kind IN ('lifecycle', 'working', 'thinking', 'output', 'tool', 'error')),
|
|
15
|
+
title TEXT NOT NULL,
|
|
16
|
+
detail TEXT NOT NULL DEFAULT '',
|
|
17
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
18
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
CREATE INDEX IF NOT EXISTS idx_agent_activity_events_run_created
|
|
22
|
+
ON agent_activity_events(run_id, datetime(created_at) DESC);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_agent_activity_events_agent_created
|
|
25
|
+
ON agent_activity_events(agent, datetime(created_at) DESC);
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
import { generateAgentAvatar } from '../agent-avatar.js';
|
|
3
|
+
|
|
4
|
+
export const version = 35;
|
|
5
|
+
export const name = 'agent_avatars';
|
|
6
|
+
|
|
7
|
+
export function up(db: Database): void {
|
|
8
|
+
db.exec(`
|
|
9
|
+
ALTER TABLE agents ADD COLUMN avatar TEXT NOT NULL DEFAULT 'sage';
|
|
10
|
+
`);
|
|
11
|
+
|
|
12
|
+
const rows = db.query('SELECT agent_key, display_name FROM agents').all() as Array<{ agent_key: string; display_name: string }>;
|
|
13
|
+
const update = db.query('UPDATE agents SET avatar = ? WHERE agent_key = ?');
|
|
14
|
+
for (const row of rows) {
|
|
15
|
+
update.run(generateAgentAvatar(row.agent_key, row.display_name), row.agent_key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 36;
|
|
4
|
+
export const name = 'project_agent_defaults';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS project_agent_defaults (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
|
11
|
+
agent TEXT NOT NULL,
|
|
12
|
+
mode TEXT NOT NULL DEFAULT 'mentions' CHECK (mode IN ('all', 'periodic', 'mentions', 'muted', 'off')),
|
|
13
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
14
|
+
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
15
|
+
UNIQUE(project_id, agent)
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX IF NOT EXISTS idx_project_agent_defaults_project
|
|
19
|
+
ON project_agent_defaults(project_id, agent);
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 37;
|
|
4
|
+
export const name = 'message_attachments';
|
|
5
|
+
|
|
6
|
+
function ensureColumn(db: Database, table: string, column: string, definition: string): void {
|
|
7
|
+
const columns = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
8
|
+
if (columns.some((row) => row.name === column)) return;
|
|
9
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function up(db: Database): void {
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS message_attachments (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
17
|
+
kind TEXT NOT NULL CHECK (kind IN ('image', 'file')),
|
|
18
|
+
mime_type TEXT NOT NULL,
|
|
19
|
+
filename TEXT NOT NULL DEFAULT '',
|
|
20
|
+
size_bytes INTEGER NOT NULL,
|
|
21
|
+
path TEXT NOT NULL,
|
|
22
|
+
source_provider TEXT,
|
|
23
|
+
source_ref TEXT,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
25
|
+
);
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
ensureColumn(db, 'message_attachments', 'path', "TEXT NOT NULL DEFAULT ''");
|
|
29
|
+
ensureColumn(db, 'message_attachments', 'source_provider', 'TEXT');
|
|
30
|
+
ensureColumn(db, 'message_attachments', 'source_ref', 'TEXT');
|
|
31
|
+
ensureColumn(db, 'message_attachments', 'created_at', "TEXT NOT NULL DEFAULT (datetime('now'))");
|
|
32
|
+
|
|
33
|
+
db.exec(`
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id, id);
|
|
35
|
+
`);
|
|
36
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 38;
|
|
4
|
+
export const name = 'agent_activity_room_scope';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
const columns = db.query('PRAGMA table_info(agent_activity_events)').all() as Array<{ name: string }>;
|
|
8
|
+
const hasRoomId = columns.some((column) => column.name === 'room_id');
|
|
9
|
+
const hasChatId = columns.some((column) => column.name === 'chat_id');
|
|
10
|
+
|
|
11
|
+
if (hasChatId) {
|
|
12
|
+
const roomIdExpression = hasRoomId ? "COALESCE(NULLIF(room_id, ''), chat_id)" : 'chat_id';
|
|
13
|
+
db.exec(`
|
|
14
|
+
DROP INDEX IF EXISTS idx_agent_activity_events_run_created;
|
|
15
|
+
DROP INDEX IF EXISTS idx_agent_activity_events_agent_created;
|
|
16
|
+
DROP INDEX IF EXISTS idx_agent_activity_events_room_agent_created;
|
|
17
|
+
|
|
18
|
+
CREATE TABLE agent_activity_events_room_scoped (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
run_id TEXT NOT NULL REFERENCES agent_runs(id) ON DELETE CASCADE,
|
|
21
|
+
session_id TEXT REFERENCES agent_sessions(id) ON DELETE SET NULL,
|
|
22
|
+
agent TEXT NOT NULL,
|
|
23
|
+
room_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
24
|
+
kind TEXT NOT NULL CHECK (kind IN ('lifecycle', 'working', 'thinking', 'output', 'tool', 'error')),
|
|
25
|
+
title TEXT NOT NULL,
|
|
26
|
+
detail TEXT NOT NULL DEFAULT '',
|
|
27
|
+
metadata TEXT NOT NULL DEFAULT '{}',
|
|
28
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
INSERT INTO agent_activity_events_room_scoped (
|
|
32
|
+
id, run_id, session_id, agent, room_id, kind, title, detail, metadata, created_at
|
|
33
|
+
)
|
|
34
|
+
SELECT
|
|
35
|
+
id,
|
|
36
|
+
run_id,
|
|
37
|
+
session_id,
|
|
38
|
+
agent,
|
|
39
|
+
${roomIdExpression},
|
|
40
|
+
kind,
|
|
41
|
+
title,
|
|
42
|
+
detail,
|
|
43
|
+
metadata,
|
|
44
|
+
created_at
|
|
45
|
+
FROM agent_activity_events;
|
|
46
|
+
|
|
47
|
+
DROP TABLE agent_activity_events;
|
|
48
|
+
ALTER TABLE agent_activity_events_room_scoped RENAME TO agent_activity_events;
|
|
49
|
+
`);
|
|
50
|
+
} else if (!hasRoomId) {
|
|
51
|
+
db.exec('ALTER TABLE agent_activity_events ADD COLUMN room_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
db.exec(`
|
|
55
|
+
CREATE INDEX IF NOT EXISTS idx_agent_activity_events_run_created
|
|
56
|
+
ON agent_activity_events(run_id, datetime(created_at) DESC);
|
|
57
|
+
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_agent_activity_events_agent_created
|
|
59
|
+
ON agent_activity_events(agent, datetime(created_at) DESC);
|
|
60
|
+
|
|
61
|
+
CREATE INDEX IF NOT EXISTS idx_agent_activity_events_room_agent_created
|
|
62
|
+
ON agent_activity_events(room_id, agent, datetime(created_at) DESC);
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 39;
|
|
4
|
+
export const name = 'message_attachments_path';
|
|
5
|
+
|
|
6
|
+
function ensureColumn(db: Database, table: string, column: string, definition: string): void {
|
|
7
|
+
const columns = db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>;
|
|
8
|
+
if (columns.some((row) => row.name === column)) return;
|
|
9
|
+
db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function up(db: Database): void {
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS message_attachments (
|
|
15
|
+
id TEXT PRIMARY KEY,
|
|
16
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
17
|
+
kind TEXT NOT NULL CHECK (kind IN ('image', 'file')),
|
|
18
|
+
mime_type TEXT NOT NULL,
|
|
19
|
+
filename TEXT NOT NULL DEFAULT '',
|
|
20
|
+
size_bytes INTEGER NOT NULL,
|
|
21
|
+
path TEXT NOT NULL DEFAULT '',
|
|
22
|
+
source_provider TEXT,
|
|
23
|
+
source_ref TEXT,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
25
|
+
);
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
ensureColumn(db, 'message_attachments', 'path', "TEXT NOT NULL DEFAULT ''");
|
|
29
|
+
ensureColumn(db, 'message_attachments', 'source_provider', 'TEXT');
|
|
30
|
+
ensureColumn(db, 'message_attachments', 'source_ref', 'TEXT');
|
|
31
|
+
ensureColumn(db, 'message_attachments', 'created_at', "TEXT NOT NULL DEFAULT (datetime('now'))");
|
|
32
|
+
|
|
33
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id, id)');
|
|
34
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 40;
|
|
4
|
+
export const name = 'message_attachments_file_schema';
|
|
5
|
+
|
|
6
|
+
function tableColumns(db: Database, table: string): string[] {
|
|
7
|
+
return (db.query(`PRAGMA table_info(${table})`).all() as Array<{ name: string }>).map((row) => row.name);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function hasTable(db: Database, table: string): boolean {
|
|
11
|
+
const row = db.query("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?").get(table) as { name: string } | null;
|
|
12
|
+
return Boolean(row);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function up(db: Database): void {
|
|
16
|
+
if (!hasTable(db, 'message_attachments')) {
|
|
17
|
+
db.exec(`
|
|
18
|
+
CREATE TABLE message_attachments (
|
|
19
|
+
id TEXT PRIMARY KEY,
|
|
20
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
21
|
+
kind TEXT NOT NULL CHECK (kind IN ('image', 'file')),
|
|
22
|
+
mime_type TEXT NOT NULL,
|
|
23
|
+
filename TEXT NOT NULL DEFAULT '',
|
|
24
|
+
size_bytes INTEGER NOT NULL,
|
|
25
|
+
path TEXT NOT NULL DEFAULT '',
|
|
26
|
+
source_provider TEXT,
|
|
27
|
+
source_ref TEXT,
|
|
28
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
29
|
+
);
|
|
30
|
+
CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id, id);
|
|
31
|
+
`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const columns = tableColumns(db, 'message_attachments');
|
|
36
|
+
const shouldRebuild = columns.includes('content') || !columns.includes('path');
|
|
37
|
+
if (!shouldRebuild) {
|
|
38
|
+
db.exec('CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id, id)');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
db.exec(`
|
|
43
|
+
DROP INDEX IF EXISTS idx_message_attachments_message_id;
|
|
44
|
+
|
|
45
|
+
ALTER TABLE message_attachments RENAME TO message_attachments_legacy;
|
|
46
|
+
|
|
47
|
+
CREATE TABLE message_attachments (
|
|
48
|
+
id TEXT PRIMARY KEY,
|
|
49
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
50
|
+
kind TEXT NOT NULL CHECK (kind IN ('image', 'file')),
|
|
51
|
+
mime_type TEXT NOT NULL,
|
|
52
|
+
filename TEXT NOT NULL DEFAULT '',
|
|
53
|
+
size_bytes INTEGER NOT NULL,
|
|
54
|
+
path TEXT NOT NULL DEFAULT '',
|
|
55
|
+
source_provider TEXT,
|
|
56
|
+
source_ref TEXT,
|
|
57
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
INSERT INTO message_attachments (
|
|
61
|
+
id, message_id, kind, mime_type, filename, size_bytes, path, source_provider, source_ref, created_at
|
|
62
|
+
)
|
|
63
|
+
SELECT
|
|
64
|
+
id,
|
|
65
|
+
message_id,
|
|
66
|
+
kind,
|
|
67
|
+
mime_type,
|
|
68
|
+
filename,
|
|
69
|
+
size_bytes,
|
|
70
|
+
COALESCE(path, ''),
|
|
71
|
+
source_provider,
|
|
72
|
+
source_ref,
|
|
73
|
+
created_at
|
|
74
|
+
FROM message_attachments_legacy;
|
|
75
|
+
|
|
76
|
+
DROP TABLE message_attachments_legacy;
|
|
77
|
+
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id, id);
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 41;
|
|
4
|
+
export const name = 'room_system_events';
|
|
5
|
+
|
|
6
|
+
export function up(db: Database): void {
|
|
7
|
+
db.exec(`
|
|
8
|
+
CREATE TABLE IF NOT EXISTS room_system_events (
|
|
9
|
+
id TEXT PRIMARY KEY,
|
|
10
|
+
room_id TEXT NOT NULL REFERENCES chats(id) ON DELETE CASCADE,
|
|
11
|
+
message_id INTEGER NOT NULL UNIQUE REFERENCES messages(id) ON DELETE CASCADE,
|
|
12
|
+
event_type TEXT NOT NULL CHECK (event_type IN ('agent_invited', 'agent_removed', 'agent_receive_mode_changed')),
|
|
13
|
+
actor_kind TEXT NOT NULL DEFAULT 'system' CHECK (actor_kind IN ('user', 'agent', 'system')),
|
|
14
|
+
actor_id TEXT,
|
|
15
|
+
subject_agent TEXT,
|
|
16
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
17
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
CREATE INDEX IF NOT EXISTS idx_room_system_events_room_created
|
|
21
|
+
ON room_system_events(room_id, created_at, message_id);
|
|
22
|
+
|
|
23
|
+
CREATE INDEX IF NOT EXISTS idx_room_system_events_type_created
|
|
24
|
+
ON room_system_events(event_type, created_at);
|
|
25
|
+
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_room_system_events_subject_created
|
|
27
|
+
ON room_system_events(subject_agent, created_at)
|
|
28
|
+
WHERE subject_agent IS NOT NULL;
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Database } from 'bun:sqlite';
|
|
2
|
+
|
|
3
|
+
export const version = 42;
|
|
4
|
+
export const name = 'message_attachment_file_kind';
|
|
5
|
+
|
|
6
|
+
function tableSql(db: Database): string {
|
|
7
|
+
const row = db.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'message_attachments'").get() as { sql: string } | null;
|
|
8
|
+
return row?.sql ?? '';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function up(db: Database): void {
|
|
12
|
+
if (!tableSql(db).includes("kind IN ('image')")) return;
|
|
13
|
+
|
|
14
|
+
db.exec(`
|
|
15
|
+
DROP INDEX IF EXISTS idx_message_attachments_message_id;
|
|
16
|
+
|
|
17
|
+
ALTER TABLE message_attachments RENAME TO message_attachments_legacy;
|
|
18
|
+
|
|
19
|
+
CREATE TABLE message_attachments (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
|
|
22
|
+
kind TEXT NOT NULL CHECK (kind IN ('image', 'file')),
|
|
23
|
+
mime_type TEXT NOT NULL,
|
|
24
|
+
filename TEXT NOT NULL DEFAULT '',
|
|
25
|
+
size_bytes INTEGER NOT NULL,
|
|
26
|
+
path TEXT NOT NULL DEFAULT '',
|
|
27
|
+
source_provider TEXT,
|
|
28
|
+
source_ref TEXT,
|
|
29
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
INSERT INTO message_attachments (
|
|
33
|
+
id, message_id, kind, mime_type, filename, size_bytes, path, source_provider, source_ref, created_at
|
|
34
|
+
)
|
|
35
|
+
SELECT
|
|
36
|
+
id,
|
|
37
|
+
message_id,
|
|
38
|
+
kind,
|
|
39
|
+
mime_type,
|
|
40
|
+
filename,
|
|
41
|
+
size_bytes,
|
|
42
|
+
path,
|
|
43
|
+
source_provider,
|
|
44
|
+
source_ref,
|
|
45
|
+
created_at
|
|
46
|
+
FROM message_attachments_legacy;
|
|
47
|
+
|
|
48
|
+
DROP TABLE message_attachments_legacy;
|
|
49
|
+
|
|
50
|
+
CREATE INDEX IF NOT EXISTS idx_message_attachments_message_id ON message_attachments(message_id, id);
|
|
51
|
+
`);
|
|
52
|
+
}
|