@yuaone/core 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-loop.d.ts +62 -0
- package/dist/agent-loop.d.ts.map +1 -1
- package/dist/agent-loop.js +705 -18
- package/dist/agent-loop.js.map +1 -1
- package/dist/background-agent.d.ts +110 -0
- package/dist/background-agent.d.ts.map +1 -0
- package/dist/background-agent.js +255 -0
- package/dist/background-agent.js.map +1 -0
- package/dist/coding-standards.d.ts +45 -0
- package/dist/coding-standards.d.ts.map +1 -0
- package/dist/coding-standards.js +1152 -0
- package/dist/coding-standards.js.map +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -6
- package/dist/constants.js.map +1 -1
- package/dist/context-manager.d.ts +6 -0
- package/dist/context-manager.d.ts.map +1 -1
- package/dist/context-manager.js +23 -4
- package/dist/context-manager.js.map +1 -1
- package/dist/index.d.ts +28 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -2
- package/dist/index.js.map +1 -1
- package/dist/llm-client.d.ts +8 -3
- package/dist/llm-client.d.ts.map +1 -1
- package/dist/llm-client.js +64 -13
- package/dist/llm-client.js.map +1 -1
- package/dist/plugin-auto-loader.d.ts +108 -0
- package/dist/plugin-auto-loader.d.ts.map +1 -0
- package/dist/plugin-auto-loader.js +743 -0
- package/dist/plugin-auto-loader.js.map +1 -0
- package/dist/plugin-registry.d.ts +112 -0
- package/dist/plugin-registry.d.ts.map +1 -0
- package/dist/plugin-registry.js +319 -0
- package/dist/plugin-registry.js.map +1 -0
- package/dist/plugin-types.d.ts +388 -0
- package/dist/plugin-types.d.ts.map +1 -0
- package/dist/plugin-types.js +8 -0
- package/dist/plugin-types.js.map +1 -0
- package/dist/plugin-validator.d.ts +54 -0
- package/dist/plugin-validator.d.ts.map +1 -0
- package/dist/plugin-validator.js +129 -0
- package/dist/plugin-validator.js.map +1 -0
- package/dist/repo-knowledge-graph.d.ts +112 -0
- package/dist/repo-knowledge-graph.d.ts.map +1 -0
- package/dist/repo-knowledge-graph.js +561 -0
- package/dist/repo-knowledge-graph.js.map +1 -0
- package/dist/role-registry.js +1 -1
- package/dist/role-registry.js.map +1 -1
- package/dist/self-debug-loop.d.ts +257 -0
- package/dist/self-debug-loop.d.ts.map +1 -0
- package/dist/self-debug-loop.js +870 -0
- package/dist/self-debug-loop.js.map +1 -0
- package/dist/skill-learner.d.ts +136 -0
- package/dist/skill-learner.d.ts.map +1 -0
- package/dist/skill-learner.js +382 -0
- package/dist/skill-learner.js.map +1 -0
- package/dist/skill-loader.d.ts +90 -0
- package/dist/skill-loader.d.ts.map +1 -0
- package/dist/skill-loader.js +309 -0
- package/dist/skill-loader.js.map +1 -0
- package/dist/specialist-registry.d.ts +132 -0
- package/dist/specialist-registry.d.ts.map +1 -0
- package/dist/specialist-registry.js +413 -0
- package/dist/specialist-registry.js.map +1 -0
- package/dist/sub-agent-prompts.d.ts +45 -0
- package/dist/sub-agent-prompts.d.ts.map +1 -0
- package/dist/sub-agent-prompts.js +177 -0
- package/dist/sub-agent-prompts.js.map +1 -0
- package/dist/sub-agent-router.d.ts +75 -0
- package/dist/sub-agent-router.d.ts.map +1 -0
- package/dist/sub-agent-router.js +174 -0
- package/dist/sub-agent-router.js.map +1 -0
- package/dist/sub-agent.d.ts +48 -0
- package/dist/sub-agent.d.ts.map +1 -1
- package/dist/sub-agent.js +108 -5
- package/dist/sub-agent.js.map +1 -1
- package/dist/system-prompt.d.ts +26 -0
- package/dist/system-prompt.d.ts.map +1 -1
- package/dist/system-prompt.js +177 -7
- package/dist/system-prompt.js.map +1 -1
- package/dist/task-classifier.d.ts +25 -1
- package/dist/task-classifier.d.ts.map +1 -1
- package/dist/task-classifier.js +171 -1
- package/dist/task-classifier.js.map +1 -1
- package/dist/tool-planner.d.ts +160 -0
- package/dist/tool-planner.d.ts.map +1 -0
- package/dist/tool-planner.js +501 -0
- package/dist/tool-planner.js.map +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
- package/plugins/git/patterns/branch-patterns.json +101 -0
- package/plugins/git/patterns/commit-patterns.json +186 -0
- package/plugins/git/plugin.yaml +128 -0
- package/plugins/git/skills/branch-strategy.md +172 -0
- package/plugins/git/skills/commit-conv.md +178 -0
- package/plugins/git/skills/conflict-resolve.md +159 -0
- package/plugins/git/skills/history-clean.md +199 -0
- package/plugins/git/skills/pr-review.md +196 -0
- package/plugins/git/strategies/conflict-resolve.json +244 -0
- package/plugins/git/strategies/release-flow.json +292 -0
- package/plugins/git/validators/rules.json +348 -0
- package/plugins/react/patterns/anti-patterns.json +88 -0
- package/plugins/react/patterns/components.json +80 -0
- package/plugins/react/patterns/hooks.json +72 -0
- package/plugins/react/plugin.yaml +229 -0
- package/plugins/react/skills/bugfix.md +208 -0
- package/plugins/react/skills/component-gen.md +206 -0
- package/plugins/react/skills/hook-extract.md +208 -0
- package/plugins/react/skills/ssr.md +256 -0
- package/plugins/react/skills/test.md +273 -0
- package/plugins/react/strategies/build-fix.json +43 -0
- package/plugins/react/strategies/hook-loop-fix.json +36 -0
- package/plugins/react/strategies/hydration-fix.json +42 -0
- package/plugins/react/validators/rules.json +92 -0
- package/plugins/typescript/patterns/best-practices.json +25 -0
- package/plugins/typescript/patterns/common-errors.json +32 -0
- package/plugins/typescript/plugin.yaml +74 -0
- package/plugins/typescript/skills/debug.md +23 -0
- package/plugins/typescript/skills/migration.md +24 -0
- package/plugins/typescript/skills/refactor.md +22 -0
- package/plugins/typescript/skills/strict-mode.md +23 -0
- package/plugins/typescript/strategies/strict-migration.json +37 -0
- package/plugins/typescript/strategies/type-error-fix.json +37 -0
- package/plugins/typescript/validators/rules.json +28 -0
|
@@ -0,0 +1,870 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module self-debug-loop
|
|
3
|
+
* @description Self-Debugging Loop — Aggressive auto-fix with escalating strategies.
|
|
4
|
+
*
|
|
5
|
+
* More aggressive than basic AutoFix:
|
|
6
|
+
* 1. Direct fix (read error, edit file)
|
|
7
|
+
* 2. Context expansion (read related files)
|
|
8
|
+
* 3. Alternative approach (different strategy)
|
|
9
|
+
* 4. Rollback + fresh approach
|
|
10
|
+
* 5. Escalate to user
|
|
11
|
+
*
|
|
12
|
+
* Integrates with FailureRecovery for rollback and error classification.
|
|
13
|
+
*
|
|
14
|
+
* @see auto-fix.ts for the simpler retry loop
|
|
15
|
+
* @see failure-recovery.ts for root cause analysis and strategy selection
|
|
16
|
+
*/
|
|
17
|
+
const ERROR_PATTERNS = [
|
|
18
|
+
{
|
|
19
|
+
category: "type_error",
|
|
20
|
+
patterns: [
|
|
21
|
+
/error TS\d+/i,
|
|
22
|
+
/Type '.+' is not assignable to type/i,
|
|
23
|
+
/Property '.+' does not exist on type/i,
|
|
24
|
+
/Argument of type '.+' is not assignable/i,
|
|
25
|
+
/Cannot find name '.+'/i,
|
|
26
|
+
],
|
|
27
|
+
suggestion: "Fix type annotations, add missing type definitions, or use type assertions.",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
category: "syntax_error",
|
|
31
|
+
patterns: [
|
|
32
|
+
/SyntaxError/i,
|
|
33
|
+
/Unexpected token/i,
|
|
34
|
+
/Unexpected end of/i,
|
|
35
|
+
/Missing semicolon/i,
|
|
36
|
+
/Unterminated string/i,
|
|
37
|
+
],
|
|
38
|
+
suggestion: "Fix syntax: check for missing brackets, semicolons, or malformed expressions.",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
category: "import_error",
|
|
42
|
+
patterns: [
|
|
43
|
+
/Cannot find module/i,
|
|
44
|
+
/Module not found/i,
|
|
45
|
+
/ERR_MODULE_NOT_FOUND/i,
|
|
46
|
+
/Unable to resolve/i,
|
|
47
|
+
/Could not resolve/i,
|
|
48
|
+
],
|
|
49
|
+
suggestion: "Check import paths, verify the module is installed, check tsconfig paths.",
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
category: "test_assertion",
|
|
53
|
+
patterns: [
|
|
54
|
+
/Expected .+ (to |but )?received/i,
|
|
55
|
+
/AssertionError/i,
|
|
56
|
+
/expect\(.+\)\./i,
|
|
57
|
+
/\bFAIL\b.*\btest\b/i,
|
|
58
|
+
/test failed/i,
|
|
59
|
+
],
|
|
60
|
+
suggestion: "Update test expectations or fix the code to match expected behavior.",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
category: "lint_error",
|
|
64
|
+
patterns: [
|
|
65
|
+
/eslint/i,
|
|
66
|
+
/prettier/i,
|
|
67
|
+
/\blint\b/i,
|
|
68
|
+
],
|
|
69
|
+
suggestion: "Fix lint violations per project rules.",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
category: "build_error",
|
|
73
|
+
patterns: [
|
|
74
|
+
/build failed/i,
|
|
75
|
+
/compilation failed/i,
|
|
76
|
+
/webpack.*error/i,
|
|
77
|
+
/esbuild.*error/i,
|
|
78
|
+
/rollup.*error/i,
|
|
79
|
+
],
|
|
80
|
+
suggestion: "Fix build configuration or source errors reported in build output.",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
category: "runtime_error",
|
|
84
|
+
patterns: [
|
|
85
|
+
/TypeError:/,
|
|
86
|
+
/ReferenceError:/,
|
|
87
|
+
/RangeError:/,
|
|
88
|
+
/\bError:/,
|
|
89
|
+
/Uncaught/i,
|
|
90
|
+
],
|
|
91
|
+
suggestion: "Add null checks, verify data shapes, and handle edge cases.",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
category: "timeout",
|
|
95
|
+
patterns: [
|
|
96
|
+
/\btimeout\b/i,
|
|
97
|
+
/timed out/i,
|
|
98
|
+
/ETIMEDOUT/i,
|
|
99
|
+
],
|
|
100
|
+
suggestion: "Reduce operation scope, increase timeout, or add pagination.",
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
category: "permission",
|
|
104
|
+
patterns: [
|
|
105
|
+
/EACCES/i,
|
|
106
|
+
/EPERM/i,
|
|
107
|
+
/permission denied/i,
|
|
108
|
+
],
|
|
109
|
+
suggestion: "Check file permissions or run with appropriate privileges.",
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
// ─── Strategy Order ───
|
|
113
|
+
/** Default strategy escalation order */
|
|
114
|
+
const STRATEGY_ORDER = [
|
|
115
|
+
"direct_fix",
|
|
116
|
+
"context_expand",
|
|
117
|
+
"alternative",
|
|
118
|
+
"rollback_fresh",
|
|
119
|
+
"escalate",
|
|
120
|
+
];
|
|
121
|
+
// ─── SelfDebugLoop ───
|
|
122
|
+
/**
|
|
123
|
+
* SelfDebugLoop — Aggressive auto-fix with escalating strategies.
|
|
124
|
+
*
|
|
125
|
+
* Runs an escalating loop of fix attempts:
|
|
126
|
+
* 1. direct_fix: Analyze the error and make a targeted edit
|
|
127
|
+
* 2. context_expand: Read more files to understand the full picture
|
|
128
|
+
* 3. alternative: Try a fundamentally different approach
|
|
129
|
+
* 4. rollback_fresh: Restore original files and start from scratch
|
|
130
|
+
* 5. escalate: Report to user with full analysis
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```typescript
|
|
134
|
+
* const debugLoop = new SelfDebugLoop({ maxAttempts: 5 });
|
|
135
|
+
*
|
|
136
|
+
* // Analyze an error
|
|
137
|
+
* const analysis = debugLoop.analyzeError(errorOutput);
|
|
138
|
+
* console.log(analysis.category, analysis.suggestion);
|
|
139
|
+
*
|
|
140
|
+
* // Build a fix prompt for the LLM
|
|
141
|
+
* const prompt = debugLoop.buildFixPrompt("direct_fix", {
|
|
142
|
+
* testCommand: "pnpm tsc --noEmit",
|
|
143
|
+
* errorOutput: "error TS2345: ...",
|
|
144
|
+
* changedFiles: ["src/agent.ts"],
|
|
145
|
+
* originalSnapshots: new Map(),
|
|
146
|
+
* previousAttempts: [],
|
|
147
|
+
* currentStrategy: "direct_fix",
|
|
148
|
+
* });
|
|
149
|
+
*
|
|
150
|
+
* // Run the full loop
|
|
151
|
+
* const result = await debugLoop.debug({
|
|
152
|
+
* testCommand: "pnpm tsc --noEmit",
|
|
153
|
+
* errorOutput: "error TS2345: ...",
|
|
154
|
+
* changedFiles: ["src/agent.ts"],
|
|
155
|
+
* originalSnapshots: snapshots,
|
|
156
|
+
* toolExecutor: executor,
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export class SelfDebugLoop {
|
|
161
|
+
maxAttempts;
|
|
162
|
+
constructor(config) {
|
|
163
|
+
this.maxAttempts = config?.maxAttempts ?? 5;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Run the self-debugging loop.
|
|
167
|
+
* Tries escalating strategies until tests pass or max attempts reached.
|
|
168
|
+
*
|
|
169
|
+
* @param params - Debug parameters
|
|
170
|
+
* @returns Debug result with success status, attempts, and root cause
|
|
171
|
+
*/
|
|
172
|
+
async debug(params) {
|
|
173
|
+
const { testCommand, errorOutput, changedFiles, originalSnapshots, toolExecutor, llmFixer } = params;
|
|
174
|
+
const attempts = [];
|
|
175
|
+
let currentError = errorOutput;
|
|
176
|
+
const startTime = Date.now();
|
|
177
|
+
for (let i = 0; i < this.maxAttempts; i++) {
|
|
178
|
+
const attemptStart = Date.now();
|
|
179
|
+
const strategy = this.selectStrategy(i, attempts);
|
|
180
|
+
// If we've reached escalation, stop
|
|
181
|
+
if (strategy === "escalate") {
|
|
182
|
+
const rootCause = this.analyzeError(currentError);
|
|
183
|
+
attempts.push({
|
|
184
|
+
attempt: i + 1,
|
|
185
|
+
strategy: "escalate",
|
|
186
|
+
error: currentError,
|
|
187
|
+
fix: "Escalated to user — automated fixes exhausted.",
|
|
188
|
+
filesChanged: [],
|
|
189
|
+
testResult: "error",
|
|
190
|
+
durationMs: Date.now() - attemptStart,
|
|
191
|
+
});
|
|
192
|
+
return {
|
|
193
|
+
success: false,
|
|
194
|
+
attempts,
|
|
195
|
+
totalDurationMs: Date.now() - startTime,
|
|
196
|
+
rootCause: rootCause.message,
|
|
197
|
+
finalStrategy: "escalate",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Execute rollback if that's the strategy
|
|
201
|
+
if (strategy === "rollback_fresh") {
|
|
202
|
+
await this.executeRollback(changedFiles, originalSnapshots, toolExecutor);
|
|
203
|
+
}
|
|
204
|
+
// If llmFixer is provided, build a fix prompt, get LLM response, and apply fixes
|
|
205
|
+
let fixDescription = "";
|
|
206
|
+
const attemptFilesChanged = [];
|
|
207
|
+
if (llmFixer) {
|
|
208
|
+
const debugContext = {
|
|
209
|
+
testCommand,
|
|
210
|
+
errorOutput: currentError,
|
|
211
|
+
changedFiles,
|
|
212
|
+
originalSnapshots,
|
|
213
|
+
previousAttempts: attempts,
|
|
214
|
+
currentStrategy: strategy,
|
|
215
|
+
};
|
|
216
|
+
const fixPrompt = this.buildFixPrompt(strategy, debugContext);
|
|
217
|
+
const llmResponse = await llmFixer(fixPrompt);
|
|
218
|
+
// Parse and execute tool calls from LLM response
|
|
219
|
+
const toolCalls = this.parseLlmToolCalls(llmResponse);
|
|
220
|
+
for (const call of toolCalls) {
|
|
221
|
+
try {
|
|
222
|
+
await toolExecutor.execute(call);
|
|
223
|
+
// Track files changed by file_write / file_edit calls
|
|
224
|
+
const args = typeof call.arguments === "string"
|
|
225
|
+
? JSON.parse(call.arguments)
|
|
226
|
+
: call.arguments;
|
|
227
|
+
if ((call.name === "file_write" || call.name === "file_edit") && typeof args.path === "string") {
|
|
228
|
+
attemptFilesChanged.push(args.path);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Individual tool call failure — continue with remaining calls
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
fixDescription = toolCalls.length > 0
|
|
236
|
+
? `Strategy "${strategy}": applied ${toolCalls.length} tool call(s) from LLM`
|
|
237
|
+
: `Strategy "${strategy}": LLM provided no tool calls`;
|
|
238
|
+
}
|
|
239
|
+
// Run the test command to see current state
|
|
240
|
+
const testResult = await this.runTest(testCommand, toolExecutor);
|
|
241
|
+
// If tests pass, we're done
|
|
242
|
+
if (testResult.passed) {
|
|
243
|
+
attempts.push({
|
|
244
|
+
attempt: i + 1,
|
|
245
|
+
strategy,
|
|
246
|
+
error: currentError,
|
|
247
|
+
fix: fixDescription || (strategy === "rollback_fresh"
|
|
248
|
+
? "Rolled back to original — tests now pass."
|
|
249
|
+
: "Tests pass after previous fix."),
|
|
250
|
+
filesChanged: attemptFilesChanged,
|
|
251
|
+
testResult: "pass",
|
|
252
|
+
durationMs: Date.now() - attemptStart,
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
attempts,
|
|
257
|
+
totalDurationMs: Date.now() - startTime,
|
|
258
|
+
finalStrategy: strategy,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
// Update current error from test output
|
|
262
|
+
currentError = testResult.output || currentError;
|
|
263
|
+
// Record the attempt
|
|
264
|
+
const rootAnalysis = this.analyzeError(currentError);
|
|
265
|
+
attempts.push({
|
|
266
|
+
attempt: i + 1,
|
|
267
|
+
strategy,
|
|
268
|
+
error: currentError,
|
|
269
|
+
fix: fixDescription || `Strategy "${strategy}": ${rootAnalysis.suggestion}`,
|
|
270
|
+
filesChanged: attemptFilesChanged.length > 0 ? attemptFilesChanged : changedFiles,
|
|
271
|
+
testResult: testResult.timedOut ? "timeout" : "fail",
|
|
272
|
+
durationMs: Date.now() - attemptStart,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// Max attempts exhausted
|
|
276
|
+
const finalRootCause = this.analyzeError(currentError);
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
attempts,
|
|
280
|
+
totalDurationMs: Date.now() - startTime,
|
|
281
|
+
rootCause: finalRootCause.message,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Select next strategy based on attempt number and previous results.
|
|
286
|
+
*
|
|
287
|
+
* Strategy escalation:
|
|
288
|
+
* - Attempt 0: direct_fix
|
|
289
|
+
* - Attempt 1: context_expand (or direct_fix again if first was timeout)
|
|
290
|
+
* - Attempt 2: alternative
|
|
291
|
+
* - Attempt 3: rollback_fresh
|
|
292
|
+
* - Attempt 4+: escalate
|
|
293
|
+
*
|
|
294
|
+
* Adjustments based on previous attempt results:
|
|
295
|
+
* - If last attempt was timeout, skip to alternative
|
|
296
|
+
* - If same error persists across 2+ attempts, escalate faster
|
|
297
|
+
*
|
|
298
|
+
* @param attempt - Current attempt number (0-based)
|
|
299
|
+
* @param previousAttempts - Previous debug attempts
|
|
300
|
+
* @returns Selected strategy
|
|
301
|
+
*/
|
|
302
|
+
selectStrategy(attempt, previousAttempts) {
|
|
303
|
+
// Check for repeated same errors — escalate faster
|
|
304
|
+
if (previousAttempts.length >= 2) {
|
|
305
|
+
const lastTwo = previousAttempts.slice(-2);
|
|
306
|
+
const sameError = lastTwo[0].error === lastTwo[1].error;
|
|
307
|
+
if (sameError && attempt >= 2) {
|
|
308
|
+
// Same error twice — skip ahead in escalation
|
|
309
|
+
const currentIdx = Math.min(attempt + 1, STRATEGY_ORDER.length - 1);
|
|
310
|
+
return STRATEGY_ORDER[currentIdx];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
// Check if last attempt timed out — skip context_expand
|
|
314
|
+
if (previousAttempts.length > 0) {
|
|
315
|
+
const lastAttempt = previousAttempts[previousAttempts.length - 1];
|
|
316
|
+
if (lastAttempt.testResult === "timeout" && attempt === 1) {
|
|
317
|
+
return "alternative"; // Skip context_expand for timeouts
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Default: follow the escalation order
|
|
321
|
+
if (attempt >= STRATEGY_ORDER.length) {
|
|
322
|
+
return "escalate";
|
|
323
|
+
}
|
|
324
|
+
return STRATEGY_ORDER[attempt];
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Build fix prompt for the LLM based on strategy.
|
|
328
|
+
*
|
|
329
|
+
* Each strategy produces a different prompt style:
|
|
330
|
+
* - direct_fix: Focused on the specific error with file/line info
|
|
331
|
+
* - context_expand: Asks LLM to read more files before fixing
|
|
332
|
+
* - alternative: Suggests trying a completely different approach
|
|
333
|
+
* - rollback_fresh: Files restored, start with clean slate
|
|
334
|
+
* - escalate: Summarize for user
|
|
335
|
+
*
|
|
336
|
+
* @param strategy - Selected debug strategy
|
|
337
|
+
* @param context - Full debug context
|
|
338
|
+
* @returns Formatted prompt for the LLM
|
|
339
|
+
*/
|
|
340
|
+
buildFixPrompt(strategy, context) {
|
|
341
|
+
const analysis = this.analyzeError(context.errorOutput);
|
|
342
|
+
switch (strategy) {
|
|
343
|
+
case "direct_fix":
|
|
344
|
+
return this.buildDirectFixPrompt(context, analysis);
|
|
345
|
+
case "context_expand":
|
|
346
|
+
return this.buildContextExpandPrompt(context, analysis);
|
|
347
|
+
case "alternative":
|
|
348
|
+
return this.buildAlternativePrompt(context, analysis);
|
|
349
|
+
case "rollback_fresh":
|
|
350
|
+
return this.buildRollbackFreshPrompt(context, analysis);
|
|
351
|
+
case "escalate":
|
|
352
|
+
return this.buildEscalatePrompt(context, analysis);
|
|
353
|
+
default:
|
|
354
|
+
return this.buildDirectFixPrompt(context, analysis);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Analyze error output for root cause.
|
|
359
|
+
*
|
|
360
|
+
* Extracts:
|
|
361
|
+
* - Error category (type, syntax, import, etc.)
|
|
362
|
+
* - File and line information
|
|
363
|
+
* - Error codes (e.g., TS2345)
|
|
364
|
+
* - Suggested fix approach
|
|
365
|
+
*
|
|
366
|
+
* @param errorOutput - Raw error output string
|
|
367
|
+
* @returns Root cause analysis
|
|
368
|
+
*/
|
|
369
|
+
analyzeError(errorOutput) {
|
|
370
|
+
// Try each pattern group
|
|
371
|
+
for (const group of ERROR_PATTERNS) {
|
|
372
|
+
for (const pattern of group.patterns) {
|
|
373
|
+
if (pattern.test(errorOutput)) {
|
|
374
|
+
const { file, line, column } = this.extractLocation(errorOutput);
|
|
375
|
+
const errorCodes = this.extractErrorCodes(errorOutput);
|
|
376
|
+
const message = this.extractCleanMessage(errorOutput);
|
|
377
|
+
const confidence = this.computeAnalysisConfidence(errorOutput, group.category, errorCodes);
|
|
378
|
+
return {
|
|
379
|
+
category: group.category,
|
|
380
|
+
message,
|
|
381
|
+
file,
|
|
382
|
+
line,
|
|
383
|
+
column,
|
|
384
|
+
suggestion: group.suggestion,
|
|
385
|
+
confidence,
|
|
386
|
+
...(errorCodes.length > 0 ? { errorCodes } : {}),
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
// No pattern matched
|
|
392
|
+
return {
|
|
393
|
+
category: "unknown",
|
|
394
|
+
message: this.extractCleanMessage(errorOutput),
|
|
395
|
+
suggestion: "Investigate the error output manually and apply an appropriate fix.",
|
|
396
|
+
confidence: 0.2,
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Get the maximum number of attempts configured.
|
|
401
|
+
*/
|
|
402
|
+
getMaxAttempts() {
|
|
403
|
+
return this.maxAttempts;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Get the strategy escalation order.
|
|
407
|
+
*/
|
|
408
|
+
getStrategyOrder() {
|
|
409
|
+
return STRATEGY_ORDER;
|
|
410
|
+
}
|
|
411
|
+
// ─── Private: Prompt Builders ───
|
|
412
|
+
buildDirectFixPrompt(context, analysis) {
|
|
413
|
+
const lines = [
|
|
414
|
+
`[SELF-DEBUG: DIRECT FIX — Attempt ${context.previousAttempts.length + 1}/${this.maxAttempts}]`,
|
|
415
|
+
"",
|
|
416
|
+
"An error occurred. Analyze and fix it directly.",
|
|
417
|
+
"",
|
|
418
|
+
"## Error Output",
|
|
419
|
+
"```",
|
|
420
|
+
this.truncate(context.errorOutput, 2500),
|
|
421
|
+
"```",
|
|
422
|
+
"",
|
|
423
|
+
"## Analysis",
|
|
424
|
+
`Category: ${analysis.category}`,
|
|
425
|
+
`Root cause: ${analysis.message}`,
|
|
426
|
+
];
|
|
427
|
+
if (analysis.file) {
|
|
428
|
+
lines.push(`File: ${analysis.file}${analysis.line ? `:${analysis.line}` : ""}`);
|
|
429
|
+
}
|
|
430
|
+
if (analysis.errorCodes?.length) {
|
|
431
|
+
lines.push(`Error codes: ${analysis.errorCodes.join(", ")}`);
|
|
432
|
+
}
|
|
433
|
+
lines.push("");
|
|
434
|
+
lines.push("## Instructions");
|
|
435
|
+
lines.push(`- ${analysis.suggestion}`);
|
|
436
|
+
lines.push("- Make minimal, targeted changes.");
|
|
437
|
+
lines.push("- Focus on the root cause, not symptoms.");
|
|
438
|
+
if (context.changedFiles.length > 0) {
|
|
439
|
+
lines.push("");
|
|
440
|
+
lines.push(`## Changed Files`);
|
|
441
|
+
for (const f of context.changedFiles) {
|
|
442
|
+
lines.push(` - ${f}`);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
lines.push("");
|
|
446
|
+
lines.push(`## Verify Command: \`${context.testCommand}\``);
|
|
447
|
+
return lines.join("\n");
|
|
448
|
+
}
|
|
449
|
+
buildContextExpandPrompt(context, analysis) {
|
|
450
|
+
const lines = [
|
|
451
|
+
`[SELF-DEBUG: CONTEXT EXPAND — Attempt ${context.previousAttempts.length + 1}/${this.maxAttempts}]`,
|
|
452
|
+
"",
|
|
453
|
+
"The direct fix didn't work. Read more context before trying again.",
|
|
454
|
+
"",
|
|
455
|
+
"## Error Output",
|
|
456
|
+
"```",
|
|
457
|
+
this.truncate(context.errorOutput, 2000),
|
|
458
|
+
"```",
|
|
459
|
+
"",
|
|
460
|
+
"## Instructions",
|
|
461
|
+
"1. Read related files to understand the full data flow.",
|
|
462
|
+
"2. Trace imports and dependencies of the failing file.",
|
|
463
|
+
"3. Check type definitions that might be involved.",
|
|
464
|
+
"4. Then apply a fix with full understanding of the context.",
|
|
465
|
+
"",
|
|
466
|
+
`Root cause: ${analysis.message}`,
|
|
467
|
+
`Suggestion: ${analysis.suggestion}`,
|
|
468
|
+
];
|
|
469
|
+
if (analysis.file) {
|
|
470
|
+
lines.push("");
|
|
471
|
+
lines.push(`Start by reading: ${analysis.file}`);
|
|
472
|
+
lines.push("Then trace its imports and dependents.");
|
|
473
|
+
}
|
|
474
|
+
if (context.previousAttempts.length > 0) {
|
|
475
|
+
lines.push("");
|
|
476
|
+
lines.push("## Previous Attempts (avoid repeating)");
|
|
477
|
+
for (const prev of context.previousAttempts) {
|
|
478
|
+
lines.push(` - Attempt ${prev.attempt} (${prev.strategy}): ${prev.fix} → ${prev.testResult}`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return lines.join("\n");
|
|
482
|
+
}
|
|
483
|
+
buildAlternativePrompt(context, analysis) {
|
|
484
|
+
const alternatives = this.suggestAlternatives(analysis);
|
|
485
|
+
const lines = [
|
|
486
|
+
`[SELF-DEBUG: ALTERNATIVE APPROACH — Attempt ${context.previousAttempts.length + 1}/${this.maxAttempts}]`,
|
|
487
|
+
"",
|
|
488
|
+
"Previous approaches failed. Try a fundamentally different strategy.",
|
|
489
|
+
"",
|
|
490
|
+
"## Error Output",
|
|
491
|
+
"```",
|
|
492
|
+
this.truncate(context.errorOutput, 1500),
|
|
493
|
+
"```",
|
|
494
|
+
"",
|
|
495
|
+
"## Alternative Approaches",
|
|
496
|
+
];
|
|
497
|
+
for (let i = 0; i < alternatives.length; i++) {
|
|
498
|
+
lines.push(`${i + 1}. ${alternatives[i]}`);
|
|
499
|
+
}
|
|
500
|
+
lines.push("");
|
|
501
|
+
lines.push("## Requirements");
|
|
502
|
+
lines.push("- Do NOT repeat previous approaches.");
|
|
503
|
+
lines.push("- Use a fundamentally different pattern/API/strategy.");
|
|
504
|
+
lines.push("- Start with the simplest possible implementation.");
|
|
505
|
+
if (context.previousAttempts.length > 0) {
|
|
506
|
+
lines.push("");
|
|
507
|
+
lines.push("## Failed Approaches (do NOT repeat)");
|
|
508
|
+
for (const prev of context.previousAttempts) {
|
|
509
|
+
lines.push(` - ${prev.fix}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return lines.join("\n");
|
|
513
|
+
}
|
|
514
|
+
buildRollbackFreshPrompt(context, analysis) {
|
|
515
|
+
const lines = [
|
|
516
|
+
`[SELF-DEBUG: ROLLBACK + FRESH START — Attempt ${context.previousAttempts.length + 1}/${this.maxAttempts}]`,
|
|
517
|
+
"",
|
|
518
|
+
"All changed files have been restored to their original state.",
|
|
519
|
+
"Start fresh with a completely new approach.",
|
|
520
|
+
"",
|
|
521
|
+
"## Original Error",
|
|
522
|
+
"```",
|
|
523
|
+
this.truncate(context.errorOutput, 1500),
|
|
524
|
+
"```",
|
|
525
|
+
"",
|
|
526
|
+
"## Root Cause Analysis",
|
|
527
|
+
`Category: ${analysis.category}`,
|
|
528
|
+
`Message: ${analysis.message}`,
|
|
529
|
+
`Suggestion: ${analysis.suggestion}`,
|
|
530
|
+
"",
|
|
531
|
+
"## Instructions",
|
|
532
|
+
"- Files are back to their original state.",
|
|
533
|
+
"- Think step by step about the correct approach.",
|
|
534
|
+
"- Read the relevant code BEFORE making changes.",
|
|
535
|
+
"- Make incremental changes and verify each step.",
|
|
536
|
+
];
|
|
537
|
+
if (context.previousAttempts.length > 0) {
|
|
538
|
+
lines.push("");
|
|
539
|
+
lines.push(`## What NOT to do (${context.previousAttempts.length} approaches failed)`);
|
|
540
|
+
for (const prev of context.previousAttempts) {
|
|
541
|
+
lines.push(` - ${prev.strategy}: ${prev.fix}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
lines.push("");
|
|
545
|
+
lines.push(`## Verify: \`${context.testCommand}\``);
|
|
546
|
+
return lines.join("\n");
|
|
547
|
+
}
|
|
548
|
+
buildEscalatePrompt(context, analysis) {
|
|
549
|
+
const lines = [
|
|
550
|
+
"[SELF-DEBUG: ESCALATE — User Help Needed]",
|
|
551
|
+
"",
|
|
552
|
+
`After ${context.previousAttempts.length} automated fix attempts, the issue could not be resolved.`,
|
|
553
|
+
"",
|
|
554
|
+
"## Error",
|
|
555
|
+
"```",
|
|
556
|
+
this.truncate(context.errorOutput, 2000),
|
|
557
|
+
"```",
|
|
558
|
+
"",
|
|
559
|
+
"## Root Cause",
|
|
560
|
+
`Category: ${analysis.category}`,
|
|
561
|
+
`Message: ${analysis.message}`,
|
|
562
|
+
];
|
|
563
|
+
if (analysis.file) {
|
|
564
|
+
lines.push(`File: ${analysis.file}${analysis.line ? `:${analysis.line}` : ""}`);
|
|
565
|
+
}
|
|
566
|
+
lines.push("");
|
|
567
|
+
lines.push("## Attempts Made");
|
|
568
|
+
for (const prev of context.previousAttempts) {
|
|
569
|
+
lines.push(` ${prev.attempt}. [${prev.strategy}] ${prev.fix} → ${prev.testResult} (${prev.durationMs}ms)`);
|
|
570
|
+
}
|
|
571
|
+
lines.push("");
|
|
572
|
+
lines.push("## Suggested Next Steps");
|
|
573
|
+
lines.push("- Review the error output and provide guidance.");
|
|
574
|
+
lines.push("- Check if the project environment is set up correctly.");
|
|
575
|
+
lines.push(`- ${analysis.suggestion}`);
|
|
576
|
+
return lines.join("\n");
|
|
577
|
+
}
|
|
578
|
+
// ─── Private: Error Analysis Helpers ───
|
|
579
|
+
/**
|
|
580
|
+
* Extract file location from error output.
|
|
581
|
+
*/
|
|
582
|
+
extractLocation(error) {
|
|
583
|
+
// TypeScript style: src/foo.ts(10,5)
|
|
584
|
+
const tsMatch = error.match(/([^\s(]+\.(?:ts|tsx|js|jsx|mjs|cjs))\((\d+),(\d+)\)/);
|
|
585
|
+
if (tsMatch) {
|
|
586
|
+
return {
|
|
587
|
+
file: tsMatch[1],
|
|
588
|
+
line: parseInt(tsMatch[2], 10),
|
|
589
|
+
column: parseInt(tsMatch[3], 10),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
// Colon style: src/foo.ts:10:5
|
|
593
|
+
const colonMatch = error.match(/([^\s:]+\.(?:ts|tsx|js|jsx|mjs|cjs)):(\d+):(\d+)/);
|
|
594
|
+
if (colonMatch) {
|
|
595
|
+
return {
|
|
596
|
+
file: colonMatch[1],
|
|
597
|
+
line: parseInt(colonMatch[2], 10),
|
|
598
|
+
column: parseInt(colonMatch[3], 10),
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
// Just file with line: src/foo.ts:10
|
|
602
|
+
const fileLineMatch = error.match(/([^\s:]+\.(?:ts|tsx|js|jsx|mjs|cjs)):(\d+)/);
|
|
603
|
+
if (fileLineMatch) {
|
|
604
|
+
return {
|
|
605
|
+
file: fileLineMatch[1],
|
|
606
|
+
line: parseInt(fileLineMatch[2], 10),
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
// Just file
|
|
610
|
+
const fileMatch = error.match(/([^\s]+\.(?:ts|tsx|js|jsx|mjs|cjs))/);
|
|
611
|
+
if (fileMatch) {
|
|
612
|
+
return { file: fileMatch[1] };
|
|
613
|
+
}
|
|
614
|
+
return {};
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Extract error codes like TS2345, TS2322, etc.
|
|
618
|
+
*/
|
|
619
|
+
extractErrorCodes(error) {
|
|
620
|
+
const codes = [];
|
|
621
|
+
const codeRegex = /\b(TS\d{4})\b/g;
|
|
622
|
+
let match;
|
|
623
|
+
while ((match = codeRegex.exec(error)) !== null) {
|
|
624
|
+
if (!codes.includes(match[1])) {
|
|
625
|
+
codes.push(match[1]);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return codes;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Extract a clean error message from raw output.
|
|
632
|
+
*/
|
|
633
|
+
extractCleanMessage(error) {
|
|
634
|
+
const lines = error.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
635
|
+
// Look for error lines
|
|
636
|
+
for (const line of lines) {
|
|
637
|
+
if (/^(error|Error|TypeError|ReferenceError|SyntaxError)/i.test(line)) {
|
|
638
|
+
return this.truncate(line, 300);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
// Look for "error TS" patterns
|
|
642
|
+
for (const line of lines) {
|
|
643
|
+
if (/error TS\d+/i.test(line)) {
|
|
644
|
+
return this.truncate(line, 300);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Look for "FAIL" lines
|
|
648
|
+
for (const line of lines) {
|
|
649
|
+
if (/\bFAIL\b/.test(line)) {
|
|
650
|
+
return this.truncate(line, 300);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// Fallback: first non-empty line
|
|
654
|
+
return this.truncate(lines[0] ?? error, 300);
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Compute confidence in the error analysis.
|
|
658
|
+
*/
|
|
659
|
+
computeAnalysisConfidence(error, category, errorCodes) {
|
|
660
|
+
let confidence = 0.5; // base
|
|
661
|
+
// Error codes are very specific
|
|
662
|
+
if (errorCodes.length > 0) {
|
|
663
|
+
confidence += 0.2;
|
|
664
|
+
}
|
|
665
|
+
// Multiple pattern matches in same category boost confidence
|
|
666
|
+
const group = ERROR_PATTERNS.find((g) => g.category === category);
|
|
667
|
+
if (group) {
|
|
668
|
+
const matchCount = group.patterns.filter((p) => p.test(error)).length;
|
|
669
|
+
if (matchCount > 1) {
|
|
670
|
+
confidence += 0.1 * Math.min(matchCount - 1, 3);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
// File location found boosts confidence
|
|
674
|
+
const { file } = this.extractLocation(error);
|
|
675
|
+
if (file) {
|
|
676
|
+
confidence += 0.1;
|
|
677
|
+
}
|
|
678
|
+
return Math.min(confidence, 0.95);
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Suggest alternative approaches based on error category.
|
|
682
|
+
*/
|
|
683
|
+
suggestAlternatives(analysis) {
|
|
684
|
+
switch (analysis.category) {
|
|
685
|
+
case "type_error":
|
|
686
|
+
return [
|
|
687
|
+
"Use `as unknown as TargetType` to bypass strict type checking temporarily.",
|
|
688
|
+
"Simplify the type structure — replace complex generics with simpler types.",
|
|
689
|
+
"Break the operation into smaller type-safe helper functions.",
|
|
690
|
+
"Add an overload signature or use a type predicate.",
|
|
691
|
+
];
|
|
692
|
+
case "syntax_error":
|
|
693
|
+
return [
|
|
694
|
+
"Rewrite the problematic expression from scratch.",
|
|
695
|
+
"Use a simpler construct (e.g., if/else instead of ternary chains).",
|
|
696
|
+
"Check for encoding issues or invisible characters.",
|
|
697
|
+
];
|
|
698
|
+
case "import_error":
|
|
699
|
+
return [
|
|
700
|
+
"Use a relative path instead of a package/alias path.",
|
|
701
|
+
"Check if the module needs to be installed (pnpm add).",
|
|
702
|
+
"Try importing from a different entry point.",
|
|
703
|
+
"Check tsconfig paths and verify the alias mapping.",
|
|
704
|
+
];
|
|
705
|
+
case "test_assertion":
|
|
706
|
+
return [
|
|
707
|
+
"Update expected values to match current behavior.",
|
|
708
|
+
"Mock the dependency that produces different output.",
|
|
709
|
+
"Simplify the test to focus on one assertion at a time.",
|
|
710
|
+
];
|
|
711
|
+
case "runtime_error":
|
|
712
|
+
return [
|
|
713
|
+
"Add defensive null/undefined checks throughout the call chain.",
|
|
714
|
+
"Wrap in try/catch and add explicit error handling.",
|
|
715
|
+
"Validate input data before passing it to the function.",
|
|
716
|
+
];
|
|
717
|
+
case "build_error":
|
|
718
|
+
return [
|
|
719
|
+
"Check for incompatible library versions in package.json.",
|
|
720
|
+
"Try removing node_modules and reinstalling.",
|
|
721
|
+
"Simplify the build target to isolate the issue.",
|
|
722
|
+
];
|
|
723
|
+
case "timeout":
|
|
724
|
+
return [
|
|
725
|
+
"Reduce the data size or add pagination.",
|
|
726
|
+
"Increase the timeout limit if the operation is inherently slow.",
|
|
727
|
+
"Use async/streaming to process data incrementally.",
|
|
728
|
+
];
|
|
729
|
+
default:
|
|
730
|
+
return [
|
|
731
|
+
"Try a simpler implementation that avoids the problematic pattern.",
|
|
732
|
+
"Break the task into smaller, independently testable steps.",
|
|
733
|
+
"Read project documentation for the expected approach.",
|
|
734
|
+
];
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// ─── Private: Execution Helpers ───
|
|
738
|
+
/**
|
|
739
|
+
* Run the test/verify command and return result.
|
|
740
|
+
*/
|
|
741
|
+
async runTest(testCommand, toolExecutor) {
|
|
742
|
+
try {
|
|
743
|
+
const result = await toolExecutor.execute({
|
|
744
|
+
id: `self-debug-test-${Date.now()}`,
|
|
745
|
+
name: "shell_exec",
|
|
746
|
+
arguments: { command: testCommand },
|
|
747
|
+
});
|
|
748
|
+
return {
|
|
749
|
+
passed: result.success,
|
|
750
|
+
output: result.output,
|
|
751
|
+
timedOut: result.output.toLowerCase().includes("timeout") ||
|
|
752
|
+
result.output.toLowerCase().includes("timed out"),
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
catch (err) {
|
|
756
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
757
|
+
return {
|
|
758
|
+
passed: false,
|
|
759
|
+
output: message,
|
|
760
|
+
timedOut: message.toLowerCase().includes("timeout"),
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Execute a rollback by restoring original file contents via the tool executor.
|
|
766
|
+
*/
|
|
767
|
+
async executeRollback(changedFiles, originalSnapshots, toolExecutor) {
|
|
768
|
+
let allSuccess = true;
|
|
769
|
+
for (const filePath of changedFiles) {
|
|
770
|
+
const originalContent = originalSnapshots.get(filePath);
|
|
771
|
+
if (originalContent === undefined)
|
|
772
|
+
continue;
|
|
773
|
+
try {
|
|
774
|
+
await toolExecutor.execute({
|
|
775
|
+
id: `self-debug-rollback-${Date.now()}`,
|
|
776
|
+
name: "file_write",
|
|
777
|
+
arguments: {
|
|
778
|
+
path: filePath,
|
|
779
|
+
content: originalContent,
|
|
780
|
+
},
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
catch {
|
|
784
|
+
allSuccess = false;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return allSuccess;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Parse LLM response text for tool calls.
|
|
791
|
+
*
|
|
792
|
+
* Looks for JSON tool call blocks in the LLM response. Supports two formats:
|
|
793
|
+
* 1. Fenced JSON blocks with a "tool_calls" array
|
|
794
|
+
* 2. Individual fenced JSON objects with "name" and "arguments" fields
|
|
795
|
+
*
|
|
796
|
+
* @param response - Raw LLM response text
|
|
797
|
+
* @returns Array of parsed ToolCall objects
|
|
798
|
+
*/
|
|
799
|
+
parseLlmToolCalls(response) {
|
|
800
|
+
const toolCalls = [];
|
|
801
|
+
// Extract all JSON code blocks from the response
|
|
802
|
+
const jsonBlocks = response.match(/```(?:json)?\s*\n([\s\S]*?)\n\s*```/g);
|
|
803
|
+
if (!jsonBlocks)
|
|
804
|
+
return toolCalls;
|
|
805
|
+
for (const block of jsonBlocks) {
|
|
806
|
+
const jsonContent = block.replace(/```(?:json)?\s*\n/, "").replace(/\n\s*```$/, "").trim();
|
|
807
|
+
try {
|
|
808
|
+
const parsed = JSON.parse(jsonContent);
|
|
809
|
+
// Format 1: { "tool_calls": [...] }
|
|
810
|
+
if (parsed !== null &&
|
|
811
|
+
typeof parsed === "object" &&
|
|
812
|
+
"tool_calls" in parsed &&
|
|
813
|
+
Array.isArray(parsed.tool_calls)) {
|
|
814
|
+
for (const tc of parsed.tool_calls) {
|
|
815
|
+
const call = this.validateToolCall(tc);
|
|
816
|
+
if (call)
|
|
817
|
+
toolCalls.push(call);
|
|
818
|
+
}
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
// Format 2: Array of tool calls
|
|
822
|
+
if (Array.isArray(parsed)) {
|
|
823
|
+
for (const tc of parsed) {
|
|
824
|
+
const call = this.validateToolCall(tc);
|
|
825
|
+
if (call)
|
|
826
|
+
toolCalls.push(call);
|
|
827
|
+
}
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
// Format 3: Single tool call object
|
|
831
|
+
const call = this.validateToolCall(parsed);
|
|
832
|
+
if (call)
|
|
833
|
+
toolCalls.push(call);
|
|
834
|
+
}
|
|
835
|
+
catch {
|
|
836
|
+
// Not valid JSON — skip this block
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return toolCalls;
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Validate and normalize a parsed object into a ToolCall.
|
|
843
|
+
*/
|
|
844
|
+
validateToolCall(obj) {
|
|
845
|
+
if (obj === null || typeof obj !== "object")
|
|
846
|
+
return null;
|
|
847
|
+
const record = obj;
|
|
848
|
+
if (typeof record.name !== "string")
|
|
849
|
+
return null;
|
|
850
|
+
if (record.arguments === undefined)
|
|
851
|
+
return null;
|
|
852
|
+
return {
|
|
853
|
+
id: typeof record.id === "string" ? record.id : `self-debug-fix-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
854
|
+
name: record.name,
|
|
855
|
+
arguments: typeof record.arguments === "string"
|
|
856
|
+
? record.arguments
|
|
857
|
+
: record.arguments,
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Truncate text with ellipsis indicator.
|
|
862
|
+
*/
|
|
863
|
+
truncate(text, maxLength) {
|
|
864
|
+
if (text.length <= maxLength)
|
|
865
|
+
return text;
|
|
866
|
+
const half = Math.floor(maxLength / 2);
|
|
867
|
+
return text.slice(0, half) + "\n... [truncated] ...\n" + text.slice(-half);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
//# sourceMappingURL=self-debug-loop.js.map
|