@yuaone/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/LICENSE +663 -0
  2. package/README.md +15 -0
  3. package/dist/__tests__/context-manager.test.d.ts +6 -0
  4. package/dist/__tests__/context-manager.test.d.ts.map +1 -0
  5. package/dist/__tests__/context-manager.test.js +220 -0
  6. package/dist/__tests__/context-manager.test.js.map +1 -0
  7. package/dist/__tests__/governor.test.d.ts +6 -0
  8. package/dist/__tests__/governor.test.d.ts.map +1 -0
  9. package/dist/__tests__/governor.test.js +210 -0
  10. package/dist/__tests__/governor.test.js.map +1 -0
  11. package/dist/__tests__/model-router.test.d.ts +6 -0
  12. package/dist/__tests__/model-router.test.d.ts.map +1 -0
  13. package/dist/__tests__/model-router.test.js +329 -0
  14. package/dist/__tests__/model-router.test.js.map +1 -0
  15. package/dist/agent-logger.d.ts +384 -0
  16. package/dist/agent-logger.d.ts.map +1 -0
  17. package/dist/agent-logger.js +820 -0
  18. package/dist/agent-logger.js.map +1 -0
  19. package/dist/agent-loop.d.ts +163 -0
  20. package/dist/agent-loop.d.ts.map +1 -0
  21. package/dist/agent-loop.js +609 -0
  22. package/dist/agent-loop.js.map +1 -0
  23. package/dist/agent-modes.d.ts +85 -0
  24. package/dist/agent-modes.d.ts.map +1 -0
  25. package/dist/agent-modes.js +418 -0
  26. package/dist/agent-modes.js.map +1 -0
  27. package/dist/approval.d.ts +137 -0
  28. package/dist/approval.d.ts.map +1 -0
  29. package/dist/approval.js +299 -0
  30. package/dist/approval.js.map +1 -0
  31. package/dist/async-completion-queue.d.ts +56 -0
  32. package/dist/async-completion-queue.d.ts.map +1 -0
  33. package/dist/async-completion-queue.js +77 -0
  34. package/dist/async-completion-queue.js.map +1 -0
  35. package/dist/auto-fix.d.ts +174 -0
  36. package/dist/auto-fix.d.ts.map +1 -0
  37. package/dist/auto-fix.js +319 -0
  38. package/dist/auto-fix.js.map +1 -0
  39. package/dist/codebase-context.d.ts +396 -0
  40. package/dist/codebase-context.d.ts.map +1 -0
  41. package/dist/codebase-context.js +1260 -0
  42. package/dist/codebase-context.js.map +1 -0
  43. package/dist/conflict-resolver.d.ts +191 -0
  44. package/dist/conflict-resolver.d.ts.map +1 -0
  45. package/dist/conflict-resolver.js +524 -0
  46. package/dist/conflict-resolver.js.map +1 -0
  47. package/dist/constants.d.ts +52 -0
  48. package/dist/constants.d.ts.map +1 -0
  49. package/dist/constants.js +141 -0
  50. package/dist/constants.js.map +1 -0
  51. package/dist/context-budget.d.ts +435 -0
  52. package/dist/context-budget.d.ts.map +1 -0
  53. package/dist/context-budget.js +903 -0
  54. package/dist/context-budget.js.map +1 -0
  55. package/dist/context-compressor.d.ts +143 -0
  56. package/dist/context-compressor.d.ts.map +1 -0
  57. package/dist/context-compressor.js +511 -0
  58. package/dist/context-compressor.js.map +1 -0
  59. package/dist/context-manager.d.ts +112 -0
  60. package/dist/context-manager.d.ts.map +1 -0
  61. package/dist/context-manager.js +247 -0
  62. package/dist/context-manager.js.map +1 -0
  63. package/dist/continuous-reflection.d.ts +267 -0
  64. package/dist/continuous-reflection.d.ts.map +1 -0
  65. package/dist/continuous-reflection.js +338 -0
  66. package/dist/continuous-reflection.js.map +1 -0
  67. package/dist/cross-file-refactor.d.ts +352 -0
  68. package/dist/cross-file-refactor.d.ts.map +1 -0
  69. package/dist/cross-file-refactor.js +1544 -0
  70. package/dist/cross-file-refactor.js.map +1 -0
  71. package/dist/dag-orchestrator.d.ts +138 -0
  72. package/dist/dag-orchestrator.d.ts.map +1 -0
  73. package/dist/dag-orchestrator.js +379 -0
  74. package/dist/dag-orchestrator.js.map +1 -0
  75. package/dist/debate-orchestrator.d.ts +301 -0
  76. package/dist/debate-orchestrator.d.ts.map +1 -0
  77. package/dist/debate-orchestrator.js +719 -0
  78. package/dist/debate-orchestrator.js.map +1 -0
  79. package/dist/dependency-analyzer.d.ts +113 -0
  80. package/dist/dependency-analyzer.d.ts.map +1 -0
  81. package/dist/dependency-analyzer.js +444 -0
  82. package/dist/dependency-analyzer.js.map +1 -0
  83. package/dist/design-loop.d.ts +59 -0
  84. package/dist/design-loop.d.ts.map +1 -0
  85. package/dist/design-loop.js +344 -0
  86. package/dist/design-loop.js.map +1 -0
  87. package/dist/doc-intelligence.d.ts +383 -0
  88. package/dist/doc-intelligence.d.ts.map +1 -0
  89. package/dist/doc-intelligence.js +1307 -0
  90. package/dist/doc-intelligence.js.map +1 -0
  91. package/dist/dynamic-role-generator.d.ts +76 -0
  92. package/dist/dynamic-role-generator.d.ts.map +1 -0
  93. package/dist/dynamic-role-generator.js +194 -0
  94. package/dist/dynamic-role-generator.js.map +1 -0
  95. package/dist/errors.d.ts +69 -0
  96. package/dist/errors.d.ts.map +1 -0
  97. package/dist/errors.js +102 -0
  98. package/dist/errors.js.map +1 -0
  99. package/dist/event-bus.d.ts +159 -0
  100. package/dist/event-bus.d.ts.map +1 -0
  101. package/dist/event-bus.js +305 -0
  102. package/dist/event-bus.js.map +1 -0
  103. package/dist/execution-engine.d.ts +425 -0
  104. package/dist/execution-engine.d.ts.map +1 -0
  105. package/dist/execution-engine.js +1555 -0
  106. package/dist/execution-engine.js.map +1 -0
  107. package/dist/git-intelligence.d.ts +306 -0
  108. package/dist/git-intelligence.d.ts.map +1 -0
  109. package/dist/git-intelligence.js +1099 -0
  110. package/dist/git-intelligence.js.map +1 -0
  111. package/dist/governor.d.ts +77 -0
  112. package/dist/governor.d.ts.map +1 -0
  113. package/dist/governor.js +161 -0
  114. package/dist/governor.js.map +1 -0
  115. package/dist/hierarchical-planner.d.ts +313 -0
  116. package/dist/hierarchical-planner.d.ts.map +1 -0
  117. package/dist/hierarchical-planner.js +981 -0
  118. package/dist/hierarchical-planner.js.map +1 -0
  119. package/dist/index.d.ts +121 -0
  120. package/dist/index.d.ts.map +1 -0
  121. package/dist/index.js +123 -0
  122. package/dist/index.js.map +1 -0
  123. package/dist/intent-inference.d.ts +103 -0
  124. package/dist/intent-inference.d.ts.map +1 -0
  125. package/dist/intent-inference.js +605 -0
  126. package/dist/intent-inference.js.map +1 -0
  127. package/dist/interrupt-manager.d.ts +143 -0
  128. package/dist/interrupt-manager.d.ts.map +1 -0
  129. package/dist/interrupt-manager.js +196 -0
  130. package/dist/interrupt-manager.js.map +1 -0
  131. package/dist/kernel.d.ts +564 -0
  132. package/dist/kernel.d.ts.map +1 -0
  133. package/dist/kernel.js +1419 -0
  134. package/dist/kernel.js.map +1 -0
  135. package/dist/language-support.d.ts +232 -0
  136. package/dist/language-support.d.ts.map +1 -0
  137. package/dist/language-support.js +1134 -0
  138. package/dist/language-support.js.map +1 -0
  139. package/dist/llm-client.d.ts +82 -0
  140. package/dist/llm-client.d.ts.map +1 -0
  141. package/dist/llm-client.js +475 -0
  142. package/dist/llm-client.js.map +1 -0
  143. package/dist/mcp-client.d.ts +232 -0
  144. package/dist/mcp-client.d.ts.map +1 -0
  145. package/dist/mcp-client.js +718 -0
  146. package/dist/mcp-client.js.map +1 -0
  147. package/dist/memory-manager.d.ts +200 -0
  148. package/dist/memory-manager.d.ts.map +1 -0
  149. package/dist/memory-manager.js +568 -0
  150. package/dist/memory-manager.js.map +1 -0
  151. package/dist/memory.d.ts +87 -0
  152. package/dist/memory.d.ts.map +1 -0
  153. package/dist/memory.js +341 -0
  154. package/dist/memory.js.map +1 -0
  155. package/dist/model-router.d.ts +245 -0
  156. package/dist/model-router.d.ts.map +1 -0
  157. package/dist/model-router.js +632 -0
  158. package/dist/model-router.js.map +1 -0
  159. package/dist/parallel-executor.d.ts +125 -0
  160. package/dist/parallel-executor.d.ts.map +1 -0
  161. package/dist/parallel-executor.js +201 -0
  162. package/dist/parallel-executor.js.map +1 -0
  163. package/dist/perf-optimizer.d.ts +212 -0
  164. package/dist/perf-optimizer.d.ts.map +1 -0
  165. package/dist/perf-optimizer.js +721 -0
  166. package/dist/perf-optimizer.js.map +1 -0
  167. package/dist/persona.d.ts +305 -0
  168. package/dist/persona.d.ts.map +1 -0
  169. package/dist/persona.js +887 -0
  170. package/dist/persona.js.map +1 -0
  171. package/dist/planner.d.ts +70 -0
  172. package/dist/planner.d.ts.map +1 -0
  173. package/dist/planner.js +264 -0
  174. package/dist/planner.js.map +1 -0
  175. package/dist/qa-pipeline.d.ts +365 -0
  176. package/dist/qa-pipeline.d.ts.map +1 -0
  177. package/dist/qa-pipeline.js +1352 -0
  178. package/dist/qa-pipeline.js.map +1 -0
  179. package/dist/reasoning-adapter.d.ts +116 -0
  180. package/dist/reasoning-adapter.d.ts.map +1 -0
  181. package/dist/reasoning-adapter.js +187 -0
  182. package/dist/reasoning-adapter.js.map +1 -0
  183. package/dist/role-registry.d.ts +55 -0
  184. package/dist/role-registry.d.ts.map +1 -0
  185. package/dist/role-registry.js +192 -0
  186. package/dist/role-registry.js.map +1 -0
  187. package/dist/sandbox-tiers.d.ts +327 -0
  188. package/dist/sandbox-tiers.d.ts.map +1 -0
  189. package/dist/sandbox-tiers.js +928 -0
  190. package/dist/sandbox-tiers.js.map +1 -0
  191. package/dist/security-scanner.d.ts +222 -0
  192. package/dist/security-scanner.d.ts.map +1 -0
  193. package/dist/security-scanner.js +1129 -0
  194. package/dist/security-scanner.js.map +1 -0
  195. package/dist/security.d.ts +93 -0
  196. package/dist/security.d.ts.map +1 -0
  197. package/dist/security.js +393 -0
  198. package/dist/security.js.map +1 -0
  199. package/dist/self-reflection.d.ts +397 -0
  200. package/dist/self-reflection.d.ts.map +1 -0
  201. package/dist/self-reflection.js +908 -0
  202. package/dist/self-reflection.js.map +1 -0
  203. package/dist/session-persistence.d.ts +191 -0
  204. package/dist/session-persistence.d.ts.map +1 -0
  205. package/dist/session-persistence.js +395 -0
  206. package/dist/session-persistence.js.map +1 -0
  207. package/dist/speculative-executor.d.ts +210 -0
  208. package/dist/speculative-executor.d.ts.map +1 -0
  209. package/dist/speculative-executor.js +618 -0
  210. package/dist/speculative-executor.js.map +1 -0
  211. package/dist/state-machine.d.ts +289 -0
  212. package/dist/state-machine.d.ts.map +1 -0
  213. package/dist/state-machine.js +695 -0
  214. package/dist/state-machine.js.map +1 -0
  215. package/dist/sub-agent.d.ts +177 -0
  216. package/dist/sub-agent.d.ts.map +1 -0
  217. package/dist/sub-agent.js +303 -0
  218. package/dist/sub-agent.js.map +1 -0
  219. package/dist/system-prompt.d.ts +26 -0
  220. package/dist/system-prompt.d.ts.map +1 -0
  221. package/dist/system-prompt.js +84 -0
  222. package/dist/system-prompt.js.map +1 -0
  223. package/dist/test-intelligence.d.ts +439 -0
  224. package/dist/test-intelligence.d.ts.map +1 -0
  225. package/dist/test-intelligence.js +1165 -0
  226. package/dist/test-intelligence.js.map +1 -0
  227. package/dist/types.d.ts +632 -0
  228. package/dist/types.d.ts.map +1 -0
  229. package/dist/types.js +6 -0
  230. package/dist/types.js.map +1 -0
  231. package/dist/vector-index.d.ts +314 -0
  232. package/dist/vector-index.d.ts.map +1 -0
  233. package/dist/vector-index.js +618 -0
  234. package/dist/vector-index.js.map +1 -0
  235. package/package.json +41 -0
