mio-agent-runtime 0.5.16 → 0.5.22
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 +163 -107
- package/adapters/claude-code.js +141 -0
- package/bin/mio.js +493 -245
- package/observe/observer.js +286 -2
- package/package.json +26 -24
- package/server/creativity-engine.js +397 -0
- package/server/digest.js +266 -0
- package/server/host-capabilities.js +5 -0
- package/server/memory-store.js +376 -0
- package/server/mio-intelligence-mcp/README.md +2 -2
- package/server/mio-intelligence-mcp/index.js +2325 -1966
- package/server/retention.js +150 -0
package/observe/observer.js
CHANGED
|
@@ -86,6 +86,9 @@ function loadState(home) {
|
|
|
86
86
|
hmSeeded: parsed.hmSeeded || false,
|
|
87
87
|
hmLastMsgId: parsed.hmLastMsgId || 0,
|
|
88
88
|
hmTurns: parsed.hmTurns || {},
|
|
89
|
+
clFiles: parsed.clFiles || {},
|
|
90
|
+
clBuffers: parsed.clBuffers || {},
|
|
91
|
+
clTurns: parsed.clTurns || {},
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
} catch (_) {}
|
|
@@ -106,6 +109,9 @@ function loadState(home) {
|
|
|
106
109
|
hmSeeded: false,
|
|
107
110
|
hmLastMsgId: 0,
|
|
108
111
|
hmTurns: {},
|
|
112
|
+
clFiles: {},
|
|
113
|
+
clBuffers: {},
|
|
114
|
+
clTurns: {},
|
|
109
115
|
}
|
|
110
116
|
}
|
|
111
117
|
|
|
@@ -282,9 +288,9 @@ const CONTEXT_MAX = 6
|
|
|
282
288
|
// Maintain a compact 'recent Mio context' block inside <workspace>/AGENTS.md,
|
|
283
289
|
// which WorkBuddy's MemoryLoader auto-injects into every session. This makes
|
|
284
290
|
// retrieval guaranteed (not dependent on the model deciding to read memory).
|
|
285
|
-
function updateProjectContext(state, cwd, line) {
|
|
291
|
+
function updateProjectContext(state, cwd, line, targetFile) {
|
|
286
292
|
if (!cwd || !line) return
|
|
287
|
-
const target = path.join(cwd, 'AGENTS.md')
|
|
293
|
+
const target = path.join(cwd, targetFile || 'AGENTS.md')
|
|
288
294
|
const list = state.contexts[cwd] || []
|
|
289
295
|
list.unshift(line)
|
|
290
296
|
if (list.length > CONTEXT_MAX) list.pop()
|
|
@@ -1133,6 +1139,275 @@ function runHermesCycle(home, state, sinks, opts) {
|
|
|
1133
1139
|
}
|
|
1134
1140
|
|
|
1135
1141
|
|
|
1142
|
+
// ---------------------------------------------------------------------------
|
|
1143
|
+
// Claude Code session observation (passive, transcript JSONL tailing)
|
|
1144
|
+
// ---------------------------------------------------------------------------
|
|
1145
|
+
|
|
1146
|
+
// Claude Code writes append-only JSONL transcripts under
|
|
1147
|
+
// <configDir>/projects/<munged-cwd>/<sessionId>.jsonl. Each line carries
|
|
1148
|
+
// {type, message:{role, content}, cwd, sessionId, timestamp, uuid, ...}.
|
|
1149
|
+
// Assistant content items are text/thinking/tool_use; tool results come back
|
|
1150
|
+
// as user lines whose content array holds tool_result items with is_error.
|
|
1151
|
+
// A user prompt line (string content or a text item) closes the previous
|
|
1152
|
+
// turn; sidechain lines (subagent transcripts) are skipped as noise.
|
|
1153
|
+
|
|
1154
|
+
function claudeConfigDir() {
|
|
1155
|
+
return process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude')
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
function claudeProjectsDir() {
|
|
1159
|
+
return path.join(claudeConfigDir(), 'projects')
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
function listClaudeTranscripts() {
|
|
1163
|
+
const root = claudeProjectsDir()
|
|
1164
|
+
const out = []
|
|
1165
|
+
if (!fs.existsSync(root)) return out
|
|
1166
|
+
const walk = (dir) => {
|
|
1167
|
+
let entries
|
|
1168
|
+
try {
|
|
1169
|
+
entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
1170
|
+
} catch (_) {
|
|
1171
|
+
return
|
|
1172
|
+
}
|
|
1173
|
+
for (const entry of entries) {
|
|
1174
|
+
const full = path.join(dir, entry.name)
|
|
1175
|
+
if (entry.isDirectory()) walk(full)
|
|
1176
|
+
else if (entry.isFile() && entry.name.endsWith('.jsonl')) out.push(full)
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
walk(root)
|
|
1180
|
+
return out
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
function createClaudeTurn(meta) {
|
|
1184
|
+
meta = meta || {}
|
|
1185
|
+
return {
|
|
1186
|
+
id: null,
|
|
1187
|
+
ts: null,
|
|
1188
|
+
sessionId: meta.sessionId || null,
|
|
1189
|
+
cwd: meta.cwd || null,
|
|
1190
|
+
project: meta.cwd ? path.basename(meta.cwd) : 'unknown',
|
|
1191
|
+
lastText: null,
|
|
1192
|
+
toolCount: 0,
|
|
1193
|
+
errors: [],
|
|
1194
|
+
apiError: false,
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
function claudeContentItems(obj) {
|
|
1199
|
+
const content = obj && obj.message && obj.message.content
|
|
1200
|
+
if (typeof content === 'string') return [{ type: 'text', text: content }]
|
|
1201
|
+
return Array.isArray(content) ? content.filter(Boolean) : []
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
function finishClaudeTurn(turn, outcome) {
|
|
1205
|
+
return {
|
|
1206
|
+
id: turn.id,
|
|
1207
|
+
ts: turn.ts,
|
|
1208
|
+
sessionId: turn.sessionId,
|
|
1209
|
+
cwd: turn.cwd,
|
|
1210
|
+
project: turn.project,
|
|
1211
|
+
lastText: turn.lastText,
|
|
1212
|
+
toolCount: turn.toolCount,
|
|
1213
|
+
errors: turn.errors.slice(),
|
|
1214
|
+
apiError: turn.apiError,
|
|
1215
|
+
outcome: outcome,
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
function feedClaudeLine(turn, obj) {
|
|
1220
|
+
if (!obj || typeof obj !== 'object') return { next: turn }
|
|
1221
|
+
// Subagent (sidechain) transcripts duplicate main-thread work; skip them.
|
|
1222
|
+
if (obj.isSidechain === true) return { next: turn }
|
|
1223
|
+
if (obj.sessionId) turn.sessionId = obj.sessionId
|
|
1224
|
+
if (obj.cwd) {
|
|
1225
|
+
turn.cwd = obj.cwd
|
|
1226
|
+
turn.project = path.basename(obj.cwd)
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
if (obj.type === 'user') {
|
|
1230
|
+
const items = claudeContentItems(obj)
|
|
1231
|
+
let hasToolError = false
|
|
1232
|
+
for (const item of items) {
|
|
1233
|
+
if (item.type === 'tool_result' && item.is_error) {
|
|
1234
|
+
turn.errors.push({ toolUseId: item.tool_use_id || 'unknown' })
|
|
1235
|
+
hasToolError = true
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
if (turn.errors.length > 20) turn.errors.splice(0, turn.errors.length - 20)
|
|
1239
|
+
|
|
1240
|
+
// tool_result-only user lines continue the current turn; a string content
|
|
1241
|
+
// or text item means a fresh prompt, which closes the previous turn.
|
|
1242
|
+
const isPrompt =
|
|
1243
|
+
typeof (obj.message && obj.message.content) === 'string' ||
|
|
1244
|
+
items.some((item) => item.type === 'text')
|
|
1245
|
+
if (isPrompt) {
|
|
1246
|
+
let finalized = null
|
|
1247
|
+
if (turn.id) {
|
|
1248
|
+
finalized = finishClaudeTurn(turn, turn.apiError ? 'failure' : turn.lastText ? 'success' : 'aborted')
|
|
1249
|
+
}
|
|
1250
|
+
const next = createClaudeTurn({ sessionId: turn.sessionId, cwd: turn.cwd })
|
|
1251
|
+
next.id = obj.uuid || null
|
|
1252
|
+
next.ts = obj.timestamp ? Date.parse(obj.timestamp) || Date.now() : Date.now()
|
|
1253
|
+
return { finalized, next }
|
|
1254
|
+
}
|
|
1255
|
+
return { next: turn }
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
if (obj.type === 'assistant') {
|
|
1259
|
+
for (const item of claudeContentItems(obj)) {
|
|
1260
|
+
if (item.type === 'text' && typeof item.text === 'string' && item.text.trim()) {
|
|
1261
|
+
turn.lastText = item.text
|
|
1262
|
+
} else if (item.type === 'tool_use') {
|
|
1263
|
+
turn.toolCount += 1
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (obj.isApiErrorMessage === true || obj.error) turn.apiError = true
|
|
1267
|
+
const ts = obj.timestamp ? Date.parse(obj.timestamp) : NaN
|
|
1268
|
+
if (Number.isFinite(ts)) turn.ts = ts
|
|
1269
|
+
return { next: turn }
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
return { next: turn }
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
function emitClaudeOutcome(turn, sinks, state) {
|
|
1276
|
+
if (!turn.id) return false
|
|
1277
|
+
const summary = cleanText(turn.lastText)
|
|
1278
|
+
if (!summary && !turn.apiError) return false
|
|
1279
|
+
const outcome = turn.apiError && turn.outcome !== 'failure' ? 'failure' : turn.outcome
|
|
1280
|
+
const turnToken = String(turn.id).slice(0, 8)
|
|
1281
|
+
const tsToken = turn.ts ? Math.floor(turn.ts / 1000) : Math.floor(Date.now() / 1000)
|
|
1282
|
+
|
|
1283
|
+
sinks.trace({
|
|
1284
|
+
trace_id: 'claude:' + (turn.sessionId || 'unknown') + ':' + turnToken + ':' + tsToken,
|
|
1285
|
+
event_type: 'task_outcome',
|
|
1286
|
+
outcome: outcome,
|
|
1287
|
+
payload: {
|
|
1288
|
+
host: 'claude-code',
|
|
1289
|
+
project: turn.project,
|
|
1290
|
+
sessionId: turn.sessionId,
|
|
1291
|
+
turnId: turn.id,
|
|
1292
|
+
toolCalls: turn.toolCount,
|
|
1293
|
+
errors: turn.errors.length,
|
|
1294
|
+
summary: summary.slice(0, 300),
|
|
1295
|
+
source: 'claude-observer',
|
|
1296
|
+
},
|
|
1297
|
+
agent: 'claude-code',
|
|
1298
|
+
host: 'claude-code',
|
|
1299
|
+
project: turn.project,
|
|
1300
|
+
})
|
|
1301
|
+
|
|
1302
|
+
if (outcome === 'success' && summary.length >= 10) {
|
|
1303
|
+
sinks.memory({
|
|
1304
|
+
kind: 'note',
|
|
1305
|
+
content: '[claude-code] 任务完成: ' + summary.slice(0, 160),
|
|
1306
|
+
tags: ['task-outcome', 'claude-code', turn.project],
|
|
1307
|
+
project: turn.project,
|
|
1308
|
+
source: 'claude-observer',
|
|
1309
|
+
})
|
|
1310
|
+
// Claude Code auto-loads <workspace>/CLAUDE.md, so write the context
|
|
1311
|
+
// block there instead of AGENTS.md.
|
|
1312
|
+
if (turn.cwd) {
|
|
1313
|
+
var ctxTime = new Date(Number(turn.ts) || Date.now()).toLocaleString('zh-CN', { hour12: false })
|
|
1314
|
+
updateProjectContext(state, turn.cwd, ctxTime + ' | ' + outcome + ' | ' + summary.slice(0, 80), 'CLAUDE.md')
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
for (const err of turn.errors) {
|
|
1319
|
+
sinks.trace({
|
|
1320
|
+
trace_id: 'claude:' + (turn.sessionId || 'unknown') + ':' + turnToken + ':' + String(err.toolUseId || 'err').slice(0, 12),
|
|
1321
|
+
event_type: 'error',
|
|
1322
|
+
outcome: 'error',
|
|
1323
|
+
payload: {
|
|
1324
|
+
host: 'claude-code',
|
|
1325
|
+
project: turn.project,
|
|
1326
|
+
sessionId: turn.sessionId,
|
|
1327
|
+
turnId: turn.id,
|
|
1328
|
+
toolUseId: err.toolUseId,
|
|
1329
|
+
},
|
|
1330
|
+
agent: 'claude-code',
|
|
1331
|
+
host: 'claude-code',
|
|
1332
|
+
project: turn.project,
|
|
1333
|
+
})
|
|
1334
|
+
}
|
|
1335
|
+
return true
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
function runClaudeCycle(home, state, sinks, opts) {
|
|
1339
|
+
const out = { files: 0, lines: 0, outcomes: 0 }
|
|
1340
|
+
const files = listClaudeTranscripts()
|
|
1341
|
+
const now = Date.now()
|
|
1342
|
+
const maxAgeMs = (opts && opts.maxAgeMs) || 24 * 60 * 60 * 1000
|
|
1343
|
+
|
|
1344
|
+
for (const file of files) {
|
|
1345
|
+
out.files += 1
|
|
1346
|
+
let st
|
|
1347
|
+
try {
|
|
1348
|
+
st = fs.statSync(file)
|
|
1349
|
+
} catch (_) {
|
|
1350
|
+
continue
|
|
1351
|
+
}
|
|
1352
|
+
const info = state.clFiles[file] || { size: 0 }
|
|
1353
|
+
if (!state.clFiles[file] && now - st.mtimeMs > maxAgeMs) continue
|
|
1354
|
+
const size = st.size
|
|
1355
|
+
if (size < info.size) {
|
|
1356
|
+
info.size = 0
|
|
1357
|
+
state.clBuffers[file] = ''
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
let turn = state.clTurns[file] || createClaudeTurn()
|
|
1361
|
+
|
|
1362
|
+
if (size > info.size) {
|
|
1363
|
+
let chunk = ''
|
|
1364
|
+
try {
|
|
1365
|
+
const fd = fs.openSync(file, 'r')
|
|
1366
|
+
const buf = Buffer.alloc(size - info.size)
|
|
1367
|
+
fs.readSync(fd, buf, 0, buf.length, info.size)
|
|
1368
|
+
fs.closeSync(fd)
|
|
1369
|
+
chunk = buf.toString('utf8')
|
|
1370
|
+
} catch (_) {
|
|
1371
|
+
continue
|
|
1372
|
+
}
|
|
1373
|
+
const pending = (state.clBuffers[file] || '') + chunk
|
|
1374
|
+
const lines = pending.split(/\r?\n/)
|
|
1375
|
+
state.clBuffers[file] = lines.pop()
|
|
1376
|
+
for (const line of lines) {
|
|
1377
|
+
if (!line.trim()) continue
|
|
1378
|
+
out.lines += 1
|
|
1379
|
+
let obj
|
|
1380
|
+
try {
|
|
1381
|
+
obj = JSON.parse(line)
|
|
1382
|
+
} catch (_) {
|
|
1383
|
+
continue
|
|
1384
|
+
}
|
|
1385
|
+
const result = feedClaudeLine(turn, obj)
|
|
1386
|
+
turn = result.next
|
|
1387
|
+
if (result.finalized) {
|
|
1388
|
+
if (emitClaudeOutcome(result.finalized, sinks, state)) out.outcomes += 1
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
state.clFiles[file] = { size: size, mtimeMs: st.mtimeMs }
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// Idle finalize: the session has answered but no new prompt arrived yet.
|
|
1395
|
+
if (turn.id && turn.lastText && now - st.mtimeMs > IDLE_MS) {
|
|
1396
|
+
if (emitClaudeOutcome(finishClaudeTurn(turn, turn.apiError ? 'failure' : 'success'), sinks, state)) {
|
|
1397
|
+
out.outcomes += 1
|
|
1398
|
+
}
|
|
1399
|
+
state.clTurns[file] = null
|
|
1400
|
+
} else if (turn.id) {
|
|
1401
|
+
state.clTurns[file] = turn
|
|
1402
|
+
} else {
|
|
1403
|
+
state.clTurns[file] = null
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
return out
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
|
|
1136
1411
|
// ---------------------------------------------------------------------------
|
|
1137
1412
|
// cycle
|
|
1138
1413
|
// ---------------------------------------------------------------------------
|
|
@@ -1225,6 +1500,11 @@ function runCycle(home, state, sinks, opts) {
|
|
|
1225
1500
|
out.lines += hmRes.lines
|
|
1226
1501
|
out.outcomes += hmRes.outcomes
|
|
1227
1502
|
|
|
1503
|
+
const clRes = runClaudeCycle(home, state, sinks, opts)
|
|
1504
|
+
out.files += clRes.files
|
|
1505
|
+
out.lines += clRes.lines
|
|
1506
|
+
out.outcomes += clRes.outcomes
|
|
1507
|
+
|
|
1228
1508
|
saveState(home, state)
|
|
1229
1509
|
return out
|
|
1230
1510
|
}
|
|
@@ -1317,6 +1597,10 @@ module.exports = {
|
|
|
1317
1597
|
runOpencodeCycle,
|
|
1318
1598
|
runHermesCycle,
|
|
1319
1599
|
hermesDbPath,
|
|
1600
|
+
claudeProjectsDir,
|
|
1601
|
+
runClaudeCycle,
|
|
1602
|
+
createClaudeTurn,
|
|
1603
|
+
feedClaudeLine,
|
|
1320
1604
|
createSinks,
|
|
1321
1605
|
startForeground,
|
|
1322
1606
|
startBackground,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.22",
|
|
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 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
|
+
"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
|
+
}
|