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/README.md CHANGED
@@ -55,8 +55,9 @@ mio memory migrate --ids a,b --scope global|project Move records between the p
55
55
  mio policy check "<action>" Check the historical risk of an action before running it (--project; reads global MIO_HOME)
56
56
  mio creativity status Show creativity hypothesis counts and recent top ideas (reads global MIO_HOME)
57
57
  mio creativity list List creativity hypotheses (--status active|validated|rejected|draft, --limit N)
58
- mio creativity generate Generate hypotheses from 2+ --source "name|content" (calls an LLM)
58
+ mio creativity generate Generate hypotheses from 2+ --source "name|content" (or --from-insights; calls an LLM)
59
59
  mio creativity ferment Review and refine active hypotheses (calls an LLM)
60
+ mio idea generate --goal "..." Grounded idea pipeline: auto sources + novelty gate, persisted (calls an LLM)
60
61
  mio insight status Insight counts: total, reported, unreported, high-value (needs @akemi-mio/insight)
61
62
  mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
62
63
  mio insight generate Generate insights from context (--memory "kind|content"/--summary "text"; calls an LLM)
@@ -101,7 +102,7 @@ mio --version Print the version and exit (-V)
101
102
 
102
103
  ## MCP 工具
103
104
 
104
- MCP 服务端在 5 大域共暴露 50 个工具:
105
+ MCP 服务端在 5 大域共暴露 51 个工具:
105
106
 
106
107
  ### 记忆(11)
107
108
  `mio.memory.query` · `mio.memory.record` · `mio.memory.archive` · `mio.memory.merge` · `mio.memory.migrate` · `mio.memory.analyze` · `mio.memory.forget` · `mio.experience.list` · `mio.experience.confirm` · `mio.experience.reuse` · `mio.policy.check`
@@ -114,15 +115,15 @@ MCP 服务端在 5 大域共暴露 50 个工具:
114
115
  ### 洞察自省(4)
115
116
  `mio.insight.status` · `mio.insight.list` · `mio.insight.mark_reported` · `mio.insight.generate`
116
117
 
117
- ### 创意引擎(4)
118
- `mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.list` · `mio.creativity.status`
118
+ ### 创意引擎(5)
119
+ `mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.list` · `mio.creativity.status` · `mio.idea.generate`
119
120
 
120
121
  ### 任务、Agent 与演化(16)
121
122
  `mio.task.route` · `mio.task.record_outcome` · `mio.agent.register` · `mio.agent.list` · `mio.agent.report` · `mio.agent.evaluation` · `mio.host.capabilities` · `mio.evolution.status` · `mio.evolution.report` · `mio.evolution.shadow.record` · `mio.evolution.dual_write.record` · `mio.evolution.cutover.readiness` · `mio.evolution.cutover.apply` · `mio.evolution.migration.plan` · `mio.evolution.authority.plan` · `mio.phase0.report`
122
123
 
123
124
  ## 包
124
125
 
125
- `mio-agent-runtime` 依赖发布在 `@akemi-mio` scope 下的 **9** 个包(声明于 `package.json`,均为 `0.1.0`)。它们仅在源码变更时才发布,所以普通的 CLI 发布不会重新发布这些包。
126
+ `mio-agent-runtime` 依赖发布在 `@akemi-mio` scope 下的 **10** 个包(声明于 `package.json`,均为 `0.1.0`)。它们仅在源码变更时才发布,所以普通的 CLI 发布不会重新发布这些包。
126
127
 
127
128
  | 依赖 | 说明 |
128
129
  |---|---|
@@ -135,6 +136,7 @@ MCP 服务端在 5 大域共暴露 50 个工具:
135
136
  | `@akemi-mio/evolution-scheduler` | 演化调度器 |
136
137
  | `@akemi-mio/observer` | ObserverService —— 多源数据采集、趋势分析、深度研究、世界模型、发酵、DAG 状态机、自演化引擎(唯一运行时依赖 `undici`,只用于走代理采集) |
137
138
  | `@akemi-mio/insight` | InsightService —— 基于 LLM 的洞察生成、冲突/漂移/重复/停滞目标/摩擦检测器、在场服务、洞察打分(零 npm 依赖) |
