@yuaone/core 0.9.8 → 0.9.10
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/dist/__tests__/context-manager.test.js +5 -9
- package/dist/__tests__/context-manager.test.js.map +1 -1
- package/dist/agent-coordinator.d.ts +172 -0
- package/dist/agent-coordinator.d.ts.map +1 -0
- package/dist/agent-coordinator.js +390 -0
- package/dist/agent-coordinator.js.map +1 -0
- package/dist/agent-loop.d.ts +83 -39
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +694 -471
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent-reputation.d.ts +72 -0
- package/dist/agent-reputation.d.ts.map +1 -0
- package/dist/agent-reputation.js +222 -0
- package/dist/agent-reputation.js.map +1 -0
- package/dist/arch-summarizer.d.ts +48 -0
- package/dist/arch-summarizer.d.ts.map +1 -0
- package/dist/arch-summarizer.js +239 -0
- package/dist/arch-summarizer.js.map +1 -0
- package/dist/autonomous/explicit-planner.d.ts +45 -0
- package/dist/autonomous/explicit-planner.d.ts.map +1 -0
- package/dist/autonomous/explicit-planner.js +99 -0
- package/dist/autonomous/explicit-planner.js.map +1 -0
- package/dist/autonomous/incident-debugger.d.ts +78 -0
- package/dist/autonomous/incident-debugger.d.ts.map +1 -0
- package/dist/autonomous/incident-debugger.js +324 -0
- package/dist/autonomous/incident-debugger.js.map +1 -0
- package/dist/autonomous/index.d.ts +15 -0
- package/dist/autonomous/index.d.ts.map +1 -0
- package/dist/autonomous/index.js +10 -0
- package/dist/autonomous/index.js.map +1 -0
- package/dist/autonomous/patch-tournament.d.ts +82 -0
- package/dist/autonomous/patch-tournament.d.ts.map +1 -0
- package/dist/autonomous/patch-tournament.js +150 -0
- package/dist/autonomous/patch-tournament.js.map +1 -0
- package/dist/autonomous/research-agent.d.ts +66 -0
- package/dist/autonomous/research-agent.d.ts.map +1 -0
- package/dist/autonomous/research-agent.js +210 -0
- package/dist/autonomous/research-agent.js.map +1 -0
- package/dist/autonomous/task-memory.d.ts +63 -0
- package/dist/autonomous/task-memory.d.ts.map +1 -0
- package/dist/autonomous/task-memory.js +143 -0
- package/dist/autonomous/task-memory.js.map +1 -0
- package/dist/budget-governor-v2.d.ts +93 -0
- package/dist/budget-governor-v2.d.ts.map +1 -0
- package/dist/budget-governor-v2.js +345 -0
- package/dist/budget-governor-v2.js.map +1 -0
- package/dist/capability-graph.d.ts +102 -0
- package/dist/capability-graph.d.ts.map +1 -0
- package/dist/capability-graph.js +397 -0
- package/dist/capability-graph.js.map +1 -0
- package/dist/capability-self-model.d.ts +144 -0
- package/dist/capability-self-model.d.ts.map +1 -0
- package/dist/capability-self-model.js +312 -0
- package/dist/capability-self-model.js.map +1 -0
- package/dist/checkpoint-manager.d.ts +94 -0
- package/dist/checkpoint-manager.d.ts.map +1 -0
- package/dist/checkpoint-manager.js +225 -0
- package/dist/checkpoint-manager.js.map +1 -0
- package/dist/continuation-engine.js +1 -1
- package/dist/continuation-engine.js.map +1 -1
- package/dist/dag-orchestrator.d.ts +0 -3
- package/dist/dag-orchestrator.d.ts.map +1 -1
- package/dist/dag-orchestrator.js +0 -1
- package/dist/dag-orchestrator.js.map +1 -1
- package/dist/evidence-chain.d.ts +99 -0
- package/dist/evidence-chain.d.ts.map +1 -0
- package/dist/evidence-chain.js +200 -0
- package/dist/evidence-chain.js.map +1 -0
- package/dist/execution-engine.d.ts.map +1 -1
- package/dist/execution-engine.js +0 -1
- package/dist/execution-engine.js.map +1 -1
- package/dist/failure-signature-memory.d.ts +61 -0
- package/dist/failure-signature-memory.d.ts.map +1 -0
- package/dist/failure-signature-memory.js +278 -0
- package/dist/failure-signature-memory.js.map +1 -0
- package/dist/index.d.ts +52 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -7
- package/dist/index.js.map +1 -1
- package/dist/language-detector.d.ts.map +1 -1
- package/dist/language-detector.js +122 -43
- package/dist/language-detector.js.map +1 -1
- package/dist/llm-client.d.ts +0 -7
- package/dist/llm-client.d.ts.map +1 -1
- package/dist/llm-client.js +29 -113
- package/dist/llm-client.js.map +1 -1
- package/dist/mcp-client.js +1 -1
- package/dist/mcp-client.js.map +1 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +0 -15
- package/dist/memory.js.map +1 -1
- package/dist/meta-learning-collector.d.ts +64 -0
- package/dist/meta-learning-collector.d.ts.map +1 -0
- package/dist/meta-learning-collector.js +169 -0
- package/dist/meta-learning-collector.js.map +1 -0
- package/dist/meta-learning-engine.d.ts +61 -0
- package/dist/meta-learning-engine.d.ts.map +1 -0
- package/dist/meta-learning-engine.js +250 -0
- package/dist/meta-learning-engine.js.map +1 -0
- package/dist/overhead-governor.d.ts +105 -0
- package/dist/overhead-governor.d.ts.map +1 -0
- package/dist/overhead-governor.js +239 -0
- package/dist/overhead-governor.js.map +1 -0
- package/dist/playbook-library.d.ts +75 -0
- package/dist/playbook-library.d.ts.map +1 -0
- package/dist/playbook-library.js +241 -0
- package/dist/playbook-library.js.map +1 -0
- package/dist/project-executive.d.ts +97 -0
- package/dist/project-executive.d.ts.map +1 -0
- package/dist/project-executive.js +223 -0
- package/dist/project-executive.js.map +1 -0
- package/dist/reasoning-adapter.d.ts.map +1 -1
- package/dist/reasoning-adapter.js +11 -36
- package/dist/reasoning-adapter.js.map +1 -1
- package/dist/research-loop.d.ts +79 -0
- package/dist/research-loop.d.ts.map +1 -0
- package/dist/research-loop.js +363 -0
- package/dist/research-loop.js.map +1 -0
- package/dist/resolve-memory-path.d.ts +32 -0
- package/dist/resolve-memory-path.d.ts.map +1 -0
- package/dist/resolve-memory-path.js +97 -0
- package/dist/resolve-memory-path.js.map +1 -0
- package/dist/safe-bounds.d.ts +101 -0
- package/dist/safe-bounds.d.ts.map +1 -0
- package/dist/safe-bounds.js +140 -0
- package/dist/safe-bounds.js.map +1 -0
- package/dist/sandbox-tiers.d.ts +5 -0
- package/dist/sandbox-tiers.d.ts.map +1 -1
- package/dist/sandbox-tiers.js +14 -6
- package/dist/sandbox-tiers.js.map +1 -1
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +3 -0
- package/dist/security.js.map +1 -1
- package/dist/self-improvement-loop.d.ts +64 -0
- package/dist/self-improvement-loop.d.ts.map +1 -0
- package/dist/self-improvement-loop.js +156 -0
- package/dist/self-improvement-loop.js.map +1 -0
- package/dist/session-persistence.d.ts +5 -0
- package/dist/session-persistence.d.ts.map +1 -1
- package/dist/session-persistence.js +19 -3
- package/dist/session-persistence.js.map +1 -1
- package/dist/skill-loader.d.ts +16 -9
- package/dist/skill-loader.d.ts.map +1 -1
- package/dist/skill-loader.js +52 -116
- package/dist/skill-loader.js.map +1 -1
- package/dist/skill-registry.d.ts +60 -0
- package/dist/skill-registry.d.ts.map +1 -0
- package/dist/skill-registry.js +162 -0
- package/dist/skill-registry.js.map +1 -0
- package/dist/stall-detector.d.ts +56 -0
- package/dist/stall-detector.d.ts.map +1 -0
- package/dist/stall-detector.js +142 -0
- package/dist/stall-detector.js.map +1 -0
- package/dist/strategy-learner.d.ts +57 -0
- package/dist/strategy-learner.d.ts.map +1 -0
- package/dist/strategy-learner.js +160 -0
- package/dist/strategy-learner.js.map +1 -0
- package/dist/strategy-market.d.ts +73 -0
- package/dist/strategy-market.d.ts.map +1 -0
- package/dist/strategy-market.js +200 -0
- package/dist/strategy-market.js.map +1 -0
- package/dist/sub-agent.d.ts +0 -3
- package/dist/sub-agent.d.ts.map +1 -1
- package/dist/sub-agent.js +0 -10
- package/dist/sub-agent.js.map +1 -1
- package/dist/system-prompt.d.ts +0 -2
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +97 -490
- package/dist/system-prompt.js.map +1 -1
- package/dist/task-classifier.d.ts.map +1 -1
- package/dist/task-classifier.js +2 -54
- package/dist/task-classifier.js.map +1 -1
- package/dist/tool-synthesizer.d.ts +149 -0
- package/dist/tool-synthesizer.d.ts.map +1 -0
- package/dist/tool-synthesizer.js +455 -0
- package/dist/tool-synthesizer.js.map +1 -0
- package/dist/trace-pattern-extractor.d.ts +76 -0
- package/dist/trace-pattern-extractor.d.ts.map +1 -0
- package/dist/trace-pattern-extractor.js +321 -0
- package/dist/trace-pattern-extractor.js.map +1 -0
- package/dist/trace-recorder.d.ts +38 -0
- package/dist/trace-recorder.d.ts.map +1 -0
- package/dist/trace-recorder.js +94 -0
- package/dist/trace-recorder.js.map +1 -0
- package/dist/trust-economics.d.ts +50 -0
- package/dist/trust-economics.d.ts.map +1 -0
- package/dist/trust-economics.js +148 -0
- package/dist/trust-economics.js.map +1 -0
- package/dist/types.d.ts +273 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/yuan-md-loader.d.ts +22 -0
- package/dist/yuan-md-loader.d.ts.map +1 -0
- package/dist/yuan-md-loader.js +75 -0
- package/dist/yuan-md-loader.js.map +1 -0
- package/package.json +1 -1
package/dist/agent-loop.js
CHANGED
|
@@ -9,17 +9,7 @@
|
|
|
9
9
|
import { EventEmitter } from "node:events";
|
|
10
10
|
import { randomUUID } from "node:crypto";
|
|
11
11
|
import { execSync } from "node:child_process";
|
|
12
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
13
|
-
import { homedir } from "node:os";
|
|
14
12
|
import { basename, join as pathJoin } from "node:path";
|
|
15
|
-
function debugLog(msg) {
|
|
16
|
-
try {
|
|
17
|
-
const dir = `${homedir()}/.yuan/logs`;
|
|
18
|
-
mkdirSync(dir, { recursive: true });
|
|
19
|
-
appendFileSync(`${dir}/debug.log`, `[${new Date().toISOString()}] ${msg}\n`);
|
|
20
|
-
}
|
|
21
|
-
catch { /* silent */ }
|
|
22
|
-
}
|
|
23
13
|
import { BYOKClient } from "./llm-client.js";
|
|
24
14
|
import { Governor } from "./governor.js";
|
|
25
15
|
import { ContextManager } from "./context-manager.js";
|
|
@@ -31,7 +21,6 @@ import { InterruptManager } from "./interrupt-manager.js";
|
|
|
31
21
|
import { YuanMemory } from "./memory.js";
|
|
32
22
|
import { MemoryManager } from "./memory-manager.js";
|
|
33
23
|
import { buildSystemPrompt } from "./system-prompt.js";
|
|
34
|
-
import { StrategySelector } from "./strategy-selector.js";
|
|
35
24
|
import { HierarchicalPlanner, } from "./hierarchical-planner.js";
|
|
36
25
|
import { TaskClassifier } from "./task-classifier.js";
|
|
37
26
|
import { PromptDefense } from "./prompt-defense.js";
|
|
@@ -40,6 +29,7 @@ import { TokenBudgetManager } from "./token-budget.js";
|
|
|
40
29
|
import { ContinuationEngine } from "./continuation-engine.js";
|
|
41
30
|
import { MemoryUpdater } from "./memory-updater.js";
|
|
42
31
|
import { MCPClient } from "./mcp-client.js";
|
|
32
|
+
import { BOUNDS, cap } from "./safe-bounds.js";
|
|
43
33
|
import { WorldStateCollector } from "./world-state.js";
|
|
44
34
|
import { FailureRecovery } from "./failure-recovery.js";
|
|
45
35
|
import { ExecutionPolicyEngine } from "./execution-policy-engine.js";
|
|
@@ -65,12 +55,27 @@ import { ContextBudgetManager } from "./context-budget.js";
|
|
|
65
55
|
import { QAPipeline } from "./qa-pipeline.js";
|
|
66
56
|
import { PersonaManager } from "./persona.js";
|
|
67
57
|
import { InMemoryVectorStore, OllamaEmbeddingProvider } from "./vector-store.js";
|
|
58
|
+
import { OverheadGovernor } from "./overhead-governor.js";
|
|
68
59
|
import { StateStore, TransitionModel, SimulationEngine, StateUpdater, } from "./world-model/index.js";
|
|
69
60
|
import { MilestoneChecker, RiskEstimator, PlanEvaluator, ReplanningEngine, } from "./planner/index.js";
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
72
|
-
import {
|
|
73
|
-
import {
|
|
61
|
+
import { TraceRecorder } from "./trace-recorder.js";
|
|
62
|
+
import { ArchSummarizer } from "./arch-summarizer.js";
|
|
63
|
+
import { FailureSignatureMemory } from "./failure-signature-memory.js";
|
|
64
|
+
import { PlaybookLibrary } from "./playbook-library.js";
|
|
65
|
+
import { ProjectExecutive } from "./project-executive.js";
|
|
66
|
+
import { StallDetector } from "./stall-detector.js";
|
|
67
|
+
import { SelfImprovementLoop } from "./self-improvement-loop.js";
|
|
68
|
+
import { MetaLearningCollector } from "./meta-learning-collector.js";
|
|
69
|
+
import { TrustEconomics } from "./trust-economics.js";
|
|
70
|
+
import { StrategyLearner } from "./strategy-learner.js";
|
|
71
|
+
import { SkillRegistry } from "./skill-registry.js";
|
|
72
|
+
import { TracePatternExtractor } from "./trace-pattern-extractor.js";
|
|
73
|
+
import { MetaLearningEngine } from "./meta-learning-engine.js";
|
|
74
|
+
import { ToolSynthesizer } from "./tool-synthesizer.js";
|
|
75
|
+
import { BudgetGovernorV2 } from "./budget-governor-v2.js";
|
|
76
|
+
import { CapabilityGraph } from "./capability-graph.js";
|
|
77
|
+
import { CapabilitySelfModel } from "./capability-self-model.js";
|
|
78
|
+
import { StrategyMarket } from "./strategy-market.js";
|
|
74
79
|
/**
|
|
75
80
|
* AgentLoop — YUAN 에이전트의 핵심 실행 루프.
|
|
76
81
|
*
|
|
@@ -100,87 +105,6 @@ import { GitIntelligence } from "./git-intelligence.js";
|
|
|
100
105
|
*/
|
|
101
106
|
/** Minimum confidence for classification-based hints/routing to activate */
|
|
102
107
|
const CLASSIFICATION_CONFIDENCE_THRESHOLD = 0.6;
|
|
103
|
-
// ─── Security Table Formatter ─────────────────────────────────────────────────
|
|
104
|
-
/**
|
|
105
|
-
* Renders SecurityScanner findings as a box-drawn terminal table with ANSI colors.
|
|
106
|
-
*
|
|
107
|
-
* ⚠ Security Scan · src/app.ts
|
|
108
|
-
* ┌────────────┬─────────┬──────────────────────────────────┐
|
|
109
|
-
* │ Severity │ Count │ Rules │
|
|
110
|
-
* ├────────────┼─────────┼──────────────────────────────────┤
|
|
111
|
-
* │ CRITICAL │ 1 │ hardcoded-secret │
|
|
112
|
-
* │ HIGH │ 2 │ sql-injection, xss-reflected │
|
|
113
|
-
* │ MEDIUM │ 0 │ — │
|
|
114
|
-
* │ LOW │ 1 │ weak-hash │
|
|
115
|
-
* └────────────┴─────────┴──────────────────────────────────┘
|
|
116
|
-
*/
|
|
117
|
-
function formatSecurityTable(findings, filePath) {
|
|
118
|
-
// ANSI
|
|
119
|
-
const R = "\x1b[0m";
|
|
120
|
-
const BOLD = "\x1b[1m";
|
|
121
|
-
const DIM = "\x1b[2m";
|
|
122
|
-
const RED = "\x1b[1;31m";
|
|
123
|
-
const YEL = "\x1b[33m";
|
|
124
|
-
const CYN = "\x1b[36m";
|
|
125
|
-
const GRN = "\x1b[32m";
|
|
126
|
-
const WHT = "\x1b[1;37m";
|
|
127
|
-
// Aggregate
|
|
128
|
-
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
129
|
-
const ruleMap = { critical: [], high: [], medium: [], low: [] };
|
|
130
|
-
for (const f of findings) {
|
|
131
|
-
const sev = f.severity.toLowerCase();
|
|
132
|
-
if (sev in counts) {
|
|
133
|
-
counts[sev]++;
|
|
134
|
-
if (sev in ruleMap && !ruleMap[sev].includes(f.rule))
|
|
135
|
-
ruleMap[sev].push(f.rule);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
const totalBad = counts.critical + counts.high;
|
|
139
|
-
// Column widths (content only, borders + spaces added separately)
|
|
140
|
-
const C1 = 10; // severity label
|
|
141
|
-
const C2 = 7; // count
|
|
142
|
-
const C3 = 34; // rules
|
|
143
|
-
const h = "─";
|
|
144
|
-
const v = "│";
|
|
145
|
-
const tl = "┌";
|
|
146
|
-
const tr = "┐";
|
|
147
|
-
const bl = "└";
|
|
148
|
-
const br = "┘";
|
|
149
|
-
const lm = "├";
|
|
150
|
-
const rm = "┤";
|
|
151
|
-
const tm = "┬";
|
|
152
|
-
const bm = "┴";
|
|
153
|
-
const x = "┼";
|
|
154
|
-
const divider = (L, M, Ri) => `${L}${h.repeat(C1 + 2)}${M}${h.repeat(C2 + 2)}${M}${h.repeat(C3 + 2)}${Ri}`;
|
|
155
|
-
const row = (label, color, count, rules) => {
|
|
156
|
-
const rulesStr = rules.length > 0 ? rules.join(", ") : "—";
|
|
157
|
-
const col1 = label.padEnd(C1);
|
|
158
|
-
const col2 = String(count).padStart(3).padEnd(C2);
|
|
159
|
-
const col3 = (rulesStr.length > C3 ? rulesStr.slice(0, C3 - 1) + "…" : rulesStr).padEnd(C3);
|
|
160
|
-
const c = count > 0 ? color : DIM;
|
|
161
|
-
return `${v} ${c}${col1}${R} ${v} ${c}${col2}${R} ${v} ${c}${col3}${R} ${v}`;
|
|
162
|
-
};
|
|
163
|
-
const headerCol1 = "Severity".padEnd(C1);
|
|
164
|
-
const headerCol2 = "Count".padEnd(C2);
|
|
165
|
-
const headerCol3 = "Rules".padEnd(C3);
|
|
166
|
-
const header = `${v} ${WHT}${headerCol1}${R} ${v} ${WHT}${headerCol2}${R} ${v} ${WHT}${headerCol3}${R} ${v}`;
|
|
167
|
-
const shortPath = filePath.length > 44 ? "…" + filePath.slice(-43) : filePath;
|
|
168
|
-
const icon = totalBad > 0 ? `${YEL}⚠${R}` : `${GRN}✓${R}`;
|
|
169
|
-
return [
|
|
170
|
-
"",
|
|
171
|
-
`${icon} ${BOLD}Security Scan${R} ${DIM}· ${shortPath}${R}`,
|
|
172
|
-
"",
|
|
173
|
-
divider(tl, tm, tr),
|
|
174
|
-
header,
|
|
175
|
-
divider(lm, x, rm),
|
|
176
|
-
row("CRITICAL", RED, counts.critical, ruleMap.critical),
|
|
177
|
-
row("HIGH", YEL, counts.high, ruleMap.high),
|
|
178
|
-
row("MEDIUM", CYN, counts.medium, ruleMap.medium),
|
|
179
|
-
row("LOW", DIM, counts.low, ruleMap.low),
|
|
180
|
-
divider(bl, bm, br),
|
|
181
|
-
"",
|
|
182
|
-
].join("\n");
|
|
183
|
-
}
|
|
184
108
|
export class AgentLoop extends EventEmitter {
|
|
185
109
|
abortSignal;
|
|
186
110
|
llmClient;
|
|
@@ -208,10 +132,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
208
132
|
_lastInjectedTaskIndex = -1; // track when plan progress was last injected
|
|
209
133
|
changedFiles = [];
|
|
210
134
|
aborted = false;
|
|
211
|
-
|
|
212
|
-
slowInitDone = false;
|
|
213
|
-
slowInitPromise = null;
|
|
214
|
-
_slowInitContext = {};
|
|
135
|
+
initialized = false;
|
|
215
136
|
continuationEngine = null;
|
|
216
137
|
mcpClient = null;
|
|
217
138
|
mcpToolDefinitions = [];
|
|
@@ -242,12 +163,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
242
163
|
skillLearner = null;
|
|
243
164
|
repoGraph = null;
|
|
244
165
|
backgroundAgentManager = null;
|
|
245
|
-
sandbox = null;
|
|
246
|
-
securityScanner = null;
|
|
247
|
-
reasoningAdapter = null;
|
|
248
|
-
gitIntelligence = null;
|
|
249
|
-
gitAvailable = false;
|
|
250
|
-
conflictCache = null;
|
|
251
166
|
sessionPersistence = null;
|
|
252
167
|
sessionId = null;
|
|
253
168
|
enableToolPlanning;
|
|
@@ -255,9 +170,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
255
170
|
enableBackgroundAgents;
|
|
256
171
|
currentToolPlan = null;
|
|
257
172
|
executedToolNames = [];
|
|
258
|
-
currentTaskType = undefined;
|
|
259
|
-
strategySelector = new StrategySelector();
|
|
260
|
-
activeStrategies = [];
|
|
261
173
|
/** Context Budget: max 3 active skills at once */
|
|
262
174
|
activeSkillIds = [];
|
|
263
175
|
static MAX_ACTIVE_SKILLS = 3;
|
|
@@ -275,6 +187,22 @@ export class AgentLoop extends EventEmitter {
|
|
|
275
187
|
iterationTsFilesModified = [];
|
|
276
188
|
/** Task 3: Whether tsc was run in the previous iteration (skip cooldown) */
|
|
277
189
|
tscRanLastIteration = false;
|
|
190
|
+
// ─── OverheadGovernor — subsystem execution policy ──────────────────────
|
|
191
|
+
overheadGovernor;
|
|
192
|
+
/** Writes since last verify ran (for QA/quickVerify trigger) */
|
|
193
|
+
writeCountSinceVerify = 0;
|
|
194
|
+
/** Current task phase (explore → implement → verify → finalize) */
|
|
195
|
+
taskPhase = "explore";
|
|
196
|
+
/** Per-iteration: verify already ran this iteration (single-flight guard) */
|
|
197
|
+
verifyRanThisIteration = false;
|
|
198
|
+
/** Per-iteration: summarize already ran this iteration */
|
|
199
|
+
summarizeRanThisIteration = false;
|
|
200
|
+
/** Per-iteration: llmFixer run count */
|
|
201
|
+
llmFixerRunCount = 0;
|
|
202
|
+
/** Repeated error signature (same error string seen 2+ times) */
|
|
203
|
+
repeatedErrorSignature = undefined;
|
|
204
|
+
_lastErrorSignature = undefined;
|
|
205
|
+
_errorSignatureCount = 0;
|
|
278
206
|
/** PersonaManager — learns user communication style, injects persona into system prompt */
|
|
279
207
|
personaManager = null;
|
|
280
208
|
/** InMemoryVectorStore — RAG: semantic code context retrieval for relevant snippets */
|
|
@@ -303,6 +231,28 @@ export class AgentLoop extends EventEmitter {
|
|
|
303
231
|
reasoningAggregator = new ReasoningAggregator();
|
|
304
232
|
reasoningTree = new ReasoningTree();
|
|
305
233
|
resumedFromSession = false;
|
|
234
|
+
traceRecorder = null;
|
|
235
|
+
archSummarizer = null;
|
|
236
|
+
failureSigMemory = null;
|
|
237
|
+
playbookLibrary = null;
|
|
238
|
+
projectExecutive = null;
|
|
239
|
+
stallDetector = null;
|
|
240
|
+
selfImprovementLoop = null;
|
|
241
|
+
metaLearningCollector = null;
|
|
242
|
+
trustEconomics = null;
|
|
243
|
+
// Phase 5: Strategy Learner + Skill Registry
|
|
244
|
+
strategyLearner = null;
|
|
245
|
+
skillRegistry = null;
|
|
246
|
+
// Phase 5 extended: TracePatternExtractor, MetaLearningEngine, ToolSynthesizer
|
|
247
|
+
tracePatternExtractor = null;
|
|
248
|
+
metaLearningEngine = null;
|
|
249
|
+
toolSynthesizer = null;
|
|
250
|
+
// Phase 6: BudgetGovernorV2, CapabilityGraph, CapabilitySelfModel, StrategyMarket
|
|
251
|
+
budgetGovernorV2 = null;
|
|
252
|
+
capabilityGraph = null;
|
|
253
|
+
capabilitySelfModel = null;
|
|
254
|
+
strategyMarket = null;
|
|
255
|
+
sessionRunCount = 0;
|
|
306
256
|
/**
|
|
307
257
|
* Restore AgentLoop state from persisted session (yuan resume)
|
|
308
258
|
*/
|
|
@@ -366,7 +316,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
366
316
|
this.abortSignal = options.abortSignal;
|
|
367
317
|
this.enableMemory = options.enableMemory !== false;
|
|
368
318
|
this.enablePlanning = options.enablePlanning !== false;
|
|
369
|
-
this.planningThreshold = options.planningThreshold ?? "
|
|
319
|
+
this.planningThreshold = options.planningThreshold ?? "moderate";
|
|
370
320
|
this.environment = options.environment;
|
|
371
321
|
this.enableSelfReflection = options.enableSelfReflection !== false;
|
|
372
322
|
this.enableDebate = options.enableDebate !== false;
|
|
@@ -374,6 +324,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
374
324
|
this.llmClient = new BYOKClient(options.config.byok);
|
|
375
325
|
// Governor 생성
|
|
376
326
|
this.governor = new Governor(options.governorConfig);
|
|
327
|
+
// OverheadGovernor — subsystem execution policy
|
|
328
|
+
this.overheadGovernor = new OverheadGovernor(options.overheadGovernorConfig, (subsystem, reason) => {
|
|
329
|
+
// Shadow log — emit as thinking event so TUI can see it (dimmed)
|
|
330
|
+
this.emitEvent({ kind: "agent:thinking", content: `[shadow] ${subsystem}: ${reason}` });
|
|
331
|
+
});
|
|
377
332
|
// ContextManager 생성
|
|
378
333
|
this.contextManager = new ContextManager({
|
|
379
334
|
maxContextTokens: options.contextConfig?.maxContextTokens ??
|
|
@@ -413,31 +368,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
413
368
|
this.enableToolPlanning = options.enableToolPlanning !== false;
|
|
414
369
|
this.enableSkillLearning = options.enableSkillLearning !== false;
|
|
415
370
|
this.enableBackgroundAgents = options.enableBackgroundAgents === true;
|
|
416
|
-
// ReasoningProgressAdapter — converts thinking deltas to progress:status events
|
|
417
|
-
this.reasoningAdapter = new ReasoningProgressAdapter({
|
|
418
|
-
agentId: options.config.loop.model ?? "coder",
|
|
419
|
-
role: "coder",
|
|
420
|
-
onThinking: (event) => this.emitEvent(event),
|
|
421
|
-
onStatus: (event) => this.emitEvent(event),
|
|
422
|
-
debounceMs: 800,
|
|
423
|
-
});
|
|
424
|
-
// GitIntelligence — auto-disabled if git not installed or not a git repo
|
|
425
|
-
const gitProject = options.config.loop.projectPath;
|
|
426
|
-
if (gitProject && options.enableGitIntelligence !== false) {
|
|
427
|
-
this.gitIntelligence = new GitIntelligence({ projectPath: gitProject });
|
|
428
|
-
}
|
|
429
|
-
// SandboxManager — preflight guard (auto-instantiated from projectPath)
|
|
430
|
-
const sandboxProject = options.sandboxConfig?.projectPath ?? options.config.loop.projectPath;
|
|
431
|
-
if (sandboxProject) {
|
|
432
|
-
this.sandbox = new SandboxManager({
|
|
433
|
-
projectPath: sandboxProject,
|
|
434
|
-
...(options.sandboxConfig ?? {}),
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
// SecurityScanner — postflight guard (opt-in)
|
|
438
|
-
if (options.enableSecurityScan && sandboxProject) {
|
|
439
|
-
this.securityScanner = new SecurityScanner({ projectPath: sandboxProject });
|
|
440
|
-
}
|
|
441
371
|
// 시스템 프롬프트 추가 (메모리 없이 기본 프롬프트로 시작, init()에서 갱신)
|
|
442
372
|
this.contextManager.addMessage({
|
|
443
373
|
role: "system",
|
|
@@ -445,44 +375,20 @@ export class AgentLoop extends EventEmitter {
|
|
|
445
375
|
});
|
|
446
376
|
}
|
|
447
377
|
/**
|
|
448
|
-
*
|
|
449
|
-
*
|
|
450
|
-
*
|
|
378
|
+
* Memory와 프로젝트 컨텍스트를 로드하여 시스템 프롬프트를 갱신.
|
|
379
|
+
* run() 호출 전에 한 번 호출하면 메모리가 자동으로 주입된다.
|
|
380
|
+
* 이미 초기화되었으면 스킵.
|
|
451
381
|
*/
|
|
452
|
-
async
|
|
453
|
-
if (this.
|
|
382
|
+
async init() {
|
|
383
|
+
if (this.initialized)
|
|
454
384
|
return;
|
|
455
|
-
this.
|
|
456
|
-
//
|
|
385
|
+
this.initialized = true;
|
|
386
|
+
// Task 1: Initialize ContextBudgetManager with the total token budget
|
|
457
387
|
this.contextBudgetManager = new ContextBudgetManager({
|
|
458
388
|
totalBudget: this.config.loop.totalTokenBudget,
|
|
459
389
|
enableSummarization: true,
|
|
460
|
-
summarizationThreshold: 0.60,
|
|
461
|
-
});
|
|
462
|
-
const projectPath = this.config.loop.projectPath;
|
|
463
|
-
if (!projectPath)
|
|
464
|
-
return;
|
|
465
|
-
// Minimal system prompt — no project structure yet (slowInit provides it later)
|
|
466
|
-
const basicPrompt = buildSystemPrompt({
|
|
467
|
-
projectStructure: undefined,
|
|
468
|
-
yuanMdContent: undefined,
|
|
469
|
-
tools: [...this.config.loop.tools],
|
|
470
|
-
projectPath,
|
|
471
|
-
environment: this.environment,
|
|
472
|
-
model: this.config.byok.model,
|
|
390
|
+
summarizationThreshold: 0.60, // trigger summarize() at 60% (before ContextCompressor at 70%)
|
|
473
391
|
});
|
|
474
|
-
this.contextManager.replaceSystemMessage(basicPrompt);
|
|
475
|
-
debugLog("[fastInit] done — LLM ready");
|
|
476
|
-
}
|
|
477
|
-
/**
|
|
478
|
-
* Slow init — background fire-and-forget.
|
|
479
|
-
* Loads memory, world state, MCP, etc. and enriches system prompt for next turn.
|
|
480
|
-
* Idempotent: subsequent calls are no-ops.
|
|
481
|
-
*/
|
|
482
|
-
async slowInit() {
|
|
483
|
-
if (this.slowInitDone)
|
|
484
|
-
return;
|
|
485
|
-
this.slowInitDone = true;
|
|
486
392
|
const projectPath = this.config.loop.projectPath;
|
|
487
393
|
if (!projectPath)
|
|
488
394
|
return;
|
|
@@ -493,47 +399,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
493
399
|
catch {
|
|
494
400
|
this.sessionPersistence = null;
|
|
495
401
|
}
|
|
496
|
-
// Session save (moved from run() into slowInit)
|
|
497
|
-
if (this.sessionPersistence) {
|
|
498
|
-
try {
|
|
499
|
-
if (!this.sessionId) {
|
|
500
|
-
this.sessionId = randomUUID();
|
|
501
|
-
}
|
|
502
|
-
const nowIso = new Date().toISOString();
|
|
503
|
-
const snapshot = {
|
|
504
|
-
id: this.sessionId,
|
|
505
|
-
createdAt: nowIso,
|
|
506
|
-
updatedAt: nowIso,
|
|
507
|
-
workDir: projectPath,
|
|
508
|
-
provider: String(this.config.byok.provider ?? "unknown"),
|
|
509
|
-
model: String(this.config.byok.model ?? "unknown"),
|
|
510
|
-
status: "running",
|
|
511
|
-
iteration: this.iterationCount,
|
|
512
|
-
tokenUsage: {
|
|
513
|
-
input: this.tokenUsage.input,
|
|
514
|
-
output: this.tokenUsage.output,
|
|
515
|
-
},
|
|
516
|
-
messageCount: this.contextManager.getMessages().length,
|
|
517
|
-
};
|
|
518
|
-
await this.sessionPersistence.save(this.sessionId, {
|
|
519
|
-
snapshot,
|
|
520
|
-
messages: this.contextManager.getMessages(),
|
|
521
|
-
plan: this.activePlan,
|
|
522
|
-
changedFiles: [],
|
|
523
|
-
});
|
|
524
|
-
}
|
|
525
|
-
catch {
|
|
526
|
-
// Session save failure is non-fatal
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
402
|
let yuanMdContent;
|
|
530
403
|
let projectStructure;
|
|
531
|
-
debugLog("[slowInit] start");
|
|
532
404
|
// Memory 로드
|
|
533
405
|
if (this.enableMemory) {
|
|
534
406
|
try {
|
|
535
407
|
// YUAN.md (raw markdown)
|
|
536
|
-
debugLog("[slowInit] yuanMemory.load start");
|
|
537
408
|
this.yuanMemory = new YuanMemory(projectPath);
|
|
538
409
|
const memData = await this.yuanMemory.load();
|
|
539
410
|
if (memData) {
|
|
@@ -563,20 +434,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
563
434
|
const indexer = new CodeIndexer({});
|
|
564
435
|
indexer.indexProject(projectPath, vectorStoreRef).catch(() => { });
|
|
565
436
|
}).catch(() => { });
|
|
566
|
-
// 프로젝트 구조 분석
|
|
567
|
-
|
|
568
|
-
const analyzeTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error("analyzeProject timeout")), 15000));
|
|
569
|
-
projectStructure = await Promise.race([
|
|
570
|
-
this.yuanMemory.analyzeProject(),
|
|
571
|
-
analyzeTimeout,
|
|
572
|
-
]).catch((e) => {
|
|
573
|
-
debugLog(`[slowInit] analyzeProject skipped: ${String(e)}`);
|
|
574
|
-
return undefined;
|
|
575
|
-
});
|
|
576
|
-
debugLog("[slowInit] analyzeProject done");
|
|
577
|
-
// Store for enriched system prompt at end of slowInit
|
|
578
|
-
this._slowInitContext.yuanMdContent = yuanMdContent;
|
|
579
|
-
this._slowInitContext.projectStructure = projectStructure;
|
|
437
|
+
// 프로젝트 구조 분석
|
|
438
|
+
projectStructure = await this.yuanMemory.analyzeProject();
|
|
580
439
|
}
|
|
581
440
|
catch (memErr) {
|
|
582
441
|
// 메모리 로드 실패는 치명적이지 않음 — 경고만 출력
|
|
@@ -603,7 +462,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
603
462
|
catch {
|
|
604
463
|
// 정책 로드 실패 → 기본값 사용
|
|
605
464
|
}
|
|
606
|
-
debugLog("[slowInit] worldState.collect start");
|
|
607
465
|
// WorldState 수집 → system prompt에 주입
|
|
608
466
|
try {
|
|
609
467
|
const worldStateCollector = new WorldStateCollector({
|
|
@@ -612,11 +470,9 @@ export class AgentLoop extends EventEmitter {
|
|
|
612
470
|
skipTest: true,
|
|
613
471
|
});
|
|
614
472
|
this.worldState = await worldStateCollector.collect();
|
|
615
|
-
this._slowInitContext.worldState = this.worldState ?? undefined;
|
|
616
|
-
debugLog("[slowInit] worldState.collect done");
|
|
617
473
|
}
|
|
618
|
-
catch
|
|
619
|
-
|
|
474
|
+
catch {
|
|
475
|
+
// WorldState 수집 실패는 치명적이지 않음
|
|
620
476
|
}
|
|
621
477
|
// Initialize World Model
|
|
622
478
|
if (this.worldState && projectPath) {
|
|
@@ -644,13 +500,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
644
500
|
catch {
|
|
645
501
|
// Not a git repo or git unavailable — FailureRecovery will use file-level rollback only
|
|
646
502
|
}
|
|
647
|
-
debugLog("[slowInit] continuationEngine + MCP start");
|
|
648
503
|
// ImpactAnalyzer 생성
|
|
649
504
|
this.impactAnalyzer = new ImpactAnalyzer({ projectPath });
|
|
650
505
|
// ContinuationEngine 생성
|
|
651
506
|
this.continuationEngine = new ContinuationEngine({ projectPath });
|
|
652
507
|
// 이전 세션 체크포인트 복원
|
|
653
|
-
debugLog("[slowInit] checkpoint restore start");
|
|
654
508
|
try {
|
|
655
509
|
const latestCheckpoint = await this.continuationEngine.findLatestCheckpoint();
|
|
656
510
|
if (latestCheckpoint) {
|
|
@@ -666,9 +520,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
666
520
|
catch {
|
|
667
521
|
// 체크포인트 복원 실패는 치명적이지 않음
|
|
668
522
|
}
|
|
669
|
-
debugLog("[slowInit] checkpoint restore done");
|
|
670
523
|
// MCP 클라이언트 연결
|
|
671
|
-
debugLog("[slowInit] MCP connect start");
|
|
672
524
|
if (this.mcpServerConfigs.length > 0) {
|
|
673
525
|
try {
|
|
674
526
|
this.mcpClient = new MCPClient({
|
|
@@ -683,7 +535,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
683
535
|
this.mcpToolDefinitions = [];
|
|
684
536
|
}
|
|
685
537
|
}
|
|
686
|
-
debugLog("[slowInit] MCP connect done");
|
|
687
538
|
// ReflexionEngine 생성
|
|
688
539
|
if (projectPath) {
|
|
689
540
|
this.reflexionEngine = new ReflexionEngine({ projectPath });
|
|
@@ -722,6 +573,22 @@ export class AgentLoop extends EventEmitter {
|
|
|
722
573
|
totalTokenBudget: Math.floor(this.config.loop.totalTokenBudget * 0.3),
|
|
723
574
|
});
|
|
724
575
|
}
|
|
576
|
+
// 향상된 시스템 프롬프트 생성
|
|
577
|
+
const enhancedPrompt = buildSystemPrompt({
|
|
578
|
+
projectStructure,
|
|
579
|
+
yuanMdContent,
|
|
580
|
+
tools: [...this.config.loop.tools, ...this.mcpToolDefinitions],
|
|
581
|
+
projectPath,
|
|
582
|
+
environment: this.environment,
|
|
583
|
+
});
|
|
584
|
+
// WorldState를 시스템 프롬프트에 추가
|
|
585
|
+
let worldStateSection = "";
|
|
586
|
+
if (this.worldState) {
|
|
587
|
+
const collector = new WorldStateCollector({ projectPath });
|
|
588
|
+
worldStateSection = "\n\n" + collector.formatForPrompt(this.worldState);
|
|
589
|
+
}
|
|
590
|
+
// 기존 시스템 메시지를 향상된 프롬프트로 교체
|
|
591
|
+
this.contextManager.replaceSystemMessage(enhancedPrompt + worldStateSection);
|
|
725
592
|
// MemoryManager의 관련 학습/경고를 추가 컨텍스트로 주입
|
|
726
593
|
if (this.memoryManager) {
|
|
727
594
|
const memory = this.memoryManager.getMemory();
|
|
@@ -736,7 +603,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
736
603
|
}
|
|
737
604
|
}
|
|
738
605
|
// SkillLearner 초기화 (경험에서 학습된 스킬 자동 로드)
|
|
739
|
-
debugLog("[slowInit] skillLearner init start");
|
|
740
606
|
if (this.enableSkillLearning && projectPath) {
|
|
741
607
|
try {
|
|
742
608
|
this.skillLearner = new SkillLearner(projectPath);
|
|
@@ -746,10 +612,67 @@ export class AgentLoop extends EventEmitter {
|
|
|
746
612
|
this.skillLearner = null;
|
|
747
613
|
}
|
|
748
614
|
}
|
|
749
|
-
|
|
615
|
+
// Phase 4: FailureSignatureMemory, PlaybookLibrary, ProjectExecutive, StallDetector
|
|
616
|
+
if (projectPath) {
|
|
617
|
+
try {
|
|
618
|
+
this.failureSigMemory = new FailureSignatureMemory({ projectPath });
|
|
619
|
+
this.playbookLibrary = new PlaybookLibrary();
|
|
620
|
+
this.projectExecutive = new ProjectExecutive(projectPath);
|
|
621
|
+
// Estimated iterations from config or default 20
|
|
622
|
+
const estimatedIter = this.config.loop.maxIterations ?? 20;
|
|
623
|
+
this.stallDetector = new StallDetector(estimatedIter);
|
|
624
|
+
// Forward project executive events
|
|
625
|
+
this.projectExecutive.on("event", (ev) => this.emitEvent(ev));
|
|
626
|
+
// Phase 4 remaining: SelfImprovementLoop, MetaLearningCollector, TrustEconomics
|
|
627
|
+
this.selfImprovementLoop = new SelfImprovementLoop({ projectPath });
|
|
628
|
+
this.metaLearningCollector = new MetaLearningCollector({ projectPath });
|
|
629
|
+
this.trustEconomics = new TrustEconomics({ projectPath });
|
|
630
|
+
}
|
|
631
|
+
catch {
|
|
632
|
+
// Phase 4 init failure is non-fatal
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// Phase 5: StrategyLearner + SkillRegistry
|
|
636
|
+
try {
|
|
637
|
+
this.strategyLearner = new StrategyLearner();
|
|
638
|
+
this.skillRegistry = new SkillRegistry();
|
|
639
|
+
// Forward Phase 5 events
|
|
640
|
+
this.strategyLearner.on("event", (ev) => this.emitEvent(ev));
|
|
641
|
+
this.skillRegistry.on("event", (ev) => this.emitEvent(ev));
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
// Phase 5 init failure is non-fatal
|
|
645
|
+
}
|
|
646
|
+
// Phase 5 extended: TracePatternExtractor, MetaLearningEngine, ToolSynthesizer
|
|
647
|
+
try {
|
|
648
|
+
this.tracePatternExtractor = new TracePatternExtractor();
|
|
649
|
+
this.metaLearningEngine = new MetaLearningEngine({
|
|
650
|
+
collector: this.metaLearningCollector ?? undefined,
|
|
651
|
+
strategyLearner: this.strategyLearner ?? undefined,
|
|
652
|
+
});
|
|
653
|
+
this.toolSynthesizer = new ToolSynthesizer();
|
|
654
|
+
// Forward Phase 5 extended events
|
|
655
|
+
this.tracePatternExtractor.on("event", (ev) => this.emitEvent(ev));
|
|
656
|
+
this.metaLearningEngine.on("event", (ev) => this.emitEvent(ev));
|
|
657
|
+
this.toolSynthesizer.on("event", (ev) => this.emitEvent(ev));
|
|
658
|
+
}
|
|
659
|
+
catch {
|
|
660
|
+
// Phase 5 extended init failure is non-fatal
|
|
661
|
+
}
|
|
662
|
+
// Phase 6: BudgetGovernorV2, CapabilityGraph, CapabilitySelfModel, StrategyMarket
|
|
663
|
+
try {
|
|
664
|
+
this.budgetGovernorV2 = new BudgetGovernorV2();
|
|
665
|
+
this.capabilityGraph = new CapabilityGraph();
|
|
666
|
+
this.capabilitySelfModel = new CapabilitySelfModel();
|
|
667
|
+
this.strategyMarket = new StrategyMarket();
|
|
668
|
+
this.budgetGovernorV2.on("event", (ev) => this.emitEvent(ev));
|
|
669
|
+
this.capabilityGraph.on("event", (ev) => this.emitEvent(ev));
|
|
670
|
+
this.capabilitySelfModel.on("event", (ev) => this.emitEvent(ev));
|
|
671
|
+
this.strategyMarket.on("event", (ev) => this.emitEvent(ev));
|
|
672
|
+
}
|
|
673
|
+
catch { /* non-fatal */ }
|
|
750
674
|
// HierarchicalPlanner 생성
|
|
751
675
|
this.planner = new HierarchicalPlanner({ projectPath });
|
|
752
|
-
debugLog("[slowInit] planner created");
|
|
753
676
|
if (this.skillLearner) {
|
|
754
677
|
this.planner.setSkillLearner(this.skillLearner);
|
|
755
678
|
}
|
|
@@ -899,44 +822,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
899
822
|
this.continuousReflection.on("reflection:context_overflow", () => {
|
|
900
823
|
void this.handleSoftContextOverflow();
|
|
901
824
|
});
|
|
902
|
-
// Enrich system prompt now that we have full context (projectStructure, yuanMd, MCP tools, worldState)
|
|
903
|
-
const enrichedPrompt = buildSystemPrompt({
|
|
904
|
-
projectStructure: this._slowInitContext.projectStructure,
|
|
905
|
-
yuanMdContent: this._slowInitContext.yuanMdContent,
|
|
906
|
-
tools: [...this.config.loop.tools, ...this.mcpToolDefinitions],
|
|
907
|
-
projectPath,
|
|
908
|
-
environment: this.environment,
|
|
909
|
-
model: this.config.byok.model,
|
|
910
|
-
// NOTE: currentTaskType is undefined on turn 1 (slowInit fires before classification).
|
|
911
|
-
// The task-type hint still reaches the LLM on turn 1 via the separate system message injection.
|
|
912
|
-
// From turn 2+ this field carries the previous turn's classification.
|
|
913
|
-
currentTaskType: this.currentTaskType,
|
|
914
|
-
experienceHints: this.buildExperienceHints(),
|
|
915
|
-
activeStrategies: this.activeStrategies.length > 0 ? this.activeStrategies : undefined,
|
|
916
|
-
});
|
|
917
|
-
let worldStateSection = "";
|
|
918
|
-
if (this.worldState) {
|
|
919
|
-
const collector = new WorldStateCollector({ projectPath, maxRecentCommits: 10, skipTest: true });
|
|
920
|
-
worldStateSection = "\n\n" + collector.formatForPrompt(this.worldState);
|
|
921
|
-
}
|
|
922
|
-
this.contextManager.replaceSystemMessage(enrichedPrompt + worldStateSection);
|
|
923
|
-
debugLog("[slowInit] DONE — context enriched for next turn");
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* MemoryManager.learnings에서 경험 힌트를 추출한다.
|
|
927
|
-
* confidence >= 0.4인 학습만, 최대 8개, "[category] content" 형식으로 반환.
|
|
928
|
-
*/
|
|
929
|
-
buildExperienceHints() {
|
|
930
|
-
if (!this.memoryManager)
|
|
931
|
-
return [];
|
|
932
|
-
const memory = this.memoryManager.getMemory();
|
|
933
|
-
if (!memory?.learnings?.length)
|
|
934
|
-
return [];
|
|
935
|
-
return memory.learnings
|
|
936
|
-
.filter((l) => l.confidence >= 0.4)
|
|
937
|
-
.sort((a, b) => b.confidence - a.confidence)
|
|
938
|
-
.slice(0, 8)
|
|
939
|
-
.map((l) => `[${l.category}] ${l.content}`);
|
|
940
825
|
}
|
|
941
826
|
/**
|
|
942
827
|
* MemoryManager의 학습/실패 기록을 시스템 메시지로 변환.
|
|
@@ -1005,35 +890,69 @@ export class AgentLoop extends EventEmitter {
|
|
|
1005
890
|
this.costOptimizer.reset();
|
|
1006
891
|
this.tokenBudgetManager.reset();
|
|
1007
892
|
const runStartTime = Date.now();
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
893
|
+
// 첫 실행 시 메모리/프로젝트 컨텍스트 자동 로드
|
|
894
|
+
await this.init();
|
|
895
|
+
if (!this.sessionId) {
|
|
896
|
+
this.sessionId = randomUUID();
|
|
897
|
+
}
|
|
898
|
+
// Phase 6: start task budget tracking
|
|
899
|
+
try {
|
|
900
|
+
this.budgetGovernorV2?.startTask(this.sessionId ?? "default");
|
|
901
|
+
}
|
|
902
|
+
catch { /* non-fatal */ }
|
|
903
|
+
// Initialize trace recorder (once per run)
|
|
904
|
+
if (!this.traceRecorder) {
|
|
905
|
+
this.traceRecorder = new TraceRecorder(this.sessionId);
|
|
906
|
+
}
|
|
907
|
+
// Initialize arch summarizer (once per run)
|
|
908
|
+
if (!this.archSummarizer && this.config.loop.projectPath) {
|
|
909
|
+
this.archSummarizer = new ArchSummarizer(this.config.loop.projectPath);
|
|
910
|
+
// Trigger refresh in background if dirty
|
|
911
|
+
this.archSummarizer.getSummary().catch(() => { });
|
|
912
|
+
}
|
|
913
|
+
// Phase 4: PlaybookLibrary — inject hint into system context based on goal
|
|
914
|
+
if (this.playbookLibrary) {
|
|
1015
915
|
try {
|
|
1016
|
-
const
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
916
|
+
const playbook = this.playbookLibrary.query(userMessage);
|
|
917
|
+
if (playbook) {
|
|
918
|
+
this.contextManager.addMessage({
|
|
919
|
+
role: "system",
|
|
920
|
+
content: `[Playbook: ${playbook.taskType} v${playbook.version}] ` +
|
|
921
|
+
`Phase order: ${playbook.phaseOrder.join(" → ")}. ` +
|
|
922
|
+
`Stop conditions: ${playbook.stopConditions.join(", ")}. ` +
|
|
923
|
+
`Evidence required: ${playbook.evidenceRequirements.join(", ")}.`,
|
|
924
|
+
});
|
|
925
|
+
}
|
|
1024
926
|
}
|
|
927
|
+
catch { /* non-fatal */ }
|
|
1025
928
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
929
|
+
if (this.sessionPersistence) {
|
|
930
|
+
if (!this.sessionId) {
|
|
931
|
+
this.sessionId = randomUUID();
|
|
932
|
+
}
|
|
933
|
+
const nowIso = new Date().toISOString();
|
|
934
|
+
const snapshot = {
|
|
935
|
+
id: this.sessionId,
|
|
936
|
+
createdAt: nowIso,
|
|
937
|
+
updatedAt: nowIso,
|
|
938
|
+
workDir: this.config.loop.projectPath ?? "",
|
|
939
|
+
provider: String(this.config.byok.provider ?? "unknown"),
|
|
940
|
+
model: String(this.config.byok.model ?? "unknown"),
|
|
941
|
+
status: "running",
|
|
942
|
+
iteration: this.iterationCount,
|
|
943
|
+
tokenUsage: {
|
|
944
|
+
input: this.tokenUsage.input,
|
|
945
|
+
output: this.tokenUsage.output,
|
|
946
|
+
},
|
|
947
|
+
messageCount: this.contextManager.getMessages().length,
|
|
948
|
+
};
|
|
949
|
+
await this.sessionPersistence.save(this.sessionId, {
|
|
950
|
+
snapshot,
|
|
951
|
+
messages: this.contextManager.getMessages(),
|
|
952
|
+
plan: this.activePlan,
|
|
953
|
+
changedFiles: prevChangedFiles,
|
|
1034
954
|
});
|
|
1035
955
|
}
|
|
1036
|
-
debugLog("[run] starting executeLoop");
|
|
1037
956
|
// 사용자 입력 검증 (prompt injection 방어)
|
|
1038
957
|
const inputValidation = this.promptDefense.validateUserInput(userMessage);
|
|
1039
958
|
if (inputValidation.injectionDetected && (inputValidation.severity === "critical" || inputValidation.severity === "high")) {
|
|
@@ -1121,12 +1040,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
1121
1040
|
}
|
|
1122
1041
|
// Task 분류 → 시스템 프롬프트에 tool sequence hint 주입
|
|
1123
1042
|
const classification = this.taskClassifier.classify(userMessage);
|
|
1124
|
-
// Store classification type for system prompt enrichment
|
|
1125
|
-
this.currentTaskType = classification.confidence >= CLASSIFICATION_CONFIDENCE_THRESHOLD
|
|
1126
|
-
? classification.type
|
|
1127
|
-
: undefined;
|
|
1128
|
-
// Select active strategies based on task type and execution mode
|
|
1129
|
-
this.activeStrategies = this.strategySelector.select(this.currentTaskType, undefined);
|
|
1130
1043
|
if (classification.confidence >= CLASSIFICATION_CONFIDENCE_THRESHOLD) {
|
|
1131
1044
|
const classificationHint = this.taskClassifier.formatForSystemPrompt(classification);
|
|
1132
1045
|
this.contextManager.addMessage({
|
|
@@ -1234,6 +1147,92 @@ export class AgentLoop extends EventEmitter {
|
|
|
1234
1147
|
}
|
|
1235
1148
|
// 실행 완료 후 메모리 자동 업데이트
|
|
1236
1149
|
await this.updateMemoryAfterRun(userMessage, result, Date.now() - runStartTime);
|
|
1150
|
+
// Phase 4: FailureSignatureMemory promote + PlaybookLibrary outcome recording
|
|
1151
|
+
if (result.reason === "GOAL_ACHIEVED") {
|
|
1152
|
+
try {
|
|
1153
|
+
if (this.failureSigMemory && this.repeatedErrorSignature) {
|
|
1154
|
+
this.failureSigMemory.promote(this.repeatedErrorSignature, "resolved", this.allToolResults.filter(r => r.success).map(r => r.name), true);
|
|
1155
|
+
}
|
|
1156
|
+
if (this.playbookLibrary) {
|
|
1157
|
+
const taskType = this.playbookLibrary.query(userMessage)?.taskType;
|
|
1158
|
+
if (taskType) {
|
|
1159
|
+
this.playbookLibrary.recordOutcome(taskType, true);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
catch { /* non-fatal */ }
|
|
1164
|
+
}
|
|
1165
|
+
// Phase 4 remaining: SelfImprovementLoop + MetaLearningCollector + TrustEconomics
|
|
1166
|
+
try {
|
|
1167
|
+
const taskType = this.playbookLibrary?.query(userMessage)?.taskType ?? "unknown";
|
|
1168
|
+
const toolSeq = this.allToolResults.slice(-20).map((r) => r.name);
|
|
1169
|
+
const runSuccess = result.reason === "GOAL_ACHIEVED";
|
|
1170
|
+
const runDurationMs = Date.now() - runStartTime;
|
|
1171
|
+
if (this.selfImprovementLoop) {
|
|
1172
|
+
this.selfImprovementLoop.recordOutcome({
|
|
1173
|
+
taskType,
|
|
1174
|
+
strategy: "default",
|
|
1175
|
+
toolSequence: toolSeq,
|
|
1176
|
+
success: runSuccess,
|
|
1177
|
+
iterationsUsed: this.iterationCount,
|
|
1178
|
+
tokensUsed: this.tokenUsage.total,
|
|
1179
|
+
durationMs: runDurationMs,
|
|
1180
|
+
errorSignatures: this.repeatedErrorSignature ? [this.repeatedErrorSignature] : [],
|
|
1181
|
+
});
|
|
1182
|
+
this.selfImprovementLoop.generateProposals();
|
|
1183
|
+
}
|
|
1184
|
+
if (this.metaLearningCollector) {
|
|
1185
|
+
this.metaLearningCollector.record({
|
|
1186
|
+
taskType,
|
|
1187
|
+
governorPolicies: {},
|
|
1188
|
+
toolSequence: toolSeq,
|
|
1189
|
+
latencyMs: runDurationMs,
|
|
1190
|
+
tokensUsed: this.tokenUsage.total,
|
|
1191
|
+
success: runSuccess,
|
|
1192
|
+
iterationsUsed: this.iterationCount,
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
if (this.trustEconomics) {
|
|
1196
|
+
for (const tr of this.allToolResults.slice(-20)) {
|
|
1197
|
+
const ac = toolNameToActionClass(tr.name);
|
|
1198
|
+
if (ac)
|
|
1199
|
+
this.trustEconomics.record(ac, tr.success);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
// Phase 5: record strategy outcome
|
|
1203
|
+
if (this.strategyLearner) {
|
|
1204
|
+
const activePlaybookId = this._activePlaybookId ?? "default";
|
|
1205
|
+
if (runSuccess) {
|
|
1206
|
+
this.strategyLearner.recordSuccess(activePlaybookId, taskType, this.iterationCount, this.tokenUsage.total);
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
this.strategyLearner.recordFailure(activePlaybookId, taskType, this.iterationCount, this.tokenUsage.total);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
// Phase 5 extended: periodic pattern extraction (every 10 runs)
|
|
1213
|
+
this.sessionRunCount += 1;
|
|
1214
|
+
if (this.sessionRunCount % 10 === 0) {
|
|
1215
|
+
if (this.tracePatternExtractor) {
|
|
1216
|
+
this.tracePatternExtractor.extract().catch(() => { });
|
|
1217
|
+
}
|
|
1218
|
+
if (this.metaLearningEngine) {
|
|
1219
|
+
this.metaLearningEngine.analyze().catch(() => { });
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
// Phase 6: record outcomes in capability self model and strategy market
|
|
1223
|
+
try {
|
|
1224
|
+
const env = this._detectedEnvironment ?? "general";
|
|
1225
|
+
this.capabilitySelfModel?.recordOutcome(env, taskType, runSuccess);
|
|
1226
|
+
this.strategyMarket?.recordResult(this._activePlaybookId ?? "default", taskType, {
|
|
1227
|
+
success: runSuccess,
|
|
1228
|
+
tokenCost: this.tokenUsage?.total ?? 0,
|
|
1229
|
+
latencyMs: Date.now() - runStartTime,
|
|
1230
|
+
});
|
|
1231
|
+
this.budgetGovernorV2?.endTask(this.sessionId ?? "default");
|
|
1232
|
+
}
|
|
1233
|
+
catch { /* non-fatal */ }
|
|
1234
|
+
}
|
|
1235
|
+
catch { /* non-fatal */ }
|
|
1237
1236
|
// SkillLearner: 성공적 에러 해결 시 새로운 스킬 학습
|
|
1238
1237
|
if (this.skillLearner && result.reason === "GOAL_ACHIEVED") {
|
|
1239
1238
|
try {
|
|
@@ -1289,22 +1288,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
1289
1288
|
}
|
|
1290
1289
|
// BackgroundAgentManager: 정리 (stopAll은 abort 시에만)
|
|
1291
1290
|
// Background agents는 세션 간 지속되므로 여기서 stop하지 않음
|
|
1292
|
-
// GitIntelligence: post-run commit suggestion
|
|
1293
|
-
if (this.gitAvailable && this.gitIntelligence && this.changedFiles.length > 0) {
|
|
1294
|
-
try {
|
|
1295
|
-
const [commitMsg, gitStatus] = await Promise.all([
|
|
1296
|
-
this.gitIntelligence.generateCommitMessage(false),
|
|
1297
|
-
this.gitIntelligence.getStatus(),
|
|
1298
|
-
]);
|
|
1299
|
-
if (commitMsg.subject && commitMsg.subject !== "chore: no changes") {
|
|
1300
|
-
const suggestion = this.formatGitSuggestion(commitMsg.subject, commitMsg.body, gitStatus.currentBranch, gitStatus.aheadOfRemote, gitStatus.stagedChanges, this.changedFiles);
|
|
1301
|
-
this.emitEvent({ kind: "agent:text_delta", text: suggestion });
|
|
1302
|
-
}
|
|
1303
|
-
}
|
|
1304
|
-
catch {
|
|
1305
|
-
// commit suggestion is best-effort
|
|
1306
|
-
}
|
|
1307
|
-
}
|
|
1308
1291
|
return result;
|
|
1309
1292
|
}
|
|
1310
1293
|
catch (err) {
|
|
@@ -1572,7 +1555,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
1572
1555
|
if ((complexityOrder[complexity] ?? 0) < effectiveThreshold) {
|
|
1573
1556
|
return;
|
|
1574
1557
|
}
|
|
1575
|
-
this.
|
|
1558
|
+
this.emitSubagent("planner", "start", `task complexity ${complexity}. creating execution plan`);
|
|
1576
1559
|
try {
|
|
1577
1560
|
const plan = await this.planner.createHierarchicalPlan(userMessage, this.llmClient);
|
|
1578
1561
|
this.activePlan = plan;
|
|
@@ -1598,7 +1581,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
1598
1581
|
role: "system",
|
|
1599
1582
|
content: planContext,
|
|
1600
1583
|
});
|
|
1601
|
-
this.
|
|
1584
|
+
this.emitSubagent("planner", "done", `plan created: ${plan.tactical.length} tasks, ${plan.totalEstimatedIterations} estimated iterations, risk ${plan.strategic.riskAssessment.level}`);
|
|
1602
1585
|
}
|
|
1603
1586
|
catch (err) {
|
|
1604
1587
|
this.emitEvent({
|
|
@@ -1756,8 +1739,29 @@ export class AgentLoop extends EventEmitter {
|
|
|
1756
1739
|
* (ambitious short requests that keywords miss across any language).
|
|
1757
1740
|
*/
|
|
1758
1741
|
async detectComplexity(message) {
|
|
1759
|
-
|
|
1760
|
-
|
|
1742
|
+
const heuristic = this._detectComplexityHeuristic(message);
|
|
1743
|
+
// Clear extremes — trust heuristic, skip LLM call cost
|
|
1744
|
+
if (heuristic === "trivial" || heuristic === "massive")
|
|
1745
|
+
return heuristic;
|
|
1746
|
+
// Borderline ambiguous range: ask LLM for one-word verdict
|
|
1747
|
+
// Short cheap call: no tools, 1-word response, ~50 tokens total
|
|
1748
|
+
try {
|
|
1749
|
+
const resp = await this.llmClient.chat([
|
|
1750
|
+
{
|
|
1751
|
+
role: "user",
|
|
1752
|
+
content: `Rate this software task complexity in ONE word only (trivial/simple/moderate/complex/massive). Task: "${message.slice(0, 300)}"`,
|
|
1753
|
+
},
|
|
1754
|
+
], []);
|
|
1755
|
+
const word = (resp.content ?? "").trim().toLowerCase().replace(/[^a-z]/g, "");
|
|
1756
|
+
const valid = ["trivial", "simple", "moderate", "complex", "massive"];
|
|
1757
|
+
if (valid.includes(word)) {
|
|
1758
|
+
return word;
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
catch {
|
|
1762
|
+
// LLM classification failure — fall back to heuristic
|
|
1763
|
+
}
|
|
1764
|
+
return heuristic;
|
|
1761
1765
|
}
|
|
1762
1766
|
/**
|
|
1763
1767
|
* HierarchicalPlan을 LLM이 따라갈 수 있는 컨텍스트 메시지로 포맷.
|
|
@@ -1895,8 +1899,17 @@ export class AgentLoop extends EventEmitter {
|
|
|
1895
1899
|
iteration++;
|
|
1896
1900
|
this.iterationCount = iteration;
|
|
1897
1901
|
const iterationStart = Date.now();
|
|
1902
|
+
// Reset per-iteration governor state
|
|
1903
|
+
this.verifyRanThisIteration = false;
|
|
1904
|
+
this.summarizeRanThisIteration = false;
|
|
1905
|
+
this.llmFixerRunCount = 0;
|
|
1906
|
+
this.iterationSystemMsgCount = 0;
|
|
1907
|
+
// Message pruning — cap in-memory messages to prevent OOM (GPT recommendation)
|
|
1908
|
+
this.pruneMessagesIfNeeded();
|
|
1909
|
+
// Cap allToolResults (prevents unbounded memory growth)
|
|
1910
|
+
this.allToolResults = cap(this.allToolResults, BOUNDS.allToolResults);
|
|
1911
|
+
this.allToolResultsSinceLastReplan = cap(this.allToolResultsSinceLastReplan, BOUNDS.toolResultsSinceReplan);
|
|
1898
1912
|
this.emitReasoning(`iteration ${iteration}: preparing context`);
|
|
1899
|
-
this.iterationSystemMsgCount = 0; // Reset per-iteration (prevents accumulation across iterations)
|
|
1900
1913
|
// Proactive replanning check (every 10 iterations when plan is active)
|
|
1901
1914
|
if (this.replanningEngine &&
|
|
1902
1915
|
this.activePlan &&
|
|
@@ -1955,10 +1968,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
1955
1968
|
// Soft context rollover:
|
|
1956
1969
|
// checkpoint first, then let ContextManager compact instead of aborting/throwing.
|
|
1957
1970
|
const contextUsageRatio = this.contextManager.getUsageRatio();
|
|
1958
|
-
// Task 1: ContextBudgetManager LLM summarization
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1971
|
+
// Task 1: ContextBudgetManager LLM summarization — Governor gated (default SHADOW)
|
|
1972
|
+
const summarizeMode = this.overheadGovernor.shouldRunSummarize(this.buildTriggerContext());
|
|
1973
|
+
if (summarizeMode === "BLOCKING")
|
|
1974
|
+
this.summarizeRanThisIteration = true;
|
|
1975
|
+
if (contextUsageRatio >= 0.75 && this.contextBudgetManager && !this._contextSummarizationDone && summarizeMode === "BLOCKING") {
|
|
1962
1976
|
this._contextSummarizationDone = true; // run at most once per agent turn
|
|
1963
1977
|
// Non-blocking: fire-and-forget so the main iteration is not stalled
|
|
1964
1978
|
this.contextBudgetManager.importMessages(this.contextManager.getMessages());
|
|
@@ -2112,12 +2126,29 @@ export class AgentLoop extends EventEmitter {
|
|
|
2112
2126
|
if (response.toolCalls.length === 0) {
|
|
2113
2127
|
const content = response.content ?? "";
|
|
2114
2128
|
let finalSummary = content || "Task completed.";
|
|
2129
|
+
// Phase transition: implement → verify when LLM signals completion (no tool calls)
|
|
2130
|
+
if (this.taskPhase === "implement" && this.changedFiles.length > 0) {
|
|
2131
|
+
this.transitionPhase("verify", "LLM completion signal (no tool calls)");
|
|
2132
|
+
// Run cheap checks — if they fail, stay in implement and continue loop
|
|
2133
|
+
const cheapOk = await this.runCheapChecks();
|
|
2134
|
+
if (cheapOk) {
|
|
2135
|
+
this.transitionPhase("finalize", "cheap checks passed");
|
|
2136
|
+
}
|
|
2137
|
+
else {
|
|
2138
|
+
// cheap checks failed — stay in implement, let LLM fix
|
|
2139
|
+
this.transitionPhase("implement", "cheap check failed, continuing");
|
|
2140
|
+
continue;
|
|
2141
|
+
}
|
|
2142
|
+
}
|
|
2115
2143
|
const finalImpactSummary = await this.buildFinalImpactSummary();
|
|
2116
2144
|
if (finalImpactSummary) {
|
|
2117
2145
|
finalSummary = `${finalSummary}\n\n${finalImpactSummary}`;
|
|
2118
2146
|
}
|
|
2119
|
-
// Level 2: Deep verification before declaring completion
|
|
2120
|
-
|
|
2147
|
+
// Level 2: Deep verification before declaring completion — Governor gated (default OFF)
|
|
2148
|
+
const deepVerifyMode = this.overheadGovernor.shouldRunDeepVerify(this.buildTriggerContext());
|
|
2149
|
+
if (deepVerifyMode === "BLOCKING")
|
|
2150
|
+
this.verifyRanThisIteration = true;
|
|
2151
|
+
if (this.selfReflection && this.changedFiles.length > 0 && deepVerifyMode === "BLOCKING") {
|
|
2121
2152
|
try {
|
|
2122
2153
|
const changedFilesMap = this.buildChangedFilesMap();
|
|
2123
2154
|
const verifyFn = async (prompt) => {
|
|
@@ -2146,10 +2177,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
2146
2177
|
this.emitSubagent("verifier", "done", `deep verification failed, score ${deepResult.overallScore}. continuing to address issues`);
|
|
2147
2178
|
continue; // Don't return GOAL_ACHIEVED, continue the loop
|
|
2148
2179
|
}
|
|
2149
|
-
// Level 3: Multi-agent debate
|
|
2180
|
+
// Level 3: Multi-agent debate — Governor gated (default OFF)
|
|
2181
|
+
const debateMode = this.overheadGovernor.shouldRunDebate(this.buildTriggerContext());
|
|
2150
2182
|
if (this.debateOrchestrator &&
|
|
2151
2183
|
["complex", "massive"].includes(this.currentComplexity) &&
|
|
2152
|
-
deepResult.verdict !== "pass"
|
|
2184
|
+
deepResult.verdict !== "pass" &&
|
|
2185
|
+
debateMode === "BLOCKING") {
|
|
2153
2186
|
try {
|
|
2154
2187
|
this.emitSubagent("reviewer", "start", `starting debate for ${this.currentComplexity} task verification`);
|
|
2155
2188
|
const debateContext = [
|
|
@@ -2316,7 +2349,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
2316
2349
|
}
|
|
2317
2350
|
// Task 2: QAPipeline — run "quick" (structural only) after any WRITE tool call this iteration
|
|
2318
2351
|
const projectPath = this.config.loop.projectPath;
|
|
2319
|
-
|
|
2352
|
+
const qaMode = this.overheadGovernor.shouldRunQaPipeline(this.buildTriggerContext());
|
|
2353
|
+
if (this.iterationWriteToolPaths.length > 0 && projectPath && qaMode !== "OFF") {
|
|
2320
2354
|
try {
|
|
2321
2355
|
const qaPipeline = new QAPipeline({
|
|
2322
2356
|
projectPath,
|
|
@@ -2330,21 +2364,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
2330
2364
|
});
|
|
2331
2365
|
const qaResult = await qaPipeline.run(this.iterationWriteToolPaths);
|
|
2332
2366
|
this.lastQAResult = qaResult;
|
|
2333
|
-
// Surface QA issues as a system message so LLM sees them next iteration
|
|
2334
2367
|
const failedChecks = qaResult.stages
|
|
2335
2368
|
.flatMap((s) => s.checks)
|
|
2336
2369
|
.filter((c) => c.status === "fail" || c.status === "warn");
|
|
2337
2370
|
const qaIssues = failedChecks
|
|
2338
2371
|
.slice(0, 10)
|
|
2339
2372
|
.map((c) => `[${c.severity}] ${c.name}: ${c.message}`);
|
|
2340
|
-
//
|
|
2373
|
+
// Always emit event for TUI display (SHADOW + BLOCKING)
|
|
2341
2374
|
this.emitEvent({
|
|
2342
2375
|
kind: "agent:qa_result",
|
|
2343
2376
|
stage: "quick",
|
|
2344
2377
|
passed: failedChecks.length === 0,
|
|
2345
2378
|
issues: qaIssues,
|
|
2346
2379
|
});
|
|
2347
|
-
|
|
2380
|
+
// Only inject into LLM context in BLOCKING mode (SHADOW = observe only)
|
|
2381
|
+
if (qaMode === "BLOCKING" && failedChecks.length > 0 && this.iterationSystemMsgCount < 5) {
|
|
2348
2382
|
const checkSummary = failedChecks
|
|
2349
2383
|
.slice(0, 5)
|
|
2350
2384
|
.map((c) => ` - [${c.severity}] ${c.name}: ${c.message}`)
|
|
@@ -2362,13 +2396,31 @@ export class AgentLoop extends EventEmitter {
|
|
|
2362
2396
|
}
|
|
2363
2397
|
// Reset per-iteration write tool tracking
|
|
2364
2398
|
this.iterationWriteToolPaths = [];
|
|
2399
|
+
// Phase 4: StallDetector check (non-blocking observer)
|
|
2400
|
+
if (this.stallDetector) {
|
|
2401
|
+
try {
|
|
2402
|
+
const stallResult = this.stallDetector.check(this.iterationCount, this.changedFiles, this.repeatedErrorSignature);
|
|
2403
|
+
if (stallResult.stalled && stallResult.reason) {
|
|
2404
|
+
this.emitEvent({
|
|
2405
|
+
kind: "agent:task_stalled",
|
|
2406
|
+
taskId: this.sessionId ?? "unknown",
|
|
2407
|
+
stallReason: stallResult.reason,
|
|
2408
|
+
iterationsElapsed: stallResult.iterationsElapsed,
|
|
2409
|
+
estimatedIterations: this.config.loop.maxIterations ?? 20,
|
|
2410
|
+
timestamp: Date.now(),
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
}
|
|
2414
|
+
catch { /* non-fatal */ }
|
|
2415
|
+
}
|
|
2365
2416
|
// Task 3: Auto-run tsc --noEmit after 2+ TS files modified in this iteration
|
|
2366
2417
|
// Skip if tsc was already run in the previous iteration (cooldown)
|
|
2367
2418
|
const tscFilesThisIteration = [...this.iterationTsFilesModified];
|
|
2368
2419
|
this.iterationTsFilesModified = []; // reset for next iteration
|
|
2369
2420
|
const tscRanPrev = this.tscRanLastIteration;
|
|
2370
2421
|
this.tscRanLastIteration = false; // will set to true below if we run it
|
|
2371
|
-
|
|
2422
|
+
const tscMode = this.overheadGovernor.shouldRunAutoTsc(this.buildTriggerContext());
|
|
2423
|
+
if (tscFilesThisIteration.length >= 2 && projectPath && !tscRanPrev && tscMode !== "OFF") {
|
|
2372
2424
|
try {
|
|
2373
2425
|
const tscResult = await this.toolExecutor.execute({
|
|
2374
2426
|
id: `auto-tsc-${Date.now()}`,
|
|
@@ -2380,27 +2432,28 @@ export class AgentLoop extends EventEmitter {
|
|
|
2380
2432
|
}),
|
|
2381
2433
|
});
|
|
2382
2434
|
this.tscRanLastIteration = true;
|
|
2383
|
-
// Inject TypeScript errors into context so LLM sees them next iteration
|
|
2384
2435
|
if (tscResult.success && tscResult.output && tscResult.output.trim().length > 0) {
|
|
2385
2436
|
const tscOutput = tscResult.output.trim();
|
|
2386
|
-
// Only inject if there are actual TS errors (output is non-empty)
|
|
2387
2437
|
const hasErrors = tscOutput.includes(": error TS") || tscOutput.includes("error TS");
|
|
2388
|
-
if (hasErrors
|
|
2389
|
-
//
|
|
2390
|
-
const truncated = tscOutput.length > 2000
|
|
2391
|
-
? tscOutput.slice(0, 2000) + "\n[...tsc output truncated]"
|
|
2392
|
-
: tscOutput;
|
|
2393
|
-
this.contextManager.addMessage({
|
|
2394
|
-
role: "system",
|
|
2395
|
-
content: `[Auto-TSC] TypeScript errors detected after modifying ${tscFilesThisIteration.length} files:\n\`\`\`\n${truncated}\n\`\`\`\nPlease fix these type errors.`,
|
|
2396
|
-
});
|
|
2397
|
-
this.iterationSystemMsgCount++;
|
|
2438
|
+
if (hasErrors) {
|
|
2439
|
+
// Always emit thinking event (SHADOW + BLOCKING)
|
|
2398
2440
|
this.emitEvent({
|
|
2399
2441
|
kind: "agent:thinking",
|
|
2400
2442
|
content: `Auto-TSC: TypeScript errors found after editing ${tscFilesThisIteration.join(", ")}.`,
|
|
2401
2443
|
});
|
|
2444
|
+
// Only inject into LLM context in BLOCKING mode
|
|
2445
|
+
if (tscMode === "BLOCKING" && this.iterationSystemMsgCount < 5) {
|
|
2446
|
+
const truncated = tscOutput.length > 2000
|
|
2447
|
+
? tscOutput.slice(0, 2000) + "\n[...tsc output truncated]"
|
|
2448
|
+
: tscOutput;
|
|
2449
|
+
this.contextManager.addMessage({
|
|
2450
|
+
role: "system",
|
|
2451
|
+
content: `[Auto-TSC] TypeScript errors detected after modifying ${tscFilesThisIteration.length} files:\n\`\`\`\n${truncated}\n\`\`\`\nPlease fix these type errors.`,
|
|
2452
|
+
});
|
|
2453
|
+
this.iterationSystemMsgCount++;
|
|
2454
|
+
}
|
|
2402
2455
|
}
|
|
2403
|
-
else
|
|
2456
|
+
else {
|
|
2404
2457
|
this.emitEvent({
|
|
2405
2458
|
kind: "agent:thinking",
|
|
2406
2459
|
content: `Auto-TSC: No type errors after editing ${tscFilesThisIteration.length} file(s).`,
|
|
@@ -2467,8 +2520,9 @@ export class AgentLoop extends EventEmitter {
|
|
|
2467
2520
|
// Reflection failure is non-fatal
|
|
2468
2521
|
}
|
|
2469
2522
|
}
|
|
2470
|
-
// Level 1: Quick verification
|
|
2471
|
-
|
|
2523
|
+
// Level 1: Quick verification — Governor gated (default SHADOW = no LLM call)
|
|
2524
|
+
const quickVerifyMode = this.overheadGovernor.shouldRunQuickVerify(this.buildTriggerContext());
|
|
2525
|
+
if (this.selfReflection && iteration % 3 === 0 && quickVerifyMode === "BLOCKING") {
|
|
2472
2526
|
try {
|
|
2473
2527
|
this.emitSubagent("verifier", "start", "running quick verification");
|
|
2474
2528
|
const changedFilesMap = this.buildChangedFilesMap();
|
|
@@ -2506,6 +2560,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
2506
2560
|
const errorSummary = errorResults
|
|
2507
2561
|
.map((r) => `${r.name}: ${r.output}`)
|
|
2508
2562
|
.join("\n");
|
|
2563
|
+
// Track repeated error signature for Governor
|
|
2564
|
+
this.trackErrorSignature(errorSummary);
|
|
2509
2565
|
// FailureRecovery: 근본 원인 분석 + 전략 선택
|
|
2510
2566
|
const rootCause = this.failureRecovery.analyzeRootCause(errorSummary, errorResults[0]?.name);
|
|
2511
2567
|
const decision = this.failureRecovery.selectStrategy(rootCause, {
|
|
@@ -2550,7 +2606,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
2550
2606
|
});
|
|
2551
2607
|
// SelfDebugLoop: merge debug strategy into recovery message (not separate system msg)
|
|
2552
2608
|
let debugSuffix = "";
|
|
2553
|
-
|
|
2609
|
+
const llmFixerMode = this.overheadGovernor.shouldRunLlmFixer(this.buildTriggerContext());
|
|
2610
|
+
if (iteration >= 3 && llmFixerMode === "BLOCKING") {
|
|
2554
2611
|
const rootCauseAnalysis = this.selfDebugLoop.analyzeError(errorSummary);
|
|
2555
2612
|
if (rootCauseAnalysis.confidence >= 0.5) {
|
|
2556
2613
|
const debugStrategy = this.selfDebugLoop.selectStrategy(iteration - 2, []);
|
|
@@ -2568,6 +2625,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
2568
2625
|
debugSuffix = `\n\n[SelfDebug L${Math.min(iteration - 2, 5)}] Strategy: ${debugStrategy}\n${debugPrompt}`;
|
|
2569
2626
|
// Bug 2 fix: wire a real llmFixer so selfDebugLoop.debug() can call LLM
|
|
2570
2627
|
if (debugStrategy !== "escalate") {
|
|
2628
|
+
this.llmFixerRunCount++;
|
|
2571
2629
|
this.selfDebugLoop.debug({
|
|
2572
2630
|
testCommand: testCmd,
|
|
2573
2631
|
errorOutput: errorSummary,
|
|
@@ -2777,7 +2835,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
2777
2835
|
model: item.model,
|
|
2778
2836
|
source: item.source ?? "llm",
|
|
2779
2837
|
});
|
|
2780
|
-
this.reasoningAdapter?.onDelta(item.text);
|
|
2781
2838
|
}
|
|
2782
2839
|
continue;
|
|
2783
2840
|
}
|
|
@@ -2851,7 +2908,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
2851
2908
|
}
|
|
2852
2909
|
if (flushTimer)
|
|
2853
2910
|
clearTimeout(flushTimer);
|
|
2854
|
-
this.reasoningAdapter?.flush();
|
|
2855
2911
|
return {
|
|
2856
2912
|
content: content || null,
|
|
2857
2913
|
toolCalls,
|
|
@@ -2944,48 +3000,32 @@ export class AgentLoop extends EventEmitter {
|
|
|
2944
3000
|
}
|
|
2945
3001
|
// MCP 도구 호출 확인
|
|
2946
3002
|
if (this.mcpClient && this.isMCPTool(toolCall.name)) {
|
|
3003
|
+
// Emit tool_start before execution (required for trace, QA pipeline, replay)
|
|
3004
|
+
this.emitEvent({
|
|
3005
|
+
kind: "agent:tool_start",
|
|
3006
|
+
tool: toolCall.name,
|
|
3007
|
+
input: args,
|
|
3008
|
+
source: "mcp",
|
|
3009
|
+
});
|
|
2947
3010
|
const mcpResult = await this.executeMCPTool(toolCall);
|
|
3011
|
+
// Normalize MCP result: wrap search tool output into structured JSON if applicable
|
|
3012
|
+
const normalizedOutput = this.normalizeMcpResult(toolCall.name, mcpResult.output);
|
|
3013
|
+
const finalResult = { ...mcpResult, output: normalizedOutput };
|
|
2948
3014
|
this.emitEvent({
|
|
2949
3015
|
kind: "agent:tool_result",
|
|
2950
3016
|
tool: toolCall.name,
|
|
2951
|
-
output:
|
|
2952
|
-
?
|
|
2953
|
-
:
|
|
2954
|
-
durationMs:
|
|
3017
|
+
output: finalResult.output.length > 200
|
|
3018
|
+
? finalResult.output.slice(0, 200) + "..."
|
|
3019
|
+
: finalResult.output,
|
|
3020
|
+
durationMs: finalResult.durationMs,
|
|
2955
3021
|
});
|
|
2956
3022
|
this.emitEvent({ kind: "agent:reasoning_delta", text: `tool finished: ${toolCall.name}` });
|
|
2957
|
-
return { result:
|
|
3023
|
+
return { result: finalResult, deferredFixPrompt: null };
|
|
2958
3024
|
}
|
|
2959
3025
|
// 도구 실행
|
|
2960
3026
|
const startTime = Date.now();
|
|
2961
3027
|
const toolAbort = new AbortController();
|
|
2962
3028
|
this.interruptManager.registerToolAbort(toolAbort);
|
|
2963
|
-
// Sandbox preflight — block disallowed tool calls before execution
|
|
2964
|
-
if (this.sandbox) {
|
|
2965
|
-
const sandboxCheck = this.sandbox.validateToolCall(toolCall.name, args);
|
|
2966
|
-
if (!sandboxCheck.allowed) {
|
|
2967
|
-
this.interruptManager.clearToolAbort();
|
|
2968
|
-
const blocked = `[SANDBOX BLOCKED] ${sandboxCheck.violations.join("; ")}`;
|
|
2969
|
-
this.emitEvent({ kind: "agent:tool_result", tool: toolCall.name, output: blocked, durationMs: 0 });
|
|
2970
|
-
return {
|
|
2971
|
-
result: { tool_call_id: toolCall.id, name: toolCall.name, output: blocked, success: false, durationMs: 0 },
|
|
2972
|
-
deferredFixPrompt: null,
|
|
2973
|
-
};
|
|
2974
|
-
}
|
|
2975
|
-
}
|
|
2976
|
-
// Git conflict prediction — warn if file has high merge-conflict risk
|
|
2977
|
-
if (this.conflictCache && ["file_write", "file_edit"].includes(toolCall.name)) {
|
|
2978
|
-
const writePath = String(args.path ?? args.file ?? "");
|
|
2979
|
-
if (writePath) {
|
|
2980
|
-
const prediction = this.conflictCache.find((c) => c.file === writePath || writePath.endsWith(c.file) || c.file.endsWith(writePath));
|
|
2981
|
-
if (prediction?.risk === "high") {
|
|
2982
|
-
this.emitEvent({
|
|
2983
|
-
kind: "agent:text_delta",
|
|
2984
|
-
text: `\n⚠ Conflict risk (${prediction.file}): ${prediction.reason}\n`,
|
|
2985
|
-
});
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
|
-
}
|
|
2989
3029
|
if (["file_write", "file_edit"].includes(toolCall.name)) {
|
|
2990
3030
|
const candidatePath = args.path ??
|
|
2991
3031
|
args.file;
|
|
@@ -3033,29 +3073,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
3033
3073
|
});
|
|
3034
3074
|
this.emitReasoning(`success: ${toolCall.name}`);
|
|
3035
3075
|
this.reasoningTree.add("tool", `success: ${toolCall.name}`);
|
|
3036
|
-
// Security Scanner postflight — scan written files for vulnerabilities (opt-in)
|
|
3037
|
-
if (this.securityScanner && result.success && ["file_write", "file_edit"].includes(toolCall.name)) {
|
|
3038
|
-
const writtenPath = args.path ?? args.file;
|
|
3039
|
-
if (typeof writtenPath === "string") {
|
|
3040
|
-
this.securityScanner.scan([writtenPath]).then((scanResult) => {
|
|
3041
|
-
// Always render table — shows ✓ when clean, ⚠ when findings exist
|
|
3042
|
-
const hasBad = scanResult.findings.some((f) => f.severity === "critical" || f.severity === "high");
|
|
3043
|
-
if (hasBad || scanResult.findings.length > 0) {
|
|
3044
|
-
this.emitEvent({
|
|
3045
|
-
kind: "agent:text_delta",
|
|
3046
|
-
text: formatSecurityTable(scanResult.findings, writtenPath),
|
|
3047
|
-
});
|
|
3048
|
-
}
|
|
3049
|
-
}).catch(() => { });
|
|
3050
|
-
}
|
|
3051
|
-
}
|
|
3052
3076
|
if (["file_write", "file_edit"].includes(toolCall.name) && result.success) {
|
|
3077
|
+
// Phase transition: explore → implement on first write
|
|
3078
|
+
if (this.taskPhase === "explore") {
|
|
3079
|
+
this.transitionPhase("implement", `first write: ${toolCall.name}`);
|
|
3080
|
+
}
|
|
3053
3081
|
const filePath = args.path ??
|
|
3054
3082
|
args.file ??
|
|
3055
3083
|
"unknown";
|
|
3056
3084
|
const filePathStr = String(filePath);
|
|
3057
3085
|
if (!this.changedFiles.includes(filePathStr)) {
|
|
3058
3086
|
this.changedFiles.push(filePathStr);
|
|
3087
|
+
// Cap changedFiles to prevent unbounded growth on massive refactors
|
|
3088
|
+
if (this.changedFiles.length > BOUNDS.changedFiles) {
|
|
3089
|
+
this.changedFiles = this.changedFiles.slice(-BOUNDS.changedFiles);
|
|
3090
|
+
}
|
|
3059
3091
|
}
|
|
3060
3092
|
// Task 2: track write tool paths per-iteration for QA triggering
|
|
3061
3093
|
if (!this.iterationWriteToolPaths.includes(filePathStr)) {
|
|
@@ -3066,6 +3098,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
3066
3098
|
this.iterationTsFilesModified.push(filePathStr);
|
|
3067
3099
|
}
|
|
3068
3100
|
this.emitEvent({ kind: "agent:file_change", path: filePathStr, diff: result.output });
|
|
3101
|
+
// Emit evidence report (async, fire-and-forget — no loop complexity added)
|
|
3102
|
+
this.emitEvidenceReport(filePathStr, toolCall.name).catch(() => { });
|
|
3069
3103
|
// Update world state after file modification
|
|
3070
3104
|
if (this.config.loop.projectPath) {
|
|
3071
3105
|
const wsProjectPath = this.config.loop.projectPath;
|
|
@@ -3773,6 +3807,44 @@ export class AgentLoop extends EventEmitter {
|
|
|
3773
3807
|
const args = this.parseToolArgs(toolCall.arguments);
|
|
3774
3808
|
return this.mcpClient.callToolAsYuan(toolCall.name, args, toolCall.id);
|
|
3775
3809
|
}
|
|
3810
|
+
/**
|
|
3811
|
+
* Normalize an MCP tool result for consistent downstream consumption.
|
|
3812
|
+
*
|
|
3813
|
+
* - Search tools (tool name contains "search"): if the output is unstructured text,
|
|
3814
|
+
* wrap it into a JSON array of `{ title, url, snippet, source }` objects.
|
|
3815
|
+
* Each non-empty line is treated as a snippet entry when structured data is absent.
|
|
3816
|
+
* - All other tools: pass through unchanged.
|
|
3817
|
+
*/
|
|
3818
|
+
normalizeMcpResult(toolName, output) {
|
|
3819
|
+
const isSearch = /search/i.test(toolName);
|
|
3820
|
+
if (!isSearch)
|
|
3821
|
+
return output;
|
|
3822
|
+
// If already valid JSON, leave as-is
|
|
3823
|
+
try {
|
|
3824
|
+
JSON.parse(output);
|
|
3825
|
+
return output;
|
|
3826
|
+
}
|
|
3827
|
+
catch {
|
|
3828
|
+
// Not JSON — wrap plain-text lines into structured objects
|
|
3829
|
+
}
|
|
3830
|
+
const lines = output
|
|
3831
|
+
.split("\n")
|
|
3832
|
+
.map((l) => l.trim())
|
|
3833
|
+
.filter(Boolean);
|
|
3834
|
+
if (lines.length === 0)
|
|
3835
|
+
return output;
|
|
3836
|
+
const structured = lines.map((line) => {
|
|
3837
|
+
// Attempt to extract a URL from the line
|
|
3838
|
+
const urlMatch = line.match(/https?:\/\/\S+/);
|
|
3839
|
+
return {
|
|
3840
|
+
title: "",
|
|
3841
|
+
url: urlMatch ? urlMatch[0] : "",
|
|
3842
|
+
snippet: line,
|
|
3843
|
+
source: toolName,
|
|
3844
|
+
};
|
|
3845
|
+
});
|
|
3846
|
+
return JSON.stringify(structured, null, 2);
|
|
3847
|
+
}
|
|
3776
3848
|
/**
|
|
3777
3849
|
* ContinuousReflection overflow signal을 soft rollover로 처리한다.
|
|
3778
3850
|
* 절대 abort하지 않고, 체크포인트 저장 후 다음 iteration에서
|
|
@@ -3802,6 +3874,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
3802
3874
|
// cleanup failure ignored
|
|
3803
3875
|
}
|
|
3804
3876
|
}
|
|
3877
|
+
this.traceRecorder?.stop();
|
|
3805
3878
|
}
|
|
3806
3879
|
// ─── Helpers ───
|
|
3807
3880
|
/**
|
|
@@ -3820,83 +3893,10 @@ export class AgentLoop extends EventEmitter {
|
|
|
3820
3893
|
}
|
|
3821
3894
|
return map;
|
|
3822
3895
|
}
|
|
3823
|
-
/** Check git is available and cache the result. Called once per run(). */
|
|
3824
|
-
async initGitAvailability() {
|
|
3825
|
-
if (!this.gitIntelligence)
|
|
3826
|
-
return;
|
|
3827
|
-
if (this.gitAvailable)
|
|
3828
|
-
return; // already confirmed
|
|
3829
|
-
try {
|
|
3830
|
-
const { execFile: ef } = await import("node:child_process");
|
|
3831
|
-
const { promisify } = await import("node:util");
|
|
3832
|
-
const exec = promisify(ef);
|
|
3833
|
-
await exec("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
3834
|
-
cwd: this.config.loop.projectPath ?? process.cwd(),
|
|
3835
|
-
});
|
|
3836
|
-
this.gitAvailable = true;
|
|
3837
|
-
}
|
|
3838
|
-
catch {
|
|
3839
|
-
this.gitAvailable = false;
|
|
3840
|
-
this.gitIntelligence = null; // disable for this session
|
|
3841
|
-
}
|
|
3842
|
-
}
|
|
3843
|
-
/** Formats post-run commit suggestion + push status as a box-drawn terminal table */
|
|
3844
|
-
formatGitSuggestion(subject, body, branch, ahead, staged, changedFiles) {
|
|
3845
|
-
const R = "\x1b[0m";
|
|
3846
|
-
const BOLD = "\x1b[1m";
|
|
3847
|
-
const DIM = "\x1b[2m";
|
|
3848
|
-
const GRN = "\x1b[32m";
|
|
3849
|
-
const CYN = "\x1b[36m";
|
|
3850
|
-
const YEL = "\x1b[33m";
|
|
3851
|
-
const W = 58;
|
|
3852
|
-
const h = "─";
|
|
3853
|
-
const v = "│";
|
|
3854
|
-
const tl = "┌";
|
|
3855
|
-
const tr = "┐";
|
|
3856
|
-
const bl = "└";
|
|
3857
|
-
const br = "┘";
|
|
3858
|
-
const lm = "├";
|
|
3859
|
-
const rm = "┤";
|
|
3860
|
-
const div = `${lm}${h.repeat(W)}${rm}`;
|
|
3861
|
-
const top = `${tl}${h.repeat(W)}${tr}`;
|
|
3862
|
-
const bot = `${bl}${h.repeat(W)}${br}`;
|
|
3863
|
-
const pad = (s) => {
|
|
3864
|
-
const plain = s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
3865
|
-
const fill = Math.max(0, W - 2 - plain.length);
|
|
3866
|
-
return `${v} ${s}${" ".repeat(fill)} ${v}`;
|
|
3867
|
-
};
|
|
3868
|
-
const aheadStr = ahead > 0 ? `${YEL}↑${ahead} ahead${R}` : `${GRN}up-to-date${R}`;
|
|
3869
|
-
const stagedStr = staged > 0 ? `${CYN}${staged} staged${R}` : `${DIM}nothing staged${R}`;
|
|
3870
|
-
const filesStr = changedFiles.slice(0, 3).map(f => f.split("/").pop() ?? f).join(", ")
|
|
3871
|
-
+ (changedFiles.length > 3 ? ` +${changedFiles.length - 3}` : "");
|
|
3872
|
-
const subjectTrunc = subject.length > W - 4 ? subject.slice(0, W - 5) + "…" : subject;
|
|
3873
|
-
const lines = [
|
|
3874
|
-
"",
|
|
3875
|
-
top,
|
|
3876
|
-
pad(` ${BOLD}💾 Commit Suggestion${R}`),
|
|
3877
|
-
div,
|
|
3878
|
-
pad(` ${DIM}${subjectTrunc}${R}`),
|
|
3879
|
-
];
|
|
3880
|
-
if (body) {
|
|
3881
|
-
for (const line of body.split("\n").slice(0, 3)) {
|
|
3882
|
-
lines.push(pad(` ${DIM}${line.length > W - 4 ? line.slice(0, W - 5) + "…" : line}${R}`));
|
|
3883
|
-
}
|
|
3884
|
-
}
|
|
3885
|
-
lines.push(div);
|
|
3886
|
-
lines.push(pad(` Branch ${BOLD}${branch}${R} · ${aheadStr} · ${stagedStr}`));
|
|
3887
|
-
lines.push(pad(` Files ${DIM}${filesStr}${R}`));
|
|
3888
|
-
lines.push(div);
|
|
3889
|
-
lines.push(pad(` ${DIM}git add -p${R}`));
|
|
3890
|
-
lines.push(pad(` ${GRN}git commit -m "${subjectTrunc}"${R}`));
|
|
3891
|
-
if (ahead > 0) {
|
|
3892
|
-
lines.push(pad(` ${YEL}git push origin ${branch}${R} ${DIM}# ${ahead} commit(s) ahead${R}`));
|
|
3893
|
-
}
|
|
3894
|
-
lines.push(bot);
|
|
3895
|
-
lines.push("");
|
|
3896
|
-
return lines.join("\n");
|
|
3897
|
-
}
|
|
3898
3896
|
emitEvent(event) {
|
|
3899
3897
|
this.emit("event", event);
|
|
3898
|
+
// Trace recording — fire-and-forget, never blocks
|
|
3899
|
+
this.traceRecorder?.record(event);
|
|
3900
3900
|
}
|
|
3901
3901
|
emitReasoning(content) {
|
|
3902
3902
|
this.reasoningTree.add("reasoning", content);
|
|
@@ -3909,6 +3909,13 @@ export class AgentLoop extends EventEmitter {
|
|
|
3909
3909
|
emitSubagent(name, phase, content) {
|
|
3910
3910
|
this.reasoningTree.add(name, `[${phase}] ${content}`);
|
|
3911
3911
|
this.emitReasoning(`[${name}:${phase}] ${content}`);
|
|
3912
|
+
// Route subagent events to task panel via proper events
|
|
3913
|
+
if (phase === "start") {
|
|
3914
|
+
this.emitEvent({ kind: "agent:subagent_phase", taskId: name, phase: content });
|
|
3915
|
+
}
|
|
3916
|
+
else {
|
|
3917
|
+
this.emitEvent({ kind: "agent:subagent_done", taskId: name, success: !content.includes("failed") });
|
|
3918
|
+
}
|
|
3912
3919
|
}
|
|
3913
3920
|
handleFatalError(err) {
|
|
3914
3921
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3922,5 +3929,221 @@ export class AgentLoop extends EventEmitter {
|
|
|
3922
3929
|
});
|
|
3923
3930
|
return { reason: "ERROR", error: message };
|
|
3924
3931
|
}
|
|
3932
|
+
// ─── Task phase transitions ───────────────────────────────────────────────
|
|
3933
|
+
/**
|
|
3934
|
+
* Deterministic phase transition — no LLM involved.
|
|
3935
|
+
* Emits agent:phase_transition event for TUI trace.
|
|
3936
|
+
*/
|
|
3937
|
+
transitionPhase(to, trigger) {
|
|
3938
|
+
if (this.taskPhase === to)
|
|
3939
|
+
return;
|
|
3940
|
+
const from = this.taskPhase;
|
|
3941
|
+
this.taskPhase = to;
|
|
3942
|
+
this.emitEvent({
|
|
3943
|
+
kind: "agent:phase_transition",
|
|
3944
|
+
from,
|
|
3945
|
+
to,
|
|
3946
|
+
iteration: this.iterationCount,
|
|
3947
|
+
trigger,
|
|
3948
|
+
});
|
|
3949
|
+
this.emitReasoning(`phase: ${from} → ${to} (${trigger})`);
|
|
3950
|
+
}
|
|
3951
|
+
/**
|
|
3952
|
+
* Cheap local checks run in verify phase — no LLM.
|
|
3953
|
+
* Returns true if all checks pass (safe to advance to finalize).
|
|
3954
|
+
*/
|
|
3955
|
+
async runCheapChecks() {
|
|
3956
|
+
const projectPath = this.config.loop.projectPath;
|
|
3957
|
+
// 1. Diff size check — if nothing changed, skip
|
|
3958
|
+
if (this.changedFiles.length === 0)
|
|
3959
|
+
return true;
|
|
3960
|
+
// 2. File existence check — all changedFiles should exist
|
|
3961
|
+
const { existsSync } = await import("node:fs");
|
|
3962
|
+
for (const f of this.changedFiles) {
|
|
3963
|
+
const abs = projectPath ? `${projectPath}/${f}` : f;
|
|
3964
|
+
if (f.startsWith("/") ? !existsSync(f) : !existsSync(abs)) {
|
|
3965
|
+
this.emitReasoning(`cheap check: file missing: ${f}`);
|
|
3966
|
+
return false;
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
// 3. Fast syntax check — tsc --noEmit --skipLibCheck only on changed TS files
|
|
3970
|
+
const tsFiles = this.changedFiles.filter(f => /\.[cm]?tsx?$/.test(f));
|
|
3971
|
+
if (tsFiles.length > 0 && projectPath) {
|
|
3972
|
+
try {
|
|
3973
|
+
const result = await this.toolExecutor.execute({
|
|
3974
|
+
id: `cheap-tsc-${Date.now()}`,
|
|
3975
|
+
name: "shell_exec",
|
|
3976
|
+
arguments: JSON.stringify({
|
|
3977
|
+
command: `npx tsc --noEmit --skipLibCheck 2>&1 | head -20 || true`,
|
|
3978
|
+
cwd: projectPath,
|
|
3979
|
+
timeout: 30000,
|
|
3980
|
+
}),
|
|
3981
|
+
});
|
|
3982
|
+
if (result.success && result.output) {
|
|
3983
|
+
const hasErrors = result.output.includes(": error TS");
|
|
3984
|
+
if (hasErrors) {
|
|
3985
|
+
this.emitReasoning(`cheap check: tsc errors found, staying in verify`);
|
|
3986
|
+
// Inject TS errors so LLM sees them and fixes them
|
|
3987
|
+
if (this.iterationSystemMsgCount < 5) {
|
|
3988
|
+
const truncated = result.output.length > 1000
|
|
3989
|
+
? result.output.slice(0, 1000) + "\n[truncated]"
|
|
3990
|
+
: result.output;
|
|
3991
|
+
this.contextManager.addMessage({
|
|
3992
|
+
role: "system",
|
|
3993
|
+
content: `[Verify Phase] TypeScript errors found:\n\`\`\`\n${truncated}\n\`\`\`\nPlease fix before completion.`,
|
|
3994
|
+
});
|
|
3995
|
+
this.iterationSystemMsgCount++;
|
|
3996
|
+
}
|
|
3997
|
+
return false;
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
catch {
|
|
4002
|
+
// cheap check failure is non-fatal — allow finalize
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
return true;
|
|
4006
|
+
}
|
|
4007
|
+
// ─── Evidence Report ───────────────────────────────────────────────────────
|
|
4008
|
+
/**
|
|
4009
|
+
* After a file write/edit, collect diff stats + syntax signal and emit
|
|
4010
|
+
* `agent:evidence_report`. Runs async and never blocks the main loop.
|
|
4011
|
+
*/
|
|
4012
|
+
async emitEvidenceReport(filePath, tool) {
|
|
4013
|
+
const timestamp = Date.now();
|
|
4014
|
+
const projectPath = this.config.loop.projectPath;
|
|
4015
|
+
// 1. Diff stats via git diff --stat
|
|
4016
|
+
let diffStats = null;
|
|
4017
|
+
try {
|
|
4018
|
+
const diffResult = await this.toolExecutor.execute({
|
|
4019
|
+
id: `evidence-diff-${timestamp}`,
|
|
4020
|
+
name: "shell_exec",
|
|
4021
|
+
arguments: JSON.stringify({
|
|
4022
|
+
command: `git diff --numstat HEAD -- "${filePath}" 2>/dev/null || echo "0\t0\t${filePath}"`,
|
|
4023
|
+
cwd: projectPath ?? ".",
|
|
4024
|
+
timeout: 5000,
|
|
4025
|
+
}),
|
|
4026
|
+
});
|
|
4027
|
+
if (diffResult.success && diffResult.output) {
|
|
4028
|
+
const match = diffResult.output.match(/^(\d+)\s+(\d+)/m);
|
|
4029
|
+
if (match) {
|
|
4030
|
+
diffStats = { added: parseInt(match[1], 10), removed: parseInt(match[2], 10) };
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4034
|
+
catch { /* non-fatal */ }
|
|
4035
|
+
// 2. Syntax check — skipped here to avoid spawning a duplicate tsc process.
|
|
4036
|
+
// runCheapChecks() already runs tsc at the implement→verify boundary and
|
|
4037
|
+
// the result flows to the QA pipeline. Spawning tsc per-write would
|
|
4038
|
+
// create N parallel tsc processes for bulk refactors.
|
|
4039
|
+
const syntax = "skipped";
|
|
4040
|
+
this.emitEvent({
|
|
4041
|
+
kind: "agent:evidence_report",
|
|
4042
|
+
filePath,
|
|
4043
|
+
tool,
|
|
4044
|
+
syntax,
|
|
4045
|
+
diffStats,
|
|
4046
|
+
lintResult: "skipped",
|
|
4047
|
+
timestamp,
|
|
4048
|
+
});
|
|
4049
|
+
// If a dep-graph file changed, invalidate arch summary cache
|
|
4050
|
+
const isDepsChange = filePath.includes("package.json") || filePath.includes("tsconfig");
|
|
4051
|
+
if (isDepsChange && this.archSummarizer) {
|
|
4052
|
+
this.archSummarizer.regenerate().catch(() => { });
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
// ─── OverheadGovernor helpers ─────────────────────────────────────────────
|
|
4056
|
+
/**
|
|
4057
|
+
* 현재 런타임 상태로 TriggerContext 빌드.
|
|
4058
|
+
*/
|
|
4059
|
+
buildTriggerContext() {
|
|
4060
|
+
const totalBudget = this.config.loop.totalTokenBudget;
|
|
4061
|
+
return {
|
|
4062
|
+
changedFiles: this.changedFiles,
|
|
4063
|
+
writeCountSinceVerify: this.writeCountSinceVerify,
|
|
4064
|
+
failureCount: this.allToolResults.filter(r => !r.success).length,
|
|
4065
|
+
repeatedErrorSignature: this.repeatedErrorSignature,
|
|
4066
|
+
plannerConfidence: undefined,
|
|
4067
|
+
contextUsageRatio: totalBudget > 0 ? this.tokenUsage.total / totalBudget : 0,
|
|
4068
|
+
riskyWrite: this.changedFiles.some(f => f.includes("tsconfig") || f.includes("package.json") ||
|
|
4069
|
+
f.endsWith("index.ts") || f.endsWith("index.tsx") || f.includes(".d.ts")),
|
|
4070
|
+
taskPhase: this.taskPhase,
|
|
4071
|
+
iteration: this.iterationCount,
|
|
4072
|
+
verifyRanThisIteration: this.verifyRanThisIteration,
|
|
4073
|
+
summarizeRanThisIteration: this.summarizeRanThisIteration,
|
|
4074
|
+
llmFixerRunCount: this.llmFixerRunCount,
|
|
4075
|
+
};
|
|
4076
|
+
}
|
|
4077
|
+
/**
|
|
4078
|
+
* 반복 에러 시그니처 추적 — 같은 에러가 2번 이상 나오면 repeatedErrorSignature 세팅.
|
|
4079
|
+
*/
|
|
4080
|
+
trackErrorSignature(errorSummary) {
|
|
4081
|
+
const sig = errorSummary.slice(0, 120);
|
|
4082
|
+
if (sig === this._lastErrorSignature) {
|
|
4083
|
+
this._errorSignatureCount++;
|
|
4084
|
+
if (this._errorSignatureCount >= 2) {
|
|
4085
|
+
this.repeatedErrorSignature = sig;
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
else {
|
|
4089
|
+
this._lastErrorSignature = sig;
|
|
4090
|
+
this._errorSignatureCount = 1;
|
|
4091
|
+
this.repeatedErrorSignature = undefined;
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
// ─── Message pruning — GPT recommendation ────────────────────────────────
|
|
4095
|
+
static MAX_MESSAGES = 40;
|
|
4096
|
+
static PRUNE_KEEP_RECENT = 10;
|
|
4097
|
+
/**
|
|
4098
|
+
* 메시지 배열이 MAX_MESSAGES를 초과하면 prune.
|
|
4099
|
+
* 구조: [system] + [task summary] + [last PRUNE_KEEP_RECENT turns]
|
|
4100
|
+
*
|
|
4101
|
+
* "turns" = user/assistant 쌍 (tool messages는 해당 assistant 메시지에 귀속)
|
|
4102
|
+
*/
|
|
4103
|
+
pruneMessagesIfNeeded() {
|
|
4104
|
+
const msgs = this.contextManager.getMessages();
|
|
4105
|
+
if (msgs.length <= AgentLoop.MAX_MESSAGES)
|
|
4106
|
+
return;
|
|
4107
|
+
const system = msgs.filter(m => m.role === "system");
|
|
4108
|
+
const nonSystem = msgs.filter(m => m.role !== "system");
|
|
4109
|
+
// last N non-system messages 유지
|
|
4110
|
+
const keep = nonSystem.slice(-AgentLoop.PRUNE_KEEP_RECENT);
|
|
4111
|
+
// pruned 범위에서 task summary 추출 (user 메시지 첫 번째)
|
|
4112
|
+
const pruned = nonSystem.slice(0, nonSystem.length - AgentLoop.PRUNE_KEEP_RECENT);
|
|
4113
|
+
const userGoals = pruned
|
|
4114
|
+
.filter(m => m.role === "user")
|
|
4115
|
+
.map(m => typeof m.content === "string" ? m.content.slice(0, 200) : "")
|
|
4116
|
+
.filter(Boolean)
|
|
4117
|
+
.slice(0, 3);
|
|
4118
|
+
const summaryContent = userGoals.length > 0
|
|
4119
|
+
? `[Context pruned — earlier goals: ${userGoals.join(" | ")}]`
|
|
4120
|
+
: `[Context pruned — ${pruned.length} older messages removed]`;
|
|
4121
|
+
const summary = [{ role: "system", content: summaryContent }];
|
|
4122
|
+
// contextManager 재구성 (system + summary + recent)
|
|
4123
|
+
this.contextManager.clear();
|
|
4124
|
+
for (const m of [...system, ...summary, ...keep]) {
|
|
4125
|
+
this.contextManager.addMessage(m);
|
|
4126
|
+
}
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
// ─── Phase 4: TrustEconomics helper ─────────────────────────────────────────
|
|
4130
|
+
function toolNameToActionClass(name) {
|
|
4131
|
+
if (name === "file_read")
|
|
4132
|
+
return "file_read";
|
|
4133
|
+
if (name === "file_write")
|
|
4134
|
+
return "file_write";
|
|
4135
|
+
if (name === "file_edit")
|
|
4136
|
+
return "file_edit";
|
|
4137
|
+
if (name === "file_delete")
|
|
4138
|
+
return "file_delete";
|
|
4139
|
+
if (name === "shell_exec")
|
|
4140
|
+
return "shell_exec_risky"; // conservative default
|
|
4141
|
+
if (name === "git_read" || name === "git_log" || name === "git_blame")
|
|
4142
|
+
return "git_read";
|
|
4143
|
+
if (name === "git_commit" || name === "git_push" || name === "git_stash")
|
|
4144
|
+
return "git_write";
|
|
4145
|
+
if (name.startsWith("mcp_"))
|
|
4146
|
+
return "mcp_call";
|
|
4147
|
+
return null;
|
|
3925
4148
|
}
|
|
3926
4149
|
//# sourceMappingURL=agent-loop.js.map
|