mio-agent-runtime 0.5.16 → 0.5.17

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mio-agent-runtime",
3
- "version": "0.5.16",
3
+ "version": "0.5.17",
4
4
  "description": "Mio Agent Runtime installer and MCP intelligence layer",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -9,28 +9,30 @@
9
9
  },
10
10
  "files": [
11
11
  "bin/",
12
- "server/",
13
- "adapters/",
14
- "observe/",
15
- "scripts/",
16
- "README.md",
12
+ "server/",
13
+ "adapters/",
14
+ "observe/",
15
+ "scripts/",
16
+ "README.md",
17
17
  "!server/mio-intelligence-mcp/__tests__"
18
18
  ],
19
- "scripts": {
20
- "check": "node --check bin/mio.js && node --check adapters/host-utils.js && node --check adapters/codex.js && node --check adapters/opencode.js && node --check adapters/workbuddy.js && node --check adapters/hermes.js && node --check server/runtime-modules.js && node --check server/host-capabilities.js && node --check server/evolution-cutover.js && node --check server/mio-intelligence-mcp/index.js && node --check server/mio-intelligence-mcp/phase0.js && node --check observe/observer.js && node --check scripts/verify-packed-runtime.js",
21
- "prepack": "npm run check",
22
- "verify:pack-install": "node scripts/verify-packed-runtime.js"
23
- },
24
- "engines": {
25
- "node": ">=18"
26
- },
27
- "dependencies": {
28
- "@akemi-mio/runtime-contracts": "^0.1.0",
29
- "@akemi-mio/runtime-foundation": "^0.1.0",
30
- "@akemi-mio/experience-memory": "^0.1.0",
31
- "@akemi-mio/evolution-learning": "^0.1.0",
32
- "@akemi-mio/evolution-strategy": "^0.1.0",
33
- "@akemi-mio/evolution-safety": "^0.1.0",
34
- "@akemi-mio/evolution-scheduler": "^0.1.0"
35
- }
36
- }
19
+ "scripts": {
20
+ "check": "node --check bin/mio.js && node --check adapters/host-utils.js && node --check adapters/codex.js && node --check adapters/opencode.js && node --check adapters/workbuddy.js && node --check adapters/hermes.js && node --check server/runtime-modules.js && node --check server/host-capabilities.js && node --check server/evolution-cutover.js && node --check server/mio-intelligence-mcp/index.js && node --check server/mio-intelligence-mcp/phase0.js && node --check observe/observer.js && node --check scripts/verify-packed-runtime.js",
21
+ "prepack": "npm run check",
22
+ "verify:pack-install": "node scripts/verify-packed-runtime.js"
23
+ },
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "dependencies": {
28
+ "@akemi-mio/runtime-contracts": "^0.1.0",
29
+ "@akemi-mio/runtime-foundation": "^0.1.0",
30
+ "@akemi-mio/experience-memory": "^0.1.0",
31
+ "@akemi-mio/evolution-learning": "^0.1.0",
32
+ "@akemi-mio/evolution-strategy": "^0.1.0",
33
+ "@akemi-mio/evolution-safety": "^0.1.0",
34
+ "@akemi-mio/evolution-scheduler": "^0.1.0",
35
+ "@akemi-mio/observer": "^0.1.0",
36
+ "@akemi-mio/insight": "^0.1.0"
37
+ }
38
+ }
@@ -0,0 +1,397 @@
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const crypto = require('crypto')
6
+
7
+ // ═══════════════════════════════════════════════
8
+ // JSONL Store — ideas, combos, experiments
9
+ // ═══════════════════════════════════════════════
10
+
11
+ class CreativityStore {
12
+ constructor(dataDir) {
13
+ this.dataDir = dataDir
14
+ this.hypothesesPath = path.join(dataDir, 'creativity-hypotheses.jsonl')
15
+ this.combosPath = path.join(dataDir, 'creativity-combos.jsonl')
16
+ this.experimentsPath = path.join(dataDir, 'creativity-experiments.jsonl')
17
+ this._hypotheses = null
18
+ this._combos = null
19
+ this._experiments = null
20
+ }
21
+
22
+ _ensureDir() {
23
+ if (!fs.existsSync(this.dataDir)) {
24
+ fs.mkdirSync(this.dataDir, { recursive: true })
25
+ }
26
+ }
27
+
28
+ _readJsonl(filePath) {
29
+ if (!fs.existsSync(filePath)) return []
30
+ return fs.readFileSync(filePath, 'utf-8')
31
+ .split('\n')
32
+ .filter(Boolean)
33
+ .map(line => {
34
+ try { return JSON.parse(line) } catch { return null }
35
+ })
36
+ .filter(Boolean)
37
+ }
38
+
39
+ _appendJsonl(filePath, record) {
40
+ this._ensureDir()
41
+ fs.appendFileSync(filePath, JSON.stringify(record) + '\n')
42
+ }
43
+
44
+ getHypotheses(opts = {}) {
45
+ if (this._hypotheses === null) {
46
+ this._hypotheses = this._readJsonl(this.hypothesesPath)
47
+ }
48
+ let list = this._hypotheses
49
+ if (opts.status) list = list.filter(h => h.status === opts.status)
50
+ if (opts.limit) list = list.slice(-opts.limit)
51
+ return list
52
+ }
53
+
54
+ addHypothesis(h) {
55
+ this._appendJsonl(this.hypothesesPath, h)
56
+ if (this._hypotheses) this._hypotheses.push(h)
57
+ }
58
+
59
+ updateHypothesis(id, patch) {
60
+ const all = this.getHypotheses()
61
+ const idx = all.findIndex(h => h.id === id)
62
+ if (idx === -1) return false
63
+ Object.assign(all[idx], patch)
64
+ this._hypotheses = all
65
+ this._rewriteFile(this.hypothesesPath, all)
66
+ return true
67
+ }
68
+
69
+ getCombos(limit) {
70
+ if (this._combos === null) {
71
+ this._combos = this._readJsonl(this.combosPath)
72
+ }
73
+ return limit ? this._combos.slice(-limit) : this._combos
74
+ }
75
+
76
+ addCombo(c) {
77
+ this._appendJsonl(this.combosPath, c)
78
+ if (this._combos) this._combos.push(c)
79
+ }
80
+
81
+ getExperiments() {
82
+ if (this._experiments === null) {
83
+ this._experiments = this._readJsonl(this.experimentsPath)
84
+ }
85
+ return this._experiments
86
+ }
87
+
88
+ addExperiment(e) {
89
+ this._appendJsonl(this.experimentsPath, e)
90
+ if (this._experiments) this._experiments.push(e)
91
+ }
92
+
93
+ count() {
94
+ return {
95
+ hypotheses: this.getHypotheses().length,
96
+ combos: this.getCombos().length,
97
+ experiments: this.getExperiments().length,
98
+ }
99
+ }
100
+
101
+ _rewriteFile(filePath, records) {
102
+ this._ensureDir()
103
+ fs.writeFileSync(filePath, records.map(r => JSON.stringify(r)).join('\n') + '\n')
104
+ }
105
+ }
106
+
107
+ // ═══════════════════════════════════════════════
108
+ // Concept Mixer — pair sources for novel combos
109
+ // ═══════════════════════════════════════════════
110
+
111
+ function mulberry32(seed) {
112
+ let t = seed | 0
113
+ return () => {
114
+ t = (t + 0x6D2B79F5) | 0
115
+ let x = Math.imul(t ^ (t >>> 15), 1 | t)
116
+ x ^= x + Math.imul(x ^ (x >>> 7), 61 | x)
117
+ return ((x ^ (x >>> 14)) >>> 0) / 4294967296
118
+ }
119
+ }
120
+
121
+ function mixConcepts(sources, exploredPairs, rng) {
122
+ if (sources.length < 2) return []
123
+ const pairs = []
124
+ for (let i = 0; i < sources.length; i++) {
125
+ for (let j = i + 1; j < sources.length; j++) {
126
+ const key = `${sources[i].name}||${sources[j].name}`
127
+ const reverseKey = `${sources[j].name}||${sources[i].name}`
128
+ if (exploredPairs.has(key) || exploredPairs.has(reverseKey)) continue
129
+ pairs.push([sources[i], sources[j]])
130
+ }
131
+ }
132
+ // Shuffle and take top pairs
133
+ for (let i = pairs.length - 1; i > 0; i--) {
134
+ const j = Math.floor(rng() * (i + 1))
135
+ ;[pairs[i], pairs[j]] = [pairs[j], pairs[i]]
136
+ }
137
+ return pairs.slice(0, Math.min(5, pairs.length))
138
+ }
139
+
140
+ // ═══════════════════════════════════════════════
141
+ // Novelty Scorer — reject duplicates
142
+ // ═══════════════════════════════════════════════
143
+
144
+ function bigramSet(text) {
145
+ const s = new Set()
146
+ const lower = text.toLowerCase()
147
+ for (let i = 0; i < lower.length - 1; i++) {
148
+ s.add(lower.slice(i, i + 2))
149
+ }
150
+ return s
151
+ }
152
+
153
+ function jaccard(a, b) {
154
+ if (a.size === 0 && b.size === 0) return 1
155
+ let inter = 0
156
+ for (const x of a) if (b.has(x)) inter++
157
+ return inter / (a.size + b.size - inter)
158
+ }
159
+
160
+ function evaluateNovelty(idea, recent, rejected) {
161
+ const ideaBigrams = bigramSet(idea.title + ' ' + idea.idea)
162
+ for (const r of recent) {
163
+ const sim = jaccard(ideaBigrams, bigramSet(r.title + ' ' + r.idea))
164
+ if (sim > 0.7) {
165
+ return { shouldReject: true, rejectReason: `too similar to recent: "${r.title}" (sim=${sim.toFixed(2)})`, adjustedNovelty: idea.novelty }
166
+ }
167
+ }
168
+ for (const r of rejected) {
169
+ const sim = jaccard(ideaBigrams, bigramSet(r.title + ' ' + r.idea))
170
+ if (sim > 0.6) {
171
+ return { shouldReject: true, rejectReason: `matches rejected: "${r.title}" (sim=${sim.toFixed(2)})`, adjustedNovelty: idea.novelty }
172
+ }
173
+ }
174
+ return { shouldReject: false, adjustedNovelty: Math.min(100, idea.novelty + 5) }
175
+ }
176
+
177
+ // ═══════════════════════════════════════════════
178
+ // Creativity Engine — main orchestrator
179
+ // ═══════════════════════════════════════════════
180
+
181
+ const STRATEGY_CYCLE = ['explore', 'signal', 'stable']
182
+
183
+ class CreativityEngine {
184
+ constructor(dataDir, chatJson) {
185
+ this.store = new CreativityStore(dataDir)
186
+ this.chatJson = chatJson
187
+ this.strategyIndex = 0
188
+ this.exploredPairs = new Set()
189
+ this.rng = mulberry32(Date.now())
190
+ }
191
+
192
+ async generate(sourceLabels, strategy) {
193
+ const sources = sourceLabels.map(s => ({
194
+ name: s.name || 'unnamed',
195
+ content: s.content || '',
196
+ type: s.type || 'knowledge',
197
+ weight: s.weight || 0.5,
198
+ }))
199
+
200
+ if (sources.length < 2) {
201
+ return { ideas: [], reason: 'need at least 2 sources' }
202
+ }
203
+
204
+ const strat = strategy || STRATEGY_CYCLE[this.strategyIndex % STRATEGY_CYCLE.length]
205
+ this.strategyIndex++
206
+
207
+ // Mix concepts
208
+ const recent = this.store.getHypotheses({ limit: 30 })
209
+ const rejected = this.store.getHypotheses({ status: 'rejected', limit: 50 })
210
+ const exploredPairs = new Set(recent.flatMap(h => (h.sourceLabels || []).map((l, i, a) => i > 0 ? `${a[i-1]}||${l}` : '')).filter(Boolean))
211
+ const pairs = mixConcepts(sources, exploredPairs, this.rng.bind(this))
212
+
213
+ if (pairs.length === 0) {
214
+ return { ideas: [], reason: 'all pairs already explored' }
215
+ }
216
+
217
+ // Generate hypotheses via LLM
218
+ const ideas = []
219
+ for (const [a, b] of pairs) {
220
+ const prompt = this._buildGenerationPrompt(a, b, strat, recent.slice(-10), rejected.slice(-5))
221
+ try {
222
+ const result = await this.chatJson(prompt, {
223
+ system: `You are a creative hypothesis generator. Output JSON with fields: title, idea, expectedBenefit, risk, novelty (0-100), feasibility (0-100), impact (0-100). Generate ONE novel hypothesis by combining the given concepts.`,
224
+ temperature: strat === 'explore' ? 0.8 : 0.4,
225
+ })
226
+
227
+ if (result.data) {
228
+ const h = {
229
+ id: crypto.randomUUID(),
230
+ title: result.data.title || 'Untitled',
231
+ idea: result.data.idea || '',
232
+ expectedBenefit: result.data.expectedBenefit || '',
233
+ risk: result.data.risk || '',
234
+ novelty: Math.min(100, Math.max(0, result.data.novelty || 50)),
235
+ feasibility: Math.min(100, Math.max(0, result.data.feasibility || 50)),
236
+ impact: Math.min(100, Math.max(0, result.data.impact || 50)),
237
+ sourceLabels: [a.name, b.name],
238
+ status: 'active',
239
+ createdAt: Date.now(),
240
+ strategy: strat,
241
+ }
242
+
243
+ // Novelty check
244
+ const verdict = evaluateNovelty(h, recent, rejected)
245
+ if (verdict.shouldReject) {
246
+ h.status = 'rejected'
247
+ h.rejectionReason = verdict.rejectReason
248
+ } else {
249
+ h.novelty = verdict.adjustedNovelty
250
+ }
251
+
252
+ this.store.addHypothesis(h)
253
+ ideas.push(h)
254
+
255
+ // Record combo
256
+ this.store.addCombo({
257
+ id: crypto.randomUUID(),
258
+ sources: [a.name, b.name],
259
+ description: h.title,
260
+ createdAt: Date.now(),
261
+ })
262
+ }
263
+ } catch (err) {
264
+ // LLM call failed, skip this pair
265
+ }
266
+ }
267
+
268
+ return { ideas, strategy: strat, pairsAttempted: pairs.length }
269
+ }
270
+
271
+ async ferment(limit = 5) {
272
+ const fermentable = this.store.getHypotheses({ status: 'active' })
273
+ .concat(this.store.getHypotheses({ status: 'draft' }))
274
+ .slice(-limit)
275
+
276
+ if (fermentable.length === 0) {
277
+ return { fermented: 0, reason: 'no fermentable hypotheses' }
278
+ }
279
+
280
+ const results = []
281
+ for (const h of fermentable) {
282
+ const prompt = `Review and refine this hypothesis. Current state:
283
+ Title: ${h.title}
284
+ Idea: ${h.idea}
285
+ Novelty: ${h.novelty}, Feasibility: ${h.feasibility}, Impact: ${h.impact}
286
+
287
+ Output JSON with: title, idea, expectedBenefit, risk, novelty, feasibility, impact, verdict (promote/keep/reject/merge), reason.`
288
+
289
+ try {
290
+ const result = await this.chatJson(prompt, {
291
+ system: 'You are a creative hypothesis reviewer. Output JSON with refined scores and a verdict.',
292
+ temperature: 0.3,
293
+ })
294
+
295
+ if (result.data) {
296
+ const patch = {
297
+ title: result.data.title || h.title,
298
+ idea: result.data.idea || h.idea,
299
+ expectedBenefit: result.data.expectedBenefit || h.expectedBenefit,
300
+ risk: result.data.risk || h.risk,
301
+ novelty: Math.min(100, Math.max(0, result.data.novelty || h.novelty)),
302
+ feasibility: Math.min(100, Math.max(0, result.data.feasibility || h.feasibility)),
303
+ impact: Math.min(100, Math.max(0, result.data.impact || h.impact)),
304
+ fermentedAt: Date.now(),
305
+ fermentCount: (h.fermentCount || 0) + 1,
306
+ }
307
+
308
+ if (result.data.verdict === 'reject') {
309
+ patch.status = 'rejected'
310
+ } else if (result.data.verdict === 'promote' && (h.novelty + h.feasibility + h.impact) > 200) {
311
+ patch.status = 'validated'
312
+ }
313
+
314
+ this.store.updateHypothesis(h.id, patch)
315
+ results.push({ id: h.id, title: h.title, verdict: result.data.verdict, reason: result.data.reason })
316
+ }
317
+ } catch (err) {
318
+ // skip
319
+ }
320
+ }
321
+
322
+ return { fermented: results.length, results }
323
+ }
324
+
325
+ status() {
326
+ const count = this.store.count()
327
+ const active = this.store.getHypotheses({ status: 'active' })
328
+ const validated = this.store.getHypotheses({ status: 'validated' })
329
+ const rejected = this.store.getHypotheses({ status: 'rejected' })
330
+
331
+ return {
332
+ ...count,
333
+ active: active.length,
334
+ validated: validated.length,
335
+ rejected: rejected.length,
336
+ recentIdeas: active.slice(-5).map(h => ({
337
+ id: h.id,
338
+ title: h.title,
339
+ novelty: h.novelty,
340
+ feasibility: h.feasibility,
341
+ impact: h.impact,
342
+ score: h.novelty + h.feasibility + h.impact,
343
+ })),
344
+ }
345
+ }
346
+
347
+ list(opts = {}) {
348
+ return this.store.getHypotheses(opts).map(h => ({
349
+ id: h.id,
350
+ title: h.title,
351
+ idea: h.idea,
352
+ status: h.status,
353
+ novelty: h.novelty,
354
+ feasibility: h.feasibility,
355
+ impact: h.impact,
356
+ score: h.novelty + h.feasibility + h.impact,
357
+ sourceLabels: h.sourceLabels,
358
+ createdAt: h.createdAt,
359
+ fermentedAt: h.fermentedAt,
360
+ fermentCount: h.fermentCount,
361
+ }))
362
+ }
363
+
364
+ _buildGenerationPrompt(a, b, strategy, recentHypotheses, rejectedHypotheses) {
365
+ let prompt = `Combine these two concepts into ONE novel hypothesis:\n\n`
366
+ prompt += `Concept A: "${a.name}" — ${a.content}\n`
367
+ prompt += `Concept B: "${b.name}" — ${b.content}\n\n`
368
+ prompt += `Strategy: ${strategy}\n`
369
+
370
+ if (strategy === 'explore') {
371
+ prompt += `Cross-domain combination. Emphasize structural differences and unexpected connections.\n`
372
+ } else if (strategy === 'signal') {
373
+ prompt += `Inject external signals. Be provocative and challenge assumptions.\n`
374
+ } else {
375
+ prompt += `Stable combination. Conservative refinement of existing ideas.\n`
376
+ }
377
+
378
+ if (recentHypotheses.length > 0) {
379
+ prompt += `\nRecent ideas (avoid duplicates):\n`
380
+ for (const h of recentHypotheses) {
381
+ prompt += `- "${h.title}" (novelty=${h.novelty})\n`
382
+ }
383
+ }
384
+
385
+ if (rejectedHypotheses.length > 0) {
386
+ prompt += `\nRejected ideas (learn from failures):\n`
387
+ for (const h of rejectedHypotheses) {
388
+ prompt += `- "${h.title}" — ${h.risk}\n`
389
+ }
390
+ }
391
+
392
+ prompt += `\nOutput JSON: {"title": "...", "idea": "...", "expectedBenefit": "...", "risk": "...", "novelty": 0-100, "feasibility": 0-100, "impact": 0-100}`
393
+ return prompt
394
+ }
395
+ }
396
+
397
+ module.exports = { CreativityEngine, CreativityStore }
@@ -103,7 +103,7 @@ task_outcome 数据经 auto-claim → confirm 后会自动升级后续同类动
103
103
  ```toml
104
104
  [mcp_servers.mio-intelligence]
105
105
  command = 'C:\nvm4w\nodejs\node.exe'
106
- args = ['D:\work\code\akemi-mio\.worktrees\mio-agent-enhancement-layer\server\mio-intelligence-mcp\index.js']
106
+ args = ['/absolute/path/to/mio-agent-runtime/server/mio-intelligence-mcp/index.js']
107
107
  startup_timeout_sec = 30
108
108
 
109
109
  [mcp_servers.mio-intelligence.env]
@@ -136,7 +136,7 @@ OpenCode / WorkBuddy 的 MCP 配置只需保持同样语义:
136
136
  "mio": {
137
137
  "command": "node",
138
138
  "args": [
139
- "D:/work/code/akemi-mio/.worktrees/mio-agent-enhancement-layer/server/mio-intelligence-mcp/index.js"
139
+ "/absolute/path/to/mio-agent-runtime/server/mio-intelligence-mcp/index.js"
140
140
  ],
141
141
  "env": {
142
142
  "MIO_DATA_DIR": "C:/Users/gf191/.mio-intelligence",
@@ -10,9 +10,74 @@ const { getEvolutionStatus } = require('../runtime-modules.js')
10
10
  const { listHostCapabilities } = require('../host-capabilities.js')
11
11
  const { createEvolutionCutoverTools } = require('../evolution-cutover.js')
12
12
 
13
+ const { CreativityEngine } = require('../creativity-engine.js')
14
+
15
+ // Observer research pipeline (optional — installed via @akemi-mio/observer)
16
+ let ObserverStore = null
17
+ let ObserverService = null
18
+ try {
19
+ const obs = require('@akemi-mio/observer')
20
+ ObserverStore = obs.ObserverStore
21
+ ObserverService = obs.ObserverService
22
+ } catch {}
23
+
24
+ // Insight self-observation engine (optional — installed via @akemi-mio/insight)
25
+ let InsightStore = null
26
+ let InsightGenerator = null
27
+ let PresenceService = null
28
+ try {
29
+ const insight = require('@akemi-mio/insight')
30
+ InsightStore = insight.InsightStore
31
+ InsightGenerator = insight.InsightGenerator
32
+ PresenceService = insight.PresenceService
33
+ } catch {}
34
+
13
35
  const SERVER_INFO = { name: 'mio-intelligence-mcp', version: '0.1.0' }
14
36
  const PROTOCOL_VERSION = '2024-11-05'
15
37
 
38
+ // ═══════════════════════════════════════════════
39
+ // LLM chatJson — used by creativity engine
40
+ // ═══════════════════════════════════════════════
41
+
42
+ async function chatJson(userText, opts = {}) {
43
+ const apiUrl = process.env.LLM_API_URL || 'https://opencode.ai/zen/go/v1/chat/completions'
44
+ const apiKey = process.env.LLM_KEY || ''
45
+ const model = process.env.LLM_CHAT_MODEL || process.env.LLM_MODEL || 'deepseek-v4-flash'
46
+ const system = opts.system || 'You are a creative AI assistant. Output JSON.'
47
+ const temperature = opts.temperature ?? 0.3
48
+
49
+ try {
50
+ const resp = await fetch(apiUrl, {
51
+ method: 'POST',
52
+ headers: {
53
+ 'Content-Type': 'application/json',
54
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
55
+ },
56
+ body: JSON.stringify({
57
+ model,
58
+ temperature,
59
+ messages: [
60
+ { role: 'system', content: system },
61
+ { role: 'user', content: userText },
62
+ ],
63
+ response_format: { type: 'json_object' },
64
+ }),
65
+ signal: AbortSignal.timeout(opts.timeoutMs || 30000),
66
+ })
67
+
68
+ if (!resp.ok) return { error: `HTTP ${resp.status}` }
69
+ const json = await resp.json()
70
+ const text = json.choices?.[0]?.message?.content || ''
71
+ try {
72
+ return { data: JSON.parse(text) }
73
+ } catch {
74
+ return { data: text }
75
+ }
76
+ } catch (err) {
77
+ return { error: String(err) }
78
+ }
79
+ }
80
+
16
81
  const dataDir = path.resolve(
17
82
  process.env.MIO_DATA_DIR || path.join(process.cwd(), '.mio-intelligence')
18
83
  )
@@ -25,8 +90,10 @@ const subscriptionPath = path.join(dataDir, 'subscriptions.jsonl')
25
90
  const digestStatePath = path.join(dataDir, 'digest_state.json')
26
91
  fs.mkdirSync(dataDir, { recursive: true })
27
92
 
28
- const { loadPhase0, summarizePhase0, renderPhase0Markdown } = require('./phase0')
29
- const evolutionCutover = createEvolutionCutoverTools({ dataDir, appendJsonl, readJsonl, projectName })
93
+ const { loadPhase0, summarizePhase0, renderPhase0Markdown } = require('./phase0')
94
+ const evolutionCutover = createEvolutionCutoverTools({ dataDir, appendJsonl, readJsonl, projectName })
95
+ const creativityEngine = new CreativityEngine(path.join(dataDir, 'creativity'), chatJson)
96
+ const insightStore = InsightStore ? new InsightStore(path.join(dataDir, 'insights', 'insights.json')) : null
30
97
 
31
98
  let runtimeContext = {}
32
99
  try {
@@ -1720,8 +1787,410 @@ const TOOLS = [
1720
1787
  required: ['outcome'],
1721
1788
  },
1722
1789
  },
1790
+ {
1791
+ name: 'mio.creativity.status',
1792
+ description: 'Show creativity engine status: hypothesis counts, active/validated/rejected, recent top ideas.',
1793
+ inputSchema: { type: 'object', properties: {} },
1794
+ },
1795
+ {
1796
+ name: 'mio.creativity.list',
1797
+ description: 'List creativity hypotheses with optional status filter.',
1798
+ inputSchema: {
1799
+ type: 'object',
1800
+ properties: {
1801
+ status: { type: 'string', description: 'Filter by status: active, validated, rejected, draft.' },
1802
+ limit: { type: 'number', description: 'Max results. Default 20.' },
1803
+ },
1804
+ },
1805
+ },
1806
+ {
1807
+ name: 'mio.creativity.generate',
1808
+ description: 'Generate new creative hypotheses by combining concept sources via LLM. Returns ideas with novelty/feasibility/impact scores.',
1809
+ inputSchema: {
1810
+ type: 'object',
1811
+ properties: {
1812
+ sources: {
1813
+ type: 'array',
1814
+ description: 'Concept sources to combine. Each has name, content, type (knowledge/behavior/insight/failure), weight.',
1815
+ items: {
1816
+ type: 'object',
1817
+ properties: {
1818
+ name: { type: 'string' },
1819
+ content: { type: 'string' },
1820
+ type: { type: 'string' },
1821
+ weight: { type: 'number' },
1822
+ },
1823
+ required: ['name', 'content'],
1824
+ },
1825
+ },
1826
+ strategy: { type: 'string', description: 'Generation strategy: explore (cross-domain), signal (provocative), stable (conservative). Auto-selected if omitted.' },
1827
+ },
1828
+ required: ['sources'],
1829
+ },
1830
+ },
1831
+ {
1832
+ name: 'mio.creativity.ferment',
1833
+ description: 'Ferment (review and refine) active hypotheses. Updates scores, can promote/reject ideas.',
1834
+ inputSchema: {
1835
+ type: 'object',
1836
+ properties: {
1837
+ limit: { type: 'number', description: 'Max hypotheses to ferment. Default 5.' },
1838
+ },
1839
+ },
1840
+ },
1841
+
1842
+ // ═══ Observer Research Pipeline ═══
1843
+
1844
+ // ═══ Insight Self-Observation ═══
1845
+
1846
+ ...(insightStore ? [{
1847
+ name: 'mio.insight.status',
1848
+ description: 'Get insight engine status: total insights, unreported count, cooldown.',
1849
+ inputSchema: { type: 'object', properties: {} },
1850
+ },
1851
+ {
1852
+ name: 'mio.insight.list',
1853
+ description: 'List stored insights with optional filters.',
1854
+ inputSchema: {
1855
+ type: 'object',
1856
+ properties: {
1857
+ unreported: { type: 'boolean', description: 'Only unreported insights' },
1858
+ minScore: { type: 'number', description: 'Minimum score filter' },
1859
+ detector: { type: 'string', description: 'Filter by detector name' },
1860
+ limit: { type: 'number', description: 'Max results (default 20)' },
1861
+ },
1862
+ },
1863
+ },
1864
+ {
1865
+ name: 'mio.insight.mark_reported',
1866
+ description: 'Mark insights as reported (acknowledged).',
1867
+ inputSchema: {
1868
+ type: 'object',
1869
+ properties: {
1870
+ ids: { type: 'array', items: { type: 'string' }, description: 'Insight IDs to mark' },
1871
+ },
1872
+ required: ['ids'],
1873
+ },
1874
+ },
1875
+ {
1876
+ name: 'mio.insight.generate',
1877
+ description: 'Run LLM-based insight generation from provided context data.',
1878
+ inputSchema: {
1879
+ type: 'object',
1880
+ properties: {
1881
+ memories: { type: 'array', items: { type: 'object' }, description: 'Memory entries to analyze' },
1882
+ summaries: { type: 'array', items: { type: 'string' }, description: 'Recent summaries' },
1883
+ plans: { type: 'array', items: { type: 'object' }, description: 'Active plans' },
1884
+ },
1885
+ },
1886
+ }] : []),
1887
+
1888
+ // ═══ Observer Research Pipeline ═══
1889
+
1890
+ ...(ObserverStore ? [{
1891
+ name: 'mio.observer.world_model',
1892
+ description: 'Query the Observer world model: entities, events, trends, narratives, uncertainties, and relations.',
1893
+ inputSchema: {
1894
+ type: 'object',
1895
+ properties: {
1896
+ baseDir: { type: 'string', description: 'Observer data directory (default: .local/observer)' },
1897
+ },
1898
+ },
1899
+ },
1900
+ {
1901
+ name: 'mio.observer.trends',
1902
+ description: 'Get recent trend reports from the Observer pipeline.',
1903
+ inputSchema: {
1904
+ type: 'object',
1905
+ properties: {
1906
+ date: { type: 'string', description: 'Specific date (YYYY-MM-DD) or null for latest' },
1907
+ limit: { type: 'number', description: 'Max reports to return (default 7)' },
1908
+ baseDir: { type: 'string', description: 'Observer data directory' },
1909
+ },
1910
+ },
1911
+ },
1912
+ {
1913
+ name: 'mio.observer.research',
1914
+ description: 'List recent deep research results from the Observer pipeline.',
1915
+ inputSchema: {
1916
+ type: 'object',
1917
+ properties: {
1918
+ limit: { type: 'number', description: 'Max results (default 10)' },
1919
+ baseDir: { type: 'string', description: 'Observer data directory' },
1920
+ },
1921
+ },
1922
+ },
1923
+ {
1924
+ name: 'mio.observer.insights',
1925
+ description: 'List Observer-generated insights (5-section structured articles).',
1926
+ inputSchema: {
1927
+ type: 'object',
1928
+ properties: {
1929
+ limit: { type: 'number', description: 'Max insights (default 20)' },
1930
+ baseDir: { type: 'string', description: 'Observer data directory' },
1931
+ },
1932
+ },
1933
+ },
1934
+ {
1935
+ name: 'mio.observer.status',
1936
+ description: 'Get Observer pipeline status: counts of observations, trends, topics, research, insights, essays, and world model entities.',
1937
+ inputSchema: {
1938
+ type: 'object',
1939
+ properties: {
1940
+ baseDir: { type: 'string', description: 'Observer data directory' },
1941
+ },
1942
+ },
1943
+ },
1944
+ {
1945
+ name: 'mio.observer.collect',
1946
+ description: 'Force data collection from all configured sources (RSS, Bilibili, HackerNews, GitHub, Douyin). Returns collected observations.',
1947
+ inputSchema: {
1948
+ type: 'object',
1949
+ properties: {
1950
+ sources: { type: 'array', items: { type: 'string' }, description: 'Specific sources to collect (default: all)' },
1951
+ keywords: { type: 'array', items: { type: 'string' }, description: 'Keyword filter (OR match)' },
1952
+ limit: { type: 'number', description: 'Max items per source (default 20)' },
1953
+ baseDir: { type: 'string', description: 'Observer data directory' },
1954
+ },
1955
+ },
1956
+ },
1957
+ {
1958
+ name: 'mio.observer.ferment',
1959
+ description: 'Run fermentation engine: find recurring themes across recent observations and optionally write an essay.',
1960
+ inputSchema: {
1961
+ type: 'object',
1962
+ properties: {
1963
+ session: { type: 'string', description: 'Session label: morning/afternoon/night (default: afternoon)' },
1964
+ baseDir: { type: 'string', description: 'Observer data directory' },
1965
+ },
1966
+ },
1967
+ },
1968
+ {
1969
+ name: 'mio.observer.essays',
1970
+ description: 'List published or draft essays from the Observer.',
1971
+ inputSchema: {
1972
+ type: 'object',
1973
+ properties: {
1974
+ type: { type: 'string', description: 'published or draft (default: published)' },
1975
+ limit: { type: 'number', description: 'Max essays (default 10)' },
1976
+ baseDir: { type: 'string', description: 'Observer data directory' },
1977
+ },
1978
+ },
1979
+ },
1980
+ {
1981
+ name: 'mio.observer.dag',
1982
+ description: 'Get DAG state machine status: today\'s task, recent summaries, retryable tasks.',
1983
+ inputSchema: {
1984
+ type: 'object',
1985
+ properties: {
1986
+ days: { type: 'number', description: 'Days to look back (default 7)' },
1987
+ baseDir: { type: 'string', description: 'Observer data directory' },
1988
+ },
1989
+ },
1990
+ }] : []),
1723
1991
  ]
1724
1992
 
1993
+ // ═══ Insight Self-Observation Handlers ═══
1994
+
1995
+ function insightStatus() {
1996
+ if (!insightStore) throw new Error('@akemi-mio/insight not installed')
1997
+ const all = insightStore.getAll()
1998
+ const unreported = insightStore.getUnreported()
1999
+ return {
2000
+ total: all.length,
2001
+ unreported: unreported.length,
2002
+ reported: all.length - unreported.length,
2003
+ highValue: insightStore.getHighValueUnreported(50, 0.7).length,
2004
+ }
2005
+ }
2006
+
2007
+ function insightList(args = {}) {
2008
+ if (!insightStore) throw new Error('@akemi-mio/insight not installed')
2009
+ let insights = insightStore.getAll()
2010
+ if (args.unreported) insights = insightStore.getUnreported()
2011
+ if (args.minScore) insights = insights.filter(i => i.score >= args.minScore)
2012
+ if (args.detector) insights = insights.filter(i => i.detector === args.detector)
2013
+ if (args.limit) insights = insights.slice(-args.limit)
2014
+ return insights
2015
+ }
2016
+
2017
+ function insightMarkReported(args = {}) {
2018
+ if (!insightStore) throw new Error('@akemi-mio/insight not installed')
2019
+ if (!args.ids || !Array.isArray(args.ids)) throw new Error('ids array required')
2020
+ for (const id of args.ids) insightStore.markReported(id)
2021
+ return { marked: args.ids.length }
2022
+ }
2023
+
2024
+ async function insightGenerate(args = {}) {
2025
+ if (!InsightGenerator) throw new Error('@akemi-mio/insight not installed')
2026
+ const generator = new InsightGenerator({ chatJson })
2027
+ const ctx = {
2028
+ memoryEntries: (args.memories || []).map(m => ({
2029
+ type: m.kind || m.type || 'note',
2030
+ content: m.content || '',
2031
+ createdAt: m.createdAt || m.timestamp || Date.now(),
2032
+ })),
2033
+ summaries: args.summaries || [],
2034
+ interactionCount: 0,
2035
+ plans: (args.plans || []).map(p => ({
2036
+ title: p.title || p.name || '',
2037
+ status: p.status || 'unknown',
2038
+ updatedAt: p.updatedAt || Date.now(),
2039
+ steps: p.steps || [],
2040
+ createdAt: p.createdAt || Date.now(),
2041
+ })),
2042
+ eventCount: 0,
2043
+ }
2044
+ const insights = await generator.generate(ctx)
2045
+ if (insightStore) {
2046
+ for (const i of insights) insightStore.addMany([i])
2047
+ }
2048
+ return { generated: insights.length, insights }
2049
+ }
2050
+
2051
+ // ═══ Observer Research Pipeline Handlers ═══
2052
+
2053
+ function getObserverStore(baseDir) {
2054
+ if (!ObserverStore) throw new Error('@akemi-mio/observer not installed')
2055
+ return new ObserverStore(baseDir || path.join(process.cwd(), '.local', 'observer'))
2056
+ }
2057
+
2058
+ function getObserverBaseDir(args) {
2059
+ return args.baseDir || path.join(process.cwd(), '.local', 'observer')
2060
+ }
2061
+
2062
+ function readJsonSafe(filePath) {
2063
+ if (!fs.existsSync(filePath)) return null
2064
+ try { return JSON.parse(fs.readFileSync(filePath, 'utf8')) } catch { return null }
2065
+ }
2066
+
2067
+ function readJsonlSafe(filePath) {
2068
+ if (!fs.existsSync(filePath)) return []
2069
+ try {
2070
+ return fs.readFileSync(filePath, 'utf8').trim().split('\n').filter(Boolean)
2071
+ .map(l => { try { return JSON.parse(l) } catch { return null } }).filter(Boolean)
2072
+ } catch { return [] }
2073
+ }
2074
+
2075
+ function observerWorldModel(args = {}) {
2076
+ const baseDir = getObserverBaseDir(args)
2077
+ const wmDir = path.join(baseDir, 'world_model')
2078
+ return {
2079
+ entities: readJsonSafe(path.join(wmDir, 'entities.json')) || [],
2080
+ events: readJsonSafe(path.join(wmDir, 'events.json')) || [],
2081
+ trends: readJsonSafe(path.join(wmDir, 'trends.json')) || [],
2082
+ narratives: readJsonSafe(path.join(wmDir, 'narratives.json')) || [],
2083
+ }
2084
+ }
2085
+
2086
+ function observerTrends(args = {}) {
2087
+ const baseDir = getObserverBaseDir(args)
2088
+ const trendsDir = path.join(baseDir, 'trends')
2089
+ if (!fs.existsSync(trendsDir)) return []
2090
+ if (args.date) {
2091
+ const report = readJsonSafe(path.join(trendsDir, `${args.date}.json`))
2092
+ return report ? [report] : []
2093
+ }
2094
+ const files = fs.readdirSync(trendsDir).filter(f => f.endsWith('.json')).sort().reverse()
2095
+ return files.slice(0, args.limit || 7).map(f => readJsonSafe(path.join(trendsDir, f))).filter(Boolean)
2096
+ }
2097
+
2098
+ function observerResearch(args = {}) {
2099
+ const baseDir = getObserverBaseDir(args)
2100
+ const researchDir = path.join(baseDir, 'research')
2101
+ if (!fs.existsSync(researchDir)) return []
2102
+ const files = fs.readdirSync(researchDir).filter(f => f.endsWith('.json')).sort().reverse()
2103
+ return files.slice(0, args.limit || 10).map(f => readJsonSafe(path.join(researchDir, f))).filter(Boolean)
2104
+ }
2105
+
2106
+ function observerInsights(args = {}) {
2107
+ const baseDir = getObserverBaseDir(args)
2108
+ const insightsDir = path.join(baseDir, 'insights')
2109
+ if (!fs.existsSync(insightsDir)) return []
2110
+ const files = fs.readdirSync(insightsDir).filter(f => f.endsWith('.json')).sort().reverse()
2111
+ return files.slice(0, args.limit || 20).map(f => readJsonSafe(path.join(insightsDir, f))).filter(Boolean)
2112
+ }
2113
+
2114
+ function observerStatus(args = {}) {
2115
+ const baseDir = getObserverBaseDir(args)
2116
+ const dirs = ['observations', 'trends', 'topics', 'research', 'insights', 'world_model', 'essays']
2117
+ const status = {}
2118
+ for (const d of dirs) {
2119
+ const dir = path.join(baseDir, d)
2120
+ if (fs.existsSync(dir)) {
2121
+ status[d] = fs.readdirSync(dir).filter(f => !f.startsWith('.')).length
2122
+ } else {
2123
+ status[d] = 0
2124
+ }
2125
+ }
2126
+ return status
2127
+ }
2128
+
2129
+ function observerCollect(args = {}) {
2130
+ if (!ObserverService) throw new Error('@akemi-mio/observer not installed')
2131
+ const baseDir = getObserverBaseDir(args)
2132
+ const service = new ObserverService({ baseDir, collectorConfig: {} })
2133
+ const sources = args.sources || ['bilibili', 'hackernews', 'github', 'douyin', 'rss']
2134
+ const allObs = []
2135
+ for (const src of sources) {
2136
+ try {
2137
+ const collectFn = service.collectBySource ? service.collectBySource : service.collect
2138
+ if (collectFn) {
2139
+ const result = collectFn.call(service, [src], args.keywords || [], args.limit || 20)
2140
+ if (Array.isArray(result)) allObs.push(...result)
2141
+ else if (result && typeof result === 'object') {
2142
+ for (const [key, items] of Object.entries(result)) allObs.push(...items)
2143
+ }
2144
+ }
2145
+ } catch (e) {
2146
+ allObs.push({ source: src, error: e.message })
2147
+ }
2148
+ }
2149
+ return { collected: allObs.length, observations: allObs }
2150
+ }
2151
+
2152
+ function observerFerment(args = {}) {
2153
+ if (!ObserverService) throw new Error('@akemi-mio/observer not installed')
2154
+ const baseDir = getObserverBaseDir(args)
2155
+ const service = new ObserverService({ baseDir, collectorConfig: {} })
2156
+ const session = args.session || 'afternoon'
2157
+ try {
2158
+ const fermentation = service.getFermentation()
2159
+ if (fermentation && fermentation.ferment) return fermentation.ferment(session)
2160
+ return { status: 'fermentation engine available but no ferment method' }
2161
+ } catch (e) {
2162
+ return { error: e.message }
2163
+ }
2164
+ }
2165
+
2166
+ function observerEssays(args = {}) {
2167
+ const baseDir = getObserverBaseDir(args)
2168
+ const type = args.type || 'published'
2169
+ const essaysDir = path.join(baseDir, 'essays', type)
2170
+ if (!fs.existsSync(essaysDir)) return []
2171
+ const files = fs.readdirSync(essaysDir).filter(f => f.endsWith('.md')).sort().reverse()
2172
+ return files.slice(0, args.limit || 10).map(f => {
2173
+ const content = fs.readFileSync(path.join(essaysDir, f), 'utf8')
2174
+ return { file: f, type, content, created: fs.statSync(path.join(essaysDir, f)).birthtime.toISOString() }
2175
+ })
2176
+ }
2177
+
2178
+ function observerDag(args = {}) {
2179
+ const baseDir = getObserverBaseDir(args)
2180
+ const days = args.days || 7
2181
+ const summaries = []
2182
+ const todayDate = new Date()
2183
+ for (let i = 0; i < days; i++) {
2184
+ const d = new Date(todayDate)
2185
+ d.setDate(d.getDate() - i)
2186
+ const dateStr = d.toISOString().slice(0, 10)
2187
+ const filePath = path.join(baseDir, 'summaries', `${dateStr}.jsonl`)
2188
+ const items = readJsonlSafe(filePath)
2189
+ if (items.length) summaries.push({ date: dateStr, summaries: items })
2190
+ }
2191
+ return { summaries, summaryCount: summaries.reduce((a, s) => a + s.summaries.length, 0) }
2192
+ }
2193
+
1725
2194
  async function callTool(name, args = {}) {
1726
2195
  switch (name) {
1727
2196
  case 'mio.memory.query':
@@ -1776,9 +2245,43 @@ async function callTool(name, args = {}) {
1776
2245
  return evolutionCutover.authorityPlan(args)
1777
2246
  case 'mio.evolution.cutover.apply':
1778
2247
  return evolutionCutover.applyCutover(args)
1779
- case 'mio.task.record_outcome':
1780
- return recordTaskOutcome(args)
1781
- default:
2248
+ case 'mio.task.record_outcome':
2249
+ return recordTaskOutcome(args)
2250
+ case 'mio.creativity.status':
2251
+ return creativityEngine.status()
2252
+ case 'mio.creativity.list':
2253
+ return creativityEngine.list(args)
2254
+ case 'mio.creativity.generate':
2255
+ return creativityEngine.generate(args.sources, args.strategy)
2256
+ case 'mio.creativity.ferment':
2257
+ return creativityEngine.ferment(args.limit)
2258
+ case 'mio.insight.status':
2259
+ return insightStatus()
2260
+ case 'mio.insight.list':
2261
+ return insightList(args)
2262
+ case 'mio.insight.mark_reported':
2263
+ return insightMarkReported(args)
2264
+ case 'mio.insight.generate':
2265
+ return insightGenerate(args)
2266
+ case 'mio.observer.world_model':
2267
+ return observerWorldModel(args)
2268
+ case 'mio.observer.trends':
2269
+ return observerTrends(args)
2270
+ case 'mio.observer.research':
2271
+ return observerResearch(args)
2272
+ case 'mio.observer.insights':
2273
+ return observerInsights(args)
2274
+ case 'mio.observer.status':
2275
+ return observerStatus(args)
2276
+ case 'mio.observer.collect':
2277
+ return observerCollect(args)
2278
+ case 'mio.observer.ferment':
2279
+ return observerFerment(args)
2280
+ case 'mio.observer.essays':
2281
+ return observerEssays(args)
2282
+ case 'mio.observer.dag':
2283
+ return observerDag(args)
2284
+ default:
1782
2285
  throw new Error(`Unknown tool: ${name}`)
1783
2286
  }
1784
2287
  }