@jossuealcala/madre 1.0.0 → 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 (174) hide show
  1. package/ADDITIONAL-TERMS.md +93 -0
  2. package/CHANGELOG.en.md +1945 -0
  3. package/CHANGELOG.md +78 -1
  4. package/CONTRIBUTING.md +5 -1
  5. package/LICENSE +661 -202
  6. package/NOTICE +34 -3
  7. package/README.en.md +245 -0
  8. package/README.md +23 -13
  9. package/TRADEMARKS.en.md +50 -0
  10. package/TRADEMARKS.md +82 -0
  11. package/bin/madre.mjs +2 -0
  12. package/docs/INTERNALS.md +8 -2
  13. package/docs/REFERENCE.md +17 -7
  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/madre-banner.png +0 -0
  19. package/docs/training/README.md +20 -8
  20. package/docs/training/train.sh +2 -1
  21. package/package.json +14 -5
  22. package/public/abduction.js +2 -0
  23. package/public/app.js +2066 -488
  24. package/public/attribution.js +71 -0
  25. package/public/brands.js +3 -1
  26. package/public/editor.js +2 -0
  27. package/public/es.js +1706 -1226
  28. package/public/i18n.js +25 -1
  29. package/public/index.html +13 -10
  30. package/public/inquiry.js +2 -0
  31. package/public/loader.js +2 -0
  32. package/public/macroverso.js +24 -2
  33. package/public/nostromo-engine.js +2 -0
  34. package/public/resay.js +6 -0
  35. package/public/review-ask.js +2 -0
  36. package/public/routing.js +23 -1
  37. package/public/salas-mac.css +539 -28
  38. package/public/salas-mac.html +30 -22
  39. package/public/salas-mac.js +1498 -144
  40. package/public/salas.css +2 -0
  41. package/public/salas.html +1 -0
  42. package/public/salas.js +4 -2
  43. package/public/styles.css +1123 -20
  44. package/public/syntax.js +2 -0
  45. package/public/touch.js +2 -0
  46. package/public/tour-chapters.js +164 -52
  47. package/public/tour.js +85 -12
  48. package/public/troubleshooting.js +20 -18
  49. package/src/adapters/claude.mjs +2 -0
  50. package/src/adapters/codex.mjs +2 -0
  51. package/src/adapters/gemini.mjs +3 -1
  52. package/src/adapters/madre.mjs +3 -1
  53. package/src/adapters/opencode.mjs +7 -3
  54. package/src/adapters/process.mjs +2 -0
  55. package/src/asking.mjs +2 -0
  56. package/src/auth-probe.mjs +32 -7
  57. package/src/bridge/devices.mjs +2 -0
  58. package/src/bridge/handoff.mjs +2 -0
  59. package/src/bridge/host.mjs +2 -0
  60. package/src/bridge/network.mjs +2 -0
  61. package/src/bridge/policy.mjs +3 -1
  62. package/src/bridge/qr.mjs +2 -0
  63. package/src/bridge/server.mjs +2 -0
  64. package/src/capabilities.mjs +2 -0
  65. package/src/chats.mjs +2 -0
  66. package/src/checkpoint.mjs +2 -0
  67. package/src/choices.mjs +2 -0
  68. package/src/cli-args.mjs +2 -0
  69. package/src/cold.mjs +2 -0
  70. package/src/commands.mjs +2 -0
  71. package/src/config.mjs +2 -0
  72. package/src/conversation-context.mjs +15 -1
  73. package/src/credentials.mjs +112 -3
  74. package/src/dataset.mjs +2 -0
  75. package/src/directives.mjs +8 -6
  76. package/src/distiller.mjs +2 -0
  77. package/src/embeddings.mjs +2 -0
  78. package/src/enlaces.mjs +6 -4
  79. package/src/event-store.mjs +2 -0
  80. package/src/exam.mjs +6 -4
  81. package/src/extensions.mjs +33 -10
  82. package/src/eyecat-watch.mjs +2 -0
  83. package/src/eyecat.mjs +2 -0
  84. package/src/file-ops.mjs +2 -0
  85. package/src/files.mjs +32 -0
  86. package/src/github.mjs +2 -0
  87. package/src/heartbeat.mjs +57 -0
  88. package/src/i18n.mjs +15 -1
  89. package/src/image-studio.mjs +2 -0
  90. package/src/json-file.mjs +2 -0
  91. package/src/launch.mjs +2 -0
  92. package/src/lease.mjs +5 -1
  93. package/src/lobby-work.mjs +2 -0
  94. package/src/local-ai.mjs +50 -0
  95. package/src/looks.mjs +40 -5
  96. package/src/maturity.mjs +3 -1
  97. package/src/mcp/connect.mjs +345 -0
  98. package/src/mcp/github-server.mjs +2 -0
  99. package/src/mcp/image-server.mjs +2 -0
  100. package/src/mcp/memory-server.mjs +2 -0
  101. package/src/mcp/proxy.mjs +153 -0
  102. package/src/mcp/smtp-server.mjs +2 -0
  103. package/src/memory-dashboard.mjs +2 -0
  104. package/src/memory-tools.mjs +2 -0
  105. package/src/memory.mjs +2 -0
  106. package/src/models.mjs +83 -3
  107. package/src/modules/ahp.mjs +2 -0
  108. package/src/modules/ash-policy.mjs +2 -0
  109. package/src/modules/ash.mjs +3 -1
  110. package/src/modules/bishop-plan.mjs +5 -3
  111. package/src/modules/bishop.mjs +13 -11
  112. package/src/modules/choices.mjs +2 -0
  113. package/src/modules/correo.mjs +2 -0
  114. package/src/modules/enlaces.mjs +2 -0
  115. package/src/modules/git-pulse.mjs +2 -0
  116. package/src/modules/github.mjs +2 -0
  117. package/src/modules/helpers.mjs +2 -0
  118. package/src/modules/image-studio.mjs +3 -1
  119. package/src/modules/index.mjs +4 -1
  120. package/src/modules/intents-calibration.mjs +2 -0
  121. package/src/modules/intents-desk.mjs +4 -2
  122. package/src/modules/intents-observe.mjs +2 -0
  123. package/src/modules/intents-policy.mjs +4 -1
  124. package/src/modules/intents-try.mjs +2 -0
  125. package/src/modules/intents.mjs +8 -6
  126. package/src/modules/mcp-connect.mjs +293 -0
  127. package/src/modules/ollama.mjs +17 -3
  128. package/src/modules/playwright.mjs +25 -10
  129. package/src/modules/ripley.mjs +2 -0
  130. package/src/modules/runs.mjs +5 -3
  131. package/src/modules/sdk.mjs +2 -0
  132. package/src/modules/updates.mjs +2 -0
  133. package/src/mother.mjs +2 -0
  134. package/src/ollama.mjs +2 -0
  135. package/src/outbound.mjs +24 -5
  136. package/src/override.mjs +2 -0
  137. package/src/ports.mjs +2 -0
  138. package/src/prices.mjs +2 -0
  139. package/src/privacy.mjs +2 -0
  140. package/src/quota-monitor.mjs +2 -0
  141. package/src/quota-sources.mjs +2 -0
  142. package/src/request-guard.mjs +2 -0
  143. package/src/review-ask.mjs +2 -0
  144. package/src/room/archivist.mjs +2 -0
  145. package/src/room/attachments.mjs +2 -0
  146. package/src/room/budget.mjs +2 -0
  147. package/src/room/context.mjs +2 -0
  148. package/src/room/control.mjs +7 -1
  149. package/src/room/copilot.mjs +76 -0
  150. package/src/room/economy.mjs +2 -0
  151. package/src/room/escalation.mjs +3 -0
  152. package/src/room/ghost.mjs +2 -0
  153. package/src/room/guard.mjs +2 -0
  154. package/src/room/prompt.mjs +13 -4
  155. package/src/room/returns.mjs +82 -0
  156. package/src/room/runs.mjs +4 -0
  157. package/src/room/vectors.mjs +2 -0
  158. package/src/room.mjs +264 -45
  159. package/src/router.mjs +2 -0
  160. package/src/runs.mjs +2 -0
  161. package/src/runtime-detection.mjs +2 -0
  162. package/src/salas.mjs +2 -0
  163. package/src/sentinel-errors.mjs +2 -0
  164. package/src/server-sentinel.mjs +2 -0
  165. package/src/server.mjs +485 -55
  166. package/src/setup.mjs +2 -0
  167. package/src/smtp.mjs +2 -0
  168. package/src/tour.mjs +26 -9
  169. package/src/updates.mjs +2 -0
  170. package/src/usage-sentinel.mjs +2 -0
  171. package/src/vault.mjs +2 -0
  172. package/src/verdict.mjs +3 -1
  173. package/src/whats-new.mjs +12 -10
  174. package/docs/madre-banner.svg +0 -25
