repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | /** |
| #2 | * Automaton Database |
| #3 | * |
| #4 | * SQLite-backed persistent state for the automaton. |
| #5 | * Uses better-sqlite3 for synchronous, single-process access. |
| #6 | */ |
| #7 | import Database from "better-sqlite3"; |
| #8 | import fs from "fs"; |
| #9 | import path from "path"; |
| #10 | import { SCHEMA_VERSION, CREATE_TABLES, MIGRATION_V2, MIGRATION_V3 } from "./schema.js"; |
| #11 | export function createDatabase(dbPath) { |
| #12 | // Ensure directory exists |
| #13 | const dir = path.dirname(dbPath); |
| #14 | if (!fs.existsSync(dir)) { |
| #15 | fs.mkdirSync(dir, { recursive: true, mode: 0o700 }); |
| #16 | } |
| #17 | const db = new Database(dbPath); |
| #18 | // Enable WAL mode for better concurrent read performance |
| #19 | db.pragma("journal_mode = WAL"); |
| #20 | db.pragma("foreign_keys = ON"); |
| #21 | // Initialize schema |
| #22 | db.exec(CREATE_TABLES); |
| #23 | // Check and apply schema version |
| #24 | const versionRow = db |
| #25 | .prepare("SELECT MAX(version) as v FROM schema_version") |
| #26 | .get(); |
| #27 | const currentVersion = versionRow?.v ?? 0; |
| #28 | if (currentVersion < 2) { |
| #29 | db.exec(MIGRATION_V2); |
| #30 | } |
| #31 | if (currentVersion < 3) { |
| #32 | db.exec(MIGRATION_V3); |
| #33 | } |
| #34 | if (currentVersion < SCHEMA_VERSION) { |
| #35 | db.prepare("INSERT OR REPLACE INTO schema_version (version, applied_at) VALUES (?, datetime('now'))").run(SCHEMA_VERSION); |
| #36 | } |
| #37 | // ─── Identity ──────────────────────────────────────────────── |
| #38 | const getIdentity = (key) => { |
| #39 | const row = db |
| #40 | .prepare("SELECT value FROM identity WHERE key = ?") |
| #41 | .get(key); |
| #42 | return row?.value; |
| #43 | }; |
| #44 | const setIdentity = (key, value) => { |
| #45 | db.prepare("INSERT OR REPLACE INTO identity (key, value) VALUES (?, ?)").run(key, value); |
| #46 | }; |
| #47 | // ─── Turns ─────────────────────────────────────────────────── |
| #48 | const insertTurn = (turn) => { |
| #49 | db.prepare(`INSERT INTO turns (id, timestamp, state, input, input_source, thinking, tool_calls, token_usage, cost_cents) |
| #50 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(turn.id, turn.timestamp, turn.state, turn.input ?? null, turn.inputSource ?? null, turn.thinking, JSON.stringify(turn.toolCalls), JSON.stringify(turn.tokenUsage), turn.costCents); |
| #51 | }; |
| #52 | const getRecentTurns = (limit) => { |
| #53 | const rows = db |
| #54 | .prepare("SELECT * FROM turns ORDER BY timestamp DESC LIMIT ?") |
| #55 | .all(limit); |
| #56 | return rows.map(deserializeTurn).reverse(); |
| #57 | }; |
| #58 | const getTurnById = (id) => { |
| #59 | const row = db |
| #60 | .prepare("SELECT * FROM turns WHERE id = ?") |
| #61 | .get(id); |
| #62 | return row ? deserializeTurn(row) : undefined; |
| #63 | }; |
| #64 | const getTurnCount = () => { |
| #65 | const row = db |
| #66 | .prepare("SELECT COUNT(*) as count FROM turns") |
| #67 | .get(); |
| #68 | return row.count; |
| #69 | }; |
| #70 | // ─── Tool Calls ────────────────────────────────────────────── |
| #71 | const insertToolCall = (turnId, call) => { |
| #72 | db.prepare(`INSERT INTO tool_calls (id, turn_id, name, arguments, result, duration_ms, error) |
| #73 | VALUES (?, ?, ?, ?, ?, ?, ?)`).run(call.id, turnId, call.name, JSON.stringify(call.arguments), call.result, call.durationMs, call.error ?? null); |
| #74 | }; |
| #75 | const getToolCallsForTurn = (turnId) => { |
| #76 | const rows = db |
| #77 | .prepare("SELECT * FROM tool_calls WHERE turn_id = ?") |
| #78 | .all(turnId); |
| #79 | return rows.map(deserializeToolCall); |
| #80 | }; |
| #81 | // ─── Heartbeat ─────────────────────────────────────────────── |
| #82 | const getHeartbeatEntries = () => { |
| #83 | const rows = db |
| #84 | .prepare("SELECT * FROM heartbeat_entries") |
| #85 | .all(); |
| #86 | return rows.map(deserializeHeartbeatEntry); |
| #87 | }; |
| #88 | const upsertHeartbeatEntry = (entry) => { |
| #89 | db.prepare(`INSERT OR REPLACE INTO heartbeat_entries (name, schedule, task, enabled, last_run, next_run, params, updated_at) |
| #90 | VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))`).run(entry.name, entry.schedule, entry.task, entry.enabled ? 1 : 0, entry.lastRun ?? null, entry.nextRun ?? null, JSON.stringify(entry.params ?? {})); |
| #91 | }; |
| #92 | const updateHeartbeatLastRun = (name, timestamp) => { |
| #93 | db.prepare("UPDATE heartbeat_entries SET last_run = ?, updated_at = datetime('now') WHERE name = ?").run(timestamp, name); |
| #94 | }; |
| #95 | // ─── Transactions ──────────────────────────────────────────── |
| #96 | const insertTransaction = (txn) => { |
| #97 | db.prepare(`INSERT INTO transactions (id, type, amount_cents, balance_after_cents, description) |
| #98 | VALUES (?, ?, ?, ?, ?)`).run(txn.id, txn.type, txn.amountCents ?? null, txn.balanceAfterCents ?? null, txn.description); |
| #99 | }; |
| #100 | const getRecentTransactions = (limit) => { |
| #101 | const rows = db |
| #102 | .prepare("SELECT * FROM transactions ORDER BY created_at DESC LIMIT ?") |
| #103 | .all(limit); |
| #104 | return rows.map(deserializeTransaction).reverse(); |
| #105 | }; |
| #106 | // ─── Installed Tools ───────────────────────────────────────── |
| #107 | const getInstalledTools = () => { |
| #108 | const rows = db |
| #109 | .prepare("SELECT * FROM installed_tools WHERE enabled = 1") |
| #110 | .all(); |
| #111 | return rows.map(deserializeInstalledTool); |
| #112 | }; |
| #113 | const installTool = (tool) => { |
| #114 | db.prepare(`INSERT OR REPLACE INTO installed_tools (id, name, type, config, installed_at, enabled) |
| #115 | VALUES (?, ?, ?, ?, ?, ?)`).run(tool.id, tool.name, tool.type, JSON.stringify(tool.config ?? {}), tool.installedAt, tool.enabled ? 1 : 0); |
| #116 | }; |
| #117 | const removeTool = (id) => { |
| #118 | db.prepare("UPDATE installed_tools SET enabled = 0 WHERE id = ?").run(id); |
| #119 | }; |
| #120 | // ─── Modifications ─────────────────────────────────────────── |
| #121 | const insertModification = (mod) => { |
| #122 | db.prepare(`INSERT INTO modifications (id, timestamp, type, description, file_path, diff, reversible) |
| #123 | VALUES (?, ?, ?, ?, ?, ?, ?)`).run(mod.id, mod.timestamp, mod.type, mod.description, mod.filePath ?? null, mod.diff ?? null, mod.reversible ? 1 : 0); |
| #124 | }; |
| #125 | const getRecentModifications = (limit) => { |
| #126 | const rows = db |
| #127 | .prepare("SELECT * FROM modifications ORDER BY timestamp DESC LIMIT ?") |
| #128 | .all(limit); |
| #129 | return rows.map(deserializeModification).reverse(); |
| #130 | }; |
| #131 | // ─── Key-Value Store ───────────────────────────────────────── |
| #132 | const getKV = (key) => { |
| #133 | const row = db |
| #134 | .prepare("SELECT value FROM kv WHERE key = ?") |
| #135 | .get(key); |
| #136 | return row?.value; |
| #137 | }; |
| #138 | const setKV = (key, value) => { |
| #139 | db.prepare("INSERT OR REPLACE INTO kv (key, value, updated_at) VALUES (?, ?, datetime('now'))").run(key, value); |
| #140 | }; |
| #141 | const deleteKV = (key) => { |
| #142 | db.prepare("DELETE FROM kv WHERE key = ?").run(key); |
| #143 | }; |
| #144 | // ─── Skills ───────────────────────────────────────────────── |
| #145 | const getSkills = (enabledOnly) => { |
| #146 | const query = enabledOnly |
| #147 | ? "SELECT * FROM skills WHERE enabled = 1" |
| #148 | : "SELECT * FROM skills"; |
| #149 | const rows = db.prepare(query).all(); |
| #150 | return rows.map(deserializeSkill); |
| #151 | }; |
| #152 | const getSkillByName = (name) => { |
| #153 | const row = db |
| #154 | .prepare("SELECT * FROM skills WHERE name = ?") |
| #155 | .get(name); |
| #156 | return row ? deserializeSkill(row) : undefined; |
| #157 | }; |
| #158 | const upsertSkill = (skill) => { |
| #159 | db.prepare(`INSERT OR REPLACE INTO skills (name, description, auto_activate, requires, instructions, source, path, enabled, installed_at) |
| #160 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(skill.name, skill.description, skill.autoActivate ? 1 : 0, JSON.stringify(skill.requires ?? {}), skill.instructions, skill.source, skill.path, skill.enabled ? 1 : 0, skill.installedAt); |
| #161 | }; |
| #162 | const removeSkill = (name) => { |
| #163 | db.prepare("UPDATE skills SET enabled = 0 WHERE name = ?").run(name); |
| #164 | }; |
| #165 | // ─── Children ────────────────────────────────────────────── |
| #166 | const getChildren = () => { |
| #167 | const rows = db |
| #168 | .prepare("SELECT * FROM children ORDER BY created_at DESC") |
| #169 | .all(); |
| #170 | return rows.map(deserializeChild); |
| #171 | }; |
| #172 | const getChildById = (id) => { |
| #173 | const row = db |
| #174 | .prepare("SELECT * FROM children WHERE id = ?") |
| #175 | .get(id); |
| #176 | return row ? deserializeChild(row) : undefined; |
| #177 | }; |
| #178 | const insertChild = (child) => { |
| #179 | db.prepare(`INSERT INTO children (id, name, address, sandbox_id, genesis_prompt, creator_message, funded_amount_cents, status, created_at) |
| #180 | VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(child.id, child.name, child.address, child.sandboxId, child.genesisPrompt, child.creatorMessage ?? null, child.fundedAmountCents, child.status, child.createdAt); |
| #181 | }; |
| #182 | const updateChildStatus = (id, status) => { |
| #183 | db.prepare("UPDATE children SET status = ?, last_checked = datetime('now') WHERE id = ?").run(status, id); |
| #184 | }; |
| #185 | // ─── Registry ────────────────────────────────────────────── |
| #186 | const getRegistryEntry = () => { |
| #187 | const row = db |
| #188 | .prepare("SELECT * FROM registry LIMIT 1") |
| #189 | .get(); |
| #190 | return row ? deserializeRegistry(row) : undefined; |
| #191 | }; |
| #192 | const setRegistryEntry = (entry) => { |
| #193 | db.prepare(`INSERT OR REPLACE INTO registry (agent_id, agent_uri, chain, contract_address, tx_hash, registered_at) |
| #194 | VALUES (?, ?, ?, ?, ?, ?)`).run(entry.agentId, entry.agentURI, entry.chain, entry.contractAddress, entry.txHash, entry.registeredAt); |
| #195 | }; |
| #196 | // ─── Reputation ──────────────────────────────────────────── |
| #197 | const insertReputation = (entry) => { |
| #198 | db.prepare(`INSERT INTO reputation (id, from_agent, to_agent, score, comment, tx_hash) |
| #199 | VALUES (?, ?, ?, ?, ?, ?)`).run(entry.id, entry.fromAgent, entry.toAgent, entry.score, entry.comment, entry.txHash ?? null); |
| #200 | }; |
| #201 | const getReputation = (agentAddress) => { |
| #202 | const query = agentAddress |
| #203 | ? "SELECT * FROM reputation WHERE to_agent = ? ORDER BY created_at DESC" |
| #204 | : "SELECT * FROM reputation ORDER BY created_at DESC"; |
| #205 | const params = agentAddress ? [agentAddress] : []; |
| #206 | const rows = db.prepare(query).all(...params); |
| #207 | return rows.map(deserializeReputation); |
| #208 | }; |
| #209 | // ─── Inbox Messages ────────────────────────────────────────── |
| #210 | const insertInboxMessage = (msg) => { |
| #211 | db.prepare(`INSERT OR IGNORE INTO inbox_messages (id, from_address, content, received_at, reply_to) |
| #212 | VALUES (?, ?, ?, ?, ?)`).run(msg.id, msg.from, msg.content, msg.createdAt || new Date().toISOString(), msg.replyTo ?? null); |
| #213 | }; |
| #214 | const getUnprocessedInboxMessages = (limit) => { |
| #215 | const rows = db |
| #216 | .prepare("SELECT * FROM inbox_messages WHERE processed_at IS NULL ORDER BY received_at ASC LIMIT ?") |
| #217 | .all(limit); |
| #218 | return rows.map(deserializeInboxMessage); |
| #219 | }; |
| #220 | const markInboxMessageProcessed = (id) => { |
| #221 | db.prepare("UPDATE inbox_messages SET processed_at = datetime('now') WHERE id = ?").run(id); |
| #222 | }; |
| #223 | // ─── Agent State ───────────────────────────────────────────── |
| #224 | const getAgentState = () => { |
| #225 | return getKV("agent_state") || "setup"; |
| #226 | }; |
| #227 | const setAgentState = (state) => { |
| #228 | setKV("agent_state", state); |
| #229 | }; |
| #230 | // ─── Close ─────────────────────────────────────────────────── |
| #231 | const close = () => { |
| #232 | db.close(); |
| #233 | }; |
| #234 | return { |
| #235 | getIdentity, |
| #236 | setIdentity, |
| #237 | insertTurn, |
| #238 | getRecentTurns, |
| #239 | getTurnById, |
| #240 | getTurnCount, |
| #241 | insertToolCall, |
| #242 | getToolCallsForTurn, |
| #243 | getHeartbeatEntries, |
| #244 | upsertHeartbeatEntry, |
| #245 | updateHeartbeatLastRun, |
| #246 | insertTransaction, |
| #247 | getRecentTransactions, |
| #248 | getInstalledTools, |
| #249 | installTool, |
| #250 | removeTool, |
| #251 | insertModification, |
| #252 | getRecentModifications, |
| #253 | getKV, |
| #254 | setKV, |
| #255 | deleteKV, |
| #256 | getSkills, |
| #257 | getSkillByName, |
| #258 | upsertSkill, |
| #259 | removeSkill, |
| #260 | getChildren, |
| #261 | getChildById, |
| #262 | insertChild, |
| #263 | updateChildStatus, |
| #264 | getRegistryEntry, |
| #265 | setRegistryEntry, |
| #266 | insertReputation, |
| #267 | getReputation, |
| #268 | insertInboxMessage, |
| #269 | getUnprocessedInboxMessages, |
| #270 | markInboxMessageProcessed, |
| #271 | getAgentState, |
| #272 | setAgentState, |
| #273 | close, |
| #274 | }; |
| #275 | } |
| #276 | // ─── Deserializers ───────────────────────────────────────────── |
| #277 | function deserializeTurn(row) { |
| #278 | return { |
| #279 | id: row.id, |
| #280 | timestamp: row.timestamp, |
| #281 | state: row.state, |
| #282 | input: row.input ?? undefined, |
| #283 | inputSource: row.input_source ?? undefined, |
| #284 | thinking: row.thinking, |
| #285 | toolCalls: JSON.parse(row.tool_calls || "[]"), |
| #286 | tokenUsage: JSON.parse(row.token_usage || "{}"), |
| #287 | costCents: row.cost_cents, |
| #288 | }; |
| #289 | } |
| #290 | function deserializeToolCall(row) { |
| #291 | return { |
| #292 | id: row.id, |
| #293 | name: row.name, |
| #294 | arguments: JSON.parse(row.arguments || "{}"), |
| #295 | result: row.result, |
| #296 | durationMs: row.duration_ms, |
| #297 | error: row.error ?? undefined, |
| #298 | }; |
| #299 | } |
| #300 | function deserializeHeartbeatEntry(row) { |
| #301 | return { |
| #302 | name: row.name, |
| #303 | schedule: row.schedule, |
| #304 | task: row.task, |
| #305 | enabled: !!row.enabled, |
| #306 | lastRun: row.last_run ?? undefined, |
| #307 | nextRun: row.next_run ?? undefined, |
| #308 | params: JSON.parse(row.params || "{}"), |
| #309 | }; |
| #310 | } |
| #311 | function deserializeTransaction(row) { |
| #312 | return { |
| #313 | id: row.id, |
| #314 | type: row.type, |
| #315 | amountCents: row.amount_cents ?? undefined, |
| #316 | balanceAfterCents: row.balance_after_cents ?? undefined, |
| #317 | description: row.description, |
| #318 | timestamp: row.created_at, |
| #319 | }; |
| #320 | } |
| #321 | function deserializeInstalledTool(row) { |
| #322 | return { |
| #323 | id: row.id, |
| #324 | name: row.name, |
| #325 | type: row.type, |
| #326 | config: JSON.parse(row.config || "{}"), |
| #327 | installedAt: row.installed_at, |
| #328 | enabled: !!row.enabled, |
| #329 | }; |
| #330 | } |
| #331 | function deserializeModification(row) { |
| #332 | return { |
| #333 | id: row.id, |
| #334 | timestamp: row.timestamp, |
| #335 | type: row.type, |
| #336 | description: row.description, |
| #337 | filePath: row.file_path ?? undefined, |
| #338 | diff: row.diff ?? undefined, |
| #339 | reversible: !!row.reversible, |
| #340 | }; |
| #341 | } |
| #342 | function deserializeSkill(row) { |
| #343 | return { |
| #344 | name: row.name, |
| #345 | description: row.description, |
| #346 | autoActivate: !!row.auto_activate, |
| #347 | requires: JSON.parse(row.requires || "{}"), |
| #348 | instructions: row.instructions, |
| #349 | source: row.source, |
| #350 | path: row.path, |
| #351 | enabled: !!row.enabled, |
| #352 | installedAt: row.installed_at, |
| #353 | }; |
| #354 | } |
| #355 | function deserializeChild(row) { |
| #356 | return { |
| #357 | id: row.id, |
| #358 | name: row.name, |
| #359 | address: row.address, |
| #360 | sandboxId: row.sandbox_id, |
| #361 | genesisPrompt: row.genesis_prompt, |
| #362 | creatorMessage: row.creator_message ?? undefined, |
| #363 | fundedAmountCents: row.funded_amount_cents, |
| #364 | status: row.status, |
| #365 | createdAt: row.created_at, |
| #366 | lastChecked: row.last_checked ?? undefined, |
| #367 | }; |
| #368 | } |
| #369 | function deserializeRegistry(row) { |
| #370 | return { |
| #371 | agentId: row.agent_id, |
| #372 | agentURI: row.agent_uri, |
| #373 | chain: row.chain, |
| #374 | contractAddress: row.contract_address, |
| #375 | txHash: row.tx_hash, |
| #376 | registeredAt: row.registered_at, |
| #377 | }; |
| #378 | } |
| #379 | function deserializeInboxMessage(row) { |
| #380 | return { |
| #381 | id: row.id, |
| #382 | from: row.from_address, |
| #383 | to: "", |
| #384 | content: row.content, |
| #385 | signedAt: row.received_at, |
| #386 | createdAt: row.received_at, |
| #387 | replyTo: row.reply_to ?? undefined, |
| #388 | }; |
| #389 | } |
| #390 | function deserializeReputation(row) { |
| #391 | return { |
| #392 | id: row.id, |
| #393 | fromAgent: row.from_agent, |
| #394 | toAgent: row.to_agent, |
| #395 | score: row.score, |
| #396 | comment: row.comment, |
| #397 | txHash: row.tx_hash ?? undefined, |
| #398 | timestamp: row.created_at, |
| #399 | }; |
| #400 | } |
| #401 | //# sourceMappingURL=database.js.map |