mio-agent-runtime 0.5.15 → 0.5.17
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 +38 -25
- package/scripts/verify-packed-runtime.js +371 -0
- package/server/creativity-engine.js +397 -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 +621 -300
- 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/adapters/workbuddy.js
CHANGED
|
@@ -1,242 +1,196 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const fs = require('fs')
|
|
4
|
-
const path = require('path')
|
|
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
|
-
}
|
|
15
|
-
|
|
16
|
-
function configPath() {
|
|
17
|
-
return path.join(configDir(), 'mcp.json')
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function legacyConfigPath() {
|
|
21
|
-
return path.join(configDir(), '.mcp.json')
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function approvalsPath() {
|
|
25
|
-
return path.join(configDir(), 'mcp-approvals.json')
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// User-level memory rules file. WorkBuddy's codebuddy CLI (a CodeBuddy fork)
|
|
29
|
-
// auto-loads CODEBUDDY.md (or CODEBUDDY.mdc) from the config dir into every
|
|
30
|
-
// session (MemoryLoader user scope -> system-reminder-md / prompt context).
|
|
31
|
-
// Note: PRODUCT_UPPERCASE_NAME in the CLI bundle is "CODEBUDDY", not "WORKBUDDY".
|
|
32
|
-
function memoryRulesPath() {
|
|
33
|
-
return path.join(configDir(), 'CODEBUDDY.md')
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Project-level memory rules. WorkBuddy walks up from the workspace dir and
|
|
37
|
-
// auto-loads CODEBUDDY.md / AGENTS.md (project scope) into every session.
|
|
38
|
-
function projectInstructionsPath(workspace) {
|
|
39
|
-
return path.join(workspace || process.cwd(), 'AGENTS.md')
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function readJson(file) {
|
|
43
|
-
if (!fs.existsSync(file)) return null
|
|
44
|
-
try {
|
|
45
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
46
|
-
} catch (_) {
|
|
47
|
-
return null
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const MEMORY_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
|
|
52
|
-
const MEMORY_END = '<!-- MIO_INTELLIGENCE_END -->'
|
|
53
|
-
const MEMORY_BLOCK = `${MEMORY_BEGIN}
|
|
54
|
-
## Mio Intelligence Layer
|
|
55
|
-
|
|
56
|
-
Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
|
|
57
|
-
|
|
58
|
-
- Before designing or implementing a solution, or when unsure about prior decisions, call \`mio-intelligence_mio.memory.query\` first. Omit \`project\` unless a specific project is required; Mio derives it from the current Git repository.
|
|
59
|
-
- After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mio-intelligence_mio.memory.record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
|
|
60
|
-
- When a tool call fails, retries, or returns an error, call \`mio-intelligence_mio.observer.ingest\` with a stable \`trace_id\`, relevant \`event_type\`, \`payload\`, and \`outcome\`.
|
|
61
|
-
- Before a high-risk operation such as deletion, migration, credential change, or production change, call \`mio-intelligence_mio.policy.check\` and follow its suggestion when available.
|
|
62
|
-
- At the end of every task, before producing the final answer, call \`mio-intelligence_mio.observer.ingest\` with \`event_type\` = \`task_outcome\`, a stable \`trace_id\`, \`outcome\` = \`success\` / \`failure\` / \`aborted\`, and a brief non-sensitive \`payload\`. If Mio is unavailable, do not block the final answer.
|
|
63
|
-
- When you reuse a prior decision or experience returned by \`mio-intelligence_mio.memory.query\` and it changes your approach or outcome, call \`mio-intelligence_mio.experience.reuse\` with \`sourceAgent\`, \`targetAgent\`, \`experienceId\`, \`reuse\`, \`behaviorChanged\`, and \`outcomeImproved\`.
|
|
64
|
-
- If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
|
|
65
|
-
- Do not store secrets, credentials, or raw sensitive content in Mio memory.
|
|
66
|
-
${MEMORY_END}`
|
|
67
|
-
|
|
68
|
-
function ensureBlock(target) {
|
|
69
|
-
let content = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
|
|
70
|
-
const original = content
|
|
71
|
-
const start = content.indexOf(MEMORY_BEGIN)
|
|
72
|
-
if (start !== -1) {
|
|
73
|
-
const end = content.indexOf(MEMORY_END, start)
|
|
74
|
-
content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + MEMORY_END.length)
|
|
75
|
-
content = content.trimEnd()
|
|
76
|
-
}
|
|
77
|
-
if (content) content += '\n\n'
|
|
78
|
-
content += MEMORY_BLOCK + '\n'
|
|
79
|
-
if (content === original) return { changed: false, target, backup: null }
|
|
80
|
-
const backup = `${target}.bak-mio-${Date.now()}`
|
|
81
|
-
if (fs.existsSync(target)) fs.copyFileSync(target, backup)
|
|
82
|
-
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
83
|
-
fs.writeFileSync(target, content, 'utf8')
|
|
84
|
-
return { changed: true, target, backup }
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function isInstalled() {
|
|
88
|
-
const config = readJson(configPath())
|
|
89
|
-
const entry = config && config.mcpServers && config.mcpServers['mio-intelligence']
|
|
90
|
-
if (!entry) return false
|
|
91
|
-
|
|
92
|
-
const approvals = readJson(approvalsPath()) || {}
|
|
93
|
-
const memory = fs.existsSync(memoryRulesPath()) ? fs.readFileSync(memoryRulesPath(), 'utf8') : ''
|
|
94
|
-
return Boolean(
|
|
95
|
-
approvals[approvalKey('mio-intelligence', entry)] &&
|
|
96
|
-
memory.includes(MEMORY_BEGIN) &&
|
|
97
|
-
memory.includes(MEMORY_END)
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function approvalKey(name, entry) {
|
|
102
|
-
let input
|
|
103
|
-
if (entry.command) {
|
|
104
|
-
input = `${entry.command || ''}|${(entry.args || []).map(String).sort().join(',')}|${Object.keys(entry.env || {}).sort().join(',')}`
|
|
105
|
-
} else if (entry.url) {
|
|
106
|
-
try {
|
|
107
|
-
input = new URL(entry.url).origin
|
|
108
|
-
} catch (_) {
|
|
109
|
-
input = entry.url
|
|
110
|
-
}
|
|
111
|
-
} else {
|
|
112
|
-
input = JSON.stringify(entry)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const hash = crypto.createHash('sha256').update(input).digest('hex')
|
|
116
|
-
return `${hash}::${name}`
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function writeApproval(name, entry) {
|
|
120
|
-
const target = approvalsPath()
|
|
121
|
-
const approvals = readJson(target) || {}
|
|
122
|
-
approvals[approvalKey(name, entry)] = Date.now()
|
|
123
|
-
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
124
|
-
fs.writeFileSync(target, `${JSON.stringify(approvals, null, 2)}\n`, 'utf8')
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
function install({ node, serverScript, home, workspace, project }) {
|
|
128
|
-
const target = configPath()
|
|
129
|
-
const canonical = readJson(target) || {}
|
|
130
|
-
const legacy = readJson(legacyConfigPath()) || {}
|
|
131
|
-
const canonicalServers =
|
|
132
|
-
canonical.mcpServers && typeof canonical.mcpServers === 'object' ? canonical.mcpServers : {}
|
|
133
|
-
const legacyServers =
|
|
134
|
-
legacy.mcpServers && typeof legacy.mcpServers === 'object' ? legacy.mcpServers : {}
|
|
135
|
-
|
|
136
|
-
const config = {
|
|
137
|
-
...legacy,
|
|
138
|
-
...canonical,
|
|
139
|
-
mcpServers: {
|
|
140
|
-
...legacyServers,
|
|
141
|
-
...canonicalServers,
|
|
142
|
-
},
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const context = JSON.stringify({
|
|
146
|
-
agentId: 'workbuddy',
|
|
147
|
-
project: project || path.basename(workspace || process.cwd()),
|
|
148
|
-
workspace: workspace || process.cwd(),
|
|
149
|
-
sessionId: 'workbuddy-session',
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
config.mcpServers = {
|
|
153
|
-
...config.mcpServers,
|
|
154
|
-
'mio-intelligence': {
|
|
155
|
-
command: node,
|
|
156
|
-
args: [serverScript],
|
|
157
|
-
env: {
|
|
158
|
-
MIO_DATA_DIR: home,
|
|
159
|
-
MIO_CONTEXT: context,
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
writeApproval('mio-intelligence', config.mcpServers['mio-intelligence'])
|
|
165
|
-
|
|
166
|
-
const backup = `${target}.bak-mio-${Date.now()}`
|
|
167
|
-
if (fs.existsSync(target)) {
|
|
168
|
-
fs.copyFileSync(target, backup)
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
172
|
-
fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
173
|
-
|
|
174
|
-
const userRules = ensureBlock(memoryRulesPath())
|
|
175
|
-
let projectRules = null
|
|
176
|
-
const workspaceDir = workspace || process.cwd()
|
|
177
|
-
if (workspaceDir && path.isAbsolute(workspaceDir)) {
|
|
178
|
-
projectRules = ensureBlock(projectInstructionsPath(workspaceDir))
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
const changed = userRules.changed || Boolean(projectRules && projectRules.changed)
|
|
182
|
-
|
|
183
|
-
return {
|
|
184
|
-
changed,
|
|
185
|
-
configPath: target,
|
|
186
|
-
approvalsPath: approvalsPath(),
|
|
187
|
-
memoryRulesPath: userRules.target,
|
|
188
|
-
projectInstructionsPath: projectRules ? projectRules.target : null,
|
|
189
|
-
userRulesChanged: userRules.changed,
|
|
190
|
-
projectRulesChanged: projectRules ? projectRules.changed : false,
|
|
191
|
-
backup,
|
|
192
|
-
message: 'WorkBuddy Mio MCP installed, approved, and Mio rules injected into CODEBUDDY.md (auto-loaded user memory) and the workspace AGENTS.md. Restart WorkBuddy to load them.',
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if (!fs.existsSync(target)) return { changed: false, target, backup: null }
|
|
198
|
-
const content = fs.readFileSync(target, 'utf8')
|
|
199
|
-
const start = content.indexOf(MEMORY_BEGIN)
|
|
200
|
-
if (start === -1) return { changed: false, target, backup: null }
|
|
201
|
-
const end = content.indexOf(MEMORY_END, start)
|
|
202
|
-
let next = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + MEMORY_END.length)
|
|
203
|
-
next = next.trimEnd() + '\n'
|
|
204
|
-
const backup = target + '.bak-mio-' + Date.now()
|
|
205
|
-
fs.copyFileSync(target, backup)
|
|
206
|
-
fs.writeFileSync(target, next, 'utf8')
|
|
207
|
-
return { changed: true, target, backup }
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function uninstall({ serverScript, workspace }) {
|
|
211
|
-
const target = configPath()
|
|
212
|
-
const config = readJson(target)
|
|
213
|
-
const entry = config && config.mcpServers && config.mcpServers['mio-intelligence']
|
|
214
|
-
if (!entry) return { changed: false, configPath: target, message: 'WorkBuddy Mio MCP not installed.' }
|
|
215
|
-
const backup = target + '.bak-mio-' + Date.now()
|
|
216
|
-
if (fs.existsSync(target)) fs.copyFileSync(target, backup)
|
|
217
|
-
delete config.mcpServers['mio-intelligence']
|
|
218
|
-
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
219
|
-
fs.writeFileSync(target, JSON.stringify(config, null, 2) + '\n', 'utf8')
|
|
220
|
-
const approvalsTarget = approvalsPath()
|
|
221
|
-
const approvals = readJson(approvalsTarget) || {}
|
|
222
|
-
const key = approvalKey('mio-intelligence', entry)
|
|
223
|
-
if (approvals[key]) {
|
|
224
|
-
delete approvals[key]
|
|
225
|
-
fs.writeFileSync(approvalsTarget, JSON.stringify(approvals, null, 2) + '\n', 'utf8')
|
|
226
|
-
}
|
|
227
|
-
const userRules = removeBlock(memoryRulesPath())
|
|
228
|
-
let projectRules = null
|
|
229
|
-
const workspaceDir = workspace || process.cwd()
|
|
230
|
-
if (workspaceDir && path.isAbsolute(workspaceDir)) projectRules = removeBlock(projectInstructionsPath(workspaceDir))
|
|
231
|
-
const changed = userRules.changed || Boolean(projectRules && projectRules.changed)
|
|
232
|
-
return {
|
|
233
|
-
changed: true,
|
|
234
|
-
configPath: target,
|
|
235
|
-
approvalsPath: approvalsTarget,
|
|
236
|
-
memoryRulesPath: userRules.target,
|
|
237
|
-
projectInstructionsPath: projectRules ? projectRules.target : null,
|
|
238
|
-
message: 'WorkBuddy Mio MCP removed, approval revoked, and Mio rules stripped from CODEBUDDY.md and workspace AGENTS.md. Restart WorkBuddy to apply.'
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
module.exports = { configDir, configPath, approvalsPath, memoryRulesPath, projectInstructionsPath, approvalKey, isInstalled, install, uninstall }
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
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
|
+
}
|
|
15
|
+
|
|
16
|
+
function configPath() {
|
|
17
|
+
return path.join(configDir(), 'mcp.json')
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function legacyConfigPath() {
|
|
21
|
+
return path.join(configDir(), '.mcp.json')
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function approvalsPath() {
|
|
25
|
+
return path.join(configDir(), 'mcp-approvals.json')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// User-level memory rules file. WorkBuddy's codebuddy CLI (a CodeBuddy fork)
|
|
29
|
+
// auto-loads CODEBUDDY.md (or CODEBUDDY.mdc) from the config dir into every
|
|
30
|
+
// session (MemoryLoader user scope -> system-reminder-md / prompt context).
|
|
31
|
+
// Note: PRODUCT_UPPERCASE_NAME in the CLI bundle is "CODEBUDDY", not "WORKBUDDY".
|
|
32
|
+
function memoryRulesPath() {
|
|
33
|
+
return path.join(configDir(), 'CODEBUDDY.md')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Project-level memory rules. WorkBuddy walks up from the workspace dir and
|
|
37
|
+
// auto-loads CODEBUDDY.md / AGENTS.md (project scope) into every session.
|
|
38
|
+
function projectInstructionsPath(workspace) {
|
|
39
|
+
return path.join(workspace || process.cwd(), 'AGENTS.md')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function readJson(file) {
|
|
43
|
+
if (!fs.existsSync(file)) return null
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'))
|
|
46
|
+
} catch (_) {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const MEMORY_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
|
|
52
|
+
const MEMORY_END = '<!-- MIO_INTELLIGENCE_END -->'
|
|
53
|
+
const MEMORY_BLOCK = `${MEMORY_BEGIN}
|
|
54
|
+
## Mio Intelligence Layer
|
|
55
|
+
|
|
56
|
+
Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
|
|
57
|
+
|
|
58
|
+
- Before designing or implementing a solution, or when unsure about prior decisions, call \`mio-intelligence_mio.memory.query\` first. Omit \`project\` unless a specific project is required; Mio derives it from the current Git repository.
|
|
59
|
+
- After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mio-intelligence_mio.memory.record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
|
|
60
|
+
- When a tool call fails, retries, or returns an error, call \`mio-intelligence_mio.observer.ingest\` with a stable \`trace_id\`, relevant \`event_type\`, \`payload\`, and \`outcome\`.
|
|
61
|
+
- Before a high-risk operation such as deletion, migration, credential change, or production change, call \`mio-intelligence_mio.policy.check\` and follow its suggestion when available.
|
|
62
|
+
- At the end of every task, before producing the final answer, call \`mio-intelligence_mio.observer.ingest\` with \`event_type\` = \`task_outcome\`, a stable \`trace_id\`, \`outcome\` = \`success\` / \`failure\` / \`aborted\`, and a brief non-sensitive \`payload\`. If Mio is unavailable, do not block the final answer.
|
|
63
|
+
- When you reuse a prior decision or experience returned by \`mio-intelligence_mio.memory.query\` and it changes your approach or outcome, call \`mio-intelligence_mio.experience.reuse\` with \`sourceAgent\`, \`targetAgent\`, \`experienceId\`, \`reuse\`, \`behaviorChanged\`, and \`outcomeImproved\`.
|
|
64
|
+
- If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
|
|
65
|
+
- Do not store secrets, credentials, or raw sensitive content in Mio memory.
|
|
66
|
+
${MEMORY_END}`
|
|
67
|
+
|
|
68
|
+
function ensureBlock(target) {
|
|
69
|
+
let content = fs.existsSync(target) ? fs.readFileSync(target, 'utf8') : ''
|
|
70
|
+
const original = content
|
|
71
|
+
const start = content.indexOf(MEMORY_BEGIN)
|
|
72
|
+
if (start !== -1) {
|
|
73
|
+
const end = content.indexOf(MEMORY_END, start)
|
|
74
|
+
content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + MEMORY_END.length)
|
|
75
|
+
content = content.trimEnd()
|
|
76
|
+
}
|
|
77
|
+
if (content) content += '\n\n'
|
|
78
|
+
content += MEMORY_BLOCK + '\n'
|
|
79
|
+
if (content === original) return { changed: false, target, backup: null }
|
|
80
|
+
const backup = `${target}.bak-mio-${Date.now()}`
|
|
81
|
+
if (fs.existsSync(target)) fs.copyFileSync(target, backup)
|
|
82
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
83
|
+
fs.writeFileSync(target, content, 'utf8')
|
|
84
|
+
return { changed: true, target, backup }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isInstalled() {
|
|
88
|
+
const config = readJson(configPath())
|
|
89
|
+
const entry = config && config.mcpServers && config.mcpServers['mio-intelligence']
|
|
90
|
+
if (!entry) return false
|
|
91
|
+
|
|
92
|
+
const approvals = readJson(approvalsPath()) || {}
|
|
93
|
+
const memory = fs.existsSync(memoryRulesPath()) ? fs.readFileSync(memoryRulesPath(), 'utf8') : ''
|
|
94
|
+
return Boolean(
|
|
95
|
+
approvals[approvalKey('mio-intelligence', entry)] &&
|
|
96
|
+
memory.includes(MEMORY_BEGIN) &&
|
|
97
|
+
memory.includes(MEMORY_END)
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function approvalKey(name, entry) {
|
|
102
|
+
let input
|
|
103
|
+
if (entry.command) {
|
|
104
|
+
input = `${entry.command || ''}|${(entry.args || []).map(String).sort().join(',')}|${Object.keys(entry.env || {}).sort().join(',')}`
|
|
105
|
+
} else if (entry.url) {
|
|
106
|
+
try {
|
|
107
|
+
input = new URL(entry.url).origin
|
|
108
|
+
} catch (_) {
|
|
109
|
+
input = entry.url
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
input = JSON.stringify(entry)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const hash = crypto.createHash('sha256').update(input).digest('hex')
|
|
116
|
+
return `${hash}::${name}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function writeApproval(name, entry) {
|
|
120
|
+
const target = approvalsPath()
|
|
121
|
+
const approvals = readJson(target) || {}
|
|
122
|
+
approvals[approvalKey(name, entry)] = Date.now()
|
|
123
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
124
|
+
fs.writeFileSync(target, `${JSON.stringify(approvals, null, 2)}\n`, 'utf8')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function install({ node, serverScript, home, workspace, project }) {
|
|
128
|
+
const target = configPath()
|
|
129
|
+
const canonical = readJson(target) || {}
|
|
130
|
+
const legacy = readJson(legacyConfigPath()) || {}
|
|
131
|
+
const canonicalServers =
|
|
132
|
+
canonical.mcpServers && typeof canonical.mcpServers === 'object' ? canonical.mcpServers : {}
|
|
133
|
+
const legacyServers =
|
|
134
|
+
legacy.mcpServers && typeof legacy.mcpServers === 'object' ? legacy.mcpServers : {}
|
|
135
|
+
|
|
136
|
+
const config = {
|
|
137
|
+
...legacy,
|
|
138
|
+
...canonical,
|
|
139
|
+
mcpServers: {
|
|
140
|
+
...legacyServers,
|
|
141
|
+
...canonicalServers,
|
|
142
|
+
},
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const context = JSON.stringify({
|
|
146
|
+
agentId: 'workbuddy',
|
|
147
|
+
project: project || path.basename(workspace || process.cwd()),
|
|
148
|
+
workspace: workspace || process.cwd(),
|
|
149
|
+
sessionId: 'workbuddy-session',
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
config.mcpServers = {
|
|
153
|
+
...config.mcpServers,
|
|
154
|
+
'mio-intelligence': {
|
|
155
|
+
command: node,
|
|
156
|
+
args: [serverScript],
|
|
157
|
+
env: {
|
|
158
|
+
MIO_DATA_DIR: home,
|
|
159
|
+
MIO_CONTEXT: context,
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
writeApproval('mio-intelligence', config.mcpServers['mio-intelligence'])
|
|
165
|
+
|
|
166
|
+
const backup = `${target}.bak-mio-${Date.now()}`
|
|
167
|
+
if (fs.existsSync(target)) {
|
|
168
|
+
fs.copyFileSync(target, backup)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fs.mkdirSync(path.dirname(target), { recursive: true })
|
|
172
|
+
fs.writeFileSync(target, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
173
|
+
|
|
174
|
+
const userRules = ensureBlock(memoryRulesPath())
|
|
175
|
+
let projectRules = null
|
|
176
|
+
const workspaceDir = workspace || process.cwd()
|
|
177
|
+
if (workspaceDir && path.isAbsolute(workspaceDir)) {
|
|
178
|
+
projectRules = ensureBlock(projectInstructionsPath(workspaceDir))
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const changed = userRules.changed || Boolean(projectRules && projectRules.changed)
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
changed,
|
|
185
|
+
configPath: target,
|
|
186
|
+
approvalsPath: approvalsPath(),
|
|
187
|
+
memoryRulesPath: userRules.target,
|
|
188
|
+
projectInstructionsPath: projectRules ? projectRules.target : null,
|
|
189
|
+
userRulesChanged: userRules.changed,
|
|
190
|
+
projectRulesChanged: projectRules ? projectRules.changed : false,
|
|
191
|
+
backup,
|
|
192
|
+
message: 'WorkBuddy Mio MCP installed, approved, and Mio rules injected into CODEBUDDY.md (auto-loaded user memory) and the workspace AGENTS.md. Restart WorkBuddy to load them.',
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { configDir, configPath, approvalsPath, memoryRulesPath, projectInstructionsPath, approvalKey, isInstalled, install }
|