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.
package/README.md CHANGED
@@ -55,7 +55,7 @@ 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
60
  mio insight status Insight counts: total, reported, unreported, high-value (needs @akemi-mio/insight)
61
61
  mio insight list List insights (--unreported, --min-score N, --detector X, --limit N)
@@ -359,7 +359,7 @@ Historically risky: ...
359
359
 
360
360
  说明:
361
361
 
362
- - **它读取全局 `MIO_HOME`,而非每项目目录。** MCP 服务端把数据目录解析为 `MIO_DATA_DIR || cwd/.mio-intelligence`,因此从任意目录发起的 MCP 调用看到的是空存储;CLI 刻意指向累积的全局日志。用 `--project` 把 trace 集合限定到某个项目。
362
+ - **它读取全局 `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
363
  - **风险即失败占比**,`>= 0.4` 为高,`>= 0.2` 为中。`retry` 与 `aborted` 与 `failure`、`error` 一并计为失败。无匹配历史报告 `UNKNOWN`,与 `LOW`(干净记录)不同。
364
364
  - **匹配可能被通用 token 带偏。** 匹配是对动作 token 的宽松 OR,所以像 `task` 这样的动作几乎匹配每条 trace,因为关键词 `task` 几乎出现在所有 payload 中。当动作中*每个* token 都至少出现在半数候选 trace 里时,CLI 会打印警告,而不是让等级自说自话:
365
365
 
@@ -386,7 +386,7 @@ mio creativity list --json
386
386
  ```text
387
387
  Creativity engine:
388
388
  hypotheses: 5 combos: 3 experiments: 1
389
- active: 2 validated: 1 rejected: 1
389
+ active: 2 validated: 1 rejected: 1 draft: 1
390
390
 
391
391
  Recent top ideas:
392
392
  - Plugin architecture (novelty=80 feasibility=70 impact=90 score=240)
@@ -396,7 +396,10 @@ Recent top ideas:
396
396
  说明:
397
397
 
398
398
  - **它委托给与 MCP 服务端相同的 `CreativityEngine`**,因此终端的 `mio creativity list` 看到的是与 `mio.creativity.list` 工具完全一致的假设。存储位于 `<MIO_HOME>/creativity/creativity-hypotheses.jsonl`;CLI 指向全局 `MIO_HOME`,与 `mio policy check` 一致。
399
+ - **`list` 默认只返回最新 20 条**,与 `mio.creativity.list` 的 schema 承诺一致;`--limit 0`(或 MCP 的 `limit: 0`)才是「全部」。存储很大时默认全量返回会把工具结果撑爆。
400
+ - **`status` 计入 `draft`。** `draft` 是发酵中的真实状态(`ferment` 会同时处理 `draft` 与 `active`),此前漏计导致 `status` 与 `list --status draft` 对不上。
399
401
  - **空存储是正常状态。** 引擎运行之前,`status` 报告全零计数,`list` 显示 "No hypotheses match." 两者都是有效输出,不是错误。
402
+ - **缓存会在文件变化时失效。** 三个 JSONL 的读取按 `(size, mtime)` 校验,因此另一个进程写入后长驻的 MCP 服务端无需重启就能看到——与 `server/insight-store.js` 同一套守卫。
400
403
 
401
404
  ### 生成与发酵(`generate` / `ferment`)
402
405
 
@@ -405,14 +408,25 @@ Recent top ideas:
405
408
 
406
409
  ```bash
407
410
  mio creativity generate --source "auth|token rotation" --source "cache|write-through"
411
+ mio creativity generate --from-insights --source "auth|token rotation"
408
412
  mio creativity ferment --limit 3
409
413
  ```
410
414
 
411
415
  - `--source "名称|内容"` **至少两个**:引擎是把概念两两配对来产生新假设的,
412
416
  一个来源在构造上就不可能产出组合(此时**不会**调用 LLM,直接返回
413
417
  `need at least 2 sources`)。
414
- - `ferment` 会复核 `active` 假设并更新分数;`verdict=promote` 且总分 > 200 时
418
+ - **`--from-insights`(MCP 侧为 `fromInsights: true`)用已存洞察补足概念来源**:
419
+ 读取 `mio insight list` 能看到的同一份存储,按分数取前 10 条映射成
420
+ `{ name, content, type: 'insight' }`,再与显式的 `--source` 合并。它只在
421
+ `@akemi-mio/insight` 已安装且至少存有一条洞察时有意义,否则明确报错而不是
422
+ 静默当作「没有来源」。因为来源可由它提供,`mio.creativity.generate` 的
423
+ `sources` 不再是 schema 必填项。
424
+ - `ferment` 会复核 `active` / `draft` 假设并更新分数;`verdict=promote` 且总分 > 200 时
415
425
  升为 `validated`,`verdict=reject` 则标记为 `rejected`。
426
+ - **失败的 LLM 调用会出现在结果里,不再被吞掉。** 返回值带 `errors`
427
+ (`generate` 是 `pair` + 原因,`ferment` 是 `id` + 原因),CLI 也会在正文后
428
+ 打印 `N pair(s) failed:`。此前 `catch {}` 让「端点挂了」与「这一对确实没有
429
+ 新意」长得一模一样——`ideas: []` / `Nothing fermented` 两种输出都无法区分。
416
430
 
417
431
  #### LLM 配置
418
432
 
@@ -491,7 +505,7 @@ mio observer trends --base-dir /path/to/.local/observer
491
505
  mio observer ferment --session morning # morning|afternoon|night
492
506
  ```
493
507
 
494
- 两者都依赖可选包 `@akemi-mio/observer`(未安装时提示 `not installed`,不是空结果)。`collect` 会**联网**抓取,`ferment` 需要 LLM;它们**不做预览**,因为预览意味着把数据抓两遍。单个源失败不会中断整次运行——该源会以 `errors: ...` 出现在输出里,其余源照常统计。
508
+ 两者都依赖可选包 `@akemi-mio/observer`(未安装时提示 `not installed`,不是空结果)。`collect` 会**联网**抓取,`ferment` 需要 LLM;它们**不做预览**,因为预览意味着把数据抓两遍。单个源失败不会中断整次运行——该源会以 `errors: ...` 出现在输出里,其余源照常统计。`collect` 会**联网**抓取,`ferment` 需要 LLM;它们**不做预览**,因为预览意味着把数据抓两遍。单个源失败不会中断整次运行——该源会以 `errors: ...` 出现在输出里,其余源照常统计。
495
509
  - **`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
510
 
497
511
  ```bash
@@ -46,12 +46,12 @@ const MEMORY_BLOCK = `${MEMORY_BEGIN}
46
46
 
47
47
  Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
48
48
 
49
- - 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.
50
- - After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mio-intelligence_mio.memory.record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
51
- - 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\`.
52
- - 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.
53
- - 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.
54
- - 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\`.
49
+ - 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.
50
+ - 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\`.
51
+ - 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\`.
52
+ - 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.
53
+ - 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.
54
+ - 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\`.
55
55
  - If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
56
56
  - Do not store secrets, credentials, or raw sensitive content in Mio memory.
57
57
  ${MEMORY_END}`
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 }