Files
calvana/lib/store.ts
Omair Saleh c79b9bcabc production: reminder cron, dashboard overhaul, shadcn components, setup wizard
- /api/cron/reminders: processes pending reminders every 15min, sends WhatsApp with email fallback
- /api/cron/overdue: marks overdue pledges daily (7d deferred, 14d immediate)
- /api/pledges: GET handler with filtering, search, pagination, sort by dueDate
- Dashboard overview: stats, collection progress bar, needs attention, upcoming payments
- Dashboard pledges: proper table with status tabs, search, actions, pagination
- New shadcn components: Table, Tabs, DropdownMenu, Progress
- Setup wizard: 4-step onboarding (org → bank → event → QR code)
- Settings API: PUT handler for org create/update
- Org resolver: single-tenant fallback to first org
- Cron jobs installed: reminders every 15min, overdue check at 6am
- Auto-generates installment dates when not provided
- HOSTNAME=0.0.0.0 in compose for multi-network binding
2026-03-03 05:11:17 +08:00

180 lines
4.4 KiB
TypeScript

import { Database } from "bun:sqlite";
import { mkdirSync } from "fs";
mkdirSync("data", { recursive: true });
const db = new Database("data/agents.db");
db.run("PRAGMA journal_mode = WAL");
db.run(`
CREATE TABLE IF NOT EXISTS agents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'spawning',
chat_id TEXT NOT NULL,
thread_msg_id INTEGER,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
summary TEXT,
error TEXT
)
`);
db.run(`
CREATE TABLE IF NOT EXISTS agent_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
`);
db.run(`
CREATE TABLE IF NOT EXISTS agent_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
agent_id INTEGER NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
tool_use TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (agent_id) REFERENCES agents(id)
)
`);
export interface Agent {
id: number;
task: string;
status: string;
chat_id: string;
thread_msg_id: number | null;
created_at: string;
updated_at: string;
summary: string | null;
error: string | null;
}
export interface AgentLog {
id: number;
agent_id: number;
role: string;
content: string;
created_at: string;
}
// --- Agent CRUD ---
export function createAgent(task: string, chatId: string): Agent {
const stmt = db.prepare(
`INSERT INTO agents (task, status, chat_id) VALUES (?, 'spawning', ?)`
);
stmt.run(task, chatId);
const id = Number(db.query("SELECT last_insert_rowid() as id").get()!.id);
return getAgent(id)!;
}
export function getAgent(id: number): Agent | undefined {
return db.query(`SELECT * FROM agents WHERE id = ?`).get(id) as
| Agent
| undefined;
}
export function updateAgent(
id: number,
updates: Partial<Pick<Agent, "status" | "thread_msg_id" | "summary" | "error">>
): void {
const fields: string[] = ["updated_at = datetime('now')"];
const values: unknown[] = [];
if (updates.status !== undefined) {
fields.push("status = ?");
values.push(updates.status);
}
if (updates.thread_msg_id !== undefined) {
fields.push("thread_msg_id = ?");
values.push(updates.thread_msg_id);
}
if (updates.summary !== undefined) {
fields.push("summary = ?");
values.push(updates.summary);
}
if (updates.error !== undefined) {
fields.push("error = ?");
values.push(updates.error);
}
values.push(id);
db.prepare(`UPDATE agents SET ${fields.join(", ")} WHERE id = ?`).run(
...values
);
}
export function listAgents(chatId?: string): Agent[] {
if (chatId) {
return db
.query(`SELECT * FROM agents WHERE chat_id = ? ORDER BY id DESC`)
.all(chatId) as Agent[];
}
return db.query(`SELECT * FROM agents ORDER BY id DESC`).all() as Agent[];
}
export function getActiveAgents(): Agent[] {
return db
.query(
`SELECT * FROM agents WHERE status IN ('spawning', 'working', 'waiting') ORDER BY id`
)
.all() as Agent[];
}
export function findAgentByThreadMsg(messageId: number): Agent | undefined {
return db
.query(`SELECT * FROM agents WHERE thread_msg_id = ?`)
.get(messageId) as Agent | undefined;
}
// --- Logs ---
export function addLog(agentId: number, role: string, content: string): void {
db.prepare(
`INSERT INTO agent_logs (agent_id, role, content) VALUES (?, ?, ?)`
).run(agentId, role, content);
}
export function getLogs(agentId: number, limit = 20): AgentLog[] {
return db
.query(
`SELECT * FROM agent_logs WHERE agent_id = ? ORDER BY id DESC LIMIT ?`
)
.all(agentId, limit) as AgentLog[];
}
// --- Messages (conversation history) ---
export function addMessage(
agentId: number,
role: string,
content: string,
toolUse?: string
): void {
db.prepare(
`INSERT INTO agent_messages (agent_id, role, content, tool_use) VALUES (?, ?, ?, ?)`
).run(agentId, role, content, toolUse || null);
}
export function getMessages(
agentId: number
): Array<{ role: string; content: string; tool_use: string | null }> {
return db
.query(
`SELECT role, content, tool_use FROM agent_messages WHERE agent_id = ? ORDER BY id ASC`
)
.all(agentId) as Array<{
role: string;
content: string;
tool_use: string | null;
}>;
}
export default db;