@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,654 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Goal Processor - Core orchestrator for goal-driven activity execution
|
|
3
|
+
*
|
|
4
|
+
* This module transforms user goals into activity executions:
|
|
5
|
+
* 1. Receive user goal message
|
|
6
|
+
* 2. Get activity recommendations from metabob-activity-api (Thompson Sampling)
|
|
7
|
+
* 3. Execute recommended activity
|
|
8
|
+
* 4. Check goal completion
|
|
9
|
+
* 5. Loop until goal is complete or max attempts reached
|
|
10
|
+
*
|
|
11
|
+
* Architecture:
|
|
12
|
+
* User goal → GoalProcessor → Backend recommendations → ActivityExecutor → Goal complete
|
|
13
|
+
*
|
|
14
|
+
* Key principle: Backend (metabob-activity-api) handles activity recommendation
|
|
15
|
+
* based on historical execution data, success rates, and goal alignment.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { ActivityExecution, Impulse } from "./types"
|
|
19
|
+
import { ActivityExecutor, loadTemplateFromMCPOrLocal } from "./activity"
|
|
20
|
+
import { getMCPClient, isMCPEnabled } from "./mcp"
|
|
21
|
+
import { GoalImproviser } from "./improviser"
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// TYPES
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Structured goal representation
|
|
29
|
+
*/
|
|
30
|
+
export interface Goal {
|
|
31
|
+
/** Original user message */
|
|
32
|
+
message: string
|
|
33
|
+
/** Goal type inferred from message */
|
|
34
|
+
type: "feature" | "bugfix" | "refactor" | "exploration" | "other"
|
|
35
|
+
/** Parsed intent (what user wants to accomplish) */
|
|
36
|
+
intent: string
|
|
37
|
+
/** Context from user (files, variables, etc.) */
|
|
38
|
+
context: Record<string, unknown>
|
|
39
|
+
/** Created timestamp */
|
|
40
|
+
createdAt: number
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Activity recommendation from backend
|
|
45
|
+
*/
|
|
46
|
+
export interface ActivityRecommendation {
|
|
47
|
+
/** Template ID to execute */
|
|
48
|
+
templateId: string
|
|
49
|
+
/** Selection metadata from backend (Thompson Sampling) */
|
|
50
|
+
selectionMetadata: {
|
|
51
|
+
method: string
|
|
52
|
+
alpha?: number
|
|
53
|
+
beta?: number
|
|
54
|
+
sample?: number
|
|
55
|
+
score?: number
|
|
56
|
+
}
|
|
57
|
+
/** Suggested variables for execution (derived from goal context) */
|
|
58
|
+
variables: Record<string, unknown>
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Goal execution result
|
|
63
|
+
*/
|
|
64
|
+
export interface GoalResult {
|
|
65
|
+
/** Goal that was executed */
|
|
66
|
+
goal: Goal
|
|
67
|
+
/** Activities executed during goal pursuit */
|
|
68
|
+
executions: ActivityExecution[]
|
|
69
|
+
/** Whether goal was completed */
|
|
70
|
+
completed: boolean
|
|
71
|
+
/** Completion reason */
|
|
72
|
+
completionReason: string
|
|
73
|
+
/** Total duration in milliseconds */
|
|
74
|
+
totalDuration: number
|
|
75
|
+
/** Total cost in dollars */
|
|
76
|
+
totalCost: number
|
|
77
|
+
/** Total tokens used */
|
|
78
|
+
totalTokens: {
|
|
79
|
+
input: number
|
|
80
|
+
output: number
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// =============================================================================
|
|
85
|
+
// GOAL PROCESSOR
|
|
86
|
+
// =============================================================================
|
|
87
|
+
|
|
88
|
+
export class GoalProcessor {
|
|
89
|
+
private readonly workingDirectory: string
|
|
90
|
+
private readonly executor: ActivityExecutor
|
|
91
|
+
|
|
92
|
+
constructor(config: {
|
|
93
|
+
workingDirectory: string
|
|
94
|
+
executor: ActivityExecutor
|
|
95
|
+
}) {
|
|
96
|
+
this.workingDirectory = config.workingDirectory
|
|
97
|
+
this.executor = config.executor
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Parse user goal message into structured Goal
|
|
102
|
+
*
|
|
103
|
+
* For now, simple parsing. Can be enhanced with LLM later.
|
|
104
|
+
*/
|
|
105
|
+
parseGoal(message: string, context?: Record<string, unknown>): Goal {
|
|
106
|
+
// Simple type inference from keywords
|
|
107
|
+
let type: Goal["type"] = "other"
|
|
108
|
+
const lowerMessage = message.toLowerCase()
|
|
109
|
+
|
|
110
|
+
if (lowerMessage.includes("add") || lowerMessage.includes("create") || lowerMessage.includes("implement")) {
|
|
111
|
+
type = "feature"
|
|
112
|
+
} else if (lowerMessage.includes("fix") || lowerMessage.includes("bug") || lowerMessage.includes("error")) {
|
|
113
|
+
type = "bugfix"
|
|
114
|
+
} else if (lowerMessage.includes("refactor") || lowerMessage.includes("clean") || lowerMessage.includes("reorganize")) {
|
|
115
|
+
type = "refactor"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
message,
|
|
120
|
+
type,
|
|
121
|
+
intent: message, // For now, intent = message
|
|
122
|
+
context: context || {},
|
|
123
|
+
createdAt: Date.now(),
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get activity recommendations from backend API
|
|
129
|
+
*
|
|
130
|
+
* NOTE: This is a placeholder. In the real implementation, this will be called
|
|
131
|
+
* by opencode's MinibobIntegration which has access to MetabobCLI.recommendActivities().
|
|
132
|
+
*
|
|
133
|
+
* The backend recommendation engine:
|
|
134
|
+
* - Applies Thompson Sampling for exploration/exploitation
|
|
135
|
+
* - Considers historical execution success rates
|
|
136
|
+
* - Aligns recommendations with goal category
|
|
137
|
+
* - Uses impulse state for context-aware recommendations
|
|
138
|
+
*
|
|
139
|
+
* @param goal - Parsed goal
|
|
140
|
+
* @param loadedImpulseIds - IDs of currently loaded impulses
|
|
141
|
+
* @param limit - Max recommendations to return
|
|
142
|
+
* @returns Array of activity recommendations from backend
|
|
143
|
+
*/
|
|
144
|
+
async getRecommendations(
|
|
145
|
+
goal: Goal,
|
|
146
|
+
loadedImpulseIds: string[] = [],
|
|
147
|
+
limit: number = 3
|
|
148
|
+
): Promise<ActivityRecommendation[]> {
|
|
149
|
+
// Get recommendations from MCP backend using Thompson Sampling
|
|
150
|
+
const mcpClient = getMCPClient()
|
|
151
|
+
|
|
152
|
+
if (!mcpClient) {
|
|
153
|
+
console.warn("[GoalProcessor] No MCP client available - cannot get recommendations")
|
|
154
|
+
console.warn("[GoalProcessor] Set MINIBOB_MCP_ENDPOINT environment variable or call initializeMCP()")
|
|
155
|
+
return []
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
console.log(`[GoalProcessor] Requesting ${limit} recommendations from backend`)
|
|
159
|
+
console.log(`[GoalProcessor] Goal intent: ${goal.intent}`)
|
|
160
|
+
console.log(`[GoalProcessor] Goal type: ${goal.type}`)
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const recommendations = await mcpClient.recommendActivities(
|
|
164
|
+
goal.intent,
|
|
165
|
+
goal.type !== 'other' ? goal.type : undefined,
|
|
166
|
+
loadedImpulseIds,
|
|
167
|
+
limit
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
console.log(`[GoalProcessor] Received ${recommendations.length} recommendations from backend`)
|
|
171
|
+
|
|
172
|
+
// Transform backend format to ActivityRecommendation
|
|
173
|
+
return recommendations.map(rec => ({
|
|
174
|
+
templateId: rec.template_id,
|
|
175
|
+
selectionMetadata: rec.selection_metadata || { method: 'thompson_sampling' },
|
|
176
|
+
variables: goal.context || {} // Use goal context as variable suggestions
|
|
177
|
+
}))
|
|
178
|
+
} catch (err) {
|
|
179
|
+
console.error("[GoalProcessor] Failed to get recommendations:", err instanceof Error ? err.message : String(err))
|
|
180
|
+
return []
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Assess relevance of a recommendation to the goal
|
|
186
|
+
*
|
|
187
|
+
* Uses simple keyword matching and semantic similarity to determine if
|
|
188
|
+
* the recommended activity is likely to help achieve the goal.
|
|
189
|
+
*
|
|
190
|
+
* @param goal - The goal being pursued
|
|
191
|
+
* @param recommendation - The activity recommendation
|
|
192
|
+
* @returns Relevance score from 0.0 (not relevant) to 1.0 (highly relevant)
|
|
193
|
+
*/
|
|
194
|
+
private assessRelevance(goal: Goal, recommendation: ActivityRecommendation): number {
|
|
195
|
+
const templateId = recommendation.templateId.toLowerCase()
|
|
196
|
+
const goalIntent = goal.intent.toLowerCase()
|
|
197
|
+
const goalType = goal.type.toLowerCase()
|
|
198
|
+
|
|
199
|
+
// Extract key terms from goal
|
|
200
|
+
const goalTerms = goalIntent.split(/\s+/).filter(term => term.length > 3)
|
|
201
|
+
|
|
202
|
+
// Check for type alignment
|
|
203
|
+
let score = 0.0
|
|
204
|
+
|
|
205
|
+
// Type match gives base score
|
|
206
|
+
if (templateId.includes(goalType) || templateId.includes(goal.type)) {
|
|
207
|
+
score += 0.3
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Keyword overlap
|
|
211
|
+
const matchedTerms = goalTerms.filter(term => templateId.includes(term))
|
|
212
|
+
if (matchedTerms.length > 0) {
|
|
213
|
+
score += 0.3 * (matchedTerms.length / Math.max(goalTerms.length, 1))
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Special patterns
|
|
217
|
+
if (goal.type === 'bugfix' && templateId.includes('fix')) score += 0.2
|
|
218
|
+
if (goal.type === 'bugfix' && templateId.includes('debug')) score += 0.2
|
|
219
|
+
if (goal.type === 'feature' && templateId.includes('add')) score += 0.2
|
|
220
|
+
if (goal.type === 'feature' && templateId.includes('implement')) score += 0.2
|
|
221
|
+
if (goal.type === 'refactor' && templateId.includes('refactor')) score += 0.2
|
|
222
|
+
if (goal.type === 'refactor' && templateId.includes('optimize')) score += 0.2
|
|
223
|
+
|
|
224
|
+
// Generic templates are less relevant (lower priority)
|
|
225
|
+
if (templateId.includes('generic') || templateId.includes('default')) {
|
|
226
|
+
score *= 0.7
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return Math.min(score, 1.0)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Check if goal is complete based on executions
|
|
234
|
+
*
|
|
235
|
+
* With optional goal verification:
|
|
236
|
+
* - If last activity succeeded AND goal provided, verify achievement objectively
|
|
237
|
+
* - If last activity failed, goal is incomplete
|
|
238
|
+
* - Without goal, uses simple status check (backward compatibility)
|
|
239
|
+
*
|
|
240
|
+
* @param executions - Activity executions performed
|
|
241
|
+
* @param goal - Optional goal for objective verification
|
|
242
|
+
* @returns Completion status with reason
|
|
243
|
+
*/
|
|
244
|
+
isGoalComplete(executions: ActivityExecution[], goal?: Goal): { complete: boolean; reason: string } {
|
|
245
|
+
if (executions.length === 0) {
|
|
246
|
+
return { complete: false, reason: "No activities executed yet" }
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const lastExecution = executions[executions.length - 1]
|
|
250
|
+
if (!lastExecution) {
|
|
251
|
+
return { complete: false, reason: "No execution data available" }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (lastExecution.status === "failed") {
|
|
255
|
+
return { complete: false, reason: "Last activity failed" }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (lastExecution.status === "completed") {
|
|
259
|
+
// With goal provided, verify achievement objectively
|
|
260
|
+
if (goal) {
|
|
261
|
+
const verification = this.verifyGoalAchievement(goal, executions)
|
|
262
|
+
if (!verification.verified) {
|
|
263
|
+
console.warn("[GoalProcessor] Activity succeeded but goal not achieved:", verification.reason)
|
|
264
|
+
return { complete: false, reason: `Activity succeeded but goal not achieved: ${verification.reason}` }
|
|
265
|
+
}
|
|
266
|
+
return { complete: true, reason: verification.reason }
|
|
267
|
+
}
|
|
268
|
+
// Backward compatibility: without goal, trust activity status
|
|
269
|
+
return { complete: true, reason: "Activity completed successfully" }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return { complete: false, reason: "Activity still in progress" }
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Verify goal was actually achieved using objective criteria
|
|
277
|
+
* Prevents LLM hallucination where activity claims success but did nothing
|
|
278
|
+
*
|
|
279
|
+
* @param goal - The goal being evaluated
|
|
280
|
+
* @param executions - Activity executions performed
|
|
281
|
+
* @returns Verification result with reason
|
|
282
|
+
*/
|
|
283
|
+
private verifyGoalAchievement(goal: Goal, executions: ActivityExecution[]): { verified: boolean; reason: string } {
|
|
284
|
+
// File modification goals
|
|
285
|
+
if (goal.intent.match(/change|modify|edit|update|replace/i)) {
|
|
286
|
+
const filesModified = executions.reduce((sum, exec) =>
|
|
287
|
+
sum + (exec.executionTrace?.filesModified.length || 0), 0
|
|
288
|
+
)
|
|
289
|
+
if (filesModified === 0) {
|
|
290
|
+
return { verified: false, reason: "No files were modified" }
|
|
291
|
+
}
|
|
292
|
+
return { verified: true, reason: `Verified: ${filesModified} file(s) modified` }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Test execution goals
|
|
296
|
+
if (goal.intent.match(/test/i)) {
|
|
297
|
+
const hasTestOutput = executions.some(exec =>
|
|
298
|
+
exec.taskResults?.some(tr => tr.output?.toLowerCase().includes('test'))
|
|
299
|
+
)
|
|
300
|
+
if (!hasTestOutput) {
|
|
301
|
+
return { verified: false, reason: "No test execution detected" }
|
|
302
|
+
}
|
|
303
|
+
return { verified: true, reason: "Verified: Tests executed" }
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Code creation goals
|
|
307
|
+
if (goal.intent.match(/create|add|implement|write/i)) {
|
|
308
|
+
const filesModified = executions.reduce((sum, exec) =>
|
|
309
|
+
sum + (exec.executionTrace?.filesModified.length || 0), 0
|
|
310
|
+
)
|
|
311
|
+
if (filesModified === 0) {
|
|
312
|
+
return { verified: false, reason: "No files created or modified" }
|
|
313
|
+
}
|
|
314
|
+
return { verified: true, reason: `Verified: ${filesModified} file(s) modified` }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Analysis/exploration goals
|
|
318
|
+
if (goal.intent.match(/analyze|explore|find|search/i)) {
|
|
319
|
+
const toolsUsed = executions.reduce((sum, exec) => {
|
|
320
|
+
const taskToolCalls = exec.taskResults?.reduce((taskSum, tr) =>
|
|
321
|
+
taskSum + (tr.metadata?.toolCalls?.length || 0), 0
|
|
322
|
+
) || 0
|
|
323
|
+
return sum + taskToolCalls
|
|
324
|
+
}, 0)
|
|
325
|
+
if (toolsUsed === 0) {
|
|
326
|
+
return { verified: false, reason: "No tools were used" }
|
|
327
|
+
}
|
|
328
|
+
return { verified: true, reason: `Verified: ${toolsUsed} tool call(s)` }
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Default: require measurable work
|
|
332
|
+
const filesModified = executions.reduce((sum, exec) =>
|
|
333
|
+
sum + (exec.executionTrace?.filesModified.length || 0), 0
|
|
334
|
+
)
|
|
335
|
+
const toolsUsed = executions.reduce((sum, exec) => {
|
|
336
|
+
const taskToolCalls = exec.taskResults?.reduce((taskSum, tr) =>
|
|
337
|
+
taskSum + (tr.metadata?.toolCalls?.length || 0), 0
|
|
338
|
+
) || 0
|
|
339
|
+
return sum + taskToolCalls
|
|
340
|
+
}, 0)
|
|
341
|
+
|
|
342
|
+
if (filesModified === 0 && toolsUsed === 0) {
|
|
343
|
+
console.warn("[GoalProcessor] No verifiable work detected for goal:", goal.intent)
|
|
344
|
+
return { verified: false, reason: "No files modified and no tools used" }
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { verified: true, reason: "Activity completed with measurable outcomes" }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Create impulses from execution output for context propagation
|
|
352
|
+
*/
|
|
353
|
+
private createImpulsesFromExecution(execution: ActivityExecution): Impulse[] {
|
|
354
|
+
const impulses: Impulse[] = []
|
|
355
|
+
const timestamp = Date.now()
|
|
356
|
+
|
|
357
|
+
// Create impulse for overall execution trace
|
|
358
|
+
impulses.push({
|
|
359
|
+
id: `impulse:trace:${execution.id}`,
|
|
360
|
+
pointer: {
|
|
361
|
+
type: 'activityExecutionTrace',
|
|
362
|
+
executionId: execution.id,
|
|
363
|
+
},
|
|
364
|
+
budget: 4000, // Generous budget for execution context
|
|
365
|
+
priority: 'high',
|
|
366
|
+
loaded: false,
|
|
367
|
+
createdAt: timestamp,
|
|
368
|
+
tags: ['execution-output', execution.templateId],
|
|
369
|
+
})
|
|
370
|
+
|
|
371
|
+
// Create impulses for individual task outputs (if they have significant output)
|
|
372
|
+
execution.taskResults?.forEach((taskResult, index) => {
|
|
373
|
+
if (taskResult.output && taskResult.output.length > 100) {
|
|
374
|
+
impulses.push({
|
|
375
|
+
id: `impulse:output:${execution.id}:${taskResult.taskId}`,
|
|
376
|
+
pointer: {
|
|
377
|
+
type: 'activityOutput',
|
|
378
|
+
activityId: execution.id,
|
|
379
|
+
taskId: taskResult.taskId,
|
|
380
|
+
},
|
|
381
|
+
budget: 2000,
|
|
382
|
+
priority: 'medium',
|
|
383
|
+
loaded: false,
|
|
384
|
+
createdAt: timestamp,
|
|
385
|
+
tags: ['task-output', execution.templateId],
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
|
|
390
|
+
// Create impulses for modified files
|
|
391
|
+
execution.executionTrace?.filesModified?.forEach((filePath, index) => {
|
|
392
|
+
// Only create file impulses for the first few files to avoid context bloat
|
|
393
|
+
if (index < 3) {
|
|
394
|
+
impulses.push({
|
|
395
|
+
id: `impulse:file:${execution.id}:${index}`,
|
|
396
|
+
pointer: {
|
|
397
|
+
type: 'file',
|
|
398
|
+
path: filePath,
|
|
399
|
+
},
|
|
400
|
+
budget: 3000,
|
|
401
|
+
priority: 'medium',
|
|
402
|
+
loaded: false,
|
|
403
|
+
createdAt: timestamp,
|
|
404
|
+
tags: ['modified-file', filePath],
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
return impulses
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Execute goal-seeking process
|
|
414
|
+
*
|
|
415
|
+
* Main loop:
|
|
416
|
+
* 1. Parse goal
|
|
417
|
+
* 2. Get recommendations from backend (Thompson Sampling)
|
|
418
|
+
* 3. Execute top-ranked activity with context from previous steps
|
|
419
|
+
* 4. Create impulses from execution output
|
|
420
|
+
* 5. Check completion
|
|
421
|
+
* 6. Repeat until complete or max attempts
|
|
422
|
+
*
|
|
423
|
+
* @param message - User goal message
|
|
424
|
+
* @param context - Additional context (files, variables, etc.)
|
|
425
|
+
* @param options - Execution options
|
|
426
|
+
* @returns Goal execution result
|
|
427
|
+
*/
|
|
428
|
+
async executeGoal(
|
|
429
|
+
message: string,
|
|
430
|
+
context?: Record<string, unknown>,
|
|
431
|
+
options?: {
|
|
432
|
+
maxActivities?: number
|
|
433
|
+
maxCost?: number
|
|
434
|
+
}
|
|
435
|
+
): Promise<GoalResult> {
|
|
436
|
+
const maxActivities = options?.maxActivities || 5
|
|
437
|
+
const maxCost = options?.maxCost || 10.0
|
|
438
|
+
|
|
439
|
+
const startTime = Date.now()
|
|
440
|
+
const executions: ActivityExecution[] = []
|
|
441
|
+
const accumulatedImpulses: Impulse[] = [] // Accumulate context across iterations
|
|
442
|
+
let totalCost = 0
|
|
443
|
+
let totalTokens = { input: 0, output: 0 }
|
|
444
|
+
|
|
445
|
+
// Parse goal
|
|
446
|
+
const goal = this.parseGoal(message, context)
|
|
447
|
+
|
|
448
|
+
console.log(`[GoalProcessor] Starting goal execution: ${goal.intent}`)
|
|
449
|
+
console.log(`[GoalProcessor] Goal type: ${goal.type}`)
|
|
450
|
+
|
|
451
|
+
// Execute activities until goal complete or limits reached
|
|
452
|
+
for (let i = 0; i < maxActivities; i++) {
|
|
453
|
+
console.log(`[GoalProcessor] Activity iteration ${i + 1}/${maxActivities}`)
|
|
454
|
+
console.log(`[GoalProcessor] Context: ${accumulatedImpulses.length} impulses from previous executions`)
|
|
455
|
+
|
|
456
|
+
// Get impulse IDs for context-aware recommendations
|
|
457
|
+
const impulseIds = accumulatedImpulses.map(imp => imp.id)
|
|
458
|
+
|
|
459
|
+
// Get recommendations from backend (with context from previous executions)
|
|
460
|
+
const recommendations = await this.getRecommendations(goal, impulseIds, 3)
|
|
461
|
+
|
|
462
|
+
if (recommendations.length === 0) {
|
|
463
|
+
console.warn("[GoalProcessor] No recommendations from backend, stopping")
|
|
464
|
+
break
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
console.log(`[GoalProcessor] Received ${recommendations.length} recommendations from backend`)
|
|
468
|
+
|
|
469
|
+
// Execute top recommendation (backend already ranked them)
|
|
470
|
+
const topRecommendation = recommendations[0]
|
|
471
|
+
if (!topRecommendation) {
|
|
472
|
+
console.warn("[GoalProcessor] No valid recommendation, stopping")
|
|
473
|
+
break
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Assess relevance of top recommendation
|
|
477
|
+
const relevanceScore = this.assessRelevance(goal, topRecommendation)
|
|
478
|
+
const RELEVANCE_THRESHOLD = 0.3 // Require at least 30% relevance
|
|
479
|
+
|
|
480
|
+
console.log(`[GoalProcessor] Top recommendation: ${topRecommendation.templateId}`)
|
|
481
|
+
console.log(`[GoalProcessor] Relevance score: ${relevanceScore.toFixed(2)}`)
|
|
482
|
+
|
|
483
|
+
let execution: ActivityExecution
|
|
484
|
+
|
|
485
|
+
if (relevanceScore < RELEVANCE_THRESHOLD) {
|
|
486
|
+
console.log(`[GoalProcessor] Relevance too low (${relevanceScore.toFixed(2)} < ${RELEVANCE_THRESHOLD})`)
|
|
487
|
+
console.log(`[GoalProcessor] Falling back to improvisation`)
|
|
488
|
+
|
|
489
|
+
// Improvise instead of using irrelevant template
|
|
490
|
+
const improviser = new GoalImproviser({
|
|
491
|
+
workingDirectory: this.workingDirectory,
|
|
492
|
+
})
|
|
493
|
+
|
|
494
|
+
const improvResult = await improviser.improvise(goal.intent, {
|
|
495
|
+
maxSteps: 10,
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
// Convert improvisation result to ActivityExecution
|
|
499
|
+
// The improviser already stores the trace via MCP
|
|
500
|
+
execution = {
|
|
501
|
+
id: improvResult.execution_id,
|
|
502
|
+
templateId: `improvised-${goal.type}`,
|
|
503
|
+
status: improvResult.outcome.status === 'success' ? 'completed' : 'failed',
|
|
504
|
+
variables: {},
|
|
505
|
+
impulses: [],
|
|
506
|
+
taskResults: improvResult.steps.map(step => ({
|
|
507
|
+
taskId: `step-${step.step}`,
|
|
508
|
+
status: step.result.success ? 'completed' : 'failed',
|
|
509
|
+
output: step.result.output,
|
|
510
|
+
error: step.result.error,
|
|
511
|
+
})),
|
|
512
|
+
startedAt: Date.parse(improvResult.started_at),
|
|
513
|
+
completedAt: improvResult.completed_at ? Date.parse(improvResult.completed_at) : undefined,
|
|
514
|
+
error: improvResult.outcome.error,
|
|
515
|
+
executionTrace: {
|
|
516
|
+
tasks: improvResult.steps.map(step => ({
|
|
517
|
+
id: `step-${step.step}`,
|
|
518
|
+
description: step.thought,
|
|
519
|
+
actualPrompt: step.thought,
|
|
520
|
+
toolCalls: [{
|
|
521
|
+
id: `tool:${step.action}:step-${step.step}:${Date.now()}`,
|
|
522
|
+
name: step.action,
|
|
523
|
+
arguments: step.params,
|
|
524
|
+
result: {
|
|
525
|
+
success: step.result.success,
|
|
526
|
+
output: step.result.output,
|
|
527
|
+
error: step.result.error,
|
|
528
|
+
},
|
|
529
|
+
}],
|
|
530
|
+
response: step.result.output || step.result.error || '',
|
|
531
|
+
result: {
|
|
532
|
+
status: step.result.success ? 'success' : 'failure',
|
|
533
|
+
error: step.result.error,
|
|
534
|
+
},
|
|
535
|
+
inputState: {
|
|
536
|
+
filesAvailable: [],
|
|
537
|
+
environment: {},
|
|
538
|
+
impulses: [],
|
|
539
|
+
variables: {},
|
|
540
|
+
},
|
|
541
|
+
outputState: {
|
|
542
|
+
filesModified: [
|
|
543
|
+
...(improvResult.outcome.files_modified || []),
|
|
544
|
+
...(improvResult.outcome.files_created || []),
|
|
545
|
+
],
|
|
546
|
+
filesCreated: improvResult.outcome.files_created || [],
|
|
547
|
+
filesDeleted: improvResult.outcome.files_deleted || [],
|
|
548
|
+
},
|
|
549
|
+
})),
|
|
550
|
+
filesModified: [
|
|
551
|
+
...(improvResult.outcome.files_modified || []),
|
|
552
|
+
...(improvResult.outcome.files_created || []),
|
|
553
|
+
],
|
|
554
|
+
impulsesCreated: [],
|
|
555
|
+
},
|
|
556
|
+
metrics: {
|
|
557
|
+
duration: 0, // Improviser doesn't track per-step duration
|
|
558
|
+
cost: 0, // Will be calculated from tokens
|
|
559
|
+
totalTokens: {
|
|
560
|
+
input: 0,
|
|
561
|
+
output: 0,
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
console.log(`[GoalProcessor] Improvisation ${execution.status}: ${execution.id}`)
|
|
567
|
+
console.log(`[GoalProcessor] Note: Ribosome will extract template from successful improvisation`)
|
|
568
|
+
} else {
|
|
569
|
+
console.log(`[GoalProcessor] Relevance acceptable (${relevanceScore.toFixed(2)} >= ${RELEVANCE_THRESHOLD})`)
|
|
570
|
+
console.log(`[GoalProcessor] Executing template: ${topRecommendation.templateId}`)
|
|
571
|
+
|
|
572
|
+
// Load template
|
|
573
|
+
const template = await loadTemplateFromMCPOrLocal(topRecommendation.templateId)
|
|
574
|
+
if (!template) {
|
|
575
|
+
console.warn(`[GoalProcessor] Template not found: ${topRecommendation.templateId}, trying next`)
|
|
576
|
+
continue
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Execute activity with template and accumulated context
|
|
580
|
+
execution = await this.executor.execute({
|
|
581
|
+
template,
|
|
582
|
+
variables: topRecommendation.variables,
|
|
583
|
+
impulses: accumulatedImpulses, // Pass context from previous executions
|
|
584
|
+
reason: `Goal: ${goal.intent}`,
|
|
585
|
+
})
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
executions.push(execution)
|
|
589
|
+
|
|
590
|
+
console.log(`[GoalProcessor] Activity ${execution.id} completed with status: ${execution.status}`)
|
|
591
|
+
|
|
592
|
+
// Create impulses from execution output for next iteration
|
|
593
|
+
const newImpulses = this.createImpulsesFromExecution(execution)
|
|
594
|
+
accumulatedImpulses.push(...newImpulses)
|
|
595
|
+
console.log(`[GoalProcessor] Created ${newImpulses.length} impulses from execution output`)
|
|
596
|
+
|
|
597
|
+
// Track costs
|
|
598
|
+
if (execution.metrics) {
|
|
599
|
+
totalCost += execution.metrics.cost
|
|
600
|
+
totalTokens.input += execution.metrics.totalTokens.input
|
|
601
|
+
totalTokens.output += execution.metrics.totalTokens.output
|
|
602
|
+
// Note: minibob metrics doesn't have cache field yet
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
console.log(`[GoalProcessor] Total cost so far: $${totalCost.toFixed(4)}`)
|
|
606
|
+
|
|
607
|
+
// Check cost limit
|
|
608
|
+
if (totalCost > maxCost) {
|
|
609
|
+
console.warn(`[GoalProcessor] Cost limit exceeded ($${maxCost})`)
|
|
610
|
+
return {
|
|
611
|
+
goal,
|
|
612
|
+
executions,
|
|
613
|
+
completed: false,
|
|
614
|
+
completionReason: `Cost limit exceeded ($${maxCost})`,
|
|
615
|
+
totalDuration: Date.now() - startTime,
|
|
616
|
+
totalCost,
|
|
617
|
+
totalTokens,
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Check goal completion
|
|
622
|
+
const { complete, reason } = this.isGoalComplete(executions, goal)
|
|
623
|
+
|
|
624
|
+
console.log(`[GoalProcessor] Goal complete check: ${complete} - ${reason}`)
|
|
625
|
+
|
|
626
|
+
if (complete) {
|
|
627
|
+
return {
|
|
628
|
+
goal,
|
|
629
|
+
executions,
|
|
630
|
+
completed: true,
|
|
631
|
+
completionReason: reason,
|
|
632
|
+
totalDuration: Date.now() - startTime,
|
|
633
|
+
totalCost,
|
|
634
|
+
totalTokens,
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Max activities reached
|
|
640
|
+
const { complete, reason } = this.isGoalComplete(executions, goal)
|
|
641
|
+
|
|
642
|
+
console.log(`[GoalProcessor] Reached max activities (${maxActivities})`)
|
|
643
|
+
|
|
644
|
+
return {
|
|
645
|
+
goal,
|
|
646
|
+
executions,
|
|
647
|
+
completed: complete,
|
|
648
|
+
completionReason: complete ? reason : `Max activities (${maxActivities}) reached`,
|
|
649
|
+
totalDuration: Date.now() - startTime,
|
|
650
|
+
totalCost,
|
|
651
|
+
totalTokens,
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|