@jossuealcala/madre 1.0.1 → 1.0.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.
Files changed (172) hide show
  1. package/ADDITIONAL-TERMS.md +93 -0
  2. package/CHANGELOG.en.md +1945 -0
  3. package/CHANGELOG.md +34 -1
  4. package/CONTRIBUTING.md +1 -1
  5. package/LICENSE +661 -202
  6. package/NOTICE +26 -14
  7. package/README.en.md +245 -0
  8. package/README.md +19 -10
  9. package/TRADEMARKS.en.md +50 -0
  10. package/TRADEMARKS.md +33 -16
  11. package/bin/madre.mjs +2 -0
  12. package/docs/INTERNALS.md +6 -0
  13. package/docs/REFERENCE.md +11 -2
  14. package/docs/en/INTERNALS.md +103 -0
  15. package/docs/en/REFERENCE.md +339 -0
  16. package/docs/en/SDK.md +306 -0
  17. package/docs/en/training/README.md +59 -0
  18. package/docs/training/README.md +20 -8
  19. package/docs/training/train.sh +2 -1
  20. package/package.json +11 -3
  21. package/public/abduction.js +2 -0
  22. package/public/app.js +1549 -409
  23. package/public/attribution.js +71 -0
  24. package/public/brands.js +3 -1
  25. package/public/editor.js +2 -0
  26. package/public/es.js +620 -400
  27. package/public/i18n.js +25 -1
  28. package/public/index.html +13 -10
  29. package/public/inquiry.js +2 -0
  30. package/public/loader.js +2 -0
  31. package/public/macroverso.js +2 -0
  32. package/public/nostromo-engine.js +2 -0
  33. package/public/resay.js +6 -0
  34. package/public/review-ask.js +2 -0
  35. package/public/routing.js +23 -1
  36. package/public/salas-mac.css +21 -4
  37. package/public/salas-mac.html +5 -1
  38. package/public/salas-mac.js +53 -28
  39. package/public/salas.css +2 -0
  40. package/public/salas.html +1 -0
  41. package/public/salas.js +4 -2
  42. package/public/styles.css +957 -18
  43. package/public/syntax.js +2 -0
  44. package/public/touch.js +2 -0
  45. package/public/tour-chapters.js +2 -0
  46. package/public/tour.js +37 -0
  47. package/public/troubleshooting.js +18 -16
  48. package/src/adapters/claude.mjs +2 -0
  49. package/src/adapters/codex.mjs +2 -0
  50. package/src/adapters/gemini.mjs +3 -1
  51. package/src/adapters/madre.mjs +3 -1
  52. package/src/adapters/opencode.mjs +7 -3
  53. package/src/adapters/process.mjs +2 -0
  54. package/src/asking.mjs +2 -0
  55. package/src/auth-probe.mjs +3 -1
  56. package/src/bridge/devices.mjs +2 -0
  57. package/src/bridge/handoff.mjs +2 -0
  58. package/src/bridge/host.mjs +2 -0
  59. package/src/bridge/network.mjs +2 -0
  60. package/src/bridge/policy.mjs +3 -1
  61. package/src/bridge/qr.mjs +2 -0
  62. package/src/bridge/server.mjs +2 -0
  63. package/src/capabilities.mjs +2 -0
  64. package/src/chats.mjs +2 -0
  65. package/src/checkpoint.mjs +2 -0
  66. package/src/choices.mjs +2 -0
  67. package/src/cli-args.mjs +2 -0
  68. package/src/cold.mjs +2 -0
  69. package/src/commands.mjs +2 -0
  70. package/src/config.mjs +2 -0
  71. package/src/conversation-context.mjs +15 -1
  72. package/src/credentials.mjs +32 -3
  73. package/src/dataset.mjs +2 -0
  74. package/src/directives.mjs +2 -0
  75. package/src/distiller.mjs +2 -0
  76. package/src/embeddings.mjs +2 -0
  77. package/src/enlaces.mjs +6 -4
  78. package/src/event-store.mjs +2 -0
  79. package/src/exam.mjs +6 -4
  80. package/src/extensions.mjs +2 -0
  81. package/src/eyecat-watch.mjs +2 -0
  82. package/src/eyecat.mjs +2 -0
  83. package/src/file-ops.mjs +2 -0
  84. package/src/files.mjs +32 -0
  85. package/src/github.mjs +2 -0
  86. package/src/heartbeat.mjs +2 -0
  87. package/src/i18n.mjs +15 -1
  88. package/src/image-studio.mjs +2 -0
  89. package/src/json-file.mjs +2 -0
  90. package/src/launch.mjs +2 -0
  91. package/src/lease.mjs +5 -1
  92. package/src/lobby-work.mjs +2 -0
  93. package/src/local-ai.mjs +2 -0
  94. package/src/looks.mjs +2 -0
  95. package/src/maturity.mjs +3 -1
  96. package/src/mcp/connect.mjs +345 -0
  97. package/src/mcp/github-server.mjs +2 -0
  98. package/src/mcp/image-server.mjs +2 -0
  99. package/src/mcp/memory-server.mjs +2 -0
  100. package/src/mcp/proxy.mjs +153 -0
  101. package/src/mcp/smtp-server.mjs +2 -0
  102. package/src/memory-dashboard.mjs +2 -0
  103. package/src/memory-tools.mjs +2 -0
  104. package/src/memory.mjs +2 -0
  105. package/src/models.mjs +45 -3
  106. package/src/modules/ahp.mjs +2 -0
  107. package/src/modules/ash-policy.mjs +2 -0
  108. package/src/modules/ash.mjs +3 -1
  109. package/src/modules/bishop-plan.mjs +5 -3
  110. package/src/modules/bishop.mjs +13 -11
  111. package/src/modules/choices.mjs +2 -0
  112. package/src/modules/correo.mjs +2 -0
  113. package/src/modules/enlaces.mjs +2 -0
  114. package/src/modules/git-pulse.mjs +2 -0
  115. package/src/modules/github.mjs +2 -0
  116. package/src/modules/helpers.mjs +2 -0
  117. package/src/modules/image-studio.mjs +3 -1
  118. package/src/modules/index.mjs +4 -1
  119. package/src/modules/intents-calibration.mjs +2 -0
  120. package/src/modules/intents-desk.mjs +4 -2
  121. package/src/modules/intents-observe.mjs +2 -0
  122. package/src/modules/intents-policy.mjs +4 -1
  123. package/src/modules/intents-try.mjs +2 -0
  124. package/src/modules/intents.mjs +8 -6
  125. package/src/modules/mcp-connect.mjs +293 -0
  126. package/src/modules/ollama.mjs +3 -1
  127. package/src/modules/playwright.mjs +25 -10
  128. package/src/modules/ripley.mjs +2 -0
  129. package/src/modules/runs.mjs +5 -3
  130. package/src/modules/sdk.mjs +2 -0
  131. package/src/modules/updates.mjs +2 -0
  132. package/src/mother.mjs +2 -0
  133. package/src/ollama.mjs +2 -0
  134. package/src/outbound.mjs +7 -5
  135. package/src/override.mjs +2 -0
  136. package/src/ports.mjs +2 -0
  137. package/src/prices.mjs +2 -0
  138. package/src/privacy.mjs +2 -0
  139. package/src/quota-monitor.mjs +2 -0
  140. package/src/quota-sources.mjs +2 -0
  141. package/src/request-guard.mjs +2 -0
  142. package/src/review-ask.mjs +2 -0
  143. package/src/room/archivist.mjs +2 -0
  144. package/src/room/attachments.mjs +2 -0
  145. package/src/room/budget.mjs +2 -0
  146. package/src/room/context.mjs +2 -0
  147. package/src/room/control.mjs +7 -1
  148. package/src/room/copilot.mjs +76 -0
  149. package/src/room/economy.mjs +2 -0
  150. package/src/room/escalation.mjs +2 -0
  151. package/src/room/ghost.mjs +2 -0
  152. package/src/room/guard.mjs +2 -0
  153. package/src/room/prompt.mjs +13 -4
  154. package/src/room/returns.mjs +82 -0
  155. package/src/room/runs.mjs +4 -0
  156. package/src/room/vectors.mjs +2 -0
  157. package/src/room.mjs +209 -31
  158. package/src/router.mjs +2 -0
  159. package/src/runs.mjs +2 -0
  160. package/src/runtime-detection.mjs +2 -0
  161. package/src/salas.mjs +2 -0
  162. package/src/sentinel-errors.mjs +2 -0
  163. package/src/server-sentinel.mjs +2 -0
  164. package/src/server.mjs +141 -34
  165. package/src/setup.mjs +2 -0
  166. package/src/smtp.mjs +2 -0
  167. package/src/tour.mjs +2 -0
  168. package/src/updates.mjs +2 -0
  169. package/src/usage-sentinel.mjs +2 -0
  170. package/src/vault.mjs +2 -0
  171. package/src/verdict.mjs +3 -1
  172. package/src/whats-new.mjs +2 -0
