@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.
Files changed (174) hide show
  1. package/ARCHITECTURE.md +255 -0
  2. package/CHANGELOG.md +112 -0
  3. package/README.md +380 -0
  4. package/bin/minibob.js +36 -0
  5. package/dist/acp-gossip.d.ts +72 -0
  6. package/dist/acp-gossip.d.ts.map +1 -0
  7. package/dist/acp-gossip.js +156 -0
  8. package/dist/acp-gossip.js.map +1 -0
  9. package/dist/acp.d.ts +62 -0
  10. package/dist/acp.d.ts.map +1 -0
  11. package/dist/acp.js +292 -0
  12. package/dist/acp.js.map +1 -0
  13. package/dist/activity.d.ts +157 -0
  14. package/dist/activity.d.ts.map +1 -0
  15. package/dist/activity.js +518 -0
  16. package/dist/activity.js.map +1 -0
  17. package/dist/agent-runtime.d.ts +104 -0
  18. package/dist/agent-runtime.d.ts.map +1 -0
  19. package/dist/boredom.d.ts +125 -0
  20. package/dist/boredom.d.ts.map +1 -0
  21. package/dist/boredom.js +244 -0
  22. package/dist/boredom.js.map +1 -0
  23. package/dist/cli/acp-server.d.ts +23 -0
  24. package/dist/cli/acp-server.d.ts.map +1 -0
  25. package/dist/cli/burrow.d.ts +26 -0
  26. package/dist/cli/burrow.d.ts.map +1 -0
  27. package/dist/cli/doctor.d.ts +22 -0
  28. package/dist/cli/doctor.d.ts.map +1 -0
  29. package/dist/cli/goal.d.ts +22 -0
  30. package/dist/cli/goal.d.ts.map +1 -0
  31. package/dist/cli/index.d.ts +47 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/instance-registry.d.ts +78 -0
  34. package/dist/cli/instance-registry.d.ts.map +1 -0
  35. package/dist/cli/observe.d.ts +35 -0
  36. package/dist/cli/observe.d.ts.map +1 -0
  37. package/dist/cli/vessel.d.ts +14 -0
  38. package/dist/cli/vessel.d.ts.map +1 -0
  39. package/dist/composition-observer.d.ts +96 -0
  40. package/dist/composition-observer.d.ts.map +1 -0
  41. package/dist/config.d.ts +36 -0
  42. package/dist/config.d.ts.map +1 -0
  43. package/dist/config.js +128 -0
  44. package/dist/config.js.map +1 -0
  45. package/dist/docker/Dockerfile +35 -0
  46. package/dist/environment.d.ts +72 -0
  47. package/dist/environment.d.ts.map +1 -0
  48. package/dist/environment.js +142 -0
  49. package/dist/environment.js.map +1 -0
  50. package/dist/goal-processor.d.ts +165 -0
  51. package/dist/goal-processor.d.ts.map +1 -0
  52. package/dist/helm/minibob-cluster/Chart.yaml +13 -0
  53. package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
  54. package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
  55. package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
  56. package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
  57. package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
  58. package/dist/helm/minibob-cluster/values-local.yaml +41 -0
  59. package/dist/helm/minibob-cluster/values-production.yaml +57 -0
  60. package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
  61. package/dist/helm/minibob-cluster/values.yaml +127 -0
  62. package/dist/improviser.d.ts +74 -0
  63. package/dist/improviser.d.ts.map +1 -0
  64. package/dist/impulse-filter.d.ts +74 -0
  65. package/dist/impulse-filter.d.ts.map +1 -0
  66. package/dist/impulse.d.ts +92 -0
  67. package/dist/impulse.d.ts.map +1 -0
  68. package/dist/impulse.js +234 -0
  69. package/dist/impulse.js.map +1 -0
  70. package/dist/lib.d.ts +29 -0
  71. package/dist/lib.d.ts.map +1 -0
  72. package/dist/lib.js +18561 -0
  73. package/dist/lib.js.map +98 -0
  74. package/dist/lifecycle-hooks.d.ts +99 -0
  75. package/dist/lifecycle-hooks.d.ts.map +1 -0
  76. package/dist/lifecycle-hooks.js +135 -0
  77. package/dist/lifecycle-hooks.js.map +1 -0
  78. package/dist/llm.d.ts +31 -0
  79. package/dist/llm.d.ts.map +1 -0
  80. package/dist/llm.js +349 -0
  81. package/dist/llm.js.map +1 -0
  82. package/dist/mcp-activity-bridge.d.ts +66 -0
  83. package/dist/mcp-activity-bridge.d.ts.map +1 -0
  84. package/dist/mcp-activity-bridge.js +126 -0
  85. package/dist/mcp-activity-bridge.js.map +1 -0
  86. package/dist/mcp.d.ts +216 -0
  87. package/dist/mcp.d.ts.map +1 -0
  88. package/dist/mcp.js +292 -0
  89. package/dist/mcp.js.map +1 -0
  90. package/dist/memory-agent.d.ts +92 -0
  91. package/dist/memory-agent.d.ts.map +1 -0
  92. package/dist/memory-agent.js +277 -0
  93. package/dist/memory-agent.js.map +1 -0
  94. package/dist/runtime-mapping.d.ts +97 -0
  95. package/dist/runtime-mapping.d.ts.map +1 -0
  96. package/dist/search-first-executor.d.ts +113 -0
  97. package/dist/search-first-executor.d.ts.map +1 -0
  98. package/dist/session.d.ts +48 -0
  99. package/dist/session.d.ts.map +1 -0
  100. package/dist/template-extractor.d.ts +9 -0
  101. package/dist/template-extractor.d.ts.map +1 -0
  102. package/dist/template-generator.d.ts +12 -0
  103. package/dist/template-generator.d.ts.map +1 -0
  104. package/dist/tools.d.ts +58 -0
  105. package/dist/tools.d.ts.map +1 -0
  106. package/dist/tools.js +771 -0
  107. package/dist/tools.js.map +1 -0
  108. package/dist/types.d.ts +503 -0
  109. package/dist/types.d.ts.map +1 -0
  110. package/dist/types.js +8 -0
  111. package/dist/types.js.map +1 -0
  112. package/dist/understanding/analyzer.d.ts +55 -0
  113. package/dist/understanding/analyzer.d.ts.map +1 -0
  114. package/dist/understanding/explorer.d.ts +73 -0
  115. package/dist/understanding/explorer.d.ts.map +1 -0
  116. package/dist/understanding/index.d.ts +7 -0
  117. package/dist/understanding/index.d.ts.map +1 -0
  118. package/dist/understanding/types.d.ts +136 -0
  119. package/dist/understanding/types.d.ts.map +1 -0
  120. package/dist/validation.d.ts +29 -0
  121. package/dist/validation.d.ts.map +1 -0
  122. package/dist/validation.js +106 -0
  123. package/dist/validation.js.map +1 -0
  124. package/dist/vessel-bootstrap.d.ts +190 -0
  125. package/dist/vessel-bootstrap.d.ts.map +1 -0
  126. package/dist/vessel-registry.d.ts +229 -0
  127. package/dist/vessel-registry.d.ts.map +1 -0
  128. package/index.ts +1329 -0
  129. package/package.json +54 -0
  130. package/src/acp-gossip.ts +193 -0
  131. package/src/acp.ts +362 -0
  132. package/src/activity.ts +1464 -0
  133. package/src/agent-runtime.ts +365 -0
  134. package/src/boredom.ts +423 -0
  135. package/src/cli/acp-server.ts +377 -0
  136. package/src/cli/burrow.ts +896 -0
  137. package/src/cli/doctor.ts +526 -0
  138. package/src/cli/goal.ts +224 -0
  139. package/src/cli/index.ts +147 -0
  140. package/src/cli/instance-registry.ts +271 -0
  141. package/src/cli/observe.ts +682 -0
  142. package/src/cli/vessel.ts +287 -0
  143. package/src/components/SystemOverview.tsx +331 -0
  144. package/src/composition-observer.ts +449 -0
  145. package/src/config.ts +172 -0
  146. package/src/environment.ts +167 -0
  147. package/src/goal-processor.ts +654 -0
  148. package/src/improviser.ts +591 -0
  149. package/src/impulse-filter.ts +273 -0
  150. package/src/impulse.ts +311 -0
  151. package/src/lib.ts +147 -0
  152. package/src/lifecycle-hooks.ts +181 -0
  153. package/src/llm.ts +434 -0
  154. package/src/mcp-activity-bridge.ts +158 -0
  155. package/src/mcp.ts +747 -0
  156. package/src/memory-agent.ts +316 -0
  157. package/src/runtime-mapping.ts +527 -0
  158. package/src/search-first-executor.ts +666 -0
  159. package/src/session.ts +141 -0
  160. package/src/template-extractor.ts +256 -0
  161. package/src/template-generator.ts +130 -0
  162. package/src/tools.ts +924 -0
  163. package/src/types.ts +497 -0
  164. package/src/understanding/analyzer.ts +354 -0
  165. package/src/understanding/explorer.ts +488 -0
  166. package/src/understanding/index.ts +27 -0
  167. package/src/understanding/types.ts +153 -0
  168. package/src/validation.ts +125 -0
  169. package/src/vessel-bootstrap.ts +440 -0
  170. package/src/vessel-registry.ts +621 -0
  171. package/templates/core/edit-file.json +85 -0
  172. package/templates/understanding/diagnose-problem.json +32 -0
  173. package/templates/understanding/explore-codebase-v2.json +57 -0
  174. package/templates/understanding/explore-codebase.json +37 -0
