@yuaone/core 0.4.2 → 0.4.3

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.
Files changed (186) hide show
  1. package/dist/agent-logger.d.ts +1 -1
  2. package/dist/agent-logger.d.ts.map +1 -1
  3. package/dist/agent-logger.js +17 -15
  4. package/dist/agent-logger.js.map +1 -1
  5. package/dist/agent-loop.d.ts +31 -0
  6. package/dist/agent-loop.d.ts.map +1 -1
  7. package/dist/agent-loop.js +514 -98
  8. package/dist/agent-loop.js.map +1 -1
  9. package/dist/agent-modes.d.ts.map +1 -1
  10. package/dist/agent-modes.js +5 -0
  11. package/dist/agent-modes.js.map +1 -1
  12. package/dist/async-completion-queue.d.ts +2 -0
  13. package/dist/async-completion-queue.d.ts.map +1 -1
  14. package/dist/async-completion-queue.js +14 -0
  15. package/dist/async-completion-queue.js.map +1 -1
  16. package/dist/auto-fix.d.ts.map +1 -1
  17. package/dist/auto-fix.js +12 -1
  18. package/dist/auto-fix.js.map +1 -1
  19. package/dist/benchmark-runner.d.ts.map +1 -1
  20. package/dist/benchmark-runner.js +5 -1
  21. package/dist/benchmark-runner.js.map +1 -1
  22. package/dist/constants.d.ts +12 -0
  23. package/dist/constants.d.ts.map +1 -1
  24. package/dist/constants.js +14 -0
  25. package/dist/constants.js.map +1 -1
  26. package/dist/context-manager.d.ts +25 -0
  27. package/dist/context-manager.d.ts.map +1 -1
  28. package/dist/context-manager.js +132 -5
  29. package/dist/context-manager.js.map +1 -1
  30. package/dist/continuation-engine.d.ts.map +1 -1
  31. package/dist/continuation-engine.js +8 -7
  32. package/dist/continuation-engine.js.map +1 -1
  33. package/dist/continuous-reflection.d.ts.map +1 -1
  34. package/dist/continuous-reflection.js +22 -12
  35. package/dist/continuous-reflection.js.map +1 -1
  36. package/dist/cost-optimizer.js +1 -1
  37. package/dist/cost-optimizer.js.map +1 -1
  38. package/dist/cross-file-refactor.d.ts.map +1 -1
  39. package/dist/cross-file-refactor.js +7 -2
  40. package/dist/cross-file-refactor.js.map +1 -1
  41. package/dist/dag-orchestrator.d.ts +10 -1
  42. package/dist/dag-orchestrator.d.ts.map +1 -1
  43. package/dist/dag-orchestrator.js +101 -6
  44. package/dist/dag-orchestrator.js.map +1 -1
  45. package/dist/debate-orchestrator.d.ts +1 -0
  46. package/dist/debate-orchestrator.d.ts.map +1 -1
  47. package/dist/debate-orchestrator.js +27 -15
  48. package/dist/debate-orchestrator.js.map +1 -1
  49. package/dist/dependency-analyzer.d.ts.map +1 -1
  50. package/dist/dependency-analyzer.js +19 -1
  51. package/dist/dependency-analyzer.js.map +1 -1
  52. package/dist/dynamic-role-generator.d.ts.map +1 -1
  53. package/dist/dynamic-role-generator.js +6 -3
  54. package/dist/dynamic-role-generator.js.map +1 -1
  55. package/dist/errors.js +1 -1
  56. package/dist/errors.js.map +1 -1
  57. package/dist/event-bus.d.ts.map +1 -1
  58. package/dist/event-bus.js +4 -3
  59. package/dist/event-bus.js.map +1 -1
  60. package/dist/execution-engine.d.ts +39 -1
  61. package/dist/execution-engine.d.ts.map +1 -1
  62. package/dist/execution-engine.js +453 -83
  63. package/dist/execution-engine.js.map +1 -1
  64. package/dist/failure-recovery.d.ts.map +1 -1
  65. package/dist/failure-recovery.js +14 -3
  66. package/dist/failure-recovery.js.map +1 -1
  67. package/dist/git-intelligence.d.ts.map +1 -1
  68. package/dist/git-intelligence.js +16 -11
  69. package/dist/git-intelligence.js.map +1 -1
  70. package/dist/governor.d.ts +8 -0
  71. package/dist/governor.d.ts.map +1 -1
  72. package/dist/governor.js +19 -1
  73. package/dist/governor.js.map +1 -1
  74. package/dist/hierarchical-planner.d.ts +3 -0
  75. package/dist/hierarchical-planner.d.ts.map +1 -1
  76. package/dist/hierarchical-planner.js +32 -2
  77. package/dist/hierarchical-planner.js.map +1 -1
  78. package/dist/impact-analyzer.d.ts +27 -0
  79. package/dist/impact-analyzer.d.ts.map +1 -1
  80. package/dist/impact-analyzer.js +415 -53
  81. package/dist/impact-analyzer.js.map +1 -1
  82. package/dist/intent-inference.d.ts.map +1 -1
  83. package/dist/intent-inference.js +20 -24
  84. package/dist/intent-inference.js.map +1 -1
  85. package/dist/kernel.d.ts.map +1 -1
  86. package/dist/kernel.js +5 -3
  87. package/dist/kernel.js.map +1 -1
  88. package/dist/language-detector.d.ts +19 -0
  89. package/dist/language-detector.d.ts.map +1 -0
  90. package/dist/language-detector.js +482 -0
  91. package/dist/language-detector.js.map +1 -0
  92. package/dist/language-support.d.ts.map +1 -1
  93. package/dist/language-support.js +5 -9
  94. package/dist/language-support.js.map +1 -1
  95. package/dist/llm-client.d.ts +21 -8
  96. package/dist/llm-client.d.ts.map +1 -1
  97. package/dist/llm-client.js +125 -21
  98. package/dist/llm-client.js.map +1 -1
  99. package/dist/mcp-client.d.ts.map +1 -1
  100. package/dist/mcp-client.js +9 -1
  101. package/dist/mcp-client.js.map +1 -1
  102. package/dist/memory-manager.d.ts +13 -8
  103. package/dist/memory-manager.d.ts.map +1 -1
  104. package/dist/memory-manager.js +125 -32
  105. package/dist/memory-manager.js.map +1 -1
  106. package/dist/memory-updater.d.ts.map +1 -1
  107. package/dist/memory-updater.js +5 -4
  108. package/dist/memory-updater.js.map +1 -1
  109. package/dist/memory.d.ts +6 -2
  110. package/dist/memory.d.ts.map +1 -1
  111. package/dist/memory.js +32 -4
  112. package/dist/memory.js.map +1 -1
  113. package/dist/parallel-executor.d.ts +7 -0
  114. package/dist/parallel-executor.d.ts.map +1 -1
  115. package/dist/parallel-executor.js +28 -0
  116. package/dist/parallel-executor.js.map +1 -1
  117. package/dist/perf-optimizer.d.ts.map +1 -1
  118. package/dist/perf-optimizer.js +18 -3
  119. package/dist/perf-optimizer.js.map +1 -1
  120. package/dist/persona.d.ts.map +1 -1
  121. package/dist/persona.js +8 -3
  122. package/dist/persona.js.map +1 -1
  123. package/dist/planner.d.ts.map +1 -1
  124. package/dist/planner.js +5 -3
  125. package/dist/planner.js.map +1 -1
  126. package/dist/plugin-auto-loader.d.ts.map +1 -1
  127. package/dist/plugin-auto-loader.js +4 -1
  128. package/dist/plugin-auto-loader.js.map +1 -1
  129. package/dist/plugin-registry.d.ts +4 -0
  130. package/dist/plugin-registry.d.ts.map +1 -1
  131. package/dist/plugin-registry.js +6 -0
  132. package/dist/plugin-registry.js.map +1 -1
  133. package/dist/plugin-validator.d.ts.map +1 -1
  134. package/dist/plugin-validator.js +10 -1
  135. package/dist/plugin-validator.js.map +1 -1
  136. package/dist/reasoning-aggregator.d.ts +35 -0
  137. package/dist/reasoning-aggregator.d.ts.map +1 -0
  138. package/dist/reasoning-aggregator.js +102 -0
  139. package/dist/reasoning-aggregator.js.map +1 -0
  140. package/dist/reasoning-tree.d.ts +23 -0
  141. package/dist/reasoning-tree.d.ts.map +1 -0
  142. package/dist/reasoning-tree.js +44 -0
  143. package/dist/reasoning-tree.js.map +1 -0
  144. package/dist/session-persistence.d.ts +8 -4
  145. package/dist/session-persistence.d.ts.map +1 -1
  146. package/dist/session-persistence.js +22 -7
  147. package/dist/session-persistence.js.map +1 -1
  148. package/dist/skill-learner.d.ts.map +1 -1
  149. package/dist/skill-learner.js +4 -2
  150. package/dist/skill-learner.js.map +1 -1
  151. package/dist/skill-loader.d.ts +4 -0
  152. package/dist/skill-loader.d.ts.map +1 -1
  153. package/dist/skill-loader.js +6 -0
  154. package/dist/skill-loader.js.map +1 -1
  155. package/dist/speculative-executor.d.ts +22 -0
  156. package/dist/speculative-executor.d.ts.map +1 -1
  157. package/dist/speculative-executor.js +90 -45
  158. package/dist/speculative-executor.js.map +1 -1
  159. package/dist/state-machine.d.ts.map +1 -1
  160. package/dist/state-machine.js +4 -2
  161. package/dist/state-machine.js.map +1 -1
  162. package/dist/sub-agent-prompts.d.ts +5 -29
  163. package/dist/sub-agent-prompts.d.ts.map +1 -1
  164. package/dist/sub-agent-prompts.js +231 -134
  165. package/dist/sub-agent-prompts.js.map +1 -1
  166. package/dist/sub-agent.d.ts +19 -0
  167. package/dist/sub-agent.d.ts.map +1 -1
  168. package/dist/sub-agent.js +135 -11
  169. package/dist/sub-agent.js.map +1 -1
  170. package/dist/system-prompt.d.ts.map +1 -1
  171. package/dist/system-prompt.js +45 -0
  172. package/dist/system-prompt.js.map +1 -1
  173. package/dist/task-classifier.js +1 -1
  174. package/dist/task-classifier.js.map +1 -1
  175. package/dist/types.d.ts +67 -1
  176. package/dist/types.d.ts.map +1 -1
  177. package/dist/types.js.map +1 -1
  178. package/dist/vector-index.d.ts +14 -0
  179. package/dist/vector-index.d.ts.map +1 -1
  180. package/dist/vector-index.js +84 -16
  181. package/dist/vector-index.js.map +1 -1
  182. package/dist/workspace-lock.d.ts +5 -0
  183. package/dist/workspace-lock.d.ts.map +1 -0
  184. package/dist/workspace-lock.js +16 -0
  185. package/dist/workspace-lock.js.map +1 -0
  186. package/package.json +1 -1
