@jossuealcala/madre 0.5.2 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +265 -1
  2. package/README.md +33 -1
  3. package/docs/SDK.md +173 -6
  4. package/docs/sdk/connector-module.mjs +186 -0
  5. package/package.json +6 -2
  6. package/public/abduction.js +384 -0
  7. package/public/app.js +3450 -540
  8. package/public/assets/abduction-cat-blink.png +0 -0
  9. package/public/assets/abduction-cat-control-left.png +0 -0
  10. package/public/assets/abduction-cat-control-right.png +0 -0
  11. package/public/assets/abduction-cat.png +0 -0
  12. package/public/es.js +354 -54
  13. package/public/index.html +65 -19
  14. package/public/routing.js +74 -0
  15. package/public/styles.css +788 -77
  16. package/src/adapters/claude.mjs +6 -2
  17. package/src/adapters/gemini.mjs +26 -5
  18. package/src/adapters/madre.mjs +5 -0
  19. package/src/choices.mjs +64 -0
  20. package/src/dataset.mjs +11 -6
  21. package/src/distiller.mjs +12 -1
  22. package/src/file-ops.mjs +45 -0
  23. package/src/lease.mjs +37 -2
  24. package/src/mcp/smtp-server.mjs +189 -0
  25. package/src/memory-dashboard.mjs +69 -0
  26. package/src/memory.mjs +222 -18
  27. package/src/modules/ahp.mjs +1 -1
  28. package/src/modules/ash-policy.mjs +67 -0
  29. package/src/modules/ash.mjs +1 -1
  30. package/src/modules/choices.mjs +34 -0
  31. package/src/modules/correo.mjs +107 -0
  32. package/src/modules/index.mjs +48 -4
  33. package/src/modules/playwright.mjs +5 -1
  34. package/src/modules/ripley.mjs +13 -1
  35. package/src/modules/sdk.mjs +44 -9
  36. package/src/outbound.mjs +37 -3
  37. package/src/ports.mjs +91 -0
  38. package/src/request-guard.mjs +26 -0
  39. package/src/room/archivist.mjs +2 -1
  40. package/src/room/economy.mjs +83 -1
  41. package/src/room/prompt.mjs +14 -2
  42. package/src/room.mjs +150 -18
  43. package/src/sentinel-errors.mjs +5 -1
  44. package/src/server.mjs +188 -8
  45. package/src/smtp.mjs +122 -0
  46. package/src/vault.mjs +100 -0
  47. package/src/whats-new.mjs +140 -0
package/public/index.html CHANGED
@@ -20,6 +20,10 @@
20
20
  <span id="project" class="project" title="Project room"></span>
21
21
  </div>
22
22
  <div id="agents" class="agents" aria-label="Agents"></div>
23
+ <button id="agents-compact" class="bar-icon agents-compact" type="button" title="Crew" aria-label="Crew">
24
+ <svg viewBox="0 0 20 20" aria-hidden="true"><rect x="3.5" y="6.5" width="13" height="9" rx="3" fill="none" stroke="currentColor" stroke-width="1.6"/><circle cx="7.6" cy="11" r="1.25" fill="currentColor"/><circle cx="12.4" cy="11" r="1.25" fill="currentColor"/><path d="M10 3.4v3.1M6.2 15.5v1.3M13.8 15.5v1.3" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
25
+ <span id="agents-count" class="bar-count"></span>
26
+ </button>
23
27
  <div id="connection" class="connection" data-state="connecting" title="Live updates">
24
28
  <span class="connection-dot"></span><span class="connection-label">connecting</span>
25
29
  </div>
@@ -28,10 +32,10 @@
28
32
  <span class="stop-all-glyph" aria-hidden="true"></span>STOP ALL
29
33
  </button>
30
34
  <button id="modules-button" class="mother-button" type="button" title="Modules · optional integrations for this project" aria-haspopup="dialog">