package/public/syntax.js CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The names below are the names of formats — JavaScript, JSON, CSS, Markdown — and a format is
2
4
  // called the same in every language, so they never pass through the catalogue.
3
5
  //
package/public/touch.js CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // A tap is not a hover (2026-10-11). On a screen with no pointer (a phone, a tablet without a
2
4
  // trackpad), the browser gives :hover to whatever was tapped and keeps it until the next tap lands
3
5
  // somewhere else: a button looked pressed after it was let go. So, where nothing can hover, the
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // What the welcome of MADRE 1.0 says (2026-10-11): twelve chapters and the sheet of gestures, in
2
4
  // both languages side by side, as the cards of What's new are (src/whats-new.mjs). Each chapter
3
5
  // fits on one view: a short scene, a line, three sentences, its gestures and «Take me there».
package/public/tour.js CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The welcome of MADRE 1.0 (2026-10-11), in Home and in every project: option B of «Recorrido de
2
4
  // MADRE 1.0» — a bonjour, then a window of chapters read at the person's own pace — with two
3
5
  // pieces of option C: a one-line hint the first time someone reaches a place that confuses, and
@@ -75,6 +77,36 @@ const SCENES = {
75
77
  gestures: () => `<div class="ts-caps"><span class="ts-cap c0">⌘ ,</span><span class="ts-cap c1">Esc</span><span class="ts-cap c2">?</span></div>`,
76
78
  };
77
79
 
80
+ // What a first-time card speaks of, lit, and the rest of the page a shade quieter: the card needs
81
+ // no beak to say where it points (2026-10-15). A ring laid over the place, never the place itself
82
+ // lifted (a key inside the bar cannot leave the bar's layer). `place()` follows it when it moves;
83
+ // `remove()` takes it away with the card.
84
+ export function spotlight(node) {
85
+ const none = { place() {}, remove() {} };
86
+ if (!node?.getBoundingClientRect || typeof document === 'undefined') return none;
87
+ const spot = document.createElement('div');
88
+ spot.className = 'hint-spot';
89
+ spot.setAttribute('aria-hidden', 'true');
90
+ document.body.append(spot);
91
+ const place = () => {
92
+ if (!node.isConnected) { remove(); return; }
93
+ const box = node.getBoundingClientRect();
94
+ spot.hidden = !box.width && !box.height;
95
+ if (spot.hidden) return;
96
+ const radius = Number.parseFloat(getComputedStyle(node).borderTopLeftRadius) || 10;
97
+ Object.assign(spot.style, { left: `${box.left - 4}px`, top: `${box.top - 4}px`, width: `${box.width + 8}px`, height: `${box.height + 8}px`, borderRadius: `${Math.min(radius + 4, (box.height + 8) / 2)}px` });
98
+ };
99
+ function remove() {
100
+ window.removeEventListener('resize', place);
101
+ window.removeEventListener('scroll', place, true);
102
+ spot.remove();
103
+ }
104
+ window.addEventListener('resize', place);
105
+ window.addEventListener('scroll', place, true);
106
+ place();
107
+ return { place, remove };
108
+ }
109
+
78
110
  // `onChange` hears the kept state each time it is known or changes (null if it could not be read).
79
111
  // `phone`: none of it here (a phone, or the right half of a split window); `device`: this page is a
80
112
  // phone, which gets one card the first time instead of the bonjour.