@@ -0,0 +1,695 @@
1
+ /**
2
+ * @module state-machine
3
+ * @description LangGraph-style Agent State Machine — 에이전트 실행을 위한 유한 상태 기계.
4
+ *
5
+ * 에이전트 실행 흐름을 명시적 phase 전이로 관리한다.
6
+ * analyze → design → plan → implement/parallel → verify → fix/replan → done
7
+ *
8
+ * 각 phase는 독립적인 핸들러로 구현되며, 전이 조건은 핸들러 반환값으로 결정.
9
+ * EventEmitter를 통해 외부에서 phase 진입/퇴장, 전이, 완료 등을 구독할 수 있다.
10
+ */
11
+ import { EventEmitter } from "node:events";
12
+ // ─── Default Config ───
13
+ const DEFAULT_CONFIG = {
14
+ maxFixAttempts: 3,
15
+ maxReplanAttempts: 2,
16
+ skipDesignForSimple: true,
17
+ enableParallel: true,
18
+ verifyAfterEachStep: false,
19
+ };
20
+ // ─── Terminal Phases ───
21
+ const TERMINAL_PHASES = new Set(["done", "idle"]);
22
+ // ─── AgentStateMachine ───
23
+ /**
24
+ * LangGraph-style Agent State Machine.
25
+ *
26
+ * 에이전트 실행을 유한 상태 기계(FSM)로 모델링한다.
27
+ * 각 phase에 대응하는 핸들러가 상태를 변환하고 다음 phase를 결정한다.
28
+ * run()을 호출하면 idle → analyze → ... → done까지 자동 진행.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const sm = new AgentStateMachine(context, { maxFixAttempts: 5 });
33
+ * sm.on("transition", (from, to, reason) => console.log(`${from} → ${to}: ${reason}`));
34
+ * const finalState = await sm.run("모든 console.log를 제거해줘");
35
+ * ```
36
+ */
37
+ export class AgentStateMachine extends EventEmitter {
38
+ state;
39
+ context;
40
+ config;
41
+ handlers;
42
+ replanCount;
43
+ constructor(context, config) {
44
+ super();
45
+ this.context = context;
46
+ this.config = { ...DEFAULT_CONFIG, ...config };
47
+ this.replanCount = 0;
48
+ this.state = this.createInitialState("");
49
+ // Phase 핸들러 등록
50
+ this.handlers = new Map();
51
+ this.handlers.set("analyze", this.handleAnalyze.bind(this));
52
+ this.handlers.set("design", this.handleDesign.bind(this));
53
+ this.handlers.set("plan", this.handlePlan.bind(this));
54
+ this.handlers.set("implement", this.handleImplement.bind(this));
55
+ this.handlers.set("parallel", this.handleParallel.bind(this));
56
+ this.handlers.set("delegate", this.handleDelegate.bind(this));
57
+ this.handlers.set("verify", this.handleVerify.bind(this));
58
+ this.handlers.set("fix", this.handleFix.bind(this));
59
+ this.handlers.set("replan", this.handleReplan.bind(this));
60
+ }
61
+ // ─── Public API ───
62
+ /**
63
+ * 상태 기계를 실행한다.
64
+ * idle → analyze부터 시작하여 done에 도달할 때까지 phase를 자동 전이한다.
65
+ *
66
+ * @param goal - 에이전트 목표
67
+ * @param abortSignal - 외부 중단 시그널 (선택)
68
+ * @returns 최종 에이전트 상태
69
+ */
70
+ async run(goal, abortSignal) {
71
+ this.state = this.createInitialState(goal);
72
+ this.replanCount = 0;
73
+ if (abortSignal) {
74
+ this.state.abortSignal = abortSignal;
75
+ }
76
+ // idle → analyze 초기 전이
77
+ await this.transition({
78
+ nextPhase: "analyze",
79
+ updates: {},
80
+ reason: "Starting agent execution",
81
+ });
82
+ // 메인 루프 — 터미널 phase에 도달할 때까지 반복
83
+ const MAX_TOTAL_ITERATIONS = 1000;
84
+ while (!this.isTerminal(this.state.phase)) {
85
+ // Guard against infinite loops
86
+ if (this.state.iterationCount >= MAX_TOTAL_ITERATIONS) {
87
+ const reason = `Maximum iteration limit reached (${MAX_TOTAL_ITERATIONS})`;
88
+ this.emit("abort", reason);
89
+ await this.transition({
90
+ nextPhase: "done",
91
+ updates: {},
92
+ reason,
93
+ });
94
+ break;
95
+ }
96
+ // 중단 체크
97
+ if (this.state.abortSignal?.aborted) {
98
+ const reason = "Execution aborted by signal";
99
+ this.emit("abort", reason);
100
+ await this.transition({
101
+ nextPhase: "done",
102
+ updates: {},
103
+ reason,
104
+ });
105
+ break;
106
+ }
107
+ const handler = this.handlers.get(this.state.phase);
108
+ if (!handler) {
109
+ const reason = `No handler registered for phase: ${this.state.phase}`;
110
+ this.emit("abort", reason);
111
+ await this.transition({
112
+ nextPhase: "done",
113
+ updates: {},
114
+ reason,
115
+ });
116
+ break;
117
+ }
118
+ try {
119
+ const next = await handler(this.state, this.context);
120
+ await this.transition(next);
121
+ }
122
+ catch (err) {
123
+ const errorMessage = err instanceof Error ? err.message : String(err);
124
+ const stepError = {
125
+ stepIndex: this.state.currentStepIndex,
126
+ phase: this.state.phase,
127
+ error: errorMessage,
128
+ recoverable: false,
129
+ };
130
+ this.state.errors.push(stepError);
131
+ this.emit("step:error", stepError);
132
+ await this.transition({
133
+ nextPhase: "done",
134
+ updates: {},
135
+ reason: `Fatal error in ${this.state.phase}: ${errorMessage}`,
136
+ });
137
+ }
138
+ this.state.iterationCount++;
139
+ }
140
+ this.emit("done", this.state);
141
+ return this.state;
142
+ }
143
+ /**
144
+ * 현재 상태를 읽기 전용으로 반환한다.
145
+ */
146
+ getState() {
147
+ return this.state;
148
+ }
149
+ /**
150
+ * 실행 중인 에이전트에 사용자 피드백을 주입한다.
151
+ * delegate phase에서 수집되거나, 외부에서 즉시 주입 가능.
152
+ *
153
+ * @param feedback - 사용자 피드백 문자열
154
+ */
155
+ injectFeedback(feedback) {
156
+ this.state.userFeedback.push(feedback);
157
+ }
158
+ /**
159
+ * 특정 phase로 강제 전이한다 (escape hatch).
160
+ * 디버깅이나 외부 제어에 사용.
161
+ *
162
+ * @param phase - 전이할 phase
163
+ * @param reason - 전이 사유
164
+ */
165
+ forceTransition(phase, reason) {
166
+ const from = this.state.phase;
167
+ this.emit("phase:exit", from, this.state);
168
+ this.state.phase = phase;
169
+ this.emit("transition", from, phase, `[FORCED] ${reason}`);
170
+ this.emit("phase:enter", phase, this.state);
171
+ }
172
+ // ─── Phase Handlers ───
173
+ /**
174
+ * analyze phase — 코드베이스와 요청을 분석한다.
175
+ * 복잡도가 trivial이고 skipDesignForSimple이면 plan으로 직행.
176
+ */
177
+ async handleAnalyze(state, ctx) {
178
+ const startMs = Date.now();
179
+ const analysis = await ctx.analyzeFn(state.goal, state);
180
+ const durationMs = Date.now() - startMs;
181
+ state.workingMemory.set("analysisContext", analysis.context);
182
+ state.workingMemory.set("complexity", analysis.complexity);
183
+ const result = {
184
+ stepIndex: 0,
185
+ phase: "analyze",
186
+ success: true,
187
+ output: `Complexity: ${analysis.complexity}. ${analysis.context}`,
188
+ changedFiles: [],
189
+ tokensUsed: 0,
190
+ durationMs,
191
+ };
192
+ state.stepResults.push(result);
193
+ this.emit("step:complete", result);
194
+ // trivial + skipDesign → plan으로 직행
195
+ if (analysis.complexity === "trivial" &&
196
+ this.config.skipDesignForSimple) {
197
+ return {
198
+ nextPhase: "plan",
199
+ updates: {},
200
+ reason: `Trivial task — skipping design phase`,
201
+ };
202
+ }
203
+ return {
204
+ nextPhase: "design",
205
+ updates: {},
206
+ reason: `Analysis complete (complexity: ${analysis.complexity})`,
207
+ };
208
+ }
209
+ /**
210
+ * design phase — 구현 접근법을 제안한다.
211
+ * 추천 접근법이 있으면 자동 선택 후 plan으로, 없으면 delegate로.
212
+ */
213
+ async handleDesign(state, ctx) {
214
+ const analysisContext = state.workingMemory.get("analysisContext") ?? "";
215
+ const startMs = Date.now();
216
+ const approaches = await ctx.designFn(state.goal, analysisContext);
217
+ const durationMs = Date.now() - startMs;
218
+ const result = {
219
+ stepIndex: state.stepResults.length,
220
+ phase: "design",
221
+ success: true,
222
+ output: `Generated ${approaches.length} approach(es)`,
223
+ changedFiles: [],
224
+ tokensUsed: 0,
225
+ durationMs,
226
+ };
227
+ state.stepResults.push(result);
228
+ this.emit("step:complete", result);
229
+ // 추천 접근법 자동 선택
230
+ const recommendedIdx = approaches.findIndex((a) => a.recommended);
231
+ if (recommendedIdx >= 0) {
232
+ return {
233
+ nextPhase: "plan",
234
+ updates: {
235
+ approaches,
236
+ selectedApproach: recommendedIdx,
237
+ },
238
+ reason: `Auto-selected recommended approach: ${approaches[recommendedIdx].name}`,
239
+ };
240
+ }
241
+ // 추천 없음 → 사용자에게 위임
242
+ return {
243
+ nextPhase: "delegate",
244
+ updates: {
245
+ approaches,
246
+ },
247
+ reason: "No recommended approach — delegating to user",
248
+ };
249
+ }
250
+ /**
251
+ * plan phase — 실행 계획을 수립한다.
252
+ * 독립적 태스크가 있고 enableParallel이면 parallel로, 아니면 implement로.
253
+ */
254
+ async handlePlan(state, ctx) {
255
+ const approach = state.approaches[state.selectedApproach];
256
+ // 접근법이 없는 경우 (trivial → plan 직행 시) 기본 접근법 생성
257
+ const effectiveApproach = approach ?? {
258
+ id: 0,
259
+ name: "direct",
260
+ description: "Direct implementation",
261
+ pros: ["Simple"],
262
+ cons: [],
263
+ estimatedComplexity: "low",
264
+ recommended: true,
265
+ };
266
+ const startMs = Date.now();
267
+ const plan = await ctx.planFn(state.goal, effectiveApproach);
268
+ const durationMs = Date.now() - startMs;
269
+ const result = {
270
+ stepIndex: state.stepResults.length,
271
+ phase: "plan",
272
+ success: true,
273
+ output: `Plan created with ${plan.steps.length} step(s)`,
274
+ changedFiles: [],
275
+ tokensUsed: 0,
276
+ durationMs,
277
+ };
278
+ state.stepResults.push(result);
279
+ this.emit("step:complete", result);
280
+ // 병렬 실행 가능 여부 판단 — 의존성 없는 독립 step이 2개 이상이면 parallel
281
+ const hasIndependentTasks = this.config.enableParallel &&
282
+ plan.steps.length > 1 &&
283
+ plan.steps.filter((s) => s.dependsOn.length === 0).length >= 2;
284
+ if (hasIndependentTasks) {
285
+ return {
286
+ nextPhase: "parallel",
287
+ updates: {
288
+ plan,
289
+ currentStepIndex: 0,
290
+ },
291
+ reason: `Plan has ${plan.steps.length} steps with independent tasks — using parallel execution`,
292
+ };
293
+ }
294
+ return {
295
+ nextPhase: "implement",
296
+ updates: {
297
+ plan,
298
+ currentStepIndex: 0,
299
+ },
300
+ reason: `Plan has ${plan.steps.length} step(s) — using sequential execution`,
301
+ };
302
+ }
303
+ /**
304
+ * implement phase — step을 순차 실행한다.
305
+ * 각 step 후: 복구 불가 에러 → fix, 모든 step 완료 → verify.
306
+ */
307
+ async handleImplement(state, ctx) {
308
+ if (!state.plan) {
309
+ return {
310
+ nextPhase: "done",
311
+ updates: {},
312
+ reason: "No plan available — cannot implement",
313
+ };
314
+ }
315
+ const { steps } = state.plan;
316
+ for (let i = state.currentStepIndex; i < steps.length; i++) {
317
+ // 중단 체크
318
+ if (state.abortSignal?.aborted) {
319
+ return {
320
+ nextPhase: "done",
321
+ updates: { currentStepIndex: i },
322
+ reason: "Aborted during implementation",
323
+ };
324
+ }
325
+ try {
326
+ const stepResult = await ctx.executeFn(state.plan, i, state);
327
+ state.stepResults.push(stepResult);
328
+ state.currentStepIndex = i + 1;
329
+ this.emit("step:complete", stepResult);
330
+ // 토큰 추적
331
+ state.tokenUsage.input += stepResult.tokensUsed;
332
+ state.toolCalls++;
333
+ if (!stepResult.success) {
334
+ const stepError = {
335
+ stepIndex: i,
336
+ phase: "implement",
337
+ error: stepResult.output,
338
+ recoverable: true,
339
+ };
340
+ state.errors.push(stepError);
341
+ this.emit("step:error", stepError);
342
+ }
343
+ // step 별 검증 (설정 시)
344
+ if (this.config.verifyAfterEachStep && stepResult.success) {
345
+ const verifyResult = await ctx.verifyFn(state);
346
+ state.reflections.push(verifyResult);
347
+ this.emit("verify:result", verifyResult);
348
+ if (verifyResult.verdict === "fail") {
349
+ return {
350
+ nextPhase: "fix",
351
+ updates: { currentStepIndex: i + 1 },
352
+ reason: `Verification failed after step ${i}: ${verifyResult.issues.join(", ")}`,
353
+ };
354
+ }
355
+ }
356
+ }
357
+ catch (err) {
358
+ const errorMessage = err instanceof Error ? err.message : String(err);
359
+ const stepError = {
360
+ stepIndex: i,
361
+ phase: "implement",
362
+ error: errorMessage,
363
+ recoverable: false,
364
+ };
365
+ state.errors.push(stepError);
366
+ this.emit("step:error", stepError);
367
+ return {
368
+ nextPhase: "fix",
369
+ updates: { currentStepIndex: i },
370
+ reason: `Non-recoverable error at step ${i}: ${errorMessage}`,
371
+ };
372
+ }
373
+ }
374
+ return {
375
+ nextPhase: "verify",
376
+ updates: {},
377
+ reason: "All implementation steps completed",
378
+ };
379
+ }
380
+ /**
381
+ * parallel phase — 독립 태스크를 병렬 실행한다.
382
+ * DAG 실행 후 verify로 이동.
383
+ */
384
+ async handleParallel(state, ctx) {
385
+ if (!state.plan) {
386
+ return {
387
+ nextPhase: "done",
388
+ updates: {},
389
+ reason: "No plan available — cannot execute in parallel",
390
+ };
391
+ }
392
+ const { steps } = state.plan;
393
+ const errors = [];
394
+ const results = [];
395
+ // 의존성 없는 step은 병렬, 의존성 있는 step은 순차
396
+ const independent = steps.filter((s) => s.dependsOn.length === 0);
397
+ const dependent = steps.filter((s) => s.dependsOn.length > 0);
398
+ // 독립 step 병렬 실행
399
+ const parallelPromises = independent.map(async (_, idx) => {
400
+ const stepIdx = steps.indexOf(independent[idx]);
401
+ try {
402
+ const stepResult = await ctx.executeFn(state.plan, stepIdx, state);
403
+ return { stepResult, stepIdx, error: null };
404
+ }
405
+ catch (err) {
406
+ const errorMessage = err instanceof Error ? err.message : String(err);
407
+ return { stepResult: null, stepIdx, error: errorMessage };
408
+ }
409
+ });
410
+ const parallelResults = await Promise.all(parallelPromises);
411
+ for (const pr of parallelResults) {
412
+ if (pr.stepResult) {
413
+ results.push(pr.stepResult);
414
+ this.emit("step:complete", pr.stepResult);
415
+ state.tokenUsage.input += pr.stepResult.tokensUsed;
416
+ state.toolCalls++;
417
+ }
418
+ if (pr.error) {
419
+ const stepError = {
420
+ stepIndex: pr.stepIdx,
421
+ phase: "parallel",
422
+ error: pr.error,
423
+ recoverable: true,
424
+ };
425
+ errors.push(stepError);
426
+ this.emit("step:error", stepError);
427
+ }
428
+ }
429
+ // 의존 step 순차 실행
430
+ for (const step of dependent) {
431
+ const stepIdx = steps.indexOf(step);
432
+ try {
433
+ const stepResult = await ctx.executeFn(state.plan, stepIdx, state);
434
+ results.push(stepResult);
435
+ this.emit("step:complete", stepResult);
436
+ state.tokenUsage.input += stepResult.tokensUsed;
437
+ state.toolCalls++;
438
+ }
439
+ catch (err) {
440
+ const errorMessage = err instanceof Error ? err.message : String(err);
441
+ const stepError = {
442
+ stepIndex: stepIdx,
443
+ phase: "parallel",
444
+ error: errorMessage,
445
+ recoverable: true,
446
+ };
447
+ errors.push(stepError);
448
+ this.emit("step:error", stepError);
449
+ }
450
+ }
451
+ return {
452
+ nextPhase: "verify",
453
+ updates: {
454
+ stepResults: [...state.stepResults, ...results],
455
+ errors: [...state.errors, ...errors],
456
+ currentStepIndex: steps.length,
457
+ },
458
+ reason: `Parallel execution complete: ${results.length} succeeded, ${errors.length} failed`,
459
+ };
460
+ }
461
+ /**
462
+ * delegate phase — 사용자에게 질문/승인을 요청한다.
463
+ * 피드백에 따라 analyze 또는 plan으로 전이.
464
+ */
465
+ async handleDelegate(state, ctx) {
466
+ // 접근법 선택 위임인 경우 질문 생성
467
+ let question = "Please provide guidance on how to proceed.";
468
+ if (state.approaches.length > 0 && state.selectedApproach < 0) {
469
+ const optionsList = state.approaches
470
+ .map((a) => `[${a.id}] ${a.name} (${a.estimatedComplexity}): ${a.description}`)
471
+ .join("\n");
472
+ question = `Multiple approaches available. Please select one:\n${optionsList}`;
473
+ }
474
+ const startMs = Date.now();
475
+ const feedback = await ctx.delegateFn(question);
476
+ const durationMs = Date.now() - startMs;
477
+ state.userFeedback.push(feedback);
478
+ const result = {
479
+ stepIndex: state.stepResults.length,
480
+ phase: "delegate",
481
+ success: true,
482
+ output: `User feedback received: ${feedback}`,
483
+ changedFiles: [],
484
+ tokensUsed: 0,
485
+ durationMs,
486
+ };
487
+ state.stepResults.push(result);
488
+ this.emit("step:complete", result);
489
+ // 피드백에서 접근법 번호 추출 시도
490
+ const approachId = parseInt(feedback.trim(), 10);
491
+ if (!isNaN(approachId) &&
492
+ state.approaches.some((a) => a.id === approachId)) {
493
+ const idx = state.approaches.findIndex((a) => a.id === approachId);
494
+ return {
495
+ nextPhase: "plan",
496
+ updates: { selectedApproach: idx },
497
+ reason: `User selected approach: ${state.approaches[idx].name}`,
498
+ };
499
+ }
500
+ // 특정 접근법 선택이 아니면 새로운 컨텍스트로 re-analyze
501
+ return {
502
+ nextPhase: "analyze",
503
+ updates: {},
504
+ reason: `User provided general feedback — re-analyzing`,
505
+ };
506
+ }
507
+ /**
508
+ * verify phase — 실행 결과를 검증한다.
509
+ * pass → done, concern + fixAttempts < max → fix, fail → replan.
510
+ */
511
+ async handleVerify(state, ctx) {
512
+ const startMs = Date.now();
513
+ const verifyResult = await ctx.verifyFn(state);
514
+ const durationMs = Date.now() - startMs;
515
+ state.reflections.push(verifyResult);
516
+ this.emit("verify:result", verifyResult);
517
+ const result = {
518
+ stepIndex: state.stepResults.length,
519
+ phase: "verify",
520
+ success: verifyResult.verdict === "pass",
521
+ output: `Verdict: ${verifyResult.verdict} (confidence: ${verifyResult.confidence}). Issues: ${verifyResult.issues.length}`,
522
+ changedFiles: [],
523
+ tokensUsed: 0,
524
+ durationMs,
525
+ };
526
+ state.stepResults.push(result);
527
+ this.emit("step:complete", result);
528
+ if (verifyResult.verdict === "pass") {
529
+ return {
530
+ nextPhase: "done",
531
+ updates: {},
532
+ reason: `Verification passed (confidence: ${verifyResult.confidence})`,
533
+ };
534
+ }
535
+ if (verifyResult.verdict === "concern" &&
536
+ state.fixAttempts < state.maxFixAttempts) {
537
+ return {
538
+ nextPhase: "fix",
539
+ updates: {},
540
+ reason: `Verification found concerns: ${verifyResult.issues.join("; ")}`,
541
+ };
542
+ }
543
+ if (verifyResult.verdict === "fail") {
544
+ return {
545
+ nextPhase: "replan",
546
+ updates: {},
547
+ reason: `Verification failed: ${verifyResult.issues.join("; ")}`,
548
+ };
549
+ }
550
+ // concern but fix attempts exhausted — done with warning
551
+ return {
552
+ nextPhase: "done",
553
+ updates: {},
554
+ reason: `Verification has concerns but fix attempts exhausted (${state.fixAttempts}/${state.maxFixAttempts})`,
555
+ };
556
+ }
557
+ /**
558
+ * fix phase — 에러를 자동 수정한다.
559
+ * fixAttempts를 증가시키고 verify로 복귀.
560
+ */
561
+ async handleFix(state, ctx) {
562
+ // 수정 대상 에러 수집 (최근 에러 + 검증 이슈)
563
+ const errorsToFix = [];
564
+ // 실행 중 발생한 에러
565
+ const recentErrors = state.errors.filter((e) => e.recoverable);
566
+ errorsToFix.push(...recentErrors);
567
+ // 검증에서 발견된 이슈를 StepError로 변환
568
+ const lastReflection = state.reflections[state.reflections.length - 1];
569
+ if (lastReflection) {
570
+ for (const issue of lastReflection.issues) {
571
+ errorsToFix.push({
572
+ stepIndex: state.currentStepIndex,
573
+ phase: "verify",
574
+ error: issue,
575
+ recoverable: true,
576
+ });
577
+ }
578
+ }
579
+ if (errorsToFix.length === 0) {
580
+ // 수정할 에러 없음 — done으로
581
+ return {
582
+ nextPhase: "done",
583
+ updates: {},
584
+ reason: "No fixable errors found",
585
+ };
586
+ }
587
+ const startMs = Date.now();
588
+ const fixResult = await ctx.fixFn(errorsToFix, state);
589
+ const durationMs = Date.now() - startMs;
590
+ state.stepResults.push(fixResult);
591
+ state.fixAttempts++;
592
+ this.emit("step:complete", fixResult);
593
+ state.tokenUsage.input += fixResult.tokensUsed;
594
+ state.toolCalls++;
595
+ return {
596
+ nextPhase: "verify",
597
+ updates: {
598
+ fixAttempts: state.fixAttempts,
599
+ },
600
+ reason: `Fix attempt ${state.fixAttempts}/${state.maxFixAttempts} — re-verifying`,
601
+ };
602
+ }
603
+ /**
604
+ * replan phase — 실패 후 재계획을 수립한다.
605
+ * maxReplanAttempts 초과 시 done으로 (경고 포함).
606
+ */
607
+ async handleReplan(state, ctx) {
608
+ this.replanCount++;
609
+ if (this.replanCount > this.config.maxReplanAttempts) {
610
+ return {
611
+ nextPhase: "done",
612
+ updates: {},
613
+ reason: `Replan attempts exhausted (${this.replanCount - 1}/${this.config.maxReplanAttempts}). Stopping with partial results.`,
614
+ };
615
+ }
616
+ const startMs = Date.now();
617
+ const newPlan = await ctx.replanFn(state);
618
+ const durationMs = Date.now() - startMs;
619
+ const result = {
620
+ stepIndex: state.stepResults.length,
621
+ phase: "replan",
622
+ success: true,
623
+ output: `Replanned with ${newPlan.steps.length} step(s) (attempt ${this.replanCount}/${this.config.maxReplanAttempts})`,
624
+ changedFiles: [],
625
+ tokensUsed: 0,
626
+ durationMs,
627
+ };
628
+ state.stepResults.push(result);
629
+ this.emit("step:complete", result);
630
+ // 수정 시도 카운터 리셋 (새 계획이므로)
631
+ return {
632
+ nextPhase: "implement",
633
+ updates: {
634
+ plan: newPlan,
635
+ currentStepIndex: 0,
636
+ fixAttempts: 0,
637
+ errors: [],
638
+ },
639
+ reason: `Replanned (attempt ${this.replanCount}/${this.config.maxReplanAttempts})`,
640
+ };
641
+ }
642
+ // ─── Internal ───
643
+ /**
644
+ * phase 전이를 수행한다.
645
+ * phase:exit → 상태 업데이트 → transition 이벤트 → phase:enter.
646
+ */
647
+ async transition(next) {
648
+ const from = this.state.phase;
649
+ const to = next.nextPhase;
650
+ // Exit 이벤트
651
+ this.emit("phase:exit", from, this.state);
652
+ // 상태 업데이트 (Partial<AgentState> 적용)
653
+ for (const [key, value] of Object.entries(next.updates)) {
654
+ // Map은 특수 처리 (shallow merge 하지 않음)
655
+ this.state[key] = value;
656
+ }
657
+ // Phase 전이
658
+ this.state.phase = to;
659
+ // 전이 이벤트
660
+ this.emit("transition", from, to, next.reason);
661
+ // Enter 이벤트
662
+ this.emit("phase:enter", to, this.state);
663
+ }
664
+ /**
665
+ * 초기 상태를 생성한다.
666
+ */
667
+ createInitialState(goal) {
668
+ return {
669
+ phase: "idle",
670
+ goal,
671
+ plan: null,
672
+ currentStepIndex: 0,
673
+ approaches: [],
674
+ selectedApproach: -1,
675
+ stepResults: [],
676
+ errors: [],
677
+ reflections: [],
678
+ fixAttempts: 0,
679
+ maxFixAttempts: this.config.maxFixAttempts,
680
+ workingMemory: new Map(),
681
+ tokenUsage: { input: 0, output: 0 },
682
+ toolCalls: 0,
683
+ iterationCount: 0,
684
+ startTime: Date.now(),
685
+ userFeedback: [],
686
+ };
687
+ }
688
+ /**
689
+ * 터미널 phase인지 확인한다.
690
+ */
691
+ isTerminal(phase) {
692
+ return TERMINAL_PHASES.has(phase);
693
+ }
694
+ }
695
+ //# sourceMappingURL=state-machine.js.map