@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
package/src/mcp.ts
ADDED
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* minibob MCP Client
|
|
3
|
+
*
|
|
4
|
+
* Integrates with Metabob backend for:
|
|
5
|
+
* - Activity template fetching
|
|
6
|
+
* - Execution metrics reporting
|
|
7
|
+
* - Impulse storage/retrieval
|
|
8
|
+
* - Learning and optimization
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ActivityTemplate, ActivityExecution, Impulse } from "./types"
|
|
12
|
+
|
|
13
|
+
// Impulse Relevance Metric (Phase 1.8)
|
|
14
|
+
export interface ImpulseRelevanceMetric {
|
|
15
|
+
impulse_id: string
|
|
16
|
+
activity_variant_id: string
|
|
17
|
+
task_id?: string
|
|
18
|
+
times_loaded: number
|
|
19
|
+
times_execution_succeeded: number
|
|
20
|
+
times_execution_failed: number
|
|
21
|
+
times_not_loaded_succeeded: number
|
|
22
|
+
times_not_loaded_failed: number
|
|
23
|
+
relevance_score: number
|
|
24
|
+
irrelevance_score: number
|
|
25
|
+
avg_content_size_tokens: number
|
|
26
|
+
typical_pointer_type?: string
|
|
27
|
+
created_at: string
|
|
28
|
+
updated_at: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// =============================================================================
|
|
32
|
+
// MCP CLIENT CONFIGURATION
|
|
33
|
+
// =============================================================================
|
|
34
|
+
|
|
35
|
+
export interface MCPConfig {
|
|
36
|
+
endpoint: string
|
|
37
|
+
apiKey?: string
|
|
38
|
+
timeout?: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// =============================================================================
|
|
42
|
+
// MCP CLIENT
|
|
43
|
+
// =============================================================================
|
|
44
|
+
|
|
45
|
+
export class MCPClient {
|
|
46
|
+
private endpoint: string
|
|
47
|
+
private apiKey?: string
|
|
48
|
+
private timeout: number
|
|
49
|
+
|
|
50
|
+
constructor(config: MCPConfig) {
|
|
51
|
+
this.endpoint = config.endpoint.replace(/\/$/, "") // Remove trailing slash
|
|
52
|
+
this.apiKey = config.apiKey
|
|
53
|
+
this.timeout = config.timeout ?? 30000
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch activity template by ID
|
|
58
|
+
*/
|
|
59
|
+
async getActivityTemplate(templateId: string): Promise<ActivityTemplate | null> {
|
|
60
|
+
try {
|
|
61
|
+
const response = await this.request("GET", `/v2/activities/templates/${templateId}`)
|
|
62
|
+
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
if (response.status === 404) return null
|
|
65
|
+
throw new Error(`Failed to fetch template: ${response.status} ${response.statusText}`)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const data = await response.json() as any
|
|
69
|
+
const raw = data.template ?? data
|
|
70
|
+
|
|
71
|
+
// Transform API schema to minibob schema
|
|
72
|
+
return {
|
|
73
|
+
id: raw.variant_id || raw.id,
|
|
74
|
+
name: raw.variant_name || raw.name,
|
|
75
|
+
description: raw.description,
|
|
76
|
+
category: raw.category,
|
|
77
|
+
tasks: raw.task_steps || raw.tasks || [],
|
|
78
|
+
variables: raw.variables || [],
|
|
79
|
+
contextRequirements: raw.contextRequirements
|
|
80
|
+
} as ActivityTemplate
|
|
81
|
+
} catch (error) {
|
|
82
|
+
console.error(`[MCP] Error fetching template ${templateId}:`, error)
|
|
83
|
+
return null
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Search activity templates
|
|
89
|
+
*/
|
|
90
|
+
async searchActivityTemplates(query?: {
|
|
91
|
+
category?: string
|
|
92
|
+
limit?: number
|
|
93
|
+
}): Promise<Array<{ id: string; name: string; category: string; successRate?: number }>> {
|
|
94
|
+
try {
|
|
95
|
+
const params = new URLSearchParams()
|
|
96
|
+
if (query?.category) params.set("category", query.category)
|
|
97
|
+
if (query?.limit) params.set("limit", String(query.limit))
|
|
98
|
+
|
|
99
|
+
const url = `/v2/activities/templates?${params.toString()}`
|
|
100
|
+
const response = await this.request("GET", url)
|
|
101
|
+
|
|
102
|
+
if (!response.ok) {
|
|
103
|
+
throw new Error(`Failed to search templates: ${response.status}`)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const data = await response.json() as { templates?: Array<{ id: string; name: string; category: string; successRate?: number }> }
|
|
107
|
+
return data.templates ?? []
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error("[MCP] Error searching templates:", error)
|
|
110
|
+
return []
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Recommend activities for a goal using Thompson Sampling
|
|
116
|
+
*
|
|
117
|
+
* This is the key method for goal-driven execution.
|
|
118
|
+
* The backend uses Thompson Sampling with historical execution data
|
|
119
|
+
* to recommend the best activities for achieving a goal.
|
|
120
|
+
*
|
|
121
|
+
* @param taskDescription - User's goal description
|
|
122
|
+
* @param category - Optional category filter
|
|
123
|
+
* @param loadedImpulses - IDs of currently loaded impulses for context
|
|
124
|
+
* @param limit - Max number of recommendations
|
|
125
|
+
* @returns Array of activity recommendations with selection metadata
|
|
126
|
+
*/
|
|
127
|
+
async recommendActivities(
|
|
128
|
+
taskDescription: string,
|
|
129
|
+
category?: string,
|
|
130
|
+
loadedImpulses?: string[],
|
|
131
|
+
limit: number = 3
|
|
132
|
+
): Promise<Array<{ template_id: string; selection_metadata: any }>> {
|
|
133
|
+
try {
|
|
134
|
+
// Call backend recommendation API using Thompson Sampling
|
|
135
|
+
const payload = {
|
|
136
|
+
task_description: taskDescription,
|
|
137
|
+
category,
|
|
138
|
+
loaded_impulses: loadedImpulses || [],
|
|
139
|
+
limit,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const response = await this.request("POST", "/v2/activities/recommend", payload)
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
console.warn(`[MCP] Recommendation failed: ${response.status}`)
|
|
146
|
+
return []
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const data = await response.json() as { recommendations?: Array<{ template_id: string; selection_metadata: any }> }
|
|
150
|
+
return data.recommendations ?? []
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("[MCP] Error getting recommendations:", error)
|
|
153
|
+
return []
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Register activity template variant
|
|
159
|
+
* Called when executing a template loaded from local JSON file
|
|
160
|
+
*/
|
|
161
|
+
async registerTemplate(template: ActivityTemplate): Promise<boolean> {
|
|
162
|
+
try {
|
|
163
|
+
// Transform tasks to match backend schema (add required fields)
|
|
164
|
+
const task_steps = template.tasks.map(task => ({
|
|
165
|
+
id: task.id,
|
|
166
|
+
subagent: "general-purpose", // Default subagent
|
|
167
|
+
description: task.description,
|
|
168
|
+
dependencies: [], // No dependencies by default
|
|
169
|
+
prompt: task.prompt,
|
|
170
|
+
validation: task.validation,
|
|
171
|
+
retry: task.retry
|
|
172
|
+
}))
|
|
173
|
+
|
|
174
|
+
const payload: Record<string, any> = {
|
|
175
|
+
variant_id: template.id,
|
|
176
|
+
activity_id: template.id, // For now, variant_id === activity_id
|
|
177
|
+
variant_name: template.name,
|
|
178
|
+
description: template.description,
|
|
179
|
+
category: template.category,
|
|
180
|
+
task_steps,
|
|
181
|
+
scope: "global",
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Omit null/undefined fields (SurrealDB doesn't accept null, needs NONE or omitted)
|
|
185
|
+
Object.keys(payload).forEach(key => {
|
|
186
|
+
if (payload[key] === null || payload[key] === undefined) {
|
|
187
|
+
delete payload[key]
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const response = await this.request("POST", "/v2/activities/templates", payload)
|
|
192
|
+
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
// 409 Conflict means template already exists - that's fine
|
|
195
|
+
if (response.status === 409) {
|
|
196
|
+
console.log(`[MCP] Template ${template.id} already registered`)
|
|
197
|
+
return true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const errorText = await response.text()
|
|
201
|
+
console.warn(`[MCP] Failed to register template: ${response.status} - ${errorText}`)
|
|
202
|
+
return false
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(`[MCP] ✓ Template ${template.id} registered successfully`)
|
|
206
|
+
return true
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error("[MCP] Error registering template:", error)
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Report activity execution results
|
|
215
|
+
*/
|
|
216
|
+
async reportExecution(execution: ActivityExecution): Promise<boolean> {
|
|
217
|
+
try {
|
|
218
|
+
// Determine first failed task if execution failed
|
|
219
|
+
const failedTask = execution.taskResults.find((t) => t.status === "failed")
|
|
220
|
+
|
|
221
|
+
const payload: Record<string, any> = {
|
|
222
|
+
variant_id: execution.templateId,
|
|
223
|
+
success: execution.status === "completed",
|
|
224
|
+
duration_ms: execution.metrics?.duration || 0,
|
|
225
|
+
cost: execution.metrics?.cost || 0,
|
|
226
|
+
tokens: {
|
|
227
|
+
input: execution.metrics?.totalTokens?.input || 0,
|
|
228
|
+
output: execution.metrics?.totalTokens?.output || 0,
|
|
229
|
+
cache: 0, // MiniBob doesn't track cache tokens yet
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Only include optional fields if they have values (SurrealDB doesn't accept null)
|
|
234
|
+
if (failedTask?.error) {
|
|
235
|
+
payload.error_message = failedTask.error
|
|
236
|
+
}
|
|
237
|
+
if (failedTask) {
|
|
238
|
+
payload.error_type = "task_execution_error"
|
|
239
|
+
payload.failed_task_id = failedTask.taskId
|
|
240
|
+
}
|
|
241
|
+
// Omit empty arrays - SurrealDB will use default [] if field allows it
|
|
242
|
+
if (execution.impulses && execution.impulses.length > 0) {
|
|
243
|
+
payload.impulses_used = execution.impulses.map(imp => imp.id)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const response = await this.request("POST", "/v2/activities/executions", payload)
|
|
247
|
+
|
|
248
|
+
if (!response.ok) {
|
|
249
|
+
const errorText = await response.text()
|
|
250
|
+
console.warn(`[MCP] Failed to report execution: ${response.status} - ${errorText}`)
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return true
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.error("[MCP] Error reporting execution:", error)
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Record activity composition event (when one activity calls another)
|
|
263
|
+
*
|
|
264
|
+
* This enables learning of composition patterns:
|
|
265
|
+
* - Which activities typically work together
|
|
266
|
+
* - Success rates of different compositions
|
|
267
|
+
* - Optimal activity sequences
|
|
268
|
+
*/
|
|
269
|
+
async recordComposition(params: {
|
|
270
|
+
parentActivityId: string
|
|
271
|
+
childActivityId: string
|
|
272
|
+
executionId: string
|
|
273
|
+
goalContext?: string
|
|
274
|
+
success: boolean
|
|
275
|
+
}): Promise<boolean> {
|
|
276
|
+
try {
|
|
277
|
+
const payload = {
|
|
278
|
+
parent_activity_id: params.parentActivityId,
|
|
279
|
+
child_activity_id: params.childActivityId,
|
|
280
|
+
execution_id: params.executionId,
|
|
281
|
+
goal_context: params.goalContext,
|
|
282
|
+
success: params.success,
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const response = await this.request("POST", "/v2/activities/composition", payload)
|
|
286
|
+
|
|
287
|
+
if (!response.ok) {
|
|
288
|
+
const errorText = await response.text()
|
|
289
|
+
console.warn(`[MCP] Failed to record composition: ${response.status} - ${errorText}`)
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const data = await response.json() as { success: boolean; edge?: any }
|
|
294
|
+
if (data.edge) {
|
|
295
|
+
console.log(`[MCP] Composition recorded: ${params.parentActivityId} → ${params.childActivityId} (weight: ${data.edge.weight})`)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return true
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error("[MCP] Error recording composition:", error)
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Record tool usage during activity execution
|
|
307
|
+
*
|
|
308
|
+
* This enables learning of tool usage patterns:
|
|
309
|
+
* - Which tools are required vs optional for each activity
|
|
310
|
+
* - Success correlation between tool usage and outcomes
|
|
311
|
+
* - Pre-flight checks (does vessel have required tools?)
|
|
312
|
+
*/
|
|
313
|
+
async recordToolUsage(params: {
|
|
314
|
+
toolName: string
|
|
315
|
+
activityVariantId: string
|
|
316
|
+
taskId?: string
|
|
317
|
+
executionId: string
|
|
318
|
+
toolSucceeded: boolean
|
|
319
|
+
activitySucceeded: boolean
|
|
320
|
+
paramsComplexity?: number
|
|
321
|
+
}): Promise<boolean> {
|
|
322
|
+
try {
|
|
323
|
+
const payload = {
|
|
324
|
+
tool_name: params.toolName,
|
|
325
|
+
activity_variant_id: params.activityVariantId,
|
|
326
|
+
task_id: params.taskId,
|
|
327
|
+
execution_id: params.executionId,
|
|
328
|
+
tool_succeeded: params.toolSucceeded,
|
|
329
|
+
activity_succeeded: params.activitySucceeded,
|
|
330
|
+
params_complexity: params.paramsComplexity,
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const response = await this.request("POST", "/v2/activities/tool-usage", payload)
|
|
334
|
+
|
|
335
|
+
if (!response.ok) {
|
|
336
|
+
const errorText = await response.text()
|
|
337
|
+
console.warn(`[MCP] Failed to record tool usage: ${response.status} - ${errorText}`)
|
|
338
|
+
return false
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log(`[MCP] Tool usage recorded: ${params.toolName} in ${params.activityVariantId}`)
|
|
342
|
+
return true
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error("[MCP] Error recording tool usage:", error)
|
|
345
|
+
return false
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Record execution sequence for session-level learning
|
|
351
|
+
*
|
|
352
|
+
* This enables learning:
|
|
353
|
+
* - Which activities typically run together
|
|
354
|
+
* - Successful sequences for achieving goals
|
|
355
|
+
* - Optimal sequence length and patterns
|
|
356
|
+
*/
|
|
357
|
+
async recordExecutionSequence(params: {
|
|
358
|
+
sessionId: string
|
|
359
|
+
goalContext?: string
|
|
360
|
+
sequence: Array<{
|
|
361
|
+
activityId: string
|
|
362
|
+
executionId: string
|
|
363
|
+
order: number
|
|
364
|
+
triggerType: 'goal' | 'nested' | 'boredom' | 'manual'
|
|
365
|
+
parentExecutionId?: string
|
|
366
|
+
success: boolean
|
|
367
|
+
durationMs: number
|
|
368
|
+
costUsd: number
|
|
369
|
+
}>
|
|
370
|
+
outcome: 'success' | 'partial' | 'failure'
|
|
371
|
+
}): Promise<boolean> {
|
|
372
|
+
try {
|
|
373
|
+
const payload = {
|
|
374
|
+
session_id: params.sessionId,
|
|
375
|
+
goal_context: params.goalContext,
|
|
376
|
+
sequence: params.sequence.map(item => ({
|
|
377
|
+
activity_id: item.activityId,
|
|
378
|
+
execution_id: item.executionId,
|
|
379
|
+
order: item.order,
|
|
380
|
+
trigger_type: item.triggerType,
|
|
381
|
+
parent_execution_id: item.parentExecutionId,
|
|
382
|
+
success: item.success,
|
|
383
|
+
duration_ms: item.durationMs,
|
|
384
|
+
cost_usd: item.costUsd,
|
|
385
|
+
})),
|
|
386
|
+
outcome: params.outcome,
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const response = await this.request("POST", "/v2/activities/execution-sequences", payload)
|
|
390
|
+
|
|
391
|
+
if (!response.ok) {
|
|
392
|
+
const errorText = await response.text()
|
|
393
|
+
console.warn(`[MCP] Failed to record execution sequence: ${response.status} - ${errorText}`)
|
|
394
|
+
return false
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
console.log(`[MCP] Execution sequence recorded: ${params.sequence.length} activities (${params.outcome})`)
|
|
398
|
+
return true
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error("[MCP] Error recording execution sequence:", error)
|
|
401
|
+
return false
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Store impulse in backend
|
|
407
|
+
*/
|
|
408
|
+
async storeImpulse(impulse: Impulse): Promise<boolean> {
|
|
409
|
+
try {
|
|
410
|
+
// Map MiniBob impulse format to API schema
|
|
411
|
+
const payload = {
|
|
412
|
+
impulse_id: impulse.id,
|
|
413
|
+
project_id: "minibob-default", // Default project for internal impulses
|
|
414
|
+
impulse_data: {
|
|
415
|
+
id: impulse.id, // Required by ImpulseDataSchema
|
|
416
|
+
type: impulse.pointer?.type ?? "memo",
|
|
417
|
+
pointer: impulse.pointer ?? { type: "memo", content: impulse.content ?? "" },
|
|
418
|
+
budget: impulse.budget ?? 4000,
|
|
419
|
+
priority: typeof impulse.priority === "string" ?
|
|
420
|
+
({ critical: 1, high: 2, medium: 3, low: 4 }[impulse.priority] ?? 3) :
|
|
421
|
+
(impulse.priority ?? 3),
|
|
422
|
+
metadata: {
|
|
423
|
+
tags: impulse.tags,
|
|
424
|
+
content: impulse.content,
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const response = await this.request("POST", "/v2/impulses", payload)
|
|
430
|
+
|
|
431
|
+
if (!response.ok) {
|
|
432
|
+
console.warn(`[MCP] Failed to store impulse: ${response.status}`)
|
|
433
|
+
return false
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return true
|
|
437
|
+
} catch (error) {
|
|
438
|
+
console.error("[MCP] Error storing impulse:", error)
|
|
439
|
+
return false
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Retrieve impulse from backend
|
|
445
|
+
*/
|
|
446
|
+
async retrieveImpulse(impulseId: string): Promise<Impulse | null> {
|
|
447
|
+
try {
|
|
448
|
+
const response = await this.request("GET", `/v2/impulses/${impulseId}`)
|
|
449
|
+
|
|
450
|
+
if (!response.ok) {
|
|
451
|
+
if (response.status === 404) return null
|
|
452
|
+
throw new Error(`Failed to retrieve impulse: ${response.status}`)
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const data = await response.json() as { impulse?: Impulse }
|
|
456
|
+
return data.impulse ?? null
|
|
457
|
+
} catch (error) {
|
|
458
|
+
console.error(`[MCP] Error retrieving impulse ${impulseId}:`, error)
|
|
459
|
+
return null
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Track impulse usage for learning
|
|
465
|
+
*/
|
|
466
|
+
async trackImpulseUsage(impulseId: string, context: {
|
|
467
|
+
activityId: string
|
|
468
|
+
taskId: string
|
|
469
|
+
tokensUsed: number
|
|
470
|
+
}): Promise<boolean> {
|
|
471
|
+
try {
|
|
472
|
+
const response = await this.request("POST", `/v2/impulses/${impulseId}/usage`, context)
|
|
473
|
+
|
|
474
|
+
if (!response.ok) {
|
|
475
|
+
console.warn(`[MCP] Failed to track impulse usage: ${response.status}`)
|
|
476
|
+
return false
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
return true
|
|
480
|
+
} catch (error) {
|
|
481
|
+
console.error("[MCP] Error tracking impulse usage:", error)
|
|
482
|
+
return false
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Query impulse relevance metrics for an activity (Phase 1.8)
|
|
488
|
+
*/
|
|
489
|
+
async queryImpulseRelevance(params: {
|
|
490
|
+
activityVariantId: string
|
|
491
|
+
impulseIds?: string[]
|
|
492
|
+
}): Promise<ImpulseRelevanceMetric[]> {
|
|
493
|
+
try {
|
|
494
|
+
const queryParams = new URLSearchParams({
|
|
495
|
+
activity_variant_id: params.activityVariantId,
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
if (params.impulseIds && params.impulseIds.length > 0) {
|
|
499
|
+
for (const id of params.impulseIds) {
|
|
500
|
+
queryParams.append('impulse_id', id)
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const response = await this.request(
|
|
505
|
+
"GET",
|
|
506
|
+
`/v2/activities/impulse-relevance?${queryParams.toString()}`
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
if (!response.ok) {
|
|
510
|
+
if (response.status === 404) return []
|
|
511
|
+
throw new Error(`Failed to query impulse relevance: ${response.status}`)
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const data = await response.json() as any
|
|
515
|
+
return data.metrics || []
|
|
516
|
+
} catch (error) {
|
|
517
|
+
console.error("[MCP] Error querying impulse relevance:", error)
|
|
518
|
+
return []
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Record impulse relevance for learning (Phase 1.8)
|
|
524
|
+
*/
|
|
525
|
+
async recordImpulseRelevance(params: {
|
|
526
|
+
impulseId: string
|
|
527
|
+
activityVariantId: string
|
|
528
|
+
taskId?: string
|
|
529
|
+
wasLoaded: boolean
|
|
530
|
+
executionSucceeded: boolean
|
|
531
|
+
contentSizeTokens?: number
|
|
532
|
+
pointerType?: string
|
|
533
|
+
}): Promise<boolean> {
|
|
534
|
+
try {
|
|
535
|
+
const response = await this.request("POST", "/v2/activities/impulse-relevance", {
|
|
536
|
+
impulse_id: params.impulseId,
|
|
537
|
+
activity_variant_id: params.activityVariantId,
|
|
538
|
+
task_id: params.taskId,
|
|
539
|
+
was_loaded: params.wasLoaded,
|
|
540
|
+
execution_succeeded: params.executionSucceeded,
|
|
541
|
+
content_size_tokens: params.contentSizeTokens,
|
|
542
|
+
pointer_type: params.pointerType,
|
|
543
|
+
})
|
|
544
|
+
|
|
545
|
+
if (!response.ok) {
|
|
546
|
+
console.warn(`[MCP] Failed to record impulse relevance: ${response.status}`)
|
|
547
|
+
return false
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return true
|
|
551
|
+
} catch (error) {
|
|
552
|
+
console.error("[MCP] Error recording impulse relevance:", error)
|
|
553
|
+
return false
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Resolve an impulse pointer via backend (Phase 1.8+)
|
|
559
|
+
*
|
|
560
|
+
* Architecture: MiniBob delegates all non-local impulse resolution to backend.
|
|
561
|
+
* This allows backend to introduce new pointer types without minibob changes.
|
|
562
|
+
*/
|
|
563
|
+
async resolveImpulse(pointer: any): Promise<string> {
|
|
564
|
+
try {
|
|
565
|
+
const response = await this.request("POST", "/v2/impulses/resolve", {
|
|
566
|
+
pointer: pointer,
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
if (!response.ok) {
|
|
570
|
+
throw new Error(`Failed to resolve impulse: ${response.status} ${response.statusText}`)
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
const data = await response.json() as { content?: string }
|
|
574
|
+
if (!data.content) {
|
|
575
|
+
throw new Error("Backend returned no content for impulse resolution")
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return data.content
|
|
579
|
+
} catch (error) {
|
|
580
|
+
throw new Error(
|
|
581
|
+
`MCP impulse resolution failed: ${error instanceof Error ? error.message : String(error)}`
|
|
582
|
+
)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Store execution trace in backend (Phase 1.8+)
|
|
588
|
+
*
|
|
589
|
+
* Enables execution traces to be referenced as impulses in future activities.
|
|
590
|
+
*/
|
|
591
|
+
async storeExecutionTrace(execution: ActivityExecution): Promise<boolean> {
|
|
592
|
+
try {
|
|
593
|
+
// Map MiniBob's status to backend's expected values
|
|
594
|
+
let status: 'success' | 'failure' | 'partial' = 'failure'
|
|
595
|
+
if (execution.status === 'completed') {
|
|
596
|
+
status = 'success'
|
|
597
|
+
} else if (execution.status === 'failed') {
|
|
598
|
+
status = 'failure'
|
|
599
|
+
} else if (execution.status === 'executing') {
|
|
600
|
+
status = 'partial'
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
const response = await this.request("POST", "/v2/activities/execution-traces", {
|
|
604
|
+
execution_id: execution.id,
|
|
605
|
+
template_id: execution.templateId,
|
|
606
|
+
activity_id: execution.templateId, // Backend expects activity_id
|
|
607
|
+
status: status, // Map to backend's enum values
|
|
608
|
+
success: execution.status === "completed",
|
|
609
|
+
duration_ms: execution.metrics?.duration || 0,
|
|
610
|
+
cost_usd: execution.metrics?.cost || 0,
|
|
611
|
+
tokens: {
|
|
612
|
+
input: execution.metrics?.totalTokens?.input || 0,
|
|
613
|
+
output: execution.metrics?.totalTokens?.output || 0,
|
|
614
|
+
cache: 0, // MiniBob doesn't track cache tokens yet
|
|
615
|
+
},
|
|
616
|
+
// Include execution trace with all collected data
|
|
617
|
+
execution_trace: execution.executionTrace || {
|
|
618
|
+
tasks: [],
|
|
619
|
+
impulsesCreated: [],
|
|
620
|
+
filesModified: [],
|
|
621
|
+
},
|
|
622
|
+
// Include impulse IDs used
|
|
623
|
+
impulses_used: execution.impulses?.map(i => i.id) || [],
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
if (!response.ok) {
|
|
627
|
+
const errorText = await response.text()
|
|
628
|
+
console.warn(`[MCP] Failed to store execution trace: ${response.status} - ${errorText}`)
|
|
629
|
+
return false
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
console.log(`[MCP] ✓ Execution trace stored: ${execution.id}`)
|
|
633
|
+
return true
|
|
634
|
+
} catch (error) {
|
|
635
|
+
console.error("[MCP] Error storing execution trace:", error)
|
|
636
|
+
return false
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Register minibob vessel with backend
|
|
642
|
+
*/
|
|
643
|
+
async registerVessel(manifest: {
|
|
644
|
+
id: string
|
|
645
|
+
name: string
|
|
646
|
+
version: string
|
|
647
|
+
capabilities: string[]
|
|
648
|
+
endpoint: string
|
|
649
|
+
}): Promise<boolean> {
|
|
650
|
+
try {
|
|
651
|
+
const response = await this.request("POST", "/v2/vessels/register", manifest)
|
|
652
|
+
|
|
653
|
+
if (!response.ok) {
|
|
654
|
+
console.warn(`[MCP] Failed to register vessel: ${response.status}`)
|
|
655
|
+
return false
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
console.log("[MCP] Vessel registered successfully")
|
|
659
|
+
return true
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error("[MCP] Error registering vessel:", error)
|
|
662
|
+
return false
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* Make HTTP request to MCP backend
|
|
668
|
+
*/
|
|
669
|
+
private async request(
|
|
670
|
+
method: "GET" | "POST" | "PUT" | "DELETE",
|
|
671
|
+
path: string,
|
|
672
|
+
body?: unknown
|
|
673
|
+
): Promise<Response> {
|
|
674
|
+
const url = `${this.endpoint}${path}`
|
|
675
|
+
const headers: Record<string, string> = {
|
|
676
|
+
"Content-Type": "application/json",
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (this.apiKey) {
|
|
680
|
+
headers["Authorization"] = `Bearer ${this.apiKey}`
|
|
681
|
+
// Also send internal service key for impulse endpoints
|
|
682
|
+
headers["X-Internal-Api-Key"] = this.apiKey
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const controller = new AbortController()
|
|
686
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout)
|
|
687
|
+
|
|
688
|
+
try {
|
|
689
|
+
const response = await fetch(url, {
|
|
690
|
+
method,
|
|
691
|
+
headers,
|
|
692
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
693
|
+
signal: controller.signal,
|
|
694
|
+
})
|
|
695
|
+
|
|
696
|
+
return response
|
|
697
|
+
} finally {
|
|
698
|
+
clearTimeout(timeoutId)
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// =============================================================================
|
|
704
|
+
// SINGLETON MCP CLIENT
|
|
705
|
+
// =============================================================================
|
|
706
|
+
|
|
707
|
+
let mcpClient: MCPClient | null = null
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Initialize MCP client with backend health check (singleton)
|
|
711
|
+
*
|
|
712
|
+
* Checks backend availability before initialization.
|
|
713
|
+
* Returns null if backend is unreachable, allowing graceful fallback to local mode.
|
|
714
|
+
*
|
|
715
|
+
* @param config MCP configuration
|
|
716
|
+
* @param skipHealthCheck Skip health check (for testing)
|
|
717
|
+
* @returns MCPClient instance or null if backend unavailable
|
|
718
|
+
*/
|
|
719
|
+
export async function initializeMCP(config: MCPConfig, skipHealthCheck = false): Promise<MCPClient | null> {
|
|
720
|
+
if (!skipHealthCheck) {
|
|
721
|
+
const { checkBackendHealth } = await import("./environment")
|
|
722
|
+
const healthy = await checkBackendHealth(config.endpoint)
|
|
723
|
+
|
|
724
|
+
if (!healthy) {
|
|
725
|
+
console.warn("[MCP] Backend unavailable, using local mode")
|
|
726
|
+
return null
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
mcpClient = new MCPClient(config)
|
|
731
|
+
console.log("[MCP] ✓ Client initialized")
|
|
732
|
+
return mcpClient
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Get MCP client instance
|
|
737
|
+
*/
|
|
738
|
+
export function getMCPClient(): MCPClient | null {
|
|
739
|
+
return mcpClient
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Check if MCP is enabled
|
|
744
|
+
*/
|
|
745
|
+
export function isMCPEnabled(): boolean {
|
|
746
|
+
return mcpClient !== null
|
|
747
|
+
}
|