139
+ | `@akemi-mio/creativity` | IdeaGenerator / NoveltyScorer / SourceAggregator —— `mio idea generate` 与创意引擎的生成算法(依赖 `@akemi-mio/core`) |
138
140
 
139
141
  以下 `@akemi-mio` 包同样位于本 monorepo 中并独立发布,但**不是**本 CLI 的依赖——其源码从不 import 它们,因此未声明在 `package.json` 里:
140
142
 
@@ -146,9 +148,9 @@ MCP 服务端在 5 大域共暴露 50 个工具:
146
148
 
147
149
  它们可直接被其它 Node 项目消费。`mio-agent-runtime` 自身是 CLI + MCP server 包。
148
150
 
149
- ### 与宿主耦合的包(不发布)
151
+ ### 曾经不发布的两个包(现已发布)
150
152
 
151
- `@akemi-mio/core` 与 `@akemi-mio/creativity` 通过 `workspace:*` 链接在本 monorepo 内部消费,因此从源码构建,**不**发布到 npm。`packages/cli/` 已不存在。不要把这两个包加进下游 `package.json`——安装会失败。
153
+ `@akemi-mio/core` 与 `@akemi-mio/creativity` 自 `mio-agent-runtime@0.14.0` 起发布到 npm(`0.1.0`)。`@akemi-mio/creativity` 是本 CLI 的直接依赖,`@akemi-mio/core` 经由它传递引入;其它 Node 项目可直接安装这两个包。
152
154
 
153
155
  ## 宿主说明
154
156
 
@@ -359,7 +361,7 @@ Historically risky: ...
359
361
 
360
362
  说明:
361
363
 
362
- - **它读取全局 `MIO_HOME`,而非每项目目录。** MCP 服务端把数据目录解析为 `MIO_DATA_DIR || cwd/.mio-intelligence`,因此从任意目录发起的 MCP 调用看到的是空存储;CLI 刻意指向累积的全局日志。用 `--project` 把 trace 集合限定到某个项目。
364
+ - **它读取全局 `MIO_HOME`,而非每项目目录。** MCP 服务端把数据目录解析为 `MIO_DATA_DIR || cwd/.mio-intelligence`,因此从任意目录发起的 MCP 调用看到的是空存储;CLI 刻意指向累积的全局日志。`mio mcp` 是例外:它先把 `MIO_DATA_DIR` 钉到 `MIO_HOME` 再启动服务端,于是 stdio 入口与终端读写同一份数据。注意:各桌面宿主适配器(codex、opencode、workbuddy、hermes、claude-code)在启动 MCP 服务端时会**显式传入**各自的 `MIO_DATA_DIR`(指向宿主的配置目录),因此它们**不共享** CLI 的 `MIO_HOME`;只有 `mio mcp`(直接 stdio 连接)才与终端读写同一份数据。用 `--project` 把 trace 集合限定到某个项目。
363
365
  - **风险即失败占比**,`>= 0.4` 为高,`>= 0.2` 为中。`retry` 与 `aborted` 与 `failure`、`error` 一并计为失败。无匹配历史报告 `UNKNOWN`,与 `LOW`(干净记录)不同。
364
366
  - **匹配可能被通用 token 带偏。** 匹配是对动作 token 的宽松 OR,所以像 `task` 这样的动作几乎匹配每条 trace,因为关键词 `task` 几乎出现在所有 payload 中。当动作中*每个* token 都至少出现在半数候选 trace 里时,CLI 会打印警告,而不是让等级自说自话:
365
367
 
