mio-agent-runtime 0.13.3 → 0.13.5

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.
@@ -1,196 +1,197 @@
1
- 'use strict'
2
-
3
- const fs = require('fs')
4
- const path = require('path')
5
- const os = require('os')
6
- const crypto = require('crypto')
7
-
8
- function configDir() {
9
- return (
10
- process.env.WORKBUDDY_CONFIG_DIR ||
11
- process.env.CODEBUDDY_CONFIG_DIR ||
12
- path.join(os.homedir(), '.workbuddy')
13
- )
14
- }
15
-
16
- function configPath() {
17
- return path.join(configDir(), 'mcp.json')
18
- }
19
-
20
- function legacyConfigPath() {
21
- return path.join(configDir(), '.mcp.json')
22
- }
23
-
24
- function approvalsPath() {
25
- return path.join(configDir(), 'mcp-approvals.json')
26
- }
27
-
28
- // User-level memory rules file. WorkBuddy's codebuddy CLI (a CodeBuddy fork)
29
- // auto-loads CODEBUDDY.md (or CODEBUDDY.mdc) from the config dir into every
30
- // session (MemoryLoader user scope -> system-reminder-md / prompt context).
31
- // Note: PRODUCT_UPPERCASE_NAME in the CLI bundle is "CODEBUDDY", not "WORKBUDDY".
32
- function memoryRulesPath() {
33
- return path.join(configDir(), 'CODEBUDDY.md')
34
- }
35
-
36
- // Project-level memory rules. WorkBuddy walks up from the workspace dir and
37
- // auto-loads CODEBUDDY.md / AGENTS.md (project scope) into every session.
38
- function projectInstructionsPath(workspace) {
39
- return path.join(workspace || process.cwd(), 'AGENTS.md')
40
- }
41
-
42
- function readJson(file) {
43
- if (!fs.existsSync(file)) return null
44
- try {
45
- return JSON.parse(fs.readFileSync(file, 'utf8'))
46
- } catch (_) {
47
- return null
48
- }
49
- }
50
-
51
- const MEMORY_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
52
- const MEMORY_END = '<!-- MIO_INTELLIGENCE_END -->'
53
- const MEMORY_BLOCK = `${MEMORY_BEGIN}
54
- ## Mio Intelligence Layer
55
-
56
- Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
57
-
58
- - Before designing or implementing a solution, or when unsure about prior decisions, call \`mio-intelligence_mio.memory.query\` first. Omit \`project\` unless a specific project is required; Mio derives it from the current Git repository.
59
- - After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mio-intelligence_mio.memory.record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
60
- - When a tool call fails, retries, or returns an error, call \`mio-intelligence_mio.observer.ingest\` with a stable \`trace_id\`, relevant \`event_type\`, \`payload\`, and \`outcome\`.
61
- - Before a high-risk operation such as deletion, migration, credential change, or production change, call \`mio-intelligence_mio.policy.check\` and follow its suggestion when available.
62
- - At the end of every task, before producing the final answer, call \`mio-intelligence_mio.observer.ingest\` with \`event_type\` = \`task_outcome\`, a stable \`trace_id\`, \`outcome\` = \`success\` / \`failure\` / \`aborted\`, and a brief non-sensitive \`payload\`. If Mio is unavailable, do not block the final answer.
63
- - When you reuse a prior decision or experience returned by \`mio-intelligence_mio.memory.query\` and it changes your approach or outcome, call \`mio-intelligence_mio.experience.reuse\` with \`sourceAgent\`, \`targetAgent\`, \`experienceId\`, \`reuse\`, \`behaviorChanged\`, and \`outcomeImproved\`.
64
- - If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
65
- - Do not store secrets, credentials, or raw sensitive content in Mio memory.
66
- ${MEMORY_END}`
67
-
68
- function ensureBlock(target) {
69
- let content = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
70
- const original = content
71
- const start = content.indexOf(MEMORY_BEGIN)
72
- if (start !== -1) {
73
- const end = content.indexOf(MEMORY_END, start)
74
- content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + MEMORY_END.length)
75
- content = content.trimEnd()
76
- }
77
- if (content) content += '\n\n'
78
- content += MEMORY_BLOCK + '\n'
79
- if (content === original) return { changed: false, target, backup: null }
80
- const backup = `${target}.bak-mio-${Date.now()}`
81
- if (fs.existsSync(target)) fs.copyFileSync(target, backup)
82
- fs.mkdirSync(path.dirname(target), { recursive: true })
83
- fs.writeFileSync(target, content, 'utf8')
84
- return { changed: true, target, backup }
85
- }
86
-
87
- function isInstalled() {
88
- const config = readJson(configPath())
89
- const entry = config && config.mcpServers && config.mcpServers['mio-intelligence']
90
- if (!entry) return false
91
-
92
- const approvals = readJson(approvalsPath()) || {}
93
- const memory = fs.existsSync(memoryRulesPath()) ? fs.readFileSync(memoryRulesPath(), 'utf8') : ''
94
- return Boolean(
95
- approvals[approvalKey('mio-intelligence', entry)] &&
96
- memory.includes(MEMORY_BEGIN) &&
97
- memory.includes(MEMORY_END)
98
- )
99
- }
100
-
101
- function approvalKey(name, entry) {
102
- let input
103
- if (entry.command) {
104
- input = `${entry.command || ''}|${(entry.args || []).map(String).sort().join(',')}|${Object.keys(entry.env || {}).sort().join(',')}`
105
- } else if (entry.url) {
106
- try {
107
- input = new URL(entry.url).origin
108
- } catch (_) {
109
- input = entry.url
110
- }
111
- } else {
112
- input = JSON.stringify(entry)
113
- }
114
-
115
- const hash = crypto.createHash('sha256').update(input).digest('hex')
116
- return `${hash}::${name}`
117
- }
118
-
119
- function writeApproval(name, entry) {
120
- const target = approvalsPath()
121
- const approvals = readJson(target) || {}
122
- approvals[approvalKey(name, entry)] = Date.now()
123
- fs.mkdirSync(path.dirname(target), { recursive: true })
124
- fs.writeFileSync(target, `${JSON.stringify(approvals, null, 2)}\n`, 'utf8')
125
- }
126
-
127
- function install({ node, serverScript, home, workspace, project }) {
128
- const target = configPath()
129
- const canonical = readJson(target) || {}
130
- const legacy = readJson(legacyConfigPath()) || {}
131
- const canonicalServers =
132
- canonical.mcpServers && typeof canonical.mcpServers === 'object' ? canonical.mcpServers : {}
133
- const legacyServers =
134
- legacy.mcpServers && typeof legacy.mcpServers === 'object' ? legacy.mcpServers : {}
135
-
136
- const config = {
137
- ...legacy,
138
- ...canonical,
139
- mcpServers: {
140
- ...legacyServers,
141
- ...canonicalServers,
142
- },
143
- }
144
-
145
- const context = JSON.stringify({
146
- agentId: 'workbuddy',
147
- project: project || path.basename(workspace || process.cwd()),
148
- workspace: workspace || process.cwd(),
149
- sessionId: 'workbuddy-session',
150
- })
151
-
152
- config.mcpServers = {
153
- ...config.mcpServers,
154
- 'mio-intelligence': {
155
- command: node,
156
- args: [serverScript],
157
- env: {
158
- MIO_DATA_DIR: home,
159
- MIO_CONTEXT: context,
160
- },
161
- },
162
- }
163
-
164
- writeApproval('mio-intelligence', config.mcpServers['mio-intelligence'])
165
-
166
- const backup = `${target}.bak-mio-${Date.now()}`
167
- if (fs.existsSync(target)) {
168
- fs.copyFileSync(target, backup)
169
- }
170
-
171
- fs.mkdirSync(path.dirname(target), { recursive: true })
172
- fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
173
-
174
- const userRules = ensureBlock(memoryRulesPath())
175
- let projectRules = null
176
- const workspaceDir = workspace || process.cwd()
177
- if (workspaceDir && path.isAbsolute(workspaceDir)) {
178
- projectRules = ensureBlock(projectInstructionsPath(workspaceDir))
179
- }
180
-
181
- const changed = userRules.changed || Boolean(projectRules && projectRules.changed)
182
-
183
- return {
184
- changed,
185
- configPath: target,
186
- approvalsPath: approvalsPath(),
187
- memoryRulesPath: userRules.target,
188
- projectInstructionsPath: projectRules ? projectRules.target : null,
189
- userRulesChanged: userRules.changed,
190
- projectRulesChanged: projectRules ? projectRules.changed : false,
191
- backup,
192
- message: 'WorkBuddy Mio MCP installed, approved, and Mio rules injected into CODEBUDDY.md (auto-loaded user memory) and the workspace AGENTS.md. Restart WorkBuddy to load them.',
193
- }
194
- }
195
-
196
- module.exports = { configDir, configPath, approvalsPath, memoryRulesPath, projectInstructionsPath, approvalKey, isInstalled, install }
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const os = require('os')
6
+ const crypto = require('crypto')
7
+
8
+ function configDir() {
9
+ return (
10
+ process.env.WORKBUDDY_CONFIG_DIR ||
11
+ process.env.CODEBUDDY_CONFIG_DIR ||
12
+ path.join(os.homedir(), '.workbuddy')
13
+ )
14
+ }
15
+
16
+ function configPath() {
17
+ return path.join(configDir(), 'mcp.json')
18
+ }
19
+
20
+ function legacyConfigPath() {
21
+ return path.join(configDir(), '.mcp.json')
22
+ }
23
+
24
+ function approvalsPath() {
25
+ return path.join(configDir(), 'mcp-approvals.json')
26
+ }
27
+
28
+ // User-level memory rules file. WorkBuddy's codebuddy CLI (a CodeBuddy fork)
29
+ // auto-loads CODEBUDDY.md (or CODEBUDDY.mdc) from the config dir into every
30
+ // session (MemoryLoader user scope -> system-reminder-md / prompt context).
31
+ // Note: PRODUCT_UPPERCASE_NAME in the CLI bundle is "CODEBUDDY", not "WORKBUDDY".
32
+ function memoryRulesPath() {
33
+ return path.join(configDir(), 'CODEBUDDY.md')
34
+ }
35
+
36
+ // Project-level memory rules. WorkBuddy walks up from the workspace dir and
37
+ // auto-loads CODEBUDDY.md / AGENTS.md (project scope) into every session.
38
+ function projectInstructionsPath(workspace) {
39
+ return path.join(workspace || process.cwd(), 'AGENTS.md')
40
+ }
41
+
42
+ function readJson(file) {
43
+ if (!fs.existsSync(file)) return null
44
+ try {
45
+ return JSON.parse(fs.readFileSync(file, 'utf8'))
46
+ } catch (_) {
47
+ return null
48
+ }
49
+ }
50
+
51
+ const MEMORY_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
52
+ const MEMORY_END = '<!-- MIO_INTELLIGENCE_END -->'
53
+ const MEMORY_BLOCK = `${MEMORY_BEGIN}
54
+ ## Mio Intelligence Layer
55
+
56
+ Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
57
+
58
+ - Before designing or implementing a solution, or when unsure about prior decisions, call \`mcp__mio-intelligence__mio.memory.query\` first. Omit \`project\` unless a specific project is required; Mio derives it from the current Git repository.
59
+ - After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mcp__mio-intelligence__mio.memory.record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
60
+ - When a tool call fails, retries, or returns an error, call \`mcp__mio-intelligence__mio.observer.ingest\` with a stable \`trace_id\`, relevant \`event_type\`, \`payload\`, and \`outcome\`.
61
+ - Before a high-risk operation such as deletion, migration, credential change, or production change, call \`mcp__mio-intelligence__mio.policy.check\` and follow its suggestion when available.
62
+ - At the end of every task, before producing the final answer, call \`mcp__mio-intelligence__mio.observer.ingest\` with \`event_type\` = \`task_outcome\`, a stable \`trace_id\`, \`outcome\` = \`success\` / \`failure\` / \`aborted\`, and a brief non-sensitive \`payload\`. If Mio is unavailable, do not block the final answer.
63
+ - When you reuse a prior decision or experience returned by \`mcp__mio-intelligence__mio.memory.query\` and it changes your approach or outcome, call \`mcp__mio-intelligence__mio.experience.reuse\` with \`sourceAgent\`, \`targetAgent\`, \`experienceId\`, \`reuse\`, \`behaviorChanged\`, and \`outcomeImproved\`.
64
+ - If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
65
+ - Do not store secrets, credentials, or raw sensitive content in Mio memory.
66
+ ${MEMORY_END}`
67
+
68
+ function ensureBlock(target) {
69
+ let content = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
70
+ const original = content
71
+ const start = content.indexOf(MEMORY_BEGIN)
72
+ if (start !== -1) {
73
+ const end = content.indexOf(MEMORY_END, start)
74
+ content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + MEMORY_END.length)
75
+ content = content.trimEnd()
76
+ }
77
+ if (content) content += '\n\n'
78
+ content += MEMORY_BLOCK + '\n'
79
+ if (content === original) return { changed: false, target, backup: null }
80
+ const backup = `${target}.bak-mio-${Date.now()}`
81
+ if (fs.existsSync(target)) fs.copyFileSync(target, backup)
82
+ fs.mkdirSync(path.dirname(target), { recursive: true })
83
+ fs.writeFileSync(target, content, 'utf8')
84
+ return { changed: true, target, backup }
85
+ }
86
+
87
+ function isInstalled() {
88
+ const config = readJson(configPath())
89
+ const entry = config && config.mcpServers && config.mcpServers['mio-intelligence']
90
+ if (!entry) return false
91
+
92
+ const approvals = readJson(approvalsPath()) || {}
93
+ const memory = fs.existsSync(memoryRulesPath()) ? fs.readFileSync(memoryRulesPath(), 'utf8') : ''
94
+ return Boolean(
95
+ approvals[approvalKey('mio-intelligence', entry)] &&
96
+ memory.includes(MEMORY_BEGIN) &&
97
+ memory.includes(MEMORY_END)
98
+ )
99
+ }
100
+
101
+ function approvalKey(name, entry) {
102
+ let input
103
+ if (entry.command) {
104
+ input = `${entry.command || ''}|${(entry.args || []).map(String).sort().join(',')}|${Object.keys(entry.env || {}).sort().join(',')}`
105
+ } else if (entry.url) {
106
+ try {
107
+ input = new URL(entry.url).origin
108
+ } catch (_) {
109
+ input = entry.url
110
+ }
111
+ } else {
112
+ input = JSON.stringify(entry)
113
+ }
114
+
115
+ const hash = crypto.createHash('sha256').update(input).digest('hex')
116
+ return `${hash}::${name}`
117
+ }
118
+
119
+ function writeApproval(name, entry) {
120
+ const target = approvalsPath()
121
+ const approvals = readJson(target) || {}
122
+ approvals[approvalKey(name, entry)] = Date.now()
123
+ fs.mkdirSync(path.dirname(target), { recursive: true })
124
+ fs.writeFileSync(target, `${JSON.stringify(approvals, null, 2)}\n`, 'utf8')
125
+ }
126
+
127
+ function install({ node, serverScript, home, workspace }) {
128
+ const target = configPath()
129
+ const canonical = readJson(target) || {}
130
+ const legacy = readJson(legacyConfigPath()) || {}
131
+ const canonicalServers =
132
+ canonical.mcpServers && typeof canonical.mcpServers === 'object' ? canonical.mcpServers : {}
133
+ const legacyServers =
134
+ legacy.mcpServers && typeof legacy.mcpServers === 'object' ? legacy.mcpServers : {}
135
+
136
+ const config = {
137
+ ...legacy,
138
+ ...canonical,
139
+ mcpServers: {
140
+ ...legacyServers,
141
+ ...canonicalServers,
142
+ },
143
+ }
144
+
145
+ // No project/workspace here: the MCP server derives the project from its own
146
+ // git cwd. A baked value from install-time cwd overrides that derivation and
147
+ // mis-attributes every memory write for the host's lifetime.
148
+ const context = JSON.stringify({
149
+ agentId: 'workbuddy',
150
+ sessionId: 'workbuddy-session',
151
+ })
152
+
153
+ config.mcpServers = {
154
+ ...config.mcpServers,
155
+ 'mio-intelligence': {
156
+ command: node,
157
+ args: [serverScript],
158
+ env: {
159
+ MIO_DATA_DIR: home,
160
+ MIO_CONTEXT: context,
161
+ },
162
+ },
163
+ }
164
+
165
+ writeApproval('mio-intelligence', config.mcpServers['mio-intelligence'])
166
+
167
+ const backup = `${target}.bak-mio-${Date.now()}`
168
+ if (fs.existsSync(target)) {
169
+ fs.copyFileSync(target, backup)
170
+ }
171
+
172
+ fs.mkdirSync(path.dirname(target), { recursive: true })
173
+ fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
174
+
175
+ const userRules = ensureBlock(memoryRulesPath())
176
+ let projectRules = null
177
+ const workspaceDir = workspace || process.cwd()
178
+ if (workspaceDir && path.isAbsolute(workspaceDir)) {
179
+ projectRules = ensureBlock(projectInstructionsPath(workspaceDir))
180
+ }
181
+
182
+ const changed = userRules.changed || Boolean(projectRules && projectRules.changed)
183
+
184
+ return {
185
+ changed,
186
+ configPath: target,
187
+ approvalsPath: approvalsPath(),
188
+ memoryRulesPath: userRules.target,
189
+ projectInstructionsPath: projectRules ? projectRules.target : null,
190
+ userRulesChanged: userRules.changed,
191
+ projectRulesChanged: projectRules ? projectRules.changed : false,
192
+ backup,
193
+ message: 'WorkBuddy Mio MCP installed, approved, and Mio rules injected into CODEBUDDY.md (auto-loaded user memory) and the workspace AGENTS.md. Restart WorkBuddy to load them.',
194
+ }
195
+ }
196
+
197
+ module.exports = { configDir, configPath, approvalsPath, memoryRulesPath, projectInstructionsPath, approvalKey, isInstalled, install }
package/bin/mio.js CHANGED
@@ -23,10 +23,10 @@ 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
27
  const { createAgentStore } = require('../server/agent-store.js')