package/public/i18n.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 room's language.
2
4
  //
3
5
  // MADRE is written in Spanish and answers in English when it is asked to. That is the way round
@@ -37,6 +39,13 @@ export function setLanguage(id) {
37
39
  return current;
38
40
  }
39
41
 
42
+ // The language of the person at this browser, for a MADRE nobody has set yet (2026-10-15): the
43
+ // browser's first language, Spanish in any of its variants, English for every other one.
44
+ export function browserLanguage(nav = globalThis.navigator) {
45
+ const first = String((Array.isArray(nav?.languages) && nav.languages[0]) || nav?.language || '').toLowerCase();
46
+ return first.startsWith('es') ? 'es' : 'en';
47
+ }
48
+
40
49
  // `{name}` is filled from `vars` after the sentence is chosen, so the Spanish can put the number
41
50
  // where Spanish puts it. A slot with nothing to fill it is left as it is rather than printed as
42
51
  // "undefined": a visible brace is a bug you can see.
@@ -45,15 +54,30 @@ export function fill(text, vars) {
45
54
  return String(text).replace(/\{(\w+)\}/g, (whole, name) => (Object.hasOwn(vars, name) ? String(vars[name]) : whole));
46
55
  }
47
56
 
57
+ // «Mac» only where MADRE runs on one (2026-10-15). It installs on Linux too, so a sentence about
58
+ // the machine it runs on («this Mac», «tu Mac», «la Mac») says «computer»/«computadora» anywhere
59
+ // else. The page is always opened on the machine that serves it (127.0.0.1), so the browser's own
60
+ // platform is that machine's. Hardware named on purpose — «a Mac with an M chip» — has no article
61
+ // in front and is left as it is.
62
+ let hostIsMac = /mac/i.test(String(globalThis.navigator?.userAgentData?.platform ?? globalThis.navigator?.platform ?? ''));
63
+ export const setHostIsMac = (value) => { hostIsMac = Boolean(value); return hostIsMac; };
64
+ export function hostWords(text) {
65
+ if (hostIsMac || typeof text !== 'string' || !text.includes('Mac')) return text;
66
+ return text
67
+ .replace(/\b(this|your|the|This|Your|The) Mac\b/g, '$1 computer')
68
+ .replace(/(^|[^\p{L}])(esta|tu|la|Esta|Tu|La) Mac\b/gu, '$1$2 computadora');
69
+ }
70
+
48
71
  export function t(text, vars = null) {
49
72
  const said = CATALOGUES[current]?.[text] ?? text;
50
- return fill(said, vars);
73
+ return fill(hostWords(said), vars);
51
74
  }