@@ -386,7 +388,7 @@ mio creativity list --json
386
388
  ```text
387
389
  Creativity engine:
388
390
  hypotheses: 5 combos: 3 experiments: 1
389
- active: 2 validated: 1 rejected: 1
391
+ active: 2 validated: 1 rejected: 1 draft: 1
390
392
 
391
393
  Recent top ideas:
392
394
  - Plugin architecture (novelty=80 feasibility=70 impact=90 score=240)
@@ -396,7 +398,10 @@ Recent top ideas:
396
398
  说明:
397
399
 
398
400
  - **它委托给与 MCP 服务端相同的 `CreativityEngine`**,因此终端的 `mio creativity list` 看到的是与 `mio.creativity.list` 工具完全一致的假设。存储位于 `<MIO_HOME>/creativity/creativity-hypotheses.jsonl`;CLI 指向全局 `MIO_HOME`,与 `mio policy check` 一致。
401
+ - **`list` 默认只返回最新 20 条**,与 `mio.creativity.list` 的 schema 承诺一致;`--limit 0`(或 MCP 的 `limit: 0`)才是「全部」。存储很大时默认全量返回会把工具结果撑爆。
402
+ - **`status` 计入 `draft`。** `draft` 是发酵中的真实状态(`ferment` 会同时处理 `draft` 与 `active`),此前漏计导致 `status` 与 `list --status draft` 对不上。
399
403
  - **空存储是正常状态。** 引擎运行之前,`status` 报告全零计数,`list` 显示 "No hypotheses match." 两者都是有效输出,不是错误。
404
+ - **缓存会在文件变化时失效。** 三个 JSONL 的读取按 `(size, mtime)` 校验,因此另一个进程写入后长驻的 MCP 服务端无需重启就能看到——与 `server/insight-store.js` 同一套守卫。
400
405
 
401
406
  ### 生成与发酵(`generate` / `ferment`)
402
407
 
@@ -405,19 +410,44 @@ Recent top ideas:
405
410
 
406
411
  ```bash
407
412
  mio creativity generate --source "auth|token rotation" --source "cache|write-through"
413
+ mio creativity generate --from-insights --source "auth|token rotation"
408
414
  mio creativity ferment --limit 3
409
415
  ```
410
416
 
411
417
  - `--source "名称|内容"` **至少两个**:引擎是把概念两两配对来产生新假设的,
412
418
  一个来源在构造上就不可能产出组合(此时**不会**调用 LLM,直接返回
413
- `need at least 2 sources`)。
414
- - `ferment` 会复核 `active` 假设并更新分数;`verdict=promote` 且总分 > 200 时
419
+ `need at least 2 sources`)。显式来源不足 2 个时,本地自动来源(memory / traces / 已存假设;MCP 侧还含 insight 与 observer 趋势)会先把列表补齐到 2——显式 `--source` 永远排在前面且不被替换;补齐后仍不足 2 个才报上面的错。
420
+ - **`--from-insights`(MCP 侧为 `fromInsights: true`)用已存洞察补足概念来源**:
421
+ 读取 `mio insight list` 能看到的同一份存储,按分数取前 10 条映射成
422
+ `{ name, content, type: 'insight' }`,再与显式的 `--source` 合并。它只在
423
+ `@akemi-mio/insight` 已安装且至少存有一条洞察时有意义,否则明确报错而不是
424
+ 静默当作「没有来源」。因为来源可由它提供,`mio.creativity.generate` 的
425
+ `sources` 不再是 schema 必填项。
426
+ - `ferment` 会复核 `active` / `draft` 假设并更新分数;`verdict=promote` 且总分 > 200 时
415
427
  升为 `validated`,`verdict=reject` 则标记为 `rejected`。
428
+ - **失败的 LLM 调用会出现在结果里,不再被吞掉。** 返回值带 `errors`
429
+ (`generate` 是 `pair` + 原因,`ferment` 是 `id` + 原因),CLI 也会在正文后
430
+ 打印 `N pair(s) failed:`。此前 `catch {}` 让「端点挂了」与「这一对确实没有
431
+ 新意」长得一模一样——`ideas: []` / `Nothing fermented` 两种输出都无法区分。
432
+
433
+ ### 目标驱动的 idea 流水线(`mio idea generate`)
434
+
435
+ 一个 goal 直接产出可实验的假设(MCP 面为 `mio.idea.generate`),来源全程本地优先组装:
436
+
437
+ ```bash
438
+ mio idea generate --goal "降低 MCP 调用延迟" --context "近三天 p95 上升" --constraint "不引入新依赖"
439
+ mio idea generate --goal "扩大命令覆盖" --json
440
+ ```
441
+
442
+ - **来源顺序**:goal → context → constraints → 记忆 grounding(取前 3 条,`scope: all`)→ 本地自动来源(memory / traces / 已存假设,MCP 侧还含 insight 与 observer 趋势),按 `origin` 去重;凑不足 2 个时返回 `reason` 文案而不是报错。
443
+ - **每条 idea 都持久化**到 `<MIO_HOME>/creativity/creativity-hypotheses.jsonl`(`draft`,带 `provenance`:strategy / technique / relatedMemoryIds / 命中来源 / generatedAt),**绝不写入 Mio memory**。
444
+ - **novelty 门禁**:与已 `rejected` 假设近似重复的候选落盘为 `rejected` 并带 `rejectionReason`;与近期假设相似的只降分。拒绝的假设同样入库,供后续去重。
445
+ - `--num` 钳制在 1..3(默认 3);`--json` 返回完整结构 `{ ideas, generatedAt, groundedWith, persistedIds }`。
416
446
 
417
447
  #### LLM 配置
418
448
 
419
- 只有 4 条命令会调用大模型:`mio creativity generate` / `ferment`、
420
- `mio insight generate`、`mio observer ferment`。它们共用
449
+ 只有 5 条命令会调用大模型:`mio creativity generate` / `ferment`、
450
+ `mio idea generate`、`mio insight generate`、`mio observer ferment`。它们共用
421
451
  `server/llm-client.js` 这一份实现,配置也共用同一套解析顺序:
422
452
 
423
453
  ```
