mio-agent-runtime 0.3.11 → 0.3.13
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/adapters/codex.js +105 -61
- package/observe/observer.js +23 -3
- package/package.json +1 -1
package/adapters/codex.js
CHANGED
|
@@ -1,62 +1,106 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const path = require('path')
|
|
5
|
-
const os = require('os')
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
|
|
7
|
+
const SERVER_SECTION = '[mcp_servers.mio-intelligence]'
|
|
8
|
+
const ENV_SECTION = '[mcp_servers.mio-intelligence.env]'
|
|
9
|
+
|
|
10
|
+
function configPath() {
|
|
11
|
+
return path.join(os.homedir(), '.codex', 'config.toml')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isInstalled() {
|
|
15
|
+
const target = configPath()
|
|
16
|
+
if (!fs.existsSync(target)) return false
|
|
17
|
+
return fs.readFileSync(target, 'utf8').includes(SERVER_SECTION)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Legacy installs pointed args at the worktree copy of the server
|
|
21
|
+
// (server/mio-intelligence-mcp/index.js) instead of this package's copy
|
|
22
|
+
// (server/mio-intelligence-mcp/index.js). Detect staleness by comparing the
|
|
23
|
+
// configured args path with the server path this package ships.
|
|
24
|
+
function configNeedsRepair(serverScript) {
|
|
25
|
+
const target = configPath()
|
|
26
|
+
if (!fs.existsSync(target)) return false
|
|
27
|
+
try {
|
|
28
|
+
const src = fs.readFileSync(target, 'utf8')
|
|
29
|
+
if (!src.includes(SERVER_SECTION)) return false
|
|
30
|
+
const needle = serverScript.replace(/\\/g, '/').toLowerCase()
|
|
31
|
+
return !src.replace(/\\/g, '/').toLowerCase().includes(needle)
|
|
32
|
+
} catch (_) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function stripMioSection(toml) {
|
|
38
|
+
const lines = toml.replace(/\r\n/g, '\n').split('\n')
|
|
39
|
+
const out = []
|
|
40
|
+
let skipping = false
|
|
41
|
+
for (const line of lines) {
|
|
42
|
+
const trimmed = line.trim()
|
|
43
|
+
if (trimmed === SERVER_SECTION || trimmed === ENV_SECTION) {
|
|
44
|
+
skipping = true
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
if (skipping && /^\[.*\]$/.test(trimmed)) {
|
|
48
|
+
skipping = false
|
|
49
|
+
}
|
|
50
|
+
if (!skipping) out.push(line)
|
|
51
|
+
}
|
|
52
|
+
return out.join('\n').replace(/\n{3,}/g, '\n\n').trimEnd() + '\n'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function install({ node, serverScript, home, workspace }) {
|
|
56
|
+
const target = configPath()
|
|
57
|
+
const backup = `${target}.bak-mio-${Date.now()}`
|
|
58
|
+
const installed = isInstalled()
|
|
59
|
+
if (installed && !configNeedsRepair(serverScript)) {
|
|
60
|
+
return {
|
|
61
|
+
changed: false,
|
|
62
|
+
configPath: target,
|
|
63
|
+
message: 'Codex Mio MCP already installed.',
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (fs.existsSync(target)) {
|
|
68
|
+
fs.copyFileSync(target, backup)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const context = JSON.stringify({
|
|
72
|
+
agentId: 'codex',
|
|
73
|
+
workspace: workspace || process.cwd(),
|
|
74
|
+
sessionId: 'codex-session',
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
78
|
+
let toml = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
|
|
79
|
+
toml = stripMioSection(toml)
|
|
80
|
+
toml = toml.replace(/\n{3,}/g, '\n\n')
|
|
81
|
+
if (!toml.endsWith('\n')) toml += '\n'
|
|
82
|
+
const section = [
|
|
83
|
+
'',
|
|
84
|
+
SERVER_SECTION,
|
|
85
|
+
`command = '${node}'`,
|
|
86
|
+
`args = ['${serverScript}']`,
|
|
87
|
+
'startup_timeout_sec = 30',
|
|
88
|
+
'',
|
|
89
|
+
ENV_SECTION,
|
|
90
|
+
`MIO_DATA_DIR = '${home}'`,
|
|
91
|
+
`MIO_CONTEXT = '${context}'`,
|
|
92
|
+
'',
|
|
93
|
+
].join('\n')
|
|
94
|
+
fs.writeFileSync(target, `${toml}${section}\n`, 'utf8')
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
changed: true,
|
|
98
|
+
configPath: target,
|
|
99
|
+
backup,
|
|
100
|
+
message: installed
|
|
101
|
+
? 'Codex Mio MCP repaired (server path updated). Restart Codex to load it.'
|
|
102
|
+
: 'Codex Mio MCP installed. Restart Codex to load it.',
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
62
106
|
module.exports = { configPath, isInstalled, install }
|
package/observe/observer.js
CHANGED
|
@@ -604,17 +604,37 @@ function findOpencodeBin() {
|
|
|
604
604
|
const resolved = path.resolve(c)
|
|
605
605
|
if (fs.existsSync(resolved)) return resolved
|
|
606
606
|
}
|
|
607
|
+
// Resolve from PATH so the SQL is never passed through cmd.exe, where
|
|
608
|
+
// '>' in the query is interpreted as output redirection and creates
|
|
609
|
+
// stray files (e.g. "WHERE p.rowid > 0" -> a file named "0").
|
|
610
|
+
try {
|
|
611
|
+
const finder = process.platform === 'win32' ? 'where' : 'which'
|
|
612
|
+
const res = spawnSync(finder, ['opencode'], { encoding: 'utf8', windowsHide: true, timeout: 15000 })
|
|
613
|
+
if (res.status === 0 && res.stdout) {
|
|
614
|
+
const lines = res.stdout
|
|
615
|
+
.split(/\r?\n/)
|
|
616
|
+
.map((s) => s.trim())
|
|
617
|
+
.filter(Boolean)
|
|
618
|
+
const preferred =
|
|
619
|
+
process.platform === 'win32'
|
|
620
|
+
? lines.filter((p) => /\.(exe|cmd|bat)$/i.test(p))
|
|
621
|
+
: lines
|
|
622
|
+
const first = (preferred.length > 0 ? preferred : lines).find((p) => fs.existsSync(p))
|
|
623
|
+
if (first) return first
|
|
624
|
+
}
|
|
625
|
+
} catch (_) {}
|
|
607
626
|
return null
|
|
608
627
|
}
|
|
609
628
|
|
|
610
629
|
function runOpencodeQuery(sql) {
|
|
611
630
|
const bin = findOpencodeBin()
|
|
631
|
+
// Never shell the SQL through cmd.exe: '>' in the query would be parsed
|
|
632
|
+
// as output redirection, creating files named after the redirect target.
|
|
633
|
+
if (!bin) return null
|
|
612
634
|
const args = ['db', sql, '--format', 'json']
|
|
613
635
|
let res
|
|
614
636
|
try {
|
|
615
|
-
res = bin
|
|
616
|
-
? spawnSync(bin, args, { encoding: 'utf8', timeout: 90000, windowsHide: true, maxBuffer: 256 * 1024 * 1024 })
|
|
617
|
-
: spawnSync('opencode', args, { encoding: 'utf8', timeout: 90000, shell: true, windowsHide: true, maxBuffer: 256 * 1024 * 1024 })
|
|
637
|
+
res = spawnSync(bin, args, { encoding: 'utf8', timeout: 90000, windowsHide: true, maxBuffer: 256 * 1024 * 1024 })
|
|
618
638
|
} catch (_) {
|
|
619
639
|
return null
|
|
620
640
|
}
|