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/package.json CHANGED
@@ -1,25 +1,36 @@
1
- {
2
- "name": "mio-agent-runtime",
3
- "version": "0.5.15",
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/mio-intelligence-mcp/index.js && node --check server/mio-intelligence-mcp/phase0.js && node --check server/mio-intelligence-mcp/dashboard-server.js && node --check observe/observer.js",
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 }