mio-agent-runtime 0.5.17 → 0.5.23
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 +188 -107
- package/adapters/claude-code.js +141 -0
- package/bin/mio.js +526 -245
- package/observe/observer.js +326 -4
- package/package.json +10 -3
- package/scripts/verify-packed-runtime.js +23 -8
- package/server/digest.js +278 -0
- package/server/host-capabilities.js +5 -0
- package/server/memory-store.js +376 -0
- package/server/mio-intelligence-mcp/index.js +2383 -2471
- package/server/retention.js +150 -0
package/bin/mio.js
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
4
|
const fs = require('fs')
|
|
5
5
|
const path = require('path')
|
|
6
6
|
const os = require('os')
|
|
7
7
|
const { execFileSync } = require('child_process')
|
|
8
|
-
|
|
9
|
-
const SERVER_SCRIPT = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'index.js')
|
|
10
|
-
const MIO_HOME = process.env.MIO_HOME || path.join(os.homedir(), '.mio-intelligence')
|
|
11
|
-
const CONFIG_FILE = path.join(MIO_HOME, 'config.json')
|
|
12
|
-
|
|
13
|
-
const adapters = {
|
|
14
|
-
codex: require('../adapters/codex.js'),
|
|
15
|
-
opencode: require('../adapters/opencode.js'),
|
|
16
|
-
workbuddy: require('../adapters/workbuddy.js'),
|
|
17
|
-
hermes: require('../adapters/hermes.js'),
|
|
18
|
-
|
|
8
|
+
|
|
9
|
+
const SERVER_SCRIPT = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'index.js')
|
|
10
|
+
const MIO_HOME = process.env.MIO_HOME || path.join(os.homedir(), '.mio-intelligence')
|
|
11
|
+
const CONFIG_FILE = path.join(MIO_HOME, 'config.json')
|
|
12
|
+
|
|
13
|
+
const adapters = {
|
|
14
|
+
codex: require('../adapters/codex.js'),
|
|
15
|
+
opencode: require('../adapters/opencode.js'),
|
|
16
|
+
workbuddy: require('../adapters/workbuddy.js'),
|
|
17
|
+
hermes: require('../adapters/hermes.js'),
|
|
18
|
+
claude: require('../adapters/claude-code.js'),
|
|
19
|
+
}
|
|
19
20
|
const observer = require('../observe/observer.js')
|
|
20
21
|
const { getEvolutionStatus, formatEvolutionStatusText } = require('../server/runtime-modules.js')
|
|
21
22
|
const { createEvolutionCutoverTools } = require('../server/evolution-cutover.js')
|
|
23
|
+
const { createMemoryStore } = require('../server/memory-store.js')
|
|
24
|
+
const { createRetention } = require('../server/retention.js')
|
|
25
|
+
const { createDigest } = require('../server/digest.js')
|
|
22
26
|
|
|
23
27
|
function appendJsonl(file, value) {
|
|
24
28
|
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
@@ -217,211 +221,473 @@ async function evolutionCommand(args, useJson) {
|
|
|
217
221
|
console.error('Usage: mio evolution status|shadow record|dual-write record|cutover readiness|cutover apply|authority plan|migration plan')
|
|
218
222
|
process.exitCode = 1
|
|
219
223
|
}
|
|
220
|
-
|
|
221
|
-
function ensureHome() {
|
|
222
|
-
fs.mkdirSync(MIO_HOME, { recursive: true })
|
|
223
|
-
fs.mkdirSync(path.join(MIO_HOME, 'logs'), { recursive: true })
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
return
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
if (
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
console.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
console.log(
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
224
|
+
|
|
225
|
+
function ensureHome() {
|
|
226
|
+
fs.mkdirSync(MIO_HOME, { recursive: true })
|
|
227
|
+
fs.mkdirSync(path.join(MIO_HOME, 'logs'), { recursive: true })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// CLI memory store: same ranking/persistence as the MCP server, pointed at
|
|
231
|
+
// MIO_HOME. No recordQuery hook — a terminal command cannot be followed by a
|
|
232
|
+
// task_outcome, so a recorded query would only be phase-0 noise.
|
|
233
|
+
function cliMemoryStore() {
|
|
234
|
+
return createMemoryStore({
|
|
235
|
+
dataDir: MIO_HOME,
|
|
236
|
+
projectName,
|
|
237
|
+
agentId: () => 'cli',
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function splitTagsOption(args, name) {
|
|
242
|
+
const raw = optionValue(args, name)
|
|
243
|
+
if (raw === undefined) return []
|
|
244
|
+
return raw
|
|
245
|
+
.split(',')
|
|
246
|
+
.map((tag) => tag.trim())
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function recallCommand(args, useJson) {
|
|
251
|
+
const query = args[1]
|
|
252
|
+
if (!query) {
|
|
253
|
+
console.error('Usage: mio recall "<query>" [--project name] [--scope project|global|all] [--kind kind] [--tags a,b] [--limit n] [--json]')
|
|
254
|
+
process.exitCode = 1
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
const flags = args.slice(2)
|
|
258
|
+
const store = cliMemoryStore()
|
|
259
|
+
let result
|
|
260
|
+
try {
|
|
261
|
+
result = store.queryMemory({
|
|
262
|
+
query,
|
|
263
|
+
project: optionValue(flags, '--project'),
|
|
264
|
+
scope: optionValue(flags, '--scope'),
|
|
265
|
+
kind: optionValue(flags, '--kind'),
|
|
266
|
+
tags: splitTagsOption(flags, '--tags'),
|
|
267
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
268
|
+
})
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error(error.message || error)
|
|
271
|
+
process.exitCode = 1
|
|
272
|
+
return
|
|
273
|
+
}
|
|
274
|
+
if (useJson) return jsonOrText(result, true)
|
|
275
|
+
console.log(
|
|
276
|
+
`Mio memory: ${result.count} result(s) for "${result.query}" (project=${result.project}, scope=${result.scope})`
|
|
277
|
+
)
|
|
278
|
+
result.results.forEach((record, index) => {
|
|
279
|
+
const date = (record.timestamp || '').slice(0, 10)
|
|
280
|
+
console.log(`${index + 1}. [${record.kind || 'note'}] ${record.content}`)
|
|
281
|
+
const meta = [
|
|
282
|
+
(record.tags || []).length > 0 ? `tags: ${record.tags.join(', ')}` : null,
|
|
283
|
+
record.source ? `source: ${record.source}` : null,
|
|
284
|
+
date,
|
|
285
|
+
record.id,
|
|
286
|
+
]
|
|
287
|
+
.filter(Boolean)
|
|
288
|
+
.join(' | ')
|
|
289
|
+
console.log(` ${meta}`)
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function tracesCommand(args, useJson) {
|
|
294
|
+
const flags = args.slice(1)
|
|
295
|
+
const store = cliMemoryStore()
|
|
296
|
+
let result
|
|
297
|
+
try {
|
|
298
|
+
result = store.queryTraces({
|
|
299
|
+
event_type: optionValue(flags, '--type'),
|
|
300
|
+
outcome: optionValue(flags, '--outcome'),
|
|
301
|
+
project: optionValue(flags, '--project'),
|
|
302
|
+
agent: optionValue(flags, '--agent'),
|
|
303
|
+
host: optionValue(flags, '--host'),
|
|
304
|
+
since: optionValue(flags, '--since'),
|
|
305
|
+
until: optionValue(flags, '--until'),
|
|
306
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
307
|
+
include_payload: !flagPresent(flags, '--compact'),
|
|
308
|
+
})
|
|
309
|
+
} catch (error) {
|
|
310
|
+
console.error(error.message || error)
|
|
311
|
+
process.exitCode = 1
|
|
312
|
+
return
|
|
313
|
+
}
|
|
314
|
+
if (useJson) return jsonOrText(result, true)
|
|
315
|
+
const filters = Object.entries(result.filters)
|
|
316
|
+
.filter(([, value]) => value !== null && value !== undefined)
|
|
317
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
318
|
+
.join(', ')
|
|
319
|
+
console.log(
|
|
320
|
+
`Mio traces: ${result.matched} matched / ${result.total} total${filters ? ' (' + filters + ')' : ''}`
|
|
321
|
+
)
|
|
322
|
+
const summary = Object.entries(result.summary.byOutcome)
|
|
323
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
324
|
+
.join(' ')
|
|
325
|
+
if (summary) console.log(`outcomes: ${summary}`)
|
|
326
|
+
result.results.forEach((trace, index) => {
|
|
327
|
+
const time = (trace.timestamp || '').replace('T', ' ').slice(0, 19)
|
|
328
|
+
const brief = trace.payload && trace.payload.summary ? ' | ' + String(trace.payload.summary).slice(0, 100) : ''
|
|
329
|
+
console.log(
|
|
330
|
+
`${index + 1}. ${time} [${trace.event_type || '?'}${trace.outcome ? '/' + trace.outcome : ''}] ${trace.agent || '?'} @ ${trace.project || '?'}${brief}`
|
|
331
|
+
)
|
|
332
|
+
console.log(` ${trace.id} trace_id=${trace.trace_id || '-'}`)
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function digestCommand(args, useJson) {
|
|
337
|
+
const flags = args.slice(1)
|
|
338
|
+
const days = parseNumberOption(flags, '--days') || 7
|
|
339
|
+
const writeBack = flagPresent(flags, '--write-back')
|
|
340
|
+
const outDir = path.join(MIO_HOME, 'digest')
|
|
341
|
+
|
|
342
|
+
const report = createDigest({ home: MIO_HOME }).generate({
|
|
343
|
+
days,
|
|
344
|
+
project: optionValue(flags, '--project'),
|
|
345
|
+
})
|
|
346
|
+
|
|
347
|
+
if (writeBack) {
|
|
348
|
+
// Feed the actionable lines back into each active workspace context file
|
|
349
|
+
// (AGENTS.md / CLAUDE.md MIO_CONTEXT block), so every agent passively
|
|
350
|
+
// receives the digest's value on its next session.
|
|
351
|
+
//
|
|
352
|
+
// Targets are resolved per project, never broadcast. An earlier version
|
|
353
|
+
// wrote one global headline into Object.keys(state.contexts) -- every
|
|
354
|
+
// workspace ever observed -- so unrelated repos (ComfyUI, douyin_store)
|
|
355
|
+
// received the same line and their real history was pushed out of the
|
|
356
|
+
// six-slot block. Write only where the digest actually has data for that
|
|
357
|
+
// project, or where the caller explicitly asked with --cwd/--project.
|
|
358
|
+
const stateFile = path.join(MIO_HOME, 'observe-state.json')
|
|
359
|
+
let state = {}
|
|
360
|
+
try {
|
|
361
|
+
state = JSON.parse(fs.readFileSync(stateFile, 'utf8'))
|
|
362
|
+
} catch (_) {}
|
|
363
|
+
state.contexts = state.contexts || {}
|
|
364
|
+
const stamp = new Date().toLocaleString('zh-CN', { hour12: false })
|
|
365
|
+
|
|
366
|
+
// Project name -> per-project digest line.
|
|
367
|
+
const projectLine = new Map()
|
|
368
|
+
for (const entry of report.projects || []) {
|
|
369
|
+
if (!entry || !entry.project) continue
|
|
370
|
+
const rate = entry.tasks > 0 ? Math.round(((entry.success || 0) / entry.tasks) * 100) : 0
|
|
371
|
+
projectLine.set(String(entry.project).toLowerCase(), `digest(${days}d): ${entry.project} ${entry.tasks} 任务 ${rate}% 成功`)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const explicitCwd = optionValue(flags, '--cwd')
|
|
375
|
+
const explicitProject = optionValue(flags, '--project')
|
|
376
|
+
let targetCwds = []
|
|
377
|
+
if (explicitCwd) {
|
|
378
|
+
targetCwds = [explicitCwd]
|
|
379
|
+
} else if (explicitProject) {
|
|
380
|
+
const wanted = String(explicitProject).toLowerCase()
|
|
381
|
+
targetCwds = Object.keys(state.contexts).filter(
|
|
382
|
+
(cwd) => path.basename(cwd).toLowerCase() === wanted,
|
|
383
|
+
)
|
|
384
|
+
} else {
|
|
385
|
+
// No explicit target: only write to workspaces this digest has data for.
|
|
386
|
+
// A workspace with no matching project gets nothing.
|
|
387
|
+
targetCwds = Object.keys(state.contexts).filter((cwd) =>
|
|
388
|
+
projectLine.has(path.basename(cwd).toLowerCase()),
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
let written = 0
|
|
393
|
+
for (const cwd of targetCwds) {
|
|
394
|
+
const line = projectLine.get(path.basename(cwd).toLowerCase())
|
|
395
|
+
if (!line) continue
|
|
396
|
+
try {
|
|
397
|
+
observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'CLAUDE.md')
|
|
398
|
+
observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'AGENTS.md')
|
|
399
|
+
written += 1
|
|
400
|
+
} catch (_) {}
|
|
401
|
+
}
|
|
402
|
+
fs.mkdirSync(MIO_HOME, { recursive: true })
|
|
403
|
+
fs.writeFileSync(stateFile, JSON.stringify(state) + '\n', 'utf8')
|
|
404
|
+
report.writeBack = {
|
|
405
|
+
workspaces: written,
|
|
406
|
+
skipped: Object.keys(state.contexts).length - written,
|
|
407
|
+
projects: [...projectLine.values()],
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Persist the report so it becomes part of the data asset itself.
|
|
412
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
413
|
+
const reportFile = path.join(outDir, 'digest-' + report.generatedAt.slice(0, 10) + '.md')
|
|
414
|
+
fs.writeFileSync(reportFile, report.markdown, 'utf8')
|
|
415
|
+
report.reportFile = reportFile
|
|
416
|
+
|
|
417
|
+
if (useJson) return jsonOrText(report, true)
|
|
418
|
+
console.log(report.markdown)
|
|
419
|
+
console.log(`Report saved: ${reportFile}`)
|
|
420
|
+
if (report.writeBack) {
|
|
421
|
+
console.log(
|
|
422
|
+
`Write-back: ${report.writeBack.workspaces} workspace(s), ${report.writeBack.skipped} skipped (no digest data)`,
|
|
423
|
+
)
|
|
424
|
+
for (const line of report.writeBack.projects) console.log(` - ${line}`)
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function pruneCommand(args, useJson) {
|
|
429
|
+
const flags = args.slice(1)
|
|
430
|
+
const days = parseNumberOption(flags, '--days') || 30
|
|
431
|
+
const includeMemory = flagPresent(flags, '--memory')
|
|
432
|
+
const dryRun = flagPresent(flags, '--dry-run')
|
|
433
|
+
if (includeMemory && !dryRun && !flagPresent(flags, '--yes')) {
|
|
434
|
+
console.error('Pruning memory.jsonl is destructive. Re-run with --yes to confirm (a .bak-prune-* backup is still written).')
|
|
435
|
+
process.exitCode = 1
|
|
436
|
+
return
|
|
437
|
+
}
|
|
438
|
+
const retention = createRetention({ home: MIO_HOME })
|
|
439
|
+
let result
|
|
440
|
+
try {
|
|
441
|
+
result = retention.prune({ days, includeMemory, dryRun })
|
|
442
|
+
} catch (error) {
|
|
443
|
+
console.error(error.message || error)
|
|
444
|
+
process.exitCode = 1
|
|
445
|
+
return
|
|
446
|
+
}
|
|
447
|
+
if (useJson) return jsonOrText(result, true)
|
|
448
|
+
console.log(
|
|
449
|
+
`Prune ${dryRun ? 'plan (dry-run)' : 'result'}: cutoff ${result.cutoff}, days=${days}${includeMemory ? ', memory included' : ''}`
|
|
450
|
+
)
|
|
451
|
+
for (const file of result.files) {
|
|
452
|
+
console.log(`- ${file.file}: ${file.total} total -> keep ${file.kept}, remove ${file.removed}`)
|
|
453
|
+
}
|
|
454
|
+
console.log(`Total removed: ${result.totalRemoved}`)
|
|
455
|
+
if (dryRun) console.log('Dry run - nothing written. Re-run without --dry-run to apply.')
|
|
456
|
+
else console.log('Backups written as <file>.bak-prune-<ts> before rewriting.')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function rememberCommand(args, useJson) {
|
|
460
|
+
const content = args[1]
|
|
461
|
+
if (!content) {
|
|
462
|
+
console.error('Usage: mio remember "<content>" [--kind decision|context|problem|note] [--tags a,b] [--scope project|global] [--project name] [--json]')
|
|
463
|
+
process.exitCode = 1
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
const flags = args.slice(2)
|
|
467
|
+
const store = cliMemoryStore()
|
|
468
|
+
let record
|
|
469
|
+
try {
|
|
470
|
+
record = store.recordMemory({
|
|
471
|
+
content,
|
|
472
|
+
kind: optionValue(flags, '--kind'),
|
|
473
|
+
tags: splitTagsOption(flags, '--tags'),
|
|
474
|
+
scope: optionValue(flags, '--scope'),
|
|
475
|
+
project: optionValue(flags, '--project'),
|
|
476
|
+
source: 'cli',
|
|
477
|
+
})
|
|
478
|
+
} catch (error) {
|
|
479
|
+
console.error(error.message || error)
|
|
480
|
+
process.exitCode = 1
|
|
481
|
+
return
|
|
482
|
+
}
|
|
483
|
+
if (useJson) return jsonOrText(record, true)
|
|
484
|
+
console.log(`Recorded ${record.id} (kind=${record.kind}, project=${record.project || 'global'}, scope=${record.scope})`)
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function readConfig() {
|
|
488
|
+
const fallback = { version: 1, home: MIO_HOME, agents: {} }
|
|
489
|
+
if (!fs.existsSync(CONFIG_FILE)) return fallback
|
|
490
|
+
try {
|
|
491
|
+
return { ...fallback, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) }
|
|
492
|
+
} catch (_) {
|
|
493
|
+
return fallback
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function writeConfig(config) {
|
|
498
|
+
ensureHome()
|
|
499
|
+
fs.writeFileSync(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function jsonOrText(payload, useJson) {
|
|
503
|
+
if (useJson) {
|
|
504
|
+
console.log(JSON.stringify(payload, null, 2))
|
|
505
|
+
return
|
|
506
|
+
}
|
|
507
|
+
return payload
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function initCommand() {
|
|
511
|
+
ensureHome()
|
|
512
|
+
const config = readConfig()
|
|
513
|
+
if (!config.createdAt) config.createdAt = new Date().toISOString()
|
|
514
|
+
writeConfig(config)
|
|
515
|
+
console.log(`MIO_HOME ready: ${MIO_HOME}`)
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function statusCommand(useJson) {
|
|
519
|
+
const config = readConfig()
|
|
520
|
+
const codexRestart = adapters.codex.restartWarningMessage()
|
|
521
|
+
const opencodeRestart = adapters.opencode.restartWarningMessage()
|
|
522
|
+
const installed = {
|
|
523
|
+
codex: adapters.codex.isInstalled(),
|
|
524
|
+
opencode: adapters.opencode.isInstalled(),
|
|
525
|
+
workbuddy: adapters.workbuddy.isInstalled(),
|
|
526
|
+
hermes: adapters.hermes.isInstalled(),
|
|
527
|
+
claude: adapters.claude.isInstalled(),
|
|
528
|
+
}
|
|
529
|
+
const payload = {
|
|
530
|
+
version: config.version,
|
|
531
|
+
home: MIO_HOME,
|
|
532
|
+
serverScript: SERVER_SCRIPT,
|
|
533
|
+
serverScriptExists: fs.existsSync(SERVER_SCRIPT),
|
|
534
|
+
agents: config.agents || {},
|
|
535
|
+
codexInstalled: installed.codex,
|
|
536
|
+
opencodeInstalled: installed.opencode,
|
|
537
|
+
workbuddyInstalled: installed.workbuddy,
|
|
538
|
+
hermesInstalled: installed.hermes,
|
|
539
|
+
claudeInstalled: installed.claude,
|
|
540
|
+
codexNeedsRestart: Boolean(codexRestart),
|
|
541
|
+
opencodeNeedsRestart: Boolean(opencodeRestart),
|
|
542
|
+
codexRestartHint: codexRestart,
|
|
543
|
+
opencodeRestartHint: opencodeRestart,
|
|
544
|
+
hermesObserverReady: fs.existsSync(observer.hermesDbPath()),
|
|
545
|
+
claudeObserverReady: fs.existsSync(observer.claudeProjectsDir()),
|
|
546
|
+
opencodeObserverReady: fs.existsSync(observer.opencodeDbPath()),
|
|
547
|
+
observerRunning: observer.isRunning(MIO_HOME),
|
|
548
|
+
observerPid: observer.isRunning(MIO_HOME) ? observer.readPid(MIO_HOME) : null,
|
|
549
|
+
}
|
|
550
|
+
if (useJson) {
|
|
551
|
+
jsonOrText(payload, true)
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
console.log(`MIO_HOME: ${MIO_HOME}`)
|
|
555
|
+
console.log(`MCP Server: ${SERVER_SCRIPT}`)
|
|
556
|
+
console.log(`Codex MCP: ${installed.codex ? 'installed' : 'not installed'}`)
|
|
557
|
+
if (codexRestart) console.log(`Codex app: running with an older Mio config - fully restart Codex to load it`)
|
|
558
|
+
console.log(`OpenCode MCP: ${installed.opencode ? 'installed' : 'not installed'}`)
|
|
559
|
+
if (opencodeRestart) console.log(`OpenCode Desktop: running with an older Mio config - restart it to load it`)
|
|
560
|
+
console.log(`OpenCode observer: ${fs.existsSync(observer.opencodeDbPath()) ? 'ready' : 'no database'}`)
|
|
561
|
+
console.log(`WorkBuddy MCP: ${installed.workbuddy ? 'installed' : 'not installed'}`)
|
|
562
|
+
console.log(`Hermes MCP: ${installed.hermes ? 'installed' : 'not installed'}`)
|
|
563
|
+
console.log(`Hermes observer: ${fs.existsSync(observer.hermesDbPath()) ? 'ready' : 'no database'}`)
|
|
564
|
+
console.log(`Claude Code MCP: ${installed.claude ? 'installed' : 'not installed'}`)
|
|
565
|
+
console.log(`Claude Code observer: ${fs.existsSync(observer.claudeProjectsDir()) ? 'ready' : 'no transcripts'}`)
|
|
566
|
+
console.log(`Observer: ${observer.isRunning(MIO_HOME) ? 'running (pid ' + observer.readPid(MIO_HOME) + ')' : 'not running'}`)
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
function agentsCommand(useJson) {
|
|
570
|
+
const config = readConfig()
|
|
571
|
+
const agents = config.agents || {}
|
|
572
|
+
if (useJson) {
|
|
573
|
+
jsonOrText(agents, true)
|
|
574
|
+
return
|
|
575
|
+
}
|
|
576
|
+
const names = Object.keys(agents)
|
|
577
|
+
if (names.length === 0) {
|
|
578
|
+
console.log('No agents installed.')
|
|
579
|
+
return
|
|
580
|
+
}
|
|
581
|
+
for (const name of names) {
|
|
582
|
+
console.log(`${name}\t${agents[name].installedAt || ''}`)
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function installCommand(host, useJson) {
|
|
587
|
+
if (!host) {
|
|
588
|
+
console.error('Usage: mio install <codex|opencode|workbuddy|hermes|claude>')
|
|
589
|
+
process.exitCode = 1
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
const adapter = adapters[host]
|
|
593
|
+
if (!adapter) {
|
|
594
|
+
console.error(`Unknown host: ${host}`)
|
|
595
|
+
process.exitCode = 1
|
|
596
|
+
return
|
|
597
|
+
}
|
|
598
|
+
ensureHome()
|
|
599
|
+
const config = readConfig()
|
|
600
|
+
let result
|
|
601
|
+
try {
|
|
602
|
+
result = adapter.install({
|
|
603
|
+
node: process.execPath,
|
|
604
|
+
serverScript: SERVER_SCRIPT,
|
|
605
|
+
home: MIO_HOME,
|
|
606
|
+
workspace: process.cwd(),
|
|
607
|
+
})
|
|
608
|
+
} catch (error) {
|
|
609
|
+
console.error(`Failed to install ${host}: ${error.message || error}`)
|
|
610
|
+
process.exitCode = 1
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
if (result.changed) {
|
|
614
|
+
config.agents = config.agents || {}
|
|
615
|
+
config.agents[host] = {
|
|
616
|
+
installedAt: new Date().toISOString(),
|
|
617
|
+
configPath: result.configPath || null,
|
|
618
|
+
}
|
|
619
|
+
writeConfig(config)
|
|
620
|
+
}
|
|
621
|
+
try {
|
|
622
|
+
observer.runCycle(MIO_HOME, observer.loadState(MIO_HOME), observer.createSinks(MIO_HOME))
|
|
623
|
+
} catch (_) {}
|
|
624
|
+
let observerStarted = false
|
|
625
|
+
try {
|
|
626
|
+
observerStarted = observer.startBackground(MIO_HOME).started
|
|
627
|
+
} catch (_) {}
|
|
628
|
+
if (useJson) {
|
|
629
|
+
result.observer = {
|
|
630
|
+
running: observer.isRunning(MIO_HOME),
|
|
631
|
+
started: observerStarted,
|
|
632
|
+
}
|
|
633
|
+
jsonOrText(result, true)
|
|
634
|
+
return
|
|
635
|
+
}
|
|
636
|
+
console.log(result.message || `${host} adapter completed.`)
|
|
637
|
+
console.log(
|
|
638
|
+
observer.isRunning(MIO_HOME)
|
|
639
|
+
? 'Observer daemon running.'
|
|
640
|
+
: 'Observer daemon not running. Run `mio observe --start`.'
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function mcpCommand() {
|
|
645
|
+
require(SERVER_SCRIPT)
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function observeCommand(args) {
|
|
649
|
+
const flags = args.slice(1)
|
|
650
|
+
const useJson = flags.includes('--json')
|
|
651
|
+
if (flags.includes('--start')) {
|
|
652
|
+
const result = observer.startBackground(MIO_HOME)
|
|
653
|
+
if (useJson) return jsonOrText(result, true)
|
|
654
|
+
console.log(result.started ? 'Observer started.' : 'Observer already running.')
|
|
655
|
+
return
|
|
656
|
+
}
|
|
657
|
+
if (flags.includes('--stop')) {
|
|
658
|
+
const result = observer.stopBackground(MIO_HOME)
|
|
659
|
+
if (useJson) return jsonOrText(result, true)
|
|
660
|
+
console.log(result.stopped ? 'Observer stopped.' : 'Observer was not running.')
|
|
661
|
+
return
|
|
662
|
+
}
|
|
663
|
+
if (flags.includes('--status')) {
|
|
664
|
+
const running = observer.isRunning(MIO_HOME)
|
|
665
|
+
const pid = observer.readPid(MIO_HOME)
|
|
666
|
+
if (useJson) return jsonOrText({ running, pid }, true)
|
|
667
|
+
console.log(running ? 'Observer: running (pid ' + pid + ')' : 'Observer: not running')
|
|
668
|
+
return
|
|
669
|
+
}
|
|
670
|
+
if (flags.includes('--once')) {
|
|
671
|
+
const res = observer.runCycle(
|
|
672
|
+
MIO_HOME,
|
|
673
|
+
observer.loadState(MIO_HOME),
|
|
674
|
+
observer.createSinks(MIO_HOME)
|
|
675
|
+
)
|
|
676
|
+
if (useJson) return jsonOrText(res, true)
|
|
677
|
+
console.log('Observed files=' + res.files + ' lines=' + res.lines + ' outcomes=' + res.outcomes)
|
|
678
|
+
return
|
|
679
|
+
}
|
|
680
|
+
console.log('Observing WorkBuddy transcripts into ' + MIO_HOME + ' (Ctrl+C to stop)...')
|
|
681
|
+
observer.startForeground(MIO_HOME)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function help() {
|
|
685
|
+
console.log(`Mio Agent Runtime CLI
|
|
686
|
+
|
|
687
|
+
Usage:
|
|
688
|
+
mio init Initialize MIO_HOME
|
|
689
|
+
mio mcp Start Mio MCP server (stdio)
|
|
690
|
+
mio install <host> Install Mio into a host (codex|opencode|workbuddy|hermes|claude)
|
|
425
691
|
mio status Show runtime and adapter status
|
|
426
692
|
mio agents List installed agents
|
|
427
693
|
mio evolution status Show composed evolution module health
|
|
@@ -433,42 +699,57 @@ Usage:
|
|
|
433
699
|
mio evolution cutover apply --dry-run Dry-run a cutover plan without switching authority
|
|
434
700
|
mio observe Watch WorkBuddy transcripts and auto-ingest task outcomes
|
|
435
701
|
mio observe --start|--stop|--status|--once Manage the background observer daemon
|
|
702
|
+
mio recall "<query>" Search Mio memory from the terminal (same ranking as mio.memory.query)
|
|
703
|
+
mio traces Show recent observer traces (--type/--outcome/--agent/--since/--limit/--compact)
|
|
704
|
+
mio remember "<content>" Write a memory record from the terminal (same schema as mio.memory.record)
|
|
705
|
+
mio prune --days 30 Trim old traces/queries/reuse records and observe.log (--dry-run to preview; --memory needs --yes)
|
|
706
|
+
mio digest --days 7 Aggregate traces/memory/reuse into an actionable report (--write-back feeds agent context files; --json)
|
|
436
707
|
mio --json status Machine-readable status
|
|
437
708
|
mio --json agents Machine-readable agents
|
|
438
709
|
mio --json evolution status Machine-readable evolution module health
|
|
439
710
|
`)
|
|
440
711
|
}
|
|
441
|
-
|
|
712
|
+
|
|
442
713
|
async function main() {
|
|
443
|
-
const args = process.argv.slice(2)
|
|
444
|
-
const jsonIndex = args.indexOf('--json')
|
|
445
|
-
const useJson = jsonIndex !== -1
|
|
446
|
-
if (useJson) args.splice(jsonIndex, 1)
|
|
447
|
-
const command = args[0] || 'help'
|
|
448
|
-
|
|
449
|
-
switch (command) {
|
|
450
|
-
case 'init':
|
|
451
|
-
return initCommand()
|
|
452
|
-
case 'mcp':
|
|
453
|
-
return mcpCommand()
|
|
454
|
-
case 'install':
|
|
455
|
-
return installCommand(args[1], useJson)
|
|
456
|
-
case 'status':
|
|
457
|
-
return statusCommand(useJson)
|
|
458
|
-
case 'agents':
|
|
459
|
-
return agentsCommand(useJson)
|
|
714
|
+
const args = process.argv.slice(2)
|
|
715
|
+
const jsonIndex = args.indexOf('--json')
|
|
716
|
+
const useJson = jsonIndex !== -1
|
|
717
|
+
if (useJson) args.splice(jsonIndex, 1)
|
|
718
|
+
const command = args[0] || 'help'
|
|
719
|
+
|
|
720
|
+
switch (command) {
|
|
721
|
+
case 'init':
|
|
722
|
+
return initCommand()
|
|
723
|
+
case 'mcp':
|
|
724
|
+
return mcpCommand()
|
|
725
|
+
case 'install':
|
|
726
|
+
return installCommand(args[1], useJson)
|
|
727
|
+
case 'status':
|
|
728
|
+
return statusCommand(useJson)
|
|
729
|
+
case 'agents':
|
|
730
|
+
return agentsCommand(useJson)
|
|
460
731
|
case 'observe':
|
|
461
732
|
return observeCommand(args)
|
|
733
|
+
case 'recall':
|
|
734
|
+
return recallCommand(args, useJson)
|
|
735
|
+
case 'traces':
|
|
736
|
+
return tracesCommand(args, useJson)
|
|
737
|
+
case 'remember':
|
|
738
|
+
return rememberCommand(args, useJson)
|
|
739
|
+
case 'prune':
|
|
740
|
+
return pruneCommand(args, useJson)
|
|
741
|
+
case 'digest':
|
|
742
|
+
return digestCommand(args, useJson)
|
|
462
743
|
case 'evolution':
|
|
463
744
|
return evolutionCommand(args, useJson)
|
|
464
|
-
case 'help':
|
|
465
|
-
case '--help':
|
|
466
|
-
return help()
|
|
467
|
-
default:
|
|
468
|
-
console.error(`Unknown command: ${command}`)
|
|
469
|
-
help()
|
|
470
|
-
process.exitCode = 1
|
|
471
|
-
}
|
|
745
|
+
case 'help':
|
|
746
|
+
case '--help':
|
|
747
|
+
return help()
|
|
748
|
+
default:
|
|
749
|
+
console.error(`Unknown command: ${command}`)
|
|
750
|
+
help()
|
|
751
|
+
process.exitCode = 1
|
|
752
|
+
}
|
|
472
753
|
}
|
|
473
754
|
|
|
474
755
|
main().catch((error) => {
|