52
75
 
53
76
  // A table that carries both languages: `{ en: …, es: … }` anywhere inside it. Whole objects,
54
77
  // whole arrays — the shape is the table's own and this only chooses the side.
55
78
  export function pick(value) {
56
79
  if (Array.isArray(value)) return value.map(pick);
80
+ if (typeof value === 'string') return hostWords(value);
57
81
  if (!value || typeof value !== 'object') return value;
58
82
  if (Object.hasOwn(value, 'en') && Object.hasOwn(value, current)) return pick(value[current] ?? value.en);
59
83
  const out = {};
package/public/index.html CHANGED
@@ -1,4 +1,5 @@
1
1
  <!doctype html>
2
+ <!-- SPDX-License-Identifier: AGPL-3.0-only · MADRE · © 2026 Jossue Alcalá · GNU AGPL v3 with additional terms (Section 7): ADDITIONAL-TERMS.md -->
2
3
  <html lang="en">
3
4
  <head>
4
5
  <meta charset="utf-8">
@@ -94,6 +95,7 @@
94
95
  <aside id="tree" class="tree" aria-label="Project files" hidden>
95
96
  <header class="tree-head">
96
97
  <span class="tree-title">FILES <span id="tree-root" class="tree-root"></span></span>
98
+ <button id="tree-refresh" class="surface-gear tree-refresh" type="button" aria-label="Refresh files" title="Refresh files"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M15.6 8.4A5.8 5.8 0 0 0 4.9 6.6M4.4 11.6a5.8 5.8 0 0 0 10.7 1.8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><path d="M15.9 3.9v4.6h-4.6M4.1 16.1v-4.6h4.6" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
97
99
  <button id="tree-settings" class="surface-gear" type="button" aria-label="Settings for files and servers" title="Settings for files and servers"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M3.5 6h6M14 6h2.5M3.5 14h2.5M10.5 14h6" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/><circle cx="11.8" cy="6" r="2.2" fill="none" stroke="currentColor" stroke-width="1.6"/><circle cx="8.2" cy="14" r="2.2" fill="none" stroke="currentColor" stroke-width="1.6"/></svg></button>
98
100
  <button id="tree-close" class="tree-close" type="button" aria-label="Close files">×</button>
99
101
  </header>
@@ -109,8 +111,8 @@
109
111
  <span class="mother-title">What is sent</span>
110
112
  <span class="mother-sub">What this project says in your name</span>
111
113
  <span class="mother-actions">
112
- <button id="core-copy" class="mother-close" type="button" title="Copy the whole briefing as the agent receives it">Copy all</button>
113
- <button id="core-close" class="mother-close" type="button" aria-label="Close">Close</button>
114
+ <button id="core-copy" class="sx-btn core-copy-all" type="button" title="Copy the whole briefing as the agent receives it">Copy all</button>
115
+ <span class="surface-modes"><button id="core-close" class="surface-mode close x-close x-only" type="button" aria-label="Close"><span class="x-icon"><svg focusable="false" viewBox="0 0 20 20" aria-hidden="true"><path d="M5.5 5.5l9 9M14.5 5.5l-9 9" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg></span></button></span>
114
116
  </span>
115
117
  </header>
116
118
  <p id="core-boot" class="mother-boot"></p>
@@ -139,7 +141,7 @@
139
141
  </form>
140
142
  <div id="mother-os" class="sx-seg mother-os" role="radiogroup" aria-label="Operating system"></div>
141
143
  <span class="mother-tools">
142
- <button id="mother-settings-button" class="surface-gear" type="button" title="Diagnostics settings: what the sentinel keeps and whether it reports" aria-label="Diagnostics settings"><svg class="gear-glyph" viewBox="0 0 20 20" aria-hidden="true"><path fill-rule="evenodd" d="M8.65 3.85L8.92 1.97L11.08 1.97L11.35 3.85L13.40 4.69L14.91 3.56L16.44 5.09L15.31 6.60L16.15 8.65L18.03 8.92L18.03 11.08L16.15 11.35L15.31 13.40L16.44 14.91L14.91 16.44L13.40 15.31L11.35 16.15L11.08 18.03L8.92 18.03L8.65 16.15L6.60 15.31L5.09 16.44L3.56 14.91L4.69 13.40L3.85 11.35L1.97 11.08L1.97 8.92L3.85 8.65L4.69 6.60L3.56 5.09L5.09 3.56L6.60 4.69ZM12.6 10a2.6 2.6 0 1 1-5.2 0a2.6 2.6 0 1 1 5.2 0Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg></button>
144
+ <button id="mother-settings-button" class="surface-gear" type="button" title="Diagnostics settings: what the failure monitor keeps and whether it reports" aria-label="Diagnostics settings"><svg class="gear-glyph" viewBox="0 0 20 20" aria-hidden="true"><path fill-rule="evenodd" d="M8.65 3.85L8.92 1.97L11.08 1.97L11.35 3.85L13.40 4.69L14.91 3.56L16.44 5.09L15.31 6.60L16.15 8.65L18.03 8.92L18.03 11.08L16.15 11.35L15.31 13.40L16.44 14.91L14.91 16.44L13.40 15.31L11.35 16.15L11.08 18.03L8.92 18.03L8.65 16.15L6.60 15.31L5.09 16.44L3.56 14.91L4.69 13.40L3.85 11.35L1.97 11.08L1.97 8.92L3.85 8.65L4.69 6.60L3.56 5.09L5.09 3.56L6.60 4.69ZM12.6 10a2.6 2.6 0 1 1-5.2 0a2.6 2.6 0 1 1 5.2 0Z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg></button>
143
145
  <button id="nostromo-button" class="surface-gear nostromo-button" type="button" title="Memory map · memory research · needs the project name" aria-label="Memory map"><svg viewBox="0 0 20 20" aria-hidden="true"><path class="ic-f" d="M9.2 4.6A2.5 2.5 0 0 0 4.9 5.9a2.6 2.6 0 0 0-1.4 4.2 2.7 2.7 0 0 0 1.6 4.1 2.5 2.5 0 0 0 4.1 1.6z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path class="ic-f" d="M10.8 4.6a2.5 2.5 0 0 1 4.3 1.3 2.6 2.6 0 0 1 1.4 4.2 2.7 2.7 0 0 1-1.6 4.1 2.5 2.5 0 0 1-4.1 1.6z" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M6.2 9.4c.9 0 1.6.6 1.8 1.4M13.8 9.4c-.9 0-1.6.6-1.8 1.4M7.2 6.6c.5.2.9.6 1 1.1M12.8 6.6c-.5.2-.9.6-1 1.1" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg></button>
144
146
  <button id="tour-button" class="surface-gear tour-button" type="button" title="The tour of MADRE" aria-label="Tour">?</button>
145
147
  </span>
@@ -147,7 +149,7 @@
147
149
  </header>
148
150
  <p id="mother-boot" class="mother-status" aria-live="polite"></p>
149
151
  <div id="mother-body" class="mother-body">
150
- <section id="mother-list" class="mother-list" aria-label="Conditions"></section>
152
+ <section id="mother-list" class="mother-list" aria-label="Problems"></section>
151
153
  <section id="mother-answer" class="mother-detail" aria-live="polite"></section>
152
154
  </div>
153
155
  </dialog>
@@ -192,7 +194,7 @@
192
194
  <textarea id="message" rows="1" placeholder="Type here, human. Ask the project…"></textarea>
193
195
  <div id="slash-menu" class="slash-menu" role="listbox" hidden></div>
194
196
  </div>
195
- <button id="create-toggle" type="button" class="create-toggle" aria-pressed="false" title="Creation lease: let the agent create files for this request, only inside .pulse/out/" hidden>
197
+ <button id="create-toggle" type="button" class="create-toggle" aria-pressed="false" title="Permission to create: let the agent create files for this request, only inside .pulse/out/" hidden>
196
198
  <svg class="lock closed" viewBox="0 0 20 20" aria-hidden="true"><path d="M6.8 8.6V6.7a3.2 3.2 0 0 1 6.4 0v1.9" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><rect x="4.4" y="8.6" width="11.2" height="8.2" rx="2.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="12.7" r="1.1" fill="currentColor"/></svg>
197
199
  <svg class="lock open" viewBox="0 0 20 20" aria-hidden="true"><path d="M13.2 8.6V6.5a3.2 3.2 0 0 0-6.2-1.15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><rect x="4.4" y="8.6" width="11.2" height="8.2" rx="2.6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="10" cy="12.7" r="1.1" fill="currentColor"/></svg>
198
200
  <span class="create-word">CREATE</span>
@@ -236,7 +238,8 @@
236
238
  <form id="nostromo-gate-form" class="key-form" autocomplete="off">
237
239
  <label class="room-ask-field"><span class="k">Type the project name</span><input id="nostromo-gate-input" autocomplete="off" spellcheck="false"></label>
238
240
  <p class="key-reply" id="nostromo-gate-reply" aria-live="polite"></p>
239
- <div class="room-ask-actions"><button id="nostromo-gate-cancel" class="room-ask-choice" type="button">Cancel</button><button type="submit" class="room-ask-choice primary">Board</button></div>
241
+ <div class="room-ask-actions"><button id="nostromo-gate-cancel" class="room-ask-choice" type="button">Cancel</button><button type="submit" class="room-ask-choice primary">Enter</button></div>
242
+ <button id="nostromo-gate-skip" class="key-skip" type="button">Don't ask again…</button>
240
243
  </form>
241
244
  </div>
242
245
  </dialog>
@@ -246,7 +249,7 @@
246
249
  <span class="mother-title">MEMORY MAP</span>
247
250
  <span id="nostromo-sub" class="mother-sub">MEMORY RESEARCH · LOADING…</span>
248
251
  <span class="nostromo-legend" aria-label="Memory kinds">
249
- <span class="k decision" title="YELLOW DWARF · DECISION"><i>DECISION</i></span><span class="k fact" title="WHITE DWARF · FACT"><i>FACT</i></span><span class="k preference" title="GREEN DWARF · PREFERENCE"><i>PREFERENCE</i></span><span class="k question" title="BLUE DWARF · QUESTION"><i>QUESTION</i></span>
252
+ <span class="k decision" title="YELLOW STAR · DECISION"><i>DECISION</i></span><span class="k fact" title="WHITE STAR · FACT"><i>FACT</i></span><span class="k preference" title="GREEN STAR · PREFERENCE"><i>PREFERENCE</i></span><span class="k question" title="BLUE STAR · QUESTION"><i>QUESTION</i></span>
250
253
  </span>
251
254
  <span class="nostromo-actions">
252
255
  <button id="nostromo-cold" class="mother-close cold" type="button" hidden aria-pressed="false" title="The memories nothing has ever reached for, that share a subject with nothing, and that have had their chances">UNUSED · 0</button>
@@ -285,7 +288,7 @@
285
288
  <dt>SOURCE</dt><dd id="nostromo-card-span" data-part="span"></dd>
286
289
  <dt>ARCHIVIST</dt><dd id="nostromo-card-agent" data-part="agent"></dd>
287
290
  <dt>DISTILLED</dt><dd id="nostromo-card-when" data-part="when"></dd>
288
- <dt>ZONE</dt><dd id="nostromo-card-zone" data-part="zone"></dd>
291
+ <dt>STATUS</dt><dd id="nostromo-card-zone" data-part="zone"></dd>
289
292
  </dl>
290
293
  <section class="wire">
291
294
  <h6>LINKS <span id="nostromo-card-degree" data-part="degree" class="n"></span></h6>
@@ -307,7 +310,7 @@
307
310
  <header class="viewer-head browser-head">
308
311
  <span class="viewer-nav" id="browser-nav">
309
312
  <button id="browser-back" class="nav" type="button" title="Back" aria-label="Back" disabled>‹</button>
310
- <button id="browser-reload" class="nav" type="button" title="Reload" aria-label="Reload">↻</button>
313
+ <button id="browser-reload" class="nav reload" type="button" title="Reload" aria-label="Reload"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M16 10a6 6 0 1 1-1.76-4.24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/><path d="M16.1 3.6v3.6h-3.6" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
311
314
  <button id="browser-servers" class="nav ports" type="button" aria-expanded="false" aria-haspopup="menu" aria-label="This project's servers"><i class="ports-dot" aria-hidden="true"></i><span>YOUR SERVERS</span><i class="ports-caret" aria-hidden="true"></i></button>
312
315
  <button id="browser-point" class="nav point" type="button" aria-pressed="false" aria-label="Point and ask"><svg viewBox="0 0 20 20" aria-hidden="true"><path d="M5.4 3.9v11.4c0 .5.6.75.95.4l2.5-2.4 1.95 4.1c.17.37.6.52.96.35l.9-.43c.36-.17.5-.6.33-.96l-1.95-4.1h3.45c.5 0 .74-.6.38-.94L6.36 3.5c-.36-.33-.96-.08-.96.4z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg><span>POINT</span></button>
313
316
  </span>
@@ -366,7 +369,7 @@
366
369
  </dialog>
367
370
  </div>
368
371
  <script type="module" src="/app.js"></script>
369
- <dialog id="intents-window" class="surface window custom-window intents-window" aria-label="Intenciones · quick test">
372
+ <dialog id="intents-window" class="surface window custom-window intents-window" aria-label="Suggested effort · quick test">
370
373
  <header class="custom-head">
371
374
  <span class="custom-title">INTENCIONES</span>
372
375
  <span class="custom-sub">QUICK TEST · YOU JUDGE, BLIND</span>
package/public/inquiry.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 console inside the core.
2
4
  //
3
5
  // MU/TH/UR 6000 answered by being asked. The core already holds everything an answer would need —
package/public/loader.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
  // MADRE loading (2026-10-10): the pulse of the logo beating, a thin bar that fills and the share
2
4
  // done under it, as a Mac starts. One loader for every wait that takes time — a RELOAD, the room
3
5
  // or Home coming back after an update, a project waking, an agent installing — so a wait always
@@ -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 MACROVERSE: every room on this Mac as a NOSTROMO in open space (docs/MACROVERSO.md, phase 3a).
2
4
  // It is entered from SALAS and only on a large screen; on a phone SALAS is the list and nothing more.
3
5
  //
@@ -573,6 +575,15 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
573
575
  now[key] = answer ?? { unreachable: true };
574
576
  if (now.loading) delete now.loading[key];
575
577
  if (!full) { now.dirty = true; redraw(); }
578
+ // A saturated machine can miss one read while the room is still alive. Do not keep
579
+ // that transient failure as the universe's permanent texture.
580
+ if (now[key].unreachable || now[key].outdated) setTimeout(() => {
581
+ const current = universes.find((item) => item.room.id === one.room.id);
582
+ if (!current || current.face !== one.face || (!current[key]?.unreachable && !current[key]?.outdated)) return;
583
+ current[key] = null;
584
+ if (full) { if (focus === current && !live) enterLive(current); }
585
+ else { current.dirty = true; redraw(); }
586
+ }, 5000);
576
587
  return now[key];
577
588
  });
