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
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// Retention for MIO_HOME append-only stores. traces.jsonl, queries.jsonl and
|
|
4
|
+
// experience_reuse.jsonl grow without bound; observe.log accumulates one line
|
|
5
|
+
// per observer cycle. prune() rewrites each file in place keeping only records
|
|
6
|
+
// newer than the cutoff, after copying the original to a .bak-prune-<ts> file.
|
|
7
|
+
// memory.jsonl is precious, so it is only touched when explicitly included.
|
|
8
|
+
|
|
9
|
+
const fs = require('fs')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
|
|
12
|
+
// Age-based JSONL stores (records carry an ISO `timestamp` field).
|
|
13
|
+
const AGE_BASED_FILES = ['traces.jsonl', 'experience_reuse.jsonl']
|
|
14
|
+
// Expiry-based stores (records carry an epoch-ms `expiresAt` field).
|
|
15
|
+
const EXPIRY_BASED_FILES = ['queries.jsonl']
|
|
16
|
+
// Log lines look like "[<ISO>]<message>".
|
|
17
|
+
const LOG_FILES = ['logs/observe.log']
|
|
18
|
+
|
|
19
|
+
function createRetention(options = {}) {
|
|
20
|
+
const home = options.home
|
|
21
|
+
const now = options.now || (() => Date.now())
|
|
22
|
+
|
|
23
|
+
function readJsonl(file) {
|
|
24
|
+
if (!fs.existsSync(file)) return []
|
|
25
|
+
return fs
|
|
26
|
+
.readFileSync(file, 'utf8')
|
|
27
|
+
.split(/\r?\n/)
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.map((line) => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(line)
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function backupFile(file) {
|
|
39
|
+
const backup = `${file}.bak-prune-${now()}`
|
|
40
|
+
fs.copyFileSync(file, backup)
|
|
41
|
+
return backup
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function writeJsonl(file, values) {
|
|
45
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
file,
|
|
48
|
+
values.length > 0 ? values.map((value) => JSON.stringify(value)).join('\n') + '\n' : '',
|
|
49
|
+
'utf8'
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function planAgeBased(name, cutoff) {
|
|
54
|
+
const file = path.join(home, name)
|
|
55
|
+
const records = readJsonl(file)
|
|
56
|
+
let removed = 0
|
|
57
|
+
const kept = []
|
|
58
|
+
for (const record of records) {
|
|
59
|
+
if (!record || typeof record !== 'object') {
|
|
60
|
+
removed += 1
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
const ts = Date.parse(record.timestamp || '')
|
|
64
|
+
if (Number.isFinite(ts) && ts < cutoff) removed += 1
|
|
65
|
+
else kept.push(record)
|
|
66
|
+
}
|
|
67
|
+
return { file: name, total: records.length, kept: kept.length, removed, records: kept }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function planExpiryBased(name, atTime) {
|
|
71
|
+
const file = path.join(home, name)
|
|
72
|
+
const records = readJsonl(file)
|
|
73
|
+
let removed = 0
|
|
74
|
+
const kept = []
|
|
75
|
+
for (const record of records) {
|
|
76
|
+
if (!record || typeof record !== 'object') {
|
|
77
|
+
removed += 1
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
const expiresAt = Number(record.expiresAt)
|
|
81
|
+
if (Number.isFinite(expiresAt) && expiresAt <= atTime) removed += 1
|
|
82
|
+
else kept.push(record)
|
|
83
|
+
}
|
|
84
|
+
return { file: name, total: records.length, kept: kept.length, removed, records: kept }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function planLog(name, cutoff) {
|
|
88
|
+
const file = path.join(home, name)
|
|
89
|
+
if (!fs.existsSync(file)) {
|
|
90
|
+
return { file: name, total: 0, kept: 0, removed: 0, lines: [] }
|
|
91
|
+
}
|
|
92
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean)
|
|
93
|
+
let removed = 0
|
|
94
|
+
const kept = []
|
|
95
|
+
for (const line of lines) {
|
|
96
|
+
const match = line.match(/^\[([^\]]+)\]/)
|
|
97
|
+
const ts = match ? Date.parse(match[1]) : NaN
|
|
98
|
+
if (Number.isFinite(ts) && ts < cutoff) removed += 1
|
|
99
|
+
else kept.push(line)
|
|
100
|
+
}
|
|
101
|
+
return { file: name, total: lines.length, kept: kept.length, removed, lines: kept }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function planPrune({ days, includeMemory }) {
|
|
105
|
+
const cutoff = now() - Math.max(1, Number(days) || 30) * 86400000
|
|
106
|
+
const plans = []
|
|
107
|
+
for (const name of AGE_BASED_FILES) plans.push(planAgeBased(name, cutoff))
|
|
108
|
+
for (const name of EXPIRY_BASED_FILES) plans.push(planExpiryBased(name, now()))
|
|
109
|
+
plans.push(planLog(LOG_FILES[0], cutoff))
|
|
110
|
+
if (includeMemory) plans.push(planAgeBased('memory.jsonl', cutoff))
|
|
111
|
+
return { cutoff: new Date(cutoff).toISOString(), plans }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function prune({ days, includeMemory, dryRun }) {
|
|
115
|
+
const { cutoff, plans } = planPrune({ days, includeMemory })
|
|
116
|
+
const results = []
|
|
117
|
+
if (!dryRun) {
|
|
118
|
+
for (const plan of plans) {
|
|
119
|
+
if (plan.removed === 0) continue
|
|
120
|
+
const target = path.join(home, plan.file)
|
|
121
|
+
if (!fs.existsSync(target)) continue
|
|
122
|
+
backupFile(target)
|
|
123
|
+
if (plan.lines) {
|
|
124
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
125
|
+
fs.writeFileSync(target, plan.lines.length > 0 ? plan.lines.join('\n') + '\n' : '', 'utf8')
|
|
126
|
+
} else {
|
|
127
|
+
writeJsonl(target, plan.records)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const plan of plans) {
|
|
132
|
+
results.push({
|
|
133
|
+
file: plan.file,
|
|
134
|
+
total: plan.total,
|
|
135
|
+
kept: plan.kept,
|
|
136
|
+
removed: plan.removed,
|
|
137
|
+
})
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
cutoff,
|
|
141
|
+
dryRun: Boolean(dryRun),
|
|
142
|
+
files: results,
|
|
143
|
+
totalRemoved: results.reduce((sum, item) => sum + item.removed, 0),
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { planPrune, prune }
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { createRetention, AGE_BASED_FILES, EXPIRY_BASED_FILES }
|