@@ -491,7 +521,7 @@ mio observer trends --base-dir /path/to/.local/observer
491
521
  mio observer ferment --session morning # morning|afternoon|night
492
522
  ```
493
523
 
494
- 两者都依赖可选包 `@akemi-mio/observer`(未安装时提示 `not installed`,不是空结果)。`collect` 会**联网**抓取,`ferment` 需要 LLM;它们**不做预览**,因为预览意味着把数据抓两遍。单个源失败不会中断整次运行——该源会以 `errors: ...` 出现在输出里,其余源照常统计。
524
+ 两者都依赖可选包 `@akemi-mio/observer`(未安装时提示 `not installed`,不是空结果)。`collect` 会**联网**抓取,`ferment` 需要 LLM;它们**不做预览**,因为预览意味着把数据抓两遍。单个源失败不会中断整次运行——该源会以 `errors: ...` 出现在输出里,其余源照常统计。`collect` 会**联网**抓取,`ferment` 需要 LLM;它们**不做预览**,因为预览意味着把数据抓两遍。单个源失败不会中断整次运行——该源会以 `errors: ...` 出现在输出里,其余源照常统计。
495
525
  - **`mio observer pipeline` 是研究 DAG 的入口**(`collect → trend → tension → research → multi-brain → compose → world model → publish → self-evolve`)。此前 `runPipeline` / `tickPipeline` / `forcePipeline` 在 mio-agent-runtime 里**没有任何调用方**,所以 trends / research / insights 默认永远是空的,只有手动实例化服务才能跑通。它同样依赖 `@akemi-mio/observer`,会**联网并调用多次 LLM**,因此和 `subscribe` 一样**默认只预览**(读今天的 DAG 状态与解析后的 LLM 端点,不构造服务、不建目录),`--run` 才真正执行:
496
526
 
497
527
  ```bash