31
- <span class="mother-glyph modules-glyph" aria-hidden="true"></span>MODULES<span id="modules-count" class="modules-count" hidden></span>
35
+ <span class="mother-glyph modules-glyph" aria-hidden="true"></span><svg class="bar-glyph" viewBox="0 0 20 20" aria-hidden="true"><path d="M8.1 3.2h3.8v1.6a1.5 1.5 0 0 0 3 0V3.2h1.9v3.6h-1.4a1.5 1.5 0 0 0 0 3h1.4v3.6h-3.6v-1.5a1.5 1.5 0 0 0-3 0v1.5H6.8V9.8H5.4a1.5 1.5 0 0 1 0-3h1.4V3.2z" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>MODULES<span id="modules-count" class="modules-count" hidden></span>
32
36
  </button>
33
37
  <button id="mother-button" class="mother-button" type="button" title="Troubleshooting · MU/TH/UR" aria-haspopup="dialog">
34
- <span class="mother-glyph" aria-hidden="true"></span>MU/TH/UR<span id="mother-count" class="mother-count" hidden></span>
38
+ <span class="mother-glyph" aria-hidden="true"></span><svg class="bar-glyph" viewBox="0 0 20 20" aria-hidden="true"><path d="M1.8 10h3.4l1.6-4.6 2.6 9.2 2-4.6h1.5l1.3-2.6 1.2 2.6h2.8" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>MU/TH/UR<span id="mother-count" class="mother-count" hidden></span>
35
39
  </button>
36
40
  <button id="lang-button" class="tree-button lang-button" type="button" data-language="es" aria-label="Language">EN</button>
37
41
  <button id="theme-button" class="tree-button theme-button" type="button" title="Theme · auto (follows the system)" data-theme="auto" aria-label="Theme">
@@ -39,6 +43,9 @@
39
43
  <svg class="t-light" viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="3.6" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M10 2.5v2M10 15.5v2M2.5 10h2M15.5 10h2M4.7 4.7l1.4 1.4M13.9 13.9l1.4 1.4M4.7 15.3l1.4-1.4M13.9 6.1l1.4-1.4" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
40
44
  <svg class="t-dark" viewBox="0 0 20 20" aria-hidden="true"><path d="M15.5 12.4A6.5 6.5 0 0 1 7.6 4.5a6.5 6.5 0 1 0 7.9 7.9z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg>
41
45
  </button>
46
+ <button id="browser-button" class="tree-button" type="button" aria-pressed="false" aria-controls="browser" title="Browser · local servers and the web" aria-label="Browser">
47
+ <svg viewBox="0 0 20 20" aria-hidden="true"><circle cx="10" cy="10" r="7.2" fill="none" stroke="currentColor" stroke-width="1.6"/><path d="M2.8 10h14.4M10 2.8c1.9 2 2.9 4.5 2.9 7.2s-1 5.2-2.9 7.2c-1.9-2-2.9-4.5-2.9-7.2s1-5.2 2.9-7.2z" fill="none" stroke="currentColor" stroke-width="1.6"/></svg>
48
+ </button>
42
49
  <button id="tree-button" class="tree-button" type="button" title="Project files panel" aria-pressed="false" aria-controls="tree">
43
50
  <svg viewBox="0 0 20 20" aria-hidden="true"><rect x="2.75" y="3.75" width="14.5" height="12.5" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M12 3.75v12.5" fill="none" stroke="currentColor" stroke-width="1.7"/><path d="M13.8 7h1.6M13.8 9.6h1.6" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" opacity=".7"/></svg>
44
51
  </button>
@@ -65,7 +72,10 @@
65
72
  <span class="tree-title">FILES <span id="tree-root" class="tree-root"></span></span>
66
73
  <button id="tree-close" class="tree-close" type="button" aria-label="Close files">×</button>
67
74
  </header>
75
+ <form id="tree-search" class="tree-search"><input id="tree-q" type="search" spellcheck="false" autocomplete="off" aria-label="Search files" placeholder="Search files…"></form>
76
+ <div id="tree-clip" class="tree-clip" hidden></div>
68
77
  <div id="tree-body" class="tree-body" role="tree"></div>
78
+ <div id="tree-menu" class="tree-menu" role="menu" hidden></div>
69
79
  </aside>
