@yuaone/core 0.9.7 → 0.9.9
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 -29
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +697 -335
- 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 +15 -122
- 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/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 +272 -3
- 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,10 +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 {
|
|
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";
|
|
72
79
|
/**
|
|
73
80
|
* AgentLoop — YUAN 에이전트의 핵심 실행 루프.
|
|
74
81
|
*
|
|
@@ -98,87 +105,6 @@ import { SecurityScanner } from "./security-scanner.js";
|
|
|
98
105
|
*/
|
|
99
106
|
/** Minimum confidence for classification-based hints/routing to activate */
|
|
100
107
|
const CLASSIFICATION_CONFIDENCE_THRESHOLD = 0.6;
|
|
101
|
-
// ─── Security Table Formatter ─────────────────────────────────────────────────
|
|
102
|
-
/**
|
|
103
|
-
* Renders SecurityScanner findings as a box-drawn terminal table with ANSI colors.
|
|
104
|
-
*
|
|
105
|
-
* ⚠ Security Scan · src/app.ts
|
|
106
|
-
* ┌────────────┬─────────┬──────────────────────────────────┐
|
|
107
|
-
* │ Severity │ Count │ Rules │
|
|
108
|
-
* ├────────────┼─────────┼──────────────────────────────────┤
|
|
109
|
-
* │ CRITICAL │ 1 │ hardcoded-secret │
|
|
110
|
-
* │ HIGH │ 2 │ sql-injection, xss-reflected │
|
|
111
|
-
* │ MEDIUM │ 0 │ — │
|
|
112
|
-
* │ LOW │ 1 │ weak-hash │
|
|
113
|
-
* └────────────┴─────────┴──────────────────────────────────┘
|
|
114
|
-
*/
|
|
115
|
-
function formatSecurityTable(findings, filePath) {
|
|
116
|
-
// ANSI
|
|
117
|
-
const R = "\x1b[0m";
|
|
118
|
-
const BOLD = "\x1b[1m";
|
|
119
|
-
const DIM = "\x1b[2m";
|
|
120
|
-
const RED = "\x1b[1;31m";
|
|
121
|
-
const YEL = "\x1b[33m";
|
|
122
|
-
const CYN = "\x1b[36m";
|
|
123
|
-
const GRN = "\x1b[32m";
|
|
124
|
-
const WHT = "\x1b[1;37m";
|
|
125
|
-
// Aggregate
|
|
126
|
-
const counts = { critical: 0, high: 0, medium: 0, low: 0 };
|
|
127
|
-
const ruleMap = { critical: [], high: [], medium: [], low: [] };
|
|
128
|
-
for (const f of findings) {
|
|
129
|
-
const sev = f.severity.toLowerCase();
|
|
130
|
-
if (sev in counts) {
|
|
131
|
-
counts[sev]++;
|
|
132
|
-
if (sev in ruleMap && !ruleMap[sev].includes(f.rule))
|
|
133
|
-
ruleMap[sev].push(f.rule);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
const totalBad = counts.critical + counts.high;
|
|
137
|
-
// Column widths (content only, borders + spaces added separately)
|
|
138
|
-
const C1 = 10; // severity label
|
|
139
|
-
const C2 = 7; // count
|
|
140
|
-
const C3 = 34; // rules
|
|
141
|
-
const h = "─";
|
|
142
|
-
const v = "│";
|
|
143
|
-
const tl = "┌";
|
|
144
|
-
const tr = "┐";
|
|
145
|
-
const bl = "└";
|
|
146
|
-
const br = "┘";
|
|
147
|
-
const lm = "├";
|
|
148
|
-
const rm = "┤";
|
|
149
|
-
const tm = "┬";
|
|
150
|
-
const bm = "┴";
|
|
151
|
-
const x = "┼";
|
|
152
|
-
const divider = (L, M, Ri) => `${L}${h.repeat(C1 + 2)}${M}${h.repeat(C2 + 2)}${M}${h.repeat(C3 + 2)}${Ri}`;
|
|
153
|
-
const row = (label, color, count, rules) => {
|
|
154
|
-
const rulesStr = rules.length > 0 ? rules.join(", ") : "—";
|
|
155
|
-
const col1 = label.padEnd(C1);
|
|
156
|
-
const col2 = String(count).padStart(3).padEnd(C2);
|
|
157
|
-
const col3 = (rulesStr.length > C3 ? rulesStr.slice(0, C3 - 1) + "…" : rulesStr).padEnd(C3);
|
|
158
|
-
const c = count > 0 ? color : DIM;
|
|
159
|
-
return `${v} ${c}${col1}${R} ${v} ${c}${col2}${R} ${v} ${c}${col3}${R} ${v}`;
|
|
160
|
-
};
|
|
161
|
-
const headerCol1 = "Severity".padEnd(C1);
|
|
162
|
-
const headerCol2 = "Count".padEnd(C2);
|
|
163
|
-
const headerCol3 = "Rules".padEnd(C3);
|
|
164
|
-
const header = `${v} ${WHT}${headerCol1}${R} ${v} ${WHT}${headerCol2}${R} ${v} ${WHT}${headerCol3}${R} ${v}`;
|
|
165
|
-
const shortPath = filePath.length > 44 ? "…" + filePath.slice(-43) : filePath;
|
|
166
|
-
const icon = totalBad > 0 ? `${YEL}⚠${R}` : `${GRN}✓${R}`;
|
|
167
|
-
return [
|
|
168
|
-
"",
|
|
169
|
-
`${icon} ${BOLD}Security Scan${R} ${DIM}· ${shortPath}${R}`,
|
|
170
|
-
"",
|
|
171
|
-
divider(tl, tm, tr),
|
|
172
|
-
header,
|
|
173
|
-
divider(lm, x, rm),
|
|
174
|
-
row("CRITICAL", RED, counts.critical, ruleMap.critical),
|
|
175
|
-
row("HIGH", YEL, counts.high, ruleMap.high),
|
|
176
|
-
row("MEDIUM", CYN, counts.medium, ruleMap.medium),
|
|
177
|
-
row("LOW", DIM, counts.low, ruleMap.low),
|
|
178
|
-
divider(bl, bm, br),
|
|
179
|
-
"",
|
|
180
|
-
].join("\n");
|
|
181
|
-
}
|
|
182
108
|
export class AgentLoop extends EventEmitter {
|
|
183
109
|
abortSignal;
|
|
184
110
|
llmClient;
|
|
@@ -206,10 +132,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
206
132
|
_lastInjectedTaskIndex = -1; // track when plan progress was last injected
|
|
207
133
|
changedFiles = [];
|
|
208
134
|
aborted = false;
|
|
209
|
-
|
|
210
|
-
slowInitDone = false;
|
|
211
|
-
slowInitPromise = null;
|
|
212
|
-
_slowInitContext = {};
|
|
135
|
+
initialized = false;
|
|
213
136
|
continuationEngine = null;
|
|
214
137
|
mcpClient = null;
|
|
215
138
|
mcpToolDefinitions = [];
|
|
@@ -240,8 +163,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
240
163
|
skillLearner = null;
|
|
241
164
|
repoGraph = null;
|
|
242
165
|
backgroundAgentManager = null;
|
|
243
|
-
sandbox = null;
|
|
244
|
-
securityScanner = null;
|
|
245
166
|
sessionPersistence = null;
|
|
246
167
|
sessionId = null;
|
|
247
168
|
enableToolPlanning;
|
|
@@ -249,9 +170,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
249
170
|
enableBackgroundAgents;
|
|
250
171
|
currentToolPlan = null;
|
|
251
172
|
executedToolNames = [];
|
|
252
|
-
currentTaskType = undefined;
|
|
253
|
-
strategySelector = new StrategySelector();
|
|
254
|
-
activeStrategies = [];
|
|
255
173
|
/** Context Budget: max 3 active skills at once */
|
|
256
174
|
activeSkillIds = [];
|
|
257
175
|
static MAX_ACTIVE_SKILLS = 3;
|
|
@@ -269,6 +187,22 @@ export class AgentLoop extends EventEmitter {
|
|
|
269
187
|
iterationTsFilesModified = [];
|
|
270
188
|
/** Task 3: Whether tsc was run in the previous iteration (skip cooldown) */
|
|
271
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;
|
|
272
206
|
/** PersonaManager — learns user communication style, injects persona into system prompt */
|
|
273
207
|
personaManager = null;
|
|
274
208
|
/** InMemoryVectorStore — RAG: semantic code context retrieval for relevant snippets */
|
|
@@ -297,6 +231,28 @@ export class AgentLoop extends EventEmitter {
|
|
|
297
231
|
reasoningAggregator = new ReasoningAggregator();
|
|
298
232
|
reasoningTree = new ReasoningTree();
|
|
299
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;
|
|
300
256
|
/**
|
|
301
257
|
* Restore AgentLoop state from persisted session (yuan resume)
|
|
302
258
|
*/
|
|
@@ -360,7 +316,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
360
316
|
this.abortSignal = options.abortSignal;
|
|
361
317
|
this.enableMemory = options.enableMemory !== false;
|
|
362
318
|
this.enablePlanning = options.enablePlanning !== false;
|
|
363
|
-
this.planningThreshold = options.planningThreshold ?? "
|
|
319
|
+
this.planningThreshold = options.planningThreshold ?? "moderate";
|
|
364
320
|
this.environment = options.environment;
|
|
365
321
|
this.enableSelfReflection = options.enableSelfReflection !== false;
|
|
366
322
|
this.enableDebate = options.enableDebate !== false;
|
|
@@ -368,6 +324,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
368
324
|
this.llmClient = new BYOKClient(options.config.byok);
|
|
369
325
|
// Governor 생성
|
|
370
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
|
+
});
|
|
371
332
|
// ContextManager 생성
|
|
372
333
|
this.contextManager = new ContextManager({
|
|
373
334
|
maxContextTokens: options.contextConfig?.maxContextTokens ??
|
|
@@ -407,18 +368,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
407
368
|
this.enableToolPlanning = options.enableToolPlanning !== false;
|
|
408
369
|
this.enableSkillLearning = options.enableSkillLearning !== false;
|
|
409
370
|
this.enableBackgroundAgents = options.enableBackgroundAgents === true;
|
|
410
|
-
// SandboxManager — preflight guard (auto-instantiated from projectPath)
|
|
411
|
-
const sandboxProject = options.sandboxConfig?.projectPath ?? options.config.loop.projectPath;
|
|
412
|
-
if (sandboxProject) {
|
|
413
|
-
this.sandbox = new SandboxManager({
|
|
414
|
-
projectPath: sandboxProject,
|
|
415
|
-
...(options.sandboxConfig ?? {}),
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
// SecurityScanner — postflight guard (opt-in)
|
|
419
|
-
if (options.enableSecurityScan && sandboxProject) {
|
|
420
|
-
this.securityScanner = new SecurityScanner({ projectPath: sandboxProject });
|
|
421
|
-
}
|
|
422
371
|
// 시스템 프롬프트 추가 (메모리 없이 기본 프롬프트로 시작, init()에서 갱신)
|
|
423
372
|
this.contextManager.addMessage({
|
|
424
373
|
role: "system",
|
|
@@ -426,45 +375,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
426
375
|
});
|
|
427
376
|
}
|
|
428
377
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
378
|
+
* Memory와 프로젝트 컨텍스트를 로드하여 시스템 프롬프트를 갱신.
|
|
379
|
+
* run() 호출 전에 한 번 호출하면 메모리가 자동으로 주입된다.
|
|
380
|
+
* 이미 초기화되었으면 스킵.
|
|
432
381
|
*/
|
|
433
|
-
async
|
|
434
|
-
if (this.
|
|
382
|
+
async init() {
|
|
383
|
+
if (this.initialized)
|
|
435
384
|
return;
|
|
436
|
-
this.
|
|
437
|
-
//
|
|
385
|
+
this.initialized = true;
|
|
386
|
+
// Task 1: Initialize ContextBudgetManager with the total token budget
|
|
438
387
|
this.contextBudgetManager = new ContextBudgetManager({
|
|
439
388
|
totalBudget: this.config.loop.totalTokenBudget,
|
|
440
389
|
enableSummarization: true,
|
|
441
|
-
summarizationThreshold: 0.60,
|
|
390
|
+
summarizationThreshold: 0.60, // trigger summarize() at 60% (before ContextCompressor at 70%)
|
|
442
391
|
});
|
|
443
392
|
const projectPath = this.config.loop.projectPath;
|
|
444
|
-
if (!projectPath)
|
|
445
|
-
return;
|
|
446
|
-
// Minimal system prompt — no project structure yet (slowInit provides it later)
|
|
447
|
-
const basicPrompt = buildSystemPrompt({
|
|
448
|
-
projectStructure: undefined,
|
|
449
|
-
yuanMdContent: undefined,
|
|
450
|
-
tools: [...this.config.loop.tools],
|
|
451
|
-
projectPath,
|
|
452
|
-
environment: this.environment,
|
|
453
|
-
model: this.config.byok.model,
|
|
454
|
-
});
|
|
455
|
-
this.contextManager.replaceSystemMessage(basicPrompt);
|
|
456
|
-
debugLog("[fastInit] done — LLM ready");
|
|
457
|
-
}
|
|
458
|
-
/**
|
|
459
|
-
* Slow init — background fire-and-forget.
|
|
460
|
-
* Loads memory, world state, MCP, etc. and enriches system prompt for next turn.
|
|
461
|
-
* Idempotent: subsequent calls are no-ops.
|
|
462
|
-
*/
|
|
463
|
-
async slowInit() {
|
|
464
|
-
if (this.slowInitDone)
|
|
465
|
-
return;
|
|
466
|
-
this.slowInitDone = true;
|
|
467
|
-
const projectPath = this.config.loop.projectPath;
|
|
468
393
|
if (!projectPath)
|
|
469
394
|
return;
|
|
470
395
|
// Session persistence init
|
|
@@ -474,47 +399,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
474
399
|
catch {
|
|
475
400
|
this.sessionPersistence = null;
|
|
476
401
|
}
|
|
477
|
-
// Session save (moved from run() into slowInit)
|
|
478
|
-
if (this.sessionPersistence) {
|
|
479
|
-
try {
|
|
480
|
-
if (!this.sessionId) {
|
|
481
|
-
this.sessionId = randomUUID();
|
|
482
|
-
}
|
|
483
|
-
const nowIso = new Date().toISOString();
|
|
484
|
-
const snapshot = {
|
|
485
|
-
id: this.sessionId,
|
|
486
|
-
createdAt: nowIso,
|
|
487
|
-
updatedAt: nowIso,
|
|
488
|
-
workDir: projectPath,
|
|
489
|
-
provider: String(this.config.byok.provider ?? "unknown"),
|
|
490
|
-
model: String(this.config.byok.model ?? "unknown"),
|
|
491
|
-
status: "running",
|
|
492
|
-
iteration: this.iterationCount,
|
|
493
|
-
tokenUsage: {
|
|
494
|
-
input: this.tokenUsage.input,
|
|
495
|
-
output: this.tokenUsage.output,
|
|
496
|
-
},
|
|
497
|
-
messageCount: this.contextManager.getMessages().length,
|
|
498
|
-
};
|
|
499
|
-
await this.sessionPersistence.save(this.sessionId, {
|
|
500
|
-
snapshot,
|
|
501
|
-
messages: this.contextManager.getMessages(),
|
|
502
|
-
plan: this.activePlan,
|
|
503
|
-
changedFiles: [],
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
catch {
|
|
507
|
-
// Session save failure is non-fatal
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
402
|
let yuanMdContent;
|
|
511
403
|
let projectStructure;
|
|
512
|
-
debugLog("[slowInit] start");
|
|
513
404
|
// Memory 로드
|
|
514
405
|
if (this.enableMemory) {
|
|
515
406
|
try {
|
|
516
407
|
// YUAN.md (raw markdown)
|
|
517
|
-
debugLog("[slowInit] yuanMemory.load start");
|
|
518
408
|
this.yuanMemory = new YuanMemory(projectPath);
|
|
519
409
|
const memData = await this.yuanMemory.load();
|
|
520
410
|
if (memData) {
|
|
@@ -544,20 +434,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
544
434
|
const indexer = new CodeIndexer({});
|
|
545
435
|
indexer.indexProject(projectPath, vectorStoreRef).catch(() => { });
|
|
546
436
|
}).catch(() => { });
|
|
547
|
-
// 프로젝트 구조 분석
|
|
548
|
-
|
|
549
|
-
const analyzeTimeout = new Promise((_, reject) => setTimeout(() => reject(new Error("analyzeProject timeout")), 15000));
|
|
550
|
-
projectStructure = await Promise.race([
|
|
551
|
-
this.yuanMemory.analyzeProject(),
|
|
552
|
-
analyzeTimeout,
|
|
553
|
-
]).catch((e) => {
|
|
554
|
-
debugLog(`[slowInit] analyzeProject skipped: ${String(e)}`);
|
|
555
|
-
return undefined;
|
|
556
|
-
});
|
|
557
|
-
debugLog("[slowInit] analyzeProject done");
|
|
558
|
-
// Store for enriched system prompt at end of slowInit
|
|
559
|
-
this._slowInitContext.yuanMdContent = yuanMdContent;
|
|
560
|
-
this._slowInitContext.projectStructure = projectStructure;
|
|
437
|
+
// 프로젝트 구조 분석
|
|
438
|
+
projectStructure = await this.yuanMemory.analyzeProject();
|
|
561
439
|
}
|
|
562
440
|
catch (memErr) {
|
|
563
441
|
// 메모리 로드 실패는 치명적이지 않음 — 경고만 출력
|
|
@@ -584,7 +462,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
584
462
|
catch {
|
|
585
463
|
// 정책 로드 실패 → 기본값 사용
|
|
586
464
|
}
|
|
587
|
-
debugLog("[slowInit] worldState.collect start");
|
|
588
465
|
// WorldState 수집 → system prompt에 주입
|
|
589
466
|
try {
|
|
590
467
|
const worldStateCollector = new WorldStateCollector({
|
|
@@ -593,11 +470,9 @@ export class AgentLoop extends EventEmitter {
|
|
|
593
470
|
skipTest: true,
|
|
594
471
|
});
|
|
595
472
|
this.worldState = await worldStateCollector.collect();
|
|
596
|
-
this._slowInitContext.worldState = this.worldState ?? undefined;
|
|
597
|
-
debugLog("[slowInit] worldState.collect done");
|
|
598
473
|
}
|
|
599
|
-
catch
|
|
600
|
-
|
|
474
|
+
catch {
|
|
475
|
+
// WorldState 수집 실패는 치명적이지 않음
|
|
601
476
|
}
|
|
602
477
|
// Initialize World Model
|
|
603
478
|
if (this.worldState && projectPath) {
|
|
@@ -625,13 +500,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
625
500
|
catch {
|
|
626
501
|
// Not a git repo or git unavailable — FailureRecovery will use file-level rollback only
|
|
627
502
|
}
|
|
628
|
-
debugLog("[slowInit] continuationEngine + MCP start");
|
|
629
503
|
// ImpactAnalyzer 생성
|
|
630
504
|
this.impactAnalyzer = new ImpactAnalyzer({ projectPath });
|
|
631
505
|
// ContinuationEngine 생성
|
|
632
506
|
this.continuationEngine = new ContinuationEngine({ projectPath });
|
|
633
507
|
// 이전 세션 체크포인트 복원
|
|
634
|
-
debugLog("[slowInit] checkpoint restore start");
|
|
635
508
|
try {
|
|
636
509
|
const latestCheckpoint = await this.continuationEngine.findLatestCheckpoint();
|
|
637
510
|
if (latestCheckpoint) {
|
|
@@ -647,9 +520,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
647
520
|
catch {
|
|
648
521
|
// 체크포인트 복원 실패는 치명적이지 않음
|
|
649
522
|
}
|
|
650
|
-
debugLog("[slowInit] checkpoint restore done");
|
|
651
523
|
// MCP 클라이언트 연결
|
|
652
|
-
debugLog("[slowInit] MCP connect start");
|
|
653
524
|
if (this.mcpServerConfigs.length > 0) {
|
|
654
525
|
try {
|
|
655
526
|
this.mcpClient = new MCPClient({
|
|
@@ -664,7 +535,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
664
535
|
this.mcpToolDefinitions = [];
|
|
665
536
|
}
|
|
666
537
|
}
|
|
667
|
-
debugLog("[slowInit] MCP connect done");
|
|
668
538
|
// ReflexionEngine 생성
|
|
669
539
|
if (projectPath) {
|
|
670
540
|
this.reflexionEngine = new ReflexionEngine({ projectPath });
|
|
@@ -703,6 +573,22 @@ export class AgentLoop extends EventEmitter {
|
|
|
703
573
|
totalTokenBudget: Math.floor(this.config.loop.totalTokenBudget * 0.3),
|
|
704
574
|
});
|
|
705
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);
|
|
706
592
|
// MemoryManager의 관련 학습/경고를 추가 컨텍스트로 주입
|
|
707
593
|
if (this.memoryManager) {
|
|
708
594
|
const memory = this.memoryManager.getMemory();
|
|
@@ -717,7 +603,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
717
603
|
}
|
|
718
604
|
}
|
|
719
605
|
// SkillLearner 초기화 (경험에서 학습된 스킬 자동 로드)
|
|
720
|
-
debugLog("[slowInit] skillLearner init start");
|
|
721
606
|
if (this.enableSkillLearning && projectPath) {
|
|
722
607
|
try {
|
|
723
608
|
this.skillLearner = new SkillLearner(projectPath);
|
|
@@ -727,10 +612,67 @@ export class AgentLoop extends EventEmitter {
|
|
|
727
612
|
this.skillLearner = null;
|
|
728
613
|
}
|
|
729
614
|
}
|
|
730
|
-
|
|
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 */ }
|
|
731
674
|
// HierarchicalPlanner 생성
|
|
732
675
|
this.planner = new HierarchicalPlanner({ projectPath });
|
|
733
|
-
debugLog("[slowInit] planner created");
|
|
734
676
|
if (this.skillLearner) {
|
|
735
677
|
this.planner.setSkillLearner(this.skillLearner);
|
|
736
678
|
}
|
|
@@ -880,44 +822,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
880
822
|
this.continuousReflection.on("reflection:context_overflow", () => {
|
|
881
823
|
void this.handleSoftContextOverflow();
|
|
882
824
|
});
|
|
883
|
-
// Enrich system prompt now that we have full context (projectStructure, yuanMd, MCP tools, worldState)
|
|
884
|
-
const enrichedPrompt = buildSystemPrompt({
|
|
885
|
-
projectStructure: this._slowInitContext.projectStructure,
|
|
886
|
-
yuanMdContent: this._slowInitContext.yuanMdContent,
|
|
887
|
-
tools: [...this.config.loop.tools, ...this.mcpToolDefinitions],
|
|
888
|
-
projectPath,
|
|
889
|
-
environment: this.environment,
|
|
890
|
-
model: this.config.byok.model,
|
|
891
|
-
// NOTE: currentTaskType is undefined on turn 1 (slowInit fires before classification).
|
|
892
|
-
// The task-type hint still reaches the LLM on turn 1 via the separate system message injection.
|
|
893
|
-
// From turn 2+ this field carries the previous turn's classification.
|
|
894
|
-
currentTaskType: this.currentTaskType,
|
|
895
|
-
experienceHints: this.buildExperienceHints(),
|
|
896
|
-
activeStrategies: this.activeStrategies.length > 0 ? this.activeStrategies : undefined,
|
|
897
|
-
});
|
|
898
|
-
let worldStateSection = "";
|
|
899
|
-
if (this.worldState) {
|
|
900
|
-
const collector = new WorldStateCollector({ projectPath, maxRecentCommits: 10, skipTest: true });
|
|
901
|
-
worldStateSection = "\n\n" + collector.formatForPrompt(this.worldState);
|
|
902
|
-
}
|
|
903
|
-
this.contextManager.replaceSystemMessage(enrichedPrompt + worldStateSection);
|
|
904
|
-
debugLog("[slowInit] DONE — context enriched for next turn");
|
|
905
|
-
}
|
|
906
|
-
/**
|
|
907
|
-
* MemoryManager.learnings에서 경험 힌트를 추출한다.
|
|
908
|
-
* confidence >= 0.4인 학습만, 최대 8개, "[category] content" 형식으로 반환.
|
|
909
|
-
*/
|
|
910
|
-
buildExperienceHints() {
|
|
911
|
-
if (!this.memoryManager)
|
|
912
|
-
return [];
|
|
913
|
-
const memory = this.memoryManager.getMemory();
|
|
914
|
-
if (!memory?.learnings?.length)
|
|
915
|
-
return [];
|
|
916
|
-
return memory.learnings
|
|
917
|
-
.filter((l) => l.confidence >= 0.4)
|
|
918
|
-
.sort((a, b) => b.confidence - a.confidence)
|
|
919
|
-
.slice(0, 8)
|
|
920
|
-
.map((l) => `[${l.category}] ${l.content}`);
|
|
921
825
|
}
|
|
922
826
|
/**
|
|
923
827
|
* MemoryManager의 학습/실패 기록을 시스템 메시지로 변환.
|
|
@@ -986,21 +890,69 @@ export class AgentLoop extends EventEmitter {
|
|
|
986
890
|
this.costOptimizer.reset();
|
|
987
891
|
this.tokenBudgetManager.reset();
|
|
988
892
|
const runStartTime = Date.now();
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
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) {
|
|
915
|
+
try {
|
|
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
|
+
}
|
|
926
|
+
}
|
|
927
|
+
catch { /* non-fatal */ }
|
|
928
|
+
}
|
|
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,
|
|
1001
954
|
});
|
|
1002
955
|
}
|
|
1003
|
-
debugLog("[run] starting executeLoop");
|
|
1004
956
|
// 사용자 입력 검증 (prompt injection 방어)
|
|
1005
957
|
const inputValidation = this.promptDefense.validateUserInput(userMessage);
|
|
1006
958
|
if (inputValidation.injectionDetected && (inputValidation.severity === "critical" || inputValidation.severity === "high")) {
|
|
@@ -1088,12 +1040,6 @@ export class AgentLoop extends EventEmitter {
|
|
|
1088
1040
|
}
|
|
1089
1041
|
// Task 분류 → 시스템 프롬프트에 tool sequence hint 주입
|
|
1090
1042
|
const classification = this.taskClassifier.classify(userMessage);
|
|
1091
|
-
// Store classification type for system prompt enrichment
|
|
1092
|
-
this.currentTaskType = classification.confidence >= CLASSIFICATION_CONFIDENCE_THRESHOLD
|
|
1093
|
-
? classification.type
|
|
1094
|
-
: undefined;
|
|
1095
|
-
// Select active strategies based on task type and execution mode
|
|
1096
|
-
this.activeStrategies = this.strategySelector.select(this.currentTaskType, undefined);
|
|
1097
1043
|
if (classification.confidence >= CLASSIFICATION_CONFIDENCE_THRESHOLD) {
|
|
1098
1044
|
const classificationHint = this.taskClassifier.formatForSystemPrompt(classification);
|
|
1099
1045
|
this.contextManager.addMessage({
|
|
@@ -1201,6 +1147,92 @@ export class AgentLoop extends EventEmitter {
|
|
|
1201
1147
|
}
|
|
1202
1148
|
// 실행 완료 후 메모리 자동 업데이트
|
|
1203
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 */ }
|
|
1204
1236
|
// SkillLearner: 성공적 에러 해결 시 새로운 스킬 학습
|
|
1205
1237
|
if (this.skillLearner && result.reason === "GOAL_ACHIEVED") {
|
|
1206
1238
|
try {
|
|
@@ -1523,7 +1555,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
1523
1555
|
if ((complexityOrder[complexity] ?? 0) < effectiveThreshold) {
|
|
1524
1556
|
return;
|
|
1525
1557
|
}
|
|
1526
|
-
this.
|
|
1558
|
+
this.emitSubagent("planner", "start", `task complexity ${complexity}. creating execution plan`);
|
|
1527
1559
|
try {
|
|
1528
1560
|
const plan = await this.planner.createHierarchicalPlan(userMessage, this.llmClient);
|
|
1529
1561
|
this.activePlan = plan;
|
|
@@ -1549,7 +1581,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
1549
1581
|
role: "system",
|
|
1550
1582
|
content: planContext,
|
|
1551
1583
|
});
|
|
1552
|
-
this.
|
|
1584
|
+
this.emitSubagent("planner", "done", `plan created: ${plan.tactical.length} tasks, ${plan.totalEstimatedIterations} estimated iterations, risk ${plan.strategic.riskAssessment.level}`);
|
|
1553
1585
|
}
|
|
1554
1586
|
catch (err) {
|
|
1555
1587
|
this.emitEvent({
|
|
@@ -1707,8 +1739,29 @@ export class AgentLoop extends EventEmitter {
|
|
|
1707
1739
|
* (ambitious short requests that keywords miss across any language).
|
|
1708
1740
|
*/
|
|
1709
1741
|
async detectComplexity(message) {
|
|
1710
|
-
|
|
1711
|
-
|
|
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;
|
|
1712
1765
|
}
|
|
1713
1766
|
/**
|
|
1714
1767
|
* HierarchicalPlan을 LLM이 따라갈 수 있는 컨텍스트 메시지로 포맷.
|
|
@@ -1846,8 +1899,17 @@ export class AgentLoop extends EventEmitter {
|
|
|
1846
1899
|
iteration++;
|
|
1847
1900
|
this.iterationCount = iteration;
|
|
1848
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);
|
|
1849
1912
|
this.emitReasoning(`iteration ${iteration}: preparing context`);
|
|
1850
|
-
this.iterationSystemMsgCount = 0; // Reset per-iteration (prevents accumulation across iterations)
|
|
1851
1913
|
// Proactive replanning check (every 10 iterations when plan is active)
|
|
1852
1914
|
if (this.replanningEngine &&
|
|
1853
1915
|
this.activePlan &&
|
|
@@ -1906,10 +1968,11 @@ export class AgentLoop extends EventEmitter {
|
|
|
1906
1968
|
// Soft context rollover:
|
|
1907
1969
|
// checkpoint first, then let ContextManager compact instead of aborting/throwing.
|
|
1908
1970
|
const contextUsageRatio = this.contextManager.getUsageRatio();
|
|
1909
|
-
// Task 1: ContextBudgetManager LLM summarization
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
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") {
|
|
1913
1976
|
this._contextSummarizationDone = true; // run at most once per agent turn
|
|
1914
1977
|
// Non-blocking: fire-and-forget so the main iteration is not stalled
|
|
1915
1978
|
this.contextBudgetManager.importMessages(this.contextManager.getMessages());
|
|
@@ -2063,12 +2126,29 @@ export class AgentLoop extends EventEmitter {
|
|
|
2063
2126
|
if (response.toolCalls.length === 0) {
|
|
2064
2127
|
const content = response.content ?? "";
|
|
2065
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
|
+
}
|
|
2066
2143
|
const finalImpactSummary = await this.buildFinalImpactSummary();
|
|
2067
2144
|
if (finalImpactSummary) {
|
|
2068
2145
|
finalSummary = `${finalSummary}\n\n${finalImpactSummary}`;
|
|
2069
2146
|
}
|
|
2070
|
-
// Level 2: Deep verification before declaring completion
|
|
2071
|
-
|
|
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") {
|
|
2072
2152
|
try {
|
|
2073
2153
|
const changedFilesMap = this.buildChangedFilesMap();
|
|
2074
2154
|
const verifyFn = async (prompt) => {
|
|
@@ -2097,10 +2177,12 @@ export class AgentLoop extends EventEmitter {
|
|
|
2097
2177
|
this.emitSubagent("verifier", "done", `deep verification failed, score ${deepResult.overallScore}. continuing to address issues`);
|
|
2098
2178
|
continue; // Don't return GOAL_ACHIEVED, continue the loop
|
|
2099
2179
|
}
|
|
2100
|
-
// Level 3: Multi-agent debate
|
|
2180
|
+
// Level 3: Multi-agent debate — Governor gated (default OFF)
|
|
2181
|
+
const debateMode = this.overheadGovernor.shouldRunDebate(this.buildTriggerContext());
|
|
2101
2182
|
if (this.debateOrchestrator &&
|
|
2102
2183
|
["complex", "massive"].includes(this.currentComplexity) &&
|
|
2103
|
-
deepResult.verdict !== "pass"
|
|
2184
|
+
deepResult.verdict !== "pass" &&
|
|
2185
|
+
debateMode === "BLOCKING") {
|
|
2104
2186
|
try {
|
|
2105
2187
|
this.emitSubagent("reviewer", "start", `starting debate for ${this.currentComplexity} task verification`);
|
|
2106
2188
|
const debateContext = [
|
|
@@ -2267,7 +2349,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
2267
2349
|
}
|
|
2268
2350
|
// Task 2: QAPipeline — run "quick" (structural only) after any WRITE tool call this iteration
|
|
2269
2351
|
const projectPath = this.config.loop.projectPath;
|
|
2270
|
-
|
|
2352
|
+
const qaMode = this.overheadGovernor.shouldRunQaPipeline(this.buildTriggerContext());
|
|
2353
|
+
if (this.iterationWriteToolPaths.length > 0 && projectPath && qaMode !== "OFF") {
|
|
2271
2354
|
try {
|
|
2272
2355
|
const qaPipeline = new QAPipeline({
|
|
2273
2356
|
projectPath,
|
|
@@ -2281,21 +2364,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
2281
2364
|
});
|
|
2282
2365
|
const qaResult = await qaPipeline.run(this.iterationWriteToolPaths);
|
|
2283
2366
|
this.lastQAResult = qaResult;
|
|
2284
|
-
// Surface QA issues as a system message so LLM sees them next iteration
|
|
2285
2367
|
const failedChecks = qaResult.stages
|
|
2286
2368
|
.flatMap((s) => s.checks)
|
|
2287
2369
|
.filter((c) => c.status === "fail" || c.status === "warn");
|
|
2288
2370
|
const qaIssues = failedChecks
|
|
2289
2371
|
.slice(0, 10)
|
|
2290
2372
|
.map((c) => `[${c.severity}] ${c.name}: ${c.message}`);
|
|
2291
|
-
//
|
|
2373
|
+
// Always emit event for TUI display (SHADOW + BLOCKING)
|
|
2292
2374
|
this.emitEvent({
|
|
2293
2375
|
kind: "agent:qa_result",
|
|
2294
2376
|
stage: "quick",
|
|
2295
2377
|
passed: failedChecks.length === 0,
|
|
2296
2378
|
issues: qaIssues,
|
|
2297
2379
|
});
|
|
2298
|
-
|
|
2380
|
+
// Only inject into LLM context in BLOCKING mode (SHADOW = observe only)
|
|
2381
|
+
if (qaMode === "BLOCKING" && failedChecks.length > 0 && this.iterationSystemMsgCount < 5) {
|
|
2299
2382
|
const checkSummary = failedChecks
|
|
2300
2383
|
.slice(0, 5)
|
|
2301
2384
|
.map((c) => ` - [${c.severity}] ${c.name}: ${c.message}`)
|
|
@@ -2313,13 +2396,31 @@ export class AgentLoop extends EventEmitter {
|
|
|
2313
2396
|
}
|
|
2314
2397
|
// Reset per-iteration write tool tracking
|
|
2315
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
|
+
}
|
|
2316
2416
|
// Task 3: Auto-run tsc --noEmit after 2+ TS files modified in this iteration
|
|
2317
2417
|
// Skip if tsc was already run in the previous iteration (cooldown)
|
|
2318
2418
|
const tscFilesThisIteration = [...this.iterationTsFilesModified];
|
|
2319
2419
|
this.iterationTsFilesModified = []; // reset for next iteration
|
|
2320
2420
|
const tscRanPrev = this.tscRanLastIteration;
|
|
2321
2421
|
this.tscRanLastIteration = false; // will set to true below if we run it
|
|
2322
|
-
|
|
2422
|
+
const tscMode = this.overheadGovernor.shouldRunAutoTsc(this.buildTriggerContext());
|
|
2423
|
+
if (tscFilesThisIteration.length >= 2 && projectPath && !tscRanPrev && tscMode !== "OFF") {
|
|
2323
2424
|
try {
|
|
2324
2425
|
const tscResult = await this.toolExecutor.execute({
|
|
2325
2426
|
id: `auto-tsc-${Date.now()}`,
|
|
@@ -2331,27 +2432,28 @@ export class AgentLoop extends EventEmitter {
|
|
|
2331
2432
|
}),
|
|
2332
2433
|
});
|
|
2333
2434
|
this.tscRanLastIteration = true;
|
|
2334
|
-
// Inject TypeScript errors into context so LLM sees them next iteration
|
|
2335
2435
|
if (tscResult.success && tscResult.output && tscResult.output.trim().length > 0) {
|
|
2336
2436
|
const tscOutput = tscResult.output.trim();
|
|
2337
|
-
// Only inject if there are actual TS errors (output is non-empty)
|
|
2338
2437
|
const hasErrors = tscOutput.includes(": error TS") || tscOutput.includes("error TS");
|
|
2339
|
-
if (hasErrors
|
|
2340
|
-
//
|
|
2341
|
-
const truncated = tscOutput.length > 2000
|
|
2342
|
-
? tscOutput.slice(0, 2000) + "\n[...tsc output truncated]"
|
|
2343
|
-
: tscOutput;
|
|
2344
|
-
this.contextManager.addMessage({
|
|
2345
|
-
role: "system",
|
|
2346
|
-
content: `[Auto-TSC] TypeScript errors detected after modifying ${tscFilesThisIteration.length} files:\n\`\`\`\n${truncated}\n\`\`\`\nPlease fix these type errors.`,
|
|
2347
|
-
});
|
|
2348
|
-
this.iterationSystemMsgCount++;
|
|
2438
|
+
if (hasErrors) {
|
|
2439
|
+
// Always emit thinking event (SHADOW + BLOCKING)
|
|
2349
2440
|
this.emitEvent({
|
|
2350
2441
|
kind: "agent:thinking",
|
|
2351
2442
|
content: `Auto-TSC: TypeScript errors found after editing ${tscFilesThisIteration.join(", ")}.`,
|
|
2352
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
|
+
}
|
|
2353
2455
|
}
|
|
2354
|
-
else
|
|
2456
|
+
else {
|
|
2355
2457
|
this.emitEvent({
|
|
2356
2458
|
kind: "agent:thinking",
|
|
2357
2459
|
content: `Auto-TSC: No type errors after editing ${tscFilesThisIteration.length} file(s).`,
|
|
@@ -2418,8 +2520,9 @@ export class AgentLoop extends EventEmitter {
|
|
|
2418
2520
|
// Reflection failure is non-fatal
|
|
2419
2521
|
}
|
|
2420
2522
|
}
|
|
2421
|
-
// Level 1: Quick verification
|
|
2422
|
-
|
|
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") {
|
|
2423
2526
|
try {
|
|
2424
2527
|
this.emitSubagent("verifier", "start", "running quick verification");
|
|
2425
2528
|
const changedFilesMap = this.buildChangedFilesMap();
|
|
@@ -2457,6 +2560,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
2457
2560
|
const errorSummary = errorResults
|
|
2458
2561
|
.map((r) => `${r.name}: ${r.output}`)
|
|
2459
2562
|
.join("\n");
|
|
2563
|
+
// Track repeated error signature for Governor
|
|
2564
|
+
this.trackErrorSignature(errorSummary);
|
|
2460
2565
|
// FailureRecovery: 근본 원인 분석 + 전략 선택
|
|
2461
2566
|
const rootCause = this.failureRecovery.analyzeRootCause(errorSummary, errorResults[0]?.name);
|
|
2462
2567
|
const decision = this.failureRecovery.selectStrategy(rootCause, {
|
|
@@ -2501,7 +2606,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
2501
2606
|
});
|
|
2502
2607
|
// SelfDebugLoop: merge debug strategy into recovery message (not separate system msg)
|
|
2503
2608
|
let debugSuffix = "";
|
|
2504
|
-
|
|
2609
|
+
const llmFixerMode = this.overheadGovernor.shouldRunLlmFixer(this.buildTriggerContext());
|
|
2610
|
+
if (iteration >= 3 && llmFixerMode === "BLOCKING") {
|
|
2505
2611
|
const rootCauseAnalysis = this.selfDebugLoop.analyzeError(errorSummary);
|
|
2506
2612
|
if (rootCauseAnalysis.confidence >= 0.5) {
|
|
2507
2613
|
const debugStrategy = this.selfDebugLoop.selectStrategy(iteration - 2, []);
|
|
@@ -2519,6 +2625,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
2519
2625
|
debugSuffix = `\n\n[SelfDebug L${Math.min(iteration - 2, 5)}] Strategy: ${debugStrategy}\n${debugPrompt}`;
|
|
2520
2626
|
// Bug 2 fix: wire a real llmFixer so selfDebugLoop.debug() can call LLM
|
|
2521
2627
|
if (debugStrategy !== "escalate") {
|
|
2628
|
+
this.llmFixerRunCount++;
|
|
2522
2629
|
this.selfDebugLoop.debug({
|
|
2523
2630
|
testCommand: testCmd,
|
|
2524
2631
|
errorOutput: errorSummary,
|
|
@@ -2893,35 +3000,32 @@ export class AgentLoop extends EventEmitter {
|
|
|
2893
3000
|
}
|
|
2894
3001
|
// MCP 도구 호출 확인
|
|
2895
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
|
+
});
|
|
2896
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 };
|
|
2897
3014
|
this.emitEvent({
|
|
2898
3015
|
kind: "agent:tool_result",
|
|
2899
3016
|
tool: toolCall.name,
|
|
2900
|
-
output:
|
|
2901
|
-
?
|
|
2902
|
-
:
|
|
2903
|
-
durationMs:
|
|
3017
|
+
output: finalResult.output.length > 200
|
|
3018
|
+
? finalResult.output.slice(0, 200) + "..."
|
|
3019
|
+
: finalResult.output,
|
|
3020
|
+
durationMs: finalResult.durationMs,
|
|
2904
3021
|
});
|
|
2905
3022
|
this.emitEvent({ kind: "agent:reasoning_delta", text: `tool finished: ${toolCall.name}` });
|
|
2906
|
-
return { result:
|
|
3023
|
+
return { result: finalResult, deferredFixPrompt: null };
|
|
2907
3024
|
}
|
|
2908
3025
|
// 도구 실행
|
|
2909
3026
|
const startTime = Date.now();
|
|
2910
3027
|
const toolAbort = new AbortController();
|
|
2911
3028
|
this.interruptManager.registerToolAbort(toolAbort);
|
|
2912
|
-
// Sandbox preflight — block disallowed tool calls before execution
|
|
2913
|
-
if (this.sandbox) {
|
|
2914
|
-
const sandboxCheck = this.sandbox.validateToolCall(toolCall.name, args);
|
|
2915
|
-
if (!sandboxCheck.allowed) {
|
|
2916
|
-
this.interruptManager.clearToolAbort();
|
|
2917
|
-
const blocked = `[SANDBOX BLOCKED] ${sandboxCheck.violations.join("; ")}`;
|
|
2918
|
-
this.emitEvent({ kind: "agent:tool_result", tool: toolCall.name, output: blocked, durationMs: 0 });
|
|
2919
|
-
return {
|
|
2920
|
-
result: { tool_call_id: toolCall.id, name: toolCall.name, output: blocked, success: false, durationMs: 0 },
|
|
2921
|
-
deferredFixPrompt: null,
|
|
2922
|
-
};
|
|
2923
|
-
}
|
|
2924
|
-
}
|
|
2925
3029
|
if (["file_write", "file_edit"].includes(toolCall.name)) {
|
|
2926
3030
|
const candidatePath = args.path ??
|
|
2927
3031
|
args.file;
|
|
@@ -2969,29 +3073,21 @@ export class AgentLoop extends EventEmitter {
|
|
|
2969
3073
|
});
|
|
2970
3074
|
this.emitReasoning(`success: ${toolCall.name}`);
|
|
2971
3075
|
this.reasoningTree.add("tool", `success: ${toolCall.name}`);
|
|
2972
|
-
// Security Scanner postflight — scan written files for vulnerabilities (opt-in)
|
|
2973
|
-
if (this.securityScanner && result.success && ["file_write", "file_edit"].includes(toolCall.name)) {
|
|
2974
|
-
const writtenPath = args.path ?? args.file;
|
|
2975
|
-
if (typeof writtenPath === "string") {
|
|
2976
|
-
this.securityScanner.scan([writtenPath]).then((scanResult) => {
|
|
2977
|
-
// Always render table — shows ✓ when clean, ⚠ when findings exist
|
|
2978
|
-
const hasBad = scanResult.findings.some((f) => f.severity === "critical" || f.severity === "high");
|
|
2979
|
-
if (hasBad || scanResult.findings.length > 0) {
|
|
2980
|
-
this.emitEvent({
|
|
2981
|
-
kind: "agent:text_delta",
|
|
2982
|
-
text: formatSecurityTable(scanResult.findings, writtenPath),
|
|
2983
|
-
});
|
|
2984
|
-
}
|
|
2985
|
-
}).catch(() => { });
|
|
2986
|
-
}
|
|
2987
|
-
}
|
|
2988
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
|
+
}
|
|
2989
3081
|
const filePath = args.path ??
|
|
2990
3082
|
args.file ??
|
|
2991
3083
|
"unknown";
|
|
2992
3084
|
const filePathStr = String(filePath);
|
|
2993
3085
|
if (!this.changedFiles.includes(filePathStr)) {
|
|
2994
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
|
+
}
|
|
2995
3091
|
}
|
|
2996
3092
|
// Task 2: track write tool paths per-iteration for QA triggering
|
|
2997
3093
|
if (!this.iterationWriteToolPaths.includes(filePathStr)) {
|
|
@@ -3002,6 +3098,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
3002
3098
|
this.iterationTsFilesModified.push(filePathStr);
|
|
3003
3099
|
}
|
|
3004
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(() => { });
|
|
3005
3103
|
// Update world state after file modification
|
|
3006
3104
|
if (this.config.loop.projectPath) {
|
|
3007
3105
|
const wsProjectPath = this.config.loop.projectPath;
|
|
@@ -3709,6 +3807,44 @@ export class AgentLoop extends EventEmitter {
|
|
|
3709
3807
|
const args = this.parseToolArgs(toolCall.arguments);
|
|
3710
3808
|
return this.mcpClient.callToolAsYuan(toolCall.name, args, toolCall.id);
|
|
3711
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
|
+
}
|
|
3712
3848
|
/**
|
|
3713
3849
|
* ContinuousReflection overflow signal을 soft rollover로 처리한다.
|
|
3714
3850
|
* 절대 abort하지 않고, 체크포인트 저장 후 다음 iteration에서
|
|
@@ -3738,6 +3874,7 @@ export class AgentLoop extends EventEmitter {
|
|
|
3738
3874
|
// cleanup failure ignored
|
|
3739
3875
|
}
|
|
3740
3876
|
}
|
|
3877
|
+
this.traceRecorder?.stop();
|
|
3741
3878
|
}
|
|
3742
3879
|
// ─── Helpers ───
|
|
3743
3880
|
/**
|
|
@@ -3758,6 +3895,8 @@ export class AgentLoop extends EventEmitter {
|
|
|
3758
3895
|
}
|
|
3759
3896
|
emitEvent(event) {
|
|
3760
3897
|
this.emit("event", event);
|
|
3898
|
+
// Trace recording — fire-and-forget, never blocks
|
|
3899
|
+
this.traceRecorder?.record(event);
|
|
3761
3900
|
}
|
|
3762
3901
|
emitReasoning(content) {
|
|
3763
3902
|
this.reasoningTree.add("reasoning", content);
|
|
@@ -3770,6 +3909,13 @@ export class AgentLoop extends EventEmitter {
|
|
|
3770
3909
|
emitSubagent(name, phase, content) {
|
|
3771
3910
|
this.reasoningTree.add(name, `[${phase}] ${content}`);
|
|
3772
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
|
+
}
|
|
3773
3919
|
}
|
|
3774
3920
|
handleFatalError(err) {
|
|
3775
3921
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -3783,5 +3929,221 @@ export class AgentLoop extends EventEmitter {
|
|
|
3783
3929
|
});
|
|
3784
3930
|
return { reason: "ERROR", error: message };
|
|
3785
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;
|
|
3786
4148
|
}
|
|
3787
4149
|
//# sourceMappingURL=agent-loop.js.map
|