@@ -180,6 +212,8 @@ export function startTour({ page, go = {}, ready = () => true, phone = false, de
180
212
  const done = list.filter((chapter) => state.done.includes(chapter.id)).length;
181
213
  const pill = el('span', 'tour-pill');
182
214
  const open = key('tour-pill-go', fill(t('Continue the tour · {n} of {total}'), { n: done, total: list.length }), () => openChapters(state.at));
215
+ // On a narrow bar it says only where it goes and how far (styles.css, .tour-pill-go[data-short]).
216
+ open.dataset.short = fill(t('Tour · {n}/{total}'), { n: done, total: list.length });
183
217
  const away = key('tour-pill-x', null, () => { void keep({ finished: true }); });
184
218
  away.innerHTML = CLOSE_ICON; // trusted: our own SVG string
185
219
  away.setAttribute('aria-label', t('Hide the tour'));
@@ -482,9 +516,11 @@ export function startTour({ page, go = {}, ready = () => true, phone = false, de
482
516
  const card = el('section', `tour-hint${inside ? ' inline' : ''}`);
483
517
  card.setAttribute('role', 'note');
484
518
  card.append(el('span', 'tour-hint-eyebrow', t('The first time')), el('b', null, title), el('p', null, text));
519
+ let spot = null;
485
520
  const done = () => {
486
521
  if (!hintNode) return;
487
522
  void keep({ hint: id });
523
+ spot?.remove();
488
524
  card.remove();
489
525
  hintNode = null;
490
526
  document.removeEventListener('keydown', onKey, true);
@@ -509,6 +545,7 @@ export function startTour({ page, go = {}, ready = () => true, phone = false, de
509
545
  const left = Math.max(12, Math.min(window.innerWidth - width - 12, onTheRight ? box.right - width : box.left));
510
546
  const below = box.bottom + 10 + height < window.innerHeight;
511
547
  Object.assign(card.style, { left: `${left}px`, top: `${below ? box.bottom + 10 : Math.max(12, box.top - height - 10)}px` });
548
+ spot = spotlight(anchor);
512
549
  document.addEventListener('keydown', onKey, true);
513
550
  return true;
514
551
  }
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { t } from './i18n.js';
2
4
 
3
5
  // MU/TH/UR knowledge base: known conditions, how to recognise them in the
@@ -244,7 +246,7 @@ export const CONDITIONS = [
244
246
  severity: 'informational',
245
247
  title: 'Where the usage numbers come from',
246
248
  match: /ring|quota|limit window|resets|rollout|oauth usage|window reset|limits-real/i,
247
- diagnosis: 'Each sphere\'s usage shows the provider\'s real limit when the CLI publishes it: Codex writes its 5-hour and weekly windows (used %, reset time) into every session rollout under ~/.codex/sessions; Claude Code\'s /usage comes from Anthropic\'s OAuth usage endpoint; MADRE can ask it with the token Claude Code keeps in the keychain when you start with PULSE_CLAUDE_USAGE=1 (macOS may ask once to allow the keychain read). Gemini and OpenCode publish nothing locally, so their usage shows MADRE\'s own rolling 5-hour window of budget tokens. A window whose reset time has passed counts as empty until the CLI reports again, and the sentinel says "clear" when a full window resets.',
249
+ diagnosis: 'Each sphere\'s usage shows the provider\'s real limit when the CLI publishes it: Codex writes its 5-hour and weekly windows (used %, reset time) into every session rollout under ~/.codex/sessions; Claude Code\'s /usage comes from Anthropic\'s OAuth usage endpoint; MADRE can ask it with the token Claude Code keeps in the keychain when you start with PULSE_CLAUDE_USAGE=1 (macOS may ask once to allow the keychain read). Gemini and OpenCode publish nothing locally, so their usage shows MADRE\'s own rolling 5-hour window of budget tokens. A window whose reset time has passed counts as empty until the CLI reports again, and the failure monitor says "clear" when a full window resets.',
248
250
  remedy: 'Click a sphere to see both windows and when they reset. If a sphere\'s usage looks stale, run one turn with that agent: Codex only rewrites its limits when it runs. Set PULSE_OFFICIAL_QUOTA=0 to stop reading provider limits altogether.',
249
251
  fixes: same(['# click the sphere → 5h / 7d windows and reset times', 'PULSE_CLAUDE_USAGE=1 madre start # also read Claude Code\'s usage windows', 'PULSE_OFFICIAL_QUOTA=0 madre start # local window only']),
250
252
  },
@@ -313,7 +315,7 @@ export const CONDITIONS = [
313
315
  severity: 'informational',
314
316
  title: 'Gemini: generating images',
315
317
  match: /scope-gemini-imageGen/,
316
- diagnosis: 'Gemini CLI 0.60 has no image-generation tool in headless mode, even though Google offers image models. MADRE fills the gap with the Images module: its own MCP server exposing generate_image on the Gemini API image models, attached to Gemini only inside a creation lease with the image scope on, billing your Gemini key.',
318
+ diagnosis: 'Gemini CLI 0.60 has no image-generation tool in headless mode, even though Google offers image models. MADRE fills the gap with the Images module: its own MCP server exposing generate_image on the Gemini API image models, attached to Gemini only inside a permission to create with images on, billing your Gemini key.',
317
319
  remedy: 'Turn Images on in Modules (it needs a Gemini API key with credits), then switch on Generate images for Gemini in Settings → Agents. Without the module, ask @codex or let the orchestrator route the image step to it.',
318
320
  fixes: same(['# Modules → Images → Active', '# Settings → Agents → Gemini → Generate images', '# then: CREATE + "generate … as name.png"']),
319
321
  },
@@ -324,7 +326,7 @@ export const CONDITIONS = [
324
326
  severity: 'informational',
325
327
  title: 'Claude Code: generating images',
326
328
  match: /scope-claude-imageGen/,
327
- diagnosis: 'Claude Code has no image-generation tool; Claude models describe and reason about images, they do not render them. The Images module gives Claude the MCP tool generate_image (MADRE\'s own server on the Gemini API), attached with --mcp-config only inside a creation lease.',
329
+ diagnosis: 'Claude Code has no image-generation tool; Claude models describe and reason about images, they do not render them. The Images module gives Claude the MCP tool generate_image (MADRE\'s own server on the Gemini API), attached with --mcp-config only inside a permission to create.',
328
330
  remedy: 'Turn Images on in Modules, switch on Generate images for Claude in Settings → Agents, then use CREATE. Or route the image step to @codex.',
329
331
  fixes: same(['# Modules → Images → Active', '# Settings → Agents → Claude → Generate images']),
330
332
  },
@@ -335,7 +337,7 @@ export const CONDITIONS = [
335
337
  severity: 'informational',
336
338
  title: 'OpenCode: generating images',
337
339
  match: /scope-opencode-imageGen/,
338
- diagnosis: 'OpenCode exposes no image-generation tool of its own. The Images module adds MADRE\'s MCP image server to OpenCode\'s ephemeral config inside a creation lease.',
340
+ diagnosis: 'OpenCode exposes no image-generation tool of its own. The Images module adds MADRE\'s MCP image server to OpenCode\'s ephemeral config inside a permission to create.',
339
341
  remedy: 'Turn Images on in Modules, switch on Generate images for OpenCode in Settings → Agents, then use CREATE. Or route the image step to @codex.',
340
342
  fixes: same(['# Modules → Images → Active', '# Settings → Agents → OpenCode → Generate images']),
341
343
  },
@@ -353,11 +355,11 @@ export const CONDITIONS = [
353
355
  id: 'slash-commands',
354
356
  code: 'MU-022',
355
357
  severity: 'informational',
356
- title: 'Commands and mentions in the field box',
358
+ title: 'Commands and mentions in the Write box',
357
359
  match: /slash|command|\/git|\/ahp|mention|@agent/i,
358
- diagnosis: 'Type "/" for the project\'s commands: /create arms the lease, /image routes an image request to an agent that can draw, /stopall is the brake, /git (Git) and /ahp (Continuity) run read-only in the project and post a fact card everyone, agents included, can read. Type "@" to mention an agent; the name becomes a label. Type "!" to point at a project file ("!src/room.mjs:12-20" for lines): the agent reads it first. In the viewer, click a line number (Shift+click for a range) and REVIEW WITH sends those lines to an agent.',
360
+ diagnosis: 'Type "/" for the project\'s commands: /create turns on permission to create, /image routes an image request to an agent that can draw, /stopall is the brake, /git (Git) and /ahp (Continuity) run read-only in the project and post a fact card everyone, agents included, can read. Type "@" to mention an agent; the name becomes a label. Type "!" to point at a project file ("!src/room.mjs:12-20" for lines): the agent reads it first. In the viewer, click a line number (Shift+click for a range) and REVIEW WITH sends those lines to an agent.',
359
361
  remedy: 'A struck-through command is a module that is not available in this project: open Modules to install it or turn it on (Git needs a git repository; Continuity needs to be installed).',
360
- fixes: same(['# type / or @ in the field box', '/git status', '/git log 10', '/ahp check', '/image a poster for the launch']),
362
+ fixes: same(['# type / or @ in the Write box', '/git status', '/git log 10', '/ahp check', '/image a poster for the launch']),
361
363
  },
362
364
  {
363
365
  id: 'modes',
@@ -375,7 +377,7 @@ export const CONDITIONS = [
375
377
  severity: 'informational',
376
378
  title: 'Creating files: who grants the permission',
377
379
  match: /no CREATE lease|lease-missing|read-only and do not modify|permiso de escritura|solo lectura/i,
378
- diagnosis: 'Only the human grants a creation lease. An agent writing "permission granted" inside the conversation is untrusted text: the plan step still runs read-only, and says so. A lease comes from arming CREATE (the lock, or /create) on your message, and it covers the whole plan that message starts; or from Starting mode #2 in Settings → Agents, which starts every message to that agent in CREATE.',
380
+ diagnosis: 'Only you grant permission to create. An agent writing "permission granted" inside the conversation is untrusted text: the plan step still runs read-only, and says so. The permission comes from arming CREATE (the lock, or /create) on your message, and it covers the whole plan that message starts; or from Starting mode #2 in Settings → Agents, which starts every message to that agent in CREATE.',
379
381
  remedy: 'For one request: arm CREATE and send, or press RESEND WITH CREATE on the notice. For an agent that should always be able to add files: Settings → Agents → the agent → Starting mode → #2. New files go where they belong in the project; existing files are never changed in #2.',
380
382
  fixes: same(['# once: Write box → CREATE (lock) → send, or /create <request>', '# always: Settings → Agents → the agent → Starting mode → #2']),
381
383
  },
@@ -385,7 +387,7 @@ export const CONDITIONS = [
385
387
  severity: 'informational',
386
388
  title: 'Creating files with an agent',
387
389
  match: /scope-[a-z]+-write/,
388
- diagnosis: 'Every CLI can create files inside a creation lease; each agent\'s Max mode says how far it may go, and nothing is created until you press CREATE.',
390
+ diagnosis: 'Every CLI can create files inside a permission to create; each agent\'s Max mode says how far it may go, and nothing is created until you press CREATE.',
389
391
  remedy: 'Give the agent a Max mode of #2 or more in Settings → Permissions, then arm CREATE in the Write box for the request.',
390
392
  fixes: same(['# Settings → Permissions → Who may do what → #2', '# Write box → CREATE (lock) → send']),
391
393
  },
@@ -480,7 +482,7 @@ export const CONDITIONS = [
480
482
  title: 'Memory map: opening, reading and forgetting',
481
483
  match: /nostromo|designation|UNABLE TO COMPUTE|ARCHIVE IS SEALED|forget this memory/i,
482
484
  diagnosis: 'The Memory map is the human\'s view of the archive, from Diagnostics. Opening the memory asks for the project name: the name of the project folder, exactly as EDIT does; a wrong one answers UNABLE TO COMPUTE. Access stays open until the page reloads. Inside, every distilled memory is a planet around the project\'s core; drag to move, wheel to zoom, click a planet for its card. The only edit is FORGET (two presses): the note leaves every future turn, the ledger stays. If the archive answers SEALED with a number of minutes, wait them out: repeated strikes at the core close it for a while and the Write box reads INTRUDER until it opens again.',
483
- remedy: 'Type the folder name shown in the project header as the project name. To find a memory quickly, ask an agent to search instead. Do not strike the core.',
485
+ remedy: 'Type the folder name shown in the project header as the project name. To find a memory quickly, ask an agent to search instead. Avoid guessing names: several wrong ones in a row lock the Memory map for a while.',
484
486
  fixes: same(['# Diagnostics → Memory map → project name = folder name', 'basename "$PWD"']),
485
487
  },
486
488
  {
@@ -500,7 +502,7 @@ export const CONDITIONS = [
500
502
  severity: 'informational',
501
503
  title: '@madre: the project\'s own memory, as an agent',
502
504
  // Older rooms recorded @madre leaving as "madre is not installed": it is this condition, not MU-009.
503
- match: /@madre|madre-local|needs Ollama running with a chat model|\bmadre is not installed on this computer|@madre left the room/i,
505
+ match: /@madre|madre-local|needs Ollama running with a chat model|\bmadre is not installed on this computer|@madre left the (room|project)/i,
504
506
  diagnosis: 'When Ollama runs with a chat model, a fifth agent joins the project: @madre. It answers from the whole archive (distilled notes and exact quotes, cited as [#n]) and from what the message carries, locally, for free. It never writes, draws, browses or makes plans; when the project never discussed something it says so. Other agents may give it a plan step to check what was decided. It leaves the project when Ollama stops, and Diagnostics says so.',
505
507
  remedy: 'Start Ollama and download a chat model from Local AI in Modules; its @madre in the project switch turns the agent off if you do not want it. Ask it "what did we decide about …" or "did we ever discuss …".',
506
508
  fixes: same(['ollama serve', 'ollama pull qwen2.5:3b', '@madre what did we decide about the webhook?']),
@@ -513,7 +515,7 @@ export const CONDITIONS = [
513
515
  title: 'Ollama: local smart search and a local archivist',
514
516
  match: /ollama|11434|local model|nomic-embed|qwen|llama3/i,
515
517
  diagnosis: 'When Ollama runs on this machine with an embedding model and a chat model, MADRE embeds the memory locally (recall by meaning without any key) and distils memories with the local model first, before Gemini and the others. Nothing leaves the machine for remembering. Off or absent, everything falls back to the providers. The Local AI module, in Modules, shows what Ollama has, downloads the recommended models, and switches each role.',
516
- remedy: 'Install Ollama (ollama.com), start it, then open Local AI in Modules, press Check again and download the models it suggests. Set PULSE_OLLAMA_MODEL or PULSE_OLLAMA_EMBED_MODEL to prefer others; PULSE_EMBED_PROVIDER=gemini keeps embeddings on Gemini even with Ollama running.',
518
+ remedy: 'Install Ollama (ollama.com), start it, then open Local AI in Modules, press RECHECK and download the models it suggests. Set PULSE_OLLAMA_MODEL or PULSE_OLLAMA_EMBED_MODEL to prefer others; PULSE_EMBED_PROVIDER=gemini keeps embeddings on Gemini even with Ollama running.',
517
519
  fixes: {
518
520
  darwin: ['brew install ollama', 'ollama serve', 'ollama pull nomic-embed-text', 'ollama pull qwen2.5:3b'],
519
521
  linux: ['curl -fsSL https://ollama.com/install.sh | sh', 'ollama serve', 'ollama pull nomic-embed-text', 'ollama pull qwen2.5:3b']
@@ -566,11 +568,11 @@ export const CONDITIONS = [
566
568
  id: 'memory-aberrations',
567
569
  code: 'MU-041',
568
570
  severity: 'informational',
569
- title: 'Aberrations: what the project established is false',
571
+ title: 'Doubtful memories: what the project established is false',
570
572
  match: /aberration|aberraci|eyecat|contradict|refut|false claim|hallucinat|alucina/i,
571
- diagnosis: 'An aberration is a claim the project established is false, kept on purpose rather than deleted: it is what the local model learns not to repeat. It never travels into a turn, and the note it refutes is taken out of circulation with it — a refuted memory is never recalled again, by words or by meaning. EYECAT watches for two agents saying opposite things about the same subject and raises the pair for a third agent to judge, one that took no part in either side. Confirming files the aberration and quarantines what it refutes; dismissing says the project was right and the pair is never raised again. Forgetting an aberration gives its note back.',
573
+ diagnosis: 'A doubtful memory is a claim the project established is false, kept on purpose rather than deleted: it is what the local model learns not to repeat. It never travels into a turn, and the note it refutes is taken out of circulation with it — a refuted memory is never recalled again, by words or by meaning. The memory reviewer watches for two agents saying opposite things about the same subject and raises the pair for a third agent to judge, one that took no part in either side. Confirming files the doubtful memory and quarantines what it refutes; dismissing says the project was right and the pair is never raised again. Forgetting a doubtful memory puts the note it refuted back in use.',
572
574
  remedy: 'The Memory map shows them as collapsed bodies: open one to read what it took down, or clear it if the project was wrong about being wrong. The second test of the Memory review, in Settings → Memory, reports open contradictions.',
573
- fixes: same(['# Diagnostics → Memory map → the dark bodies are aberrations', '# Settings → Memory → Analysis and tests → the second test']),
575
+ fixes: same(['# Diagnostics → Memory map → the dark bodies are doubtful memories', '# Settings → Memory → Analysis and tests → the second test']),
574
576
  },
575
577
  {
576
578
  id: 'memory-cascade',
@@ -598,7 +600,7 @@ export const CONDITIONS = [
598
600
  severity: 'informational',
599
601
  title: 'Memory review: whether the archive works',
600
602
  match: /three tests|coverage|consistency|held-out|match test|maturity|madurez|ready to be worked in/i,
601
- diagnosis: 'The six readings count what the archive is made of; they cannot say whether it works, because nothing about a pile of notes says whether the right one comes back when it is needed. COVERAGE takes real questions from this project, runs recall at the exact point each was asked, and scores what came back against the reply that was actually given. CONSISTENCY reads the contradictions EYECAT still holds and whether aberrations are being filed more often lately. MATCH puts real questions a frontier CLI answered back to the local model with this archive behind it. All three measure against a control, because everything in one project is about the same handful of subjects and any two pieces of it read as close to an embedder: to count, what the archive handed over has to beat what it would have handed over for a different question.',
603
+ diagnosis: 'The six readings count what the archive is made of; they cannot say whether it works, because nothing about a pile of notes says whether the right one comes back when it is needed. COVERAGE takes real questions from this project, runs recall at the exact point each was asked, and scores what came back against the reply that was actually given. CONSISTENCY reads the contradictions the memory reviewer still holds and whether doubtful memories are being filed more often lately. MATCH asks the local model, with this archive behind it, real questions that one of your agents (Codex, Claude, Gemini or OpenCode) already answered. All three measure against a control, because everything in one project is about the same handful of subjects and any two pieces of it look alike to search by meaning: to count, what the archive handed over has to beat what it would have handed over for a different question.',
602
604
  remedy: 'Read the one sentence and the one instruction at the top of Settings → Memory; the readings and the tests are in Analysis and tests, below it. The free test is read as the panel opens, the cheap one keeps itself fresh once a day while the embeddings are local, and the slow one is yours to start and to stop. None of them spends a provider turn.',
603
605
  fixes: same(['# Settings → Memory → Analysis and tests → Memory review', '# MATCH needs Ollama running with a chat model, and smart search on']),
604
606
  },
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { runReadonlyProcess } from './process.mjs';
2
4
 
3
5
  // dontAsk denies any tool use that is not pre-approved, so under a lease the
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { readdir } from 'node:fs/promises';
2
4
  import { homedir } from 'node:os';
3
5
  import { join } from 'node:path';
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { priceTokens, PRICES_CHECKED } from '../prices.mjs';
2
4
  import { copyFile, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
5
  import { t } from '../i18n.mjs';
@@ -288,7 +290,7 @@ export function diagnoseGeminiStderr(stderr) {
288
290
  if (/Unauthorized tool call:\s*'?invoke_agent'?|Blocked call:[^\n]*invoke_agent/i.test(text)) {
289
291
  return {
290
292
  code: 'SUBAGENT_BLOCKED',
291
- message: t('Gemini tried to hand this turn to one of its own subagents. MADRE does not allow that: a subagent answers outside the lease, so nothing it wrote would be checked, shown or undoable.'),
293
+ message: t('Gemini tried to hand this task to one of its own subagents. MADRE does not allow that: a subagent answers outside the permission MADRE controls, so nothing it wrote would be checked, shown or undoable.'),
292
294
  hint: t('Ask again saying it should do the work itself, without subagents.'),
293
295
  };
294
296
  }
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // @madre: the room's own intelligence. A local model (Ollama) that answers
2
4
  // from what the room remembers: the whole archive, not only the recent window,
3
5
  // plus the files the human points at. It never writes, never delegates, never
@@ -264,7 +266,7 @@ export async function gather(memory, text, { maxChars = 7000 } = {}) {
264
266
  export function madreInvoker({ memory, ollama, fetchImpl = globalThis.fetch }) {
265
267
  return async ({ prompt, text = '', timeoutMs = 180000, model = null, requester = 'you', crew = [], delegation = false, maxSteps = 4, round = null, bishop = null }) => {
266
268
  const state = typeof ollama === 'function' ? ollama() : ollama;
267
- if (!state?.running || !state.chatModel) throw new Error('@madre left the room: Ollama is not running. Start Ollama, or open Ollama in Modules.');
269
+ if (!state?.running || !state.chatModel) throw new Error('@madre left the project: Ollama is not running. Start Ollama, or open Local AI in Modules.');
268
270
  const question = text || prompt.match(/User message: ([\s\S]*)$/)?.[1] || prompt.slice(-2000);
269
271
  const local = localReply(question, { requester, crew, delegation, maxSteps, model: model ?? state.chatModel });
270
272
  if (local) return { text: local.text, usage: { ...NO_USAGE }, grounded: { notes: 0, quotes: 0 }, synthetic: local.kind, declined: local.kind === 'declined' ? 'action' : undefined };
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { runReadonlyProcess } from './process.mjs';
2
4
 
3
5
  const readonlyConfig = {
@@ -21,13 +23,15 @@ const readonlyConfig = {
21
23
  // Optional provider/model override (e.g. "openai/gpt-5.6-sol"). Without it,
22
24
  // OpenCode picks its own default provider, which may not be the one the user
23
25
  // actually signed in to.
24
- export function buildOpenCodeArgs({ projectRoot, prompt, model = process.env.PULSE_OPENCODE_MODEL, attachments = [] }) {
26
+ export function buildOpenCodeArgs({ projectRoot, prompt, model = process.env.PULSE_OPENCODE_MODEL, effort = null, attachments = [] }) {
25
27
  return [
26
28
  '--pure',
27
29
  'run',
28
30
  '--format', 'json',
29
31
  '--agent', 'pulse-readonly',
30
32
  ...(model ? ['--model', model] : []),
33
+ // The model's reasoning rung, as OpenCode names it (`--variant`, 1.18).
34
+ ...(effort ? ['--variant', effort] : []),
31
35
  ...attachments.flatMap((file) => ['--file', file.path]),
32
36
  '--dir', projectRoot,
33
37
  prompt,
@@ -154,11 +158,11 @@ export function parseOpenCodeOutput(output) {
154
158
  return { text: text.join('').trim(), usage, ...(error ? { error } : {}) };
155
159
  }
156
160
 
157
- export function invokeOpenCode({ executable, projectRoot, prompt, timeoutMs = 120000, signal, model = null, attachments = [], lease = null, scopes = null, imageStudio = null, memoryServer = null, mcpServers = [], onProgress = null }) {
161
+ export function invokeOpenCode({ executable, projectRoot, prompt, timeoutMs = 120000, signal, model = null, effort = null, attachments = [], lease = null, scopes = null, imageStudio = null, memoryServer = null, mcpServers = [], onProgress = null }) {
158
162
  return runReadonlyProcess({
159
163
  onProgress: onProgress ? (out) => onProgress({ chars: out.length }) : null,
160
164
  executable,
161
- args: buildOpenCodeArgs({ projectRoot, prompt, attachments, ...(model ? { model } : {}) }),
165
+ args: buildOpenCodeArgs({ projectRoot, prompt, attachments, effort, ...(model ? { model } : {}) }),
162
166
  cwd: projectRoot,
163
167
  env: openCodeEnvironment(process.env, { lease, scopes, imageStudio, memoryServer, mcpServers }),
164
168
  timeoutMs,
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { spawn } from 'node:child_process';
2
4
  import { t } from '../i18n.mjs';
3
5
 
package/src/asking.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // What the room should ask next.
2
4
  //
3
5
  // The maturity reading says where the archive is thin; it does not say what to do about it on a
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { execFile } from 'node:child_process';
2
4
  import { t } from './i18n.mjs';
3
5
  import { access, readFile } from 'node:fs/promises';
@@ -121,7 +123,7 @@ export const AGENT_SETUP = {
121
123
  gemini: {
122
124
  install: ['npm install -g @google/gemini-cli'],
123
125
  login: [],
124
- loginNote: 'Gemini signs in from its own prompt: run `gemini`, type /auth, pick "Use Gemini API key" (get one at aistudio.google.com/app/apikey) or Google login.',
126
+ loginNote: 'Gemini signs in from your terminal: run `gemini`, type /auth, pick "Use Gemini API key" (get one at aistudio.google.com/app/apikey) or Google login.',
125
127
  interactive: true,
126
128
  vendor: 'Google',
127
129
  account: 'Signs in with a Google account and has a free tier. A Gemini API key from AI Studio works too.',
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The phones that may reach this Mac, and the networks where they may.
2
4
  //
3
5
  // One file, `~/.pulse/devices.json`, owner-only, shared by every room on this Mac: any of them can
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Where the person was, on the Mac and on the phone (docs/MOBILE.md, MOV-005).
2
4
  //
3
5
  // One small file for the whole Mac, `~/.pulse/handoff.json`, owner-only: the last place each side
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Who keeps the bridge up. Every room on this Mac offers to; the port decides.
2
4
  //
3
5
  // No process of its own and nothing to start: each room, every few seconds, asks whether a phone
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Which network this Mac is on, and how a phone on it reaches the Mac.
2
4
  //
3
5
  // A network is known by its router: the hardware address of the default gateway, hashed. Two
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // What a phone may do in a room (docs/MOBILE.md, section 4).
2
4
  //
3
5
  // A list of what passes, not of what does not: a route the room grows tomorrow stays on the Mac
@@ -15,7 +17,7 @@ import { t } from '../i18n.mjs';
15
17
 
16
18
  // Every file the room's page loads: one missing here and app.js cannot import it, so the whole page
17
19
  // stops on the phone (test/phone-bridge.test.mjs reads app.js's imports to keep this whole).
18
- const STATIC = new Set(['/', '/app.js', '/nostromo-engine.js', '/brands.js', '/troubleshooting.js', '/inquiry.js', '/i18n.js', '/es.js', '/resay.js', '/syntax.js', '/editor.js', '/routing.js', '/abduction.js', '/styles.css', '/review-ask.js', '/loader.js', '/tour.js', '/tour-chapters.js', '/touch.js']);
20
+ const STATIC = new Set(['/', '/app.js', '/nostromo-engine.js', '/brands.js', '/troubleshooting.js', '/inquiry.js', '/i18n.js', '/es.js', '/resay.js', '/syntax.js', '/editor.js', '/routing.js', '/abduction.js', '/styles.css', '/review-ask.js', '/loader.js', '/tour.js', '/tour-chapters.js', '/touch.js', '/attribution.js']);
19
21
 
20
22
  const READ = new Set([
21
23
  '/api/state', '/api/events', '/api/chats', '/api/version', '/api/whats-new', '/api/glance', '/api/handoff',
package/src/bridge/qr.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The QR a phone scans to pair with this Mac: text in, a grid of dark and light modules out, and
2
4
  // an SVG of that grid the room sizes and colours with CSS.
3
5
  //
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The bridge: the one thing on this Mac a phone can reach (docs/MOBILE.md, MOV-001).
2
4
  //
3
5
  // Rooms keep listening on 127.0.0.1 and keep refusing anything that does not call them by that
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // What each CLI can do from MADRE, verified against the installed versions on
2
4
  // 2026-09-16 (Codex 0.153, Claude Code 2.1, Gemini CLI 0.60, OpenCode 1.18).
3
5
  // The room shows this so a human knows whom to ask for what, and an
package/src/chats.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // A project has one memory and many conversations.
2
4
  //
3
5
  // The archive, the crew, the modules and the privacy list belong to the project: they are what
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { execFile } from 'node:child_process';
2
4
  import { access, mkdir, mkdtemp, readFile, readdir, rm, rmdir, stat, unlink, writeFile } from 'node:fs/promises';
3
5
  import { tmpdir } from 'node:os';
package/src/choices.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // An agent asking the human to choose.
2
4
  //
3
5
  // The room already has one fenced channel, ```pulse, and it puts other agents to work. This is
package/src/cli-args.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Tiny argv reader for the MADRE CLI. Accepts `--name value` and
2
4
  // `--name=value`, and remembers which options the user set explicitly so the
3
5
  // room can choose sensible defaults (like the next free port) only when they
package/src/cold.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Cold zones: the part of the archive nothing has ever reached for.
2
4
  //
3
5
  // A memory is cold when three things are true at once, and the three together are what make the
package/src/commands.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { execFile } from 'node:child_process';
2
4
  import { access } from 'node:fs/promises';
3
5
  import { join } from 'node:path';
package/src/config.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  import { randomUUID } from 'node:crypto';
2
4
  import { copyFile, mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';
3
5
  import { homedir } from 'node:os';
@@ -1,3 +1,17 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
3
+ // What a command's output may weigh in the shared window, and which part of it. It used to be the
4
+ // first 6000 characters: five outputs filled the 16 000 of the window, the request that asked for
5
+ // them went out of it, and the agent lost the thread (2026-10-14). Now it is the start and the end
6
+ // — a test run says what it is at the top and how it went at the bottom — and the agent that asked
7
+ // gets the fuller output in a turn of its own (src/room/returns.mjs).
8
+ const COMMAND_HEAD = 300;
9
+ const COMMAND_TAIL = 900;
10
+ function commandEnds(text) {
11
+ if (text.length <= COMMAND_HEAD + COMMAND_TAIL) return text;
12
+ return `${text.slice(0, COMMAND_HEAD)}\n… ${text.length - COMMAND_HEAD - COMMAND_TAIL} characters left out …\n${text.slice(-COMMAND_TAIL)}`;
13
+ }
14
+
1
15
  export function messageEntry(event) {
2
16
  if (event.type === 'message.failed') {
3
17
  // Failures are part of the conversation: an orchestrator closing a plan
@@ -9,7 +23,7 @@ export function messageEntry(event) {
9
23
  if (event.type === 'command.output') {
10
24
  const { name, title, text } = event.payload;
11
25
  if (typeof text !== 'string' || !text.trim()) return null;
12
- return { sequence: event.sequence, messageId: `${event.id}:command`, role: 'command', sender: `/${name}`, target: 'room', text: `${title}\n${text.trim()}`.slice(0, 6000) };
26
+ return { sequence: event.sequence, messageId: `${event.id}:command`, role: 'command', sender: `/${name}`, target: 'room', text: `${title}\n${commandEnds(text.trim())}` };
13
27
  }
14
28
  if (event.type !== 'message.created') return null;
15
29
  const { messageId, role, sender, target, text } = event.payload;
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Handing a provider key to a CLI that signs in from its own prompt.
2
4
  //
3
5
  // MADRE keeps its promise: it never stores a credential of its own. The key the human pastes is
@@ -36,6 +38,32 @@ export const OPENCODE_PROVIDERS = [
36
38
  { id: 'google', label: 'Google AI Studio', keyUrl: 'https://aistudio.google.com/app/apikey' },
37
39
  ];
38
40
 
41
+ // The rest of the providers OpenCode knows (2026-10-14): its own catalogue, models.dev, which it
42
+ // caches itself. Those a single key opens can be wired from MADRE; the ones that need an account
43
+ // id, a region or a second secret stay with `opencode auth login`. Read only, never fetched.
44
+ export const OPENCODE_FEATURED = ['anthropic', 'openai', 'google', 'openrouter', 'groq', 'deepseek', 'mistral', 'xai', 'togetherai', 'fireworks-ai', 'cerebras', 'perplexity', 'huggingface', 'deepinfra', 'moonshotai', 'zai', 'cohere', 'vercel'];
45
+ export async function readOpenCodeCatalog({ home = homedir(), env = process.env } = {}) {
46
+ const raw = await readIfThere(join(env.XDG_CACHE_HOME || join(home, '.cache'), 'opencode', 'models.json'));
47
+ let data = {};
48
+ try { data = JSON.parse(raw ?? '{}') ?? {}; } catch { data = {}; }
49
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return [];
50
+ const out = [];
51
+ for (const [id, one] of Object.entries(data)) {
52
+ if (!/^[\w.-]{1,60}$/.test(id) || !one || typeof one !== 'object' || !Array.isArray(one.env) || one.env.length !== 1) continue;
53
+ out.push({ id, label: typeof one.name === 'string' && one.name.trim() ? one.name.trim().slice(0, 60) : id, docUrl: typeof one.doc === 'string' && /^https:\/\//.test(one.doc) ? one.doc : null });
54
+ }
55
+ return out;
56
+ }
57
+ // Every provider a key can be pasted for: MADRE's own four first (their key pages), then the
58
+ // catalogue's, each once, the popular ones marked.
59
+ export async function openCodeKeyProviders({ home = homedir(), env = process.env } = {}) {
60
+ const catalog = await readOpenCodeCatalog({ home, env });
61
+ const byId = new Map(OPENCODE_PROVIDERS.map((one) => [one.id, { ...one, featured: true }]));
62
+ for (const one of catalog) if (!byId.has(one.id)) byId.set(one.id, { id: one.id, label: one.label, keyUrl: one.docUrl, featured: OPENCODE_FEATURED.includes(one.id) });
63
+ const rank = (one) => { const at = OPENCODE_FEATURED.indexOf(one.id); return at < 0 ? Infinity : at; };
64
+ return [...byId.values()].sort((a, b) => Number(b.featured) - Number(a.featured) || rank(a) - rank(b));
65
+ }
66
+
39
67
  // What the room offers for each agent that cannot be signed in with a click.
40
68
  export function keyPlanFor(id) {
41
69
  if (id === 'claude') {
@@ -140,7 +168,7 @@ async function applyClaudeKey(key, { home, probe }) {
140
168
 
141
169
  // ---- OpenCode: an entry in its auth.json, confirmed by asking `opencode auth list`.
142
170
  async function applyOpenCodeKey(key, { home, provider, executable, run }) {
143
- const known = OPENCODE_PROVIDERS.some((item) => item.id === provider);
171
+ const known = typeof provider === 'string' ? (await openCodeKeyProviders({ home })).find((item) => item.id === provider) : null;
144
172
  if (!known) return { ok: false, error: 'Pick the provider this key belongs to.' };
145
173
  const file = join(home, '.local', 'share', 'opencode', 'auth.json');
146
174
  await mkdir(join(home, '.local', 'share', 'opencode'), { recursive: true });
@@ -153,8 +181,9 @@ async function applyOpenCodeKey(key, { home, provider, executable, run }) {
153
181
  await chmod(file, OWNER_ONLY).catch(() => {});
154
182
  // OpenCode itself is the judge: if its own listing does not show the provider, the shape
155
183
  // MADRE wrote is not the shape it reads, and nothing should be left behind.
156
- const listed = await run(executable, ['auth', 'list']);
157
- if (!new RegExp(provider, 'i').test(listed)) {
184
+ // It lists the provider by its name («Together AI»), sometimes by its id.
185
+ const listed = String(await run(executable, ['auth', 'list'])).toLowerCase();
186
+ if (!listed.includes(provider.toLowerCase()) && !listed.includes(String(known.label).toLowerCase())) {
158
187
  await restore(file, before);
159
188
  return { ok: false, error: `OpenCode does not list ${provider} after the key was written, so MADRE put the file back. Run \`opencode auth login\` in a terminal for this one.` };
160
189
  }
package/src/dataset.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The dataset behind MADRE AI: every exchange the room kept (never GHOST),
2
4
  // redacted, as chat pairs a small model can learn from, plus the distilled
3
5
  // notes. Written next to the room's ledger in the format mlx-lm and most
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Agent-to-agent delegation. An agent answering in the room may end its reply
2
4
  // with a fenced block that puts other agents to work, in order:
3
5
  //
package/src/distiller.mjs CHANGED
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // The archivist's turn: every so often the cheapest agent in the room reads
2
4
  // the exchanges nobody has distilled yet and writes the few notes worth
3
5
  // keeping for every later turn. Pure functions here; the room schedules and
@@ -1,3 +1,5 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-only
2
+ // MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md
1
3
  // Embeddings for the room memory: meaning-aware recall on top of the lexical
2
4
  // index. Gemini's embedding model through the user's own key (the same key
3
5
  // Image Studio and the Gemini CLI use); no key, no embeddings, and recall stays