package/adapters/codex.js CHANGED
@@ -1,122 +1,122 @@
1
- 'use strict'
2
-
3
- const fs = require('fs')
4
- const path = require('path')
5
- const os = require('os')
6
- const { restartWarning } = require('./host-utils')
7
-
8
- const SERVER_SECTION = '[mcp_servers.mio-intelligence]'
9
- const ENV_SECTION = '[mcp_servers.mio-intelligence.env]'
10
-
11
- function configPath() {
12
- return path.join(os.homedir(), '.codex', 'config.toml')
13
- }
14
-
15
- function isInstalled() {
16
- const target = configPath()
17
- if (!fs.existsSync(target)) return false
18
- return fs.readFileSync(target, 'utf8').includes(SERVER_SECTION)
19
- }
20
-
21
- // Legacy installs pointed args at the worktree copy of the server
22
- // (server/mio-intelligence-mcp/index.js) instead of this package's copy
23
- // (server/mio-intelligence-mcp/index.js). Detect staleness by comparing the
24
- // configured args path with the server path this package ships.
25
- function configNeedsRepair(serverScript) {
26
- const target = configPath()
27
- if (!fs.existsSync(target)) return false
28
- try {
29
- const src = fs.readFileSync(target, 'utf8')
30
- if (!src.includes(SERVER_SECTION)) return false
31
- const needle = serverScript.replace(/\\/g, '/').toLowerCase()
32
- return !src.replace(/\\/g, '/').toLowerCase().includes(needle)
33
- } catch (_) {
34
- return false
35
- }
36
- }
37
-
38
- function installMessage(installed, restartHint) {
39
- let message = installed
40
- ? 'Codex Mio MCP repaired (server path updated). Restart Codex to load it.'
41
- : 'Codex Mio MCP installed. Restart Codex to load it.'
42
- if (restartHint) message += ' ' + restartHint
43
- return message
44
- }
45
-
46
- function restartWarningMessage() {
47
- return restartWarning({
48
- hostLabel: 'Codex',
49
- exeName: 'codex.exe',
50
- exePathFragment: 'OpenAI.Codex',
51
- configPath: configPath(),
52
- })
53
- }
54
-
55
- function stripMioSection(toml) {
56
- const lines = toml.replace(/\r\n/g, '\n').split('\n')
57
- const out = []
58
- let skipping = false
59
- for (const line of lines) {
60
- const trimmed = line.trim()
61
- if (trimmed === SERVER_SECTION || trimmed === ENV_SECTION) {
62
- skipping = true
63
- continue
64
- }
65
- if (skipping && /^\[.*\]$/.test(trimmed)) {
66
- skipping = false
67
- }
68
- if (!skipping) out.push(line)
69
- }
70
- return out.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n'
71
- }
72
-
73
- function install({ node, serverScript, home, workspace }) {
74
- const target = configPath()
75
- const backup = `${target}.bak-mio-${Date.now()}`
76
- const installed = isInstalled()
77
- if (installed && !configNeedsRepair(serverScript)) {
78
- return {
79
- changed: false,
80
- configPath: target,
81
- message: 'Codex Mio MCP already installed.',
82
- }
83
- }
84
-
85
- if (fs.existsSync(target)) {
86
- fs.copyFileSync(target, backup)
87
- }
88
-
89
- const context = JSON.stringify({
90
- agentId: 'codex',
91
- workspace: workspace || process.cwd(),
92
- sessionId: 'codex-session',
93
- })
94
-
95
- fs.mkdirSync(path.dirname(target), { recursive: true })
96
- let toml = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
97
- toml = stripMioSection(toml)
98
- toml = toml.replace(/\n{3,}/g, '\n\n')
99
- if (!toml.endsWith('\n')) toml += '\n'
100
- const section = [
101
- '',
102
- SERVER_SECTION,
103
- `command = '${node}'`,
104
- `args = ['${serverScript}']`,
105
- 'startup_timeout_sec = 30',
106
- '',
107
- ENV_SECTION,
108
- `MIO_DATA_DIR = '${home}'`,
109
- `MIO_CONTEXT = '${context}'`,
110
- '',
111
- ].join('\n')
112
- fs.writeFileSync(target, `${toml}${section}\n`, 'utf8')
113
-
114
- return {
115
- changed: true,
116
- configPath: target,
117
- backup,
118
- message: installMessage(installed, restartWarningMessage()),
119
- }
120
- }
121
-
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const os = require('os')
6
+ const { restartWarning } = require('./host-utils')
7
+
8
+ const SERVER_SECTION = '[mcp_servers.mio-intelligence]'
9
+ const ENV_SECTION = '[mcp_servers.mio-intelligence.env]'
10
+
11
+ function configPath() {
12
+ return path.join(os.homedir(), '.codex', 'config.toml')
13
+ }
14
+
15
+ function isInstalled() {
16
+ const target = configPath()
17
+ if (!fs.existsSync(target)) return false
18
+ return fs.readFileSync(target, 'utf8').includes(SERVER_SECTION)
19
+ }
20
+
21
+ // Legacy installs pointed args at the worktree copy of the server
22
+ // (server/mio-intelligence-mcp/index.js) instead of this package's copy
23
+ // (server/mio-intelligence-mcp/index.js). Detect staleness by comparing the
24
+ // configured args path with the server path this package ships.
25
+ function configNeedsRepair(serverScript) {
26
+ const target = configPath()
27
+ if (!fs.existsSync(target)) return false
28
+ try {
29
+ const src = fs.readFileSync(target, 'utf8')
30
+ if (!src.includes(SERVER_SECTION)) return false
31
+ const needle = serverScript.replace(/\\/g, '/').toLowerCase()
32
+ return !src.replace(/\\/g, '/').toLowerCase().includes(needle)
33
+ } catch (_) {
34
+ return false
35
+ }
36
+ }
37
+
38
+ function installMessage(installed, restartHint) {
39
+ let message = installed
40
+ ? 'Codex Mio MCP repaired (server path updated). Restart Codex to load it.'
41
+ : 'Codex Mio MCP installed. Restart Codex to load it.'
42
+ if (restartHint) message += ' ' + restartHint
43
+ return message
44
+ }
45
+
46
+ function restartWarningMessage() {
47
+ return restartWarning({
48
+ hostLabel: 'Codex',
49
+ exeName: 'codex.exe',
50
+ exePathFragment: 'OpenAI.Codex',
51
+ configPath: configPath(),
52
+ })
53
+ }
54
+
55
+ function stripMioSection(toml) {
56
+ const lines = toml.replace(/\r\n/g, '\n').split('\n')
57
+ const out = []
58
+ let skipping = false
59
+ for (const line of lines) {
60
+ const trimmed = line.trim()
61
+ if (trimmed === SERVER_SECTION || trimmed === ENV_SECTION) {
62
+ skipping = true
63
+ continue
64
+ }
65
+ if (skipping && /^\[.*\]$/.test(trimmed)) {
66
+ skipping = false
67
+ }
68
+ if (!skipping) out.push(line)
69
+ }
70
+ return out.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n'
71
+ }
72
+
73
+ function install({ node, serverScript, home, workspace }) {
74
+ const target = configPath()
75
+ const backup = `${target}.bak-mio-${Date.now()}`
76
+ const installed = isInstalled()
77
+ if (installed && !configNeedsRepair(serverScript)) {
78
+ return {
79
+ changed: false,
80
+ configPath: target,
81
+ message: 'Codex Mio MCP already installed.',
82
+ }
83
+ }
84
+
85
+ if (fs.existsSync(target)) {
86
+ fs.copyFileSync(target, backup)
87
+ }
88
+
89
+ const context = JSON.stringify({
90
+ agentId: 'codex',
91
+ workspace: workspace || process.cwd(),
92
+ sessionId: 'codex-session',
93
+ })
94
+
95
+ fs.mkdirSync(path.dirname(target), { recursive: true })
96
+ let toml = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
97
+ toml = stripMioSection(toml)
98
+ toml = toml.replace(/\n{3,}/g, '\n\n')
99
+ if (!toml.endsWith('\n')) toml += '\n'
100
+ const section = [
101
+ '',
102
+ SERVER_SECTION,
103
+ `command = '${node}'`,
104
+ `args = ['${serverScript}']`,
105
+ 'startup_timeout_sec = 30',
106
+ '',
107
+ ENV_SECTION,
108
+ `MIO_DATA_DIR = '${home}'`,
109
+ `MIO_CONTEXT = '${context}'`,
110
+ '',
111
+ ].join('\n')
112
+ fs.writeFileSync(target, `${toml}${section}\n`, 'utf8')
113
+
114
+ return {
115
+ changed: true,
116
+ configPath: target,
117
+ backup,
118
+ message: installMessage(installed, restartWarningMessage()),
119
+ }
120
+ }
121
+
122
122
  module.exports = { configPath, isInstalled, install, restartWarningMessage }