@@ -28,6 +28,7 @@
28
28
  */
29
29
  import { EventEmitter } from "node:events";
30
30
  import { readFile } from "node:fs/promises";
31
+ import path from "node:path";
31
32
  import { BYOKClient } from "./llm-client.js";
32
33
  import { AgentLoop } from "./agent-loop.js";
33
34
  import { AgentLogger } from "./agent-logger.js";
@@ -46,6 +47,12 @@ import { SecurityScanner } from "./security-scanner.js";
46
47
  import { DocIntelligence } from "./doc-intelligence.js";
47
48
  import { IntentInferenceEngine } from "./intent-inference.js";
48
49
  import { SpeculativeExecutor } from "./speculative-executor.js";
50
+ import { SubAgent } from "./sub-agent.js";
51
+ import { routeSubAgent } from "./sub-agent-router.js";
52
+ import { DAGOrchestrator } from "./dag-orchestrator.js";
53
+ import { WorkspaceLock } from "./workspace-lock.js";
54
+ import { SkillLoader, } from "./skill-loader.js";
55
+ import { SkillLearner, } from "./skill-learner.js";
49
56
  // ─── Defaults ────────────────────────────────────────────────────
50
57
  const ENGINE_DEFAULTS = {
51
58
  maxIterations: 100,
@@ -82,6 +89,7 @@ export class ExecutionEngine extends EventEmitter {
82
89
  reflection;
83
90
  codebaseContext;
84
91
  vectorIndex;
92
+ embeddingCache;
85
93
  stateMachine;
86
94
  abortController;
87
95
  changedFiles;
@@ -97,6 +105,8 @@ export class ExecutionEngine extends EventEmitter {
97
105
  parallelStepResults;
98
106
  /** DAG 기반 병렬 실행이 완료되었는지 */
99
107
  parallelExecutionDone;
108
+ dagOrchestrator = null;
109
+ workspaceLock;
100
110
  /** MCP server configurations (stored from constructor config) */
101
111
  mcpServerConfigs;
102
112
  /** MCP Client — external tool bridge (null if disabled) */
@@ -105,10 +115,32 @@ export class ExecutionEngine extends EventEmitter {
105
115
  mcpToolDefinitions = [];
106
116
  /** Performance optimizer (null if disabled) */
107
117
  perfOptimizer = null;
118
+ /**
119
+ * step / sub-agent 단위 토큰 예산 SSOT.
120
+ * - 최소 5k 보장
121
+ * - 병렬도 고려해서 전체 예산을 안전하게 분할
122
+ */
123
+ getSubAgentTokenBudget() {
124
+ const divisor = Math.max(1, this.config.maxParallelAgents + 1);
125
+ return Math.max(5000, Math.floor(this.config.totalTokenBudget / divisor));
126
+ }
108
127
  /** Sandbox manager for tool call validation (null if disabled) */
109
128
  sandboxManager = null;
129
+ /** Skill loader */
130
+ skillLoader;
131
+ /** Skill learner */
132
+ skillLearner;
133
+ /** Static + learned skill definitions */
134
+ skills;
135
+ /** Run-level skill context */
136
+ activeSkillContext = null;
137
+ /** Step resolved skills */
138
+ resolvedSkillsByStepId = new Map();
139
+ /** Global complexity inferred during analyze phase */
140
+ globalComplexity = "moderate";
110
141
  constructor(config) {
111
142
  super();
143
+ this.setMaxListeners(200);
112
144
  this.config = {
113
145
  byokConfig: config.byokConfig,
114
146
  projectPath: config.projectPath,
@@ -155,6 +187,15 @@ export class ExecutionEngine extends EventEmitter {
155
187
  });
156
188
  this.codebaseContext = null;
157
189
  this.vectorIndex = null;
190
+ const memoryCache = new Map();
191
+ this.embeddingCache = {
192
+ async get(key) {
193
+ return memoryCache.get(key) ?? null;
194
+ },
195
+ async set(key, embedding) {
196
+ memoryCache.set(key, embedding);
197
+ },
198
+ };
158
199
  this.stateMachine = null;
159
200
  this.planGraph = null;
160
201
  this.abortController = new AbortController();
@@ -163,6 +204,28 @@ export class ExecutionEngine extends EventEmitter {
163
204
  this.lastTermination = { reason: "USER_CANCELLED" };
164
205
  this.parallelStepResults = new Map();
165
206
  this.parallelExecutionDone = false;
207
+ this.skillLoader = new SkillLoader();
208
+ this.skillLearner = new SkillLearner(this.config.projectPath);
209
+ this.skills = config.skills ?? [];
210
+ this.activeSkillContext = null;
211
+ this.resolvedSkillsByStepId = new Map();
212
+ this.workspaceLock = new WorkspaceLock();
213
+ // Agent DAG orchestrator
214
+ this.dagOrchestrator = new DAGOrchestrator({
215
+ maxParallelAgents: this.config.maxParallelAgents,
216
+ maxRetries: 2,
217
+ tokenBudget: this.config.totalTokenBudget,
218
+ wallTimeLimit: 600000,
219
+ spawnAgent: this.spawnAgent.bind(this),
220
+ });
221
+ this.dagOrchestrator.on("dag:agent_reasoning", (e) => {
222
+ this.emit("agent:event", {
223
+ kind: "agent:reasoning_timeline",
224
+ source: "dag",
225
+ taskId: e.taskId,
226
+ text: e.text,
227
+ });
228
+ });
166
229
  // Store MCP server configs for use in execute()
167
230
  this.mcpServerConfigs = config.mcpServerConfigs ?? [];
168
231
  // Initialize PerfOptimizer if enabled
@@ -229,10 +292,21 @@ export class ExecutionEngine extends EventEmitter {
229
292
  this.planGraph = null;
230
293
  this.parallelStepResults = new Map();
231
294
  this.parallelExecutionDone = false;
295
+ this.resolvedSkillsByStepId.clear();
296
+ this.activeSkillContext = {
297
+ taskDescription: goal,
298
+ };
232
299
  this.emit("engine:start", goal);
233
300
  this._logger.logInput(goal);
234
301
  this.reflection.think("start", `Goal received: "${goal}"`);
235
302
  try {
303
+ // 0.5 Load learned skills
304
+ try {
305
+ await this.skillLearner.init();
306
+ }
307
+ catch (err) {
308
+ this._logger.warn("system", `SkillLearner init failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
309
+ }
236
310
  // 0. Intent inference pre-processing (refine ambiguous goals)
237
311
  if (this.config.enableIntentInference) {
238
312
  try {
@@ -247,6 +321,10 @@ export class ExecutionEngine extends EventEmitter {
247
321
  this._logger.info("system", `Intent inference: ambiguous input refined — "${goal}" → "${intentResult.refinedGoal}" (category: ${intentResult.category}, confidence: ${intentResult.confidence.toFixed(2)})`);
248
322
  this.reflection.think("analyze", `Intent inference refined ambiguous goal: "${goal}" → "${intentResult.refinedGoal}" [${intentResult.category}, confidence=${intentResult.confidence.toFixed(2)}]`);
249
323
  goal = intentResult.refinedGoal;
324
+ this.activeSkillContext = {
325
+ ...(this.activeSkillContext ?? {}),
326
+ taskDescription: goal,
327
+ };
250
328
  }
251
329
  else {
252
330
  this._logger.info("system", `Intent inference: input is clear (category: ${intentResult.category}, confidence: ${intentResult.confidence.toFixed(2)})`);
@@ -267,24 +345,39 @@ export class ExecutionEngine extends EventEmitter {
267
345
  this._logger.info("system", `Codebase indexed: ${stats.totalFiles} files, ${stats.totalSymbols} symbols`);
268
346
  this.reflection.think("analyze", `Index built: ${stats.totalFiles} files, ${stats.totalSymbols} symbols`);
269
347
  }
270
- // 1b. Conditional VectorIndex initialization (requires pgvector)
348
+ // 1b. VectorIndex initialization (pgvector semantic search)
271
349
  if (this.config.enableVectorSearch && !this.vectorIndex) {
272
- // VectorIndex requires a real SQLExecutor and EmbeddingProvider.
273
- // When no DB is available, create a no-op stub so the engine
274
- // doesn't crash vector search simply returns empty results.
275
- const noopSqlExecutor = {
276
- query: async () => ({ rows: [] }),
277
- };
278
- const noopEmbeddingProvider = {
279
- embed: async (texts) => texts.map(() => []),
280
- dimension: 1536,
281
- };
282
- this.vectorIndex = new VectorIndex({
283
- projectId: this.config.projectPath,
284
- sqlExecutor: noopSqlExecutor,
285
- embeddingProvider: noopEmbeddingProvider,
286
- });
287
- this._logger.info("system", "VectorIndex initialized (no-op stub — supply real DB for pgvector search)");
350
+ try {
351
+ const sqlExecutor = {
352
+ query: async (sql, params) => {
353
+ return this.config.toolExecutor.executeSQL
354
+ ? await this.config.toolExecutor.executeSQL(sql, params)
355
+ : { rows: [] };
356
+ },
357
+ };
358
+ const embeddingProvider = {
359
+ dimension: 1536,
360
+ embed: async (texts) => {
361
+ const results = [];
362
+ for (const text of texts) {
363
+ const resp = await this.llmClient.embed(text);
364
+ results.push(resp.embedding);
365
+ }
366
+ return results;
367
+ },
368
+ };
369
+ this.vectorIndex = new VectorIndex({
370
+ projectId: path.basename(this.config.projectPath),
371
+ sqlExecutor,
372
+ embeddingProvider,
373
+ embeddingCache: this.embeddingCache
374
+ });
375
+ await this.vectorIndex.initialize();
376
+ this._logger.info("system", "VectorIndex initialized (pgvector semantic search enabled)");
377
+ }
378
+ catch (err) {
379
+ this._logger.warn("system", `VectorIndex initialization failed: ${err instanceof Error ? err.message : String(err)}`);
380
+ }
288
381
  }
289
382
  // 1c. MCP Client initialization (optional — connect to external MCP servers)
290
383
  if (this.mcpServerConfigs.length > 0) {
@@ -463,9 +556,19 @@ export class ExecutionEngine extends EventEmitter {
463
556
  if (complexityMatch) {
464
557
  complexity = complexityMatch[1].toLowerCase();
465
558
  }
559
+ this.globalComplexity =
560
+ complexity;
466
561
  this._logger.logDecision("Goal complexity assessment", ["trivial", "simple", "moderate", "complex", "massive"], complexity, `LLM analysis determined complexity based on goal and codebase context`);
467
562
  this.reflection.think("analyze", `Analysis complete — complexity: ${complexity}`);
563
+ this.activeSkillContext = {
564
+ ...(this.activeSkillContext ?? {}),
565
+ taskDescription: goal,
566
+ };
468
567
  exitAnalyze();
568
+ this.activeSkillContext = {
569
+ ...(this.activeSkillContext ?? {}),
570
+ taskDescription: goal,
571
+ };
469
572
  return { complexity, context: content };
470
573
  }
471
574
  /**
@@ -557,6 +660,28 @@ Exactly one approach should have recommended=true.`;
557
660
  `${hPlan.totalEstimatedIterations} estimated iterations, ` +
558
661
  `${hPlan.parallelizableGroups.length} parallel groups`);
559
662
  const executionPlan = planner.toExecutionPlan(hPlan);
663
+ for (const step of executionPlan.steps) {
664
+ const stepSkillContext = {
665
+ ...(this.activeSkillContext ?? {}),
666
+ taskDescription: step.goal,
667
+ filePath: step.targetFiles[0],
668
+ };
669
+ const matched = this.skillLoader.matchTriggers(this.skills, stepSkillContext);
670
+ const learned = this.skillLearner
671
+ .getRelevantSkills({
672
+ filePath: step.targetFiles[0],
673
+ })
674
+ .map((s) => this.skillLearner.toSkillDefinition(s));
675
+ const combined = [...matched, ...learned];
676
+ const deduped = new Map();
677
+ for (const skill of combined) {
678
+ deduped.set(skill.id, skill);
679
+ }
680
+ const resolvedSkills = [...deduped.values()].map((skill) => this.skillLoader.loadTemplate(skill));
681
+ step.skillIds = resolvedSkills.map((s) => s.definition.id);
682
+ step.resolvedSkills = resolvedSkills;
683
+ this.resolvedSkillsByStepId.set(step.id, resolvedSkills);
684
+ }
560
685
  // Initialize PlanGraphManager for step progress tracking
561
686
  this.planGraph = PlanGraphManager.fromExecutionPlan(this.sessionId, executionPlan);
562
687
  this._logger.info("system", `PlanGraph initialized: ${executionPlan.steps.length} nodes, ${this.planGraph.getState().parallelGroups.length} parallel groups`);
@@ -574,7 +699,10 @@ Exactly one approach should have recommended=true.`;
574
699
  id: "step-1",
575
700
  goal: `${approach.description}: ${goal}`,
576
701
  targetFiles: [],
702
+ role: "coder",
577
703
  readFiles: [],
704
+ skillIds: [],
705
+ resolvedSkills: [],
578
706
  tools: ["file_read", "file_write", "file_edit", "grep", "glob"],
579
707
  estimatedIterations: 10,
580
708
  dependsOn: [],
@@ -582,6 +710,25 @@ Exactly one approach should have recommended=true.`;
582
710
  ],
583
711
  estimatedTokens: 50_000,
584
712
  };
713
+ {
714
+ const step = fallbackPlan.steps[0];
715
+ const stepSkillContext = {
716
+ ...(this.activeSkillContext ?? {}),
717
+ taskDescription: step.goal,
718
+ };
719
+ const matched = this.skillLoader.matchTriggers(this.skills, stepSkillContext);
720
+ const learned = this.skillLearner
721
+ .getRelevantSkills({})
722
+ .map((s) => this.skillLearner.toSkillDefinition(s));
723
+ const combined = [...matched, ...learned];
724
+ const deduped = new Map();
725
+ for (const skill of combined)
726
+ deduped.set(skill.id, skill);
727
+ const resolvedSkills = [...deduped.values()].map((skill) => this.skillLoader.loadTemplate(skill));
728
+ step.skillIds = resolvedSkills.map((s) => s.definition.id);
729
+ step.resolvedSkills = resolvedSkills;
730
+ this.resolvedSkillsByStepId.set(step.id, resolvedSkills);
731
+ }
585
732
  // Initialize PlanGraphManager for fallback plan too
586
733
  this.planGraph = PlanGraphManager.fromExecutionPlan(this.sessionId, fallbackPlan);
587
734
  this._logger.info("system", `PlanGraph initialized (fallback): 1 node`);
@@ -629,7 +776,47 @@ Exactly one approach should have recommended=true.`;
629
776
  };
630
777
  }
631
778
  // First call triggers full DAG execution for ALL steps
632
- await this.executeStepsWithDAG(plan, state);
779
+ if (!this.dagOrchestrator) {
780
+ throw new Error("DAGOrchestrator not initialized");
781
+ }
782
+ const agentPlan = {
783
+ tasks: plan.steps.map((s, i) => ({
784
+ id: s.id,
785
+ goal: s.goal,
786
+ targetFiles: s.targetFiles,
787
+ readFiles: s.readFiles,
788
+ tools: s.tools,
789
+ estimatedIterations: s.estimatedIterations,
790
+ priority: plan.steps.length - i,
791
+ complexity: state.workingMemory.get("complexity") ?? "moderate",
792
+ role: s.role ?? "coder",
793
+ skillIds: s.skillIds ?? [],
794
+ resolvedSkills: s.resolvedSkills ?? this.resolvedSkillsByStepId.get(s.id) ?? [],
795
+ })),
796
+ estimatedTokens: plan.estimatedTokens,
797
+ estimatedDurationMs: plan.steps.reduce((sum, s) => sum + s.estimatedIterations * 1000, 0),
798
+ dependencies: plan.steps.flatMap((s) => (s.dependsOn ?? []).map((dep) => [dep, s.id])),
799
+ maxParallelAgents: this.config.maxParallelAgents,
800
+ };
801
+ const dagResult = await this.dagOrchestrator.execute(agentPlan, {
802
+ overallGoal: plan.goal,
803
+ projectPath: this.config.projectPath,
804
+ projectStructure: this.config.projectPath,
805
+ });
806
+ for (const task of dagResult.completedTasks) {
807
+ const stepIndex = plan.steps.findIndex(s => s.id === task.taskId);
808
+ if (stepIndex >= 0) {
809
+ this.parallelStepResults.set(stepIndex, {
810
+ stepIndex,
811
+ phase: "implement",
812
+ success: true,
813
+ output: task.summary,
814
+ changedFiles: task.changedFiles.map((f) => f.path),
815
+ tokensUsed: task.tokensUsed,
816
+ durationMs: 0
817
+ });
818
+ }
819
+ }
633
820
  this.parallelExecutionDone = true;
634
821
  // Return this step's result from cache
635
822
  const result = this.parallelStepResults.get(stepIndex);
@@ -758,6 +945,11 @@ Exactly one approach should have recommended=true.`;
758
945
  }
759
946
  const exitImplement = this._logger.enterLayer("implement", `Step ${stepIndex + 1}/${plan.steps.length}: "${step.goal}"`);
760
947
  this.reflection.think("implement", `Executing step ${stepIndex + 1}/${plan.steps.length}: "${step.goal}"`);
948
+ const stepSkillContext = {
949
+ ...(this.activeSkillContext ?? {}),
950
+ taskDescription: step.goal,
951
+ filePath: step.targetFiles[0],
952
+ };
761
953
  // Mark step as running in PlanGraph
762
954
  const stepId = step.id;
763
955
  if (this.planGraph) {
@@ -793,12 +985,21 @@ Exactly one approach should have recommended=true.`;
793
985
  "speculative:approach:complete",
794
986
  "speculative:evaluation",
795
987
  "speculative:complete",
988
+ "speculative:timeline",
796
989
  ];
797
990
  for (const eventName of specEvents) {
798
991
  specExecutor.on(eventName, (...args) => {
799
992
  this.emit(eventName, ...args);
800
993
  });
801
994
  }
995
+ specExecutor.on("speculative:timeline", (payload) => {
996
+ this.emit("agent:event", {
997
+ kind: "agent:reasoning_timeline",
998
+ source: "speculative",
999
+ taskId: payload.taskId,
1000
+ text: payload.text,
1001
+ });
1002
+ });
802
1003
  // Build codebase context summary for speculative executor
803
1004
  let codebaseContextSummary = "";
804
1005
  if (this.codebaseContext) {
@@ -848,40 +1049,88 @@ Exactly one approach should have recommended=true.`;
848
1049
  }
849
1050
  }
850
1051
  // ─── Normal AgentLoop Execution ─────────────────────────────
851
- // Build step-specific system prompt
852
- const systemPrompt = this.buildStepPrompt(plan, step, stepIndex);
853
- // Create and run AgentLoop for this step
854
- const loop = this.createAgentLoop(systemPrompt);
855
- this.wireAgentLoopEvents(loop);
1052
+ // ─────────────────────────────────────────
1053
+ // SubAgent execution
1054
+ // ─────────────────────────────────────────
856
1055
  const startMs = Date.now();
857
- const termination = await loop.run(step.goal);
1056
+ const routing = routeSubAgent({
1057
+ role: step.role ?? "coder",
1058
+ complexity: state.workingMemory.get("complexity") ?? "moderate",
1059
+ fileCount: step.targetFiles.length,
1060
+ hasTests: step.tools.includes("test_run"),
1061
+ isCriticalPath: false,
1062
+ previousFailures: 0,
1063
+ parentModelTier: "NORMAL"
1064
+ });
1065
+ const role = step.role ?? "coder";
1066
+ const tier = routing.tier;
1067
+ const subAgent = new SubAgent({
1068
+ taskId: step.id,
1069
+ goal: step.goal,
1070
+ targetFiles: step.targetFiles,
1071
+ readFiles: step.readFiles,
1072
+ maxIterations: this.config.maxIterations,
1073
+ totalTokenBudget: this.getSubAgentTokenBudget(),
1074
+ projectPath: this.config.projectPath,
1075
+ byokConfig: this.config.byokConfig,
1076
+ tools: step.tools,
1077
+ createToolExecutor: () => this.config.toolExecutor,
1078
+ role,
1079
+ parentModelTier: tier,
1080
+ });
1081
+ const agentRole = subAgent.role;
1082
+ // ─────────────────────────────────────────
1083
+ // SubAgent lifecycle forwarding (CLI stream)
1084
+ // ─────────────────────────────────────────
1085
+ subAgent.on("subagent:phase", (_taskId, phase) => {
1086
+ this.emit("agent:subagent_phase", {
1087
+ role: agentRole,
1088
+ phase,
1089
+ taskId: step.id,
1090
+ goal: step.goal,
1091
+ });
1092
+ });
1093
+ // ─────────────────────────────────────────
1094
+ // Forward SubAgent events → ExecutionEngine
1095
+ // ─────────────────────────────────────────
1096
+ subAgent.on("event", (event) => {
1097
+ this.emit("agent:event", event);
1098
+ });
1099
+ const subResult = await subAgent.run({
1100
+ overallGoal: plan.goal,
1101
+ taskGoal: step.goal,
1102
+ targetFiles: step.targetFiles,
1103
+ readFiles: step.readFiles,
1104
+ projectStructure: this.config.projectPath,
1105
+ skillContext: stepSkillContext,
1106
+ resolvedSkills: step.resolvedSkills ?? this.resolvedSkillsByStepId.get(step.id) ?? [],
1107
+ totalTasks: plan.steps.length,
1108
+ completedTasks: [],
1109
+ runningTasks: [],
1110
+ });
858
1111
  const durationMs = Date.now() - startMs;
859
- // Track last termination
860
- this.lastTermination = termination;
861
- // Collect changed files from loop
862
- const loopUsage = loop.getTokenUsage();
863
- const stepChangedFiles = [];
864
- // Extract changed files from agent events (tracked via tool results)
865
- // The changedFiles set is updated via wireAgentLoopEvents
1112
+ const stepChangedFiles = new Set(subResult.changedFiles.map((f) => typeof f === "string" ? f : f.path));
1113
+ const loopUsage = {
1114
+ input: subResult.tokensUsed?.input ?? 0,
1115
+ output: subResult.tokensUsed?.output ?? 0,
1116
+ };
1117
+ const success = subResult.success;
866
1118
  for (const file of this.changedFiles) {
867
- if (step.targetFiles.includes(file) || step.targetFiles.length === 0) {
868
- stepChangedFiles.push(file);
1119
+ if (step.targetFiles.length === 0)
1120
+ continue;
1121
+ if (step.targetFiles.some(f => file.endsWith(f))) {
1122
+ stepChangedFiles.add(file);
869
1123
  }
870
1124
  }
871
- const success = termination.reason === "GOAL_ACHIEVED";
872
1125
  // Update PlanGraph with step outcome
873
1126
  if (this.planGraph) {
874
1127
  try {
875
1128
  if (success) {
876
- this.planGraph.markCompleted(stepId, termination.reason === "GOAL_ACHIEVED"
877
- ? termination.summary
878
- : "completed", stepChangedFiles, { input: loopUsage.input, output: loopUsage.output });
1129
+ this.planGraph.markCompleted(stepId, subResult.summary, [...stepChangedFiles], { input: loopUsage.input, output: loopUsage.output });
879
1130
  this._logger.info("system", `PlanGraph: node "${stepId}" → completed`);
880
1131
  }
881
1132
  else {
882
- const errorMsg = termination.reason === "ERROR"
883
- ? termination.error
884
- : `Step failed: ${termination.reason}`;
1133
+ const errorMsg = subResult.error ?? "SubAgent failed";
885
1134
  this.planGraph.markFailed(stepId, errorMsg);
886
1135
  this._logger.info("system", `PlanGraph: node "${stepId}" → failed`);
887
1136
  }
@@ -893,10 +1142,8 @@ Exactly one approach should have recommended=true.`;
893
1142
  const progress = this.planGraph.getProgress();
894
1143
  this._logger.info("system", `PlanGraph progress: ${progress.completed}/${progress.total} (${progress.percent}%)`);
895
1144
  }
896
- this._logger.logDecision(`Step ${stepIndex + 1} outcome`, ["succeeded", "failed"], success ? "succeeded" : "failed", `AgentLoop terminated with reason: ${termination.reason}`);
897
- this.reflection.think("implement", `Step ${stepIndex + 1} ${success ? "succeeded" : "failed"}: ${termination.reason === "GOAL_ACHIEVED"
898
- ? termination.summary
899
- : termination.reason}`);
1145
+ this._logger.logDecision(`Step ${stepIndex + 1} outcome`, ["succeeded", "failed"], success ? "succeeded" : "failed", `SubAgent execution result`);
1146
+ this.reflection.think("implement", `Step ${stepIndex + 1} ${success ? "succeeded" : "failed"}: ${subResult.summary}`);
900
1147
  // Checkpoint after step completes (success or failure)
901
1148
  await this.checkpoint(stepIndex);
902
1149
  exitImplement();
@@ -904,12 +1151,8 @@ Exactly one approach should have recommended=true.`;
904
1151
  stepIndex,
905
1152
  phase: "implement",
906
1153
  success,
907
- output: termination.reason === "GOAL_ACHIEVED"
908
- ? termination.summary
909
- : termination.reason === "ERROR"
910
- ? termination.error
911
- : `Terminated: ${termination.reason}`,
912
- changedFiles: stepChangedFiles,
1154
+ output: subResult.summary,
1155
+ changedFiles: [...stepChangedFiles],
913
1156
  tokensUsed: loopUsage.input + loopUsage.output,
914
1157
  durationMs,
915
1158
  };
@@ -1109,6 +1352,28 @@ Exactly one approach should have recommended=true.`;
1109
1352
  }
1110
1353
  }
1111
1354
  exitVerify();
1355
+ // Best-effort learned skill persistence hook
1356
+ try {
1357
+ const syntheticAnalysis = {
1358
+ errorPatterns: deepResult.suggestedFixes
1359
+ .filter((f) => !!f.description)
1360
+ .map((f) => ({
1361
+ message: f.description,
1362
+ resolution: f.description,
1363
+ frequency: 1,
1364
+ type: "VerificationIssue",
1365
+ tool: "verify",
1366
+ })),
1367
+ toolPatterns: [],
1368
+ };
1369
+ const learned = this.skillLearner.extractSkillFromRun(syntheticAnalysis, this.sessionId);
1370
+ if (learned) {
1371
+ await this.skillLearner.save();
1372
+ }
1373
+ }
1374
+ catch (err) {
1375
+ this._logger.warn("system", `Skill learn/save failed (continuing): ${err instanceof Error ? err.message : String(err)}`);
1376
+ }
1112
1377
  // Map DeepVerifyResult → VerifyResult
1113
1378
  return this.mapVerifyResult(deepResult);
1114
1379
  }
@@ -1277,6 +1542,62 @@ ${[...this.changedFiles].join(", ")}
1277
1542
  }
1278
1543
  }
1279
1544
  }
1545
+ /**
1546
+ * DAGOrchestrator가 호출하는 Agent worker
1547
+ */
1548
+ async spawnAgent(task, context, _signal) {
1549
+ const routing = routeSubAgent({
1550
+ role: task.role ?? "coder",
1551
+ complexity: this.globalComplexity,
1552
+ fileCount: task.targetFiles.length,
1553
+ hasTests: task.tools?.includes("test_run") ?? false,
1554
+ isCriticalPath: false,
1555
+ previousFailures: 0,
1556
+ parentModelTier: "NORMAL"
1557
+ });
1558
+ const role = task.role ?? "coder";
1559
+ const subAgent = new SubAgent({
1560
+ taskId: task.id,
1561
+ goal: task.goal,
1562
+ targetFiles: task.targetFiles,
1563
+ readFiles: task.readFiles,
1564
+ maxIterations: this.config.maxIterations,
1565
+ totalTokenBudget: this.getSubAgentTokenBudget(),
1566
+ projectPath: this.config.projectPath,
1567
+ byokConfig: this.config.byokConfig,
1568
+ tools: task.tools,
1569
+ createToolExecutor: () => this.config.toolExecutor,
1570
+ role,
1571
+ parentModelTier: routing.tier,
1572
+ });
1573
+ const result = await subAgent.run({
1574
+ overallGoal: context.overallGoal,
1575
+ taskGoal: context.taskGoal ?? task.goal,
1576
+ targetFiles: context.targetFiles ?? task.targetFiles,
1577
+ readFiles: context.readFiles ?? task.readFiles,
1578
+ projectStructure: context.projectStructure,
1579
+ skillContext: context.skillContext ?? {
1580
+ taskDescription: task.goal,
1581
+ filePath: task.targetFiles[0],
1582
+ },
1583
+ resolvedSkills: context.resolvedSkills ?? task.resolvedSkills ?? [],
1584
+ totalTasks: 1,
1585
+ completedTasks: [],
1586
+ runningTasks: [],
1587
+ });
1588
+ return {
1589
+ taskId: task.id,
1590
+ summary: result.summary,
1591
+ changedFiles: result.changedFiles.map((file) => ({
1592
+ path: typeof file === "string" ? file : file.path,
1593
+ diff: "",
1594
+ })),
1595
+ tokensUsed: (result.tokensUsed?.input ?? 0) +
1596
+ (result.tokensUsed?.output ?? 0),
1597
+ iterations: this.config.maxIterations,
1598
+ usedSkillIds: task.skillIds ?? [],
1599
+ };
1600
+ }
1280
1601
  /**
1281
1602
  * 개별 step 실행을 위한 AgentLoop 인스턴스를 생성한다.
1282
1603
  *
@@ -1285,9 +1606,12 @@ ${[...this.changedFiles].join(", ")}
1285
1606
  */
1286
1607
  createAgentLoop(systemPrompt) {
1287
1608
  // Merge base tool definitions with MCP tool definitions
1609
+ // Tool filtering to avoid token explosion
1610
+ const baseTools = this.config.toolExecutor.definitions;
1611
+ const mcpToolsFiltered = this.mcpToolDefinitions.filter((t) => true);
1288
1612
  const tools = [
1289
- ...this.config.toolExecutor.definitions,
1290
- ...this.mcpToolDefinitions,
1613
+ ...baseTools,
1614
+ ...mcpToolsFiltered,
1291
1615
  ];
1292
1616
  // Wrap tool executor to add sandbox validation and MCP tool routing
1293
1617
  const baseExecutor = this.config.toolExecutor;
@@ -1297,44 +1621,77 @@ ${[...this.changedFiles].join(", ")}
1297
1621
  const wrappedExecutor = {
1298
1622
  definitions: tools,
1299
1623
  execute: async (call, abortSignal) => {
1300
- // Parse arguments to object if needed
1301
- let args;
1624
+ let args = {};
1625
+ let release = null;
1302
1626
  try {
1303
- args = typeof call.arguments === "string"
1304
- ? JSON.parse(call.arguments)
1305
- : call.arguments;
1306
- }
1307
- catch {
1308
- return {
1309
- tool_call_id: call.id,
1310
- name: call.name,
1311
- output: `[Error] Invalid JSON in tool arguments: ${String(call.arguments).slice(0, 200)}`,
1312
- success: false,
1313
- durationMs: 0,
1314
- };
1315
- }
1316
- // Sandbox pre-validation
1317
- if (sandboxMgr) {
1318
- const validation = sandboxMgr.validateToolCall(call.name, args);
1319
- if (!validation.allowed) {
1627
+ // Parse arguments to object if needed
1628
+ try {
1629
+ args = typeof call.arguments === "string"
1630
+ ? JSON.parse(call.arguments)
1631
+ : call.arguments;
1632
+ }
1633
+ catch {
1320
1634
  return {
1321
1635
  tool_call_id: call.id,
1322
1636
  name: call.name,
1323
- output: `[Sandbox blocked] ${validation.violations.join("; ")}`,
1637
+ output: "[Error] invalid tool arguments JSON",
1324
1638
  success: false,
1325
1639
  durationMs: 0,
1326
1640
  };
1327
1641
  }
1642
+ const targetFile = typeof args?.path === "string"
1643
+ ? args.path
1644
+ : typeof args?.file === "string"
1645
+ ? args.file
1646
+ : null;
1647
+ if (targetFile && this.isFileMutationTool(call.name)) {
1648
+ release = await this.workspaceLock.acquire(targetFile);
1649
+ }
1650
+ // Sandbox pre-validation
1651
+ if (sandboxMgr) {
1652
+ const validation = sandboxMgr.validateToolCall(call.name, args);
1653
+ if (!validation.allowed) {
1654
+ return {
1655
+ tool_call_id: call.id,
1656
+ name: call.name,
1657
+ output: `[Sandbox blocked] ${validation.violations.join("; ")}`,
1658
+ success: false,
1659
+ durationMs: 0,
1660
+ };
1661
+ }
1662
+ }
1663
+ // Route MCP tool calls to MCPClient (check MCP registry, not string pattern)
1664
+ if (mcpCli && mcpToolDefs.some((t) => t.name === call.name)) {
1665
+ const MCP_TIMEOUT = 20000;
1666
+ const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("MCP timeout")), MCP_TIMEOUT));
1667
+ try {
1668
+ return (await Promise.race([
1669
+ mcpCli.callToolAsYuan(call.name, args, call.id),
1670
+ timeout,
1671
+ ]));
1672
+ }
1673
+ catch (err) {
1674
+ return {
1675
+ tool_call_id: call.id,
1676
+ name: call.name,
1677
+ output: `[MCP error] ${err instanceof Error ? err.message : String(err)}`,
1678
+ success: false,
1679
+ durationMs: MCP_TIMEOUT,
1680
+ };
1681
+ }
1682
+ }
1683
+ // Default: use base executor
1684
+ return await baseExecutor.execute(call, abortSignal);
1328
1685
  }
1329
- // Route MCP tool calls to MCPClient (check MCP registry, not string pattern)
1330
- if (mcpCli && mcpToolDefs.some((t) => t.name === call.name)) {
1331
- return mcpCli.callToolAsYuan(call.name, args, call.id);
1686
+ finally {
1687
+ if (release) {
1688
+ release();
1689
+ }
1332
1690
  }
1333
- // Default: use base executor
1334
- return baseExecutor.execute(call, abortSignal);
1335
1691
  },
1336
1692
  };
1337
1693
  return new AgentLoop({
1694
+ abortSignal: this.abortController.signal,
1338
1695
  config: {
1339
1696
  byok: this.config.byokConfig,
1340
1697
  loop: {
@@ -1491,6 +1848,17 @@ Then provide your analysis.`;
1491
1848
  confidence: deep.confidence,
1492
1849
  };
1493
1850
  }
1851
+ isFileMutationTool(toolName) {
1852
+ const mutationTools = new Set([
1853
+ "file_write",
1854
+ "file_edit",
1855
+ "file_delete",
1856
+ "file_move",
1857
+ "file_rename",
1858
+ "apply_patch",
1859
+ ]);
1860
+ return mutationTools.has(toolName);
1861
+ }
1494
1862
  /**
1495
1863
  * StateMachine 최종 상태에서 ExecutionResult를 생성한다.
1496
1864
  *
@@ -1546,13 +1914,15 @@ Then provide your analysis.`;
1546
1914
  return fenced[1].trim();
1547
1915
  }
1548
1916
  // Find first JSON array or object
1549
- const arrayMatch = content.match(/\[[\s\S]*\]/);
1550
- if (arrayMatch) {
1551
- return arrayMatch[0];
1917
+ const start = content.indexOf("[");
1918
+ const end = content.lastIndexOf("]");
1919
+ if (start !== -1 && end !== -1 && end > start) {
1920
+ return content.slice(start, end + 1);
1552
1921
  }
1553
- const objectMatch = content.match(/\{[\s\S]*\}/);
1554
- if (objectMatch) {
1555
- return objectMatch[0];
1922
+ const objStart = content.indexOf("{");
1923
+ const objEnd = content.lastIndexOf("}");
1924
+ if (objStart !== -1 && objEnd !== -1 && objEnd > objStart) {
1925
+ return content.slice(objStart, objEnd + 1);
1556
1926
  }
1557
1927
  return content.trim();
1558
1928
  }