mio-agent-runtime 0.5.22 → 0.5.23
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 +32 -7
- package/bin/mio.js +47 -14
- package/observe/observer.js +40 -2
- package/package.json +9 -2
- package/scripts/verify-packed-runtime.js +23 -8
- package/server/digest.js +12 -0
- package/server/mio-intelligence-mcp/index.js +57 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Mio Agent Runtime 的可发布 CLI 包。
|
|
|
10
10
|
npm install -g mio-agent-runtime
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
> 新机器 / 升级:请安装 `>=0.
|
|
13
|
+
> 新机器 / 升级:请安装 `>=0.5.22`(5 host 被动观察 + trace 查询 + digest 价值管线)。
|
|
14
14
|
> 旧版本只有主动 MCP 规则注入,没有「无感存/取」。安装后执行一次
|
|
15
15
|
> `mio init && mio install <host>`。
|
|
16
16
|
|
|
@@ -43,12 +43,12 @@ mio --json evolution status Machine-readable evolution module health
|
|
|
43
43
|
|
|
44
44
|
## MCP Tools
|
|
45
45
|
|
|
46
|
-
MCP server exposes
|
|
46
|
+
MCP server exposes 46 tools across 5 domains:
|
|
47
47
|
|
|
48
|
-
### Memory (
|
|
48
|
+
### Memory (9)
|
|
49
49
|
`mio.memory.query` · `mio.memory.record` · `mio.memory.archive` · `mio.memory.migrate` · `mio.memory.analyze` · `mio.experience.list` · `mio.experience.confirm` · `mio.experience.reuse` · `mio.policy.check`
|
|
50
50
|
|
|
51
|
-
### Observer Pipeline (
|
|
51
|
+
### Observer Pipeline (14)
|
|
52
52
|
`mio.observer.world_model` · `mio.observer.trends` · `mio.observer.research` · `mio.observer.insights` · `mio.observer.status` · `mio.observer.collect` · `mio.observer.ferment` · `mio.observer.essays` · `mio.observer.dag` · `mio.observer.ingest` · `mio.observer.subscribe` · `mio.observer.digest`
|
|
53
53
|
`mio.trace.query` — query the trace log (task outcomes, tool errors) by type/outcome/agent/project/time window; also available as `mio traces` on the CLI
|
|
54
54
|
`mio.digest.generate` — aggregate recent data into an actionable digest (agent success rates, project activity, error hotspots, reuse evidence, suggestions); also available as `mio digest` on the CLI
|
|
@@ -59,8 +59,8 @@ MCP server exposes 47 tools across 5 domains:
|
|
|
59
59
|
### Creativity Engine (4)
|
|
60
60
|
`mio.creativity.generate` · `mio.creativity.ferment` · `mio.creativity.list` · `mio.creativity.status`
|
|
61
61
|
|
|
62
|
-
###
|
|
63
|
-
`mio.
|
|
62
|
+
### Task, Agent & Evolution (15)
|
|
63
|
+
`mio.task.route` · `mio.task.record_outcome` · `mio.agent.register` · `mio.agent.list` · `mio.agent.report` · `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`
|
|
64
64
|
|
|
65
65
|
## Packages
|
|
66
66
|
|
|
@@ -77,6 +77,11 @@ Published to npm under `@akemi-mio` scope:
|
|
|
77
77
|
| `@akemi-mio/evolution-strategy` | Evolution strategy engine |
|
|
78
78
|
| `@akemi-mio/evolution-safety` | Safety guardrails |
|
|
79
79
|
| `@akemi-mio/evolution-scheduler` | Evolution scheduler |
|
|
80
|
+
| `@akemi-mio/analysis` | ModuleScanner — static analysis of package source trees (zero deps) |
|
|
81
|
+
| `@akemi-mio/messaging` | ExternalMessageGateway — inbound message routing + notification dispatch via injected renderer/outbox (zero deps; telegram adapters stay host-side) |
|
|
82
|
+
| `@akemi-mio/reasoning` | Reasoning planner — pure-function scoring for reasoning directives (injectable logger) |
|
|
83
|
+
| `@akemi-mio/resource-control` | Resource budgets, background task runner, budget rebalancing (injectable runtime) |
|
|
84
|
+
| `@akemi-mio/agent-persona` | Persona drift control, content classification, user behavior analysis (injectable logger) |
|
|
80
85
|
| `@akemi-mio/creativity` | CreativityService — LLM-powered concept generation, conflict detection, merging, fermentation (TypeScript, depends on `@akemi-mio/core`) |
|
|
81
86
|
| `@akemi-mio/observer` | ObserverService — multi-source data collection, trend analysis, deep research, world model, fermentation, DAG state machine, self-evolution engine (zero npm deps) |
|
|
82
87
|
| `@akemi-mio/insight` | InsightService — LLM-based insight generation, conflict/drift/repetition/stalled-goal/friction detectors, presence service, insight scoring (zero npm deps) |
|
|
@@ -132,6 +137,21 @@ schema,`source=workbuddy-observer`)。
|
|
|
132
137
|
- 已处理的文件偏移记录在 `MIO_HOME/observe-state.json`,不会重复写入。
|
|
133
138
|
- 只回填最近 24h 内更新的会话,避免把历史记录灌入记忆库。
|
|
134
139
|
|
|
140
|
+
### Context 块的写入范围(`MIO_CONTEXT`)
|
|
141
|
+
|
|
142
|
+
`<workspace>/AGENTS.md`(或 `CLAUDE.md`)里的 `MIO_CONTEXT` 块最多保留
|
|
143
|
+
6 条(`CONTEXT_MAX`),按时间倒序。两条规则保证真实历史不被挤掉:
|
|
144
|
+
|
|
145
|
+
- **同一条内容只占一个位置**。重复写入只把它移到最前。旧版本会把同一行
|
|
146
|
+
反复插入,占满全部 6 个位置,把真实任务摘要整体挤出(存量
|
|
147
|
+
`observe-state.json` 里的重复条目会在下次写入时自动清理)。
|
|
148
|
+
- **`mio digest --write-back` 只写回有数据的项目**。写回的是一行项目维度
|
|
149
|
+
摘要(`digest(7d): <项目> N 任务 M% 成功`),不是全局统计;`observe-state`
|
|
150
|
+
里没有对应 digest 数据的 workspace 会被直接跳过,不会被写入。旧版本把
|
|
151
|
+
同一条全局 headline 广播给所有历史项目,导致无关仓库(如 ComfyUI)被污染。
|
|
152
|
+
|
|
153
|
+
需要强制指定目标时用 `--cwd <path>` 或 `--project <name>`;`--cwd` 优先。
|
|
154
|
+
|
|
135
155
|
OpenCode 会话不依赖模型自觉:`mio observe` 通过 `opencode db` 增量读取
|
|
136
156
|
`~/.local/share/opencode/opencode.db`(Windows 下该文件被 OpenCode 独占锁定,
|
|
137
157
|
必须经 opencode 自带 CLI 读取),按 `part` 表的 rowid 做增量游标,只传输
|
|
@@ -169,6 +189,8 @@ packages/mio-cli
|
|
|
169
189
|
│ ├── runtime-modules.js
|
|
170
190
|
│ ├── creativity-engine.js
|
|
171
191
|
│ ├── memory-store.js
|
|
192
|
+
│ ├── retention.js
|
|
193
|
+
│ ├── digest.js
|
|
172
194
|
│ └── mio-intelligence-mcp/index.js
|
|
173
195
|
└── package.json
|
|
174
196
|
```
|
|
@@ -178,7 +200,10 @@ query/record ranking (Latin token + CJK bigram scoring, project/global
|
|
|
178
200
|
scope layers, reuse-evidence weighting). Both the MCP server
|
|
179
201
|
(`mio.memory.query` / `mio.memory.record`) and the CLI
|
|
180
202
|
(`mio recall` / `mio remember`) go through it, so rankings are identical
|
|
181
|
-
across entry points.
|
|
203
|
+
across entry points. `server/digest.js` aggregates traces/memory/reuse
|
|
204
|
+
into actionable reports (also exposed as `mio.digest.generate`), and
|
|
205
|
+
`server/retention.js` powers `mio prune` (age/expiry trimming with
|
|
206
|
+
backups; `memory.jsonl` is never touched without explicit `--memory --yes`).
|
|
182
207
|
|
|
183
208
|
## Release verification
|
|
184
209
|
|
package/bin/mio.js
CHANGED
|
@@ -348,6 +348,13 @@ function digestCommand(args, useJson) {
|
|
|
348
348
|
// Feed the actionable lines back into each active workspace context file
|
|
349
349
|
// (AGENTS.md / CLAUDE.md MIO_CONTEXT block), so every agent passively
|
|
350
350
|
// receives the digest's value on its next session.
|
|
351
|
+
//
|
|
352
|
+
// Targets are resolved per project, never broadcast. An earlier version
|
|
353
|
+
// wrote one global headline into Object.keys(state.contexts) -- every
|
|
354
|
+
// workspace ever observed -- so unrelated repos (ComfyUI, douyin_store)
|
|
355
|
+
// received the same line and their real history was pushed out of the
|
|
356
|
+
// six-slot block. Write only where the digest actually has data for that
|
|
357
|
+
// project, or where the caller explicitly asked with --cwd/--project.
|
|
351
358
|
const stateFile = path.join(MIO_HOME, 'observe-state.json')
|
|
352
359
|
let state = {}
|
|
353
360
|
try {
|
|
@@ -355,27 +362,50 @@ function digestCommand(args, useJson) {
|
|
|
355
362
|
} catch (_) {}
|
|
356
363
|
state.contexts = state.contexts || {}
|
|
357
364
|
const stamp = new Date().toLocaleString('zh-CN', { hour12: false })
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
365
|
+
|
|
366
|
+
// Project name -> per-project digest line.
|
|
367
|
+
const projectLine = new Map()
|
|
368
|
+
for (const entry of report.projects || []) {
|
|
369
|
+
if (!entry || !entry.project) continue
|
|
370
|
+
const rate = entry.tasks > 0 ? Math.round(((entry.success || 0) / entry.tasks) * 100) : 0
|
|
371
|
+
projectLine.set(String(entry.project).toLowerCase(), `digest(${days}d): ${entry.project} ${entry.tasks} 任务 ${rate}% 成功`)
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const explicitCwd = optionValue(flags, '--cwd')
|
|
375
|
+
const explicitProject = optionValue(flags, '--project')
|
|
376
|
+
let targetCwds = []
|
|
377
|
+
if (explicitCwd) {
|
|
378
|
+
targetCwds = [explicitCwd]
|
|
379
|
+
} else if (explicitProject) {
|
|
380
|
+
const wanted = String(explicitProject).toLowerCase()
|
|
381
|
+
targetCwds = Object.keys(state.contexts).filter(
|
|
382
|
+
(cwd) => path.basename(cwd).toLowerCase() === wanted,
|
|
383
|
+
)
|
|
384
|
+
} else {
|
|
385
|
+
// No explicit target: only write to workspaces this digest has data for.
|
|
386
|
+
// A workspace with no matching project gets nothing.
|
|
387
|
+
targetCwds = Object.keys(state.contexts).filter((cwd) =>
|
|
388
|
+
projectLine.has(path.basename(cwd).toLowerCase()),
|
|
389
|
+
)
|
|
390
|
+
}
|
|
391
|
+
|
|
368
392
|
let written = 0
|
|
369
393
|
for (const cwd of targetCwds) {
|
|
394
|
+
const line = projectLine.get(path.basename(cwd).toLowerCase())
|
|
395
|
+
if (!line) continue
|
|
370
396
|
try {
|
|
371
|
-
observer.updateProjectContext(state, cwd, stamp + ' | ' +
|
|
372
|
-
observer.updateProjectContext(state, cwd, stamp + ' | ' +
|
|
397
|
+
observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'CLAUDE.md')
|
|
398
|
+
observer.updateProjectContext(state, cwd, stamp + ' | ' + line, 'AGENTS.md')
|
|
373
399
|
written += 1
|
|
374
400
|
} catch (_) {}
|
|
375
401
|
}
|
|
376
402
|
fs.mkdirSync(MIO_HOME, { recursive: true })
|
|
377
403
|
fs.writeFileSync(stateFile, JSON.stringify(state) + '\n', 'utf8')
|
|
378
|
-
report.writeBack = {
|
|
404
|
+
report.writeBack = {
|
|
405
|
+
workspaces: written,
|
|
406
|
+
skipped: Object.keys(state.contexts).length - written,
|
|
407
|
+
projects: [...projectLine.values()],
|
|
408
|
+
}
|
|
379
409
|
}
|
|
380
410
|
|
|
381
411
|
// Persist the report so it becomes part of the data asset itself.
|
|
@@ -388,7 +418,10 @@ function digestCommand(args, useJson) {
|
|
|
388
418
|
console.log(report.markdown)
|
|
389
419
|
console.log(`Report saved: ${reportFile}`)
|
|
390
420
|
if (report.writeBack) {
|
|
391
|
-
console.log(
|
|
421
|
+
console.log(
|
|
422
|
+
`Write-back: ${report.writeBack.workspaces} workspace(s), ${report.writeBack.skipped} skipped (no digest data)`,
|
|
423
|
+
)
|
|
424
|
+
for (const line of report.writeBack.projects) console.log(` - ${line}`)
|
|
392
425
|
}
|
|
393
426
|
}
|
|
394
427
|
|
package/observe/observer.js
CHANGED
|
@@ -65,6 +65,30 @@ function stateFile(home) {
|
|
|
65
65
|
return path.join(home, 'observe-state.json')
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
// Drop repeated lines from stored context lists, preserving the most recent
|
|
69
|
+
// occurrence order. Older builds could write the same line into every slot of
|
|
70
|
+
// a workspace block (a generated digest headline broadcast to all projects),
|
|
71
|
+
// so existing observe-state files still carry duplicates until they are
|
|
72
|
+
// rewritten. Normalising on load heals that data without a migration step.
|
|
73
|
+
function dedupeContexts(contexts) {
|
|
74
|
+
const out = {}
|
|
75
|
+
for (const [cwd, list] of Object.entries(contexts)) {
|
|
76
|
+
if (!Array.isArray(list)) {
|
|
77
|
+
out[cwd] = []
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
const seen = new Set()
|
|
81
|
+
const deduped = []
|
|
82
|
+
for (const line of list) {
|
|
83
|
+
if (seen.has(line)) continue
|
|
84
|
+
seen.add(line)
|
|
85
|
+
deduped.push(line)
|
|
86
|
+
}
|
|
87
|
+
out[cwd] = deduped
|
|
88
|
+
}
|
|
89
|
+
return out
|
|
90
|
+
}
|
|
91
|
+
|
|
68
92
|
function loadState(home) {
|
|
69
93
|
try {
|
|
70
94
|
const parsed = JSON.parse(fs.readFileSync(stateFile(home), 'utf8'))
|
|
@@ -74,7 +98,7 @@ function loadState(home) {
|
|
|
74
98
|
files: parsed.files || {},
|
|
75
99
|
buffers: parsed.buffers || {},
|
|
76
100
|
openTurns: parsed.openTurns || {},
|
|
77
|
-
contexts: parsed.contexts || {},
|
|
101
|
+
contexts: dedupeContexts(parsed.contexts || {}),
|
|
78
102
|
codexFiles: parsed.codexFiles || {},
|
|
79
103
|
codexBuffers: parsed.codexBuffers || {},
|
|
80
104
|
codexTurns: parsed.codexTurns || {},
|
|
@@ -291,7 +315,20 @@ const CONTEXT_MAX = 6
|
|
|
291
315
|
function updateProjectContext(state, cwd, line, targetFile) {
|
|
292
316
|
if (!cwd || !line) return
|
|
293
317
|
const target = path.join(cwd, targetFile || 'AGENTS.md')
|
|
294
|
-
|
|
318
|
+
// Normalise the whole list before inserting, not just the incoming line.
|
|
319
|
+
// Callers may load state without going through loadState (the digest
|
|
320
|
+
// write-back reads the file directly), so stale duplicates have to be
|
|
321
|
+
// cleared here or they keep consuming slots and push real history out.
|
|
322
|
+
const list = []
|
|
323
|
+
const seen = new Set()
|
|
324
|
+
for (const item of state.contexts[cwd] || []) {
|
|
325
|
+
if (seen.has(item)) continue
|
|
326
|
+
seen.add(item)
|
|
327
|
+
list.push(item)
|
|
328
|
+
}
|
|
329
|
+
// Re-inserting an existing line moves it to the front instead of duplicating.
|
|
330
|
+
const existing = list.indexOf(line)
|
|
331
|
+
if (existing !== -1) list.splice(existing, 1)
|
|
295
332
|
list.unshift(line)
|
|
296
333
|
if (list.length > CONTEXT_MAX) list.pop()
|
|
297
334
|
state.contexts[cwd] = list
|
|
@@ -1591,6 +1628,7 @@ module.exports = {
|
|
|
1591
1628
|
workbuddyConfigDir,
|
|
1592
1629
|
loadState,
|
|
1593
1630
|
saveState,
|
|
1631
|
+
dedupeContexts,
|
|
1594
1632
|
updateProjectContext,
|
|
1595
1633
|
runCycle,
|
|
1596
1634
|
opencodeDbPath,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.23",
|
|
4
4
|
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
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 adapters/claude-code.js && node --check server/runtime-modules.js && node --check server/memory-store.js && node --check server/retention.js && node --check server/digest.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
|
+
"test": "node --test \"__tests__/*.test.js\" \"server/mio-intelligence-mcp/__tests__/*.test.js\"",
|
|
21
22
|
"prepack": "npm run check",
|
|
22
23
|
"verify:pack-install": "node scripts/verify-packed-runtime.js"
|
|
23
24
|
},
|
|
@@ -34,5 +35,11 @@
|
|
|
34
35
|
"@akemi-mio/evolution-scheduler": "^0.1.0",
|
|
35
36
|
"@akemi-mio/observer": "^0.1.0",
|
|
36
37
|
"@akemi-mio/insight": "^0.1.0"
|
|
37
|
-
}
|
|
38
|
+
},
|
|
39
|
+
"main": "index.js",
|
|
40
|
+
"directories": {
|
|
41
|
+
"doc": "docs"
|
|
42
|
+
},
|
|
43
|
+
"keywords": [],
|
|
44
|
+
"author": ""
|
|
38
45
|
}
|
|
@@ -76,12 +76,13 @@ function npmCommand(platform = process.platform) {
|
|
|
76
76
|
|
|
77
77
|
function npmInvocation(args, env = process.env, platform = process.platform, node = process.execPath) {
|
|
78
78
|
if (env.npm_execpath) {
|
|
79
|
-
return { command: node, args: [env.npm_execpath, ...args] }
|
|
79
|
+
return { command: node, args: [env.npm_execpath, ...args], shell: false }
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
// Fall back to the npm shim on PATH instead of routing through cmd.exe.
|
|
82
|
+
// cmd.exe is unavailable in some sandboxes, and a bare .cmd/.bat shim cannot
|
|
83
|
+
// be spawned directly (Node requires shell:true for those on Windows), so
|
|
84
|
+
// signal the shell requirement to the runner instead of guessing there.
|
|
85
|
+
return { command: npmCommand(platform), args: [...args], shell: platform === 'win32' }
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
function packageDirName(name) {
|
|
@@ -95,9 +96,12 @@ function tarballFileName(name, version) {
|
|
|
95
96
|
|
|
96
97
|
function readPackageJson(workspaceRoot, name) {
|
|
97
98
|
const packageDir = path.join(workspaceRoot, 'packages', packageDirName(name))
|
|
99
|
+
// Windows editors may write package.json with a UTF-8 BOM; JSON.parse
|
|
100
|
+
// rejects the leading U+FEFF, so strip it before parsing.
|
|
101
|
+
const raw = fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8').replace(/^\uFEFF/, '')
|
|
98
102
|
return {
|
|
99
103
|
packageDir,
|
|
100
|
-
manifest: JSON.parse(
|
|
104
|
+
manifest: JSON.parse(raw),
|
|
101
105
|
}
|
|
102
106
|
}
|
|
103
107
|
|
|
@@ -211,6 +215,11 @@ function run(command, args, options = {}) {
|
|
|
211
215
|
env: options.env || process.env,
|
|
212
216
|
encoding: 'utf8',
|
|
213
217
|
stdio: options.stdio || ['ignore', 'pipe', 'pipe'],
|
|
218
|
+
// Windows .cmd/.bat shims (npm.cmd) cannot be spawned directly by
|
|
219
|
+
// execFileSync since Node's CVE-2024-27980 hardening; they need a shell.
|
|
220
|
+
// This only kicks in for the npm shim path — node-based invocations pass
|
|
221
|
+
// an absolute node executable and still run shell-free.
|
|
222
|
+
shell: options.shell === true,
|
|
214
223
|
})
|
|
215
224
|
return output ? output.trim() : ''
|
|
216
225
|
}
|
|
@@ -296,14 +305,19 @@ function verifyPackedRuntimeInstall(input = {}) {
|
|
|
296
305
|
run(pack.command, pack.args, {
|
|
297
306
|
cwd: pkg.packageDir,
|
|
298
307
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
308
|
+
shell: pack.shell,
|
|
299
309
|
})
|
|
300
310
|
packageArtifactEvidence(pkg)
|
|
301
311
|
}
|
|
302
312
|
|
|
303
313
|
const init = npmInvocation(['init', '-y'])
|
|
304
|
-
run(init.command, init.args, { cwd: plan.installDir, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
314
|
+
run(init.command, init.args, { cwd: plan.installDir, stdio: ['ignore', 'pipe', 'pipe'], shell: init.shell })
|
|
305
315
|
const install = npmInvocation(plan.install.args)
|
|
306
|
-
run(install.command, install.args, {
|
|
316
|
+
run(install.command, install.args, {
|
|
317
|
+
cwd: plan.install.cwd,
|
|
318
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
319
|
+
shell: install.shell,
|
|
320
|
+
})
|
|
307
321
|
const smokeEnv = {
|
|
308
322
|
...process.env,
|
|
309
323
|
MIO_HOME: path.join(plan.installDir, '.mio-home'),
|
|
@@ -360,6 +374,7 @@ module.exports = {
|
|
|
360
374
|
createVerifyPackedRuntimeOptions,
|
|
361
375
|
createPackedRuntimeInstallPlan,
|
|
362
376
|
packageArtifactEvidence,
|
|
377
|
+
readPackageJson,
|
|
363
378
|
npmInvocation,
|
|
364
379
|
npmCommand,
|
|
365
380
|
parseVerifyPackedRuntimeArgs,
|
package/server/digest.js
CHANGED
|
@@ -186,6 +186,18 @@ function createDigest(options = {}) {
|
|
|
186
186
|
suggestions,
|
|
187
187
|
}
|
|
188
188
|
report.markdown = renderMarkdown(report)
|
|
189
|
+
|
|
190
|
+
// Persist a machine-readable snapshot for consumers that should not
|
|
191
|
+
// recompute the aggregation — notably mio.task.route's agent-health signal.
|
|
192
|
+
try {
|
|
193
|
+
const digestDir = path.join(home, 'digest')
|
|
194
|
+
fs.mkdirSync(digestDir, { recursive: true })
|
|
195
|
+
const snapshot = { ...report }
|
|
196
|
+
delete snapshot.markdown
|
|
197
|
+
fs.writeFileSync(path.join(digestDir, 'latest.json'), JSON.stringify(snapshot) + '\n', 'utf8')
|
|
198
|
+
} catch (_) {
|
|
199
|
+
// best-effort persistence; the in-memory report is still returned
|
|
200
|
+
}
|
|
189
201
|
return report
|
|
190
202
|
}
|
|
191
203
|
|
|
@@ -1108,6 +1108,40 @@ function reportAgent(args = {}) {
|
|
|
1108
1108
|
return { project, count: reports.length, reports }
|
|
1109
1109
|
}
|
|
1110
1110
|
|
|
1111
|
+
const AGENT_HEALTH_FRESH_MS = 48 * 3600000
|
|
1112
|
+
|
|
1113
|
+
function readAgentHealth() {
|
|
1114
|
+
const file = path.join(dataDir, 'digest', 'latest.json')
|
|
1115
|
+
if (!fs.existsSync(file)) return null
|
|
1116
|
+
try {
|
|
1117
|
+
const snapshot = JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
1118
|
+
const ts = Date.parse(snapshot.generatedAt || '')
|
|
1119
|
+
const generatedAt = Number.isFinite(ts) ? snapshot.generatedAt : null
|
|
1120
|
+
if (!Number.isFinite(ts) || Date.now() - ts > AGENT_HEALTH_FRESH_MS) {
|
|
1121
|
+
return { stale: true, generatedAt, best: null, degraded: [], agents: [] }
|
|
1122
|
+
}
|
|
1123
|
+
const agents = Array.isArray(snapshot.agents) ? snapshot.agents : []
|
|
1124
|
+
const reliable = agents.filter((a) => Number(a.total) >= 5)
|
|
1125
|
+
const best =
|
|
1126
|
+
reliable.length > 0
|
|
1127
|
+
? reliable.reduce((a, b) => (b.successRate > a.successRate ? b : a))
|
|
1128
|
+
: null
|
|
1129
|
+
const degraded = reliable
|
|
1130
|
+
.filter((a) => a.successRate < 50)
|
|
1131
|
+
.map((a) => ({ agent: a.agent, successRate: a.successRate, total: a.total }))
|
|
1132
|
+
return {
|
|
1133
|
+
stale: false,
|
|
1134
|
+
generatedAt,
|
|
1135
|
+
periodDays: snapshot.periodDays || null,
|
|
1136
|
+
best: best ? { agent: best.agent, successRate: best.successRate, total: best.total } : null,
|
|
1137
|
+
degraded,
|
|
1138
|
+
agents: agents.map((a) => ({ agent: a.agent, total: a.total, successRate: a.successRate })),
|
|
1139
|
+
}
|
|
1140
|
+
} catch (_) {
|
|
1141
|
+
return null
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1111
1145
|
function routeTask(args = {}) {
|
|
1112
1146
|
const task = String(args.task || '').trim()
|
|
1113
1147
|
if (!task) throw new Error('task.route requires a non-empty task')
|
|
@@ -1223,6 +1257,26 @@ function routeTask(args = {}) {
|
|
|
1223
1257
|
'No matching experience or memory found. Proceed with care and record the outcome to grow the experience base.'
|
|
1224
1258
|
}
|
|
1225
1259
|
|
|
1260
|
+
// Agent-health routing signal from the latest digest snapshot (value loop:
|
|
1261
|
+
// collected task outcomes influence who should execute the next task).
|
|
1262
|
+
const agentHealth = readAgentHealth()
|
|
1263
|
+
let routingSignal = null
|
|
1264
|
+
if (agentHealth && !agentHealth.stale && agentHealth.best) {
|
|
1265
|
+
const parts = []
|
|
1266
|
+
if (agentHealth.best.successRate >= 80) {
|
|
1267
|
+
parts.push(
|
|
1268
|
+
`prefer ${agentHealth.best.agent} (${agentHealth.best.successRate}% success over ${agentHealth.best.total} tasks)`,
|
|
1269
|
+
)
|
|
1270
|
+
}
|
|
1271
|
+
for (const bad of agentHealth.degraded) {
|
|
1272
|
+
parts.push(`avoid ${bad.agent} (${bad.successRate}% success over ${bad.total} tasks)`)
|
|
1273
|
+
}
|
|
1274
|
+
if (parts.length > 0) {
|
|
1275
|
+
routingSignal = `Agent health (last ${agentHealth.periodDays || '?'}d): ${parts.join('; ')}.`
|
|
1276
|
+
suggestion = `${suggestion} ${routingSignal}`
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1226
1280
|
return {
|
|
1227
1281
|
task,
|
|
1228
1282
|
project,
|
|
@@ -1230,9 +1284,11 @@ function routeTask(args = {}) {
|
|
|
1230
1284
|
count: routes.length,
|
|
1231
1285
|
routes,
|
|
1232
1286
|
relatedMemories,
|
|
1287
|
+
agentHealth,
|
|
1233
1288
|
summary: {
|
|
1234
1289
|
verifiedRoutes: routes.length,
|
|
1235
1290
|
relatedMemories: relatedMemories.length,
|
|
1291
|
+
routingSignal,
|
|
1236
1292
|
suggestion,
|
|
1237
1293
|
},
|
|
1238
1294
|
}
|
|
@@ -1468,7 +1524,7 @@ const TOOLS = [
|
|
|
1468
1524
|
},
|
|
1469
1525
|
{
|
|
1470
1526
|
name: 'mio.task.route',
|
|
1471
|
-
description: 'Route an incoming task to the most relevant verified experiences and related memories,
|
|
1527
|
+
description: 'Route an incoming task to the most relevant verified experiences and related memories, and include an agent-health signal (from the latest digest) saying which agents to prefer or avoid. Turns recall plus observed performance into decision influence (P1).',
|
|
1472
1528
|
inputSchema: {
|
|
1473
1529
|
type: 'object',
|
|
1474
1530
|
properties: {
|