@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,287 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vessel Command
|
|
3
|
+
*
|
|
4
|
+
* Manage the vessel registry - list, inspect, and manage burrowed codebases.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { parseArgs, formatHelp, printSuccess, printWarning, printInfo, exitWithError } from './index'
|
|
8
|
+
import {
|
|
9
|
+
listVessels,
|
|
10
|
+
getVessel,
|
|
11
|
+
getVesselByPath,
|
|
12
|
+
deregisterVessel,
|
|
13
|
+
type Vessel,
|
|
14
|
+
type VesselIdentity,
|
|
15
|
+
} from '../vessel-registry'
|
|
16
|
+
import { resolve } from 'path'
|
|
17
|
+
|
|
18
|
+
export interface VesselOptions {
|
|
19
|
+
json?: boolean
|
|
20
|
+
verbose?: boolean
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Run vessel command
|
|
25
|
+
*/
|
|
26
|
+
export async function vessel(args: string[]): Promise<void> {
|
|
27
|
+
const { flags, positional } = parseArgs(args)
|
|
28
|
+
|
|
29
|
+
const subcommand = positional[0]
|
|
30
|
+
const options: VesselOptions = {
|
|
31
|
+
json: flags.json,
|
|
32
|
+
verbose: flags.verbose || flags.v,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Show help
|
|
36
|
+
if (flags.help || flags.h || !subcommand) {
|
|
37
|
+
console.log(formatHelp('vessel', 'Manage the vessel registry', [
|
|
38
|
+
{ flag: 'list', description: 'List all registered vessels' },
|
|
39
|
+
{ flag: 'info [path]', description: 'Show vessel info (current dir if no path)' },
|
|
40
|
+
{ flag: 'remove <id|path>', description: 'Remove a vessel from the registry' },
|
|
41
|
+
{ flag: '--json', description: 'Output in JSON format' },
|
|
42
|
+
{ flag: '--verbose, -v', description: 'Show detailed information' },
|
|
43
|
+
{ flag: '--help, -h', description: 'Show this help message' },
|
|
44
|
+
]))
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
switch (subcommand) {
|
|
49
|
+
case 'list':
|
|
50
|
+
case 'ls':
|
|
51
|
+
await listVesselsCommand(options)
|
|
52
|
+
break
|
|
53
|
+
|
|
54
|
+
case 'info':
|
|
55
|
+
case 'show':
|
|
56
|
+
const infoPath = positional[1] || '.'
|
|
57
|
+
await showVesselInfo(infoPath, options)
|
|
58
|
+
break
|
|
59
|
+
|
|
60
|
+
case 'remove':
|
|
61
|
+
case 'rm':
|
|
62
|
+
case 'deregister':
|
|
63
|
+
const removeTarget = positional[1]
|
|
64
|
+
if (!removeTarget) {
|
|
65
|
+
exitWithError('Please specify a vessel ID or path to remove')
|
|
66
|
+
}
|
|
67
|
+
await removeVessel(removeTarget, options)
|
|
68
|
+
break
|
|
69
|
+
|
|
70
|
+
default:
|
|
71
|
+
exitWithError(`Unknown subcommand: ${subcommand}`)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* List all registered vessels
|
|
77
|
+
*/
|
|
78
|
+
async function listVesselsCommand(options: VesselOptions): Promise<void> {
|
|
79
|
+
const vessels = listVessels()
|
|
80
|
+
|
|
81
|
+
if (vessels.length === 0) {
|
|
82
|
+
if (options.json) {
|
|
83
|
+
console.log(JSON.stringify({ vessels: [] }, null, 2))
|
|
84
|
+
} else {
|
|
85
|
+
printInfo('No vessels registered. Use "minibob burrow <path>" to register a codebase.')
|
|
86
|
+
}
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (options.json) {
|
|
91
|
+
console.log(JSON.stringify({ vessels }, null, 2))
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
console.log('\n=== Registered Vessels ===\n')
|
|
96
|
+
|
|
97
|
+
for (const v of vessels) {
|
|
98
|
+
const lastActive = new Date(v.lastActiveAt).toLocaleDateString()
|
|
99
|
+
console.log(` ${v.name}`)
|
|
100
|
+
console.log(` ID: ${v.id}`)
|
|
101
|
+
console.log(` Path: ${v.path}`)
|
|
102
|
+
console.log(` Last Active: ${lastActive}`)
|
|
103
|
+
if (v.description) {
|
|
104
|
+
console.log(` Description: ${v.description}`)
|
|
105
|
+
}
|
|
106
|
+
console.log('')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(`Total: ${vessels.length} vessel(s)\n`)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Show detailed vessel information
|
|
114
|
+
*/
|
|
115
|
+
async function showVesselInfo(pathOrId: string, options: VesselOptions): Promise<void> {
|
|
116
|
+
// Try to find by path first
|
|
117
|
+
let vesselData: Vessel | null = null
|
|
118
|
+
|
|
119
|
+
// Check if it looks like an ID
|
|
120
|
+
if (pathOrId.startsWith('vessel_')) {
|
|
121
|
+
vesselData = getVessel(pathOrId)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Try by path
|
|
125
|
+
if (!vesselData) {
|
|
126
|
+
const absPath = resolve(pathOrId)
|
|
127
|
+
vesselData = getVesselByPath(absPath)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (!vesselData) {
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(JSON.stringify({ error: 'Vessel not found' }, null, 2))
|
|
133
|
+
} else {
|
|
134
|
+
exitWithError(`Vessel not found at: ${pathOrId}\nRun "minibob burrow ${pathOrId}" to register it.`)
|
|
135
|
+
}
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (options.json) {
|
|
140
|
+
// Serialize Maps to objects for JSON output
|
|
141
|
+
const jsonSafe = {
|
|
142
|
+
...vesselData,
|
|
143
|
+
mapping: {
|
|
144
|
+
functionMap: Object.fromEntries(vesselData.mapping.functionMap),
|
|
145
|
+
intentMap: Object.fromEntries(vesselData.mapping.intentMap),
|
|
146
|
+
patternMap: Object.fromEntries(vesselData.mapping.patternMap),
|
|
147
|
+
},
|
|
148
|
+
}
|
|
149
|
+
console.log(JSON.stringify(jsonSafe, null, 2))
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
console.log('\n=== Vessel Information ===\n')
|
|
154
|
+
console.log(`Name: ${vesselData.name}`)
|
|
155
|
+
console.log(`ID: ${vesselData.id}`)
|
|
156
|
+
console.log(`Path: ${vesselData.path}`)
|
|
157
|
+
if (vesselData.version) {
|
|
158
|
+
console.log(`Version: ${vesselData.version}`)
|
|
159
|
+
}
|
|
160
|
+
if (vesselData.description) {
|
|
161
|
+
console.log(`Description: ${vesselData.description}`)
|
|
162
|
+
}
|
|
163
|
+
console.log(`Created: ${new Date(vesselData.createdAt).toLocaleString()}`)
|
|
164
|
+
console.log(`Last Active: ${new Date(vesselData.lastActiveAt).toLocaleString()}`)
|
|
165
|
+
|
|
166
|
+
// Activities
|
|
167
|
+
console.log('\n--- Activities ---')
|
|
168
|
+
if (vesselData.activities.length === 0) {
|
|
169
|
+
console.log(' No activities defined')
|
|
170
|
+
} else {
|
|
171
|
+
for (const activity of vesselData.activities) {
|
|
172
|
+
const successPct = activity.successRate > 0 ? `${(activity.successRate * 100).toFixed(0)}%` : 'N/A'
|
|
173
|
+
console.log(` ${activity.name} (${activity.id})`)
|
|
174
|
+
console.log(` Origin: ${activity.origin}, Executions: ${activity.executions}, Success: ${successPct}`)
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Impulses
|
|
179
|
+
if (vesselData.impulses.length > 0) {
|
|
180
|
+
console.log('\n--- Impulses ---')
|
|
181
|
+
for (const impulse of vesselData.impulses) {
|
|
182
|
+
console.log(` ${impulse.id} (${impulse.type})`)
|
|
183
|
+
if (impulse.description) {
|
|
184
|
+
console.log(` ${impulse.description}`)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Hooks
|
|
190
|
+
if (vesselData.hooks.length > 0) {
|
|
191
|
+
console.log('\n--- Hooks ---')
|
|
192
|
+
for (const hook of vesselData.hooks) {
|
|
193
|
+
const status = hook.enabled ? 'enabled' : 'disabled'
|
|
194
|
+
console.log(` ${hook.id} (${hook.type}) - ${status}`)
|
|
195
|
+
console.log(` Handler: ${hook.handler}`)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Tools
|
|
200
|
+
if (vesselData.tools.length > 0) {
|
|
201
|
+
console.log('\n--- Tools ---')
|
|
202
|
+
for (const tool of vesselData.tools) {
|
|
203
|
+
console.log(` ${tool.name}: ${tool.description}`)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Runtime Mappings (verbose only)
|
|
208
|
+
if (options.verbose) {
|
|
209
|
+
console.log('\n--- Runtime Mappings ---')
|
|
210
|
+
const functionCount = vesselData.mapping.functionMap.size
|
|
211
|
+
const intentCount = vesselData.mapping.intentMap.size
|
|
212
|
+
const patternCount = vesselData.mapping.patternMap.size
|
|
213
|
+
console.log(` Functions mapped: ${functionCount}`)
|
|
214
|
+
console.log(` Intents inferred: ${intentCount}`)
|
|
215
|
+
console.log(` Patterns recorded: ${patternCount}`)
|
|
216
|
+
|
|
217
|
+
if (functionCount > 0 && functionCount <= 10) {
|
|
218
|
+
console.log('\n Function Mappings:')
|
|
219
|
+
for (const [name, loc] of vesselData.mapping.functionMap) {
|
|
220
|
+
console.log(` ${name} -> ${loc.file}:${loc.line}`)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (intentCount > 0 && intentCount <= 10) {
|
|
225
|
+
console.log('\n Intent Mappings:')
|
|
226
|
+
for (const [name, intent] of vesselData.mapping.intentMap) {
|
|
227
|
+
console.log(` ${name}: ${intent.purpose} (${(intent.confidence * 100).toFixed(0)}%)`)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Composition
|
|
233
|
+
if (vesselData.composition.compositions.length > 0) {
|
|
234
|
+
console.log('\n--- Activity Composition ---')
|
|
235
|
+
for (const comp of vesselData.composition.compositions.slice(0, 10)) {
|
|
236
|
+
console.log(` ${comp.parent} -> ${comp.child} (${comp.count} times, ${comp.successRate.toFixed(0)}% success)`)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log('')
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Remove a vessel from the registry
|
|
245
|
+
*/
|
|
246
|
+
async function removeVessel(target: string, options: VesselOptions): Promise<void> {
|
|
247
|
+
// Find the vessel
|
|
248
|
+
let vesselId: string | null = null
|
|
249
|
+
|
|
250
|
+
if (target.startsWith('vessel_')) {
|
|
251
|
+
vesselId = target
|
|
252
|
+
} else {
|
|
253
|
+
const absPath = resolve(target)
|
|
254
|
+
const v = getVesselByPath(absPath)
|
|
255
|
+
if (v) {
|
|
256
|
+
vesselId = v.id
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!vesselId) {
|
|
261
|
+
if (options.json) {
|
|
262
|
+
console.log(JSON.stringify({ error: 'Vessel not found', target }, null, 2))
|
|
263
|
+
} else {
|
|
264
|
+
exitWithError(`Vessel not found: ${target}`)
|
|
265
|
+
}
|
|
266
|
+
return
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const removed = deregisterVessel(vesselId)
|
|
270
|
+
|
|
271
|
+
if (options.json) {
|
|
272
|
+
console.log(JSON.stringify({ success: removed, vesselId }, null, 2))
|
|
273
|
+
} else if (removed) {
|
|
274
|
+
printSuccess(`Removed vessel: ${vesselId}`)
|
|
275
|
+
} else {
|
|
276
|
+
printWarning(`Failed to remove vessel: ${vesselId}`)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// CLI entry point
|
|
281
|
+
if (import.meta.main) {
|
|
282
|
+
const args = process.argv.slice(2)
|
|
283
|
+
vessel(args).catch((error) => {
|
|
284
|
+
console.error('Vessel command failed:', error)
|
|
285
|
+
process.exit(1)
|
|
286
|
+
})
|
|
287
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* System Overview Component
|
|
3
|
+
*
|
|
4
|
+
* Shows:
|
|
5
|
+
* - MiniBob instance status (from K8s API or metrics endpoint)
|
|
6
|
+
* - API health and connectivity
|
|
7
|
+
* - Overall system metrics (total executions, success rate, error rate, costs)
|
|
8
|
+
* - Recent activity feed
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
12
|
+
import { Badge } from "@/components/ui/badge";
|
|
13
|
+
import { Progress } from "@/components/ui/progress";
|
|
14
|
+
import { Activity, Server, Database, Cpu, TrendingUp, TrendingDown, Clock, DollarSign, Wifi, WifiOff } from "lucide-react";
|
|
15
|
+
import { useHealth } from "@/hooks/useHealth";
|
|
16
|
+
import { useTemplates } from "@/hooks/useTemplates";
|
|
17
|
+
import { useWebSocket } from "@/hooks/useWebSocket";
|
|
18
|
+
import { useMemo, useCallback } from "react";
|
|
19
|
+
import type { ActivityTemplate, WebSocketMessage } from "@/lib/types";
|
|
20
|
+
|
|
21
|
+
interface SystemMetrics {
|
|
22
|
+
totalExecutions: number;
|
|
23
|
+
successRate: number;
|
|
24
|
+
totalCost: number;
|
|
25
|
+
avgDuration: number;
|
|
26
|
+
activeTemplates: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function SystemOverview() {
|
|
30
|
+
const { health, loading: healthLoading } = useHealth();
|
|
31
|
+
const { templates, total, loading: templatesLoading, refresh: refreshTemplates } = useTemplates();
|
|
32
|
+
|
|
33
|
+
// WebSocket for real-time updates
|
|
34
|
+
const handleWebSocketMessage = useCallback((message: WebSocketMessage) => {
|
|
35
|
+
console.log('[SystemOverview] Received WebSocket message:', message.type);
|
|
36
|
+
|
|
37
|
+
// Refresh templates when metrics are updated
|
|
38
|
+
if (message.type === 'execution_completed' || message.type === 'template_updated') {
|
|
39
|
+
refreshTemplates();
|
|
40
|
+
}
|
|
41
|
+
}, [refreshTemplates]);
|
|
42
|
+
|
|
43
|
+
const { connected: wsConnected } = useWebSocket({
|
|
44
|
+
enabled: true,
|
|
45
|
+
onMessage: handleWebSocketMessage,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Calculate aggregate metrics
|
|
49
|
+
const metrics: SystemMetrics = useMemo(() => {
|
|
50
|
+
if (!templates || templates.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
totalExecutions: 0,
|
|
53
|
+
successRate: 0,
|
|
54
|
+
totalCost: 0,
|
|
55
|
+
avgDuration: 0,
|
|
56
|
+
activeTemplates: 0,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const allTemplates: ActivityTemplate[] = templates;
|
|
61
|
+
const totalExec = allTemplates.reduce((sum: number, t: ActivityTemplate) =>
|
|
62
|
+
sum + (t.metrics?.total_executions || 0), 0
|
|
63
|
+
);
|
|
64
|
+
const successfulExec = allTemplates.reduce((sum: number, t: ActivityTemplate) =>
|
|
65
|
+
sum + (t.metrics?.successful_executions || 0), 0
|
|
66
|
+
);
|
|
67
|
+
const totalCost = allTemplates.reduce((sum: number, t: ActivityTemplate) =>
|
|
68
|
+
sum + ((t.metrics?.avg_cost_usd || 0) * (t.metrics?.total_executions || 0)), 0
|
|
69
|
+
);
|
|
70
|
+
const totalDuration = allTemplates.reduce((sum: number, t: ActivityTemplate) =>
|
|
71
|
+
sum + ((t.metrics?.avg_duration_ms || 0) * (t.metrics?.total_executions || 0)), 0
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
totalExecutions: totalExec,
|
|
76
|
+
successRate: totalExec > 0 ? (successfulExec / totalExec) * 100 : 0,
|
|
77
|
+
totalCost,
|
|
78
|
+
avgDuration: totalExec > 0 ? totalDuration / totalExec : 0,
|
|
79
|
+
activeTemplates: allTemplates.filter((t: ActivityTemplate) =>
|
|
80
|
+
(t.metrics?.total_executions || 0) > 0
|
|
81
|
+
).length,
|
|
82
|
+
};
|
|
83
|
+
}, [templates]);
|
|
84
|
+
|
|
85
|
+
// Health status badge
|
|
86
|
+
const healthStatus = health?.status || 'unknown';
|
|
87
|
+
const healthColor = healthStatus === 'healthy' || healthStatus === 'ok' ? 'default' :
|
|
88
|
+
healthStatus === 'degraded' ? 'secondary' : 'destructive';
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div className="space-y-6">
|
|
92
|
+
{/* Header */}
|
|
93
|
+
<div className="flex items-start justify-between">
|
|
94
|
+
<div>
|
|
95
|
+
<h2 className="text-3xl font-bold tracking-tight">System Overview</h2>
|
|
96
|
+
<p className="text-muted-foreground">
|
|
97
|
+
Real-time monitoring of MiniBob instances and activity execution
|
|
98
|
+
</p>
|
|
99
|
+
</div>
|
|
100
|
+
{/* WebSocket Status Indicator */}
|
|
101
|
+
<div className="flex items-center gap-2 text-sm">
|
|
102
|
+
{wsConnected ? (
|
|
103
|
+
<>
|
|
104
|
+
<Wifi className="h-4 w-4 text-green-600" />
|
|
105
|
+
<span className="text-green-600">Live</span>
|
|
106
|
+
</>
|
|
107
|
+
) : (
|
|
108
|
+
<>
|
|
109
|
+
<WifiOff className="h-4 w-4 text-muted-foreground" />
|
|
110
|
+
<span className="text-muted-foreground">Reconnecting...</span>
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Top-level Status Cards */}
|
|
117
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-5">
|
|
118
|
+
{/* API Health */}
|
|
119
|
+
<Card>
|
|
120
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
121
|
+
<CardTitle className="text-sm font-medium">API Health</CardTitle>
|
|
122
|
+
<Server className="h-4 w-4 text-muted-foreground" />
|
|
123
|
+
</CardHeader>
|
|
124
|
+
<CardContent>
|
|
125
|
+
{healthLoading ? (
|
|
126
|
+
<div className="text-2xl font-bold">Loading...</div>
|
|
127
|
+
) : (
|
|
128
|
+
<>
|
|
129
|
+
<div className="flex items-center gap-2">
|
|
130
|
+
<Badge variant={healthColor}>{healthStatus}</Badge>
|
|
131
|
+
{health?.service && (
|
|
132
|
+
<span className="text-xs text-muted-foreground">{health.service}</span>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
{health?.checks && (
|
|
136
|
+
<div className="mt-2 space-y-1 text-xs text-muted-foreground">
|
|
137
|
+
<div className="flex justify-between">
|
|
138
|
+
<span>Redis:</span>
|
|
139
|
+
<span className={health.checks.redis?.status === 'healthy' ? 'text-green-600' : 'text-red-600'}>
|
|
140
|
+
{health.checks.redis?.status} ({health.checks.redis?.latency_ms}ms)
|
|
141
|
+
</span>
|
|
142
|
+
</div>
|
|
143
|
+
<div className="flex justify-between">
|
|
144
|
+
<span>SurrealDB:</span>
|
|
145
|
+
<span className={health.checks.surrealdb?.status === 'healthy' ? 'text-green-600' : 'text-red-600'}>
|
|
146
|
+
{health.checks.surrealdb?.status} ({health.checks.surrealdb?.latency_ms}ms)
|
|
147
|
+
</span>
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
)}
|
|
151
|
+
</>
|
|
152
|
+
)}
|
|
153
|
+
</CardContent>
|
|
154
|
+
</Card>
|
|
155
|
+
|
|
156
|
+
{/* Total Executions */}
|
|
157
|
+
<Card>
|
|
158
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
159
|
+
<CardTitle className="text-sm font-medium">Total Executions</CardTitle>
|
|
160
|
+
<Activity className="h-4 w-4 text-muted-foreground" />
|
|
161
|
+
</CardHeader>
|
|
162
|
+
<CardContent>
|
|
163
|
+
<div className="text-2xl font-bold">
|
|
164
|
+
{templatesLoading ? '...' : metrics.totalExecutions.toLocaleString()}
|
|
165
|
+
</div>
|
|
166
|
+
<p className="text-xs text-muted-foreground">
|
|
167
|
+
{metrics.activeTemplates} active templates
|
|
168
|
+
</p>
|
|
169
|
+
</CardContent>
|
|
170
|
+
</Card>
|
|
171
|
+
|
|
172
|
+
{/* Success Rate */}
|
|
173
|
+
<Card>
|
|
174
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
175
|
+
<CardTitle className="text-sm font-medium">Success Rate</CardTitle>
|
|
176
|
+
<TrendingUp className="h-4 w-4 text-green-500" />
|
|
177
|
+
</CardHeader>
|
|
178
|
+
<CardContent>
|
|
179
|
+
<div className="text-2xl font-bold text-green-600">
|
|
180
|
+
{templatesLoading ? '...' : `${metrics.successRate.toFixed(1)}%`}
|
|
181
|
+
</div>
|
|
182
|
+
<p className="text-xs text-muted-foreground">
|
|
183
|
+
{Math.round(metrics.totalExecutions * metrics.successRate / 100)} successful executions
|
|
184
|
+
</p>
|
|
185
|
+
<Progress
|
|
186
|
+
value={metrics.successRate}
|
|
187
|
+
className="mt-2"
|
|
188
|
+
/>
|
|
189
|
+
</CardContent>
|
|
190
|
+
</Card>
|
|
191
|
+
|
|
192
|
+
{/* Error Rate */}
|
|
193
|
+
<Card>
|
|
194
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
195
|
+
<CardTitle className="text-sm font-medium">Error Rate</CardTitle>
|
|
196
|
+
<TrendingDown className="h-4 w-4 text-red-500" />
|
|
197
|
+
</CardHeader>
|
|
198
|
+
<CardContent>
|
|
199
|
+
<div className="text-2xl font-bold text-red-600">
|
|
200
|
+
{templatesLoading ? '...' : `${(100 - metrics.successRate).toFixed(1)}%`}
|
|
201
|
+
</div>
|
|
202
|
+
<p className="text-xs text-muted-foreground">
|
|
203
|
+
{Math.round(metrics.totalExecutions * (100 - metrics.successRate) / 100)} failed executions
|
|
204
|
+
</p>
|
|
205
|
+
<Progress
|
|
206
|
+
value={100 - metrics.successRate}
|
|
207
|
+
className="mt-2"
|
|
208
|
+
/>
|
|
209
|
+
</CardContent>
|
|
210
|
+
</Card>
|
|
211
|
+
|
|
212
|
+
{/* Average Duration */}
|
|
213
|
+
<Card>
|
|
214
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
215
|
+
<CardTitle className="text-sm font-medium">Avg Duration</CardTitle>
|
|
216
|
+
<Clock className="h-4 w-4 text-muted-foreground" />
|
|
217
|
+
</CardHeader>
|
|
218
|
+
<CardContent>
|
|
219
|
+
<div className="text-2xl font-bold">
|
|
220
|
+
{templatesLoading ? '...' : (metrics.avgDuration / 1000).toFixed(1)}s
|
|
221
|
+
</div>
|
|
222
|
+
<p className="text-xs text-muted-foreground">
|
|
223
|
+
Per activity execution
|
|
224
|
+
</p>
|
|
225
|
+
</CardContent>
|
|
226
|
+
</Card>
|
|
227
|
+
</div>
|
|
228
|
+
|
|
229
|
+
{/* Second Row - Total Cost moved here */}
|
|
230
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
|
231
|
+
{/* Total Cost */}
|
|
232
|
+
<Card>
|
|
233
|
+
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
|
234
|
+
<CardTitle className="text-sm font-medium">Total Cost</CardTitle>
|
|
235
|
+
<DollarSign className="h-4 w-4 text-muted-foreground" />
|
|
236
|
+
</CardHeader>
|
|
237
|
+
<CardContent>
|
|
238
|
+
<div className="text-2xl font-bold">
|
|
239
|
+
${templatesLoading ? '...' : metrics.totalCost.toFixed(2)}
|
|
240
|
+
</div>
|
|
241
|
+
<p className="text-xs text-muted-foreground">
|
|
242
|
+
${metrics.totalExecutions > 0 ? (metrics.totalCost / metrics.totalExecutions).toFixed(3) : '0.000'} per execution
|
|
243
|
+
</p>
|
|
244
|
+
</CardContent>
|
|
245
|
+
</Card>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
248
|
+
{/* MiniBob Instances */}
|
|
249
|
+
<Card>
|
|
250
|
+
<CardHeader>
|
|
251
|
+
<CardTitle className="flex items-center gap-2">
|
|
252
|
+
<Cpu className="h-5 w-5" />
|
|
253
|
+
MiniBob Instances
|
|
254
|
+
</CardTitle>
|
|
255
|
+
<CardDescription>
|
|
256
|
+
Connected execution vessels (future: live K8s pod monitoring)
|
|
257
|
+
</CardDescription>
|
|
258
|
+
</CardHeader>
|
|
259
|
+
<CardContent>
|
|
260
|
+
<div className="space-y-3">
|
|
261
|
+
{/* Placeholder for MiniBob pods - will be populated via K8s API or metrics */}
|
|
262
|
+
<div className="flex items-center justify-between p-3 border rounded-lg">
|
|
263
|
+
<div className="flex items-center gap-3">
|
|
264
|
+
<div className="h-2 w-2 rounded-full bg-green-500 animate-pulse" />
|
|
265
|
+
<div>
|
|
266
|
+
<div className="font-medium">minibob-cluster-0</div>
|
|
267
|
+
<div className="text-xs text-muted-foreground">Namespace: activity-system</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
<Badge variant="outline">Idle</Badge>
|
|
271
|
+
</div>
|
|
272
|
+
|
|
273
|
+
<div className="text-center p-6 border-2 border-dashed rounded-lg">
|
|
274
|
+
<Database className="h-8 w-8 mx-auto mb-2 text-muted-foreground" />
|
|
275
|
+
<p className="text-sm text-muted-foreground">
|
|
276
|
+
Additional MiniBob instances will appear here
|
|
277
|
+
</p>
|
|
278
|
+
<p className="text-xs text-muted-foreground mt-1">
|
|
279
|
+
Deploy more vessels to scale execution capacity
|
|
280
|
+
</p>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
</CardContent>
|
|
284
|
+
</Card>
|
|
285
|
+
|
|
286
|
+
{/* Learning System Health */}
|
|
287
|
+
<Card>
|
|
288
|
+
<CardHeader>
|
|
289
|
+
<CardTitle className="flex items-center gap-2">
|
|
290
|
+
<TrendingUp className="h-5 w-5" />
|
|
291
|
+
Learning System Status
|
|
292
|
+
</CardTitle>
|
|
293
|
+
<CardDescription>
|
|
294
|
+
Thompson sampling and adaptive template selection
|
|
295
|
+
</CardDescription>
|
|
296
|
+
</CardHeader>
|
|
297
|
+
<CardContent>
|
|
298
|
+
<div className="space-y-4">
|
|
299
|
+
<div className="grid grid-cols-3 gap-4 text-center">
|
|
300
|
+
<div>
|
|
301
|
+
<div className="text-2xl font-bold">{total || 0}</div>
|
|
302
|
+
<div className="text-xs text-muted-foreground">Templates</div>
|
|
303
|
+
</div>
|
|
304
|
+
<div>
|
|
305
|
+
<div className="text-2xl font-bold">
|
|
306
|
+
{templatesLoading ? '...' : metrics.activeTemplates}
|
|
307
|
+
</div>
|
|
308
|
+
<div className="text-xs text-muted-foreground">Active (0+ exec)</div>
|
|
309
|
+
</div>
|
|
310
|
+
<div>
|
|
311
|
+
<div className="text-2xl font-bold">
|
|
312
|
+
{metrics.successRate > 0 ? metrics.successRate.toFixed(0) : '0'}%
|
|
313
|
+
</div>
|
|
314
|
+
<div className="text-xs text-muted-foreground">Avg Success</div>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
|
|
318
|
+
<div className="pt-4 border-t">
|
|
319
|
+
<div className="text-sm font-medium mb-2">Thompson Sampling Status</div>
|
|
320
|
+
<div className="text-xs text-muted-foreground space-y-1">
|
|
321
|
+
<div>✓ Bayesian optimization enabled</div>
|
|
322
|
+
<div>✓ Exploration/exploitation balanced</div>
|
|
323
|
+
<div>✓ Real-time metric updates via API</div>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</CardContent>
|
|
328
|
+
</Card>
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
}
|