// @ts-nocheck const MAX_STEPS = 20; export class AgentGraph { constructor(opts = {}) { this.nodes = new Map(); this.edges = new Map(); this.entryPoint = null; this.checkpointStore = opts.checkpointStore || null; this.maxSteps = opts.maxSteps || MAX_STEPS; this.onStep = opts.onStep || null; } addNode(name, fn) { if (typeof fn !== 'function') throw new Error(`节点 ${name} 必须是函数`); this.nodes.set(name, fn); return this; } addEdge(from, to) { if (!this.edges.has(from)) this.edges.set(from, []); this.edges.get(from).push(to); return this; } addConditionalEdge(from, condition) { if (!this.edges.has(from)) this.edges.set(from, []); this.edges.get(from).push(condition); return this; } setEntryPoint(name) { this.entryPoint = name; return this; } async run(initialCtx = {}) { if (!this.entryPoint) throw new Error('未设置 entry point'); if (!this.nodes.has(this.entryPoint)) throw new Error(`入口节点 ${this.entryPoint} 不存在`); let state = { step: 0, current: this.entryPoint, done: false, history: [] }; let ctx = { ...initialCtx, graph: this }; if (this.checkpointStore) { const restored = await this.checkpointStore.load(); if (restored) { state = restored; ctx.resumed = true; } } while (!state.done && state.step < this.maxSteps) { const nodeName = state.current; const nodeFn = this.nodes.get(nodeName); if (!nodeFn) { throw new Error(`节点 ${nodeName} 不存在`); } const update = await nodeFn(state, ctx); if (update && typeof update === 'object') { Object.assign(state, update); } state.step += 1; state.history.push({ step: state.step, node: nodeName, ts: Date.now() }); if (this.checkpointStore) { await this.checkpointStore.save(state); } if (this.onStep) this.onStep(state, nodeName); const next = this._decideNext(nodeName, state); if (next === null) { state.done = true; break; } state.current = next; } if (state.step >= this.maxSteps && !state.done) { state.error = `超出最大步数 ${this.maxSteps}`; } if (state.done && this.checkpointStore) { await this.checkpointStore.clear(); } return state; } _decideNext(from, state) { const edges = this.edges.get(from) || []; for (const edge of edges) { if (typeof edge === 'function') { const next = edge(state); if (next) return next; } else if (typeof edge === 'string') { return edge; } } return null; } } export class MemoryCheckpoint { constructor() { this._state = null; } async save(state) { this._state = JSON.parse(JSON.stringify(state)); } async load() { return this._state; } async clear() { this._state = null; } } export class LocalStorageCheckpoint { constructor(key = 'ccsparkle_graph_checkpoint') { this.key = key; } async save(state) { try { const safe = JSON.parse(JSON.stringify(state, (key, val) => typeof val === 'function' ? '[function]' : val )); localStorage.setItem(this.key, JSON.stringify(safe)); } catch (e) { console.warn('[checkpoint] 保存失败:', e); } } async load() { try { const s = localStorage.getItem(this.key); return s ? JSON.parse(s) : null; } catch (e) { return null; } } async clear() { localStorage.removeItem(this.key); } } export function buildAgentLoopGraph(opts) { const { chat, callTool, shouldApprove, humanApprove, maxTurns = 8, onStep } = opts; const graph = new AgentGraph({ maxSteps: maxTurns * 3, onStep }); graph.addNode('agent', async (state, ctx) => { const result = await chat(state, ctx); return { lastResponse: result, messages: [...(state.messages || []), ...(result.messages || [])], pendingToolCalls: result.toolCalls || [], }; }); graph.addNode('tools', async (state, ctx) => { const calls = state.pendingToolCalls || []; const results = []; for (const tc of calls) { if (shouldApprove) { const needsApproval = shouldApprove(state, tc); if (needsApproval) { state.pendingApproval = tc; return {}; } } const result = await callTool(state, tc, ctx); results.push({ tc, result }); } return { toolResults: results, pendingToolCalls: [], messages: [...(state.messages || []), ...results.map(r => ({ role: 'tool', toolCallId: r.tc.id, content: JSON.stringify(r.result), }))], }; }); graph.addNode('approve', async (state, ctx) => { if (!humanApprove) return {}; const decision = await humanApprove(state, ctx); return { approvalDecisions: [...(state.approvalDecisions || []), { toolCall: state.pendingApproval, approved: decision.approved, }], pendingApproval: null, }; }); graph.setEntryPoint('agent'); graph.addConditionalEdge('agent', (state) => { if (!state.pendingToolCalls || state.pendingToolCalls.length === 0) return null; return 'tools'; }); graph.addConditionalEdge('tools', (state) => { if (state.pendingApproval) return 'approve'; return 'agent'; }); graph.addConditionalEdge('approve', (state) => { const lastDecision = state.approvalDecisions?.[state.approvalDecisions.length - 1]; return lastDecision?.approved ? 'tools' : 'agent'; }); return graph; }