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 | import { createWriteStream, promises as fs } from "node:fs"; |
| #2 | import path from "node:path"; |
| #3 | const TIER_FILES = { |
| #4 | KNOWN: "known.jsonl", |
| #5 | LEARNED: "learned.jsonl", |
| #6 | INFERRED: "inferred.jsonl", |
| #7 | }; |
| #8 | export class ClawdVault { |
| #9 | #opts; |
| #10 | #buffer = new Map(); |
| #11 | #flushTimer; |
| #12 | constructor(opts) { |
| #13 | this.#opts = { |
| #14 | workspace: opts.workspace, |
| #15 | vaultDir: opts.vaultDir ?? process.env.CLAWD_VAULT_DIR ?? "/vault", |
| #16 | }; |
| #17 | void fs.mkdir(this.#opts.vaultDir, { recursive: true }).catch(() => undefined); |
| #18 | this.#flushTimer = setInterval(() => { |
| #19 | void this.flushAll().catch(() => undefined); |
| #20 | }, 30_000); |
| #21 | this.#flushTimer.unref?.(); |
| #22 | } |
| #23 | writeKnown(owner, entry) { |
| #24 | this.#push(owner, { ...entry, tier: "KNOWN", ts: Date.now() }); |
| #25 | } |
| #26 | writeLearned(owner, entry) { |
| #27 | this.#push(owner, { ...entry, tier: "LEARNED", ts: Date.now() }); |
| #28 | } |
| #29 | writeInferred(owner, data) { |
| #30 | this.#push(owner, { |
| #31 | tier: "INFERRED", |
| #32 | key: `ev_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`, |
| #33 | value: data, |
| #34 | ts: Date.now(), |
| #35 | }); |
| #36 | } |
| #37 | async read(owner, tier) { |
| #38 | const local = this.#buffer.get(`${owner}:${tier}`) ?? []; |
| #39 | const onDisk = await this.#readFromDisk(owner, tier); |
| #40 | return [...onDisk, ...local]; |
| #41 | } |
| #42 | async flushAll() { |
| #43 | for (const [key, entries] of this.#buffer.entries()) { |
| #44 | const [, tier] = key.split(":"); |
| #45 | if (tier === "INFERRED") { |
| #46 | const cutoff = Date.now() - 5 * 60_000; |
| #47 | this.#buffer.set(key, entries.filter((entry) => entry.ts > cutoff)); |
| #48 | continue; |
| #49 | } |
| #50 | this.#buffer.set(key, []); |
| #51 | } |
| #52 | } |
| #53 | async snapshot(owner) { |
| #54 | await this.flushAll(); |
| #55 | const [known, learned, inferred, workspace_manifest] = await Promise.all([ |
| #56 | this.read(owner, "KNOWN"), |
| #57 | this.read(owner, "LEARNED"), |
| #58 | this.read(owner, "INFERRED"), |
| #59 | this.#workspaceManifest(owner), |
| #60 | ]); |
| #61 | return { |
| #62 | owner, |
| #63 | tiers: { KNOWN: known, LEARNED: learned, INFERRED: inferred }, |
| #64 | workspace_manifest, |
| #65 | }; |
| #66 | } |
| #67 | async rehydrateFromSnapshot(snapshot) { |
| #68 | for (const tier of ["KNOWN", "LEARNED", "INFERRED"]) { |
| #69 | const file = path.join(this.#opts.vaultDir, TIER_FILES[tier]); |
| #70 | const stream = createWriteStream(file, { flags: "w" }); |
| #71 | for (const entry of snapshot.tiers[tier] ?? []) { |
| #72 | stream.write(`${JSON.stringify({ owner: snapshot.owner, ...entry })}\n`); |
| #73 | } |
| #74 | await new Promise((resolve, reject) => stream.end((err) => (err ? reject(err) : resolve()))); |
| #75 | } |
| #76 | } |
| #77 | async brainAsk(owner, query) { |
| #78 | const needle = query.trim().toLowerCase(); |
| #79 | if (!needle) |
| #80 | return null; |
| #81 | const [known, learned] = await Promise.all([this.read(owner, "KNOWN"), this.read(owner, "LEARNED")]); |
| #82 | const haystack = [...learned, ...known]; |
| #83 | const matches = haystack.filter((entry) => `${entry.key} ${renderValue(entry.value)} ${entry.provenance ?? ""}`.toLowerCase().includes(needle)); |
| #84 | if (matches.length === 0) |
| #85 | return null; |
| #86 | return matches |
| #87 | .slice(-5) |
| #88 | .map((entry) => `[${entry.tier}] ${entry.key}: ${renderValue(entry.value)}`) |
| #89 | .join("\n"); |
| #90 | } |
| #91 | #push(owner, entry) { |
| #92 | const key = `${owner}:${entry.tier}`; |
| #93 | const entries = this.#buffer.get(key) ?? []; |
| #94 | entries.push(entry); |
| #95 | this.#buffer.set(key, entries); |
| #96 | void this.#appendToDisk(owner, entry).catch(() => undefined); |
| #97 | } |
| #98 | async #appendToDisk(owner, entry) { |
| #99 | const file = path.join(this.#opts.vaultDir, TIER_FILES[entry.tier]); |
| #100 | await fs.appendFile(file, `${JSON.stringify({ owner, ...entry })}\n`, "utf8"); |
| #101 | } |
| #102 | async #readFromDisk(owner, tier) { |
| #103 | const file = path.join(this.#opts.vaultDir, TIER_FILES[tier]); |
| #104 | try { |
| #105 | const text = await fs.readFile(file, "utf8"); |
| #106 | return text |
| #107 | .split("\n") |
| #108 | .filter(Boolean) |
| #109 | .flatMap((line) => { |
| #110 | try { |
| #111 | const parsed = JSON.parse(line); |
| #112 | if (parsed.owner && parsed.owner !== owner) |
| #113 | return []; |
| #114 | return [ |
| #115 | { |
| #116 | tier, |
| #117 | key: parsed.key, |
| #118 | value: parsed.value, |
| #119 | ts: parsed.ts, |
| #120 | provenance: parsed.provenance, |
| #121 | }, |
| #122 | ]; |
| #123 | } |
| #124 | catch { |
| #125 | return []; |
| #126 | } |
| #127 | }); |
| #128 | } |
| #129 | catch { |
| #130 | return []; |
| #131 | } |
| #132 | } |
| #133 | async #workspaceManifest(owner) { |
| #134 | const dir = path.join(this.#opts.workspace, owner); |
| #135 | const files = []; |
| #136 | const walk = async (current) => { |
| #137 | const entries = await fs.readdir(current, { withFileTypes: true }); |
| #138 | for (const entry of entries) { |
| #139 | const fullPath = path.join(current, entry.name); |
| #140 | if (entry.isDirectory()) { |
| #141 | await walk(fullPath); |
| #142 | } |
| #143 | else if (entry.isFile()) { |
| #144 | files.push(fullPath); |
| #145 | } |
| #146 | } |
| #147 | }; |
| #148 | try { |
| #149 | await walk(dir); |
| #150 | return files.sort(); |
| #151 | } |
| #152 | catch { |
| #153 | return []; |
| #154 | } |
| #155 | } |
| #156 | } |
| #157 | function renderValue(value) { |
| #158 | if (typeof value === "string") |
| #159 | return value; |
| #160 | try { |
| #161 | return JSON.stringify(value); |
| #162 | } |
| #163 | catch { |
| #164 | return String(value); |
| #165 | } |
| #166 | } |
| #167 | //# sourceMappingURL=index.js.map |