- /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
180 lines
4.4 KiB
TypeScript
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;
|