578
589
  one.loading = { ...(one.loading ?? {}), [key]: asked };
@@ -1345,8 +1356,16 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
1345
1356
 
1346
1357
  // Pointer: a drag on the space moves it; a drag on the chosen universe turns it; a click picks a
1347
1358
  // universe, or a memory inside the chosen one; the wheel brings the space closer round the pointer.
1359
+ const covered = (event) => {
1360
+ const panels = [...windows.map((win) => win.el), card, ...document.querySelectorAll('dialog[open], .tree.window, .surface.window, .conv-window, .browser-dock.window')];
1361
+ return panels.some((panel) => {
1362
+ if (panel.hidden || panel.contains(canvas)) return false;
1363
+ const rect = panel.getBoundingClientRect();
1364
+ return event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom;
1365
+ });
1366
+ };
1348
1367
  canvas.addEventListener('pointerdown', (event) => {
1349
- if (event.button !== 0) return;
1368
+ if (event.button !== 0 || covered(event)) return;
1350
1369
  drag.active = true;
1351
1370
  drag.moved = false;
1352
1371
  drag.turning = Boolean(liveAt(event)?.inside);
@@ -1357,6 +1376,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
1357
1376
  try { canvas.setPointerCapture(event.pointerId); } catch { /* the pointer already left */ }
1358
1377
  });
