mio-agent-runtime 0.5.9 → 0.5.11
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/README.md +5 -0
- package/bin/mio.js +160 -0
- package/package.json +2 -2
- package/server/mio-intelligence-mcp/dashboard-server.js +171 -0
- package/server/mio-intelligence-mcp/index.js +56 -0
package/README.md
CHANGED
|
@@ -54,6 +54,9 @@ Restart your agent after install. That's it.
|
|
|
54
54
|
| `mio init` | Create `~/.mio-intelligence` and default config |
|
|
55
55
|
| `mio install <host>` | Wire MCP + rules + observer into a host |
|
|
56
56
|
| `mio uninstall <host>` | Remove Mio MCP, rules, approvals, and observer from a host |
|
|
57
|
+
| `mio upgrade` | Upgrade Mio via npm and re-wire all installed hosts to the new server |
|
|
58
|
+
| `mio doctor` | Diagnose install/runtime issues: hosts, observer, version drift, PATH |
|
|
59
|
+
| `mio dashboard [--start\|--stop\|--status\|--open]` | Start the live dashboard HTTP server (browser view of MIO_HOME data) |
|
|
57
60
|
| `mio mcp` | Start MCP server (stdio, for agent integration) |
|
|
58
61
|
| `mio status` | Show runtime state, adapter status, observer PID |
|
|
59
62
|
| `mio agents` | List installed hosts |
|
|
@@ -239,6 +242,8 @@ WorkBuddy ─┘ + Observer ......... automatic data collection
|
|
|
239
242
|
| >= 0.5.0 | Idea generation + evolution suggestions |
|
|
240
243
|
| >= 0.5.6 | Rewritten documentation |
|
|
241
244
|
| >= 0.5.9 | `mio uninstall <host>` command |
|
|
245
|
+
| >= 0.5.10 | `mio upgrade` and `mio doctor` commands |
|
|
246
|
+
| >= 0.5.11 | Live web dashboard (`mio dashboard` + `mio.dashboard.*`) |
|
|
242
247
|
|
|
243
248
|
---
|
|
244
249
|
|
package/bin/mio.js
CHANGED
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const os = require('os')
|
|
7
|
+
const { spawnSync, spawn } = require('child_process')
|
|
7
8
|
|
|
8
9
|
const SERVER_SCRIPT = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'index.js')
|
|
10
|
+
const SERVER_SCRIPT_DASH = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'dashboard-server.js')
|
|
11
|
+
const DASH_PORT = 8787
|
|
9
12
|
const MIO_HOME = process.env.MIO_HOME || path.join(os.homedir(), '.mio-intelligence')
|
|
10
13
|
const CONFIG_FILE = path.join(MIO_HOME, 'config.json')
|
|
11
14
|
|
|
@@ -262,6 +265,154 @@ function uninstallCommand(host, useJson) {
|
|
|
262
265
|
console.log(result.message || `${host} adapter removed.`)
|
|
263
266
|
}
|
|
264
267
|
|
|
268
|
+
function runNpm(args) {
|
|
269
|
+
try {
|
|
270
|
+
const res = spawnSync('npm', args, { encoding: 'utf8', shell: true, windowsHide: true })
|
|
271
|
+
if (res.error) return { ok: false, error: res.error.message, stdout: '', stderr: '' }
|
|
272
|
+
return { ok: true, status: res.status, stdout: res.stdout || '', stderr: res.stderr || '' }
|
|
273
|
+
} catch (e) {
|
|
274
|
+
return { ok: false, error: e.message, stdout: '', stderr: '' }
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function npmGlobalVersion() {
|
|
279
|
+
const r = runNpm(['ls', '-g', 'mio-agent-runtime'])
|
|
280
|
+
const m = r.stdout && r.stdout.match(/mio-agent-runtime@(\d+\.\d+\.\d+)/)
|
|
281
|
+
return m ? m[1] : null
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function npmLatestVersion() {
|
|
285
|
+
const r = runNpm(['view', 'mio-agent-runtime', 'version'])
|
|
286
|
+
const m = r.stdout && r.stdout.trim().match(/(\d+\.\d+\.\d+)/)
|
|
287
|
+
return m ? m[1] : null
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function upgradeCommand(useJson) {
|
|
291
|
+
const before = npmGlobalVersion()
|
|
292
|
+
const installRes = runNpm(['install', '-g', 'mio-agent-runtime@latest'])
|
|
293
|
+
if (!installRes.ok || installRes.status !== 0) {
|
|
294
|
+
const msg = 'npm install failed: ' + (installRes.error || installRes.stderr || 'unknown error')
|
|
295
|
+
if (useJson) return jsonOrText({ ok: false, error: msg }, true)
|
|
296
|
+
console.error(msg)
|
|
297
|
+
process.exitCode = 1
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
const after = npmGlobalVersion()
|
|
301
|
+
const prefix = (() => { const r = runNpm(['config', 'get', 'prefix']); return r.stdout ? r.stdout.trim() : '' })()
|
|
302
|
+
const isWin = process.platform === 'win32'
|
|
303
|
+
const globalServer = prefix ? path.join(prefix, 'node_modules', 'mio-agent-runtime', 'server', 'mio-intelligence-mcp', 'index.js') : SERVER_SCRIPT
|
|
304
|
+
const globalNode = prefix ? (isWin ? path.join(prefix, 'node.exe') : path.join(prefix, 'bin', 'node')) : process.execPath
|
|
305
|
+
const nodeBin = fs.existsSync(globalNode) ? globalNode : process.execPath
|
|
306
|
+
const config = readConfig()
|
|
307
|
+
const hosts = Object.keys(config.agents || {})
|
|
308
|
+
const rewired = []
|
|
309
|
+
for (const host of hosts) {
|
|
310
|
+
const adapter = adapters[host]
|
|
311
|
+
if (!adapter) continue
|
|
312
|
+
try {
|
|
313
|
+
const result = adapter.install({ node: nodeBin, serverScript: globalServer, home: MIO_HOME, workspace: process.cwd() })
|
|
314
|
+
if (result.changed) rewired.push(host)
|
|
315
|
+
} catch (_) {}
|
|
316
|
+
}
|
|
317
|
+
let observerStarted = false
|
|
318
|
+
try { observerStarted = observer.startBackground(MIO_HOME).started } catch (_) {}
|
|
319
|
+
const payload = { ok: true, before, after, rewiredHosts: rewired, observerStarted }
|
|
320
|
+
if (useJson) return jsonOrText(payload, true)
|
|
321
|
+
console.log('Upgraded Mio: ' + (before || '?') + ' -> ' + (after || '?'))
|
|
322
|
+
if (rewired.length) console.log('Re-wired hosts: ' + rewired.join(', '))
|
|
323
|
+
else console.log('All installed hosts already up to date.')
|
|
324
|
+
console.log(observerStarted ? 'Observer started.' : 'Observer already running.')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function doctorCommand(useJson) {
|
|
328
|
+
const checks = []
|
|
329
|
+
function add(name, ok, detail) { checks.push({ name: name, ok: Boolean(ok), detail: detail || '' }) }
|
|
330
|
+
add('MIO_HOME exists', fs.existsSync(MIO_HOME), MIO_HOME)
|
|
331
|
+
if (fs.existsSync(MIO_HOME)) {
|
|
332
|
+
try { fs.accessSync(MIO_HOME, fs.constants.W_OK); add('MIO_HOME writable', true) }
|
|
333
|
+
catch (_) { add('MIO_HOME writable', false, 'no write permission') }
|
|
334
|
+
}
|
|
335
|
+
add('MCP server present', fs.existsSync(SERVER_SCRIPT), SERVER_SCRIPT)
|
|
336
|
+
for (const host of ['codex', 'opencode', 'workbuddy', 'hermes']) {
|
|
337
|
+
const adapter = adapters[host]
|
|
338
|
+
const installed = adapter ? adapter.isInstalled() : false
|
|
339
|
+
add('host:' + host + ' installed', installed)
|
|
340
|
+
}
|
|
341
|
+
const obsRunning = observer.isRunning(MIO_HOME)
|
|
342
|
+
add('observer running', obsRunning, obsRunning ? 'pid ' + observer.readPid(MIO_HOME) : 'not running')
|
|
343
|
+
const installed = npmGlobalVersion()
|
|
344
|
+
const latest = npmLatestVersion()
|
|
345
|
+
add('npm up to date', installed === latest, 'installed ' + installed + ' / latest ' + latest)
|
|
346
|
+
const prefix = (() => { const r = runNpm(['config', 'get', 'prefix']); return r.stdout ? r.stdout.trim() : '' })()
|
|
347
|
+
const execPrefix = path.dirname(process.execPath)
|
|
348
|
+
add('global bin on PATH', prefix && execPrefix && prefix.toLowerCase() === execPrefix.toLowerCase(), 'npm prefix ' + prefix + ' / node ' + execPrefix)
|
|
349
|
+
const stray0 = path.join(process.cwd(), '0')
|
|
350
|
+
add('no stray 0 file', !fs.existsSync(stray0), fs.existsSync(stray0) ? stray0 : '')
|
|
351
|
+
const failed = checks.filter(function (c) { return !c.ok })
|
|
352
|
+
const payload = { ok: failed.length === 0, checks: checks, failedCount: failed.length }
|
|
353
|
+
if (useJson) return jsonOrText(payload, true)
|
|
354
|
+
for (const c of checks) {
|
|
355
|
+
console.log((c.ok ? 'OK ' : 'FAIL ') + c.name + (c.detail ? ' (' + c.detail + ')' : ''))
|
|
356
|
+
}
|
|
357
|
+
console.log('')
|
|
358
|
+
console.log(failed.length === 0 ? 'All checks passed.' : failed.length + ' check(s) failed. Run `mio upgrade` or reinstall affected hosts.')
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function dashPidPath() { return path.join(MIO_HOME, 'dashboard.pid') }
|
|
362
|
+
function isDashRunning() {
|
|
363
|
+
const pidFile = dashPidPath()
|
|
364
|
+
if (!fs.existsSync(pidFile)) return false
|
|
365
|
+
try { process.kill(parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10), 0); return true } catch (_) { return false }
|
|
366
|
+
}
|
|
367
|
+
function startDashBackground() {
|
|
368
|
+
if (isDashRunning()) return { started: false, already: true }
|
|
369
|
+
fs.mkdirSync(MIO_HOME, { recursive: true })
|
|
370
|
+
const child = spawn(process.execPath, [SERVER_SCRIPT_DASH, String(DASH_PORT)], { env: Object.assign({}, process.env, { MIO_DATA_DIR: MIO_HOME }), detached: true, stdio: 'ignore' })
|
|
371
|
+
child.unref()
|
|
372
|
+
fs.writeFileSync(dashPidPath(), String(child.pid))
|
|
373
|
+
return { started: true, pid: child.pid }
|
|
374
|
+
}
|
|
375
|
+
function stopDash() {
|
|
376
|
+
const pidFile = dashPidPath()
|
|
377
|
+
if (!fs.existsSync(pidFile)) return { stopped: false }
|
|
378
|
+
try { process.kill(parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10), 'SIGTERM') } catch (_) {}
|
|
379
|
+
try { fs.unlinkSync(pidFile) } catch (_) {}
|
|
380
|
+
return { stopped: true }
|
|
381
|
+
}
|
|
382
|
+
function dashboardCommand(args) {
|
|
383
|
+
const flags = args.slice(1)
|
|
384
|
+
const useJson = flags.includes('--json')
|
|
385
|
+
const url = 'http://localhost:' + DASH_PORT
|
|
386
|
+
if (flags.includes('--start')) {
|
|
387
|
+
const r = startDashBackground()
|
|
388
|
+
const started = r.started
|
|
389
|
+
if (useJson) return jsonOrText({ running: true, started: started, url: url }, true)
|
|
390
|
+
console.log(started ? 'Dashboard started at ' + url : 'Dashboard already running at ' + url)
|
|
391
|
+
return
|
|
392
|
+
}
|
|
393
|
+
if (flags.includes('--stop')) {
|
|
394
|
+
const r = stopDash()
|
|
395
|
+
if (useJson) return jsonOrText({ running: false, stopped: r.stopped }, true)
|
|
396
|
+
console.log(r.stopped ? 'Dashboard stopped.' : 'Dashboard was not running.')
|
|
397
|
+
return
|
|
398
|
+
}
|
|
399
|
+
if (flags.includes('--status')) {
|
|
400
|
+
const running = isDashRunning()
|
|
401
|
+
if (useJson) return jsonOrText({ running: running, url: running ? url : null }, true)
|
|
402
|
+
console.log(running ? 'Dashboard: running (' + url + ')' : 'Dashboard: not running')
|
|
403
|
+
return
|
|
404
|
+
}
|
|
405
|
+
if (flags.includes('--open')) {
|
|
406
|
+
startDashBackground()
|
|
407
|
+
try { require('child_process').exec('start ' + url) } catch (_) {}
|
|
408
|
+
if (useJson) return jsonOrText({ running: true, url: url }, true)
|
|
409
|
+
console.log('Dashboard opened at ' + url)
|
|
410
|
+
return
|
|
411
|
+
}
|
|
412
|
+
const child = spawn(process.execPath, [SERVER_SCRIPT_DASH, String(DASH_PORT)], { env: Object.assign({}, process.env, { MIO_DATA_DIR: MIO_HOME }), stdio: 'inherit' })
|
|
413
|
+
child.on('exit', function () { try { fs.unlinkSync(dashPidPath()) } catch (_) {} })
|
|
414
|
+
}
|
|
415
|
+
|
|
265
416
|
function help() {
|
|
266
417
|
console.log(`Mio Agent Runtime CLI
|
|
267
418
|
|
|
@@ -271,10 +422,13 @@ Usage:
|
|
|
271
422
|
mio mcp Start Mio MCP server (stdio)
|
|
272
423
|
mio install <host> Install Mio into a host (codex|opencode|workbuddy|hermes)
|
|
273
424
|
mio uninstall <host> Remove Mio from a host
|
|
425
|
+
mio upgrade Upgrade Mio and re-wire all installed hosts
|
|
426
|
+
mio doctor Diagnose install and runtime issues
|
|
274
427
|
mio status Show runtime and adapter status
|
|
275
428
|
mio agents List installed agents
|
|
276
429
|
mio observe Watch WorkBuddy transcripts and auto-ingest task outcomes
|
|
277
430
|
mio observe --start|--stop|--status|--once Manage the background observer daemon
|
|
431
|
+
mio dashboard [--start|--stop|--status|--open] Start the live dashboard HTTP server
|
|
278
432
|
mio --json status Machine-readable status
|
|
279
433
|
mio --json agents Machine-readable agents
|
|
280
434
|
`)
|
|
@@ -299,12 +453,18 @@ function main() {
|
|
|
299
453
|
return installCommand(args[1], useJson)
|
|
300
454
|
case 'uninstall':
|
|
301
455
|
return uninstallCommand(args[1], useJson)
|
|
456
|
+
case 'upgrade':
|
|
457
|
+
return upgradeCommand(useJson)
|
|
458
|
+
case 'doctor':
|
|
459
|
+
return doctorCommand(useJson)
|
|
302
460
|
case 'status':
|
|
303
461
|
return statusCommand(useJson)
|
|
304
462
|
case 'agents':
|
|
305
463
|
return agentsCommand(useJson)
|
|
306
464
|
case 'observe':
|
|
307
465
|
return observeCommand(args)
|
|
466
|
+
case 'dashboard':
|
|
467
|
+
return dashboardCommand(args)
|
|
308
468
|
case 'help':
|
|
309
469
|
case '--help':
|
|
310
470
|
return help()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
4
4
|
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"!server/mio-intelligence-mcp/__tests__"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
-
"check": "node --check bin/mio.js && node --check adapters/host-utils.js && node --check adapters/codex.js && node --check adapters/opencode.js && node --check adapters/workbuddy.js && node --check adapters/hermes.js && node --check server/mio-intelligence-mcp/index.js && node --check server/mio-intelligence-mcp/phase0.js && node --check observe/observer.js",
|
|
19
|
+
"check": "node --check bin/mio.js && node --check adapters/host-utils.js && node --check adapters/codex.js && node --check adapters/opencode.js && node --check adapters/workbuddy.js && node --check adapters/hermes.js && node --check server/mio-intelligence-mcp/index.js && node --check server/mio-intelligence-mcp/phase0.js && node --check server/mio-intelligence-mcp/dashboard-server.js && node --check observe/observer.js",
|
|
20
20
|
"prepack": "npm run check"
|
|
21
21
|
},
|
|
22
22
|
"engines": {
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const path = require('path')
|
|
3
|
+
const http = require('http')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
const { spawn } = require('child_process')
|
|
6
|
+
|
|
7
|
+
function dataDir() {
|
|
8
|
+
return process.env.MIO_DATA_DIR || path.join(os.homedir(), '.mio-intelligence')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readJsonl(file) {
|
|
12
|
+
const full = path.join(dataDir(), file)
|
|
13
|
+
if (!fs.existsSync(full)) return []
|
|
14
|
+
try {
|
|
15
|
+
return fs.readFileSync(full, 'utf8')
|
|
16
|
+
.split('\n')
|
|
17
|
+
.map(function (l) { return l.trim() })
|
|
18
|
+
.filter(function (l) { return l.length })
|
|
19
|
+
.map(function (l) {
|
|
20
|
+
try { return JSON.parse(l) } catch (_) { return null }
|
|
21
|
+
})
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
} catch (_) {
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function tally(rows, keyFn) {
|
|
29
|
+
const out = {}
|
|
30
|
+
for (const r of rows) {
|
|
31
|
+
const k = keyFn(r)
|
|
32
|
+
if (k == null || k === '') continue
|
|
33
|
+
out[k] = (out[k] || 0) + 1
|
|
34
|
+
}
|
|
35
|
+
return out
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function topN(obj, n) {
|
|
39
|
+
return Object.keys(obj)
|
|
40
|
+
.sort(function (a, b) { return obj[b] - obj[a] })
|
|
41
|
+
.slice(0, n)
|
|
42
|
+
.map(function (k) { return { key: k, count: obj[k] } })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function aggregate() {
|
|
46
|
+
const memory = readJsonl('memory.jsonl')
|
|
47
|
+
const traces = readJsonl('traces.jsonl')
|
|
48
|
+
const experience = readJsonl('experience_reuse.jsonl')
|
|
49
|
+
const ideas = readJsonl('ideas.jsonl')
|
|
50
|
+
const suggestions = readJsonl('suggestions.jsonl')
|
|
51
|
+
const agents = readJsonl('agents.jsonl')
|
|
52
|
+
|
|
53
|
+
const memoryByKind = tally(memory, function (r) { return r.kind })
|
|
54
|
+
const memoryBySource = tally(memory, function (r) { return r.source })
|
|
55
|
+
const memoryByProject = tally(memory, function (r) { return r.project })
|
|
56
|
+
|
|
57
|
+
const tracesByHost = tally(traces, function (r) { return r.host || 'unknown' })
|
|
58
|
+
const tracesByOutcome = tally(traces, function (r) { return r.outcome || 'unknown' })
|
|
59
|
+
const tracesByEventType = tally(traces, function (r) { return r.event_type || 'unknown' })
|
|
60
|
+
|
|
61
|
+
const taskOutcomes = traces.filter(function (t) { return t.event_type === 'task_outcome' })
|
|
62
|
+
const successCount = taskOutcomes.filter(function (t) { return t.outcome === 'success' }).length
|
|
63
|
+
const taskTotal = taskOutcomes.length
|
|
64
|
+
const successRate = taskTotal ? (successCount / taskTotal * 100).toFixed(1) : '0.0'
|
|
65
|
+
|
|
66
|
+
const verified = experience.filter(function (e) {
|
|
67
|
+
return e.reuse && e.behaviorChanged && e.outcomeImproved
|
|
68
|
+
}).length
|
|
69
|
+
const reuseCount = experience.filter(function (e) { return e.reuse }).length
|
|
70
|
+
const crossAgent = experience.filter(function (e) {
|
|
71
|
+
return e.sourceAgent && e.targetAgent && e.sourceAgent !== e.targetAgent
|
|
72
|
+
}).length
|
|
73
|
+
|
|
74
|
+
const hosts = Object.keys(tracesByHost).filter(function (h) { return h && h !== 'mcp' })
|
|
75
|
+
const phase0 = {
|
|
76
|
+
hosts: hosts.length,
|
|
77
|
+
tasks: taskTotal,
|
|
78
|
+
verifiedReuse: verified,
|
|
79
|
+
successRate: successRate,
|
|
80
|
+
thresholds: { hosts: 2, tasks: 20, verifiedReuse: 5 },
|
|
81
|
+
passed: hosts.length >= 2 && taskTotal >= 20 && verified >= 5,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function recent(rows, n) {
|
|
85
|
+
return rows.slice(-n).map(function (r) {
|
|
86
|
+
return {
|
|
87
|
+
id: r.id,
|
|
88
|
+
timestamp: r.timestamp,
|
|
89
|
+
kind: r.kind || r.event_type || null,
|
|
90
|
+
source: r.source || r.host || null,
|
|
91
|
+
text: (r.content || r.notes || r.trace_id || '').toString().slice(0, 160),
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
generatedAt: new Date().toISOString(),
|
|
98
|
+
dataDir: dataDir(),
|
|
99
|
+
summary: {
|
|
100
|
+
memory: memory.length,
|
|
101
|
+
traces: traces.length,
|
|
102
|
+
experience: experience.length,
|
|
103
|
+
ideas: ideas.length,
|
|
104
|
+
suggestions: suggestions.length,
|
|
105
|
+
agents: agents.length,
|
|
106
|
+
},
|
|
107
|
+
memoryByKind: topN(memoryByKind, 10),
|
|
108
|
+
memoryBySource: topN(memoryBySource, 10),
|
|
109
|
+
memoryByProject: topN(memoryByProject, 10),
|
|
110
|
+
tracesByHost: topN(tracesByHost, 10),
|
|
111
|
+
tracesByOutcome: topN(tracesByOutcome, 10),
|
|
112
|
+
tracesByEventType: topN(tracesByEventType, 10),
|
|
113
|
+
experience: {
|
|
114
|
+
total: experience.length,
|
|
115
|
+
reuse: reuseCount,
|
|
116
|
+
verified: verified,
|
|
117
|
+
crossAgent: crossAgent,
|
|
118
|
+
reuseRate: experience.length ? (reuseCount / experience.length * 100).toFixed(1) : '0.0',
|
|
119
|
+
},
|
|
120
|
+
agents: agents.slice(-10),
|
|
121
|
+
phase0: phase0,
|
|
122
|
+
ideas: ideas.slice(-10).map(function (i) {
|
|
123
|
+
return { id: i.id, title: i.title, goal: i.goal, strategy: i.provenance && i.provenance.strategy }
|
|
124
|
+
}),
|
|
125
|
+
suggestions: suggestions.slice(-10).map(function (s) {
|
|
126
|
+
return { id: s.id, title: s.title, nextAction: s.nextAction, impact: s.impact }
|
|
127
|
+
}),
|
|
128
|
+
recentMemory: recent(memory, 10).reverse(),
|
|
129
|
+
recentTraces: recent(traces, 10).reverse(),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function startServer(port, htmlPath) {
|
|
134
|
+
const server = http.createServer(function (req, res) {
|
|
135
|
+
const url = (req.url || '/').split('?')[0]
|
|
136
|
+
if (url === '/api/data' || url === '/api/health') {
|
|
137
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
138
|
+
res.setHeader('Access-Control-Allow-Origin', '*')
|
|
139
|
+
if (url === '/api/health') {
|
|
140
|
+
res.end(JSON.stringify({ ok: true }))
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
res.end(JSON.stringify(aggregate(), null, 2))
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
if (url === '/' || url === '/index.html' || url === '/dashboard') {
|
|
147
|
+
res.setHeader('Content-Type', 'text/html; charset=utf-8')
|
|
148
|
+
if (htmlPath && fs.existsSync(htmlPath)) {
|
|
149
|
+
res.end(fs.readFileSync(htmlPath, 'utf8'))
|
|
150
|
+
} else {
|
|
151
|
+
res.end('<h1>Mio Dashboard</h1><p>HTML not found at ' + htmlPath + '</p>')
|
|
152
|
+
}
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
res.statusCode = 404
|
|
156
|
+
res.end('Not found')
|
|
157
|
+
})
|
|
158
|
+
server.listen(port, function () {
|
|
159
|
+
console.log('Mio dashboard running at http://localhost:' + port)
|
|
160
|
+
})
|
|
161
|
+
return server
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// When run directly: `node dashboard-server.js [port]`
|
|
165
|
+
if (require.main === module) {
|
|
166
|
+
const port = parseInt(process.argv[2] || process.env.MIO_DASHBOARD_PORT || '8787', 10)
|
|
167
|
+
const htmlPath = path.join(__dirname, '..', '..', 'mio-dashboard.html')
|
|
168
|
+
startServer(port, htmlPath)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { startServer, aggregate, dataDir }
|
|
@@ -1729,11 +1729,67 @@ async function callTool(name, args = {}) {
|
|
|
1729
1729
|
return listIdeas(args)
|
|
1730
1730
|
case 'mio.evolution.suggest':
|
|
1731
1731
|
return evolutionSuggest(args)
|
|
1732
|
+
case 'mio.dashboard.start':
|
|
1733
|
+
return handleDashboardStart(args)
|
|
1734
|
+
case 'mio.dashboard.stop':
|
|
1735
|
+
return handleDashboardStop()
|
|
1736
|
+
case 'mio.dashboard.status':
|
|
1737
|
+
return handleDashboardStatus()
|
|
1732
1738
|
default:
|
|
1733
1739
|
throw new Error(`Unknown tool: ${name}`)
|
|
1734
1740
|
}
|
|
1735
1741
|
}
|
|
1736
1742
|
|
|
1743
|
+
const DASHBOARD_PORT = 8787
|
|
1744
|
+
const dashboardPidPath = () => path.join(dataDir, 'dashboard.pid')
|
|
1745
|
+
|
|
1746
|
+
function isDashboardRunning() {
|
|
1747
|
+
const pidFile = dashboardPidPath()
|
|
1748
|
+
if (!fs.existsSync(pidFile)) return false
|
|
1749
|
+
try {
|
|
1750
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10)
|
|
1751
|
+
process.kill(pid, 0)
|
|
1752
|
+
return true
|
|
1753
|
+
} catch (_) {
|
|
1754
|
+
return false
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
function handleDashboardStart(args = {}) {
|
|
1759
|
+
const port = parseInt(args.port || process.env.MIO_DASHBOARD_PORT || DASHBOARD_PORT, 10)
|
|
1760
|
+
const url = 'http://localhost:' + port
|
|
1761
|
+
if (isDashboardRunning()) {
|
|
1762
|
+
if (args.open) { try { require('child_process').exec('start ' + url) } catch (_) {} }
|
|
1763
|
+
return { url: url, running: true, message: 'Dashboard already running at ' + url }
|
|
1764
|
+
}
|
|
1765
|
+
const serverScript = path.join(__dirname, 'dashboard-server.js')
|
|
1766
|
+
const child = require('child_process').spawn(process.execPath, [serverScript, String(port)], {
|
|
1767
|
+
env: Object.assign({}, process.env, { MIO_DATA_DIR: dataDir }),
|
|
1768
|
+
detached: true,
|
|
1769
|
+
stdio: 'ignore',
|
|
1770
|
+
})
|
|
1771
|
+
child.unref()
|
|
1772
|
+
fs.writeFileSync(dashboardPidPath(), String(child.pid))
|
|
1773
|
+
if (args.open) { try { require('child_process').exec('start ' + url) } catch (_) {} }
|
|
1774
|
+
return { url: url, running: true, pid: child.pid, message: 'Dashboard started at ' + url }
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
function handleDashboardStop() {
|
|
1778
|
+
const pidFile = dashboardPidPath()
|
|
1779
|
+
if (!fs.existsSync(pidFile)) return { running: false, message: 'Dashboard not running.' }
|
|
1780
|
+
try {
|
|
1781
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10)
|
|
1782
|
+
process.kill(pid, 'SIGTERM')
|
|
1783
|
+
} catch (_) {}
|
|
1784
|
+
try { fs.unlinkSync(pidFile) } catch (_) {}
|
|
1785
|
+
return { running: false, message: 'Dashboard stopped.' }
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
function handleDashboardStatus() {
|
|
1789
|
+
const running = isDashboardRunning()
|
|
1790
|
+
return { running: running, url: running ? 'http://localhost:' + DASHBOARD_PORT : null }
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1737
1793
|
function jsonRpcResult(id, result) {
|
|
1738
1794
|
return { jsonrpc: '2.0', id, result }
|
|
1739
1795
|
}
|