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 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
- function configPath() {
8
- return path.join(os.homedir(), '.codex', 'config.toml')
9
- }
10
-
11
- function isInstalled() {
12
- const target = configPath()
13
- if (!fs.existsSync(target)) return false
14
- return fs.readFileSync(target, 'utf8').includes('[mcp_servers.mio-intelligence]')
15
- }
16
-
17
- function install({ node, serverScript, home, workspace }) {
18
- const target = configPath()
19
- const backup = `${target}.bak-mio-${Date.now()}`
20
- if (isInstalled()) {
21
- return {
22
- changed: false,
23
- configPath: target,
24
- message: 'Codex Mio MCP already installed.',
25
- }
26
- }
27
-
28
- if (fs.existsSync(target)) {
29
- fs.copyFileSync(target, backup)
30
- }
31
-
32
- const context = JSON.stringify({
33
- agentId: 'codex',
34
- workspace: workspace || process.cwd(),
35
- sessionId: 'codex-session',
36
- })
37
-
38
- fs.mkdirSync(path.dirname(target), { recursive: true })
39
- let toml = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
40
- if (!toml.endsWith('\n')) toml += '\n'
41
- const section = [
42
- '[mcp_servers.mio-intelligence]',
43
- `command = '${node}'`,
44
- `args = ['${serverScript}']`,
45
- 'startup_timeout_sec = 30',
46
- '',
47
- '[mcp_servers.mio-intelligence.env]',
48
- `MIO_DATA_DIR = '${home}'`,
49
- `MIO_CONTEXT = '${context}'`,
50
- '',
51
- ].join('\n')
52
- fs.writeFileSync(target, `${toml}${section}\n`, 'utf8')
53
-
54
- return {
55
- changed: true,
56
- configPath: target,
57
- backup,
58
- message: 'Codex Mio MCP installed. Restart Codex to load it.',
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 }
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mio-agent-runtime",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "description": "Mio Agent Runtime installer and MCP intelligence layer",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",