28
28
  const { createEvaluationStore } = require('../server/evaluation-store.js')
29
- const { createInsightStore } = require('../server/insight-store.js')
29
+ const { createInsightStore, isInsightAvailable } = require('../server/insight-store.js')
30
30
  const { createObserverStore, isObserverAvailable } = require('../server/observer-store.js')
31
31
  const { loadPhase0, renderPhase0Markdown } = require('../server/mio-intelligence-mcp/phase0.js')
32
32
  const { listHostCapabilities } = require('../server/host-capabilities.js')
@@ -564,7 +564,9 @@ function digestCommand(args, useJson) {
564
564
  let state = {}
565
565
  try {
566
566
  state = JSON.parse(fs.readFileSync(stateFile, 'utf8'))
567
- } catch (_) {}
567
+ } catch (err) {
568
+ throw new Error(`Failed to parse ${stateFile}: ${err.message}`)
569
+ }
568
570
  state.contexts = state.contexts || {}
569
571
  const stamp = new Date().toLocaleString('zh-CN', { hour12: false })
570
572
 
@@ -595,6 +597,7 @@ function digestCommand(args, useJson) {
595
597
  }
596
598
 
597
599
  let written = 0
600
+ const writeBackFailures = []
598
601
  for (const cwd of targetCwds) {
599
602
  const line = projectLine.get(path.basename(cwd).toLowerCase())
600
603
  if (!line) continue
@@ -602,13 +605,18 @@ function digestCommand(args, useJson) {
602
605
  observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'CLAUDE.md')
603
606
  observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'AGENTS.md')
