@jmanuelcorral/openteam 0.1.37 → 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 (84) 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/setupAdapters.d.ts.map +1 -1
  8. package/dist/cli.js +1122 -250
  9. package/dist/commands/frontierCatalog.d.ts +27 -0
  10. package/dist/commands/frontierCatalog.d.ts.map +1 -0
  11. package/dist/commands/orchestratorAgent.d.ts +2 -1
  12. package/dist/commands/orchestratorAgent.d.ts.map +1 -1
  13. package/dist/commands/setup.d.ts +16 -18
  14. package/dist/commands/setup.d.ts.map +1 -1
  15. package/dist/config/schema.d.ts +196 -0
  16. package/dist/config/schema.d.ts.map +1 -1
  17. package/dist/console/assets.d.ts +9 -0
  18. package/dist/console/assets.d.ts.map +1 -0
  19. package/dist/console/render.d.ts.map +1 -1
  20. package/dist/contract/opencode.d.ts +1 -1
  21. package/dist/contract/opencode.d.ts.map +1 -1
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +835 -87
  24. package/dist/local/embeddings.d.ts +29 -0
  25. package/dist/local/embeddings.d.ts.map +1 -0
  26. package/dist/local/extractClient.d.ts +31 -0
  27. package/dist/local/extractClient.d.ts.map +1 -0
  28. package/dist/local/openai-compatible.d.ts +1 -0
  29. package/dist/local/openai-compatible.d.ts.map +1 -1
  30. package/dist/mcp/semanticRecall.d.ts +25 -0
  31. package/dist/mcp/semanticRecall.d.ts.map +1 -0
  32. package/dist/mcp/server.d.ts +22 -0
  33. package/dist/mcp/server.d.ts.map +1 -1
  34. package/dist/memory/consolidate.d.ts +21 -0
  35. package/dist/memory/consolidate.d.ts.map +1 -0
  36. package/dist/memory/extract.d.ts +68 -0
  37. package/dist/memory/extract.d.ts.map +1 -0
  38. package/dist/memory/fold.d.ts +24 -0
  39. package/dist/memory/fold.d.ts.map +1 -0
  40. package/dist/memory/index.d.ts +18 -0
  41. package/dist/memory/index.d.ts.map +1 -0
  42. package/dist/memory/inject.d.ts +29 -0
  43. package/dist/memory/inject.d.ts.map +1 -0
  44. package/dist/memory/log.d.ts +16 -0
  45. package/dist/memory/log.d.ts.map +1 -0
  46. package/dist/memory/merge.d.ts +38 -0
  47. package/dist/memory/merge.d.ts.map +1 -0
  48. package/dist/memory/rank.d.ts +32 -0
  49. package/dist/memory/rank.d.ts.map +1 -0
  50. package/dist/memory/recall.d.ts +30 -0
  51. package/dist/memory/recall.d.ts.map +1 -0
  52. package/dist/memory/temporal.d.ts +39 -0
  53. package/dist/memory/temporal.d.ts.map +1 -0
  54. package/dist/memory/types.d.ts +159 -0
  55. package/dist/memory/types.d.ts.map +1 -0
  56. package/dist/memory/vector.d.ts +18 -0
  57. package/dist/memory/vector.d.ts.map +1 -0
  58. package/dist/orchestrator/roles.d.ts +1 -1
  59. package/dist/orchestrator/roles.d.ts.map +1 -1
  60. package/dist/plugin/hooks.d.ts +13 -0
  61. package/dist/plugin/hooks.d.ts.map +1 -1
  62. package/dist/plugin/memoryInject.d.ts +25 -0
  63. package/dist/plugin/memoryInject.d.ts.map +1 -0
  64. package/dist/router/chooseModel.d.ts.map +1 -1
  65. package/dist/router/cost.d.ts +4 -0
  66. package/dist/router/cost.d.ts.map +1 -0
  67. package/dist/router/frontierBaseline.d.ts +13 -0
  68. package/dist/router/frontierBaseline.d.ts.map +1 -0
  69. package/dist/router/modelSelection.d.ts +18 -0
  70. package/dist/router/modelSelection.d.ts.map +1 -0
  71. package/dist/storage/index/memoryConsolidate.d.ts +24 -0
  72. package/dist/storage/index/memoryConsolidate.d.ts.map +1 -0
  73. package/dist/storage/index/memoryIndex.d.ts +52 -0
  74. package/dist/storage/index/memoryIndex.d.ts.map +1 -0
  75. package/dist/storage/index/memoryRecall.d.ts +23 -0
  76. package/dist/storage/index/memoryRecall.d.ts.map +1 -0
  77. package/dist/storage/index/memoryRuntime.d.ts +41 -0
  78. package/dist/storage/index/memoryRuntime.d.ts.map +1 -0
  79. package/dist/telemetry/events.d.ts +3 -1
  80. package/dist/telemetry/events.d.ts.map +1 -1
  81. package/dist/telemetry/hash.d.ts.map +1 -1
  82. package/dist/web/server.d.ts +4 -0
  83. package/dist/web/server.d.ts.map +1 -1
  84. package/package.json +14 -6
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
1
4
  // src/index.ts
2
5
  import { dirname as dirname2 } from "node:path";
3
6
 
@@ -21,7 +24,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
21
24
  ];
22
25
  const frontmatter = [
23
26
  "---",
24
- '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."',
27
+ '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."',
25
28
  "mode: primary",
26
29
  `model: ${model}`,
27
30
  "temperature: 0.2",
@@ -30,7 +33,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
30
33
  ].join(`
31
34
  `);
