@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/index.ts
ADDED
|
@@ -0,0 +1,1329 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* minibob - Minimal Vessel for the Process-of-Becoming
|
|
4
|
+
*
|
|
5
|
+
* Just describe what you want. MiniBob figures out how to do it.
|
|
6
|
+
*
|
|
7
|
+
* Primary usage:
|
|
8
|
+
* minibob goal "Fix the login bug"
|
|
9
|
+
* minibob goal "Add a logout button"
|
|
10
|
+
* minibob goal "Optimize slow queries"
|
|
11
|
+
*
|
|
12
|
+
* MiniBob automatically:
|
|
13
|
+
* 1. Searches for proven solutions (existing templates)
|
|
14
|
+
* 2. Adapts to your context (impulse interpolation)
|
|
15
|
+
* 3. Recovers from failures (adaptive retry & trailblazing)
|
|
16
|
+
* 4. Improvises when needed (creative exploration)
|
|
17
|
+
* 5. Learns which approaches work (Thompson Sampling)
|
|
18
|
+
*
|
|
19
|
+
* Advanced usage:
|
|
20
|
+
* minibob run <template> # Execute specific template
|
|
21
|
+
* minibob improvise <goal> # Pure improvisation
|
|
22
|
+
* minibob understand <path> # Codebase exploration
|
|
23
|
+
* minibob diagnose <problem> # Problem diagnosis
|
|
24
|
+
*
|
|
25
|
+
* For local development:
|
|
26
|
+
* bun run index.ts [command] # Run from source
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { loadConfig, generateManifest, configSummary, type RuntimeContext } from "./src/config"
|
|
30
|
+
import { ActivityExecutor, loadTemplate, loadTemplateFromMCPOrLocal, type ExecutorConfig } from "./src/activity"
|
|
31
|
+
import { handleACPRequest, type ACPServerConfig } from "./src/acp"
|
|
32
|
+
import { initializeMCP, type MCPClient } from "./src/mcp"
|
|
33
|
+
import { initializeBoredom, startBoredom, markBoredomActivity } from "./src/boredom"
|
|
34
|
+
import { validateRunActivityRequest, validateRequestSize, ValidationException } from "./src/validation"
|
|
35
|
+
import { MCPActivityBridge } from "./src/mcp-activity-bridge"
|
|
36
|
+
import { ActivityCompositionService } from "./activity_composition_service"
|
|
37
|
+
import { GoalProcessor } from "./src/goal-processor"
|
|
38
|
+
import { SearchFirstExecutor } from "./src/search-first-executor"
|
|
39
|
+
import { CodeExplorer, ApplicationAnalyzer } from "./src/understanding"
|
|
40
|
+
|
|
41
|
+
// CLI command imports
|
|
42
|
+
import { burrow } from "./src/cli/burrow"
|
|
43
|
+
import { observe } from "./src/cli/observe"
|
|
44
|
+
import { goal } from "./src/cli/goal"
|
|
45
|
+
import { doctor } from "./src/cli/doctor"
|
|
46
|
+
import { launchACPServer } from "./src/cli/acp-server"
|
|
47
|
+
// Idealization is handled within vessel-bootstrap, not as a separate CLI command
|
|
48
|
+
import { registerInstance, deregisterInstance, updateInstanceStatus, generateInstanceId } from "./src/cli/instance-registry"
|
|
49
|
+
import { vessel as vesselCommand } from "./src/cli/vessel"
|
|
50
|
+
|
|
51
|
+
// Vessel registry and runtime mapping
|
|
52
|
+
import {
|
|
53
|
+
registerVessel,
|
|
54
|
+
registerVesselWithSync,
|
|
55
|
+
recordActivityExecution,
|
|
56
|
+
fetchVesselFromBackend,
|
|
57
|
+
getVessel,
|
|
58
|
+
getVesselByPath,
|
|
59
|
+
listVessels,
|
|
60
|
+
updateVesselActivities,
|
|
61
|
+
addVesselActivity,
|
|
62
|
+
updateVesselMapping,
|
|
63
|
+
recordVesselComposition,
|
|
64
|
+
deregisterVessel,
|
|
65
|
+
vesselRegistry,
|
|
66
|
+
type Vessel,
|
|
67
|
+
type VesselIdentity,
|
|
68
|
+
type VesselActivity,
|
|
69
|
+
type VesselImpulse,
|
|
70
|
+
type VesselHook,
|
|
71
|
+
type VesselTool,
|
|
72
|
+
type VesselComposition,
|
|
73
|
+
type RuntimeMapping,
|
|
74
|
+
type SourceLocation,
|
|
75
|
+
type InferredIntent,
|
|
76
|
+
} from "./src/vessel-registry"
|
|
77
|
+
|
|
78
|
+
import {
|
|
79
|
+
analyzeCodebase as analyzeCodebaseMapping,
|
|
80
|
+
mapTraceToSource,
|
|
81
|
+
inferIntent,
|
|
82
|
+
runtimeMapping,
|
|
83
|
+
type TraceToSourceMapping,
|
|
84
|
+
type CodebaseAnalysis,
|
|
85
|
+
type FunctionInfo,
|
|
86
|
+
type ClassInfo,
|
|
87
|
+
type ModuleInfo,
|
|
88
|
+
type DocumentationInfo,
|
|
89
|
+
} from "./src/runtime-mapping"
|
|
90
|
+
|
|
91
|
+
// Re-export for library usage
|
|
92
|
+
export {
|
|
93
|
+
// Vessel registry
|
|
94
|
+
registerVessel,
|
|
95
|
+
getVessel,
|
|
96
|
+
getVesselByPath,
|
|
97
|
+
listVessels,
|
|
98
|
+
updateVesselActivities,
|
|
99
|
+
addVesselActivity,
|
|
100
|
+
updateVesselMapping,
|
|
101
|
+
recordVesselComposition,
|
|
102
|
+
deregisterVessel,
|
|
103
|
+
vesselRegistry,
|
|
104
|
+
// Runtime mapping
|
|
105
|
+
analyzeCodebaseMapping,
|
|
106
|
+
mapTraceToSource,
|
|
107
|
+
inferIntent,
|
|
108
|
+
runtimeMapping,
|
|
109
|
+
// CLI commands
|
|
110
|
+
burrow,
|
|
111
|
+
observe,
|
|
112
|
+
goal,
|
|
113
|
+
doctor,
|
|
114
|
+
launchACPServer,
|
|
115
|
+
vesselCommand,
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Re-export AgentRuntime for library usage
|
|
119
|
+
export { AgentRuntime } from "./src/agent-runtime"
|
|
120
|
+
|
|
121
|
+
// Re-export types
|
|
122
|
+
export type {
|
|
123
|
+
Vessel,
|
|
124
|
+
VesselIdentity,
|
|
125
|
+
VesselActivity,
|
|
126
|
+
VesselImpulse,
|
|
127
|
+
VesselHook,
|
|
128
|
+
VesselTool,
|
|
129
|
+
VesselComposition,
|
|
130
|
+
RuntimeMapping,
|
|
131
|
+
SourceLocation,
|
|
132
|
+
InferredIntent,
|
|
133
|
+
TraceToSourceMapping,
|
|
134
|
+
CodebaseAnalysis,
|
|
135
|
+
FunctionInfo,
|
|
136
|
+
ClassInfo,
|
|
137
|
+
ModuleInfo,
|
|
138
|
+
DocumentationInfo,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// =============================================================================
|
|
142
|
+
// CLI ARGUMENT PARSING
|
|
143
|
+
// =============================================================================
|
|
144
|
+
|
|
145
|
+
const args = process.argv.slice(2)
|
|
146
|
+
const command = args[0]
|
|
147
|
+
|
|
148
|
+
// =============================================================================
|
|
149
|
+
// HELP COMMAND
|
|
150
|
+
// =============================================================================
|
|
151
|
+
|
|
152
|
+
if (command === "--help" || command === "-h") {
|
|
153
|
+
console.log(`
|
|
154
|
+
minibob - Minimal Vessel for the Process-of-Becoming
|
|
155
|
+
|
|
156
|
+
Just describe what you want. MiniBob figures out how to do it.
|
|
157
|
+
|
|
158
|
+
USAGE:
|
|
159
|
+
minibob goal "<description>" # Recommended: Describe what you want
|
|
160
|
+
minibob [command] [options] # Advanced: Direct command access
|
|
161
|
+
|
|
162
|
+
PRIMARY COMMAND:
|
|
163
|
+
goal <description> Achieve a goal (recommended approach)
|
|
164
|
+
MiniBob will:
|
|
165
|
+
1. Try routine activities (learned patterns)
|
|
166
|
+
2. Recover from issues (adaptive retry)
|
|
167
|
+
3. Improvise solutions (creative exploration)
|
|
168
|
+
|
|
169
|
+
Example: minibob goal "Fix the login bug"
|
|
170
|
+
Example: minibob goal "Add a logout button"
|
|
171
|
+
|
|
172
|
+
UTILITY COMMANDS:
|
|
173
|
+
doctor Health check and configuration
|
|
174
|
+
observe View running minibob instances
|
|
175
|
+
vessel <subcommand> Manage vessel registry (list, info, remove)
|
|
176
|
+
burrow [path] Inject minibob into a codebase
|
|
177
|
+
(no command) Start the HTTP/ACP server
|
|
178
|
+
acp Launch standalone ACP server
|
|
179
|
+
|
|
180
|
+
ADVANCED COMMANDS (Direct Execution):
|
|
181
|
+
run <template> Execute a specific activity template
|
|
182
|
+
Example: minibob run templates/fix-bug.json
|
|
183
|
+
|
|
184
|
+
improvise <goal> Pure improvisation (no template search)
|
|
185
|
+
Example: minibob improvise "Create HTTP server"
|
|
186
|
+
|
|
187
|
+
understand [path] Explore and analyze codebase structure
|
|
188
|
+
Example: minibob understand ./my-app
|
|
189
|
+
|
|
190
|
+
diagnose <problem> Diagnose application problems
|
|
191
|
+
Example: minibob diagnose "API returns 500"
|
|
192
|
+
|
|
193
|
+
--help Show this help message
|
|
194
|
+
|
|
195
|
+
HOW MINIBOB WORKS:
|
|
196
|
+
When you use 'goal', MiniBob automatically:
|
|
197
|
+
• Searches for proven solutions (existing templates)
|
|
198
|
+
• Adapts to your specific context (impulse interpolation)
|
|
199
|
+
• Recovers from failures (trailblazing new variants)
|
|
200
|
+
• Creates new templates from successful improvisations
|
|
201
|
+
• Learns which approaches work best (Thompson Sampling)
|
|
202
|
+
|
|
203
|
+
You don't choose the method - MiniBob does. Just describe the goal.
|
|
204
|
+
|
|
205
|
+
ENVIRONMENT VARIABLES:
|
|
206
|
+
ANTHROPIC_API_KEY API key for Claude (required)
|
|
207
|
+
OPENAI_API_KEY API key for OpenAI (if using OpenAI provider)
|
|
208
|
+
MINIBOB_MCP_ENDPOINT Backend API endpoint (default: http://localhost:8080)
|
|
209
|
+
MINIBOB_PORT Server port (default: 8080)
|
|
210
|
+
MINIBOB_HOST Server host (default: 0.0.0.0)
|
|
211
|
+
MINIBOB_PROVIDER LLM provider: anthropic or openai (default: anthropic)
|
|
212
|
+
MINIBOB_MODEL Model to use (default: claude-sonnet-4-20250514)
|
|
213
|
+
MINIBOB_WORKDIR Working directory (default: current directory)
|
|
214
|
+
MINIBOB_TEMPLATES Templates directory (default: ./templates)
|
|
215
|
+
MINIBOB_AUTO_COMMIT Auto-commit changes: true or false (default: false)
|
|
216
|
+
|
|
217
|
+
QUICK START:
|
|
218
|
+
# Install
|
|
219
|
+
npm install -g @metabob/minibob
|
|
220
|
+
|
|
221
|
+
# Set API key
|
|
222
|
+
export ANTHROPIC_API_KEY="sk-ant-..."
|
|
223
|
+
|
|
224
|
+
# Achieve a goal
|
|
225
|
+
minibob goal "Add error handling to the API"
|
|
226
|
+
|
|
227
|
+
# That's it! MiniBob handles the rest.
|
|
228
|
+
|
|
229
|
+
EXAMPLES:
|
|
230
|
+
# Goal-first (recommended)
|
|
231
|
+
minibob goal "Fix the broken test suite"
|
|
232
|
+
minibob goal "Add dark mode to the dashboard"
|
|
233
|
+
minibob goal "Optimize database queries"
|
|
234
|
+
minibob goal "Debug why the API is slow"
|
|
235
|
+
|
|
236
|
+
# Configuration
|
|
237
|
+
minibob doctor # Check setup
|
|
238
|
+
minibob observe --scan # View running instances
|
|
239
|
+
|
|
240
|
+
# Advanced (direct execution)
|
|
241
|
+
minibob run templates/fix-bug.json --var bug="login timeout"
|
|
242
|
+
minibob improvise "Create a countdown timer"
|
|
243
|
+
minibob understand ./my-project components
|
|
244
|
+
minibob diagnose "dashboard crashes on load"
|
|
245
|
+
|
|
246
|
+
DEVELOPMENT:
|
|
247
|
+
# Run from source (contributors)
|
|
248
|
+
bun run index.ts goal "your goal here"
|
|
249
|
+
`)
|
|
250
|
+
process.exit(0)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// RUN ACTIVITY COMMAND
|
|
255
|
+
// =============================================================================
|
|
256
|
+
|
|
257
|
+
if (command === "run") {
|
|
258
|
+
const templatePath = args[1]
|
|
259
|
+
if (!templatePath) {
|
|
260
|
+
console.error("Error: Template path required")
|
|
261
|
+
console.error("Usage: minibob run <template.json> [--var key=value...]")
|
|
262
|
+
process.exit(1)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Parse variables from command line
|
|
266
|
+
const variables: Record<string, unknown> = {}
|
|
267
|
+
for (let i = 2; i < args.length; i++) {
|
|
268
|
+
const arg = args[i]
|
|
269
|
+
if (arg?.startsWith("--var")) {
|
|
270
|
+
const [, varArg] = arg.split("=", 1)
|
|
271
|
+
if (varArg) {
|
|
272
|
+
const eqIndex = varArg.indexOf("=")
|
|
273
|
+
if (eqIndex > 0) {
|
|
274
|
+
const key = varArg.slice(0, eqIndex)
|
|
275
|
+
const value = varArg.slice(eqIndex + 1)
|
|
276
|
+
variables[key] = value
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} else if (arg && args[i - 1] === "--var") {
|
|
280
|
+
const eqIndex = arg.indexOf("=")
|
|
281
|
+
if (eqIndex > 0) {
|
|
282
|
+
const key = arg.slice(0, eqIndex)
|
|
283
|
+
const value = arg.slice(eqIndex + 1)
|
|
284
|
+
variables[key] = value
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Find reason argument
|
|
290
|
+
const reasonIndex = args.indexOf("--reason")
|
|
291
|
+
const reason = reasonIndex > -1 ? args[reasonIndex + 1] : undefined
|
|
292
|
+
|
|
293
|
+
// Run the activity
|
|
294
|
+
runActivity(templatePath, variables, reason)
|
|
295
|
+
.then((result) => {
|
|
296
|
+
console.log("\n=== Activity Result ===")
|
|
297
|
+
console.log(`Status: ${result.status}`)
|
|
298
|
+
console.log(`Duration: ${result.metrics?.duration}ms`)
|
|
299
|
+
console.log(`Tokens: ${result.metrics?.totalTokens.input} in / ${result.metrics?.totalTokens.output} out`)
|
|
300
|
+
console.log(`Cost: $${result.metrics?.cost.toFixed(4)}`)
|
|
301
|
+
|
|
302
|
+
if (result.status === "failed") {
|
|
303
|
+
const failedTask = result.taskResults.find((t) => t.status === "failed")
|
|
304
|
+
console.error(`\nFailed task: ${failedTask?.taskId}`)
|
|
305
|
+
console.error(`Error: ${failedTask?.error}`)
|
|
306
|
+
process.exit(1)
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
.catch((error) => {
|
|
310
|
+
console.error("Error running activity:", error)
|
|
311
|
+
process.exit(1)
|
|
312
|
+
})
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// =============================================================================
|
|
316
|
+
// IMPROVISE COMMAND
|
|
317
|
+
// =============================================================================
|
|
318
|
+
|
|
319
|
+
else if (command === "improvise") {
|
|
320
|
+
const goal = args.slice(1).join(" ")
|
|
321
|
+
if (!goal) {
|
|
322
|
+
console.error("Error: Goal description required")
|
|
323
|
+
console.error('Usage: minibob improvise "your goal here"')
|
|
324
|
+
process.exit(1)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
runImprovise(goal)
|
|
328
|
+
.then(() => {
|
|
329
|
+
process.exit(0)
|
|
330
|
+
})
|
|
331
|
+
.catch((error) => {
|
|
332
|
+
console.error("Error during improvisation:", error)
|
|
333
|
+
process.exit(1)
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// =============================================================================
|
|
338
|
+
// UNDERSTAND COMMAND
|
|
339
|
+
// =============================================================================
|
|
340
|
+
|
|
341
|
+
else if (command === "understand") {
|
|
342
|
+
const rootPath = args[1] || process.cwd()
|
|
343
|
+
const focus = args[2] || "architecture"
|
|
344
|
+
|
|
345
|
+
runUnderstand(rootPath, focus)
|
|
346
|
+
.then(() => {
|
|
347
|
+
process.exit(0)
|
|
348
|
+
})
|
|
349
|
+
.catch((error) => {
|
|
350
|
+
console.error("Error understanding codebase:", error)
|
|
351
|
+
process.exit(1)
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// =============================================================================
|
|
356
|
+
// DIAGNOSE COMMAND
|
|
357
|
+
// =============================================================================
|
|
358
|
+
|
|
359
|
+
else if (command === "diagnose") {
|
|
360
|
+
if (args.length < 2) {
|
|
361
|
+
console.error("Error: Problem description required")
|
|
362
|
+
console.error('Usage: minibob diagnose "description of problem"')
|
|
363
|
+
process.exit(1)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const problem = args.slice(1).join(" ")
|
|
367
|
+
|
|
368
|
+
runDiagnose(problem)
|
|
369
|
+
.then(() => {
|
|
370
|
+
process.exit(0)
|
|
371
|
+
})
|
|
372
|
+
.catch((error) => {
|
|
373
|
+
console.error("Error diagnosing problem:", error)
|
|
374
|
+
process.exit(1)
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// =============================================================================
|
|
379
|
+
// GOAL COMMAND (NEW)
|
|
380
|
+
// =============================================================================
|
|
381
|
+
|
|
382
|
+
else if (command === "goal") {
|
|
383
|
+
goal(args.slice(1))
|
|
384
|
+
.then(() => {
|
|
385
|
+
process.exit(0)
|
|
386
|
+
})
|
|
387
|
+
.catch((error) => {
|
|
388
|
+
console.error("Error executing goal:", error)
|
|
389
|
+
process.exit(1)
|
|
390
|
+
})
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// =============================================================================
|
|
394
|
+
// BURROW COMMAND (NEW)
|
|
395
|
+
// =============================================================================
|
|
396
|
+
|
|
397
|
+
else if (command === "burrow") {
|
|
398
|
+
burrow(args.slice(1))
|
|
399
|
+
.then(() => {
|
|
400
|
+
process.exit(0)
|
|
401
|
+
})
|
|
402
|
+
.catch((error) => {
|
|
403
|
+
console.error("Error during burrow:", error)
|
|
404
|
+
process.exit(1)
|
|
405
|
+
})
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// =============================================================================
|
|
409
|
+
// OBSERVE COMMAND (NEW)
|
|
410
|
+
// =============================================================================
|
|
411
|
+
|
|
412
|
+
else if (command === "observe") {
|
|
413
|
+
observe(args.slice(1))
|
|
414
|
+
.then(() => {
|
|
415
|
+
process.exit(0)
|
|
416
|
+
})
|
|
417
|
+
.catch((error) => {
|
|
418
|
+
console.error("Error observing:", error)
|
|
419
|
+
process.exit(1)
|
|
420
|
+
})
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// =============================================================================
|
|
424
|
+
// DOCTOR COMMAND (NEW)
|
|
425
|
+
// =============================================================================
|
|
426
|
+
|
|
427
|
+
else if (command === "doctor") {
|
|
428
|
+
doctor(args.slice(1))
|
|
429
|
+
.then(() => {
|
|
430
|
+
process.exit(0)
|
|
431
|
+
})
|
|
432
|
+
.catch((error) => {
|
|
433
|
+
console.error("Error running doctor:", error)
|
|
434
|
+
process.exit(1)
|
|
435
|
+
})
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// =============================================================================
|
|
439
|
+
// ACP SERVER COMMAND (NEW)
|
|
440
|
+
// =============================================================================
|
|
441
|
+
|
|
442
|
+
else if (command === "acp") {
|
|
443
|
+
launchACPServer(args.slice(1))
|
|
444
|
+
.then(() => {
|
|
445
|
+
// ACP server runs until terminated
|
|
446
|
+
})
|
|
447
|
+
.catch((error) => {
|
|
448
|
+
console.error("Error starting ACP server:", error)
|
|
449
|
+
process.exit(1)
|
|
450
|
+
})
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// =============================================================================
|
|
454
|
+
// VESSEL COMMAND (NEW)
|
|
455
|
+
// =============================================================================
|
|
456
|
+
|
|
457
|
+
else if (command === "vessel") {
|
|
458
|
+
vesselCommand(args.slice(1))
|
|
459
|
+
.then(() => {
|
|
460
|
+
process.exit(0)
|
|
461
|
+
})
|
|
462
|
+
.catch((error) => {
|
|
463
|
+
console.error("Error with vessel command:", error)
|
|
464
|
+
process.exit(1)
|
|
465
|
+
})
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
else {
|
|
469
|
+
// =============================================================================
|
|
470
|
+
// START SERVER (default)
|
|
471
|
+
// =============================================================================
|
|
472
|
+
startServer()
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// =============================================================================
|
|
476
|
+
// ACTIVITY RUNNER
|
|
477
|
+
// =============================================================================
|
|
478
|
+
|
|
479
|
+
async function runActivity(
|
|
480
|
+
templatePath: string,
|
|
481
|
+
variables: Record<string, unknown>,
|
|
482
|
+
reason?: string
|
|
483
|
+
) {
|
|
484
|
+
const config = await loadConfig()
|
|
485
|
+
console.log(configSummary(config))
|
|
486
|
+
|
|
487
|
+
// Initialize agent runtime with backend integration
|
|
488
|
+
const { AgentRuntime } = await import("./src/agent-runtime")
|
|
489
|
+
const runtime = await AgentRuntime.initialize({
|
|
490
|
+
config,
|
|
491
|
+
mode: "cli",
|
|
492
|
+
enableHeartbeat: false, // No heartbeat for one-shot CLI commands
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
console.log(`\nRunning activity: ${templatePath}`)
|
|
496
|
+
console.log(`Variables: ${JSON.stringify(variables)}`)
|
|
497
|
+
if (reason) console.log(`Reason: ${reason}`)
|
|
498
|
+
|
|
499
|
+
try {
|
|
500
|
+
// Load template from MCP or local
|
|
501
|
+
const template = await loadTemplateFromMCPOrLocal(templatePath)
|
|
502
|
+
const executorConfig: ExecutorConfig = {
|
|
503
|
+
provider: config.provider,
|
|
504
|
+
apiKey: config.apiKey,
|
|
505
|
+
model: config.model,
|
|
506
|
+
workingDirectory: config.workingDirectory,
|
|
507
|
+
// Wire MCP activity callbacks for autonomous trailblazing
|
|
508
|
+
onSearchActivities: MCPActivityBridge.isAvailable() ? MCPActivityBridge.searchActivities : undefined,
|
|
509
|
+
onCreateActivity: MCPActivityBridge.isAvailable() ? MCPActivityBridge.createActivity : undefined,
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const executor = new ActivityExecutor(executorConfig)
|
|
513
|
+
|
|
514
|
+
// Track activity
|
|
515
|
+
runtime.setCurrentActivity({
|
|
516
|
+
id: template.id,
|
|
517
|
+
name: template.name,
|
|
518
|
+
startedAt: Date.now()
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
const result = await executor.execute({
|
|
522
|
+
template,
|
|
523
|
+
variables,
|
|
524
|
+
reason,
|
|
525
|
+
onTaskStart: (taskId) => {
|
|
526
|
+
console.log(`\n>>> Starting task: ${taskId}`)
|
|
527
|
+
runtime.setCurrentActivity({
|
|
528
|
+
id: template.id,
|
|
529
|
+
name: template.name,
|
|
530
|
+
task: taskId,
|
|
531
|
+
startedAt: Date.now()
|
|
532
|
+
})
|
|
533
|
+
},
|
|
534
|
+
onTaskComplete: (taskId, taskResult) => {
|
|
535
|
+
const status = taskResult.status === "completed" ? "✓" : "✗"
|
|
536
|
+
console.log(`${status} Completed task: ${taskId}`)
|
|
537
|
+
},
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
// Report execution to backend
|
|
541
|
+
await runtime.reportExecution(result)
|
|
542
|
+
runtime.clearCurrentActivity()
|
|
543
|
+
|
|
544
|
+
return result
|
|
545
|
+
} finally {
|
|
546
|
+
// Clean shutdown
|
|
547
|
+
await runtime.shutdown()
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// =============================================================================
|
|
552
|
+
// UNDERSTAND COMMAND RUNNER
|
|
553
|
+
// =============================================================================
|
|
554
|
+
|
|
555
|
+
async function runUnderstand(rootPath: string, focus: string) {
|
|
556
|
+
const config = await loadConfig()
|
|
557
|
+
console.log(configSummary(config))
|
|
558
|
+
|
|
559
|
+
// Initialize MCP if configured (for potential backend features)
|
|
560
|
+
if (config.vessels.metabob) {
|
|
561
|
+
const mcpEndpoint = config.vessels.metabob.endpoint
|
|
562
|
+
console.log(`\nInitializing MCP client: ${mcpEndpoint}`)
|
|
563
|
+
await initializeMCP({ endpoint: mcpEndpoint, apiKey: config.apiKey }, true)
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
console.log(`\n=== Understanding Codebase ===`)
|
|
567
|
+
console.log(`Path: ${rootPath}`)
|
|
568
|
+
console.log(`Focus: ${focus}\n`)
|
|
569
|
+
|
|
570
|
+
// 1. Fast static exploration
|
|
571
|
+
const explorer = new CodeExplorer(config.workingDirectory)
|
|
572
|
+
console.log('Exploring codebase structure...')
|
|
573
|
+
const structure = await explorer.explore(rootPath)
|
|
574
|
+
|
|
575
|
+
console.log(`\n📊 Codebase Overview:`)
|
|
576
|
+
console.log(` Total Files: ${structure.totalFiles}`)
|
|
577
|
+
console.log(` Total Lines of Code: ${structure.totalLoc.toLocaleString()}`)
|
|
578
|
+
console.log(` Total Size: ${(structure.totalSize / 1024).toFixed(2)} KB`)
|
|
579
|
+
console.log(`\n📁 File Types:`)
|
|
580
|
+
for (const [ext, count] of Object.entries(structure.filesByType).sort((a, b) => b[1] - a[1]).slice(0, 10)) {
|
|
581
|
+
console.log(` .${ext}: ${count} files`)
|
|
582
|
+
}
|
|
583
|
+
console.log(`\n🔧 Frameworks:`)
|
|
584
|
+
if (structure.dependencies.frameworks.length > 0) {
|
|
585
|
+
for (const framework of structure.dependencies.frameworks) {
|
|
586
|
+
console.log(` - ${framework}`)
|
|
587
|
+
}
|
|
588
|
+
} else {
|
|
589
|
+
console.log(` (none detected)`)
|
|
590
|
+
}
|
|
591
|
+
console.log(`\n🚀 Entry Points:`)
|
|
592
|
+
for (const entry of structure.entryPoints) {
|
|
593
|
+
console.log(` - ${entry}`)
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 2. LLM-powered semantic analysis
|
|
597
|
+
console.log(`\n🤖 Performing semantic analysis with LLM...`)
|
|
598
|
+
|
|
599
|
+
// Use the target path as working directory for file reads
|
|
600
|
+
const analyzePath = rootPath.startsWith('/') ? rootPath : `${config.workingDirectory}/${rootPath}`
|
|
601
|
+
|
|
602
|
+
const executorConfig: ExecutorConfig = {
|
|
603
|
+
provider: config.provider,
|
|
604
|
+
apiKey: config.apiKey,
|
|
605
|
+
model: config.model,
|
|
606
|
+
workingDirectory: analyzePath,
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
const executor = new ActivityExecutor(executorConfig)
|
|
610
|
+
const analyzer = new ApplicationAnalyzer(executor, explorer)
|
|
611
|
+
|
|
612
|
+
const analysis = await analyzer.analyze({ rootPath, focus: focus as any, depth: 'medium' })
|
|
613
|
+
|
|
614
|
+
console.log(`\n📝 Analysis Results:\n`)
|
|
615
|
+
console.log(`Architecture Pattern: ${analysis.architecture.pattern}`)
|
|
616
|
+
console.log(`Description: ${analysis.architecture.description}`)
|
|
617
|
+
|
|
618
|
+
if (analysis.architecture.layers.length > 0) {
|
|
619
|
+
console.log(`\nArchitectural Layers:`)
|
|
620
|
+
for (const layer of analysis.architecture.layers) {
|
|
621
|
+
console.log(` - ${layer}`)
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (analysis.components.keyModules.length > 0) {
|
|
626
|
+
console.log(`\nKey Modules:`)
|
|
627
|
+
for (const module of analysis.components.keyModules) {
|
|
628
|
+
console.log(` - ${module.name}: ${module.purpose}`)
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
console.log(`\n✅ Understanding complete!\n`)
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// =============================================================================
|
|
636
|
+
// DIAGNOSE COMMAND RUNNER
|
|
637
|
+
// =============================================================================
|
|
638
|
+
|
|
639
|
+
async function runDiagnose(problem: string) {
|
|
640
|
+
const config = await loadConfig()
|
|
641
|
+
console.log(configSummary(config))
|
|
642
|
+
|
|
643
|
+
// Initialize MCP if configured
|
|
644
|
+
if (config.vessels.metabob) {
|
|
645
|
+
const mcpEndpoint = config.vessels.metabob.endpoint
|
|
646
|
+
console.log(`\nInitializing MCP client: ${mcpEndpoint}`)
|
|
647
|
+
await initializeMCP({ endpoint: mcpEndpoint, apiKey: config.apiKey }, true)
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
console.log(`\n=== Diagnosing Problem ===`)
|
|
651
|
+
console.log(`Problem: ${problem}\n`)
|
|
652
|
+
|
|
653
|
+
const executorConfig: ExecutorConfig = {
|
|
654
|
+
provider: config.provider,
|
|
655
|
+
apiKey: config.apiKey,
|
|
656
|
+
model: config.model,
|
|
657
|
+
workingDirectory: config.workingDirectory,
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const executor = new ActivityExecutor(executorConfig)
|
|
661
|
+
const explorer = new CodeExplorer(config.workingDirectory)
|
|
662
|
+
const analyzer = new ApplicationAnalyzer(executor, explorer)
|
|
663
|
+
|
|
664
|
+
console.log('🔍 Analyzing problem...')
|
|
665
|
+
const diagnosis = await analyzer.diagnose(problem)
|
|
666
|
+
|
|
667
|
+
console.log(`\n📊 Diagnosis Results:\n`)
|
|
668
|
+
console.log(`Confidence: ${diagnosis.confidence.toUpperCase()}`)
|
|
669
|
+
console.log(`\nRoot Cause:`)
|
|
670
|
+
console.log(` ${diagnosis.rootCause}`)
|
|
671
|
+
|
|
672
|
+
if (diagnosis.symptoms.length > 0) {
|
|
673
|
+
console.log(`\nSymptoms:`)
|
|
674
|
+
for (const symptom of diagnosis.symptoms) {
|
|
675
|
+
console.log(` - ${symptom}`)
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
if (diagnosis.affectedFiles.length > 0) {
|
|
680
|
+
console.log(`\nAffected Files:`)
|
|
681
|
+
for (const file of diagnosis.affectedFiles) {
|
|
682
|
+
console.log(` - ${file}`)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
console.log(`\nRecommended Fix:`)
|
|
687
|
+
console.log(` ${diagnosis.recommendedFix}`)
|
|
688
|
+
|
|
689
|
+
if (diagnosis.validationSteps.length > 0) {
|
|
690
|
+
console.log(`\nValidation Steps:`)
|
|
691
|
+
for (const step of diagnosis.validationSteps) {
|
|
692
|
+
console.log(` - ${step}`)
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
console.log(`\n✅ Diagnosis complete!\n`)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// =============================================================================
|
|
700
|
+
// IMPROVISE COMMAND RUNNER
|
|
701
|
+
// =============================================================================
|
|
702
|
+
|
|
703
|
+
async function runImprovise(goal: string) {
|
|
704
|
+
const config = await loadConfig()
|
|
705
|
+
console.log(configSummary(config))
|
|
706
|
+
|
|
707
|
+
// Initialize agent runtime with backend integration
|
|
708
|
+
const { AgentRuntime } = await import("./src/agent-runtime")
|
|
709
|
+
const runtime = await AgentRuntime.initialize({
|
|
710
|
+
config,
|
|
711
|
+
mode: "cli",
|
|
712
|
+
enableHeartbeat: false,
|
|
713
|
+
})
|
|
714
|
+
|
|
715
|
+
console.log(`\n🎭 Improvising toward goal: ${goal}`)
|
|
716
|
+
console.log('Recording all steps...\n')
|
|
717
|
+
|
|
718
|
+
try {
|
|
719
|
+
const { GoalImproviser } = await import('./src/improviser')
|
|
720
|
+
const improviser = new GoalImproviser({
|
|
721
|
+
provider: config.provider,
|
|
722
|
+
apiKey: config.apiKey,
|
|
723
|
+
model: config.model,
|
|
724
|
+
workingDirectory: config.workingDirectory
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
// Track activity
|
|
728
|
+
runtime.setCurrentActivity({
|
|
729
|
+
id: `improvise-${Date.now()}`,
|
|
730
|
+
name: 'Pure Improvisation',
|
|
731
|
+
task: goal,
|
|
732
|
+
startedAt: Date.now()
|
|
733
|
+
})
|
|
734
|
+
|
|
735
|
+
const trace = await improviser.improvise(goal)
|
|
736
|
+
runtime.clearCurrentActivity()
|
|
737
|
+
|
|
738
|
+
// Display results
|
|
739
|
+
console.log(`\n${'='.repeat(60)}`)
|
|
740
|
+
console.log(`Goal: ${goal}`)
|
|
741
|
+
console.log(`Status: ${trace.outcome.goal_achieved ? '✅ Achieved' : '❌ Not achieved'}`)
|
|
742
|
+
console.log(`Steps: ${trace.steps.length}`)
|
|
743
|
+
console.log(`Duration: ${trace.outcome.total_duration_ms}ms`)
|
|
744
|
+
console.log(`Cost: $${trace.outcome.total_cost.toFixed(4)}`)
|
|
745
|
+
console.log(`${'='.repeat(60)}\n`)
|
|
746
|
+
|
|
747
|
+
// Show steps summary
|
|
748
|
+
console.log('Steps taken:')
|
|
749
|
+
trace.steps.forEach(step => {
|
|
750
|
+
const paramsPreview = JSON.stringify(step.params).substring(0, 50)
|
|
751
|
+
console.log(` ${step.step}. ${step.thought}`)
|
|
752
|
+
console.log(` → ${step.action}(${paramsPreview}${paramsPreview.length >= 50 ? '...' : ''})`)
|
|
753
|
+
console.log(` ${step.result.success ? '✓' : '✗'} ${step.result.success ? 'Success' : 'Failed'} (${step.duration_ms}ms)`)
|
|
754
|
+
})
|
|
755
|
+
|
|
756
|
+
// Extract template if successful
|
|
757
|
+
if (trace.outcome.goal_achieved) {
|
|
758
|
+
console.log('\n🧬 Extracting template from successful improvisation...')
|
|
759
|
+
|
|
760
|
+
const { extractTemplateFromImprovisation } = await import('./src/template-extractor')
|
|
761
|
+
const template = await extractTemplateFromImprovisation(trace)
|
|
762
|
+
|
|
763
|
+
console.log(`✅ Template created: ${template.id}`)
|
|
764
|
+
console.log(` Name: ${template.name}`)
|
|
765
|
+
console.log(` Tasks: ${template.tasks.length}`)
|
|
766
|
+
|
|
767
|
+
// Register with backend if available
|
|
768
|
+
const { getMCPClient, isMCPEnabled } = await import('./src/mcp')
|
|
769
|
+
if (isMCPEnabled()) {
|
|
770
|
+
try {
|
|
771
|
+
const mcp = getMCPClient()
|
|
772
|
+
// Store the template - assuming there's a method for this
|
|
773
|
+
// For now, just log that we would register it
|
|
774
|
+
console.log(` Would register with backend (not yet implemented)`)
|
|
775
|
+
} catch (error) {
|
|
776
|
+
console.log(` Backend registration skipped: ${error}`)
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
// TODO: Review and create variants
|
|
781
|
+
// This requires the template-reviewer module which we'll implement next
|
|
782
|
+
console.log('\n💡 Template extraction complete!')
|
|
783
|
+
console.log(' Next: Create variants through review (not yet implemented)')
|
|
784
|
+
} else {
|
|
785
|
+
console.log('\n❌ Goal not achieved - no template extracted')
|
|
786
|
+
if (trace.outcome.error) {
|
|
787
|
+
console.log(` Error: ${trace.outcome.error}`)
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
console.log(`\n✅ Improvisation complete!\n`)
|
|
792
|
+
} finally {
|
|
793
|
+
// Clean shutdown
|
|
794
|
+
await runtime.shutdown()
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// =============================================================================
|
|
799
|
+
// HTTP SERVER
|
|
800
|
+
// =============================================================================
|
|
801
|
+
|
|
802
|
+
async function startServer() {
|
|
803
|
+
const config = await loadConfig()
|
|
804
|
+
console.log(configSummary(config))
|
|
805
|
+
|
|
806
|
+
// =============================================================================
|
|
807
|
+
// ENVIRONMENT AUTO-DETECTION
|
|
808
|
+
// =============================================================================
|
|
809
|
+
|
|
810
|
+
console.log("\n=== Environment Detection ===")
|
|
811
|
+
const { detectCompleteEnvironment } = await import("./src/environment")
|
|
812
|
+
|
|
813
|
+
const mcpEndpoint = config.vessels.metabob?.endpoint
|
|
814
|
+
const envInfo = await detectCompleteEnvironment(mcpEndpoint)
|
|
815
|
+
|
|
816
|
+
console.log(`Environment: ${envInfo.environment}`)
|
|
817
|
+
console.log(`Cluster Mode: ${envInfo.clusterMode} (${envInfo.peerCount} peer(s))`)
|
|
818
|
+
console.log(`Backend Available: ${envInfo.backendAvailable}`)
|
|
819
|
+
console.log("=============================\n")
|
|
820
|
+
|
|
821
|
+
// Build runtime context for dynamic manifest
|
|
822
|
+
const runtime: RuntimeContext = {
|
|
823
|
+
environment: envInfo.environment,
|
|
824
|
+
clusterMode: envInfo.clusterMode,
|
|
825
|
+
peerCount: envInfo.peerCount,
|
|
826
|
+
boredomEnabled: false,
|
|
827
|
+
acpGossipEnabled: false,
|
|
828
|
+
backendAvailable: envInfo.backendAvailable,
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// =============================================================================
|
|
832
|
+
// CONDITIONAL MCP INITIALIZATION
|
|
833
|
+
// =============================================================================
|
|
834
|
+
|
|
835
|
+
let mcp: MCPClient | null = null
|
|
836
|
+
if (config.vessels.metabob && envInfo.backendAvailable) {
|
|
837
|
+
console.log(`Initializing MCP client: ${mcpEndpoint}`)
|
|
838
|
+
mcp = await initializeMCP({ endpoint: mcpEndpoint!, apiKey: config.apiKey }, false)
|
|
839
|
+
|
|
840
|
+
if (mcp) {
|
|
841
|
+
// Register this vessel with the backend
|
|
842
|
+
const tempManifest = generateManifest(config, runtime)
|
|
843
|
+
await mcp.registerVessel({
|
|
844
|
+
id: tempManifest.id,
|
|
845
|
+
name: tempManifest.name,
|
|
846
|
+
version: tempManifest.version,
|
|
847
|
+
capabilities: tempManifest.capabilities,
|
|
848
|
+
endpoint: tempManifest.acpEndpoint,
|
|
849
|
+
})
|
|
850
|
+
console.log("✓ Registered with backend")
|
|
851
|
+
}
|
|
852
|
+
} else if (config.vessels.metabob && !envInfo.backendAvailable) {
|
|
853
|
+
console.log("Backend unavailable, running in local mode")
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// =============================================================================
|
|
857
|
+
// CONDITIONAL BOREDOM INITIALIZATION (Cluster Mode Only)
|
|
858
|
+
// =============================================================================
|
|
859
|
+
|
|
860
|
+
if (envInfo.clusterMode && mcp) {
|
|
861
|
+
console.log("\nInitializing boredom task executor (cluster mode)...")
|
|
862
|
+
const executorConfig: ExecutorConfig = {
|
|
863
|
+
provider: config.provider,
|
|
864
|
+
apiKey: config.apiKey,
|
|
865
|
+
model: config.model,
|
|
866
|
+
workingDirectory: config.workingDirectory,
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
initializeBoredom({
|
|
870
|
+
pollInterval: 30000, // Poll every 30 seconds
|
|
871
|
+
idleThreshold: 60000, // Consider idle after 60 seconds
|
|
872
|
+
// LLM config for goal-based execution via SearchFirstExecutor
|
|
873
|
+
llmConfig: {
|
|
874
|
+
provider: config.provider,
|
|
875
|
+
apiKey: config.apiKey,
|
|
876
|
+
model: config.model,
|
|
877
|
+
workingDirectory: config.workingDirectory,
|
|
878
|
+
},
|
|
879
|
+
// Template-based execution callback (legacy mode)
|
|
880
|
+
onExecuteTask: async (template, variables, reason) => {
|
|
881
|
+
// Create executor with MCP activity callbacks for boredom tasks
|
|
882
|
+
const boredomExecutorConfig = {
|
|
883
|
+
...executorConfig,
|
|
884
|
+
onSearchActivities: MCPActivityBridge.isAvailable() ? MCPActivityBridge.searchActivities : undefined,
|
|
885
|
+
onCreateActivity: MCPActivityBridge.isAvailable() ? MCPActivityBridge.createActivity : undefined,
|
|
886
|
+
}
|
|
887
|
+
const executor = new ActivityExecutor(boredomExecutorConfig)
|
|
888
|
+
return executor.execute({ template, variables, reason })
|
|
889
|
+
},
|
|
890
|
+
})
|
|
891
|
+
|
|
892
|
+
// Start boredom executor with cluster mode flag
|
|
893
|
+
startBoredom(envInfo.clusterMode)
|
|
894
|
+
runtime.boredomEnabled = true
|
|
895
|
+
console.log("✓ Boredom task executor started")
|
|
896
|
+
} else if (!envInfo.clusterMode) {
|
|
897
|
+
console.log("Boredom disabled: Not in cluster mode")
|
|
898
|
+
} else if (!mcp) {
|
|
899
|
+
console.log("Boredom disabled: Backend unavailable")
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// =============================================================================
|
|
903
|
+
// TODO: ACP GOSSIP (Cluster Mode Only)
|
|
904
|
+
// =============================================================================
|
|
905
|
+
|
|
906
|
+
if (envInfo.clusterMode) {
|
|
907
|
+
console.log("\n[TODO] ACP Gossip protocol would be initialized here")
|
|
908
|
+
console.log(" - DNS peer discovery")
|
|
909
|
+
console.log(" - Periodic health broadcasts")
|
|
910
|
+
runtime.acpGossipEnabled = true
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
// Generate final manifest with runtime capabilities
|
|
914
|
+
const manifest = generateManifest(config, runtime)
|
|
915
|
+
const acpConfig: ACPServerConfig = {
|
|
916
|
+
provider: config.provider,
|
|
917
|
+
apiKey: config.apiKey,
|
|
918
|
+
model: config.model,
|
|
919
|
+
workingDirectory: config.workingDirectory,
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// =============================================================================
|
|
923
|
+
// INSTANCE STATE TRACKING (for observe command)
|
|
924
|
+
// =============================================================================
|
|
925
|
+
const instanceId = generateInstanceId()
|
|
926
|
+
const startTime = Date.now()
|
|
927
|
+
let activitiesExecuted = 0
|
|
928
|
+
let totalCost = 0
|
|
929
|
+
let currentActivity: {
|
|
930
|
+
id: string
|
|
931
|
+
name?: string
|
|
932
|
+
task?: string
|
|
933
|
+
startedAt: number
|
|
934
|
+
} | null = null
|
|
935
|
+
|
|
936
|
+
// Register instance
|
|
937
|
+
await registerInstance({
|
|
938
|
+
id: instanceId,
|
|
939
|
+
pid: process.pid,
|
|
940
|
+
port: config.port,
|
|
941
|
+
host: config.host,
|
|
942
|
+
startTime,
|
|
943
|
+
workingDirectory: config.workingDirectory,
|
|
944
|
+
status: 'idle',
|
|
945
|
+
capabilities: manifest.capabilities,
|
|
946
|
+
})
|
|
947
|
+
console.log(`✓ Registered instance: ${instanceId}`)
|
|
948
|
+
|
|
949
|
+
const server = Bun.serve({
|
|
950
|
+
port: config.port,
|
|
951
|
+
hostname: config.host,
|
|
952
|
+
|
|
953
|
+
fetch(request) {
|
|
954
|
+
const url = new URL(request.url)
|
|
955
|
+
const path = url.pathname
|
|
956
|
+
|
|
957
|
+
// Health check
|
|
958
|
+
if (path === "/health") {
|
|
959
|
+
return new Response(JSON.stringify({ status: "ok", vessel: "minibob" }), {
|
|
960
|
+
headers: { "Content-Type": "application/json" },
|
|
961
|
+
})
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Status endpoint (for observe command)
|
|
965
|
+
if (path === "/status") {
|
|
966
|
+
const status = currentActivity ? 'executing' : (runtime.boredomEnabled ? 'boredom' : 'idle')
|
|
967
|
+
return new Response(JSON.stringify({
|
|
968
|
+
id: instanceId,
|
|
969
|
+
status,
|
|
970
|
+
currentActivity: currentActivity ? {
|
|
971
|
+
id: currentActivity.id,
|
|
972
|
+
name: currentActivity.name,
|
|
973
|
+
task: currentActivity.task,
|
|
974
|
+
progress: 0.5, // TODO: Track actual progress
|
|
975
|
+
} : undefined,
|
|
976
|
+
metrics: {
|
|
977
|
+
activitiesExecuted,
|
|
978
|
+
uptime: Date.now() - startTime,
|
|
979
|
+
totalCost,
|
|
980
|
+
},
|
|
981
|
+
config: {
|
|
982
|
+
workingDirectory: config.workingDirectory,
|
|
983
|
+
model: config.model,
|
|
984
|
+
port: config.port,
|
|
985
|
+
},
|
|
986
|
+
}), {
|
|
987
|
+
headers: { "Content-Type": "application/json" },
|
|
988
|
+
})
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
// Vessel manifest/config
|
|
992
|
+
if (path === "/config" || path === "/manifest") {
|
|
993
|
+
return new Response(JSON.stringify(manifest), {
|
|
994
|
+
headers: { "Content-Type": "application/json" },
|
|
995
|
+
})
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// ACP endpoint
|
|
999
|
+
if (path === "/acp" || path === "/acp/stream") {
|
|
1000
|
+
if (request.method === "POST") {
|
|
1001
|
+
return handleACPRequest(acpConfig, request)
|
|
1002
|
+
}
|
|
1003
|
+
// WebSocket upgrade would happen here
|
|
1004
|
+
return new Response("ACP endpoint - use POST or WebSocket", {
|
|
1005
|
+
status: 400,
|
|
1006
|
+
})
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Run activity endpoint
|
|
1010
|
+
if (path === "/run" && request.method === "POST") {
|
|
1011
|
+
markBoredomActivity() // Mark activity to prevent boredom tasks during user requests
|
|
1012
|
+
return handleRunActivity(request, config)
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
// List templates
|
|
1016
|
+
if (path === "/templates" && request.method === "GET") {
|
|
1017
|
+
return handleListTemplates(config.templatesDir)
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
// Goal-seeking endpoint
|
|
1021
|
+
if (path === "/goal" && request.method === "POST") {
|
|
1022
|
+
markBoredomActivity() // Mark activity to prevent boredom tasks during user requests
|
|
1023
|
+
return handleGoal(request, config)
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
// Search-first goal endpoint (experimental)
|
|
1027
|
+
if (path === "/goal/search-first" && request.method === "POST") {
|
|
1028
|
+
markBoredomActivity()
|
|
1029
|
+
return handleSearchFirstGoal(request, config)
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// 404
|
|
1033
|
+
return new Response(JSON.stringify({
|
|
1034
|
+
error: "Not found",
|
|
1035
|
+
endpoints: [
|
|
1036
|
+
"GET /health",
|
|
1037
|
+
"GET /status",
|
|
1038
|
+
"GET /config",
|
|
1039
|
+
"POST /acp",
|
|
1040
|
+
"POST /run",
|
|
1041
|
+
"POST /goal",
|
|
1042
|
+
"POST /goal/search-first",
|
|
1043
|
+
"GET /templates",
|
|
1044
|
+
],
|
|
1045
|
+
}), {
|
|
1046
|
+
status: 404,
|
|
1047
|
+
headers: { "Content-Type": "application/json" },
|
|
1048
|
+
})
|
|
1049
|
+
},
|
|
1050
|
+
})
|
|
1051
|
+
|
|
1052
|
+
console.log(`\nminibob server running at http://${config.host}:${config.port}`)
|
|
1053
|
+
console.log(`\nEndpoints:`)
|
|
1054
|
+
console.log(` GET /health - Health check`)
|
|
1055
|
+
console.log(` GET /status - Instance status (for observe)`)
|
|
1056
|
+
console.log(` GET /config - Vessel manifest`)
|
|
1057
|
+
console.log(` POST /acp - ACP protocol handler`)
|
|
1058
|
+
console.log(` POST /run - Run activity template`)
|
|
1059
|
+
console.log(` POST /goal - Goal-seeking execution`)
|
|
1060
|
+
console.log(` GET /templates - List available templates`)
|
|
1061
|
+
|
|
1062
|
+
// =============================================================================
|
|
1063
|
+
// HEARTBEAT TO BACKEND (for centralized observation)
|
|
1064
|
+
// =============================================================================
|
|
1065
|
+
let heartbeatInterval: ReturnType<typeof setInterval> | null = null
|
|
1066
|
+
|
|
1067
|
+
const sendHeartbeat = async () => {
|
|
1068
|
+
if (!envInfo.backendAvailable) return
|
|
1069
|
+
|
|
1070
|
+
try {
|
|
1071
|
+
const heartbeatUrl = `${mcpEndpoint}/v2/vessels/heartbeat`
|
|
1072
|
+
const status = currentActivity ? 'executing' : (runtime.boredomEnabled ? 'bored' : 'idle')
|
|
1073
|
+
|
|
1074
|
+
await fetch(heartbeatUrl, {
|
|
1075
|
+
method: 'POST',
|
|
1076
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1077
|
+
body: JSON.stringify({
|
|
1078
|
+
pod_name: instanceId,
|
|
1079
|
+
namespace: 'local', // Use 'local' for non-k8s environments
|
|
1080
|
+
status,
|
|
1081
|
+
current_activity: currentActivity ? {
|
|
1082
|
+
variant_id: currentActivity.id,
|
|
1083
|
+
activity_id: currentActivity.id,
|
|
1084
|
+
variant_name: currentActivity.name || currentActivity.id,
|
|
1085
|
+
started_at: new Date(currentActivity.startedAt).toISOString(),
|
|
1086
|
+
current_task: currentActivity.task,
|
|
1087
|
+
} : null,
|
|
1088
|
+
metrics: {
|
|
1089
|
+
executions_completed: activitiesExecuted,
|
|
1090
|
+
total_cost_usd: totalCost,
|
|
1091
|
+
uptime_seconds: Math.floor((Date.now() - startTime) / 1000),
|
|
1092
|
+
},
|
|
1093
|
+
}),
|
|
1094
|
+
signal: AbortSignal.timeout(5000),
|
|
1095
|
+
})
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
// Silently ignore heartbeat failures - backend might be temporarily unavailable
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Send initial heartbeat and start interval
|
|
1102
|
+
if (envInfo.backendAvailable) {
|
|
1103
|
+
sendHeartbeat()
|
|
1104
|
+
heartbeatInterval = setInterval(sendHeartbeat, 30000) // Every 30 seconds
|
|
1105
|
+
console.log("✓ Heartbeat reporting started (30s interval)")
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
// RELIABILITY: Graceful shutdown on SIGTERM/SIGINT
|
|
1109
|
+
let isShuttingDown = false
|
|
1110
|
+
const gracefulShutdown = async (signal: string) => {
|
|
1111
|
+
if (isShuttingDown) return
|
|
1112
|
+
isShuttingDown = true
|
|
1113
|
+
|
|
1114
|
+
console.log(`\n\nReceived ${signal}, starting graceful shutdown...`)
|
|
1115
|
+
|
|
1116
|
+
// Stop heartbeat
|
|
1117
|
+
if (heartbeatInterval) {
|
|
1118
|
+
clearInterval(heartbeatInterval)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// Deregister instance
|
|
1122
|
+
try {
|
|
1123
|
+
await deregisterInstance(instanceId)
|
|
1124
|
+
console.log("✓ Deregistered instance")
|
|
1125
|
+
} catch (error) {
|
|
1126
|
+
console.error("Error deregistering instance:", error)
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Stop accepting new requests
|
|
1130
|
+
try {
|
|
1131
|
+
server.stop()
|
|
1132
|
+
console.log("✓ Stopped accepting new requests")
|
|
1133
|
+
} catch (error) {
|
|
1134
|
+
console.error("Error stopping server:", error)
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// Give in-flight requests time to complete (5 seconds)
|
|
1138
|
+
await new Promise((resolve) => setTimeout(resolve, 5000))
|
|
1139
|
+
|
|
1140
|
+
console.log("✓ Graceful shutdown complete")
|
|
1141
|
+
process.exit(0)
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"))
|
|
1145
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"))
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
// =============================================================================
|
|
1149
|
+
// REQUEST HANDLERS
|
|
1150
|
+
// =============================================================================
|
|
1151
|
+
|
|
1152
|
+
async function handleRunActivity(request: Request, config: Awaited<ReturnType<typeof loadConfig>>) {
|
|
1153
|
+
try {
|
|
1154
|
+
// SECURITY: Validate request size to prevent DoS
|
|
1155
|
+
const contentLength = request.headers.get("Content-Length")
|
|
1156
|
+
validateRequestSize(contentLength, 10 * 1024 * 1024) // 10MB limit
|
|
1157
|
+
|
|
1158
|
+
const body = await request.json()
|
|
1159
|
+
|
|
1160
|
+
// SECURITY: Validate input schema
|
|
1161
|
+
const { template: templatePath, variables, reason } = validateRunActivityRequest(body)
|
|
1162
|
+
|
|
1163
|
+
const template = await loadTemplateFromMCPOrLocal(templatePath)
|
|
1164
|
+
const executorConfig: ExecutorConfig = {
|
|
1165
|
+
provider: config.provider,
|
|
1166
|
+
apiKey: config.apiKey,
|
|
1167
|
+
model: config.model,
|
|
1168
|
+
workingDirectory: config.workingDirectory,
|
|
1169
|
+
// Wire MCP activity callbacks for autonomous trailblazing
|
|
1170
|
+
onSearchActivities: MCPActivityBridge.isAvailable() ? MCPActivityBridge.searchActivities : undefined,
|
|
1171
|
+
onCreateActivity: MCPActivityBridge.isAvailable() ? MCPActivityBridge.createActivity : undefined,
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
const executor = new ActivityExecutor(executorConfig)
|
|
1175
|
+
const result = await executor.execute({
|
|
1176
|
+
template,
|
|
1177
|
+
variables,
|
|
1178
|
+
reason,
|
|
1179
|
+
})
|
|
1180
|
+
|
|
1181
|
+
return new Response(JSON.stringify(result), {
|
|
1182
|
+
headers: { "Content-Type": "application/json" },
|
|
1183
|
+
})
|
|
1184
|
+
} catch (error) {
|
|
1185
|
+
// Return appropriate status code for validation errors
|
|
1186
|
+
const isValidationError = error instanceof ValidationException
|
|
1187
|
+
return new Response(JSON.stringify({
|
|
1188
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1189
|
+
...(isValidationError && { validationErrors: (error as ValidationException).errors }),
|
|
1190
|
+
}), {
|
|
1191
|
+
status: isValidationError ? 400 : 500,
|
|
1192
|
+
headers: { "Content-Type": "application/json" },
|
|
1193
|
+
})
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
async function handleGoal(request: Request, config: Awaited<ReturnType<typeof loadConfig>>) {
|
|
1198
|
+
try {
|
|
1199
|
+
// SECURITY: Validate request size to prevent DoS
|
|
1200
|
+
const contentLength = request.headers.get("Content-Length")
|
|
1201
|
+
validateRequestSize(contentLength, 10 * 1024 * 1024) // 10MB limit
|
|
1202
|
+
|
|
1203
|
+
const body = await request.json() as Record<string, unknown>
|
|
1204
|
+
|
|
1205
|
+
// Parse goal request
|
|
1206
|
+
const goal = body.goal as string
|
|
1207
|
+
const context = (body.context as Record<string, unknown>) || {}
|
|
1208
|
+
const impulseRefs = (body.impulseRefs as string[]) || []
|
|
1209
|
+
const maxActivities = (body.maxActivities as number) || 5
|
|
1210
|
+
const maxCost = (body.maxCost as number) || 10.0
|
|
1211
|
+
|
|
1212
|
+
if (!goal || typeof goal !== 'string') {
|
|
1213
|
+
throw new ValidationException([
|
|
1214
|
+
{ field: 'goal', message: 'Must be a non-empty string' }
|
|
1215
|
+
])
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
console.log(`[Goal] Received: ${goal}`)
|
|
1219
|
+
console.log(`[Goal] Context:`, context)
|
|
1220
|
+
console.log(`[Goal] Impulse refs:`, impulseRefs)
|
|
1221
|
+
console.log(`[Goal] Max activities: ${maxActivities}, Max cost: $${maxCost}`)
|
|
1222
|
+
|
|
1223
|
+
// Create executor with MCP activity callbacks
|
|
1224
|
+
const executorConfig: ExecutorConfig = {
|
|
1225
|
+
provider: config.provider,
|
|
1226
|
+
apiKey: config.apiKey,
|
|
1227
|
+
model: config.model,
|
|
1228
|
+
workingDirectory: config.workingDirectory,
|
|
1229
|
+
onSearchActivities: MCPActivityBridge.isAvailable() ? MCPActivityBridge.searchActivities : undefined,
|
|
1230
|
+
onCreateActivity: MCPActivityBridge.isAvailable() ? MCPActivityBridge.createActivity : undefined,
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const executor = new ActivityExecutor(executorConfig)
|
|
1234
|
+
const goalProcessor = new GoalProcessor({
|
|
1235
|
+
workingDirectory: config.workingDirectory,
|
|
1236
|
+
executor,
|
|
1237
|
+
})
|
|
1238
|
+
|
|
1239
|
+
// Execute goal-seeking (GoalProcessor signature is just: message, context, options)
|
|
1240
|
+
const result = await goalProcessor.executeGoal(goal, context, {
|
|
1241
|
+
maxActivities,
|
|
1242
|
+
maxCost,
|
|
1243
|
+
})
|
|
1244
|
+
|
|
1245
|
+
return new Response(JSON.stringify(result), {
|
|
1246
|
+
headers: { "Content-Type": "application/json" },
|
|
1247
|
+
})
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
// Return appropriate status code for validation errors
|
|
1250
|
+
const isValidationError = error instanceof ValidationException
|
|
1251
|
+
return new Response(JSON.stringify({
|
|
1252
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1253
|
+
...(isValidationError && { validationErrors: (error as ValidationException).errors }),
|
|
1254
|
+
}), {
|
|
1255
|
+
status: isValidationError ? 400 : 500,
|
|
1256
|
+
headers: { "Content-Type": "application/json" },
|
|
1257
|
+
})
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
/**
|
|
1262
|
+
* Handle search-first goal execution
|
|
1263
|
+
* Uses step-by-step decomposition with activity reuse
|
|
1264
|
+
*/
|
|
1265
|
+
async function handleSearchFirstGoal(request: Request, config: Awaited<ReturnType<typeof loadConfig>>) {
|
|
1266
|
+
try {
|
|
1267
|
+
const contentLength = request.headers.get("Content-Length")
|
|
1268
|
+
validateRequestSize(contentLength, 10 * 1024 * 1024)
|
|
1269
|
+
|
|
1270
|
+
const body = await request.json() as Record<string, unknown>
|
|
1271
|
+
|
|
1272
|
+
const goal = body.goal as string
|
|
1273
|
+
const context = (body.context as Record<string, unknown>) || {}
|
|
1274
|
+
|
|
1275
|
+
if (!goal || typeof goal !== 'string') {
|
|
1276
|
+
throw new ValidationException([
|
|
1277
|
+
{ field: 'goal', message: 'Must be a non-empty string' }
|
|
1278
|
+
])
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
console.log(`[SearchFirst] Received goal: ${goal}`)
|
|
1282
|
+
|
|
1283
|
+
const executor = new SearchFirstExecutor({
|
|
1284
|
+
provider: config.provider,
|
|
1285
|
+
apiKey: config.apiKey,
|
|
1286
|
+
model: config.model,
|
|
1287
|
+
workingDirectory: config.workingDirectory,
|
|
1288
|
+
maxSteps: 5,
|
|
1289
|
+
maxTokensPerStep: 4096,
|
|
1290
|
+
})
|
|
1291
|
+
|
|
1292
|
+
const result = await executor.execute(goal, context)
|
|
1293
|
+
|
|
1294
|
+
return new Response(JSON.stringify(result), {
|
|
1295
|
+
headers: { "Content-Type": "application/json" },
|
|
1296
|
+
})
|
|
1297
|
+
} catch (error) {
|
|
1298
|
+
const isValidationError = error instanceof ValidationException
|
|
1299
|
+
return new Response(JSON.stringify({
|
|
1300
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1301
|
+
...(isValidationError && { validationErrors: (error as ValidationException).errors }),
|
|
1302
|
+
}), {
|
|
1303
|
+
status: isValidationError ? 400 : 500,
|
|
1304
|
+
headers: { "Content-Type": "application/json" },
|
|
1305
|
+
})
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
async function handleListTemplates(templatesDir: string) {
|
|
1310
|
+
try {
|
|
1311
|
+
const glob = new Bun.Glob("**/*.json")
|
|
1312
|
+
const templates: string[] = []
|
|
1313
|
+
|
|
1314
|
+
for await (const file of glob.scan({ cwd: templatesDir })) {
|
|
1315
|
+
templates.push(file)
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
return new Response(JSON.stringify({ templates }), {
|
|
1319
|
+
headers: { "Content-Type": "application/json" },
|
|
1320
|
+
})
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
return new Response(JSON.stringify({
|
|
1323
|
+
templates: [],
|
|
1324
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1325
|
+
}), {
|
|
1326
|
+
headers: { "Content-Type": "application/json" },
|
|
1327
|
+
})
|
|
1328
|
+
}
|
|
1329
|
+
}
|