604
607
  written += 1
605
- } catch (_) {}
608
+ } catch (err) {
609
+ const wsName = path.basename(cwd)
610
+ console.error(`Write-back failed for ${wsName}:`, err.message || err)
611
+ writeBackFailures.push({ workspace: wsName, error: err.message || String(err) })
612
+ }
606
613
  }
607
614
  fs.mkdirSync(MIO_HOME, { recursive: true })
608
615
  fs.writeFileSync(stateFile, JSON.stringify(state) + '\n', 'utf8')
609
616
  report.writeBack = {
610
617
  workspaces: written,
611
618
  skipped: Object.keys(state.contexts).length - written,
619
+ failures: writeBackFailures,
612
620
  projects: [...projectLine.values()],
613
621
  }
614
622
  }
@@ -626,6 +634,10 @@ function digestCommand(args, useJson) {
626
634
  console.log(
627
635
  `Write-back: ${report.writeBack.workspaces} workspace(s), ${report.writeBack.skipped} skipped (no digest data)`,
628
636
  )
637
+ if (report.writeBack.failures && report.writeBack.failures.length > 0) {
638
+ console.error(` ${report.writeBack.failures.length} write-back failure(s):`)
639
+ for (const f of report.writeBack.failures) console.error(` - ${f.workspace}: ${f.error}`)
640
+ }
629
641
  for (const line of report.writeBack.projects) console.log(` - ${line}`)
630
642
  }