70
80
 
71
81
  <dialog id="core" class="mother core" aria-label="The core">
@@ -84,6 +94,20 @@
84
94
  </div>
85
95
  </dialog>
86
96
 
97
+ <dialog id="economy" class="mother economy" aria-label="Token economy">
98
+ <div class="mother-frame">
99
+ <header class="mother-head">
100
+ <span class="mother-title">ECONOMY</span>
101
+ <span class="mother-sub">MADRE · WHAT THIS ROOM SPENT AND WHAT IT NEVER SPENT</span>
102
+ <span class="mother-actions">
103
+ <button id="economy-close" class="mother-close" type="button">CLOSE ×</button>
104
+ </span>
105
+ </header>
106
+ <section id="economy-body" class="mother-section"></section>
107
+ <footer class="mother-foot">MEASURED FROM WHAT THE CLIs CHARGED · A SAVING NOBODY MEASURED IS NEVER SHOWN</footer>
108
+ </div>
109
+ </dialog>
110
+
87
111
  <dialog id="modules" class="mother modules" aria-label="Modules">
88
112
  <div class="mother-frame">
89
113
  <header class="mother-head">
@@ -109,15 +133,17 @@
109
133
  <span class="mother-sub">MADRE · INTERFACE 2037 · TROUBLESHOOTING</span>
110
134
  <span class="mother-actions">
111
135
  <button id="mother-settings-button" class="mother-close" type="button" aria-pressed="false" title="The crew and this room's settings: install an agent, sign it in, set what each one may do">⚙ CONNECTIONS</button>
136
+ <button id="memory-button" class="mother-close" type="button" aria-pressed="false" title="What the archive holds, who distils it, and how well it is answering">▤ MEMORY</button>
112
137
  <button id="nostromo-button" class="mother-close nostromo-button" type="button" title="NOSTROMO · memory research · needs the project designation">◉ NOSTROMO</button>
113
138
  <button id="feedback-button" class="mother-close" type="button" title="Tell MADRE's author what happened or what you wish for · opens a prefilled issue">✎ FEEDBACK</button>
114
- <button id="fold-all" class="mother-close" type="button" title="Open or close every section of this panel">EXPAND ALL</button>
115
139
  <button id="tour-button" class="mother-close tour-button" type="button" title="The four-step tour of MADRE" aria-label="Tour">?</button>
116
140
  <button id="mother-close" class="mother-close" type="button">END SESSION ×</button>
117
141
  </span>
118
142
  </header>
143
+ <div class="fold-all-row"><button id="fold-all" class="fold-all" type="button" title="Open or close every section of this panel"></button></div>
119
144
  <section id="mother-update" class="mother-section update"></section>
120
145
  <section id="mother-settings" class="mother-section settings" hidden></section>
146
+ <section id="mother-memory" class="mother-section memory-panel" hidden></section>
121
147
  <section id="mother-sentinel" class="mother-section sentinel"></section>
122
148
  <pre id="mother-boot" class="mother-boot" aria-live="polite"></pre>
123
149
  <form id="mother-query" class="mother-query" autocomplete="off">
@@ -134,6 +160,7 @@
134
160
 
135
161
  <main id="messages" class="thread" aria-live="polite">
136
162
  <div id="thread-inner" class="thread-inner">
163
+ <div id="settled" class="settled" hidden></div>
137
164
  <div class="empty">
138
165
  <svg class="empty-mark" viewBox="0 0 72 20" aria-hidden="true">
139
166
  <path class="beat" d="M1 10 H16 L20 10 L23.5 2 L28 18 L32 10 L35 10 L37 6 L39 10 H52" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" pathLength="100"/>
@@ -159,6 +186,8 @@
159
186
  <div id="picker" class="picker" role="radiogroup" aria-label="Agent"></div>
160
187
  <select id="target" aria-label="Agent" class="visually-hidden"></select>
161
188
  <div id="attachments" class="attachments" hidden></div>
189
+ <div id="free-offer" class="free-offer" hidden></div>
190
+ <div id="openq" class="openq" hidden></div>
162
191
  <div class="field">
