mio-agent-runtime 0.13.4 → 0.14.0

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.
@@ -1,202 +1,203 @@
1
- 'use strict'
2
-
3
- const fs = require('fs')
4
- const path = require('path')
5
- const os = require('os')
6
- const { restartWarning } = require('./host-utils')
7
-
8
- const INSTRUCTIONS_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
9
- const INSTRUCTIONS_END = '<!-- MIO_INTELLIGENCE_END -->'
10
- const INSTRUCTIONS_BLOCK = `${INSTRUCTIONS_BEGIN}
11
- ## Mio Intelligence Layer
12
-
13
- Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
14
-
15
- - 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.
16
- - After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mio-intelligence_mio_memory_record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
17
- - 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\`.
18
- - 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.
19
- - 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.
20
- - 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\`.
21
- - If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
22
- - Do not store secrets, credentials, or raw sensitive content in Mio memory.
23
- ${INSTRUCTIONS_END}`
24
-
25
- function configPath() {
26
- return path.join(os.homedir(), '.config', 'opencode', 'opencode.json')
27
- }
28
-
29
- function globalInstructionsPath() {
30
- return path.join(os.homedir(), '.config', 'opencode', 'AGENTS.md')
31
- }
32
-
33
- function readConfig() {
34
- const target = configPath()
35
- if (!fs.existsSync(target)) return null
36
- try {
37
- return JSON.parse(fs.readFileSync(target, 'utf8'))
38
- } catch (_) {
39
- return null
40
- }
41
- }
42
-
43
- function readGlobalInstructions() {
44
- const target = globalInstructionsPath()
45
- if (!fs.existsSync(target)) return ''
46
- try {
47
- return fs.readFileSync(target, 'utf8')
48
- } catch (_) {
49
- return ''
50
- }
51
- }
52
-
53
- function ensureGlobalInstructions() {
54
- const target = globalInstructionsPath()
55
- let content = readGlobalInstructions()
56
- const original = content
57
- const start = content.indexOf(INSTRUCTIONS_BEGIN)
58
- if (start !== -1) {
59
- const end = content.indexOf(INSTRUCTIONS_END, start)
60
- content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + INSTRUCTIONS_END.length)
61
- content = content.trimEnd()
62
- }
63
- if (content) content += '\n\n'
64
- content += INSTRUCTIONS_BLOCK + '\n'
65
- if (content === original) {
66
- return { changed: false, target, backup: null }
67
- }
68
-
69
- const backup = `${target}.bak-mio-${Date.now()}`
70
- if (fs.existsSync(target)) {
71
- fs.copyFileSync(target, backup)
72
- }
73
- fs.mkdirSync(path.dirname(target), { recursive: true })
74
- fs.writeFileSync(target, content, 'utf8')
75
- return { changed: true, target, backup }
76
- }
77
-
78
- function configNeedsRepair(serverScript) {
79
- const config = readConfig()
80
- const mcp = config && config.mcp && config.mcp['mio-intelligence']
81
- if (!mcp) return false
82
- const command = mcp.command
83
- if (!Array.isArray(command) || command.length < 2) return true
84
- const configured = String(command[1]).replace(/\\/g, '/').toLowerCase()
85
- const needle = String(serverScript).replace(/\\/g, '/').toLowerCase()
86
- return configured !== needle
87
- }
88
-
89
- // Installed configs written before 0.13.4 baked an install-time project into
90
- // MIO_CONTEXT, which overrides the server's git-based project derivation and
91
- // mis-attributes memory writes. Rewriting the entry drops it; `cwd` pins the
92
- // server process to the workspace so derivation has a stable starting point.
93
- function contextNeedsRepair() {
94
- const config = readConfig()
95
- const mcp = config && config.mcp && config.mcp['mio-intelligence']
96
- if (!mcp) return false
97
- const raw = mcp.environment && mcp.environment.MIO_CONTEXT
98
- if (typeof raw !== 'string') return true
99
- let context
100
- try {
101
- context = JSON.parse(raw)
102
- } catch (_) {
103
- return true
104
- }
105
- return Boolean(context && typeof context === 'object' && ('project' in context || 'workspace' in context))
106
- }
107
-
108
- function restartWarningMessage() {
109
- return restartWarning({
110
- hostLabel: 'OpenCode Desktop',
111
- exeName: 'OpenCode.exe',
112
- exePathFragment: '@opencode-aidesktop',
113
- configPath: configPath(),
114
- })
115
- }
116
-
117
- function isInstalled() {
118
- const config = readConfig()
119
- if (!config || !config.mcp || !config.mcp['mio-intelligence']) return false
120
- const instructions = readGlobalInstructions()
121
- return instructions.includes(INSTRUCTIONS_BEGIN) && instructions.includes(INSTRUCTIONS_END)
122
- }
123
-
124
- function install({ node, serverScript, home }) {
125
- const target = configPath()
126
- const config = readConfig() || {}
127
- const configInstalled = Boolean(config.mcp && config.mcp['mio-intelligence'])
128
- const needsRepair = configInstalled && (configNeedsRepair(serverScript) || contextNeedsRepair())
129
-
130
- let configChanged = false
131
- let backup = null
132
-
133
- if (!configInstalled || needsRepair) {
134
- backup = `${target}.bak-mio-${Date.now()}`
135
- if (fs.existsSync(target)) {
136
- fs.copyFileSync(target, backup)
137
- }
138
-
139
- const existingMcp = config.mcp && typeof config.mcp === 'object' ? config.mcp : {}
140
- const context = JSON.stringify({
141
- agentId: 'opencode',
142
- sessionId: 'opencode-session',
143
- })
144
- const next = {
145
- ...config,
146
- mcp: {
147
- ...existingMcp,
148
- 'mio-intelligence': {
149
- enabled: true,
150
- type: 'local',
151
- command: [node, serverScript],
152
- cwd: '.',
153
- environment: {
154
- MIO_DATA_DIR: home,
155
- MIO_CONTEXT: context,
156
- },
157
- },
158
- },
159
- }
160
-
161
- fs.mkdirSync(path.dirname(target), { recursive: true })
162
- fs.writeFileSync(target, `${JSON.stringify(next, null, 2)}\n`, 'utf8')
163
- configChanged = true
164
- }
165
-
166
- const instructionsResult = ensureGlobalInstructions()
167
- const changed = configChanged || instructionsResult.changed
168
-
169
- let message
170
- if (configChanged && instructionsResult.changed) {
171
- message = 'OpenCode Mio MCP installed and global Mio rules injected into AGENTS.md. Restart OpenCode to load them.'
172
- } else if (needsRepair && !instructionsResult.changed) {
173
- message = 'OpenCode Mio MCP repaired (server path or context updated). Restart OpenCode to load it.'
174
- } else if (instructionsResult.changed) {
175
- message = 'OpenCode Mio MCP already installed. Injected global Mio rules into AGENTS.md. Restart OpenCode to load them.'
176
- } else {
177
- message = 'OpenCode Mio MCP already installed.'
178
- }
179
- const restartHint = restartWarningMessage()
180
- if (restartHint) message += ' ' + restartHint
181
-
182
- return {
183
- changed,
184
- configPath: target,
185
- globalInstructionsPath: instructionsResult.target,
186
- configChanged,
187
- instructionsChanged: instructionsResult.changed,
188
- backup,
189
- backupInstructions: instructionsResult.backup,
190
- message,
191
- }
192
- }
193
-
194
- module.exports = {
195
- configPath,
196
- globalInstructionsPath,
197
- isInstalled,
198
- install,
199
- configNeedsRepair,
200
- contextNeedsRepair,
201
- restartWarningMessage,
202
- }
1
+ 'use strict'
2
+
3
+ const fs = require('fs')
4
+ const path = require('path')
5
+ const os = require('os')
6
+ const { restartWarning } = require('./host-utils')
7
+
8
+ const INSTRUCTIONS_BEGIN = '<!-- MIO_INTELLIGENCE_BEGIN -->'
9
+ const INSTRUCTIONS_END = '<!-- MIO_INTELLIGENCE_END -->'
10
+ const INSTRUCTIONS_BLOCK = `${INSTRUCTIONS_BEGIN}
11
+ ## Mio Intelligence Layer
12
+
13
+ Mio MCP tools are available. Treat these as mandatory runtime rules for every workspace.
14
+
15
+ - 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.
16
+ - After a decision affecting architecture, deployment, dependencies, or long-term behavior, call \`mio-intelligence_mio_memory_record\` with \`kind\`: \`decision\`, \`context\`, \`problem\`, or \`note\`.
17
+ - 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\`.
18
+ - 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.
19
+ - 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.
20
+ - 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\`.
21
+ - If Mio MCP is unavailable, do not block the task. Continue normally and note the missed memory or observation.
22
+ - Do not store secrets, credentials, or raw sensitive content in Mio memory.
23
+ ${INSTRUCTIONS_END}`
24
+
25
+ function configPath() {
26
+ return path.join(os.homedir(), '.config', 'opencode', 'opencode.json')
27
+ }
28
+
29
+ function globalInstructionsPath() {
30
+ return path.join(os.homedir(), '.config', 'opencode', 'AGENTS.md')
31
+ }
32
+
33
+ function readConfig() {
34
+ const target = configPath()
35
+ if (!fs.existsSync(target)) return null
36
+ try {
37
+ return JSON.parse(fs.readFileSync(target, 'utf8'))
38
+ } catch (_) {
39
+ return null
40
+ }
41
+ }
42
+
43
+ function readGlobalInstructions() {
44
+ const target = globalInstructionsPath()
45
+ if (!fs.existsSync(target)) return ''
46
+ try {
47
+ return fs.readFileSync(target, 'utf8')
48
+ } catch (_) {
49
+ return ''
50
+ }
51
+ }
52
+
53
+ function ensureGlobalInstructions() {
54
+ const target = globalInstructionsPath()
55
+ let content = readGlobalInstructions()
56
+ const original = content
57
+ const start = content.indexOf(INSTRUCTIONS_BEGIN)
58
+ if (start !== -1) {
59
+ const end = content.indexOf(INSTRUCTIONS_END, start)
60
+ content = end === -1 ? content.slice(0, start) : content.slice(0, start) + content.slice(end + INSTRUCTIONS_END.length)
61
+ content = content.trimEnd()
62
+ }
63
+ if (content) content += '\n\n'
64
+ content += INSTRUCTIONS_BLOCK + '\n'
65
+ if (content === original) {
66
+ return { changed: false, target, backup: null }
67
+ }
68
+
69
+ const backup = `${target}.bak-mio-${Date.now()}`
70
+ if (fs.existsSync(target)) {
71
+ fs.copyFileSync(target, backup)
72
+ }
73
+ fs.mkdirSync(path.dirname(target), { recursive: true })
74
+ fs.writeFileSync(target, content, 'utf8')
75
+ return { changed: true, target, backup }
76
+ }
77
+
78
+ function configNeedsRepair(serverScript) {
79
+ const config = readConfig()
80
+ const mcp = config && config.mcp && config.mcp['mio-intelligence']
81
+ if (!mcp) return false
82
+ const command = mcp.command
83
+ if (!Array.isArray(command) || command.length < 2) return true
84
+ const configured = String(command[1]).replace(/\\/g, '/').toLowerCase()
85
+ const needle = String(serverScript).replace(/\\/g, '/').toLowerCase()
86
+ return configured !== needle
87
+ }
88
+
89
+ // Installed configs written before 0.13.4 baked an install-time project into
90
+ // MIO_CONTEXT, which overrides the server's git-based project derivation and
91
+ // mis-attributes memory writes. Rewriting the entry drops it; `cwd` pins the
92
+ // server process to the workspace so derivation has a stable starting point.
93
+ function contextNeedsRepair() {
94
+ const config = readConfig()
95
+ const mcp = config && config.mcp && config.mcp['mio-intelligence']
96
+ if (!mcp) return false
97
+ const raw = mcp.environment && mcp.environment.MIO_CONTEXT
98
+ if (typeof raw !== 'string') return true
99
+ let context
100
+ try {
101
+ context = JSON.parse(raw)
102
+ } catch (err) {
103
+ console.error('Invalid MIO_CONTEXT JSON in opencode config:', err.message)
104
+ return false
105
+ }
106
+ return Boolean(context && typeof context === 'object' && ('project' in context || 'workspace' in context))
107
+ }
108
+
109
+ function restartWarningMessage() {
110
+ return restartWarning({
111
+ hostLabel: 'OpenCode Desktop',
112
+ exeName: 'OpenCode.exe',
113
+ exePathFragment: '@opencode-aidesktop',
114
+ configPath: configPath(),
115
+ })
116
+ }
117
+
118
+ function isInstalled() {
119
+ const config = readConfig()
120
+ if (!config || !config.mcp || !config.mcp['mio-intelligence']) return false
121
+ const instructions = readGlobalInstructions()
122
+ return instructions.includes(INSTRUCTIONS_BEGIN) && instructions.includes(INSTRUCTIONS_END)
123
+ }
124
+
125
+ function install({ node, serverScript, home }) {
126
+ const target = configPath()
127
+ const config = readConfig() || {}
128
+ const configInstalled = Boolean(config.mcp && config.mcp['mio-intelligence'])
129
+ const needsRepair = configInstalled && (configNeedsRepair(serverScript) || contextNeedsRepair())
130
+
131
+ let configChanged = false
132
+ let backup = null
133
+
134
+ if (!configInstalled || needsRepair) {
135
+ backup = `${target}.bak-mio-${Date.now()}`
136
+ if (fs.existsSync(target)) {
137
+ fs.copyFileSync(target, backup)
138
+ }
139
+
140
+ const existingMcp = config.mcp && typeof config.mcp === 'object' ? config.mcp : {}
141
+ const context = JSON.stringify({
142
+ agentId: 'opencode',
143
+ sessionId: 'opencode-session',
144
+ })
145
+ const next = {
146
+ ...config,
147
+ mcp: {
148
+ ...existingMcp,
149
+ 'mio-intelligence': {
150
+ enabled: true,
151
+ type: 'local',
152
+ command: [node, serverScript],
153
+ cwd: '.',
154
+ environment: {
155
+ MIO_DATA_DIR: home,
156
+ MIO_CONTEXT: context,
157
+ },
158
+ },
159
+ },
160
+ }
161
+
162
+ fs.mkdirSync(path.dirname(target), { recursive: true })
163
+ fs.writeFileSync(target, `${JSON.stringify(next, null, 2)}\n`, 'utf8')
164
+ configChanged = true
165
+ }
166
+
167
+ const instructionsResult = ensureGlobalInstructions()
168
+ const changed = configChanged || instructionsResult.changed
169
+
170
+ let message
171
+ if (configChanged && instructionsResult.changed) {
172
+ message = 'OpenCode Mio MCP installed and global Mio rules injected into AGENTS.md. Restart OpenCode to load them.'
173
+ } else if (needsRepair && !instructionsResult.changed) {
174
+ message = 'OpenCode Mio MCP repaired (server path or context updated). Restart OpenCode to load it.'
175
+ } else if (instructionsResult.changed) {
176
+ message = 'OpenCode Mio MCP already installed. Injected global Mio rules into AGENTS.md. Restart OpenCode to load them.'
177
+ } else {
178
+ message = 'OpenCode Mio MCP already installed.'
179
+ }
180
+ const restartHint = restartWarningMessage()
181
+ if (restartHint) message += ' ' + restartHint
182
+
183
+ return {
184
+ changed,
185
+ configPath: target,
186
+ globalInstructionsPath: instructionsResult.target,
187
+ configChanged,
188
+ instructionsChanged: instructionsResult.changed,
189
+ backup,
190
+ backupInstructions: instructionsResult.backup,
191
+ message,
192
+ }
193
+ }
194
+
195
+ module.exports = {
196
+ configPath,
197
+ globalInstructionsPath,
198
+ isInstalled,
199
+ install,
200
+ configNeedsRepair,
201
+ contextNeedsRepair,
202
+ restartWarningMessage,
203
+ }