631
643
  }
@@ -2060,8 +2072,9 @@ function creativityUsage(write = console.log) {
2060
2072
 
2061
2073
  Options:
2062
2074
  --status name Filter list by status
2063
- --limit N Max results for list / ferment (default 20 / 5)
2075
+ --limit N Max results for list / ferment (default 20 / 5); --limit 0 lists everything
2064
2076
  --source "name|content" A concept source for generate; repeat 2+ times
2077
+ --from-insights Seed generate's sources from stored insights (needs @akemi-mio/insight)
2065
2078
  --strategy explore|signal|stable Generation strategy (auto-selected if omitted)
2066
2079
  --json Machine-readable output (same shape as the mio.creativity.* tools)
2067
2080
 
@@ -2069,6 +2082,7 @@ Examples:
2069
2082
  mio creativity status
2070
2083
  mio creativity list --status rejected --limit 10
2071
2084
  mio creativity generate --source "auth|token rotation" --source "cache|write-through"
2085
+ mio creativity generate --from-insights --source "auth|token rotation"
2072
2086
 
2073
2087
  generate/ferment call an LLM using the shared client. Configure it with
2074
2088
  LLM_API_URL / LLM_KEY / LLM_CHAT_MODEL (the same environment the MCP server
@@ -2079,7 +2093,7 @@ reads). A local Ollama works:
2079
2093
  function printCreativityStatus(result) {
2080
2094
  console.log('Creativity engine:')
2081
2095
  console.log(` hypotheses: ${result.hypotheses} combos: ${result.combos} experiments: ${result.experiments}`)
2082
- console.log(` active: ${result.active} validated: ${result.validated} rejected: ${result.rejected}`)
2096
+ console.log(` active: ${result.active} validated: ${result.validated} rejected: ${result.rejected} draft: ${result.draft}`)
2083
2097
  if (result.recentIdeas.length === 0) {
2084
2098
  console.log('No hypotheses yet.')
2085
2099
  return
@@ -2136,12 +2150,56 @@ function warnIfLlmUnconfigured() {
2136
2150
  console.log('(or set LLM_API_URL / LLM_KEY / LLM_CHAT_MODEL in the environment)')
2137
2151
  }
2138
2152
 
2153
+ // Failed LLM calls used to disappear: `generate` swallowed them and `ferment`
2154
+ // reported "Nothing fermented", so a dead endpoint looked exactly like "no new
2155
+ // ideas". Both now surface them; the JSON shapes carry `errors` as well.
2156
+ function printCreativityErrors(errors, label) {
2157
+ if (!Array.isArray(errors) || errors.length === 0) return
2158
+ console.log(`\n${errors.length} ${label}(s) failed:`)
2159
+ for (const err of errors) {
2160
+ const who = err.pair ? err.pair.join(' + ') : (err.title || err.id)
2161
+ console.log(`- ${who}: ${err.error}`)
2162
+ }
2163
+ }
2164
+
2139
2165
  function creativityGenerateCommand(args, useJson) {
2140
2166
  const flags = args.slice(2)
2141
- const sources = parseSources(flags)
2167
+ let sources = parseSources(flags)
2168
+
2169
+ // `--from-insights` seeds the mixer with whatever the insight engine has
2170
+ // already noticed. It reads the *CLI's* store (global MIO_HOME), the same
2171
+ // one `mio insight list` shows, and merges with any explicit --source.
2172
+ if (flagPresent(flags, '--from-insights')) {
2173
+ if (!isInsightAvailable()) {
2174
+ console.error('mio creativity generate --from-insights requires the optional package @akemi-mio/insight')
2175
+ process.exitCode = 1
2176
+ return
2177
+ }
2178
+ let seeds
2179
+ try {
2180
+ seeds = sourcesFromInsights(cliInsightStore().list({}))
2181
+ } catch (error) {
2182
+ console.error(error.message || error)
2183
+ process.exitCode = 1
2184
+ return
2185
+ }
2186
+ if (seeds.length === 0) {
2187
+ console.error('mio creativity generate --from-insights: no stored insights yet (run `mio insight generate` first)')
2188
+ process.exitCode = 1
2189
+ return
2190
+ }
2191
+ sources = sources.concat(seeds)
2192
+ }
2193
+
2142
2194
  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"')
2195
+ if (flagPresent(flags, '--from-insights')) {
2196
+ console.error(`mio creativity generate needs at least 2 concept sources; --from-insights only supplied ${sources.length}.`)
2197
+ console.error('Add more --source "name|content" arguments, or generate more insights first.')
2198
+ } else {
2199
+ console.error('mio creativity generate requires at least two --source "name|content" arguments')
2200
+ console.error('Example: mio creativity generate --source "auth|token rotation" --source "cache|write-through cache"')
2201
+ console.error('Or seed them with --from-insights (uses stored insights as concepts).')
2202
+ }
2145
2203
  process.exitCode = 1
2146
2204
  return
2147
2205
  }
@@ -2170,6 +2228,7 @@ function creativityGenerateCommand(args, useJson) {
2170
2228
  console.log(` novelty=${idea.novelty} feasibility=${idea.feasibility} impact=${idea.impact} | ${idea.id}`)
2171
2229
  if (idea.rejectionReason) console.log(` rejected: ${idea.rejectionReason}`)
2172
2230
  })
2231
+ printCreativityErrors(resolved.errors, 'pair')
2173
2232
  })
