@jmanuelcorral/openteam 0.1.38 → 0.1.40

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 (83) hide show
  1. package/.opencode/command/openteam.md +5 -5
  2. package/AGENTS.md +27 -27
  3. package/README.es.md +759 -0
  4. package/README.md +213 -480
  5. package/dist/capabilities/types.d.ts +1 -1
  6. package/dist/cli/consoleServe.d.ts.map +1 -1
  7. package/dist/cli.js +1085 -231
  8. package/dist/commands/frontierCatalog.d.ts +27 -0
  9. package/dist/commands/frontierCatalog.d.ts.map +1 -0
  10. package/dist/commands/orchestratorAgent.d.ts +2 -1
  11. package/dist/commands/orchestratorAgent.d.ts.map +1 -1
  12. package/dist/commands/setup.d.ts +3 -18
  13. package/dist/commands/setup.d.ts.map +1 -1
  14. package/dist/config/schema.d.ts +196 -0
  15. package/dist/config/schema.d.ts.map +1 -1
  16. package/dist/console/assets.d.ts +9 -0
  17. package/dist/console/assets.d.ts.map +1 -0
  18. package/dist/console/render.d.ts.map +1 -1
  19. package/dist/contract/opencode.d.ts +1 -1
  20. package/dist/contract/opencode.d.ts.map +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +835 -87
  23. package/dist/local/embeddings.d.ts +29 -0
  24. package/dist/local/embeddings.d.ts.map +1 -0
  25. package/dist/local/extractClient.d.ts +31 -0
  26. package/dist/local/extractClient.d.ts.map +1 -0
  27. package/dist/local/openai-compatible.d.ts +1 -0
  28. package/dist/local/openai-compatible.d.ts.map +1 -1
  29. package/dist/mcp/semanticRecall.d.ts +25 -0
  30. package/dist/mcp/semanticRecall.d.ts.map +1 -0
  31. package/dist/mcp/server.d.ts +22 -0
  32. package/dist/mcp/server.d.ts.map +1 -1
  33. package/dist/memory/consolidate.d.ts +21 -0
  34. package/dist/memory/consolidate.d.ts.map +1 -0
  35. package/dist/memory/extract.d.ts +68 -0
  36. package/dist/memory/extract.d.ts.map +1 -0
  37. package/dist/memory/fold.d.ts +24 -0
  38. package/dist/memory/fold.d.ts.map +1 -0
  39. package/dist/memory/index.d.ts +18 -0
  40. package/dist/memory/index.d.ts.map +1 -0
  41. package/dist/memory/inject.d.ts +29 -0
  42. package/dist/memory/inject.d.ts.map +1 -0
  43. package/dist/memory/log.d.ts +16 -0
  44. package/dist/memory/log.d.ts.map +1 -0
  45. package/dist/memory/merge.d.ts +38 -0
  46. package/dist/memory/merge.d.ts.map +1 -0
  47. package/dist/memory/rank.d.ts +32 -0
  48. package/dist/memory/rank.d.ts.map +1 -0
  49. package/dist/memory/recall.d.ts +30 -0
  50. package/dist/memory/recall.d.ts.map +1 -0
  51. package/dist/memory/temporal.d.ts +39 -0
  52. package/dist/memory/temporal.d.ts.map +1 -0
  53. package/dist/memory/types.d.ts +159 -0
  54. package/dist/memory/types.d.ts.map +1 -0
  55. package/dist/memory/vector.d.ts +18 -0
  56. package/dist/memory/vector.d.ts.map +1 -0
  57. package/dist/orchestrator/roles.d.ts +1 -1
  58. package/dist/orchestrator/roles.d.ts.map +1 -1
  59. package/dist/plugin/hooks.d.ts +13 -0
  60. package/dist/plugin/hooks.d.ts.map +1 -1
  61. package/dist/plugin/memoryInject.d.ts +25 -0
  62. package/dist/plugin/memoryInject.d.ts.map +1 -0
  63. package/dist/router/chooseModel.d.ts.map +1 -1
  64. package/dist/router/cost.d.ts +4 -0
  65. package/dist/router/cost.d.ts.map +1 -0
  66. package/dist/router/frontierBaseline.d.ts +13 -0
  67. package/dist/router/frontierBaseline.d.ts.map +1 -0
  68. package/dist/router/modelSelection.d.ts +18 -0
  69. package/dist/router/modelSelection.d.ts.map +1 -0
  70. package/dist/storage/index/memoryConsolidate.d.ts +24 -0
  71. package/dist/storage/index/memoryConsolidate.d.ts.map +1 -0
  72. package/dist/storage/index/memoryIndex.d.ts +52 -0
  73. package/dist/storage/index/memoryIndex.d.ts.map +1 -0
  74. package/dist/storage/index/memoryRecall.d.ts +23 -0
  75. package/dist/storage/index/memoryRecall.d.ts.map +1 -0
  76. package/dist/storage/index/memoryRuntime.d.ts +41 -0
  77. package/dist/storage/index/memoryRuntime.d.ts.map +1 -0
  78. package/dist/telemetry/events.d.ts +3 -1
  79. package/dist/telemetry/events.d.ts.map +1 -1
  80. package/dist/telemetry/hash.d.ts.map +1 -1
  81. package/dist/web/server.d.ts +4 -0
  82. package/dist/web/server.d.ts.map +1 -1
  83. package/package.json +14 -6
package/dist/cli.js CHANGED
@@ -674,6 +674,50 @@ var LocalRuntimeSchema = z3.object({
674
674
  discovery: z3.enum(["cli", "sdk", "manual"]).optional(),
675
675
  defaultModel: ModelRefSchema
676
676
  });
