mio-agent-runtime 0.5.15 → 0.5.16
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 +144 -236
- package/adapters/codex.js +122 -134
- package/adapters/hermes.js +122 -140
- package/adapters/host-utils.js +61 -61
- package/adapters/opencode.js +176 -215
- package/adapters/workbuddy.js +196 -242
- package/bin/mio.js +423 -424
- package/observe/observer.js +1326 -1335
- package/package.json +27 -16
- package/scripts/verify-packed-runtime.js +371 -0
- package/server/evolution-cutover.js +119 -0
- package/server/host-capabilities.js +50 -0
- package/server/mio-intelligence-mcp/README.md +184 -173
- package/server/mio-intelligence-mcp/index.js +116 -298
- package/server/mio-intelligence-mcp/package.json +17 -17
- package/server/mio-intelligence-mcp/phase0.js +381 -381
- package/server/runtime-modules.js +111 -0
- package/server/mio-intelligence-mcp/dashboard-server.js +0 -171
package/package.json
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "mio-agent-runtime",
|
|
3
|
-
"version": "0.5.
|
|
4
|
-
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
|
-
"license": "MIT",
|
|
6
|
-
"type": "commonjs",
|
|
7
|
-
"bin": {
|
|
8
|
-
"mio": "bin/mio.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"bin/",
|
|
1
|
+
{
|
|
2
|
+
"name": "mio-agent-runtime",
|
|
3
|
+
"version": "0.5.16",
|
|
4
|
+
"description": "Mio Agent Runtime installer and MCP intelligence layer",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "commonjs",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mio": "bin/mio.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
12
|
"server/",
|
|
13
13
|
"adapters/",
|
|
14
14
|
"observe/",
|
|
15
|
+
"scripts/",
|
|
15
16
|
"README.md",
|
|
16
|
-
"!server/mio-intelligence-mcp/__tests__"
|
|
17
|
-
],
|
|
17
|
+
"!server/mio-intelligence-mcp/__tests__"
|
|
18
|
+
],
|
|
18
19
|
"scripts": {
|
|
19
|
-
"check": "node --check bin/mio.js && node --check adapters/host-utils.js && node --check adapters/codex.js && node --check adapters/opencode.js && node --check adapters/workbuddy.js && node --check adapters/hermes.js && node --check server/
|
|
20
|
-
"prepack": "npm run check"
|
|
20
|
+
"check": "node --check bin/mio.js && node --check adapters/host-utils.js && node --check adapters/codex.js && node --check adapters/opencode.js && node --check adapters/workbuddy.js && node --check adapters/hermes.js && node --check server/runtime-modules.js && node --check server/host-capabilities.js && node --check server/evolution-cutover.js && node --check server/mio-intelligence-mcp/index.js && node --check server/mio-intelligence-mcp/phase0.js && node --check observe/observer.js && node --check scripts/verify-packed-runtime.js",
|
|
21
|
+
"prepack": "npm run check",
|
|
22
|
+
"verify:pack-install": "node scripts/verify-packed-runtime.js"
|
|
21
23
|
},
|
|
22
24
|
"engines": {
|
|
23
25
|
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@akemi-mio/runtime-contracts": "^0.1.0",
|
|
29
|
+
"@akemi-mio/runtime-foundation": "^0.1.0",
|
|
30
|
+
"@akemi-mio/experience-memory": "^0.1.0",
|
|
31
|
+
"@akemi-mio/evolution-learning": "^0.1.0",
|
|
32
|
+
"@akemi-mio/evolution-strategy": "^0.1.0",
|
|
33
|
+
"@akemi-mio/evolution-safety": "^0.1.0",
|
|
34
|
+
"@akemi-mio/evolution-scheduler": "^0.1.0"
|
|
24
35
|
}
|
|
25
|
-
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
const os = require('node:os')
|
|
6
|
+
const path = require('node:path')
|
|
7
|
+
const crypto = require('node:crypto')
|
|
8
|
+
const { execFileSync } = require('node:child_process')
|
|
9
|
+
|
|
10
|
+
const RUNTIME_PACKAGE_NAMES = [
|
|
11
|
+
'@akemi-mio/runtime-contracts',
|
|
12
|
+
'@akemi-mio/runtime-foundation',
|
|
13
|
+
'@akemi-mio/experience-memory',
|
|
14
|
+
'@akemi-mio/evolution-learning',
|
|
15
|
+
'@akemi-mio/evolution-strategy',
|
|
16
|
+
'@akemi-mio/evolution-safety',
|
|
17
|
+
'@akemi-mio/evolution-scheduler',
|
|
18
|
+
'mio-agent-runtime',
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
function runtimePackageNames() {
|
|
22
|
+
return [...RUNTIME_PACKAGE_NAMES]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function usage() {
|
|
26
|
+
return [
|
|
27
|
+
'Usage: node scripts/verify-packed-runtime.js [options]',
|
|
28
|
+
'',
|
|
29
|
+
'Options:',
|
|
30
|
+
' --workspace-root <path> Workspace root that contains packages/',
|
|
31
|
+
' --artifact-dir <path> Directory where npm pack writes tarballs',
|
|
32
|
+
' --install-dir <path> Clean temporary npm project directory',
|
|
33
|
+
' --report-path <path> Optional JSON evidence report path; must not exist',
|
|
34
|
+
' --help Show this help message',
|
|
35
|
+
].join('\n')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function parseVerifyPackedRuntimeArgs(argv) {
|
|
39
|
+
const options = { help: false }
|
|
40
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
41
|
+
const option = argv[index]
|
|
42
|
+
if (option === '--help' || option === '-h') return { help: true }
|
|
43
|
+
if (!['--workspace-root', '--artifact-dir', '--install-dir', '--report-path'].includes(option)) {
|
|
44
|
+
throw new Error(`Unknown option: ${option}`)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const value = argv[index + 1]
|
|
48
|
+
if (!value || value.startsWith('--')) {
|
|
49
|
+
throw new Error(`Missing value for ${option}`)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (option === '--workspace-root') options.workspaceRoot = value
|
|
53
|
+
if (option === '--artifact-dir') options.artifactDir = value
|
|
54
|
+
if (option === '--install-dir') options.installDir = value
|
|
55
|
+
if (option === '--report-path') options.reportPath = value
|
|
56
|
+
index += 1
|
|
57
|
+
}
|
|
58
|
+
return options
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createVerifyPackedRuntimeOptions(argv, env = process.env) {
|
|
62
|
+
const envOptions = {}
|
|
63
|
+
if (env.MIO_PACK_WORKSPACE_ROOT) envOptions.workspaceRoot = env.MIO_PACK_WORKSPACE_ROOT
|
|
64
|
+
if (env.MIO_PACK_ARTIFACT_DIR) envOptions.artifactDir = env.MIO_PACK_ARTIFACT_DIR
|
|
65
|
+
if (env.MIO_PACK_INSTALL_DIR) envOptions.installDir = env.MIO_PACK_INSTALL_DIR
|
|
66
|
+
if (env.MIO_PACK_REPORT_PATH) envOptions.reportPath = env.MIO_PACK_REPORT_PATH
|
|
67
|
+
|
|
68
|
+
const cliOptions = parseVerifyPackedRuntimeArgs(argv)
|
|
69
|
+
if (cliOptions.help) return { help: true }
|
|
70
|
+
return { ...envOptions, ...cliOptions, help: false }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function npmCommand(platform = process.platform) {
|
|
74
|
+
return platform === 'win32' ? 'npm.cmd' : 'npm'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function npmInvocation(args, env = process.env, platform = process.platform, node = process.execPath) {
|
|
78
|
+
if (env.npm_execpath) {
|
|
79
|
+
return { command: node, args: [env.npm_execpath, ...args] }
|
|
80
|
+
}
|
|
81
|
+
if (platform === 'win32') {
|
|
82
|
+
return { command: 'cmd.exe', args: ['/d', '/s', '/c', 'npm', ...args] }
|
|
83
|
+
}
|
|
84
|
+
return { command: npmCommand(platform), args }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function packageDirName(name) {
|
|
88
|
+
if (name === 'mio-agent-runtime') return 'mio-cli'
|
|
89
|
+
return name.replace('@akemi-mio/', '')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function tarballFileName(name, version) {
|
|
93
|
+
return `${name.replace(/^@/, '').replace('/', '-')}-${version}.tgz`
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function readPackageJson(workspaceRoot, name) {
|
|
97
|
+
const packageDir = path.join(workspaceRoot, 'packages', packageDirName(name))
|
|
98
|
+
return {
|
|
99
|
+
packageDir,
|
|
100
|
+
manifest: JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8')),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function createPackedRuntimeInstallPlan(input = {}) {
|
|
105
|
+
const workspaceRoot = path.resolve(input.workspaceRoot || path.join(__dirname, '..', '..', '..'))
|
|
106
|
+
const artifactDir = path.resolve(input.artifactDir || fs.mkdtempSync(path.join(os.tmpdir(), 'mio-pack-artifacts-')))
|
|
107
|
+
const installDir = path.resolve(input.installDir || fs.mkdtempSync(path.join(os.tmpdir(), 'mio-pack-install-')))
|
|
108
|
+
const cli = path.join(installDir, 'node_modules', 'mio-agent-runtime', 'bin', 'mio.js')
|
|
109
|
+
const packages = RUNTIME_PACKAGE_NAMES.map((name) => {
|
|
110
|
+
const { packageDir, manifest } = readPackageJson(workspaceRoot, name)
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
version: manifest.version,
|
|
114
|
+
packageDir,
|
|
115
|
+
tarball: path.join(artifactDir, tarballFileName(name, manifest.version)),
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
workspaceRoot,
|
|
121
|
+
artifactDir,
|
|
122
|
+
installDir,
|
|
123
|
+
packages,
|
|
124
|
+
install: {
|
|
125
|
+
cwd: installDir,
|
|
126
|
+
args: ['install', '--ignore-scripts', '--no-audit', '--no-fund', ...packages.map((pkg) => pkg.tarball)],
|
|
127
|
+
},
|
|
128
|
+
smoke: {
|
|
129
|
+
cwd: installDir,
|
|
130
|
+
args: [cli, '--json', 'evolution', 'status'],
|
|
131
|
+
status: {
|
|
132
|
+
cwd: installDir,
|
|
133
|
+
args: [cli, '--json', 'evolution', 'status'],
|
|
134
|
+
},
|
|
135
|
+
cutover: [
|
|
136
|
+
{
|
|
137
|
+
cwd: installDir,
|
|
138
|
+
args: [
|
|
139
|
+
cli,
|
|
140
|
+
'--json',
|
|
141
|
+
'evolution',
|
|
142
|
+
'shadow',
|
|
143
|
+
'record',
|
|
144
|
+
'--project',
|
|
145
|
+
'pack-install-smoke',
|
|
146
|
+
'--label',
|
|
147
|
+
'installed status parity',
|
|
148
|
+
'--legacy',
|
|
149
|
+
'{"modules":7}',
|
|
150
|
+
'--modular',
|
|
151
|
+
'{"modules":7}',
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
cwd: installDir,
|
|
156
|
+
args: [
|
|
157
|
+
cli,
|
|
158
|
+
'--json',
|
|
159
|
+
'evolution',
|
|
160
|
+
'dual-write',
|
|
161
|
+
'record',
|
|
162
|
+
'--project',
|
|
163
|
+
'pack-install-smoke',
|
|
164
|
+
'--label',
|
|
165
|
+
'installed memory parity',
|
|
166
|
+
'--authoritative',
|
|
167
|
+
'legacy',
|
|
168
|
+
'--legacy-result',
|
|
169
|
+
'{"id":"sample","ok":true}',
|
|
170
|
+
'--modular-result',
|
|
171
|
+
'{"id":"sample","ok":true}',
|
|
172
|
+
],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
cwd: installDir,
|
|
176
|
+
args: [
|
|
177
|
+
cli,
|
|
178
|
+
'--json',
|
|
179
|
+
'evolution',
|
|
180
|
+
'cutover',
|
|
181
|
+
'readiness',
|
|
182
|
+
'--project',
|
|
183
|
+
'pack-install-smoke',
|
|
184
|
+
'--min-shadow-runs',
|
|
185
|
+
'1',
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
cwd: installDir,
|
|
190
|
+
args: [
|
|
191
|
+
cli,
|
|
192
|
+
'--json',
|
|
193
|
+
'evolution',
|
|
194
|
+
'cutover',
|
|
195
|
+
'apply',
|
|
196
|
+
'--dry-run',
|
|
197
|
+
'--project',
|
|
198
|
+
'pack-install-smoke',
|
|
199
|
+
'--plan',
|
|
200
|
+
'{"approved":true,"from":"legacy","to":"modular","actions":[{"type":"set_authority","from":"legacy","to":"modular"}]}',
|
|
201
|
+
],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function run(command, args, options = {}) {
|
|
209
|
+
const output = execFileSync(command, args, {
|
|
210
|
+
cwd: options.cwd,
|
|
211
|
+
env: options.env || process.env,
|
|
212
|
+
encoding: 'utf8',
|
|
213
|
+
stdio: options.stdio || ['ignore', 'pipe', 'pipe'],
|
|
214
|
+
})
|
|
215
|
+
return output ? output.trim() : ''
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function packageArtifactEvidence(pkg) {
|
|
219
|
+
if (!fs.existsSync(pkg.tarball)) {
|
|
220
|
+
throw new Error(`Expected tarball was not created: ${pkg.tarball}`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const content = fs.readFileSync(pkg.tarball)
|
|
224
|
+
return {
|
|
225
|
+
name: pkg.name,
|
|
226
|
+
version: pkg.version,
|
|
227
|
+
tarball: pkg.tarball,
|
|
228
|
+
sizeBytes: fs.statSync(pkg.tarball).size,
|
|
229
|
+
sha512: crypto.createHash('sha512').update(content).digest('hex'),
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function writeVerificationReport(result, reportPath) {
|
|
234
|
+
const resolvedReportPath = path.resolve(reportPath)
|
|
235
|
+
if (fs.existsSync(resolvedReportPath)) {
|
|
236
|
+
throw new Error(`Verification report already exists: ${resolvedReportPath}`)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fs.mkdirSync(path.dirname(resolvedReportPath), { recursive: true })
|
|
240
|
+
fs.writeFileSync(resolvedReportPath, `${JSON.stringify(result, null, 2)}\n`)
|
|
241
|
+
return resolvedReportPath
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function summarizeInstalledSmoke(status, cutover) {
|
|
245
|
+
const modules = Array.isArray(status.modules) ? status.modules : []
|
|
246
|
+
const moduleNames = new Set(modules.map((module) => module.name))
|
|
247
|
+
const expectedModules = RUNTIME_PACKAGE_NAMES.filter((name) => name.startsWith('@akemi-mio/'))
|
|
248
|
+
const missingModules = expectedModules.filter((name) => !moduleNames.has(name))
|
|
249
|
+
if (missingModules.length > 0) {
|
|
250
|
+
throw new Error(`Installed runtime status is missing modules: ${missingModules.join(', ')}`)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const failedModules = modules.filter((module) => module.loaded !== true || module.healthy !== true)
|
|
254
|
+
if (failedModules.length > 0) {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Installed runtime modules failed health check: ${failedModules.map((module) => module.name).join(', ')}`,
|
|
257
|
+
)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const shadow = cutover[0]
|
|
261
|
+
const dualWrite = cutover[1]
|
|
262
|
+
const readiness = cutover.find((entry) => entry && entry.status === 'pass')
|
|
263
|
+
const apply = cutover.find((entry) => entry && entry.dryRun === true)
|
|
264
|
+
|
|
265
|
+
if (!shadow || shadow.matched !== true) throw new Error('Installed shadow smoke did not match')
|
|
266
|
+
if (!dualWrite || !dualWrite.shadow || dualWrite.shadow.matched !== true) {
|
|
267
|
+
throw new Error('Installed dual-write smoke did not match')
|
|
268
|
+
}
|
|
269
|
+
if (!readiness) throw new Error('Installed cutover readiness smoke did not pass')
|
|
270
|
+
if (!apply || apply.applied !== false) throw new Error('Installed cutover apply smoke did not stay dry-run')
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
runtime: status.runtime,
|
|
274
|
+
version: status.version,
|
|
275
|
+
modules: modules.map((module) => ({
|
|
276
|
+
name: module.name,
|
|
277
|
+
loaded: module.loaded,
|
|
278
|
+
healthy: module.healthy,
|
|
279
|
+
})),
|
|
280
|
+
cutover: {
|
|
281
|
+
shadowMatched: shadow.matched === true,
|
|
282
|
+
dualWriteMatched: dualWrite.shadow.matched === true,
|
|
283
|
+
readiness: readiness.status,
|
|
284
|
+
applied: apply.applied,
|
|
285
|
+
},
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function verifyPackedRuntimeInstall(input = {}) {
|
|
290
|
+
const plan = createPackedRuntimeInstallPlan(input)
|
|
291
|
+
fs.mkdirSync(plan.artifactDir, { recursive: true })
|
|
292
|
+
fs.mkdirSync(plan.installDir, { recursive: true })
|
|
293
|
+
|
|
294
|
+
for (const pkg of plan.packages) {
|
|
295
|
+
const pack = npmInvocation(['pack', '--pack-destination', plan.artifactDir])
|
|
296
|
+
run(pack.command, pack.args, {
|
|
297
|
+
cwd: pkg.packageDir,
|
|
298
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
299
|
+
})
|
|
300
|
+
packageArtifactEvidence(pkg)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const init = npmInvocation(['init', '-y'])
|
|
304
|
+
run(init.command, init.args, { cwd: plan.installDir, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
305
|
+
const install = npmInvocation(plan.install.args)
|
|
306
|
+
run(install.command, install.args, { cwd: plan.install.cwd, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
307
|
+
const smokeEnv = {
|
|
308
|
+
...process.env,
|
|
309
|
+
MIO_HOME: path.join(plan.installDir, '.mio-home'),
|
|
310
|
+
}
|
|
311
|
+
const smokeStdout = run(process.execPath, plan.smoke.status.args, {
|
|
312
|
+
cwd: plan.smoke.status.cwd,
|
|
313
|
+
env: smokeEnv,
|
|
314
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
315
|
+
})
|
|
316
|
+
const cutover = plan.smoke.cutover.map((command) => {
|
|
317
|
+
const stdout = run(process.execPath, command.args, {
|
|
318
|
+
cwd: command.cwd,
|
|
319
|
+
env: smokeEnv,
|
|
320
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
321
|
+
})
|
|
322
|
+
return JSON.parse(stdout)
|
|
323
|
+
})
|
|
324
|
+
const status = JSON.parse(smokeStdout)
|
|
325
|
+
const smoke = summarizeInstalledSmoke(status, cutover)
|
|
326
|
+
return {
|
|
327
|
+
ok: true,
|
|
328
|
+
artifactDir: plan.artifactDir,
|
|
329
|
+
installDir: plan.installDir,
|
|
330
|
+
packages: plan.packages.map(packageArtifactEvidence),
|
|
331
|
+
smoke,
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function main() {
|
|
336
|
+
const options = createVerifyPackedRuntimeOptions(process.argv.slice(2))
|
|
337
|
+
if (options.help) {
|
|
338
|
+
console.log(usage())
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const result = verifyPackedRuntimeInstall(options)
|
|
343
|
+
if (options.reportPath) {
|
|
344
|
+
result.reportPath = path.resolve(options.reportPath)
|
|
345
|
+
writeVerificationReport(result, result.reportPath)
|
|
346
|
+
}
|
|
347
|
+
console.log(JSON.stringify(result, null, 2))
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (require.main === module) {
|
|
351
|
+
try {
|
|
352
|
+
main()
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error(error && error.stack ? error.stack : error)
|
|
355
|
+
process.exitCode = 1
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
module.exports = {
|
|
360
|
+
createVerifyPackedRuntimeOptions,
|
|
361
|
+
createPackedRuntimeInstallPlan,
|
|
362
|
+
packageArtifactEvidence,
|
|
363
|
+
npmInvocation,
|
|
364
|
+
npmCommand,
|
|
365
|
+
parseVerifyPackedRuntimeArgs,
|
|
366
|
+
runtimePackageNames,
|
|
367
|
+
summarizeInstalledSmoke,
|
|
368
|
+
usage,
|
|
369
|
+
verifyPackedRuntimeInstall,
|
|
370
|
+
writeVerificationReport,
|
|
371
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const path = require('path')
|
|
4
|
+
|
|
5
|
+
function requireWorkspacePackage(name, fallbackPath) {
|
|
6
|
+
try {
|
|
7
|
+
return require(name)
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (!error || error.code !== 'MODULE_NOT_FOUND') throw error
|
|
10
|
+
return require(fallbackPath)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const {
|
|
15
|
+
assessCutoverReadiness,
|
|
16
|
+
createAuthoritySwitchDryRun,
|
|
17
|
+
createAuthoritySwitchPlan,
|
|
18
|
+
createDualWriteRunner,
|
|
19
|
+
createStateMigrationPlan,
|
|
20
|
+
runShadowComparison,
|
|
21
|
+
} = requireWorkspacePackage(
|
|
22
|
+
'@akemi-mio/evolution-scheduler',
|
|
23
|
+
path.resolve(__dirname, '..', '..', 'evolution-scheduler'),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
function createEvolutionCutoverTools({ dataDir, appendJsonl, readJsonl, projectName }) {
|
|
27
|
+
const shadowPath = path.join(dataDir, 'evolution_shadow.jsonl')
|
|
28
|
+
const dualWritePath = path.join(dataDir, 'evolution_dual_write.jsonl')
|
|
29
|
+
|
|
30
|
+
function projectFromArgs(args) {
|
|
31
|
+
return args.project || projectName()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function recordShadowComparison(args = {}) {
|
|
35
|
+
const project = projectFromArgs(args)
|
|
36
|
+
const comparison = await runShadowComparison({
|
|
37
|
+
input: args.input || {},
|
|
38
|
+
legacy: { run: () => args.legacy },
|
|
39
|
+
modular: { run: () => args.modular },
|
|
40
|
+
})
|
|
41
|
+
const record = {
|
|
42
|
+
id: `shadow_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
43
|
+
timestamp: new Date().toISOString(),
|
|
44
|
+
project,
|
|
45
|
+
label: String(args.label || 'shadow comparison'),
|
|
46
|
+
...comparison,
|
|
47
|
+
}
|
|
48
|
+
appendJsonl(shadowPath, record)
|
|
49
|
+
return record
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function recordDualWrite(args = {}) {
|
|
53
|
+
const project = projectFromArgs(args)
|
|
54
|
+
const runner = createDualWriteRunner({
|
|
55
|
+
authoritative: args.authoritative,
|
|
56
|
+
legacyWriter: { write: () => args.legacyResult },
|
|
57
|
+
modularWriter: { write: () => args.modularResult },
|
|
58
|
+
})
|
|
59
|
+
const result = await runner.write(args.record || {})
|
|
60
|
+
const stored = {
|
|
61
|
+
id: `dual_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
project,
|
|
64
|
+
label: String(args.label || 'dual write'),
|
|
65
|
+
...result,
|
|
66
|
+
}
|
|
67
|
+
appendJsonl(dualWritePath, stored)
|
|
68
|
+
return stored
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cutoverReadiness(args = {}) {
|
|
72
|
+
const project = projectFromArgs(args)
|
|
73
|
+
const shadowRuns = readJsonl(shadowPath).filter((record) => record.project === project)
|
|
74
|
+
const dualWriteRuns = readJsonl(dualWritePath).filter((record) => record.project === project)
|
|
75
|
+
return {
|
|
76
|
+
project,
|
|
77
|
+
...assessCutoverReadiness({
|
|
78
|
+
shadowRuns,
|
|
79
|
+
dualWriteRuns,
|
|
80
|
+
minShadowRuns: Number.isFinite(args.minShadowRuns) ? args.minShadowRuns : undefined,
|
|
81
|
+
maxMismatchRate: Number.isFinite(args.maxMismatchRate) ? args.maxMismatchRate : undefined,
|
|
82
|
+
}),
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function migrationPlan(args = {}) {
|
|
87
|
+
return createStateMigrationPlan({
|
|
88
|
+
legacyRecords: args.legacyRecords,
|
|
89
|
+
modularRecords: args.modularRecords,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function authorityPlan(args = {}) {
|
|
94
|
+
return createAuthoritySwitchPlan({
|
|
95
|
+
readiness: args.readiness,
|
|
96
|
+
from: args.from,
|
|
97
|
+
to: args.to,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function applyCutover(args = {}) {
|
|
102
|
+
return createAuthoritySwitchDryRun({
|
|
103
|
+
dryRun: args.dryRun,
|
|
104
|
+
project: projectFromArgs(args),
|
|
105
|
+
plan: args.plan || args.authorityPlan,
|
|
106
|
+
})
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
recordShadowComparison,
|
|
111
|
+
recordDualWrite,
|
|
112
|
+
cutoverReadiness,
|
|
113
|
+
migrationPlan,
|
|
114
|
+
authorityPlan,
|
|
115
|
+
applyCutover,
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { createEvolutionCutoverTools }
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const adapters = {
|
|
4
|
+
codex: require('../adapters/codex.js'),
|
|
5
|
+
opencode: require('../adapters/opencode.js'),
|
|
6
|
+
workbuddy: require('../adapters/workbuddy.js'),
|
|
7
|
+
hermes: require('../adapters/hermes.js'),
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const HOST_CAPABILITIES = [
|
|
11
|
+
{
|
|
12
|
+
name: 'codex',
|
|
13
|
+
capabilities: ['mcp-tools', 'memory', 'observer-ingest', 'policy-check', 'experience-reuse', 'runtime-status'],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: 'opencode',
|
|
17
|
+
capabilities: ['mcp-tools', 'memory', 'observer-ingest', 'policy-check', 'experience-reuse', 'runtime-status'],
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'workbuddy',
|
|
21
|
+
capabilities: ['mcp-tools', 'memory', 'observer-ingest', 'observer-transcript', 'policy-check', 'experience-reuse', 'runtime-status'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: 'hermes',
|
|
25
|
+
capabilities: ['mcp-tools', 'memory', 'observer-ingest', 'observer-transcript', 'policy-check', 'experience-reuse', 'runtime-status'],
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
function safeInstalled(adapter) {
|
|
30
|
+
try {
|
|
31
|
+
return Boolean(adapter && typeof adapter.isInstalled === 'function' && adapter.isInstalled())
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function listHostCapabilities(adapterOverrides = adapters) {
|
|
38
|
+
return {
|
|
39
|
+
schemaVersion: 1,
|
|
40
|
+
generatedAt: new Date().toISOString(),
|
|
41
|
+
hosts: HOST_CAPABILITIES.map((host) => ({
|
|
42
|
+
name: host.name,
|
|
43
|
+
installed: safeInstalled(adapterOverrides[host.name]),
|
|
44
|
+
capabilities: host.capabilities,
|
|
45
|
+
mutationMode: 'host-capability',
|
|
46
|
+
})),
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = { HOST_CAPABILITIES, listHostCapabilities }
|