mio-agent-runtime 0.13.4 → 0.14.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/bin/mio.js CHANGED
@@ -23,10 +23,12 @@ const { createEvolutionCutoverTools } = require('../server/evolution-cutover.js'
23
23
  const { createMemoryStore } = require('../server/memory-store.js')
24
24
  const { createExperienceStore } = require('../server/experience-store.js')
25
25
  const { createPolicyStore } = require('../server/policy-store.js')
26
- const { CreativityEngine } = require('../server/creativity-engine.js')
26
+ const { CreativityEngine, sourcesFromInsights } = require('../server/creativity-engine.js')
27
+ const { buildAutoSources, topUpSources } = require('../server/creativity-sources.js')
28
+ const { runIdeaGenerate, groundingFrom } = require('../server/idea-generate.js')
27
29
  const { createAgentStore } = require('../server/agent-store.js')
28
30
  const { createEvaluationStore } = require('../server/evaluation-store.js')
29
- const { createInsightStore } = require('../server/insight-store.js')
31
+ const { createInsightStore, isInsightAvailable } = require('../server/insight-store.js')
30
32
  const { createObserverStore, isObserverAvailable } = require('../server/observer-store.js')
31
33
  const { loadPhase0, renderPhase0Markdown } = require('../server/mio-intelligence-mcp/phase0.js')
32
34
  const { listHostCapabilities } = require('../server/host-capabilities.js')
@@ -564,7 +566,9 @@ function digestCommand(args, useJson) {
564
566
  let state = {}
565
567
  try {
566
568
  state = JSON.parse(fs.readFileSync(stateFile, 'utf8'))
567
- } catch (_) {}
569
+ } catch (err) {
570
+ throw new Error(`Failed to parse ${stateFile}: ${err.message}`)
571
+ }
568
572
  state.contexts = state.contexts || {}
569
573
  const stamp = new Date().toLocaleString('zh-CN', { hour12: false })
570
574
 
@@ -595,6 +599,7 @@ function digestCommand(args, useJson) {
595
599
  }
596
600
 
597
601
  let written = 0
602
+ const writeBackFailures = []
598
603
  for (const cwd of targetCwds) {
599
604
  const line = projectLine.get(path.basename(cwd).toLowerCase())
600
605
  if (!line) continue
@@ -602,13 +607,18 @@ function digestCommand(args, useJson) {
602
607
  observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'CLAUDE.md')
603
608
  observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'AGENTS.md')
604
609
  written += 1
605
- } catch (_) {}
610
+ } catch (err) {
611
+ const wsName = path.basename(cwd)
612
+ console.error(`Write-back failed for ${wsName}:`, err.message || err)
613
+ writeBackFailures.push({ workspace: wsName, error: err.message || String(err) })
614
+ }
606
615
  }
607
616
  fs.mkdirSync(MIO_HOME, { recursive: true })
608
617
  fs.writeFileSync(stateFile, JSON.stringify(state) + '\n', 'utf8')
609
618
  report.writeBack = {
610
619
  workspaces: written,
611
620
  skipped: Object.keys(state.contexts).length - written,
621
+ failures: writeBackFailures,
612
622
  projects: [...projectLine.values()],
613
623
  }
614
624
  }
@@ -626,6 +636,10 @@ function digestCommand(args, useJson) {
626
636
  console.log(
627
637
  `Write-back: ${report.writeBack.workspaces} workspace(s), ${report.writeBack.skipped} skipped (no digest data)`,
628
638
  )
639
+ if (report.writeBack.failures && report.writeBack.failures.length > 0) {
640
+ console.error(` ${report.writeBack.failures.length} write-back failure(s):`)
641
+ for (const f of report.writeBack.failures) console.error(` - ${f.workspace}: ${f.error}`)
642
+ }
629
643
  for (const line of report.writeBack.projects) console.log(` - ${line}`)
630
644
  }
631
645
  }
