@jmanuelcorral/openteam 0.1.38 → 0.1.41

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 (162) hide show
  1. package/.opencode/command/openteam.md +5 -5
  2. package/AGENTS.md +50 -35
  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 +1123 -232
  8. package/dist/commands/frontierCatalog.d.ts +27 -0
  9. package/dist/commands/frontierCatalog.d.ts.map +1 -0
  10. package/dist/commands/graph.d.ts +65 -0
  11. package/dist/commands/graph.d.ts.map +1 -0
  12. package/dist/commands/orchestratorAgent.d.ts +2 -1
  13. package/dist/commands/orchestratorAgent.d.ts.map +1 -1
  14. package/dist/commands/setup.d.ts +3 -18
  15. package/dist/commands/setup.d.ts.map +1 -1
  16. package/dist/config/load.d.ts.map +1 -1
  17. package/dist/config/migrations.d.ts +27 -0
  18. package/dist/config/migrations.d.ts.map +1 -0
  19. package/dist/config/resolve.d.ts +34 -0
  20. package/dist/config/resolve.d.ts.map +1 -0
  21. package/dist/config/schema.d.ts +235 -0
  22. package/dist/config/schema.d.ts.map +1 -1
  23. package/dist/console/assets.d.ts +9 -0
  24. package/dist/console/assets.d.ts.map +1 -0
  25. package/dist/console/render.d.ts.map +1 -1
  26. package/dist/context/redaction.d.ts +48 -0
  27. package/dist/context/redaction.d.ts.map +1 -0
  28. package/dist/context/schema.d.ts +115 -0
  29. package/dist/context/schema.d.ts.map +1 -0
  30. package/dist/context/types.d.ts +8 -0
  31. package/dist/context/types.d.ts.map +1 -0
  32. package/dist/contract/opencode.d.ts +1 -1
  33. package/dist/contract/opencode.d.ts.map +1 -1
  34. package/dist/graph/events.d.ts +25 -0
  35. package/dist/graph/events.d.ts.map +1 -0
  36. package/dist/graph/ids.d.ts +5 -0
  37. package/dist/graph/ids.d.ts.map +1 -0
  38. package/dist/graph/planner.d.ts +15 -0
  39. package/dist/graph/planner.d.ts.map +1 -0
  40. package/dist/graph/policies.d.ts +34 -0
  41. package/dist/graph/policies.d.ts.map +1 -0
  42. package/dist/graph/privacyPolicy.d.ts +29 -0
  43. package/dist/graph/privacyPolicy.d.ts.map +1 -0
  44. package/dist/graph/reducer.d.ts +27 -0
  45. package/dist/graph/reducer.d.ts.map +1 -0
  46. package/dist/graph/replay.d.ts +20 -0
  47. package/dist/graph/replay.d.ts.map +1 -0
  48. package/dist/graph/reviewerPolicy.d.ts +27 -0
  49. package/dist/graph/reviewerPolicy.d.ts.map +1 -0
  50. package/dist/graph/schema.d.ts +220 -0
  51. package/dist/graph/schema.d.ts.map +1 -0
  52. package/dist/graph/types.d.ts +40 -0
  53. package/dist/graph/types.d.ts.map +1 -0
  54. package/dist/graph/validate.d.ts +19 -0
  55. package/dist/graph/validate.d.ts.map +1 -0
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +873 -88
  58. package/dist/local/embeddings.d.ts +29 -0
  59. package/dist/local/embeddings.d.ts.map +1 -0
  60. package/dist/local/extractClient.d.ts +31 -0
  61. package/dist/local/extractClient.d.ts.map +1 -0
  62. package/dist/local/openai-compatible.d.ts +1 -0
  63. package/dist/local/openai-compatible.d.ts.map +1 -1
  64. package/dist/mcp/semanticRecall.d.ts +25 -0
  65. package/dist/mcp/semanticRecall.d.ts.map +1 -0
  66. package/dist/mcp/server.d.ts +22 -0
  67. package/dist/mcp/server.d.ts.map +1 -1
  68. package/dist/memory/consolidate.d.ts +21 -0
  69. package/dist/memory/consolidate.d.ts.map +1 -0
  70. package/dist/memory/extract.d.ts +68 -0
  71. package/dist/memory/extract.d.ts.map +1 -0
  72. package/dist/memory/fold.d.ts +24 -0
  73. package/dist/memory/fold.d.ts.map +1 -0
  74. package/dist/memory/index.d.ts +18 -0
  75. package/dist/memory/index.d.ts.map +1 -0
  76. package/dist/memory/inject.d.ts +29 -0
  77. package/dist/memory/inject.d.ts.map +1 -0
  78. package/dist/memory/log.d.ts +16 -0
  79. package/dist/memory/log.d.ts.map +1 -0
  80. package/dist/memory/merge.d.ts +38 -0
  81. package/dist/memory/merge.d.ts.map +1 -0
  82. package/dist/memory/rank.d.ts +32 -0
  83. package/dist/memory/rank.d.ts.map +1 -0
  84. package/dist/memory/recall.d.ts +30 -0
  85. package/dist/memory/recall.d.ts.map +1 -0
  86. package/dist/memory/temporal.d.ts +39 -0
  87. package/dist/memory/temporal.d.ts.map +1 -0
  88. package/dist/memory/types.d.ts +159 -0
  89. package/dist/memory/types.d.ts.map +1 -0
  90. package/dist/memory/vector.d.ts +18 -0
  91. package/dist/memory/vector.d.ts.map +1 -0
  92. package/dist/orchestrator/graphCancellation.d.ts +84 -0
  93. package/dist/orchestrator/graphCancellation.d.ts.map +1 -0
  94. package/dist/orchestrator/graphEffects.d.ts +63 -0
  95. package/dist/orchestrator/graphEffects.d.ts.map +1 -0
  96. package/dist/orchestrator/graphReview.d.ts +84 -0
  97. package/dist/orchestrator/graphReview.d.ts.map +1 -0
  98. package/dist/orchestrator/graphRuntime.d.ts +69 -0
  99. package/dist/orchestrator/graphRuntime.d.ts.map +1 -0
  100. package/dist/orchestrator/opencodeSessionAdapter.d.ts +117 -0
  101. package/dist/orchestrator/opencodeSessionAdapter.d.ts.map +1 -0
  102. package/dist/orchestrator/roles.d.ts +1 -1
  103. package/dist/orchestrator/roles.d.ts.map +1 -1
  104. package/dist/orchestrator/sdd.d.ts +38 -2
  105. package/dist/orchestrator/sdd.d.ts.map +1 -1
  106. package/dist/orchestrator/sddCompiler.d.ts +44 -0
  107. package/dist/orchestrator/sddCompiler.d.ts.map +1 -0
  108. package/dist/orchestrator/sessionReconciler.d.ts +74 -0
  109. package/dist/orchestrator/sessionReconciler.d.ts.map +1 -0
  110. package/dist/orchestrator/shadowComparator.d.ts +62 -0
  111. package/dist/orchestrator/shadowComparator.d.ts.map +1 -0
  112. package/dist/plugin/hooks.d.ts +13 -0
  113. package/dist/plugin/hooks.d.ts.map +1 -1
  114. package/dist/plugin/memoryInject.d.ts +25 -0
  115. package/dist/plugin/memoryInject.d.ts.map +1 -0
  116. package/dist/router/chooseModel.d.ts.map +1 -1
  117. package/dist/router/cost.d.ts +4 -0
  118. package/dist/router/cost.d.ts.map +1 -0
  119. package/dist/router/frontierBaseline.d.ts +13 -0
  120. package/dist/router/frontierBaseline.d.ts.map +1 -0
  121. package/dist/router/modelSelection.d.ts +18 -0
  122. package/dist/router/modelSelection.d.ts.map +1 -0
  123. package/dist/storage/graph/codec.d.ts +52 -0
  124. package/dist/storage/graph/codec.d.ts.map +1 -0
  125. package/dist/storage/graph/fsGraphJournal.d.ts +3 -0
  126. package/dist/storage/graph/fsGraphJournal.d.ts.map +1 -0
  127. package/dist/storage/graph/importLegacy.d.ts +62 -0
  128. package/dist/storage/graph/importLegacy.d.ts.map +1 -0
  129. package/dist/storage/graph/memoryGraphJournal.d.ts +11 -0
  130. package/dist/storage/graph/memoryGraphJournal.d.ts.map +1 -0
  131. package/dist/storage/graph/migrateLegacyBriefs.d.ts +32 -0
  132. package/dist/storage/graph/migrateLegacyBriefs.d.ts.map +1 -0
  133. package/dist/storage/graph/projections.d.ts +31 -0
  134. package/dist/storage/graph/projections.d.ts.map +1 -0
  135. package/dist/storage/graph/provider.d.ts +52 -0
  136. package/dist/storage/graph/provider.d.ts.map +1 -0
  137. package/dist/storage/graph/recovery.d.ts +38 -0
  138. package/dist/storage/graph/recovery.d.ts.map +1 -0
  139. package/dist/storage/graph/runRegistry.d.ts +17 -0
  140. package/dist/storage/graph/runRegistry.d.ts.map +1 -0
  141. package/dist/storage/graph/snapshot.d.ts +44 -0
  142. package/dist/storage/graph/snapshot.d.ts.map +1 -0
  143. package/dist/storage/graph/workspaceLease.d.ts +36 -0
  144. package/dist/storage/graph/workspaceLease.d.ts.map +1 -0
  145. package/dist/storage/graph/writer.d.ts +20 -0
  146. package/dist/storage/graph/writer.d.ts.map +1 -0
  147. package/dist/storage/index/memoryConsolidate.d.ts +24 -0
  148. package/dist/storage/index/memoryConsolidate.d.ts.map +1 -0
  149. package/dist/storage/index/memoryIndex.d.ts +52 -0
  150. package/dist/storage/index/memoryIndex.d.ts.map +1 -0
  151. package/dist/storage/index/memoryRecall.d.ts +23 -0
  152. package/dist/storage/index/memoryRecall.d.ts.map +1 -0
  153. package/dist/storage/index/memoryRuntime.d.ts +41 -0
  154. package/dist/storage/index/memoryRuntime.d.ts.map +1 -0
  155. package/dist/telemetry/events.d.ts +3 -1
  156. package/dist/telemetry/events.d.ts.map +1 -1
  157. package/dist/telemetry/graphProjection.d.ts +36 -0
  158. package/dist/telemetry/graphProjection.d.ts.map +1 -0
  159. package/dist/telemetry/hash.d.ts.map +1 -1
  160. package/dist/web/server.d.ts +4 -0
  161. package/dist/web/server.d.ts.map +1 -1
  162. package/package.json +14 -6