163
192
  <div id="reply-quote" class="reply-quote" hidden></div>
164
193
  <span id="crew-label" class="crew-label" aria-hidden="true">HUMAN ›</span>
@@ -183,7 +212,8 @@
183
212
  </button>
184
213
  </div>
185
214
  </form>
186
- <p class="safety">Consultation mode · Project writes stay under your control: agents create files only with CREATE or an opt-in standing lease, only inside .pulse/out/ · Content an agent reads may be sent to its configured model provider.</p>
215
+ <p class="safety">Consultation mode · Agents write only with CREATE or a standing lease, only inside .pulse/out/ · What an agent reads may reach its configured model provider.</p>
216
+ <small id="colophon" class="colophon" aria-label="MADRE version"></small>
187
217
  <div id="toast" class="toast" role="status" hidden></div>
188
218
  <div id="agent-pop" class="agent-pop" role="dialog" aria-label="Agent usage" hidden></div>
189
219
  <dialog id="override" class="mother override" aria-label="Emergency command override">
@@ -203,24 +233,22 @@
203
233
  <footer class="mother-foot">ONE HOLDER AT A TIME · EVERY TURN IS CHECKPOINTED · WHAT LEAVES THE SHIP DOES NOT COME BACK · STOPALL REVOKES IT</footer>
204
234
  </div>
205
235
  </dialog>
206
- <dialog id="tour" class="mother override tour" aria-label="MADRE tour">
207
- <div class="mother-frame override-frame tour-frame">
208
- <header class="mother-head">
209
- <span class="mother-title">MADRE</span>
210
- <span class="mother-sub" id="tour-sub">INTERFACE 2037 · FIRST CONTACT</span>
211
- <button id="tour-skip" class="mother-close" type="button">SKIP ×</button>
212
- </header>
213
- <div class="tour-body">
214
- <div class="tour-step" id="tour-step"></div>
215
- </div>
236
+ <dialog id="tour" class="tour" aria-label="MADRE tour">
237
+ <div id="tour-spot" class="tour-spot" aria-hidden="true"></div>
238
+ <section id="tour-card" class="tour-card" aria-live="polite">
239
+ <span id="tour-count" class="tour-count"></span>
240
+ <h3 id="tour-title"></h3>
241
+ <p id="tour-text"></p>
242
+ <ol id="tour-modes" class="tour-modes" hidden></ol>
243
+ <p id="tour-note" class="tour-note" hidden></p>
216
244
  <footer class="tour-foot">
217
- <span class="tour-dots" id="tour-dots"></span>
245
+ <button id="tour-skip" class="tour-skip" type="button"></button>
218
246
  <span class="tour-nav">
219
- <button id="tour-back" class="mother-close" type="button">BACK</button>
220
- <button id="tour-next" class="mother-close primary" type="button">NEXT ›</button>
247
+ <button id="tour-back" class="tour-back" type="button"></button>
248
+ <button id="tour-next" class="tour-next" type="button"></button>
221
249
  </span>
222
250
  </footer>
223
- </div>
251
+ </section>
224
252
  </dialog>
225
253
  <dialog id="nostromo-gate" class="mother override" aria-label="NOSTROMO access">
226
254
  <div class="mother-frame override-frame">
@@ -253,7 +281,7 @@
253
281
  <button id="nostromo-close" class="mother-close" type="button">LEAVE ×</button>
254
282
  </header>
255
283
  <canvas id="nostromo-canvas" class="nostromo-canvas" tabindex="-1" aria-label="Memories orbiting MOTHER's core"></canvas>
256
- <p class="nostromo-help">DRAG TO MOVE · WHEEL TO ZOOM · CLICK A MEMORY · DO NOT TOUCH MOTHER</p>
284
+ <p class="nostromo-help">DRAG TO TURN · SHIFT+DRAG TO MOVE · WHEEL TO ZOOM · CLICK A MEMORY · DO NOT TOUCH MOTHER</p>
257
285
  <div id="nostromo-alert" class="nostromo-alert" role="alert" hidden>