2174
2233
  }
2175
2234
 
@@ -2191,12 +2250,16 @@ function creativityFermentCommand(args, useJson) {
2191
2250
  if (useJson) return jsonOrText(resolved, true)
2192
2251
  if (resolved.fermented === 0) {
2193
2252
  console.log(`Nothing fermented: ${resolved.reason || 'no eligible hypotheses'}`)
2253
+ // Every review can fail while there was something to review; without
2254
+ // this the endpoint outage read as "the store is empty".
2255
+ printCreativityErrors(resolved.errors, 'review')
2194
2256
  return
2195
2257
  }
2196
2258
  console.log(`Fermented ${resolved.fermented} hypothesis/hypotheses`)
2197
2259
  for (const r of resolved.results || []) {
2198
2260
  console.log(`- ${r.title || r.id}: ${r.verdict || 'updated'}${r.reason ? ` — ${r.reason}` : ''}`)
2199
2261
  }
2262
+ printCreativityErrors(resolved.errors, 'review')
2200
2263
  })
2201
2264
  }
2202
2265
 
@@ -3136,22 +3199,33 @@ function installCommand(host, useJson) {
3136
3199
  }
3137
3200
  writeConfig(config)
3138
3201
  }
3202
+ let observerCycleOk = true
3139
3203
  try {
3140
3204
  observer.runCycle(MIO_HOME, observer.loadState(MIO_HOME), observer.createSinks(MIO_HOME))
3141
- } catch (_) {}
3205
+ } catch (err) {
3206
+ console.error('Observer cycle failed during install:', err.message || err)
3207
+ observerCycleOk = false
3208
+ }
3142
3209
  let observerStarted = false