1359
1378
  canvas.addEventListener('pointermove', (event) => {
1379
+ if (covered(event)) return;
1360
1380
  const box = canvas.getBoundingClientRect();
1361
1381
  pointer = { x: event.clientX - box.left, y: event.clientY - box.top };
1362
1382
  if (drag.active) {
@@ -1400,7 +1420,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
1400
1420
  });
1401
1421
  const release = (event) => {
1402
1422
  // Let go over another universe: the notes of the one carried would travel to it.
1403
- if (linking?.over && event?.type === 'pointerup') actions.link?.(linking.from.room, linking.over.room);
1423
+ if (linking?.over && event?.type === 'pointerup' && !covered(event)) actions.link?.(linking.from.room, linking.over.room);
1404
1424
  linking = null;
1405
1425
  drag.active = false;
1406
1426
  drag.turning = false;
@@ -1412,6 +1432,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
1412
1432
  canvas.addEventListener('pointercancel', release);
1413
1433
  canvas.addEventListener('pointerleave', () => { hover = null; pointer = null; if (live) live.scene.hover = null; redraw(); });
1414
1434
  canvas.addEventListener('click', (event) => {
1435
+ if (covered(event)) return;
1415
1436
  if (drag.moved) { drag.moved = false; return; }
1416
1437
  const over = liveAt(event);
1417
1438
  if (over?.inside && live) {
@@ -1428,6 +1449,7 @@ export function openMacroverso({ host, rooms, here, enlaces = [], accent = '#9bf
1428
1449
  choose(one && one !== focus ? one : focus && !one ? null : one);
1429
1450
  });
1430
1451
  canvas.addEventListener('wheel', (event) => {
1452
+ if (covered(event)) return;
1431
1453
  event.preventDefault();
1432
1454
  const rect = canvas.getBoundingClientRect();
1433
1455
  const px = event.clientX - rect.left, py = event.clientY - rect.top;
@@ -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
  // NOSTROMO's engine: the physics and the painters of the archive as a body in space, lifted out
2
4
  // of the room's page so the Macroverso can draw every room the way its own NOSTROMO is drawn —
3
5
  // with these very functions, not a copy of them (docs/MACROVERSO.md, phase 3a).
package/public/resay.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
  // What MADRE said in another language, said again.
2
4
  //
3
5
  // A failure is recorded in the ledger with the sentence MADRE wrote at the time, and a room that
@@ -41,6 +43,7 @@ const SAID = [
41
43
  '{n} agents are working at once. If this is not what you asked for, type STOPALL.',
42
44
  '@{agent} is already answering another turn; answers may cross.',
43
45
  ' STOPALL halts everything.',
46
+ 'The copilot ran commands by itself {n} times in a row; the next ones wait for you. Write to it to go on.',
44
47
  'The plan by @{agent} has been running for {n} min.',
45
48
  ' STOPALL halts it.',
46
49
  '@{holder} still holds EDIT; @{agent} answers read-only this turn.',
@@ -55,6 +58,9 @@ const SAID = [
55
58
  '@{agent} is capped at #{n} {label}; raise its MAX MODE to #{wanted} in CONNECTIONS to arm {mode}.',
56
59
  '@{agent} already holds {mode} of this project; one holder at a time. STOPALL revokes it.',
57
60
  '@{agent} left the project: Ollama is not running. Start Ollama, or open Local AI in Modules.',
61
+ '@madre is in the project · {version}',
62
+ '{agent} is not installed on this computer.',
63
+ '@madre left the project: Ollama has no chat model running',
58
64
  '@{agent} left the room: Ollama is not running. Start Ollama, or open Ollama in Modules.',
59
65
  'Google reported the model as unavailable (HTTP 503) and the CLI kept retrying.',
60
66
  'Try again shortly or choose another model.',
@@ -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
  // Asking for a star or a review (2026-10-10), in the room and in Home: the server keeps when to
2
4
  // ask (src/review-ask.mjs); this page says once a minute that it is in view, and when a moment is
3
5
  // due it asks — only when nothing is going on: no turn running, nothing half written, no other
package/public/routing.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
  // Which questions the room can answer without paying for them.
2
4
  //
3
5
  // @madre runs on Ollama, on this computer. A turn it answers costs nothing: no input tokens, no
@@ -59,6 +61,13 @@ export function archiveQuestion(text = '') {
59
61
  const NEEDS = /(necesit|requier|hace falta|no alcanza|insuficiente|permiso|conced|eleva|sub[ei]r|needs?|requires?|not enough|insufficient|permission|grant|raise|escalat)/i;
60
62
 
61
63
  export function modeAsked(text = '', { ran = 1 } = {}) {
64
+ return modeAskedWithTime(text, { ran })?.mode ?? null;
65
+ }
66
+
67
+ // The same reading, and the time it asked for beside the number, if it said one (2026-10-14:
68
+ // agents are taught to write «I need #3 for 15 min: why»). Minutes, or null when it said none.
69
+ const TIME = /(\d{1,4})\s*(min(?:uto)?s?\b|m\b|h(?:ora)?s?\b|hours?\b)/i;
70
+ export function modeAskedWithTime(text = '', { ran = 1 } = {}) {
62
71
  const reply = String(text ?? '');
63
72
  if (reply.length > 8000) return null;
64
73
  let wanted = null;
@@ -68,7 +77,20 @@ export function modeAsked(text = '', { ran = 1 } = {}) {
68
77
  // The word of need has to sit beside the number, not anywhere in a long answer.
69
78
  const around = reply.slice(Math.max(0, match.index - 90), match.index + 90);
70
79
  if (!NEEDS.test(around)) continue;
71
- if (wanted === null || mode > wanted) wanted = mode;
80
+ if (wanted === null || mode > wanted.mode) {
81
+ const after = reply.slice(match.index, match.index + 60).match(TIME);
82
+ const amount = after ? Number(after[1]) : null;
83
+ wanted = { mode, minutes: amount ? (/^h/i.test(after[2]) ? amount * 60 : amount) : null };
84
+ }
72
85
  }
73
86
  return wanted;
74
87
  }
88
+
89
+ // The windows a key may open, as the project allows them (src/override.mjs): what the agent asked
90
+ // for becomes the smallest one that covers it, or the longest there is.
91
+ export const MODE_WINDOWS = Object.freeze({ 3: Object.freeze([5, 15, 30]), 4: Object.freeze([5]) });
92
+ export function windowFor(mode, minutes) {
93
+ const allowed = MODE_WINDOWS[mode];
94
+ if (!allowed || !Number.isFinite(minutes) || minutes <= 0) return 0;
95
+ return allowed.find((one) => one >= minutes) ?? allowed.at(-1);
96
+ }