@@ -2053,22 +2067,26 @@ function observerCommand(args, useJson) {
2053
2067
  function creativityUsage(write = console.log) {
2054
2068
  write(`Usage:
2055
2069
  mio creativity status Show hypothesis counts and recent top ideas
2056
- mio creativity list List hypotheses (--status active|validated|rejected|draft, --limit N)
2070
+ mio creativity list List hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
2057
2071
 
2058
2072
  mio creativity generate Generate hypotheses by combining sources (needs 2+ --source, calls an LLM)
2059
2073
  mio creativity ferment Review/refine active hypotheses (calls an LLM)
2060
2074
 
2061
2075
  Options:
2062
2076
  --status name Filter list by status
2063
- --limit N Max results for list / ferment (default 20 / 5)
2077
+ --limit N Max results for list / ferment (default 20 / 5); --limit 0 lists everything
2078
+ --sort novelty Rank list by relative noveltyScore (a dedup signal, not a quality score)
2064
2079
  --source "name|content" A concept source for generate; repeat 2+ times
2080
+ --from-insights Seed generate's sources from stored insights (needs @akemi-mio/insight)
2065
2081
  --strategy explore|signal|stable Generation strategy (auto-selected if omitted)
2066
2082
  --json Machine-readable output (same shape as the mio.creativity.* tools)
2067
2083
 
2068
2084
  Examples:
2069
2085
  mio creativity status
2070
2086
  mio creativity list --status rejected --limit 10
2087
+ mio creativity list --sort novelty
2071
2088
  mio creativity generate --source "auth|token rotation" --source "cache|write-through"
2089
+ mio creativity generate --from-insights --source "auth|token rotation"
2072
2090
 
2073
2091
  generate/ferment call an LLM using the shared client. Configure it with
2074
2092
  LLM_API_URL / LLM_KEY / LLM_CHAT_MODEL (the same environment the MCP server
@@ -2079,7 +2097,7 @@ reads). A local Ollama works:
2079
2097
  function printCreativityStatus(result) {
2080
2098
  console.log('Creativity engine:')
2081
2099
  console.log(` hypotheses: ${result.hypotheses} combos: ${result.combos} experiments: ${result.experiments}`)
2082
- console.log(` active: ${result.active} validated: ${result.validated} rejected: ${result.rejected}`)
2100
+ console.log(` active: ${result.active} validated: ${result.validated} rejected: ${result.rejected} draft: ${result.draft}`)
2083
2101
  if (result.recentIdeas.length === 0) {
2084
2102
  console.log('No hypotheses yet.')
2085
2103
  return
@@ -2100,7 +2118,7 @@ function printCreativityList(items) {
2100
2118
  items.forEach((h, index) => {
2101
2119
  const when = h.createdAt ? new Date(h.createdAt).toISOString().slice(0, 19).replace('T', ' ') : ''
2102
2120
  const labels = Array.isArray(h.sourceLabels) && h.sourceLabels.length > 0 ? h.sourceLabels.join(' + ') : '—'
2103
- console.log(`${index + 1}. [${h.status}] ${h.title} (N=${h.novelty} F=${h.feasibility} I=${h.impact} score=${h.score})`)
2121
+ console.log(`${index + 1}. [${h.status}] ${h.title} (N=${h.novelty} F=${h.feasibility} I=${h.impact} score=${h.score}${h.noveltyScore !== undefined ? ` noveltyScore=${h.noveltyScore}` : ''})`)
2104
2122
  console.log(` ${labels} | ${when} | ${h.id}`)
2105
2123
  if (h.fermentCount) console.log(` fermented ${h.fermentCount}x`)
2106
2124
  })
@@ -2136,12 +2154,85 @@ function warnIfLlmUnconfigured() {
2136
2154
  console.log('(or set LLM_API_URL / LLM_KEY / LLM_CHAT_MODEL in the environment)')
2137
2155
  }
2138
2156
 
2157
+ // Failed LLM calls used to disappear: `generate` swallowed them and `ferment`
2158
+ // reported "Nothing fermented", so a dead endpoint looked exactly like "no new
2159
+ // ideas". Both now surface them; the JSON shapes carry `errors` as well.
2160
+ function printCreativityErrors(errors, label) {
2161
+ if (!Array.isArray(errors) || errors.length === 0) return
2162
+ console.log(`\n${errors.length} ${label}(s) failed:`)
2163
+ for (const err of errors) {
2164
+ const who = err.pair ? err.pair.join(' + ') : (err.title || err.id)
2165
+ console.log(`- ${who}: ${err.error}`)
2166
+ }
2167
+ }
2168
+
2169
+ // Auto sources for generate's top-up: the same local providers the MCP server
2170
+ // uses, assembled from the CLI's global MIO_HOME (spec Capability 2). Every
2171
+ // layer degrades to [] — a missing optional package or an unreadable store
2172
+ // must never fail `mio creativity generate`.
2173
+ function cliAutoSources() {
2174
+ let insights = []
2175
+ let trends = []
2176
+ try {
2177
+ if (isInsightAvailable()) insights = cliInsightStore().list({}) || []
2178
+ } catch (_) {}
2179
+ try {
2180
+ if (isObserverAvailable()) trends = cliObserverStore().trends({ limit: 3 }) || []
2181
+ } catch (_) {}
2182
+ try {
2183
+ return buildAutoSources({ dataDir: MIO_HOME, insights, trends })
2184
+ } catch (_) {
2185
+ return []
2186
+ }
2187
+ }
2188
+
2139
2189
  function creativityGenerateCommand(args, useJson) {
2140
2190
  const flags = args.slice(2)
2141
- const sources = parseSources(flags)
2191
+ let sources = parseSources(flags)
2192
+
2193
+ // `--from-insights` seeds the mixer with whatever the insight engine has
2194
+ // already noticed. It reads the *CLI's* store (global MIO_HOME), the same
2195
+ // one `mio insight list` shows, and merges with any explicit --source.
2196
+ if (flagPresent(flags, '--from-insights')) {
2197
+ if (!isInsightAvailable()) {
2198
+ console.error('mio creativity generate --from-insights requires the optional package @akemi-mio/insight')
2199
+ process.exitCode = 1
2200
+ return
2201
+ }
2202
+ let seeds
2203
+ try {
2204
+ seeds = sourcesFromInsights(cliInsightStore().list({}))
2205
+ } catch (error) {
2206
+ console.error(error.message || error)
2207
+ process.exitCode = 1
2208
+ return
2209
+ }
2210
+ if (seeds.length === 0) {
2211
+ console.error('mio creativity generate --from-insights: no stored insights yet (run `mio insight generate` first)')
2212
+ process.exitCode = 1
2213
+ return
2214
+ }
2215
+ sources = sources.concat(seeds)
2216
+ }
2217
+
2218
+ // Auto top-up (spec Capability 2, injection point 2): explicit --source
2219
+ // entries win; when fewer than two remain, local memory/traces/stored
2220
+ // hypotheses (plus insight/observer observations) fill the gap. If the
2221
+ // topped-up set is still short, the errors below keep their old wording.
2142
2222
  if (sources.length < 2) {
2143
- console.error('mio creativity generate requires at least two --source "name|content" arguments')
2144
- console.error('Example: mio creativity generate --source "auth|token rotation" --source "cache|write-through cache"')
2223
+ const topped = topUpSources(sources, cliAutoSources())
2224
+ if (topped.length >= 2) sources = topped
2225
+ }
2226
+
2227
+ if (sources.length < 2) {
2228
+ if (flagPresent(flags, '--from-insights')) {
2229
+ console.error(`mio creativity generate needs at least 2 concept sources; --from-insights only supplied ${sources.length}.`)
2230
+ console.error('Add more --source "name|content" arguments, or generate more insights first.')
2231
+ } else {
2232
+ console.error('mio creativity generate requires at least two --source "name|content" arguments')
2233
+ console.error('Example: mio creativity generate --source "auth|token rotation" --source "cache|write-through cache"')
2234
+ console.error('Or seed them with --from-insights (uses stored insights as concepts).')
2235
+ }
2145
2236
  process.exitCode = 1
2146
2237
  return
2147
2238
  }
@@ -2170,6 +2261,7 @@ function creativityGenerateCommand(args, useJson) {
2170
2261
  console.log(` novelty=${idea.novelty} feasibility=${idea.feasibility} impact=${idea.impact} | ${idea.id}`)
2171
2262
  if (idea.rejectionReason) console.log(` rejected: ${idea.rejectionReason}`)
2172
2263
  })
2264
+ printCreativityErrors(resolved.errors, 'pair')
2173
2265
  })
2174
2266
  }
2175
2267
 
@@ -2191,15 +2283,103 @@ function creativityFermentCommand(args, useJson) {
2191
2283
  if (useJson) return jsonOrText(resolved, true)
2192
2284
  if (resolved.fermented === 0) {
2193
2285
  console.log(`Nothing fermented: ${resolved.reason || 'no eligible hypotheses'}`)
2286
+ // Every review can fail while there was something to review; without
2287
+ // this the endpoint outage read as "the store is empty".
2288
+ printCreativityErrors(resolved.errors, 'review')
2194
2289
  return
2195
2290
  }
2196
2291
  console.log(`Fermented ${resolved.fermented} hypothesis/hypotheses`)
2197
2292
  for (const r of resolved.results || []) {
2198
2293
  console.log(`- ${r.title || r.id}: ${r.verdict || 'updated'}${r.reason ? ` — ${r.reason}` : ''}`)
2199
2294
  }
2295
+ printCreativityErrors(resolved.errors, 'review')
2200
2296
  })
2201
2297
  }
2202
2298
 
2299
+ function ideaUsage() {
2300
+ console.error('usage: mio idea generate --goal "one line" [--context "..."] [--constraint "..."] [--num N] [--json]')
2301
+ }
2302
+
2303
+ async function ideaGenerateCommand(args, useJson) {
2304
+ const flags = args.slice(2)
2305
+ const goal = (optionValue(flags, '--goal') || '').trim()
2306
+ if (!goal) {
2307
+ console.error('mio idea generate requires --goal "..."')
2308
+ ideaUsage()
2309
+ process.exitCode = 1
2310
+ return
2311
+ }
2312
+ const context = optionValue(flags, '--context') || ''
2313
+ const constraints = optionValues(flags, '--constraint')
2314
+ let numIdeas
2315
+ try {
2316
+ numIdeas = parseNumberOption(flags, '--num')
2317
+ } catch (error) {
2318
+ console.error(error.message || error)
2319
+ ideaUsage()
2320
+ process.exitCode = 1
2321
+ return
2322
+ }
2323
+
2324
+ // Grounding from the CLI's global MIO_HOME. A failed query degrades to "no
2325
+ // grounding" — never a hard error (spec Capability 1: local-first, tolerant).
2326
+ // The core logger echoes every event to stdout; in --json mode that would
2327
+ // corrupt the payload jsonOrText must own, so the echo is routed to stderr
2328
+ // while the pipeline runs (executed deviation — the plan assumed pure JSON).
2329
+ const echoLog = useJson ? console.log : null
2330
+ if (useJson) console.log = (...params) => console.error(...params)
2331
+
2332
+ let result
2333
+ try {
2334
+ let grounding = { sources: [], relatedMemoryIds: [], groundedWith: 0 }
2335
+ try {
2336
+ const qr = await cliMemoryStore().queryMemory({ query: goal, limit: 3, scope: 'all' })
2337
+ grounding = groundingFrom(qr)
2338
+ } catch (_) {}
2339
+
2340
+ if (!useJson) warnIfLlmUnconfigured()
2341
+
2342
+ result = await runIdeaGenerate({
2343
+ goal,
2344
+ context,
2345
+ constraints,
2346
+ numIdeas,
2347
+ chatJson,
2348
+ store: cliCreativityEngine().store,
2349
+ groundingSources: grounding.sources,
2350
+ relatedMemoryIds: grounding.relatedMemoryIds,
2351
+ autoSources: cliAutoSources(),
2352
+ })
2353
+ } catch (error) {
2354
+ console.error(error.message || error)
2355
+ process.exitCode = 1
2356
+ return
2357
+ } finally {
2358
+ if (useJson) console.log = echoLog
2359
+ }
2360
+
2361
+ if (useJson) return jsonOrText(result, true)
2362
+ if (result.reason) {
2363
+ console.log(`No ideas generated: ${result.reason}`)
2364
+ return
2365
+ }
2366
+ console.log(`Generated ${result.ideas.length} idea(s) (grounded with ${result.groundedWith} memories)`)
2367
+ result.ideas.forEach((idea) => {
2368
+ console.log(`- [${idea.hypothesis.status}] ${idea.title}`)
2369
+ console.log(` novelty=${idea.hypothesis.novelty} feasibility=${idea.hypothesis.feasibility} impact=${idea.hypothesis.impact} | ${idea.hypothesis.id}`)
2370
+ if (idea.provenance && idea.provenance.technique) console.log(` technique=${idea.provenance.technique}`)
2371
+ if (idea.hypothesis.rejectionReason) console.log(` rejected: ${idea.hypothesis.rejectionReason}`)
2372
+ })
2373
+ console.log(`Persisted ${result.persistedIds.length} idea(s)`)
2374
+ }
2375
+
2376
+ function ideaCommand(args, useJson) {
2377
+ const sub = args[1]
2378
+ if (sub === 'generate') return ideaGenerateCommand(args, useJson)
2379
+ ideaUsage()
2380
+ process.exitCode = 1
2381
+ }
2382
+
2203
2383
  function creativityCommand(args, useJson) {
2204
2384
  const sub = args[1]
2205
2385
 
@@ -2226,9 +2406,16 @@ function creativityCommand(args, useJson) {
2226
2406
  if (sub === 'status') {
2227
2407
  result = engine.status()
2228
2408
  } else {
2409
+ const sort = optionValue(flags, '--sort')
2410
+ if (sort !== undefined && sort !== 'novelty') {
2411
+ console.error(`mio creativity list: unknown --sort "${sort}" (supported: novelty)`)
2412
+ process.exitCode = 1
2413
+ return
2414
+ }
2229
2415
  result = engine.list({
2230
2416
  status: optionValue(flags, '--status'),
2231
2417
  limit: parseNumberOption(flags, '--limit'),
2418
+ sort,
2232
2419
  })
2233
2420
  }
2234
2421
  } catch (error) {
@@ -3136,22 +3323,33 @@ function installCommand(host, useJson) {
3136
3323
  }
3137
3324
  writeConfig(config)
3138
3325
  }
3326
+ let observerCycleOk = true
3139
3327
  try {
3140
3328
  observer.runCycle(MIO_HOME, observer.loadState(MIO_HOME), observer.createSinks(MIO_HOME))
3141
- } catch (_) {}
3329
+ } catch (err) {
3330
+ console.error('Observer cycle failed during install:', err.message || err)
3331
+ observerCycleOk = false
3332
+ }
3142
3333
  let observerStarted = false
3143
3334
  try {
3144
3335
  observerStarted = observer.startBackground(MIO_HOME).started
3145
- } catch (_) {}
3336
+ } catch (err) {
3337
+ console.error('Observer background startup failed during install:', err.message || err)
3338
+ observerStarted = false
3339
+ }
3146
3340
  if (useJson) {
3147
3341
  result.observer = {
3148
3342
  running: observer.isRunning(MIO_HOME),
3149
3343
  started: observerStarted,
3344
+ cycleOk: observerCycleOk,
3150
3345
  }
3151
3346
  jsonOrText(result, true)
3152
3347
  return
3153
3348
  }
3154
3349
  console.log(result.message || `${host} adapter completed.`)
3350
+ if (!observerCycleOk) {
3351
+ console.error('Warning: observer cycle failed during install; run `mio observe --once` manually.')
3352
+ }
3155
3353
  console.log(
3156
3354
  observer.isRunning(MIO_HOME)
3157
3355
  ? 'Observer daemon running.'
@@ -3160,6 +3358,13 @@ function installCommand(host, useJson) {
3160
3358
  }
3161
3359
 
3162
3360
  function mcpCommand() {
3361
+ // The MCP server resolves its data dir as `MIO_DATA_DIR || <cwd>/.mio-intelligence`,
3362
+ // while every CLI subcommand reads the global MIO_HOME. Left alone, `mio mcp`
3363
+ // launched from a scratch directory built a second, empty store there and
3364
+ // `mio.creativity.list` / `mio.recall` disagreed with `mio creativity list` /
3365
+ // `mio recall`. Pinning it makes the stdio server see the same data as the
3366
+ // terminal; an explicit MIO_DATA_DIR still wins (adapters set one on purpose).
3367
+ if (!process.env.MIO_DATA_DIR) process.env.MIO_DATA_DIR = MIO_HOME
3163
3368
  require(SERVER_SCRIPT)
3164
3369
  }
3165
3370
 
@@ -3288,9 +3493,10 @@ Usage:
3288
3493
  mio experience confirm --ids a,b Confirm auto-claimed reuse (bulk supported)
3289
3494
  mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse
3290
3495
  mio creativity status Show creativity hypothesis counts and recent top ideas
3291
- mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --limit N)
3292
- mio creativity generate Generate hypotheses from 2+ --source "name|content" (calls an LLM)
3496
+ mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --sort novelty, --limit N)
3497
+ mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
3293
3498
  mio creativity ferment Review and refine active hypotheses (calls an LLM)
3499
+ mio idea generate --goal "..." Grounded idea pipeline: auto sources, novelty gate, persisted (calls an LLM)
3294
3500
  mio insight status Insight counts: total, reported, unreported, high-value
3295
3501
  mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
3296
3502
  mio insight generate Generate insights from context (--memory "kind|content"/--summary "text"; calls an LLM)
@@ -3354,6 +3560,8 @@ async function main() {
3354
3560
  return policyCommand(args, useJson)
3355
3561
  case 'creativity':
3356
3562
  return creativityCommand(args, useJson)
3563
+ case 'idea':
3564
+ return ideaCommand(args, useJson)
3357
3565
  case 'insight':
3358
3566
  return insightCommand(args, useJson)
3359
3567
  case 'observer':