677
+ var MemoryScopeSchema = z3.enum(["project", "user"]);
678
+ var MemoryExtractionModeSchema = z3.enum([
679
+ "local",
680
+ "localWithFrontierFallback"
681
+ ]);
682
+ var MemoryEmbeddingsProviderSchema = z3.enum(["local", "none"]);
683
+ var MemorySemanticSchema = z3.object({
684
+ enabled: z3.boolean().default(false),
685
+ storeContent: z3.boolean().default(false),
686
+ logPath: z3.string().min(1).default(".opencode/openteam/memory/records.jsonl"),
687
+ indexPath: z3.string().min(1).default(".opencode/openteam/memory/index.sqlite"),
688
+ scope: MemoryScopeSchema.default("project"),
689
+ extraction: z3.object({
690
+ mode: MemoryExtractionModeSchema.default("local"),
691
+ model: ModelRefSchema.nullable().default(null)
692
+ }).default({ mode: "local", model: null }),
693
+ embeddings: z3.object({
694
+ provider: MemoryEmbeddingsProviderSchema.default("local"),
695
+ model: ModelRefSchema.nullable().default(null),
696
+ dim: z3.number().int().positive().default(768)
697
+ }).default({ provider: "local", model: null, dim: 768 }),
698
+ recall: z3.object({
699
+ maxResults: z3.number().int().positive().default(8),
700
+ minSimilarity: z3.number().min(0).max(1).default(0.2)
701
+ }).default({ maxResults: 8, minSimilarity: 0.2 }),
702
+ injection: z3.object({
703
+ enabled: z3.boolean().default(false),
704
+ maxChars: z3.number().int().positive().default(1200),
705
+ maxItems: z3.number().int().positive().default(8)
706
+ }).default({ enabled: false, maxChars: 1200, maxItems: 8 })
707
+ }).default({
708
+ enabled: false,
709
+ storeContent: false,
710
+ logPath: ".opencode/openteam/memory/records.jsonl",
711
+ indexPath: ".opencode/openteam/memory/index.sqlite",
712
+ scope: "project",
713
+ extraction: { mode: "local", model: null },
714
+ embeddings: { provider: "local", model: null, dim: 768 },
715
+ recall: { maxResults: 8, minSimilarity: 0.2 },
716
+ injection: { enabled: false, maxChars: 1200, maxItems: 8 }
717
+ });
718
+ var MemoryConfigSchema = z3.object({
719
+ semantic: MemorySemanticSchema
720
+ });
677
721
  var OpenTeamConfigObjectSchema = z3.object({
678
722
  baseline: z3.object({
679
723
  mode: BaselineModeSchema.default("auto"),
@@ -724,9 +768,13 @@ var OpenTeamConfigObjectSchema = z3.object({
724
768
  }).default({ hardStopOnBudgetExhaustion: false }),
725
769
  privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
726
770
  console: ConsoleConfigSchema,
727
- storage: StorageConfigSchema
771
+ storage: StorageConfigSchema,
772
+ memory: MemoryConfigSchema.optional()
728
773
  });
729
774
  var OpenTeamConfigSchema = OpenTeamConfigObjectSchema;
775
+ function resolveMemorySemantic(config) {
776
+ return config.memory?.semantic ?? MemorySemanticSchema.parse(undefined);
777
+ }
730
778
 
731
779
  // src/telemetry/events.ts
732
780
  var EVENT_SCHEMA_VERSION = 1;
@@ -1182,6 +1230,208 @@ import {
1182
1230
  createServer as createHttpServer
1183
1231
  } from "node:http";
1184
1232
 
1233
+ // src/console/assets.ts
1234
+ var CONSOLE_STYLE = `
1235
+ :root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
1236
+ *{box-sizing:border-box}
1237
+ body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}
1238
+ header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
1239
+ header h1{font-size:18px;margin:0}
1240
+ header .meta{color:var(--muted);font-size:12px}
1241
+ main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}
1242
+ .panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px}
1243
+ .panel h2{font-size:14px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
1244
+ .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
1245
+ .stat{display:flex;flex-direction:column;gap:2px}
1246
+ .stat .k{color:var(--muted);font-size:12px}
1247
+ .stat .v{font-size:18px;font-weight:600}
1248
+ .good{color:var(--good)}
1249
+ .local{color:var(--local)}
1250
+ .frontier{color:var(--frontier)}
1251
+ .muted{color:var(--muted)}
1252
+ table{width:100%;border-collapse:collapse;font-size:13px}
1253
+ th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)}
1254
+ th{color:var(--muted);font-weight:600}
1255
+ .hash{font-family:ui-monospace,Consolas,monospace;color:var(--muted)}
1256
+ .progress{height:10px;background:#0b0d11;border-radius:6px;overflow:hidden;border:1px solid var(--line)}
1257
+ .progress .bar{height:100%;background:var(--good)}
1258
+ ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}
1259
+ ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
1260
+ ul.items li.done{color:var(--muted)}
1261
+ ul.items .box{font-family:monospace}
1262
+ .who{color:var(--local)}
1263
+ .tag{color:var(--frontier);font-size:12px}
1264
+ .kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
1265
+ code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
1266
+ .mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
1267
+ .tabs{display:flex;gap:6px;padding:12px 24px 0;flex-wrap:wrap;border-bottom:1px solid var(--line)}
1268
+ .tabs .tab{background:var(--panel);color:var(--fg);border:1px solid var(--line);border-bottom:none;border-radius:8px 8px 0 0;padding:8px 14px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:6px}
1269
+ .tabs .tab.active{background:var(--bg);border-color:var(--local);color:var(--fg)}
1270
+ .dot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--muted)}
1271
+ .badge{font-size:11px;padding:1px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted);vertical-align:middle}
1272
+ .status-running{background:var(--good)}
1273
+ .badge.status-running{color:var(--good);border-color:var(--good)}
1274
+ .status-idle{background:var(--muted)}
1275
+ .badge.status-idle{color:var(--muted)}
1276
+ .status-waiting-input{background:var(--frontier)}
1277
+ .badge.status-waiting-input{color:var(--frontier);border-color:var(--frontier)}
1278
+ .status-ended{background:#6e7681}
1279
+ .badge.status-ended{color:#6e7681}
1280
+ .term .termlog{background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap}
1281
+ .term .tl{padding:1px 0}
1282
+ .term .tl.input{color:var(--local)}
1283
+ .term .tl.tool{color:var(--frontier)}
1284
+ .term .tl.status{color:var(--muted)}
1285
+ .term .tl.error{color:#f85149}
1286
+ .term .tl.permission{color:var(--frontier);font-weight:600}
1287
+ .term form.terminput{display:flex;gap:8px;margin-top:10px}
1288
+ .term form.terminput input{flex:1;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
1289
+ .term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600}
1290
+ .pty .ptylog{background:#05070a;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap;color:#c8d1dc}
1291
+ .pty form.ptyinput{display:flex;gap:8px;margin-top:10px}
1292
+ .pty form.ptyinput input{flex:1;background:#05070a;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
1293
+ .pty form.ptyinput button,.pty .pty-start{background:var(--frontier);color:#1a1200;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600;margin-top:8px}
1294
+ .pty .pty-start:disabled{opacity:.5;cursor:default}
1295
+ `;
1296
+ var CONSOLE_CLIENT_JS = `
1297
+ (function(){
1298
+ var authTok='';
1299
+ try{
1300
+ var qp=new URLSearchParams(window.location.search).get('token');
1301
+ if(qp){authTok='?token='+encodeURIComponent(qp)}
1302
+ }catch(e){/* ignore */}
1303
+ function reloadIfChanged(prev){
1304
+ fetch('/api/state'+authTok).then(function(r){return r.json()}).then(function(s){
1305
+ if(s.generatedAt!==prev){location.reload()}
1306
+ }).catch(function(){});
1307
+ }
1308
+ var current=document.documentElement.getAttribute('data-generated')||'';
1309
+ if('EventSource' in window){
1310
+ try{
1311
+ var es=new EventSource('/events'+authTok);
1312
+ es.addEventListener('state',function(){location.reload()});
1313
+ es.onerror=function(){/* fallback below */};
1314
+ }catch(e){/* ignore */}
1315
+ }
1316
+ var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
1317
+ setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
1318
+
1319
+ function selectTab(name){
1320
+ var tabs=document.querySelectorAll('#tabbar .tab');
1321
+ for(var i=0;i<tabs.length;i++){tabs[i].classList.toggle('active',tabs[i].getAttribute('data-tab')===name)}
1322
+ var views=document.querySelectorAll('main .view');
1323
+ var matched=false;
1324
+ for(var j=0;j<views.length;j++){
1325
+ var show=views[j].getAttribute('data-view')===name;
1326
+ views[j].hidden=!show;
1327
+ if(show){matched=true;if(name!=='all'){connectTerminal(views[j],name)}}
1328
+ }
1329
+ if(!matched){return false}
1330
+ try{sessionStorage.setItem('openteam.tab',name)}catch(e){}
1331
+ return true;
1332
+ }
1333
+ function connectTerminal(view,id){
1334
+ if(view.__wired){return}
1335
+ var root=document.documentElement;
1336
+ var token=root.getAttribute('data-token');
1337
+ if(root.getAttribute('data-terminal')!=='1'||!token){return}
1338
+ var log=view.querySelector('.termlog');
1339
+ if(!log){return}
1340
+ view.__wired=true;
1341
+ function append(cls,text){
1342
+ var line=document.createElement('div');
1343
+ line.className='tl '+cls;line.textContent=text;
1344
+ log.appendChild(line);log.scrollTop=log.scrollHeight;
1345
+ }
1346
+ var q='/console/session/'+encodeURIComponent(id);
1347
+ var tk='token='+encodeURIComponent(token);
1348
+ if('EventSource' in window){
1349
+ try{
1350
+ var es=new EventSource(q+'/stream?'+tk);
1351
+ es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
1352
+ es.onerror=function(){/* reconecta solo */};
1353
+ }catch(_){/* ignore */}
1354
+ }
1355
+ var form=view.querySelector('form.terminput');
1356
+ if(form){
1357
+ form.addEventListener('submit',function(e){
1358
+ e.preventDefault();
1359
+ var inp=form.querySelector('input[name=text]');
1360
+ var text=inp.value;
1361
+ if(!text){return}
1362
+ inp.value='';append('input','› '+text);
1363
+ fetch(q+'/input?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text:text})})
1364
+ .then(function(r){if(!r.ok){append('error','✖ envío rechazado ('+r.status+')')}})
1365
+ .catch(function(){append('error','✖ no se pudo enviar')});
1366
+ });
1367
+ }
1368
+ connectPty(view,id,q,tk);
1369
+ }
1370
+ function connectPty(view,id,q,tk){
1371
+ var root=document.documentElement;
1372
+ if(root.getAttribute('data-pty')!=='1'){return}
1373
+ var pane=view.querySelector('.pty');
1374
+ if(!pane){return}
1375
+ var log=pane.querySelector('.ptylog');
1376
+ var startBtn=pane.querySelector('.pty-start');
1377
+ var input=pane.querySelector('form.ptyinput');
1378
+ function plog(text){
1379
+ var d=document.createElement('span');d.textContent=text;
1380
+ log.appendChild(d);log.scrollTop=log.scrollHeight;
1381
+ }
1382
+ if(!startBtn){return}
1383
+ startBtn.addEventListener('click',function(){
1384
+ startBtn.disabled=true;plog('Abriendo shell…
1385
+ ');
1386
+ fetch(q+'/pty?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:'{}'})
1387
+ .then(function(r){return r.ok?r.json():Promise.reject(r.status)})
1388
+ .then(function(info){openPtyWs(info.wsUrl)})
1389
+ .catch(function(s){plog('✖ no se pudo abrir PTY ('+s+')
1390
+ ');startBtn.disabled=false});
1391
+ });
1392
+ function openPtyWs(url){
1393
+ try{
1394
+ var ws=new WebSocket(url);
1395
+ ws.binaryType='arraybuffer';
1396
+ ws.onmessage=function(e){
1397
+ var data=e.data;
1398
+ if(data instanceof ArrayBuffer){data=new TextDecoder().decode(new Uint8Array(data))}
1399
+ plog(data);
1400
+ };
1401
+ ws.onclose=function(){plog('
1402
+ [shell cerrada]
1403
+ ');startBtn.disabled=false};
1404
+ ws.onerror=function(){plog('
1405
+ ✖ error de WebSocket
1406
+ ')};
1407
+ if(input){
1408
+ input.hidden=false;
1409
+ input.addEventListener('submit',function(e){
1410
+ e.preventDefault();
1411
+ var c=input.querySelector('input[name=cmd]');
1412
+ if(!c.value){return}
1413
+ ws.send(c.value+'\r');c.value='';
1414
+ });
1415
+ }
1416
+ }catch(_){plog('
1417
+ ✖ WebSocket no disponible
1418
+ ');startBtn.disabled=false}
1419
+ }
1420
+ }
1421
+ var bar=document.getElementById('tabbar');
1422
+ if(bar){
1423
+ bar.addEventListener('click',function(e){
1424
+ var t=e.target;
1425
+ while(t&&t!==bar&&!t.getAttribute('data-tab')){t=t.parentNode}
1426
+ if(t&&t.getAttribute('data-tab')){selectTab(t.getAttribute('data-tab'))}
1427
+ });
1428
+ }
1429
+ var saved='all';
1430
+ try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
1431
+ if(!selectTab(saved)){selectTab('all')}
1432
+ })();
1433
+ `;
1434
+
1185
1435
  // src/console/render.ts
1186
1436
  var TIERS = [
1187
1437
  "trivial",
@@ -1497,201 +1747,6 @@ function sessionView(tab, terminalEnabled, ptyEnabled) {
1497
1747
  ].join("");
1498
1748
  return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
1499
1749
  }
1500
- var STYLE = `
1501
- :root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
1502
- *{box-sizing:border-box}
1503
- body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}
1504
- header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
1505
- header h1{font-size:18px;margin:0}
1506
- header .meta{color:var(--muted);font-size:12px}
1507
- main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}
1508
- .panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px}
1509
- .panel h2{font-size:14px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
1510
- .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
1511
- .stat{display:flex;flex-direction:column;gap:2px}
1512
- .stat .k{color:var(--muted);font-size:12px}
1513
- .stat .v{font-size:18px;font-weight:600}
1514
- .good{color:var(--good)}
1515
- .local{color:var(--local)}
1516
- .frontier{color:var(--frontier)}
1517
- .muted{color:var(--muted)}
1518
- table{width:100%;border-collapse:collapse;font-size:13px}
1519
- th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)}
1520
- th{color:var(--muted);font-weight:600}
1521
- .hash{font-family:ui-monospace,Consolas,monospace;color:var(--muted)}
1522
- .progress{height:10px;background:#0b0d11;border-radius:6px;overflow:hidden;border:1px solid var(--line)}
1523
- .progress .bar{height:100%;background:var(--good)}
1524
- ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}
1525
- ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
1526
- ul.items li.done{color:var(--muted)}
1527
- ul.items .box{font-family:monospace}
1528
- .who{color:var(--local)}
1529
- .tag{color:var(--frontier);font-size:12px}
1530
- .kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
1531
- code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
1532
- .mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
1533
- .tabs{display:flex;gap:6px;padding:12px 24px 0;flex-wrap:wrap;border-bottom:1px solid var(--line)}
1534
- .tabs .tab{background:var(--panel);color:var(--fg);border:1px solid var(--line);border-bottom:none;border-radius:8px 8px 0 0;padding:8px 14px;cursor:pointer;font:inherit;display:flex;align-items:center;gap:6px}
1535
- .tabs .tab.active{background:var(--bg);border-color:var(--local);color:var(--fg)}
1536
- .dot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--muted)}
1537
- .badge{font-size:11px;padding:1px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted);vertical-align:middle}
1538
- .status-running{background:var(--good)}
1539
- .badge.status-running{color:var(--good);border-color:var(--good)}
1540
- .status-idle{background:var(--muted)}
1541
- .badge.status-idle{color:var(--muted)}
1542
- .status-waiting-input{background:var(--frontier)}
1543
- .badge.status-waiting-input{color:var(--frontier);border-color:var(--frontier)}
1544
- .status-ended{background:#6e7681}
1545
- .badge.status-ended{color:#6e7681}
1546
- .term .termlog{background:#0b0d11;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap}
1547
- .term .tl{padding:1px 0}
1548
- .term .tl.input{color:var(--local)}
1549
- .term .tl.tool{color:var(--frontier)}
1550
- .term .tl.status{color:var(--muted)}
1551
- .term .tl.error{color:#f85149}
1552
- .term .tl.permission{color:var(--frontier);font-weight:600}
1553
- .term form.terminput{display:flex;gap:8px;margin-top:10px}
1554
- .term form.terminput input{flex:1;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
1555
- .term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600}
1556
- .pty .ptylog{background:#05070a;border:1px solid var(--line);border-radius:8px;padding:10px;max-height:340px;overflow:auto;font-family:ui-monospace,Consolas,monospace;font-size:12px;white-space:pre-wrap;color:#c8d1dc}
1557
- .pty form.ptyinput{display:flex;gap:8px;margin-top:10px}
1558
- .pty form.ptyinput input{flex:1;background:#05070a;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
1559
- .pty form.ptyinput button,.pty .pty-start{background:var(--frontier);color:#1a1200;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600;margin-top:8px}
1560
- .pty .pty-start:disabled{opacity:.5;cursor:default}
1561
- `;
1562
- var CLIENT_JS = `
1563
- (function(){
1564
- function reloadIfChanged(prev){
1565
- fetch('/api/state').then(function(r){return r.json()}).then(function(s){
1566
- if(s.generatedAt!==prev){location.reload()}
1567
- }).catch(function(){});
1568
- }
1569
- var current=document.documentElement.getAttribute('data-generated')||'';
1570
- if('EventSource' in window){
1571
- try{
1572
- var es=new EventSource('/events');
1573
- es.addEventListener('state',function(){location.reload()});
1574
- es.onerror=function(){/* fallback below */};
1575
- }catch(e){/* ignore */}
1576
- }
1577
- var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
1578
- setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
1579
-
1580
- function selectTab(name){
1581
- var tabs=document.querySelectorAll('#tabbar .tab');
1582
- for(var i=0;i<tabs.length;i++){tabs[i].classList.toggle('active',tabs[i].getAttribute('data-tab')===name)}
1583
- var views=document.querySelectorAll('main .view');
1584
- var matched=false;
1585
- for(var j=0;j<views.length;j++){
1586
- var show=views[j].getAttribute('data-view')===name;
1587
- views[j].hidden=!show;
1588
- if(show){matched=true;if(name!=='all'){connectTerminal(views[j],name)}}
1589
- }
1590
- if(!matched){return false}
1591
- try{sessionStorage.setItem('openteam.tab',name)}catch(e){}
1592
- return true;
1593
- }
1594
- function connectTerminal(view,id){
1595
- if(view.__wired){return}
1596
- var root=document.documentElement;
1597
- var token=root.getAttribute('data-token');
1598
- if(root.getAttribute('data-terminal')!=='1'||!token){return}
1599
- var log=view.querySelector('.termlog');
1600
- if(!log){return}
1601
- view.__wired=true;
1602
- function append(cls,text){
1603
- var line=document.createElement('div');
1604
- line.className='tl '+cls;line.textContent=text;
1605
- log.appendChild(line);log.scrollTop=log.scrollHeight;
1606
- }
1607
- var q='/console/session/'+encodeURIComponent(id);
1608
- var tk='token='+encodeURIComponent(token);
1609
- if('EventSource' in window){
1610
- try{
1611
- var es=new EventSource(q+'/stream?'+tk);
1612
- es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
1613
- es.onerror=function(){/* reconecta solo */};
1614
- }catch(_){/* ignore */}
1615
- }
1616
- var form=view.querySelector('form.terminput');
1617
- if(form){
1618
- form.addEventListener('submit',function(e){
1619
- e.preventDefault();
1620
- var inp=form.querySelector('input[name=text]');
1621
- var text=inp.value;
1622
- if(!text){return}
1623
- inp.value='';append('input','› '+text);
1624
- fetch(q+'/input?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text:text})})
1625
- .then(function(r){if(!r.ok){append('error','✖ envío rechazado ('+r.status+')')}})
1626
- .catch(function(){append('error','✖ no se pudo enviar')});
1627
- });
1628
- }
1629
- connectPty(view,id,q,tk);
1630
- }
1631
- function connectPty(view,id,q,tk){
1632
- var root=document.documentElement;
1633
- if(root.getAttribute('data-pty')!=='1'){return}
1634
- var pane=view.querySelector('.pty');
1635
- if(!pane){return}
1636
- var log=pane.querySelector('.ptylog');
1637
- var startBtn=pane.querySelector('.pty-start');
1638
- var input=pane.querySelector('form.ptyinput');
1639
- function plog(text){
1640
- var d=document.createElement('span');d.textContent=text;
1641
- log.appendChild(d);log.scrollTop=log.scrollHeight;
1642
- }
1643
- if(!startBtn){return}
1644
- startBtn.addEventListener('click',function(){
1645
- startBtn.disabled=true;plog('Abriendo shell…
1646
- ');
1647
- fetch(q+'/pty?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:'{}'})
1648
- .then(function(r){return r.ok?r.json():Promise.reject(r.status)})
1649
- .then(function(info){openPtyWs(info.wsUrl)})
1650
- .catch(function(s){plog('✖ no se pudo abrir PTY ('+s+')
1651
- ');startBtn.disabled=false});
1652
- });
1653
- function openPtyWs(url){
1654
- try{
1655
- var ws=new WebSocket(url);
1656
- ws.binaryType='arraybuffer';
1657
- ws.onmessage=function(e){
1658
- var data=e.data;
1659
- if(data instanceof ArrayBuffer){data=new TextDecoder().decode(new Uint8Array(data))}
1660
- plog(data);
1661
- };
1662
- ws.onclose=function(){plog('
1663
- [shell cerrada]
1664
- ');startBtn.disabled=false};
1665
- ws.onerror=function(){plog('
1666
- ✖ error de WebSocket
1667
- ')};
1668
- if(input){
1669
- input.hidden=false;
1670
- input.addEventListener('submit',function(e){
1671
- e.preventDefault();
1672
- var c=input.querySelector('input[name=cmd]');
1673
- if(!c.value){return}
1674
- ws.send(c.value+'\r');c.value='';
1675
- });
1676
- }
1677
- }catch(_){plog('
1678
- ✖ WebSocket no disponible
1679
- ');startBtn.disabled=false}
1680
- }
1681
- }
1682
- var bar=document.getElementById('tabbar');
1683
- if(bar){
1684
- bar.addEventListener('click',function(e){
1685
- var t=e.target;
1686
- while(t&&t!==bar&&!t.getAttribute('data-tab')){t=t.parentNode}
1687
- if(t&&t.getAttribute('data-tab')){selectTab(t.getAttribute('data-tab'))}
1688
- });
1689
- }
1690
- var saved='all';
1691
- try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
1692
- if(!selectTab(saved)){selectTab('all')}
1693
- })();
1694
- `;
1695
1750
  function renderConsoleHtml(state, options = {}) {
1696
1751
  const refreshMs = options.refreshMs ?? 2000;
1697
1752
  const terminalEnabled = options.terminalEnabled === true;
@@ -1720,7 +1775,7 @@ function renderConsoleHtml(state, options = {}) {
1720
1775
  '<meta charset="utf-8">',
1721
1776
  '<meta name="viewport" content="width=device-width,initial-scale=1">',
1722
1777
  "<title>openteam console</title>",
1723
- `<style>${STYLE}</style>`,
1778
+ `<style>${CONSOLE_STYLE}</style>`,
1724
1779
  "</head>",
1725
1780
  "<body>",
1726
1781
  "<header>",
@@ -1729,7 +1784,7 @@ function renderConsoleHtml(state, options = {}) {
1729
1784
  "</header>",
1730
1785
  tabBar(state.sessionsDetail),
1731
1786
  `<main>${body}</main>`,
1732
- `<script>${CLIENT_JS}</script>`,
1787
+ `<script>${CONSOLE_CLIENT_JS}</script>`,
1733
1788
  "</body>",
1734
1789
  "</html>"
1735
1790
  ].join("");
@@ -2359,6 +2414,18 @@ var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
2359
2414
  function isAddressInUse(error) {
2360
2415
  return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
2361
2416
  }
2417
+ function bearerFromHeader3(request) {
2418
+ const header = request.headers.authorization;
2419
+ if (typeof header !== "string" || !header.startsWith("Bearer ")) {
2420
+ return;
2421
+ }
2422
+ const value = header.slice("Bearer ".length).trim();
2423
+ return value.length === 0 ? undefined : value;
2424
+ }
2425
+ function readAuthorized(request, url, token) {
2426
+ const provided = tokenFromQuery(url.searchParams) ?? bearerFromHeader3(request);
2427
+ return provided !== undefined && safeEqualToken(provided, token);
2428
+ }
2362
2429
  async function renderState(deps) {
2363
2430
  return renderConsoleHtml(buildConsoleState(await deps.readSnapshot()), {
2364
2431
  refreshMs: deps.config.refreshMs,
@@ -2426,6 +2493,11 @@ async function createConsoleServer(deps) {
2426
2493
  return;
2427
2494
  }
2428
2495
  const path = url2.pathname;
2496
+ const remoteToken = deps.storageApi?.token;
2497
+ if (remoteToken !== undefined && (path === "/" || path === "/index.html" || path === "/api/state" || path === "/events") && !readAuthorized(request, url2, remoteToken)) {
2498
+ response.writeHead(401, JSON_HEADERS3).end(JSON.stringify({ error: "unauthorized" }));
2499
+ return;
2500
+ }
2429
2501
  if (path === "/" || path === "/index.html") {
2430
2502
  response.writeHead(200, HTML_HEADERS).end(await renderState(deps));
2431
2503
  return;
@@ -2465,7 +2537,12 @@ async function createConsoleServer(deps) {
2465
2537
  break;
2466
2538
  }
2467
2539
  if (await tryListen(server, deps.config.host, port)) {
2468
- boundPort = port;
2540
+ const address = server.address();
2541
+ if (typeof address !== "object" || address === null) {
2542
+ server.close();
2543
+ throw new Error("console: bound address is unavailable");
2544
+ }
2545
+ boundPort = address.port;
2469
2546
  bound = true;
2470
2547
  break;
2471
2548
  }
@@ -2899,6 +2976,8 @@ async function runConsoleServe(deps, options) {
2899
2976
  deps.log("Escuchando en loopback. Pulsa Ctrl+C para parar.");
2900
2977
  if (config.console.remoteStorage) {
2901
2978
  deps.log("Storage remoto expuesto en /storage/* (read-only, requiere token).");
2979
+ deps.log("La consola y el estado exigen token; ábrela con la URL firmada:");
2980
+ deps.log(` ${runtime.url}/?token=${token}`);
2902
2981
  }
2903
2982
  if (config.storage.sqliteIndex.enabled) {
2904
2983
  const buildIndex = deps.buildSqliteIndex ?? buildSqliteIndexFromConfig;
@@ -3220,6 +3299,9 @@ function normalizeBaseURL(baseURL) {
3220
3299
  function modelsEndpoint(baseURL) {
3221
3300
  return `${normalizeBaseURL(baseURL)}/models`;
3222
3301
  }
3302
+ function embeddingsEndpoint(baseURL) {
3303
+ return `${normalizeBaseURL(baseURL)}/embeddings`;
3304
+ }
3223
3305
  function errorMessage(error) {
3224
3306
  if (error instanceof Error) {
3225
3307
  return error.message;
@@ -4031,7 +4113,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
4031
4113
  ];
4032
4114
  const frontmatter = [
4033
4115
  "---",
4034
- 'description: "Orquestador openteam estilo Squad: en cada petición comprueba si existe equipo; si no, entiende las tareas y crea el equipo de subagentes a demanda, planifica dependencias, paraleliza el trabajo independiente y mantiene el todo con el responsable de cada tarea, con routing local-first."',
4116
+ 'description: "Orquestador multiagente de openteam: en cada petición comprueba si existe equipo; si no, entiende las tareas y crea el equipo de subagentes a demanda, planifica dependencias, paraleliza el trabajo independiente y mantiene el todo con el responsable de cada tarea, con routing local-first."',
4035
4117
  "mode: primary",
4036
4118
  `model: ${model}`,
4037
4119
  "temperature: 0.2",
@@ -4040,7 +4122,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
4040
4122
  ].join(`
4041
4123
  `);
4042
4124
  const body = [
4043
- "Eres el orquestador de **openteam**, al estilo Squad. Eres el único agente",
4125
+ "Eres el orquestador multiagente de **openteam**. Eres el único agente",
4044
4126
  "primario: no existe un equipo precreado. Tú **construyes el equipo de",
4045
4127
  "subagentes a demanda** según el objetivo del usuario y del repositorio.",
4046
4128
  "",
@@ -4066,7 +4148,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
4066
4148
  "",
4067
4149
  "## Casting: elige un universo y nombra al equipo",
4068
4150
  "",
4069
- "Como Squad, el equipo tiene una identidad temática persistente:",
4151
+ "El equipo tiene una identidad temática persistente:",
4070
4152
  "",
4071
4153
  "1. Al crear el equipo por primera vez, **elige un universo** temático (p. ej.",
4072
4154
  " una película, serie, cómic o mitología). Si el usuario tiene preferencia,",
@@ -4251,9 +4333,9 @@ var HELP = [
4251
4333
  " openteam local on Reactiva el routing local-first",
4252
4334
  " openteam doctor Diagnóstico de runtimes y config",
4253
4335
  " openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
4254
- " openteam console Lanza la Console web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
4255
- " openteam console --status Muestra la config de la Console sin lanzarla",
4256
- " openteam tunnel Expone la Console vía Dev Tunnels (--terminal expone opencode; Ctrl+C para cerrar)",
4336
+ " openteam console [alfa] Lanza la Console web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
4337
+ " openteam console --status [alfa] Muestra la config de la Console sin lanzarla",
4338
+ " openteam tunnel [alfa] Expone la Console vía Dev Tunnels (--terminal expone opencode; Ctrl+C para cerrar)",
4257
4339
  " openteam mcp Servidor MCP de solo lectura (memoria del equipo) sobre stdio",
4258
4340
  " openteam report Resumen de coste/ahorro (telemetría)",
4259
4341
  " openteam yolo status Muestra si el modo YOLO está activo",
@@ -4497,6 +4579,32 @@ async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, stora
4497
4579
  await storage.write(path, serializeOpenTeamConfig(config));
4498
4580
  }
4499
4581
 
4582
+ // src/commands/frontierCatalog.ts
4583
+ function weightedCost(inputUSD, outputUSD) {
4584
+ return 0.75 * inputUSD + 0.25 * outputUSD;
4585
+ }
4586
+ function profileWeightedCost(profile) {
4587
+ return weightedCost(profile.costPer1M.inputUSD, profile.costPer1M.outputUSD);
4588
+ }
4589
+ function sortFrontierByCost(profiles) {
4590
+ return [...profiles].sort((a, b) => profileWeightedCost(a) - profileWeightedCost(b));
4591
+ }
4592
+ function isFreeFrontier(profile) {
4593
+ return profile.costPer1M.inputUSD === 0 && profile.costPer1M.outputUSD === 0;
4594
+ }
4595
+ function frontierCostLabel(profile) {
4596
+ if (isFreeFrontier(profile)) {
4597
+ return "gratis";
4598
+ }
4599
+ return `$${profile.costPer1M.inputUSD}/$${profile.costPer1M.outputUSD} por 1M`;
4600
+ }
4601
+ function frontierProviders(profiles) {
4602
+ return [...new Set(profiles.map((profile) => profile.ref.providerID))].sort((a, b) => a.localeCompare(b));
4603
+ }
4604
+ function frontierModelsForProvider(profiles, providerID) {
4605
+ return sortFrontierByCost(profiles.filter((profile) => profile.ref.providerID === providerID));
4606
+ }
4607
+
4500
4608
  // src/commands/slashCommand.ts
4501
4609
  var OPENTEAM_COMMAND_DIR = ".opencode/command";
4502
4610
  var OPENTEAM_COMMAND_PATH = `${OPENTEAM_COMMAND_DIR}/openteam.md`;
@@ -4580,30 +4688,6 @@ var KNOWN_RUNTIMES = [
4580
4688
  dynamicPort: true
4581
4689
  }
4582
4690
  ];
4583
- function weightedCost(inputUSD, outputUSD) {
4584
- return 0.75 * inputUSD + 0.25 * outputUSD;
4585
- }
4586
- function profileWeightedCost(profile) {
4587
- return weightedCost(profile.costPer1M.inputUSD, profile.costPer1M.outputUSD);
4588
- }
4589
- function sortFrontierByCost(profiles) {
4590
- return [...profiles].sort((a, b) => profileWeightedCost(a) - profileWeightedCost(b));
4591
- }
4592
- function isFreeFrontier(profile) {
4593
- return profile.costPer1M.inputUSD === 0 && profile.costPer1M.outputUSD === 0;
4594
- }
4595
- function frontierCostLabel(profile) {
4596
- if (isFreeFrontier(profile)) {
4597
- return "gratis";
4598
- }
4599
- return `$${profile.costPer1M.inputUSD}/$${profile.costPer1M.outputUSD} por 1M`;
4600
- }
4601
- function frontierProviders(profiles) {
4602
- return [...new Set(profiles.map((profile) => profile.ref.providerID))].sort((a, b) => a.localeCompare(b));
4603
- }
4604
- function frontierModelsForProvider(profiles, providerID) {
4605
- return sortFrontierByCost(profiles.filter((profile) => profile.ref.providerID === providerID));
4606
- }
4607
4691
  function knownRuntime(id) {
4608
4692
  const found = KNOWN_RUNTIMES.find((runtime) => runtime.id === id);
4609
4693
  if (found === undefined) {
@@ -5044,6 +5128,210 @@ function loadOpenTeamConfig(raw) {
5044
5128
  return OpenTeamConfigSchema.parse(raw ?? {});
5045
5129
  }
5046
5130
 
5131
+ // src/memory/types.ts
5132
+ import { z as z6 } from "zod";
5133
+ var SHARED_OWNER_KEY = "*";
5134
+ var OwnerKeySchema = z6.string().min(1);
5135
+ var MemoryKindSchema = z6.enum(["fact", "preference", "entity"]);
5136
+ var MemoryBaseSchema = z6.object({
5137
+ id: z6.string().min(1),
5138
+ ownerKey: OwnerKeySchema,
5139
+ confidence: z6.number().min(0).max(1),
5140
+ validFrom: z6.number().finite(),
5141
+ validUntil: z6.number().finite().nullable().default(null),
5142
+ createdAt: z6.number().finite(),
5143
+ invalidatedAt: z6.number().finite().nullable().default(null),
5144
+ sourceHash: z6.string().min(1),
5145
+ supersededBy: z6.string().min(1).nullable().default(null)
5146
+ });
5147
+ var FactSchema = MemoryBaseSchema.extend({
5148
+ kind: z6.literal("fact"),
5149
+ subject: z6.string().min(1),
5150
+ predicate: z6.string().min(1),
5151
+ object: z6.string().min(1),
5152
+ category: z6.string().min(1).nullable().default(null)
5153
+ });
5154
+ var PreferenceSchema = MemoryBaseSchema.extend({
5155
+ kind: z6.literal("preference"),
5156
+ category: z6.string().min(1),
5157
+ preference: z6.string().min(1),
5158
+ context: z6.string().min(1).nullable().default(null),
5159
+ lastAccessedAt: z6.number().finite().nullable().default(null),
5160
+ accessCount: z6.number().int().min(0).default(0)
5161
+ });
5162
+ var EntitySchema = MemoryBaseSchema.extend({
5163
+ kind: z6.literal("entity"),
5164
+ canonicalName: z6.string().min(1),
5165
+ type: z6.string().min(1),
5166
+ aliases: z6.array(z6.string().min(1)).default([])
5167
+ });
5168
+ var MemoryRecordSchema = z6.discriminatedUnion("kind", [
5169
+ FactSchema,
5170
+ PreferenceSchema,
5171
+ EntitySchema
5172
+ ]);
5173
+ var RecallQuerySchema = z6.object({
5174
+ ownerKey: OwnerKeySchema,
5175
+ kinds: z6.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
5176
+ asOf: z6.number().finite().nullable().default(null),
5177
+ limit: z6.number().int().positive().default(8),
5178
+ minSimilarity: z6.number().min(0).max(1).default(0.2),
5179
+ includeShared: z6.boolean().default(true)
5180
+ });
5181
+
5182
+ // src/memory/rank.ts
5183
+ var DAY_MS = 86400000;
5184
+ var DEFAULT_RANK_SIGNALS = {
5185
+ similarityWeight: 1,
5186
+ recencyWeight: 0.2,
5187
+ accessWeight: 0.1,
5188
+ recencyHalfLifeMs: 14 * DAY_MS
5189
+ };
5190
+ function recencyTimestamp(record) {
5191
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
5192
+ return record.lastAccessedAt;
5193
+ }
5194
+ return record.createdAt;
5195
+ }
5196
+ function recencyScore(record, signals) {
5197
+ const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
5198
+ return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
5199
+ }
5200
+ function accessScore(record) {
5201
+ if (record.kind !== "preference") {
5202
+ return 0;
5203
+ }
5204
+ return 1 - 1 / (1 + record.accessCount);
5205
+ }
5206
+ function scoreCandidate(candidate, signals) {
5207
+ return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
5208
+ }
5209
+ function rankRecall(candidates, signals) {
5210
+ return candidates.map((candidate) => ({
5211
+ ...candidate,
5212
+ score: scoreCandidate(candidate, signals)
5213
+ })).sort((a, b) => {
5214
+ if (b.score !== a.score) {
5215
+ return b.score - a.score;
5216
+ }
5217
+ return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
5218
+ });
5219
+ }
5220
+
5221
+ // src/memory/temporal.ts
5222
+ function isTransactionActiveAt(record, asOf) {
5223
+ return record.invalidatedAt === null || record.invalidatedAt > asOf;
5224
+ }
5225
+ function isValidAt(record, asOf) {
5226
+ if (record.validFrom > asOf) {
5227
+ return false;
5228
+ }
5229
+ return record.validUntil === null || record.validUntil > asOf;
5230
+ }
5231
+ function isActiveAt(record, asOf) {
5232
+ return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
5233
+ }
5234
+ function filterActive(records, asOf) {
5235
+ return records.filter((record) => isActiveAt(record, asOf));
5236
+ }
5237
+ function coalesceWindow(a, b) {
5238
+ const validFrom = Math.min(a.validFrom, b.validFrom);
5239
+ const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
5240
+ return { validFrom, validUntil };
5241
+ }
5242
+
5243
+ // src/memory/vector.ts
5244
+ function dot(a, b) {
5245
+ if (a.length !== b.length) {
5246
+ throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
5247
+ }
5248
+ let sum = 0;
5249
+ for (let i = 0;i < a.length; i += 1) {
5250
+ sum += a[i] * b[i];
5251
+ }
5252
+ return sum;
5253
+ }
5254
+ function norm(a) {
5255
+ let sum = 0;
5256
+ for (let i = 0;i < a.length; i += 1) {
5257
+ const value = a[i];
5258
+ sum += value * value;
5259
+ }
5260
+ return Math.sqrt(sum);
5261
+ }
5262
+ function cosineSimilarity(a, b) {
5263
+ const denom = norm(a) * norm(b);
5264
+ if (denom === 0) {
5265
+ return 0;
5266
+ }
5267
+ return dot(a, b) / denom;
5268
+ }
5269
+
5270
+ // src/memory/recall.ts
5271
+ function matchesScope(record, query) {
5272
+ if (record.ownerKey === query.ownerKey) {
5273
+ return true;
5274
+ }
5275
+ return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
5276
+ }
5277
+ function recall(records, queryEmbedding, query, deps) {
5278
+ const asOf = query.asOf ?? deps.now;
5279
+ const kinds = new Set(query.kinds);
5280
+ const candidates = [];
5281
+ for (const record of filterActive(records, asOf)) {
5282
+ if (!kinds.has(record.kind) || !matchesScope(record, query)) {
5283
+ continue;
5284
+ }
5285
+ const embedding = deps.embeddings.get(record.id);
5286
+ if (embedding === undefined) {
5287
+ continue;
5288
+ }
5289
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
5290
+ if (similarity < query.minSimilarity) {
5291
+ continue;
5292
+ }
5293
+ candidates.push({ record, similarity });
5294
+ }
5295
+ const signals = {
5296
+ now: deps.now,
5297
+ ...deps.signals ?? DEFAULT_RANK_SIGNALS
5298
+ };
5299
+ return rankRecall(candidates, signals).slice(0, query.limit);
5300
+ }
5301
+
5302
+ // src/storage/index/memoryRecall.ts
5303
+ async function recallMemory(deps, queryText, query) {
5304
+ const [queryEmbedding] = await deps.embedder([queryText]);
5305
+ if (queryEmbedding === undefined) {
5306
+ return [];
5307
+ }
5308
+ const records = deps.index.allRecords();
5309
+ const embeddings = deps.index.embeddingMap();
5310
+ const recallDeps = deps.signals === undefined ? { embeddings, now: deps.now } : { embeddings, now: deps.now, signals: deps.signals };
5311
+ return recall(records, queryEmbedding, query, recallDeps);
5312
+ }
5313
+
5314
+ // src/mcp/semanticRecall.ts
5315
+ function buildSemanticRecall(deps, policy) {
5316
+ return async ({ queryText, kinds, limit }) => {
5317
+ const query = RecallQuerySchema.parse({
5318
+ ownerKey: policy.ownerKey,
5319
+ kinds,
5320
+ asOf: policy.asOf ?? null,
5321
+ limit,
5322
+ minSimilarity: policy.minSimilarity,
5323
+ includeShared: policy.includeShared
5324
+ });
5325
+ const recallDeps = deps.signals === undefined ? { index: deps.index, embedder: deps.embedder, now: deps.now() } : {
5326
+ index: deps.index,
5327
+ embedder: deps.embedder,
5328
+ now: deps.now(),
5329
+ signals: deps.signals
5330
+ };
5331
+ return recallMemory(recallDeps, queryText, query);
5332
+ };
5333
+ }
5334
+
5047
5335
  // src/mcp/teamMemory.ts
5048
5336
  function buildTeamMemory(events) {
5049
5337
  const aggregate = aggregateEvents(events);
@@ -5133,6 +5421,45 @@ var MCP_TOOLS = [
5133
5421
  inputSchema: { type: "object", properties: {} }
5134
5422
  }
5135
5423
  ];
5424
+ var RECALL_QUERY_PROP = {
5425
+ query: {
5426
+ type: "string",
5427
+ description: "Texto de consulta para el recall semántico."
5428
+ },
5429
+ ...LIMIT_PROP
5430
+ };
5431
+ var MEMORY_TOOLS = [
5432
+ {
5433
+ name: "recall_facts",
5434
+ description: "Hechos relevantes (sujeto-predicado-objeto) para la consulta, por similitud semántica. Contenido redactado salvo storeContent.",
5435
+ inputSchema: {
5436
+ type: "object",
5437
+ properties: { ...RECALL_QUERY_PROP },
5438
+ required: ["query"]
5439
+ }
5440
+ },
5441
+ {
5442
+ name: "recall_preferences",
5443
+ description: "Preferencias del usuario/proyecto relevantes para la consulta, por similitud semántica.",
5444
+ inputSchema: {
5445
+ type: "object",
5446
+ properties: { ...RECALL_QUERY_PROP },
5447
+ required: ["query"]
5448
+ }
5449
+ },
5450
+ {
5451
+ name: "similar_tasks",
5452
+ description: "Memoria (hechos/preferencias/entidades) relevante a una tarea, por similitud semántica; útil para reutilizar contexto previo.",
5453
+ inputSchema: {
5454
+ type: "object",
5455
+ properties: { ...RECALL_QUERY_PROP },
5456
+ required: ["query"]
5457
+ }
5458
+ }
5459
+ ];
5460
+ function availableTools(deps) {
5461
+ return deps.recallSemantic === undefined ? MCP_TOOLS : [...MCP_TOOLS, ...MEMORY_TOOLS];
5462
+ }
5136
5463
  function response(id, result) {
5137
5464
  return { jsonrpc: "2.0", id, result };
5138
5465
  }
@@ -5148,7 +5475,58 @@ function optionalString(value) {
5148
5475
  function optionalLimit(value) {
5149
5476
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
5150
5477
  }
5478
+ var DEFAULT_RECALL_LIMIT = 8;
5479
+ function projectRecall(results) {
5480
+ return results.map(({ record, similarity, score }) => {
5481
+ const base = {
5482
+ id: record.id,
5483
+ kind: record.kind,
5484
+ ownerKey: record.ownerKey,
5485
+ confidence: record.confidence,
5486
+ similarity,
5487
+ score
5488
+ };
5489
+ if (record.kind === "fact") {
5490
+ return {
5491
+ ...base,
5492
+ subject: record.subject,
5493
+ predicate: record.predicate,
5494
+ object: record.object,
5495
+ category: record.category
5496
+ };
5497
+ }
5498
+ if (record.kind === "preference") {
5499
+ return {
5500
+ ...base,
5501
+ category: record.category,
5502
+ preference: record.preference
5503
+ };
5504
+ }
5505
+ return { ...base, canonicalName: record.canonicalName, type: record.type };
5506
+ });
5507
+ }
5508
+ async function callSemanticRecall(recall2, kinds, args) {
5509
+ const query = optionalString(args.query);
5510
+ if (query === undefined) {
5511
+ return textContent({ error: "requiere 'query'" });
5512
+ }
5513
+ const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
5514
+ const results = await recall2({ queryText: query, kinds, limit });
5515
+ return textContent(projectRecall(results));
5516
+ }
5151
5517
  async function callTool(name, args, deps) {
5518
+ if (deps.recallSemantic !== undefined) {
5519
+ switch (name) {
5520
+ case "recall_facts":
5521
+ return callSemanticRecall(deps.recallSemantic, ["fact"], args);
5522
+ case "recall_preferences":
5523
+ return callSemanticRecall(deps.recallSemantic, ["preference"], args);
5524
+ case "similar_tasks":
5525
+ return callSemanticRecall(deps.recallSemantic, ["fact", "preference", "entity"], args);
5526
+ default:
5527
+ break;
5528
+ }
5529
+ }
5152
5530
  const memory = await deps.readMemory();
5153
5531
  switch (name) {
5154
5532
  case "list_decisions": {
@@ -5189,7 +5567,7 @@ async function handleMcpMessage(message, deps) {
5189
5567
  serverInfo
5190
5568
  });
5191
5569
  case "tools/list":
5192
- return response(id, { tools: MCP_TOOLS });
5570
+ return response(id, { tools: availableTools(deps) });
5193
5571
  case "tools/call": {
5194
5572
  const params = message.params ?? {};
5195
5573
  const name = optionalString(params.name);
@@ -5258,6 +5636,456 @@ async function* linesFromReadable(readable) {
5258
5636
  }
5259
5637
  }
5260
5638
 
5639
+ // src/memory/consolidate.ts
5640
+ var DAY_MS2 = 86400000;
5641
+ var DEFAULT_DECAY_PARAMS = {
5642
+ halfLifeMs: 90 * DAY_MS2,
5643
+ forgetThreshold: 0.05
5644
+ };
5645
+ function lastReinforcedAt(record) {
5646
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
5647
+ return record.lastAccessedAt;
5648
+ }
5649
+ return record.createdAt;
5650
+ }
5651
+ function decayedConfidence(record, now, params = DEFAULT_DECAY_PARAMS) {
5652
+ const ageMs = Math.max(0, now - lastReinforcedAt(record));
5653
+ return record.confidence * 0.5 ** (ageMs / params.halfLifeMs);
5654
+ }
5655
+ function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
5656
+ const tombstones = [];
5657
+ for (const record of records) {
5658
+ if (!isTransactionActiveAt(record, now)) {
5659
+ continue;
5660
+ }
5661
+ if (decayedConfidence(record, now, params) < params.forgetThreshold) {
5662
+ tombstones.push({ ...record, invalidatedAt: now, supersededBy: null });
5663
+ }
5664
+ }
5665
+ return tombstones;
5666
+ }
5667
+
5668
+ // src/memory/merge.ts
5669
+ var SEP = "\x00";
5670
+ function factSlotKey(fact) {
5671
+ return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
5672
+ }
5673
+ function factValueKey(fact) {
5674
+ return [factSlotKey(fact), fact.object].join(SEP);
5675
+ }
5676
+ function preferenceSlotKey(preference) {
5677
+ return [preference.category, preference.ownerKey].join(SEP);
5678
+ }
5679
+ function preferenceValueKey(preference) {
5680
+ return [preferenceSlotKey(preference), preference.preference].join(SEP);
5681
+ }
5682
+ function entitySlotKey(entity) {
5683
+ return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
5684
+ }
5685
+ function coalesceFact(existing, incoming) {
5686
+ const window = coalesceWindow(existing, incoming);
5687
+ return {
5688
+ ...existing,
5689
+ validFrom: window.validFrom,
5690
+ validUntil: window.validUntil,
5691
+ confidence: Math.max(existing.confidence, incoming.confidence)
5692
+ };
5693
+ }
5694
+ function applyFact(state, incoming, now) {
5695
+ const incomingSlot = factSlotKey(incoming);
5696
+ const incomingValue = factValueKey(incoming);
5697
+ const superseded = [];
5698
+ const records = [];
5699
+ let reaffirmed = false;
5700
+ for (const fact of state) {
5701
+ if (!isActiveAt(fact, now)) {
5702
+ records.push(fact);
5703
+ continue;
5704
+ }
5705
+ if (factValueKey(fact) === incomingValue) {
5706
+ records.push(coalesceFact(fact, incoming));
5707
+ reaffirmed = true;
5708
+ continue;
5709
+ }
5710
+ if (factSlotKey(fact) === incomingSlot) {
5711
+ const dead = {
5712
+ ...fact,
5713
+ invalidatedAt: now,
5714
+ supersededBy: incoming.id
5715
+ };
5716
+ superseded.push(dead);
5717
+ records.push(dead);
5718
+ continue;
5719
+ }
5720
+ records.push(fact);
5721
+ }
5722
+ if (!reaffirmed) {
5723
+ records.push(incoming);
5724
+ }
5725
+ return { records, superseded };
5726
+ }
5727
+ function applyPreference(state, incoming, now) {
5728
+ const incomingSlot = preferenceSlotKey(incoming);
5729
+ const incomingValue = preferenceValueKey(incoming);
5730
+ const superseded = [];
5731
+ const records = [];
5732
+ let reaffirmed = false;
5733
+ for (const preference of state) {
5734
+ if (!isActiveAt(preference, now)) {
5735
+ records.push(preference);
5736
+ continue;
5737
+ }
5738
+ if (preferenceValueKey(preference) === incomingValue) {
5739
+ records.push({
5740
+ ...preference,
5741
+ confidence: Math.max(preference.confidence, incoming.confidence)
5742
+ });
5743
+ reaffirmed = true;
5744
+ continue;
5745
+ }
5746
+ if (preferenceSlotKey(preference) === incomingSlot) {
5747
+ const dead = {
5748
+ ...preference,
5749
+ invalidatedAt: now,
5750
+ supersededBy: incoming.id
5751
+ };
5752
+ superseded.push(dead);
5753
+ records.push(dead);
5754
+ continue;
5755
+ }
5756
+ records.push(preference);
5757
+ }
5758
+ if (!reaffirmed) {
5759
+ records.push(incoming);
5760
+ }
5761
+ return { records, superseded };
5762
+ }
5763
+ function mergeAliases(existing, incoming) {
5764
+ const seen = new Set(existing);
5765
+ const merged = [...existing];
5766
+ for (const alias of incoming) {
5767
+ if (!seen.has(alias)) {
5768
+ seen.add(alias);
5769
+ merged.push(alias);
5770
+ }
5771
+ }
5772
+ return merged;
5773
+ }
5774
+ function applyEntity(state, incoming, now) {
5775
+ const incomingSlot = entitySlotKey(incoming);
5776
+ const records = [];
5777
+ let merged = false;
5778
+ for (const entity of state) {
5779
+ if (isActiveAt(entity, now) && entitySlotKey(entity) === incomingSlot) {
5780
+ records.push({
5781
+ ...entity,
5782
+ aliases: mergeAliases(entity.aliases, incoming.aliases),
5783
+ confidence: Math.max(entity.confidence, incoming.confidence)
5784
+ });
5785
+ merged = true;
5786
+ continue;
5787
+ }
5788
+ records.push(entity);
5789
+ }
5790
+ if (!merged) {
5791
+ records.push(incoming);
5792
+ }
5793
+ return { records, superseded: [] };
5794
+ }
5795
+
5796
+ // src/memory/fold.ts
5797
+ function emptyState() {
5798
+ return { facts: [], preferences: [], entities: [] };
5799
+ }
5800
+ function applyTombstone(state, tombstone) {
5801
+ const mark = (records) => records.map((record) => record.id === tombstone.id ? {
5802
+ ...record,
5803
+ invalidatedAt: tombstone.invalidatedAt,
5804
+ supersededBy: tombstone.supersededBy
5805
+ } : record);
5806
+ if (tombstone.kind === "fact") {
5807
+ state.facts = mark(state.facts);
5808
+ } else if (tombstone.kind === "preference") {
5809
+ state.preferences = mark(state.preferences);
5810
+ } else {
5811
+ state.entities = mark(state.entities);
5812
+ }
5813
+ }
5814
+ function foldAssertions(assertions) {
5815
+ const state = emptyState();
5816
+ for (const assertion of assertions) {
5817
+ if (assertion.invalidatedAt !== null) {
5818
+ applyTombstone(state, assertion);
5819
+ continue;
5820
+ }
5821
+ if (assertion.kind === "fact") {
5822
+ state.facts = applyFact(state.facts, assertion, assertion.createdAt).records;
5823
+ } else if (assertion.kind === "preference") {
5824
+ state.preferences = applyPreference(state.preferences, assertion, assertion.createdAt).records;
5825
+ } else {
5826
+ state.entities = applyEntity(state.entities, assertion, assertion.createdAt).records;
5827
+ }
5828
+ }
5829
+ return state;
5830
+ }
5831
+ function allRecords(state) {
5832
+ return [...state.facts, ...state.preferences, ...state.entities];
5833
+ }
5834
+
5835
+ // src/memory/log.ts
5836
+ function serializeAssertion(record) {
5837
+ return JSON.stringify(record);
5838
+ }
5839
+ function parseMemoryLog(text2) {
5840
+ const records = [];
5841
+ for (const line of text2.split(`
5842
+ `)) {
5843
+ const trimmed = line.trim();
5844
+ if (trimmed.length === 0) {
5845
+ continue;
5846
+ }
5847
+ let candidate;
5848
+ try {
5849
+ candidate = JSON.parse(trimmed);
5850
+ } catch {
5851
+ continue;
5852
+ }
5853
+ const parsed = MemoryRecordSchema.safeParse(candidate);
5854
+ if (parsed.success) {
5855
+ records.push(parsed.data);
5856
+ }
5857
+ }
5858
+ return records;
5859
+ }
5860
+
5861
+ // src/storage/index/memoryConsolidate.ts
5862
+ async function runConsolidation(logPath, deps) {
5863
+ const params = deps.params ?? DEFAULT_DECAY_PARAMS;
5864
+ const text2 = await deps.storage.read(logPath) ?? "";
5865
+ const state = foldAssertions(parseMemoryLog(text2));
5866
+ const tombstones = consolidate(allRecords(state), deps.now, params);
5867
+ if (tombstones.length === 0) {
5868
+ return { forgotten: 0 };
5869
+ }
5870
+ const appended = `${tombstones.map(serializeAssertion).join(`
5871
+ `)}
5872
+ `;
5873
+ await deps.storage.append(logPath, appended);
5874
+ return { forgotten: tombstones.length };
5875
+ }
5876
+
5877
+ // src/local/embeddings.ts
5878
+ function parseEmbeddingsResponse(payload) {
5879
+ if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
5880
+ throw new Error("Respuesta de /embeddings malformada: falta 'data'");
5881
+ }
5882
+ const data = payload.data;
5883
+ return data.map((entry, index) => {
5884
+ const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
5885
+ if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
5886
+ throw new Error(`Respuesta de /embeddings malformada: vector inválido en índice ${index}`);
5887
+ }
5888
+ return embedding;
5889
+ });
5890
+ }
5891
+ async function embedTexts(request, deps) {
5892
+ if (request.texts.length === 0) {
5893
+ return [];
5894
+ }
5895
+ let response2;
5896
+ try {
5897
+ response2 = await deps.fetch(embeddingsEndpoint(request.baseURL), {
5898
+ method: "POST",
5899
+ headers: {
5900
+ "content-type": "application/json",
5901
+ accept: "application/json"
5902
+ },
5903
+ body: JSON.stringify({
5904
+ model: request.modelID,
5905
+ input: [...request.texts]
5906
+ })
5907
+ });
5908
+ } catch (error) {
5909
+ throw new Error(`Embeddings local falló: ${errorMessage(error)}`);
5910
+ }
5911
+ if (!response2.ok) {
5912
+ throw new Error(`Embeddings local falló: HTTP ${response2.status} en /embeddings`);
5913
+ }
5914
+ let payload;
5915
+ try {
5916
+ payload = await response2.json();
5917
+ } catch (error) {
5918
+ throw new Error(`Embeddings local falló: ${errorMessage(error)}`);
5919
+ }
5920
+ const vectors = parseEmbeddingsResponse(payload);
5921
+ if (vectors.length !== request.texts.length) {
5922
+ throw new Error(`Embeddings local devolvió ${vectors.length} vectores para ${request.texts.length} textos`);
5923
+ }
5924
+ return vectors;
5925
+ }
5926
+ function createLocalEmbedder(request, deps) {
5927
+ return (texts) => embedTexts({ ...request, texts }, deps);
5928
+ }
5929
+
5930
+ // src/storage/index/memoryIndex.ts
5931
+ var SCHEMA2 = `
5932
+ CREATE TABLE IF NOT EXISTS memory_records (
5933
+ id TEXT PRIMARY KEY,
5934
+ kind TEXT NOT NULL,
5935
+ owner_key TEXT NOT NULL,
5936
+ created_at INTEGER NOT NULL,
5937
+ invalidated_at INTEGER,
5938
+ payload TEXT NOT NULL
5939
+ );
5940
+ CREATE INDEX IF NOT EXISTS idx_memory_owner ON memory_records(owner_key, kind);
5941
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
5942
+ record_id TEXT PRIMARY KEY,
5943
+ dim INTEGER NOT NULL,
5944
+ vector TEXT NOT NULL
5945
+ );
5946
+ `;
5947
+ var INSERT_SQL2 = `INSERT OR REPLACE INTO memory_records
5948
+ (id, kind, owner_key, created_at, invalidated_at, payload)
5949
+ VALUES (?, ?, ?, ?, ?, ?)`;
5950
+ var INSERT_EMBEDDING_SQL = `INSERT OR REPLACE INTO memory_embeddings
5951
+ (record_id, dim, vector) VALUES (?, ?, ?)`;
5952
+ function insertRecords(db, records) {
5953
+ const stmt = db.query(INSERT_SQL2);
5954
+ db.run("BEGIN");
5955
+ try {
5956
+ for (const record of records) {
5957
+ stmt.run(record.id, record.kind, record.ownerKey, record.createdAt, record.invalidatedAt, JSON.stringify(record));
5958
+ }
5959
+ db.run("COMMIT");
5960
+ } catch (error) {
5961
+ db.run("ROLLBACK");
5962
+ throw error;
5963
+ }
5964
+ }
5965
+ function createMemoryIndex(db) {
5966
+ db.exec(SCHEMA2);
5967
+ const allRecordsFn = () => {
5968
+ const rows = db.query("SELECT payload FROM memory_records ORDER BY created_at ASC, id ASC").all();
5969
+ const records = [];
5970
+ for (const row of rows) {
5971
+ let candidate;
5972
+ try {
5973
+ candidate = JSON.parse(row.payload);
5974
+ } catch {
5975
+ continue;
5976
+ }
5977
+ const parsed = MemoryRecordSchema.safeParse(candidate);
5978
+ if (parsed.success) {
5979
+ records.push(parsed.data);
5980
+ }
5981
+ }
5982
+ return records;
5983
+ };
5984
+ const state = () => {
5985
+ const facts = [];
5986
+ const preferences = [];
5987
+ const entities = [];
5988
+ for (const record of allRecordsFn()) {
5989
+ if (record.kind === "fact") {
5990
+ facts.push(record);
5991
+ } else if (record.kind === "preference") {
5992
+ preferences.push(record);
5993
+ } else {
5994
+ entities.push(record);
5995
+ }
5996
+ }
5997
+ return { facts, preferences, entities };
5998
+ };
5999
+ return {
6000
+ rebuild(assertions) {
6001
+ const folded = allRecords(foldAssertions(assertions));
6002
+ db.run("DELETE FROM memory_records");
6003
+ insertRecords(db, folded);
6004
+ },
6005
+ allRecords: allRecordsFn,
6006
+ state,
6007
+ activeAt(asOf) {
6008
+ return filterActive(allRecordsFn(), asOf);
6009
+ },
6010
+ putEmbeddings(entries) {
6011
+ const stmt = db.query(INSERT_EMBEDDING_SQL);
6012
+ db.run("BEGIN");
6013
+ try {
6014
+ for (const entry of entries) {
6015
+ stmt.run(entry.recordId, entry.vector.length, JSON.stringify(entry.vector));
6016
+ }
6017
+ db.run("COMMIT");
6018
+ } catch (error) {
6019
+ db.run("ROLLBACK");
6020
+ throw error;
6021
+ }
6022
+ },
6023
+ embeddingMap() {
6024
+ const rows = db.query("SELECT record_id, vector FROM memory_embeddings").all();
6025
+ const map = new Map;
6026
+ for (const row of rows) {
6027
+ try {
6028
+ const vector = JSON.parse(row.vector);
6029
+ if (Array.isArray(vector) && vector.every((n) => typeof n === "number")) {
6030
+ map.set(row.record_id, vector);
6031
+ }
6032
+ } catch {}
6033
+ }
6034
+ return map;
6035
+ },
6036
+ pruneEmbeddings() {
6037
+ const result = db.run("DELETE FROM memory_embeddings WHERE record_id NOT IN (SELECT id FROM memory_records)");
6038
+ return typeof result?.changes === "number" ? result.changes : 0;
6039
+ },
6040
+ close() {
6041
+ db.close();
6042
+ }
6043
+ };
6044
+ }
6045
+ async function rebuildMemoryIndexFromStorage(index, logPath, deps) {
6046
+ const text2 = await deps.storage.read(logPath) ?? "";
6047
+ const assertions = parseMemoryLog(text2);
6048
+ index.rebuild(assertions);
6049
+ return assertions.length;
6050
+ }
6051
+
6052
+ // src/storage/index/memoryRuntime.ts
6053
+ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
6054
+ const semantic = resolveMemorySemantic(config);
6055
+ if (!semantic.enabled || semantic.embeddings.provider === "none") {
6056
+ return;
6057
+ }
6058
+ if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
6059
+ return;
6060
+ }
6061
+ const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
6062
+ if (runtime?.baseURL === undefined) {
6063
+ return;
6064
+ }
6065
+ const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
6066
+ const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
6067
+ const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
6068
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
6069
+ const now = deps.now ?? (() => Date.now());
6070
+ try {
6071
+ const db = await openDatabase(semantic.indexPath);
6072
+ const index = createMemoryIndex(db);
6073
+ await rebuildMemoryIndexFromStorage(index, semantic.logPath, {
6074
+ storage: deps.storage
6075
+ });
6076
+ const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
6077
+ return {
6078
+ index,
6079
+ embedder,
6080
+ now,
6081
+ ownerKey: semantic.scope === "user" ? "user" : "project",
6082
+ semantic
6083
+ };
6084
+ } catch {
6085
+ return;
6086
+ }
6087
+ }
6088
+
5261
6089
  // src/web/git.ts
5262
6090
  function createGitLastCommit(exec) {
5263
6091
  return async () => {
@@ -5298,6 +6126,18 @@ async function loadConfig(path) {
5298
6126
  const content = await storage.read(path);
5299
6127
  return content === undefined ? loadOpenTeamConfig({}) : loadOpenTeamConfig(JSON.parse(content));
5300
6128
  }
6129
+ async function buildMcpSemanticRecall(config) {
6130
+ const runtime = await buildMemoryRuntimeFromConfig(config, { storage });
6131
+ if (runtime === undefined) {
6132
+ return;
6133
+ }
6134
+ const { index, embedder, now, ownerKey, semantic } = runtime;
6135
+ return buildSemanticRecall({ index, embedder, now }, {
6136
+ ownerKey,
6137
+ minSimilarity: semantic.recall.minSimilarity,
6138
+ includeShared: true
6139
+ });
6140
+ }
5301
6141
  async function openBrowser(url) {
5302
6142
  if (process.platform === "win32") {
5303
6143
  await nodeExec("cmd", ["/c", "start", "", url]);
@@ -5424,17 +6264,31 @@ async function main() {
5424
6264
  return;
5425
6265
  }
5426
6266
  if (argv[0] === "mcp") {
6267
+ const mcpConfig = await loadConfig(DEFAULT_CONFIG_PATH);
6268
+ const recallSemantic = await buildMcpSemanticRecall(mcpConfig);
5427
6269
  await runMcpStdio({
5428
6270
  readMemory: async () => buildTeamMemory(await readSessionEvents(DEFAULT_SESSIONS_DIR, {
5429
6271
  storage,
5430
6272
  legacyTelemetryPath: DEFAULT_TELEMETRY_PATH
5431
- }))
6273
+ })),
6274
+ ...recallSemantic === undefined ? {} : { recallSemantic }
5432
6275
  }, {
5433
6276
  lines: linesFromReadable(process.stdin),
5434
6277
  write: (line) => process.stdout.write(line)
5435
6278
  });
5436
6279
  return;
5437
6280
  }
6281
+ if (argv[0] === "memory" && argv[1] === "consolidate") {
6282
+ const memConfig = await loadConfig(DEFAULT_CONFIG_PATH);
6283
+ const semantic = resolveMemorySemantic(memConfig);
6284
+ const result2 = await runConsolidation(semantic.logPath, {
6285
+ storage,
6286
+ now: Date.now()
6287
+ });
6288
+ process.stdout.write(`Consolidación de memoria: ${result2.forgotten} record(s) olvidado(s).
6289
+ `);
6290
+ return;
6291
+ }
5438
6292
  const result = await runCli(argv, deps);
5439
6293
  process.stdout.write(`${result.stdout}
5440
6294
  `);