@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.
- package/.opencode/command/openteam.md +5 -5
- package/AGENTS.md +27 -27
- package/README.es.md +759 -0
- package/README.md +213 -480
- package/dist/capabilities/types.d.ts +1 -1
- package/dist/cli/consoleServe.d.ts.map +1 -1
- package/dist/cli/setupAdapters.d.ts.map +1 -1
- package/dist/cli.js +1122 -250
- package/dist/commands/frontierCatalog.d.ts +27 -0
- package/dist/commands/frontierCatalog.d.ts.map +1 -0
- package/dist/commands/orchestratorAgent.d.ts +2 -1
- package/dist/commands/orchestratorAgent.d.ts.map +1 -1
- package/dist/commands/setup.d.ts +16 -18
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/config/schema.d.ts +196 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/console/assets.d.ts +9 -0
- package/dist/console/assets.d.ts.map +1 -0
- package/dist/console/render.d.ts.map +1 -1
- package/dist/contract/opencode.d.ts +1 -1
- package/dist/contract/opencode.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +835 -87
- package/dist/local/embeddings.d.ts +29 -0
- package/dist/local/embeddings.d.ts.map +1 -0
- package/dist/local/extractClient.d.ts +31 -0
- package/dist/local/extractClient.d.ts.map +1 -0
- package/dist/local/openai-compatible.d.ts +1 -0
- package/dist/local/openai-compatible.d.ts.map +1 -1
- package/dist/mcp/semanticRecall.d.ts +25 -0
- package/dist/mcp/semanticRecall.d.ts.map +1 -0
- package/dist/mcp/server.d.ts +22 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/memory/consolidate.d.ts +21 -0
- package/dist/memory/consolidate.d.ts.map +1 -0
- package/dist/memory/extract.d.ts +68 -0
- package/dist/memory/extract.d.ts.map +1 -0
- package/dist/memory/fold.d.ts +24 -0
- package/dist/memory/fold.d.ts.map +1 -0
- package/dist/memory/index.d.ts +18 -0
- package/dist/memory/index.d.ts.map +1 -0
- package/dist/memory/inject.d.ts +29 -0
- package/dist/memory/inject.d.ts.map +1 -0
- package/dist/memory/log.d.ts +16 -0
- package/dist/memory/log.d.ts.map +1 -0
- package/dist/memory/merge.d.ts +38 -0
- package/dist/memory/merge.d.ts.map +1 -0
- package/dist/memory/rank.d.ts +32 -0
- package/dist/memory/rank.d.ts.map +1 -0
- package/dist/memory/recall.d.ts +30 -0
- package/dist/memory/recall.d.ts.map +1 -0
- package/dist/memory/temporal.d.ts +39 -0
- package/dist/memory/temporal.d.ts.map +1 -0
- package/dist/memory/types.d.ts +159 -0
- package/dist/memory/types.d.ts.map +1 -0
- package/dist/memory/vector.d.ts +18 -0
- package/dist/memory/vector.d.ts.map +1 -0
- package/dist/orchestrator/roles.d.ts +1 -1
- package/dist/orchestrator/roles.d.ts.map +1 -1
- package/dist/plugin/hooks.d.ts +13 -0
- package/dist/plugin/hooks.d.ts.map +1 -1
- package/dist/plugin/memoryInject.d.ts +25 -0
- package/dist/plugin/memoryInject.d.ts.map +1 -0
- package/dist/router/chooseModel.d.ts.map +1 -1
- package/dist/router/cost.d.ts +4 -0
- package/dist/router/cost.d.ts.map +1 -0
- package/dist/router/frontierBaseline.d.ts +13 -0
- package/dist/router/frontierBaseline.d.ts.map +1 -0
- package/dist/router/modelSelection.d.ts +18 -0
- package/dist/router/modelSelection.d.ts.map +1 -0
- package/dist/storage/index/memoryConsolidate.d.ts +24 -0
- package/dist/storage/index/memoryConsolidate.d.ts.map +1 -0
- package/dist/storage/index/memoryIndex.d.ts +52 -0
- package/dist/storage/index/memoryIndex.d.ts.map +1 -0
- package/dist/storage/index/memoryRecall.d.ts +23 -0
- package/dist/storage/index/memoryRecall.d.ts.map +1 -0
- package/dist/storage/index/memoryRuntime.d.ts +41 -0
- package/dist/storage/index/memoryRuntime.d.ts.map +1 -0
- package/dist/telemetry/events.d.ts +3 -1
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/hash.d.ts.map +1 -1
- package/dist/web/server.d.ts +4 -0
- package/dist/web/server.d.ts.map +1 -1
- 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
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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/
|
|
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
|
-
|
|
2029
|
-
|
|
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(
|
|
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
|
|
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();
|