mio-agent-runtime 0.2.0 → 0.2.2

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 CHANGED
@@ -22,6 +22,14 @@ mio --json status Machine-readable status
22
22
  mio --json agents Machine-readable agents
23
23
  ```
24
24
 
25
+ ## Host notes
26
+
27
+ - WorkBuddy uses `~/.workbuddy/mcp.json` and requires each custom MCP server to
28
+ be approved in `~/.workbuddy/mcp-approvals.json`. `mio install workbuddy`
29
+ writes both files and injects Mio usage rules into `~/.workbuddy/MEMORY.md`
30
+ so WorkBuddy calls Mio memory/observer/policy automatically. If WorkBuddy is
31
+ already running, restart it once so it reloads the approval and memory files.
32
+
25
33
  ## MIO_HOME
26
34
 
27
35
  默认 `~/.mio-intelligence`,可通过环境变量 `MIO_HOME` 覆盖。
@@ -38,4 +46,4 @@ packages/mio-cli
38
46
  ├── server/
39
47
  │ └── mio-intelligence-mcp/index.js
40
48
  └── package.json
41
- ```
49
+ ```
package/adapters/codex.js CHANGED
@@ -14,7 +14,7 @@ function isInstalled() {
14
14
  return fs.readFileSync(target, 'utf8').includes('[mcp_servers.mio-intelligence]')
15
15
  }
16
16
 
17
- function install({ node, serverScript, home, workspace }) {
17
+ function install({ node, serverScript, home, workspace, project }) {
18
18
  const target = configPath()
19
19
  const backup = `${target}.bak-mio-${Date.now()}`
20
20
  if (isInstalled()) {
@@ -31,6 +31,7 @@ function install({ node, serverScript, home, workspace }) {
31
31
 
32
32
  const context = JSON.stringify({
33
33
  agentId: 'codex',
34
+ project: project || path.basename(workspace || process.cwd()),
34
35
  workspace: workspace || process.cwd(),
35
36
  sessionId: 'codex-session',
36
37
  })
@@ -23,7 +23,7 @@ function isInstalled() {
23
23
  return Boolean(config && config.mcp && config.mcp['mio-intelligence'])
24
24
  }
25
25
 
26
- function install({ node, serverScript, home, workspace }) {
26
+ function install({ node, serverScript, home, workspace, project }) {
27
27
  const target = configPath()
28
28
  if (isInstalled()) {
29
29
  return {
@@ -42,6 +42,7 @@ function install({ node, serverScript, home, workspace }) {
42
42
  const existingMcp = config.mcp && typeof config.mcp === 'object' ? config.mcp : {}
43
43
  const context = JSON.stringify({
44
44
  agentId: 'opencode',
45
+ project: project || path.basename(workspace || process.cwd()),
45
46
  workspace: workspace || process.cwd(),
46
47
  sessionId: 'opencode-session',
47
48
  })
@@ -3,13 +3,30 @@
3
3
  const fs = require('fs')
4
4
  const path = require('path')
5
5
  const os = require('os')
6
+ const crypto = require('crypto')
7
+
8
+ function configDir() {
9
+ return (
10
+ process.env.WORKBUDDY_CONFIG_DIR ||
11
+ process.env.CODEBUDDY_CONFIG_DIR ||
12
+ path.join(os.homedir(), '.workbuddy')
13
+ )
14
+ }
6
15
 
7
16
  function configPath() {
8
- return path.join(os.homedir(), '.workbuddy', 'mcp.json')
17
+ return path.join(configDir(), 'mcp.json')
9
18
  }
10
19
 
11
20
  function legacyConfigPath() {
12
- return path.join(os.homedir(), '.workbuddy', '.mcp.json')
21
+ return path.join(configDir(), '.mcp.json')
22
+ }
23
+
24
+ function approvalsPath() {
25
+ return path.join(configDir(), 'mcp-approvals.json')
26
+ }
27
+
28
+ function memoryPath() {
29
+ return path.join(configDir(), 'MEMORY.md')
13
30
  }
14
31
 
15
32
  function readJson(file) {
@@ -21,26 +38,82 @@ function readJson(file) {
21
38
  }
22
39
  }
23
40
 
41
+
42
+ const MEMORY_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
43
+ const MEMORY_END = '<!-- MIO_INTELLIGENCE_END -->'
44
+ const MEMORY_BLOCK = `${MEMORY_BEGIN}
45
+ ## Mio Intelligence Layer
46
+
47
+ Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
48
+
49
+ - Before designing or implementing a solution, or when unsure about prior decisions, call \`mcp__mio-intelligence__mio.memory.query\` first. Omit \`project\` unless a specific project is required; Mio derives it from the current Git repository.
50
+ - After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mcp__mio-intelligence__mio.memory.record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
51
+ - When a tool call fails, retries, or returns an error, call \`mcp__mio-intelligence__mio.observer.ingest\` with a stable \`trace_id\`, relevant \`event_type\`, \`payload\`, and \`outcome\`.
52
+ - Before a high-risk operation such as deletion, migration, credential change, or production change, call \`mcp__mio-intelligence__mio.policy.check\` and follow its suggestion when available.
53
+ - If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
54
+ - Do not store secrets, credentials, or raw sensitive content in Mio memory.
55
+ ${MEMORY_END}`
56
+
57
+ function ensureMemoryRules() {
58
+ const target = memoryPath()
59
+ let content = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
60
+ const start = content.indexOf(MEMORY_BEGIN)
61
+ if (start !== -1) {
62
+ const end = content.indexOf(MEMORY_END, start)
63
+ content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + MEMORY_END.length)
64
+ content = content.trimEnd()
65
+ }
66
+ const before = content
67
+ if (content) content += '\n\n'
68
+ content += MEMORY_BLOCK + '\n'
69
+ if (content === before) return false
70
+ fs.mkdirSync(path.dirname(target), { recursive: true })
71
+ fs.writeFileSync(target, content, 'utf8')
72
+ return true
73
+ }
74
+
24
75
  function isInstalled() {
25
76
  const config = readJson(configPath())
26
- return Boolean(config && config.mcpServers && config.mcpServers['mio-intelligence'])
77
+ const entry = config && config.mcpServers && config.mcpServers['mio-intelligence']
78
+ if (!entry) return false
79
+
80
+ const approvals = readJson(approvalsPath()) || {}
81
+ const memory = fs.existsSync(memoryPath()) ? fs.readFileSync(memoryPath(), 'utf8') : ''
82
+ return Boolean(
83
+ approvals[approvalKey('mio-intelligence', entry)] &&
84
+ memory.includes(MEMORY_BEGIN) &&
85
+ memory.includes(MEMORY_END)
86
+ )
27
87
  }
