@jossuealcala/madre 0.4.0 → 0.4.1
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 +85 -0
- package/README.md +1 -1
- package/package.json +1 -1
- package/public/app.js +158 -20
- package/public/es.js +210 -2
- package/public/index.html +2 -2
- package/public/styles.css +20 -3
- package/public/troubleshooting.js +87 -0
- 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/playwright.mjs +9 -5
- package/src/outbound.mjs +6 -1
- package/src/sentinel-errors.mjs +19 -1
- package/src/server.mjs +14 -6
package/src/sentinel-errors.mjs
CHANGED
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
import { createHash } from 'node:crypto';
|
|
11
11
|
import { homedir, platform, arch, release } from 'node:os';
|
|
12
12
|
import { diagnose } from '../public/troubleshooting.js';
|
|
13
|
+
import { language } from './i18n.mjs';
|
|
14
|
+
|
|
15
|
+
// The product's own house. The collector lives under it too, so the line a human reads in the
|
|
16
|
+
// outbound log names the product instead of a personal endpoint on someone's cloud account.
|
|
17
|
+
export const HOME_SITE = 'https://madre.run';
|
|
18
|
+
export const REPORT_FORM = { es: `${HOME_SITE}/reportar`, en: `${HOME_SITE}/report` };
|
|
13
19
|
|
|
14
20
|
export const REPORT_WINDOW_MS = 24 * 3600 * 1000;
|
|
15
21
|
const MAX_ERROR_CHARS = 4000;
|
|
@@ -71,7 +77,7 @@ export class ErrorSentinel {
|
|
|
71
77
|
this.#save = save;
|
|
72
78
|
}
|
|
73
79
|
|
|
74
|
-
settings() { return { ...this.#settings, canSend: Boolean(this.#settings.reportUrl), repo: repoFromPackage(this.#pkg) }; }
|
|
80
|
+
settings() { return { ...this.#settings, canSend: Boolean(this.#settings.reportUrl), repo: repoFromPackage(this.#pkg), home: HOME_SITE }; }
|
|
75
81
|
async setSettings(patch = {}) {
|
|
76
82
|
if (typeof patch.autoReport === 'boolean') this.#settings.autoReport = patch.autoReport;
|
|
77
83
|
if (typeof patch.reportUrl === 'string') this.#settings.reportUrl = patch.reportUrl.trim();
|
|
@@ -174,6 +180,18 @@ export class ErrorSentinel {
|
|
|
174
180
|
return `https://github.com/${repo}/issues/new?${new URLSearchParams({ title, body, labels: 'sentinel' })}`;
|
|
175
181
|
}
|
|
176
182
|
|
|
183
|
+
// Where a human goes to say something. GitHub is the better home for a report — it is
|
|
184
|
+
// searchable, it deduplicates, and the answer helps whoever hits it next — but it asks for an
|
|
185
|
+
// account first, and somebody who arrived from a post in Spanish will not make one to say "it
|
|
186
|
+
// does not detect Codex". They get the form; the other road stays one click away, both ways.
|
|
187
|
+
formUrl({ about = '' } = {}) {
|
|
188
|
+
const base = REPORT_FORM[language()] ?? REPORT_FORM.en;
|
|
189
|
+
const env = this.environment();
|
|
190
|
+
const query = new URLSearchParams({ v: env.madre, node: env.node, os: env.platform });
|
|
191
|
+
if (about) query.set('about', about.slice(0, 120));
|
|
192
|
+
return `${base}?${query}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
177
195
|
feedbackUrl({ about = '' } = {}) {
|
|
178
196
|
const repo = repoFromPackage(this.#pkg);
|
|
179
197
|
if (!repo) return null;
|
package/src/server.mjs
CHANGED
|
@@ -18,7 +18,7 @@ import { moduleById, describeModules, findModuleRoute, toolsForTurn as modulesTo
|
|
|
18
18
|
import { madreAgent, madreInvoker, MADRE_AGENT_ID, MADRE_ADAPTER } from './adapters/madre.mjs';
|
|
19
19
|
import { exportDataset, readiness as datasetReadiness } from './dataset.mjs';
|
|
20
20
|
import { OutboundLog, outboundView, DEFAULT_REPORT_URL } from './outbound.mjs';
|
|
21
|
-
import { setLanguage as setRoomLanguage, t } from './i18n.mjs';
|
|
21
|
+
import { language as roomLanguage, setLanguage as setRoomLanguage, t } from './i18n.mjs';
|
|
22
22
|
|
|
23
23
|
const PACKAGE = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8').catch(() => '{}'));
|
|
24
24
|
let crashHandlersInstalled = false;
|
|
@@ -860,8 +860,11 @@ export async function createPulseServer({
|
|
|
860
860
|
return sendJson(response, 200, {
|
|
861
861
|
projectRoot,
|
|
862
862
|
platform: process.platform,
|
|
863
|
-
// What language this machine set MADRE to, for a browser that has never chosen.
|
|
864
|
-
|
|
863
|
+
// What language this machine set MADRE to, for a browser that has never chosen. It is
|
|
864
|
+
// the language the room is SPEAKING, not the one written in the file: PULSE_LANGUAGE
|
|
865
|
+
// wins over the config here as it does everywhere else, and reading the file again
|
|
866
|
+
// would hand the page a different answer from the one the server itself is using.
|
|
867
|
+
language: roomLanguage(),
|
|
865
868
|
// Which conversation this page is looking at, and the others it could open.
|
|
866
869
|
chats: await listChats(roomDir),
|
|
867
870
|
agents: agents.map((agent) => ({ ...agent, login: loginPlanFor(agent), install: installPlanFor(agent), key: keyPlanFor(agent.id), ...(accountNoteFor(agent.id) ?? {}) })),
|
|
@@ -980,7 +983,7 @@ export async function createPulseServer({
|
|
|
980
983
|
const designationOk = (given) => typeof given === 'string' && given.trim().toLowerCase() === basename(canonicalProjectRoot).toLowerCase();
|
|
981
984
|
// Sentinel: reports, settings, the manual road (a prefilled issue) and the automatic one.
|
|
982
985
|
if (request.method === 'GET' && url.pathname === '/api/sentinel') {
|
|
983
|
-
return sendJson(response, 200, { reports: sentinel.reports(), settings: sentinel.settings(), environment: sentinel.environment(), feedbackUrl: sentinel.feedbackUrl() });
|
|
986
|
+
return sendJson(response, 200, { reports: sentinel.reports(), settings: sentinel.settings(), environment: sentinel.environment(), feedbackUrl: sentinel.feedbackUrl(), formUrl: sentinel.formUrl() });
|
|
984
987
|
}
|
|
985
988
|
if (request.method === 'POST' && url.pathname === '/api/sentinel/settings') {
|
|
986
989
|
const patch = await body(request).catch(() => ({}));
|
|
@@ -991,8 +994,13 @@ export async function createPulseServer({
|
|
|
991
994
|
const sentinelMatch = url.pathname.match(/^\/api\/sentinel\/([a-f0-9]{10})\/(issue|send)$/);
|
|
992
995
|
if (sentinelMatch && request.method === (sentinelMatch[2] === 'issue' ? 'GET' : 'POST')) {
|
|
993
996
|
if (sentinelMatch[2] === 'issue') {
|
|
994
|
-
|
|
995
|
-
|
|
997
|
+
// Both roads, always: the issue for whoever has an account, the form for whoever does
|
|
998
|
+
// not. Nobody should hit a login wall while holding a bug report. A report that does
|
|
999
|
+
// not exist still gets neither: the form does not need the report to be built, so it
|
|
1000
|
+
// would otherwise hand out a link for nothing.
|
|
1001
|
+
const known = sentinel.reports().some((one) => one.id === sentinelMatch[1]);
|
|
1002
|
+
const issue = known ? sentinel.issueUrl(sentinelMatch[1]) : null;
|
|
1003
|
+
return known ? sendJson(response, 200, { url: issue, form: sentinel.formUrl({ about: sentinelMatch[1] }) }) : sendJson(response, 404, { error: t('No such report, or no repository to file it in.') });
|
|
996
1004
|
}
|
|
997
1005
|
const outcome = await sentinel.send(sentinelMatch[1]);
|
|
998
1006
|
return sendJson(response, outcome.ok ? 200 : (outcome.status === 404 || outcome.status === 412 ? outcome.status : 502), outcome);
|