3143
3210
  try {
3144
3211
  observerStarted = observer.startBackground(MIO_HOME).started
3145
- } catch (_) {}
3212
+ } catch (err) {
3213
+ console.error('Observer background startup failed during install:', err.message || err)
3214
+ observerStarted = false
3215
+ }
3146
3216
  if (useJson) {
3147
3217
  result.observer = {
3148
3218
  running: observer.isRunning(MIO_HOME),
3149
3219
  started: observerStarted,
3220
+ cycleOk: observerCycleOk,
3150
3221
  }
3151
3222
  jsonOrText(result, true)
3152
3223
  return
3153
3224
  }
3154
3225
  console.log(result.message || `${host} adapter completed.`)
3226
+ if (!observerCycleOk) {
3227
+ console.error('Warning: observer cycle failed during install; run `mio observe --once` manually.')
3228
+ }
3155
3229
  console.log(
3156
3230
  observer.isRunning(MIO_HOME)
3157
3231
  ? 'Observer daemon running.'
@@ -3160,6 +3234,13 @@ function installCommand(host, useJson) {
3160
3234
  }
3161
3235
 
3162
3236
  function mcpCommand() {
3237
+ // The MCP server resolves its data dir as `MIO_DATA_DIR || <cwd>/.mio-intelligence`,
3238
+ // while every CLI subcommand reads the global MIO_HOME. Left alone, `mio mcp`
3239
+ // launched from a scratch directory built a second, empty store there and
3240
+ // `mio.creativity.list` / `mio.recall` disagreed with `mio creativity list` /
3241
+ // `mio recall`. Pinning it makes the stdio server see the same data as the
3242
+ // terminal; an explicit MIO_DATA_DIR still wins (adapters set one on purpose).
3243
+ if (!process.env.MIO_DATA_DIR) process.env.MIO_DATA_DIR = MIO_HOME
3163
3244
  require(SERVER_SCRIPT)
3164
3245
  }
3165
3246
 
@@ -3289,7 +3370,7 @@ Usage:
3289
3370
  mio experience reuse --source-agent A --target-agent B --experience-id X Record a reuse
3290
3371
  mio creativity status Show creativity hypothesis counts and recent top ideas
3291
3372
  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)
3373
+ mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
3293
3374
  mio creativity ferment Review and refine active hypotheses (calls an LLM)
3294
3375
  mio insight status Insight counts: total, reported, unreported, high-value
3295
3376
  mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)