28
88
 
29
- function install({ node, serverScript, home, workspace }) {
30
- const target = configPath()
31
- if (isInstalled()) {
32
- return {
33
- changed: false,
34
- configPath: target,
35
- message: 'WorkBuddy Mio MCP already installed.',
89
+ function approvalKey(name, entry) {
90
+ let input
91
+ if (entry.command) {
92
+ input = `${entry.command || ''}|${(entry.args || []).map(String).sort().join(',')}|${Object.keys(entry.env || {}).sort().join(',')}`
93
+ } else if (entry.url) {
94
+ try {
95
+ input = new URL(entry.url).origin
96
+ } catch (_) {
97
+ input = entry.url
36
98
  }
99
+ } else {
100
+ input = JSON.stringify(entry)
37
101
  }
38
102
 
39
- const backup = `${target}.bak-mio-${Date.now()}`
40
- if (fs.existsSync(target)) {
41
- fs.copyFileSync(target, backup)
42
- }
103
+ const hash = crypto.createHash('sha256').update(input).digest('hex')
104
+ return `${hash}::${name}`
105
+ }
106
+
107
+ function writeApproval(name, entry) {
108
+ const target = approvalsPath()
109
+ const approvals = readJson(target) || {}
110
+ approvals[approvalKey(name, entry)] = Date.now()
111
+ fs.mkdirSync(path.dirname(target), { recursive: true })
112
+ fs.writeFileSync(target, `${JSON.stringify(approvals, null, 2)}\n`, 'utf8')
113
+ }
43
114
 
115
+ function install({ node, serverScript, home, workspace, project }) {
116
+ const target = configPath()
44
117
  const canonical = readJson(target) || {}
45
118
  const legacy = readJson(legacyConfigPath()) || {}
46
119
  const canonicalServers =
@@ -59,6 +132,7 @@ function install({ node, serverScript, home, workspace }) {
59
132
 
60
133
  const context = JSON.stringify({
61
134
  agentId: 'workbuddy',
135
+ project: project || path.basename(workspace || process.cwd()),
62
136
  workspace: workspace || process.cwd(),
63
137
  sessionId: 'workbuddy-session',
64
138
  })
@@ -66,7 +140,6 @@ function install({ node, serverScript, home, workspace }) {
66
140
  config.mcpServers = {
67
141
  ...config.mcpServers,
68
142
  'mio-intelligence': {
69
- type: 'stdio',
70
143
  command: node,
71
144
  args: [serverScript],
72
145
  env: {
@@ -76,15 +149,26 @@ function install({ node, serverScript, home, workspace }) {
76
149
  },
77
150
  }
78
151
 
152
+ writeApproval('mio-intelligence', config.mcpServers['mio-intelligence'])
153
+ const memoryChanged = ensureMemoryRules()
154
+
155
+ const backup = `${target}.bak-mio-${Date.now()}`
156
+ if (fs.existsSync(target)) {
157
+ fs.copyFileSync(target, backup)
158
+ }
159
+
79
160
  fs.mkdirSync(path.dirname(target), { recursive: true })
80
161
  fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
81
162
 
82
163
  return {
83
164
  changed: true,
84
165
  configPath: target,
166
+ approvalsPath: approvalsPath(),
167
+ memoryPath: memoryPath(),
168
+ memoryChanged,
85
169
  backup,
86
- message: 'WorkBuddy Mio MCP installed. Restart WorkBuddy to load it.',
170
+ message: 'WorkBuddy Mio MCP installed, approved, and auto-memory rules injected. WorkBuddy should auto-load it; restart if it was already running.',
87
171
  }
88
172
  }
89
173
 
90
- module.exports = { configPath, isInstalled, install }
174
+ module.exports = { configDir, configPath, approvalsPath, memoryPath, approvalKey, isInstalled, install }
package/bin/mio.js CHANGED
@@ -4,6 +4,7 @@
4
4
  const fs = require('fs')
5
5
  const path = require('path')
6
6
  const os = require('os')
7
+ const { execFileSync } = require('child_process')
7
8
 
8
9
  const SERVER_SCRIPT = path.resolve(__dirname, '..', 'server', 'mio-intelligence-mcp', 'index.js')
9
10
  const MIO_HOME = process.env.MIO_HOME || path.join(os.homedir(), '.mio-intelligence')
@@ -15,6 +16,23 @@ const adapters = {
15
16
  workbuddy: require('../adapters/workbuddy.js'),
16
17
  }
17
18
 
19
+ function projectFromWorkspace(workspace) {
20
+ try {
21
+ const commonDir = execFileSync(
22
+ 'git',
23
+ ['-C', workspace, 'rev-parse', '--path-format=absolute', '--git-common-dir'],
24
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
25
+ ).trim()
26
+ if (commonDir) {
27
+ const name = path.basename(path.dirname(commonDir))
28
+ if (name) return name
29
+ }
30
+ } catch (_) {
31
+ // fall through to workspace basename
32
+ }
33
+ return path.basename(workspace)
34
+ }
35
+
18
36
  function ensureHome() {
19
37
  fs.mkdirSync(MIO_HOME, { recursive: true })
20
38
  fs.mkdirSync(path.join(MIO_HOME, 'logs'), { recursive: true })
@@ -117,6 +135,7 @@ function installCommand(host, useJson) {
117
135
  serverScript: SERVER_SCRIPT,
118
136
  home: MIO_HOME,
119
137
  workspace: process.cwd(),
138
+ project: projectFromWorkspace(process.cwd()),
120
139
  })
121
140
  } catch (error) {
122
141
  console.error(`Failed to install ${host}: ${error.message || error}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mio-agent-runtime",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Mio Agent Runtime installer and MCP intelligence layer",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",