@yuaone/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +663 -0
- package/README.md +15 -0
- package/dist/__tests__/context-manager.test.d.ts +6 -0
- package/dist/__tests__/context-manager.test.d.ts.map +1 -0
- package/dist/__tests__/context-manager.test.js +220 -0
- package/dist/__tests__/context-manager.test.js.map +1 -0
- package/dist/__tests__/governor.test.d.ts +6 -0
- package/dist/__tests__/governor.test.d.ts.map +1 -0
- package/dist/__tests__/governor.test.js +210 -0
- package/dist/__tests__/governor.test.js.map +1 -0
- package/dist/__tests__/model-router.test.d.ts +6 -0
- package/dist/__tests__/model-router.test.d.ts.map +1 -0
- package/dist/__tests__/model-router.test.js +329 -0
- package/dist/__tests__/model-router.test.js.map +1 -0
- package/dist/agent-logger.d.ts +384 -0
- package/dist/agent-logger.d.ts.map +1 -0
- package/dist/agent-logger.js +820 -0
- package/dist/agent-logger.js.map +1 -0
- package/dist/agent-loop.d.ts +163 -0
- package/dist/agent-loop.d.ts.map +1 -0
- package/dist/agent-loop.js +609 -0
- package/dist/agent-loop.js.map +1 -0
- package/dist/agent-modes.d.ts +85 -0
- package/dist/agent-modes.d.ts.map +1 -0
- package/dist/agent-modes.js +418 -0
- package/dist/agent-modes.js.map +1 -0
- package/dist/approval.d.ts +137 -0
- package/dist/approval.d.ts.map +1 -0
- package/dist/approval.js +299 -0
- package/dist/approval.js.map +1 -0
- package/dist/async-completion-queue.d.ts +56 -0
- package/dist/async-completion-queue.d.ts.map +1 -0
- package/dist/async-completion-queue.js +77 -0
- package/dist/async-completion-queue.js.map +1 -0
- package/dist/auto-fix.d.ts +174 -0
- package/dist/auto-fix.d.ts.map +1 -0
- package/dist/auto-fix.js +319 -0
- package/dist/auto-fix.js.map +1 -0
- package/dist/codebase-context.d.ts +396 -0
- package/dist/codebase-context.d.ts.map +1 -0
- package/dist/codebase-context.js +1260 -0
- package/dist/codebase-context.js.map +1 -0
- package/dist/conflict-resolver.d.ts +191 -0
- package/dist/conflict-resolver.d.ts.map +1 -0
- package/dist/conflict-resolver.js +524 -0
- package/dist/conflict-resolver.js.map +1 -0
- package/dist/constants.d.ts +52 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +141 -0
- package/dist/constants.js.map +1 -0
- package/dist/context-budget.d.ts +435 -0
- package/dist/context-budget.d.ts.map +1 -0
- package/dist/context-budget.js +903 -0
- package/dist/context-budget.js.map +1 -0
- package/dist/context-compressor.d.ts +143 -0
- package/dist/context-compressor.d.ts.map +1 -0
- package/dist/context-compressor.js +511 -0
- package/dist/context-compressor.js.map +1 -0
- package/dist/context-manager.d.ts +112 -0
- package/dist/context-manager.d.ts.map +1 -0
- package/dist/context-manager.js +247 -0
- package/dist/context-manager.js.map +1 -0
- package/dist/continuous-reflection.d.ts +267 -0
- package/dist/continuous-reflection.d.ts.map +1 -0
- package/dist/continuous-reflection.js +338 -0
- package/dist/continuous-reflection.js.map +1 -0
- package/dist/cross-file-refactor.d.ts +352 -0
- package/dist/cross-file-refactor.d.ts.map +1 -0
- package/dist/cross-file-refactor.js +1544 -0
- package/dist/cross-file-refactor.js.map +1 -0
- package/dist/dag-orchestrator.d.ts +138 -0
- package/dist/dag-orchestrator.d.ts.map +1 -0
- package/dist/dag-orchestrator.js +379 -0
- package/dist/dag-orchestrator.js.map +1 -0
- package/dist/debate-orchestrator.d.ts +301 -0
- package/dist/debate-orchestrator.d.ts.map +1 -0
- package/dist/debate-orchestrator.js +719 -0
- package/dist/debate-orchestrator.js.map +1 -0
- package/dist/dependency-analyzer.d.ts +113 -0
- package/dist/dependency-analyzer.d.ts.map +1 -0
- package/dist/dependency-analyzer.js +444 -0
- package/dist/dependency-analyzer.js.map +1 -0
- package/dist/design-loop.d.ts +59 -0
- package/dist/design-loop.d.ts.map +1 -0
- package/dist/design-loop.js +344 -0
- package/dist/design-loop.js.map +1 -0
- package/dist/doc-intelligence.d.ts +383 -0
- package/dist/doc-intelligence.d.ts.map +1 -0
- package/dist/doc-intelligence.js +1307 -0
- package/dist/doc-intelligence.js.map +1 -0
- package/dist/dynamic-role-generator.d.ts +76 -0
- package/dist/dynamic-role-generator.d.ts.map +1 -0
- package/dist/dynamic-role-generator.js +194 -0
- package/dist/dynamic-role-generator.js.map +1 -0
- package/dist/errors.d.ts +69 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +102 -0
- package/dist/errors.js.map +1 -0
- package/dist/event-bus.d.ts +159 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +305 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/execution-engine.d.ts +425 -0
- package/dist/execution-engine.d.ts.map +1 -0
- package/dist/execution-engine.js +1555 -0
- package/dist/execution-engine.js.map +1 -0
- package/dist/git-intelligence.d.ts +306 -0
- package/dist/git-intelligence.d.ts.map +1 -0
- package/dist/git-intelligence.js +1099 -0
- package/dist/git-intelligence.js.map +1 -0
- package/dist/governor.d.ts +77 -0
- package/dist/governor.d.ts.map +1 -0
- package/dist/governor.js +161 -0
- package/dist/governor.js.map +1 -0
- package/dist/hierarchical-planner.d.ts +313 -0
- package/dist/hierarchical-planner.d.ts.map +1 -0
- package/dist/hierarchical-planner.js +981 -0
- package/dist/hierarchical-planner.js.map +1 -0
- package/dist/index.d.ts +121 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +123 -0
- package/dist/index.js.map +1 -0
- package/dist/intent-inference.d.ts +103 -0
- package/dist/intent-inference.d.ts.map +1 -0
- package/dist/intent-inference.js +605 -0
- package/dist/intent-inference.js.map +1 -0
- package/dist/interrupt-manager.d.ts +143 -0
- package/dist/interrupt-manager.d.ts.map +1 -0
- package/dist/interrupt-manager.js +196 -0
- package/dist/interrupt-manager.js.map +1 -0
- package/dist/kernel.d.ts +564 -0
- package/dist/kernel.d.ts.map +1 -0
- package/dist/kernel.js +1419 -0
- package/dist/kernel.js.map +1 -0
- package/dist/language-support.d.ts +232 -0
- package/dist/language-support.d.ts.map +1 -0
- package/dist/language-support.js +1134 -0
- package/dist/language-support.js.map +1 -0
- package/dist/llm-client.d.ts +82 -0
- package/dist/llm-client.d.ts.map +1 -0
- package/dist/llm-client.js +475 -0
- package/dist/llm-client.js.map +1 -0
- package/dist/mcp-client.d.ts +232 -0
- package/dist/mcp-client.d.ts.map +1 -0
- package/dist/mcp-client.js +718 -0
- package/dist/mcp-client.js.map +1 -0
- package/dist/memory-manager.d.ts +200 -0
- package/dist/memory-manager.d.ts.map +1 -0
- package/dist/memory-manager.js +568 -0
- package/dist/memory-manager.js.map +1 -0
- package/dist/memory.d.ts +87 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +341 -0
- package/dist/memory.js.map +1 -0
- package/dist/model-router.d.ts +245 -0
- package/dist/model-router.d.ts.map +1 -0
- package/dist/model-router.js +632 -0
- package/dist/model-router.js.map +1 -0
- package/dist/parallel-executor.d.ts +125 -0
- package/dist/parallel-executor.d.ts.map +1 -0
- package/dist/parallel-executor.js +201 -0
- package/dist/parallel-executor.js.map +1 -0
- package/dist/perf-optimizer.d.ts +212 -0
- package/dist/perf-optimizer.d.ts.map +1 -0
- package/dist/perf-optimizer.js +721 -0
- package/dist/perf-optimizer.js.map +1 -0
- package/dist/persona.d.ts +305 -0
- package/dist/persona.d.ts.map +1 -0
- package/dist/persona.js +887 -0
- package/dist/persona.js.map +1 -0
- package/dist/planner.d.ts +70 -0
- package/dist/planner.d.ts.map +1 -0
- package/dist/planner.js +264 -0
- package/dist/planner.js.map +1 -0
- package/dist/qa-pipeline.d.ts +365 -0
- package/dist/qa-pipeline.d.ts.map +1 -0
- package/dist/qa-pipeline.js +1352 -0
- package/dist/qa-pipeline.js.map +1 -0
- package/dist/reasoning-adapter.d.ts +116 -0
- package/dist/reasoning-adapter.d.ts.map +1 -0
- package/dist/reasoning-adapter.js +187 -0
- package/dist/reasoning-adapter.js.map +1 -0
- package/dist/role-registry.d.ts +55 -0
- package/dist/role-registry.d.ts.map +1 -0
- package/dist/role-registry.js +192 -0
- package/dist/role-registry.js.map +1 -0
- package/dist/sandbox-tiers.d.ts +327 -0
- package/dist/sandbox-tiers.d.ts.map +1 -0
- package/dist/sandbox-tiers.js +928 -0
- package/dist/sandbox-tiers.js.map +1 -0
- package/dist/security-scanner.d.ts +222 -0
- package/dist/security-scanner.d.ts.map +1 -0
- package/dist/security-scanner.js +1129 -0
- package/dist/security-scanner.js.map +1 -0
- package/dist/security.d.ts +93 -0
- package/dist/security.d.ts.map +1 -0
- package/dist/security.js +393 -0
- package/dist/security.js.map +1 -0
- package/dist/self-reflection.d.ts +397 -0
- package/dist/self-reflection.d.ts.map +1 -0
- package/dist/self-reflection.js +908 -0
- package/dist/self-reflection.js.map +1 -0
- package/dist/session-persistence.d.ts +191 -0
- package/dist/session-persistence.d.ts.map +1 -0
- package/dist/session-persistence.js +395 -0
- package/dist/session-persistence.js.map +1 -0
- package/dist/speculative-executor.d.ts +210 -0
- package/dist/speculative-executor.d.ts.map +1 -0
- package/dist/speculative-executor.js +618 -0
- package/dist/speculative-executor.js.map +1 -0
- package/dist/state-machine.d.ts +289 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +695 -0
- package/dist/state-machine.js.map +1 -0
- package/dist/sub-agent.d.ts +177 -0
- package/dist/sub-agent.d.ts.map +1 -0
- package/dist/sub-agent.js +303 -0
- package/dist/sub-agent.js.map +1 -0
- package/dist/system-prompt.d.ts +26 -0
- package/dist/system-prompt.d.ts.map +1 -0
- package/dist/system-prompt.js +84 -0
- package/dist/system-prompt.js.map +1 -0
- package/dist/test-intelligence.d.ts +439 -0
- package/dist/test-intelligence.d.ts.map +1 -0
- package/dist/test-intelligence.js +1165 -0
- package/dist/test-intelligence.js.map +1 -0
- package/dist/types.d.ts +632 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/vector-index.d.ts +314 -0
- package/dist/vector-index.d.ts.map +1 -0
- package/dist/vector-index.js +618 -0
- package/dist/vector-index.js.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,820 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module agent-logger
|
|
3
|
+
* @description YUAN Agent Logger — Structured, layered logging for agent execution.
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Layer-transition logging (fires ONCE per layer entry)
|
|
7
|
+
* - Internal reasoning/decision tracking
|
|
8
|
+
* - Parallel agent log separation
|
|
9
|
+
* - Formatted human-readable output with tree-drawing characters
|
|
10
|
+
* - JSONL file output for machine parsing
|
|
11
|
+
* - Query and summary APIs for debugging
|
|
12
|
+
*
|
|
13
|
+
* Only depends on Node builtins (fs, path, crypto).
|
|
14
|
+
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
19
|
+
// Constants
|
|
20
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
21
|
+
const LOG_LEVEL_ORDER = {
|
|
22
|
+
trace: 0,
|
|
23
|
+
debug: 1,
|
|
24
|
+
info: 2,
|
|
25
|
+
warn: 3,
|
|
26
|
+
error: 4,
|
|
27
|
+
fatal: 5,
|
|
28
|
+
};
|
|
29
|
+
const LOG_LEVEL_LABELS = {
|
|
30
|
+
trace: "TRACE",
|
|
31
|
+
debug: "DEBUG",
|
|
32
|
+
info: "INFO",
|
|
33
|
+
warn: "WARN",
|
|
34
|
+
error: "ERROR",
|
|
35
|
+
fatal: "FATAL",
|
|
36
|
+
};
|
|
37
|
+
/** ANSI color codes for console output */
|
|
38
|
+
const COLORS = {
|
|
39
|
+
reset: "\x1b[0m",
|
|
40
|
+
dim: "\x1b[2m",
|
|
41
|
+
red: "\x1b[31m",
|
|
42
|
+
green: "\x1b[32m",
|
|
43
|
+
yellow: "\x1b[33m",
|
|
44
|
+
blue: "\x1b[34m",
|
|
45
|
+
magenta: "\x1b[35m",
|
|
46
|
+
cyan: "\x1b[36m",
|
|
47
|
+
white: "\x1b[37m",
|
|
48
|
+
bgRed: "\x1b[41m",
|
|
49
|
+
};
|
|
50
|
+
const LEVEL_COLORS = {
|
|
51
|
+
trace: COLORS.dim,
|
|
52
|
+
debug: COLORS.cyan,
|
|
53
|
+
info: COLORS.green,
|
|
54
|
+
warn: COLORS.yellow,
|
|
55
|
+
error: COLORS.red,
|
|
56
|
+
fatal: COLORS.bgRed + COLORS.white,
|
|
57
|
+
};
|
|
58
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
59
|
+
// AgentLogger
|
|
60
|
+
// ══════════════════════════════════════════════════════════════════════
|
|
61
|
+
/**
|
|
62
|
+
* Structured logger for YUAN agent execution.
|
|
63
|
+
*
|
|
64
|
+
* Tracks layer transitions (firing once per entry), internal reasoning,
|
|
65
|
+
* decisions, tool calls, parallel agent lifecycles, and conflicts.
|
|
66
|
+
* Supports memory, file (JSONL), console, and callback outputs.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* const logger = new AgentLogger({ sessionId: "sess-1" });
|
|
71
|
+
* logger.logInput("Add OAuth to CLI");
|
|
72
|
+
* const exit = logger.enterLayer("analyze");
|
|
73
|
+
* logger.logReasoning("Need to check existing auth...");
|
|
74
|
+
* exit(); // logs duration
|
|
75
|
+
* console.log(logger.getFormattedLog());
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export class AgentLogger {
|
|
79
|
+
sessionId;
|
|
80
|
+
agentId;
|
|
81
|
+
parentAgentId;
|
|
82
|
+
level;
|
|
83
|
+
outputs;
|
|
84
|
+
fireOncePerLayer;
|
|
85
|
+
includeReasoning;
|
|
86
|
+
includeParallel;
|
|
87
|
+
maxLogSize;
|
|
88
|
+
logDir;
|
|
89
|
+
separateParallelLogs;
|
|
90
|
+
/** In-memory log storage */
|
|
91
|
+
entries = [];
|
|
92
|
+
/** Number of entries already flushed to file (prevents duplicate writes) */
|
|
93
|
+
flushedCount = 0;
|
|
94
|
+
/** Layers that have already emitted an "entered" log */
|
|
95
|
+
enteredLayers = new Set();
|
|
96
|
+
/** Current layer nesting stack */
|
|
97
|
+
layerStack = [];
|
|
98
|
+
/** Child loggers (parallel agents) keyed by agentId */
|
|
99
|
+
children = new Map();
|
|
100
|
+
/** Monotonic counter for ordering */
|
|
101
|
+
idCounter = 0;
|
|
102
|
+
constructor(config) {
|
|
103
|
+
this.sessionId = config.sessionId;
|
|
104
|
+
this.agentId = config.agentId;
|
|
105
|
+
this.parentAgentId = config.parentAgentId;
|
|
106
|
+
this.level = config.level ?? "info";
|
|
107
|
+
this.outputs = config.outputs ?? [{ type: "memory" }];
|
|
108
|
+
this.fireOncePerLayer = config.fireOncePerLayer ?? true;
|
|
109
|
+
this.includeReasoning = config.includeReasoning ?? true;
|
|
110
|
+
this.includeParallel = config.includeParallel ?? true;
|
|
111
|
+
this.maxLogSize = config.maxLogSize ?? 5000;
|
|
112
|
+
this.logDir = config.logDir;
|
|
113
|
+
this.separateParallelLogs = config.separateParallelLogs ?? true;
|
|
114
|
+
}
|
|
115
|
+
// ════════════════════════════════════════════════════════════════════
|
|
116
|
+
// Core Level Methods
|
|
117
|
+
// ════════════════════════════════════════════════════════════════════
|
|
118
|
+
/** Log at trace level */
|
|
119
|
+
trace(category, message, data) {
|
|
120
|
+
this.log("trace", category, message, data);
|
|
121
|
+
}
|
|
122
|
+
/** Log at debug level */
|
|
123
|
+
debug(category, message, data) {
|
|
124
|
+
this.log("debug", category, message, data);
|
|
125
|
+
}
|
|
126
|
+
/** Log at info level */
|
|
127
|
+
info(category, message, data) {
|
|
128
|
+
this.log("info", category, message, data);
|
|
129
|
+
}
|
|
130
|
+
/** Log at warn level */
|
|
131
|
+
warn(category, message, data) {
|
|
132
|
+
this.log("warn", category, message, data);
|
|
133
|
+
}
|
|
134
|
+
/** Log at error level */
|
|
135
|
+
error(category, message, data) {
|
|
136
|
+
this.log("error", category, message, data);
|
|
137
|
+
}
|
|
138
|
+
/** Log at fatal level */
|
|
139
|
+
fatal(category, message, data) {
|
|
140
|
+
this.log("fatal", category, message, data);
|
|
141
|
+
}
|
|
142
|
+
// ════════════════════════════════════════════════════════════════════
|
|
143
|
+
// Layer Tracking
|
|
144
|
+
// ════════════════════════════════════════════════════════════════════
|
|
145
|
+
/**
|
|
146
|
+
* Enter a layer. Logs the entry ONCE (if fireOncePerLayer is true).
|
|
147
|
+
* Returns an exit function that logs duration when called.
|
|
148
|
+
*
|
|
149
|
+
* @param layer - Layer name (e.g., "analyze", "plan", "implement", "verify")
|
|
150
|
+
* @param message - Optional entry message
|
|
151
|
+
* @returns Exit function — call it when the layer completes
|
|
152
|
+
*/
|
|
153
|
+
enterLayer(layer, message) {
|
|
154
|
+
const startTime = Date.now();
|
|
155
|
+
const depth = this.layerStack.length;
|
|
156
|
+
this.layerStack.push(layer);
|
|
157
|
+
// Fire entry log only once per layer (unless disabled)
|
|
158
|
+
if (!this.fireOncePerLayer || !this.enteredLayers.has(layer)) {
|
|
159
|
+
this.enteredLayers.add(layer);
|
|
160
|
+
const entry = this.createEntry("info", "layer", message ?? `${layer.toUpperCase()} (entered)`);
|
|
161
|
+
entry.layer = layer;
|
|
162
|
+
entry.layerDepth = depth;
|
|
163
|
+
this.emit(entry);
|
|
164
|
+
}
|
|
165
|
+
// Return exit function
|
|
166
|
+
return () => {
|
|
167
|
+
const durationMs = Date.now() - startTime;
|
|
168
|
+
// Pop the layer from stack
|
|
169
|
+
const idx = this.layerStack.lastIndexOf(layer);
|
|
170
|
+
if (idx !== -1)
|
|
171
|
+
this.layerStack.splice(idx, 1);
|
|
172
|
+
const exitEntry = this.createEntry("info", "layer", `${layer.toUpperCase()} (${(durationMs / 1000).toFixed(2)}s)`);
|
|
173
|
+
exitEntry.layer = layer;
|
|
174
|
+
exitEntry.layerDepth = depth;
|
|
175
|
+
exitEntry.durationMs = durationMs;
|
|
176
|
+
this.emit(exitEntry);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// ════════════════════════════════════════════════════════════════════
|
|
180
|
+
// Reasoning & Decisions
|
|
181
|
+
// ════════════════════════════════════════════════════════════════════
|
|
182
|
+
/**
|
|
183
|
+
* Log internal reasoning — what the agent is thinking.
|
|
184
|
+
*
|
|
185
|
+
* @param thought - The reasoning text
|
|
186
|
+
* @param options - Options being considered
|
|
187
|
+
* @param chosen - Which option was chosen
|
|
188
|
+
* @param confidence - Confidence level 0–1
|
|
189
|
+
* @param why - Explanation for the choice
|
|
190
|
+
*/
|
|
191
|
+
logReasoning(thought, options, chosen, confidence, why) {
|
|
192
|
+
if (!this.includeReasoning)
|
|
193
|
+
return;
|
|
194
|
+
const entry = this.createEntry("debug", "reasoning", `Thinking: "${thought}"`);
|
|
195
|
+
entry.reasoning = { thought, options, chosen, confidence, why };
|
|
196
|
+
this.emit(entry);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Log a decision point — a fork where the agent chose one path.
|
|
200
|
+
*
|
|
201
|
+
* @param question - What decision was made
|
|
202
|
+
* @param options - Available options
|
|
203
|
+
* @param chosen - Selected option
|
|
204
|
+
* @param why - Rationale
|
|
205
|
+
* @param confidence - Confidence level 0–1
|
|
206
|
+
*/
|
|
207
|
+
logDecision(question, options, chosen, why, confidence) {
|
|
208
|
+
const entry = this.createEntry("info", "decision", `Decision: "${chosen}"`);
|
|
209
|
+
entry.reasoning = {
|
|
210
|
+
thought: question,
|
|
211
|
+
options,
|
|
212
|
+
chosen,
|
|
213
|
+
confidence: confidence ?? 0.5,
|
|
214
|
+
why,
|
|
215
|
+
};
|
|
216
|
+
this.emit(entry);
|
|
217
|
+
}
|
|
218
|
+
// ════════════════════════════════════════════════════════════════════
|
|
219
|
+
// Parallel Agent Tracking
|
|
220
|
+
// ════════════════════════════════════════════════════════════════════
|
|
221
|
+
/**
|
|
222
|
+
* Create a child logger for a parallel agent.
|
|
223
|
+
* The child has its own in-memory buffer and optionally a separate log file.
|
|
224
|
+
*
|
|
225
|
+
* @param agentId - Unique ID for the child agent
|
|
226
|
+
* @param task - What this agent is doing
|
|
227
|
+
* @param groupId - DAG execution group ID
|
|
228
|
+
* @param agentIndex - Agent index within the group
|
|
229
|
+
* @param totalAgents - Total agents in the group
|
|
230
|
+
* @returns A new AgentLogger for the child agent
|
|
231
|
+
*/
|
|
232
|
+
createChildLogger(agentId, task, groupId, agentIndex, totalAgents) {
|
|
233
|
+
const childOutputs = [{ type: "memory" }];
|
|
234
|
+
// Add file output if logDir is set and separateParallelLogs is on
|
|
235
|
+
if (this.logDir && this.separateParallelLogs) {
|
|
236
|
+
const filePath = join(this.logDir, `agent-${agentId}.jsonl`);
|
|
237
|
+
childOutputs.push({ type: "file", path: filePath });
|
|
238
|
+
}
|
|
239
|
+
// Forward callbacks from parent
|
|
240
|
+
for (const output of this.outputs) {
|
|
241
|
+
if (output.type === "callback") {
|
|
242
|
+
childOutputs.push(output);
|
|
243
|
+
}
|
|
244
|
+
if (output.type === "console") {
|
|
245
|
+
childOutputs.push(output);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const child = new AgentLogger({
|
|
249
|
+
sessionId: this.sessionId,
|
|
250
|
+
agentId,
|
|
251
|
+
parentAgentId: this.agentId ?? this.sessionId,
|
|
252
|
+
level: this.level,
|
|
253
|
+
outputs: childOutputs,
|
|
254
|
+
fireOncePerLayer: this.fireOncePerLayer,
|
|
255
|
+
includeReasoning: this.includeReasoning,
|
|
256
|
+
includeParallel: this.includeParallel,
|
|
257
|
+
maxLogSize: this.maxLogSize,
|
|
258
|
+
logDir: this.logDir,
|
|
259
|
+
separateParallelLogs: this.separateParallelLogs,
|
|
260
|
+
});
|
|
261
|
+
this.children.set(agentId, child);
|
|
262
|
+
// Log spawn on the parent
|
|
263
|
+
this.logParallelSpawn(groupId, agentId, task, agentIndex, totalAgents);
|
|
264
|
+
return child;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Log parallel agent spawning.
|
|
268
|
+
*/
|
|
269
|
+
logParallelSpawn(groupId, agentId, task, index, total) {
|
|
270
|
+
if (!this.includeParallel)
|
|
271
|
+
return;
|
|
272
|
+
const entry = this.createEntry("info", "parallel", `Agent[${index}]: "${task}"`);
|
|
273
|
+
entry.parallel = {
|
|
274
|
+
groupId,
|
|
275
|
+
agentIndex: index,
|
|
276
|
+
totalAgents: total,
|
|
277
|
+
task,
|
|
278
|
+
status: "spawned",
|
|
279
|
+
};
|
|
280
|
+
entry.agentId = agentId;
|
|
281
|
+
this.emit(entry);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Log parallel agent completion.
|
|
285
|
+
*/
|
|
286
|
+
logParallelComplete(groupId, agentId, result, tokens) {
|
|
287
|
+
if (!this.includeParallel)
|
|
288
|
+
return;
|
|
289
|
+
const totalTokens = tokens ? tokens.input + tokens.output : 0;
|
|
290
|
+
const entry = this.createEntry("info", "parallel", `Agent ${agentId}: completed (${totalTokens.toLocaleString()} tokens)`);
|
|
291
|
+
entry.parallel = {
|
|
292
|
+
groupId,
|
|
293
|
+
agentIndex: 0,
|
|
294
|
+
totalAgents: 0,
|
|
295
|
+
task: result,
|
|
296
|
+
status: "completed",
|
|
297
|
+
};
|
|
298
|
+
entry.agentId = agentId;
|
|
299
|
+
if (tokens)
|
|
300
|
+
entry.tokens = tokens;
|
|
301
|
+
this.emit(entry);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Log parallel agent failure.
|
|
305
|
+
*/
|
|
306
|
+
logParallelFailed(groupId, agentId, error) {
|
|
307
|
+
if (!this.includeParallel)
|
|
308
|
+
return;
|
|
309
|
+
const entry = this.createEntry("error", "parallel", `Agent ${agentId}: FAILED — ${error}`);
|
|
310
|
+
entry.parallel = {
|
|
311
|
+
groupId,
|
|
312
|
+
agentIndex: 0,
|
|
313
|
+
totalAgents: 0,
|
|
314
|
+
task: error,
|
|
315
|
+
status: "failed",
|
|
316
|
+
};
|
|
317
|
+
entry.agentId = agentId;
|
|
318
|
+
this.emit(entry);
|
|
319
|
+
}
|
|
320
|
+
// ════════════════════════════════════════════════════════════════════
|
|
321
|
+
// Input / Output
|
|
322
|
+
// ════════════════════════════════════════════════════════════════════
|
|
323
|
+
/**
|
|
324
|
+
* Log user input — the very first thing on every request.
|
|
325
|
+
*
|
|
326
|
+
* @param goal - The user's goal
|
|
327
|
+
* @param mode - Agent mode (e.g., "code", "architect")
|
|
328
|
+
* @param model - Model being used
|
|
329
|
+
*/
|
|
330
|
+
logInput(goal, mode, model) {
|
|
331
|
+
const parts = [`Goal: "${goal}"`];
|
|
332
|
+
if (mode)
|
|
333
|
+
parts.push(`mode=${mode}`);
|
|
334
|
+
if (model)
|
|
335
|
+
parts.push(`model=${model}`);
|
|
336
|
+
const entry = this.createEntry("info", "input", parts.join(", "));
|
|
337
|
+
entry.data = { goal, mode, model };
|
|
338
|
+
this.emit(entry);
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Log final output — the last entry for a request.
|
|
342
|
+
*
|
|
343
|
+
* @param result - Summary of the result
|
|
344
|
+
* @param success - Whether the agent succeeded
|
|
345
|
+
* @param tokens - Total token usage
|
|
346
|
+
*/
|
|
347
|
+
logOutput(result, success, tokens) {
|
|
348
|
+
const totalTokens = tokens ? tokens.input + tokens.output : 0;
|
|
349
|
+
const status = success ? "Success" : "Failed";
|
|
350
|
+
const msg = `Output: ${status} (${totalTokens.toLocaleString()} tokens)`;
|
|
351
|
+
const entry = this.createEntry("info", "input", msg);
|
|
352
|
+
entry.data = { result, success };
|
|
353
|
+
if (tokens)
|
|
354
|
+
entry.tokens = tokens;
|
|
355
|
+
this.emit(entry);
|
|
356
|
+
}
|
|
357
|
+
// ════════════════════════════════════════════════════════════════════
|
|
358
|
+
// Tool Tracking
|
|
359
|
+
// ════════════════════════════════════════════════════════════════════
|
|
360
|
+
/** Log a tool call (before execution) */
|
|
361
|
+
logToolCall(toolName, input) {
|
|
362
|
+
const entry = this.createEntry("info", "tool", `Calling ${toolName}`);
|
|
363
|
+
entry.data = { toolName, input };
|
|
364
|
+
this.emit(entry);
|
|
365
|
+
}
|
|
366
|
+
/** Log a tool result (after execution) */
|
|
367
|
+
logToolResult(toolName, output, durationMs, success) {
|
|
368
|
+
const status = success ? "OK" : "FAILED";
|
|
369
|
+
const entry = this.createEntry(success ? "info" : "error", "tool", `${toolName} ${status} (${durationMs}ms)`);
|
|
370
|
+
entry.data = { toolName, output: output.slice(0, 500), success };
|
|
371
|
+
entry.durationMs = durationMs;
|
|
372
|
+
this.emit(entry);
|
|
373
|
+
}
|
|
374
|
+
// ════════════════════════════════════════════════════════════════════
|
|
375
|
+
// Conflict Tracking
|
|
376
|
+
// ════════════════════════════════════════════════════════════════════
|
|
377
|
+
/** Log a detected conflict between agents/files */
|
|
378
|
+
logConflict(type, fileA, fileB, severity) {
|
|
379
|
+
const entry = this.createEntry(severity === "critical" ? "error" : "warn", "conflict", `Conflict [${type}]: ${fileA} <-> ${fileB} (${severity})`);
|
|
380
|
+
entry.data = { type, fileA, fileB, severity };
|
|
381
|
+
this.emit(entry);
|
|
382
|
+
}
|
|
383
|
+
/** Log conflict resolution */
|
|
384
|
+
logConflictResolved(strategy, result) {
|
|
385
|
+
const entry = this.createEntry("info", "conflict", `Resolved via ${strategy}: ${result}`);
|
|
386
|
+
entry.data = { strategy, result };
|
|
387
|
+
this.emit(entry);
|
|
388
|
+
}
|
|
389
|
+
// ════════════════════════════════════════════════════════════════════
|
|
390
|
+
// Reflection Tracking
|
|
391
|
+
// ════════════════════════════════════════════════════════════════════
|
|
392
|
+
/** Log self-reflection result */
|
|
393
|
+
logReflection(verdict, score, issues) {
|
|
394
|
+
const entry = this.createEntry("info", "reflection", `Verdict: ${verdict} (score: ${score})`);
|
|
395
|
+
entry.data = { verdict, score, issues };
|
|
396
|
+
this.emit(entry);
|
|
397
|
+
}
|
|
398
|
+
// ════════════════════════════════════════════════════════════════════
|
|
399
|
+
// Query & Read Back
|
|
400
|
+
// ════════════════════════════════════════════════════════════════════
|
|
401
|
+
/**
|
|
402
|
+
* Get log entries, optionally filtered by a query.
|
|
403
|
+
*
|
|
404
|
+
* @param query - Filter criteria
|
|
405
|
+
* @returns Matching log entries
|
|
406
|
+
*/
|
|
407
|
+
getEntries(query) {
|
|
408
|
+
let result = this.entries;
|
|
409
|
+
if (query) {
|
|
410
|
+
result = result.filter((e) => {
|
|
411
|
+
if (query.sessionId && e.sessionId !== query.sessionId)
|
|
412
|
+
return false;
|
|
413
|
+
if (query.agentId && e.agentId !== query.agentId)
|
|
414
|
+
return false;
|
|
415
|
+
if (query.category && e.category !== query.category)
|
|
416
|
+
return false;
|
|
417
|
+
if (query.level && LOG_LEVEL_ORDER[e.level] < LOG_LEVEL_ORDER[query.level])
|
|
418
|
+
return false;
|
|
419
|
+
if (query.layer && e.layer !== query.layer)
|
|
420
|
+
return false;
|
|
421
|
+
if (query.fromTimestamp && e.timestamp < query.fromTimestamp)
|
|
422
|
+
return false;
|
|
423
|
+
if (query.toTimestamp && e.timestamp > query.toTimestamp)
|
|
424
|
+
return false;
|
|
425
|
+
return true;
|
|
426
|
+
});
|
|
427
|
+
const offset = query.offset ?? 0;
|
|
428
|
+
const limit = query.limit ?? result.length;
|
|
429
|
+
result = result.slice(offset, offset + limit);
|
|
430
|
+
}
|
|
431
|
+
return result;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Get formatted, human-readable text output.
|
|
435
|
+
* Uses tree-drawing characters to visualize layer nesting.
|
|
436
|
+
*
|
|
437
|
+
* @param query - Optional filter
|
|
438
|
+
* @returns Formatted multi-line string
|
|
439
|
+
*/
|
|
440
|
+
getFormattedLog(query) {
|
|
441
|
+
const entries = this.getEntries(query);
|
|
442
|
+
return entries.map((e) => this.formatEntry(e)).join("\n");
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Get a summary overview of the logging session.
|
|
446
|
+
*/
|
|
447
|
+
getSummary() {
|
|
448
|
+
const layerMap = new Map();
|
|
449
|
+
const decisions = [];
|
|
450
|
+
const parallelMap = new Map();
|
|
451
|
+
const errors = [];
|
|
452
|
+
let totalInput = 0;
|
|
453
|
+
let totalOutput = 0;
|
|
454
|
+
for (const entry of this.entries) {
|
|
455
|
+
// Layer tracking
|
|
456
|
+
if (entry.layer) {
|
|
457
|
+
const existing = layerMap.get(entry.layer);
|
|
458
|
+
if (!existing) {
|
|
459
|
+
layerMap.set(entry.layer, {
|
|
460
|
+
name: entry.layer,
|
|
461
|
+
enteredAt: entry.timestamp,
|
|
462
|
+
exitedAt: entry.durationMs != null ? entry.timestamp : undefined,
|
|
463
|
+
duration: entry.durationMs,
|
|
464
|
+
entriesCount: 1,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
existing.entriesCount++;
|
|
469
|
+
if (entry.durationMs != null) {
|
|
470
|
+
existing.exitedAt = entry.timestamp;
|
|
471
|
+
existing.duration = entry.durationMs;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
// Decision tracking
|
|
476
|
+
if (entry.category === "decision" && entry.reasoning) {
|
|
477
|
+
decisions.push({
|
|
478
|
+
question: entry.reasoning.thought,
|
|
479
|
+
chosen: entry.reasoning.chosen ?? "",
|
|
480
|
+
confidence: entry.reasoning.confidence ?? 0,
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
// Parallel tracking
|
|
484
|
+
if (entry.parallel) {
|
|
485
|
+
const gid = entry.parallel.groupId;
|
|
486
|
+
if (!parallelMap.has(gid)) {
|
|
487
|
+
parallelMap.set(gid, { agents: 0, completed: 0, failed: 0 });
|
|
488
|
+
}
|
|
489
|
+
const pg = parallelMap.get(gid);
|
|
490
|
+
if (entry.parallel.status === "spawned")
|
|
491
|
+
pg.agents++;
|
|
492
|
+
if (entry.parallel.status === "completed")
|
|
493
|
+
pg.completed++;
|
|
494
|
+
if (entry.parallel.status === "failed")
|
|
495
|
+
pg.failed++;
|
|
496
|
+
}
|
|
497
|
+
// Errors
|
|
498
|
+
if (entry.level === "error" || entry.level === "fatal") {
|
|
499
|
+
errors.push(entry);
|
|
500
|
+
}
|
|
501
|
+
// Tokens
|
|
502
|
+
if (entry.tokens) {
|
|
503
|
+
totalInput += entry.tokens.input;
|
|
504
|
+
totalOutput += entry.tokens.output;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const firstTs = this.entries.length > 0 ? this.entries[0].timestamp : Date.now();
|
|
508
|
+
const lastTs = this.entries.length > 0 ? this.entries[this.entries.length - 1].timestamp : Date.now();
|
|
509
|
+
return {
|
|
510
|
+
sessionId: this.sessionId,
|
|
511
|
+
totalEntries: this.entries.length,
|
|
512
|
+
duration: lastTs - firstTs,
|
|
513
|
+
layers: Array.from(layerMap.values()),
|
|
514
|
+
decisions,
|
|
515
|
+
parallelGroups: Array.from(parallelMap.entries()).map(([groupId, v]) => ({
|
|
516
|
+
groupId,
|
|
517
|
+
...v,
|
|
518
|
+
})),
|
|
519
|
+
errors,
|
|
520
|
+
tokenUsage: { input: totalInput, output: totalOutput },
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Get only decision-type log entries (the decision trail).
|
|
525
|
+
*/
|
|
526
|
+
getDecisionTrail() {
|
|
527
|
+
return this.entries.filter((e) => e.category === "decision");
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Get parallel agent log entries from this logger and all children.
|
|
531
|
+
*
|
|
532
|
+
* @param groupId - Optional filter by group ID
|
|
533
|
+
*/
|
|
534
|
+
getParallelLogs(groupId) {
|
|
535
|
+
const ownParallel = this.entries.filter((e) => {
|
|
536
|
+
if (e.category !== "parallel")
|
|
537
|
+
return false;
|
|
538
|
+
if (groupId && e.parallel?.groupId !== groupId)
|
|
539
|
+
return false;
|
|
540
|
+
return true;
|
|
541
|
+
});
|
|
542
|
+
// Collect from children
|
|
543
|
+
const childEntries = [];
|
|
544
|
+
for (const child of this.children.values()) {
|
|
545
|
+
const childAll = child.getEntries();
|
|
546
|
+
for (const entry of childAll) {
|
|
547
|
+
if (groupId && entry.parallel?.groupId !== groupId)
|
|
548
|
+
continue;
|
|
549
|
+
childEntries.push(entry);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return [...ownParallel, ...childEntries].sort((a, b) => a.timestamp - b.timestamp);
|
|
553
|
+
}
|
|
554
|
+
/**
|
|
555
|
+
* Get error and fatal entries only.
|
|
556
|
+
*/
|
|
557
|
+
getErrors() {
|
|
558
|
+
return this.entries.filter((e) => e.level === "error" || e.level === "fatal");
|
|
559
|
+
}
|
|
560
|
+
// ════════════════════════════════════════════════════════════════════
|
|
561
|
+
// File Output
|
|
562
|
+
// ════════════════════════════════════════════════════════════════════
|
|
563
|
+
/**
|
|
564
|
+
* Flush all in-memory entries to file outputs.
|
|
565
|
+
* Appends JSONL to file outputs defined in the config.
|
|
566
|
+
*/
|
|
567
|
+
async flush() {
|
|
568
|
+
// Only flush entries that haven't been flushed yet
|
|
569
|
+
const unflushed = this.entries.slice(this.flushedCount);
|
|
570
|
+
if (unflushed.length === 0 && this.children.size === 0)
|
|
571
|
+
return;
|
|
572
|
+
for (const output of this.outputs) {
|
|
573
|
+
if (output.type === "file" && unflushed.length > 0) {
|
|
574
|
+
this.ensureDir(dirname(output.path));
|
|
575
|
+
const lines = unflushed.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
576
|
+
appendFileSync(output.path, lines, "utf-8");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
this.flushedCount = this.entries.length;
|
|
580
|
+
// Flush children
|
|
581
|
+
for (const child of this.children.values()) {
|
|
582
|
+
await child.flush();
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Write the full session log to a specific file path.
|
|
587
|
+
* Writes both JSONL (machine) and .txt (human-readable) versions.
|
|
588
|
+
*
|
|
589
|
+
* @param filePath - Base file path (will create .jsonl and .txt)
|
|
590
|
+
*/
|
|
591
|
+
async writeToFile(filePath) {
|
|
592
|
+
this.ensureDir(dirname(filePath));
|
|
593
|
+
// JSONL version
|
|
594
|
+
const jsonl = this.entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
595
|
+
writeFileSync(filePath, jsonl, "utf-8");
|
|
596
|
+
// Human-readable text version
|
|
597
|
+
const txtPath = filePath.replace(/\.\w+$/, ".txt");
|
|
598
|
+
if (txtPath !== filePath) {
|
|
599
|
+
const formatted = this.getFormattedLog();
|
|
600
|
+
writeFileSync(txtPath, formatted, "utf-8");
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
// ════════════════════════════════════════════════════════════════════
|
|
604
|
+
// Cleanup
|
|
605
|
+
// ════════════════════════════════════════════════════════════════════
|
|
606
|
+
/** Clear all log entries and reset layer tracking */
|
|
607
|
+
clear() {
|
|
608
|
+
this.entries = [];
|
|
609
|
+
this.enteredLayers.clear();
|
|
610
|
+
this.layerStack = [];
|
|
611
|
+
this.idCounter = 0;
|
|
612
|
+
for (const child of this.children.values()) {
|
|
613
|
+
child.clear();
|
|
614
|
+
}
|
|
615
|
+
this.children.clear();
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Prune entries to keep only the most recent N.
|
|
619
|
+
*
|
|
620
|
+
* @param keepLast - Number of entries to keep (default: maxLogSize / 2)
|
|
621
|
+
* @returns Number of entries pruned
|
|
622
|
+
*/
|
|
623
|
+
prune(keepLast) {
|
|
624
|
+
const keep = keepLast ?? Math.floor(this.maxLogSize / 2);
|
|
625
|
+
if (this.entries.length <= keep)
|
|
626
|
+
return 0;
|
|
627
|
+
const pruned = this.entries.length - keep;
|
|
628
|
+
this.entries = this.entries.slice(-keep);
|
|
629
|
+
return pruned;
|
|
630
|
+
}
|
|
631
|
+
// ════════════════════════════════════════════════════════════════════
|
|
632
|
+
// Private
|
|
633
|
+
// ════════════════════════════════════════════════════════════════════
|
|
634
|
+
/**
|
|
635
|
+
* Core log method — creates an entry and emits to all outputs.
|
|
636
|
+
*/
|
|
637
|
+
log(level, category, message, data) {
|
|
638
|
+
if (!this.shouldLog(level))
|
|
639
|
+
return;
|
|
640
|
+
const entry = this.createEntry(level, category, message);
|
|
641
|
+
if (data)
|
|
642
|
+
entry.data = data;
|
|
643
|
+
this.emit(entry);
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Create a new LogEntry with session/agent metadata.
|
|
647
|
+
*/
|
|
648
|
+
createEntry(level, category, message) {
|
|
649
|
+
const entry = {
|
|
650
|
+
id: `log-${this.idCounter++}-${randomUUID().slice(0, 8)}`,
|
|
651
|
+
timestamp: Date.now(),
|
|
652
|
+
level,
|
|
653
|
+
category,
|
|
654
|
+
sessionId: this.sessionId,
|
|
655
|
+
message,
|
|
656
|
+
};
|
|
657
|
+
if (this.agentId)
|
|
658
|
+
entry.agentId = this.agentId;
|
|
659
|
+
if (this.parentAgentId)
|
|
660
|
+
entry.parentAgentId = this.parentAgentId;
|
|
661
|
+
// Attach current layer context
|
|
662
|
+
if (this.layerStack.length > 0) {
|
|
663
|
+
entry.layer = this.layerStack[this.layerStack.length - 1];
|
|
664
|
+
entry.layerDepth = this.layerStack.length - 1;
|
|
665
|
+
}
|
|
666
|
+
return entry;
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Check if a given level meets the minimum threshold.
|
|
670
|
+
*/
|
|
671
|
+
shouldLog(level) {
|
|
672
|
+
return LOG_LEVEL_ORDER[level] >= LOG_LEVEL_ORDER[this.level];
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Emit a log entry to all configured outputs.
|
|
676
|
+
*/
|
|
677
|
+
emit(entry) {
|
|
678
|
+
// Store in memory
|
|
679
|
+
const hasMemory = this.outputs.some((o) => o.type === "memory");
|
|
680
|
+
if (hasMemory) {
|
|
681
|
+
this.entries.push(entry);
|
|
682
|
+
// Enforce max size
|
|
683
|
+
if (this.entries.length > this.maxLogSize) {
|
|
684
|
+
this.entries = this.entries.slice(-Math.floor(this.maxLogSize * 0.8));
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
// Send to other outputs
|
|
688
|
+
for (const output of this.outputs) {
|
|
689
|
+
switch (output.type) {
|
|
690
|
+
case "memory":
|
|
691
|
+
// Already handled above
|
|
692
|
+
break;
|
|
693
|
+
case "file":
|
|
694
|
+
try {
|
|
695
|
+
this.ensureDir(dirname(output.path));
|
|
696
|
+
appendFileSync(output.path, JSON.stringify(entry) + "\n", "utf-8");
|
|
697
|
+
}
|
|
698
|
+
catch {
|
|
699
|
+
// Silently fail file writes to avoid crashing the agent
|
|
700
|
+
}
|
|
701
|
+
break;
|
|
702
|
+
case "console":
|
|
703
|
+
process.stderr.write((output.colorize ? this.formatEntryColor(entry) : this.formatEntry(entry)) + "\n");
|
|
704
|
+
break;
|
|
705
|
+
case "callback":
|
|
706
|
+
try {
|
|
707
|
+
output.fn(entry);
|
|
708
|
+
}
|
|
709
|
+
catch {
|
|
710
|
+
// Silently fail callbacks
|
|
711
|
+
}
|
|
712
|
+
break;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Format a single log entry as a human-readable line with tree characters.
|
|
718
|
+
*/
|
|
719
|
+
formatEntry(entry) {
|
|
720
|
+
const time = this.formatTime(entry.timestamp);
|
|
721
|
+
const level = LOG_LEVEL_LABELS[entry.level].padEnd(5);
|
|
722
|
+
const cat = entry.category;
|
|
723
|
+
// Determine prefix based on category and content
|
|
724
|
+
let prefix = "";
|
|
725
|
+
let body = entry.message;
|
|
726
|
+
if (entry.category === "input") {
|
|
727
|
+
// Input/output markers
|
|
728
|
+
if (entry.message.startsWith("Output:")) {
|
|
729
|
+
prefix = "\u25C0 ";
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
prefix = "\u25B6 ";
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
else if (entry.category === "layer") {
|
|
736
|
+
// Layer entry/exit with box-drawing
|
|
737
|
+
if (entry.durationMs != null) {
|
|
738
|
+
prefix = "\u2514\u2500 ";
|
|
739
|
+
}
|
|
740
|
+
else {
|
|
741
|
+
prefix = "\u250C\u2500 ";
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
else if (entry.category === "parallel") {
|
|
745
|
+
if (entry.parallel?.status === "spawned" && entry.message.includes("Agent[0]")) {
|
|
746
|
+
prefix = "\u2502 \u250C\u2500 ";
|
|
747
|
+
}
|
|
748
|
+
else if (entry.parallel?.status === "completed" || entry.parallel?.status === "failed") {
|
|
749
|
+
const icon = entry.parallel.status === "completed" ? "\u2705" : "\u274C";
|
|
750
|
+
prefix = `\u2502 \u2502 ${icon} `;
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
prefix = "\u2502 \u2502 ";
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
else if (entry.category === "decision" && entry.reasoning) {
|
|
757
|
+
// Decision with indented details
|
|
758
|
+
const opts = entry.reasoning.options?.join(", ") ?? "";
|
|
759
|
+
const conf = entry.reasoning.confidence != null
|
|
760
|
+
? ` (confidence: ${entry.reasoning.confidence.toFixed(2)})`
|
|
761
|
+
: "";
|
|
762
|
+
body = `Decision: "${entry.reasoning.chosen}"`;
|
|
763
|
+
const lines = [
|
|
764
|
+
`[${time}] [${level}] [${cat}] \u2502 ${body}`,
|
|
765
|
+
`\u2502 Options: [${opts}]`,
|
|
766
|
+
`\u2502 Chosen: ${entry.reasoning.chosen}${conf}`,
|
|
767
|
+
];
|
|
768
|
+
if (entry.reasoning.why) {
|
|
769
|
+
lines.push(`\u2502 Why: "${entry.reasoning.why}"`);
|
|
770
|
+
}
|
|
771
|
+
return lines.join("\n");
|
|
772
|
+
}
|
|
773
|
+
else {
|
|
774
|
+
// Inside a layer — use pipe prefix
|
|
775
|
+
if (this.layerStack.length > 0 || entry.layer) {
|
|
776
|
+
prefix = "\u2502 ";
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return `[${time}] [${level}] [${cat}] ${prefix}${body}`;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Format a log entry with ANSI colors for console output.
|
|
783
|
+
*/
|
|
784
|
+
formatEntryColor(entry) {
|
|
785
|
+
const plain = this.formatEntry(entry);
|
|
786
|
+
const color = LEVEL_COLORS[entry.level] ?? "";
|
|
787
|
+
return `${color}${plain}${COLORS.reset}`;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Format epoch ms to HH:MM:SS.mmm
|
|
791
|
+
*/
|
|
792
|
+
formatTime(timestamp) {
|
|
793
|
+
const d = new Date(timestamp);
|
|
794
|
+
const h = String(d.getHours()).padStart(2, "0");
|
|
795
|
+
const m = String(d.getMinutes()).padStart(2, "0");
|
|
796
|
+
const s = String(d.getSeconds()).padStart(2, "0");
|
|
797
|
+
const ms = String(d.getMilliseconds()).padStart(3, "0");
|
|
798
|
+
return `${h}:${m}:${s}.${ms}`;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Ensure a directory exists, creating it recursively if needed.
|
|
802
|
+
*/
|
|
803
|
+
ensureDir(dir) {
|
|
804
|
+
try {
|
|
805
|
+
mkdirSync(dir, { recursive: true });
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
// Ignore — directory may already exist
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Get the log file path for a given agent ID.
|
|
813
|
+
*/
|
|
814
|
+
getLogFilePath(agentId) {
|
|
815
|
+
const dir = this.logDir ?? "/tmp/yuan-logs";
|
|
816
|
+
const name = agentId ? `agent-${agentId}` : `session-${this.sessionId}`;
|
|
817
|
+
return join(dir, `${name}.jsonl`);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
//# sourceMappingURL=agent-logger.js.map
|