my harness for niri
1import Database from "better-sqlite3"
2import path from "path"
3import { fileURLToPath } from "url"
4
5const HOME_DIR = path.resolve(fileURLToPath(import.meta.url), "../../home")
6const DB_PATH = path.join(HOME_DIR, "niri.db")
7
8let db: Database.Database
9
10export function initDb(): void {
11 db = new Database(DB_PATH)
12
13 db.pragma("journal_mode = WAL")
14 db.pragma("foreign_keys = ON")
15
16 db.exec(`
17 create table if not exists conversations (
18 id integer primary key autoincrement,
19 startedAt text not null,
20 source text not null,
21 tokens integer not null default 0
22 );
23
24 create table if not exists messages (
25 id integer primary key autoincrement,
26 convId integer not null references conversations(id),
27 role text not null,
28 content text not null,
29 toolCalls text, -- json blob, null if none
30 toolCallId text, -- for role=tool responses
31 createdAt text not null default (datetime('now'))
32 );
33
34 create table if not exists discord_messages (
35 message_id text primary key,
36 channel_id text not null,
37 guild_id text,
38 channel_type integer,
39 author_id text,
40 author_username text,
41 content text not null default '',
42 created_at text not null,
43 is_dm integer not null default 0,
44 mentions_bot integer not null default 0,
45 is_from_bot integer not null default 0,
46 first_seen_at text not null,
47 last_seen_at text not null,
48 raw_json text not null
49 );
50
51 create index if not exists idx_discord_messages_channel
52 on discord_messages(channel_id, message_id desc);
53 create index if not exists idx_discord_messages_created
54 on discord_messages(created_at desc);
55
56 create table if not exists discord_items (
57 item_id text primary key,
58 message_id text not null references discord_messages(message_id) on delete cascade,
59 bucket text not null,
60 status text not null default 'pending',
61 action_taken text not null default 'none',
62 decision_note text,
63 first_seen_at text not null,
64 last_seen_at text not null,
65 last_decision_at text
66 );
67
68 create index if not exists idx_discord_items_status
69 on discord_items(status, last_seen_at desc);
70 create index if not exists idx_discord_items_message
71 on discord_items(message_id);
72
73 create table if not exists discord_channels (
74 channel_id text primary key,
75 guild_id text,
76 channel_type integer,
77 channel_name text,
78 guild_name text,
79 topic text,
80 is_dm integer not null default 0,
81 configured integer not null default 0,
82 note text,
83 last_note_at text,
84 first_seen_at text not null,
85 last_seen_at text not null,
86 raw_json text not null
87 );
88
89 create index if not exists idx_discord_channels_configured
90 on discord_channels(configured, guild_name, channel_name);
91 create index if not exists idx_discord_channels_last_seen
92 on discord_channels(last_seen_at desc);
93
94 create table if not exists discord_meta (
95 key text primary key,
96 value text not null,
97 updated_at text not null
98 );
99 `)
100
101 console.log("[db] ready")
102}
103
104export function startConversation(source: string, startedAt: string): number {
105 const stmt = db.prepare("insert into conversations (startedAt, source) values (?, ?)")
106 const result = stmt.run(startedAt, source)
107 return result.lastInsertRowid as number
108}
109
110export function logMessage(
111 convId: number,
112 role: string,
113 content: string,
114 toolCalls?: unknown,
115 toolCallId?: string,
116): void {
117 const stmt = db.prepare(
118 "insert into messages (convId, role, content, toolCalls, toolCallId) values (?, ?, ?, ?, ?)",
119 )
120 stmt.run(
121 convId,
122 role,
123 content,
124 toolCalls ? JSON.stringify(toolCalls) : null,
125 toolCallId ?? null,
126 )
127}
128
129export function endConversation(id: number, tokens: number): void {
130 db.prepare("update conversations set tokens = ? where id = ?").run(tokens, id)
131}
132
133export function getDb(): Database.Database {
134 if (!db) throw new Error("Database not initialized")
135 return db
136}