package/dist/cli.js CHANGED
@@ -628,6 +628,11 @@ var PrivacyModeSchema = z3.enum([
628
628
  "off"
629
629
  ]);
630
630
  var BaselineModeSchema = z3.enum(["auto", "pinned"]);
631
+ var GraphModeSchema = z3.enum(["off", "shadow", "active"]);
632
+ var GraphConfigSchema = z3.object({
633
+ mode: GraphModeSchema.default("off"),
634
+ killSwitch: z3.boolean().default(false)
635
+ });
631
636
  var ConsoleHostSchema = z3.enum(["127.0.0.1", "localhost"]);
632
637
  var ConsoleConfigSchema = z3.object({
633
638
  host: ConsoleHostSchema.default("127.0.0.1"),
@@ -674,6 +679,50 @@ var LocalRuntimeSchema = z3.object({
674
679
  discovery: z3.enum(["cli", "sdk", "manual"]).optional(),
675
680
  defaultModel: ModelRefSchema
676
681
  });
682
+ var MemoryScopeSchema = z3.enum(["project", "user"]);
683
+ var MemoryExtractionModeSchema = z3.enum([
684
+ "local",
685
+ "localWithFrontierFallback"
686
+ ]);
687
+ var MemoryEmbeddingsProviderSchema = z3.enum(["local", "none"]);
688
+ var MemorySemanticSchema = z3.object({
689
+ enabled: z3.boolean().default(false),
690
+ storeContent: z3.boolean().default(false),
691
+ logPath: z3.string().min(1).default(".opencode/openteam/memory/records.jsonl"),
692
+ indexPath: z3.string().min(1).default(".opencode/openteam/memory/index.sqlite"),
693
+ scope: MemoryScopeSchema.default("project"),
694
+ extraction: z3.object({
695
+ mode: MemoryExtractionModeSchema.default("local"),
696
+ model: ModelRefSchema.nullable().default(null)
697
+ }).default({ mode: "local", model: null }),
698
+ embeddings: z3.object({
699
+ provider: MemoryEmbeddingsProviderSchema.default("local"),
700
+ model: ModelRefSchema.nullable().default(null),
701
+ dim: z3.number().int().positive().default(768)
702
+ }).default({ provider: "local", model: null, dim: 768 }),
703
+ recall: z3.object({
704
+ maxResults: z3.number().int().positive().default(8),
705
+ minSimilarity: z3.number().min(0).max(1).default(0.2)
706
+ }).default({ maxResults: 8, minSimilarity: 0.2 }),
707
+ injection: z3.object({
708
+ enabled: z3.boolean().default(false),
709
+ maxChars: z3.number().int().positive().default(1200),
710
+ maxItems: z3.number().int().positive().default(8)
711
+ }).default({ enabled: false, maxChars: 1200, maxItems: 8 })
712
+ }).default({
713
+ enabled: false,
714
+ storeContent: false,
715
+ logPath: ".opencode/openteam/memory/records.jsonl",
716
+ indexPath: ".opencode/openteam/memory/index.sqlite",
717
+ scope: "project",
718
+ extraction: { mode: "local", model: null },
719
+ embeddings: { provider: "local", model: null, dim: 768 },
720
+ recall: { maxResults: 8, minSimilarity: 0.2 },
721
+ injection: { enabled: false, maxChars: 1200, maxItems: 8 }
722
+ });
723
+ var MemoryConfigSchema = z3.object({
724
+ semantic: MemorySemanticSchema
725
+ });
677
726
  var OpenTeamConfigObjectSchema = z3.object({
678
727
  baseline: z3.object({
679
728
  mode: BaselineModeSchema.default("auto"),
@@ -724,9 +773,14 @@ var OpenTeamConfigObjectSchema = z3.object({
724
773
  }).default({ hardStopOnBudgetExhaustion: false }),
725
774
  privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
726
775
  console: ConsoleConfigSchema,
727
- storage: StorageConfigSchema
776
+ storage: StorageConfigSchema,
777
+ memory: MemoryConfigSchema.optional(),
778
+ graph: GraphConfigSchema.optional()
728
779
  });
729
780
  var OpenTeamConfigSchema = OpenTeamConfigObjectSchema;
781
+ function resolveMemorySemantic(config) {
782
+ return config.memory?.semantic ?? MemorySemanticSchema.parse(undefined);
783
+ }
730
784
 
731
785
  // src/telemetry/events.ts
732
786
  var EVENT_SCHEMA_VERSION = 1;
@@ -1182,6 +1236,208 @@ import {
1182
1236
  createServer as createHttpServer
1183
1237
  } from "node:http";
1184
1238
 
1239
+ // src/console/assets.ts
1240
+ var CONSOLE_STYLE = `
1241
+ :root{color-scheme:dark;--bg:#0f1115;--panel:#171a21;--fg:#e6e8eb;--muted:#8b929c;--good:#3fb950;--local:#58a6ff;--frontier:#d29922;--line:#262b34}
1242
+ *{box-sizing:border-box}
1243
+ body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.5 ui-sans-serif,system-ui,Segoe UI,Roboto,sans-serif}
1244
+ header{padding:16px 24px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
1245
+ header h1{font-size:18px;margin:0}
1246
+ header .meta{color:var(--muted);font-size:12px}
1247
+ main{display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:16px;padding:24px}
1248
+ .panel{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:16px}
1249
+ .panel h2{font-size:14px;margin:0 0 12px;color:var(--muted);text-transform:uppercase;letter-spacing:.04em}
1250
+ .grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
1251
+ .stat{display:flex;flex-direction:column;gap:2px}
1252
+ .stat .k{color:var(--muted);font-size:12px}
1253
+ .stat .v{font-size:18px;font-weight:600}
1254
+ .good{color:var(--good)}
1255
+ .local{color:var(--local)}
1256
+ .frontier{color:var(--frontier)}
1257
+ .muted{color:var(--muted)}
1258
+ table{width:100%;border-collapse:collapse;font-size:13px}
1259
+ th,td{text-align:left;padding:6px 8px;border-bottom:1px solid var(--line)}
1260
+ th{color:var(--muted);font-weight:600}
1261
+ .hash{font-family:ui-monospace,Consolas,monospace;color:var(--muted)}
1262
+ .progress{height:10px;background:#0b0d11;border-radius:6px;overflow:hidden;border:1px solid var(--line)}
1263
+ .progress .bar{height:100%;background:var(--good)}
1264
+ ul.items,ul.timeline{list-style:none;margin:8px 0 0;padding:0;max-height:320px;overflow:auto}
1265
+ ul.items li,ul.timeline li{padding:4px 0;border-bottom:1px solid var(--line)}
1266
+ ul.items li.done{color:var(--muted)}
1267
+ ul.items .box{font-family:monospace}
1268
+ .who{color:var(--local)}
1269
+ .tag{color:var(--frontier);font-size:12px}
1270
+ .kind{display:inline-block;min-width:66px;color:var(--muted);font-size:12px}
1271
+ code{font-family:ui-monospace,Consolas,monospace;background:#0b0d11;padding:1px 5px;border-radius:4px}
1272
+ .mono{font-family:ui-monospace,Consolas,monospace;font-size:12px}
1273
+ .tabs{display:flex;gap:6px;padding:12px 24px 0;flex-wrap:wrap;border-bottom:1px solid var(--line)}
1274
+ .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}
1275
+ .tabs .tab.active{background:var(--bg);border-color:var(--local);color:var(--fg)}
1276
+ .dot{width:8px;height:8px;border-radius:50%;display:inline-block;background:var(--muted)}
1277
+ .badge{font-size:11px;padding:1px 8px;border-radius:10px;border:1px solid var(--line);color:var(--muted);vertical-align:middle}
1278
+ .status-running{background:var(--good)}
1279
+ .badge.status-running{color:var(--good);border-color:var(--good)}
1280
+ .status-idle{background:var(--muted)}
1281
+ .badge.status-idle{color:var(--muted)}
1282
+ .status-waiting-input{background:var(--frontier)}
1283
+ .badge.status-waiting-input{color:var(--frontier);border-color:var(--frontier)}
1284
+ .status-ended{background:#6e7681}
1285
+ .badge.status-ended{color:#6e7681}
1286
+ .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}
1287
+ .term .tl{padding:1px 0}
1288
+ .term .tl.input{color:var(--local)}
1289
+ .term .tl.tool{color:var(--frontier)}
1290
+ .term .tl.status{color:var(--muted)}
1291
+ .term .tl.error{color:#f85149}
1292
+ .term .tl.permission{color:var(--frontier);font-weight:600}
1293
+ .term form.terminput{display:flex;gap:8px;margin-top:10px}
1294
+ .term form.terminput input{flex:1;background:#0b0d11;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
1295
+ .term form.terminput button{background:var(--local);color:#04122b;border:none;border-radius:8px;padding:8px 16px;cursor:pointer;font:inherit;font-weight:600}
1296
+ .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}
1297
+ .pty form.ptyinput{display:flex;gap:8px;margin-top:10px}
1298
+ .pty form.ptyinput input{flex:1;background:#05070a;color:var(--fg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;font:inherit}
1299
+ .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}
1300
+ .pty .pty-start:disabled{opacity:.5;cursor:default}
1301
+ `;
1302
+ var CONSOLE_CLIENT_JS = `
1303
+ (function(){
1304
+ var authTok='';
1305
+ try{
1306
+ var qp=new URLSearchParams(window.location.search).get('token');
1307
+ if(qp){authTok='?token='+encodeURIComponent(qp)}
1308
+ }catch(e){/* ignore */}
1309
+ function reloadIfChanged(prev){
1310
+ fetch('/api/state'+authTok).then(function(r){return r.json()}).then(function(s){
1311
+ if(s.generatedAt!==prev){location.reload()}
1312
+ }).catch(function(){});
1313
+ }
1314
+ var current=document.documentElement.getAttribute('data-generated')||'';
1315
+ if('EventSource' in window){
1316
+ try{
1317
+ var es=new EventSource('/events'+authTok);
1318
+ es.addEventListener('state',function(){location.reload()});
1319
+ es.onerror=function(){/* fallback below */};
1320
+ }catch(e){/* ignore */}
1321
+ }
1322
+ var ms=parseInt(document.documentElement.getAttribute('data-refresh')||'2000',10);
1323
+ setInterval(function(){reloadIfChanged(current)},isNaN(ms)?2000:Math.max(250,ms));
1324
+
1325
+ function selectTab(name){
1326
+ var tabs=document.querySelectorAll('#tabbar .tab');
1327
+ for(var i=0;i<tabs.length;i++){tabs[i].classList.toggle('active',tabs[i].getAttribute('data-tab')===name)}
1328
+ var views=document.querySelectorAll('main .view');
1329
+ var matched=false;
1330
+ for(var j=0;j<views.length;j++){
1331
+ var show=views[j].getAttribute('data-view')===name;
1332
+ views[j].hidden=!show;
1333
+ if(show){matched=true;if(name!=='all'){connectTerminal(views[j],name)}}
1334
+ }
1335
+ if(!matched){return false}
1336
+ try{sessionStorage.setItem('openteam.tab',name)}catch(e){}
1337
+ return true;
1338
+ }
1339
+ function connectTerminal(view,id){
1340
+ if(view.__wired){return}
1341
+ var root=document.documentElement;
1342
+ var token=root.getAttribute('data-token');
1343
+ if(root.getAttribute('data-terminal')!=='1'||!token){return}
1344
+ var log=view.querySelector('.termlog');
1345
+ if(!log){return}
1346
+ view.__wired=true;
1347
+ function append(cls,text){
1348
+ var line=document.createElement('div');
1349
+ line.className='tl '+cls;line.textContent=text;
1350
+ log.appendChild(line);log.scrollTop=log.scrollHeight;
1351
+ }
1352
+ var q='/console/session/'+encodeURIComponent(id);
1353
+ var tk='token='+encodeURIComponent(token);
1354
+ if('EventSource' in window){
1355
+ try{
1356
+ var es=new EventSource(q+'/stream?'+tk);
1357
+ es.addEventListener('frame',function(e){try{var f=JSON.parse(e.data);append(f.kind,f.text)}catch(_){}});
1358
+ es.onerror=function(){/* reconecta solo */};
1359
+ }catch(_){/* ignore */}
1360
+ }
1361
+ var form=view.querySelector('form.terminput');
1362
+ if(form){
1363
+ form.addEventListener('submit',function(e){
1364
+ e.preventDefault();
1365
+ var inp=form.querySelector('input[name=text]');
1366
+ var text=inp.value;
1367
+ if(!text){return}
1368
+ inp.value='';append('input','› '+text);
1369
+ fetch(q+'/input?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({text:text})})
1370
+ .then(function(r){if(!r.ok){append('error','✖ envío rechazado ('+r.status+')')}})
1371
+ .catch(function(){append('error','✖ no se pudo enviar')});
1372
+ });
1373
+ }
1374
+ connectPty(view,id,q,tk);
1375
+ }
1376
+ function connectPty(view,id,q,tk){
1377
+ var root=document.documentElement;
1378
+ if(root.getAttribute('data-pty')!=='1'){return}
1379
+ var pane=view.querySelector('.pty');
1380
+ if(!pane){return}
1381
+ var log=pane.querySelector('.ptylog');
1382
+ var startBtn=pane.querySelector('.pty-start');
1383
+ var input=pane.querySelector('form.ptyinput');
1384
+ function plog(text){
1385
+ var d=document.createElement('span');d.textContent=text;
1386
+ log.appendChild(d);log.scrollTop=log.scrollHeight;
1387
+ }
1388
+ if(!startBtn){return}
1389
+ startBtn.addEventListener('click',function(){
1390
+ startBtn.disabled=true;plog('Abriendo shell…
1391
+ ');
1392
+ fetch(q+'/pty?'+tk,{method:'POST',headers:{'content-type':'application/json'},body:'{}'})
1393
+ .then(function(r){return r.ok?r.json():Promise.reject(r.status)})
1394
+ .then(function(info){openPtyWs(info.wsUrl)})
1395
+ .catch(function(s){plog('✖ no se pudo abrir PTY ('+s+')
1396
+ ');startBtn.disabled=false});
1397
+ });
1398
+ function openPtyWs(url){
1399
+ try{
1400
+ var ws=new WebSocket(url);
1401
+ ws.binaryType='arraybuffer';
1402
+ ws.onmessage=function(e){
1403
+ var data=e.data;
1404
+ if(data instanceof ArrayBuffer){data=new TextDecoder().decode(new Uint8Array(data))}
1405
+ plog(data);
1406
+ };
1407
+ ws.onclose=function(){plog('
1408
+ [shell cerrada]
1409
+ ');startBtn.disabled=false};
1410
+ ws.onerror=function(){plog('
1411
+ ✖ error de WebSocket
1412
+ ')};
1413
+ if(input){
1414
+ input.hidden=false;
1415
+ input.addEventListener('submit',function(e){
1416
+ e.preventDefault();
1417
+ var c=input.querySelector('input[name=cmd]');
1418
+ if(!c.value){return}
1419
+ ws.send(c.value+'\r');c.value='';
1420
+ });
1421
+ }
1422
+ }catch(_){plog('
1423
+ ✖ WebSocket no disponible
1424
+ ');startBtn.disabled=false}
1425
+ }
1426
+ }
1427
+ var bar=document.getElementById('tabbar');
1428
+ if(bar){
1429
+ bar.addEventListener('click',function(e){
1430
+ var t=e.target;
1431
+ while(t&&t!==bar&&!t.getAttribute('data-tab')){t=t.parentNode}
1432
+ if(t&&t.getAttribute('data-tab')){selectTab(t.getAttribute('data-tab'))}
1433
+ });
1434
+ }
1435
+ var saved='all';
1436
+ try{saved=sessionStorage.getItem('openteam.tab')||'all'}catch(e){}
1437
+ if(!selectTab(saved)){selectTab('all')}
1438
+ })();
1439
+ `;
1440
+
1185
1441
  // src/console/render.ts
1186
1442
  var TIERS = [
1187
1443
  "trivial",
@@ -1497,201 +1753,6 @@ function sessionView(tab, terminalEnabled, ptyEnabled) {
1497
1753
  ].join("");
1498
1754
  return `<div class="view" data-view="${escapeHtml(tab.sessionID)}" hidden>${body}</div>`;
1499
1755
  }
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
1756
  function renderConsoleHtml(state, options = {}) {
1696
1757
  const refreshMs = options.refreshMs ?? 2000;
1697
1758
  const terminalEnabled = options.terminalEnabled === true;
@@ -1720,7 +1781,7 @@ function renderConsoleHtml(state, options = {}) {
1720
1781
  '<meta charset="utf-8">',
1721
1782
  '<meta name="viewport" content="width=device-width,initial-scale=1">',
1722
1783
  "<title>openteam console</title>",
1723
- `<style>${STYLE}</style>`,
1784
+ `<style>${CONSOLE_STYLE}</style>`,
1724
1785
  "</head>",
1725
1786
  "<body>",
1726
1787
  "<header>",
@@ -1729,7 +1790,7 @@ function renderConsoleHtml(state, options = {}) {
1729
1790
  "</header>",
1730
1791
  tabBar(state.sessionsDetail),
1731
1792
  `<main>${body}</main>`,
1732
- `<script>${CLIENT_JS}</script>`,
1793
+ `<script>${CONSOLE_CLIENT_JS}</script>`,
1733
1794
  "</body>",
1734
1795
  "</html>"
1735
1796
  ].join("");
@@ -2359,6 +2420,18 @@ var HTML_HEADERS = { "content-type": "text/html; charset=utf-8" };
2359
2420
  function isAddressInUse(error) {
2360
2421
  return typeof error === "object" && error !== null && "code" in error && error.code === "EADDRINUSE";
2361
2422
  }
2423
+ function bearerFromHeader3(request) {
2424
+ const header = request.headers.authorization;
2425
+ if (typeof header !== "string" || !header.startsWith("Bearer ")) {
2426
+ return;
2427
+ }
2428
+ const value = header.slice("Bearer ".length).trim();
2429
+ return value.length === 0 ? undefined : value;
2430
+ }
2431
+ function readAuthorized(request, url, token) {
2432
+ const provided = tokenFromQuery(url.searchParams) ?? bearerFromHeader3(request);
2433
+ return provided !== undefined && safeEqualToken(provided, token);
2434
+ }
2362
2435
  async function renderState(deps) {
2363
2436
  return renderConsoleHtml(buildConsoleState(await deps.readSnapshot()), {
2364
2437
  refreshMs: deps.config.refreshMs,
@@ -2426,6 +2499,11 @@ async function createConsoleServer(deps) {
2426
2499
  return;
2427
2500
  }
2428
2501
  const path = url2.pathname;
2502
+ const remoteToken = deps.storageApi?.token;
2503
+ if (remoteToken !== undefined && (path === "/" || path === "/index.html" || path === "/api/state" || path === "/events") && !readAuthorized(request, url2, remoteToken)) {
2504
+ response.writeHead(401, JSON_HEADERS3).end(JSON.stringify({ error: "unauthorized" }));
2505
+ return;
2506
+ }
2429
2507
  if (path === "/" || path === "/index.html") {
2430
2508
  response.writeHead(200, HTML_HEADERS).end(await renderState(deps));
2431
2509
  return;
@@ -2465,7 +2543,12 @@ async function createConsoleServer(deps) {
2465
2543
  break;
2466
2544
  }
2467
2545
  if (await tryListen(server, deps.config.host, port)) {
2468
- boundPort = port;
2546
+ const address = server.address();
2547
+ if (typeof address !== "object" || address === null) {
2548
+ server.close();
2549
+ throw new Error("console: bound address is unavailable");
2550
+ }
2551
+ boundPort = address.port;
2469
2552
  bound = true;
2470
2553
  break;
2471
2554
  }
@@ -2899,6 +2982,8 @@ async function runConsoleServe(deps, options) {
2899
2982
  deps.log("Escuchando en loopback. Pulsa Ctrl+C para parar.");
2900
2983
  if (config.console.remoteStorage) {
2901
2984
  deps.log("Storage remoto expuesto en /storage/* (read-only, requiere token).");
2985
+ deps.log("La consola y el estado exigen token; ábrela con la URL firmada:");
2986
+ deps.log(` ${runtime.url}/?token=${token}`);
2902
2987
  }
2903
2988
  if (config.storage.sqliteIndex.enabled) {
2904
2989
  const buildIndex = deps.buildSqliteIndex ?? buildSqliteIndexFromConfig;
@@ -3220,6 +3305,9 @@ function normalizeBaseURL(baseURL) {
3220
3305
  function modelsEndpoint(baseURL) {
3221
3306
  return `${normalizeBaseURL(baseURL)}/models`;
3222
3307
  }
3308
+ function embeddingsEndpoint(baseURL) {
3309
+ return `${normalizeBaseURL(baseURL)}/embeddings`;
3310
+ }
3223
3311
  function errorMessage(error) {
3224
3312
  if (error instanceof Error) {
3225
3313
  return error.message;
@@ -4031,7 +4119,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
4031
4119
  ];
4032
4120
  const frontmatter = [
4033
4121
  "---",
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."',
4122
+ '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
4123
  "mode: primary",
4036
4124
  `model: ${model}`,
4037
4125
  "temperature: 0.2",
@@ -4040,7 +4128,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
4040
4128
  ].join(`
4041
4129
  `);
4042
4130
  const body = [
4043
- "Eres el orquestador de **openteam**, al estilo Squad. Eres el único agente",
4131
+ "Eres el orquestador multiagente de **openteam**. Eres el único agente",
4044
4132
  "primario: no existe un equipo precreado. Tú **construyes el equipo de",
4045
4133
  "subagentes a demanda** según el objetivo del usuario y del repositorio.",
4046
4134
  "",
@@ -4066,7 +4154,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
4066
4154
  "",
4067
4155
  "## Casting: elige un universo y nombra al equipo",
4068
4156
  "",
4069
- "Como Squad, el equipo tiene una identidad temática persistente:",
4157
+ "El equipo tiene una identidad temática persistente:",
4070
4158
  "",
4071
4159
  "1. Al crear el equipo por primera vez, **elige un universo** temático (p. ej.",
4072
4160
  " una película, serie, cómic o mitología). Si el usuario tiene preferencia,",
@@ -4251,9 +4339,9 @@ var HELP = [
4251
4339
  " openteam local on Reactiva el routing local-first",
4252
4340
  " openteam doctor Diagnóstico de runtimes y config",
4253
4341
  " 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)",
4342
+ " openteam console [alfa] Lanza la Console web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
4343
+ " openteam console --status [alfa] Muestra la config de la Console sin lanzarla",
4344
+ " openteam tunnel [alfa] Expone la Console vía Dev Tunnels (--terminal expone opencode; Ctrl+C para cerrar)",
4257
4345
  " openteam mcp Servidor MCP de solo lectura (memoria del equipo) sobre stdio",
4258
4346
  " openteam report Resumen de coste/ahorro (telemetría)",
4259
4347
  " openteam yolo status Muestra si el modo YOLO está activo",
@@ -4497,6 +4585,32 @@ async function writeOpenTeamConfigFile(config, path = DEFAULT_CONFIG_PATH, stora
4497
4585
  await storage.write(path, serializeOpenTeamConfig(config));
4498
4586
  }
4499
4587
 
4588
+ // src/commands/frontierCatalog.ts
4589
+ function weightedCost(inputUSD, outputUSD) {
4590
+ return 0.75 * inputUSD + 0.25 * outputUSD;
4591
+ }
4592
+ function profileWeightedCost(profile) {
4593
+ return weightedCost(profile.costPer1M.inputUSD, profile.costPer1M.outputUSD);
4594
+ }
4595
+ function sortFrontierByCost(profiles) {
4596
+ return [...profiles].sort((a, b) => profileWeightedCost(a) - profileWeightedCost(b));
4597
+ }
4598
+ function isFreeFrontier(profile) {
4599
+ return profile.costPer1M.inputUSD === 0 && profile.costPer1M.outputUSD === 0;
4600
+ }
4601
+ function frontierCostLabel(profile) {
4602
+ if (isFreeFrontier(profile)) {
4603
+ return "gratis";
4604
+ }
4605
+ return `$${profile.costPer1M.inputUSD}/$${profile.costPer1M.outputUSD} por 1M`;
4606
+ }
4607
+ function frontierProviders(profiles) {
4608
+ return [...new Set(profiles.map((profile) => profile.ref.providerID))].sort((a, b) => a.localeCompare(b));
4609
+ }
4610
+ function frontierModelsForProvider(profiles, providerID) {
4611
+ return sortFrontierByCost(profiles.filter((profile) => profile.ref.providerID === providerID));
4612
+ }
4613
+
4500
4614
  // src/commands/slashCommand.ts
4501
4615
  var OPENTEAM_COMMAND_DIR = ".opencode/command";
4502
4616
  var OPENTEAM_COMMAND_PATH = `${OPENTEAM_COMMAND_DIR}/openteam.md`;
@@ -4580,30 +4694,6 @@ var KNOWN_RUNTIMES = [
4580
4694
  dynamicPort: true
4581
4695
  }
4582
4696
  ];
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
4697
  function knownRuntime(id) {
4608
4698
  const found = KNOWN_RUNTIMES.find((runtime) => runtime.id === id);
4609
4699
  if (found === undefined) {
@@ -5039,9 +5129,244 @@ async function runSetup(deps) {
5039
5129
  }
5040
5130
  }
5041
5131
 
5132
+ // src/config/migrations.ts
5133
+ var CURRENT_CONFIG_VERSION = 1;
5134
+
5135
+ class ConfigMigrationError extends Error {
5136
+ code;
5137
+ detail;
5138
+ constructor(code, detail) {
5139
+ super(`${code}: ${detail}`);
5140
+ this.name = "ConfigMigrationError";
5141
+ this.code = code;
5142
+ this.detail = detail;
5143
+ }
5144
+ }
5145
+ function migrateConfig(raw) {
5146
+ if (typeof raw !== "object" || raw === null) {
5147
+ return raw;
5148
+ }
5149
+ const record = raw;
5150
+ const version = record.configVersion;
5151
+ if (version === undefined) {
5152
+ return { ...record, configVersion: CURRENT_CONFIG_VERSION };
5153
+ }
5154
+ if (typeof version !== "number" || !Number.isInteger(version) || version < 1) {
5155
+ throw new ConfigMigrationError("unknown-version", String(version));
5156
+ }
5157
+ if (version > CURRENT_CONFIG_VERSION) {
5158
+ throw new ConfigMigrationError("unsupported-version", String(version));
5159
+ }
5160
+ return record;
5161
+ }
5162
+
5042
5163
  // src/config/load.ts
5043
5164
  function loadOpenTeamConfig(raw) {
5044
- return OpenTeamConfigSchema.parse(raw ?? {});
5165
+ return OpenTeamConfigSchema.parse(migrateConfig(raw ?? {}));
5166
+ }
5167
+
5168
+ // src/memory/types.ts
5169
+ import { z as z6 } from "zod";
5170
+ var SHARED_OWNER_KEY = "*";
5171
+ var OwnerKeySchema = z6.string().min(1);
5172
+ var MemoryKindSchema = z6.enum(["fact", "preference", "entity"]);
5173
+ var MemoryBaseSchema = z6.object({
5174
+ id: z6.string().min(1),
5175
+ ownerKey: OwnerKeySchema,
5176
+ confidence: z6.number().min(0).max(1),
5177
+ validFrom: z6.number().finite(),
5178
+ validUntil: z6.number().finite().nullable().default(null),
5179
+ createdAt: z6.number().finite(),
5180
+ invalidatedAt: z6.number().finite().nullable().default(null),
5181
+ sourceHash: z6.string().min(1),
5182
+ supersededBy: z6.string().min(1).nullable().default(null)
5183
+ });
5184
+ var FactSchema = MemoryBaseSchema.extend({
5185
+ kind: z6.literal("fact"),
5186
+ subject: z6.string().min(1),
5187
+ predicate: z6.string().min(1),
5188
+ object: z6.string().min(1),
5189
+ category: z6.string().min(1).nullable().default(null)
5190
+ });
5191
+ var PreferenceSchema = MemoryBaseSchema.extend({
5192
+ kind: z6.literal("preference"),
5193
+ category: z6.string().min(1),
5194
+ preference: z6.string().min(1),
5195
+ context: z6.string().min(1).nullable().default(null),
5196
+ lastAccessedAt: z6.number().finite().nullable().default(null),
5197
+ accessCount: z6.number().int().min(0).default(0)
5198
+ });
5199
+ var EntitySchema = MemoryBaseSchema.extend({
5200
+ kind: z6.literal("entity"),
5201
+ canonicalName: z6.string().min(1),
5202
+ type: z6.string().min(1),
5203
+ aliases: z6.array(z6.string().min(1)).default([])
5204
+ });
5205
+ var MemoryRecordSchema = z6.discriminatedUnion("kind", [
5206
+ FactSchema,
5207
+ PreferenceSchema,
5208
+ EntitySchema
5209
+ ]);
5210
+ var RecallQuerySchema = z6.object({
5211
+ ownerKey: OwnerKeySchema,
5212
+ kinds: z6.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
5213
+ asOf: z6.number().finite().nullable().default(null),
5214
+ limit: z6.number().int().positive().default(8),
5215
+ minSimilarity: z6.number().min(0).max(1).default(0.2),
5216
+ includeShared: z6.boolean().default(true)
5217
+ });
5218
+
5219
+ // src/memory/rank.ts
5220
+ var DAY_MS = 86400000;
5221
+ var DEFAULT_RANK_SIGNALS = {
5222
+ similarityWeight: 1,
5223
+ recencyWeight: 0.2,
5224
+ accessWeight: 0.1,
5225
+ recencyHalfLifeMs: 14 * DAY_MS
5226
+ };
5227
+ function recencyTimestamp(record) {
5228
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
5229
+ return record.lastAccessedAt;
5230
+ }
5231
+ return record.createdAt;
5232
+ }
5233
+ function recencyScore(record, signals) {
5234
+ const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
5235
+ return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
5236
+ }
5237
+ function accessScore(record) {
5238
+ if (record.kind !== "preference") {
5239
+ return 0;
5240
+ }
5241
+ return 1 - 1 / (1 + record.accessCount);
5242
+ }
5243
+ function scoreCandidate(candidate, signals) {
5244
+ return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
5245
+ }
5246
+ function rankRecall(candidates, signals) {
5247
+ return candidates.map((candidate) => ({
5248
+ ...candidate,
5249
+ score: scoreCandidate(candidate, signals)
5250
+ })).sort((a, b) => {
5251
+ if (b.score !== a.score) {
5252
+ return b.score - a.score;
5253
+ }
5254
+ return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
5255
+ });
5256
+ }
5257
+
5258
+ // src/memory/temporal.ts
5259
+ function isTransactionActiveAt(record, asOf) {
5260
+ return record.invalidatedAt === null || record.invalidatedAt > asOf;
5261
+ }
5262
+ function isValidAt(record, asOf) {
5263
+ if (record.validFrom > asOf) {
5264
+ return false;
5265
+ }
5266
+ return record.validUntil === null || record.validUntil > asOf;
5267
+ }
5268
+ function isActiveAt(record, asOf) {
5269
+ return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
5270
+ }
5271
+ function filterActive(records, asOf) {
5272
+ return records.filter((record) => isActiveAt(record, asOf));
5273
+ }
5274
+ function coalesceWindow(a, b) {
5275
+ const validFrom = Math.min(a.validFrom, b.validFrom);
5276
+ const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
5277
+ return { validFrom, validUntil };
5278
+ }
5279
+
5280
+ // src/memory/vector.ts
5281
+ function dot(a, b) {
5282
+ if (a.length !== b.length) {
5283
+ throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
5284
+ }
5285
+ let sum = 0;
5286
+ for (let i = 0;i < a.length; i += 1) {
5287
+ sum += a[i] * b[i];
5288
+ }
5289
+ return sum;
5290
+ }
5291
+ function norm(a) {
5292
+ let sum = 0;
5293
+ for (let i = 0;i < a.length; i += 1) {
5294
+ const value = a[i];
5295
+ sum += value * value;
5296
+ }
5297
+ return Math.sqrt(sum);
5298
+ }
5299
+ function cosineSimilarity(a, b) {
5300
+ const denom = norm(a) * norm(b);
5301
+ if (denom === 0) {
5302
+ return 0;
5303
+ }
5304
+ return dot(a, b) / denom;
5305
+ }
5306
+
5307
+ // src/memory/recall.ts
5308
+ function matchesScope(record, query) {
5309
+ if (record.ownerKey === query.ownerKey) {
5310
+ return true;
5311
+ }
5312
+ return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
5313
+ }
5314
+ function recall(records, queryEmbedding, query, deps) {
5315
+ const asOf = query.asOf ?? deps.now;
5316
+ const kinds = new Set(query.kinds);
5317
+ const candidates = [];
5318
+ for (const record of filterActive(records, asOf)) {
5319
+ if (!kinds.has(record.kind) || !matchesScope(record, query)) {
5320
+ continue;
5321
+ }
5322
+ const embedding = deps.embeddings.get(record.id);
5323
+ if (embedding === undefined) {
5324
+ continue;
5325
+ }
5326
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
5327
+ if (similarity < query.minSimilarity) {
5328
+ continue;
5329
+ }
5330
+ candidates.push({ record, similarity });
5331
+ }
5332
+ const signals = {
5333
+ now: deps.now,
5334
+ ...deps.signals ?? DEFAULT_RANK_SIGNALS
5335
+ };
5336
+ return rankRecall(candidates, signals).slice(0, query.limit);
5337
+ }
5338
+
5339
+ // src/storage/index/memoryRecall.ts
5340
+ async function recallMemory(deps, queryText, query) {
5341
+ const [queryEmbedding] = await deps.embedder([queryText]);
5342
+ if (queryEmbedding === undefined) {
5343
+ return [];
5344
+ }
5345
+ const records = deps.index.allRecords();
5346
+ const embeddings = deps.index.embeddingMap();
5347
+ const recallDeps = deps.signals === undefined ? { embeddings, now: deps.now } : { embeddings, now: deps.now, signals: deps.signals };
5348
+ return recall(records, queryEmbedding, query, recallDeps);
5349
+ }
5350
+
5351
+ // src/mcp/semanticRecall.ts
5352
+ function buildSemanticRecall(deps, policy) {
5353
+ return async ({ queryText, kinds, limit }) => {
5354
+ const query = RecallQuerySchema.parse({
5355
+ ownerKey: policy.ownerKey,
5356
+ kinds,
5357
+ asOf: policy.asOf ?? null,
5358
+ limit,
5359
+ minSimilarity: policy.minSimilarity,
5360
+ includeShared: policy.includeShared
5361
+ });
5362
+ const recallDeps = deps.signals === undefined ? { index: deps.index, embedder: deps.embedder, now: deps.now() } : {
5363
+ index: deps.index,
5364
+ embedder: deps.embedder,
5365
+ now: deps.now(),
5366
+ signals: deps.signals
5367
+ };
5368
+ return recallMemory(recallDeps, queryText, query);
5369
+ };
5045
5370
  }
5046
5371
 
5047
5372
  // src/mcp/teamMemory.ts
@@ -5133,6 +5458,45 @@ var MCP_TOOLS = [
5133
5458
  inputSchema: { type: "object", properties: {} }
5134
5459
  }
5135
5460
  ];
5461
+ var RECALL_QUERY_PROP = {
5462
+ query: {
5463
+ type: "string",
5464
+ description: "Texto de consulta para el recall semántico."
5465
+ },
5466
+ ...LIMIT_PROP
5467
+ };
5468
+ var MEMORY_TOOLS = [
5469
+ {
5470
+ name: "recall_facts",
5471
+ description: "Hechos relevantes (sujeto-predicado-objeto) para la consulta, por similitud semántica. Contenido redactado salvo storeContent.",
5472
+ inputSchema: {
5473
+ type: "object",
5474
+ properties: { ...RECALL_QUERY_PROP },
5475
+ required: ["query"]
5476
+ }
5477
+ },
5478
+ {
5479
+ name: "recall_preferences",
5480
+ description: "Preferencias del usuario/proyecto relevantes para la consulta, por similitud semántica.",
5481
+ inputSchema: {
5482
+ type: "object",
5483
+ properties: { ...RECALL_QUERY_PROP },
5484
+ required: ["query"]
5485
+ }
5486
+ },
5487
+ {
5488
+ name: "similar_tasks",
5489
+ description: "Memoria (hechos/preferencias/entidades) relevante a una tarea, por similitud semántica; útil para reutilizar contexto previo.",
5490
+ inputSchema: {
5491
+ type: "object",
5492
+ properties: { ...RECALL_QUERY_PROP },
5493
+ required: ["query"]
5494
+ }
5495
+ }
5496
+ ];
5497
+ function availableTools(deps) {
5498
+ return deps.recallSemantic === undefined ? MCP_TOOLS : [...MCP_TOOLS, ...MEMORY_TOOLS];
5499
+ }
5136
5500
  function response(id, result) {
5137
5501
  return { jsonrpc: "2.0", id, result };
5138
5502
  }
@@ -5148,7 +5512,58 @@ function optionalString(value) {
5148
5512
  function optionalLimit(value) {
5149
5513
  return typeof value === "number" && Number.isFinite(value) ? value : undefined;
5150
5514
  }
5515
+ var DEFAULT_RECALL_LIMIT = 8;
5516
+ function projectRecall(results) {
5517
+ return results.map(({ record, similarity, score }) => {
5518
+ const base = {
5519
+ id: record.id,
5520
+ kind: record.kind,
5521
+ ownerKey: record.ownerKey,
5522
+ confidence: record.confidence,
5523
+ similarity,
5524
+ score
5525
+ };
5526
+ if (record.kind === "fact") {
5527
+ return {
5528
+ ...base,
5529
+ subject: record.subject,
5530
+ predicate: record.predicate,
5531
+ object: record.object,
5532
+ category: record.category
5533
+ };
5534
+ }
5535
+ if (record.kind === "preference") {
5536
+ return {
5537
+ ...base,
5538
+ category: record.category,
5539
+ preference: record.preference
5540
+ };
5541
+ }
5542
+ return { ...base, canonicalName: record.canonicalName, type: record.type };
5543
+ });
5544
+ }
5545
+ async function callSemanticRecall(recall2, kinds, args) {
5546
+ const query = optionalString(args.query);
5547
+ if (query === undefined) {
5548
+ return textContent({ error: "requiere 'query'" });
5549
+ }
5550
+ const limit = optionalLimit(args.limit) ?? DEFAULT_RECALL_LIMIT;
5551
+ const results = await recall2({ queryText: query, kinds, limit });
5552
+ return textContent(projectRecall(results));
5553
+ }
5151
5554
  async function callTool(name, args, deps) {
5555
+ if (deps.recallSemantic !== undefined) {
5556
+ switch (name) {
5557
+ case "recall_facts":
5558
+ return callSemanticRecall(deps.recallSemantic, ["fact"], args);
5559
+ case "recall_preferences":
5560
+ return callSemanticRecall(deps.recallSemantic, ["preference"], args);
5561
+ case "similar_tasks":
5562
+ return callSemanticRecall(deps.recallSemantic, ["fact", "preference", "entity"], args);
5563
+ default:
5564
+ break;
5565
+ }
5566
+ }
5152
5567
  const memory = await deps.readMemory();
5153
5568
  switch (name) {
5154
5569
  case "list_decisions": {
@@ -5189,7 +5604,7 @@ async function handleMcpMessage(message, deps) {
5189
5604
  serverInfo
5190
5605
  });
5191
5606
  case "tools/list":
5192
- return response(id, { tools: MCP_TOOLS });
5607
+ return response(id, { tools: availableTools(deps) });
5193
5608
  case "tools/call": {
5194
5609
  const params = message.params ?? {};
5195
5610
  const name = optionalString(params.name);
@@ -5258,6 +5673,456 @@ async function* linesFromReadable(readable) {
5258
5673
  }
5259
5674
  }
5260
5675
 
5676
+ // src/memory/consolidate.ts
5677
+ var DAY_MS2 = 86400000;
5678
+ var DEFAULT_DECAY_PARAMS = {
5679
+ halfLifeMs: 90 * DAY_MS2,
5680
+ forgetThreshold: 0.05
5681
+ };
5682
+ function lastReinforcedAt(record) {
5683
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
5684
+ return record.lastAccessedAt;
5685
+ }
5686
+ return record.createdAt;
5687
+ }
5688
+ function decayedConfidence(record, now, params = DEFAULT_DECAY_PARAMS) {
5689
+ const ageMs = Math.max(0, now - lastReinforcedAt(record));
5690
+ return record.confidence * 0.5 ** (ageMs / params.halfLifeMs);
5691
+ }
5692
+ function consolidate(records, now, params = DEFAULT_DECAY_PARAMS) {
5693
+ const tombstones = [];
5694
+ for (const record of records) {
5695
+ if (!isTransactionActiveAt(record, now)) {
5696
+ continue;
5697
+ }
5698
+ if (decayedConfidence(record, now, params) < params.forgetThreshold) {
5699
+ tombstones.push({ ...record, invalidatedAt: now, supersededBy: null });
5700
+ }
5701
+ }
5702
+ return tombstones;
5703
+ }
5704
+
5705
+ // src/memory/merge.ts
5706
+ var SEP = "\x00";
5707
+ function factSlotKey(fact) {
5708
+ return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
5709
+ }
5710
+ function factValueKey(fact) {
5711
+ return [factSlotKey(fact), fact.object].join(SEP);
5712
+ }
5713
+ function preferenceSlotKey(preference) {
5714
+ return [preference.category, preference.ownerKey].join(SEP);
5715
+ }
5716
+ function preferenceValueKey(preference) {
5717
+ return [preferenceSlotKey(preference), preference.preference].join(SEP);
5718
+ }
5719
+ function entitySlotKey(entity) {
5720
+ return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
5721
+ }
5722
+ function coalesceFact(existing, incoming) {
5723
+ const window = coalesceWindow(existing, incoming);
5724
+ return {
5725
+ ...existing,
5726
+ validFrom: window.validFrom,
5727
+ validUntil: window.validUntil,
5728
+ confidence: Math.max(existing.confidence, incoming.confidence)
5729
+ };
5730
+ }
5731
+ function applyFact(state, incoming, now) {
5732
+ const incomingSlot = factSlotKey(incoming);
5733
+ const incomingValue = factValueKey(incoming);
5734
+ const superseded = [];
5735
+ const records = [];
5736
+ let reaffirmed = false;
5737
+ for (const fact of state) {
5738
+ if (!isActiveAt(fact, now)) {
5739
+ records.push(fact);
5740
+ continue;
5741
+ }
5742
+ if (factValueKey(fact) === incomingValue) {
5743
+ records.push(coalesceFact(fact, incoming));
5744
+ reaffirmed = true;
5745
+ continue;
5746
+ }
5747
+ if (factSlotKey(fact) === incomingSlot) {
5748
+ const dead = {
5749
+ ...fact,
5750
+ invalidatedAt: now,
5751
+ supersededBy: incoming.id
5752
+ };
5753
+ superseded.push(dead);
5754
+ records.push(dead);
5755
+ continue;
5756
+ }
5757
+ records.push(fact);
5758
+ }
5759
+ if (!reaffirmed) {
5760
+ records.push(incoming);
5761
+ }
5762
+ return { records, superseded };
5763
+ }
5764
+ function applyPreference(state, incoming, now) {
5765
+ const incomingSlot = preferenceSlotKey(incoming);
5766
+ const incomingValue = preferenceValueKey(incoming);
5767
+ const superseded = [];
5768
+ const records = [];
5769
+ let reaffirmed = false;
5770
+ for (const preference of state) {
5771
+ if (!isActiveAt(preference, now)) {
5772
+ records.push(preference);
5773
+ continue;
5774
+ }
5775
+ if (preferenceValueKey(preference) === incomingValue) {
5776
+ records.push({
5777
+ ...preference,
5778
+ confidence: Math.max(preference.confidence, incoming.confidence)
5779
+ });
5780
+ reaffirmed = true;
5781
+ continue;
5782
+ }
5783
+ if (preferenceSlotKey(preference) === incomingSlot) {
5784
+ const dead = {
5785
+ ...preference,
5786
+ invalidatedAt: now,
5787
+ supersededBy: incoming.id
5788
+ };
5789
+ superseded.push(dead);
5790
+ records.push(dead);
5791
+ continue;
5792
+ }
5793
+ records.push(preference);
5794
+ }
5795
+ if (!reaffirmed) {
5796
+ records.push(incoming);
5797
+ }
5798
+ return { records, superseded };
5799
+ }
5800
+ function mergeAliases(existing, incoming) {
5801
+ const seen = new Set(existing);
5802
+ const merged = [...existing];
5803
+ for (const alias of incoming) {
5804
+ if (!seen.has(alias)) {
5805
+ seen.add(alias);
5806
+ merged.push(alias);
5807
+ }
5808
+ }
5809
+ return merged;
5810
+ }
5811
+ function applyEntity(state, incoming, now) {
5812
+ const incomingSlot = entitySlotKey(incoming);
5813
+ const records = [];
5814
+ let merged = false;
5815
+ for (const entity of state) {
5816
+ if (isActiveAt(entity, now) && entitySlotKey(entity) === incomingSlot) {
5817
+ records.push({
5818
+ ...entity,
5819
+ aliases: mergeAliases(entity.aliases, incoming.aliases),
5820
+ confidence: Math.max(entity.confidence, incoming.confidence)
5821
+ });
5822
+ merged = true;
5823
+ continue;
5824
+ }
5825
+ records.push(entity);
5826
+ }
5827
+ if (!merged) {
5828
+ records.push(incoming);
5829
+ }
5830
+ return { records, superseded: [] };
5831
+ }
5832
+
5833
+ // src/memory/fold.ts
5834
+ function emptyState() {
5835
+ return { facts: [], preferences: [], entities: [] };
5836
+ }
5837
+ function applyTombstone(state, tombstone) {
5838
+ const mark = (records) => records.map((record) => record.id === tombstone.id ? {
5839
+ ...record,
5840
+ invalidatedAt: tombstone.invalidatedAt,
5841
+ supersededBy: tombstone.supersededBy
5842
+ } : record);
5843
+ if (tombstone.kind === "fact") {
5844
+ state.facts = mark(state.facts);
5845
+ } else if (tombstone.kind === "preference") {
5846
+ state.preferences = mark(state.preferences);
5847
+ } else {
5848
+ state.entities = mark(state.entities);
5849
+ }
5850
+ }
5851
+ function foldAssertions(assertions) {
5852
+ const state = emptyState();
5853
+ for (const assertion of assertions) {
5854
+ if (assertion.invalidatedAt !== null) {
5855
+ applyTombstone(state, assertion);
5856
+ continue;
5857
+ }
5858
+ if (assertion.kind === "fact") {
5859
+ state.facts = applyFact(state.facts, assertion, assertion.createdAt).records;
5860
+ } else if (assertion.kind === "preference") {
5861
+ state.preferences = applyPreference(state.preferences, assertion, assertion.createdAt).records;
5862
+ } else {
5863
+ state.entities = applyEntity(state.entities, assertion, assertion.createdAt).records;
5864
+ }
5865
+ }
5866
+ return state;
5867
+ }
5868
+ function allRecords(state) {
5869
+ return [...state.facts, ...state.preferences, ...state.entities];
5870
+ }
5871
+
5872
+ // src/memory/log.ts
5873
+ function serializeAssertion(record) {
5874
+ return JSON.stringify(record);
5875
+ }
5876
+ function parseMemoryLog(text2) {
5877
+ const records = [];
5878
+ for (const line of text2.split(`
5879
+ `)) {
5880
+ const trimmed = line.trim();
5881
+ if (trimmed.length === 0) {
5882
+ continue;
5883
+ }
5884
+ let candidate;
5885
+ try {
5886
+ candidate = JSON.parse(trimmed);
5887
+ } catch {
5888
+ continue;
5889
+ }
5890
+ const parsed = MemoryRecordSchema.safeParse(candidate);
5891
+ if (parsed.success) {
5892
+ records.push(parsed.data);
5893
+ }
5894
+ }
5895
+ return records;
5896
+ }
5897
+
5898
+ // src/storage/index/memoryConsolidate.ts
5899
+ async function runConsolidation(logPath, deps) {
5900
+ const params = deps.params ?? DEFAULT_DECAY_PARAMS;
5901
+ const text2 = await deps.storage.read(logPath) ?? "";
5902
+ const state = foldAssertions(parseMemoryLog(text2));
5903
+ const tombstones = consolidate(allRecords(state), deps.now, params);
5904
+ if (tombstones.length === 0) {
5905
+ return { forgotten: 0 };
5906
+ }
5907
+ const appended = `${tombstones.map(serializeAssertion).join(`
5908
+ `)}
5909
+ `;
5910
+ await deps.storage.append(logPath, appended);
5911
+ return { forgotten: tombstones.length };
5912
+ }
5913
+
5914
+ // src/local/embeddings.ts
5915
+ function parseEmbeddingsResponse(payload) {
5916
+ if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
5917
+ throw new Error("Respuesta de /embeddings malformada: falta 'data'");
5918
+ }
5919
+ const data = payload.data;
5920
+ return data.map((entry, index) => {
5921
+ const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
5922
+ if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
5923
+ throw new Error(`Respuesta de /embeddings malformada: vector inválido en índice ${index}`);
5924
+ }
5925
+ return embedding;
5926
+ });
5927
+ }
5928
+ async function embedTexts(request, deps) {
5929
+ if (request.texts.length === 0) {
5930
+ return [];
5931
+ }
5932
+ let response2;
5933
+ try {
5934
+ response2 = await deps.fetch(embeddingsEndpoint(request.baseURL), {
5935
+ method: "POST",
5936
+ headers: {
5937
+ "content-type": "application/json",
5938
+ accept: "application/json"
5939
+ },
5940
+ body: JSON.stringify({
5941
+ model: request.modelID,
5942
+ input: [...request.texts]
5943
+ })
5944
+ });
5945
+ } catch (error) {
5946
+ throw new Error(`Embeddings local falló: ${errorMessage(error)}`);
5947
+ }
5948
+ if (!response2.ok) {
5949
+ throw new Error(`Embeddings local falló: HTTP ${response2.status} en /embeddings`);
5950
+ }
5951
+ let payload;
5952
+ try {
5953
+ payload = await response2.json();
5954
+ } catch (error) {
5955
+ throw new Error(`Embeddings local falló: ${errorMessage(error)}`);
5956
+ }
5957
+ const vectors = parseEmbeddingsResponse(payload);
5958
+ if (vectors.length !== request.texts.length) {
5959
+ throw new Error(`Embeddings local devolvió ${vectors.length} vectores para ${request.texts.length} textos`);
5960
+ }
5961
+ return vectors;
5962
+ }
5963
+ function createLocalEmbedder(request, deps) {
5964
+ return (texts) => embedTexts({ ...request, texts }, deps);
5965
+ }
5966
+
5967
+ // src/storage/index/memoryIndex.ts
5968
+ var SCHEMA2 = `
5969
+ CREATE TABLE IF NOT EXISTS memory_records (
5970
+ id TEXT PRIMARY KEY,
5971
+ kind TEXT NOT NULL,
5972
+ owner_key TEXT NOT NULL,
5973
+ created_at INTEGER NOT NULL,
5974
+ invalidated_at INTEGER,
5975
+ payload TEXT NOT NULL
5976
+ );
5977
+ CREATE INDEX IF NOT EXISTS idx_memory_owner ON memory_records(owner_key, kind);
5978
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
5979
+ record_id TEXT PRIMARY KEY,
5980
+ dim INTEGER NOT NULL,
5981
+ vector TEXT NOT NULL
5982
+ );
5983
+ `;
5984
+ var INSERT_SQL2 = `INSERT OR REPLACE INTO memory_records
5985
+ (id, kind, owner_key, created_at, invalidated_at, payload)
5986
+ VALUES (?, ?, ?, ?, ?, ?)`;
5987
+ var INSERT_EMBEDDING_SQL = `INSERT OR REPLACE INTO memory_embeddings
5988
+ (record_id, dim, vector) VALUES (?, ?, ?)`;
5989
+ function insertRecords(db, records) {
5990
+ const stmt = db.query(INSERT_SQL2);
5991
+ db.run("BEGIN");
5992
+ try {
5993
+ for (const record of records) {
5994
+ stmt.run(record.id, record.kind, record.ownerKey, record.createdAt, record.invalidatedAt, JSON.stringify(record));
5995
+ }
5996
+ db.run("COMMIT");
5997
+ } catch (error) {
5998
+ db.run("ROLLBACK");
5999
+ throw error;
6000
+ }
6001
+ }
6002
+ function createMemoryIndex(db) {
6003
+ db.exec(SCHEMA2);
6004
+ const allRecordsFn = () => {
6005
+ const rows = db.query("SELECT payload FROM memory_records ORDER BY created_at ASC, id ASC").all();
6006
+ const records = [];
6007
+ for (const row of rows) {
6008
+ let candidate;
6009
+ try {
6010
+ candidate = JSON.parse(row.payload);
6011
+ } catch {
6012
+ continue;
6013
+ }
6014
+ const parsed = MemoryRecordSchema.safeParse(candidate);
6015
+ if (parsed.success) {
6016
+ records.push(parsed.data);
6017
+ }
6018
+ }
6019
+ return records;
6020
+ };
6021
+ const state = () => {
6022
+ const facts = [];
6023
+ const preferences = [];
6024
+ const entities = [];
6025
+ for (const record of allRecordsFn()) {
6026
+ if (record.kind === "fact") {
6027
+ facts.push(record);
6028
+ } else if (record.kind === "preference") {
6029
+ preferences.push(record);
6030
+ } else {
6031
+ entities.push(record);
6032
+ }
6033
+ }
6034
+ return { facts, preferences, entities };
6035
+ };
6036
+ return {
6037
+ rebuild(assertions) {
6038
+ const folded = allRecords(foldAssertions(assertions));
6039
+ db.run("DELETE FROM memory_records");
6040
+ insertRecords(db, folded);
6041
+ },
6042
+ allRecords: allRecordsFn,
6043
+ state,
6044
+ activeAt(asOf) {
6045
+ return filterActive(allRecordsFn(), asOf);
6046
+ },
6047
+ putEmbeddings(entries) {
6048
+ const stmt = db.query(INSERT_EMBEDDING_SQL);
6049
+ db.run("BEGIN");
6050
+ try {
6051
+ for (const entry of entries) {
6052
+ stmt.run(entry.recordId, entry.vector.length, JSON.stringify(entry.vector));
6053
+ }
6054
+ db.run("COMMIT");
6055
+ } catch (error) {
6056
+ db.run("ROLLBACK");
6057
+ throw error;
6058
+ }
6059
+ },
6060
+ embeddingMap() {
6061
+ const rows = db.query("SELECT record_id, vector FROM memory_embeddings").all();
6062
+ const map = new Map;
6063
+ for (const row of rows) {
6064
+ try {
6065
+ const vector = JSON.parse(row.vector);
6066
+ if (Array.isArray(vector) && vector.every((n) => typeof n === "number")) {
6067
+ map.set(row.record_id, vector);
6068
+ }
6069
+ } catch {}
6070
+ }
6071
+ return map;
6072
+ },
6073
+ pruneEmbeddings() {
6074
+ const result = db.run("DELETE FROM memory_embeddings WHERE record_id NOT IN (SELECT id FROM memory_records)");
6075
+ return typeof result?.changes === "number" ? result.changes : 0;
6076
+ },
6077
+ close() {
6078
+ db.close();
6079
+ }
6080
+ };
6081
+ }
6082
+ async function rebuildMemoryIndexFromStorage(index, logPath, deps) {
6083
+ const text2 = await deps.storage.read(logPath) ?? "";
6084
+ const assertions = parseMemoryLog(text2);
6085
+ index.rebuild(assertions);
6086
+ return assertions.length;
6087
+ }
6088
+
6089
+ // src/storage/index/memoryRuntime.ts
6090
+ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
6091
+ const semantic = resolveMemorySemantic(config);
6092
+ if (!semantic.enabled || semantic.embeddings.provider === "none") {
6093
+ return;
6094
+ }
6095
+ if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
6096
+ return;
6097
+ }
6098
+ const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
6099
+ if (runtime?.baseURL === undefined) {
6100
+ return;
6101
+ }
6102
+ const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
6103
+ const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
6104
+ const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
6105
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
6106
+ const now = deps.now ?? (() => Date.now());
6107
+ try {
6108
+ const db = await openDatabase(semantic.indexPath);
6109
+ const index = createMemoryIndex(db);
6110
+ await rebuildMemoryIndexFromStorage(index, semantic.logPath, {
6111
+ storage: deps.storage
6112
+ });
6113
+ const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
6114
+ return {
6115
+ index,
6116
+ embedder,
6117
+ now,
6118
+ ownerKey: semantic.scope === "user" ? "user" : "project",
6119
+ semantic
6120
+ };
6121
+ } catch {
6122
+ return;
6123
+ }
6124
+ }
6125
+
5261
6126
  // src/web/git.ts
5262
6127
  function createGitLastCommit(exec) {
5263
6128
  return async () => {
@@ -5298,6 +6163,18 @@ async function loadConfig(path) {
5298
6163
  const content = await storage.read(path);
5299
6164
  return content === undefined ? loadOpenTeamConfig({}) : loadOpenTeamConfig(JSON.parse(content));
5300
6165
  }
6166
+ async function buildMcpSemanticRecall(config) {
6167
+ const runtime = await buildMemoryRuntimeFromConfig(config, { storage });
6168
+ if (runtime === undefined) {
6169
+ return;
6170
+ }
6171
+ const { index, embedder, now, ownerKey, semantic } = runtime;
6172
+ return buildSemanticRecall({ index, embedder, now }, {
6173
+ ownerKey,
6174
+ minSimilarity: semantic.recall.minSimilarity,
6175
+ includeShared: true
6176
+ });
6177
+ }
5301
6178
  async function openBrowser(url) {
5302
6179
  if (process.platform === "win32") {
5303
6180
  await nodeExec("cmd", ["/c", "start", "", url]);
@@ -5424,17 +6301,31 @@ async function main() {
5424
6301
  return;
5425
6302
  }
5426
6303
  if (argv[0] === "mcp") {
6304
+ const mcpConfig = await loadConfig(DEFAULT_CONFIG_PATH);
6305
+ const recallSemantic = await buildMcpSemanticRecall(mcpConfig);
5427
6306
  await runMcpStdio({
5428
6307
  readMemory: async () => buildTeamMemory(await readSessionEvents(DEFAULT_SESSIONS_DIR, {
5429
6308
  storage,
5430
6309
  legacyTelemetryPath: DEFAULT_TELEMETRY_PATH
5431
- }))
6310
+ })),
6311
+ ...recallSemantic === undefined ? {} : { recallSemantic }
5432
6312
  }, {
5433
6313
  lines: linesFromReadable(process.stdin),
5434
6314
  write: (line) => process.stdout.write(line)
5435
6315
  });
5436
6316
  return;
5437
6317
  }
6318
+ if (argv[0] === "memory" && argv[1] === "consolidate") {
6319
+ const memConfig = await loadConfig(DEFAULT_CONFIG_PATH);
6320
+ const semantic = resolveMemorySemantic(memConfig);
6321
+ const result2 = await runConsolidation(semantic.logPath, {
6322
+ storage,
6323
+ now: Date.now()
6324
+ });
6325
+ process.stdout.write(`Consolidación de memoria: ${result2.forgotten} record(s) olvidado(s).
6326
+ `);
6327
+ return;
6328
+ }
5438
6329
  const result = await runCli(argv, deps);
5439
6330
  process.stdout.write(`${result.stdout}
5440
6331
  `);