mcp-claude-duo/broker/db.js
StillHammer 0bb8af199e v2.0 - Architecture unifiée avec SQLite
- MCP unifié : mcp-partner remplace mcp-master et mcp-slave
- Messages bufferisés : SQLite stocke tout, pas besoin d'être connecté en permanence
- Tools simplifiés : register, talk, check_messages, listen, reply, list_partners, history
- Suppression du hook Stop (plus nécessaire avec reply explicite)
- Heartbeat 30s pour éviter les déconnexions idle
- ID basé sur le nom du dossier (unique par projet)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 04:05:01 +07:00

182 lines
4.6 KiB
JavaScript

import Database from "better-sqlite3";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { mkdirSync } from "fs";
const __dirname = dirname(fileURLToPath(import.meta.url));
const dataDir = join(__dirname, "..", "data");
// Créer le dossier data
try {
mkdirSync(dataDir, { recursive: true });
} catch {}
const dbPath = join(dataDir, "duo.db");
const db = new Database(dbPath);
// Activer les foreign keys
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
// Créer les tables
db.exec(`
-- Partenaires
CREATE TABLE IF NOT EXISTS partners (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'online'
);
-- Messages
CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
from_id TEXT NOT NULL,
to_id TEXT NOT NULL,
content TEXT NOT NULL,
request_id TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
delivered_at DATETIME,
response_to INTEGER REFERENCES messages(id),
FOREIGN KEY (from_id) REFERENCES partners(id),
FOREIGN KEY (to_id) REFERENCES partners(id)
);
-- Index pour les requêtes fréquentes
CREATE INDEX IF NOT EXISTS idx_messages_to_id ON messages(to_id, delivered_at);
CREATE INDEX IF NOT EXISTS idx_messages_request_id ON messages(request_id);
`);
// Prepared statements
const stmts = {
// Partners
upsertPartner: db.prepare(`
INSERT INTO partners (id, name, last_seen, status)
VALUES (?, ?, CURRENT_TIMESTAMP, 'online')
ON CONFLICT(id) DO UPDATE SET
name = excluded.name,
last_seen = CURRENT_TIMESTAMP,
status = 'online'
`),
getPartner: db.prepare(`SELECT * FROM partners WHERE id = ?`),
getAllPartners: db.prepare(`SELECT * FROM partners ORDER BY last_seen DESC`),
updatePartnerStatus: db.prepare(`
UPDATE partners SET status = ?, last_seen = CURRENT_TIMESTAMP WHERE id = ?
`),
// Messages
insertMessage: db.prepare(`
INSERT INTO messages (from_id, to_id, content, request_id)
VALUES (?, ?, ?, ?)
`),
getUndeliveredMessages: db.prepare(`
SELECT * FROM messages
WHERE to_id = ? AND delivered_at IS NULL
ORDER BY created_at ASC
`),
markDelivered: db.prepare(`
UPDATE messages SET delivered_at = CURRENT_TIMESTAMP WHERE id = ?
`),
getMessageByRequestId: db.prepare(`
SELECT * FROM messages WHERE request_id = ?
`),
insertResponse: db.prepare(`
INSERT INTO messages (from_id, to_id, content, response_to)
VALUES (?, ?, ?, ?)
`),
getResponse: db.prepare(`
SELECT * FROM messages WHERE response_to = ? AND delivered_at IS NULL
`),
markResponseDelivered: db.prepare(`
UPDATE messages SET delivered_at = CURRENT_TIMESTAMP WHERE response_to = ?
`),
// Conversations history
getConversation: db.prepare(`
SELECT * FROM messages
WHERE (from_id = ? AND to_id = ?) OR (from_id = ? AND to_id = ?)
ORDER BY created_at DESC
LIMIT ?
`),
};
// API
export const DB = {
// Partners
registerPartner(id, name) {
stmts.upsertPartner.run(id, name);
return stmts.getPartner.get(id);
},
getPartner(id) {
return stmts.getPartner.get(id);
},
getAllPartners() {
return stmts.getAllPartners.all();
},
setPartnerOffline(id) {
stmts.updatePartnerStatus.run("offline", id);
},
setPartnerOnline(id) {
stmts.updatePartnerStatus.run("online", id);
},
// Messages
sendMessage(fromId, toId, content, requestId = null) {
const result = stmts.insertMessage.run(fromId, toId, content, requestId);
return result.lastInsertRowid;
},
getUndeliveredMessages(toId) {
return stmts.getUndeliveredMessages.all(toId);
},
markDelivered(messageId) {
stmts.markDelivered.run(messageId);
},
// Pour talk() qui attend une réponse
sendAndWaitResponse(fromId, toId, content, requestId) {
stmts.insertMessage.run(fromId, toId, content, requestId);
},
getMessageByRequestId(requestId) {
return stmts.getMessageByRequestId.get(requestId);
},
sendResponse(fromId, toId, content, originalMessageId) {
stmts.insertResponse.run(fromId, toId, content, originalMessageId);
},
getResponse(originalMessageId) {
return stmts.getResponse.get(originalMessageId);
},
markResponseDelivered(originalMessageId) {
stmts.markResponseDelivered.run(originalMessageId);
},
// History
getConversation(partnerId1, partnerId2, limit = 50) {
return stmts.getConversation.all(partnerId1, partnerId2, partnerId2, partnerId1, limit);
},
// Raw access for complex queries
raw: db,
};
export default DB;