UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

79 lines • 2.87 kB
import { randomBytes } from 'node:crypto'; import { chmod, mkdir, readFile, rename, stat, writeFile } from 'node:fs/promises'; import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; export function defaultStatePath() { if (process.env.POPPY_AGENT_STATE) return process.env.POPPY_AGENT_STATE; const base = process.env.XDG_CONFIG_HOME || join(homedir(), '.config'); return join(base, 'poppy-agent', 'state.json'); } /** * The local state file. It holds private keys and Account Tokens, so it is written with mode * 0600 (directory 0700) and atomically (temp file + rename). */ export class StateStore { path; data; persist; constructor(path, data, persist) { this.path = path; this.data = data; this.persist = persist; } /** * A state kept by the caller instead of a file — a server keeping each user's Companies in its * own database. `persist` receives the whole state on every save (serialized, like the file). */ static memory(data = { version: 1, profiles: {} }, persist) { data.profiles ??= {}; return new StateStore('', data, persist); } static async open(path = defaultStatePath()) { await mkdir(dirname(path), { recursive: true, mode: 0o700 }); let data = { version: 1, profiles: {} }; try { const raw = await readFile(path, 'utf8'); data = JSON.parse(raw); const st = await stat(path); if ((st.mode & 0o077) !== 0) await chmod(path, 0o600); } catch (e) { if (e.code !== 'ENOENT') throw e; } data.profiles ??= {}; return new StateStore(path, data); } /** Directory next to the state file, used for the Direct Sign-In callback relay. */ get dir() { return dirname(this.path); } profile(name) { this.data.profiles[name] ??= { companies: {} }; return this.data.profiles[name]; } #saving = Promise.resolve(); /** * Writes the state atomically (tmp file + rename). Saves are serialized: a conversation stream * and the prompt can save at the same time, and two writes sharing one tmp file made the second * `rename` fail with ENOENT. */ save() { const run = this.#saving.then(() => this.#write()); this.#saving = run.catch(() => { }); return run; } async #write() { if (this.path === '') { await this.persist?.(this.data); return; } const tmp = `${this.path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`; await writeFile(tmp, `${JSON.stringify(this.data, null, 2)}\n`, { mode: 0o600 }); await chmod(tmp, 0o600); await rename(tmp, this.path); } } //# sourceMappingURL=store.js.map