32
35
  const body = [
33
- "Eres el orquestador de **openteam**, al estilo Squad. Eres el único agente",
36
+ "Eres el orquestador multiagente de **openteam**. Eres el único agente",
34
37
  "primario: no existe un equipo precreado. Tú **construyes el equipo de",
35
38
  "subagentes a demanda** según el objetivo del usuario y del repositorio.",
36
39
  "",
@@ -56,7 +59,7 @@ function buildOrchestratorAgent(frontier, options = {}) {
56
59
  "",
57
60
  "## Casting: elige un universo y nombra al equipo",
58
61
  "",
59
- "Como Squad, el equipo tiene una identidad temática persistente:",
62
+ "El equipo tiene una identidad temática persistente:",
60
63
  "",
61
64
  "1. Al crear el equipo por primera vez, **elige un universo** temático (p. ej.",
62
65
  " una película, serie, cómic o mitología). Si el usuario tiene preferencia,",
@@ -379,6 +382,50 @@ var LocalRuntimeSchema = z.object({
379
382
  discovery: z.enum(["cli", "sdk", "manual"]).optional(),
380
383
  defaultModel: ModelRefSchema
381
384
  });
385
+ var MemoryScopeSchema = z.enum(["project", "user"]);
386
+ var MemoryExtractionModeSchema = z.enum([
387
+ "local",
388
+ "localWithFrontierFallback"
389
+ ]);
390
+ var MemoryEmbeddingsProviderSchema = z.enum(["local", "none"]);
391
+ var MemorySemanticSchema = z.object({
392
+ enabled: z.boolean().default(false),
393
+ storeContent: z.boolean().default(false),
394
+ logPath: z.string().min(1).default(".opencode/openteam/memory/records.jsonl"),
395
+ indexPath: z.string().min(1).default(".opencode/openteam/memory/index.sqlite"),
396
+ scope: MemoryScopeSchema.default("project"),
397
+ extraction: z.object({
398
+ mode: MemoryExtractionModeSchema.default("local"),
399
+ model: ModelRefSchema.nullable().default(null)
400
+ }).default({ mode: "local", model: null }),
401
+ embeddings: z.object({
402
+ provider: MemoryEmbeddingsProviderSchema.default("local"),
403
+ model: ModelRefSchema.nullable().default(null),
404
+ dim: z.number().int().positive().default(768)
405
+ }).default({ provider: "local", model: null, dim: 768 }),
406
+ recall: z.object({
407
+ maxResults: z.number().int().positive().default(8),
408
+ minSimilarity: z.number().min(0).max(1).default(0.2)
409
+ }).default({ maxResults: 8, minSimilarity: 0.2 }),
410
+ injection: z.object({
411
+ enabled: z.boolean().default(false),
412
+ maxChars: z.number().int().positive().default(1200),
413
+ maxItems: z.number().int().positive().default(8)
414
+ }).default({ enabled: false, maxChars: 1200, maxItems: 8 })
415
+ }).default({
416
+ enabled: false,
417
+ storeContent: false,
418
+ logPath: ".opencode/openteam/memory/records.jsonl",
419
+ indexPath: ".opencode/openteam/memory/index.sqlite",
420
+ scope: "project",
421
+ extraction: { mode: "local", model: null },
422
+ embeddings: { provider: "local", model: null, dim: 768 },
423
+ recall: { maxResults: 8, minSimilarity: 0.2 },
424
+ injection: { enabled: false, maxChars: 1200, maxItems: 8 }
425
+ });
426
+ var MemoryConfigSchema = z.object({
427
+ semantic: MemorySemanticSchema
428
+ });
382
429
  var OpenTeamConfigObjectSchema = z.object({
383
430
  baseline: z.object({
384
431
  mode: BaselineModeSchema.default("auto"),
@@ -429,9 +476,13 @@ var OpenTeamConfigObjectSchema = z.object({
429
476
  }).default({ hardStopOnBudgetExhaustion: false }),
430
477
  privacyMode: PrivacyModeSchema.default("forceLocalOnSensitive"),
431
478
  console: ConsoleConfigSchema,
432
- storage: StorageConfigSchema
479
+ storage: StorageConfigSchema,
480
+ memory: MemoryConfigSchema.optional()
433
481
  });
434
482
  var OpenTeamConfigSchema = OpenTeamConfigObjectSchema;
483
+ function resolveMemorySemantic(config) {
484
+ return config.memory?.semantic ?? MemorySemanticSchema.parse(undefined);
485
+ }
435
486
 
436
487
  // src/config/persist.ts
437
488
  var DEFAULT_CONFIG_PATH = ".opencode/openteam.json";
@@ -459,6 +510,9 @@ function normalizeBaseURL(baseURL) {
459
510
  function modelsEndpoint(baseURL) {
460
511
  return `${normalizeBaseURL(baseURL)}/models`;
461
512
  }
513
+ function embeddingsEndpoint(baseURL) {
514
+ return `${normalizeBaseURL(baseURL)}/embeddings`;
515
+ }
462
516
  function errorMessage(error) {
463
517
  if (error instanceof Error) {
464
518
  return error.message;
@@ -1562,9 +1616,9 @@ var HELP = [
1562
1616
  " openteam local on Reactiva el routing local-first",
1563
1617
  " openteam doctor Diagnóstico de runtimes y config",
1564
1618
  " openteam agents Lista los agentes y el LLM (local/frontier) de cada uno",
1565
- " openteam console Lanza la Console web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
1566
- " openteam console --status Muestra la config de la Console sin lanzarla",
1567
- " openteam tunnel Expone la Console vía Dev Tunnels (--terminal expone opencode; Ctrl+C para cerrar)",
1619
+ " openteam console [alfa] Lanza la Console web multi-sesión (Ctrl+C para parar; --open abre el navegador)",
1620
+ " openteam console --status [alfa] Muestra la config de la Console sin lanzarla",
1621
+ " openteam tunnel [alfa] Expone la Console vía Dev Tunnels (--terminal expone opencode; Ctrl+C para cerrar)",
1568
1622
  " openteam mcp Servidor MCP de solo lectura (memoria del equipo) sobre stdio",
1569
1623
  " openteam report Resumen de coste/ahorro (telemetría)",
1570
1624
  " openteam yolo status Muestra si el modo YOLO está activo",
@@ -1942,6 +1996,85 @@ function classifyHeuristic(task) {
1942
1996
  };
1943
1997
  }
1944
1998
 
1999
+ // src/router/modelSelection.ts
2000
+ function modelKey2(model) {
2001
+ return `${model.providerID}/${model.modelID}`;
2002
+ }
2003
+ function sameModel(left, right) {
2004
+ return left.providerID === right.providerID && left.modelID === right.modelID;
2005
+ }
2006
+ function compareModels(left, right) {
2007
+ return modelKey2(left).localeCompare(modelKey2(right));
2008
+ }
2009
+ function uniqueModelSelections(models) {
2010
+ const byKey = new Map;
2011
+ for (const model of models) {
2012
+ byKey.set(modelKey2(model), model);
2013
+ }
2014
+ return [...byKey.values()].sort(compareModels);
2015
+ }
2016
+ function localDefault(config) {
2017
+ return config.local.runtimes.find((runtime) => runtime.enabled)?.defaultModel ?? config.router.localDefault;
2018
+ }
2019
+ function resolveConfiguredFrontierBaseline(config) {
2020
+ if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
2021
+ return config.baseline.pinnedModel;
2022
+ }
2023
+ return config.baseline.hardDefault;
2024
+ }
2025
+ function compareAvailableModels2(left, right) {
2026
+ return compareModels(left, right);
2027
+ }
2028
+ function defaultAvailableModels2(config) {
2029
+ return [
2030
+ {
2031
+ ...localDefault(config),
2032
+ kind: "local",
2033
+ available: true
2034
+ },
2035
+ {
2036
+ ...resolveConfiguredFrontierBaseline(config),
2037
+ kind: "frontier",
2038
+ available: true
2039
+ }
2040
+ ].sort(compareAvailableModels2);
2041
+ }
2042
+ function availableModels(input) {
2043
+ return (input.availableModels ?? defaultAvailableModels2(input.config)).slice().sort(compareAvailableModels2);
2044
+ }
2045
+ function listedModel(input, model) {
2046
+ return availableModels(input).find((candidate) => sameModel(candidate, model));
2047
+ }
2048
+ function frontierAvailable(input, model) {
2049
+ return listedModel(input, model)?.available !== false;
2050
+ }
2051
+ function localCandidateIsUsable(input, model) {
2052
+ return model.kind === "local" && model.available && !(input.task.requiresTools === true && model.supportsTools === false);
2053
+ }
2054
+ function availableLocalFallbacks(input, selected) {
2055
+ return uniqueModelSelections(availableModels(input).filter((model) => localCandidateIsUsable(input, model)).filter((model) => !sameModel(model, selected)).map((model) => ({
2056
+ providerID: model.providerID,
2057
+ modelID: model.modelID
2058
+ })));
2059
+ }
2060
+
2061
+ // src/router/cost.ts
2062
+ function modelProfile(input, model) {
2063
+ return input.profiles?.find((profile) => sameModel(profile.ref, model));
2064
+ }
2065
+ function estimatedCostUSD(input, model) {
2066
+ const profile = modelProfile(input, model);
2067
+ if (profile === undefined) {
2068
+ return 0;
2069
+ }
2070
+ const inputTokens = input.task.estimatedInputTokens ?? 0;
2071
+ const outputTokens = input.task.estimatedOutputTokens ?? 0;
2072
+ return inputTokens / 1e6 * profile.costPer1M.inputUSD + outputTokens / 1e6 * profile.costPer1M.outputUSD;
2073
+ }
2074
+ function roundUSD(value) {
2075
+ return Math.round(value * 1000000000000) / 1000000000000;
2076
+ }
2077
+
1945
2078
  // src/capabilities/select.ts
1946
2079
  var BLENDED_COST_INPUT_WEIGHT = 0.75;
1947
2080
  var BLENDED_COST_OUTPUT_WEIGHT = 1 - BLENDED_COST_INPUT_WEIGHT;
@@ -1973,7 +2106,7 @@ function deriveRequirement(tier, task) {
1973
2106
  function blendedCostPer1M(profile) {
1974
2107
  return profile.costPer1M.inputUSD * BLENDED_COST_INPUT_WEIGHT + profile.costPer1M.outputUSD * BLENDED_COST_OUTPUT_WEIGHT;
1975
2108
  }
1976
- function modelKey2(profile) {
2109
+ function modelKey3(profile) {
1977
2110
  return `${profile.ref.providerID}/${profile.ref.modelID}`;
1978
2111
  }
1979
2112
  function compareCapableFrontier(req, left, right) {
@@ -1989,7 +2122,7 @@ function compareCapableFrontier(req, left, right) {
1989
2122
  if (reasoningWasteDelta !== 0) {
1990
2123
  return reasoningWasteDelta;
1991
2124
  }
1992
- return modelKey2(left).localeCompare(modelKey2(right));
2125
+ return modelKey3(left).localeCompare(modelKey3(right));
1993
2126
  }
1994
2127
  function satisfiesRequirement(req, profile) {
1995
2128
  return profile.kind === "frontier" && profile.availability !== "unavailable" && profile.contextWindow >= req.minContextWindow && profile.reasoningTier >= req.minReasoningTier && profile.codeQualityTier >= req.minCodeQualityTier && (!req.needsTools || profile.supportsToolCalling) && (!req.needsVision || profile.supportsVision);
@@ -1999,13 +2132,7 @@ function selectCheapestCapableFrontier(req, profiles) {
1999
2132
  return selected?.ref ?? null;
2000
2133
  }
2001
2134
 
2002
- // src/router/chooseModel.ts
2003
- function resolveConfiguredFrontierBaseline(config) {
2004
- if (config.baseline.mode === "pinned" && config.baseline.pinnedModel !== null) {
2005
- return config.baseline.pinnedModel;
2006
- }
2007
- return config.baseline.hardDefault;
2008
- }
2135
+ // src/router/frontierBaseline.ts
2009
2136
  function resolveFrontierBaseline(input, tier) {
2010
2137
  if (input.config.baseline.mode === "pinned" && input.config.baseline.pinnedModel !== null) {
2011
2138
  return { model: input.config.baseline.pinnedModel, rationale: [] };
@@ -2025,48 +2152,8 @@ function resolveFrontierBaseline(input, tier) {
2025
2152
  }
2026
2153
  return { model: input.config.baseline.hardDefault, rationale: [] };
2027
2154
  }
2028
- function localDefault(config) {
2029
- return config.local.runtimes.find((runtime) => runtime.enabled)?.defaultModel ?? config.router.localDefault;
2030
- }
2031
- function modelKey3(model) {
2032
- return `${model.providerID}/${model.modelID}`;
2033
- }
2034
- function sameModel(left, right) {
2035
- return left.providerID === right.providerID && left.modelID === right.modelID;
2036
- }
2037
- function compareModels(left, right) {
2038
- return modelKey3(left).localeCompare(modelKey3(right));
2039
- }
2040
- function uniqueModelSelections(models) {
2041
- const byKey = new Map;
2042
- for (const model of models) {
2043
- byKey.set(modelKey3(model), model);
2044
- }
2045
- return [...byKey.values()].sort(compareModels);
2046
- }
2047
- function defaultAvailableModels2(config) {
2048
- return [
2049
- {
2050
- ...localDefault(config),
2051
- kind: "local",
2052
- available: true
2053
- },
2054
- {
2055
- ...resolveConfiguredFrontierBaseline(config),
2056
- kind: "frontier",
2057
- available: true
2058
- }
2059
- ].sort(compareAvailableModels2);
2060
- }
2061
- function compareAvailableModels2(left, right) {
2062
- return compareModels(left, right);
2063
- }
2064
- function availableModels(input) {
2065
- return (input.availableModels ?? defaultAvailableModels2(input.config)).slice().sort(compareAvailableModels2);
2066
- }
2067
- function listedModel(input, model) {
2068
- return availableModels(input).find((candidate) => sameModel(candidate, model));
2069
- }
2155
+
2156
+ // src/router/chooseModel.ts
2070
2157
  function localStatus(input, model) {
2071
2158
  const listed = listedModel(input, model);
2072
2159
  return {
@@ -2074,18 +2161,6 @@ function localStatus(input, model) {
2074
2161
  missingTools: input.task.requiresTools === true && listed?.supportsTools === false
2075
2162
  };
2076
2163
  }
2077
- function frontierAvailable(input, model) {
2078
- return listedModel(input, model)?.available !== false;
2079
- }
2080
- function localCandidateIsUsable(input, model) {
2081
- return model.kind === "local" && model.available && !(input.task.requiresTools === true && model.supportsTools === false);
2082
- }
2083
- function availableLocalFallbacks(input, selected) {
2084
- return uniqueModelSelections(availableModels(input).filter((model) => localCandidateIsUsable(input, model)).filter((model) => !sameModel(model, selected)).map((model) => ({
2085
- providerID: model.providerID,
2086
- modelID: model.modelID
2087
- })));
2088
- }
2089
2164
  function buildFallbackChain(input, selected, frontier, allowFrontier) {
2090
2165
  const chain = availableLocalFallbacks(input, selected);
2091
2166
  if (allowFrontier && !sameModel(frontier, selected) && frontierAvailable(input, frontier)) {
@@ -2141,21 +2216,6 @@ function resolveFrontierPrimary(input, frontier, rationale) {
2141
2216
  function tierForInput(input) {
2142
2217
  return input.tier ?? classifyHeuristic(input.task).tier;
2143
2218
  }
2144
- function modelProfile(input, model) {
2145
- return input.profiles?.find((profile) => sameModel(profile.ref, model));
2146
- }
2147
- function estimatedCostUSD(input, model) {
2148
- const profile = modelProfile(input, model);
2149
- if (profile === undefined) {
2150
- return 0;
2151
- }
2152
- const inputTokens = input.task.estimatedInputTokens ?? 0;
2153
- const outputTokens = input.task.estimatedOutputTokens ?? 0;
2154
- return inputTokens / 1e6 * profile.costPer1M.inputUSD + outputTokens / 1e6 * profile.costPer1M.outputUSD;
2155
- }
2156
- function roundUSD(value) {
2157
- return Math.round(value * 1000000000000) / 1000000000000;
2158
- }
2159
2219
  function finalizeDecision(input, tier, frontier, budgetAction, core) {
2160
2220
  const selectedCostUSD = estimatedCostUSD(input, core.selected);
2161
2221
  const baselineCostUSD = estimatedCostUSD(input, frontier.model);
@@ -2482,10 +2542,22 @@ function deriveTaskSignals(input, output) {
2482
2542
  }
2483
2543
  return signals;
2484
2544
  }
2545
+ function injectMemoryBlock(output, block) {
2546
+ for (const part of output.parts) {
2547
+ if (part.type === "text" && typeof part.text === "string") {
2548
+ part.text = `${block}
2549
+
2550
+ ${part.text}`;
2551
+ return true;
2552
+ }
2553
+ }
2554
+ return false;
2555
+ }
2485
2556
  function createChatMessageHook(config, getAvailable, options = {}) {
2486
2557
  const sink = options.sink ?? createNullEventSink();
2487
2558
  const now = options.now ?? Date.now;
2488
2559
  return async (input, output) => {
2560
+ const prompt = promptText(output);
2489
2561
  const signals = deriveTaskSignals(input, output);
2490
2562
  const decision2 = chooseModel({
2491
2563
  config,
@@ -2495,7 +2567,7 @@ function createChatMessageHook(config, getAvailable, options = {}) {
2495
2567
  output.message.model = decision2.selected;
2496
2568
  const telemetryContext = {
2497
2569
  ts: now(),
2498
- promptHash: hashPrompt(promptText(output)),
2570
+ promptHash: hashPrompt(prompt),
2499
2571
  promptChars: signals.promptChars,
2500
2572
  sessionID: input.sessionID ?? output.message.sessionID
2501
2573
  };
@@ -2506,6 +2578,14 @@ function createChatMessageHook(config, getAvailable, options = {}) {
2506
2578
  telemetryContext.tokensOut = signals.estimatedOutputTokens;
2507
2579
  }
2508
2580
  emitTelemetry(sink, costRecordToRouteEvent(toCostRecord(decision2, telemetryContext)));
2581
+ if (options.injectMemory !== undefined) {
2582
+ try {
2583
+ const block = await options.injectMemory(prompt);
2584
+ if (block !== null && block.length > 0) {
2585
+ injectMemoryBlock(output, block);
2586
+ }
2587
+ } catch {}
2588
+ }
2509
2589
  };
2510
2590
  }
2511
2591
  function createHooks(config, getAvailable, options = {}) {
@@ -2514,6 +2594,247 @@ function createHooks(config, getAvailable, options = {}) {
2514
2594
  };
2515
2595
  }
2516
2596
 
2597
+ // src/memory/inject.ts
2598
+ var DEFAULT_INJECTION_PARAMS = {
2599
+ maxChars: 1200,
2600
+ header: "Contexto de memoria de openteam (solo referencia):",
2601
+ maxItems: 8
2602
+ };
2603
+ function formatRecordLine(record) {
2604
+ if (record.kind === "fact") {
2605
+ const base = `- ${record.subject} ${record.predicate} ${record.object}`;
2606
+ return record.category === null ? base : `${base} (${record.category})`;
2607
+ }
2608
+ if (record.kind === "preference") {
2609
+ const base = `- [${record.category}] ${record.preference}`;
2610
+ return record.context === null ? base : `${base} — ${record.context}`;
2611
+ }
2612
+ const aliases = record.aliases.length > 0 ? ` (alias: ${record.aliases.join(", ")})` : "";
2613
+ return `- ${record.canonicalName} [${record.type}]${aliases}`;
2614
+ }
2615
+ function buildMemoryBlock(results, params = DEFAULT_INJECTION_PARAMS) {
2616
+ if (results.length === 0) {
2617
+ return null;
2618
+ }
2619
+ const lines = [];
2620
+ let used = params.header.length;
2621
+ for (const result of results) {
2622
+ if (lines.length >= params.maxItems) {
2623
+ break;
2624
+ }
2625
+ const line = formatRecordLine(result.record);
2626
+ const cost = line.length + 1;
2627
+ if (used + cost > params.maxChars) {
2628
+ break;
2629
+ }
2630
+ lines.push(line);
2631
+ used += cost;
2632
+ }
2633
+ if (lines.length === 0) {
2634
+ return null;
2635
+ }
2636
+ return `${params.header}
2637
+ ${lines.join(`
2638
+ `)}`;
2639
+ }
2640
+
2641
+ // src/memory/types.ts
2642
+ import { z as z5 } from "zod";
2643
+ var SHARED_OWNER_KEY = "*";
2644
+ var OwnerKeySchema = z5.string().min(1);
2645
+ var MemoryKindSchema = z5.enum(["fact", "preference", "entity"]);
2646
+ var MemoryBaseSchema = z5.object({
2647
+ id: z5.string().min(1),
2648
+ ownerKey: OwnerKeySchema,
2649
+ confidence: z5.number().min(0).max(1),
2650
+ validFrom: z5.number().finite(),
2651
+ validUntil: z5.number().finite().nullable().default(null),
2652
+ createdAt: z5.number().finite(),
2653
+ invalidatedAt: z5.number().finite().nullable().default(null),
2654
+ sourceHash: z5.string().min(1),
2655
+ supersededBy: z5.string().min(1).nullable().default(null)
2656
+ });
2657
+ var FactSchema = MemoryBaseSchema.extend({
2658
+ kind: z5.literal("fact"),
2659
+ subject: z5.string().min(1),
2660
+ predicate: z5.string().min(1),
2661
+ object: z5.string().min(1),
2662
+ category: z5.string().min(1).nullable().default(null)
2663
+ });
2664
+ var PreferenceSchema = MemoryBaseSchema.extend({
2665
+ kind: z5.literal("preference"),
2666
+ category: z5.string().min(1),
2667
+ preference: z5.string().min(1),
2668
+ context: z5.string().min(1).nullable().default(null),
2669
+ lastAccessedAt: z5.number().finite().nullable().default(null),
2670
+ accessCount: z5.number().int().min(0).default(0)
2671
+ });
2672
+ var EntitySchema = MemoryBaseSchema.extend({
2673
+ kind: z5.literal("entity"),
2674
+ canonicalName: z5.string().min(1),
2675
+ type: z5.string().min(1),
2676
+ aliases: z5.array(z5.string().min(1)).default([])
2677
+ });
2678
+ var MemoryRecordSchema = z5.discriminatedUnion("kind", [
2679
+ FactSchema,
2680
+ PreferenceSchema,
2681
+ EntitySchema
2682
+ ]);
2683
+ var RecallQuerySchema = z5.object({
2684
+ ownerKey: OwnerKeySchema,
2685
+ kinds: z5.array(MemoryKindSchema).default(["fact", "preference", "entity"]),
2686
+ asOf: z5.number().finite().nullable().default(null),
2687
+ limit: z5.number().int().positive().default(8),
2688
+ minSimilarity: z5.number().min(0).max(1).default(0.2),
2689
+ includeShared: z5.boolean().default(true)
2690
+ });
2691
+
2692
+ // src/memory/rank.ts
2693
+ var DAY_MS = 86400000;
2694
+ var DEFAULT_RANK_SIGNALS = {
2695
+ similarityWeight: 1,
2696
+ recencyWeight: 0.2,
2697
+ accessWeight: 0.1,
2698
+ recencyHalfLifeMs: 14 * DAY_MS
2699
+ };
2700
+ function recencyTimestamp(record) {
2701
+ if (record.kind === "preference" && record.lastAccessedAt !== null) {
2702
+ return record.lastAccessedAt;
2703
+ }
2704
+ return record.createdAt;
2705
+ }
2706
+ function recencyScore(record, signals) {
2707
+ const ageMs = Math.max(0, signals.now - recencyTimestamp(record));
2708
+ return 0.5 ** (ageMs / signals.recencyHalfLifeMs);
2709
+ }
2710
+ function accessScore(record) {
2711
+ if (record.kind !== "preference") {
2712
+ return 0;
2713
+ }
2714
+ return 1 - 1 / (1 + record.accessCount);
2715
+ }
2716
+ function scoreCandidate(candidate, signals) {
2717
+ return signals.similarityWeight * candidate.similarity + signals.recencyWeight * recencyScore(candidate.record, signals) + signals.accessWeight * accessScore(candidate.record);
2718
+ }
2719
+ function rankRecall(candidates, signals) {
2720
+ return candidates.map((candidate) => ({
2721
+ ...candidate,
2722
+ score: scoreCandidate(candidate, signals)
2723
+ })).sort((a, b) => {
2724
+ if (b.score !== a.score) {
2725
+ return b.score - a.score;
2726
+ }
2727
+ return a.record.id < b.record.id ? -1 : a.record.id > b.record.id ? 1 : 0;
2728
+ });
2729
+ }
2730
+
2731
+ // src/memory/temporal.ts
2732
+ function isTransactionActiveAt(record, asOf) {
2733
+ return record.invalidatedAt === null || record.invalidatedAt > asOf;
2734
+ }
2735
+ function isValidAt(record, asOf) {
2736
+ if (record.validFrom > asOf) {
2737
+ return false;
2738
+ }
2739
+ return record.validUntil === null || record.validUntil > asOf;
2740
+ }
2741
+ function isActiveAt(record, asOf) {
2742
+ return isTransactionActiveAt(record, asOf) && isValidAt(record, asOf);
2743
+ }
2744
+ function filterActive(records, asOf) {
2745
+ return records.filter((record) => isActiveAt(record, asOf));
2746
+ }
2747
+ function coalesceWindow(a, b) {
2748
+ const validFrom = Math.min(a.validFrom, b.validFrom);
2749
+ const validUntil = a.validUntil === null || b.validUntil === null ? null : Math.max(a.validUntil, b.validUntil);
2750
+ return { validFrom, validUntil };
2751
+ }
2752
+
2753
+ // src/memory/vector.ts
2754
+ function dot(a, b) {
2755
+ if (a.length !== b.length) {
2756
+ throw new Error(`dimension mismatch: ${a.length} vs ${b.length}`);
2757
+ }
2758
+ let sum = 0;
2759
+ for (let i = 0;i < a.length; i += 1) {
2760
+ sum += a[i] * b[i];
2761
+ }
2762
+ return sum;
2763
+ }
2764
+ function norm(a) {
2765
+ let sum = 0;
2766
+ for (let i = 0;i < a.length; i += 1) {
2767
+ const value = a[i];
2768
+ sum += value * value;
2769
+ }
2770
+ return Math.sqrt(sum);
2771
+ }
2772
+ function cosineSimilarity(a, b) {
2773
+ const denom = norm(a) * norm(b);
2774
+ if (denom === 0) {
2775
+ return 0;
2776
+ }
2777
+ return dot(a, b) / denom;
2778
+ }
2779
+
2780
+ // src/memory/recall.ts
2781
+ function matchesScope(record, query) {
2782
+ if (record.ownerKey === query.ownerKey) {
2783
+ return true;
2784
+ }
2785
+ return query.includeShared && record.ownerKey === SHARED_OWNER_KEY;
2786
+ }
2787
+ function recall(records, queryEmbedding, query, deps) {
2788
+ const asOf = query.asOf ?? deps.now;
2789
+ const kinds = new Set(query.kinds);
2790
+ const candidates = [];
2791
+ for (const record of filterActive(records, asOf)) {
2792
+ if (!kinds.has(record.kind) || !matchesScope(record, query)) {
2793
+ continue;
2794
+ }
2795
+ const embedding = deps.embeddings.get(record.id);
2796
+ if (embedding === undefined) {
2797
+ continue;
2798
+ }
2799
+ const similarity = cosineSimilarity(queryEmbedding, embedding);
2800
+ if (similarity < query.minSimilarity) {
2801
+ continue;
2802
+ }
2803
+ candidates.push({ record, similarity });
2804
+ }
2805
+ const signals = {
2806
+ now: deps.now,
2807
+ ...deps.signals ?? DEFAULT_RANK_SIGNALS
2808
+ };
2809
+ return rankRecall(candidates, signals).slice(0, query.limit);
2810
+ }
2811
+
2812
+ // src/storage/index/memoryRecall.ts
2813
+ async function recallMemory(deps, queryText, query) {
2814
+ const [queryEmbedding] = await deps.embedder([queryText]);
2815
+ if (queryEmbedding === undefined) {
2816
+ return [];
2817
+ }
2818
+ const records = deps.index.allRecords();
2819
+ const embeddings = deps.index.embeddingMap();
2820
+ const recallDeps = deps.signals === undefined ? { embeddings, now: deps.now } : { embeddings, now: deps.now, signals: deps.signals };
2821
+ return recall(records, queryEmbedding, query, recallDeps);
2822
+ }
2823
+
2824
+ // src/plugin/memoryInject.ts
2825
+ function buildMemoryInjector(deps, policy) {
2826
+ return async (queryText) => {
2827
+ const query = RecallQuerySchema.parse({
2828
+ ownerKey: policy.ownerKey,
2829
+ limit: policy.maxResults,
2830
+ minSimilarity: policy.minSimilarity,
2831
+ includeShared: policy.includeShared
2832
+ });
2833
+ const results = await recallMemory({ index: deps.index, embedder: deps.embedder, now: deps.now() }, queryText, query);
2834
+ return buildMemoryBlock(results, policy.injection);
2835
+ };
2836
+ }
2837
+
2517
2838
  // src/plugin/toolcalls.ts
2518
2839
  function key(sessionID, callID) {
2519
2840
  return `${sessionID}\x00${callID}`;
@@ -2546,6 +2867,414 @@ function createToolcallTracker(deps) {
2546
2867
  };
2547
2868
  }
2548
2869
 
2870
+ // src/local/embeddings.ts
2871
+ function parseEmbeddingsResponse(payload) {
2872
+ if (typeof payload !== "object" || payload === null || !Array.isArray(payload.data)) {
2873
+ throw new Error("Respuesta de /embeddings malformada: falta 'data'");
2874
+ }
2875
+ const data = payload.data;
2876
+ return data.map((entry, index) => {
2877
+ const embedding = typeof entry === "object" && entry !== null ? entry.embedding : undefined;
2878
+ if (!Array.isArray(embedding) || !embedding.every((n) => typeof n === "number" && Number.isFinite(n))) {
2879
+ throw new Error(`Respuesta de /embeddings malformada: vector inválido en índice ${index}`);
2880
+ }
2881
+ return embedding;
2882
+ });
2883
+ }
2884
+ async function embedTexts(request, deps) {
2885
+ if (request.texts.length === 0) {
2886
+ return [];
2887
+ }
2888
+ let response;
2889
+ try {
2890
+ response = await deps.fetch(embeddingsEndpoint(request.baseURL), {
2891
+ method: "POST",
2892
+ headers: {
2893
+ "content-type": "application/json",
2894
+ accept: "application/json"
2895
+ },
2896
+ body: JSON.stringify({
2897
+ model: request.modelID,
2898
+ input: [...request.texts]
2899
+ })
2900
+ });
2901
+ } catch (error) {
2902
+ throw new Error(`Embeddings local falló: ${errorMessage(error)}`);
2903
+ }
2904
+ if (!response.ok) {
2905
+ throw new Error(`Embeddings local falló: HTTP ${response.status} en /embeddings`);
2906
+ }
2907
+ let payload;
2908
+ try {
2909
+ payload = await response.json();
2910
+ } catch (error) {
2911
+ throw new Error(`Embeddings local falló: ${errorMessage(error)}`);
2912
+ }
2913
+ const vectors = parseEmbeddingsResponse(payload);
2914
+ if (vectors.length !== request.texts.length) {
2915
+ throw new Error(`Embeddings local devolvió ${vectors.length} vectores para ${request.texts.length} textos`);
2916
+ }
2917
+ return vectors;
2918
+ }
2919
+ function createLocalEmbedder(request, deps) {
2920
+ return (texts) => embedTexts({ ...request, texts }, deps);
2921
+ }
2922
+
2923
+ // src/storage/index/bunSqlite.ts
2924
+ async function createBunSqliteDatabase(path) {
2925
+ const mod = await import("bun:sqlite");
2926
+ return new mod.Database(path);
2927
+ }
2928
+
2929
+ // src/memory/merge.ts
2930
+ var SEP = "\x00";
2931
+ function factSlotKey(fact) {
2932
+ return [fact.subject, fact.predicate, fact.ownerKey].join(SEP);
2933
+ }
2934
+ function factValueKey(fact) {
2935
+ return [factSlotKey(fact), fact.object].join(SEP);
2936
+ }
2937
+ function preferenceSlotKey(preference) {
2938
+ return [preference.category, preference.ownerKey].join(SEP);
2939
+ }
2940
+ function preferenceValueKey(preference) {
2941
+ return [preferenceSlotKey(preference), preference.preference].join(SEP);
2942
+ }
2943
+ function entitySlotKey(entity) {
2944
+ return [entity.canonicalName, entity.type, entity.ownerKey].join(SEP);
2945
+ }
2946
+ function coalesceFact(existing, incoming) {
2947
+ const window = coalesceWindow(existing, incoming);
2948
+ return {
2949
+ ...existing,
2950
+ validFrom: window.validFrom,
2951
+ validUntil: window.validUntil,
2952
+ confidence: Math.max(existing.confidence, incoming.confidence)
2953
+ };
2954
+ }
2955
+ function applyFact(state, incoming, now) {
2956
+ const incomingSlot = factSlotKey(incoming);
2957
+ const incomingValue = factValueKey(incoming);
2958
+ const superseded = [];
2959
+ const records = [];
2960
+ let reaffirmed = false;
2961
+ for (const fact of state) {
2962
+ if (!isActiveAt(fact, now)) {
2963
+ records.push(fact);
2964
+ continue;
2965
+ }
2966
+ if (factValueKey(fact) === incomingValue) {
2967
+ records.push(coalesceFact(fact, incoming));
2968
+ reaffirmed = true;
2969
+ continue;
2970
+ }
2971
+ if (factSlotKey(fact) === incomingSlot) {
2972
+ const dead = {
2973
+ ...fact,
2974
+ invalidatedAt: now,
2975
+ supersededBy: incoming.id
2976
+ };
2977
+ superseded.push(dead);
2978
+ records.push(dead);
2979
+ continue;
2980
+ }
2981
+ records.push(fact);
2982
+ }
2983
+ if (!reaffirmed) {
2984
+ records.push(incoming);
2985
+ }
2986
+ return { records, superseded };
2987
+ }
2988
+ function applyPreference(state, incoming, now) {
2989
+ const incomingSlot = preferenceSlotKey(incoming);
2990
+ const incomingValue = preferenceValueKey(incoming);
2991
+ const superseded = [];
2992
+ const records = [];
2993
+ let reaffirmed = false;
2994
+ for (const preference of state) {
2995
+ if (!isActiveAt(preference, now)) {
2996
+ records.push(preference);
2997
+ continue;
2998
+ }
2999
+ if (preferenceValueKey(preference) === incomingValue) {
3000
+ records.push({
3001
+ ...preference,
3002
+ confidence: Math.max(preference.confidence, incoming.confidence)
3003
+ });
3004
+ reaffirmed = true;
3005
+ continue;
3006
+ }
3007
+ if (preferenceSlotKey(preference) === incomingSlot) {
3008
+ const dead = {
3009
+ ...preference,
3010
+ invalidatedAt: now,
3011
+ supersededBy: incoming.id
3012
+ };
3013
+ superseded.push(dead);
3014
+ records.push(dead);
3015
+ continue;
3016
+ }
3017
+ records.push(preference);
3018
+ }
3019
+ if (!reaffirmed) {
3020
+ records.push(incoming);
3021
+ }
3022
+ return { records, superseded };
3023
+ }
3024
+ function mergeAliases(existing, incoming) {
3025
+ const seen = new Set(existing);
3026
+ const merged = [...existing];
3027
+ for (const alias of incoming) {
3028
+ if (!seen.has(alias)) {
3029
+ seen.add(alias);
3030
+ merged.push(alias);
3031
+ }
3032
+ }
3033
+ return merged;
3034
+ }
3035
+ function applyEntity(state, incoming, now) {
3036
+ const incomingSlot = entitySlotKey(incoming);
3037
+ const records = [];
3038
+ let merged = false;
3039
+ for (const entity of state) {
3040
+ if (isActiveAt(entity, now) && entitySlotKey(entity) === incomingSlot) {
3041
+ records.push({
3042
+ ...entity,
3043
+ aliases: mergeAliases(entity.aliases, incoming.aliases),
3044
+ confidence: Math.max(entity.confidence, incoming.confidence)
3045
+ });
3046
+ merged = true;
3047
+ continue;
3048
+ }
3049
+ records.push(entity);
3050
+ }
3051
+ if (!merged) {
3052
+ records.push(incoming);
3053
+ }
3054
+ return { records, superseded: [] };
3055
+ }
3056
+
3057
+ // src/memory/fold.ts
3058
+ function emptyState() {
3059
+ return { facts: [], preferences: [], entities: [] };
3060
+ }
3061
+ function applyTombstone(state, tombstone) {
3062
+ const mark = (records) => records.map((record) => record.id === tombstone.id ? {
3063
+ ...record,
3064
+ invalidatedAt: tombstone.invalidatedAt,
3065
+ supersededBy: tombstone.supersededBy
3066
+ } : record);
3067
+ if (tombstone.kind === "fact") {
3068
+ state.facts = mark(state.facts);
3069
+ } else if (tombstone.kind === "preference") {
3070
+ state.preferences = mark(state.preferences);
3071
+ } else {
3072
+ state.entities = mark(state.entities);
3073
+ }
3074
+ }
3075
+ function foldAssertions(assertions) {
3076
+ const state = emptyState();
3077
+ for (const assertion of assertions) {
3078
+ if (assertion.invalidatedAt !== null) {
3079
+ applyTombstone(state, assertion);
3080
+ continue;
3081
+ }
3082
+ if (assertion.kind === "fact") {
3083
+ state.facts = applyFact(state.facts, assertion, assertion.createdAt).records;
3084
+ } else if (assertion.kind === "preference") {
3085
+ state.preferences = applyPreference(state.preferences, assertion, assertion.createdAt).records;
3086
+ } else {
3087
+ state.entities = applyEntity(state.entities, assertion, assertion.createdAt).records;
3088
+ }
3089
+ }
3090
+ return state;
3091
+ }
3092
+ function allRecords(state) {
3093
+ return [...state.facts, ...state.preferences, ...state.entities];
3094
+ }
3095
+
3096
+ // src/memory/log.ts
3097
+ function parseMemoryLog(text) {
3098
+ const records = [];
3099
+ for (const line of text.split(`
3100
+ `)) {
3101
+ const trimmed = line.trim();
3102
+ if (trimmed.length === 0) {
3103
+ continue;
3104
+ }
3105
+ let candidate;
3106
+ try {
3107
+ candidate = JSON.parse(trimmed);
3108
+ } catch {
3109
+ continue;
3110
+ }
3111
+ const parsed = MemoryRecordSchema.safeParse(candidate);
3112
+ if (parsed.success) {
3113
+ records.push(parsed.data);
3114
+ }
3115
+ }
3116
+ return records;
3117
+ }
3118
+
3119
+ // src/storage/index/memoryIndex.ts
3120
+ var SCHEMA = `
3121
+ CREATE TABLE IF NOT EXISTS memory_records (
3122
+ id TEXT PRIMARY KEY,
3123
+ kind TEXT NOT NULL,
3124
+ owner_key TEXT NOT NULL,
3125
+ created_at INTEGER NOT NULL,
3126
+ invalidated_at INTEGER,
3127
+ payload TEXT NOT NULL
3128
+ );
3129
+ CREATE INDEX IF NOT EXISTS idx_memory_owner ON memory_records(owner_key, kind);
3130
+ CREATE TABLE IF NOT EXISTS memory_embeddings (
3131
+ record_id TEXT PRIMARY KEY,
3132
+ dim INTEGER NOT NULL,
3133
+ vector TEXT NOT NULL
3134
+ );
3135
+ `;
3136
+ var INSERT_SQL = `INSERT OR REPLACE INTO memory_records
3137
+ (id, kind, owner_key, created_at, invalidated_at, payload)
3138
+ VALUES (?, ?, ?, ?, ?, ?)`;
3139
+ var INSERT_EMBEDDING_SQL = `INSERT OR REPLACE INTO memory_embeddings
3140
+ (record_id, dim, vector) VALUES (?, ?, ?)`;
3141
+ function insertRecords(db, records) {
3142
+ const stmt = db.query(INSERT_SQL);
3143
+ db.run("BEGIN");
3144
+ try {
3145
+ for (const record of records) {
3146
+ stmt.run(record.id, record.kind, record.ownerKey, record.createdAt, record.invalidatedAt, JSON.stringify(record));
3147
+ }
3148
+ db.run("COMMIT");
3149
+ } catch (error) {
3150
+ db.run("ROLLBACK");
3151
+ throw error;
3152
+ }
3153
+ }
3154
+ function createMemoryIndex(db) {
3155
+ db.exec(SCHEMA);
3156
+ const allRecordsFn = () => {
3157
+ const rows = db.query("SELECT payload FROM memory_records ORDER BY created_at ASC, id ASC").all();
3158
+ const records = [];
3159
+ for (const row of rows) {
3160
+ let candidate;
3161
+ try {
3162
+ candidate = JSON.parse(row.payload);
3163
+ } catch {
3164
+ continue;
3165
+ }
3166
+ const parsed = MemoryRecordSchema.safeParse(candidate);
3167
+ if (parsed.success) {
3168
+ records.push(parsed.data);
3169
+ }
3170
+ }
3171
+ return records;
3172
+ };
3173
+ const state = () => {
3174
+ const facts = [];
3175
+ const preferences = [];
3176
+ const entities = [];
3177
+ for (const record of allRecordsFn()) {
3178
+ if (record.kind === "fact") {
3179
+ facts.push(record);
3180
+ } else if (record.kind === "preference") {
3181
+ preferences.push(record);
3182
+ } else {
3183
+ entities.push(record);
3184
+ }
3185
+ }
3186
+ return { facts, preferences, entities };
3187
+ };
3188
+ return {
3189
+ rebuild(assertions) {
3190
+ const folded = allRecords(foldAssertions(assertions));
3191
+ db.run("DELETE FROM memory_records");
3192
+ insertRecords(db, folded);
3193
+ },
3194
+ allRecords: allRecordsFn,
3195
+ state,
3196
+ activeAt(asOf) {
3197
+ return filterActive(allRecordsFn(), asOf);
3198
+ },
3199
+ putEmbeddings(entries) {
3200
+ const stmt = db.query(INSERT_EMBEDDING_SQL);
3201
+ db.run("BEGIN");
3202
+ try {
3203
+ for (const entry of entries) {
3204
+ stmt.run(entry.recordId, entry.vector.length, JSON.stringify(entry.vector));
3205
+ }
3206
+ db.run("COMMIT");
3207
+ } catch (error) {
3208
+ db.run("ROLLBACK");
3209
+ throw error;
3210
+ }
3211
+ },
3212
+ embeddingMap() {
3213
+ const rows = db.query("SELECT record_id, vector FROM memory_embeddings").all();
3214
+ const map = new Map;
3215
+ for (const row of rows) {
3216
+ try {
3217
+ const vector = JSON.parse(row.vector);
3218
+ if (Array.isArray(vector) && vector.every((n) => typeof n === "number")) {
3219
+ map.set(row.record_id, vector);
3220
+ }
3221
+ } catch {}
3222
+ }
3223
+ return map;
3224
+ },
3225
+ pruneEmbeddings() {
3226
+ const result = db.run("DELETE FROM memory_embeddings WHERE record_id NOT IN (SELECT id FROM memory_records)");
3227
+ return typeof result?.changes === "number" ? result.changes : 0;
3228
+ },
3229
+ close() {
3230
+ db.close();
3231
+ }
3232
+ };
3233
+ }
3234
+ async function rebuildMemoryIndexFromStorage(index, logPath, deps) {
3235
+ const text = await deps.storage.read(logPath) ?? "";
3236
+ const assertions = parseMemoryLog(text);
3237
+ index.rebuild(assertions);
3238
+ return assertions.length;
3239
+ }
3240
+
3241
+ // src/storage/index/memoryRuntime.ts
3242
+ async function buildMemoryRuntimeFromConfig(config, deps, options = {}) {
3243
+ const semantic = resolveMemorySemantic(config);
3244
+ if (!semantic.enabled || semantic.embeddings.provider === "none") {
3245
+ return;
3246
+ }
3247
+ if (options.requireInjectionEnabled === true && !semantic.injection.enabled) {
3248
+ return;
3249
+ }
3250
+ const runtime = config.local.runtimes.find((rt) => rt.enabled && rt.baseURL !== undefined);
3251
+ if (runtime?.baseURL === undefined) {
3252
+ return;
3253
+ }
3254
+ const modelID = semantic.embeddings.model?.modelID ?? runtime.defaultModel.modelID;
3255
+ const openDatabase = deps.openDatabase ?? createBunSqliteDatabase;
3256
+ const makeEmbedder = deps.createEmbedder ?? createLocalEmbedder;
3257
+ const fetchImpl = deps.fetch ?? globalThis.fetch;
3258
+ const now = deps.now ?? (() => Date.now());
3259
+ try {
3260
+ const db = await openDatabase(semantic.indexPath);
3261
+ const index = createMemoryIndex(db);
3262
+ await rebuildMemoryIndexFromStorage(index, semantic.logPath, {
3263
+ storage: deps.storage
3264
+ });
3265
+ const embedder = makeEmbedder({ baseURL: runtime.baseURL, modelID }, { fetch: fetchImpl });
3266
+ return {
3267
+ index,
3268
+ embedder,
3269
+ now,
3270
+ ownerKey: semantic.scope === "user" ? "user" : "project",
3271
+ semantic
3272
+ };
3273
+ } catch {
3274
+ return;
3275
+ }
3276
+ }
3277
+
2549
3278
  // src/index.ts
2550
3279
  function createShellExec($) {
2551
3280
  return async (command, args) => {
@@ -2565,6 +3294,24 @@ function logTelemetryError(error) {
2565
3294
  const message = error instanceof Error ? error.message : String(error);
2566
3295
  console.warn(`[openteam] telemetry write failed: ${message}`);
2567
3296
  }
3297
+ async function buildMemoryInjectorFromConfig(config, storage) {
3298
+ const runtime = await buildMemoryRuntimeFromConfig(config, { storage }, { requireInjectionEnabled: true });
3299
+ if (runtime === undefined) {
3300
+ return;
3301
+ }
3302
+ const { index, embedder, now, ownerKey, semantic } = runtime;
3303
+ return buildMemoryInjector({ index, embedder, now }, {
3304
+ ownerKey,
3305
+ minSimilarity: semantic.recall.minSimilarity,
3306
+ maxResults: semantic.recall.maxResults,
3307
+ includeShared: true,
3308
+ injection: {
3309
+ maxChars: semantic.injection.maxChars,
3310
+ maxItems: semantic.injection.maxItems,
3311
+ header: "Contexto de memoria de openteam (solo referencia):"
3312
+ }
3313
+ });
3314
+ }
2568
3315
  function telemetryOptions(rawOptions) {
2569
3316
  const telemetry = rawOptions !== null && typeof rawOptions === "object" && "telemetry" in rawOptions && rawOptions.telemetry !== null && typeof rawOptions.telemetry === "object" ? rawOptions.telemetry : {};
2570
3317
  const enabled = !("enabled" in telemetry) || telemetry.enabled !== false;
@@ -2645,7 +3392,8 @@ var server = async (ctx, rawOptions) => {
2645
3392
  const sessionsDir = telemetryOptions(rawOptions).sessionsDir;
2646
3393
  const storage = createFsStorageProvider(process.cwd());
2647
3394
  const sink = createEventSink(rawOptions, storage);
2648
- const hooks = createHooks(config, cache.get, { sink });
3395
+ const injectMemory = await buildMemoryInjectorFromConfig(config, storage);
3396
+ const hooks = createHooks(config, cache.get, injectMemory === undefined ? { sink } : { sink, injectMemory });
2649
3397
  const cliDeps = createCliDeps(config, registry, telemetryPath, sessionsDir, storage);
2650
3398
  const toolcalls = createToolcallTracker({ sink });
2651
3399
  const now = () => Date.now();