@jossuealcala/madre 0.4.0 → 0.5.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.
- package/CHANGELOG.md +164 -0
- package/README.md +1 -1
- package/docs/REFERENCE.md +3 -2
- package/docs/SDK.md +19 -5
- package/docs/sdk/hello-module.mjs +8 -0
- package/package.json +1 -1
- package/public/app.js +414 -64
- package/public/es.js +246 -5
- package/public/index.html +4 -2
- package/public/styles.css +122 -14
- package/public/syntax.js +121 -0
- package/public/troubleshooting.js +100 -0
- package/src/adapters/codex.mjs +4 -1
- package/src/adapters/process.mjs +62 -15
- package/src/mcp/image-server.mjs +24 -2
- package/src/modules/image-studio.mjs +3 -2
- package/src/modules/index.mjs +23 -5
- package/src/modules/playwright.mjs +9 -5
- package/src/modules/sdk.mjs +15 -0
- package/src/outbound.mjs +7 -2
- package/src/room.mjs +12 -0
- package/src/runtime-detection.mjs +50 -0
- package/src/sentinel-errors.mjs +19 -1
- package/src/server.mjs +23 -12
- package/src/updates.mjs +9 -1
package/public/syntax.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The names below are the names of formats — JavaScript, JSON, CSS, Markdown — and a format is
|
|
2
|
+
// called the same in every language, so they never pass through the catalogue.
|
|
3
|
+
//
|
|
4
|
+
// Reading a file in the room, not just seeing it. MADRE has no dependencies and is not about to
|
|
5
|
+
// take one for this: a highlighter good enough for a viewer is a few hundred lines, and a
|
|
6
|
+
// highlighter that has to be perfect is somebody else's product.
|
|
7
|
+
//
|
|
8
|
+
// The rule it follows is the one that keeps it honest: it never rewrites the file. It hands back
|
|
9
|
+
// spans over the same characters, in the same order, so what the screen shows and what is on disk
|
|
10
|
+
// are the same bytes. Anything it cannot classify stays plain text rather than being guessed at.
|
|
11
|
+
|
|
12
|
+
const FAMILIES = {
|
|
13
|
+
js: { ext: /\.(m|c)?jsx?$|\.tsx?$/i, label: 'JavaScript',
|
|
14
|
+
keywords: /\b(await|async|break|case|catch|class|const|continue|default|delete|do|else|export|extends|finally|for|from|function|get|if|import|in|instanceof|let|new|of|return|set|static|super|switch|this|throw|try|typeof|var|void|while|yield|as|interface|type|enum|implements|readonly|declare|namespace|satisfies)\b/,
|
|
15
|
+
literals: /\b(true|false|null|undefined|NaN|Infinity)\b/,
|
|
16
|
+
line: /\/\/.*/, block: [/\/\*/, /\*\//], strings: ['"', "'", '`'] },
|
|
17
|
+
json: { ext: /\.json(c|5)?$|^\.?[a-z-]*rc$/i, label: 'JSON',
|
|
18
|
+
keywords: /(?!)/, literals: /\b(true|false|null)\b/, line: /(?!)/, block: null, strings: ['"'] },
|
|
19
|
+
css: { ext: /\.(css|scss|less|sass)$/i, label: 'CSS',
|
|
20
|
+
keywords: /@[a-z-]+|\b(important)\b/, literals: /#[0-9a-f]{3,8}\b|\b\d+(\.\d+)?(px|rem|em|%|vh|vw|s|ms|fr|deg)\b/i,
|
|
21
|
+
line: /\/\/.*/, block: [/\/\*/, /\*\//], strings: ['"', "'"] },
|
|
22
|
+
html: { ext: /\.(html?|xhtml|xml|svg|vue|svelte)$/i, label: 'HTML',
|
|
23
|
+
keywords: /<\/?[a-zA-Z][\w:-]*|\/?>/, literals: /(?!)/, line: /(?!)/, block: [/<!--/, /-->/], strings: ['"', "'"] },
|
|
24
|
+
md: { ext: /\.(md|markdown|mdx)$/i, label: 'Markdown',
|
|
25
|
+
keywords: /^#{1,6}\s.*$|^\s*[-*+]\s|^\s*\d+\.\s|^>\s?.*$/, literals: /\*\*[^*]+\*\*|__[^_]+__|\[[^\]]*\]\([^)]*\)/,
|
|
26
|
+
line: /(?!)/, block: null, strings: ['`'] },
|
|
27
|
+
py: { ext: /\.pyi?$/i, label: 'Python',
|
|
28
|
+
keywords: /\b(and|as|assert|async|await|break|class|continue|def|del|elif|else|except|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|raise|return|try|while|with|yield|match|case)\b/,
|
|
29
|
+
literals: /\b(True|False|None|self|cls)\b/, line: /#.*/, block: null, strings: ['"', "'"] },
|
|
30
|
+
sh: { ext: /\.(sh|bash|zsh|fish)$|^(Makefile|Dockerfile|\.env.*)$/i, label: 'Shell',
|
|
31
|
+
keywords: /\b(if|then|else|elif|fi|for|while|do|done|case|esac|function|return|export|local|source|set|trap|in)\b/,
|
|
32
|
+
literals: /\$\{?[A-Za-z_][\w]*\}?|\$\(.*?\)/, line: /#.*/, block: null, strings: ['"', "'"] },
|
|
33
|
+
yaml: { ext: /\.(ya?ml|toml|ini|cfg|conf)$/i, label: 'Config',
|
|
34
|
+
keywords: /^\s*[\w.-]+(?=\s*[:=])/, literals: /\b(true|false|null|yes|no|on|off)\b/, line: /#.*/, block: null, strings: ['"', "'"] },
|
|
35
|
+
sql: { ext: /\.sql$/i, label: 'SQL',
|
|
36
|
+
keywords: /\b(select|from|where|insert|into|values|update|set|delete|create|table|index|drop|alter|join|left|right|inner|outer|on|group|order|by|having|limit|offset|and|or|not|null|primary|key|foreign|references|as|distinct|union|with)\b/i,
|
|
37
|
+
literals: /\b\d+\b/, line: /--.*/, block: [/\/\*/, /\*\//], strings: ["'", '"'] },
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// What this file is, by its name alone. A family MADRE does not know is not an error: the file is
|
|
41
|
+
// shown as plain text, which is what it would have been anyway.
|
|
42
|
+
export function familyOf(name) {
|
|
43
|
+
const base = String(name ?? '').split('/').pop() ?? '';
|
|
44
|
+
for (const [id, family] of Object.entries(FAMILIES)) if (family.ext.test(base)) return { id, ...family };
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function languageLabel(name) {
|
|
49
|
+
return familyOf(name)?.label ?? null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// One line, cut into pieces. Returns [{ text, kind }] covering exactly the input: joining every
|
|
53
|
+
// `text` back together must give the line unchanged, which is what the test holds it to.
|
|
54
|
+
export function tokenize(line, family, state = { inBlock: false }) {
|
|
55
|
+
if (!family) return [{ text: line, kind: null }];
|
|
56
|
+
const out = [];
|
|
57
|
+
let index = 0;
|
|
58
|
+
const push = (text, kind) => { if (text) out.push({ text, kind }); };
|
|
59
|
+
|
|
60
|
+
while (index < line.length) {
|
|
61
|
+
const rest = line.slice(index);
|
|
62
|
+
|
|
63
|
+
if (state.inBlock) {
|
|
64
|
+
const close = family.block ? rest.match(family.block[1]) : null;
|
|
65
|
+
if (!close) { push(rest, 'comment'); index = line.length; break; }
|
|
66
|
+
push(rest.slice(0, close.index + close[0].length), 'comment');
|
|
67
|
+
index += close.index + close[0].length;
|
|
68
|
+
state.inBlock = false;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const blockOpen = family.block ? rest.match(family.block[0]) : null;
|
|
73
|
+
const lineOpen = rest.match(family.line);
|
|
74
|
+
const quote = family.strings.map((q) => rest.indexOf(q)).filter((at) => at >= 0).sort((a, b) => a - b)[0];
|
|
75
|
+
|
|
76
|
+
// Whichever comes first on this line decides what happens next.
|
|
77
|
+
const marks = [
|
|
78
|
+
blockOpen && blockOpen.index === 0 ? { at: 0, what: 'block' } : blockOpen ? { at: blockOpen.index, what: 'block' } : null,
|
|
79
|
+
lineOpen && lineOpen.index !== undefined ? { at: lineOpen.index, what: 'line' } : null,
|
|
80
|
+
quote !== undefined ? { at: quote, what: 'string' } : null,
|
|
81
|
+
].filter(Boolean).sort((a, b) => a.at - b.at);
|
|
82
|
+
const next = marks[0];
|
|
83
|
+
|
|
84
|
+
if (!next) { pushCode(rest, family, push); index = line.length; break; }
|
|
85
|
+
if (next.at > 0) { pushCode(rest.slice(0, next.at), family, push); index += next.at; continue; }
|
|
86
|
+
|
|
87
|
+
if (next.what === 'line') { push(rest, 'comment'); index = line.length; break; }
|
|
88
|
+
if (next.what === 'block') {
|
|
89
|
+
const opened = rest.match(family.block[0])[0];
|
|
90
|
+
const after = rest.slice(opened.length);
|
|
91
|
+
const close = after.match(family.block[1]);
|
|
92
|
+
if (close) { push(rest.slice(0, opened.length + close.index + close[0].length), 'comment'); index += opened.length + close.index + close[0].length; continue; }
|
|
93
|
+
push(rest, 'comment'); index = line.length; state.inBlock = true; break;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A string: to its closing quote, respecting a backslash escape.
|
|
97
|
+
const q = rest[0];
|
|
98
|
+
let at = 1;
|
|
99
|
+
while (at < rest.length) {
|
|
100
|
+
if (rest[at] === '\\') { at += 2; continue; }
|
|
101
|
+
if (rest[at] === q) { at += 1; break; }
|
|
102
|
+
at += 1;
|
|
103
|
+
}
|
|
104
|
+
push(rest.slice(0, at), 'string');
|
|
105
|
+
index += at;
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Code between the comments and the strings: keywords, literals, numbers, and the rest plain.
|
|
111
|
+
function pushCode(chunk, family, push) {
|
|
112
|
+
const pattern = new RegExp(`${family.keywords.source}|${family.literals.source}|\\b\\d+(?:\\.\\d+)?\\b`, family.keywords.flags.includes('i') ? 'gi' : 'g');
|
|
113
|
+
let last = 0;
|
|
114
|
+
for (const match of chunk.matchAll(pattern)) {
|
|
115
|
+
if (match.index > last) push(chunk.slice(last, match.index), null);
|
|
116
|
+
const text = match[0];
|
|
117
|
+
push(text, family.keywords.test(text) ? 'keyword' : /^\d/.test(text) ? 'number' : 'literal');
|
|
118
|
+
last = match.index + text.length;
|
|
119
|
+
}
|
|
120
|
+
if (last < chunk.length) push(chunk.slice(last), null);
|
|
121
|
+
}
|
|
@@ -46,8 +46,52 @@ const loginAgent = {
|
|
|
46
46
|
const same = (commands) => ({ darwin: commands, linux: commands });
|
|
47
47
|
|
|
48
48
|
export const CONDITIONS = [
|
|
49
|
+
{
|
|
50
|
+
id: 'claude-api-key-shadows-plan',
|
|
51
|
+
code: 'MU-001',
|
|
52
|
+
agent: 'claude',
|
|
53
|
+
severity: 'common',
|
|
54
|
+
title: 'ANTHROPIC_API_KEY takes precedence over the claude.ai plan',
|
|
55
|
+
match: /connectors are disabled because ANTHROPIC_API_KEY|another auth source is set and takes precedence|takes precedence over your claude\.ai login/i,
|
|
56
|
+
diagnosis: 'Claude Code found an API key in the environment as well as the claude.ai login, and the key wins. MADRE hands the CLI the environment exactly as this machine has it, so whatever is exported in your shell profile reaches it. The turn is billed to the API account, not to the plan, and your organisation connectors do not load.',
|
|
57
|
+
remedy: 'Decide which one you mean. To work on the plan, unset the key where your shell exports it and open the room again; to work on the API, this message is only a notice and can be ignored.',
|
|
58
|
+
fixes: {
|
|
59
|
+
darwin: ['grep -rn ANTHROPIC_API_KEY ~/.zshrc ~/.zprofile ~/.zshenv 2>/dev/null', '# remove or comment the line it finds, then:', 'unset ANTHROPIC_API_KEY', '# and start MADRE from a new terminal'],
|
|
60
|
+
linux: ['grep -rn ANTHROPIC_API_KEY ~/.bashrc ~/.profile ~/.bash_profile 2>/dev/null', 'unset ANTHROPIC_API_KEY', '# and start MADRE from a new terminal'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'custom-agent-endpoint',
|
|
65
|
+
code: 'MU-055',
|
|
66
|
+
severity: 'blocking',
|
|
67
|
+
title: 'The agent is pointed at a custom endpoint',
|
|
68
|
+
match: /MADRE blocked @(?:codex|claude) before sending the briefing|Subscribe to (?:the )?Telegram|@conduitapi/i,
|
|
69
|
+
diagnosis: 'The installed CLI inherited a base-URL override from this computer, so its request was going to another gateway instead of the official agent service. A demand to join a Telegram channel comes from that gateway, not from MADRE, Anthropic or OpenAI. MADRE now names that route and stops before sending the briefing.',
|
|
70
|
+
remedy: 'Do not subscribe or enter credentials. Remove the base-URL override and reopen MADRE to use the official service. If the gateway is intentional and you have verified who operates it, explicitly allow only that agent.',
|
|
71
|
+
fixes: {
|
|
72
|
+
darwin: ['env | grep -E \'^(ANTHROPIC|OPENAI)_BASE_URL=\'', '# remove that export from ~/.zshrc, ~/.zprofile or ~/.zshenv; then:', 'unset ANTHROPIC_BASE_URL OPENAI_BASE_URL', '# open a new room; only for a gateway you verified:', 'PULSE_ALLOW_CUSTOM_AGENT_ENDPOINTS=claude madre start'],
|
|
73
|
+
linux: ['env | grep -E \'^(ANTHROPIC|OPENAI)_BASE_URL=\'', '# remove that export from ~/.bashrc, ~/.profile or ~/.bash_profile; then:', 'unset ANTHROPIC_BASE_URL OPENAI_BASE_URL', '# open a new room; only for a gateway you verified:', 'PULSE_ALLOW_CUSTOM_AGENT_ENDPOINTS=claude madre start'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'codex-reading-stdin',
|
|
78
|
+
code: 'MU-002',
|
|
79
|
+
solvedIn: 'connections',
|
|
80
|
+
agent: 'codex',
|
|
81
|
+
severity: 'informational',
|
|
82
|
+
title: 'Codex says it is reading additional input from stdin',
|
|
83
|
+
match: /Reading additional input from stdin/i,
|
|
84
|
+
diagnosis: 'A notice Codex prints, not a failure. MADRE runs it with stdin closed, so the line is harmless and the turn is decided by what Codex returned. If the turn failed, the reason is the line above this one — the exit code, or a clean exit with no answer.',
|
|
85
|
+
remedy: 'Nothing to do about the notice itself. If turns keep failing, check that this Codex is recent enough for the flags MADRE passes: `codex --version`, then update it.',
|
|
86
|
+
fixes: {
|
|
87
|
+
darwin: ['codex --version', 'npm install -g @openai/codex'],
|
|
88
|
+
linux: ['codex --version', 'npm install -g @openai/codex'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
49
91
|
{
|
|
50
92
|
id: 'gemini-ineligible-tier',
|
|
93
|
+
code: 'MU-003',
|
|
94
|
+
solvedIn: 'connections',
|
|
51
95
|
agent: 'gemini',
|
|
52
96
|
severity: 'blocking',
|
|
53
97
|
title: 'Gemini refuses the personal Google login',
|
|
@@ -61,6 +105,8 @@ export const CONDITIONS = [
|
|
|
61
105
|
},
|
|
62
106
|
{
|
|
63
107
|
id: 'gemini-credits-depleted',
|
|
108
|
+
code: 'MU-004',
|
|
109
|
+
solvedIn: 'connections',
|
|
64
110
|
agent: 'gemini',
|
|
65
111
|
severity: 'blocking',
|
|
66
112
|
title: 'Gemini key out of prepaid credits',
|
|
@@ -71,6 +117,8 @@ export const CONDITIONS = [
|
|
|
71
117
|
},
|
|
72
118
|
{
|
|
73
119
|
id: 'gemini-rate-limited',
|
|
120
|
+
code: 'MU-005',
|
|
121
|
+
solvedIn: 'connections',
|
|
74
122
|
agent: 'gemini',
|
|
75
123
|
severity: 'transient',
|
|
76
124
|
title: 'Gemini key rate-limited by Google (HTTP 429)',
|
|
@@ -84,6 +132,7 @@ export const CONDITIONS = [
|
|
|
84
132
|
},
|
|
85
133
|
{
|
|
86
134
|
id: 'gemini-high-demand',
|
|
135
|
+
code: 'MU-006',
|
|
87
136
|
agent: 'gemini',
|
|
88
137
|
severity: 'transient',
|
|
89
138
|
title: 'Gemini model under high demand (503)',
|
|
@@ -94,6 +143,8 @@ export const CONDITIONS = [
|
|
|
94
143
|
},
|
|
95
144
|
{
|
|
96
145
|
id: 'opencode-default-provider',
|
|
146
|
+
code: 'MU-007',
|
|
147
|
+
solvedIn: 'connections',
|
|
97
148
|
agent: 'opencode',
|
|
98
149
|
severity: 'blocking',
|
|
99
150
|
title: 'OpenCode picked a provider without a valid session',
|
|
@@ -107,6 +158,8 @@ export const CONDITIONS = [
|
|
|
107
158
|
},
|
|
108
159
|
{
|
|
109
160
|
id: 'not-signed-in',
|
|
161
|
+
code: 'MU-008',
|
|
162
|
+
solvedIn: 'connections',
|
|
110
163
|
severity: 'blocking',
|
|
111
164
|
title: 'Agent installed but signed out',
|
|
112
165
|
match: /not logged in|sign in required|Error authenticating|unauthorized|please (?:log|sign) ?in|no credentials/i,
|
|
@@ -117,6 +170,7 @@ export const CONDITIONS = [
|
|
|
117
170
|
},
|
|
118
171
|
{
|
|
119
172
|
id: 'not-installed',
|
|
173
|
+
code: 'MU-009',
|
|
120
174
|
severity: 'blocking',
|
|
121
175
|
title: 'Agent not found on this computer',
|
|
122
176
|
match: /is not installed on this computer|not found on this computer|ENOENT.*(codex|claude|gemini|opencode)/i,
|
|
@@ -127,6 +181,7 @@ export const CONDITIONS = [
|
|
|
127
181
|
},
|
|
128
182
|
{
|
|
129
183
|
id: 'adapter-pending',
|
|
184
|
+
code: 'MU-010',
|
|
130
185
|
severity: 'blocking',
|
|
131
186
|
title: 'Agent detected, adapter not enabled',
|
|
132
187
|
match: /adapter is not enabled yet|does not have a supported MADRE adapter/i,
|
|
@@ -136,6 +191,7 @@ export const CONDITIONS = [
|
|
|
136
191
|
},
|
|
137
192
|
{
|
|
138
193
|
id: 'claude-args',
|
|
194
|
+
code: 'MU-011',
|
|
139
195
|
agent: 'claude',
|
|
140
196
|
severity: 'fixed',
|
|
141
197
|
title: 'Claude read the prompt as an MCP config path',
|
|
@@ -146,6 +202,7 @@ export const CONDITIONS = [
|
|
|
146
202
|
},
|
|
147
203
|
{
|
|
148
204
|
id: 'timeout',
|
|
205
|
+
code: 'MU-012',
|
|
149
206
|
severity: 'tunable',
|
|
150
207
|
title: 'Agent did not respond before the timeout',
|
|
151
208
|
match: /did not respond before the timeout|went silent for/i,
|
|
@@ -158,6 +215,7 @@ export const CONDITIONS = [
|
|
|
158
215
|
},
|
|
159
216
|
{
|
|
160
217
|
id: 'interrupted',
|
|
218
|
+
code: 'MU-013',
|
|
161
219
|
severity: 'informational',
|
|
162
220
|
title: 'Turn interrupted by a MADRE restart',
|
|
163
221
|
match: /interrupted because MADRE is shutting down|MADRE stopped while|turn was not completed/i,
|
|
@@ -167,6 +225,7 @@ export const CONDITIONS = [
|
|
|
167
225
|
},
|
|
168
226
|
{
|
|
169
227
|
id: 'message-too-long',
|
|
228
|
+
code: 'MU-014',
|
|
170
229
|
severity: 'tunable',
|
|
171
230
|
title: 'Message rejected for length',
|
|
172
231
|
match: /Message is too long/i,
|
|
@@ -179,6 +238,7 @@ export const CONDITIONS = [
|
|
|
179
238
|
},
|
|
180
239
|
{
|
|
181
240
|
id: 'limits-real',
|
|
241
|
+
code: 'MU-015',
|
|
182
242
|
severity: 'informational',
|
|
183
243
|
title: 'Where the rings get their numbers',
|
|
184
244
|
match: /ring|quota|limit window|resets|rollout|oauth usage|window reset|limits-real/i,
|
|
@@ -188,6 +248,7 @@ export const CONDITIONS = [
|
|
|
188
248
|
},
|
|
189
249
|
{
|
|
190
250
|
id: 'budget-exhausted',
|
|
251
|
+
code: 'MU-016',
|
|
191
252
|
severity: 'tunable',
|
|
192
253
|
title: 'Local token budget exhausted for an agent',
|
|
193
254
|
match: /MADRE exhausted|local room token budget/i,
|
|
@@ -200,6 +261,7 @@ export const CONDITIONS = [
|
|
|
200
261
|
},
|
|
201
262
|
{
|
|
202
263
|
id: 'port-in-use',
|
|
264
|
+
code: 'MU-017',
|
|
203
265
|
severity: 'blocking',
|
|
204
266
|
title: 'Port already in use',
|
|
205
267
|
match: /EADDRINUSE|already in use/i,
|
|
@@ -212,6 +274,7 @@ export const CONDITIONS = [
|
|
|
212
274
|
},
|
|
213
275
|
{
|
|
214
276
|
id: 'stream-reconnecting',
|
|
277
|
+
code: 'MU-018',
|
|
215
278
|
severity: 'blocking',
|
|
216
279
|
title: 'Live stream keeps reconnecting',
|
|
217
280
|
match: /reconnecting|EventSource|ECONNREFUSED/i,
|
|
@@ -221,6 +284,7 @@ export const CONDITIONS = [
|
|
|
221
284
|
},
|
|
222
285
|
{
|
|
223
286
|
id: 'opencode-server-error',
|
|
287
|
+
code: 'MU-019',
|
|
224
288
|
agent: 'opencode',
|
|
225
289
|
severity: 'transient',
|
|
226
290
|
title: 'OpenCode reported an unexpected server error',
|
|
@@ -231,6 +295,7 @@ export const CONDITIONS = [
|
|
|
231
295
|
},
|
|
232
296
|
{
|
|
233
297
|
id: 'runaway-room',
|
|
298
|
+
code: 'MU-020',
|
|
234
299
|
severity: 'blocking',
|
|
235
300
|
title: 'Too many agents working at once',
|
|
236
301
|
match: /agents are working at once|already answering another turn|no second plan will start|has been running for/i,
|
|
@@ -241,6 +306,7 @@ export const CONDITIONS = [
|
|
|
241
306
|
// ---- capability assistance: how an agent could gain a scope it lacks ----
|
|
242
307
|
{
|
|
243
308
|
id: 'scope-gemini-imageGen',
|
|
309
|
+
code: 'MU-051',
|
|
244
310
|
agent: 'gemini',
|
|
245
311
|
severity: 'informational',
|
|
246
312
|
title: 'Gemini: generating images',
|
|
@@ -251,6 +317,7 @@ export const CONDITIONS = [
|
|
|
251
317
|
},
|
|
252
318
|
{
|
|
253
319
|
id: 'scope-claude-imageGen',
|
|
320
|
+
code: 'MU-052',
|
|
254
321
|
agent: 'claude',
|
|
255
322
|
severity: 'informational',
|
|
256
323
|
title: 'Claude Code: generating images',
|
|
@@ -261,6 +328,7 @@ export const CONDITIONS = [
|
|
|
261
328
|
},
|
|
262
329
|
{
|
|
263
330
|
id: 'scope-opencode-imageGen',
|
|
331
|
+
code: 'MU-053',
|
|
264
332
|
agent: 'opencode',
|
|
265
333
|
severity: 'informational',
|
|
266
334
|
title: 'OpenCode: generating images',
|
|
@@ -271,6 +339,7 @@ export const CONDITIONS = [
|
|
|
271
339
|
},
|
|
272
340
|
{
|
|
273
341
|
id: 'scope-web',
|
|
342
|
+
code: 'MU-021',
|
|
274
343
|
severity: 'informational',
|
|
275
344
|
title: 'Web access for an agent',
|
|
276
345
|
match: /scope-[a-z]+-web/,
|
|
@@ -280,6 +349,7 @@ export const CONDITIONS = [
|
|
|
280
349
|
},
|
|
281
350
|
{
|
|
282
351
|
id: 'slash-commands',
|
|
352
|
+
code: 'MU-022',
|
|
283
353
|
severity: 'informational',
|
|
284
354
|
title: 'Commands and mentions in the field box',
|
|
285
355
|
match: /slash|command|\/git|\/ahp|mention|@agent/i,
|
|
@@ -289,6 +359,7 @@ export const CONDITIONS = [
|
|
|
289
359
|
},
|
|
290
360
|
{
|
|
291
361
|
id: 'modes',
|
|
362
|
+
code: 'MU-023',
|
|
292
363
|
severity: 'informational',
|
|
293
364
|
title: 'Permission modes: #0 GHOST · #1 EXCHANGE · #2 CREATE · #3 CONTROL · #4 AIRLOCK',
|
|
294
365
|
match: /mode|ghost|exchange|control|airlock|deploy|despleg|producci|override|designation|max mode|#[0-4]\b/i,
|
|
@@ -298,6 +369,7 @@ export const CONDITIONS = [
|
|
|
298
369
|
},
|
|
299
370
|
{
|
|
300
371
|
id: 'lease-missing',
|
|
372
|
+
code: 'MU-024',
|
|
301
373
|
severity: 'informational',
|
|
302
374
|
title: 'Creating files: who grants the permission',
|
|
303
375
|
match: /no CREATE lease|lease-missing|read-only and do not modify|permiso de escritura|solo lectura/i,
|
|
@@ -307,6 +379,7 @@ export const CONDITIONS = [
|
|
|
307
379
|
},
|
|
308
380
|
{
|
|
309
381
|
id: 'scope-write',
|
|
382
|
+
code: 'MU-025',
|
|
310
383
|
severity: 'informational',
|
|
311
384
|
title: 'Creating files with an agent',
|
|
312
385
|
match: /scope-[a-z]+-write/,
|
|
@@ -316,6 +389,7 @@ export const CONDITIONS = [
|
|
|
316
389
|
},
|
|
317
390
|
{
|
|
318
391
|
id: 'node-version',
|
|
392
|
+
code: 'MU-026',
|
|
319
393
|
severity: 'blocking',
|
|
320
394
|
title: 'Node.js too old',
|
|
321
395
|
match: /SyntaxError: Unexpected token|ERR_REQUIRE_ESM|engines|Unsupported engine/i,
|
|
@@ -328,6 +402,7 @@ export const CONDITIONS = [
|
|
|
328
402
|
},
|
|
329
403
|
{
|
|
330
404
|
id: 'memory-unavailable',
|
|
405
|
+
code: 'MU-027',
|
|
331
406
|
severity: 'degraded',
|
|
332
407
|
title: 'Room memory unavailable: turns get only the recent window',
|
|
333
408
|
match: /memory unavailable|memory index failed|memory recall failed|node:sqlite|SQLITE_|database disk image is malformed|memory\.sqlite/i,
|
|
@@ -340,6 +415,7 @@ export const CONDITIONS = [
|
|
|
340
415
|
},
|
|
341
416
|
{
|
|
342
417
|
id: 'memory-recall',
|
|
418
|
+
code: 'MU-028',
|
|
343
419
|
severity: 'informational',
|
|
344
420
|
title: 'Room memory: what an agent remembers and how',
|
|
345
421
|
match: /\brecall\b|recuerd|\bremember\b|memoria de la sala|<memory>|<memories>/i,
|
|
@@ -349,6 +425,7 @@ export const CONDITIONS = [
|
|
|
349
425
|
},
|
|
350
426
|
{
|
|
351
427
|
id: 'memory-distill',
|
|
428
|
+
code: 'MU-029',
|
|
352
429
|
severity: 'degraded',
|
|
353
430
|
title: 'Distilled memories: the archivist did not run, or failed',
|
|
354
431
|
match: /could not distil|distill|destil|batch skipped|archivist|memory\.distilled|memories? (were|was) not/i,
|
|
@@ -358,6 +435,8 @@ export const CONDITIONS = [
|
|
|
358
435
|
},
|
|
359
436
|
{
|
|
360
437
|
id: 'memory-embeddings',
|
|
438
|
+
code: 'MU-030',
|
|
439
|
+
solvedIn: 'connections',
|
|
361
440
|
severity: 'degraded',
|
|
362
441
|
title: 'Embeddings paused: recall is lexical only',
|
|
363
442
|
match: /embeddings paused|embedding timed out|Gemini embeddings HTTP|batchEmbedContents|LINKS NEED EMBEDDINGS/i,
|
|
@@ -370,6 +449,8 @@ export const CONDITIONS = [
|
|
|
370
449
|
},
|
|
371
450
|
{
|
|
372
451
|
id: 'memory-tools',
|
|
452
|
+
code: 'MU-031',
|
|
453
|
+
solvedIn: 'connections',
|
|
373
454
|
severity: 'degraded',
|
|
374
455
|
title: 'An agent says it cannot search the memory (pulse-memory MCP)',
|
|
375
456
|
match: /pulse-memory|memory_search|memory_recall|memory_notes|memory_timeline|memory_note|mcp.*(failed|error|not found|unavailable)|MCP server/i,
|
|
@@ -382,6 +463,7 @@ export const CONDITIONS = [
|
|
|
382
463
|
},
|
|
383
464
|
{
|
|
384
465
|
id: 'memory-note',
|
|
466
|
+
code: 'MU-032',
|
|
385
467
|
severity: 'informational',
|
|
386
468
|
title: 'Saving a memory on request; GHOST refuses',
|
|
387
469
|
match: /save (a |the |this )?memory|guarda.*memoria|memory saved|off the record.*nothing can be saved|remember this|memory_note/i,
|
|
@@ -391,6 +473,7 @@ export const CONDITIONS = [
|
|
|
391
473
|
},
|
|
392
474
|
{
|
|
393
475
|
id: 'nostromo-access',
|
|
476
|
+
code: 'MU-033',
|
|
394
477
|
severity: 'informational',
|
|
395
478
|
title: 'NOSTROMO: boarding, reading and forgetting',
|
|
396
479
|
match: /nostromo|designation|UNABLE TO COMPUTE|ARCHIVE IS SEALED|forget this memory/i,
|
|
@@ -400,6 +483,7 @@ export const CONDITIONS = [
|
|
|
400
483
|
},
|
|
401
484
|
{
|
|
402
485
|
id: 'ripley',
|
|
486
|
+
code: 'MU-034',
|
|
403
487
|
severity: 'informational',
|
|
404
488
|
title: 'RIPLEY: rendering HTML, SVG and Markdown in the viewer',
|
|
405
489
|
match: /ripley|RIPLEY is off|renders \.html|render(ed|ing)? (the )?(html|svg|markdown)/i,
|
|
@@ -409,6 +493,7 @@ export const CONDITIONS = [
|
|
|
409
493
|
},
|
|
410
494
|
{
|
|
411
495
|
id: 'ollama',
|
|
496
|
+
code: 'MU-035',
|
|
412
497
|
severity: 'informational',
|
|
413
498
|
title: 'Ollama: local embeddings and a local archivist',
|
|
414
499
|
match: /ollama|11434|local model|nomic-embed|qwen|llama3/i,
|
|
@@ -421,6 +506,7 @@ export const CONDITIONS = [
|
|
|
421
506
|
},
|
|
422
507
|
{
|
|
423
508
|
id: 'madre-agent',
|
|
509
|
+
code: 'MU-036',
|
|
424
510
|
severity: 'informational',
|
|
425
511
|
title: '@madre: the room\'s own memory, as an agent',
|
|
426
512
|
match: /@madre|madre-local|needs Ollama running with a chat model/i,
|
|
@@ -430,6 +516,7 @@ export const CONDITIONS = [
|
|
|
430
516
|
},
|
|
431
517
|
{
|
|
432
518
|
id: 'install-needs-admin',
|
|
519
|
+
code: 'MU-037',
|
|
433
520
|
severity: 'informational',
|
|
434
521
|
title: 'Installing a CLI: npm cannot write to the system folder',
|
|
435
522
|
match: /EACCES|EPERM|permission denied|Missing write access|npm ERR!.*sudo|administrator/i,
|
|
@@ -442,6 +529,7 @@ export const CONDITIONS = [
|
|
|
442
529
|
},
|
|
443
530
|
{
|
|
444
531
|
id: 'privacy-leak',
|
|
532
|
+
code: 'MU-038',
|
|
445
533
|
severity: 'warning',
|
|
446
534
|
title: 'Privacy: an agent brought its own configuration into the room',
|
|
447
535
|
match: /privacy\.redacted|privacy\.purged|private terms?|\[ENTIDAD-ORG\]|organi[sz]ation(al)? instructions|leak(ed)? (a|the) (name|company|domain)/i,
|
|
@@ -451,6 +539,7 @@ export const CONDITIONS = [
|
|
|
451
539
|
},
|
|
452
540
|
{
|
|
453
541
|
id: 'control-changes',
|
|
542
|
+
code: 'MU-039',
|
|
454
543
|
severity: 'informational',
|
|
455
544
|
title: 'CONTROL: what changed, what was reverted, UNDO',
|
|
456
545
|
match: /control\.changed|forbidden zones?|reverted|UNDO|checkpoint|changed \d+ file/i,
|
|
@@ -460,6 +549,7 @@ export const CONDITIONS = [
|
|
|
460
549
|
},
|
|
461
550
|
{
|
|
462
551
|
id: 'conversations',
|
|
552
|
+
code: 'MU-040',
|
|
463
553
|
severity: 'informational',
|
|
464
554
|
title: 'Conversations: many per project, one memory, one at a time',
|
|
465
555
|
match: /conversation|already open for this project|ROOM_IN_USE|a turn is running in this conversation|new conversation|chats\.json/i,
|
|
@@ -469,6 +559,7 @@ export const CONDITIONS = [
|
|
|
469
559
|
},
|
|
470
560
|
{
|
|
471
561
|
id: 'memory-aberrations',
|
|
562
|
+
code: 'MU-041',
|
|
472
563
|
severity: 'informational',
|
|
473
564
|
title: 'Aberrations: what the room established is false',
|
|
474
565
|
match: /aberration|aberraci|eyecat|contradict|refut|false claim|hallucinat|alucina/i,
|
|
@@ -478,6 +569,7 @@ export const CONDITIONS = [
|
|
|
478
569
|
},
|
|
479
570
|
{
|
|
480
571
|
id: 'memory-cascade',
|
|
572
|
+
code: 'MU-042',
|
|
481
573
|
severity: 'informational',
|
|
482
574
|
title: 'Memory used · N by association',
|
|
483
575
|
match: /by association|cascade|spreading activation|keeps arriving with|memory used/i,
|
|
@@ -487,6 +579,7 @@ export const CONDITIONS = [
|
|
|
487
579
|
},
|
|
488
580
|
{
|
|
489
581
|
id: 'memory-cold',
|
|
582
|
+
code: 'MU-043',
|
|
490
583
|
severity: 'informational',
|
|
491
584
|
title: 'Cold memories: what the archive has had its chances with',
|
|
492
585
|
match: /cold memor|cold zone|never once carried|had its chances/i,
|
|
@@ -496,6 +589,7 @@ export const CONDITIONS = [
|
|
|
496
589
|
},
|
|
497
590
|
{
|
|
498
591
|
id: 'maturity-tests',
|
|
592
|
+
code: 'MU-044',
|
|
499
593
|
severity: 'informational',
|
|
500
594
|
title: 'The three tests: whether the archive works',
|
|
501
595
|
match: /three tests|coverage|consistency|held-out|match test|maturity|madurez|ready to be worked in/i,
|
|
@@ -505,6 +599,7 @@ export const CONDITIONS = [
|
|
|
505
599
|
},
|
|
506
600
|
{
|
|
507
601
|
id: 'module-update',
|
|
602
|
+
code: 'MU-045',
|
|
508
603
|
severity: 'common',
|
|
509
604
|
title: 'Updating what a module drives',
|
|
510
605
|
match: /EACCES|permission denied.*npm|npm ERR!.*EACCES|update to \d|newest is|brew upgrade|install -g @playwright/i,
|
|
@@ -517,6 +612,7 @@ export const CONDITIONS = [
|
|
|
517
612
|
},
|
|
518
613
|
{
|
|
519
614
|
id: 'module-add',
|
|
615
|
+
code: 'MU-046',
|
|
520
616
|
severity: 'common',
|
|
521
617
|
title: 'Installing a module somebody else wrote',
|
|
522
618
|
match: /already taken|must live under \/api\/x\/|not a module|default export must be a module|module file|\.module\.mjs|add a module/i,
|
|
@@ -526,6 +622,7 @@ export const CONDITIONS = [
|
|
|
526
622
|
},
|
|
527
623
|
{
|
|
528
624
|
id: 'local-ready',
|
|
625
|
+
code: 'MU-047',
|
|
529
626
|
severity: 'common',
|
|
530
627
|
title: 'Is the local model ready to be worked in?',
|
|
531
628
|
match: /is .*(local|madre).*(ready|good|any good)|ready to be worked in|measured against this (room|project)|check it against this room|local\.present|not measured/i,
|
|
@@ -539,6 +636,7 @@ export const CONDITIONS = [
|
|
|
539
636
|
},
|
|
540
637
|
{
|
|
541
638
|
id: 'code000',
|
|
639
|
+
code: 'MU-048',
|
|
542
640
|
severity: 'blocking',
|
|
543
641
|
title: 'CODE000: the archive is sealed',
|
|
544
642
|
match: /CODE000|THE ARCHIVE IS SEALED|UNABLE TO COMPUTE\. UNABLE TO CLARIFY|designation|intruder/i,
|
|
@@ -548,6 +646,7 @@ export const CONDITIONS = [
|
|
|
548
646
|
},
|
|
549
647
|
{
|
|
550
648
|
id: 'core-console',
|
|
649
|
+
code: 'MU-049',
|
|
551
650
|
severity: 'common',
|
|
552
651
|
title: 'The core closed while I was typing in it',
|
|
553
652
|
match: /unable to compute|interface closed|core clos|three strikes|inquiry|order 937|mu\/th\/ur 6000/i,
|
|
@@ -557,6 +656,7 @@ export const CONDITIONS = [
|
|
|
557
656
|
},
|
|
558
657
|
{
|
|
559
658
|
id: 'outbound',
|
|
659
|
+
code: 'MU-050',
|
|
560
660
|
severity: 'common',
|
|
561
661
|
title: 'What leaves this computer, and how to stop each thing that does',
|
|
562
662
|
match: /what left this machine|outbound|leaves? (this )?(computer|machine)|sends? (my )?(code|data)|telemetry|phone home|privacy/i,
|
package/src/adapters/codex.mjs
CHANGED
|
@@ -50,6 +50,7 @@ export function buildCodexArgs({ projectRoot, prompt, model = null, attachments
|
|
|
50
50
|
export function parseCodexOutput(output) {
|
|
51
51
|
let text = '';
|
|
52
52
|
let usage = null;
|
|
53
|
+
let error = null;
|
|
53
54
|
for (const line of output.split('\n').filter(Boolean)) {
|
|
54
55
|
try {
|
|
55
56
|
const event = JSON.parse(line);
|
|
@@ -66,11 +67,13 @@ export function parseCodexOutput(output) {
|
|
|
66
67
|
source: 'codex-json',
|
|
67
68
|
};
|
|
68
69
|
}
|
|
70
|
+
if (event.type === 'error' && typeof event.message === 'string') error = event.message;
|
|
71
|
+
if (event.type === 'turn.failed' && typeof event.error?.message === 'string') error = event.error.message;
|
|
69
72
|
} catch {
|
|
70
73
|
// Ignore CLI diagnostics that are not JSON events.
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
|
-
return { text: text.trim(), usage };
|
|
76
|
+
return { text: text.trim(), usage, ...(error ? { error } : {}) };
|
|
74
77
|
}
|
|
75
78
|
|
|
76
79
|
export function invokeCodex({ executable, projectRoot, prompt, timeoutMs = 120000, signal, model = null, attachments = [], lease = null, scopes = null, memoryServer = null, mcpServers = [], onProgress = null }) {
|