package/src/llm.ts ADDED
@@ -0,0 +1,434 @@
1
+ /**
2
+ * minibob LLM Client
3
+ *
4
+ * Minimal LLM integration supporting Anthropic Claude.
5
+ * Handles tool calling loop for activity execution.
6
+ */
7
+
8
+ import type {
9
+ CompletionOptions,
10
+ CompletionResult,
11
+ ToolResult,
12
+ ToolHandler,
13
+ } from "./types"
14
+
15
+ // =============================================================================
16
+ // LLM CLIENT
17
+ // =============================================================================
18
+
19
+ export interface LLMClient {
20
+ complete(options: CompletionOptions): Promise<CompletionResult>
21
+ completeWithTools(
22
+ options: CompletionOptions,
23
+ toolHandlers: Record<string, ToolHandler>
24
+ ): Promise<{ content: string; toolsUsed: string[]; usage: { inputTokens: number; outputTokens: number } }>
25
+ }
26
+
27
+ /**
28
+ * Create Anthropic LLM client
29
+ */
30
+ export function createAnthropicClient(apiKey: string): LLMClient {
31
+ const baseUrl = "https://api.anthropic.com/v1"
32
+
33
+ async function complete(options: CompletionOptions): Promise<CompletionResult> {
34
+ // Convert messages to Anthropic format
35
+ const systemMessage = options.messages.find((m) => m.role === "system")
36
+ const conversationMessages = options.messages
37
+ .filter((m) => m.role !== "system")
38
+ .map((m) => {
39
+ // Tool result message
40
+ if (m.role === "tool") {
41
+ return {
42
+ role: "user" as const,
43
+ content: [
44
+ {
45
+ type: "tool_result" as const,
46
+ tool_use_id: m.toolCallId!,
47
+ content: m.content,
48
+ },
49
+ ],
50
+ }
51
+ }
52
+
53
+ // Assistant message with tool calls
54
+ if (m.role === "assistant" && m.toolCalls && m.toolCalls.length > 0) {
55
+ const content: Array<any> = []
56
+
57
+ // Add text content if present
58
+ if (m.content) {
59
+ content.push({ type: "text", text: m.content })
60
+ }
61
+
62
+ // Add tool_use blocks
63
+ for (const toolCall of m.toolCalls) {
64
+ content.push({
65
+ type: "tool_use",
66
+ id: toolCall.id,
67
+ name: toolCall.name,
68
+ input: toolCall.arguments,
69
+ })
70
+ }
71
+
72
+ return {
73
+ role: "assistant" as const,
74
+ content,
75
+ }
76
+ }
77
+
78
+ // Regular message
79
+ return {
80
+ role: m.role,
81
+ content: m.content,
82
+ }
83
+ })
84
+
85
+ // Convert tools to Anthropic format
86
+ const tools = options.tools?.map((t) => ({
87
+ name: t.name,
88
+ description: t.description,
89
+ input_schema: t.parameters,
90
+ }))
91
+
92
+ const body: Record<string, unknown> = {
93
+ model: options.model,
94
+ max_tokens: options.maxTokens ?? 4096,
95
+ messages: conversationMessages,
96
+ }
97
+
98
+ if (systemMessage) {
99
+ body.system = systemMessage.content
100
+ }
101
+
102
+ if (tools && tools.length > 0) {
103
+ body.tools = tools
104
+ }
105
+
106
+ if (options.temperature !== undefined) {
107
+ body.temperature = options.temperature
108
+ }
109
+
110
+ const response = await fetch(`${baseUrl}/messages`, {
111
+ method: "POST",
112
+ headers: {
113
+ "Content-Type": "application/json",
114
+ "x-api-key": apiKey,
115
+ "anthropic-version": "2023-06-01",
116
+ },
117
+ body: JSON.stringify(body),
118
+ })
119
+
120
+ if (!response.ok) {
121
+ const error = await response.text()
122
+ throw new Error(`Anthropic API error: ${response.status} - ${error}`)
123
+ }
124
+
125
+ const data = (await response.json()) as {
126
+ content: Array<{ type: string; text?: string; id?: string; name?: string; input?: Record<string, unknown> }>
127
+ stop_reason: string
128
+ usage: { input_tokens: number; output_tokens: number }
129
+ }
130
+
131
+ // Extract content and tool calls
132
+ let content = ""
133
+ const toolCalls: Array<{ id: string; name: string; arguments: Record<string, unknown> }> = []
134
+
135
+ for (const block of data.content) {
136
+ if (block.type === "text" && block.text) {
137
+ content += block.text
138
+ } else if (block.type === "tool_use" && block.id && block.name) {
139
+ toolCalls.push({
140
+ id: block.id,
141
+ name: block.name,
142
+ arguments: block.input ?? {},
143
+ })
144
+ }
145
+ }
146
+
147
+ return {
148
+ content,
149
+ toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
150
+ finishReason: data.stop_reason === "end_turn" ? "stop" : data.stop_reason === "tool_use" ? "tool_calls" : "stop",
151
+ usage: {
152
+ inputTokens: data.usage.input_tokens,
153
+ outputTokens: data.usage.output_tokens,
154
+ },
155
+ }
156
+ }
157
+
158
+ async function completeWithTools(
159
+ options: CompletionOptions,
160
+ toolHandlers: Record<string, ToolHandler>
161
+ ): Promise<{ content: string; toolsUsed: string[]; usage: { inputTokens: number; outputTokens: number } }> {
162
+ const messages = [...options.messages]
163
+ const toolsUsed: string[] = []
164
+ let totalInputTokens = 0
165
+ let totalOutputTokens = 0
166
+ let finalContent = ""
167
+ let maxIterations = 20 // Prevent infinite loops
168
+
169
+ while (maxIterations-- > 0) {
170
+ const result = await complete({
171
+ ...options,
172
+ messages,
173
+ })
174
+
175
+ totalInputTokens += result.usage.inputTokens
176
+ totalOutputTokens += result.usage.outputTokens
177
+
178
+ // If no tool calls, we're done
179
+ if (!result.toolCalls || result.toolCalls.length === 0) {
180
+ finalContent = result.content
181
+ break
182
+ }
183
+
184
+ // Add assistant message with tool calls
185
+ messages.push({
186
+ role: "assistant",
187
+ content: result.content,
188
+ toolCalls: result.toolCalls,
189
+ })
190
+
191
+ // Execute tool calls
192
+ for (const toolCall of result.toolCalls) {
193
+ const handler = toolHandlers[toolCall.name]
194
+ let toolResult: ToolResult
195
+
196
+ if (!handler) {
197
+ toolResult = {
198
+ success: false,
199
+ error: `Unknown tool: ${toolCall.name}`,
200
+ }
201
+ } else {
202
+ try {
203
+ toolResult = await handler(toolCall.arguments)
204
+ toolsUsed.push(toolCall.name)
205
+ } catch (error) {
206
+ toolResult = {
207
+ success: false,
208
+ error: error instanceof Error ? error.message : String(error),
209
+ }
210
+ }
211
+ }
212
+
213
+ // Add tool result message
214
+ messages.push({
215
+ role: "tool",
216
+ content: toolResult.success
217
+ ? toolResult.output ?? "Success"
218
+ : `Error: ${toolResult.error}`,
219
+ toolCallId: toolCall.id,
220
+ })
221
+ }
222
+ }
223
+
224
+ return {
225
+ content: finalContent,
226
+ toolsUsed: [...new Set(toolsUsed)],
227
+ usage: {
228
+ inputTokens: totalInputTokens,
229
+ outputTokens: totalOutputTokens,
230
+ },
231
+ }
232
+ }
233
+
234
+ return { complete, completeWithTools }
235
+ }
236
+
237
+ /**
238
+ * Create OpenAI-compatible LLM client
239
+ */
240
+ export function createOpenAIClient(apiKey: string, baseUrl = "https://api.openai.com/v1"): LLMClient {
241
+ async function complete(options: CompletionOptions): Promise<CompletionResult> {
242
+ // Convert messages to OpenAI format
243
+ const messages = options.messages.map((m) => {
244
+ if (m.role === "tool") {
245
+ return {
246
+ role: "tool" as const,
247
+ tool_call_id: m.toolCallId,
248
+ content: m.content,
249
+ }
250
+ }
251
+ if (m.role === "assistant" && m.toolCalls) {
252
+ return {
253
+ role: "assistant" as const,
254
+ content: m.content || null,
255
+ tool_calls: m.toolCalls.map((tc) => ({
256
+ id: tc.id,
257
+ type: "function" as const,
258
+ function: {
259
+ name: tc.name,
260
+ arguments: JSON.stringify(tc.arguments),
261
+ },
262
+ })),
263
+ }
264
+ }
265
+ return {
266
+ role: m.role,
267
+ content: m.content,
268
+ }
269
+ })
270
+
271
+ // Convert tools to OpenAI format
272
+ const tools = options.tools?.map((t) => ({
273
+ type: "function" as const,
274
+ function: {
275
+ name: t.name,
276
+ description: t.description,
277
+ parameters: t.parameters,
278
+ },
279
+ }))
280
+
281
+ const body: Record<string, unknown> = {
282
+ model: options.model,
283
+ messages,
284
+ max_tokens: options.maxTokens ?? 4096,
285
+ }
286
+
287
+ if (tools && tools.length > 0) {
288
+ body.tools = tools
289
+ }
290
+
291
+ if (options.temperature !== undefined) {
292
+ body.temperature = options.temperature
293
+ }
294
+
295
+ const response = await fetch(`${baseUrl}/chat/completions`, {
296
+ method: "POST",
297
+ headers: {
298
+ "Content-Type": "application/json",
299
+ Authorization: `Bearer ${apiKey}`,
300
+ },
301
+ body: JSON.stringify(body),
302
+ })
303
+
304
+ if (!response.ok) {
305
+ const error = await response.text()
306
+ throw new Error(`OpenAI API error: ${response.status} - ${error}`)
307
+ }
308
+
309
+ const data = (await response.json()) as {
310
+ choices: Array<{
311
+ message: {
312
+ content: string | null
313
+ tool_calls?: Array<{
314
+ id: string
315
+ function: { name: string; arguments: string }
316
+ }>
317
+ }
318
+ finish_reason: string
319
+ }>
320
+ usage: { prompt_tokens: number; completion_tokens: number }
321
+ }
322
+
323
+ const choice = data.choices[0]
324
+ if (!choice) {
325
+ throw new Error("No response from OpenAI API")
326
+ }
327
+ const toolCalls = choice.message.tool_calls?.map((tc) => ({
328
+ id: tc.id,
329
+ name: tc.function.name,
330
+ arguments: JSON.parse(tc.function.arguments) as Record<string, unknown>,
331
+ }))
332
+
333
+ return {
334
+ content: choice.message.content ?? "",
335
+ toolCalls,
336
+ finishReason:
337
+ choice.finish_reason === "stop"
338
+ ? "stop"
339
+ : choice.finish_reason === "tool_calls"
340
+ ? "tool_calls"
341
+ : "stop",
342
+ usage: {
343
+ inputTokens: data.usage.prompt_tokens,
344
+ outputTokens: data.usage.completion_tokens,
345
+ },
346
+ }
347
+ }
348
+
349
+ async function completeWithTools(
350
+ options: CompletionOptions,
351
+ toolHandlers: Record<string, ToolHandler>
352
+ ): Promise<{ content: string; toolsUsed: string[]; usage: { inputTokens: number; outputTokens: number } }> {
353
+ const messages = [...options.messages]
354
+ const toolsUsed: string[] = []
355
+ let totalInputTokens = 0
356
+ let totalOutputTokens = 0
357
+ let finalContent = ""
358
+ let maxIterations = 20
359
+
360
+ while (maxIterations-- > 0) {
361
+ const result = await complete({
362
+ ...options,
363
+ messages,
364
+ })
365
+
366
+ totalInputTokens += result.usage.inputTokens
367
+ totalOutputTokens += result.usage.outputTokens
368
+
369
+ if (!result.toolCalls || result.toolCalls.length === 0) {
370
+ finalContent = result.content
371
+ break
372
+ }
373
+
374
+ messages.push({
375
+ role: "assistant",
376
+ content: result.content,
377
+ toolCalls: result.toolCalls,
378
+ })
379
+
380
+ for (const toolCall of result.toolCalls) {
381
+ const handler = toolHandlers[toolCall.name]
382
+ let toolResult: ToolResult
383
+
384
+ if (!handler) {
385
+ toolResult = {
386
+ success: false,
387
+ error: `Unknown tool: ${toolCall.name}`,
388
+ }
389
+ } else {
390
+ try {
391
+ toolResult = await handler(toolCall.arguments)
392
+ toolsUsed.push(toolCall.name)
393
+ } catch (error) {
394
+ toolResult = {
395
+ success: false,
396
+ error: error instanceof Error ? error.message : String(error),
397
+ }
398
+ }
399
+ }
400
+
401
+ messages.push({
402
+ role: "tool",
403
+ content: toolResult.success ? toolResult.output ?? "Success" : `Error: ${toolResult.error}`,
404
+ toolCallId: toolCall.id,
405
+ })
406
+ }
407
+ }
408
+
409
+ return {
410
+ content: finalContent,
411
+ toolsUsed: [...new Set(toolsUsed)],
412
+ usage: {
413
+ inputTokens: totalInputTokens,
414
+ outputTokens: totalOutputTokens,
415
+ },
416
+ }
417
+ }
418
+
419
+ return { complete, completeWithTools }
420
+ }
421
+
422
+ /**
423
+ * Create LLM client based on provider
424
+ */
425
+ export function createLLMClient(provider: "anthropic" | "openai", apiKey: string): LLMClient {
426
+ switch (provider) {
427
+ case "anthropic":
428
+ return createAnthropicClient(apiKey)
429
+ case "openai":
430
+ return createOpenAIClient(apiKey)
431
+ default:
432
+ throw new Error(`Unknown provider: ${provider}`)
433
+ }
434
+ }
@@ -0,0 +1,158 @@
1
+ /**
2
+ * MCP Activity Bridge
3
+ *
4
+ * Bridges MiniBob's activity callbacks to Metabob MCP tools.
5
+ *
6
+ * Architecture:
7
+ * - MiniBob activity.ts defines callbacks (onSearchActivities, onCreateActivity)
8
+ * - This bridge translates those callbacks to MCP tool calls
9
+ * - MCP tools (in metabob-cli Python) delegate to RPC API
10
+ * - RPC API handles Thompson Sampling and learning (NOT OpenCode)
11
+ *
12
+ * Specification: minibob-trailblazing-activity-system
13
+ * Compliance: Activity-first constraint enforcement
14
+ */
15
+
16
+ import { getMCPClient } from "./mcp"
17
+
18
+ export namespace MCPActivityBridge {
19
+ /**
20
+ * Search activities via /v2/activities/templates endpoint.
21
+ *
22
+ * This implements the onSearchActivities callback expected by MiniBob tools.
23
+ *
24
+ * @param category Optional category filter (feature, bugfix, refactor, tool, infrastructure)
25
+ * @param verbose If true, return full template details; if false, return minimal info
26
+ * @returns Activity search results
27
+ */
28
+ export async function searchActivities(
29
+ category?: string,
30
+ _verbose?: boolean // Reserved for future use
31
+ ): Promise<{ count: number; activities: unknown[] }> {
32
+ const mcp = getMCPClient()
33
+
34
+ if (!mcp) {
35
+ console.warn("[MCPActivityBridge] MCP not available, returning empty results")
36
+ return { count: 0, activities: [] }
37
+ }
38
+
39
+ try {
40
+ // Call the /v2/activities/templates endpoint directly
41
+ const params = new URLSearchParams()
42
+ if (category) params.set("category", category)
43
+ params.set("limit", "50")
44
+
45
+ const url = `/v2/activities/templates?${params.toString()}`
46
+ const response = await mcp["request"]("GET", url)
47
+
48
+ if (!response.ok) {
49
+ throw new Error(`MCP search failed: ${response.status} ${response.statusText}`)
50
+ }
51
+
52
+ const result = await response.json() as {
53
+ templates?: unknown[]
54
+ total?: number
55
+ error?: string
56
+ message?: string
57
+ }
58
+
59
+ if (result.error) {
60
+ console.error("[MCPActivityBridge] Search error:", result.error)
61
+ return { count: 0, activities: [] }
62
+ }
63
+
64
+ return {
65
+ count: result.total ?? (result.templates?.length ?? 0),
66
+ activities: result.templates ?? [],
67
+ }
68
+ } catch (error) {
69
+ console.error("[MCPActivityBridge] Search activities failed:", error)
70
+ return { count: 0, activities: [] }
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Create activity via metabob_create_activity_goal_seeking MCP tool.
76
+ *
77
+ * This implements the onCreateActivity callback expected by MiniBob tools.
78
+ *
79
+ * Architecture Note:
80
+ * - This delegates to MCP tool (metabob_create_activity_goal_seeking)
81
+ * - MCP tool calls RPC API /v2/activities/create-goal-seeking
82
+ * - RPC API handles:
83
+ * - Thompson Sampling initialization (NOT OpenCode)
84
+ * - Goal decomposition
85
+ * - Template registration
86
+ *
87
+ * @param params Goal seeking parameters
88
+ * @returns Created template ID
89
+ */
90
+ export async function createActivity(params: {
91
+ goalDescription: string
92
+ templateName: string
93
+ category: string
94
+ variables: Record<string, unknown>
95
+ impulseRefs?: string[]
96
+ constraints?: {
97
+ maxTasks?: number
98
+ maxCost?: number
99
+ preferComposition?: boolean
100
+ }
101
+ }): Promise<{ templateId: string }> {
102
+ const mcp = getMCPClient()
103
+
104
+ if (!mcp) {
105
+ throw new Error("MCP not available - cannot create activity template")
106
+ }
107
+
108
+ try {
109
+ // Call backend create-goal-seeking API
110
+ const response = await mcp["request"]("POST", "/v2/activities/create-goal-seeking", {
111
+ goal_description: params.goalDescription,
112
+ template_name: params.templateName,
113
+ category: params.category,
114
+ variables: params.variables,
115
+ impulse_refs: params.impulseRefs ?? [],
116
+ constraints: params.constraints ?? {
117
+ max_tasks: 7,
118
+ max_cost: 5.0,
119
+ prefer_composition: true,
120
+ },
121
+ })
122
+
123
+ if (!response.ok) {
124
+ throw new Error(`MCP create activity failed: ${response.status} ${response.statusText}`)
125
+ }
126
+
127
+ const result = await response.json() as {
128
+ status: string
129
+ template_id?: string
130
+ error?: string
131
+ }
132
+
133
+ if (result.status !== "success" || !result.template_id) {
134
+ throw new Error(`Failed to create activity: ${result.error ?? "Unknown error"}`)
135
+ }
136
+
137
+ console.log(
138
+ `[MCPActivityBridge] Created activity template: ${result.template_id} via goal-seeking`
139
+ )
140
+
141
+ return {
142
+ templateId: result.template_id,
143
+ }
144
+ } catch (error) {
145
+ console.error("[MCPActivityBridge] Create activity failed:", error)
146
+ throw error
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Check if MCP activity bridge is available.
152
+ *
153
+ * Returns true if MCP client is initialized and bridge can be used.
154
+ */
155
+ export function isAvailable(): boolean {
156
+ return getMCPClient() !== null
157
+ }
158
+ }