@metabob/minibob 0.1.2
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/ARCHITECTURE.md +255 -0
- package/CHANGELOG.md +112 -0
- package/README.md +380 -0
- package/bin/minibob.js +36 -0
- package/dist/acp-gossip.d.ts +72 -0
- package/dist/acp-gossip.d.ts.map +1 -0
- package/dist/acp-gossip.js +156 -0
- package/dist/acp-gossip.js.map +1 -0
- package/dist/acp.d.ts +62 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +292 -0
- package/dist/acp.js.map +1 -0
- package/dist/activity.d.ts +157 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +518 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-runtime.d.ts +104 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/boredom.d.ts +125 -0
- package/dist/boredom.d.ts.map +1 -0
- package/dist/boredom.js +244 -0
- package/dist/boredom.js.map +1 -0
- package/dist/cli/acp-server.d.ts +23 -0
- package/dist/cli/acp-server.d.ts.map +1 -0
- package/dist/cli/burrow.d.ts +26 -0
- package/dist/cli/burrow.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts +22 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/goal.d.ts +22 -0
- package/dist/cli/goal.d.ts.map +1 -0
- package/dist/cli/index.d.ts +47 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/instance-registry.d.ts +78 -0
- package/dist/cli/instance-registry.d.ts.map +1 -0
- package/dist/cli/observe.d.ts +35 -0
- package/dist/cli/observe.d.ts.map +1 -0
- package/dist/cli/vessel.d.ts +14 -0
- package/dist/cli/vessel.d.ts.map +1 -0
- package/dist/composition-observer.d.ts +96 -0
- package/dist/composition-observer.d.ts.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/Dockerfile +35 -0
- package/dist/environment.d.ts +72 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +142 -0
- package/dist/environment.js.map +1 -0
- package/dist/goal-processor.d.ts +165 -0
- package/dist/goal-processor.d.ts.map +1 -0
- package/dist/helm/minibob-cluster/Chart.yaml +13 -0
- package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
- package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
- package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
- package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
- package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
- package/dist/helm/minibob-cluster/values-local.yaml +41 -0
- package/dist/helm/minibob-cluster/values-production.yaml +57 -0
- package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
- package/dist/helm/minibob-cluster/values.yaml +127 -0
- package/dist/improviser.d.ts +74 -0
- package/dist/improviser.d.ts.map +1 -0
- package/dist/impulse-filter.d.ts +74 -0
- package/dist/impulse-filter.d.ts.map +1 -0
- package/dist/impulse.d.ts +92 -0
- package/dist/impulse.d.ts.map +1 -0
- package/dist/impulse.js +234 -0
- package/dist/impulse.js.map +1 -0
- package/dist/lib.d.ts +29 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +18561 -0
- package/dist/lib.js.map +98 -0
- package/dist/lifecycle-hooks.d.ts +99 -0
- package/dist/lifecycle-hooks.d.ts.map +1 -0
- package/dist/lifecycle-hooks.js +135 -0
- package/dist/lifecycle-hooks.js.map +1 -0
- package/dist/llm.d.ts +31 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +349 -0
- package/dist/llm.js.map +1 -0
- package/dist/mcp-activity-bridge.d.ts +66 -0
- package/dist/mcp-activity-bridge.d.ts.map +1 -0
- package/dist/mcp-activity-bridge.js +126 -0
- package/dist/mcp-activity-bridge.js.map +1 -0
- package/dist/mcp.d.ts +216 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +292 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-agent.d.ts +92 -0
- package/dist/memory-agent.d.ts.map +1 -0
- package/dist/memory-agent.js +277 -0
- package/dist/memory-agent.js.map +1 -0
- package/dist/runtime-mapping.d.ts +97 -0
- package/dist/runtime-mapping.d.ts.map +1 -0
- package/dist/search-first-executor.d.ts +113 -0
- package/dist/search-first-executor.d.ts.map +1 -0
- package/dist/session.d.ts +48 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/template-extractor.d.ts +9 -0
- package/dist/template-extractor.d.ts.map +1 -0
- package/dist/template-generator.d.ts +12 -0
- package/dist/template-generator.d.ts.map +1 -0
- package/dist/tools.d.ts +58 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +771 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +503 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/understanding/analyzer.d.ts +55 -0
- package/dist/understanding/analyzer.d.ts.map +1 -0
- package/dist/understanding/explorer.d.ts +73 -0
- package/dist/understanding/explorer.d.ts.map +1 -0
- package/dist/understanding/index.d.ts +7 -0
- package/dist/understanding/index.d.ts.map +1 -0
- package/dist/understanding/types.d.ts +136 -0
- package/dist/understanding/types.d.ts.map +1 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +106 -0
- package/dist/validation.js.map +1 -0
- package/dist/vessel-bootstrap.d.ts +190 -0
- package/dist/vessel-bootstrap.d.ts.map +1 -0
- package/dist/vessel-registry.d.ts +229 -0
- package/dist/vessel-registry.d.ts.map +1 -0
- package/index.ts +1329 -0
- package/package.json +54 -0
- package/src/acp-gossip.ts +193 -0
- package/src/acp.ts +362 -0
- package/src/activity.ts +1464 -0
- package/src/agent-runtime.ts +365 -0
- package/src/boredom.ts +423 -0
- package/src/cli/acp-server.ts +377 -0
- package/src/cli/burrow.ts +896 -0
- package/src/cli/doctor.ts +526 -0
- package/src/cli/goal.ts +224 -0
- package/src/cli/index.ts +147 -0
- package/src/cli/instance-registry.ts +271 -0
- package/src/cli/observe.ts +682 -0
- package/src/cli/vessel.ts +287 -0
- package/src/components/SystemOverview.tsx +331 -0
- package/src/composition-observer.ts +449 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +167 -0
- package/src/goal-processor.ts +654 -0
- package/src/improviser.ts +591 -0
- package/src/impulse-filter.ts +273 -0
- package/src/impulse.ts +311 -0
- package/src/lib.ts +147 -0
- package/src/lifecycle-hooks.ts +181 -0
- package/src/llm.ts +434 -0
- package/src/mcp-activity-bridge.ts +158 -0
- package/src/mcp.ts +747 -0
- package/src/memory-agent.ts +316 -0
- package/src/runtime-mapping.ts +527 -0
- package/src/search-first-executor.ts +666 -0
- package/src/session.ts +141 -0
- package/src/template-extractor.ts +256 -0
- package/src/template-generator.ts +130 -0
- package/src/tools.ts +924 -0
- package/src/types.ts +497 -0
- package/src/understanding/analyzer.ts +354 -0
- package/src/understanding/explorer.ts +488 -0
- package/src/understanding/index.ts +27 -0
- package/src/understanding/types.ts +153 -0
- package/src/validation.ts +125 -0
- package/src/vessel-bootstrap.ts +440 -0
- package/src/vessel-registry.ts +621 -0
- package/templates/core/edit-file.json +85 -0
- package/templates/understanding/diagnose-problem.json +32 -0
- package/templates/understanding/explore-codebase-v2.json +57 -0
- package/templates/understanding/explore-codebase.json +37 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal Improviser - Execute goals without templates
|
|
3
|
+
*
|
|
4
|
+
* Pure improvisation: LLM figures out what to do step by step,
|
|
5
|
+
* using available tools. Everything is recorded for template extraction.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { LLMClient } from './llm'
|
|
9
|
+
import type { ToolHandler, ToolResult, Message, ActivityExecution, TaskResult } from './types'
|
|
10
|
+
import { createLLMClient } from './llm'
|
|
11
|
+
import { createToolHandlers } from './tools'
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// TYPES
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
export interface ImprovisationStep {
|
|
18
|
+
step: number
|
|
19
|
+
thought: string // LLM's reasoning about what to do
|
|
20
|
+
action: string // Tool name to use
|
|
21
|
+
params: Record<string, unknown>
|
|
22
|
+
result: ToolResult
|
|
23
|
+
duration_ms: number
|
|
24
|
+
timestamp: string
|
|
25
|
+
cost_estimate: number // Tokens used this step
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ImprovisationTrace {
|
|
29
|
+
execution_id: string
|
|
30
|
+
goal: string
|
|
31
|
+
improvisation: true
|
|
32
|
+
context?: Record<string, unknown>
|
|
33
|
+
started_at: string
|
|
34
|
+
completed_at?: string
|
|
35
|
+
steps: ImprovisationStep[]
|
|
36
|
+
outcome: {
|
|
37
|
+
status: 'success' | 'failure' | 'stuck'
|
|
38
|
+
goal_achieved: boolean
|
|
39
|
+
total_duration_ms: number
|
|
40
|
+
total_cost: number
|
|
41
|
+
total_tokens: {
|
|
42
|
+
input: number
|
|
43
|
+
output: number
|
|
44
|
+
}
|
|
45
|
+
files_modified: string[]
|
|
46
|
+
files_created: string[]
|
|
47
|
+
files_deleted: string[]
|
|
48
|
+
error?: string
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ImprovisationConfig {
|
|
53
|
+
maxSteps?: number // Max steps before giving up (default: 50)
|
|
54
|
+
temperature?: number // LLM temperature for creativity (default: 0.7)
|
|
55
|
+
stuckThreshold?: number // Same action repeated N times = stuck (default: 3)
|
|
56
|
+
saveTrace?: boolean // Save to backend (default: true)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// IMPROVISER
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
export interface ImproviserConfig {
|
|
64
|
+
provider?: string
|
|
65
|
+
apiKey?: string
|
|
66
|
+
model?: string
|
|
67
|
+
workingDirectory: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class GoalImproviser {
|
|
71
|
+
private llm: LLMClient
|
|
72
|
+
private tools: Record<string, ToolHandler>
|
|
73
|
+
private config: ImproviserConfig
|
|
74
|
+
|
|
75
|
+
constructor(config: ImproviserConfig) {
|
|
76
|
+
this.config = config
|
|
77
|
+
const provider = (config.provider || 'anthropic') as 'anthropic' | 'openai'
|
|
78
|
+
const apiKey = config.apiKey || process.env.ANTHROPIC_API_KEY || ''
|
|
79
|
+
this.llm = createLLMClient(provider, apiKey)
|
|
80
|
+
this.tools = createToolHandlers({
|
|
81
|
+
workingDirectory: config.workingDirectory
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Improvise solution to a goal without pre-defined template
|
|
87
|
+
*/
|
|
88
|
+
async improvise(
|
|
89
|
+
goal: string,
|
|
90
|
+
config: ImprovisationConfig = {}
|
|
91
|
+
): Promise<ImprovisationTrace> {
|
|
92
|
+
|
|
93
|
+
const {
|
|
94
|
+
maxSteps = 50,
|
|
95
|
+
temperature = 0.7,
|
|
96
|
+
stuckThreshold = 3,
|
|
97
|
+
saveTrace = true
|
|
98
|
+
} = config
|
|
99
|
+
|
|
100
|
+
console.log(`[Improviser] Starting improvisation for goal: ${goal} (max ${maxSteps} steps)`)
|
|
101
|
+
|
|
102
|
+
// Initialize trace
|
|
103
|
+
const trace: ImprovisationTrace = {
|
|
104
|
+
execution_id: `exec_improv_${Date.now()}_${this.randomId()}`,
|
|
105
|
+
goal,
|
|
106
|
+
improvisation: true,
|
|
107
|
+
started_at: new Date().toISOString(),
|
|
108
|
+
steps: [],
|
|
109
|
+
outcome: {
|
|
110
|
+
status: 'success',
|
|
111
|
+
goal_achieved: false,
|
|
112
|
+
total_duration_ms: 0,
|
|
113
|
+
total_cost: 0,
|
|
114
|
+
total_tokens: { input: 0, output: 0 },
|
|
115
|
+
files_modified: [],
|
|
116
|
+
files_created: [],
|
|
117
|
+
files_deleted: []
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const startTime = Date.now()
|
|
122
|
+
const messages: Message[] = []
|
|
123
|
+
|
|
124
|
+
// System prompt for improvisation
|
|
125
|
+
messages.push({
|
|
126
|
+
role: 'system',
|
|
127
|
+
content: this.buildSystemPrompt(goal)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
// Initial user message
|
|
131
|
+
messages.push({
|
|
132
|
+
role: 'user',
|
|
133
|
+
content: 'Start working toward the goal. What should you do first?'
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
let stepNumber = 0
|
|
137
|
+
let goalAchieved = false
|
|
138
|
+
const maxRetriesPerStep = 3
|
|
139
|
+
|
|
140
|
+
// Improvise step by step
|
|
141
|
+
while (!goalAchieved && stepNumber < maxSteps) {
|
|
142
|
+
stepNumber++
|
|
143
|
+
const stepStartTime = Date.now()
|
|
144
|
+
let retryCount = 0
|
|
145
|
+
let decision: any = null
|
|
146
|
+
let lastResponse: any = null
|
|
147
|
+
|
|
148
|
+
// Retry loop for JSON parse errors
|
|
149
|
+
while (retryCount <= maxRetriesPerStep && !decision) {
|
|
150
|
+
try {
|
|
151
|
+
// Get LLM decision
|
|
152
|
+
console.log(`[Improviser] Step ${stepNumber}: Requesting LLM decision${retryCount > 0 ? ` (retry ${retryCount}/${maxRetriesPerStep})` : ''}`)
|
|
153
|
+
|
|
154
|
+
lastResponse = await this.llm.complete({
|
|
155
|
+
model: this.config.model || 'claude-sonnet-4-20250514',
|
|
156
|
+
messages,
|
|
157
|
+
temperature,
|
|
158
|
+
maxTokens: 2000
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
// Parse decision (LLM should output structured JSON)
|
|
162
|
+
try {
|
|
163
|
+
decision = this.parseDecision(lastResponse.content)
|
|
164
|
+
// Successfully parsed - will break the while loop
|
|
165
|
+
} catch (parseError) {
|
|
166
|
+
// If JSON parse error and we have retries left, provide detailed feedback
|
|
167
|
+
if (parseError instanceof SyntaxError && retryCount < maxRetriesPerStep) {
|
|
168
|
+
const errorDetails = this.formatJSONParseError(parseError, lastResponse.content)
|
|
169
|
+
console.warn(`[Improviser] JSON parse error on step ${stepNumber}, retry ${retryCount + 1}/${maxRetriesPerStep}`)
|
|
170
|
+
console.warn(`[Improviser] Error: ${errorDetails}`)
|
|
171
|
+
|
|
172
|
+
// Add error feedback to messages and retry
|
|
173
|
+
messages.push({
|
|
174
|
+
role: 'assistant',
|
|
175
|
+
content: lastResponse.content
|
|
176
|
+
})
|
|
177
|
+
messages.push({
|
|
178
|
+
role: 'user',
|
|
179
|
+
content: `Your JSON output has a syntax error. Please fix it and try again.
|
|
180
|
+
|
|
181
|
+
${errorDetails}
|
|
182
|
+
|
|
183
|
+
Remember the correct format:
|
|
184
|
+
{
|
|
185
|
+
"thought": "your reasoning",
|
|
186
|
+
"action": "tool_name",
|
|
187
|
+
"params": { ... },
|
|
188
|
+
"goal_achieved": true/false
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
Please output the corrected JSON now.`
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
retryCount++
|
|
195
|
+
continue // Retry the LLM call
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Out of retries or different error type
|
|
199
|
+
throw parseError
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
// If we've exhausted retries, throw the error up
|
|
203
|
+
if (retryCount >= maxRetriesPerStep) {
|
|
204
|
+
throw error
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// At this point we have a valid decision
|
|
210
|
+
if (!decision) {
|
|
211
|
+
throw new Error('Failed to get valid decision after retries')
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
|
|
216
|
+
console.log(`[Improviser] Step ${stepNumber}: ${decision.action} - ${decision.thought}`)
|
|
217
|
+
|
|
218
|
+
// Execute the action
|
|
219
|
+
const toolHandler = this.tools[decision.action]
|
|
220
|
+
if (!toolHandler) {
|
|
221
|
+
throw new Error(`Unknown tool: ${decision.action}`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const toolResult = await toolHandler(decision.params)
|
|
225
|
+
|
|
226
|
+
// Record step
|
|
227
|
+
const step: ImprovisationStep = {
|
|
228
|
+
step: stepNumber,
|
|
229
|
+
thought: decision.thought,
|
|
230
|
+
action: decision.action,
|
|
231
|
+
params: decision.params,
|
|
232
|
+
result: toolResult,
|
|
233
|
+
duration_ms: Date.now() - stepStartTime,
|
|
234
|
+
timestamp: new Date().toISOString(),
|
|
235
|
+
cost_estimate: this.estimateCost(lastResponse)
|
|
236
|
+
}
|
|
237
|
+
trace.steps.push(step)
|
|
238
|
+
|
|
239
|
+
// Update conversation history
|
|
240
|
+
messages.push({
|
|
241
|
+
role: 'assistant',
|
|
242
|
+
content: JSON.stringify({
|
|
243
|
+
thought: decision.thought,
|
|
244
|
+
action: decision.action,
|
|
245
|
+
params: decision.params
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
messages.push({
|
|
250
|
+
role: 'user',
|
|
251
|
+
content: this.formatToolResult(toolResult, decision.action)
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
// Check if goal achieved
|
|
255
|
+
goalAchieved = decision.goal_achieved || false
|
|
256
|
+
|
|
257
|
+
if (goalAchieved) {
|
|
258
|
+
console.log(`[Improviser] Goal achieved at step ${stepNumber}!`)
|
|
259
|
+
break
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Check if stuck
|
|
263
|
+
if (this.isStuck(trace.steps, stuckThreshold)) {
|
|
264
|
+
console.warn(`[Improviser] Appears stuck - repeated action: ${trace.steps.slice(-1)[0]?.action}`)
|
|
265
|
+
trace.outcome.status = 'stuck'
|
|
266
|
+
trace.outcome.error = 'Repeated same action too many times'
|
|
267
|
+
break
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(`[Improviser] Step ${stepNumber} failed:`, error instanceof Error ? error.message : String(error))
|
|
272
|
+
|
|
273
|
+
// Add error to conversation so LLM can recover
|
|
274
|
+
messages.push({
|
|
275
|
+
role: 'user',
|
|
276
|
+
content: `Error occurred: ${error instanceof Error ? error.message : String(error)}\nHow can you recover?`
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Finalize trace
|
|
282
|
+
trace.completed_at = new Date().toISOString()
|
|
283
|
+
trace.outcome.total_duration_ms = Date.now() - startTime
|
|
284
|
+
trace.outcome.goal_achieved = goalAchieved
|
|
285
|
+
trace.outcome.status = goalAchieved ? 'success' : (trace.outcome.status || 'failure')
|
|
286
|
+
|
|
287
|
+
// Extract file changes
|
|
288
|
+
trace.outcome.files_modified = this.extractFilesModified(trace.steps)
|
|
289
|
+
trace.outcome.files_created = this.extractFilesCreated(trace.steps)
|
|
290
|
+
trace.outcome.files_deleted = this.extractFilesDeleted(trace.steps)
|
|
291
|
+
|
|
292
|
+
// Calculate totals
|
|
293
|
+
trace.outcome.total_cost = trace.steps.reduce((sum, s) => sum + s.cost_estimate, 0)
|
|
294
|
+
|
|
295
|
+
console.log(`[Improviser] Complete: ${trace.execution_id} - ${trace.outcome.goal_achieved ? 'SUCCESS' : 'FAILED'} (${trace.steps.length} steps, ${trace.outcome.total_duration_ms}ms, $${trace.outcome.total_cost.toFixed(4)})`)
|
|
296
|
+
|
|
297
|
+
// Save trace if configured
|
|
298
|
+
if (saveTrace) {
|
|
299
|
+
await this.saveTrace(trace)
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return trace
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// ==========================================================================
|
|
306
|
+
// HELPER METHODS
|
|
307
|
+
// ==========================================================================
|
|
308
|
+
|
|
309
|
+
private buildSystemPrompt(goal: string): string {
|
|
310
|
+
return `You are MiniBob, an autonomous agent that achieves goals through improvisation.
|
|
311
|
+
|
|
312
|
+
GOAL: ${goal}
|
|
313
|
+
|
|
314
|
+
You have access to these tools:
|
|
315
|
+
- bash: Execute shell commands
|
|
316
|
+
- read: Read file contents
|
|
317
|
+
- write: Create new files
|
|
318
|
+
- edit: Modify existing files
|
|
319
|
+
- git: Git operations
|
|
320
|
+
|
|
321
|
+
Your approach:
|
|
322
|
+
1. Think about what to do next (reasoning)
|
|
323
|
+
2. Choose a tool and parameters
|
|
324
|
+
3. Execute the action
|
|
325
|
+
4. Observe the result
|
|
326
|
+
5. Decide if goal is achieved or continue
|
|
327
|
+
|
|
328
|
+
IMPORTANT OUTPUT FORMAT:
|
|
329
|
+
After reasoning, output ONLY valid JSON in this format:
|
|
330
|
+
{
|
|
331
|
+
"thought": "your reasoning about what to do and why",
|
|
332
|
+
"action": "tool_name",
|
|
333
|
+
"params": { "param1": "value1", ... },
|
|
334
|
+
"goal_achieved": true/false
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
Start by understanding the current state, then work systematically toward the goal.
|
|
338
|
+
Take concrete actions - don't just plan, actually do things.`
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private formatJSONParseError(error: SyntaxError, content: string): string {
|
|
342
|
+
const errorMessage = error.message
|
|
343
|
+
|
|
344
|
+
// Extract JSON from content
|
|
345
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/)
|
|
346
|
+
if (!jsonMatch) {
|
|
347
|
+
return `No JSON object found in output. Make sure to output a valid JSON object.`
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const jsonStr = jsonMatch[0]
|
|
351
|
+
|
|
352
|
+
// Try to extract position information from error message
|
|
353
|
+
const positionMatch = errorMessage.match(/position (\d+)/)
|
|
354
|
+
if (positionMatch && positionMatch[1]) {
|
|
355
|
+
const position = parseInt(positionMatch[1], 10)
|
|
356
|
+
const before = jsonStr.substring(Math.max(0, position - 50), position)
|
|
357
|
+
const after = jsonStr.substring(position, Math.min(jsonStr.length, position + 50))
|
|
358
|
+
|
|
359
|
+
return `JSON Syntax Error: ${errorMessage}
|
|
360
|
+
|
|
361
|
+
Context around error position ${position}:
|
|
362
|
+
...${before}⚠️HERE⚠️${after}...
|
|
363
|
+
|
|
364
|
+
The JSON you generated:
|
|
365
|
+
${jsonStr.substring(0, 500)}${jsonStr.length > 500 ? '...' : ''}
|
|
366
|
+
|
|
367
|
+
Common issues:
|
|
368
|
+
- Unterminated strings (missing closing quote)
|
|
369
|
+
- Unescaped quotes inside strings (use \\" instead of ")
|
|
370
|
+
- Unescaped newlines in strings (use \\n)
|
|
371
|
+
- Missing commas between fields
|
|
372
|
+
- Trailing commas before closing braces`
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// If no position info, provide general feedback
|
|
376
|
+
return `JSON Syntax Error: ${errorMessage}
|
|
377
|
+
|
|
378
|
+
The JSON you generated:
|
|
379
|
+
${jsonStr.substring(0, 500)}${jsonStr.length > 500 ? '...' : ''}
|
|
380
|
+
|
|
381
|
+
Common issues:
|
|
382
|
+
- Unterminated strings (missing closing quote)
|
|
383
|
+
- Unescaped quotes inside strings (use \\" instead of ")
|
|
384
|
+
- Unescaped newlines in strings (use \\n)
|
|
385
|
+
- Missing commas between fields
|
|
386
|
+
- Trailing commas before closing braces
|
|
387
|
+
- Make sure all strings are properly quoted`
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
private parseDecision(content: string): {
|
|
391
|
+
thought: string
|
|
392
|
+
action: string
|
|
393
|
+
params: Record<string, unknown>
|
|
394
|
+
goal_achieved: boolean
|
|
395
|
+
} {
|
|
396
|
+
// Extract JSON from content (may have markdown formatting)
|
|
397
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/)
|
|
398
|
+
if (!jsonMatch) {
|
|
399
|
+
throw new Error('LLM did not return valid JSON')
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const decision = JSON.parse(jsonMatch[0])
|
|
403
|
+
|
|
404
|
+
if (!decision.thought || !decision.action) {
|
|
405
|
+
throw new Error('Decision missing required fields')
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
thought: decision.thought,
|
|
410
|
+
action: decision.action,
|
|
411
|
+
params: decision.params || {},
|
|
412
|
+
goal_achieved: decision.goal_achieved || false
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private formatToolResult(result: ToolResult, action: string): string {
|
|
417
|
+
return `Tool ${action} result:
|
|
418
|
+
Success: ${result.success}
|
|
419
|
+
${result.output ? `Output:\n${result.output}` : ''}
|
|
420
|
+
${result.error ? `Error:\n${result.error}` : ''}
|
|
421
|
+
|
|
422
|
+
What should you do next?`
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
private isStuck(steps: ImprovisationStep[], threshold: number): boolean {
|
|
426
|
+
if (steps.length < threshold) return false
|
|
427
|
+
|
|
428
|
+
const lastN = steps.slice(-threshold)
|
|
429
|
+
const actions = lastN.map(s => s.action)
|
|
430
|
+
|
|
431
|
+
// Stuck if all last N actions are the same
|
|
432
|
+
return actions.every(a => a === actions[0])
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private extractFilesModified(steps: ImprovisationStep[]): string[] {
|
|
436
|
+
return [...new Set(
|
|
437
|
+
steps
|
|
438
|
+
.filter(s => s.action === 'edit' && s.result.success)
|
|
439
|
+
.map(s => s.params.path as string)
|
|
440
|
+
.filter(Boolean)
|
|
441
|
+
)]
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private extractFilesCreated(steps: ImprovisationStep[]): string[] {
|
|
445
|
+
return [...new Set(
|
|
446
|
+
steps
|
|
447
|
+
.filter(s => s.action === 'write' && s.result.success)
|
|
448
|
+
.map(s => s.params.path as string)
|
|
449
|
+
.filter(Boolean)
|
|
450
|
+
)]
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private extractFilesDeleted(steps: ImprovisationStep[]): string[] {
|
|
454
|
+
return [...new Set(
|
|
455
|
+
steps
|
|
456
|
+
.filter(s => s.action === 'bash' && s.params.command?.toString().includes('rm '))
|
|
457
|
+
.map(s => {
|
|
458
|
+
// Extract file from rm command
|
|
459
|
+
const cmd = s.params.command as string
|
|
460
|
+
const match = cmd.match(/rm\s+(.+)/)
|
|
461
|
+
return match ? match[1] : null
|
|
462
|
+
})
|
|
463
|
+
.filter(Boolean) as string[]
|
|
464
|
+
)]
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
private estimateCost(response: any): number {
|
|
468
|
+
// Rough estimate: $0.003 per 1K input tokens, $0.015 per 1K output tokens
|
|
469
|
+
const inputTokens = response.usage?.input_tokens || 0
|
|
470
|
+
const outputTokens = response.usage?.output_tokens || 0
|
|
471
|
+
|
|
472
|
+
return (inputTokens / 1000) * 0.003 + (outputTokens / 1000) * 0.015
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private async saveTrace(trace: ImprovisationTrace): Promise<void> {
|
|
476
|
+
// Convert ImprovisationTrace to ActivityExecution format
|
|
477
|
+
// This unifies all execution modes (template, goal-seeking, improvisation)
|
|
478
|
+
// to use the same backend storage and learning mechanisms
|
|
479
|
+
try {
|
|
480
|
+
const { getMCPClient, isMCPEnabled } = await import('./mcp')
|
|
481
|
+
if (!isMCPEnabled()) return
|
|
482
|
+
|
|
483
|
+
const mcp = getMCPClient()
|
|
484
|
+
if (!mcp) return
|
|
485
|
+
|
|
486
|
+
// Generate templateId from goal (enables learning which goals work)
|
|
487
|
+
// Format: "improvised-{sanitized-goal}-{hash}"
|
|
488
|
+
const goalSlug = trace.goal
|
|
489
|
+
.toLowerCase()
|
|
490
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
491
|
+
.substring(0, 50)
|
|
492
|
+
const templateId = `improvised-${goalSlug}`
|
|
493
|
+
|
|
494
|
+
// Convert to ActivityExecution format
|
|
495
|
+
const activityExecution = {
|
|
496
|
+
id: trace.execution_id,
|
|
497
|
+
templateId: templateId,
|
|
498
|
+
status: trace.outcome.status === 'success' ? 'completed' as const : 'failed' as const,
|
|
499
|
+
variables: trace.context || {},
|
|
500
|
+
impulses: [], // Improvisation starts with no impulses
|
|
501
|
+
taskResults: trace.steps.map((step, idx) => ({
|
|
502
|
+
taskId: `step-${step.step}`,
|
|
503
|
+
status: step.result.success ? 'completed' as const : 'failed' as const,
|
|
504
|
+
output: step.result.output,
|
|
505
|
+
error: step.result.error,
|
|
506
|
+
duration: step.duration_ms,
|
|
507
|
+
})),
|
|
508
|
+
startedAt: Date.parse(trace.started_at),
|
|
509
|
+
completedAt: trace.completed_at ? Date.parse(trace.completed_at) : undefined,
|
|
510
|
+
error: trace.outcome.error,
|
|
511
|
+
executionTrace: {
|
|
512
|
+
tasks: trace.steps.map(step => ({
|
|
513
|
+
id: `step-${step.step}`,
|
|
514
|
+
description: step.thought,
|
|
515
|
+
actualPrompt: step.thought, // The thought is the prompt in improvisation
|
|
516
|
+
toolCalls: [{
|
|
517
|
+
id: `tool:${step.action}:step-${step.step}:${Date.now()}`,
|
|
518
|
+
name: step.action,
|
|
519
|
+
arguments: step.params,
|
|
520
|
+
result: {
|
|
521
|
+
success: step.result.success,
|
|
522
|
+
output: step.result.output,
|
|
523
|
+
error: step.result.error,
|
|
524
|
+
},
|
|
525
|
+
}],
|
|
526
|
+
response: step.result.output || step.result.error || '',
|
|
527
|
+
result: {
|
|
528
|
+
status: step.result.success ? 'success' as const : 'failure' as const,
|
|
529
|
+
error: step.result.error,
|
|
530
|
+
},
|
|
531
|
+
inputState: {
|
|
532
|
+
filesAvailable: [],
|
|
533
|
+
environment: {},
|
|
534
|
+
impulses: [],
|
|
535
|
+
variables: {},
|
|
536
|
+
},
|
|
537
|
+
outputState: {
|
|
538
|
+
filesModified: trace.outcome.files_modified,
|
|
539
|
+
filesCreated: trace.outcome.files_created,
|
|
540
|
+
filesDeleted: trace.outcome.files_deleted,
|
|
541
|
+
},
|
|
542
|
+
stateTransition: {
|
|
543
|
+
before: {},
|
|
544
|
+
after: {},
|
|
545
|
+
workingDirectory: process.cwd(),
|
|
546
|
+
},
|
|
547
|
+
impulsesUsed: [],
|
|
548
|
+
impulsesCreated: [],
|
|
549
|
+
llmInteractions: [{
|
|
550
|
+
messages: [
|
|
551
|
+
{
|
|
552
|
+
role: 'user' as const,
|
|
553
|
+
content: step.thought,
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
response: {
|
|
557
|
+
content: JSON.stringify({ action: step.action, params: step.params }),
|
|
558
|
+
toolCalls: [],
|
|
559
|
+
},
|
|
560
|
+
tokens: { input: 0, output: 0 },
|
|
561
|
+
cost: 0,
|
|
562
|
+
duration: step.duration_ms,
|
|
563
|
+
}],
|
|
564
|
+
duration: step.duration_ms,
|
|
565
|
+
})),
|
|
566
|
+
impulsesCreated: [],
|
|
567
|
+
filesModified: trace.outcome.files_modified,
|
|
568
|
+
goalContext: {
|
|
569
|
+
goal: trace.goal,
|
|
570
|
+
intent: 'improvisation',
|
|
571
|
+
context: trace.context || {},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
metrics: {
|
|
575
|
+
duration: trace.outcome.total_duration_ms,
|
|
576
|
+
cost: trace.outcome.total_cost,
|
|
577
|
+
totalTokens: trace.outcome.total_tokens,
|
|
578
|
+
},
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
await mcp.storeExecutionTrace(activityExecution)
|
|
582
|
+
console.log(`[Improviser] Trace saved to backend: ${trace.execution_id} (template: ${templateId})`)
|
|
583
|
+
} catch (error) {
|
|
584
|
+
console.error('[Improviser] Failed to save trace:', error)
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
private randomId(): string {
|
|
589
|
+
return Math.random().toString(36).substring(7)
|
|
590
|
+
}
|
|
591
|
+
}
|