258
286
  <div class="klaxon"></div>
259
287
  <div class="panel">
@@ -282,6 +310,7 @@
282
310
  <dt>SOURCE</dt><dd id="nostromo-card-span"></dd>
283
311
  <dt>ARCHIVIST</dt><dd id="nostromo-card-agent"></dd>
284
312
  <dt>DISTILLED</dt><dd id="nostromo-card-when"></dd>
313
+ <dt>ZONE</dt><dd id="nostromo-card-zone"></dd>
285
314
  </dl>
286
315
  <section class="wire">
287
316
  <h6>LINKS <span id="nostromo-card-degree" class="n"></span></h6>
@@ -298,6 +327,23 @@
298
327
  <p id="nostromo-empty" class="nostromo-empty" hidden>NO MEMORIES YET. THE ARCHIVIST WRITES THE FIRST ONES AFTER A FEW EXCHANGES.</p>
299
328
  </div>
300
329
  </dialog>
330
+ <aside id="browser" class="browser-dock" aria-label="Browser" hidden>
331
+ <div id="browser-grip" class="dock-grip" role="separator" aria-orientation="vertical" aria-label="Resize the browser"></div>
332
+ <header class="viewer-head browser-head">
333
+ <span class="viewer-nav" id="browser-nav">
334
+ <button id="browser-back" class="nav" type="button" title="Back" aria-label="Back" disabled>‹</button>
335
+ <button id="browser-reload" class="nav" type="button" title="Reload" aria-label="Reload">↻</button>
336
+ <button id="browser-servers" class="nav ports" type="button" aria-expanded="false" aria-haspopup="menu" title="Local servers answering on this computer"><i class="ports-dot" aria-hidden="true"></i><span>SERVERS</span><i class="ports-caret" aria-hidden="true"></i></button>
337
+ </span>
338
+ <div id="browser-ports-menu" class="ports-menu" role="menu" hidden></div>
339
+ <span id="browser-where" class="browser-where"></span>
340
+ <span id="browser-sizes" class="viewer-sizes"></span>
341
+ <button id="browser-close" class="mother-close" type="button">CLOSE ×</button>
342
+ </header>
343
+ <div id="browser-tabs" class="browser-tabs" role="tablist"></div>
344
+ <div id="browser-body" class="viewer-body browsing"></div>
345
+ </aside>
346
+
301
347
  <dialog id="viewer" class="viewer" aria-label="File viewer">
302
348
  <header class="viewer-head">
303
349
  <span class="viewer-nav" id="viewer-nav" hidden>
