mio-agent-runtime 0.5.17 → 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 +188 -107
- package/adapters/claude-code.js +141 -0
- package/bin/mio.js +526 -245
- package/observe/observer.js +326 -4
- package/package.json +10 -3
- package/scripts/verify-packed-runtime.js +23 -8
- package/server/digest.js +278 -0
- package/server/host-capabilities.js +5 -0
- package/server/memory-store.js +376 -0
- package/server/mio-intelligence-mcp/index.js +2383 -2471
- package/server/retention.js +150 -0
|
@@ -1,1677 +1,1585 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
'use strict'
|
|
3
|
-
|
|
4
|
-
const fs = require('fs')
|
|
5
|
-
const path = require('path')
|
|
6
|
-
const crypto = require('crypto')
|
|
7
|
-
const readline = require('readline')
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const crypto = require('crypto')
|
|
7
|
+
const readline = require('readline')
|
|
8
8
|
const { execFileSync } = require('child_process')
|
|
9
9
|
const { getEvolutionStatus } = require('../runtime-modules.js')
|
|
10
10
|
const { listHostCapabilities } = require('../host-capabilities.js')
|
|
11
11
|
const { createEvolutionCutoverTools } = require('../evolution-cutover.js')
|
|
12
|
-
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// ═══════════════════════════════════════════════
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return { data: text }
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
12
|
+
const { createMemoryStore } = require('../memory-store.js')
|
|
13
|
+
const { createDigest } = require('../digest.js')
|
|
14
|
+
|
|
15
|
+
const { CreativityEngine } = require('../creativity-engine.js')
|
|
16
|
+
|
|
17
|
+
// Observer research pipeline (optional — installed via @akemi-mio/observer)
|
|
18
|
+
let ObserverStore = null
|
|
19
|
+
let ObserverService = null
|
|
20
|
+
try {
|
|
21
|
+
const obs = require('@akemi-mio/observer')
|
|
22
|
+
ObserverStore = obs.ObserverStore
|
|
23
|
+
ObserverService = obs.ObserverService
|
|
24
|
+
} catch {}
|
|
25
|
+
|
|
26
|
+
// Insight self-observation engine (optional — installed via @akemi-mio/insight)
|
|
27
|
+
let InsightStore = null
|
|
28
|
+
let InsightGenerator = null
|
|
29
|
+
let PresenceService = null
|
|
30
|
+
try {
|
|
31
|
+
const insight = require('@akemi-mio/insight')
|
|
32
|
+
InsightStore = insight.InsightStore
|
|
33
|
+
InsightGenerator = insight.InsightGenerator
|
|
34
|
+
PresenceService = insight.PresenceService
|
|
35
|
+
} catch {}
|
|
36
|
+
|
|
37
|
+
const SERVER_INFO = { name: 'mio-intelligence-mcp', version: '0.1.0' }
|
|
38
|
+
const PROTOCOL_VERSION = '2024-11-05'
|
|
39
|
+
|
|
40
|
+
// ═══════════════════════════════════════════════
|
|
41
|
+
// LLM chatJson — used by creativity engine
|
|
42
|
+
// ═══════════════════════════════════════════════
|
|
43
|
+
|
|
44
|
+
async function chatJson(userText, opts = {}) {
|
|
45
|
+
const apiUrl = process.env.LLM_API_URL || 'https://opencode.ai/zen/go/v1/chat/completions'
|
|
46
|
+
const apiKey = process.env.LLM_KEY || ''
|
|
47
|
+
const model = process.env.LLM_CHAT_MODEL || process.env.LLM_MODEL || 'deepseek-v4-flash'
|
|
48
|
+
const system = opts.system || 'You are a creative AI assistant. Output JSON.'
|
|
49
|
+
const temperature = opts.temperature ?? 0.3
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const resp = await fetch(apiUrl, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify({
|
|
59
|
+
model,
|
|
60
|
+
temperature,
|
|
61
|
+
messages: [
|
|
62
|
+
{ role: 'system', content: system },
|
|
63
|
+
{ role: 'user', content: userText },
|
|
64
|
+
],
|
|
65
|
+
response_format: { type: 'json_object' },
|
|
66
|
+
}),
|
|
67
|
+
signal: AbortSignal.timeout(opts.timeoutMs || 30000),
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
if (!resp.ok) return { error: `HTTP ${resp.status}` }
|
|
71
|
+
const json = await resp.json()
|
|
72
|
+
const text = json.choices?.[0]?.message?.content || ''
|
|
73
|
+
try {
|
|
74
|
+
return { data: JSON.parse(text) }
|
|
75
|
+
} catch {
|
|
76
|
+
return { data: text }
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
return { error: String(err) }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const dataDir = path.resolve(
|
|
84
|
+
process.env.MIO_DATA_DIR || path.join(process.cwd(), '.mio-intelligence')
|
|
85
|
+
)
|
|
86
|
+
const memoryPath = path.join(dataDir, 'memory.jsonl')
|
|
87
|
+
const tracePath = path.join(dataDir, 'traces.jsonl')
|
|
88
|
+
const experienceReusePath = path.join(dataDir, 'experience_reuse.jsonl')
|
|
89
|
+
const queryPath = path.join(dataDir, 'queries.jsonl')
|
|
90
|
+
const agentsPath = path.join(dataDir, 'agents.jsonl')
|
|
91
|
+
const subscriptionPath = path.join(dataDir, 'subscriptions.jsonl')
|
|
90
92
|
const digestStatePath = path.join(dataDir, 'digest_state.json')
|
|
91
93
|
fs.mkdirSync(dataDir, { recursive: true })
|
|
92
94
|
|
|
93
|
-
const { loadPhase0, summarizePhase0, renderPhase0Markdown } = require('./phase0')
|
|
94
|
-
const evolutionCutover = createEvolutionCutoverTools({ dataDir, appendJsonl, readJsonl, projectName })
|
|
95
|
-
const creativityEngine = new CreativityEngine(path.join(dataDir, 'creativity'), chatJson)
|
|
95
|
+
const { loadPhase0, summarizePhase0, renderPhase0Markdown } = require('./phase0')
|
|
96
|
+
const evolutionCutover = createEvolutionCutoverTools({ dataDir, appendJsonl, readJsonl, projectName })
|
|
97
|
+
const creativityEngine = new CreativityEngine(path.join(dataDir, 'creativity'), chatJson)
|
|
96
98
|
const insightStore = InsightStore ? new InsightStore(path.join(dataDir, 'insights', 'insights.json')) : null
|
|
97
|
-
|
|
98
|
-
let runtimeContext = {}
|
|
99
|
-
try {
|
|
100
|
-
const rawContext = process.env.MIO_CONTEXT
|
|
101
|
-
if (rawContext) {
|
|
102
|
-
const parsed = JSON.parse(rawContext)
|
|
103
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
104
|
-
runtimeContext = parsed
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
} catch (_) {
|
|
108
|
-
runtimeContext = {}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function runtimeAgentId() {
|
|
112
|
-
return runtimeContext.agentId || null
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Phase 0 auto-claim: match memory.query results with a later task_outcome
|
|
116
|
-
// from the same agent+project, so reuse evidence does not depend on the
|
|
117
|
-
// agent self-reporting mio.experience.reuse.
|
|
118
|
-
const REUSE_MATCH_WINDOW_MS = (() => {
|
|
119
|
-
const minutes = Number(process.env.MIO_REUSE_MATCH_WINDOW_MIN)
|
|
120
|
-
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : 60 * 60 * 1000
|
|
121
|
-
})()
|
|
122
|
-
const MAX_RECENT_QUERIES = 200
|
|
123
|
-
let recentQueries = []
|
|
124
|
-
|
|
125
|
-
function loadRecentQueries() {
|
|
126
|
-
const now = Date.now()
|
|
127
|
-
const entries = readJsonl(queryPath)
|
|
128
|
-
.filter(
|
|
129
|
-
(entry) =>
|
|
130
|
-
entry &&
|
|
131
|
-
typeof entry.agent === 'string' &&
|
|
132
|
-
entry.agent &&
|
|
133
|
-
typeof entry.expiresAt === 'number' &&
|
|
134
|
-
entry.expiresAt > now
|
|
135
|
-
)
|
|
136
|
-
.slice(-MAX_RECENT_QUERIES)
|
|
137
|
-
recentQueries = entries
|
|
138
|
-
return entries
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function persistRecentQueries(entries) {
|
|
142
|
-
const limited = entries.slice(-MAX_RECENT_QUERIES)
|
|
143
|
-
fs.mkdirSync(dataDir, { recursive: true })
|
|
144
|
-
fs.writeFileSync(
|
|
145
|
-
queryPath,
|
|
146
|
-
limited.length > 0 ? limited.map((entry) => JSON.stringify(entry)).join('\n') + '\n' : '',
|
|
147
|
-
'utf8'
|
|
148
|
-
)
|
|
149
|
-
recentQueries = limited
|
|
150
|
-
return limited
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function createId(prefix) {
|
|
154
|
-
return `${prefix}_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function appendJsonl(file, value) {
|
|
158
|
-
fs.appendFileSync(file, `${JSON.stringify(value)}\n`, 'utf8')
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function writeJsonl(file, values) {
|
|
162
|
-
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
163
|
-
fs.writeFileSync(
|
|
164
|
-
file,
|
|
165
|
-
values.length > 0 ? values.map((value) => JSON.stringify(value)).join('\n') + '\n' : '',
|
|
166
|
-
'utf8'
|
|
167
|
-
)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function readJsonl(file) {
|
|
171
|
-
if (!fs.existsSync(file)) return []
|
|
172
|
-
return fs
|
|
173
|
-
.readFileSync(file, 'utf8')
|
|
174
|
-
.split(/\r?\n/)
|
|
175
|
-
.filter(Boolean)
|
|
176
|
-
.map((line) => {
|
|
177
|
-
try {
|
|
178
|
-
return JSON.parse(line)
|
|
179
|
-
} catch (_) {
|
|
180
|
-
return null
|
|
181
|
-
}
|
|
182
|
-
})
|
|
183
|
-
.filter(Boolean)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
let lastProjectCwd = null
|
|
187
|
-
let lastProjectName = null
|
|
188
|
-
|
|
189
|
-
function projectName() {
|
|
190
|
-
const currentCwd = process.cwd()
|
|
191
|
-
if (lastProjectCwd === currentCwd && lastProjectName) {
|
|
192
|
-
return lastProjectName
|
|
193
|
-
}
|
|
194
|
-
lastProjectCwd = currentCwd
|
|
195
|
-
if (runtimeContext.project) {
|
|
196
|
-
lastProjectName = runtimeContext.project
|
|
197
|
-
return lastProjectName
|
|
198
|
-
}
|
|
199
|
-
try {
|
|
200
|
-
const commonDir = execFileSync(
|
|
201
|
-
'git',
|
|
202
|
-
['rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
203
|
-
{
|
|
204
|
-
cwd: currentCwd,
|
|
205
|
-
encoding: 'utf8',
|
|
206
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
207
|
-
}
|
|
208
|
-
).trim()
|
|
209
|
-
if (commonDir) {
|
|
210
|
-
const repoRoot = path.dirname(commonDir)
|
|
211
|
-
const name = path.basename(repoRoot)
|
|
212
|
-
if (name) {
|
|
213
|
-
lastProjectName = name
|
|
214
|
-
return lastProjectName
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
} catch (_) {
|
|
218
|
-
// fall through to cwd basename
|
|
219
|
-
}
|
|
220
|
-
lastProjectName = path.basename(currentCwd)
|
|
221
|
-
return lastProjectName
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
for (const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
const
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
)
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
const
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
})
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
const
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
const
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if (
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
const
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
const
|
|
827
|
-
const
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
const
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
if (
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
const
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
?
|
|
952
|
-
:
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const project = args.project || projectName()
|
|
975
|
-
const
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
.
|
|
996
|
-
.
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
.
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
const
|
|
1056
|
-
|
|
1057
|
-
if (
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
:
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
const
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
const
|
|
1156
|
-
const
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
)
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
const
|
|
1205
|
-
const
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
const
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
const
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
const
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
.
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
.
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
}
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
},
|
|
1511
|
-
},
|
|
1512
|
-
{
|
|
1513
|
-
name: 'mio.
|
|
1514
|
-
description: '
|
|
1515
|
-
inputSchema: {
|
|
1516
|
-
type: 'object',
|
|
1517
|
-
properties: {
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
},
|
|
1536
|
-
},
|
|
1537
|
-
{
|
|
1538
|
-
name: 'mio.
|
|
1539
|
-
description: '
|
|
1540
|
-
inputSchema: {
|
|
1541
|
-
type: 'object',
|
|
1542
|
-
properties: {
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
},
|
|
1559
|
-
},
|
|
1560
|
-
},
|
|
1561
|
-
{
|
|
1562
|
-
name: 'mio.
|
|
1563
|
-
description: '
|
|
1564
|
-
inputSchema: {
|
|
1565
|
-
type: 'object',
|
|
1566
|
-
properties: {
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
outcomeImproved: { type: 'boolean', description: 'True when the task outcome improved.' },
|
|
1573
|
-
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1574
|
-
notes: { type: 'string', description: 'Optional evidence notes.' },
|
|
1575
|
-
},
|
|
1576
|
-
required: ['sourceAgent', 'targetAgent', 'experienceId', 'reuse', 'behaviorChanged', 'outcomeImproved'],
|
|
1577
|
-
},
|
|
1578
|
-
},
|
|
1579
|
-
{
|
|
1580
|
-
name: 'mio.experience.confirm',
|
|
1581
|
-
description: 'Confirm an auto-claimed reuse record as verified reuse evidence.',
|
|
1582
|
-
inputSchema: {
|
|
1583
|
-
type: 'object',
|
|
1584
|
-
properties: {
|
|
1585
|
-
id: { type: 'string', description: 'Id of the auto-claimed reuse record returned by mio.observer.ingest.' },
|
|
1586
|
-
confirmedBy: { type: 'string', description: 'Who confirmed the reuse: agent or human. Defaults to agent.' },
|
|
1587
|
-
outcomeImproved: { type: 'boolean', description: 'Override: true when the reused experience improved the task outcome. Defaults to the auto-claim value.' },
|
|
1588
|
-
notes: { type: 'string', description: 'Optional confirmation notes.' },
|
|
1589
|
-
},
|
|
1590
|
-
required: ['id'],
|
|
1591
|
-
},
|
|
1592
|
-
},
|
|
1593
|
-
{
|
|
1594
|
-
name: 'mio.experience.list',
|
|
1595
|
-
description: 'List Phase 0 experience reuse evidence, optionally filtered by status or target agent.',
|
|
1596
|
-
inputSchema: {
|
|
1597
|
-
type: 'object',
|
|
1598
|
-
properties: {
|
|
1599
|
-
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1600
|
-
status: { type: 'string', description: 'Filter: pending (auto-claim awaiting confirmation), confirmed, verified, auto_claim, agent_report, all. Defaults to all.' },
|
|
1601
|
-
targetAgent: { type: 'string', description: 'Filter records by target agent.' },
|
|
1602
|
-
limit: { type: 'number', description: 'Maximum records to return, 1-100. Defaults to 20.' },
|
|
1603
|
-
},
|
|
1604
|
-
},
|
|
1605
|
-
},
|
|
1606
|
-
{
|
|
1607
|
-
name: 'mio.phase0.report',
|
|
1608
|
-
description: 'Generate the current Phase 0 cross-agent validation report, as JSON or rendered Markdown.',
|
|
1609
|
-
inputSchema: {
|
|
1610
|
-
type: 'object',
|
|
1611
|
-
properties: {
|
|
1612
|
-
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1613
|
-
format: { type: 'string', description: 'Output format: json or markdown. Defaults to json.' },
|
|
1614
|
-
},
|
|
1615
|
-
},
|
|
1616
|
-
},
|
|
1617
|
-
{
|
|
1618
|
-
name: 'mio.task.route',
|
|
1619
|
-
description: 'Route an incoming task to the most relevant verified experiences and related memories, turning recall into decision influence (P1).',
|
|
1620
|
-
inputSchema: {
|
|
1621
|
-
type: 'object',
|
|
1622
|
-
properties: {
|
|
1623
|
-
task: { type: 'string', description: 'Task description to route, e.g. \'fix IPC communication bug\'.' },
|
|
1624
|
-
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1625
|
-
scope: { type: 'string', description: 'Search scope: project (default), global, or all.' },
|
|
1626
|
-
limit: { type: 'number', description: 'Maximum routes, between 1 and 10. Defaults to 5.' },
|
|
1627
|
-
},
|
|
1628
|
-
required: ['task'],
|
|
1629
|
-
},
|
|
1630
|
-
},
|
|
1631
|
-
{
|
|
1632
|
-
name: 'mio.agent.register',
|
|
1633
|
-
description: 'Register or update an agent in the Mio Agent Registry. Upserts by agentId+project; updates lastSeenAt and sessionCount.',
|
|
1634
|
-
inputSchema: {
|
|
1635
|
-
type: 'object',
|
|
1636
|
-
properties: {
|
|
1637
|
-
agentId: { type: 'string', description: 'Unique agent identifier (e.g. codex, opencode).' },
|
|
1638
|
-
hostType: { type: 'string', description: 'Host type: mcp, observer, or plugin. Defaults to mcp.' },
|
|
1639
|
-
capabilities: { type: 'array', items: { type: 'string' }, description: 'Agent capabilities.' },
|
|
1640
|
-
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1641
|
-
},
|
|
1642
|
-
required: ['agentId'],
|
|
1643
|
-
},
|
|
1644
|
-
},
|
|
1645
|
-
{
|
|
1646
|
-
name: 'mio.agent.list',
|
|
1647
|
-
description: 'List all registered agents, optionally filtered by project.',
|
|
1648
|
-
inputSchema: {
|
|
1649
|
-
type: 'object',
|
|
1650
|
-
properties: {
|
|
1651
|
-
project: { type: 'string', description: 'Project name filter. Omit for all agents.' },
|
|
1652
|
-
},
|
|
1653
|
-
},
|
|
1654
|
-
},
|
|
1655
|
-
{
|
|
1656
|
-
name: 'mio.agent.report',
|
|
1657
|
-
description: 'Performance report for one or all agents, aggregating task outcomes, memory, and experience reuse.',
|
|
1658
|
-
inputSchema: {
|
|
1659
|
-
type: 'object',
|
|
1660
|
-
properties: {
|
|
1661
|
-
agentId: { type: 'string', description: 'Agent to report on. Omit for all agents.' },
|
|
1662
|
-
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1663
|
-
},
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
99
|
+
|
|
100
|
+
let runtimeContext = {}
|
|
101
|
+
try {
|
|
102
|
+
const rawContext = process.env.MIO_CONTEXT
|
|
103
|
+
if (rawContext) {
|
|
104
|
+
const parsed = JSON.parse(rawContext)
|
|
105
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
106
|
+
runtimeContext = parsed
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (_) {
|
|
110
|
+
runtimeContext = {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function runtimeAgentId() {
|
|
114
|
+
return runtimeContext.agentId || null
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Phase 0 auto-claim: match memory.query results with a later task_outcome
|
|
118
|
+
// from the same agent+project, so reuse evidence does not depend on the
|
|
119
|
+
// agent self-reporting mio.experience.reuse.
|
|
120
|
+
const REUSE_MATCH_WINDOW_MS = (() => {
|
|
121
|
+
const minutes = Number(process.env.MIO_REUSE_MATCH_WINDOW_MIN)
|
|
122
|
+
return Number.isFinite(minutes) && minutes > 0 ? minutes * 60 * 1000 : 60 * 60 * 1000
|
|
123
|
+
})()
|
|
124
|
+
const MAX_RECENT_QUERIES = 200
|
|
125
|
+
let recentQueries = []
|
|
126
|
+
|
|
127
|
+
function loadRecentQueries() {
|
|
128
|
+
const now = Date.now()
|
|
129
|
+
const entries = readJsonl(queryPath)
|
|
130
|
+
.filter(
|
|
131
|
+
(entry) =>
|
|
132
|
+
entry &&
|
|
133
|
+
typeof entry.agent === 'string' &&
|
|
134
|
+
entry.agent &&
|
|
135
|
+
typeof entry.expiresAt === 'number' &&
|
|
136
|
+
entry.expiresAt > now
|
|
137
|
+
)
|
|
138
|
+
.slice(-MAX_RECENT_QUERIES)
|
|
139
|
+
recentQueries = entries
|
|
140
|
+
return entries
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function persistRecentQueries(entries) {
|
|
144
|
+
const limited = entries.slice(-MAX_RECENT_QUERIES)
|
|
145
|
+
fs.mkdirSync(dataDir, { recursive: true })
|
|
146
|
+
fs.writeFileSync(
|
|
147
|
+
queryPath,
|
|
148
|
+
limited.length > 0 ? limited.map((entry) => JSON.stringify(entry)).join('\n') + '\n' : '',
|
|
149
|
+
'utf8'
|
|
150
|
+
)
|
|
151
|
+
recentQueries = limited
|
|
152
|
+
return limited
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function createId(prefix) {
|
|
156
|
+
return `${prefix}_${Date.now()}_${crypto.randomBytes(6).toString('hex')}`
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function appendJsonl(file, value) {
|
|
160
|
+
fs.appendFileSync(file, `${JSON.stringify(value)}\n`, 'utf8')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function writeJsonl(file, values) {
|
|
164
|
+
fs.mkdirSync(path.dirname(file), { recursive: true })
|
|
165
|
+
fs.writeFileSync(
|
|
166
|
+
file,
|
|
167
|
+
values.length > 0 ? values.map((value) => JSON.stringify(value)).join('\n') + '\n' : '',
|
|
168
|
+
'utf8'
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function readJsonl(file) {
|
|
173
|
+
if (!fs.existsSync(file)) return []
|
|
174
|
+
return fs
|
|
175
|
+
.readFileSync(file, 'utf8')
|
|
176
|
+
.split(/\r?\n/)
|
|
177
|
+
.filter(Boolean)
|
|
178
|
+
.map((line) => {
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(line)
|
|
181
|
+
} catch (_) {
|
|
182
|
+
return null
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
.filter(Boolean)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
let lastProjectCwd = null
|
|
189
|
+
let lastProjectName = null
|
|
190
|
+
|
|
191
|
+
function projectName() {
|
|
192
|
+
const currentCwd = process.cwd()
|
|
193
|
+
if (lastProjectCwd === currentCwd && lastProjectName) {
|
|
194
|
+
return lastProjectName
|
|
195
|
+
}
|
|
196
|
+
lastProjectCwd = currentCwd
|
|
197
|
+
if (runtimeContext.project) {
|
|
198
|
+
lastProjectName = runtimeContext.project
|
|
199
|
+
return lastProjectName
|
|
200
|
+
}
|
|
201
|
+
try {
|
|
202
|
+
const commonDir = execFileSync(
|
|
203
|
+
'git',
|
|
204
|
+
['rev-parse', '--path-format=absolute', '--git-common-dir'],
|
|
205
|
+
{
|
|
206
|
+
cwd: currentCwd,
|
|
207
|
+
encoding: 'utf8',
|
|
208
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
209
|
+
}
|
|
210
|
+
).trim()
|
|
211
|
+
if (commonDir) {
|
|
212
|
+
const repoRoot = path.dirname(commonDir)
|
|
213
|
+
const name = path.basename(repoRoot)
|
|
214
|
+
if (name) {
|
|
215
|
+
lastProjectName = name
|
|
216
|
+
return lastProjectName
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (_) {
|
|
220
|
+
// fall through to cwd basename
|
|
221
|
+
}
|
|
222
|
+
lastProjectName = path.basename(currentCwd)
|
|
223
|
+
return lastProjectName
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Memory store: shared implementation with the CLI (`mio recall`/`mio remember`).
|
|
227
|
+
// The pure scoring/filter helpers and queryMemory/recordMemory live in
|
|
228
|
+
// server/memory-store.js so both entry points rank and persist identically.
|
|
229
|
+
const REUSE_STATUS_FILTERS = Object.freeze({
|
|
230
|
+
pending: (record) => record.source === 'auto_claim' && record.confirmed !== true,
|
|
231
|
+
confirmed: (record) => record.confirmed === true,
|
|
232
|
+
verified: (record) =>
|
|
233
|
+
(record.reuse === true || record.reuse === 'true') &&
|
|
234
|
+
(record.behaviorChanged === true || record.behaviorChanged === 'true') &&
|
|
235
|
+
(record.outcomeImproved === true || record.outcomeImproved === 'true'),
|
|
236
|
+
auto_claim: (record) => record.source === 'auto_claim',
|
|
237
|
+
agent_report: (record) => record.source !== 'auto_claim',
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const memoryStore = createMemoryStore({
|
|
241
|
+
dataDir,
|
|
242
|
+
projectName,
|
|
243
|
+
agentId: runtimeAgentId,
|
|
244
|
+
reuseMatchWindowMs: REUSE_MATCH_WINDOW_MS,
|
|
245
|
+
verifiedFilter: REUSE_STATUS_FILTERS.verified,
|
|
246
|
+
recordQuery(entry) {
|
|
247
|
+
const entries = loadRecentQueries()
|
|
248
|
+
entries.push(entry)
|
|
249
|
+
persistRecentQueries(entries)
|
|
250
|
+
},
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const digestEngine = createDigest({ home: dataDir })
|
|
254
|
+
|
|
255
|
+
const {
|
|
256
|
+
normalizeTags,
|
|
257
|
+
tokenize,
|
|
258
|
+
normalizeScope,
|
|
259
|
+
isGlobalRecord,
|
|
260
|
+
matchesProjectScope,
|
|
261
|
+
latinTokens,
|
|
262
|
+
cjkBigrams,
|
|
263
|
+
scoreRecord,
|
|
264
|
+
matchesMemoryFilters,
|
|
265
|
+
queryMemory,
|
|
266
|
+
recordMemory,
|
|
267
|
+
queryTraces,
|
|
268
|
+
loadEvidenceWeights,
|
|
269
|
+
} = memoryStore
|
|
270
|
+
|
|
271
|
+
function ingestObservation(args = {}) {
|
|
272
|
+
const traceId = String(args.trace_id || '').trim()
|
|
273
|
+
const eventType = String(args.event_type || '').trim()
|
|
274
|
+
if (!traceId) throw new Error('observer.ingest requires trace_id')
|
|
275
|
+
if (!eventType) throw new Error('observer.ingest requires event_type')
|
|
276
|
+
const event = {
|
|
277
|
+
id: createId('trace'),
|
|
278
|
+
timestamp: new Date().toISOString(),
|
|
279
|
+
trace_id: traceId,
|
|
280
|
+
event_type: eventType,
|
|
281
|
+
outcome: args.outcome || null,
|
|
282
|
+
payload: args.payload || {},
|
|
283
|
+
agent: args.agent || runtimeAgentId(),
|
|
284
|
+
host: args.host || 'mcp',
|
|
285
|
+
project: args.project || projectName(),
|
|
286
|
+
}
|
|
287
|
+
appendJsonl(tracePath, event)
|
|
288
|
+
const autoClaims = autoClaimExperienceReuse(event)
|
|
289
|
+
if (autoClaims.length > 0) {
|
|
290
|
+
return { recorded: true, event, autoClaims }
|
|
291
|
+
}
|
|
292
|
+
return { recorded: true, event }
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function autoClaimExperienceReuse(event) {
|
|
296
|
+
if (String(event.event_type || '').toLowerCase() !== 'task_outcome') return []
|
|
297
|
+
const targetAgent = String(event.agent || '').trim()
|
|
298
|
+
if (!targetAgent) return []
|
|
299
|
+
const now = Date.now()
|
|
300
|
+
const claims = []
|
|
301
|
+
const remaining = []
|
|
302
|
+
const entries = loadRecentQueries()
|
|
303
|
+
for (const entry of entries) {
|
|
304
|
+
const expired = now - entry.timestamp > REUSE_MATCH_WINDOW_MS
|
|
305
|
+
const matches =
|
|
306
|
+
entry.agent === targetAgent && (!event.project || entry.project === event.project)
|
|
307
|
+
if (expired || matches) {
|
|
308
|
+
if (matches && !expired) {
|
|
309
|
+
claims.push(buildAutoClaim(entry, event, targetAgent))
|
|
310
|
+
}
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
remaining.push(entry)
|
|
314
|
+
}
|
|
315
|
+
persistRecentQueries(remaining)
|
|
316
|
+
for (const evidence of claims) {
|
|
317
|
+
appendJsonl(experienceReusePath, evidence)
|
|
318
|
+
}
|
|
319
|
+
return claims
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function buildAutoClaim(entry, event, targetAgent) {
|
|
323
|
+
const crossSourceIndex = entry.resultSources.findIndex(
|
|
324
|
+
(source) => source && String(source).toLowerCase() !== targetAgent.toLowerCase()
|
|
325
|
+
)
|
|
326
|
+
const sourceAgent =
|
|
327
|
+
crossSourceIndex >= 0
|
|
328
|
+
? String(entry.resultSources[crossSourceIndex])
|
|
329
|
+
: targetAgent
|
|
330
|
+
const experienceId =
|
|
331
|
+
entry.resultIds[crossSourceIndex >= 0 ? crossSourceIndex : 0] ||
|
|
332
|
+
entry.resultIds[0] ||
|
|
333
|
+
'unknown'
|
|
334
|
+
const outcomeImproved = String(event.outcome || '').toLowerCase() === 'success'
|
|
335
|
+
return {
|
|
336
|
+
id: createId('xfer'),
|
|
337
|
+
timestamp: new Date().toISOString(),
|
|
338
|
+
sourceAgent,
|
|
339
|
+
targetAgent,
|
|
340
|
+
experienceId,
|
|
341
|
+
reuse: true,
|
|
342
|
+
behaviorChanged: false,
|
|
343
|
+
outcomeImproved,
|
|
344
|
+
project: entry.project,
|
|
345
|
+
source: 'auto_claim',
|
|
346
|
+
traceId: event.trace_id || null,
|
|
347
|
+
notes: `Auto-claimed: memory.query "${entry.query}" matched task_outcome ${event.outcome || 'unknown'} in trace ${event.trace_id || 'unknown'}.`,
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const SUBSCRIPTION_DEFAULT_TTL_DAYS = 30
|
|
352
|
+
const MAX_SUBSCRIPTIONS_PER_AGENT = 20
|
|
353
|
+
const MAX_DIGEST_EVENTS = 100
|
|
354
|
+
|
|
355
|
+
function readDigestState() {
|
|
356
|
+
if (!fs.existsSync(digestStatePath)) return {}
|
|
357
|
+
try {
|
|
358
|
+
const parsed = JSON.parse(fs.readFileSync(digestStatePath, 'utf8'))
|
|
359
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
|
360
|
+
} catch (_) {
|
|
361
|
+
return {}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function writeDigestState(state) {
|
|
366
|
+
fs.mkdirSync(dataDir, { recursive: true })
|
|
367
|
+
fs.writeFileSync(digestStatePath, JSON.stringify(state), 'utf8')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function subscribeKey(agent, project, eventTypes, topic) {
|
|
371
|
+
return `${agent}|${project}|${eventTypes.join(',')}|${topic || ''}`
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
function loadActiveSubscriptions(agent) {
|
|
375
|
+
const now = Date.now()
|
|
376
|
+
return readJsonl(subscriptionPath)
|
|
377
|
+
.filter((subscription) => subscription && subscription.agent === agent)
|
|
378
|
+
.filter((subscription) => {
|
|
379
|
+
if (subscription.expiresAt) {
|
|
380
|
+
const expires = new Date(subscription.expiresAt).getTime()
|
|
381
|
+
if (Number.isFinite(expires) && expires <= now) return false
|
|
382
|
+
}
|
|
383
|
+
return true
|
|
384
|
+
})
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function subscribeObserver(args = {}) {
|
|
388
|
+
const agent = runtimeAgentId() || 'mcp'
|
|
389
|
+
const project = args.project || projectName()
|
|
390
|
+
const eventTypes = Array.isArray(args.eventTypes)
|
|
391
|
+
? args.eventTypes.map((value) => String(value).trim().toLowerCase()).filter(Boolean)
|
|
392
|
+
: []
|
|
393
|
+
const topic = args.topic ? String(args.topic).trim().slice(0, 120) : ''
|
|
394
|
+
const ttlDays = Number(args.ttlDays) || SUBSCRIPTION_DEFAULT_TTL_DAYS
|
|
395
|
+
const ttlMs = Math.max(1, ttlDays) * 24 * 60 * 60 * 1000
|
|
396
|
+
|
|
397
|
+
const subscriptions = readJsonl(subscriptionPath)
|
|
398
|
+
const key = subscribeKey(agent, project, eventTypes, topic)
|
|
399
|
+
let existing = subscriptions.find((subscription) => {
|
|
400
|
+
return (
|
|
401
|
+
subscription &&
|
|
402
|
+
subscribeKey(
|
|
403
|
+
subscription.agent,
|
|
404
|
+
subscription.project,
|
|
405
|
+
subscription.eventTypes || [],
|
|
406
|
+
subscription.topic || '',
|
|
407
|
+
) === key
|
|
408
|
+
)
|
|
409
|
+
})
|
|
410
|
+
const now = new Date()
|
|
411
|
+
const expiresAt = new Date(now.getTime() + ttlMs).toISOString()
|
|
412
|
+
if (existing) {
|
|
413
|
+
existing.updatedAt = now.toISOString()
|
|
414
|
+
existing.expiresAt = expiresAt
|
|
415
|
+
} else {
|
|
416
|
+
existing = {
|
|
417
|
+
id: createId('sub'),
|
|
418
|
+
agent,
|
|
419
|
+
project,
|
|
420
|
+
eventTypes,
|
|
421
|
+
topic: topic || null,
|
|
422
|
+
createdAt: now.toISOString(),
|
|
423
|
+
updatedAt: now.toISOString(),
|
|
424
|
+
expiresAt,
|
|
425
|
+
}
|
|
426
|
+
subscriptions.push(existing)
|
|
427
|
+
}
|
|
428
|
+
const agentSubscriptions = subscriptions.filter((subscription) => subscription.agent === agent)
|
|
429
|
+
if (agentSubscriptions.length > MAX_SUBSCRIPTIONS_PER_AGENT) {
|
|
430
|
+
throw new Error(`Too many subscriptions for agent ${agent} (max ${MAX_SUBSCRIPTIONS_PER_AGENT})`)
|
|
431
|
+
}
|
|
432
|
+
writeJsonl(subscriptionPath, subscriptions)
|
|
433
|
+
return { subscribed: true, subscription: existing, project }
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function observerDigest(args = {}) {
|
|
437
|
+
const agent = runtimeAgentId() || 'mcp'
|
|
438
|
+
const project = args.project || projectName()
|
|
439
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), MAX_DIGEST_EVENTS)
|
|
440
|
+
const filterEventTypes = Array.isArray(args.eventTypes)
|
|
441
|
+
? args.eventTypes.map((value) => String(value).trim().toLowerCase()).filter(Boolean)
|
|
442
|
+
: null
|
|
443
|
+
|
|
444
|
+
let subscriptions = loadActiveSubscriptions(agent)
|
|
445
|
+
if (args.project) {
|
|
446
|
+
subscriptions = subscriptions.filter((subscription) => subscription.project === project)
|
|
447
|
+
}
|
|
448
|
+
if (filterEventTypes) {
|
|
449
|
+
subscriptions = subscriptions.filter((subscription) => {
|
|
450
|
+
if ((subscription.eventTypes || []).length === 0) return true
|
|
451
|
+
return filterEventTypes.some((eventType) =>
|
|
452
|
+
(subscription.eventTypes || []).includes(eventType),
|
|
453
|
+
)
|
|
454
|
+
})
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const state = readDigestState()
|
|
458
|
+
const traces = readJsonl(tracePath)
|
|
459
|
+
const digestTime = new Date().toISOString()
|
|
460
|
+
const events = []
|
|
461
|
+
const seenIds = new Set()
|
|
462
|
+
const matchedSubscriptionIds = new Set()
|
|
463
|
+
|
|
464
|
+
for (const subscription of subscriptions) {
|
|
465
|
+
const cursor = state[subscription.id] || null
|
|
466
|
+
const cursorTime = cursor ? Date.parse(cursor) : null
|
|
467
|
+
const subscriptionEventTypes = subscription.eventTypes || []
|
|
468
|
+
let lastEventTime = null
|
|
469
|
+
for (const event of traces) {
|
|
470
|
+
if (subscription.project && event.project && event.project !== subscription.project) {
|
|
471
|
+
continue
|
|
472
|
+
}
|
|
473
|
+
if (
|
|
474
|
+
subscriptionEventTypes.length > 0 &&
|
|
475
|
+
!subscriptionEventTypes.includes(String(event.event_type || '').toLowerCase())
|
|
476
|
+
) {
|
|
477
|
+
continue
|
|
478
|
+
}
|
|
479
|
+
if (subscription.topic) {
|
|
480
|
+
const haystack = `${event.event_type || ''} ${JSON.stringify(event.payload || {})}`
|
|
481
|
+
.toLowerCase()
|
|
482
|
+
if (!haystack.includes(subscription.topic.toLowerCase())) continue
|
|
483
|
+
}
|
|
484
|
+
const eventTime = event.timestamp ? Date.parse(event.timestamp) : null
|
|
485
|
+
if (cursorTime !== null && eventTime !== null && eventTime <= cursorTime) continue
|
|
486
|
+
if (eventTime !== null && (lastEventTime === null || eventTime > lastEventTime)) {
|
|
487
|
+
lastEventTime = eventTime
|
|
488
|
+
}
|
|
489
|
+
if (seenIds.has(event.id)) continue
|
|
490
|
+
seenIds.add(event.id)
|
|
491
|
+
events.push({
|
|
492
|
+
id: event.id,
|
|
493
|
+
trace_id: event.trace_id || null,
|
|
494
|
+
event_type: event.event_type,
|
|
495
|
+
outcome: event.outcome || null,
|
|
496
|
+
timestamp: event.timestamp,
|
|
497
|
+
agent: event.agent || null,
|
|
498
|
+
payload: event.payload || null,
|
|
499
|
+
})
|
|
500
|
+
matchedSubscriptionIds.add(subscription.id)
|
|
501
|
+
if (events.length >= limit) break
|
|
502
|
+
}
|
|
503
|
+
if (lastEventTime !== null) {
|
|
504
|
+
state[subscription.id] = new Date(lastEventTime).toISOString()
|
|
505
|
+
} else if (cursor === null) {
|
|
506
|
+
state[subscription.id] = digestTime
|
|
507
|
+
}
|
|
508
|
+
if (events.length >= limit) break
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
writeDigestState(state)
|
|
512
|
+
return {
|
|
513
|
+
project,
|
|
514
|
+
agent,
|
|
515
|
+
subscriptionCount: subscriptions.length,
|
|
516
|
+
matchedSubscriptions: Array.from(matchedSubscriptionIds),
|
|
517
|
+
count: events.length,
|
|
518
|
+
events,
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function contentTokens(value) {
|
|
523
|
+
const text = String(value || '').toLowerCase()
|
|
524
|
+
const latin = text.match(/[a-z0-9]+/g) || []
|
|
525
|
+
const chars = text.match(/[\u4e00-\u9fff]/g) || []
|
|
526
|
+
const bigrams = []
|
|
527
|
+
for (let i = 0; i < chars.length - 1; i++) {
|
|
528
|
+
bigrams.push(`${chars[i]}${chars[i + 1]}`)
|
|
529
|
+
}
|
|
530
|
+
return [...latin, ...bigrams]
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
function jaccardSimilarity(a, b) {
|
|
534
|
+
const setA = new Set(contentTokens(a))
|
|
535
|
+
const setB = new Set(contentTokens(b))
|
|
536
|
+
if (setA.size === 0 || setB.size === 0) return 0
|
|
537
|
+
let intersection = 0
|
|
538
|
+
for (const token of setA) {
|
|
539
|
+
if (setB.has(token)) intersection += 1
|
|
540
|
+
}
|
|
541
|
+
const union = setA.size + setB.size - intersection
|
|
542
|
+
return union === 0 ? 0 : intersection / union
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function findDuplicateGroups(records, limit) {
|
|
546
|
+
const parent = new Map()
|
|
547
|
+
for (const record of records) parent.set(record.id, record.id)
|
|
548
|
+
const find = (id) => {
|
|
549
|
+
while (parent.get(id) !== id) {
|
|
550
|
+
parent.set(id, parent.get(parent.get(id)))
|
|
551
|
+
id = parent.get(id)
|
|
552
|
+
}
|
|
553
|
+
return id
|
|
554
|
+
}
|
|
555
|
+
const union = (a, b) => {
|
|
556
|
+
const ra = find(a)
|
|
557
|
+
const rb = find(b)
|
|
558
|
+
if (ra !== rb) parent.set(ra, rb)
|
|
559
|
+
}
|
|
560
|
+
for (let i = 0; i < records.length; i++) {
|
|
561
|
+
for (let j = i + 1; j < records.length; j++) {
|
|
562
|
+
if (
|
|
563
|
+
jaccardSimilarity(records[i].content, records[j].content) >= MEMORY_DUPLICATE_SIMILARITY
|
|
564
|
+
) {
|
|
565
|
+
union(records[i].id, records[j].id)
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const byRoot = new Map()
|
|
570
|
+
for (const record of records) {
|
|
571
|
+
const root = find(record.id)
|
|
572
|
+
if (!byRoot.has(root)) byRoot.set(root, [])
|
|
573
|
+
byRoot.get(root).push(record)
|
|
574
|
+
}
|
|
575
|
+
return Array.from(byRoot.values())
|
|
576
|
+
.filter((group) => group.length >= 2)
|
|
577
|
+
.sort((a, b) => b.length - a.length)
|
|
578
|
+
.slice(0, limit)
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function analyzeMemory(args = {}) {
|
|
582
|
+
const project = args.project || projectName()
|
|
583
|
+
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 20)
|
|
584
|
+
const records = readJsonl(memoryPath).filter(
|
|
585
|
+
(record) => !project || record.project === project || !record.project,
|
|
586
|
+
)
|
|
587
|
+
const archived = records.filter((record) => record.archived === true).length
|
|
588
|
+
const activeRecords = records.filter((record) => record.archived !== true)
|
|
589
|
+
const total = activeRecords.length
|
|
590
|
+
const layerCounts = { project: 0, global: 0 }
|
|
591
|
+
for (const record of activeRecords) {
|
|
592
|
+
if (record.scope === 'global' || !record.project) layerCounts.global += 1
|
|
593
|
+
else layerCounts.project += 1
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const byKind = {}
|
|
597
|
+
const lowQuality = []
|
|
598
|
+
for (const record of activeRecords) {
|
|
599
|
+
const kind = String(record.kind || '').trim() || 'unknown'
|
|
600
|
+
byKind[kind] = (byKind[kind] || 0) + 1
|
|
601
|
+
const content = String(record.content || '').trim()
|
|
602
|
+
const issues = []
|
|
603
|
+
if (!content) issues.push('missing content')
|
|
604
|
+
else if (content.length < MEMORY_SHORT_CONTENT_MIN) issues.push('content too short')
|
|
605
|
+
if (!record.kind || !String(record.kind).trim()) issues.push('missing kind')
|
|
606
|
+
if (issues.length > 0) {
|
|
607
|
+
lowQuality.push({
|
|
608
|
+
id: record.id,
|
|
609
|
+
timestamp: record.timestamp,
|
|
610
|
+
kind: record.kind || null,
|
|
611
|
+
content: content.slice(0, 200) || null,
|
|
612
|
+
issues,
|
|
613
|
+
})
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
let duplicateGroups = []
|
|
618
|
+
let duplicatesSkipped = false
|
|
619
|
+
if (activeRecords.length > MAX_PAIRWISE_MEMORY) {
|
|
620
|
+
duplicatesSkipped = true
|
|
621
|
+
} else {
|
|
622
|
+
duplicateGroups = findDuplicateGroups(activeRecords, limit).map((group) => ({
|
|
623
|
+
size: group.length,
|
|
624
|
+
records: group.map((record) => ({
|
|
625
|
+
id: record.id,
|
|
626
|
+
timestamp: record.timestamp,
|
|
627
|
+
kind: record.kind || null,
|
|
628
|
+
content: String(record.content || '').slice(0, 200),
|
|
629
|
+
})),
|
|
630
|
+
}))
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const issues = {
|
|
634
|
+
lowQuality: lowQuality.length,
|
|
635
|
+
duplicateGroups: duplicateGroups.length,
|
|
636
|
+
duplicatesSkipped,
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const suggestions = []
|
|
640
|
+
if (duplicateGroups.length > 0) {
|
|
641
|
+
suggestions.push(
|
|
642
|
+
`Found ${duplicateGroups.length} duplicate group(s); consider archiving or merging duplicates to keep recall precise.`,
|
|
643
|
+
)
|
|
644
|
+
}
|
|
645
|
+
if (lowQuality.length > 0) {
|
|
646
|
+
suggestions.push(
|
|
647
|
+
`${lowQuality.length} low-quality record(s) (missing kind/content or too short); consider fixing or removing them.`,
|
|
648
|
+
)
|
|
649
|
+
}
|
|
650
|
+
if (total === 0) {
|
|
651
|
+
suggestions.push('No memory records for this project yet.')
|
|
652
|
+
} else if (duplicateGroups.length === 0 && lowQuality.length === 0) {
|
|
653
|
+
suggestions.push('Memory looks healthy; no dedup or quality fixes needed.')
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
return {
|
|
657
|
+
project: project || null,
|
|
658
|
+
total,
|
|
659
|
+
archived,
|
|
660
|
+
layers: layerCounts,
|
|
661
|
+
byKind,
|
|
662
|
+
issues,
|
|
663
|
+
duplicates: duplicateGroups,
|
|
664
|
+
lowQuality: lowQuality.slice(0, limit),
|
|
665
|
+
suggestions,
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
function migrateMemory(args = {}) {
|
|
670
|
+
const idsInput = Array.isArray(args.ids) ? args.ids : []
|
|
671
|
+
const ids = idsInput.map((value) => String(value).trim()).filter(Boolean)
|
|
672
|
+
if (ids.length === 0) throw new Error('memory.migrate requires ids (array of memory record ids)')
|
|
673
|
+
const targetScope = normalizeScope(args.scope)
|
|
674
|
+
if (targetScope === 'all') throw new Error('memory.migrate scope must be project or global')
|
|
675
|
+
const project = args.project || projectName()
|
|
676
|
+
const reason = args.reason ? String(args.reason).trim().slice(0, 200) : null
|
|
677
|
+
|
|
678
|
+
const records = readJsonl(memoryPath)
|
|
679
|
+
const idSet = new Set(ids)
|
|
680
|
+
const updated = []
|
|
681
|
+
const unchanged = []
|
|
682
|
+
const notFound = [...ids]
|
|
683
|
+
for (const record of records) {
|
|
684
|
+
if (!idSet.has(record.id)) continue
|
|
685
|
+
const fromScope = record.scope || 'project'
|
|
686
|
+
const fromProject = record.project || null
|
|
687
|
+
if (fromScope === targetScope) {
|
|
688
|
+
unchanged.push(record.id)
|
|
689
|
+
} else {
|
|
690
|
+
record.scope = targetScope
|
|
691
|
+
record.project = targetScope === 'global' ? null : project
|
|
692
|
+
record.migratedAt = new Date().toISOString()
|
|
693
|
+
record.migratedFrom = fromScope === 'global' ? 'global' : fromProject || 'project'
|
|
694
|
+
if (reason) record.migrateReason = reason
|
|
695
|
+
updated.push(record.id)
|
|
696
|
+
}
|
|
697
|
+
const index = notFound.indexOf(record.id)
|
|
698
|
+
if (index >= 0) notFound.splice(index, 1)
|
|
699
|
+
}
|
|
700
|
+
writeJsonl(memoryPath, records)
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
project: project || null,
|
|
704
|
+
scope: targetScope,
|
|
705
|
+
updated,
|
|
706
|
+
updatedCount: updated.length,
|
|
707
|
+
unchanged,
|
|
708
|
+
notFound,
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
function archiveMemory(args = {}) {
|
|
712
|
+
const idsInput = Array.isArray(args.ids) ? args.ids : []
|
|
713
|
+
const ids = idsInput.map((value) => String(value).trim()).filter(Boolean)
|
|
714
|
+
if (ids.length === 0) throw new Error('memory.archive requires ids (array of memory record ids)')
|
|
715
|
+
const project = args.project || projectName()
|
|
716
|
+
const restore = args.restore === true || args.restore === 'true'
|
|
717
|
+
const reason = args.reason ? String(args.reason).trim().slice(0, 200) : null
|
|
718
|
+
|
|
719
|
+
const records = readJsonl(memoryPath)
|
|
720
|
+
const idSet = new Set(ids)
|
|
721
|
+
const archived = []
|
|
722
|
+
const notFound = [...ids]
|
|
723
|
+
for (const record of records) {
|
|
724
|
+
if (!idSet.has(record.id)) continue
|
|
725
|
+
if (project && record.project && record.project !== project) continue
|
|
726
|
+
if (restore) {
|
|
727
|
+
if (record.archived === true) {
|
|
728
|
+
delete record.archived
|
|
729
|
+
delete record.archivedAt
|
|
730
|
+
delete record.archiveReason
|
|
731
|
+
archived.push(record.id)
|
|
732
|
+
}
|
|
733
|
+
} else if (record.archived !== true) {
|
|
734
|
+
record.archived = true
|
|
735
|
+
record.archivedAt = new Date().toISOString()
|
|
736
|
+
if (reason) record.archiveReason = reason
|
|
737
|
+
archived.push(record.id)
|
|
738
|
+
}
|
|
739
|
+
const index = notFound.indexOf(record.id)
|
|
740
|
+
if (index >= 0) notFound.splice(index, 1)
|
|
741
|
+
}
|
|
742
|
+
writeJsonl(memoryPath, records)
|
|
743
|
+
|
|
744
|
+
return {
|
|
745
|
+
project: project || null,
|
|
746
|
+
restored: restore,
|
|
747
|
+
archived,
|
|
748
|
+
archivedCount: archived.length,
|
|
749
|
+
notFound,
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
const FAILURE_OUTCOMES = new Set(['failure', 'error', 'aborted', 'retry'])
|
|
754
|
+
const MEMORY_DUPLICATE_SIMILARITY = 0.75
|
|
755
|
+
const MEMORY_SHORT_CONTENT_MIN = 20
|
|
756
|
+
const MAX_PAIRWISE_MEMORY = 1500
|
|
757
|
+
|
|
758
|
+
function isFailureOutcome(value) {
|
|
759
|
+
return FAILURE_OUTCOMES.has(String(value || '').toLowerCase())
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function summarizeTrace(event) {
|
|
763
|
+
const payload = event.payload
|
|
764
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload)) {
|
|
765
|
+
if (typeof payload.summary === 'string' && payload.summary.trim()) {
|
|
766
|
+
return payload.summary.trim().slice(0, 160)
|
|
767
|
+
}
|
|
768
|
+
if (typeof payload.tool === 'string' && payload.tool.trim()) {
|
|
769
|
+
return `tool: ${payload.tool.trim().slice(0, 120)}`
|
|
770
|
+
}
|
|
771
|
+
if (typeof payload.error === 'string' && payload.error.trim()) {
|
|
772
|
+
return payload.error.trim().slice(0, 160)
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return String(event.event_type || 'trace').slice(0, 120)
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function buildPolicyGuidance({ total, failures, risk, failureExamples, relatedMemories }) {
|
|
779
|
+
const guidance = {
|
|
780
|
+
level: 'none',
|
|
781
|
+
rationale: null,
|
|
782
|
+
saferAlternatives: [],
|
|
783
|
+
verificationSteps: [],
|
|
784
|
+
avoid: [],
|
|
785
|
+
hardGate: false,
|
|
786
|
+
}
|
|
787
|
+
if (total === 0) {
|
|
788
|
+
guidance.rationale =
|
|
789
|
+
'No historical evidence for this action yet. Treat as normal risk and record the outcome.'
|
|
790
|
+
return guidance
|
|
791
|
+
}
|
|
792
|
+
const verifiedMemories = relatedMemories.filter((record) => record.evidence)
|
|
793
|
+
const hasVerified = verifiedMemories.length > 0
|
|
794
|
+
if (risk >= 0.4 && failures >= 2) {
|
|
795
|
+
guidance.level = hasVerified ? 'actionable' : 'advisory'
|
|
796
|
+
guidance.rationale =
|
|
797
|
+
'Repeated historical failures. Prefer an alternative approach and add rollback/verification.'
|
|
798
|
+
guidance.avoid = failureExamples
|
|
799
|
+
.map((example) => example.summary)
|
|
800
|
+
.filter(Boolean)
|
|
801
|
+
.slice(0, 3)
|
|
802
|
+
} else if (risk >= 0.2 || hasVerified) {
|
|
803
|
+
guidance.level = hasVerified ? 'actionable' : 'advisory'
|
|
804
|
+
guidance.rationale = hasVerified
|
|
805
|
+
? 'Verified experience exists for this action; apply it before proceeding.'
|
|
806
|
+
: 'Moderate historical risk. Add verification before proceeding.'
|
|
807
|
+
} else {
|
|
808
|
+
guidance.level = 'advisory'
|
|
809
|
+
guidance.rationale = 'Low historical risk. Proceed with normal checks and record the outcome.'
|
|
810
|
+
}
|
|
811
|
+
if (hasVerified) {
|
|
812
|
+
guidance.saferAlternatives = verifiedMemories.slice(0, 3).map((record) => ({
|
|
813
|
+
memoryId: record.id,
|
|
814
|
+
reuseCount: record.evidence.reuseCount,
|
|
815
|
+
content: String(record.content || '').slice(0, 300),
|
|
816
|
+
}))
|
|
817
|
+
}
|
|
818
|
+
guidance.verificationSteps = [
|
|
819
|
+
'Add an explicit verification step after execution (dry-run, rollback plan, or assertion).',
|
|
820
|
+
'Record the outcome with mio.observer.ingest so the evidence base improves.',
|
|
821
|
+
]
|
|
822
|
+
return guidance
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function policyCheck(args = {}) {
|
|
826
|
+
const action = String(args.action || '').trim()
|
|
827
|
+
if (!action) throw new Error('policy.check requires action')
|
|
828
|
+
const project = args.project || projectName()
|
|
829
|
+
const actionTokens = tokenize(action)
|
|
830
|
+
const traces = readJsonl(tracePath).filter((event) => {
|
|
831
|
+
if (project && event.project && event.project !== project) return false
|
|
832
|
+
const haystack = tokenize(
|
|
833
|
+
`${event.event_type || ''} ${JSON.stringify(event.payload || {})}`
|
|
834
|
+
)
|
|
835
|
+
return actionTokens.some((token) => haystack.includes(token))
|
|
836
|
+
})
|
|
837
|
+
const total = traces.length
|
|
838
|
+
const failures = traces.filter((event) => isFailureOutcome(event.outcome)).length
|
|
839
|
+
const risk = total > 0 ? failures / total : null
|
|
840
|
+
const riskLevel = total === 0 ? 'unknown' : risk >= 0.4 ? 'high' : risk >= 0.2 ? 'moderate' : 'low'
|
|
841
|
+
const outcomeCounts = traces.reduce((counts, event) => {
|
|
842
|
+
const outcome = String(event.outcome || 'unknown').toLowerCase() || 'unknown'
|
|
843
|
+
counts[outcome] = (counts[outcome] || 0) + 1
|
|
844
|
+
return counts
|
|
845
|
+
}, {})
|
|
846
|
+
const failureExamples = traces
|
|
847
|
+
.filter((event) => isFailureOutcome(event.outcome))
|
|
848
|
+
.sort((a, b) => new Date(b.timestamp || 0).getTime() - new Date(a.timestamp || 0).getTime())
|
|
849
|
+
.slice(0, 3)
|
|
850
|
+
.map((event) => ({
|
|
851
|
+
trace_id: event.trace_id || event.id || null,
|
|
852
|
+
event_type: event.event_type || null,
|
|
853
|
+
outcome: event.outcome || null,
|
|
854
|
+
timestamp: event.timestamp || null,
|
|
855
|
+
agent: event.agent || null,
|
|
856
|
+
summary: summarizeTrace(event),
|
|
857
|
+
}))
|
|
858
|
+
let suggestion
|
|
859
|
+
if (total === 0) {
|
|
860
|
+
suggestion = 'No history for this action. Treat as normal risk and record the outcome.'
|
|
861
|
+
} else if (risk >= 0.4) {
|
|
862
|
+
suggestion = 'Historically risky. Prefer an alternative approach or add rollback/verification.'
|
|
863
|
+
} else if (risk >= 0.2) {
|
|
864
|
+
suggestion = 'Moderate risk. Add verification before proceeding.'
|
|
865
|
+
} else {
|
|
866
|
+
suggestion = 'Low historical risk. Proceed with normal checks.'
|
|
867
|
+
}
|
|
868
|
+
const evidence = loadEvidenceWeights()
|
|
869
|
+
const relatedMemories = readJsonl(memoryPath)
|
|
870
|
+
.filter((record) => scoreRecord(record, action, project, evidence) > 0)
|
|
871
|
+
.sort((a, b) => scoreRecord(b, action, project, evidence) - scoreRecord(a, action, project, evidence))
|
|
872
|
+
.slice(0, 3)
|
|
873
|
+
.map((record) => {
|
|
874
|
+
const ev = evidence.get(record.id)
|
|
875
|
+
return {
|
|
876
|
+
id: record.id,
|
|
877
|
+
timestamp: record.timestamp,
|
|
878
|
+
kind: record.kind,
|
|
879
|
+
content: record.content,
|
|
880
|
+
tags: record.tags,
|
|
881
|
+
evidence: ev
|
|
882
|
+
? { reuseCount: ev.reuseCount, confirmedCount: ev.confirmedCount }
|
|
883
|
+
: null,
|
|
884
|
+
}
|
|
885
|
+
})
|
|
886
|
+
return {
|
|
887
|
+
action,
|
|
888
|
+
project,
|
|
889
|
+
total,
|
|
890
|
+
failures,
|
|
891
|
+
risk: risk === null ? null : Number(risk.toFixed(3)),
|
|
892
|
+
riskLevel,
|
|
893
|
+
outcomeCounts,
|
|
894
|
+
failureExamples,
|
|
895
|
+
suggestion,
|
|
896
|
+
guidance: buildPolicyGuidance({
|
|
897
|
+
total,
|
|
898
|
+
failures,
|
|
899
|
+
risk,
|
|
900
|
+
failureExamples,
|
|
901
|
+
relatedMemories,
|
|
902
|
+
}),
|
|
903
|
+
related_memories: relatedMemories,
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function recordExperienceReuse(args = {}) {
|
|
908
|
+
const sourceAgent = String(args.sourceAgent || '').trim()
|
|
909
|
+
const targetAgent = String(args.targetAgent || '').trim()
|
|
910
|
+
const experienceId = String(args.experienceId || '').trim()
|
|
911
|
+
if (!sourceAgent) throw new Error('experience.reuse requires sourceAgent')
|
|
912
|
+
if (!targetAgent) throw new Error('experience.reuse requires targetAgent')
|
|
913
|
+
if (!experienceId) throw new Error('experience.reuse requires experienceId')
|
|
914
|
+
|
|
915
|
+
const project = args.project || projectName()
|
|
916
|
+
const evidence = {
|
|
917
|
+
id: createId('xfer'),
|
|
918
|
+
timestamp: new Date().toISOString(),
|
|
919
|
+
sourceAgent,
|
|
920
|
+
targetAgent,
|
|
921
|
+
experienceId,
|
|
922
|
+
reuse: args.reuse === true || args.reuse === 'true',
|
|
923
|
+
behaviorChanged: args.behaviorChanged === true || args.behaviorChanged === 'true',
|
|
924
|
+
outcomeImproved: args.outcomeImproved === true || args.outcomeImproved === 'true',
|
|
925
|
+
project,
|
|
926
|
+
source: args.source || 'agent_report',
|
|
927
|
+
notes: args.notes || null,
|
|
928
|
+
}
|
|
929
|
+
appendJsonl(experienceReusePath, evidence)
|
|
930
|
+
|
|
931
|
+
const report = loadPhase0(dataDir, project)
|
|
932
|
+
return {
|
|
933
|
+
recorded: true,
|
|
934
|
+
evidence,
|
|
935
|
+
phase0: summarizePhase0(report),
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
function confirmExperienceReuse(args = {}) {
|
|
940
|
+
const id = String(args.id || '').trim()
|
|
941
|
+
if (!id) throw new Error('experience.confirm requires id of an auto-claimed reuse record')
|
|
942
|
+
const records = readJsonl(experienceReusePath)
|
|
943
|
+
const index = records.findIndex((record) => record && record.id === id)
|
|
944
|
+
if (index < 0) {
|
|
945
|
+
throw new Error(`No experience reuse record found with id ${id}`)
|
|
946
|
+
}
|
|
947
|
+
const record = records[index]
|
|
948
|
+
if (record.source !== 'auto_claim') {
|
|
949
|
+
throw new Error('experience.confirm only applies to source=auto_claim reuse records')
|
|
950
|
+
}
|
|
951
|
+
const outcomeImproved =
|
|
952
|
+
args.outcomeImproved === undefined || args.outcomeImproved === null
|
|
953
|
+
? record.outcomeImproved === true
|
|
954
|
+
: args.outcomeImproved === true || args.outcomeImproved === 'true'
|
|
955
|
+
const updated = {
|
|
956
|
+
...record,
|
|
957
|
+
behaviorChanged: true,
|
|
958
|
+
outcomeImproved,
|
|
959
|
+
confirmed: true,
|
|
960
|
+
confirmedAt: new Date().toISOString(),
|
|
961
|
+
confirmedBy: String(args.confirmedBy || 'agent').trim() || 'agent',
|
|
962
|
+
notes: args.notes ? String(args.notes).trim() : record.notes || null,
|
|
963
|
+
}
|
|
964
|
+
records[index] = updated
|
|
965
|
+
writeJsonl(experienceReusePath, records)
|
|
966
|
+
|
|
967
|
+
const report = loadPhase0(dataDir, updated.project)
|
|
968
|
+
return {
|
|
969
|
+
confirmed: true,
|
|
970
|
+
evidence: updated,
|
|
971
|
+
phase0: summarizePhase0(report),
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function listExperienceReuse(args = {}) {
|
|
976
|
+
const project = args.project || projectName()
|
|
977
|
+
const status = String(args.status || 'all').trim().toLowerCase()
|
|
978
|
+
const targetAgent = String(args.targetAgent || '').trim().toLowerCase()
|
|
979
|
+
const limit = Math.min(Math.max(Number(args.limit) || 20, 1), 100)
|
|
980
|
+
|
|
981
|
+
let records = readJsonl(experienceReusePath)
|
|
982
|
+
if (project) records = records.filter((record) => record.project === project)
|
|
983
|
+
if (targetAgent) {
|
|
984
|
+
records = records.filter(
|
|
985
|
+
(record) => String(record.targetAgent || '').toLowerCase() === targetAgent,
|
|
986
|
+
)
|
|
987
|
+
}
|
|
988
|
+
const matcher = REUSE_STATUS_FILTERS[status]
|
|
989
|
+
if (matcher) records = records.filter(matcher)
|
|
990
|
+
records.sort((a, b) => {
|
|
991
|
+
const at = new Date(a.timestamp || 0).getTime()
|
|
992
|
+
const bt = new Date(b.timestamp || 0).getTime()
|
|
993
|
+
return bt - at
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
const visible = records.slice(0, limit).map((record) => ({
|
|
997
|
+
id: record.id,
|
|
998
|
+
source: record.source,
|
|
999
|
+
sourceAgent: record.sourceAgent,
|
|
1000
|
+
targetAgent: record.targetAgent,
|
|
1001
|
+
experienceId: record.experienceId,
|
|
1002
|
+
reuse: record.reuse === true || record.reuse === 'true',
|
|
1003
|
+
behaviorChanged: record.behaviorChanged === true || record.behaviorChanged === 'true',
|
|
1004
|
+
outcomeImproved: record.outcomeImproved === true || record.outcomeImproved === 'true',
|
|
1005
|
+
confirmed: record.confirmed === true,
|
|
1006
|
+
confirmedBy: record.confirmedBy || null,
|
|
1007
|
+
traceId: record.traceId || null,
|
|
1008
|
+
timestamp: record.timestamp,
|
|
1009
|
+
project: record.project,
|
|
1010
|
+
notes:
|
|
1011
|
+
typeof record.notes === 'string' && record.notes ? record.notes.slice(0, 300) : null,
|
|
1012
|
+
}))
|
|
1013
|
+
|
|
1014
|
+
return {
|
|
1015
|
+
project: project || null,
|
|
1016
|
+
status,
|
|
1017
|
+
count: visible.length,
|
|
1018
|
+
total: records.length,
|
|
1019
|
+
records: visible,
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
function registerAgent(args = {}) {
|
|
1024
|
+
const agentId = String(args.agentId || '').trim()
|
|
1025
|
+
if (!agentId) throw new Error('agent.register requires a non-empty agentId')
|
|
1026
|
+
const project = args.project || projectName()
|
|
1027
|
+
const hostType = args.hostType || 'mcp'
|
|
1028
|
+
const capabilities = Array.isArray(args.capabilities) ? args.capabilities : []
|
|
1029
|
+
const now = new Date().toISOString()
|
|
1030
|
+
const agents = readJsonl(agentsPath)
|
|
1031
|
+
const existing = agents.find((a) => a.agentId === agentId && a.project === project)
|
|
1032
|
+
let result
|
|
1033
|
+
if (existing) {
|
|
1034
|
+
existing.lastSeenAt = now
|
|
1035
|
+
existing.sessionCount = (existing.sessionCount || 1) + 1
|
|
1036
|
+
existing.hostType = hostType
|
|
1037
|
+
if (capabilities.length > 0) existing.capabilities = capabilities
|
|
1038
|
+
result = existing
|
|
1039
|
+
} else {
|
|
1040
|
+
result = {
|
|
1041
|
+
id: createId('agent'),
|
|
1042
|
+
agentId,
|
|
1043
|
+
hostType,
|
|
1044
|
+
capabilities,
|
|
1045
|
+
registeredAt: now,
|
|
1046
|
+
lastSeenAt: now,
|
|
1047
|
+
sessionCount: 1,
|
|
1048
|
+
project,
|
|
1049
|
+
}
|
|
1050
|
+
agents.push(result)
|
|
1051
|
+
}
|
|
1052
|
+
writeJsonl(agentsPath, agents)
|
|
1053
|
+
return { id: result.id, agentId: result.agentId, registered: !existing, sessionCount: result.sessionCount, hostType: result.hostType, capabilities: result.capabilities }
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
function listAgents(args = {}) {
|
|
1057
|
+
const project = args.project || null
|
|
1058
|
+
let agents = readJsonl(agentsPath)
|
|
1059
|
+
if (project) agents = agents.filter((a) => a.project === project)
|
|
1060
|
+
return {
|
|
1061
|
+
count: agents.length,
|
|
1062
|
+
agents: agents.map((a) => ({
|
|
1063
|
+
id: a.id, agentId: a.agentId, hostType: a.hostType,
|
|
1064
|
+
capabilities: a.capabilities, registeredAt: a.registeredAt,
|
|
1065
|
+
lastSeenAt: a.lastSeenAt, sessionCount: a.sessionCount,
|
|
1066
|
+
taskCount: a.taskCount || 0, successCount: a.successCount || 0, failureCount: a.failureCount || 0,
|
|
1067
|
+
project: a.project,
|
|
1068
|
+
})),
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function reportAgent(args = {}) {
|
|
1073
|
+
const targetAgent = args.agentId || null
|
|
1074
|
+
const project = args.project || projectName()
|
|
1075
|
+
const agents = readJsonl(agentsPath)
|
|
1076
|
+
const filtered = agents.filter((a) =>
|
|
1077
|
+
(!targetAgent || a.agentId === targetAgent) && (!project || a.project === project)
|
|
1078
|
+
)
|
|
1079
|
+
const traces = readJsonl(tracePath)
|
|
1080
|
+
const memories = readJsonl(memoryPath)
|
|
1081
|
+
const reuses = readJsonl(experienceReusePath)
|
|
1082
|
+
const reports = filtered.map((agent) => {
|
|
1083
|
+
const agentTraces = traces.filter((t) => t.agent === agent.agentId)
|
|
1084
|
+
const outcomes = agentTraces.filter((t) => t.event_type === 'task_outcome')
|
|
1085
|
+
const successes = outcomes.filter((t) => t.outcome === 'success').length
|
|
1086
|
+
const failures = outcomes.filter((t) => t.outcome === 'failure').length
|
|
1087
|
+
const agentMemories = memories.filter((m) => m.source === agent.agentId)
|
|
1088
|
+
const agentReuses = reuses.filter((r) => r.sourceAgent === agent.agentId || r.targetAgent === agent.agentId)
|
|
1089
|
+
const verified = agentReuses.filter((r) => r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved)
|
|
1090
|
+
const regTasks = agent.taskCount || 0
|
|
1091
|
+
const regSuccess = agent.successCount || 0
|
|
1092
|
+
const regFail = agent.failureCount || 0
|
|
1093
|
+
const taskTotal = regTasks > 0 ? regTasks : outcomes.length
|
|
1094
|
+
const taskSuccess = regTasks > 0 ? regSuccess : successes
|
|
1095
|
+
const taskFail = regTasks > 0 ? regFail : failures
|
|
1096
|
+
return {
|
|
1097
|
+
agentId: agent.agentId,
|
|
1098
|
+
hostType: agent.hostType,
|
|
1099
|
+
sessionCount: agent.sessionCount,
|
|
1100
|
+
registeredAt: agent.registeredAt,
|
|
1101
|
+
lastSeenAt: agent.lastSeenAt,
|
|
1102
|
+
taskOutcomes: { total: taskTotal, success: taskSuccess, failure: taskFail, successRate: taskTotal > 0 ? Math.round((taskSuccess / taskTotal) * 100) : 0 },
|
|
1103
|
+
memories: agentMemories.length,
|
|
1104
|
+
experienceReuses: { total: agentReuses.length, verified: verified.length },
|
|
1105
|
+
active: (Date.now() - new Date(agent.lastSeenAt).getTime()) < 24 * 60 * 60 * 1000,
|
|
1106
|
+
}
|
|
1107
|
+
})
|
|
1108
|
+
return { project, count: reports.length, reports }
|
|
1109
|
+
}
|
|
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
|
+
|
|
1145
|
+
function routeTask(args = {}) {
|
|
1146
|
+
const task = String(args.task || '').trim()
|
|
1147
|
+
if (!task) throw new Error('task.route requires a non-empty task')
|
|
1148
|
+
const project = args.project || projectName()
|
|
1149
|
+
const scope = normalizeScope(args.scope)
|
|
1150
|
+
const limit = Math.min(Math.max(Number(args.limit) || 5, 1), 10)
|
|
1151
|
+
|
|
1152
|
+
const memories = readJsonl(memoryPath).filter((record) => record.archived !== true)
|
|
1153
|
+
const memoryById = new Map(memories.map((record) => [record.id, record]))
|
|
1154
|
+
const evidence = loadEvidenceWeights()
|
|
1155
|
+
|
|
1156
|
+
// Match verified reuse experiences whose source memory is relevant to the task.
|
|
1157
|
+
const routeByMemory = new Map()
|
|
1158
|
+
for (const record of readJsonl(experienceReusePath)) {
|
|
1159
|
+
if (!REUSE_STATUS_FILTERS.verified(record)) continue
|
|
1160
|
+
const memory = memoryById.get(record.experienceId)
|
|
1161
|
+
if (!memory || !matchesProjectScope(memory, project, scope)) continue
|
|
1162
|
+
const score = scoreRecord(memory, task, project)
|
|
1163
|
+
if (score <= 0) continue
|
|
1164
|
+
let entry = routeByMemory.get(record.experienceId)
|
|
1165
|
+
if (!entry) {
|
|
1166
|
+
entry = {
|
|
1167
|
+
experienceId: record.experienceId,
|
|
1168
|
+
memory,
|
|
1169
|
+
weight: 0,
|
|
1170
|
+
confirmed: false,
|
|
1171
|
+
sourceAgents: new Set(),
|
|
1172
|
+
targetAgents: new Set(),
|
|
1173
|
+
reuseCount: 0,
|
|
1174
|
+
}
|
|
1175
|
+
routeByMemory.set(record.experienceId, entry)
|
|
1176
|
+
}
|
|
1177
|
+
entry.reuseCount += 1
|
|
1178
|
+
if (record.confirmed === true) entry.confirmed = true
|
|
1179
|
+
if (record.sourceAgent) entry.sourceAgents.add(record.sourceAgent)
|
|
1180
|
+
if (record.targetAgent) entry.targetAgents.add(record.targetAgent)
|
|
1181
|
+
entry.weight = Math.max(entry.weight, score + (entry.confirmed ? 2 : 1.5))
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
const routes = [...routeByMemory.values()]
|
|
1185
|
+
.sort((a, b) => b.weight - a.weight)
|
|
1186
|
+
.slice(0, limit)
|
|
1187
|
+
.map((entry) => ({
|
|
1188
|
+
experienceId: entry.experienceId,
|
|
1189
|
+
reuseCount: entry.reuseCount,
|
|
1190
|
+
confirmed: entry.confirmed,
|
|
1191
|
+
sourceAgents: [...entry.sourceAgents].filter(Boolean),
|
|
1192
|
+
targetAgents: [...entry.targetAgents].filter(Boolean),
|
|
1193
|
+
score: Number(entry.weight.toFixed(2)),
|
|
1194
|
+
memory: {
|
|
1195
|
+
id: entry.memory.id,
|
|
1196
|
+
timestamp: entry.memory.timestamp,
|
|
1197
|
+
kind: entry.memory.kind,
|
|
1198
|
+
content: entry.memory.content,
|
|
1199
|
+
tags: entry.memory.tags || [],
|
|
1200
|
+
project: entry.memory.project || null,
|
|
1201
|
+
scope: entry.memory.scope || 'project',
|
|
1202
|
+
},
|
|
1203
|
+
}))
|
|
1204
|
+
|
|
1205
|
+
// Broader context: top related memories not already routed.
|
|
1206
|
+
const routedIds = new Set(routes.map((route) => route.experienceId))
|
|
1207
|
+
const scoreRelated = (record) =>
|
|
1208
|
+
scoreRecord(record, task, project, evidence) +
|
|
1209
|
+
(scope === 'all' && isGlobalRecord(record) ? 0.5 : 0)
|
|
1210
|
+
const relatedMemories = memories
|
|
1211
|
+
.filter((record) => !routedIds.has(record.id))
|
|
1212
|
+
.filter((record) => matchesProjectScope(record, project, scope))
|
|
1213
|
+
.filter((record) => scoreRelated(record) > 0)
|
|
1214
|
+
.sort((a, b) => scoreRelated(b) - scoreRelated(a))
|
|
1215
|
+
.slice(0, Math.max(limit, 5))
|
|
1216
|
+
.map((record) => {
|
|
1217
|
+
const ev = evidence.get(record.id)
|
|
1218
|
+
return {
|
|
1219
|
+
id: record.id,
|
|
1220
|
+
timestamp: record.timestamp,
|
|
1221
|
+
kind: record.kind,
|
|
1222
|
+
content: record.content,
|
|
1223
|
+
tags: record.tags || [],
|
|
1224
|
+
project: record.project || null,
|
|
1225
|
+
scope: record.scope || 'project',
|
|
1226
|
+
evidence: ev
|
|
1227
|
+
? { reuseCount: ev.reuseCount, confirmedCount: ev.confirmedCount }
|
|
1228
|
+
: null,
|
|
1229
|
+
}
|
|
1230
|
+
})
|
|
1231
|
+
|
|
1232
|
+
// Feed the query log so a later task_outcome can auto-claim route-driven reuse.
|
|
1233
|
+
if (routes.length > 0 || relatedMemories.length > 0) {
|
|
1234
|
+
const now = Date.now()
|
|
1235
|
+
const entries = loadRecentQueries()
|
|
1236
|
+
entries.push({
|
|
1237
|
+
agent: runtimeAgentId() || 'mcp',
|
|
1238
|
+
project,
|
|
1239
|
+
query: task,
|
|
1240
|
+
resultIds: [...routedIds, ...relatedMemories.map((record) => record.id)].filter(Boolean),
|
|
1241
|
+
resultSources: [...routes.map(() => 'experience'), ...relatedMemories.map((record) => record.source || null)],
|
|
1242
|
+
timestamp: now,
|
|
1243
|
+
expiresAt: now + REUSE_MATCH_WINDOW_MS,
|
|
1244
|
+
})
|
|
1245
|
+
persistRecentQueries(entries)
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
let suggestion
|
|
1249
|
+
if (routes.length > 0) {
|
|
1250
|
+
suggestion =
|
|
1251
|
+
`Route ${routes.length} verified experience(s) to this task; apply the top match first and record the outcome with mio.observer.ingest / mio.experience.reuse.`
|
|
1252
|
+
} else if (relatedMemories.length > 0) {
|
|
1253
|
+
suggestion =
|
|
1254
|
+
'No verified experience matches yet. Consult the related memories, then record the outcome so this task can seed future routes.'
|
|
1255
|
+
} else {
|
|
1256
|
+
suggestion =
|
|
1257
|
+
'No matching experience or memory found. Proceed with care and record the outcome to grow the experience base.'
|
|
1258
|
+
}
|
|
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
|
+
|
|
1280
|
+
return {
|
|
1281
|
+
task,
|
|
1282
|
+
project,
|
|
1283
|
+
scope,
|
|
1284
|
+
count: routes.length,
|
|
1285
|
+
routes,
|
|
1286
|
+
relatedMemories,
|
|
1287
|
+
agentHealth,
|
|
1288
|
+
summary: {
|
|
1289
|
+
verifiedRoutes: routes.length,
|
|
1290
|
+
relatedMemories: relatedMemories.length,
|
|
1291
|
+
routingSignal,
|
|
1292
|
+
suggestion,
|
|
1293
|
+
},
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
function phase0Report(args = {}) {
|
|
1297
|
+
const project = args.project || projectName()
|
|
1298
|
+
const report = loadPhase0(dataDir, project)
|
|
1299
|
+
const format = String(args.format || 'json').trim().toLowerCase()
|
|
1300
|
+
if (format === 'markdown') {
|
|
1301
|
+
return {
|
|
1302
|
+
project,
|
|
1303
|
+
format: 'markdown',
|
|
1304
|
+
markdown: renderPhase0Markdown(report),
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return {
|
|
1308
|
+
project,
|
|
1309
|
+
...report,
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
const TOOLS = [
|
|
1314
|
+
{
|
|
1315
|
+
name: 'mio.memory.query',
|
|
1316
|
+
description: 'Search local Mio project memory for context, decisions, and historical problems.',
|
|
1317
|
+
inputSchema: {
|
|
1318
|
+
type: 'object',
|
|
1319
|
+
properties: {
|
|
1320
|
+
query: { type: 'string', description: 'Question or topic to recall.' },
|
|
1321
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current directory name.' },
|
|
1322
|
+
limit: { type: 'number', description: 'Maximum results, between 1 and 20. Defaults to 5.' },
|
|
1323
|
+
kind: { type: 'string', description: 'Only return records with this kind (decision, context, problem, note).' },
|
|
1324
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Only return records containing all of these tags (AND).' },
|
|
1325
|
+
scope: { type: 'string', description: 'Search scope: project (default), global, or all.' },
|
|
1326
|
+
},
|
|
1327
|
+
required: ['query'],
|
|
1328
|
+
},
|
|
1329
|
+
},
|
|
1330
|
+
{
|
|
1331
|
+
name: 'mio.memory.archive',
|
|
1332
|
+
description: 'Archive (or restore) memory records by id. Archived records are excluded from memory.query and memory.analyze.',
|
|
1333
|
+
inputSchema: {
|
|
1334
|
+
type: 'object',
|
|
1335
|
+
properties: {
|
|
1336
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Memory record ids to archive or restore.' },
|
|
1337
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1338
|
+
restore: { type: 'boolean', description: 'When true, un-archive the given ids instead.' },
|
|
1339
|
+
reason: { type: 'string', description: 'Optional archive reason.' },
|
|
1340
|
+
},
|
|
1341
|
+
required: ['ids'],
|
|
1342
|
+
},
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
name: 'mio.memory.migrate',
|
|
1346
|
+
description: 'Migrate memory records between project and global layers (P3). Moving to global makes the record reusable across projects (project set to null); moving to project restores project scoping.',
|
|
1347
|
+
inputSchema: {
|
|
1348
|
+
type: 'object',
|
|
1349
|
+
properties: {
|
|
1350
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Memory record ids to migrate.' },
|
|
1351
|
+
scope: { type: 'string', description: 'Target layer: global or project.' },
|
|
1352
|
+
project: { type: 'string', description: 'Target project for project scope. Defaults to current repository.' },
|
|
1353
|
+
reason: { type: 'string', description: 'Optional migration reason.' },
|
|
1354
|
+
},
|
|
1355
|
+
required: ['ids', 'scope'],
|
|
1356
|
+
},
|
|
1357
|
+
},
|
|
1358
|
+
{
|
|
1359
|
+
name: 'mio.memory.record',
|
|
1360
|
+
description: 'Record a decision, project context, or historical problem into local Mio memory.',
|
|
1361
|
+
inputSchema: {
|
|
1362
|
+
type: 'object',
|
|
1363
|
+
properties: {
|
|
1364
|
+
content: { type: 'string', description: 'Memory content.' },
|
|
1365
|
+
kind: { type: 'string', description: 'Memory kind, such as decision, context, or problem.' },
|
|
1366
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Optional tags.' },
|
|
1367
|
+
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1368
|
+
scope: { type: 'string', description: 'Record scope: project (default) or global.' },
|
|
1369
|
+
source: { type: 'string', description: 'Recording source. Defaults to mcp.' },
|
|
1370
|
+
},
|
|
1371
|
+
required: ['content'],
|
|
1372
|
+
},
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
name: 'mio.observer.ingest',
|
|
1376
|
+
description: 'Ingest an Agent Trace event such as tool_call, error, retry, or task_outcome.',
|
|
1377
|
+
inputSchema: {
|
|
1378
|
+
type: 'object',
|
|
1379
|
+
properties: {
|
|
1380
|
+
trace_id: { type: 'string', description: 'Stable trace identifier.' },
|
|
1381
|
+
event_type: { type: 'string', description: 'Event type, such as tool_call, error, retry, task_outcome.' },
|
|
1382
|
+
payload: { type: 'object', description: 'Event payload.' },
|
|
1383
|
+
outcome: { type: 'string', description: 'Optional outcome: success, failure, error, aborted, retry.' },
|
|
1384
|
+
agent: { type: 'string', description: 'Agent identifier.' },
|
|
1385
|
+
host: { type: 'string', description: 'Host name. Defaults to mcp.' },
|
|
1386
|
+
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1387
|
+
},
|
|
1388
|
+
required: ['trace_id', 'event_type'],
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
name: 'mio.trace.query',
|
|
1393
|
+
description: 'Query the observer trace log (traces.jsonl): task outcomes, tool errors, and ingested events. Newest first, with counts by event_type and outcome over the full filtered set.',
|
|
1394
|
+
inputSchema: {
|
|
1395
|
+
type: 'object',
|
|
1396
|
+
properties: {
|
|
1397
|
+
event_type: { type: 'string', description: 'Filter by event type (e.g. task_outcome, error, tool_call).' },
|
|
1398
|
+
outcome: { type: 'string', description: 'Filter by outcome (success, failure, error, aborted).' },
|
|
1399
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository; pass empty string for all projects.' },
|
|
1400
|
+
agent: { type: 'string', description: 'Filter by agent identifier (e.g. codex, claude-code, cli).' },
|
|
1401
|
+
host: { type: 'string', description: 'Filter by host identifier.' },
|
|
1402
|
+
since: { type: 'string', description: 'Only traces at or after this time (ISO 8601 or epoch ms).' },
|
|
1403
|
+
until: { type: 'string', description: 'Only traces at or before this time (ISO 8601 or epoch ms).' },
|
|
1404
|
+
limit: { type: 'number', description: 'Maximum traces to return (1-200). Defaults to 20.' },
|
|
1405
|
+
include_payload: { type: 'boolean', description: 'Include full payload objects (default true). Set false for compact listings.' },
|
|
1406
|
+
},
|
|
1407
|
+
},
|
|
1408
|
+
},
|
|
1409
|
+
{
|
|
1410
|
+
name: 'mio.digest.generate',
|
|
1411
|
+
description: 'Generate an actionable digest from recent traces/memory/reuse data: task throughput and success rates per agent, project activity, error hotspots, reuse evidence, and rule-based suggestions. Returns the report object with a markdown field.',
|
|
1412
|
+
inputSchema: {
|
|
1413
|
+
type: 'object',
|
|
1414
|
+
properties: {
|
|
1415
|
+
days: { type: 'number', description: 'Look-back window in days. Defaults to 7.' },
|
|
1416
|
+
project: { type: 'string', description: 'Restrict the digest to one project. Defaults to all projects.' },
|
|
1417
|
+
},
|
|
1418
|
+
},
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
name: 'mio.observer.subscribe',
|
|
1422
|
+
description: 'Subscribe to observer events (project, event types, optional topic). New matching events are delivered via mio.observer.digest.',
|
|
1423
|
+
inputSchema: {
|
|
1424
|
+
type: 'object',
|
|
1425
|
+
properties: {
|
|
1426
|
+
eventTypes: { type: 'array', items: { type: 'string' }, description: 'Event types to watch, e.g. error, retry, task_outcome. Empty means all.' },
|
|
1427
|
+
project: { type: 'string', description: 'Project name. Defaults to current repository.' },
|
|
1428
|
+
topic: { type: 'string', description: 'Optional keyword; only events whose payload mentions it match.' },
|
|
1429
|
+
ttlDays: { type: 'number', description: 'Subscription lifetime in days. Defaults to 30.' },
|
|
1430
|
+
},
|
|
1431
|
+
},
|
|
1432
|
+
},
|
|
1433
|
+
{
|
|
1434
|
+
name: 'mio.observer.digest',
|
|
1435
|
+
description: 'Return new observer events matching active subscriptions since the last digest, and advance the cursor.',
|
|
1436
|
+
inputSchema: {
|
|
1437
|
+
type: 'object',
|
|
1438
|
+
properties: {
|
|
1439
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1440
|
+
eventTypes: { type: 'array', items: { type: 'string' }, description: 'Optional extra event type filter.' },
|
|
1441
|
+
limit: { type: 'number', description: 'Maximum events to return, 1-100. Defaults to 20.' },
|
|
1442
|
+
},
|
|
1443
|
+
},
|
|
1444
|
+
},
|
|
1445
|
+
{
|
|
1446
|
+
name: 'mio.policy.check',
|
|
1447
|
+
description: 'Check historical risk for an action before executing it, returning risk level, outcome counts, and recent failure examples.',
|
|
1448
|
+
inputSchema: {
|
|
1449
|
+
type: 'object',
|
|
1450
|
+
properties: {
|
|
1451
|
+
action: { type: 'string', description: 'The action or tool operation to check.' },
|
|
1452
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current directory name.' },
|
|
1453
|
+
context: { type: 'string', description: 'Optional additional context.' },
|
|
1454
|
+
},
|
|
1455
|
+
required: ['action'],
|
|
1456
|
+
},
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: 'mio.memory.analyze',
|
|
1460
|
+
description: 'Analyze local Mio memory quality: kind distribution, duplicate groups, and low-quality records.',
|
|
1461
|
+
inputSchema: {
|
|
1462
|
+
type: 'object',
|
|
1463
|
+
properties: {
|
|
1464
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1465
|
+
limit: { type: 'number', description: 'Maximum duplicates/low-quality examples to return, 1-20. Defaults to 5.' },
|
|
1466
|
+
},
|
|
1467
|
+
},
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
name: 'mio.experience.reuse',
|
|
1471
|
+
description: 'Record Phase 0 evidence of cross-agent experience reuse.',
|
|
1472
|
+
inputSchema: {
|
|
1473
|
+
type: 'object',
|
|
1474
|
+
properties: {
|
|
1475
|
+
sourceAgent: { type: 'string', description: 'Agent that produced the experience.' },
|
|
1476
|
+
targetAgent: { type: 'string', description: 'Agent that reused the experience.' },
|
|
1477
|
+
experienceId: { type: 'string', description: 'Stable experience or memory id.' },
|
|
1478
|
+
reuse: { type: 'boolean', description: 'True when memory.query returned historical experience and it was reused.' },
|
|
1479
|
+
behaviorChanged: { type: 'boolean', description: 'True when target Agent behavior changed because of the reused experience.' },
|
|
1480
|
+
outcomeImproved: { type: 'boolean', description: 'True when the task outcome improved.' },
|
|
1481
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1482
|
+
notes: { type: 'string', description: 'Optional evidence notes.' },
|
|
1483
|
+
},
|
|
1484
|
+
required: ['sourceAgent', 'targetAgent', 'experienceId', 'reuse', 'behaviorChanged', 'outcomeImproved'],
|
|
1485
|
+
},
|
|
1486
|
+
},
|
|
1487
|
+
{
|
|
1488
|
+
name: 'mio.experience.confirm',
|
|
1489
|
+
description: 'Confirm an auto-claimed reuse record as verified reuse evidence.',
|
|
1490
|
+
inputSchema: {
|
|
1491
|
+
type: 'object',
|
|
1492
|
+
properties: {
|
|
1493
|
+
id: { type: 'string', description: 'Id of the auto-claimed reuse record returned by mio.observer.ingest.' },
|
|
1494
|
+
confirmedBy: { type: 'string', description: 'Who confirmed the reuse: agent or human. Defaults to agent.' },
|
|
1495
|
+
outcomeImproved: { type: 'boolean', description: 'Override: true when the reused experience improved the task outcome. Defaults to the auto-claim value.' },
|
|
1496
|
+
notes: { type: 'string', description: 'Optional confirmation notes.' },
|
|
1497
|
+
},
|
|
1498
|
+
required: ['id'],
|
|
1499
|
+
},
|
|
1500
|
+
},
|
|
1501
|
+
{
|
|
1502
|
+
name: 'mio.experience.list',
|
|
1503
|
+
description: 'List Phase 0 experience reuse evidence, optionally filtered by status or target agent.',
|
|
1504
|
+
inputSchema: {
|
|
1505
|
+
type: 'object',
|
|
1506
|
+
properties: {
|
|
1507
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1508
|
+
status: { type: 'string', description: 'Filter: pending (auto-claim awaiting confirmation), confirmed, verified, auto_claim, agent_report, all. Defaults to all.' },
|
|
1509
|
+
targetAgent: { type: 'string', description: 'Filter records by target agent.' },
|
|
1510
|
+
limit: { type: 'number', description: 'Maximum records to return, 1-100. Defaults to 20.' },
|
|
1511
|
+
},
|
|
1512
|
+
},
|
|
1513
|
+
},
|
|
1514
|
+
{
|
|
1515
|
+
name: 'mio.phase0.report',
|
|
1516
|
+
description: 'Generate the current Phase 0 cross-agent validation report, as JSON or rendered Markdown.',
|
|
1517
|
+
inputSchema: {
|
|
1518
|
+
type: 'object',
|
|
1519
|
+
properties: {
|
|
1520
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1521
|
+
format: { type: 'string', description: 'Output format: json or markdown. Defaults to json.' },
|
|
1522
|
+
},
|
|
1523
|
+
},
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
name: 'mio.task.route',
|
|
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).',
|
|
1528
|
+
inputSchema: {
|
|
1529
|
+
type: 'object',
|
|
1530
|
+
properties: {
|
|
1531
|
+
task: { type: 'string', description: 'Task description to route, e.g. \'fix IPC communication bug\'.' },
|
|
1532
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1533
|
+
scope: { type: 'string', description: 'Search scope: project (default), global, or all.' },
|
|
1534
|
+
limit: { type: 'number', description: 'Maximum routes, between 1 and 10. Defaults to 5.' },
|
|
1535
|
+
},
|
|
1536
|
+
required: ['task'],
|
|
1537
|
+
},
|
|
1538
|
+
},
|
|
1539
|
+
{
|
|
1540
|
+
name: 'mio.agent.register',
|
|
1541
|
+
description: 'Register or update an agent in the Mio Agent Registry. Upserts by agentId+project; updates lastSeenAt and sessionCount.',
|
|
1542
|
+
inputSchema: {
|
|
1543
|
+
type: 'object',
|
|
1544
|
+
properties: {
|
|
1545
|
+
agentId: { type: 'string', description: 'Unique agent identifier (e.g. codex, opencode).' },
|
|
1546
|
+
hostType: { type: 'string', description: 'Host type: mcp, observer, or plugin. Defaults to mcp.' },
|
|
1547
|
+
capabilities: { type: 'array', items: { type: 'string' }, description: 'Agent capabilities.' },
|
|
1548
|
+
project: { type: 'string', description: 'Project name. Defaults to current directory name.' },
|
|
1549
|
+
},
|
|
1550
|
+
required: ['agentId'],
|
|
1551
|
+
},
|
|
1552
|
+
},
|
|
1553
|
+
{
|
|
1554
|
+
name: 'mio.agent.list',
|
|
1555
|
+
description: 'List all registered agents, optionally filtered by project.',
|
|
1556
|
+
inputSchema: {
|
|
1557
|
+
type: 'object',
|
|
1558
|
+
properties: {
|
|
1559
|
+
project: { type: 'string', description: 'Project name filter. Omit for all agents.' },
|
|
1560
|
+
},
|
|
1561
|
+
},
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
name: 'mio.agent.report',
|
|
1565
|
+
description: 'Performance report for one or all agents, aggregating task outcomes, memory, and experience reuse.',
|
|
1566
|
+
inputSchema: {
|
|
1567
|
+
type: 'object',
|
|
1568
|
+
properties: {
|
|
1569
|
+
agentId: { type: 'string', description: 'Agent to report on. Omit for all agents.' },
|
|
1570
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1571
|
+
},
|
|
1572
|
+
},
|
|
1573
|
+
},
|
|
1666
1574
|
{
|
|
1667
1575
|
name: 'mio.evolution.report',
|
|
1668
1576
|
description: 'Generate cross-agent evolution report: ecosystem summary, agent performance comparison, cross-agent patterns, memory health, and optimization suggestions (ADR-017 Evolution plane).',
|
|
1669
|
-
inputSchema: {
|
|
1670
|
-
type: 'object',
|
|
1671
|
-
properties: {
|
|
1672
|
-
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1673
|
-
period: { type: 'string', description: 'Time window: 24h, 7d, 30d, or all (default).' },
|
|
1674
|
-
},
|
|
1577
|
+
inputSchema: {
|
|
1578
|
+
type: 'object',
|
|
1579
|
+
properties: {
|
|
1580
|
+
project: { type: 'string', description: 'Project name filter. Defaults to current repository.' },
|
|
1581
|
+
period: { type: 'string', description: 'Time window: 24h, 7d, 30d, or all (default).' },
|
|
1582
|
+
},
|
|
1675
1583
|
},
|
|
1676
1584
|
},
|
|
1677
1585
|
{
|
|
@@ -1774,459 +1682,463 @@ const TOOLS = [
|
|
|
1774
1682
|
name: 'mio.task.record_outcome',
|
|
1775
1683
|
description: 'Record task outcome with agent registry update and auto-claim experience reuse in one atomic call. Combines observer.ingest + agent stats + auto-claim for a complete task lifecycle closure.',
|
|
1776
1684
|
inputSchema: {
|
|
1777
|
-
type: 'object',
|
|
1778
|
-
properties: {
|
|
1779
|
-
outcome: { type: 'string', description: 'Task outcome: success, failure, or aborted.' },
|
|
1780
|
-
task: { type: 'string', description: 'Brief task description.' },
|
|
1781
|
-
summary: { type: 'string', description: 'Optional task summary.' },
|
|
1782
|
-
verification: { type: 'string', description: 'Optional verification details.' },
|
|
1783
|
-
agentId: { type: 'string', description: 'Agent identifier. Defaults to MIO_CONTEXT agentId.' },
|
|
1784
|
-
project: { type: 'string', description: 'Project name. Defaults to current repository.' },
|
|
1785
|
-
traceId: { type: 'string', description: 'Optional custom trace ID. Auto-generated if omitted.' },
|
|
1786
|
-
},
|
|
1787
|
-
required: ['outcome'],
|
|
1788
|
-
},
|
|
1789
|
-
},
|
|
1790
|
-
{
|
|
1791
|
-
name: 'mio.creativity.status',
|
|
1792
|
-
description: 'Show creativity engine status: hypothesis counts, active/validated/rejected, recent top ideas.',
|
|
1793
|
-
inputSchema: { type: 'object', properties: {} },
|
|
1794
|
-
},
|
|
1795
|
-
{
|
|
1796
|
-
name: 'mio.creativity.list',
|
|
1797
|
-
description: 'List creativity hypotheses with optional status filter.',
|
|
1798
|
-
inputSchema: {
|
|
1799
|
-
type: 'object',
|
|
1800
|
-
properties: {
|
|
1801
|
-
status: { type: 'string', description: 'Filter by status: active, validated, rejected, draft.' },
|
|
1802
|
-
limit: { type: 'number', description: 'Max results. Default 20.' },
|
|
1803
|
-
},
|
|
1804
|
-
},
|
|
1805
|
-
},
|
|
1806
|
-
{
|
|
1807
|
-
name: 'mio.creativity.generate',
|
|
1808
|
-
description: 'Generate new creative hypotheses by combining concept sources via LLM. Returns ideas with novelty/feasibility/impact scores.',
|
|
1809
|
-
inputSchema: {
|
|
1810
|
-
type: 'object',
|
|
1811
|
-
properties: {
|
|
1812
|
-
sources: {
|
|
1813
|
-
type: 'array',
|
|
1814
|
-
description: 'Concept sources to combine. Each has name, content, type (knowledge/behavior/insight/failure), weight.',
|
|
1815
|
-
items: {
|
|
1816
|
-
type: 'object',
|
|
1817
|
-
properties: {
|
|
1818
|
-
name: { type: 'string' },
|
|
1819
|
-
content: { type: 'string' },
|
|
1820
|
-
type: { type: 'string' },
|
|
1821
|
-
weight: { type: 'number' },
|
|
1822
|
-
},
|
|
1823
|
-
required: ['name', 'content'],
|
|
1824
|
-
},
|
|
1825
|
-
},
|
|
1826
|
-
strategy: { type: 'string', description: 'Generation strategy: explore (cross-domain), signal (provocative), stable (conservative). Auto-selected if omitted.' },
|
|
1827
|
-
},
|
|
1828
|
-
required: ['sources'],
|
|
1829
|
-
},
|
|
1830
|
-
},
|
|
1831
|
-
{
|
|
1832
|
-
name: 'mio.creativity.ferment',
|
|
1833
|
-
description: 'Ferment (review and refine) active hypotheses. Updates scores, can promote/reject ideas.',
|
|
1834
|
-
inputSchema: {
|
|
1835
|
-
type: 'object',
|
|
1836
|
-
properties: {
|
|
1837
|
-
limit: { type: 'number', description: 'Max hypotheses to ferment. Default 5.' },
|
|
1838
|
-
},
|
|
1839
|
-
},
|
|
1840
|
-
},
|
|
1841
|
-
|
|
1842
|
-
// ═══ Observer Research Pipeline ═══
|
|
1843
|
-
|
|
1844
|
-
// ═══ Insight Self-Observation ═══
|
|
1845
|
-
|
|
1846
|
-
...(insightStore ? [{
|
|
1847
|
-
name: 'mio.insight.status',
|
|
1848
|
-
description: 'Get insight engine status: total insights, unreported count, cooldown.',
|
|
1849
|
-
inputSchema: { type: 'object', properties: {} },
|
|
1850
|
-
},
|
|
1851
|
-
{
|
|
1852
|
-
name: 'mio.insight.list',
|
|
1853
|
-
description: 'List stored insights with optional filters.',
|
|
1854
|
-
inputSchema: {
|
|
1855
|
-
type: 'object',
|
|
1856
|
-
properties: {
|
|
1857
|
-
unreported: { type: 'boolean', description: 'Only unreported insights' },
|
|
1858
|
-
minScore: { type: 'number', description: 'Minimum score filter' },
|
|
1859
|
-
detector: { type: 'string', description: 'Filter by detector name' },
|
|
1860
|
-
limit: { type: 'number', description: 'Max results (default 20)' },
|
|
1861
|
-
},
|
|
1862
|
-
},
|
|
1863
|
-
},
|
|
1864
|
-
{
|
|
1865
|
-
name: 'mio.insight.mark_reported',
|
|
1866
|
-
description: 'Mark insights as reported (acknowledged).',
|
|
1867
|
-
inputSchema: {
|
|
1868
|
-
type: 'object',
|
|
1869
|
-
properties: {
|
|
1870
|
-
ids: { type: 'array', items: { type: 'string' }, description: 'Insight IDs to mark' },
|
|
1871
|
-
},
|
|
1872
|
-
required: ['ids'],
|
|
1873
|
-
},
|
|
1874
|
-
},
|
|
1875
|
-
{
|
|
1876
|
-
name: 'mio.insight.generate',
|
|
1877
|
-
description: 'Run LLM-based insight generation from provided context data.',
|
|
1878
|
-
inputSchema: {
|
|
1879
|
-
type: 'object',
|
|
1880
|
-
properties: {
|
|
1881
|
-
memories: { type: 'array', items: { type: 'object' }, description: 'Memory entries to analyze' },
|
|
1882
|
-
summaries: { type: 'array', items: { type: 'string' }, description: 'Recent summaries' },
|
|
1883
|
-
plans: { type: 'array', items: { type: 'object' }, description: 'Active plans' },
|
|
1884
|
-
},
|
|
1885
|
-
},
|
|
1886
|
-
}] : []),
|
|
1887
|
-
|
|
1888
|
-
// ═══ Observer Research Pipeline ═══
|
|
1889
|
-
|
|
1890
|
-
...(ObserverStore ? [{
|
|
1891
|
-
name: 'mio.observer.world_model',
|
|
1892
|
-
description: 'Query the Observer world model: entities, events, trends, narratives, uncertainties, and relations.',
|
|
1893
|
-
inputSchema: {
|
|
1894
|
-
type: 'object',
|
|
1895
|
-
properties: {
|
|
1896
|
-
baseDir: { type: 'string', description: 'Observer data directory (default: .local/observer)' },
|
|
1897
|
-
},
|
|
1898
|
-
},
|
|
1899
|
-
},
|
|
1900
|
-
{
|
|
1901
|
-
name: 'mio.observer.trends',
|
|
1902
|
-
description: 'Get recent trend reports from the Observer pipeline.',
|
|
1903
|
-
inputSchema: {
|
|
1904
|
-
type: 'object',
|
|
1905
|
-
properties: {
|
|
1906
|
-
date: { type: 'string', description: 'Specific date (YYYY-MM-DD) or null for latest' },
|
|
1907
|
-
limit: { type: 'number', description: 'Max reports to return (default 7)' },
|
|
1908
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1909
|
-
},
|
|
1910
|
-
},
|
|
1911
|
-
},
|
|
1912
|
-
{
|
|
1913
|
-
name: 'mio.observer.research',
|
|
1914
|
-
description: 'List recent deep research results from the Observer pipeline.',
|
|
1915
|
-
inputSchema: {
|
|
1916
|
-
type: 'object',
|
|
1917
|
-
properties: {
|
|
1918
|
-
limit: { type: 'number', description: 'Max results (default 10)' },
|
|
1919
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1920
|
-
},
|
|
1921
|
-
},
|
|
1922
|
-
},
|
|
1923
|
-
{
|
|
1924
|
-
name: 'mio.observer.insights',
|
|
1925
|
-
description: 'List Observer-generated insights (5-section structured articles).',
|
|
1926
|
-
inputSchema: {
|
|
1927
|
-
type: 'object',
|
|
1928
|
-
properties: {
|
|
1929
|
-
limit: { type: 'number', description: 'Max insights (default 20)' },
|
|
1930
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1931
|
-
},
|
|
1932
|
-
},
|
|
1933
|
-
},
|
|
1934
|
-
{
|
|
1935
|
-
name: 'mio.observer.status',
|
|
1936
|
-
description: 'Get Observer pipeline status: counts of observations, trends, topics, research, insights, essays, and world model entities.',
|
|
1937
|
-
inputSchema: {
|
|
1938
|
-
type: 'object',
|
|
1939
|
-
properties: {
|
|
1940
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1941
|
-
},
|
|
1942
|
-
},
|
|
1943
|
-
},
|
|
1944
|
-
{
|
|
1945
|
-
name: 'mio.observer.collect',
|
|
1946
|
-
description: 'Force data collection from all configured sources (RSS, Bilibili, HackerNews, GitHub, Douyin). Returns collected observations.',
|
|
1947
|
-
inputSchema: {
|
|
1948
|
-
type: 'object',
|
|
1949
|
-
properties: {
|
|
1950
|
-
sources: { type: 'array', items: { type: 'string' }, description: 'Specific sources to collect (default: all)' },
|
|
1951
|
-
keywords: { type: 'array', items: { type: 'string' }, description: 'Keyword filter (OR match)' },
|
|
1952
|
-
limit: { type: 'number', description: 'Max items per source (default 20)' },
|
|
1953
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1954
|
-
},
|
|
1955
|
-
},
|
|
1956
|
-
},
|
|
1957
|
-
{
|
|
1958
|
-
name: 'mio.observer.ferment',
|
|
1959
|
-
description: 'Run fermentation engine: find recurring themes across recent observations and optionally write an essay.',
|
|
1960
|
-
inputSchema: {
|
|
1961
|
-
type: 'object',
|
|
1962
|
-
properties: {
|
|
1963
|
-
session: { type: 'string', description: 'Session label: morning/afternoon/night (default: afternoon)' },
|
|
1964
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1965
|
-
},
|
|
1966
|
-
},
|
|
1967
|
-
},
|
|
1968
|
-
{
|
|
1969
|
-
name: 'mio.observer.essays',
|
|
1970
|
-
description: 'List published or draft essays from the Observer.',
|
|
1971
|
-
inputSchema: {
|
|
1972
|
-
type: 'object',
|
|
1973
|
-
properties: {
|
|
1974
|
-
type: { type: 'string', description: 'published or draft (default: published)' },
|
|
1975
|
-
limit: { type: 'number', description: 'Max essays (default 10)' },
|
|
1976
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1977
|
-
},
|
|
1978
|
-
},
|
|
1979
|
-
},
|
|
1980
|
-
{
|
|
1981
|
-
name: 'mio.observer.dag',
|
|
1982
|
-
description: 'Get DAG state machine status: today\'s task, recent summaries, retryable tasks.',
|
|
1983
|
-
inputSchema: {
|
|
1984
|
-
type: 'object',
|
|
1985
|
-
properties: {
|
|
1986
|
-
days: { type: 'number', description: 'Days to look back (default 7)' },
|
|
1987
|
-
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1988
|
-
},
|
|
1989
|
-
},
|
|
1990
|
-
}] : []),
|
|
1991
|
-
]
|
|
1992
|
-
|
|
1993
|
-
// ═══ Insight Self-Observation Handlers ═══
|
|
1994
|
-
|
|
1995
|
-
function insightStatus() {
|
|
1996
|
-
if (!insightStore) throw new Error('@akemi-mio/insight not installed')
|
|
1997
|
-
const all = insightStore.getAll()
|
|
1998
|
-
const unreported = insightStore.getUnreported()
|
|
1999
|
-
return {
|
|
2000
|
-
total: all.length,
|
|
2001
|
-
unreported: unreported.length,
|
|
2002
|
-
reported: all.length - unreported.length,
|
|
2003
|
-
highValue: insightStore.getHighValueUnreported(50, 0.7).length,
|
|
2004
|
-
}
|
|
2005
|
-
}
|
|
2006
|
-
|
|
2007
|
-
function insightList(args = {}) {
|
|
2008
|
-
if (!insightStore) throw new Error('@akemi-mio/insight not installed')
|
|
2009
|
-
let insights = insightStore.getAll()
|
|
2010
|
-
if (args.unreported) insights = insightStore.getUnreported()
|
|
2011
|
-
if (args.minScore) insights = insights.filter(i => i.score >= args.minScore)
|
|
2012
|
-
if (args.detector) insights = insights.filter(i => i.detector === args.detector)
|
|
2013
|
-
if (args.limit) insights = insights.slice(-args.limit)
|
|
2014
|
-
return insights
|
|
2015
|
-
}
|
|
2016
|
-
|
|
2017
|
-
function insightMarkReported(args = {}) {
|
|
2018
|
-
if (!insightStore) throw new Error('@akemi-mio/insight not installed')
|
|
2019
|
-
if (!args.ids || !Array.isArray(args.ids)) throw new Error('ids array required')
|
|
2020
|
-
for (const id of args.ids) insightStore.markReported(id)
|
|
2021
|
-
return { marked: args.ids.length }
|
|
2022
|
-
}
|
|
2023
|
-
|
|
2024
|
-
async function insightGenerate(args = {}) {
|
|
2025
|
-
if (!InsightGenerator) throw new Error('@akemi-mio/insight not installed')
|
|
2026
|
-
const generator = new InsightGenerator({ chatJson })
|
|
2027
|
-
const ctx = {
|
|
2028
|
-
memoryEntries: (args.memories || []).map(m => ({
|
|
2029
|
-
type: m.kind || m.type || 'note',
|
|
2030
|
-
content: m.content || '',
|
|
2031
|
-
createdAt: m.createdAt || m.timestamp || Date.now(),
|
|
2032
|
-
})),
|
|
2033
|
-
summaries: args.summaries || [],
|
|
2034
|
-
interactionCount: 0,
|
|
2035
|
-
plans: (args.plans || []).map(p => ({
|
|
2036
|
-
title: p.title || p.name || '',
|
|
2037
|
-
status: p.status || 'unknown',
|
|
2038
|
-
updatedAt: p.updatedAt || Date.now(),
|
|
2039
|
-
steps: p.steps || [],
|
|
2040
|
-
createdAt: p.createdAt || Date.now(),
|
|
2041
|
-
})),
|
|
2042
|
-
eventCount: 0,
|
|
2043
|
-
}
|
|
2044
|
-
const insights = await generator.generate(ctx)
|
|
2045
|
-
if (insightStore) {
|
|
2046
|
-
for (const i of insights) insightStore.addMany([i])
|
|
2047
|
-
}
|
|
2048
|
-
return { generated: insights.length, insights }
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// ═══ Observer Research Pipeline Handlers ═══
|
|
2052
|
-
|
|
2053
|
-
function getObserverStore(baseDir) {
|
|
2054
|
-
if (!ObserverStore) throw new Error('@akemi-mio/observer not installed')
|
|
2055
|
-
return new ObserverStore(baseDir || path.join(process.cwd(), '.local', 'observer'))
|
|
2056
|
-
}
|
|
2057
|
-
|
|
2058
|
-
function getObserverBaseDir(args) {
|
|
2059
|
-
return args.baseDir || path.join(process.cwd(), '.local', 'observer')
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
function readJsonSafe(filePath) {
|
|
2063
|
-
if (!fs.existsSync(filePath)) return null
|
|
2064
|
-
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')) } catch { return null }
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
function readJsonlSafe(filePath) {
|
|
2068
|
-
if (!fs.existsSync(filePath)) return []
|
|
2069
|
-
try {
|
|
2070
|
-
return fs.readFileSync(filePath, 'utf8').trim().split('\n').filter(Boolean)
|
|
2071
|
-
.map(l => { try { return JSON.parse(l) } catch { return null } }).filter(Boolean)
|
|
2072
|
-
} catch { return [] }
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
function observerWorldModel(args = {}) {
|
|
2076
|
-
const baseDir = getObserverBaseDir(args)
|
|
2077
|
-
const wmDir = path.join(baseDir, 'world_model')
|
|
2078
|
-
return {
|
|
2079
|
-
entities: readJsonSafe(path.join(wmDir, 'entities.json')) || [],
|
|
2080
|
-
events: readJsonSafe(path.join(wmDir, 'events.json')) || [],
|
|
2081
|
-
trends: readJsonSafe(path.join(wmDir, 'trends.json')) || [],
|
|
2082
|
-
narratives: readJsonSafe(path.join(wmDir, 'narratives.json')) || [],
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
|
|
2086
|
-
function observerTrends(args = {}) {
|
|
2087
|
-
const baseDir = getObserverBaseDir(args)
|
|
2088
|
-
const trendsDir = path.join(baseDir, 'trends')
|
|
2089
|
-
if (!fs.existsSync(trendsDir)) return []
|
|
2090
|
-
if (args.date) {
|
|
2091
|
-
const report = readJsonSafe(path.join(trendsDir, `${args.date}.json`))
|
|
2092
|
-
return report ? [report] : []
|
|
2093
|
-
}
|
|
2094
|
-
const files = fs.readdirSync(trendsDir).filter(f => f.endsWith('.json')).sort().reverse()
|
|
2095
|
-
return files.slice(0, args.limit || 7).map(f => readJsonSafe(path.join(trendsDir, f))).filter(Boolean)
|
|
2096
|
-
}
|
|
2097
|
-
|
|
2098
|
-
function observerResearch(args = {}) {
|
|
2099
|
-
const baseDir = getObserverBaseDir(args)
|
|
2100
|
-
const researchDir = path.join(baseDir, 'research')
|
|
2101
|
-
if (!fs.existsSync(researchDir)) return []
|
|
2102
|
-
const files = fs.readdirSync(researchDir).filter(f => f.endsWith('.json')).sort().reverse()
|
|
2103
|
-
return files.slice(0, args.limit || 10).map(f => readJsonSafe(path.join(researchDir, f))).filter(Boolean)
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
function observerInsights(args = {}) {
|
|
2107
|
-
const baseDir = getObserverBaseDir(args)
|
|
2108
|
-
const insightsDir = path.join(baseDir, 'insights')
|
|
2109
|
-
if (!fs.existsSync(insightsDir)) return []
|
|
2110
|
-
const files = fs.readdirSync(insightsDir).filter(f => f.endsWith('.json')).sort().reverse()
|
|
2111
|
-
return files.slice(0, args.limit || 20).map(f => readJsonSafe(path.join(insightsDir, f))).filter(Boolean)
|
|
2112
|
-
}
|
|
2113
|
-
|
|
2114
|
-
function observerStatus(args = {}) {
|
|
2115
|
-
const baseDir = getObserverBaseDir(args)
|
|
2116
|
-
const dirs = ['observations', 'trends', 'topics', 'research', 'insights', 'world_model', 'essays']
|
|
2117
|
-
const status = {}
|
|
2118
|
-
for (const d of dirs) {
|
|
2119
|
-
const dir = path.join(baseDir, d)
|
|
2120
|
-
if (fs.existsSync(dir)) {
|
|
2121
|
-
status[d] = fs.readdirSync(dir).filter(f => !f.startsWith('.')).length
|
|
2122
|
-
} else {
|
|
2123
|
-
status[d] = 0
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
return status
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
function observerCollect(args = {}) {
|
|
2130
|
-
if (!ObserverService) throw new Error('@akemi-mio/observer not installed')
|
|
2131
|
-
const baseDir = getObserverBaseDir(args)
|
|
2132
|
-
const service = new ObserverService({ baseDir, collectorConfig: {} })
|
|
2133
|
-
const sources = args.sources || ['bilibili', 'hackernews', 'github', 'douyin', 'rss']
|
|
2134
|
-
const allObs = []
|
|
2135
|
-
for (const src of sources) {
|
|
2136
|
-
try {
|
|
2137
|
-
const collectFn = service.collectBySource ? service.collectBySource : service.collect
|
|
2138
|
-
if (collectFn) {
|
|
2139
|
-
const result = collectFn.call(service, [src], args.keywords || [], args.limit || 20)
|
|
2140
|
-
if (Array.isArray(result)) allObs.push(...result)
|
|
2141
|
-
else if (result && typeof result === 'object') {
|
|
2142
|
-
for (const [key, items] of Object.entries(result)) allObs.push(...items)
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
} catch (e) {
|
|
2146
|
-
allObs.push({ source: src, error: e.message })
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
return { collected: allObs.length, observations: allObs }
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
function observerFerment(args = {}) {
|
|
2153
|
-
if (!ObserverService) throw new Error('@akemi-mio/observer not installed')
|
|
2154
|
-
const baseDir = getObserverBaseDir(args)
|
|
2155
|
-
const service = new ObserverService({ baseDir, collectorConfig: {} })
|
|
2156
|
-
const session = args.session || 'afternoon'
|
|
2157
|
-
try {
|
|
2158
|
-
const fermentation = service.getFermentation()
|
|
2159
|
-
if (fermentation && fermentation.ferment) return fermentation.ferment(session)
|
|
2160
|
-
return { status: 'fermentation engine available but no ferment method' }
|
|
2161
|
-
} catch (e) {
|
|
2162
|
-
return { error: e.message }
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2165
|
-
|
|
2166
|
-
function observerEssays(args = {}) {
|
|
2167
|
-
const baseDir = getObserverBaseDir(args)
|
|
2168
|
-
const type = args.type || 'published'
|
|
2169
|
-
const essaysDir = path.join(baseDir, 'essays', type)
|
|
2170
|
-
if (!fs.existsSync(essaysDir)) return []
|
|
2171
|
-
const files = fs.readdirSync(essaysDir).filter(f => f.endsWith('.md')).sort().reverse()
|
|
2172
|
-
return files.slice(0, args.limit || 10).map(f => {
|
|
2173
|
-
const content = fs.readFileSync(path.join(essaysDir, f), 'utf8')
|
|
2174
|
-
return { file: f, type, content, created: fs.statSync(path.join(essaysDir, f)).birthtime.toISOString() }
|
|
2175
|
-
})
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
function observerDag(args = {}) {
|
|
2179
|
-
const baseDir = getObserverBaseDir(args)
|
|
2180
|
-
const days = args.days || 7
|
|
2181
|
-
const summaries = []
|
|
2182
|
-
const todayDate = new Date()
|
|
2183
|
-
for (let i = 0; i < days; i++) {
|
|
2184
|
-
const d = new Date(todayDate)
|
|
2185
|
-
d.setDate(d.getDate() - i)
|
|
2186
|
-
const dateStr = d.toISOString().slice(0, 10)
|
|
2187
|
-
const filePath = path.join(baseDir, 'summaries', `${dateStr}.jsonl`)
|
|
2188
|
-
const items = readJsonlSafe(filePath)
|
|
2189
|
-
if (items.length) summaries.push({ date: dateStr, summaries: items })
|
|
2190
|
-
}
|
|
2191
|
-
return { summaries, summaryCount: summaries.reduce((a, s) => a + s.summaries.length, 0) }
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
async function callTool(name, args = {}) {
|
|
2195
|
-
switch (name) {
|
|
2196
|
-
case 'mio.memory.query':
|
|
2197
|
-
return queryMemory(args)
|
|
2198
|
-
case 'mio.memory.record':
|
|
2199
|
-
return recordMemory(args)
|
|
2200
|
-
case 'mio.memory.archive':
|
|
2201
|
-
return archiveMemory(args)
|
|
2202
|
-
case 'mio.memory.migrate':
|
|
2203
|
-
return migrateMemory(args)
|
|
2204
|
-
case 'mio.observer.ingest':
|
|
2205
|
-
return ingestObservation(args)
|
|
2206
|
-
case 'mio.observer.subscribe':
|
|
2207
|
-
return subscribeObserver(args)
|
|
2208
|
-
case 'mio.observer.digest':
|
|
2209
|
-
return observerDigest(args)
|
|
2210
|
-
case 'mio.
|
|
2211
|
-
return
|
|
2212
|
-
case 'mio.
|
|
2213
|
-
return
|
|
2214
|
-
case 'mio.
|
|
2215
|
-
return
|
|
2216
|
-
case 'mio.
|
|
2217
|
-
return
|
|
2218
|
-
case 'mio.experience.
|
|
2219
|
-
return
|
|
2220
|
-
case 'mio.
|
|
2221
|
-
return
|
|
2222
|
-
case 'mio.
|
|
2223
|
-
return
|
|
2224
|
-
case 'mio.
|
|
2225
|
-
return
|
|
2226
|
-
case 'mio.
|
|
2227
|
-
return
|
|
2228
|
-
case 'mio.agent.
|
|
2229
|
-
return
|
|
1685
|
+
type: 'object',
|
|
1686
|
+
properties: {
|
|
1687
|
+
outcome: { type: 'string', description: 'Task outcome: success, failure, or aborted.' },
|
|
1688
|
+
task: { type: 'string', description: 'Brief task description.' },
|
|
1689
|
+
summary: { type: 'string', description: 'Optional task summary.' },
|
|
1690
|
+
verification: { type: 'string', description: 'Optional verification details.' },
|
|
1691
|
+
agentId: { type: 'string', description: 'Agent identifier. Defaults to MIO_CONTEXT agentId.' },
|
|
1692
|
+
project: { type: 'string', description: 'Project name. Defaults to current repository.' },
|
|
1693
|
+
traceId: { type: 'string', description: 'Optional custom trace ID. Auto-generated if omitted.' },
|
|
1694
|
+
},
|
|
1695
|
+
required: ['outcome'],
|
|
1696
|
+
},
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
name: 'mio.creativity.status',
|
|
1700
|
+
description: 'Show creativity engine status: hypothesis counts, active/validated/rejected, recent top ideas.',
|
|
1701
|
+
inputSchema: { type: 'object', properties: {} },
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
name: 'mio.creativity.list',
|
|
1705
|
+
description: 'List creativity hypotheses with optional status filter.',
|
|
1706
|
+
inputSchema: {
|
|
1707
|
+
type: 'object',
|
|
1708
|
+
properties: {
|
|
1709
|
+
status: { type: 'string', description: 'Filter by status: active, validated, rejected, draft.' },
|
|
1710
|
+
limit: { type: 'number', description: 'Max results. Default 20.' },
|
|
1711
|
+
},
|
|
1712
|
+
},
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
name: 'mio.creativity.generate',
|
|
1716
|
+
description: 'Generate new creative hypotheses by combining concept sources via LLM. Returns ideas with novelty/feasibility/impact scores.',
|
|
1717
|
+
inputSchema: {
|
|
1718
|
+
type: 'object',
|
|
1719
|
+
properties: {
|
|
1720
|
+
sources: {
|
|
1721
|
+
type: 'array',
|
|
1722
|
+
description: 'Concept sources to combine. Each has name, content, type (knowledge/behavior/insight/failure), weight.',
|
|
1723
|
+
items: {
|
|
1724
|
+
type: 'object',
|
|
1725
|
+
properties: {
|
|
1726
|
+
name: { type: 'string' },
|
|
1727
|
+
content: { type: 'string' },
|
|
1728
|
+
type: { type: 'string' },
|
|
1729
|
+
weight: { type: 'number' },
|
|
1730
|
+
},
|
|
1731
|
+
required: ['name', 'content'],
|
|
1732
|
+
},
|
|
1733
|
+
},
|
|
1734
|
+
strategy: { type: 'string', description: 'Generation strategy: explore (cross-domain), signal (provocative), stable (conservative). Auto-selected if omitted.' },
|
|
1735
|
+
},
|
|
1736
|
+
required: ['sources'],
|
|
1737
|
+
},
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
name: 'mio.creativity.ferment',
|
|
1741
|
+
description: 'Ferment (review and refine) active hypotheses. Updates scores, can promote/reject ideas.',
|
|
1742
|
+
inputSchema: {
|
|
1743
|
+
type: 'object',
|
|
1744
|
+
properties: {
|
|
1745
|
+
limit: { type: 'number', description: 'Max hypotheses to ferment. Default 5.' },
|
|
1746
|
+
},
|
|
1747
|
+
},
|
|
1748
|
+
},
|
|
1749
|
+
|
|
1750
|
+
// ═══ Observer Research Pipeline ═══
|
|
1751
|
+
|
|
1752
|
+
// ═══ Insight Self-Observation ═══
|
|
1753
|
+
|
|
1754
|
+
...(insightStore ? [{
|
|
1755
|
+
name: 'mio.insight.status',
|
|
1756
|
+
description: 'Get insight engine status: total insights, unreported count, cooldown.',
|
|
1757
|
+
inputSchema: { type: 'object', properties: {} },
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
name: 'mio.insight.list',
|
|
1761
|
+
description: 'List stored insights with optional filters.',
|
|
1762
|
+
inputSchema: {
|
|
1763
|
+
type: 'object',
|
|
1764
|
+
properties: {
|
|
1765
|
+
unreported: { type: 'boolean', description: 'Only unreported insights' },
|
|
1766
|
+
minScore: { type: 'number', description: 'Minimum score filter' },
|
|
1767
|
+
detector: { type: 'string', description: 'Filter by detector name' },
|
|
1768
|
+
limit: { type: 'number', description: 'Max results (default 20)' },
|
|
1769
|
+
},
|
|
1770
|
+
},
|
|
1771
|
+
},
|
|
1772
|
+
{
|
|
1773
|
+
name: 'mio.insight.mark_reported',
|
|
1774
|
+
description: 'Mark insights as reported (acknowledged).',
|
|
1775
|
+
inputSchema: {
|
|
1776
|
+
type: 'object',
|
|
1777
|
+
properties: {
|
|
1778
|
+
ids: { type: 'array', items: { type: 'string' }, description: 'Insight IDs to mark' },
|
|
1779
|
+
},
|
|
1780
|
+
required: ['ids'],
|
|
1781
|
+
},
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
name: 'mio.insight.generate',
|
|
1785
|
+
description: 'Run LLM-based insight generation from provided context data.',
|
|
1786
|
+
inputSchema: {
|
|
1787
|
+
type: 'object',
|
|
1788
|
+
properties: {
|
|
1789
|
+
memories: { type: 'array', items: { type: 'object' }, description: 'Memory entries to analyze' },
|
|
1790
|
+
summaries: { type: 'array', items: { type: 'string' }, description: 'Recent summaries' },
|
|
1791
|
+
plans: { type: 'array', items: { type: 'object' }, description: 'Active plans' },
|
|
1792
|
+
},
|
|
1793
|
+
},
|
|
1794
|
+
}] : []),
|
|
1795
|
+
|
|
1796
|
+
// ═══ Observer Research Pipeline ═══
|
|
1797
|
+
|
|
1798
|
+
...(ObserverStore ? [{
|
|
1799
|
+
name: 'mio.observer.world_model',
|
|
1800
|
+
description: 'Query the Observer world model: entities, events, trends, narratives, uncertainties, and relations.',
|
|
1801
|
+
inputSchema: {
|
|
1802
|
+
type: 'object',
|
|
1803
|
+
properties: {
|
|
1804
|
+
baseDir: { type: 'string', description: 'Observer data directory (default: .local/observer)' },
|
|
1805
|
+
},
|
|
1806
|
+
},
|
|
1807
|
+
},
|
|
1808
|
+
{
|
|
1809
|
+
name: 'mio.observer.trends',
|
|
1810
|
+
description: 'Get recent trend reports from the Observer pipeline.',
|
|
1811
|
+
inputSchema: {
|
|
1812
|
+
type: 'object',
|
|
1813
|
+
properties: {
|
|
1814
|
+
date: { type: 'string', description: 'Specific date (YYYY-MM-DD) or null for latest' },
|
|
1815
|
+
limit: { type: 'number', description: 'Max reports to return (default 7)' },
|
|
1816
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1817
|
+
},
|
|
1818
|
+
},
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
name: 'mio.observer.research',
|
|
1822
|
+
description: 'List recent deep research results from the Observer pipeline.',
|
|
1823
|
+
inputSchema: {
|
|
1824
|
+
type: 'object',
|
|
1825
|
+
properties: {
|
|
1826
|
+
limit: { type: 'number', description: 'Max results (default 10)' },
|
|
1827
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1828
|
+
},
|
|
1829
|
+
},
|
|
1830
|
+
},
|
|
1831
|
+
{
|
|
1832
|
+
name: 'mio.observer.insights',
|
|
1833
|
+
description: 'List Observer-generated insights (5-section structured articles).',
|
|
1834
|
+
inputSchema: {
|
|
1835
|
+
type: 'object',
|
|
1836
|
+
properties: {
|
|
1837
|
+
limit: { type: 'number', description: 'Max insights (default 20)' },
|
|
1838
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1839
|
+
},
|
|
1840
|
+
},
|
|
1841
|
+
},
|
|
1842
|
+
{
|
|
1843
|
+
name: 'mio.observer.status',
|
|
1844
|
+
description: 'Get Observer pipeline status: counts of observations, trends, topics, research, insights, essays, and world model entities.',
|
|
1845
|
+
inputSchema: {
|
|
1846
|
+
type: 'object',
|
|
1847
|
+
properties: {
|
|
1848
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1849
|
+
},
|
|
1850
|
+
},
|
|
1851
|
+
},
|
|
1852
|
+
{
|
|
1853
|
+
name: 'mio.observer.collect',
|
|
1854
|
+
description: 'Force data collection from all configured sources (RSS, Bilibili, HackerNews, GitHub, Douyin). Returns collected observations.',
|
|
1855
|
+
inputSchema: {
|
|
1856
|
+
type: 'object',
|
|
1857
|
+
properties: {
|
|
1858
|
+
sources: { type: 'array', items: { type: 'string' }, description: 'Specific sources to collect (default: all)' },
|
|
1859
|
+
keywords: { type: 'array', items: { type: 'string' }, description: 'Keyword filter (OR match)' },
|
|
1860
|
+
limit: { type: 'number', description: 'Max items per source (default 20)' },
|
|
1861
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1862
|
+
},
|
|
1863
|
+
},
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
name: 'mio.observer.ferment',
|
|
1867
|
+
description: 'Run fermentation engine: find recurring themes across recent observations and optionally write an essay.',
|
|
1868
|
+
inputSchema: {
|
|
1869
|
+
type: 'object',
|
|
1870
|
+
properties: {
|
|
1871
|
+
session: { type: 'string', description: 'Session label: morning/afternoon/night (default: afternoon)' },
|
|
1872
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1873
|
+
},
|
|
1874
|
+
},
|
|
1875
|
+
},
|
|
1876
|
+
{
|
|
1877
|
+
name: 'mio.observer.essays',
|
|
1878
|
+
description: 'List published or draft essays from the Observer.',
|
|
1879
|
+
inputSchema: {
|
|
1880
|
+
type: 'object',
|
|
1881
|
+
properties: {
|
|
1882
|
+
type: { type: 'string', description: 'published or draft (default: published)' },
|
|
1883
|
+
limit: { type: 'number', description: 'Max essays (default 10)' },
|
|
1884
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1885
|
+
},
|
|
1886
|
+
},
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
name: 'mio.observer.dag',
|
|
1890
|
+
description: 'Get DAG state machine status: today\'s task, recent summaries, retryable tasks.',
|
|
1891
|
+
inputSchema: {
|
|
1892
|
+
type: 'object',
|
|
1893
|
+
properties: {
|
|
1894
|
+
days: { type: 'number', description: 'Days to look back (default 7)' },
|
|
1895
|
+
baseDir: { type: 'string', description: 'Observer data directory' },
|
|
1896
|
+
},
|
|
1897
|
+
},
|
|
1898
|
+
}] : []),
|
|
1899
|
+
]
|
|
1900
|
+
|
|
1901
|
+
// ═══ Insight Self-Observation Handlers ═══
|
|
1902
|
+
|
|
1903
|
+
function insightStatus() {
|
|
1904
|
+
if (!insightStore) throw new Error('@akemi-mio/insight not installed')
|
|
1905
|
+
const all = insightStore.getAll()
|
|
1906
|
+
const unreported = insightStore.getUnreported()
|
|
1907
|
+
return {
|
|
1908
|
+
total: all.length,
|
|
1909
|
+
unreported: unreported.length,
|
|
1910
|
+
reported: all.length - unreported.length,
|
|
1911
|
+
highValue: insightStore.getHighValueUnreported(50, 0.7).length,
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
function insightList(args = {}) {
|
|
1916
|
+
if (!insightStore) throw new Error('@akemi-mio/insight not installed')
|
|
1917
|
+
let insights = insightStore.getAll()
|
|
1918
|
+
if (args.unreported) insights = insightStore.getUnreported()
|
|
1919
|
+
if (args.minScore) insights = insights.filter(i => i.score >= args.minScore)
|
|
1920
|
+
if (args.detector) insights = insights.filter(i => i.detector === args.detector)
|
|
1921
|
+
if (args.limit) insights = insights.slice(-args.limit)
|
|
1922
|
+
return insights
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
function insightMarkReported(args = {}) {
|
|
1926
|
+
if (!insightStore) throw new Error('@akemi-mio/insight not installed')
|
|
1927
|
+
if (!args.ids || !Array.isArray(args.ids)) throw new Error('ids array required')
|
|
1928
|
+
for (const id of args.ids) insightStore.markReported(id)
|
|
1929
|
+
return { marked: args.ids.length }
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
async function insightGenerate(args = {}) {
|
|
1933
|
+
if (!InsightGenerator) throw new Error('@akemi-mio/insight not installed')
|
|
1934
|
+
const generator = new InsightGenerator({ chatJson })
|
|
1935
|
+
const ctx = {
|
|
1936
|
+
memoryEntries: (args.memories || []).map(m => ({
|
|
1937
|
+
type: m.kind || m.type || 'note',
|
|
1938
|
+
content: m.content || '',
|
|
1939
|
+
createdAt: m.createdAt || m.timestamp || Date.now(),
|
|
1940
|
+
})),
|
|
1941
|
+
summaries: args.summaries || [],
|
|
1942
|
+
interactionCount: 0,
|
|
1943
|
+
plans: (args.plans || []).map(p => ({
|
|
1944
|
+
title: p.title || p.name || '',
|
|
1945
|
+
status: p.status || 'unknown',
|
|
1946
|
+
updatedAt: p.updatedAt || Date.now(),
|
|
1947
|
+
steps: p.steps || [],
|
|
1948
|
+
createdAt: p.createdAt || Date.now(),
|
|
1949
|
+
})),
|
|
1950
|
+
eventCount: 0,
|
|
1951
|
+
}
|
|
1952
|
+
const insights = await generator.generate(ctx)
|
|
1953
|
+
if (insightStore) {
|
|
1954
|
+
for (const i of insights) insightStore.addMany([i])
|
|
1955
|
+
}
|
|
1956
|
+
return { generated: insights.length, insights }
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// ═══ Observer Research Pipeline Handlers ═══
|
|
1960
|
+
|
|
1961
|
+
function getObserverStore(baseDir) {
|
|
1962
|
+
if (!ObserverStore) throw new Error('@akemi-mio/observer not installed')
|
|
1963
|
+
return new ObserverStore(baseDir || path.join(process.cwd(), '.local', 'observer'))
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
function getObserverBaseDir(args) {
|
|
1967
|
+
return args.baseDir || path.join(process.cwd(), '.local', 'observer')
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function readJsonSafe(filePath) {
|
|
1971
|
+
if (!fs.existsSync(filePath)) return null
|
|
1972
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')) } catch { return null }
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
function readJsonlSafe(filePath) {
|
|
1976
|
+
if (!fs.existsSync(filePath)) return []
|
|
1977
|
+
try {
|
|
1978
|
+
return fs.readFileSync(filePath, 'utf8').trim().split('\n').filter(Boolean)
|
|
1979
|
+
.map(l => { try { return JSON.parse(l) } catch { return null } }).filter(Boolean)
|
|
1980
|
+
} catch { return [] }
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
function observerWorldModel(args = {}) {
|
|
1984
|
+
const baseDir = getObserverBaseDir(args)
|
|
1985
|
+
const wmDir = path.join(baseDir, 'world_model')
|
|
1986
|
+
return {
|
|
1987
|
+
entities: readJsonSafe(path.join(wmDir, 'entities.json')) || [],
|
|
1988
|
+
events: readJsonSafe(path.join(wmDir, 'events.json')) || [],
|
|
1989
|
+
trends: readJsonSafe(path.join(wmDir, 'trends.json')) || [],
|
|
1990
|
+
narratives: readJsonSafe(path.join(wmDir, 'narratives.json')) || [],
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
function observerTrends(args = {}) {
|
|
1995
|
+
const baseDir = getObserverBaseDir(args)
|
|
1996
|
+
const trendsDir = path.join(baseDir, 'trends')
|
|
1997
|
+
if (!fs.existsSync(trendsDir)) return []
|
|
1998
|
+
if (args.date) {
|
|
1999
|
+
const report = readJsonSafe(path.join(trendsDir, `${args.date}.json`))
|
|
2000
|
+
return report ? [report] : []
|
|
2001
|
+
}
|
|
2002
|
+
const files = fs.readdirSync(trendsDir).filter(f => f.endsWith('.json')).sort().reverse()
|
|
2003
|
+
return files.slice(0, args.limit || 7).map(f => readJsonSafe(path.join(trendsDir, f))).filter(Boolean)
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
function observerResearch(args = {}) {
|
|
2007
|
+
const baseDir = getObserverBaseDir(args)
|
|
2008
|
+
const researchDir = path.join(baseDir, 'research')
|
|
2009
|
+
if (!fs.existsSync(researchDir)) return []
|
|
2010
|
+
const files = fs.readdirSync(researchDir).filter(f => f.endsWith('.json')).sort().reverse()
|
|
2011
|
+
return files.slice(0, args.limit || 10).map(f => readJsonSafe(path.join(researchDir, f))).filter(Boolean)
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
function observerInsights(args = {}) {
|
|
2015
|
+
const baseDir = getObserverBaseDir(args)
|
|
2016
|
+
const insightsDir = path.join(baseDir, 'insights')
|
|
2017
|
+
if (!fs.existsSync(insightsDir)) return []
|
|
2018
|
+
const files = fs.readdirSync(insightsDir).filter(f => f.endsWith('.json')).sort().reverse()
|
|
2019
|
+
return files.slice(0, args.limit || 20).map(f => readJsonSafe(path.join(insightsDir, f))).filter(Boolean)
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
function observerStatus(args = {}) {
|
|
2023
|
+
const baseDir = getObserverBaseDir(args)
|
|
2024
|
+
const dirs = ['observations', 'trends', 'topics', 'research', 'insights', 'world_model', 'essays']
|
|
2025
|
+
const status = {}
|
|
2026
|
+
for (const d of dirs) {
|
|
2027
|
+
const dir = path.join(baseDir, d)
|
|
2028
|
+
if (fs.existsSync(dir)) {
|
|
2029
|
+
status[d] = fs.readdirSync(dir).filter(f => !f.startsWith('.')).length
|
|
2030
|
+
} else {
|
|
2031
|
+
status[d] = 0
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
return status
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
function observerCollect(args = {}) {
|
|
2038
|
+
if (!ObserverService) throw new Error('@akemi-mio/observer not installed')
|
|
2039
|
+
const baseDir = getObserverBaseDir(args)
|
|
2040
|
+
const service = new ObserverService({ baseDir, collectorConfig: {} })
|
|
2041
|
+
const sources = args.sources || ['bilibili', 'hackernews', 'github', 'douyin', 'rss']
|
|
2042
|
+
const allObs = []
|
|
2043
|
+
for (const src of sources) {
|
|
2044
|
+
try {
|
|
2045
|
+
const collectFn = service.collectBySource ? service.collectBySource : service.collect
|
|
2046
|
+
if (collectFn) {
|
|
2047
|
+
const result = collectFn.call(service, [src], args.keywords || [], args.limit || 20)
|
|
2048
|
+
if (Array.isArray(result)) allObs.push(...result)
|
|
2049
|
+
else if (result && typeof result === 'object') {
|
|
2050
|
+
for (const [key, items] of Object.entries(result)) allObs.push(...items)
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
} catch (e) {
|
|
2054
|
+
allObs.push({ source: src, error: e.message })
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
return { collected: allObs.length, observations: allObs }
|
|
2058
|
+
}
|
|
2059
|
+
|
|
2060
|
+
function observerFerment(args = {}) {
|
|
2061
|
+
if (!ObserverService) throw new Error('@akemi-mio/observer not installed')
|
|
2062
|
+
const baseDir = getObserverBaseDir(args)
|
|
2063
|
+
const service = new ObserverService({ baseDir, collectorConfig: {} })
|
|
2064
|
+
const session = args.session || 'afternoon'
|
|
2065
|
+
try {
|
|
2066
|
+
const fermentation = service.getFermentation()
|
|
2067
|
+
if (fermentation && fermentation.ferment) return fermentation.ferment(session)
|
|
2068
|
+
return { status: 'fermentation engine available but no ferment method' }
|
|
2069
|
+
} catch (e) {
|
|
2070
|
+
return { error: e.message }
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
function observerEssays(args = {}) {
|
|
2075
|
+
const baseDir = getObserverBaseDir(args)
|
|
2076
|
+
const type = args.type || 'published'
|
|
2077
|
+
const essaysDir = path.join(baseDir, 'essays', type)
|
|
2078
|
+
if (!fs.existsSync(essaysDir)) return []
|
|
2079
|
+
const files = fs.readdirSync(essaysDir).filter(f => f.endsWith('.md')).sort().reverse()
|
|
2080
|
+
return files.slice(0, args.limit || 10).map(f => {
|
|
2081
|
+
const content = fs.readFileSync(path.join(essaysDir, f), 'utf8')
|
|
2082
|
+
return { file: f, type, content, created: fs.statSync(path.join(essaysDir, f)).birthtime.toISOString() }
|
|
2083
|
+
})
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
function observerDag(args = {}) {
|
|
2087
|
+
const baseDir = getObserverBaseDir(args)
|
|
2088
|
+
const days = args.days || 7
|
|
2089
|
+
const summaries = []
|
|
2090
|
+
const todayDate = new Date()
|
|
2091
|
+
for (let i = 0; i < days; i++) {
|
|
2092
|
+
const d = new Date(todayDate)
|
|
2093
|
+
d.setDate(d.getDate() - i)
|
|
2094
|
+
const dateStr = d.toISOString().slice(0, 10)
|
|
2095
|
+
const filePath = path.join(baseDir, 'summaries', `${dateStr}.jsonl`)
|
|
2096
|
+
const items = readJsonlSafe(filePath)
|
|
2097
|
+
if (items.length) summaries.push({ date: dateStr, summaries: items })
|
|
2098
|
+
}
|
|
2099
|
+
return { summaries, summaryCount: summaries.reduce((a, s) => a + s.summaries.length, 0) }
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
async function callTool(name, args = {}) {
|
|
2103
|
+
switch (name) {
|
|
2104
|
+
case 'mio.memory.query':
|
|
2105
|
+
return queryMemory(args)
|
|
2106
|
+
case 'mio.memory.record':
|
|
2107
|
+
return recordMemory(args)
|
|
2108
|
+
case 'mio.memory.archive':
|
|
2109
|
+
return archiveMemory(args)
|
|
2110
|
+
case 'mio.memory.migrate':
|
|
2111
|
+
return migrateMemory(args)
|
|
2112
|
+
case 'mio.observer.ingest':
|
|
2113
|
+
return ingestObservation(args)
|
|
2114
|
+
case 'mio.observer.subscribe':
|
|
2115
|
+
return subscribeObserver(args)
|
|
2116
|
+
case 'mio.observer.digest':
|
|
2117
|
+
return observerDigest(args)
|
|
2118
|
+
case 'mio.digest.generate':
|
|
2119
|
+
return digestEngine.generate({ days: args.days, project: args.project })
|
|
2120
|
+
case 'mio.trace.query':
|
|
2121
|
+
return queryTraces(args)
|
|
2122
|
+
case 'mio.policy.check':
|
|
2123
|
+
return policyCheck(args)
|
|
2124
|
+
case 'mio.memory.analyze':
|
|
2125
|
+
return analyzeMemory(args)
|
|
2126
|
+
case 'mio.experience.reuse':
|
|
2127
|
+
return recordExperienceReuse(args)
|
|
2128
|
+
case 'mio.experience.confirm':
|
|
2129
|
+
return confirmExperienceReuse(args)
|
|
2130
|
+
case 'mio.experience.list':
|
|
2131
|
+
return listExperienceReuse(args)
|
|
2132
|
+
case 'mio.phase0.report':
|
|
2133
|
+
return phase0Report(args)
|
|
2134
|
+
case 'mio.task.route':
|
|
2135
|
+
return routeTask(args)
|
|
2136
|
+
case 'mio.agent.register':
|
|
2137
|
+
return registerAgent(args)
|
|
2138
|
+
case 'mio.agent.list':
|
|
2139
|
+
return listAgents(args)
|
|
2140
|
+
case 'mio.agent.report':
|
|
2141
|
+
return reportAgent(args)
|
|
2230
2142
|
case 'mio.evolution.report':
|
|
2231
2143
|
return evolutionReport(args)
|
|
2232
2144
|
case 'mio.evolution.status':
|
|
@@ -2245,359 +2157,359 @@ async function callTool(name, args = {}) {
|
|
|
2245
2157
|
return evolutionCutover.authorityPlan(args)
|
|
2246
2158
|
case 'mio.evolution.cutover.apply':
|
|
2247
2159
|
return evolutionCutover.applyCutover(args)
|
|
2248
|
-
case 'mio.task.record_outcome':
|
|
2249
|
-
return recordTaskOutcome(args)
|
|
2250
|
-
case 'mio.creativity.status':
|
|
2251
|
-
return creativityEngine.status()
|
|
2252
|
-
case 'mio.creativity.list':
|
|
2253
|
-
return creativityEngine.list(args)
|
|
2254
|
-
case 'mio.creativity.generate':
|
|
2255
|
-
return creativityEngine.generate(args.sources, args.strategy)
|
|
2256
|
-
case 'mio.creativity.ferment':
|
|
2257
|
-
return creativityEngine.ferment(args.limit)
|
|
2258
|
-
case 'mio.insight.status':
|
|
2259
|
-
return insightStatus()
|
|
2260
|
-
case 'mio.insight.list':
|
|
2261
|
-
return insightList(args)
|
|
2262
|
-
case 'mio.insight.mark_reported':
|
|
2263
|
-
return insightMarkReported(args)
|
|
2264
|
-
case 'mio.insight.generate':
|
|
2265
|
-
return insightGenerate(args)
|
|
2266
|
-
case 'mio.observer.world_model':
|
|
2267
|
-
return observerWorldModel(args)
|
|
2268
|
-
case 'mio.observer.trends':
|
|
2269
|
-
return observerTrends(args)
|
|
2270
|
-
case 'mio.observer.research':
|
|
2271
|
-
return observerResearch(args)
|
|
2272
|
-
case 'mio.observer.insights':
|
|
2273
|
-
return observerInsights(args)
|
|
2274
|
-
case 'mio.observer.status':
|
|
2275
|
-
return observerStatus(args)
|
|
2276
|
-
case 'mio.observer.collect':
|
|
2277
|
-
return observerCollect(args)
|
|
2278
|
-
case 'mio.observer.ferment':
|
|
2279
|
-
return observerFerment(args)
|
|
2280
|
-
case 'mio.observer.essays':
|
|
2281
|
-
return observerEssays(args)
|
|
2282
|
-
case 'mio.observer.dag':
|
|
2283
|
-
return observerDag(args)
|
|
2160
|
+
case 'mio.task.record_outcome':
|
|
2161
|
+
return recordTaskOutcome(args)
|
|
2162
|
+
case 'mio.creativity.status':
|
|
2163
|
+
return creativityEngine.status()
|
|
2164
|
+
case 'mio.creativity.list':
|
|
2165
|
+
return creativityEngine.list(args)
|
|
2166
|
+
case 'mio.creativity.generate':
|
|
2167
|
+
return creativityEngine.generate(args.sources, args.strategy)
|
|
2168
|
+
case 'mio.creativity.ferment':
|
|
2169
|
+
return creativityEngine.ferment(args.limit)
|
|
2170
|
+
case 'mio.insight.status':
|
|
2171
|
+
return insightStatus()
|
|
2172
|
+
case 'mio.insight.list':
|
|
2173
|
+
return insightList(args)
|
|
2174
|
+
case 'mio.insight.mark_reported':
|
|
2175
|
+
return insightMarkReported(args)
|
|
2176
|
+
case 'mio.insight.generate':
|
|
2177
|
+
return insightGenerate(args)
|
|
2178
|
+
case 'mio.observer.world_model':
|
|
2179
|
+
return observerWorldModel(args)
|
|
2180
|
+
case 'mio.observer.trends':
|
|
2181
|
+
return observerTrends(args)
|
|
2182
|
+
case 'mio.observer.research':
|
|
2183
|
+
return observerResearch(args)
|
|
2184
|
+
case 'mio.observer.insights':
|
|
2185
|
+
return observerInsights(args)
|
|
2186
|
+
case 'mio.observer.status':
|
|
2187
|
+
return observerStatus(args)
|
|
2188
|
+
case 'mio.observer.collect':
|
|
2189
|
+
return observerCollect(args)
|
|
2190
|
+
case 'mio.observer.ferment':
|
|
2191
|
+
return observerFerment(args)
|
|
2192
|
+
case 'mio.observer.essays':
|
|
2193
|
+
return observerEssays(args)
|
|
2194
|
+
case 'mio.observer.dag':
|
|
2195
|
+
return observerDag(args)
|
|
2284
2196
|
default:
|
|
2285
|
-
throw new Error(`Unknown tool: ${name}`)
|
|
2286
|
-
}
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
function jsonRpcResult(id, result) {
|
|
2290
|
-
return { jsonrpc: '2.0', id, result }
|
|
2291
|
-
}
|
|
2292
|
-
|
|
2293
|
-
function jsonRpcError(id, code, message, data) {
|
|
2294
|
-
const error = { code, message }
|
|
2295
|
-
if (data !== undefined) error.data = data
|
|
2296
|
-
return { jsonrpc: '2.0', id, error }
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
function send(message) {
|
|
2300
|
-
if (message) process.stdout.write(`${JSON.stringify(message)}\n`)
|
|
2301
|
-
}
|
|
2302
|
-
|
|
2303
|
-
async function handleMessage(message) {
|
|
2304
|
-
if (!message || typeof message !== 'object' || message.jsonrpc !== '2.0') return
|
|
2305
|
-
if (message.method && message.method.startsWith('notifications/')) return
|
|
2306
|
-
if (message.id === undefined) return
|
|
2307
|
-
|
|
2308
|
-
try {
|
|
2309
|
-
if (message.method === 'initialize') {
|
|
2310
|
-
send(
|
|
2311
|
-
jsonRpcResult(message.id, {
|
|
2312
|
-
protocolVersion: PROTOCOL_VERSION,
|
|
2313
|
-
capabilities: { tools: {} },
|
|
2314
|
-
serverInfo: SERVER_INFO,
|
|
2315
|
-
})
|
|
2316
|
-
)
|
|
2317
|
-
return
|
|
2318
|
-
}
|
|
2319
|
-
if (message.method === 'ping') {
|
|
2320
|
-
send(jsonRpcResult(message.id, {}))
|
|
2321
|
-
return
|
|
2322
|
-
}
|
|
2323
|
-
if (message.method === 'tools/list') {
|
|
2324
|
-
send(jsonRpcResult(message.id, { tools: TOOLS }))
|
|
2325
|
-
return
|
|
2326
|
-
}
|
|
2327
|
-
if (message.method === 'tools/call') {
|
|
2328
|
-
const name = message.params && message.params.name
|
|
2329
|
-
const args = (message.params && message.params.arguments) || {}
|
|
2330
|
-
const result = await callTool(name, args)
|
|
2331
|
-
send(
|
|
2332
|
-
jsonRpcResult(message.id, {
|
|
2333
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
2334
|
-
isError: false,
|
|
2335
|
-
})
|
|
2336
|
-
)
|
|
2337
|
-
return
|
|
2338
|
-
}
|
|
2339
|
-
send(jsonRpcError(message.id, -32601, `Method not found: ${message.method}`))
|
|
2340
|
-
} catch (error) {
|
|
2341
|
-
send(jsonRpcError(message.id, -32603, error.message || String(error)))
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity })
|
|
2346
|
-
|
|
2347
|
-
rl.on('line', (line) => {
|
|
2348
|
-
let message
|
|
2349
|
-
try {
|
|
2350
|
-
message = JSON.parse(line)
|
|
2351
|
-
} catch (_) {
|
|
2352
|
-
return
|
|
2353
|
-
}
|
|
2354
|
-
handleMessage(message).catch((error) => {
|
|
2355
|
-
send(jsonRpcError(message && message.id, -32603, error.message || String(error)))
|
|
2356
|
-
})
|
|
2357
|
-
})
|
|
2358
|
-
|
|
2359
|
-
rl.on('close', () => {
|
|
2360
|
-
if (require.main === module) {
|
|
2361
|
-
process.exit(0)
|
|
2362
|
-
}
|
|
2363
|
-
})
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
function evolutionReport(args = {}) {
|
|
2367
|
-
const project = args.project || projectName()
|
|
2368
|
-
const period = String(args.period || 'all').trim()
|
|
2369
|
-
const now = Date.now()
|
|
2370
|
-
const periodMs = period === '24h' ? 86400000 : period === '7d' ? 604800000 : period === '30d' ? 2592000000 : Infinity
|
|
2371
|
-
const cutoff = periodMs === Infinity ? 0 : now - periodMs
|
|
2372
|
-
|
|
2373
|
-
const memories = readJsonl(memoryPath).filter((m) => !project || m.project === project || !m.project)
|
|
2374
|
-
const traces = readJsonl(tracePath).filter((t) => !project || t.project === project)
|
|
2375
|
-
const reuses = readJsonl(experienceReusePath).filter((r) => !project || r.project === project)
|
|
2376
|
-
const agents = readJsonl(agentsPath).filter((a) => !project || a.project === project)
|
|
2377
|
-
const queries = readJsonl(queryPath).filter((q) => !project || q.project === project)
|
|
2378
|
-
|
|
2379
|
-
const filteredTraces = cutoff ? traces.filter((t) => new Date(t.timestamp).getTime() >= cutoff) : traces
|
|
2380
|
-
const filteredMemories = cutoff ? memories.filter((m) => new Date(m.timestamp).getTime() >= cutoff) : memories
|
|
2381
|
-
|
|
2382
|
-
// Ecosystem summary
|
|
2383
|
-
const agentIds = new Set()
|
|
2384
|
-
filteredTraces.forEach((t) => { if (t.agent) agentIds.add(t.agent) })
|
|
2385
|
-
agents.forEach((a) => agentIds.add(a.agentId))
|
|
2386
|
-
const outcomes = filteredTraces.filter((t) => t.event_type === 'task_outcome')
|
|
2387
|
-
const successes = outcomes.filter((t) => t.outcome === 'success').length
|
|
2388
|
-
const failures = outcomes.filter((t) => t.outcome === 'failure').length
|
|
2389
|
-
const verifiedReuses = reuses.filter((r) => r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved)
|
|
2390
|
-
const pendingReuses = reuses.filter((r) => r.source === 'auto_claim' && !r.confirmed)
|
|
2391
|
-
|
|
2392
|
-
const ecosystem = {
|
|
2393
|
-
agents: agentIds.size,
|
|
2394
|
-
totalTasks: outcomes.length,
|
|
2395
|
-
successRate: outcomes.length > 0 ? Math.round((successes / outcomes.length) * 100) : 0,
|
|
2396
|
-
totalMemories: filteredMemories.length,
|
|
2397
|
-
totalReuses: reuses.length,
|
|
2398
|
-
verifiedReuses: verifiedReuses.length,
|
|
2399
|
-
pendingReuses: pendingReuses.length,
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
// Agent performance comparison
|
|
2403
|
-
const agentPerf = []
|
|
2404
|
-
for (const agentId of agentIds) {
|
|
2405
|
-
const agentTraces = filteredTraces.filter((t) => t.agent === agentId)
|
|
2406
|
-
const agentOutcomes = agentTraces.filter((t) => t.event_type === 'task_outcome')
|
|
2407
|
-
const agentSuccesses = agentOutcomes.filter((t) => t.outcome === 'success').length
|
|
2408
|
-
const agentFailures = agentOutcomes.filter((t) => t.outcome === 'failure').length
|
|
2409
|
-
const agentMemories = filteredMemories.filter((m) => m.source === agentId)
|
|
2410
|
-
const agentVerified = reuses.filter((r) =>
|
|
2411
|
-
(r.sourceAgent === agentId || r.targetAgent === agentId) && r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved
|
|
2412
|
-
)
|
|
2413
|
-
const reg = agents.find((a) => a.agentId === agentId)
|
|
2414
|
-
agentPerf.push({
|
|
2415
|
-
agentId,
|
|
2416
|
-
tasks: {
|
|
2417
|
-
total: agentOutcomes.length,
|
|
2418
|
-
success: agentSuccesses,
|
|
2419
|
-
failure: agentFailures,
|
|
2420
|
-
successRate: agentOutcomes.length > 0 ? Math.round((agentSuccesses / agentOutcomes.length) * 100) : 0,
|
|
2421
|
-
},
|
|
2422
|
-
memories: agentMemories.length,
|
|
2423
|
-
verifiedReuses: agentVerified.length,
|
|
2424
|
-
registered: !!reg,
|
|
2425
|
-
lastActive: reg ? reg.lastSeenAt : (agentTraces.length > 0 ? agentTraces[agentTraces.length - 1].timestamp : null),
|
|
2426
|
-
})
|
|
2427
|
-
}
|
|
2428
|
-
agentPerf.sort((a, b) => b.tasks.successRate - a.tasks.successRate || b.tasks.total - a.tasks.total)
|
|
2429
|
-
|
|
2430
|
-
// Cross-agent patterns
|
|
2431
|
-
const crossPatterns = []
|
|
2432
|
-
const reuseByPair = {}
|
|
2433
|
-
for (const r of reuses) {
|
|
2434
|
-
const key = r.sourceAgent + '->' + r.targetAgent
|
|
2435
|
-
if (!reuseByPair[key]) reuseByPair[key] = { source: r.sourceAgent, target: r.targetAgent, total: 0, verified: 0, improved: 0 }
|
|
2436
|
-
reuseByPair[key].total++
|
|
2437
|
-
if (r.confirmed) reuseByPair[key].verified++
|
|
2438
|
-
if (r.outcomeImproved) reuseByPair[key].improved++
|
|
2439
|
-
}
|
|
2440
|
-
for (const [key, pair] of Object.entries(reuseByPair)) {
|
|
2441
|
-
if (pair.source !== pair.target) {
|
|
2442
|
-
crossPatterns.push({
|
|
2443
|
-
type: 'reuse_path',
|
|
2444
|
-
from: pair.source,
|
|
2445
|
-
to: pair.target,
|
|
2446
|
-
total: pair.total,
|
|
2447
|
-
verified: pair.verified,
|
|
2448
|
-
improved: pair.improved,
|
|
2449
|
-
})
|
|
2450
|
-
}
|
|
2451
|
-
}
|
|
2452
|
-
crossPatterns.sort((a, b) => b.verified - a.verified || b.total - a.total)
|
|
2453
|
-
|
|
2454
|
-
// Memory health
|
|
2455
|
-
const byKind = {}
|
|
2456
|
-
let shortContent = 0
|
|
2457
|
-
let missingKind = 0
|
|
2458
|
-
for (const m of filteredMemories) {
|
|
2459
|
-
const kind = m.kind || 'unknown'
|
|
2460
|
-
byKind[kind] = (byKind[kind] || 0) + 1
|
|
2461
|
-
const content = String(m.content || '').trim()
|
|
2462
|
-
if (content.length < 20) shortContent++
|
|
2463
|
-
if (!m.kind) missingKind++
|
|
2464
|
-
}
|
|
2465
|
-
const memoryHealth = {
|
|
2466
|
-
total: filteredMemories.length,
|
|
2467
|
-
byKind,
|
|
2468
|
-
shortContent,
|
|
2469
|
-
missingKind,
|
|
2470
|
-
qualityScore: filteredMemories.length > 0
|
|
2471
|
-
? Math.round(((filteredMemories.length - shortContent - missingKind) / filteredMemories.length) * 100)
|
|
2472
|
-
: 100,
|
|
2473
|
-
}
|
|
2474
|
-
|
|
2475
|
-
// Suggestions engine
|
|
2476
|
-
const suggestions = []
|
|
2477
|
-
|
|
2478
|
-
// Routing suggestion: compare agent success rates
|
|
2479
|
-
if (agentPerf.length >= 2) {
|
|
2480
|
-
const best = agentPerf[0]
|
|
2481
|
-
const worst = agentPerf[agentPerf.length - 1]
|
|
2482
|
-
if (best.tasks.total >= 5 && worst.tasks.total >= 5 && best.tasks.successRate - worst.tasks.successRate >= 15) {
|
|
2483
|
-
suggestions.push({
|
|
2484
|
-
category: 'routing',
|
|
2485
|
-
priority: 'high',
|
|
2486
|
-
description: best.agentId + ' has ' + best.tasks.successRate + '% success rate vs ' + worst.agentId + ' at ' + worst.tasks.successRate + '%; prefer routing to ' + best.agentId,
|
|
2487
|
-
basedOn: 'task_outcome comparison',
|
|
2488
|
-
})
|
|
2489
|
-
}
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
// Pending auto-claim suggestion
|
|
2493
|
-
if (pendingReuses.length >= 10) {
|
|
2494
|
-
suggestions.push({
|
|
2495
|
-
category: 'evidence',
|
|
2496
|
-
priority: 'medium',
|
|
2497
|
-
description: pendingReuses.length + ' pending auto-claims awaiting confirmation; review cross-agent claims to increase verified reuse count',
|
|
2498
|
-
basedOn: 'experience_reuse.jsonl',
|
|
2499
|
-
})
|
|
2500
|
-
}
|
|
2501
|
-
|
|
2502
|
-
// Memory quality suggestion
|
|
2503
|
-
if (memoryHealth.qualityScore < 80 && filteredMemories.length > 10) {
|
|
2504
|
-
suggestions.push({
|
|
2505
|
-
category: 'memory',
|
|
2506
|
-
priority: 'medium',
|
|
2507
|
-
description: 'Memory quality score is ' + memoryHealth.qualityScore + '%; ' + shortContent + ' short records and ' + missingKind + ' missing kind fields found',
|
|
2508
|
-
basedOn: 'memory.jsonl quality analysis',
|
|
2509
|
-
})
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
// Cross-agent value suggestion
|
|
2513
|
-
if (crossPatterns.length === 0 && agentIds.size >= 2) {
|
|
2514
|
-
suggestions.push({
|
|
2515
|
-
category: 'interop',
|
|
2516
|
-
priority: 'high',
|
|
2517
|
-
description: 'Multiple agents registered but no cross-agent reuse patterns detected; ensure agents query shared memory',
|
|
2518
|
-
basedOn: 'experience_reuse.jsonl',
|
|
2519
|
-
})
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
|
-
// Active agent suggestion
|
|
2523
|
-
const inactiveAgents = agentPerf.filter((a) => {
|
|
2524
|
-
if (!a.lastActive) return false
|
|
2525
|
-
return (now - new Date(a.lastActive).getTime()) > 7 * 86400000
|
|
2526
|
-
})
|
|
2527
|
-
if (inactiveAgents.length > 0) {
|
|
2528
|
-
suggestions.push({
|
|
2529
|
-
category: 'coverage',
|
|
2530
|
-
priority: 'low',
|
|
2531
|
-
description: inactiveAgents.map((a) => a.agentId).join(', ') + ' inactive for >7 days; consider removal or reactivation',
|
|
2532
|
-
basedOn: 'agent registry lastSeenAt',
|
|
2533
|
-
})
|
|
2534
|
-
}
|
|
2535
|
-
|
|
2536
|
-
suggestions.sort((a, b) => {
|
|
2537
|
-
const p = { high: 0, medium: 1, low: 2 }
|
|
2538
|
-
return (p[a.priority] || 2) - (p[b.priority] || 2)
|
|
2539
|
-
})
|
|
2540
|
-
|
|
2541
|
-
return {
|
|
2542
|
-
project,
|
|
2543
|
-
generatedAt: new Date().toISOString(),
|
|
2544
|
-
period,
|
|
2545
|
-
ecosystem,
|
|
2546
|
-
agentPerformance: agentPerf,
|
|
2547
|
-
crossAgentPatterns: crossPatterns,
|
|
2548
|
-
memoryHealth,
|
|
2549
|
-
suggestions,
|
|
2550
|
-
}
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
function recordTaskOutcome(args = {}) {
|
|
2555
|
-
const outcome = String(args.outcome || '').trim().toLowerCase()
|
|
2556
|
-
if (!['success', 'failure', 'aborted'].includes(outcome)) {
|
|
2557
|
-
throw new Error('task.record_outcome requires outcome to be success, failure, or aborted')
|
|
2558
|
-
}
|
|
2559
|
-
const agentId = String(args.agentId || runtimeAgentId() || 'unknown').trim()
|
|
2560
|
-
const project = args.project || projectName()
|
|
2561
|
-
const task = String(args.task || '').trim()
|
|
2562
|
-
const summary = String(args.summary || '').trim()
|
|
2563
|
-
const verification = String(args.verification || '').trim()
|
|
2564
|
-
const traceId = String(args.traceId || '').trim() || createId('trace:' + agentId + ':' + project)
|
|
2565
|
-
|
|
2566
|
-
// 1. Record trace event
|
|
2567
|
-
const event = {
|
|
2568
|
-
id: createId('trace'),
|
|
2569
|
-
timestamp: new Date().toISOString(),
|
|
2570
|
-
trace_id: traceId,
|
|
2571
|
-
event_type: 'task_outcome',
|
|
2572
|
-
outcome,
|
|
2573
|
-
payload: { task, summary, verification },
|
|
2574
|
-
agent: agentId,
|
|
2575
|
-
host: 'mcp',
|
|
2576
|
-
project,
|
|
2577
|
-
}
|
|
2578
|
-
appendJsonl(tracePath, event)
|
|
2579
|
-
|
|
2580
|
-
// 2. Auto-claim experience reuse
|
|
2581
|
-
const autoClaims = autoClaimExperienceReuse(event)
|
|
2582
|
-
|
|
2583
|
-
// 3. Update agent registry
|
|
2584
|
-
const now = new Date().toISOString()
|
|
2585
|
-
const agents = readJsonl(agentsPath)
|
|
2586
|
-
const agent = agents.find((a) => a.agentId === agentId && a.project === project)
|
|
2587
|
-
if (agent) {
|
|
2588
|
-
agent.lastSeenAt = now
|
|
2589
|
-
agent.taskCount = (agent.taskCount || 0) + 1
|
|
2590
|
-
agent.successCount = (agent.successCount || 0) + (outcome === 'success' ? 1 : 0)
|
|
2591
|
-
agent.failureCount = (agent.failureCount || 0) + (outcome === 'failure' ? 1 : 0)
|
|
2592
|
-
writeJsonl(agentsPath, agents)
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
return {
|
|
2596
|
-
recorded: true,
|
|
2597
|
-
event: { id: event.id, trace_id: traceId, outcome, agent: agentId, project },
|
|
2598
|
-
agentUpdated: !!agent,
|
|
2599
|
-
autoClaims: autoClaims.length > 0 ? autoClaims : undefined,
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
|
|
2603
|
-
module.exports = { TOOLS, callTool, handleMessage, readJsonl, appendJsonl, rl }
|
|
2197
|
+
throw new Error(`Unknown tool: ${name}`)
|
|
2198
|
+
}
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
function jsonRpcResult(id, result) {
|
|
2202
|
+
return { jsonrpc: '2.0', id, result }
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
function jsonRpcError(id, code, message, data) {
|
|
2206
|
+
const error = { code, message }
|
|
2207
|
+
if (data !== undefined) error.data = data
|
|
2208
|
+
return { jsonrpc: '2.0', id, error }
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
function send(message) {
|
|
2212
|
+
if (message) process.stdout.write(`${JSON.stringify(message)}\n`)
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
async function handleMessage(message) {
|
|
2216
|
+
if (!message || typeof message !== 'object' || message.jsonrpc !== '2.0') return
|
|
2217
|
+
if (message.method && message.method.startsWith('notifications/')) return
|
|
2218
|
+
if (message.id === undefined) return
|
|
2219
|
+
|
|
2220
|
+
try {
|
|
2221
|
+
if (message.method === 'initialize') {
|
|
2222
|
+
send(
|
|
2223
|
+
jsonRpcResult(message.id, {
|
|
2224
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
2225
|
+
capabilities: { tools: {} },
|
|
2226
|
+
serverInfo: SERVER_INFO,
|
|
2227
|
+
})
|
|
2228
|
+
)
|
|
2229
|
+
return
|
|
2230
|
+
}
|
|
2231
|
+
if (message.method === 'ping') {
|
|
2232
|
+
send(jsonRpcResult(message.id, {}))
|
|
2233
|
+
return
|
|
2234
|
+
}
|
|
2235
|
+
if (message.method === 'tools/list') {
|
|
2236
|
+
send(jsonRpcResult(message.id, { tools: TOOLS }))
|
|
2237
|
+
return
|
|
2238
|
+
}
|
|
2239
|
+
if (message.method === 'tools/call') {
|
|
2240
|
+
const name = message.params && message.params.name
|
|
2241
|
+
const args = (message.params && message.params.arguments) || {}
|
|
2242
|
+
const result = await callTool(name, args)
|
|
2243
|
+
send(
|
|
2244
|
+
jsonRpcResult(message.id, {
|
|
2245
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
2246
|
+
isError: false,
|
|
2247
|
+
})
|
|
2248
|
+
)
|
|
2249
|
+
return
|
|
2250
|
+
}
|
|
2251
|
+
send(jsonRpcError(message.id, -32601, `Method not found: ${message.method}`))
|
|
2252
|
+
} catch (error) {
|
|
2253
|
+
send(jsonRpcError(message.id, -32603, error.message || String(error)))
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
const rl = readline.createInterface({ input: process.stdin, crlfDelay: Infinity })
|
|
2258
|
+
|
|
2259
|
+
rl.on('line', (line) => {
|
|
2260
|
+
let message
|
|
2261
|
+
try {
|
|
2262
|
+
message = JSON.parse(line)
|
|
2263
|
+
} catch (_) {
|
|
2264
|
+
return
|
|
2265
|
+
}
|
|
2266
|
+
handleMessage(message).catch((error) => {
|
|
2267
|
+
send(jsonRpcError(message && message.id, -32603, error.message || String(error)))
|
|
2268
|
+
})
|
|
2269
|
+
})
|
|
2270
|
+
|
|
2271
|
+
rl.on('close', () => {
|
|
2272
|
+
if (require.main === module) {
|
|
2273
|
+
process.exit(0)
|
|
2274
|
+
}
|
|
2275
|
+
})
|
|
2276
|
+
|
|
2277
|
+
|
|
2278
|
+
function evolutionReport(args = {}) {
|
|
2279
|
+
const project = args.project || projectName()
|
|
2280
|
+
const period = String(args.period || 'all').trim()
|
|
2281
|
+
const now = Date.now()
|
|
2282
|
+
const periodMs = period === '24h' ? 86400000 : period === '7d' ? 604800000 : period === '30d' ? 2592000000 : Infinity
|
|
2283
|
+
const cutoff = periodMs === Infinity ? 0 : now - periodMs
|
|
2284
|
+
|
|
2285
|
+
const memories = readJsonl(memoryPath).filter((m) => !project || m.project === project || !m.project)
|
|
2286
|
+
const traces = readJsonl(tracePath).filter((t) => !project || t.project === project)
|
|
2287
|
+
const reuses = readJsonl(experienceReusePath).filter((r) => !project || r.project === project)
|
|
2288
|
+
const agents = readJsonl(agentsPath).filter((a) => !project || a.project === project)
|
|
2289
|
+
const queries = readJsonl(queryPath).filter((q) => !project || q.project === project)
|
|
2290
|
+
|
|
2291
|
+
const filteredTraces = cutoff ? traces.filter((t) => new Date(t.timestamp).getTime() >= cutoff) : traces
|
|
2292
|
+
const filteredMemories = cutoff ? memories.filter((m) => new Date(m.timestamp).getTime() >= cutoff) : memories
|
|
2293
|
+
|
|
2294
|
+
// Ecosystem summary
|
|
2295
|
+
const agentIds = new Set()
|
|
2296
|
+
filteredTraces.forEach((t) => { if (t.agent) agentIds.add(t.agent) })
|
|
2297
|
+
agents.forEach((a) => agentIds.add(a.agentId))
|
|
2298
|
+
const outcomes = filteredTraces.filter((t) => t.event_type === 'task_outcome')
|
|
2299
|
+
const successes = outcomes.filter((t) => t.outcome === 'success').length
|
|
2300
|
+
const failures = outcomes.filter((t) => t.outcome === 'failure').length
|
|
2301
|
+
const verifiedReuses = reuses.filter((r) => r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved)
|
|
2302
|
+
const pendingReuses = reuses.filter((r) => r.source === 'auto_claim' && !r.confirmed)
|
|
2303
|
+
|
|
2304
|
+
const ecosystem = {
|
|
2305
|
+
agents: agentIds.size,
|
|
2306
|
+
totalTasks: outcomes.length,
|
|
2307
|
+
successRate: outcomes.length > 0 ? Math.round((successes / outcomes.length) * 100) : 0,
|
|
2308
|
+
totalMemories: filteredMemories.length,
|
|
2309
|
+
totalReuses: reuses.length,
|
|
2310
|
+
verifiedReuses: verifiedReuses.length,
|
|
2311
|
+
pendingReuses: pendingReuses.length,
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
// Agent performance comparison
|
|
2315
|
+
const agentPerf = []
|
|
2316
|
+
for (const agentId of agentIds) {
|
|
2317
|
+
const agentTraces = filteredTraces.filter((t) => t.agent === agentId)
|
|
2318
|
+
const agentOutcomes = agentTraces.filter((t) => t.event_type === 'task_outcome')
|
|
2319
|
+
const agentSuccesses = agentOutcomes.filter((t) => t.outcome === 'success').length
|
|
2320
|
+
const agentFailures = agentOutcomes.filter((t) => t.outcome === 'failure').length
|
|
2321
|
+
const agentMemories = filteredMemories.filter((m) => m.source === agentId)
|
|
2322
|
+
const agentVerified = reuses.filter((r) =>
|
|
2323
|
+
(r.sourceAgent === agentId || r.targetAgent === agentId) && r.confirmed && r.reuse && r.behaviorChanged && r.outcomeImproved
|
|
2324
|
+
)
|
|
2325
|
+
const reg = agents.find((a) => a.agentId === agentId)
|
|
2326
|
+
agentPerf.push({
|
|
2327
|
+
agentId,
|
|
2328
|
+
tasks: {
|
|
2329
|
+
total: agentOutcomes.length,
|
|
2330
|
+
success: agentSuccesses,
|
|
2331
|
+
failure: agentFailures,
|
|
2332
|
+
successRate: agentOutcomes.length > 0 ? Math.round((agentSuccesses / agentOutcomes.length) * 100) : 0,
|
|
2333
|
+
},
|
|
2334
|
+
memories: agentMemories.length,
|
|
2335
|
+
verifiedReuses: agentVerified.length,
|
|
2336
|
+
registered: !!reg,
|
|
2337
|
+
lastActive: reg ? reg.lastSeenAt : (agentTraces.length > 0 ? agentTraces[agentTraces.length - 1].timestamp : null),
|
|
2338
|
+
})
|
|
2339
|
+
}
|
|
2340
|
+
agentPerf.sort((a, b) => b.tasks.successRate - a.tasks.successRate || b.tasks.total - a.tasks.total)
|
|
2341
|
+
|
|
2342
|
+
// Cross-agent patterns
|
|
2343
|
+
const crossPatterns = []
|
|
2344
|
+
const reuseByPair = {}
|
|
2345
|
+
for (const r of reuses) {
|
|
2346
|
+
const key = r.sourceAgent + '->' + r.targetAgent
|
|
2347
|
+
if (!reuseByPair[key]) reuseByPair[key] = { source: r.sourceAgent, target: r.targetAgent, total: 0, verified: 0, improved: 0 }
|
|
2348
|
+
reuseByPair[key].total++
|
|
2349
|
+
if (r.confirmed) reuseByPair[key].verified++
|
|
2350
|
+
if (r.outcomeImproved) reuseByPair[key].improved++
|
|
2351
|
+
}
|
|
2352
|
+
for (const [key, pair] of Object.entries(reuseByPair)) {
|
|
2353
|
+
if (pair.source !== pair.target) {
|
|
2354
|
+
crossPatterns.push({
|
|
2355
|
+
type: 'reuse_path',
|
|
2356
|
+
from: pair.source,
|
|
2357
|
+
to: pair.target,
|
|
2358
|
+
total: pair.total,
|
|
2359
|
+
verified: pair.verified,
|
|
2360
|
+
improved: pair.improved,
|
|
2361
|
+
})
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
crossPatterns.sort((a, b) => b.verified - a.verified || b.total - a.total)
|
|
2365
|
+
|
|
2366
|
+
// Memory health
|
|
2367
|
+
const byKind = {}
|
|
2368
|
+
let shortContent = 0
|
|
2369
|
+
let missingKind = 0
|
|
2370
|
+
for (const m of filteredMemories) {
|
|
2371
|
+
const kind = m.kind || 'unknown'
|
|
2372
|
+
byKind[kind] = (byKind[kind] || 0) + 1
|
|
2373
|
+
const content = String(m.content || '').trim()
|
|
2374
|
+
if (content.length < 20) shortContent++
|
|
2375
|
+
if (!m.kind) missingKind++
|
|
2376
|
+
}
|
|
2377
|
+
const memoryHealth = {
|
|
2378
|
+
total: filteredMemories.length,
|
|
2379
|
+
byKind,
|
|
2380
|
+
shortContent,
|
|
2381
|
+
missingKind,
|
|
2382
|
+
qualityScore: filteredMemories.length > 0
|
|
2383
|
+
? Math.round(((filteredMemories.length - shortContent - missingKind) / filteredMemories.length) * 100)
|
|
2384
|
+
: 100,
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
// Suggestions engine
|
|
2388
|
+
const suggestions = []
|
|
2389
|
+
|
|
2390
|
+
// Routing suggestion: compare agent success rates
|
|
2391
|
+
if (agentPerf.length >= 2) {
|
|
2392
|
+
const best = agentPerf[0]
|
|
2393
|
+
const worst = agentPerf[agentPerf.length - 1]
|
|
2394
|
+
if (best.tasks.total >= 5 && worst.tasks.total >= 5 && best.tasks.successRate - worst.tasks.successRate >= 15) {
|
|
2395
|
+
suggestions.push({
|
|
2396
|
+
category: 'routing',
|
|
2397
|
+
priority: 'high',
|
|
2398
|
+
description: best.agentId + ' has ' + best.tasks.successRate + '% success rate vs ' + worst.agentId + ' at ' + worst.tasks.successRate + '%; prefer routing to ' + best.agentId,
|
|
2399
|
+
basedOn: 'task_outcome comparison',
|
|
2400
|
+
})
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
// Pending auto-claim suggestion
|
|
2405
|
+
if (pendingReuses.length >= 10) {
|
|
2406
|
+
suggestions.push({
|
|
2407
|
+
category: 'evidence',
|
|
2408
|
+
priority: 'medium',
|
|
2409
|
+
description: pendingReuses.length + ' pending auto-claims awaiting confirmation; review cross-agent claims to increase verified reuse count',
|
|
2410
|
+
basedOn: 'experience_reuse.jsonl',
|
|
2411
|
+
})
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// Memory quality suggestion
|
|
2415
|
+
if (memoryHealth.qualityScore < 80 && filteredMemories.length > 10) {
|
|
2416
|
+
suggestions.push({
|
|
2417
|
+
category: 'memory',
|
|
2418
|
+
priority: 'medium',
|
|
2419
|
+
description: 'Memory quality score is ' + memoryHealth.qualityScore + '%; ' + shortContent + ' short records and ' + missingKind + ' missing kind fields found',
|
|
2420
|
+
basedOn: 'memory.jsonl quality analysis',
|
|
2421
|
+
})
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2424
|
+
// Cross-agent value suggestion
|
|
2425
|
+
if (crossPatterns.length === 0 && agentIds.size >= 2) {
|
|
2426
|
+
suggestions.push({
|
|
2427
|
+
category: 'interop',
|
|
2428
|
+
priority: 'high',
|
|
2429
|
+
description: 'Multiple agents registered but no cross-agent reuse patterns detected; ensure agents query shared memory',
|
|
2430
|
+
basedOn: 'experience_reuse.jsonl',
|
|
2431
|
+
})
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// Active agent suggestion
|
|
2435
|
+
const inactiveAgents = agentPerf.filter((a) => {
|
|
2436
|
+
if (!a.lastActive) return false
|
|
2437
|
+
return (now - new Date(a.lastActive).getTime()) > 7 * 86400000
|
|
2438
|
+
})
|
|
2439
|
+
if (inactiveAgents.length > 0) {
|
|
2440
|
+
suggestions.push({
|
|
2441
|
+
category: 'coverage',
|
|
2442
|
+
priority: 'low',
|
|
2443
|
+
description: inactiveAgents.map((a) => a.agentId).join(', ') + ' inactive for >7 days; consider removal or reactivation',
|
|
2444
|
+
basedOn: 'agent registry lastSeenAt',
|
|
2445
|
+
})
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
suggestions.sort((a, b) => {
|
|
2449
|
+
const p = { high: 0, medium: 1, low: 2 }
|
|
2450
|
+
return (p[a.priority] || 2) - (p[b.priority] || 2)
|
|
2451
|
+
})
|
|
2452
|
+
|
|
2453
|
+
return {
|
|
2454
|
+
project,
|
|
2455
|
+
generatedAt: new Date().toISOString(),
|
|
2456
|
+
period,
|
|
2457
|
+
ecosystem,
|
|
2458
|
+
agentPerformance: agentPerf,
|
|
2459
|
+
crossAgentPatterns: crossPatterns,
|
|
2460
|
+
memoryHealth,
|
|
2461
|
+
suggestions,
|
|
2462
|
+
}
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
|
|
2466
|
+
function recordTaskOutcome(args = {}) {
|
|
2467
|
+
const outcome = String(args.outcome || '').trim().toLowerCase()
|
|
2468
|
+
if (!['success', 'failure', 'aborted'].includes(outcome)) {
|
|
2469
|
+
throw new Error('task.record_outcome requires outcome to be success, failure, or aborted')
|
|
2470
|
+
}
|
|
2471
|
+
const agentId = String(args.agentId || runtimeAgentId() || 'unknown').trim()
|
|
2472
|
+
const project = args.project || projectName()
|
|
2473
|
+
const task = String(args.task || '').trim()
|
|
2474
|
+
const summary = String(args.summary || '').trim()
|
|
2475
|
+
const verification = String(args.verification || '').trim()
|
|
2476
|
+
const traceId = String(args.traceId || '').trim() || createId('trace:' + agentId + ':' + project)
|
|
2477
|
+
|
|
2478
|
+
// 1. Record trace event
|
|
2479
|
+
const event = {
|
|
2480
|
+
id: createId('trace'),
|
|
2481
|
+
timestamp: new Date().toISOString(),
|
|
2482
|
+
trace_id: traceId,
|
|
2483
|
+
event_type: 'task_outcome',
|
|
2484
|
+
outcome,
|
|
2485
|
+
payload: { task, summary, verification },
|
|
2486
|
+
agent: agentId,
|
|
2487
|
+
host: 'mcp',
|
|
2488
|
+
project,
|
|
2489
|
+
}
|
|
2490
|
+
appendJsonl(tracePath, event)
|
|
2491
|
+
|
|
2492
|
+
// 2. Auto-claim experience reuse
|
|
2493
|
+
const autoClaims = autoClaimExperienceReuse(event)
|
|
2494
|
+
|
|
2495
|
+
// 3. Update agent registry
|
|
2496
|
+
const now = new Date().toISOString()
|
|
2497
|
+
const agents = readJsonl(agentsPath)
|
|
2498
|
+
const agent = agents.find((a) => a.agentId === agentId && a.project === project)
|
|
2499
|
+
if (agent) {
|
|
2500
|
+
agent.lastSeenAt = now
|
|
2501
|
+
agent.taskCount = (agent.taskCount || 0) + 1
|
|
2502
|
+
agent.successCount = (agent.successCount || 0) + (outcome === 'success' ? 1 : 0)
|
|
2503
|
+
agent.failureCount = (agent.failureCount || 0) + (outcome === 'failure' ? 1 : 0)
|
|
2504
|
+
writeJsonl(agentsPath, agents)
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
return {
|
|
2508
|
+
recorded: true,
|
|
2509
|
+
event: { id: event.id, trace_id: traceId, outcome, agent: agentId, project },
|
|
2510
|
+
agentUpdated: !!agent,
|
|
2511
|
+
autoClaims: autoClaims.length > 0 ? autoClaims : undefined,
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
|
|
2515
|
+
module.exports = { TOOLS, callTool, handleMessage, readJsonl, appendJsonl, rl }
|