// 一次运行:准备工作区、创建 Agent、给每个事件打上序号和相对时间。 // 本地服务和命令行脚本都用它。 import { randomBytes } from 'node:crypto'; import type { RunRecording, TraceBody, TraceEvent } from '../shared/types.ts'; import { Agent } from './agent.ts'; import type { LLM } from './llm.ts'; import { getLevel } from './levels.ts'; import { prepareWorkspace } from './workspace.ts'; export class Run { readonly runId: string; readonly level: number; readonly task: string; readonly startedAt = Date.now(); readonly events: TraceEvent[] = []; readonly agent: Agent; readonly workspace: string; private listeners = new Set<(event: TraceEvent) => void>(); private seq = 0; constructor(llm: LLM, level: number, task?: string) { const config = getLevel(level); this.level = level; this.task = task?.trim() || config.defaultTask; this.runId = `L${level}-${new Date().toISOString().slice(11, 19).replaceAll(':', '')}-${randomBytes(2).toString('hex')}`; this.workspace = prepareWorkspace(this.runId); this.agent = new Agent({ level: config, workspace: this.workspace, llm, emit: (body) => this.record(body) }); this.record({ type: 'run.start', runId: this.runId, level, model: llm.provider.model, baseURL: llm.provider.baseURL, tools: this.agent.tools.specs(), startedAt: this.startedAt, }); } start(): void { this.agent.sendUserMessage(this.task); void this.agent.run(); } stop(): void { this.agent.stop(); this.record({ type: 'run.stop', reason: 'stopped' }); } subscribe(listener: (event: TraceEvent) => void): () => void { this.listeners.add(listener); return () => this.listeners.delete(listener); } recording(): RunRecording { return { runId: this.runId, level: this.level, task: this.task, startedAt: this.startedAt, events: this.events }; } private record(body: TraceBody): void { const event = { seq: ++this.seq, t: Date.now() - this.startedAt, ...body } as TraceEvent; this.events.push(event); for (const listener of this.listeners) listener(event); } }