@metabob/minibob 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +255 -0
- package/CHANGELOG.md +112 -0
- package/README.md +380 -0
- package/bin/minibob.js +36 -0
- package/dist/acp-gossip.d.ts +72 -0
- package/dist/acp-gossip.d.ts.map +1 -0
- package/dist/acp-gossip.js +156 -0
- package/dist/acp-gossip.js.map +1 -0
- package/dist/acp.d.ts +62 -0
- package/dist/acp.d.ts.map +1 -0
- package/dist/acp.js +292 -0
- package/dist/acp.js.map +1 -0
- package/dist/activity.d.ts +157 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +518 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-runtime.d.ts +104 -0
- package/dist/agent-runtime.d.ts.map +1 -0
- package/dist/boredom.d.ts +125 -0
- package/dist/boredom.d.ts.map +1 -0
- package/dist/boredom.js +244 -0
- package/dist/boredom.js.map +1 -0
- package/dist/cli/acp-server.d.ts +23 -0
- package/dist/cli/acp-server.d.ts.map +1 -0
- package/dist/cli/burrow.d.ts +26 -0
- package/dist/cli/burrow.d.ts.map +1 -0
- package/dist/cli/doctor.d.ts +22 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/goal.d.ts +22 -0
- package/dist/cli/goal.d.ts.map +1 -0
- package/dist/cli/index.d.ts +47 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/instance-registry.d.ts +78 -0
- package/dist/cli/instance-registry.d.ts.map +1 -0
- package/dist/cli/observe.d.ts +35 -0
- package/dist/cli/observe.d.ts.map +1 -0
- package/dist/cli/vessel.d.ts +14 -0
- package/dist/cli/vessel.d.ts.map +1 -0
- package/dist/composition-observer.d.ts +96 -0
- package/dist/composition-observer.d.ts.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +128 -0
- package/dist/config.js.map +1 -0
- package/dist/docker/Dockerfile +35 -0
- package/dist/environment.d.ts +72 -0
- package/dist/environment.d.ts.map +1 -0
- package/dist/environment.js +142 -0
- package/dist/environment.js.map +1 -0
- package/dist/goal-processor.d.ts +165 -0
- package/dist/goal-processor.d.ts.map +1 -0
- package/dist/helm/minibob-cluster/Chart.yaml +13 -0
- package/dist/helm/minibob-cluster/templates/_helpers.tpl +60 -0
- package/dist/helm/minibob-cluster/templates/configmap.yaml +11 -0
- package/dist/helm/minibob-cluster/templates/deployment.yaml +108 -0
- package/dist/helm/minibob-cluster/templates/secret.yaml +10 -0
- package/dist/helm/minibob-cluster/templates/service.yaml +37 -0
- package/dist/helm/minibob-cluster/values-local.yaml +41 -0
- package/dist/helm/minibob-cluster/values-production.yaml +57 -0
- package/dist/helm/minibob-cluster/values-testing-cluster.yaml +43 -0
- package/dist/helm/minibob-cluster/values.yaml +127 -0
- package/dist/improviser.d.ts +74 -0
- package/dist/improviser.d.ts.map +1 -0
- package/dist/impulse-filter.d.ts +74 -0
- package/dist/impulse-filter.d.ts.map +1 -0
- package/dist/impulse.d.ts +92 -0
- package/dist/impulse.d.ts.map +1 -0
- package/dist/impulse.js +234 -0
- package/dist/impulse.js.map +1 -0
- package/dist/lib.d.ts +29 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +18561 -0
- package/dist/lib.js.map +98 -0
- package/dist/lifecycle-hooks.d.ts +99 -0
- package/dist/lifecycle-hooks.d.ts.map +1 -0
- package/dist/lifecycle-hooks.js +135 -0
- package/dist/lifecycle-hooks.js.map +1 -0
- package/dist/llm.d.ts +31 -0
- package/dist/llm.d.ts.map +1 -0
- package/dist/llm.js +349 -0
- package/dist/llm.js.map +1 -0
- package/dist/mcp-activity-bridge.d.ts +66 -0
- package/dist/mcp-activity-bridge.d.ts.map +1 -0
- package/dist/mcp-activity-bridge.js +126 -0
- package/dist/mcp-activity-bridge.js.map +1 -0
- package/dist/mcp.d.ts +216 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +292 -0
- package/dist/mcp.js.map +1 -0
- package/dist/memory-agent.d.ts +92 -0
- package/dist/memory-agent.d.ts.map +1 -0
- package/dist/memory-agent.js +277 -0
- package/dist/memory-agent.js.map +1 -0
- package/dist/runtime-mapping.d.ts +97 -0
- package/dist/runtime-mapping.d.ts.map +1 -0
- package/dist/search-first-executor.d.ts +113 -0
- package/dist/search-first-executor.d.ts.map +1 -0
- package/dist/session.d.ts +48 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/template-extractor.d.ts +9 -0
- package/dist/template-extractor.d.ts.map +1 -0
- package/dist/template-generator.d.ts +12 -0
- package/dist/template-generator.d.ts.map +1 -0
- package/dist/tools.d.ts +58 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +771 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +503 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/understanding/analyzer.d.ts +55 -0
- package/dist/understanding/analyzer.d.ts.map +1 -0
- package/dist/understanding/explorer.d.ts +73 -0
- package/dist/understanding/explorer.d.ts.map +1 -0
- package/dist/understanding/index.d.ts +7 -0
- package/dist/understanding/index.d.ts.map +1 -0
- package/dist/understanding/types.d.ts +136 -0
- package/dist/understanding/types.d.ts.map +1 -0
- package/dist/validation.d.ts +29 -0
- package/dist/validation.d.ts.map +1 -0
- package/dist/validation.js +106 -0
- package/dist/validation.js.map +1 -0
- package/dist/vessel-bootstrap.d.ts +190 -0
- package/dist/vessel-bootstrap.d.ts.map +1 -0
- package/dist/vessel-registry.d.ts +229 -0
- package/dist/vessel-registry.d.ts.map +1 -0
- package/index.ts +1329 -0
- package/package.json +54 -0
- package/src/acp-gossip.ts +193 -0
- package/src/acp.ts +362 -0
- package/src/activity.ts +1464 -0
- package/src/agent-runtime.ts +365 -0
- package/src/boredom.ts +423 -0
- package/src/cli/acp-server.ts +377 -0
- package/src/cli/burrow.ts +896 -0
- package/src/cli/doctor.ts +526 -0
- package/src/cli/goal.ts +224 -0
- package/src/cli/index.ts +147 -0
- package/src/cli/instance-registry.ts +271 -0
- package/src/cli/observe.ts +682 -0
- package/src/cli/vessel.ts +287 -0
- package/src/components/SystemOverview.tsx +331 -0
- package/src/composition-observer.ts +449 -0
- package/src/config.ts +172 -0
- package/src/environment.ts +167 -0
- package/src/goal-processor.ts +654 -0
- package/src/improviser.ts +591 -0
- package/src/impulse-filter.ts +273 -0
- package/src/impulse.ts +311 -0
- package/src/lib.ts +147 -0
- package/src/lifecycle-hooks.ts +181 -0
- package/src/llm.ts +434 -0
- package/src/mcp-activity-bridge.ts +158 -0
- package/src/mcp.ts +747 -0
- package/src/memory-agent.ts +316 -0
- package/src/runtime-mapping.ts +527 -0
- package/src/search-first-executor.ts +666 -0
- package/src/session.ts +141 -0
- package/src/template-extractor.ts +256 -0
- package/src/template-generator.ts +130 -0
- package/src/tools.ts +924 -0
- package/src/types.ts +497 -0
- package/src/understanding/analyzer.ts +354 -0
- package/src/understanding/explorer.ts +488 -0
- package/src/understanding/index.ts +27 -0
- package/src/understanding/types.ts +153 -0
- package/src/validation.ts +125 -0
- package/src/vessel-bootstrap.ts +440 -0
- package/src/vessel-registry.ts +621 -0
- package/templates/core/edit-file.json +85 -0
- package/templates/understanding/diagnose-problem.json +32 -0
- package/templates/understanding/explore-codebase-v2.json +57 -0
- package/templates/understanding/explore-codebase.json +37 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Runtime - Reusable backend integration for CLI and server modes
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Backend registration/deregistration
|
|
6
|
+
* - Heartbeat reporting
|
|
7
|
+
* - Execution metrics tracking
|
|
8
|
+
* - Graceful shutdown
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* const runtime = await AgentRuntime.initialize(config)
|
|
12
|
+
* // ... do work ...
|
|
13
|
+
* await runtime.reportExecution(execution)
|
|
14
|
+
* await runtime.shutdown()
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { MinibobConfig, ActivityExecution, VesselManifest } from "./types"
|
|
18
|
+
import type { MCPClient } from "./mcp"
|
|
19
|
+
import { initializeMCP, getMCPClient, isMCPEnabled } from "./mcp"
|
|
20
|
+
import { generateManifest, type RuntimeContext } from "./config"
|
|
21
|
+
import { detectCompleteEnvironment } from "./environment"
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// TYPES
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
export interface AgentRuntimeConfig {
|
|
28
|
+
config: MinibobConfig
|
|
29
|
+
mode: "cli" | "server"
|
|
30
|
+
instanceId?: string
|
|
31
|
+
enableHeartbeat?: boolean
|
|
32
|
+
heartbeatInterval?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AgentMetrics {
|
|
36
|
+
executionsCompleted: number
|
|
37
|
+
executionsFailed: number
|
|
38
|
+
totalCost: number
|
|
39
|
+
totalTokens: {
|
|
40
|
+
input: number
|
|
41
|
+
output: number
|
|
42
|
+
}
|
|
43
|
+
uptime: number
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface CurrentActivity {
|
|
47
|
+
id: string
|
|
48
|
+
name?: string
|
|
49
|
+
task?: string
|
|
50
|
+
startedAt: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// =============================================================================
|
|
54
|
+
// AGENT RUNTIME
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
export class AgentRuntime {
|
|
58
|
+
private config: MinibobConfig
|
|
59
|
+
private mode: "cli" | "server"
|
|
60
|
+
private instanceId: string
|
|
61
|
+
private startTime: number
|
|
62
|
+
private mcp: MCPClient | null = null
|
|
63
|
+
private manifest: VesselManifest | null = null
|
|
64
|
+
|
|
65
|
+
private heartbeatInterval: NodeJS.Timeout | null = null
|
|
66
|
+
private heartbeatIntervalMs: number
|
|
67
|
+
private enableHeartbeat: boolean
|
|
68
|
+
|
|
69
|
+
private metrics: AgentMetrics = {
|
|
70
|
+
executionsCompleted: 0,
|
|
71
|
+
executionsFailed: 0,
|
|
72
|
+
totalCost: 0,
|
|
73
|
+
totalTokens: { input: 0, output: 0 },
|
|
74
|
+
uptime: 0
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private currentActivity: CurrentActivity | null = null
|
|
78
|
+
private shuttingDown = false
|
|
79
|
+
|
|
80
|
+
private constructor(options: AgentRuntimeConfig) {
|
|
81
|
+
this.config = options.config
|
|
82
|
+
this.mode = options.mode
|
|
83
|
+
this.instanceId = options.instanceId || this.generateInstanceId()
|
|
84
|
+
this.startTime = Date.now()
|
|
85
|
+
this.enableHeartbeat = options.enableHeartbeat ?? (options.mode === "server")
|
|
86
|
+
this.heartbeatIntervalMs = options.heartbeatInterval ?? 30000
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Initialize agent runtime with backend connection
|
|
91
|
+
*/
|
|
92
|
+
static async initialize(options: AgentRuntimeConfig): Promise<AgentRuntime> {
|
|
93
|
+
const runtime = new AgentRuntime(options)
|
|
94
|
+
|
|
95
|
+
console.log(`[AgentRuntime] Initializing in ${options.mode} mode (instance: ${runtime.instanceId})`)
|
|
96
|
+
|
|
97
|
+
// Detect environment
|
|
98
|
+
const mcpEndpoint = runtime.config.vessels.metabob?.endpoint
|
|
99
|
+
const envInfo = await detectCompleteEnvironment(mcpEndpoint)
|
|
100
|
+
|
|
101
|
+
console.log(`[AgentRuntime] Environment: ${envInfo.environment}`)
|
|
102
|
+
console.log(`[AgentRuntime] Backend Available: ${envInfo.backendAvailable}`)
|
|
103
|
+
|
|
104
|
+
// Initialize MCP if backend available
|
|
105
|
+
if (runtime.config.vessels.metabob && envInfo.backendAvailable) {
|
|
106
|
+
console.log(`[AgentRuntime] Connecting to backend: ${mcpEndpoint}`)
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
runtime.mcp = await initializeMCP(
|
|
110
|
+
{
|
|
111
|
+
endpoint: mcpEndpoint!,
|
|
112
|
+
apiKey: runtime.config.apiKey
|
|
113
|
+
},
|
|
114
|
+
options.mode === "cli" // Skip health check in CLI mode for faster startup
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Generate manifest
|
|
118
|
+
const runtimeContext: RuntimeContext = {
|
|
119
|
+
environment: envInfo.environment,
|
|
120
|
+
clusterMode: envInfo.clusterMode,
|
|
121
|
+
peerCount: envInfo.peerCount,
|
|
122
|
+
boredomEnabled: false, // CLI mode doesn't do boredom
|
|
123
|
+
acpGossipEnabled: false,
|
|
124
|
+
backendAvailable: true,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
runtime.manifest = generateManifest(runtime.config, runtimeContext)
|
|
128
|
+
|
|
129
|
+
// Register with backend
|
|
130
|
+
if (runtime.mcp) {
|
|
131
|
+
await runtime.mcp.registerVessel({
|
|
132
|
+
id: runtime.manifest.id,
|
|
133
|
+
name: runtime.manifest.name,
|
|
134
|
+
version: runtime.manifest.version,
|
|
135
|
+
capabilities: runtime.manifest.capabilities,
|
|
136
|
+
endpoint: runtime.manifest.acpEndpoint,
|
|
137
|
+
})
|
|
138
|
+
console.log("[AgentRuntime] ✓ Registered with backend")
|
|
139
|
+
|
|
140
|
+
// Start heartbeat if enabled
|
|
141
|
+
if (runtime.enableHeartbeat) {
|
|
142
|
+
runtime.startHeartbeat()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.warn("[AgentRuntime] Failed to connect to backend:", error instanceof Error ? error.message : String(error))
|
|
147
|
+
console.warn("[AgentRuntime] Continuing in standalone mode")
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.log("[AgentRuntime] Running in standalone mode (no backend)")
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Setup graceful shutdown handlers
|
|
154
|
+
runtime.setupShutdownHandlers()
|
|
155
|
+
|
|
156
|
+
return runtime
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Start heartbeat reporting
|
|
161
|
+
*/
|
|
162
|
+
private startHeartbeat(): void {
|
|
163
|
+
if (!this.mcp || !this.manifest) return
|
|
164
|
+
|
|
165
|
+
const sendHeartbeat = async () => {
|
|
166
|
+
if (!this.mcp || this.shuttingDown) return
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const endpoint = this.config.vessels.metabob?.endpoint
|
|
170
|
+
if (!endpoint) return
|
|
171
|
+
|
|
172
|
+
const status = this.currentActivity ? 'executing' : 'idle'
|
|
173
|
+
const heartbeatUrl = `${endpoint}/v2/vessels/heartbeat`
|
|
174
|
+
|
|
175
|
+
await fetch(heartbeatUrl, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
body: JSON.stringify({
|
|
179
|
+
pod_name: this.instanceId,
|
|
180
|
+
namespace: this.mode === 'cli' ? 'cli' : 'local',
|
|
181
|
+
status,
|
|
182
|
+
current_activity: this.currentActivity ? {
|
|
183
|
+
variant_id: this.currentActivity.id,
|
|
184
|
+
activity_id: this.currentActivity.id,
|
|
185
|
+
variant_name: this.currentActivity.name || this.currentActivity.id,
|
|
186
|
+
started_at: new Date(this.currentActivity.startedAt).toISOString(),
|
|
187
|
+
current_task: this.currentActivity.task,
|
|
188
|
+
} : null,
|
|
189
|
+
metrics: {
|
|
190
|
+
executions_completed: this.metrics.executionsCompleted,
|
|
191
|
+
total_cost_usd: this.metrics.totalCost,
|
|
192
|
+
uptime_seconds: Math.floor((Date.now() - this.startTime) / 1000),
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
195
|
+
signal: AbortSignal.timeout(5000),
|
|
196
|
+
})
|
|
197
|
+
} catch (error) {
|
|
198
|
+
// Silently ignore heartbeat failures
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Send initial heartbeat
|
|
203
|
+
sendHeartbeat()
|
|
204
|
+
|
|
205
|
+
// Start interval
|
|
206
|
+
this.heartbeatInterval = setInterval(sendHeartbeat, this.heartbeatIntervalMs)
|
|
207
|
+
console.log(`[AgentRuntime] ✓ Heartbeat started (${this.heartbeatIntervalMs}ms interval)`)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Mark start of activity execution
|
|
212
|
+
*/
|
|
213
|
+
setCurrentActivity(activity: CurrentActivity): void {
|
|
214
|
+
this.currentActivity = activity
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Clear current activity
|
|
219
|
+
*/
|
|
220
|
+
clearCurrentActivity(): void {
|
|
221
|
+
this.currentActivity = null
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Report execution to backend
|
|
226
|
+
*/
|
|
227
|
+
async reportExecution(execution: ActivityExecution): Promise<void> {
|
|
228
|
+
// Update metrics
|
|
229
|
+
if (execution.status === "completed") {
|
|
230
|
+
this.metrics.executionsCompleted++
|
|
231
|
+
} else if (execution.status === "failed") {
|
|
232
|
+
this.metrics.executionsFailed++
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (execution.metrics) {
|
|
236
|
+
this.metrics.totalCost += execution.metrics.cost
|
|
237
|
+
this.metrics.totalTokens.input += execution.metrics.totalTokens.input
|
|
238
|
+
this.metrics.totalTokens.output += execution.metrics.totalTokens.output
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Store execution trace in backend if available
|
|
242
|
+
if (this.mcp) {
|
|
243
|
+
try {
|
|
244
|
+
await this.mcp.storeExecutionTrace(execution)
|
|
245
|
+
console.log(`[AgentRuntime] ✓ Execution trace stored: ${execution.id}`)
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.warn("[AgentRuntime] Failed to store execution trace:", error instanceof Error ? error.message : String(error))
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get current metrics
|
|
254
|
+
*/
|
|
255
|
+
getMetrics(): AgentMetrics {
|
|
256
|
+
return {
|
|
257
|
+
...this.metrics,
|
|
258
|
+
uptime: Date.now() - this.startTime
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Get instance ID
|
|
264
|
+
*/
|
|
265
|
+
getInstanceId(): string {
|
|
266
|
+
return this.instanceId
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Check if backend is available
|
|
271
|
+
*/
|
|
272
|
+
hasBackend(): boolean {
|
|
273
|
+
return this.mcp !== null
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Get MCP client (if available)
|
|
278
|
+
*/
|
|
279
|
+
getMCP(): MCPClient | null {
|
|
280
|
+
return this.mcp
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Setup graceful shutdown handlers
|
|
285
|
+
*/
|
|
286
|
+
private setupShutdownHandlers(): void {
|
|
287
|
+
const gracefulShutdown = async (signal: string) => {
|
|
288
|
+
if (this.shuttingDown) return
|
|
289
|
+
this.shuttingDown = true
|
|
290
|
+
|
|
291
|
+
console.log(`\n[AgentRuntime] Received ${signal}, shutting down gracefully...`)
|
|
292
|
+
await this.shutdown()
|
|
293
|
+
process.exit(0)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"))
|
|
297
|
+
process.on("SIGINT", () => gracefulShutdown("SIGINT"))
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Shutdown runtime and cleanup
|
|
302
|
+
*/
|
|
303
|
+
async shutdown(): Promise<void> {
|
|
304
|
+
if (this.shuttingDown && !this.heartbeatInterval) {
|
|
305
|
+
return // Already shut down
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
console.log("[AgentRuntime] Shutting down...")
|
|
309
|
+
|
|
310
|
+
// Stop heartbeat
|
|
311
|
+
if (this.heartbeatInterval) {
|
|
312
|
+
clearInterval(this.heartbeatInterval)
|
|
313
|
+
this.heartbeatInterval = null
|
|
314
|
+
console.log("[AgentRuntime] ✓ Heartbeat stopped")
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Deregister from backend
|
|
318
|
+
if (this.mcp && this.manifest) {
|
|
319
|
+
try {
|
|
320
|
+
// Send final heartbeat with shutdown status
|
|
321
|
+
const endpoint = this.config.vessels.metabob?.endpoint
|
|
322
|
+
if (endpoint) {
|
|
323
|
+
await fetch(`${endpoint}/v2/vessels/heartbeat`, {
|
|
324
|
+
method: 'POST',
|
|
325
|
+
headers: { 'Content-Type': 'application/json' },
|
|
326
|
+
body: JSON.stringify({
|
|
327
|
+
pod_name: this.instanceId,
|
|
328
|
+
namespace: this.mode === 'cli' ? 'cli' : 'local',
|
|
329
|
+
status: 'shutdown',
|
|
330
|
+
metrics: {
|
|
331
|
+
executions_completed: this.metrics.executionsCompleted,
|
|
332
|
+
total_cost_usd: this.metrics.totalCost,
|
|
333
|
+
uptime_seconds: Math.floor((Date.now() - this.startTime) / 1000),
|
|
334
|
+
},
|
|
335
|
+
}),
|
|
336
|
+
signal: AbortSignal.timeout(2000),
|
|
337
|
+
})
|
|
338
|
+
}
|
|
339
|
+
console.log("[AgentRuntime] ✓ Deregistered from backend")
|
|
340
|
+
} catch (error) {
|
|
341
|
+
// Ignore errors during shutdown
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Print final metrics
|
|
346
|
+
const metrics = this.getMetrics()
|
|
347
|
+
console.log("\n[AgentRuntime] Final Metrics:")
|
|
348
|
+
console.log(` Executions: ${metrics.executionsCompleted} completed, ${metrics.executionsFailed} failed`)
|
|
349
|
+
console.log(` Cost: $${metrics.totalCost.toFixed(4)}`)
|
|
350
|
+
console.log(` Tokens: ${metrics.totalTokens.input} in / ${metrics.totalTokens.output} out`)
|
|
351
|
+
console.log(` Uptime: ${Math.floor(metrics.uptime / 1000)}s`)
|
|
352
|
+
|
|
353
|
+
console.log("[AgentRuntime] ✓ Shutdown complete")
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Generate unique instance ID
|
|
358
|
+
*/
|
|
359
|
+
private generateInstanceId(): string {
|
|
360
|
+
const hostname = process.env.HOSTNAME || "local"
|
|
361
|
+
const timestamp = Date.now()
|
|
362
|
+
const random = Math.random().toString(36).substring(7)
|
|
363
|
+
return `minibob-${hostname}-${timestamp}-${random}`
|
|
364
|
+
}
|
|
365
|
+
}
|