import { mkdirSync, writeFileSync, rmSync } from "node:fs"; import { dirname, join } from "node:path"; import type { AgentConfig } from "./types.js"; import { EventLoop } from "./event-loop.js"; import { MailClient } from "../io/mail.js"; import { MemoryStore } from "../io/memory.js"; import { ContextManager } from "../io/context.js"; import { ProviderManager } from "../llm/provider.js"; import { BoundaryManager } from "../governance/boundary.js"; import { createDefaultToolset } from "../tools/index.js"; export class AgentRuntime { private loop: EventLoop; private readonly mail: MailClient; private readonly boundary: BoundaryManager; constructor(public readonly config: AgentConfig) { const mail = new MailClient(config.mailDir); const memory = new MemoryStore(config.memoryPath); const context = new ContextManager(memory, config.contextWindowTokens ?? 8000); const provider = new ProviderManager(config.llm); this.mail = mail; this.boundary = new BoundaryManager(config.workspace); const tools = createDefaultToolset({ boundary: this.boundary, mail, tools: config.tools, execAllowlist: config.execAllowlist, }); this.loop = new EventLoop({ config, memory, context, provider, tools }); } async start(): Promise { const checkInbox = async () => this.mail.checkNewMail(); const pidPath = join(this.config.workspace, ".tps-agent.pid"); mkdirSync(dirname(pidPath), { recursive: true }); writeFileSync(pidPath, `${process.pid}\n`, "utf-8"); const shutdown = new Promise((resolve) => { const onStop = async () => { await this.loop.stop(); resolve(); }; process.once("SIGINT", onStop); process.once("SIGTERM", onStop); }); try { await Promise.race([this.loop.run(checkInbox), shutdown]); } finally { rmSync(pidPath, { force: true }); } } async stop(): Promise { await this.loop.stop(); } async runOnce(message: string): Promise { await this.loop.runOnce(message); } getState(): string { return this.loop.getState(); } isHealthy(): boolean { return this.getState() !== "stopped"; } describeBoundaries(): string { return this.boundary.describeCapabilities(); } }