/** * LogSession - 会话级日志管理 * * 为单个会话(任务)提供结构化的日志记录接口 */ const { EventEmitter } = require('events'); class LogSession extends EventEmitter { constructor(sessionId, backend, options = {}) { super(); this.sessionId = sessionId; this.backend = backend; this.options = options; this.sequence = 0; this.agentContexts = new Map(); // agentName -> context this.startTime = new Date().toISOString(); } /** * 生成下一个序列号 * @private */ _nextSeq() { return ++this.sequence; } /** * 构建基础日志条目 * @private */ _buildEntry(category, eventType, level, payload, agentName = null, metadata = {}) { return { sessionId: this.sessionId, sequence: this._nextSeq(), timestamp: new Date().toISOString(), level, category, eventType, agentName, payload, metadata: { ...this.options.metadata, ...metadata, }, }; } /** * 写入日志 * @private */ async _write(entry) { try { await this.backend.write(entry); this.emit('log', entry); } catch (err) { console.error('[LogSession] Failed to write log:', err); } } // ============ Agent 生命周期 ============ /** * 记录 Agent 开始 */ logAgentStart(agentName, task, metadata = {}) { // 设置 Agent 上下文 this.agentContexts.set(agentName, { startTime: Date.now(), task, ...metadata, }); const entry = this._buildEntry( 'agent', 'agent_start', 'info', { task }, agentName, metadata ); return this._write(entry); } /** * 记录 Agent 结束 */ logAgentEnd(agentName, output, status, metadata = {}) { const context = this.agentContexts.get(agentName) || {}; const duration = context.startTime ? Date.now() - context.startTime : null; const entry = this._buildEntry( 'agent', 'agent_end', status === 'success' ? 'info' : 'warn', { output: this._truncate(output), status, duration }, agentName, metadata ); this.agentContexts.delete(agentName); return this._write(entry); } /** * 记录 Agent 错误 */ logAgentError(agentName, error, metadata = {}) { const entry = this._buildEntry( 'agent', 'agent_error', 'error', { message: error.message, stack: error.stack, }, agentName, metadata ); return this._write(entry); } // ============ 流式输出 ============ /** * 记录流式输出块 * @param {string} agentName - Agent 名称 * @param {string} chunk - 输出内容 * @param {string} type - 类型: 'stdout'|'stderr'|'thought'|'message' */ logStreamChunk(agentName, chunk, type = 'stdout') { const entry = this._buildEntry( 'agent', 'stream_chunk', 'debug', { chunk, chunkType: type }, agentName ); // 流式输出通常是高频的,使用 debug 级别 return this._write(entry); } // ============ 协议事件 ============ /** * 记录协议事件(ACP/MCP) * @param {string} agentName - Agent 名称 * @param {string} direction - 方向: 'in'|'out' * @param {Object} event - 事件对象 */ logProtocolEvent(agentName, direction, event) { const entry = this._buildEntry( 'protocol', 'protocol_event', 'debug', { direction, event }, agentName ); return this._write(entry); } // ============ 工具调用 ============ /** * 记录工具调用 */ logToolCall(agentName, toolName, args, callId) { const entry = this._buildEntry( 'tool', 'tool_call', 'info', { toolName, args, callId }, agentName ); return this._write(entry); } /** * 记录工具结果 */ logToolResult(agentName, callId, result, error = null) { const level = error ? 'error' : 'info'; const payload = error ? { callId, error: { message: error.message, stack: error.stack } } : { callId, result: this._truncate(result) }; const entry = this._buildEntry('tool', 'tool_result', level, payload, agentName); return this._write(entry); } // ============ 子代理调用 ============ /** * 记录子代理调用 */ logSubagentCall(parentAgent, subagentName, task) { const entry = this._buildEntry( 'agent', 'subagent_call', 'info', { subagentName, task: this._truncate(task) }, parentAgent ); return this._write(entry); } /** * 记录子代理返回 */ logSubagentReturn(parentAgent, subagentName, result) { const entry = this._buildEntry( 'agent', 'subagent_return', 'info', { subagentName, result: this._truncate(result) }, parentAgent ); return this._write(entry); } // ============ 文件变更 ============ /** * 记录文件变更 * @param {string} agentName - Agent 名称 * @param {string} operation - 操作: 'create'|'modify'|'delete'|'read' * @param {string} filePath - 文件路径 * @param {Object} metadata - 额外信息 */ logFileChange(agentName, operation, filePath, metadata = {}) { const entry = this._buildEntry( 'file', 'file_change', 'info', { operation, filePath }, agentName, metadata ); return this._write(entry); } // ============ 自定义事件 ============ /** * 记录自定义事件 * @param {string} agentName - Agent 名称(可选,null 表示系统级) * @param {string} eventType - 事件类型 * @param {Object} payload - 事件数据 * @param {string} level - 日志级别 */ logEvent(agentName, eventType, payload, level = 'info') { const category = agentName ? 'agent' : 'system'; const entry = this._buildEntry(category, eventType, level, payload, agentName); return this._write(entry); } // ============ 系统级快捷方法 ============ logInfo(eventType, payload) { return this.logEvent(null, eventType, payload, 'info'); } logWarn(eventType, payload) { return this.logEvent(null, eventType, payload, 'warn'); } logError(eventType, payload) { return this.logEvent(null, eventType, payload, 'error'); } logDebug(eventType, payload) { return this.logEvent(null, eventType, payload, 'debug'); } // ============ 会话管理 ============ /** * 生成会话报告 */ async generateReport() { // 查询所有日志 const entries = await this.backend.query({ sessionId: this.sessionId, limit: 10000 }); // 统计信息 const stats = { totalEvents: entries.length, agents: new Set(), tools: new Set(), files: new Set(), errors: 0, }; for (const entry of entries) { if (entry.agentName) stats.agents.add(entry.agentName); if (entry.category === 'tool' && entry.payload?.toolName) { stats.tools.add(entry.payload.toolName); } if (entry.category === 'file' && entry.payload?.filePath) { stats.files.add(entry.payload.filePath); } if (entry.level === 'error' || entry.level === 'fatal') { stats.errors++; } } return { sessionId: this.sessionId, startTime: this.startTime, endTime: new Date().toISOString(), stats: { ...stats, agents: Array.from(stats.agents), tools: Array.from(stats.tools), files: Array.from(stats.files), }, }; } /** * 关闭会话 */ async close() { this.emit('close'); this.removeAllListeners(); } // ============ 辅助方法 ============ /** * 截断长文本 * @private */ _truncate(text, maxLength = 10000) { if (!text) return text; const str = typeof text === 'string' ? text : JSON.stringify(text); if (str.length <= maxLength) return str; return str.substring(0, maxLength) + `... [truncated ${str.length - maxLength} chars]`; } } module.exports = { LogSession };