@agentmemory/agentmemory 0.7.5 → 0.7.7
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/dist/cli.mjs +3 -3
- package/dist/index.mjs +480 -10
- package/dist/index.mjs.map +1 -1
- package/dist/{src-Dflj3k63.mjs → src-C_TC9frp.mjs} +481 -11
- package/dist/src-C_TC9frp.mjs.map +1 -0
- package/dist/standalone.mjs +1 -1
- package/dist/standalone.mjs.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +7 -4
- package/dist/src-Dflj3k63.mjs.map +0 -1
|
@@ -1182,12 +1182,66 @@ function extractEntitiesFromQuery(query) {
|
|
|
1182
1182
|
return [...new Set(entities)];
|
|
1183
1183
|
}
|
|
1184
1184
|
|
|
1185
|
+
//#endregion
|
|
1186
|
+
//#region src/state/reranker.ts
|
|
1187
|
+
let pipeline = null;
|
|
1188
|
+
let pipelineLoading = null;
|
|
1189
|
+
let pipelineUnavailable = false;
|
|
1190
|
+
async function loadPipeline() {
|
|
1191
|
+
if (pipelineUnavailable) return null;
|
|
1192
|
+
if (pipeline) return pipeline;
|
|
1193
|
+
if (pipelineLoading) return pipelineLoading;
|
|
1194
|
+
pipelineLoading = (async () => {
|
|
1195
|
+
try {
|
|
1196
|
+
const { pipeline: createPipeline } = await import("./transformers-KMm1i9no.mjs");
|
|
1197
|
+
pipeline = await createPipeline("text-classification", "Xenova/ms-marco-MiniLM-L-6-v2", { quantized: true });
|
|
1198
|
+
return pipeline;
|
|
1199
|
+
} catch {
|
|
1200
|
+
pipeline = null;
|
|
1201
|
+
pipelineUnavailable = true;
|
|
1202
|
+
return null;
|
|
1203
|
+
} finally {
|
|
1204
|
+
pipelineLoading = null;
|
|
1205
|
+
}
|
|
1206
|
+
})();
|
|
1207
|
+
return pipelineLoading;
|
|
1208
|
+
}
|
|
1209
|
+
async function rerank(query, results, topK = 20) {
|
|
1210
|
+
if (results.length <= 1) return results;
|
|
1211
|
+
const reranker = await loadPipeline();
|
|
1212
|
+
if (!reranker) return results;
|
|
1213
|
+
const pairs = results.slice(0, Math.min(results.length, topK)).map((r) => ({
|
|
1214
|
+
text: `${query} [SEP] ${r.observation.title || ""} ${r.observation.narrative || ""}`.slice(0, 512),
|
|
1215
|
+
result: r
|
|
1216
|
+
}));
|
|
1217
|
+
const scores = [];
|
|
1218
|
+
for (const pair of pairs) try {
|
|
1219
|
+
const output = await reranker(pair.text);
|
|
1220
|
+
const score = Array.isArray(output) ? output[0]?.score ?? 0 : 0;
|
|
1221
|
+
scores.push({
|
|
1222
|
+
result: pair.result,
|
|
1223
|
+
rerankScore: score
|
|
1224
|
+
});
|
|
1225
|
+
} catch {
|
|
1226
|
+
scores.push({
|
|
1227
|
+
result: pair.result,
|
|
1228
|
+
rerankScore: pair.result.combinedScore
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
scores.sort((a, b) => b.rerankScore - a.rerankScore);
|
|
1232
|
+
return scores.map((s, i) => ({
|
|
1233
|
+
...s.result,
|
|
1234
|
+
combinedScore: s.rerankScore,
|
|
1235
|
+
rerankPosition: i + 1
|
|
1236
|
+
}));
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1185
1239
|
//#endregion
|
|
1186
1240
|
//#region src/state/hybrid-search.ts
|
|
1187
1241
|
const RRF_K = 60;
|
|
1188
1242
|
var HybridSearch = class {
|
|
1189
1243
|
graphRetrieval;
|
|
1190
|
-
constructor(bm25, vector, embeddingProvider, kv, bm25Weight = .4, vectorWeight = .6, graphWeight = .3) {
|
|
1244
|
+
constructor(bm25, vector, embeddingProvider, kv, bm25Weight = .4, vectorWeight = .6, graphWeight = .3, rerankEnabled = process.env.RERANK_ENABLED === "true") {
|
|
1191
1245
|
this.bm25 = bm25;
|
|
1192
1246
|
this.vector = vector;
|
|
1193
1247
|
this.embeddingProvider = embeddingProvider;
|
|
@@ -1195,6 +1249,7 @@ var HybridSearch = class {
|
|
|
1195
1249
|
this.bm25Weight = bm25Weight;
|
|
1196
1250
|
this.vectorWeight = vectorWeight;
|
|
1197
1251
|
this.graphWeight = graphWeight;
|
|
1252
|
+
this.rerankEnabled = rerankEnabled;
|
|
1198
1253
|
this.graphRetrieval = new GraphRetrieval(kv);
|
|
1199
1254
|
}
|
|
1200
1255
|
async search(query, limit = 20) {
|
|
@@ -1298,8 +1353,18 @@ var HybridSearch = class {
|
|
|
1298
1353
|
combinedScore: effectiveBm25W * (1 / (RRF_K + s.bm25Rank)) + effectiveVectorW * (1 / (RRF_K + s.vectorRank)) + effectiveGraphW * (1 / (RRF_K + s.graphRank))
|
|
1299
1354
|
}));
|
|
1300
1355
|
combined.sort((a, b) => b.combinedScore - a.combinedScore);
|
|
1301
|
-
const
|
|
1302
|
-
|
|
1356
|
+
const retrievalDepth = Math.max(limit, 20);
|
|
1357
|
+
const rerankWindow = 20;
|
|
1358
|
+
const diversified = this.diversifyBySession(combined, retrievalDepth);
|
|
1359
|
+
const enriched = await this.enrichResults(diversified, retrievalDepth);
|
|
1360
|
+
if (this.rerankEnabled && enriched.length > 1) try {
|
|
1361
|
+
const head = enriched.slice(0, rerankWindow);
|
|
1362
|
+
const tail = enriched.slice(rerankWindow);
|
|
1363
|
+
return (await rerank(query, head, rerankWindow)).concat(tail).slice(0, limit);
|
|
1364
|
+
} catch {
|
|
1365
|
+
return enriched.slice(0, limit);
|
|
1366
|
+
}
|
|
1367
|
+
return enriched.slice(0, limit);
|
|
1303
1368
|
}
|
|
1304
1369
|
diversifyBySession(results, limit, maxPerSession = 3) {
|
|
1305
1370
|
const selected = [];
|
|
@@ -2413,7 +2478,7 @@ function registerCompressFunction(sdk, kv, provider, metricsStore) {
|
|
|
2413
2478
|
|
|
2414
2479
|
//#endregion
|
|
2415
2480
|
//#region src/functions/context.ts
|
|
2416
|
-
function estimateTokens(text) {
|
|
2481
|
+
function estimateTokens$1(text) {
|
|
2417
2482
|
return Math.ceil(text.length / 3);
|
|
2418
2483
|
}
|
|
2419
2484
|
function escapeXmlAttr(s) {
|
|
@@ -2439,7 +2504,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
2439
2504
|
blocks.push({
|
|
2440
2505
|
type: "memory",
|
|
2441
2506
|
content: profileContent,
|
|
2442
|
-
tokens: estimateTokens(profileContent),
|
|
2507
|
+
tokens: estimateTokens$1(profileContent),
|
|
2443
2508
|
recency: new Date(profile.updatedAt).getTime()
|
|
2444
2509
|
});
|
|
2445
2510
|
}
|
|
@@ -2454,7 +2519,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
2454
2519
|
blocks.push({
|
|
2455
2520
|
type: "summary",
|
|
2456
2521
|
content,
|
|
2457
|
-
tokens: estimateTokens(content),
|
|
2522
|
+
tokens: estimateTokens$1(content),
|
|
2458
2523
|
recency: new Date(summary.createdAt).getTime()
|
|
2459
2524
|
});
|
|
2460
2525
|
} else sessionsNeedingObs.push(i);
|
|
@@ -2469,7 +2534,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
2469
2534
|
blocks.push({
|
|
2470
2535
|
type: "observation",
|
|
2471
2536
|
content,
|
|
2472
|
-
tokens: estimateTokens(content),
|
|
2537
|
+
tokens: estimateTokens$1(content),
|
|
2473
2538
|
recency: new Date(sessions[i].startedAt).getTime()
|
|
2474
2539
|
});
|
|
2475
2540
|
}
|
|
@@ -2479,7 +2544,7 @@ function registerContextFunction(sdk, kv, tokenBudget) {
|
|
|
2479
2544
|
const selected = [];
|
|
2480
2545
|
const header = `<agentmemory-context project="${escapeXmlAttr(data.project)}">`;
|
|
2481
2546
|
const footer = `</agentmemory-context>`;
|
|
2482
|
-
usedTokens += estimateTokens(header) + estimateTokens(footer);
|
|
2547
|
+
usedTokens += estimateTokens$1(header) + estimateTokens$1(footer);
|
|
2483
2548
|
for (const block of blocks) {
|
|
2484
2549
|
if (usedTokens + block.tokens > budget) break;
|
|
2485
2550
|
selected.push(block.content);
|
|
@@ -3739,7 +3804,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
3739
3804
|
|
|
3740
3805
|
//#endregion
|
|
3741
3806
|
//#region src/version.ts
|
|
3742
|
-
const VERSION = "0.7.
|
|
3807
|
+
const VERSION = "0.7.7";
|
|
3743
3808
|
|
|
3744
3809
|
//#endregion
|
|
3745
3810
|
//#region src/functions/export-import.ts
|
|
@@ -3841,7 +3906,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
3841
3906
|
"0.7.2",
|
|
3842
3907
|
"0.7.3",
|
|
3843
3908
|
"0.7.4",
|
|
3844
|
-
"0.7.5"
|
|
3909
|
+
"0.7.5",
|
|
3910
|
+
"0.7.6",
|
|
3911
|
+
"0.7.7"
|
|
3845
3912
|
]).has(importData.version)) return {
|
|
3846
3913
|
success: false,
|
|
3847
3914
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -8743,6 +8810,407 @@ function registerReflectFunctions(sdk, kv, provider) {
|
|
|
8743
8810
|
});
|
|
8744
8811
|
}
|
|
8745
8812
|
|
|
8813
|
+
//#endregion
|
|
8814
|
+
//#region src/functions/working-memory.ts
|
|
8815
|
+
const CORE_SCOPE = "mem:core-memory";
|
|
8816
|
+
function estimateTokens(text) {
|
|
8817
|
+
return Math.ceil(text.length / 3);
|
|
8818
|
+
}
|
|
8819
|
+
function scoreEntry(entry, now) {
|
|
8820
|
+
const recencyScore = 1 / (1 + (now - new Date(entry.lastAccessedAt).getTime()) / (1e3 * 60 * 60 * 24) * .1);
|
|
8821
|
+
const accessScore = Math.log2(entry.accessCount + 1) / 10;
|
|
8822
|
+
return entry.importance / 10 * .5 + recencyScore * .3 + accessScore * .2;
|
|
8823
|
+
}
|
|
8824
|
+
function registerWorkingMemoryFunctions(sdk, kv, tokenBudget) {
|
|
8825
|
+
sdk.registerFunction({
|
|
8826
|
+
id: "mem::core-add",
|
|
8827
|
+
description: "Add a fact to core memory (always included in context)"
|
|
8828
|
+
}, async (data) => {
|
|
8829
|
+
if (!data?.content?.trim()) return {
|
|
8830
|
+
success: false,
|
|
8831
|
+
error: "content is required"
|
|
8832
|
+
};
|
|
8833
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8834
|
+
const entry = {
|
|
8835
|
+
id: generateId("core"),
|
|
8836
|
+
content: data.content.trim(),
|
|
8837
|
+
importance: Math.min(10, Math.max(1, data.importance ?? 7)),
|
|
8838
|
+
pinned: data.pinned ?? false,
|
|
8839
|
+
accessCount: 0,
|
|
8840
|
+
lastAccessedAt: now,
|
|
8841
|
+
createdAt: now
|
|
8842
|
+
};
|
|
8843
|
+
await kv.set(CORE_SCOPE, entry.id, entry);
|
|
8844
|
+
try {
|
|
8845
|
+
await recordAudit(kv, "core_add", "mem::core-add", [entry.id], {
|
|
8846
|
+
content: entry.content.slice(0, 100),
|
|
8847
|
+
importance: entry.importance,
|
|
8848
|
+
pinned: entry.pinned
|
|
8849
|
+
});
|
|
8850
|
+
} catch {}
|
|
8851
|
+
return {
|
|
8852
|
+
success: true,
|
|
8853
|
+
id: entry.id
|
|
8854
|
+
};
|
|
8855
|
+
});
|
|
8856
|
+
sdk.registerFunction({
|
|
8857
|
+
id: "mem::core-remove",
|
|
8858
|
+
description: "Remove a fact from core memory"
|
|
8859
|
+
}, async (data) => {
|
|
8860
|
+
if (!data?.id) return {
|
|
8861
|
+
success: false,
|
|
8862
|
+
error: "id is required"
|
|
8863
|
+
};
|
|
8864
|
+
await kv.delete(CORE_SCOPE, data.id);
|
|
8865
|
+
try {
|
|
8866
|
+
await recordAudit(kv, "core_remove", "mem::core-remove", [data.id], {});
|
|
8867
|
+
} catch {}
|
|
8868
|
+
return { success: true };
|
|
8869
|
+
});
|
|
8870
|
+
sdk.registerFunction({
|
|
8871
|
+
id: "mem::core-list",
|
|
8872
|
+
description: "List all core memory entries"
|
|
8873
|
+
}, async () => {
|
|
8874
|
+
const entries = await kv.list(CORE_SCOPE);
|
|
8875
|
+
entries.sort((a, b) => b.importance - a.importance);
|
|
8876
|
+
return {
|
|
8877
|
+
success: true,
|
|
8878
|
+
entries,
|
|
8879
|
+
totalTokens: entries.reduce((sum, e) => sum + estimateTokens(e.content), 0)
|
|
8880
|
+
};
|
|
8881
|
+
});
|
|
8882
|
+
sdk.registerFunction({
|
|
8883
|
+
id: "mem::working-context",
|
|
8884
|
+
description: "Build working context: core memory (pinned) + paged archival (by relevance/recency)"
|
|
8885
|
+
}, async (data) => {
|
|
8886
|
+
const ctx = getContext();
|
|
8887
|
+
const budget = data.budget || tokenBudget;
|
|
8888
|
+
const now = Date.now();
|
|
8889
|
+
let usedTokens = 0;
|
|
8890
|
+
const coreEntries = await kv.list(CORE_SCOPE);
|
|
8891
|
+
const pinned = coreEntries.filter((e) => e.pinned);
|
|
8892
|
+
const unpinned = coreEntries.filter((e) => !e.pinned).sort((a, b) => scoreEntry(b, now) - scoreEntry(a, now));
|
|
8893
|
+
const coreLines = [];
|
|
8894
|
+
const coreBudget = Math.floor(budget * .3);
|
|
8895
|
+
const accessUpdates = [];
|
|
8896
|
+
const accessTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8897
|
+
for (const entry of [...pinned, ...unpinned]) {
|
|
8898
|
+
const tokens = estimateTokens(entry.content);
|
|
8899
|
+
if (usedTokens + tokens > coreBudget && !entry.pinned) continue;
|
|
8900
|
+
coreLines.push(`- ${entry.content}`);
|
|
8901
|
+
usedTokens += tokens;
|
|
8902
|
+
entry.accessCount++;
|
|
8903
|
+
entry.lastAccessedAt = accessTimestamp;
|
|
8904
|
+
accessUpdates.push({
|
|
8905
|
+
id: entry.id,
|
|
8906
|
+
entry
|
|
8907
|
+
});
|
|
8908
|
+
}
|
|
8909
|
+
Promise.allSettled(accessUpdates.map(({ id, entry }) => kv.set(CORE_SCOPE, id, entry))).catch(() => {});
|
|
8910
|
+
const archivalLines = [];
|
|
8911
|
+
const active = (await kv.list(KV.memories)).filter((m) => m.isLatest !== false).sort((a, b) => {
|
|
8912
|
+
const strengthDiff = b.strength - a.strength;
|
|
8913
|
+
if (Math.abs(strengthDiff) > .2) return strengthDiff;
|
|
8914
|
+
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
|
|
8915
|
+
});
|
|
8916
|
+
for (const mem of active) {
|
|
8917
|
+
const tokens = estimateTokens(mem.content);
|
|
8918
|
+
if (usedTokens + tokens > budget) continue;
|
|
8919
|
+
archivalLines.push(`- [${mem.type}] ${mem.title}: ${mem.content}`);
|
|
8920
|
+
usedTokens += tokens;
|
|
8921
|
+
}
|
|
8922
|
+
const pagedOut = active.length - archivalLines.length;
|
|
8923
|
+
const sections = [];
|
|
8924
|
+
if (coreLines.length > 0) sections.push(`## Core Memory\n${coreLines.join("\n")}`);
|
|
8925
|
+
if (archivalLines.length > 0) sections.push(`## Archival Memory\n${archivalLines.join("\n")}`);
|
|
8926
|
+
if (pagedOut > 0) sections.push(`_${pagedOut} memories paged to archival (use mem::search to retrieve)_`);
|
|
8927
|
+
const context = sections.join("\n\n");
|
|
8928
|
+
ctx.logger.info("Working context built", {
|
|
8929
|
+
coreEntries: coreLines.length,
|
|
8930
|
+
archivalEntries: archivalLines.length,
|
|
8931
|
+
pagedOut,
|
|
8932
|
+
tokens: usedTokens,
|
|
8933
|
+
budget
|
|
8934
|
+
});
|
|
8935
|
+
return {
|
|
8936
|
+
success: true,
|
|
8937
|
+
context,
|
|
8938
|
+
coreEntries: coreLines.length,
|
|
8939
|
+
archivalEntries: archivalLines.length,
|
|
8940
|
+
pagedOut,
|
|
8941
|
+
tokens: usedTokens,
|
|
8942
|
+
budget
|
|
8943
|
+
};
|
|
8944
|
+
});
|
|
8945
|
+
sdk.registerFunction({
|
|
8946
|
+
id: "mem::auto-page",
|
|
8947
|
+
description: "Automatically page low-value core memory entries to archival when over budget"
|
|
8948
|
+
}, async (data) => {
|
|
8949
|
+
const budget = data?.budget || tokenBudget;
|
|
8950
|
+
const coreBudget = Math.floor(budget * .3);
|
|
8951
|
+
const entries = await kv.list(CORE_SCOPE);
|
|
8952
|
+
let totalTokens = entries.reduce((sum, e) => sum + estimateTokens(e.content), 0);
|
|
8953
|
+
if (totalTokens <= coreBudget) return {
|
|
8954
|
+
success: true,
|
|
8955
|
+
paged: 0,
|
|
8956
|
+
totalTokens,
|
|
8957
|
+
budget: coreBudget
|
|
8958
|
+
};
|
|
8959
|
+
const now = Date.now();
|
|
8960
|
+
const unpinned = entries.filter((e) => !e.pinned).sort((a, b) => scoreEntry(a, now) - scoreEntry(b, now));
|
|
8961
|
+
let paged = 0;
|
|
8962
|
+
const pagedIds = [];
|
|
8963
|
+
for (const entry of unpinned) {
|
|
8964
|
+
if (totalTokens <= coreBudget) break;
|
|
8965
|
+
const tokens = estimateTokens(entry.content);
|
|
8966
|
+
const archivalMemory = {
|
|
8967
|
+
id: generateId("mem"),
|
|
8968
|
+
createdAt: entry.createdAt,
|
|
8969
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
8970
|
+
type: "fact",
|
|
8971
|
+
title: entry.content.slice(0, 80),
|
|
8972
|
+
content: entry.content,
|
|
8973
|
+
concepts: [],
|
|
8974
|
+
files: [],
|
|
8975
|
+
sessionIds: [],
|
|
8976
|
+
strength: entry.importance / 10,
|
|
8977
|
+
version: 1,
|
|
8978
|
+
isLatest: true
|
|
8979
|
+
};
|
|
8980
|
+
await kv.set(KV.memories, archivalMemory.id, archivalMemory);
|
|
8981
|
+
await kv.delete(CORE_SCOPE, entry.id);
|
|
8982
|
+
totalTokens -= tokens;
|
|
8983
|
+
paged++;
|
|
8984
|
+
pagedIds.push(entry.id);
|
|
8985
|
+
}
|
|
8986
|
+
if (paged > 0) try {
|
|
8987
|
+
await recordAudit(kv, "auto_page", "mem::auto-page", pagedIds, {
|
|
8988
|
+
paged,
|
|
8989
|
+
budget: coreBudget
|
|
8990
|
+
});
|
|
8991
|
+
} catch {}
|
|
8992
|
+
return {
|
|
8993
|
+
success: true,
|
|
8994
|
+
paged,
|
|
8995
|
+
totalTokens,
|
|
8996
|
+
budget: coreBudget
|
|
8997
|
+
};
|
|
8998
|
+
});
|
|
8999
|
+
}
|
|
9000
|
+
|
|
9001
|
+
//#endregion
|
|
9002
|
+
//#region src/functions/skill-extract.ts
|
|
9003
|
+
const SKILL_EXTRACT_SYSTEM = `You are a skill extraction engine. Given a completed multi-step task session, extract a reusable procedural skill document.
|
|
9004
|
+
|
|
9005
|
+
Output format:
|
|
9006
|
+
<skill>
|
|
9007
|
+
<trigger>When the agent encounters [specific situation/pattern]</trigger>
|
|
9008
|
+
<title>Short skill title</title>
|
|
9009
|
+
<steps>
|
|
9010
|
+
<step>First concrete action</step>
|
|
9011
|
+
<step>Second concrete action</step>
|
|
9012
|
+
</steps>
|
|
9013
|
+
<expected_outcome>What success looks like</expected_outcome>
|
|
9014
|
+
<tags>comma,separated,tags</tags>
|
|
9015
|
+
</skill>
|
|
9016
|
+
|
|
9017
|
+
Rules:
|
|
9018
|
+
- Extract ONLY if the session shows a clear multi-step procedure that succeeded
|
|
9019
|
+
- Steps must be concrete and actionable, not vague
|
|
9020
|
+
- The trigger should describe WHEN to apply this skill
|
|
9021
|
+
- If the session is exploratory with no clear procedure, output <no-skill/>
|
|
9022
|
+
- Maximum 10 steps per skill`;
|
|
9023
|
+
function buildSkillPrompt(summary, observations) {
|
|
9024
|
+
const obsText = observations.filter((o) => o.importance >= 4).sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()).slice(0, 30).map((o) => `[${o.type}] ${o.title}${o.narrative ? ": " + o.narrative : ""}`).join("\n");
|
|
9025
|
+
return `## Session Summary
|
|
9026
|
+
Title: ${summary.title}
|
|
9027
|
+
Narrative: ${summary.narrative}
|
|
9028
|
+
Key Decisions: ${summary.keyDecisions.join("; ")}
|
|
9029
|
+
Files Modified: ${summary.filesModified.join(", ")}
|
|
9030
|
+
Concepts: ${summary.concepts.join(", ")}
|
|
9031
|
+
|
|
9032
|
+
## Observations (${observations.length} total, showing top by importance)
|
|
9033
|
+
${obsText}`;
|
|
9034
|
+
}
|
|
9035
|
+
function parseSkillXml(xml) {
|
|
9036
|
+
if (xml.includes("<no-skill/>")) return null;
|
|
9037
|
+
const triggerMatch = xml.match(/<trigger>([\s\S]*?)<\/trigger>/);
|
|
9038
|
+
const titleMatch = xml.match(/<title>([\s\S]*?)<\/title>/);
|
|
9039
|
+
const stepsMatch = xml.match(/<steps>([\s\S]*?)<\/steps>/);
|
|
9040
|
+
const outcomeMatch = xml.match(/<expected_outcome>([\s\S]*?)<\/expected_outcome>/);
|
|
9041
|
+
const tagsMatch = xml.match(/<tags>([\s\S]*?)<\/tags>/);
|
|
9042
|
+
if (!triggerMatch || !titleMatch || !stepsMatch) return null;
|
|
9043
|
+
const stepRegex = /<step>([\s\S]*?)<\/step>/g;
|
|
9044
|
+
const steps = [];
|
|
9045
|
+
let match;
|
|
9046
|
+
while ((match = stepRegex.exec(stepsMatch[1])) !== null) {
|
|
9047
|
+
const step = match[1].trim();
|
|
9048
|
+
if (step) steps.push(step);
|
|
9049
|
+
}
|
|
9050
|
+
if (steps.length < 2) return null;
|
|
9051
|
+
return {
|
|
9052
|
+
trigger: triggerMatch[1].trim(),
|
|
9053
|
+
title: titleMatch[1].trim(),
|
|
9054
|
+
steps,
|
|
9055
|
+
expectedOutcome: outcomeMatch?.[1]?.trim() || "",
|
|
9056
|
+
tags: tagsMatch?.[1]?.split(",").map((t) => t.trim()).filter(Boolean) || []
|
|
9057
|
+
};
|
|
9058
|
+
}
|
|
9059
|
+
function registerSkillExtractFunctions(sdk, kv, provider) {
|
|
9060
|
+
sdk.registerFunction({
|
|
9061
|
+
id: "mem::skill-extract",
|
|
9062
|
+
description: "Extract a reusable skill document from a completed session"
|
|
9063
|
+
}, async (data) => {
|
|
9064
|
+
const ctx = getContext();
|
|
9065
|
+
if (!data?.sessionId) return {
|
|
9066
|
+
success: false,
|
|
9067
|
+
error: "sessionId is required"
|
|
9068
|
+
};
|
|
9069
|
+
const session = await kv.get(KV.sessions, data.sessionId).catch(() => null);
|
|
9070
|
+
if (!session) return {
|
|
9071
|
+
success: false,
|
|
9072
|
+
error: "session not found"
|
|
9073
|
+
};
|
|
9074
|
+
if (session.status !== "completed") return {
|
|
9075
|
+
success: false,
|
|
9076
|
+
error: "session must be completed before skill extraction"
|
|
9077
|
+
};
|
|
9078
|
+
const [summary, observations] = await Promise.all([kv.get(KV.summaries, data.sessionId).catch(() => null), kv.list(KV.observations(data.sessionId)).catch(() => [])]);
|
|
9079
|
+
if (!summary) return {
|
|
9080
|
+
success: false,
|
|
9081
|
+
error: "no summary — run mem::summarize first"
|
|
9082
|
+
};
|
|
9083
|
+
if (observations.length < 3) return {
|
|
9084
|
+
success: false,
|
|
9085
|
+
error: "too few observations for skill extraction"
|
|
9086
|
+
};
|
|
9087
|
+
try {
|
|
9088
|
+
const prompt = buildSkillPrompt(summary, observations);
|
|
9089
|
+
const parsed = parseSkillXml(await provider.summarize(SKILL_EXTRACT_SYSTEM, prompt));
|
|
9090
|
+
if (!parsed) {
|
|
9091
|
+
ctx.logger.info("No skill extracted — session was exploratory", { sessionId: data.sessionId });
|
|
9092
|
+
return {
|
|
9093
|
+
success: true,
|
|
9094
|
+
extracted: false,
|
|
9095
|
+
reason: "no clear procedure found"
|
|
9096
|
+
};
|
|
9097
|
+
}
|
|
9098
|
+
const fp = fingerprintId("skill", JSON.stringify({
|
|
9099
|
+
title: parsed.title.toLowerCase(),
|
|
9100
|
+
trigger: parsed.trigger.toLowerCase(),
|
|
9101
|
+
steps: parsed.steps.map((s) => s.toLowerCase().trim())
|
|
9102
|
+
}));
|
|
9103
|
+
const existing = await kv.get(KV.procedural, fp).catch(() => null);
|
|
9104
|
+
if (existing) {
|
|
9105
|
+
if (!existing.sourceSessionIds.includes(data.sessionId)) {
|
|
9106
|
+
existing.strength = Math.min(1, existing.strength + .15);
|
|
9107
|
+
existing.frequency++;
|
|
9108
|
+
existing.sourceSessionIds = [...existing.sourceSessionIds, data.sessionId];
|
|
9109
|
+
}
|
|
9110
|
+
existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9111
|
+
await kv.set(KV.procedural, existing.id, existing);
|
|
9112
|
+
try {
|
|
9113
|
+
await recordAudit(kv, "skill_extract", "mem::skill-extract", [], {
|
|
9114
|
+
skillId: existing.id,
|
|
9115
|
+
reinforced: true,
|
|
9116
|
+
sessionId: data.sessionId
|
|
9117
|
+
});
|
|
9118
|
+
} catch {}
|
|
9119
|
+
ctx.logger.info("Skill reinforced", {
|
|
9120
|
+
id: existing.id,
|
|
9121
|
+
name: parsed.title
|
|
9122
|
+
});
|
|
9123
|
+
return {
|
|
9124
|
+
success: true,
|
|
9125
|
+
extracted: true,
|
|
9126
|
+
reinforced: true,
|
|
9127
|
+
skill: existing
|
|
9128
|
+
};
|
|
9129
|
+
}
|
|
9130
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9131
|
+
const skill = {
|
|
9132
|
+
id: fp,
|
|
9133
|
+
name: parsed.title,
|
|
9134
|
+
triggerCondition: parsed.trigger,
|
|
9135
|
+
steps: parsed.steps,
|
|
9136
|
+
expectedOutcome: parsed.expectedOutcome,
|
|
9137
|
+
strength: .6,
|
|
9138
|
+
frequency: 1,
|
|
9139
|
+
tags: parsed.tags,
|
|
9140
|
+
concepts: summary.concepts,
|
|
9141
|
+
sourceSessionIds: [data.sessionId],
|
|
9142
|
+
sourceObservationIds: observations.slice(0, 10).map((o) => o.id),
|
|
9143
|
+
createdAt: now,
|
|
9144
|
+
updatedAt: now
|
|
9145
|
+
};
|
|
9146
|
+
await kv.set(KV.procedural, skill.id, skill);
|
|
9147
|
+
try {
|
|
9148
|
+
await recordAudit(kv, "skill_extract", "mem::skill-extract", [], {
|
|
9149
|
+
skillId: skill.id,
|
|
9150
|
+
title: parsed.title,
|
|
9151
|
+
steps: parsed.steps.length,
|
|
9152
|
+
sessionId: data.sessionId
|
|
9153
|
+
});
|
|
9154
|
+
} catch {}
|
|
9155
|
+
ctx.logger.info("Skill extracted", {
|
|
9156
|
+
id: skill.id,
|
|
9157
|
+
title: parsed.title,
|
|
9158
|
+
steps: parsed.steps.length
|
|
9159
|
+
});
|
|
9160
|
+
return {
|
|
9161
|
+
success: true,
|
|
9162
|
+
extracted: true,
|
|
9163
|
+
reinforced: false,
|
|
9164
|
+
skill
|
|
9165
|
+
};
|
|
9166
|
+
} catch (err) {
|
|
9167
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9168
|
+
ctx.logger.error("Skill extraction failed", { error: msg });
|
|
9169
|
+
return {
|
|
9170
|
+
success: false,
|
|
9171
|
+
error: msg
|
|
9172
|
+
};
|
|
9173
|
+
}
|
|
9174
|
+
});
|
|
9175
|
+
sdk.registerFunction({
|
|
9176
|
+
id: "mem::skill-list",
|
|
9177
|
+
description: "List extracted procedural skills"
|
|
9178
|
+
}, async (data) => {
|
|
9179
|
+
const limit = data?.limit ?? 50;
|
|
9180
|
+
const sorted = (await kv.list(KV.procedural)).sort((a, b) => b.strength - a.strength);
|
|
9181
|
+
return {
|
|
9182
|
+
success: true,
|
|
9183
|
+
skills: sorted.slice(0, limit),
|
|
9184
|
+
total: sorted.length
|
|
9185
|
+
};
|
|
9186
|
+
});
|
|
9187
|
+
sdk.registerFunction({
|
|
9188
|
+
id: "mem::skill-match",
|
|
9189
|
+
description: "Find skills relevant to a given task description"
|
|
9190
|
+
}, async (data) => {
|
|
9191
|
+
if (!data?.query?.trim()) return {
|
|
9192
|
+
success: false,
|
|
9193
|
+
error: "query is required"
|
|
9194
|
+
};
|
|
9195
|
+
const limit = data.limit ?? 5;
|
|
9196
|
+
const terms = data.query.toLowerCase().split(/\s+/).filter((t) => t.length > 2);
|
|
9197
|
+
const scored = (await kv.list(KV.procedural)).map((skill) => {
|
|
9198
|
+
const text = `${skill.name} ${skill.triggerCondition} ${(skill.tags || []).join(" ")} ${skill.steps.join(" ")}`.toLowerCase();
|
|
9199
|
+
const matchCount = terms.filter((t) => text.includes(t)).length;
|
|
9200
|
+
if (matchCount === 0) return null;
|
|
9201
|
+
return {
|
|
9202
|
+
skill,
|
|
9203
|
+
score: matchCount / terms.length * skill.strength
|
|
9204
|
+
};
|
|
9205
|
+
}).filter(Boolean);
|
|
9206
|
+
scored.sort((a, b) => b.score - a.score);
|
|
9207
|
+
return {
|
|
9208
|
+
success: true,
|
|
9209
|
+
matches: scored.slice(0, limit)
|
|
9210
|
+
};
|
|
9211
|
+
});
|
|
9212
|
+
}
|
|
9213
|
+
|
|
8746
9214
|
//#endregion
|
|
8747
9215
|
//#region src/functions/sliding-window.ts
|
|
8748
9216
|
const SLIDING_WINDOW_SYSTEM = `You are a contextual enrichment engine. Given a primary observation and its surrounding context window (previous and next observations from the same session), produce an enriched version.
|
|
@@ -14157,6 +14625,8 @@ async function main() {
|
|
|
14157
14625
|
registerLessonsFunctions(sdk, kv);
|
|
14158
14626
|
registerObsidianExportFunction(sdk, kv);
|
|
14159
14627
|
registerReflectFunctions(sdk, kv, provider);
|
|
14628
|
+
registerWorkingMemoryFunctions(sdk, kv, config.tokenBudget);
|
|
14629
|
+
registerSkillExtractFunctions(sdk, kv, provider);
|
|
14160
14630
|
registerCascadeFunction(sdk, kv);
|
|
14161
14631
|
registerSlidingWindowFunction(sdk, kv, provider);
|
|
14162
14632
|
registerQueryExpansionFunction(sdk, provider);
|
|
@@ -14256,4 +14726,4 @@ main().catch((err) => {
|
|
|
14256
14726
|
|
|
14257
14727
|
//#endregion
|
|
14258
14728
|
export { };
|
|
14259
|
-
//# sourceMappingURL=src-
|
|
14729
|
+
//# sourceMappingURL=src-C_TC9frp.mjs.map
|