@kernel.chat/kbot 3.9.0 → 3.11.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/a2a-client.d.ts +162 -0
- package/dist/a2a-client.d.ts.map +1 -0
- package/dist/a2a-client.js +376 -0
- package/dist/a2a-client.js.map +1 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +96 -5
- package/dist/agent.js.map +1 -1
- package/dist/cli.js +295 -3
- package/dist/cli.js.map +1 -1
- package/dist/mcp-apps.d.ts +90 -0
- package/dist/mcp-apps.d.ts.map +1 -0
- package/dist/mcp-apps.js +497 -0
- package/dist/mcp-apps.js.map +1 -0
- package/dist/memory-synthesis.d.ts +67 -0
- package/dist/memory-synthesis.d.ts.map +1 -0
- package/dist/memory-synthesis.js +557 -0
- package/dist/memory-synthesis.js.map +1 -0
- package/dist/prompt-evolution.d.ts +92 -0
- package/dist/prompt-evolution.d.ts.map +1 -0
- package/dist/prompt-evolution.js +371 -0
- package/dist/prompt-evolution.js.map +1 -0
- package/dist/reflection.d.ts +33 -0
- package/dist/reflection.d.ts.map +1 -0
- package/dist/reflection.js +368 -0
- package/dist/reflection.js.map +1 -0
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +56 -6
- package/dist/serve.js.map +1 -1
- package/dist/skill-library.d.ts +60 -0
- package/dist/skill-library.d.ts.map +1 -0
- package/dist/skill-library.js +475 -0
- package/dist/skill-library.js.map +1 -0
- package/dist/spec.d.ts +23 -0
- package/dist/spec.d.ts.map +1 -0
- package/dist/spec.js +177 -0
- package/dist/spec.js.map +1 -0
- package/dist/tool-pipeline.d.ts +7 -0
- package/dist/tool-pipeline.d.ts.map +1 -1
- package/dist/tool-pipeline.js +32 -0
- package/dist/tool-pipeline.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tree-planner.d.ts +63 -0
- package/dist/tree-planner.d.ts.map +1 -0
- package/dist/tree-planner.js +818 -0
- package/dist/tree-planner.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,818 @@
|
|
|
1
|
+
// kbot LATS (Language Agent Tree Search) Planner
|
|
2
|
+
//
|
|
3
|
+
// Instead of linear plans, explores branching plans, evaluates partway
|
|
4
|
+
// through, and commits to the best path. Uses UCB1 for exploration vs
|
|
5
|
+
// exploitation balance.
|
|
6
|
+
//
|
|
7
|
+
// Core loop:
|
|
8
|
+
// 1. Generate 2-3 candidate actions per step (heuristic, no LLM)
|
|
9
|
+
// 2. Evaluate candidates using skill library + collective patterns + strategy history
|
|
10
|
+
// 3. Select best path via UCB1 (Upper Confidence Bound)
|
|
11
|
+
// 4. Execute the selected action
|
|
12
|
+
// 5. Backpropagate results — success raises ancestor values, failure lowers them
|
|
13
|
+
//
|
|
14
|
+
// Activation:
|
|
15
|
+
// $ kbot --tree "refactor the auth system"
|
|
16
|
+
import { classifyTask, findPattern } from './learning.js';
|
|
17
|
+
import { retrieveSkills } from './skill-library.js';
|
|
18
|
+
import { getCollectiveToolSequence } from './collective.js';
|
|
19
|
+
import { getHistoricalBestStrategy } from './reasoning.js';
|
|
20
|
+
import { getSkillRatingSystem } from './skill-rating.js';
|
|
21
|
+
import { runAgent } from './agent.js';
|
|
22
|
+
import { gatherContext, formatContextForPrompt } from './context.js';
|
|
23
|
+
import { createSpinner, printInfo, printSuccess, printError, printWarn } from './ui.js';
|
|
24
|
+
import chalk from 'chalk';
|
|
25
|
+
const AMETHYST = chalk.hex('#6B5B95');
|
|
26
|
+
// ── Constants ──
|
|
27
|
+
const MAX_DEPTH = 5;
|
|
28
|
+
const MAX_WIDTH = 3;
|
|
29
|
+
const DEFAULT_EXPLORATION_CONSTANT = 1.41; // sqrt(2), standard UCB1
|
|
30
|
+
const MIN_VALUE = 0.01;
|
|
31
|
+
const MAX_VALUE = 0.99;
|
|
32
|
+
// ── Internal helpers ──
|
|
33
|
+
let nodeCounter = 0;
|
|
34
|
+
function generateNodeId() {
|
|
35
|
+
return `lats_${Date.now().toString(36)}_${(nodeCounter++).toString(36)}`;
|
|
36
|
+
}
|
|
37
|
+
function clamp(val, min, max) {
|
|
38
|
+
return Math.max(min, Math.min(max, val));
|
|
39
|
+
}
|
|
40
|
+
const APPROACH_TEMPLATES = {
|
|
41
|
+
debug: [
|
|
42
|
+
{
|
|
43
|
+
name: 'read-grep-fix',
|
|
44
|
+
steps: [
|
|
45
|
+
{ action: 'Read error logs and stack traces', toolHint: 'bash' },
|
|
46
|
+
{ action: 'Grep codebase for error source', toolHint: 'grep' },
|
|
47
|
+
{ action: 'Read the offending file', toolHint: 'read_file' },
|
|
48
|
+
{ action: 'Apply targeted fix', toolHint: 'edit_file' },
|
|
49
|
+
{ action: 'Verify fix compiles', toolHint: 'bash' },
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'reproduce-isolate-fix',
|
|
54
|
+
steps: [
|
|
55
|
+
{ action: 'Reproduce the error in isolation', toolHint: 'bash' },
|
|
56
|
+
{ action: 'Add diagnostic logging', toolHint: 'edit_file' },
|
|
57
|
+
{ action: 'Run again and analyze output', toolHint: 'bash' },
|
|
58
|
+
{ action: 'Apply root-cause fix', toolHint: 'edit_file' },
|
|
59
|
+
{ action: 'Run tests to confirm', toolHint: 'bash' },
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'bisect-and-revert',
|
|
64
|
+
steps: [
|
|
65
|
+
{ action: 'Check git log for recent changes', toolHint: 'bash' },
|
|
66
|
+
{ action: 'Identify suspect commits', toolHint: 'bash' },
|
|
67
|
+
{ action: 'Diff suspect commit', toolHint: 'bash' },
|
|
68
|
+
{ action: 'Revert or fix the breaking change', toolHint: 'edit_file' },
|
|
69
|
+
{ action: 'Verify with tests', toolHint: 'bash' },
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
build: [
|
|
74
|
+
{
|
|
75
|
+
name: 'scaffold-write-test',
|
|
76
|
+
steps: [
|
|
77
|
+
{ action: 'Analyze project structure and conventions', toolHint: 'glob' },
|
|
78
|
+
{ action: 'Create new file(s) with scaffolded code', toolHint: 'write_file' },
|
|
79
|
+
{ action: 'Wire up imports and exports', toolHint: 'edit_file' },
|
|
80
|
+
{ action: 'Write tests for new code', toolHint: 'write_file' },
|
|
81
|
+
{ action: 'Build and verify', toolHint: 'bash' },
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'prototype-iterate',
|
|
86
|
+
steps: [
|
|
87
|
+
{ action: 'Study existing patterns in codebase', toolHint: 'read_file' },
|
|
88
|
+
{ action: 'Write minimal prototype', toolHint: 'write_file' },
|
|
89
|
+
{ action: 'Test prototype manually', toolHint: 'bash' },
|
|
90
|
+
{ action: 'Iterate: refine and complete', toolHint: 'edit_file' },
|
|
91
|
+
{ action: 'Add tests and documentation', toolHint: 'write_file' },
|
|
92
|
+
],
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'plan-implement-integrate',
|
|
96
|
+
steps: [
|
|
97
|
+
{ action: 'Read related modules for API surface', toolHint: 'read_file' },
|
|
98
|
+
{ action: 'Design the interface and types', toolHint: 'write_file' },
|
|
99
|
+
{ action: 'Implement core logic', toolHint: 'write_file' },
|
|
100
|
+
{ action: 'Integrate with existing code', toolHint: 'edit_file' },
|
|
101
|
+
{ action: 'Run full test suite', toolHint: 'bash' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
refactor: [
|
|
106
|
+
{
|
|
107
|
+
name: 'read-restructure-verify',
|
|
108
|
+
steps: [
|
|
109
|
+
{ action: 'Read and map the code to refactor', toolHint: 'read_file' },
|
|
110
|
+
{ action: 'Extract common patterns', toolHint: 'edit_file' },
|
|
111
|
+
{ action: 'Apply structural changes', toolHint: 'edit_file' },
|
|
112
|
+
{ action: 'Update imports and references', toolHint: 'edit_file' },
|
|
113
|
+
{ action: 'Run type-check and tests', toolHint: 'bash' },
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'incremental-refactor',
|
|
118
|
+
steps: [
|
|
119
|
+
{ action: 'Identify all code to refactor', toolHint: 'grep' },
|
|
120
|
+
{ action: 'Refactor first occurrence as template', toolHint: 'edit_file' },
|
|
121
|
+
{ action: 'Apply pattern to remaining occurrences', toolHint: 'edit_file' },
|
|
122
|
+
{ action: 'Clean up unused code', toolHint: 'edit_file' },
|
|
123
|
+
{ action: 'Verify nothing broke', toolHint: 'bash' },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
test: [
|
|
128
|
+
{
|
|
129
|
+
name: 'read-write-run',
|
|
130
|
+
steps: [
|
|
131
|
+
{ action: 'Read the code under test', toolHint: 'read_file' },
|
|
132
|
+
{ action: 'Write test file with happy path tests', toolHint: 'write_file' },
|
|
133
|
+
{ action: 'Add edge case and error tests', toolHint: 'edit_file' },
|
|
134
|
+
{ action: 'Run tests', toolHint: 'bash' },
|
|
135
|
+
{ action: 'Fix any failures and re-run', toolHint: 'bash' },
|
|
136
|
+
],
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
search: [
|
|
140
|
+
{
|
|
141
|
+
name: 'grep-read-summarize',
|
|
142
|
+
steps: [
|
|
143
|
+
{ action: 'Search codebase with grep', toolHint: 'grep' },
|
|
144
|
+
{ action: 'Read matching files for context', toolHint: 'read_file' },
|
|
145
|
+
{ action: 'Synthesize findings', toolHint: 'bash' },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'glob-tree-read',
|
|
150
|
+
steps: [
|
|
151
|
+
{ action: 'Map directory structure', toolHint: 'glob' },
|
|
152
|
+
{ action: 'Read key files', toolHint: 'read_file' },
|
|
153
|
+
{ action: 'Report findings', toolHint: 'bash' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
deploy: [
|
|
158
|
+
{
|
|
159
|
+
name: 'check-build-deploy',
|
|
160
|
+
steps: [
|
|
161
|
+
{ action: 'Check current status (git, tests)', toolHint: 'bash' },
|
|
162
|
+
{ action: 'Run full build', toolHint: 'bash' },
|
|
163
|
+
{ action: 'Run test suite', toolHint: 'bash' },
|
|
164
|
+
{ action: 'Deploy to target environment', toolHint: 'bash' },
|
|
165
|
+
{ action: 'Verify deployment', toolHint: 'bash' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
general: [
|
|
170
|
+
{
|
|
171
|
+
name: 'explore-analyze-act',
|
|
172
|
+
steps: [
|
|
173
|
+
{ action: 'Explore project context', toolHint: 'glob' },
|
|
174
|
+
{ action: 'Read relevant files', toolHint: 'read_file' },
|
|
175
|
+
{ action: 'Analyze and plan approach', toolHint: 'bash' },
|
|
176
|
+
{ action: 'Execute primary action', toolHint: 'bash' },
|
|
177
|
+
{ action: 'Verify results', toolHint: 'bash' },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'search-understand-respond',
|
|
182
|
+
steps: [
|
|
183
|
+
{ action: 'Search for relevant code', toolHint: 'grep' },
|
|
184
|
+
{ action: 'Read key files', toolHint: 'read_file' },
|
|
185
|
+
{ action: 'Formulate response or action', toolHint: 'bash' },
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
explain: [
|
|
190
|
+
{
|
|
191
|
+
name: 'read-trace-explain',
|
|
192
|
+
steps: [
|
|
193
|
+
{ action: 'Read the code in question', toolHint: 'read_file' },
|
|
194
|
+
{ action: 'Trace call chain and dependencies', toolHint: 'grep' },
|
|
195
|
+
{ action: 'Read dependent files', toolHint: 'read_file' },
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
review: [
|
|
200
|
+
{
|
|
201
|
+
name: 'diff-read-analyze',
|
|
202
|
+
steps: [
|
|
203
|
+
{ action: 'Get git diff of changes', toolHint: 'bash' },
|
|
204
|
+
{ action: 'Read changed files in full', toolHint: 'read_file' },
|
|
205
|
+
{ action: 'Check for common issues', toolHint: 'grep' },
|
|
206
|
+
{ action: 'Run type-check', toolHint: 'bash' },
|
|
207
|
+
{ action: 'Run tests', toolHint: 'bash' },
|
|
208
|
+
],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
// ── Value estimation heuristics ──
|
|
213
|
+
/**
|
|
214
|
+
* Estimate the success likelihood of an approach based on available signals.
|
|
215
|
+
* Returns a value between 0 and 1.
|
|
216
|
+
*
|
|
217
|
+
* Signals used:
|
|
218
|
+
* - Skill library match (proven tool sequences)
|
|
219
|
+
* - Collective patterns (community-aggregated sequences)
|
|
220
|
+
* - Historical strategy outcomes
|
|
221
|
+
* - Bayesian skill ratings (agent proficiency for the task category)
|
|
222
|
+
*/
|
|
223
|
+
function estimateValue(action, toolHint, taskType, matchedSkills, collectiveSequence, historicalStrategy, approachName) {
|
|
224
|
+
let value = 0.5; // baseline
|
|
225
|
+
let signals = 0;
|
|
226
|
+
// Signal 1: Skill library — does this tool appear in proven skill steps?
|
|
227
|
+
for (const skill of matchedSkills) {
|
|
228
|
+
const toolMatch = skill.steps.some(s => s.tool === toolHint);
|
|
229
|
+
if (toolMatch) {
|
|
230
|
+
const reliability = skill.successCount / Math.max(1, skill.successCount + skill.failureCount);
|
|
231
|
+
value += reliability * 0.15;
|
|
232
|
+
signals++;
|
|
233
|
+
}
|
|
234
|
+
// Check if the action description matches the skill description
|
|
235
|
+
const actionWords = new Set(action.toLowerCase().split(/\s+/).filter(w => w.length > 2));
|
|
236
|
+
const descWords = new Set(skill.description.toLowerCase().split(/\s+/).filter(w => w.length > 2));
|
|
237
|
+
const overlap = [...actionWords].filter(w => descWords.has(w)).length;
|
|
238
|
+
if (overlap > 2) {
|
|
239
|
+
value += 0.1;
|
|
240
|
+
signals++;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
// Signal 2: Collective patterns — does this tool appear in community-proven sequences?
|
|
244
|
+
if (collectiveSequence) {
|
|
245
|
+
if (collectiveSequence.includes(toolHint)) {
|
|
246
|
+
value += 0.12;
|
|
247
|
+
signals++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Signal 3: Historical strategy match — does the approach name match the best strategy?
|
|
251
|
+
if (historicalStrategy) {
|
|
252
|
+
// Fuzzy match: check if the approach name shares words with the historical strategy
|
|
253
|
+
const approachWords = new Set(approachName.toLowerCase().split('-'));
|
|
254
|
+
const stratWords = new Set(historicalStrategy.toLowerCase().split(/[\s-]+/));
|
|
255
|
+
const overlap = [...approachWords].filter(w => stratWords.has(w)).length;
|
|
256
|
+
if (overlap > 0) {
|
|
257
|
+
value += 0.1 * overlap;
|
|
258
|
+
signals++;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// Signal 4: Bayesian skill ratings — does this tool type align with strong agents?
|
|
262
|
+
try {
|
|
263
|
+
const skillRating = getSkillRatingSystem();
|
|
264
|
+
const category = skillRating.categorizeMessage(`${action} ${toolHint}`);
|
|
265
|
+
const ranked = skillRating.getRankedAgents(category);
|
|
266
|
+
if (ranked.length > 0 && ranked[0].confidence > 0.3) {
|
|
267
|
+
// High-confidence routing available = higher value (we know who to route to)
|
|
268
|
+
value += 0.08;
|
|
269
|
+
signals++;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
// Non-critical — skill rating might not be available
|
|
274
|
+
}
|
|
275
|
+
// If no signals found, apply a small penalty for unknown territory
|
|
276
|
+
if (signals === 0) {
|
|
277
|
+
value -= 0.1;
|
|
278
|
+
}
|
|
279
|
+
return clamp(value, MIN_VALUE, MAX_VALUE);
|
|
280
|
+
}
|
|
281
|
+
// ── Core LATS functions ──
|
|
282
|
+
/**
|
|
283
|
+
* Create a plan tree for a task.
|
|
284
|
+
* Generates the root node + first-level branches (2-3 alternative approaches).
|
|
285
|
+
*/
|
|
286
|
+
export async function createPlanTree(task, context) {
|
|
287
|
+
const taskType = classifyTask(task);
|
|
288
|
+
// Gather signals for value estimation
|
|
289
|
+
const matchedSkills = await retrieveSkills(task, 5);
|
|
290
|
+
const collectiveSequence = getCollectiveToolSequence(taskType);
|
|
291
|
+
const historicalStrategy = getHistoricalBestStrategy(taskType);
|
|
292
|
+
// Check for cached patterns
|
|
293
|
+
const cachedPattern = findPattern(task);
|
|
294
|
+
// Get approach templates for this task type
|
|
295
|
+
const templates = APPROACH_TEMPLATES[taskType] || APPROACH_TEMPLATES['general'];
|
|
296
|
+
// If we have a cached pattern, create a dedicated approach from it
|
|
297
|
+
const approaches = [];
|
|
298
|
+
if (cachedPattern && cachedPattern.successRate > 0.5) {
|
|
299
|
+
approaches.push({
|
|
300
|
+
name: 'cached-pattern',
|
|
301
|
+
steps: cachedPattern.toolSequence.map((tool, i) => ({
|
|
302
|
+
action: `Step ${i + 1}: Execute ${tool} (proven pattern, ${cachedPattern.hits}x success)`,
|
|
303
|
+
toolHint: tool,
|
|
304
|
+
})),
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
// Add template approaches (limit total to MAX_WIDTH)
|
|
308
|
+
for (const tmpl of templates) {
|
|
309
|
+
if (approaches.length >= MAX_WIDTH)
|
|
310
|
+
break;
|
|
311
|
+
approaches.push(tmpl);
|
|
312
|
+
}
|
|
313
|
+
// If we still don't have enough, add from general
|
|
314
|
+
if (approaches.length < 2 && taskType !== 'general') {
|
|
315
|
+
for (const tmpl of APPROACH_TEMPLATES['general']) {
|
|
316
|
+
if (approaches.length >= MAX_WIDTH)
|
|
317
|
+
break;
|
|
318
|
+
if (!approaches.find(a => a.name === tmpl.name)) {
|
|
319
|
+
approaches.push(tmpl);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// Create root node
|
|
324
|
+
const rootId = generateNodeId();
|
|
325
|
+
const rootNode = {
|
|
326
|
+
id: rootId,
|
|
327
|
+
parentId: null,
|
|
328
|
+
depth: 0,
|
|
329
|
+
action: task,
|
|
330
|
+
toolHint: '',
|
|
331
|
+
value: 0.5,
|
|
332
|
+
visits: 0,
|
|
333
|
+
children: [],
|
|
334
|
+
status: 'pending',
|
|
335
|
+
};
|
|
336
|
+
const tree = {
|
|
337
|
+
root: rootId,
|
|
338
|
+
nodes: { [rootId]: rootNode },
|
|
339
|
+
bestPath: [rootId],
|
|
340
|
+
explorationConstant: DEFAULT_EXPLORATION_CONSTANT,
|
|
341
|
+
};
|
|
342
|
+
// Create first-level branches — one per approach
|
|
343
|
+
for (const approach of approaches) {
|
|
344
|
+
if (approach.steps.length === 0)
|
|
345
|
+
continue;
|
|
346
|
+
const firstStep = approach.steps[0];
|
|
347
|
+
const childId = generateNodeId();
|
|
348
|
+
const childValue = estimateValue(firstStep.action, firstStep.toolHint, taskType, matchedSkills, collectiveSequence, historicalStrategy, approach.name);
|
|
349
|
+
const childNode = {
|
|
350
|
+
id: childId,
|
|
351
|
+
parentId: rootId,
|
|
352
|
+
depth: 1,
|
|
353
|
+
action: `[${approach.name}] ${firstStep.action}`,
|
|
354
|
+
toolHint: firstStep.toolHint,
|
|
355
|
+
value: childValue,
|
|
356
|
+
visits: 0,
|
|
357
|
+
children: [],
|
|
358
|
+
status: 'pending',
|
|
359
|
+
};
|
|
360
|
+
tree.nodes[childId] = childNode;
|
|
361
|
+
rootNode.children.push(childId);
|
|
362
|
+
// Pre-populate subsequent steps as deeper nodes if within depth limit
|
|
363
|
+
let currentParentId = childId;
|
|
364
|
+
for (let i = 1; i < approach.steps.length && i < MAX_DEPTH; i++) {
|
|
365
|
+
const step = approach.steps[i];
|
|
366
|
+
const stepId = generateNodeId();
|
|
367
|
+
const stepValue = estimateValue(step.action, step.toolHint, taskType, matchedSkills, collectiveSequence, historicalStrategy, approach.name);
|
|
368
|
+
const stepNode = {
|
|
369
|
+
id: stepId,
|
|
370
|
+
parentId: currentParentId,
|
|
371
|
+
depth: i + 1,
|
|
372
|
+
action: step.action,
|
|
373
|
+
toolHint: step.toolHint,
|
|
374
|
+
value: stepValue,
|
|
375
|
+
visits: 0,
|
|
376
|
+
children: [],
|
|
377
|
+
status: 'pending',
|
|
378
|
+
};
|
|
379
|
+
tree.nodes[stepId] = stepNode;
|
|
380
|
+
tree.nodes[currentParentId].children.push(stepId);
|
|
381
|
+
currentParentId = stepId;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
// Compute initial best path
|
|
385
|
+
tree.bestPath = selectBestPath(tree);
|
|
386
|
+
return tree;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Expand a node by generating child alternatives for its next step.
|
|
390
|
+
* Uses the same heuristic approach as createPlanTree but adapted for mid-plan branching.
|
|
391
|
+
*/
|
|
392
|
+
export async function expandNode(tree, nodeId) {
|
|
393
|
+
const node = tree.nodes[nodeId];
|
|
394
|
+
if (!node)
|
|
395
|
+
return [];
|
|
396
|
+
if (node.depth >= MAX_DEPTH)
|
|
397
|
+
return [];
|
|
398
|
+
if (node.children.length >= MAX_WIDTH)
|
|
399
|
+
return [];
|
|
400
|
+
const taskType = classifyTask(node.action);
|
|
401
|
+
const matchedSkills = await retrieveSkills(node.action, 3);
|
|
402
|
+
const collectiveSequence = getCollectiveToolSequence(taskType);
|
|
403
|
+
const historicalStrategy = getHistoricalBestStrategy(taskType);
|
|
404
|
+
// Generate candidate next actions based on what tools typically follow the current tool
|
|
405
|
+
const nextToolCandidates = getNextToolCandidates(node.toolHint, taskType);
|
|
406
|
+
const newChildren = [];
|
|
407
|
+
for (const candidate of nextToolCandidates) {
|
|
408
|
+
if (node.children.length + newChildren.length >= MAX_WIDTH)
|
|
409
|
+
break;
|
|
410
|
+
const childId = generateNodeId();
|
|
411
|
+
const childValue = estimateValue(candidate.action, candidate.toolHint, taskType, matchedSkills, collectiveSequence, historicalStrategy, `expand-${node.toolHint}`);
|
|
412
|
+
const childNode = {
|
|
413
|
+
id: childId,
|
|
414
|
+
parentId: nodeId,
|
|
415
|
+
depth: node.depth + 1,
|
|
416
|
+
action: candidate.action,
|
|
417
|
+
toolHint: candidate.toolHint,
|
|
418
|
+
value: childValue,
|
|
419
|
+
visits: 0,
|
|
420
|
+
children: [],
|
|
421
|
+
status: 'pending',
|
|
422
|
+
};
|
|
423
|
+
tree.nodes[childId] = childNode;
|
|
424
|
+
newChildren.push(childId);
|
|
425
|
+
}
|
|
426
|
+
node.children.push(...newChildren);
|
|
427
|
+
return newChildren;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Get candidate next tools based on what typically follows a given tool.
|
|
431
|
+
* This encodes common tool-chain patterns without LLM calls.
|
|
432
|
+
*/
|
|
433
|
+
function getNextToolCandidates(currentTool, taskType) {
|
|
434
|
+
// Common tool transitions
|
|
435
|
+
const transitions = {
|
|
436
|
+
glob: [
|
|
437
|
+
{ action: 'Read files found by search', toolHint: 'read_file' },
|
|
438
|
+
{ action: 'Search within matched files', toolHint: 'grep' },
|
|
439
|
+
],
|
|
440
|
+
grep: [
|
|
441
|
+
{ action: 'Read file with match', toolHint: 'read_file' },
|
|
442
|
+
{ action: 'Edit the matched code', toolHint: 'edit_file' },
|
|
443
|
+
{ action: 'Search for related patterns', toolHint: 'grep' },
|
|
444
|
+
],
|
|
445
|
+
read_file: [
|
|
446
|
+
{ action: 'Edit based on analysis', toolHint: 'edit_file' },
|
|
447
|
+
{ action: 'Search for related code', toolHint: 'grep' },
|
|
448
|
+
{ action: 'Run command based on findings', toolHint: 'bash' },
|
|
449
|
+
],
|
|
450
|
+
edit_file: [
|
|
451
|
+
{ action: 'Verify changes compile', toolHint: 'bash' },
|
|
452
|
+
{ action: 'Read edited file to confirm', toolHint: 'read_file' },
|
|
453
|
+
{ action: 'Edit another related file', toolHint: 'edit_file' },
|
|
454
|
+
],
|
|
455
|
+
write_file: [
|
|
456
|
+
{ action: 'Verify new file compiles', toolHint: 'bash' },
|
|
457
|
+
{ action: 'Wire up imports in existing code', toolHint: 'edit_file' },
|
|
458
|
+
{ action: 'Read new file to confirm', toolHint: 'read_file' },
|
|
459
|
+
],
|
|
460
|
+
bash: [
|
|
461
|
+
{ action: 'Read output files', toolHint: 'read_file' },
|
|
462
|
+
{ action: 'Fix issues found by command', toolHint: 'edit_file' },
|
|
463
|
+
{ action: 'Run follow-up command', toolHint: 'bash' },
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
const candidates = transitions[currentTool] || transitions['bash'] || [];
|
|
467
|
+
// Also add task-type-specific candidates
|
|
468
|
+
if (taskType === 'debug' && currentTool !== 'grep') {
|
|
469
|
+
candidates.push({ action: 'Search for error origin', toolHint: 'grep' });
|
|
470
|
+
}
|
|
471
|
+
if (taskType === 'test' && currentTool !== 'bash') {
|
|
472
|
+
candidates.push({ action: 'Run test suite', toolHint: 'bash' });
|
|
473
|
+
}
|
|
474
|
+
return candidates.slice(0, MAX_WIDTH);
|
|
475
|
+
}
|
|
476
|
+
// ── UCB1 Selection ──
|
|
477
|
+
/**
|
|
478
|
+
* Compute the UCB1 score for a node.
|
|
479
|
+
*
|
|
480
|
+
* UCB1 = value + C * sqrt(ln(parent_visits) / visits)
|
|
481
|
+
*
|
|
482
|
+
* Unvisited nodes get Infinity to ensure they're explored first.
|
|
483
|
+
*/
|
|
484
|
+
function ucb1Score(node, parentVisits, C) {
|
|
485
|
+
if (node.visits === 0)
|
|
486
|
+
return Infinity;
|
|
487
|
+
const exploitation = node.value;
|
|
488
|
+
const exploration = C * Math.sqrt(Math.log(parentVisits) / node.visits);
|
|
489
|
+
return exploitation + exploration;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Select the best path from root to leaf using UCB1.
|
|
493
|
+
* At each level, picks the child with the highest UCB1 score.
|
|
494
|
+
*/
|
|
495
|
+
export function selectBestPath(tree) {
|
|
496
|
+
const path = [tree.root];
|
|
497
|
+
let currentId = tree.root;
|
|
498
|
+
while (true) {
|
|
499
|
+
const node = tree.nodes[currentId];
|
|
500
|
+
if (!node || node.children.length === 0)
|
|
501
|
+
break;
|
|
502
|
+
// Pick child with highest UCB1 score
|
|
503
|
+
let bestChild = null;
|
|
504
|
+
let bestScore = -Infinity;
|
|
505
|
+
for (const childId of node.children) {
|
|
506
|
+
const child = tree.nodes[childId];
|
|
507
|
+
if (!child)
|
|
508
|
+
continue;
|
|
509
|
+
const score = ucb1Score(child, Math.max(1, node.visits), tree.explorationConstant);
|
|
510
|
+
if (score > bestScore) {
|
|
511
|
+
bestScore = score;
|
|
512
|
+
bestChild = childId;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (!bestChild)
|
|
516
|
+
break;
|
|
517
|
+
path.push(bestChild);
|
|
518
|
+
currentId = bestChild;
|
|
519
|
+
}
|
|
520
|
+
return path;
|
|
521
|
+
}
|
|
522
|
+
// ── Backpropagation ──
|
|
523
|
+
/**
|
|
524
|
+
* Backpropagate a reward up the tree from a leaf node.
|
|
525
|
+
* Updates value and visit count for all ancestors.
|
|
526
|
+
*
|
|
527
|
+
* @param reward - 1.0 for success, 0.0 for failure, 0.5 for partial
|
|
528
|
+
*/
|
|
529
|
+
export function backpropagate(tree, leafId, reward) {
|
|
530
|
+
let currentId = leafId;
|
|
531
|
+
while (currentId) {
|
|
532
|
+
const node = tree.nodes[currentId];
|
|
533
|
+
if (!node)
|
|
534
|
+
break;
|
|
535
|
+
node.visits++;
|
|
536
|
+
// Incremental mean update: value = value + (reward - value) / visits
|
|
537
|
+
node.value = node.value + (reward - node.value) / node.visits;
|
|
538
|
+
// Clamp to valid range
|
|
539
|
+
node.value = clamp(node.value, MIN_VALUE, MAX_VALUE);
|
|
540
|
+
currentId = node.parentId;
|
|
541
|
+
}
|
|
542
|
+
// Recompute best path after backpropagation
|
|
543
|
+
tree.bestPath = selectBestPath(tree);
|
|
544
|
+
}
|
|
545
|
+
// ── Best plan extraction ──
|
|
546
|
+
/**
|
|
547
|
+
* Get the highest-value complete path as a linear plan.
|
|
548
|
+
* Returns the path with the highest average value from root to leaf.
|
|
549
|
+
*/
|
|
550
|
+
export function getBestPlan(tree) {
|
|
551
|
+
// Find all leaf nodes (nodes with no children)
|
|
552
|
+
const leaves = [];
|
|
553
|
+
for (const [id, node] of Object.entries(tree.nodes)) {
|
|
554
|
+
if (node.children.length === 0 && id !== tree.root) {
|
|
555
|
+
leaves.push(id);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (leaves.length === 0)
|
|
559
|
+
return [];
|
|
560
|
+
// For each leaf, trace back to root and compute average value
|
|
561
|
+
let bestLeaf = '';
|
|
562
|
+
let bestAvgValue = -Infinity;
|
|
563
|
+
for (const leafId of leaves) {
|
|
564
|
+
let totalValue = 0;
|
|
565
|
+
let count = 0;
|
|
566
|
+
let currentId = leafId;
|
|
567
|
+
while (currentId && currentId !== tree.root) {
|
|
568
|
+
const node = tree.nodes[currentId];
|
|
569
|
+
if (!node)
|
|
570
|
+
break;
|
|
571
|
+
totalValue += node.value;
|
|
572
|
+
count++;
|
|
573
|
+
currentId = node.parentId;
|
|
574
|
+
}
|
|
575
|
+
const avgValue = count > 0 ? totalValue / count : 0;
|
|
576
|
+
if (avgValue > bestAvgValue) {
|
|
577
|
+
bestAvgValue = avgValue;
|
|
578
|
+
bestLeaf = leafId;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
if (!bestLeaf)
|
|
582
|
+
return [];
|
|
583
|
+
// Trace from leaf to root to build the path
|
|
584
|
+
const reversePath = [];
|
|
585
|
+
let currentId = bestLeaf;
|
|
586
|
+
while (currentId && currentId !== tree.root) {
|
|
587
|
+
const node = tree.nodes[currentId];
|
|
588
|
+
if (!node)
|
|
589
|
+
break;
|
|
590
|
+
reversePath.push({
|
|
591
|
+
action: node.action,
|
|
592
|
+
toolHint: node.toolHint,
|
|
593
|
+
value: node.value,
|
|
594
|
+
});
|
|
595
|
+
currentId = node.parentId;
|
|
596
|
+
}
|
|
597
|
+
return reversePath.reverse();
|
|
598
|
+
}
|
|
599
|
+
// ── ASCII tree visualization ──
|
|
600
|
+
/**
|
|
601
|
+
* Format the plan tree as an ASCII visualization for terminal display.
|
|
602
|
+
*/
|
|
603
|
+
export function formatTreeForDisplay(tree) {
|
|
604
|
+
const lines = [];
|
|
605
|
+
lines.push('');
|
|
606
|
+
lines.push(` ${AMETHYST('◆ LATS Plan Tree')}`);
|
|
607
|
+
lines.push(` ${chalk.dim('─'.repeat(60))}`);
|
|
608
|
+
lines.push('');
|
|
609
|
+
function renderNode(nodeId, prefix, isLast) {
|
|
610
|
+
const node = tree.nodes[nodeId];
|
|
611
|
+
if (!node)
|
|
612
|
+
return;
|
|
613
|
+
const connector = node.depth === 0 ? ' ' : (isLast ? ' └─ ' : ' ├─ ');
|
|
614
|
+
const childPrefix = node.depth === 0 ? ' ' : (isLast ? ' ' : ' │ ');
|
|
615
|
+
// Status icon
|
|
616
|
+
const statusIcon = {
|
|
617
|
+
pending: chalk.dim('○'),
|
|
618
|
+
executing: chalk.yellow('●'),
|
|
619
|
+
success: chalk.green('✓'),
|
|
620
|
+
failure: chalk.red('✗'),
|
|
621
|
+
}[node.status];
|
|
622
|
+
// Value bar (visual indicator)
|
|
623
|
+
const barLen = Math.round(node.value * 10);
|
|
624
|
+
const bar = chalk.green('█'.repeat(barLen)) + chalk.dim('░'.repeat(10 - barLen));
|
|
625
|
+
// Highlight if node is on the best path
|
|
626
|
+
const isBest = tree.bestPath.includes(nodeId);
|
|
627
|
+
const actionStr = isBest
|
|
628
|
+
? AMETHYST(node.action)
|
|
629
|
+
: node.action;
|
|
630
|
+
const toolStr = node.toolHint ? chalk.cyan(` [${node.toolHint}]`) : '';
|
|
631
|
+
const valueStr = chalk.dim(` v=${node.value.toFixed(2)} n=${node.visits}`);
|
|
632
|
+
if (node.depth === 0) {
|
|
633
|
+
lines.push(`${prefix}${statusIcon} ${actionStr}`);
|
|
634
|
+
lines.push(`${prefix} ${bar} ${valueStr}`);
|
|
635
|
+
}
|
|
636
|
+
else {
|
|
637
|
+
lines.push(`${prefix}${connector}${statusIcon} ${actionStr}${toolStr}`);
|
|
638
|
+
lines.push(`${prefix}${childPrefix}${bar} ${valueStr}`);
|
|
639
|
+
}
|
|
640
|
+
// Render children
|
|
641
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
642
|
+
const childIsLast = i === node.children.length - 1;
|
|
643
|
+
renderNode(node.children[i], prefix + childPrefix, childIsLast);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
renderNode(tree.root, '', true);
|
|
647
|
+
// Summary
|
|
648
|
+
lines.push('');
|
|
649
|
+
lines.push(` ${chalk.dim('─'.repeat(60))}`);
|
|
650
|
+
const totalNodes = Object.keys(tree.nodes).length;
|
|
651
|
+
const bestPlanSteps = getBestPlan(tree);
|
|
652
|
+
const avgValue = bestPlanSteps.length > 0
|
|
653
|
+
? bestPlanSteps.reduce((s, p) => s + p.value, 0) / bestPlanSteps.length
|
|
654
|
+
: 0;
|
|
655
|
+
lines.push(` ${chalk.dim(`${totalNodes} nodes · ${bestPlanSteps.length} steps in best path · avg value: ${avgValue.toFixed(2)}`)}`);
|
|
656
|
+
// Show best path summary
|
|
657
|
+
if (bestPlanSteps.length > 0) {
|
|
658
|
+
lines.push('');
|
|
659
|
+
lines.push(` ${AMETHYST('Best path:')}`);
|
|
660
|
+
for (let i = 0; i < bestPlanSteps.length; i++) {
|
|
661
|
+
const step = bestPlanSteps[i];
|
|
662
|
+
const valueBar = chalk.green('█'.repeat(Math.round(step.value * 5)));
|
|
663
|
+
lines.push(` ${chalk.dim(`${i + 1}.`)} ${step.action} ${chalk.cyan(`[${step.toolHint}]`)} ${valueBar} ${chalk.dim(`${(step.value * 100).toFixed(0)}%`)}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
lines.push('');
|
|
667
|
+
return lines.join('\n');
|
|
668
|
+
}
|
|
669
|
+
// ── Execution engine ──
|
|
670
|
+
/**
|
|
671
|
+
* Execute the tree plan — selects the best path, runs each step,
|
|
672
|
+
* backpropagates results, and adapts if steps fail.
|
|
673
|
+
*/
|
|
674
|
+
export async function executeTreePlan(task, agentOpts) {
|
|
675
|
+
// Phase 1: Build the plan tree
|
|
676
|
+
const spinner = createSpinner('Building LATS plan tree...');
|
|
677
|
+
spinner.start();
|
|
678
|
+
const context = gatherContext();
|
|
679
|
+
const contextStr = formatContextForPrompt(context);
|
|
680
|
+
const tree = await createPlanTree(task, contextStr);
|
|
681
|
+
spinner.stop();
|
|
682
|
+
// Display the tree
|
|
683
|
+
console.log(formatTreeForDisplay(tree));
|
|
684
|
+
// Phase 2: Execute along the best path
|
|
685
|
+
printInfo('Executing best path...');
|
|
686
|
+
const bestPlan = getBestPlan(tree);
|
|
687
|
+
if (bestPlan.length === 0) {
|
|
688
|
+
printWarn('No executable plan found in tree. Falling back to direct execution.');
|
|
689
|
+
return {
|
|
690
|
+
tree,
|
|
691
|
+
success: false,
|
|
692
|
+
summary: 'LATS could not generate a viable plan tree.',
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
let stepsDone = 0;
|
|
696
|
+
let stepsFailed = 0;
|
|
697
|
+
// Walk the best path and match to tree node IDs for backpropagation
|
|
698
|
+
const bestPathNodeIds = tree.bestPath.slice(1); // skip root
|
|
699
|
+
for (let i = 0; i < bestPlan.length; i++) {
|
|
700
|
+
const step = bestPlan[i];
|
|
701
|
+
const nodeId = bestPathNodeIds[i];
|
|
702
|
+
const node = nodeId ? tree.nodes[nodeId] : undefined;
|
|
703
|
+
if (node)
|
|
704
|
+
node.status = 'executing';
|
|
705
|
+
const stepSpinner = createSpinner(`Step ${i + 1}/${bestPlan.length}: ${step.action}`);
|
|
706
|
+
stepSpinner.start();
|
|
707
|
+
try {
|
|
708
|
+
// Execute the step by routing to the agent
|
|
709
|
+
const prompt = `You are executing step ${i + 1} of a plan to: "${task}"
|
|
710
|
+
|
|
711
|
+
This step: "${step.action}"
|
|
712
|
+
Suggested tool: ${step.toolHint}
|
|
713
|
+
|
|
714
|
+
Context: ${contextStr.slice(0, 500)}
|
|
715
|
+
|
|
716
|
+
Execute this step now using your tools. Be precise and verify your work.`;
|
|
717
|
+
const response = await runAgent(prompt, {
|
|
718
|
+
...agentOpts,
|
|
719
|
+
skipPlanner: true,
|
|
720
|
+
});
|
|
721
|
+
stepSpinner.stop();
|
|
722
|
+
printSuccess(`Step ${i + 1}: ${step.action}`);
|
|
723
|
+
stepsDone++;
|
|
724
|
+
if (node)
|
|
725
|
+
node.status = 'success';
|
|
726
|
+
// Backpropagate success
|
|
727
|
+
if (nodeId) {
|
|
728
|
+
backpropagate(tree, nodeId, 1.0);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
catch (err) {
|
|
732
|
+
stepSpinner.stop();
|
|
733
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
734
|
+
printError(`Step ${i + 1} failed: ${errorMsg}`);
|
|
735
|
+
stepsFailed++;
|
|
736
|
+
if (node)
|
|
737
|
+
node.status = 'failure';
|
|
738
|
+
// Backpropagate failure
|
|
739
|
+
if (nodeId) {
|
|
740
|
+
backpropagate(tree, nodeId, 0.0);
|
|
741
|
+
}
|
|
742
|
+
// Try to expand and find an alternative branch
|
|
743
|
+
if (node && node.parentId) {
|
|
744
|
+
printWarn('Searching for alternative approach...');
|
|
745
|
+
const newChildren = await expandNode(tree, node.parentId);
|
|
746
|
+
if (newChildren.length > 0) {
|
|
747
|
+
// Re-select best path after expansion
|
|
748
|
+
tree.bestPath = selectBestPath(tree);
|
|
749
|
+
const newBestPlan = getBestPlan(tree);
|
|
750
|
+
if (newBestPlan.length > i) {
|
|
751
|
+
printInfo(`Found alternative: ${newBestPlan[i].action}`);
|
|
752
|
+
// Continue with the new best plan from this point
|
|
753
|
+
// Re-execute the current step with the alternative
|
|
754
|
+
try {
|
|
755
|
+
const altStep = newBestPlan[i];
|
|
756
|
+
const altNodeId = newChildren[0];
|
|
757
|
+
const altNode = tree.nodes[altNodeId];
|
|
758
|
+
if (altNode)
|
|
759
|
+
altNode.status = 'executing';
|
|
760
|
+
const altPrompt = `Previous approach failed. Try alternative: "${altStep.action}"
|
|
761
|
+
Suggested tool: ${altStep.toolHint}
|
|
762
|
+
Task: "${task}"
|
|
763
|
+
|
|
764
|
+
Execute this step now.`;
|
|
765
|
+
await runAgent(altPrompt, {
|
|
766
|
+
...agentOpts,
|
|
767
|
+
skipPlanner: true,
|
|
768
|
+
});
|
|
769
|
+
printSuccess(`Alternative succeeded: ${altStep.action}`);
|
|
770
|
+
if (altNode)
|
|
771
|
+
altNode.status = 'success';
|
|
772
|
+
backpropagate(tree, altNodeId, 1.0);
|
|
773
|
+
stepsFailed--; // Recovered
|
|
774
|
+
stepsDone++;
|
|
775
|
+
}
|
|
776
|
+
catch {
|
|
777
|
+
printError('Alternative also failed. Continuing...');
|
|
778
|
+
backpropagate(tree, newChildren[0], 0.0);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
// If too many consecutive failures, abort
|
|
784
|
+
if (stepsFailed >= 3) {
|
|
785
|
+
printError('Too many failures. Aborting tree execution.');
|
|
786
|
+
break;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
// Phase 3: Summary
|
|
791
|
+
const success = stepsFailed === 0;
|
|
792
|
+
const totalSteps = bestPlan.length;
|
|
793
|
+
console.log('');
|
|
794
|
+
console.log(formatTreeForDisplay(tree));
|
|
795
|
+
const summaryLines = [
|
|
796
|
+
`LATS Plan: ${task}`,
|
|
797
|
+
`Result: ${stepsDone}/${totalSteps} steps succeeded, ${stepsFailed} failed`,
|
|
798
|
+
`Status: ${success ? 'COMPLETED' : 'PARTIAL'}`,
|
|
799
|
+
'',
|
|
800
|
+
'Best path:',
|
|
801
|
+
...bestPlan.map((s, i) => {
|
|
802
|
+
const nodeId = bestPathNodeIds[i];
|
|
803
|
+
const node = nodeId ? tree.nodes[nodeId] : undefined;
|
|
804
|
+
const status = node?.status ?? 'pending';
|
|
805
|
+
const icon = { pending: '○', executing: '●', success: '✓', failure: '✗' }[status];
|
|
806
|
+
return ` ${icon} ${i + 1}. ${s.action} [${s.toolHint}] (value: ${s.value.toFixed(2)})`;
|
|
807
|
+
}),
|
|
808
|
+
];
|
|
809
|
+
const summary = summaryLines.join('\n');
|
|
810
|
+
if (success) {
|
|
811
|
+
printSuccess(`LATS plan complete: ${stepsDone}/${totalSteps} steps succeeded`);
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
printWarn(`LATS plan finished with errors: ${stepsDone} done, ${stepsFailed} failed`);
|
|
815
|
+
}
|
|
816
|
+
return { tree, success, summary };
|
|
817
|
+
}
|
|
818
|
+
//# sourceMappingURL=tree-planner.js.map
|