mio-agent-runtime 0.5.23 → 0.6.0
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 +485 -85
- package/bin/mio.js +1882 -78
- package/observe/observer.js +73 -15
- package/package.json +45 -45
- package/scripts/check-syntax.cjs +72 -0
- package/server/agent-store.js +140 -0
- package/server/evolution-report.js +311 -0
- package/server/experience-store.js +214 -0
- package/server/insight-store.js +118 -0
- package/server/llm-client.js +69 -0
- package/server/memory-store.js +505 -3
- package/server/mio-intelligence-mcp/index.js +126 -1521
- package/server/observer-store.js +219 -0
- package/server/policy-store.js +263 -0
- package/server/query-log.js +66 -0
- package/server/subscription-store.js +213 -0
- package/server/task-store.js +385 -0
package/bin/mio.js
CHANGED
|
@@ -21,8 +21,21 @@ const observer = require('../observe/observer.js')
|
|
|
21
21
|
const { getEvolutionStatus, formatEvolutionStatusText } = require('../server/runtime-modules.js')
|
|
22
22
|
const { createEvolutionCutoverTools } = require('../server/evolution-cutover.js')
|
|
23
23
|
const { createMemoryStore } = require('../server/memory-store.js')
|
|
24
|
+
const { createExperienceStore } = require('../server/experience-store.js')
|
|
25
|
+
const { createPolicyStore } = require('../server/policy-store.js')
|
|
26
|
+
const { CreativityEngine } = require('../server/creativity-engine.js')
|
|
27
|
+
const { createAgentStore } = require('../server/agent-store.js')
|
|
28
|
+
const { createInsightStore } = require('../server/insight-store.js')
|
|
29
|
+
const { createObserverStore } = require('../server/observer-store.js')
|
|
30
|
+
const { loadPhase0, renderPhase0Markdown } = require('../server/mio-intelligence-mcp/phase0.js')
|
|
31
|
+
const { listHostCapabilities } = require('../server/host-capabilities.js')
|
|
32
|
+
const { createTaskStore } = require('../server/task-store.js')
|
|
33
|
+
const { chatJson, llmConfig, isLlmConfigured } = require('../server/llm-client.js')
|
|
34
|
+
const { createQueryLog } = require('../server/query-log.js')
|
|
35
|
+
const { createSubscriptionStore, subscribeKey } = require('../server/subscription-store.js')
|
|
24
36
|
const { createRetention } = require('../server/retention.js')
|
|
25
37
|
const { createDigest } = require('../server/digest.js')
|
|
38
|
+
const { createEvolutionReport, formatEvolutionReportText } = require('../server/evolution-report.js')
|
|
26
39
|
|
|
27
40
|
function appendJsonl(file, value) {
|
|
28
41
|
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
@@ -96,6 +109,39 @@ function parseNumberOption(args, name) {
|
|
|
96
109
|
return value
|
|
97
110
|
}
|
|
98
111
|
|
|
112
|
+
// mio evolution report shares one implementation with mio.evolution.report
|
|
113
|
+
// (see ../server/evolution-report.js), so CLI and MCP cannot report different numbers.
|
|
114
|
+
function cliEvolutionReport() {
|
|
115
|
+
return createEvolutionReport({ dataDir: MIO_HOME, projectName })
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const EVOLUTION_REPORT_PERIODS = new Set(['24h', '7d', '30d', 'all'])
|
|
119
|
+
|
|
120
|
+
function evolutionReportCommand(args, useJson) {
|
|
121
|
+
const flags = args.slice(2)
|
|
122
|
+
const period = optionValue(flags, '--period')
|
|
123
|
+
if (period !== undefined && !EVOLUTION_REPORT_PERIODS.has(period)) {
|
|
124
|
+
console.error(`--period must be one of: ${[...EVOLUTION_REPORT_PERIODS].join(', ')}`)
|
|
125
|
+
process.exitCode = 1
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let result
|
|
130
|
+
try {
|
|
131
|
+
result = cliEvolutionReport().report({
|
|
132
|
+
project: optionValue(flags, '--project'),
|
|
133
|
+
period,
|
|
134
|
+
})
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.error(error.message || error)
|
|
137
|
+
process.exitCode = 1
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (useJson) return jsonOrText(result, true)
|
|
142
|
+
console.log(formatEvolutionReportText(result))
|
|
143
|
+
}
|
|
144
|
+
|
|
99
145
|
function evolutionStatus(useJson) {
|
|
100
146
|
const payload = getEvolutionStatus()
|
|
101
147
|
if (useJson) {
|
|
@@ -148,6 +194,7 @@ function printDualWriteRecord(record, useJson) {
|
|
|
148
194
|
|
|
149
195
|
async function evolutionCommand(args, useJson) {
|
|
150
196
|
if (args[1] === 'status') return evolutionStatus(useJson)
|
|
197
|
+
if (args[1] === 'report') return evolutionReportCommand(args, useJson)
|
|
151
198
|
try {
|
|
152
199
|
if (args[1] === 'shadow' && args[2] === 'record') {
|
|
153
200
|
return printShadowRecord(
|
|
@@ -218,7 +265,7 @@ async function evolutionCommand(args, useJson) {
|
|
|
218
265
|
process.exitCode = 1
|
|
219
266
|
return
|
|
220
267
|
}
|
|
221
|
-
console.error('Usage: mio evolution status|shadow record|dual-write record|cutover readiness|cutover apply|authority plan|migration plan')
|
|
268
|
+
console.error('Usage: mio evolution status|report|shadow record|dual-write record|cutover readiness|cutover apply|authority plan|migration plan')
|
|
222
269
|
process.exitCode = 1
|
|
223
270
|
}
|
|
224
271
|
|
|
@@ -238,6 +285,54 @@ function cliMemoryStore() {
|
|
|
238
285
|
})
|
|
239
286
|
}
|
|
240
287
|
|
|
288
|
+
// policy.check deliberately reads the *global* MIO_HOME rather than a
|
|
289
|
+
// per-project directory, so its history matches `mio recall` and `mio traces`.
|
|
290
|
+
// It shares the memory store's tokenizer and scoring, so risk evidence and
|
|
291
|
+
// related-memory ranking line up with `mio.memory.query`.
|
|
292
|
+
function cliPolicyStore() {
|
|
293
|
+
const memoryStore = cliMemoryStore()
|
|
294
|
+
return createPolicyStore({
|
|
295
|
+
dataDir: MIO_HOME,
|
|
296
|
+
projectName,
|
|
297
|
+
memoryStore,
|
|
298
|
+
})
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// The creativity engine is the single implementation behind mio.creativity.*;
|
|
302
|
+
// the CLI points it at the global MIO_HOME so a terminal `mio creativity list`
|
|
303
|
+
// sees the same hypotheses the MCP server would. generate/ferment need an LLM,
|
|
304
|
+
// so they use the shared client -- configured with LLM_API_URL / LLM_KEY /
|
|
305
|
+
// LLM_CHAT_MODEL, the same environment the MCP server reads. A local Ollama
|
|
306
|
+
// works: LLM_API_URL=http://localhost:11434/v1/chat/completions
|
|
307
|
+
function cliCreativityEngine() {
|
|
308
|
+
const creativityDir = path.join(MIO_HOME, 'creativity')
|
|
309
|
+
return new CreativityEngine(creativityDir, chatJson)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// Observed-agent telemetry (agents.jsonl + traces/memory/reuse cross-reference)
|
|
313
|
+
// shares one store with the MCP server, so CLI and MCP report identical agents.
|
|
314
|
+
function cliAgentStore() {
|
|
315
|
+
return createAgentStore({ dataDir: MIO_HOME, projectName })
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Task routing shares one store with mio.task.route. persistQuery stays false:
|
|
319
|
+
// the MCP call feeds the query log for auto-claim, but a terminal inspection
|
|
320
|
+
// must not write to queries.jsonl every time it runs.
|
|
321
|
+
function cliTaskStore() {
|
|
322
|
+
// Shares one query-log instance with nothing else here, but created from the
|
|
323
|
+
// same module the MCP server uses, so queries.jsonl has a single reader/writer
|
|
324
|
+
// implementation. cliMemoryStore deliberately does NOT get one: a terminal
|
|
325
|
+
// recall is never followed by a task_outcome, so recording those queries
|
|
326
|
+
// would only add noise.
|
|
327
|
+
return createTaskStore({
|
|
328
|
+
dataDir: MIO_HOME,
|
|
329
|
+
projectName,
|
|
330
|
+
memoryStore: cliMemoryStore(),
|
|
331
|
+
queryLog: createQueryLog({ dataDir: MIO_HOME }),
|
|
332
|
+
agentId: () => 'cli',
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
|
|
241
336
|
function splitTagsOption(args, name) {
|
|
242
337
|
const raw = optionValue(args, name)
|
|
243
338
|
if (raw === undefined) return []
|
|
@@ -484,102 +579,1763 @@ function rememberCommand(args, useJson) {
|
|
|
484
579
|
console.log(`Recorded ${record.id} (kind=${record.kind}, project=${record.project || 'global'}, scope=${record.scope})`)
|
|
485
580
|
}
|
|
486
581
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
582
|
+
// Every flag that consumes the next token. A positional directly after one of
|
|
583
|
+
// these is that flag's value, not an id.
|
|
584
|
+
const VALUE_FLAGS = new Set([
|
|
585
|
+
'--ids', '--reason', '--project', '--limit', '--scope', '--keep',
|
|
586
|
+
'--by', '--notes', '--source-agent', '--target-agent', '--experience-id', '--status',
|
|
587
|
+
])
|
|
588
|
+
|
|
589
|
+
// Accept ids both as `--ids a,b,c` and as bare positionals
|
|
590
|
+
// (`mio memory archive mem_a mem_b`).
|
|
591
|
+
function collectIds(flags) {
|
|
592
|
+
const ids = []
|
|
593
|
+
for (let i = 0; i < flags.length; i += 1) {
|
|
594
|
+
const token = flags[i]
|
|
595
|
+
if (token.startsWith('--')) continue
|
|
596
|
+
const previous = flags[i - 1]
|
|
597
|
+
if (previous && VALUE_FLAGS.has(previous)) continue
|
|
598
|
+
// Ids never contain commas, so a comma-separated positional is unambiguous.
|
|
599
|
+
for (const id of token.split(',')) {
|
|
600
|
+
const trimmed = id.trim()
|
|
601
|
+
if (trimmed) ids.push(trimmed)
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const raw = optionValue(flags, '--ids')
|
|
605
|
+
if (raw !== undefined) {
|
|
606
|
+
for (const id of raw.split(',')) {
|
|
607
|
+
const trimmed = id.trim()
|
|
608
|
+
if (trimmed) ids.push(trimmed)
|
|
609
|
+
}
|
|
494
610
|
}
|
|
611
|
+
return [...new Set(ids)]
|
|
495
612
|
}
|
|
496
613
|
|
|
497
|
-
function
|
|
498
|
-
|
|
499
|
-
|
|
614
|
+
function printMemoryAnalyze(result) {
|
|
615
|
+
const project = result.project || 'all'
|
|
616
|
+
console.log(
|
|
617
|
+
`Memory analyze: ${result.total} active, ${result.archived} archived (project=${project})`,
|
|
618
|
+
)
|
|
619
|
+
console.log(`layers: project=${result.layers.project} global=${result.layers.global}`)
|
|
620
|
+
const kinds = Object.entries(result.byKind)
|
|
621
|
+
if (kinds.length > 0) {
|
|
622
|
+
console.log(`by kind: ${kinds.map(([kind, count]) => `${kind}=${count}`).join(', ')}`)
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (result.duplicates.length > 0) {
|
|
626
|
+
console.log(`\nDuplicate groups: ${result.duplicates.length}`)
|
|
627
|
+
for (const group of result.duplicates) {
|
|
628
|
+
console.log(`- ${group.size} record(s): ${group.records.map((record) => record.id).join(', ')}`)
|
|
629
|
+
const preview = String(group.records[0].content || '')
|
|
630
|
+
if (preview) {
|
|
631
|
+
console.log(` ${preview.length > 80 ? `${preview.slice(0, 80)}...` : preview}`)
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (result.lowQuality.length > 0) {
|
|
637
|
+
console.log(`\nLow quality: ${result.issues.lowQuality} record(s)`)
|
|
638
|
+
for (const record of result.lowQuality) {
|
|
639
|
+
console.log(`- ${record.id} [${record.kind || 'note'}] ${record.issues.join(', ')}`)
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (result.issues.duplicatesSkipped) {
|
|
644
|
+
console.log('\nDuplicate scan skipped: too many active records to compare pairwise.')
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (result.suggestions.length > 0) {
|
|
648
|
+
console.log('')
|
|
649
|
+
for (const suggestion of result.suggestions) console.log(`suggestion: ${suggestion}`)
|
|
650
|
+
}
|
|
500
651
|
}
|
|
501
652
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
653
|
+
// Merging divergent content is refused by default; when that happens the
|
|
654
|
+
// caller needs to see *what* differs before deciding, so print the candidates.
|
|
655
|
+
function printMemoryMerge(result) {
|
|
656
|
+
if (!result.merged) {
|
|
657
|
+
console.log(`No merge applied (project=${result.project || 'current'}): ${result.reason}`)
|
|
658
|
+
if (result.divergent.length > 0) {
|
|
659
|
+
console.log('\nDivergent content — inspect before choosing a survivor:')
|
|
660
|
+
for (const record of result.divergent) {
|
|
661
|
+
console.log(`- ${record.id} (${record.contentLength} chars, ${record.timestamp || 'no timestamp'})`)
|
|
662
|
+
console.log(` ${record.content.split('\n')[0].slice(0, 110)}`)
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
if (result.notFound.length > 0) console.log(`\nnot found: ${result.notFound.join(', ')}`)
|
|
666
|
+
if (result.outOfScope && result.outOfScope.length > 0) {
|
|
667
|
+
console.log(`\nbelongs to another project: ${result.outOfScope.join(', ')}`)
|
|
668
|
+
}
|
|
669
|
+
if (result.hint) console.log(`\n${result.hint}`)
|
|
670
|
+
process.exitCode = 1
|
|
505
671
|
return
|
|
506
672
|
}
|
|
507
|
-
|
|
673
|
+
|
|
674
|
+
// A merge that archived nothing is a no-op: every requested id was already
|
|
675
|
+
// archived (i.e. these were merged before). Report it as failure the same way
|
|
676
|
+
// archive does, so scripts do not read success out of an idempotent re-run.
|
|
677
|
+
if (result.archivedCount === 0) {
|
|
678
|
+
console.log(`No merge applied (project=${result.project || 'current'}): nothing left to merge`)
|
|
679
|
+
if (result.skipped.length > 0) {
|
|
680
|
+
console.log(`already archived (previously merged): ${result.skipped.join(', ')}`)
|
|
681
|
+
console.log(`superseded by: ${result.survivor.id}`)
|
|
682
|
+
}
|
|
683
|
+
if (result.notFound.length > 0) console.log(`not found: ${result.notFound.join(', ')}`)
|
|
684
|
+
process.exitCode = 1
|
|
685
|
+
return
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
console.log(
|
|
689
|
+
`Merged ${result.archivedCount} record(s) into ${result.survivor.id} (project=${result.project || 'current'})`,
|
|
690
|
+
)
|
|
691
|
+
console.log(`survivor: ${result.survivor.contentLength} chars, ${result.survivor.timestamp || 'no timestamp'}`)
|
|
692
|
+
if (result.divergentContent) console.log('note: content differed across members; the survivor body was chosen explicitly')
|
|
693
|
+
console.log(`supersedes: ${result.survivor.supersedes.join(', ')}`)
|
|
694
|
+
if (result.skipped.length > 0) console.log(`already archived: ${result.skipped.join(', ')}`)
|
|
695
|
+
if (result.notFound.length > 0) console.log(`not found: ${result.notFound.join(', ')}`)
|
|
696
|
+
console.log(`Undo with: mio memory restore --ids ${result.archived.join(',')}`)
|
|
508
697
|
}
|
|
509
698
|
|
|
510
|
-
function
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
699
|
+
function memoryUsage() {
|
|
700
|
+
console.log(`Usage:
|
|
701
|
+
mio memory analyze Report duplicates, low-quality records and kind histogram
|
|
702
|
+
mio memory archive --ids a,b Archive records (soft delete; hidden from recall/analyze)
|
|
703
|
+
mio memory restore --ids a,b Un-archive previously archived records
|
|
704
|
+
mio memory forget --ids a,b PERMANENTLY delete records (--yes required; writes an audit entry)
|
|
705
|
+
mio memory merge --ids a,b Merge duplicates: one survives, the rest are archived
|
|
706
|
+
mio memory migrate --ids a,b --scope global Move records between project and global layers
|
|
707
|
+
|
|
708
|
+
Options:
|
|
709
|
+
--ids a,b,c Memory record ids (also accepted as bare positionals)
|
|
710
|
+
--keep id Merge: which record's content survives (default: newest)
|
|
711
|
+
--allow-divergent Merge: permit merging records whose content differs (requires --keep)
|
|
712
|
+
--scope global|project Target layer for migrate
|
|
713
|
+
--project name Project filter (defaults to current directory name)
|
|
714
|
+
--reason text Optional note stored on the record
|
|
715
|
+
--limit n Max duplicate groups / low-quality rows to show (analyze, 1-20)
|
|
716
|
+
--yes Apply archive/merge (without it they only preview)
|
|
717
|
+
--json Machine-readable output
|
|
718
|
+
`)
|
|
516
719
|
}
|
|
517
720
|
|
|
518
|
-
function
|
|
519
|
-
const
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
hermes: adapters.hermes.isInstalled(),
|
|
527
|
-
claude: adapters.claude.isInstalled(),
|
|
721
|
+
function memoryCommand(args, useJson) {
|
|
722
|
+
const sub = args[1]
|
|
723
|
+
const flags = args.slice(2)
|
|
724
|
+
|
|
725
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
726
|
+
memoryUsage()
|
|
727
|
+
if (!sub) process.exitCode = 1
|
|
728
|
+
return
|
|
528
729
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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,
|
|
730
|
+
if (!['analyze', 'archive', 'restore', 'forget', 'merge', 'migrate'].includes(sub)) {
|
|
731
|
+
console.error(`Unknown memory subcommand: ${sub}`)
|
|
732
|
+
memoryUsage()
|
|
733
|
+
process.exitCode = 1
|
|
734
|
+
return
|
|
549
735
|
}
|
|
550
|
-
|
|
551
|
-
|
|
736
|
+
|
|
737
|
+
const store = cliMemoryStore()
|
|
738
|
+
|
|
739
|
+
if (sub === 'analyze') {
|
|
740
|
+
let result
|
|
741
|
+
try {
|
|
742
|
+
result = store.analyzeMemory({
|
|
743
|
+
project: optionValue(flags, '--project'),
|
|
744
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
745
|
+
})
|
|
746
|
+
} catch (error) {
|
|
747
|
+
console.error(error.message || error)
|
|
748
|
+
process.exitCode = 1
|
|
749
|
+
return
|
|
750
|
+
}
|
|
751
|
+
if (useJson) return jsonOrText(result, true)
|
|
752
|
+
return printMemoryAnalyze(result)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const ids = collectIds(flags)
|
|
756
|
+
if (ids.length === 0) {
|
|
757
|
+
console.error(`mio memory ${sub} requires --ids <id,id,...> (or bare ids)`)
|
|
758
|
+
process.exitCode = 1
|
|
552
759
|
return
|
|
553
760
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
761
|
+
if (sub === 'merge' && ids.length < 2) {
|
|
762
|
+
console.error('mio memory merge requires at least 2 ids (nothing to merge otherwise)')
|
|
763
|
+
process.exitCode = 1
|
|
764
|
+
return
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
if ((sub === 'archive' || sub === 'merge' || sub === 'forget') && !flagPresent(flags, '--yes')) {
|
|
768
|
+
// Archiving hides records from recall, merging additionally rewrites the
|
|
769
|
+
// survivor's supersedes list, and forgetting deletes outright. Preview first
|
|
770
|
+
// so a mistyped id cannot silently destroy data.
|
|
771
|
+
const previewProject = optionValue(flags, '--project') || projectName() || 'current'
|
|
772
|
+
if (useJson) {
|
|
773
|
+
jsonOrText(
|
|
774
|
+
{
|
|
775
|
+
preview: true,
|
|
776
|
+
applied: false,
|
|
777
|
+
project: previewProject,
|
|
778
|
+
ids,
|
|
779
|
+
keep: optionValue(flags, '--keep') || null,
|
|
780
|
+
reversible: sub !== 'forget',
|
|
781
|
+
hint:
|
|
782
|
+
sub === 'forget'
|
|
783
|
+
? 'PERMANENT: re-run with --yes to delete. Consider mio memory archive instead.'
|
|
784
|
+
: 'Re-run with --yes to apply.',
|
|
785
|
+
},
|
|
786
|
+
true,
|
|
787
|
+
)
|
|
788
|
+
} else if (sub === 'forget') {
|
|
789
|
+
// Show *what* is about to disappear, not just the ids: this is the one
|
|
790
|
+
// memory operation that cannot be undone.
|
|
791
|
+
const all = readJsonl(cliMemoryStore().memoryPath)
|
|
792
|
+
const idSet = new Set(ids)
|
|
793
|
+
const matched = all.filter((r) => idSet.has(r.id))
|
|
794
|
+
console.log(`Forget preview: ${ids.length} id(s) (project=${previewProject})`)
|
|
795
|
+
for (const record of matched) {
|
|
796
|
+
console.log(` ${record.id} ${String(record.content || '').replace(/\s+/g, ' ').slice(0, 80)}`)
|
|
797
|
+
}
|
|
798
|
+
for (const id of ids) {
|
|
799
|
+
if (!matched.some((r) => r.id === id)) console.log(` ${id} (not found)`)
|
|
800
|
+
}
|
|
801
|
+
console.log('')
|
|
802
|
+
console.log('WARNING: this permanently deletes the record(s) and cannot be undone.')
|
|
803
|
+
console.log('An audit entry (excerpt only) is written to memory-forget-audit.jsonl.')
|
|
804
|
+
console.log(`Reversible alternative: mio memory archive --ids ${ids.join(',')} --yes`)
|
|
805
|
+
console.log('Re-run with --yes to delete permanently.')
|
|
806
|
+
} else {
|
|
807
|
+
// Archive keeps its historical "Archive preview" wording; merge is newer
|
|
808
|
+
// and uses a lowercase verb to read naturally mid-sentence.
|
|
809
|
+
const label = sub === 'merge' ? 'merge preview' : 'Archive preview'
|
|
810
|
+
console.log(`${label}: ${ids.length} id(s) (project=${previewProject})`)
|
|
811
|
+
console.log(` ${ids.join(', ')}`)
|
|
812
|
+
if (sub === 'merge') {
|
|
813
|
+
const keep = optionValue(flags, '--keep')
|
|
814
|
+
console.log(` survivor: ${keep || 'newest by timestamp (default)'}`)
|
|
815
|
+
}
|
|
816
|
+
console.log(`Re-run with --yes to apply. Undo with: mio memory restore --ids <ids>`)
|
|
817
|
+
}
|
|
818
|
+
process.exitCode = 1
|
|
819
|
+
return
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
let result
|
|
823
|
+
try {
|
|
824
|
+
if (sub === 'archive' || sub === 'restore') {
|
|
825
|
+
result = store.archiveMemory({
|
|
826
|
+
ids,
|
|
827
|
+
project: optionValue(flags, '--project'),
|
|
828
|
+
restore: sub === 'restore',
|
|
829
|
+
reason: optionValue(flags, '--reason'),
|
|
830
|
+
})
|
|
831
|
+
} else if (sub === 'merge') {
|
|
832
|
+
result = store.mergeMemory({
|
|
833
|
+
ids,
|
|
834
|
+
project: optionValue(flags, '--project'),
|
|
835
|
+
keep: optionValue(flags, '--keep'),
|
|
836
|
+
allowDivergent: flagPresent(flags, '--allow-divergent'),
|
|
837
|
+
reason: optionValue(flags, '--reason'),
|
|
838
|
+
})
|
|
839
|
+
} else if (sub === 'forget') {
|
|
840
|
+
result = store.forgetMemory({
|
|
841
|
+
ids,
|
|
842
|
+
project: optionValue(flags, '--project'),
|
|
843
|
+
reason: optionValue(flags, '--reason'),
|
|
844
|
+
by: 'cli',
|
|
845
|
+
})
|
|
846
|
+
} else {
|
|
847
|
+
result = store.migrateMemory({
|
|
848
|
+
ids,
|
|
849
|
+
scope: optionValue(flags, '--scope'),
|
|
850
|
+
project: optionValue(flags, '--project'),
|
|
851
|
+
reason: optionValue(flags, '--reason'),
|
|
852
|
+
})
|
|
853
|
+
}
|
|
854
|
+
} catch (error) {
|
|
855
|
+
console.error(error.message || error)
|
|
856
|
+
process.exitCode = 1
|
|
857
|
+
return
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
if (useJson) return jsonOrText(result, true)
|
|
861
|
+
|
|
862
|
+
if (sub === 'merge') return printMemoryMerge(result)
|
|
863
|
+
|
|
864
|
+
if (sub === 'forget') {
|
|
865
|
+
console.log(`Forgot ${result.forgottenCount} record(s) permanently (project=${result.project || 'current'})`)
|
|
866
|
+
if (result.forgotten.length > 0) console.log(`deleted: ${result.forgotten.join(', ')}`)
|
|
867
|
+
if (result.notFound.length > 0) console.log(`not found: ${result.notFound.join(', ')}`)
|
|
868
|
+
console.log(`audit: ${result.auditPath}`)
|
|
869
|
+
// Everything missing is a typo; do not let a no-op read as success.
|
|
870
|
+
if (result.forgottenCount === 0) {
|
|
871
|
+
process.exitCode = 1
|
|
872
|
+
return
|
|
873
|
+
}
|
|
874
|
+
// No undo is possible -- say so, and point at what should have been used.
|
|
875
|
+
console.log('This cannot be undone. (Next time consider `mio memory archive`, which is reversible.)')
|
|
876
|
+
return
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
if (sub === 'migrate') {
|
|
880
|
+
const target = result.scope === 'global' ? 'global' : `project=${result.project || 'current'}`
|
|
881
|
+
console.log(`Migrated ${result.updatedCount} record(s) to ${target}`)
|
|
882
|
+
if (result.updated.length > 0) console.log(`updated: ${result.updated.join(', ')}`)
|
|
883
|
+
if (result.unchanged.length > 0) console.log(`already ${result.scope}: ${result.unchanged.join(', ')}`)
|
|
884
|
+
if (result.notFound.length > 0) console.log(`not found: ${result.notFound.join(', ')}`)
|
|
885
|
+
// Every requested id missing is almost certainly a typo; signal it so
|
|
886
|
+
// scripts do not treat a no-op as success.
|
|
887
|
+
if (result.updatedCount === 0 && result.unchanged.length === 0) {
|
|
888
|
+
process.exitCode = 1
|
|
889
|
+
return
|
|
890
|
+
}
|
|
891
|
+
if (result.updated.length > 0) {
|
|
892
|
+
const back = result.scope === 'global' ? 'project' : 'global'
|
|
893
|
+
console.log(`Undo with: mio memory migrate --ids ${result.updated.join(',')} --scope ${back}`)
|
|
894
|
+
}
|
|
895
|
+
return
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const verb = sub === 'restore' ? 'Restored' : 'Archived'
|
|
899
|
+
console.log(`${verb} ${result.archivedCount} record(s) (project=${result.project || 'current'})`)
|
|
900
|
+
if (result.archived.length > 0) console.log(`${sub}d: ${result.archived.join(', ')}`)
|
|
901
|
+
if (result.skipped.length > 0) console.log(`already ${sub}d: ${result.skipped.join(', ')}`)
|
|
902
|
+
if (result.notFound.length > 0) console.log(`not found: ${result.notFound.join(', ')}`)
|
|
903
|
+
if (result.archivedCount === 0) {
|
|
904
|
+
// Nothing changed: either the ids do not exist or they were already in the
|
|
905
|
+
// target state. Either way the caller's intent was not fulfilled, so say so
|
|
906
|
+
// instead of silently exiting 0.
|
|
907
|
+
console.error(`No records ${sub}d. Check the ids, or run: mio memory analyze`)
|
|
908
|
+
process.exitCode = 1
|
|
909
|
+
return
|
|
910
|
+
}
|
|
911
|
+
if (sub === 'archive' && result.archived.length > 0) {
|
|
912
|
+
console.log(`Undo with: mio memory restore --ids ${result.archived.join(',')}`)
|
|
913
|
+
}
|
|
567
914
|
}
|
|
568
915
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
916
|
+
// The observer auto-claims experience reuse, but an unconfirmed auto_claim
|
|
917
|
+
// never counts as `verified` and so never feeds memory ranking or task
|
|
918
|
+
// routing. In practice most claims sat unconfirmed forever because confirming
|
|
919
|
+
// was only possible through the MCP server.
|
|
920
|
+
function cliExperienceStore() {
|
|
921
|
+
return createExperienceStore({
|
|
922
|
+
dataDir: MIO_HOME,
|
|
923
|
+
projectName,
|
|
924
|
+
})
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function experienceUsage() {
|
|
928
|
+
console.log(`Usage:
|
|
929
|
+
mio experience list List reuse records (--status/--target-agent/--project/--limit)
|
|
930
|
+
mio experience confirm --ids a,b Confirm auto-claimed reuse (bulk supported)
|
|
931
|
+
mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse manually
|
|
932
|
+
|
|
933
|
+
Options:
|
|
934
|
+
--ids a,b,c Record ids (also accepted as bare positionals; confirm only)
|
|
935
|
+
--status pending|confirmed|verified|auto_claim|agent_report|all Filter for list (default all)
|
|
936
|
+
--target-agent name Only records reused by this agent
|
|
937
|
+
--experience-id id The memory/experience that was reused
|
|
938
|
+
--source-agent name Agent the experience came from
|
|
939
|
+
--reuse --behavior-changed --outcome-improved Booleans describing the reuse
|
|
940
|
+
--outcome-improved Confirm: accept the claim (--no-improved rejects it)
|
|
941
|
+
--by name Who is confirming (default: cli)
|
|
942
|
+
--notes text Optional note
|
|
943
|
+
--force Re-confirm records that are already confirmed
|
|
944
|
+
--project name Project filter (defaults to current directory name)
|
|
945
|
+
--json Machine-readable output
|
|
946
|
+
`)
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function printExperienceList(result) {
|
|
950
|
+
console.log(
|
|
951
|
+
`Experience reuse: ${result.count} shown of ${result.total} (project=${result.project || 'all'}, status=${result.status})`,
|
|
952
|
+
)
|
|
953
|
+
if (result.count === 0) {
|
|
954
|
+
console.log('No reuse records match.')
|
|
955
|
+
return
|
|
956
|
+
}
|
|
957
|
+
result.records.forEach((record, index) => {
|
|
958
|
+
const state = record.confirmed ? 'confirmed' : 'pending'
|
|
959
|
+
const badge = record.reuse && record.behaviorChanged && record.outcomeImproved ? 'verified' : state
|
|
960
|
+
const arrow = `${record.sourceAgent || '?'} -> ${record.targetAgent || '?'}`
|
|
961
|
+
const flags = [
|
|
962
|
+
record.reuse ? 'reused' : null,
|
|
963
|
+
record.behaviorChanged ? 'changed' : null,
|
|
964
|
+
record.outcomeImproved ? 'improved' : null,
|
|
965
|
+
]
|
|
966
|
+
.filter(Boolean)
|
|
967
|
+
.join(',')
|
|
968
|
+
console.log(`${index + 1}. [${badge}] ${record.experienceId} ${arrow}`)
|
|
969
|
+
console.log(
|
|
970
|
+
` ${flags || 'no effect flags'} | ${(record.timestamp || '').slice(0, 19)} | ${record.id}`,
|
|
971
|
+
)
|
|
972
|
+
if (record.notes) console.log(` note: ${record.notes}`)
|
|
973
|
+
})
|
|
974
|
+
// Only auto_claims are confirmable — experience.confirm rejects anything else.
|
|
975
|
+
const pending = result.records
|
|
976
|
+
.filter((record) => record.source === 'auto_claim' && !record.confirmed)
|
|
977
|
+
.map((record) => record.id)
|
|
978
|
+
if (pending.length > 0) {
|
|
979
|
+
console.log('')
|
|
980
|
+
console.log(
|
|
981
|
+
`${pending.length} unconfirmed auto-claim(s) shown. Unconfirmed auto-claims never count as verified, so they do not affect ranking or routing.`,
|
|
982
|
+
)
|
|
983
|
+
console.log(`Confirm with: mio experience confirm --ids ${pending.join(',')}`)
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function experienceCommand(args, useJson) {
|
|
988
|
+
const sub = args[1]
|
|
989
|
+
const flags = args.slice(2)
|
|
990
|
+
|
|
991
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
992
|
+
experienceUsage()
|
|
993
|
+
if (!sub) process.exitCode = 1
|
|
994
|
+
return
|
|
995
|
+
}
|
|
996
|
+
if (!['list', 'confirm', 'reuse'].includes(sub)) {
|
|
997
|
+
console.error(`Unknown experience subcommand: ${sub}`)
|
|
998
|
+
experienceUsage()
|
|
999
|
+
process.exitCode = 1
|
|
1000
|
+
return
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const store = cliExperienceStore()
|
|
1004
|
+
|
|
1005
|
+
if (sub === 'list') {
|
|
1006
|
+
let result
|
|
1007
|
+
try {
|
|
1008
|
+
result = store.listReuse({
|
|
1009
|
+
status: optionValue(flags, '--status'),
|
|
1010
|
+
targetAgent: optionValue(flags, '--target-agent'),
|
|
1011
|
+
project: optionValue(flags, '--project'),
|
|
1012
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
1013
|
+
})
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
console.error(error.message || error)
|
|
1016
|
+
process.exitCode = 1
|
|
1017
|
+
return
|
|
1018
|
+
}
|
|
1019
|
+
if (useJson) return jsonOrText(result, true)
|
|
1020
|
+
return printExperienceList(result)
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
let result
|
|
1024
|
+
try {
|
|
1025
|
+
if (sub === 'confirm') {
|
|
1026
|
+
const ids = collectIds(flags)
|
|
1027
|
+
if (ids.length === 0) {
|
|
1028
|
+
console.error('mio experience confirm requires --ids <id,id,...> (or bare ids)')
|
|
1029
|
+
process.exitCode = 1
|
|
1030
|
+
return
|
|
1031
|
+
}
|
|
1032
|
+
// Absent = keep whatever the auto-claim recorded; --no-improved rejects it.
|
|
1033
|
+
const improved = flagPresent(flags, '--no-improved')
|
|
1034
|
+
? false
|
|
1035
|
+
: flagPresent(flags, '--outcome-improved')
|
|
1036
|
+
? true
|
|
1037
|
+
: undefined
|
|
1038
|
+
result = store.confirmReuse({
|
|
1039
|
+
ids,
|
|
1040
|
+
outcomeImproved: improved,
|
|
1041
|
+
confirmedBy: optionValue(flags, '--by') || 'cli',
|
|
1042
|
+
notes: optionValue(flags, '--notes'),
|
|
1043
|
+
force: flagPresent(flags, '--force'),
|
|
1044
|
+
})
|
|
1045
|
+
} else {
|
|
1046
|
+
result = store.recordReuse({
|
|
1047
|
+
sourceAgent: optionValue(flags, '--source-agent'),
|
|
1048
|
+
targetAgent: optionValue(flags, '--target-agent'),
|
|
1049
|
+
experienceId: optionValue(flags, '--experience-id'),
|
|
1050
|
+
reuse: flagPresent(flags, '--reuse'),
|
|
1051
|
+
behaviorChanged: flagPresent(flags, '--behavior-changed'),
|
|
1052
|
+
outcomeImproved: flagPresent(flags, '--outcome-improved'),
|
|
1053
|
+
project: optionValue(flags, '--project'),
|
|
1054
|
+
notes: optionValue(flags, '--notes'),
|
|
1055
|
+
})
|
|
1056
|
+
}
|
|
1057
|
+
} catch (error) {
|
|
1058
|
+
console.error(error.message || error)
|
|
1059
|
+
process.exitCode = 1
|
|
574
1060
|
return
|
|
575
1061
|
}
|
|
576
|
-
|
|
577
|
-
if (
|
|
578
|
-
|
|
1062
|
+
|
|
1063
|
+
if (useJson) return jsonOrText(result, true)
|
|
1064
|
+
|
|
1065
|
+
if (sub === 'reuse') {
|
|
1066
|
+
console.log(
|
|
1067
|
+
`Recorded reuse ${result.evidence.id}: ${result.evidence.sourceAgent} -> ${result.evidence.targetAgent} (${result.evidence.experienceId})`,
|
|
1068
|
+
)
|
|
579
1069
|
return
|
|
580
1070
|
}
|
|
581
|
-
|
|
582
|
-
|
|
1071
|
+
|
|
1072
|
+
console.log(`Confirmed ${result.confirmedCount} reuse record(s)`)
|
|
1073
|
+
if (result.confirmedIds.length > 0) console.log(`confirmed: ${result.confirmedIds.join(', ')}`)
|
|
1074
|
+
if (result.rejected.length > 0) {
|
|
1075
|
+
console.log(`skipped (not auto_claim): ${result.rejected.join(', ')}`)
|
|
1076
|
+
}
|
|
1077
|
+
if (result.alreadyConfirmed.length > 0) {
|
|
1078
|
+
console.log(`already confirmed: ${result.alreadyConfirmed.join(', ')} (use --force to redo)`)
|
|
1079
|
+
}
|
|
1080
|
+
if (result.notFound.length > 0) console.log(`not found: ${result.notFound.join(', ')}`)
|
|
1081
|
+
if (result.confirmedCount === 0) {
|
|
1082
|
+
console.error('Nothing confirmed. Check the ids, or run: mio experience list --status pending')
|
|
1083
|
+
process.exitCode = 1
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// mio.policy.check has always been able to answer "has this action failed
|
|
1088
|
+
// before?", but only from inside an MCP session -- so in practice nobody asked
|
|
1089
|
+
// before running the command. This gives it a terminal entry point.
|
|
1090
|
+
function policyUsage() {
|
|
1091
|
+
console.log(`Usage:
|
|
1092
|
+
mio policy check "<action>" Historical risk for an action, from trace + memory evidence
|
|
1093
|
+
|
|
1094
|
+
Options:
|
|
1095
|
+
--project name Project filter (defaults to current directory name)
|
|
1096
|
+
--json Machine-readable output (same shape as mio.policy.check)
|
|
1097
|
+
|
|
1098
|
+
Examples:
|
|
1099
|
+
mio policy check "npm publish"
|
|
1100
|
+
mio policy check "git reset --hard" --project akemi-mio
|
|
1101
|
+
|
|
1102
|
+
Reading the result:
|
|
1103
|
+
unknown no history for this action; treat as normal risk
|
|
1104
|
+
low history exists and is mostly clean
|
|
1105
|
+
moderate add verification before proceeding
|
|
1106
|
+
low/high driven by the failure share of matching traces`)
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
function printPolicyCheck(result) {
|
|
1110
|
+
const level = result.riskLevel.toUpperCase()
|
|
1111
|
+
console.log(`Policy check: "${result.action}" (project=${result.project || 'all'})`)
|
|
1112
|
+
console.log(`risk: ${level}${result.risk === null ? '' : ` (${result.risk})`}`)
|
|
1113
|
+
if (result.total === 0) {
|
|
1114
|
+
console.log('evidence: no matching traces')
|
|
1115
|
+
} else {
|
|
1116
|
+
console.log(`evidence: ${result.total} matching trace(s), ${result.failures} failure(s)`)
|
|
1117
|
+
const outcomes = Object.entries(result.outcomeCounts)
|
|
1118
|
+
if (outcomes.length > 0) {
|
|
1119
|
+
console.log(`outcomes: ${outcomes.map(([k, v]) => `${k}=${v}`).join(', ')}`)
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// A risk level derived from tokens that match most of the corpus looks
|
|
1124
|
+
// authoritative but means nothing. Say so instead of letting it stand.
|
|
1125
|
+
const diag = result.diagnostics
|
|
1126
|
+
if (diag && diag.lowSignal) {
|
|
1127
|
+
console.log('')
|
|
1128
|
+
console.log(
|
|
1129
|
+
`WARNING: low-signal match. Every token in this action (${diag.genericTokens.join(', ')}) ` +
|
|
1130
|
+
'appears in most traces regardless of subject, so the level above is not meaningful.',
|
|
1131
|
+
)
|
|
1132
|
+
console.log('Try a more specific action, e.g. mio policy check "npm publish".')
|
|
1133
|
+
} else if (diag && diag.lowSample) {
|
|
1134
|
+
console.log('')
|
|
1135
|
+
console.log(
|
|
1136
|
+
`NOTE: only ${diag.sampleSize} matching trace(s); treat the level above as a weak signal.`,
|
|
1137
|
+
)
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
console.log(`\n${result.suggestion}`)
|
|
1141
|
+
|
|
1142
|
+
if (result.failureExamples.length > 0) {
|
|
1143
|
+
console.log('\nRecent failures:')
|
|
1144
|
+
for (const example of result.failureExamples) {
|
|
1145
|
+
const when = example.timestamp ? example.timestamp.slice(0, 19).replace('T', ' ') : 'unknown time'
|
|
1146
|
+
console.log(`- [${example.outcome || 'failure'}] ${when}`)
|
|
1147
|
+
console.log(` ${example.summary}`)
|
|
1148
|
+
if (example.trace_id) console.log(` trace: ${example.trace_id}`)
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
if (result.guidance.level !== 'none') {
|
|
1153
|
+
console.log(`\nGuidance (${result.guidance.level}): ${result.guidance.rationale}`)
|
|
1154
|
+
if (result.guidance.avoid.length > 0) {
|
|
1155
|
+
console.log('avoid repeating:')
|
|
1156
|
+
for (const item of result.guidance.avoid) console.log(`- ${item}`)
|
|
1157
|
+
}
|
|
1158
|
+
for (const step of result.guidance.verificationSteps) console.log(`- ${step}`)
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (result.related_memories.length > 0) {
|
|
1162
|
+
console.log('\nRelated memory:')
|
|
1163
|
+
for (const record of result.related_memories) {
|
|
1164
|
+
const badge = record.evidence ? ` [verified x${record.evidence.reuseCount}]` : ''
|
|
1165
|
+
console.log(`- ${record.id}${badge} ${String(record.content).replace(/\s+/g, ' ').slice(0, 110)}`)
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
function policyCommand(args, useJson) {
|
|
1171
|
+
const sub = args[1]
|
|
1172
|
+
|
|
1173
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
1174
|
+
policyUsage()
|
|
1175
|
+
if (!sub) process.exitCode = 1
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
1178
|
+
if (sub !== 'check') {
|
|
1179
|
+
console.error(`Unknown policy subcommand: ${sub}`)
|
|
1180
|
+
policyUsage()
|
|
1181
|
+
process.exitCode = 1
|
|
1182
|
+
return
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const flags = args.slice(2)
|
|
1186
|
+
// The action is a positional, and may be quoted as one argument or passed as
|
|
1187
|
+
// several words (`mio policy check npm publish`). Only flags this command
|
|
1188
|
+
// actually understands are treated as options -- anything else, including
|
|
1189
|
+
// `--hard` in `git reset --hard`, belongs to the action being described.
|
|
1190
|
+
const POLICY_KNOWN_FLAGS = new Set(['--project', '--json'])
|
|
1191
|
+
const actionParts = []
|
|
1192
|
+
for (let i = 0; i < flags.length; i += 1) {
|
|
1193
|
+
const token = flags[i]
|
|
1194
|
+
if (POLICY_KNOWN_FLAGS.has(token)) continue
|
|
1195
|
+
if (i > 0 && POLICY_KNOWN_FLAGS.has(flags[i - 1])) continue
|
|
1196
|
+
actionParts.push(token)
|
|
1197
|
+
}
|
|
1198
|
+
const action = actionParts.join(' ').trim()
|
|
1199
|
+
|
|
1200
|
+
if (!action) {
|
|
1201
|
+
console.error('mio policy check requires an action, e.g. mio policy check "npm publish"')
|
|
1202
|
+
process.exitCode = 1
|
|
1203
|
+
return
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
let result
|
|
1207
|
+
try {
|
|
1208
|
+
result = cliPolicyStore().policyCheck({
|
|
1209
|
+
action,
|
|
1210
|
+
project: optionValue(flags, '--project'),
|
|
1211
|
+
})
|
|
1212
|
+
} catch (error) {
|
|
1213
|
+
console.error(error.message || error)
|
|
1214
|
+
process.exitCode = 1
|
|
1215
|
+
return
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
if (useJson) return jsonOrText(result, true)
|
|
1219
|
+
printPolicyCheck(result)
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
// ═════════════════════════════════════════════════════════════
|
|
1223
|
+
// mio insight — terminal entry point for mio.insight.*
|
|
1224
|
+
//
|
|
1225
|
+
// The insight engine (self-observation) shipped MCP-only: an agent could read
|
|
1226
|
+
// its own insights, but there was no way to see them from a terminal. These
|
|
1227
|
+
// commands delegate to the same shared store as the MCP tools, so the two
|
|
1228
|
+
// cannot disagree.
|
|
1229
|
+
//
|
|
1230
|
+
// `generate` is deliberately not exposed. It needs an LLM and the CLI has no
|
|
1231
|
+
// LLM wiring; the same call was made for creativity generate/ferment.
|
|
1232
|
+
// ═════════════════════════════════════════════════════════════
|
|
1233
|
+
function cliInsightStore() {
|
|
1234
|
+
return createInsightStore({ dataDir: MIO_HOME })
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
function insightUsage() {
|
|
1238
|
+
console.log(`Usage:
|
|
1239
|
+
mio insight status Insight counts: total, reported, unreported, high-value
|
|
1240
|
+
mio insight list List insights
|
|
1241
|
+
mio insight mark-reported --ids a,b Mark insights as reported (acknowledged)
|
|
1242
|
+
|
|
1243
|
+
Options:
|
|
1244
|
+
--unreported Only unreported insights (list)
|
|
1245
|
+
--min-score N Minimum score (list)
|
|
1246
|
+
--detector name Filter by detector (list)
|
|
1247
|
+
--limit N Max insights (list)
|
|
1248
|
+
--ids a,b Comma-separated insight ids (mark-reported)
|
|
1249
|
+
--json Machine-readable output (same shape as mio.insight.*)
|
|
1250
|
+
|
|
1251
|
+
The insight engine is an optional package (@akemi-mio/insight). If it is not
|
|
1252
|
+
installed these commands say so, rather than reporting a misleading empty list.`)
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
function printInsightStatus(result) {
|
|
1256
|
+
console.log('Insight engine')
|
|
1257
|
+
console.log(` total: ${result.total}`)
|
|
1258
|
+
console.log(` reported: ${result.reported}`)
|
|
1259
|
+
console.log(` unreported: ${result.unreported}`)
|
|
1260
|
+
console.log(` high-value: ${result.highValue}`)
|
|
1261
|
+
if (result.unreported > 0) {
|
|
1262
|
+
console.log('\nUnreported insights are waiting: mio insight list --unreported')
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
function printInsightList(insights) {
|
|
1267
|
+
if (insights.length === 0) {
|
|
1268
|
+
console.log('No insights match.')
|
|
1269
|
+
return
|
|
1270
|
+
}
|
|
1271
|
+
for (const item of insights) {
|
|
1272
|
+
const score = typeof item.score === 'number' ? item.score.toFixed(2) : 'n/a'
|
|
1273
|
+
const state = item.reported ? 'reported' : 'unreported'
|
|
1274
|
+
const detector = item.detector ? `, ${item.detector}` : ''
|
|
1275
|
+
console.log(`- [${score}] ${item.id || '(no id)'} (${state}${detector})`)
|
|
1276
|
+
const text = String(item.content || item.summary || item.title || '')
|
|
1277
|
+
if (text) console.log(` ${text.replace(/\s+/g, ' ').slice(0, 140)}`)
|
|
1278
|
+
}
|
|
1279
|
+
console.log(`\n${insights.length} insight(s)`)
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function insightCommand(args, useJson) {
|
|
1283
|
+
const sub = args[1]
|
|
1284
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
1285
|
+
insightUsage()
|
|
1286
|
+
if (!sub) process.exitCode = 1
|
|
1287
|
+
return
|
|
1288
|
+
}
|
|
1289
|
+
if (!['status', 'list', 'mark-reported'].includes(sub)) {
|
|
1290
|
+
console.error(`Unknown insight subcommand: ${sub}`)
|
|
1291
|
+
insightUsage()
|
|
1292
|
+
process.exitCode = 1
|
|
1293
|
+
return
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
const flags = args.slice(2)
|
|
1297
|
+
const store = cliInsightStore()
|
|
1298
|
+
try {
|
|
1299
|
+
if (sub === 'status') {
|
|
1300
|
+
const result = store.status()
|
|
1301
|
+
if (useJson) return jsonOrText(result, true)
|
|
1302
|
+
return printInsightStatus(result)
|
|
1303
|
+
}
|
|
1304
|
+
if (sub === 'list') {
|
|
1305
|
+
const result = store.list({
|
|
1306
|
+
unreported: flagPresent(flags, '--unreported'),
|
|
1307
|
+
minScore: parseNumberOption(flags, '--min-score'),
|
|
1308
|
+
detector: optionValue(flags, '--detector'),
|
|
1309
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
1310
|
+
})
|
|
1311
|
+
if (useJson) return jsonOrText(result, true)
|
|
1312
|
+
return printInsightList(result)
|
|
1313
|
+
}
|
|
1314
|
+
const ids = splitTagsOption(flags, '--ids')
|
|
1315
|
+
if (ids.length === 0) {
|
|
1316
|
+
console.error('mio insight mark-reported requires --ids a,b')
|
|
1317
|
+
process.exitCode = 1
|
|
1318
|
+
return
|
|
1319
|
+
}
|
|
1320
|
+
const result = store.markReported({ ids })
|
|
1321
|
+
if (useJson) return jsonOrText(result, true)
|
|
1322
|
+
console.log(`Marked ${result.marked} insight(s) as reported.`)
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
console.error(error.message || error)
|
|
1325
|
+
process.exitCode = 1
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
// ═════════════════════════════════════════════════════════════
|
|
1330
|
+
// mio observer — terminal entry point for the observer research pipeline
|
|
1331
|
+
// (mio.observer.*). Not to be confused with `mio observe`, which is the
|
|
1332
|
+
// WorkBuddy transcript daemon.
|
|
1333
|
+
//
|
|
1334
|
+
// Only the read-only views are exposed. collect hits the network and ferment
|
|
1335
|
+
// drives the engine; both belong to the daemon, not an interactive shell (same
|
|
1336
|
+
// reasoning as skipping creativity generate from the CLI).
|
|
1337
|
+
//
|
|
1338
|
+
// Base directory defaults to <cwd>/.local/observer -- the MCP server's default
|
|
1339
|
+
// -- because the pipeline is project-local by design. --base-dir overrides it.
|
|
1340
|
+
// ═════════════════════════════════════════════════════════════
|
|
1341
|
+
function cliObserverStore() {
|
|
1342
|
+
return createObserverStore({})
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function observerUsage() {
|
|
1346
|
+
console.log(`Usage:
|
|
1347
|
+
mio observer status Pipeline stage counts (observations/trends/topics/...)
|
|
1348
|
+
mio observer world-model Entities, events, trends and narratives
|
|
1349
|
+
mio observer trends Recent trend reports
|
|
1350
|
+
mio observer research Research reports
|
|
1351
|
+
mio observer insights Observer-generated insight articles
|
|
1352
|
+
mio observer essays Published essays
|
|
1353
|
+
mio observer dag Daily summaries for the last N days
|
|
1354
|
+
mio observer ingest Record a trace event (tool_call/error/retry/task_outcome)
|
|
1355
|
+
--trace-id <id> --event-type <type> [--payload '<json>'] [--outcome]
|
|
1356
|
+
[--agent] [--host] [--project] (writes immediately, like mio remember)
|
|
1357
|
+
mio observer subscribe Subscribe to events: --event-types a,b [--topic T] [--ttl-days N]
|
|
1358
|
+
(--yes required to apply; previews by default)
|
|
1359
|
+
mio observer digest New events since the last digest (--limit/--project/--event-types)
|
|
1360
|
+
NOTE: advances the cursor, so a second run returns only newer events
|
|
1361
|
+
|
|
1362
|
+
Options:
|
|
1363
|
+
--base-dir DIR Observer data directory (default: <cwd>/.local/observer)
|
|
1364
|
+
--date YYYY-MM-DD A single trend report (trends)
|
|
1365
|
+
--type name Essay type (default: published)
|
|
1366
|
+
--days N Days of history (dag, default 7)
|
|
1367
|
+
--limit N Max items
|
|
1368
|
+
--json Machine-readable output (same shape as mio.observer.*)`)
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Pipeline items are free-form JSON written by different stages, so pull a
|
|
1372
|
+
// human label from whichever well-known field happens to be present.
|
|
1373
|
+
function observerItemLabel(item) {
|
|
1374
|
+
if (item === null || typeof item !== 'object') return String(item)
|
|
1375
|
+
for (const key of ['title', 'name', 'topic', 'headline', 'summary', 'text', 'id', 'date']) {
|
|
1376
|
+
const value = item[key]
|
|
1377
|
+
if (typeof value === 'string' && value.trim()) return value.trim().replace(/\s+/g, ' ').slice(0, 120)
|
|
1378
|
+
}
|
|
1379
|
+
return JSON.stringify(item).slice(0, 120)
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
function printObserverList(kind, items) {
|
|
1383
|
+
if (items.length === 0) {
|
|
1384
|
+
console.log(`No ${kind} found. (Run the observer pipeline, or check --base-dir.)`)
|
|
1385
|
+
return
|
|
1386
|
+
}
|
|
1387
|
+
for (const item of items) console.log(`- ${observerItemLabel(item)}`)
|
|
1388
|
+
console.log(`\n${items.length} ${kind}`)
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function printObserverStatus(status) {
|
|
1392
|
+
console.log('Observer pipeline (file counts per stage)')
|
|
1393
|
+
let total = 0
|
|
1394
|
+
for (const [stage, count] of Object.entries(status)) {
|
|
1395
|
+
total += count
|
|
1396
|
+
console.log(` ${stage.padEnd(14)} ${count}`)
|
|
1397
|
+
}
|
|
1398
|
+
console.log(`\n ${'total'.padEnd(14)} ${total}`)
|
|
1399
|
+
if (total === 0) {
|
|
1400
|
+
console.log('\nNo pipeline data yet. Nothing has been collected into this directory.')
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
function printObserverWorldModel(model) {
|
|
1405
|
+
const sections = ['entities', 'events', 'trends', 'narratives']
|
|
1406
|
+
console.log('World model')
|
|
1407
|
+
for (const key of sections) {
|
|
1408
|
+
const value = model[key]
|
|
1409
|
+
const count = Array.isArray(value) ? value.length : Object.keys(value || {}).length
|
|
1410
|
+
console.log(` ${key.padEnd(12)} ${count}`)
|
|
1411
|
+
}
|
|
1412
|
+
if (sections.every((key) => (Array.isArray(model[key]) ? model[key].length : 0) === 0)) {
|
|
1413
|
+
console.log('\nWorld model is empty.')
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function printObserverEssays(essays) {
|
|
1418
|
+
if (essays.length === 0) {
|
|
1419
|
+
console.log('No essays found.')
|
|
1420
|
+
return
|
|
1421
|
+
}
|
|
1422
|
+
for (const essay of essays) {
|
|
1423
|
+
console.log(`- ${essay.file} (${essay.type}, ${String(essay.created).slice(0, 10)})`)
|
|
1424
|
+
const firstLine = String(essay.content || '').split('\n').find((l) => l.trim()) || ''
|
|
1425
|
+
if (firstLine) console.log(` ${firstLine.replace(/\s+/g, ' ').slice(0, 120)}`)
|
|
1426
|
+
}
|
|
1427
|
+
console.log(`\n${essays.length} essay(s)`)
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
function printObserverDag(result, days) {
|
|
1431
|
+
if (result.summaryCount === 0) {
|
|
1432
|
+
console.log(`No daily summaries found in the last ${days || 7} day(s).`)
|
|
1433
|
+
return
|
|
1434
|
+
}
|
|
1435
|
+
for (const day of result.summaries) {
|
|
1436
|
+
console.log(`- ${day.date}: ${day.summaries.length} summary(ies)`)
|
|
1437
|
+
}
|
|
1438
|
+
console.log(`\n${result.summaryCount} summary(ies) across ${result.summaries.length} day(s)`)
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function cliSubscriptionStore() {
|
|
1442
|
+
return createSubscriptionStore({
|
|
1443
|
+
dataDir: MIO_HOME,
|
|
1444
|
+
projectName,
|
|
1445
|
+
agentId: () => 'cli',
|
|
1446
|
+
})
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
// Records an arbitrary trace event (tool_call / error / retry / task_outcome).
|
|
1450
|
+
// Unlike `mio task record-outcome`, which is specific to task outcomes, this is
|
|
1451
|
+
// the general-purpose half -- and the one AGENTS.md asks agents to call.
|
|
1452
|
+
//
|
|
1453
|
+
// It appends rather than modifies, so like `mio remember` it writes straight
|
|
1454
|
+
// away instead of previewing: there is nothing to destroy. Missing arguments are
|
|
1455
|
+
// still rejected before anything is written.
|
|
1456
|
+
function observerIngestCommand(args, useJson) {
|
|
1457
|
+
const flags = args.slice(2)
|
|
1458
|
+
const traceId = optionValue(flags, '--trace-id')
|
|
1459
|
+
const eventType = optionValue(flags, '--event-type')
|
|
1460
|
+
if (!traceId || !eventType) {
|
|
1461
|
+
console.error('mio observer ingest requires --trace-id <id> and --event-type <type>')
|
|
1462
|
+
console.error('Example: mio observer ingest --trace-id t1 --event-type tool_call --payload \'{"tool":"Bash"}\'')
|
|
1463
|
+
process.exitCode = 1
|
|
1464
|
+
return
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
let payload = {}
|
|
1468
|
+
const rawPayload = optionValue(flags, '--payload')
|
|
1469
|
+
if (rawPayload) {
|
|
1470
|
+
try {
|
|
1471
|
+
payload = JSON.parse(rawPayload)
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
console.error(`--payload must be valid JSON: ${error.message}`)
|
|
1474
|
+
process.exitCode = 1
|
|
1475
|
+
return
|
|
1476
|
+
}
|
|
1477
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
1478
|
+
console.error('--payload must be a JSON object')
|
|
1479
|
+
process.exitCode = 1
|
|
1480
|
+
return
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
let result
|
|
1485
|
+
try {
|
|
1486
|
+
result = cliTaskStore().ingestObservation({
|
|
1487
|
+
trace_id: traceId,
|
|
1488
|
+
event_type: eventType,
|
|
1489
|
+
outcome: optionValue(flags, '--outcome'),
|
|
1490
|
+
payload,
|
|
1491
|
+
agent: optionValue(flags, '--agent'),
|
|
1492
|
+
host: optionValue(flags, '--host'),
|
|
1493
|
+
project: optionValue(flags, '--project'),
|
|
1494
|
+
})
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
console.error(error.message || error)
|
|
1497
|
+
process.exitCode = 1
|
|
1498
|
+
return
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
if (useJson) return jsonOrText(result, true)
|
|
1502
|
+
console.log(`Ingested ${result.event.event_type} (trace=${result.event.trace_id})`)
|
|
1503
|
+
console.log(` agent=${result.event.agent} project=${result.event.project} host=${result.event.host}`)
|
|
1504
|
+
if (result.autoClaims && result.autoClaims.length > 0) {
|
|
1505
|
+
console.log(` auto-claims: ${result.autoClaims.length}`)
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Subscribe creates or renews a subscription, so it previews like every other
|
|
1510
|
+
// mutating command (`mio agents register`, `mio memory archive`): exit 1 and
|
|
1511
|
+
// nothing written without --yes.
|
|
1512
|
+
function observerSubscribeCommand(args, useJson) {
|
|
1513
|
+
const flags = args.slice(2)
|
|
1514
|
+
const project = optionValue(flags, '--project') || projectName()
|
|
1515
|
+
const eventTypes = splitTagsOption(flags, '--event-types')
|
|
1516
|
+
const topic = optionValue(flags, '--topic') || ''
|
|
1517
|
+
const ttlDays = optionValue(flags, '--ttl-days')
|
|
1518
|
+
|
|
1519
|
+
if (!flagPresent(flags, '--yes')) {
|
|
1520
|
+
const store = cliSubscriptionStore()
|
|
1521
|
+
const existing = readJsonl(store.subscriptionPath).find((subscription) => {
|
|
1522
|
+
return (
|
|
1523
|
+
subscription &&
|
|
1524
|
+
subscription.agent === 'cli' &&
|
|
1525
|
+
subscribeKey(subscription.agent, subscription.project, subscription.eventTypes || [], subscription.topic || '') ===
|
|
1526
|
+
subscribeKey('cli', project, eventTypes, topic)
|
|
1527
|
+
)
|
|
1528
|
+
})
|
|
1529
|
+
if (useJson) {
|
|
1530
|
+
jsonOrText(
|
|
1531
|
+
{
|
|
1532
|
+
preview: true,
|
|
1533
|
+
applied: false,
|
|
1534
|
+
project,
|
|
1535
|
+
eventTypes,
|
|
1536
|
+
topic: topic || null,
|
|
1537
|
+
ttlDays: ttlDays ? Number(ttlDays) : null,
|
|
1538
|
+
willCreate: !existing,
|
|
1539
|
+
hint: 'Re-run with --yes to apply.',
|
|
1540
|
+
},
|
|
1541
|
+
true,
|
|
1542
|
+
)
|
|
1543
|
+
} else {
|
|
1544
|
+
console.log(`Subscribe preview: project=${project}`)
|
|
1545
|
+
console.log(` event types: ${eventTypes.length > 0 ? eventTypes.join(', ') : '(all)'}`)
|
|
1546
|
+
if (topic) console.log(` topic: ${topic}`)
|
|
1547
|
+
console.log(` ${existing ? 'will renew the existing subscription' : 'will create a new subscription'}`)
|
|
1548
|
+
console.log('Re-run with --yes to apply.')
|
|
1549
|
+
}
|
|
1550
|
+
process.exitCode = 1
|
|
1551
|
+
return
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
let result
|
|
1555
|
+
try {
|
|
1556
|
+
result = cliSubscriptionStore().subscribe({ project, eventTypes, topic, ttlDays })
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
console.error(error.message || error)
|
|
1559
|
+
process.exitCode = 1
|
|
1560
|
+
return
|
|
1561
|
+
}
|
|
1562
|
+
if (useJson) return jsonOrText(result, true)
|
|
1563
|
+
console.log(`Subscribed (project=${result.project})`)
|
|
1564
|
+
console.log(` id=${result.subscription.id} expires=${result.subscription.expiresAt}`)
|
|
1565
|
+
console.log(` event types: ${(result.subscription.eventTypes || []).join(', ') || '(all)'}`)
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// The digest is cursor-based: it returns only events newer than the last one it
|
|
1569
|
+
// delivered, and ADVANCES that cursor. Running it twice therefore gives different
|
|
1570
|
+
// results -- so it cannot be previewed, and the output says so rather than
|
|
1571
|
+
// leaving that surprise for the user to discover.
|
|
1572
|
+
function observerDigestCommand(args, useJson) {
|
|
1573
|
+
const flags = args.slice(2)
|
|
1574
|
+
let result
|
|
1575
|
+
try {
|
|
1576
|
+
result = cliSubscriptionStore().digest({
|
|
1577
|
+
project: optionValue(flags, '--project'),
|
|
1578
|
+
limit: optionValue(flags, '--limit'),
|
|
1579
|
+
eventTypes: splitTagsOption(flags, '--event-types'),
|
|
1580
|
+
})
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
console.error(error.message || error)
|
|
1583
|
+
process.exitCode = 1
|
|
1584
|
+
return
|
|
1585
|
+
}
|
|
1586
|
+
if (useJson) return jsonOrText(result, true)
|
|
1587
|
+
console.log(`Observer digest: ${result.count} event(s) (project=${result.project}, agent=${result.agent})`)
|
|
1588
|
+
console.log(` subscriptions: ${result.subscriptionCount} active, ${result.matchedSubscriptions.length} matched`)
|
|
1589
|
+
for (const event of result.events) {
|
|
1590
|
+
console.log(`- ${event.event_type}${event.outcome ? ` (${event.outcome})` : ''} trace=${event.trace_id || '-'}`)
|
|
1591
|
+
}
|
|
1592
|
+
if (result.count === 0) {
|
|
1593
|
+
console.log('Nothing new since the last digest.')
|
|
1594
|
+
}
|
|
1595
|
+
console.log('Note: this advances the cursor; the next digest returns only newer events.')
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
function observerCommand(args, useJson) {
|
|
1599
|
+
const sub = args[1]
|
|
1600
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
1601
|
+
observerUsage()
|
|
1602
|
+
if (!sub) process.exitCode = 1
|
|
1603
|
+
return
|
|
1604
|
+
}
|
|
1605
|
+
if (sub === 'ingest') return observerIngestCommand(args, useJson)
|
|
1606
|
+
if (sub === 'subscribe') return observerSubscribeCommand(args, useJson)
|
|
1607
|
+
if (sub === 'digest') return observerDigestCommand(args, useJson)
|
|
1608
|
+
|
|
1609
|
+
if (!['status', 'world-model', 'trends', 'research', 'insights', 'essays', 'dag'].includes(sub)) {
|
|
1610
|
+
console.error(`Unknown observer subcommand: ${sub}`)
|
|
1611
|
+
observerUsage()
|
|
1612
|
+
process.exitCode = 1
|
|
1613
|
+
return
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
const flags = args.slice(2)
|
|
1617
|
+
const store = cliObserverStore()
|
|
1618
|
+
const base = { baseDir: optionValue(flags, '--base-dir') }
|
|
1619
|
+
try {
|
|
1620
|
+
if (sub === 'status') {
|
|
1621
|
+
const result = store.status(base)
|
|
1622
|
+
if (useJson) return jsonOrText(result, true)
|
|
1623
|
+
return printObserverStatus(result)
|
|
1624
|
+
}
|
|
1625
|
+
if (sub === 'world-model') {
|
|
1626
|
+
const result = store.worldModel(base)
|
|
1627
|
+
if (useJson) return jsonOrText(result, true)
|
|
1628
|
+
return printObserverWorldModel(result)
|
|
1629
|
+
}
|
|
1630
|
+
if (sub === 'trends') {
|
|
1631
|
+
const result = store.trends({ ...base, date: optionValue(flags, '--date'), limit: parseNumberOption(flags, '--limit') })
|
|
1632
|
+
if (useJson) return jsonOrText(result, true)
|
|
1633
|
+
return printObserverList('trend report(s)', result)
|
|
1634
|
+
}
|
|
1635
|
+
if (sub === 'research') {
|
|
1636
|
+
const result = store.research({ ...base, limit: parseNumberOption(flags, '--limit') })
|
|
1637
|
+
if (useJson) return jsonOrText(result, true)
|
|
1638
|
+
return printObserverList('research report(s)', result)
|
|
1639
|
+
}
|
|
1640
|
+
if (sub === 'insights') {
|
|
1641
|
+
const result = store.insights({ ...base, limit: parseNumberOption(flags, '--limit') })
|
|
1642
|
+
if (useJson) return jsonOrText(result, true)
|
|
1643
|
+
return printObserverList('insight(s)', result)
|
|
1644
|
+
}
|
|
1645
|
+
if (sub === 'essays') {
|
|
1646
|
+
const result = store.essays({ ...base, type: optionValue(flags, '--type'), limit: parseNumberOption(flags, '--limit') })
|
|
1647
|
+
if (useJson) return jsonOrText(result, true)
|
|
1648
|
+
return printObserverEssays(result)
|
|
1649
|
+
}
|
|
1650
|
+
const days = parseNumberOption(flags, '--days')
|
|
1651
|
+
const result = store.dag({ ...base, days })
|
|
1652
|
+
if (useJson) return jsonOrText(result, true)
|
|
1653
|
+
return printObserverDag(result, days)
|
|
1654
|
+
} catch (error) {
|
|
1655
|
+
console.error(error.message || error)
|
|
1656
|
+
process.exitCode = 1
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
function creativityUsage() {
|
|
1661
|
+
console.log(`Usage:
|
|
1662
|
+
mio creativity status Show hypothesis counts and recent top ideas
|
|
1663
|
+
mio creativity list List hypotheses (--status active|validated|rejected|draft, --limit N)
|
|
1664
|
+
|
|
1665
|
+
mio creativity generate Generate hypotheses by combining sources (needs 2+ --source, calls an LLM)
|
|
1666
|
+
mio creativity ferment Review/refine active hypotheses (calls an LLM)
|
|
1667
|
+
|
|
1668
|
+
Options:
|
|
1669
|
+
--status name Filter list by status
|
|
1670
|
+
--limit N Max results for list / ferment (default 20 / 5)
|
|
1671
|
+
--source "name|content" A concept source for generate; repeat 2+ times
|
|
1672
|
+
--strategy explore|signal|stable Generation strategy (auto-selected if omitted)
|
|
1673
|
+
--json Machine-readable output (same shape as the mio.creativity.* tools)
|
|
1674
|
+
|
|
1675
|
+
Examples:
|
|
1676
|
+
mio creativity status
|
|
1677
|
+
mio creativity list --status rejected --limit 10
|
|
1678
|
+
mio creativity generate --source "auth|token rotation" --source "cache|write-through"
|
|
1679
|
+
|
|
1680
|
+
generate/ferment call an LLM using the shared client. Configure it with
|
|
1681
|
+
LLM_API_URL / LLM_KEY / LLM_CHAT_MODEL (the same environment the MCP server
|
|
1682
|
+
reads). A local Ollama works:
|
|
1683
|
+
LLM_API_URL=http://localhost:11434/v1/chat/completions`)
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
function printCreativityStatus(result) {
|
|
1687
|
+
console.log('Creativity engine:')
|
|
1688
|
+
console.log(` hypotheses: ${result.hypotheses} combos: ${result.combos} experiments: ${result.experiments}`)
|
|
1689
|
+
console.log(` active: ${result.active} validated: ${result.validated} rejected: ${result.rejected}`)
|
|
1690
|
+
if (result.recentIdeas.length === 0) {
|
|
1691
|
+
console.log('No hypotheses yet.')
|
|
1692
|
+
return
|
|
1693
|
+
}
|
|
1694
|
+
console.log('\nRecent top ideas:')
|
|
1695
|
+
for (const idea of result.recentIdeas) {
|
|
1696
|
+
console.log(`- ${idea.title} (novelty=${idea.novelty} feasibility=${idea.feasibility} impact=${idea.impact} score=${idea.score})`)
|
|
1697
|
+
console.log(` ${idea.id}`)
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
function printCreativityList(items) {
|
|
1702
|
+
if (items.length === 0) {
|
|
1703
|
+
console.log('No hypotheses match.')
|
|
1704
|
+
return
|
|
1705
|
+
}
|
|
1706
|
+
console.log(`Hypotheses: ${items.length} shown`)
|
|
1707
|
+
items.forEach((h, index) => {
|
|
1708
|
+
const when = h.createdAt ? new Date(h.createdAt).toISOString().slice(0, 19).replace('T', ' ') : ''
|
|
1709
|
+
const labels = Array.isArray(h.sourceLabels) && h.sourceLabels.length > 0 ? h.sourceLabels.join(' + ') : '—'
|
|
1710
|
+
console.log(`${index + 1}. [${h.status}] ${h.title} (N=${h.novelty} F=${h.feasibility} I=${h.impact} score=${h.score})`)
|
|
1711
|
+
console.log(` ${labels} | ${when} | ${h.id}`)
|
|
1712
|
+
if (h.fermentCount) console.log(` fermented ${h.fermentCount}x`)
|
|
1713
|
+
})
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// Parses repeated `--source "name|content"` into the shape CreativityEngine
|
|
1717
|
+
// expects. At least two are required: the engine pairs up sources, so one
|
|
1718
|
+
// source can never produce a combination.
|
|
1719
|
+
function parseSources(flags) {
|
|
1720
|
+
const sources = []
|
|
1721
|
+
for (let i = 0; i < flags.length; i += 1) {
|
|
1722
|
+
if (flags[i] !== '--source') continue
|
|
1723
|
+
const raw = flags[i + 1]
|
|
1724
|
+
if (!raw) continue
|
|
1725
|
+
const sep = raw.indexOf('|')
|
|
1726
|
+
if (sep === -1) {
|
|
1727
|
+
sources.push({ name: raw, content: raw })
|
|
1728
|
+
} else {
|
|
1729
|
+
sources.push({
|
|
1730
|
+
name: raw.slice(0, sep).trim(),
|
|
1731
|
+
content: raw.slice(sep + 1).trim(),
|
|
1732
|
+
})
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
return sources
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function warnIfLlmUnconfigured() {
|
|
1739
|
+
if (isLlmConfigured()) return
|
|
1740
|
+
const { apiUrl, model } = llmConfig()
|
|
1741
|
+
console.log(`Note: no LLM_API_URL / LLM_KEY set, so this will call the default endpoint (${model} @ ${apiUrl}).`)
|
|
1742
|
+
console.log('Set LLM_API_URL (e.g. http://localhost:11434/v1/chat/completions for a local Ollama) to point elsewhere.')
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function creativityGenerateCommand(args, useJson) {
|
|
1746
|
+
const flags = args.slice(2)
|
|
1747
|
+
const sources = parseSources(flags)
|
|
1748
|
+
if (sources.length < 2) {
|
|
1749
|
+
console.error('mio creativity generate requires at least two --source "name|content" arguments')
|
|
1750
|
+
console.error('Example: mio creativity generate --source "auth|token rotation" --source "cache|write-through cache"')
|
|
1751
|
+
process.exitCode = 1
|
|
1752
|
+
return
|
|
1753
|
+
}
|
|
1754
|
+
const strategy = optionValue(flags, '--strategy')
|
|
1755
|
+
|
|
1756
|
+
if (!useJson) warnIfLlmUnconfigured()
|
|
1757
|
+
|
|
1758
|
+
let result
|
|
1759
|
+
try {
|
|
1760
|
+
result = cliCreativityEngine().generate(sources, strategy)
|
|
1761
|
+
} catch (error) {
|
|
1762
|
+
console.error(error.message || error)
|
|
1763
|
+
process.exitCode = 1
|
|
1764
|
+
return
|
|
1765
|
+
}
|
|
1766
|
+
// generate is async in the engine.
|
|
1767
|
+
return Promise.resolve(result).then((resolved) => {
|
|
1768
|
+
if (useJson) return jsonOrText(resolved, true)
|
|
1769
|
+
if (resolved.reason) {
|
|
1770
|
+
console.log(`No hypotheses generated: ${resolved.reason}`)
|
|
1771
|
+
return
|
|
1772
|
+
}
|
|
1773
|
+
console.log(`Generated ${resolved.ideas.length} hypothesis/hypotheses (strategy=${resolved.strategy})`)
|
|
1774
|
+
resolved.ideas.forEach((idea, index) => {
|
|
1775
|
+
console.log(`${index + 1}. [${idea.status}] ${idea.title}`)
|
|
1776
|
+
console.log(` novelty=${idea.novelty} feasibility=${idea.feasibility} impact=${idea.impact} | ${idea.id}`)
|
|
1777
|
+
if (idea.rejectionReason) console.log(` rejected: ${idea.rejectionReason}`)
|
|
1778
|
+
})
|
|
1779
|
+
})
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function creativityFermentCommand(args, useJson) {
|
|
1783
|
+
const flags = args.slice(2)
|
|
1784
|
+
const limit = parseNumberOption(flags, '--limit')
|
|
1785
|
+
|
|
1786
|
+
if (!useJson) warnIfLlmUnconfigured()
|
|
1787
|
+
|
|
1788
|
+
let result
|
|
1789
|
+
try {
|
|
1790
|
+
result = cliCreativityEngine().ferment(limit)
|
|
1791
|
+
} catch (error) {
|
|
1792
|
+
console.error(error.message || error)
|
|
1793
|
+
process.exitCode = 1
|
|
1794
|
+
return
|
|
1795
|
+
}
|
|
1796
|
+
return Promise.resolve(result).then((resolved) => {
|
|
1797
|
+
if (useJson) return jsonOrText(resolved, true)
|
|
1798
|
+
if (resolved.fermented === 0) {
|
|
1799
|
+
console.log(`Nothing fermented: ${resolved.reason || 'no eligible hypotheses'}`)
|
|
1800
|
+
return
|
|
1801
|
+
}
|
|
1802
|
+
console.log(`Fermented ${resolved.fermented} hypothesis/hypotheses`)
|
|
1803
|
+
for (const r of resolved.results || []) {
|
|
1804
|
+
console.log(`- ${r.title || r.id}: ${r.verdict || 'updated'}${r.reason ? ` — ${r.reason}` : ''}`)
|
|
1805
|
+
}
|
|
1806
|
+
})
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
function creativityCommand(args, useJson) {
|
|
1810
|
+
const sub = args[1]
|
|
1811
|
+
|
|
1812
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
1813
|
+
creativityUsage()
|
|
1814
|
+
if (!sub) process.exitCode = 1
|
|
1815
|
+
return
|
|
1816
|
+
}
|
|
1817
|
+
if (sub === 'generate') return creativityGenerateCommand(args, useJson)
|
|
1818
|
+
if (sub === 'ferment') return creativityFermentCommand(args, useJson)
|
|
1819
|
+
|
|
1820
|
+
if (!['status', 'list'].includes(sub)) {
|
|
1821
|
+
console.error(`Unknown creativity subcommand: ${sub}`)
|
|
1822
|
+
creativityUsage()
|
|
1823
|
+
process.exitCode = 1
|
|
1824
|
+
return
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
const flags = args.slice(2)
|
|
1828
|
+
const engine = cliCreativityEngine()
|
|
1829
|
+
|
|
1830
|
+
let result
|
|
1831
|
+
try {
|
|
1832
|
+
if (sub === 'status') {
|
|
1833
|
+
result = engine.status()
|
|
1834
|
+
} else {
|
|
1835
|
+
result = engine.list({
|
|
1836
|
+
status: optionValue(flags, '--status'),
|
|
1837
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
1838
|
+
})
|
|
1839
|
+
}
|
|
1840
|
+
} catch (error) {
|
|
1841
|
+
console.error(error.message || error)
|
|
1842
|
+
process.exitCode = 1
|
|
1843
|
+
return
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
if (useJson) return jsonOrText(result, true)
|
|
1847
|
+
if (sub === 'status') printCreativityStatus(result)
|
|
1848
|
+
else printCreativityList(result)
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
function readConfig() {
|
|
1852
|
+
const fallback = { version: 1, home: MIO_HOME, agents: {} }
|
|
1853
|
+
if (!fs.existsSync(CONFIG_FILE)) return fallback
|
|
1854
|
+
try {
|
|
1855
|
+
return { ...fallback, ...JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8')) }
|
|
1856
|
+
} catch (_) {
|
|
1857
|
+
return fallback
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function writeConfig(config) {
|
|
1862
|
+
ensureHome()
|
|
1863
|
+
fs.writeFileSync(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function jsonOrText(payload, useJson) {
|
|
1867
|
+
if (useJson) {
|
|
1868
|
+
console.log(JSON.stringify(payload, null, 2))
|
|
1869
|
+
return
|
|
1870
|
+
}
|
|
1871
|
+
return payload
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
function initCommand() {
|
|
1875
|
+
ensureHome()
|
|
1876
|
+
const config = readConfig()
|
|
1877
|
+
if (!config.createdAt) config.createdAt = new Date().toISOString()
|
|
1878
|
+
writeConfig(config)
|
|
1879
|
+
console.log(`MIO_HOME ready: ${MIO_HOME}`)
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function statusCommand(useJson) {
|
|
1883
|
+
const config = readConfig()
|
|
1884
|
+
const codexRestart = adapters.codex.restartWarningMessage()
|
|
1885
|
+
const opencodeRestart = adapters.opencode.restartWarningMessage()
|
|
1886
|
+
const installed = {
|
|
1887
|
+
codex: adapters.codex.isInstalled(),
|
|
1888
|
+
opencode: adapters.opencode.isInstalled(),
|
|
1889
|
+
workbuddy: adapters.workbuddy.isInstalled(),
|
|
1890
|
+
hermes: adapters.hermes.isInstalled(),
|
|
1891
|
+
claude: adapters.claude.isInstalled(),
|
|
1892
|
+
}
|
|
1893
|
+
const payload = {
|
|
1894
|
+
version: config.version,
|
|
1895
|
+
home: MIO_HOME,
|
|
1896
|
+
serverScript: SERVER_SCRIPT,
|
|
1897
|
+
serverScriptExists: fs.existsSync(SERVER_SCRIPT),
|
|
1898
|
+
agents: config.agents || {},
|
|
1899
|
+
codexInstalled: installed.codex,
|
|
1900
|
+
opencodeInstalled: installed.opencode,
|
|
1901
|
+
workbuddyInstalled: installed.workbuddy,
|
|
1902
|
+
hermesInstalled: installed.hermes,
|
|
1903
|
+
claudeInstalled: installed.claude,
|
|
1904
|
+
codexNeedsRestart: Boolean(codexRestart),
|
|
1905
|
+
opencodeNeedsRestart: Boolean(opencodeRestart),
|
|
1906
|
+
codexRestartHint: codexRestart,
|
|
1907
|
+
opencodeRestartHint: opencodeRestart,
|
|
1908
|
+
hermesObserverReady: fs.existsSync(observer.hermesDbPath()),
|
|
1909
|
+
claudeObserverReady: fs.existsSync(observer.claudeProjectsDir()),
|
|
1910
|
+
opencodeObserverReady: fs.existsSync(observer.opencodeDbPath()),
|
|
1911
|
+
observerRunning: observer.isRunning(MIO_HOME),
|
|
1912
|
+
observerPid: observer.isRunning(MIO_HOME) ? observer.readPid(MIO_HOME) : null,
|
|
1913
|
+
}
|
|
1914
|
+
if (useJson) {
|
|
1915
|
+
jsonOrText(payload, true)
|
|
1916
|
+
return
|
|
1917
|
+
}
|
|
1918
|
+
console.log(`MIO_HOME: ${MIO_HOME}`)
|
|
1919
|
+
console.log(`MCP Server: ${SERVER_SCRIPT}`)
|
|
1920
|
+
console.log(`Codex MCP: ${installed.codex ? 'installed' : 'not installed'}`)
|
|
1921
|
+
if (codexRestart) console.log(`Codex app: running with an older Mio config - fully restart Codex to load it`)
|
|
1922
|
+
console.log(`OpenCode MCP: ${installed.opencode ? 'installed' : 'not installed'}`)
|
|
1923
|
+
if (opencodeRestart) console.log(`OpenCode Desktop: running with an older Mio config - restart it to load it`)
|
|
1924
|
+
console.log(`OpenCode observer: ${fs.existsSync(observer.opencodeDbPath()) ? 'ready' : 'no database'}`)
|
|
1925
|
+
console.log(`WorkBuddy MCP: ${installed.workbuddy ? 'installed' : 'not installed'}`)
|
|
1926
|
+
console.log(`Hermes MCP: ${installed.hermes ? 'installed' : 'not installed'}`)
|
|
1927
|
+
console.log(`Hermes observer: ${fs.existsSync(observer.hermesDbPath()) ? 'ready' : 'no database'}`)
|
|
1928
|
+
console.log(`Claude Code MCP: ${installed.claude ? 'installed' : 'not installed'}`)
|
|
1929
|
+
console.log(`Claude Code observer: ${fs.existsSync(observer.claudeProjectsDir()) ? 'ready' : 'no transcripts'}`)
|
|
1930
|
+
console.log(`Observer: ${observer.isRunning(MIO_HOME) ? 'running (pid ' + observer.readPid(MIO_HOME) + ')' : 'not running'}`)
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
function agentsCommand(args, useJson) {
|
|
1934
|
+
const sub = args[1]
|
|
1935
|
+
|
|
1936
|
+
// `mio agents` with no subcommand lists installed host adapters (config) —
|
|
1937
|
+
// distinct from the observed-agent telemetry below.
|
|
1938
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
1939
|
+
const config = readConfig()
|
|
1940
|
+
const agents = config.agents || {}
|
|
1941
|
+
if (useJson) {
|
|
1942
|
+
jsonOrText(agents, true)
|
|
1943
|
+
return
|
|
1944
|
+
}
|
|
1945
|
+
const names = Object.keys(agents)
|
|
1946
|
+
if (names.length === 0) {
|
|
1947
|
+
console.log('No agents installed. Run `mio agents list` for observed agents, or `mio install <host>` to add one.')
|
|
1948
|
+
return
|
|
1949
|
+
}
|
|
1950
|
+
for (const name of names) {
|
|
1951
|
+
console.log(`${name}\t${agents[name].installedAt || ''}`)
|
|
1952
|
+
}
|
|
1953
|
+
return
|
|
1954
|
+
}
|
|
1955
|
+
|
|
1956
|
+
if (sub === 'list') {
|
|
1957
|
+
const flags = args.slice(2)
|
|
1958
|
+
let result
|
|
1959
|
+
try {
|
|
1960
|
+
result = cliAgentStore().listAgents({ project: optionValue(flags, '--project') })
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
console.error(error.message || error)
|
|
1963
|
+
process.exitCode = 1
|
|
1964
|
+
return
|
|
1965
|
+
}
|
|
1966
|
+
if (useJson) return jsonOrText(result, true)
|
|
1967
|
+
return printAgentList(result)
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
if (sub === 'report') {
|
|
1971
|
+
const flags = args.slice(2)
|
|
1972
|
+
let result
|
|
1973
|
+
try {
|
|
1974
|
+
result = cliAgentStore().reportAgent({
|
|
1975
|
+
agentId: optionValue(flags, '--agent'),
|
|
1976
|
+
project: optionValue(flags, '--project'),
|
|
1977
|
+
})
|
|
1978
|
+
} catch (error) {
|
|
1979
|
+
console.error(error.message || error)
|
|
1980
|
+
process.exitCode = 1
|
|
1981
|
+
return
|
|
1982
|
+
}
|
|
1983
|
+
if (useJson) return jsonOrText(result, true)
|
|
1984
|
+
return printAgentReport(result)
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
if (sub === 'register') {
|
|
1988
|
+
const flags = args.slice(2)
|
|
1989
|
+
const agentId = optionValue(flags, '--agent-id')
|
|
1990
|
+
if (!agentId) {
|
|
1991
|
+
console.error('mio agents register requires --agent-id <id>')
|
|
1992
|
+
process.exitCode = 1
|
|
1993
|
+
return
|
|
1994
|
+
}
|
|
1995
|
+
const project = optionValue(flags, '--project') || projectName()
|
|
1996
|
+
const hostType = optionValue(flags, '--host-type') || 'mcp'
|
|
1997
|
+
const capabilities = splitTagsOption(flags, '--capabilities')
|
|
1998
|
+
|
|
1999
|
+
// Preview by default, like `mio memory archive`: nothing is written
|
|
2000
|
+
// without --yes, in --json mode either.
|
|
2001
|
+
if (!flagPresent(flags, '--yes')) {
|
|
2002
|
+
const existing = cliAgentStore()
|
|
2003
|
+
.listAgents({ project })
|
|
2004
|
+
.agents.find((a) => a.agentId === agentId)
|
|
2005
|
+
if (useJson) {
|
|
2006
|
+
jsonOrText(
|
|
2007
|
+
{
|
|
2008
|
+
preview: true,
|
|
2009
|
+
applied: false,
|
|
2010
|
+
agentId,
|
|
2011
|
+
project,
|
|
2012
|
+
hostType,
|
|
2013
|
+
capabilities,
|
|
2014
|
+
willCreate: !existing,
|
|
2015
|
+
existingSessionCount: existing ? existing.sessionCount || 0 : null,
|
|
2016
|
+
hint: 'Re-run with --yes to apply.',
|
|
2017
|
+
},
|
|
2018
|
+
true,
|
|
2019
|
+
)
|
|
2020
|
+
} else {
|
|
2021
|
+
console.log(`Register preview: ${agentId} (project=${project}, host=${hostType})`)
|
|
2022
|
+
if (existing) {
|
|
2023
|
+
const next = (existing.sessionCount || 0) + 1
|
|
2024
|
+
console.log(` will update existing agent (sessionCount ${existing.sessionCount || 0} -> ${next})`)
|
|
2025
|
+
} else {
|
|
2026
|
+
console.log(' will create a new agent record')
|
|
2027
|
+
}
|
|
2028
|
+
if (capabilities.length > 0) console.log(` capabilities: ${capabilities.join(', ')}`)
|
|
2029
|
+
console.log('Re-run with --yes to apply.')
|
|
2030
|
+
}
|
|
2031
|
+
process.exitCode = 1
|
|
2032
|
+
return
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
let result
|
|
2036
|
+
try {
|
|
2037
|
+
result = cliAgentStore().registerAgent({ agentId, project, hostType, capabilities })
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
console.error(error.message || error)
|
|
2040
|
+
process.exitCode = 1
|
|
2041
|
+
return
|
|
2042
|
+
}
|
|
2043
|
+
if (useJson) return jsonOrText(result, true)
|
|
2044
|
+
console.log(`Registered ${result.agentId} (project=${project})`)
|
|
2045
|
+
console.log(
|
|
2046
|
+
` ${result.registered ? 'created' : `updated (session ${result.sessionCount})`} | host=${result.hostType}`,
|
|
2047
|
+
)
|
|
2048
|
+
if (result.capabilities && result.capabilities.length > 0) {
|
|
2049
|
+
console.log(` capabilities: ${result.capabilities.join(', ')}`)
|
|
2050
|
+
}
|
|
2051
|
+
return
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
console.error(`Unknown agents subcommand: ${sub}`)
|
|
2055
|
+
console.error('Usage: mio agents [list|register|report] [--agent X] [--project Y]')
|
|
2056
|
+
process.exitCode = 1
|
|
2057
|
+
}
|
|
2058
|
+
|
|
2059
|
+
function printAgentList(result) {
|
|
2060
|
+
console.log(`Observed agents: ${result.count} shown${result.project ? ` (project=${result.project})` : ''}`)
|
|
2061
|
+
if (result.count === 0) {
|
|
2062
|
+
console.log('No observed agents match.')
|
|
2063
|
+
return
|
|
2064
|
+
}
|
|
2065
|
+
for (const a of result.agents) {
|
|
2066
|
+
const caps = Array.isArray(a.capabilities) && a.capabilities.length > 0 ? ` [${a.capabilities.join(', ')}]` : ''
|
|
2067
|
+
console.log(`${a.agentId} (${a.hostType})${caps} project=${a.project || '-'}`)
|
|
2068
|
+
console.log(
|
|
2069
|
+
` sessions=${a.sessionCount || 0} tasks=${a.taskCount} success=${a.successCount} failure=${a.failureCount} | ${a.id}`,
|
|
2070
|
+
)
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function printAgentReport(result) {
|
|
2075
|
+
console.log(`Agent report${result.project ? ` (project=${result.project})` : ''}: ${result.count} agent(s)`)
|
|
2076
|
+
if (result.count === 0) {
|
|
2077
|
+
console.log('No observed agents match.')
|
|
2078
|
+
return
|
|
2079
|
+
}
|
|
2080
|
+
for (const r of result.reports) {
|
|
2081
|
+
const o = r.taskOutcomes
|
|
2082
|
+
const when = r.lastSeenAt ? r.lastSeenAt.slice(0, 19).replace('T', ' ') : 'unknown'
|
|
2083
|
+
console.log(`${r.agentId} (${r.hostType}) ${r.active ? 'active' : 'idle'}`)
|
|
2084
|
+
console.log(
|
|
2085
|
+
` tasks: ${o.total} total, ${o.success} success, ${o.failure} failure (${o.successRate}% success)`,
|
|
2086
|
+
)
|
|
2087
|
+
console.log(` memories: ${r.memories} experience reuses: ${r.experienceReuses.total} (verified ${r.experienceReuses.verified})`)
|
|
2088
|
+
console.log(` last seen: ${when} | sessions=${r.sessionCount}`)
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
function phase0Command(args, useJson) {
|
|
2093
|
+
const sub = args[1]
|
|
2094
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
2095
|
+
console.log(`Usage:
|
|
2096
|
+
mio phase0 report Show the Phase 0 validation report (memory/trace/reuse evidence)
|
|
2097
|
+
mio phase0 report --format markdown Render as Markdown
|
|
2098
|
+
mio phase0 report --json Machine-readable report (same shape as mio.phase0.report)
|
|
2099
|
+
|
|
2100
|
+
Options:
|
|
2101
|
+
--project name Project filter (defaults to the current directory name)
|
|
2102
|
+
--format markdown Human-readable Markdown instead of JSON`)
|
|
2103
|
+
if (!sub) process.exitCode = 1
|
|
2104
|
+
return
|
|
2105
|
+
}
|
|
2106
|
+
if (sub !== 'report') {
|
|
2107
|
+
console.error(`Unknown phase0 subcommand: ${sub}`)
|
|
2108
|
+
process.exitCode = 1
|
|
2109
|
+
return
|
|
2110
|
+
}
|
|
2111
|
+
|
|
2112
|
+
const flags = args.slice(2)
|
|
2113
|
+
const format = (optionValue(flags, '--format') || '').toLowerCase()
|
|
2114
|
+
let report
|
|
2115
|
+
try {
|
|
2116
|
+
report = loadPhase0(MIO_HOME, optionValue(flags, '--project') || projectName())
|
|
2117
|
+
} catch (error) {
|
|
2118
|
+
console.error(error.message || error)
|
|
2119
|
+
process.exitCode = 1
|
|
2120
|
+
return
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
if (useJson) {
|
|
2124
|
+
return jsonOrText({ project: optionValue(flags, '--project') || projectName(), ...report }, true)
|
|
2125
|
+
}
|
|
2126
|
+
// Markdown is the human-readable form; the MCP tool exposes the same renderer
|
|
2127
|
+
// via --format markdown, so text and MCP output cannot diverge.
|
|
2128
|
+
console.log(renderPhase0Markdown(report))
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
function hostCommand(args, useJson) {
|
|
2132
|
+
const sub = args[1]
|
|
2133
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
2134
|
+
console.log(`Usage:
|
|
2135
|
+
mio host capabilities Show what each host supports and whether it is installed
|
|
2136
|
+
mio host capabilities --json Machine-readable (same shape as mio.host.capabilities)
|
|
2137
|
+
|
|
2138
|
+
Notes:
|
|
2139
|
+
"installed" is probed live from each adapter, so it reflects this machine right
|
|
2140
|
+
now -- unlike \`mio install <host>\`, which only records intent in config.json.`)
|
|
2141
|
+
if (!sub) process.exitCode = 1
|
|
2142
|
+
return
|
|
2143
|
+
}
|
|
2144
|
+
if (sub !== 'capabilities') {
|
|
2145
|
+
console.error(`Unknown host subcommand: ${sub}`)
|
|
2146
|
+
process.exitCode = 1
|
|
2147
|
+
return
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
const result = listHostCapabilities()
|
|
2151
|
+
if (useJson) return jsonOrText(result, true)
|
|
2152
|
+
|
|
2153
|
+
console.log(`Host capabilities: ${result.hosts.length} host(s)`)
|
|
2154
|
+
for (const host of result.hosts) {
|
|
2155
|
+
const mark = host.installed ? 'installed' : 'not installed'
|
|
2156
|
+
console.log(` ${host.name.padEnd(10)} ${mark}`)
|
|
2157
|
+
console.log(` ${host.capabilities.join(', ')}`)
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
function recordOutcomeCommand(args, useJson) {
|
|
2162
|
+
const flags = args.slice(2)
|
|
2163
|
+
const outcome = String(optionValue(flags, '--outcome') || '').toLowerCase()
|
|
2164
|
+
if (!['success', 'failure', 'aborted'].includes(outcome)) {
|
|
2165
|
+
console.error('mio task record-outcome requires --outcome success|failure|aborted')
|
|
2166
|
+
process.exitCode = 1
|
|
2167
|
+
return
|
|
2168
|
+
}
|
|
2169
|
+
const project = optionValue(flags, '--project') || projectName()
|
|
2170
|
+
// Mirrors the store default so the preview reports the same agent that the
|
|
2171
|
+
// applied run would use.
|
|
2172
|
+
const agentId = optionValue(flags, '--agent-id') || 'cli'
|
|
2173
|
+
const task = optionValue(flags, '--task') || ''
|
|
2174
|
+
const summary = optionValue(flags, '--summary') || ''
|
|
2175
|
+
const verification = optionValue(flags, '--verification') || ''
|
|
2176
|
+
const traceId = optionValue(flags, '--trace-id') || ''
|
|
2177
|
+
|
|
2178
|
+
// Preview by default, like every other CLI write (mio memory archive,
|
|
2179
|
+
// mio agents register): nothing is written without --yes, --json included.
|
|
2180
|
+
if (!flagPresent(flags, '--yes')) {
|
|
2181
|
+
const existing = cliAgentStore()
|
|
2182
|
+
.listAgents({ project })
|
|
2183
|
+
.agents.find((a) => a.agentId === agentId)
|
|
2184
|
+
if (useJson) {
|
|
2185
|
+
jsonOrText(
|
|
2186
|
+
{
|
|
2187
|
+
preview: true,
|
|
2188
|
+
applied: false,
|
|
2189
|
+
outcome,
|
|
2190
|
+
agentId,
|
|
2191
|
+
project,
|
|
2192
|
+
task,
|
|
2193
|
+
summary,
|
|
2194
|
+
verification,
|
|
2195
|
+
agentWillUpdate: !!existing,
|
|
2196
|
+
currentTaskCount: existing ? existing.taskCount || 0 : null,
|
|
2197
|
+
hint: 'Re-run with --yes to apply.',
|
|
2198
|
+
},
|
|
2199
|
+
true,
|
|
2200
|
+
)
|
|
2201
|
+
} else {
|
|
2202
|
+
console.log(`Record outcome preview: ${outcome} (project=${project}, agent=${agentId})`)
|
|
2203
|
+
if (task) console.log(` task: ${task}`)
|
|
2204
|
+
if (summary) console.log(` summary: ${summary}`)
|
|
2205
|
+
if (existing) {
|
|
2206
|
+
const nextTasks = (existing.taskCount || 0) + 1
|
|
2207
|
+
const nextSuccess = (existing.successCount || 0) + (outcome === 'success' ? 1 : 0)
|
|
2208
|
+
console.log(
|
|
2209
|
+
` will update agent (taskCount ${existing.taskCount || 0} -> ${nextTasks}, successCount ${existing.successCount || 0} -> ${nextSuccess})`,
|
|
2210
|
+
)
|
|
2211
|
+
} else {
|
|
2212
|
+
console.log(` agent not registered: trace only, agent registry untouched`)
|
|
2213
|
+
console.log(` register it with: mio agents register --agent-id ${agentId} --yes`)
|
|
2214
|
+
}
|
|
2215
|
+
console.log('Re-run with --yes to apply.')
|
|
2216
|
+
}
|
|
2217
|
+
process.exitCode = 1
|
|
2218
|
+
return
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
let result
|
|
2222
|
+
try {
|
|
2223
|
+
result = cliTaskStore().recordTaskOutcome({
|
|
2224
|
+
outcome,
|
|
2225
|
+
agentId,
|
|
2226
|
+
project,
|
|
2227
|
+
task,
|
|
2228
|
+
summary,
|
|
2229
|
+
verification,
|
|
2230
|
+
traceId,
|
|
2231
|
+
})
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
console.error(error.message || error)
|
|
2234
|
+
process.exitCode = 1
|
|
2235
|
+
return
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
if (useJson) return jsonOrText(result, true)
|
|
2239
|
+
console.log(`Recorded ${result.event.outcome} for ${result.event.agent} (project=${result.event.project})`)
|
|
2240
|
+
console.log(` trace: ${result.event.trace_id}`)
|
|
2241
|
+
console.log(` agent registry: ${result.agentUpdated ? 'updated' : 'not updated (agent not registered)'}`)
|
|
2242
|
+
if (result.autoClaims) console.log(` auto-claims: ${result.autoClaims.length}`)
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
function taskCommand(args, useJson) {
|
|
2246
|
+
const sub = args[1]
|
|
2247
|
+
if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
|
|
2248
|
+
console.log(`Usage:
|
|
2249
|
+
mio task route "<task>" Which verified experiences apply to this task
|
|
2250
|
+
mio task route "<task>" --json Machine-readable (same shape as mio.task.route)
|
|
2251
|
+
|
|
2252
|
+
Options:
|
|
2253
|
+
--project name Project filter (defaults to the current directory name)
|
|
2254
|
+
--scope all|project|global Memory scope to consider
|
|
2255
|
+
--limit N Max routes (1-10, default 5)
|
|
2256
|
+
|
|
2257
|
+
This is read-only: unlike the MCP call it does not write to the query log.`)
|
|
2258
|
+
if (!sub) process.exitCode = 1
|
|
2259
|
+
return
|
|
2260
|
+
}
|
|
2261
|
+
if (sub === 'record-outcome' || sub === 'record_outcome') {
|
|
2262
|
+
return recordOutcomeCommand(args, useJson)
|
|
2263
|
+
}
|
|
2264
|
+
|
|
2265
|
+
if (sub !== 'route') {
|
|
2266
|
+
console.error(`Unknown task subcommand: ${sub}`)
|
|
2267
|
+
process.exitCode = 1
|
|
2268
|
+
return
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
const flags = args.slice(2)
|
|
2272
|
+
// The task is free text and may be quoted as one argument or passed as
|
|
2273
|
+
// several words. Only flags this command understands are treated as options.
|
|
2274
|
+
const TASK_KNOWN_FLAGS = new Set(['--project', '--scope', '--limit', '--json'])
|
|
2275
|
+
const taskParts = []
|
|
2276
|
+
for (let i = 0; i < flags.length; i += 1) {
|
|
2277
|
+
const token = flags[i]
|
|
2278
|
+
if (TASK_KNOWN_FLAGS.has(token)) continue
|
|
2279
|
+
if (i > 0 && TASK_KNOWN_FLAGS.has(flags[i - 1])) continue
|
|
2280
|
+
taskParts.push(token)
|
|
2281
|
+
}
|
|
2282
|
+
const task = taskParts.join(' ').trim()
|
|
2283
|
+
if (!task) {
|
|
2284
|
+
console.error('mio task route requires a task, e.g. mio task route "publish the npm package"')
|
|
2285
|
+
process.exitCode = 1
|
|
2286
|
+
return
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
let result
|
|
2290
|
+
try {
|
|
2291
|
+
result = cliTaskStore().routeTask({
|
|
2292
|
+
task,
|
|
2293
|
+
project: optionValue(flags, '--project'),
|
|
2294
|
+
scope: optionValue(flags, '--scope'),
|
|
2295
|
+
limit: parseNumberOption(flags, '--limit'),
|
|
2296
|
+
persistQuery: false,
|
|
2297
|
+
})
|
|
2298
|
+
} catch (error) {
|
|
2299
|
+
console.error(error.message || error)
|
|
2300
|
+
process.exitCode = 1
|
|
2301
|
+
return
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
if (useJson) return jsonOrText(result, true)
|
|
2305
|
+
printTaskRoute(result)
|
|
2306
|
+
}
|
|
2307
|
+
|
|
2308
|
+
function printTaskRoute(result) {
|
|
2309
|
+
console.log(`Task route: "${result.task}" (project=${result.project || 'all'}, scope=${result.scope})`)
|
|
2310
|
+
console.log(`verified routes: ${result.count}`)
|
|
2311
|
+
|
|
2312
|
+
if (result.count > 0) {
|
|
2313
|
+
console.log('')
|
|
2314
|
+
console.log('Routes (apply the top match first):')
|
|
2315
|
+
result.routes.forEach((route, index) => {
|
|
2316
|
+
const via = route.sourceAgents.length > 0 ? route.sourceAgents.join(', ') : '?'
|
|
2317
|
+
const to = route.targetAgents.length > 0 ? route.targetAgents.join(', ') : '?'
|
|
2318
|
+
console.log(
|
|
2319
|
+
`${index + 1}. [score ${route.score}] ${route.experienceId} — reused ${route.reuseCount}x${route.confirmed ? ' (confirmed)' : ''}`,
|
|
2320
|
+
)
|
|
2321
|
+
console.log(` ${String(route.memory.content).replace(/\s+/g, ' ').slice(0, 110)}`)
|
|
2322
|
+
console.log(` ${via} -> ${to}`)
|
|
2323
|
+
})
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
if (result.relatedMemories.length > 0) {
|
|
2327
|
+
console.log('')
|
|
2328
|
+
console.log('Related memories:')
|
|
2329
|
+
for (const record of result.relatedMemories) {
|
|
2330
|
+
console.log(`- ${record.id} ${String(record.content).replace(/\s+/g, ' ').slice(0, 100)}`)
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
if (result.summary && result.summary.routingSignal) {
|
|
2335
|
+
console.log(`\nAgent health: ${result.summary.routingSignal}`)
|
|
2336
|
+
}
|
|
2337
|
+
if (result.summary && result.summary.suggestion) {
|
|
2338
|
+
console.log(`\n${result.summary.suggestion}`)
|
|
583
2339
|
}
|
|
584
2340
|
}
|
|
585
2341
|
|
|
@@ -689,8 +2445,12 @@ Usage:
|
|
|
689
2445
|
mio mcp Start Mio MCP server (stdio)
|
|
690
2446
|
mio install <host> Install Mio into a host (codex|opencode|workbuddy|hermes|claude)
|
|
691
2447
|
mio status Show runtime and adapter status
|
|
692
|
-
mio agents List installed
|
|
2448
|
+
mio agents List installed host adapters
|
|
2449
|
+
mio agents list List observed agents (from agents.jsonl, --project X)
|
|
2450
|
+
mio agents report Report per-agent task/memory/reuse telemetry (--agent X, --project Y)
|
|
2451
|
+
mio agents register --agent-id X Register an observed agent (--yes to apply; previews by default)
|
|
693
2452
|
mio evolution status Show composed evolution module health
|
|
2453
|
+
mio evolution report Cross-agent evolution report: ecosystem, agents, memory health, suggestions (--period 24h|7d|30d|all)
|
|
694
2454
|
mio evolution shadow record Record a shadow comparison sample
|
|
695
2455
|
mio evolution dual-write record Record a dual-write comparison sample
|
|
696
2456
|
mio evolution cutover readiness Assess shadow/dual-write cutover readiness
|
|
@@ -702,6 +2462,32 @@ Usage:
|
|
|
702
2462
|
mio recall "<query>" Search Mio memory from the terminal (same ranking as mio.memory.query)
|
|
703
2463
|
mio traces Show recent observer traces (--type/--outcome/--agent/--since/--limit/--compact)
|
|
704
2464
|
mio remember "<content>" Write a memory record from the terminal (same schema as mio.memory.record)
|
|
2465
|
+
mio memory analyze Report duplicates, low-quality records and kind histogram
|
|
2466
|
+
mio memory archive --ids a,b Archive records (soft delete; --yes required to apply)
|
|
2467
|
+
mio memory forget --ids a,b PERMANENTLY delete records (--yes required; not undoable)
|
|
2468
|
+
mio memory restore --ids a,b Un-archive previously archived records
|
|
2469
|
+
mio memory merge --ids a,b Merge duplicates into one survivor (--yes required to apply)
|
|
2470
|
+
mio memory migrate --ids a,b --scope global|project Move records between layers
|
|
2471
|
+
mio experience list List experience reuse (--status pending|confirmed|verified)
|
|
2472
|
+
mio experience confirm --ids a,b Confirm auto-claimed reuse (bulk supported)
|
|
2473
|
+
mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse
|
|
2474
|
+
mio creativity status Show creativity hypothesis counts and recent top ideas
|
|
2475
|
+
mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --limit N)
|
|
2476
|
+
mio creativity generate Generate hypotheses from 2+ --source "name|content" (calls an LLM)
|
|
2477
|
+
mio creativity ferment Review and refine active hypotheses (calls an LLM)
|
|
2478
|
+
mio insight status Insight counts: total, reported, unreported, high-value
|
|
2479
|
+
mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
|
|
2480
|
+
mio insight mark-reported Mark insights as reported (--ids a,b)
|
|
2481
|
+
mio observer <view> Observer pipeline views (research pipeline, not the observe daemon):
|
|
2482
|
+
status | world-model | trends | research | insights | essays | dag
|
|
2483
|
+
mio observer ingest --trace-id T --event-type E Record a trace event (--payload/--outcome)
|
|
2484
|
+
mio observer subscribe --event-types a,b Subscribe to events (--yes to apply)
|
|
2485
|
+
mio observer digest New events since the last digest (advances cursor)
|
|
2486
|
+
mio phase0 report Show the Phase 0 validation report (--project X, --format markdown)
|
|
2487
|
+
mio host capabilities Show what each host supports and whether it is installed
|
|
2488
|
+
mio task route "<task>" Which verified experiences apply to this task (--project/--scope/--limit)
|
|
2489
|
+
mio task record-outcome --outcome success Record a task outcome (--yes to apply; previews by default)
|
|
2490
|
+
mio policy check "<action>" Check historical risk for an action before running it
|
|
705
2491
|
mio prune --days 30 Trim old traces/queries/reuse records and observe.log (--dry-run to preview; --memory needs --yes)
|
|
706
2492
|
mio digest --days 7 Aggregate traces/memory/reuse into an actionable report (--write-back feeds agent context files; --json)
|
|
707
2493
|
mio --json status Machine-readable status
|
|
@@ -727,7 +2513,7 @@ async function main() {
|
|
|
727
2513
|
case 'status':
|
|
728
2514
|
return statusCommand(useJson)
|
|
729
2515
|
case 'agents':
|
|
730
|
-
return agentsCommand(useJson)
|
|
2516
|
+
return agentsCommand(args, useJson)
|
|
731
2517
|
case 'observe':
|
|
732
2518
|
return observeCommand(args)
|
|
733
2519
|
case 'recall':
|
|
@@ -736,6 +2522,24 @@ async function main() {
|
|
|
736
2522
|
return tracesCommand(args, useJson)
|
|
737
2523
|
case 'remember':
|
|
738
2524
|
return rememberCommand(args, useJson)
|
|
2525
|
+
case 'memory':
|
|
2526
|
+
return memoryCommand(args, useJson)
|
|
2527
|
+
case 'experience':
|
|
2528
|
+
return experienceCommand(args, useJson)
|
|
2529
|
+
case 'policy':
|
|
2530
|
+
return policyCommand(args, useJson)
|
|
2531
|
+
case 'creativity':
|
|
2532
|
+
return creativityCommand(args, useJson)
|
|
2533
|
+
case 'insight':
|
|
2534
|
+
return insightCommand(args, useJson)
|
|
2535
|
+
case 'observer':
|
|
2536
|
+
return observerCommand(args, useJson)
|
|
2537
|
+
case 'phase0':
|
|
2538
|
+
return phase0Command(args, useJson)
|
|
2539
|
+
case 'host':
|
|
2540
|
+
return hostCommand(args, useJson)
|
|
2541
|
+
case 'task':
|
|
2542
|
+
return taskCommand(args, useJson)
|
|
739
2543
|
case 'prune':
|
|
740
2544
|
return pruneCommand(args, useJson)
|
|
741
2545
|
case 'digest':
|