@@ -0,0 +1,74 @@
1
+ // Which questions the room can answer without paying for them.
2
+ //
3
+ // @madre runs on Ollama, on this computer. A turn it answers costs nothing: no input tokens, no
4
+ // output tokens, no provider. Measured against the paid CLIs on the same five questions, the
5
+ // room's own bill for those turns is exactly zero, so the saving here is not an estimate or a
6
+ // comparison — it is the absence of a charge.
7
+ //
8
+ // What this file does NOT do: decide. It recognises a shape of question that the room's own
9
+ // archive is the right place to answer, and says so. Sending it anywhere is the human's, always.
10
+ // A wrong guess costs a click; a guess acted on without asking would cost trust.
11
+
12
+ // The shape: a question about what this room already said, decided or left open. Not "how does
13
+ // X work" — that needs the project read, which is what the paid agents are for.
14
+ const PATTERNS = [
15
+ // Spanish
16
+ { re: /\b(qu[eé]|cu[aá]l(es)?)\s+(hab[ií]amos\s+)?(decidimos|decidido|acordamos|acordado|quedamos|concluimos)\b/i, why: 'decision' },
17
+ { re: /\b(ya|alguna vez)\s+(hab[ií]amos\s+)?(hablamos|hablado|discutimos|discutido|vimos|visto|tratamos)\b/i, why: 'discussed' },
18
+ { re: /\b(d[oó]nde|en qu[eé] punto)\s+(nos\s+)?(quedamos|qued[oó]|dejamos)\b/i, why: 'where' },
19
+ { re: /\bpor\s+qu[eé]\s+(elegimos|escogimos|decidimos|usamos|optamos)\b/i, why: 'why' },
20
+ { re: /\b(qu[eé]\s+)?(dijo|dijimos|dije)\s+\S+\s+(sobre|de|acerca)\b/i, why: 'said' },
21
+ { re: /\bqu[eé]\s+(qued[oó]|quedaba)\s+pendiente\b/i, why: 'open' },
22
+ // English
23
+ { re: /\bwhat\s+(did|have)\s+we\s+(decide|decided|agree|agreed|conclude|concluded)\b/i, why: 'decision' },
24
+ { re: /\b(did|have)\s+we\s+(ever\s+)?(discuss|discussed|talk|talked|cover|covered)\b/i, why: 'discussed' },
25
+ { re: /\bwhere\s+(did\s+we|do\s+we)\s+(leave|left|stand)\b/i, why: 'where' },
26
+ { re: /\bwhy\s+(did\s+we|do\s+we)\s+(choose|chose|pick|picked|use|go with)\b/i, why: 'why' },
27
+ { re: /\bwhat\s+(did|does)\s+\S+\s+say\s+about\b/i, why: 'said' },
28
+ { re: /\bwhat('s| is| was)\s+(still\s+)?(open|pending|unresolved)\b/i, why: 'open' },
29
+ ];
30
+
31
+ // A question aimed at the archive rather than at the project. Returns the reason it matched, or
32
+ // null: a caller that cannot say WHY it is suggesting something should not suggest it.
33
+ export function archiveQuestion(text = '') {
34
+ const question = String(text ?? '').trim();
35
+ if (question.length < 8 || question.length > 400) return null;
36
+ // An explicit address wins over any guess: the human already chose who answers.
37
+ if (/(^|\s)@[a-z0-9_-]+/i.test(question)) return null;
38
+ // A request to do something is not a question about what was said, however it is worded.
39
+ if (/\b(crea|escribe|implementa|arregla|corre|ejecuta|borra|create|write|implement|fix|run|delete|refactor)\b/i.test(question)) return null;
40
+ for (const pattern of PATTERNS) if (pattern.re.test(question)) return pattern.why;
41
+ return null;
42
+ }
43
+
44
+ // An agent saying, in prose, that it cannot do this at the mode it was given.
45
+ //
46
+ // The room could only ever ask for one escalation: a plan step that wants #2 (room.mjs calls
47
+ // askForMode with the number written in). An agent in a direct turn that needs #3 has no channel
48
+ // at all — it can only say so in a sentence and hope the human reads it, leaves the message, and
49
+ // finds the mode selector. That gap is what a real session turned into a loop: the human granted
50
+ // #2 twice while the reply kept saying the fix was editing a file that already exists, which is
51
+ // #3, and the turn went nowhere until they worked it out themselves.
52
+ //
53
+ // So this reads the reply. It is a heuristic and it is narrow on purpose: a bare "#3" is not
54
+ // enough, because an agent explaining the ladder mentions every rung. There has to be a word of
55
+ // need beside it, and the mode has to be above the one the turn actually ran at.
56
+ //
57
+ // It costs nothing in any prompt. And it never grants: #3 and #4 still open the ceremony where
58
+ // the human types the project designation, exactly as if they had chosen it themselves.
59
+ 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
+
61
+ export function modeAsked(text = '', { ran = 1 } = {}) {
62
+ const reply = String(text ?? '');
63
+ if (reply.length > 8000) return null;
64
+ let wanted = null;
65
+ for (const match of reply.matchAll(/#([2-4])\b/g)) {
66
+ const mode = Number(match[1]);
67
+ if (mode <= ran) continue;
68
+ // The word of need has to sit beside the number, not anywhere in a long answer.
69
+ const around = reply.slice(Math.max(0, match.index - 90), match.index + 90);
70
+ if (!NEEDS.test(around)) continue;
71
+ if (wanted === null || mode > wanted) wanted = mode;
72
+ }
73
+ return wanted;
74
+ }