@agentmemory/agentmemory 0.9.18 → 0.9.19

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.
@@ -1,6 +1,6 @@
1
1
  import { a as STREAM, c as jaccardSimilarity, i as KV, n as bootLog, o as fingerprintId, r as logger, s as generateId, t as VERSION } from "./cli.mjs";
2
2
  import { a as isManagedImagePath, getImageRefCount, i as getMaxBytes, n as IMAGES_DIR, r as deleteImage, t as withKeyedLock } from "./image-refs-R3tin9MR.mjs";
3
- import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-BFKFKmYh.mjs";
3
+ import { _ as loadTeamConfig, a as getConsolidationDecayDays, c as isAutoCompressEnabled, d as isGraphExtractionEnabled, f as loadClaudeBridgeConfig, g as loadSnapshotConfig, h as loadFallbackConfig, i as detectLlmProviderKind, l as isConsolidationEnabled, m as loadEmbeddingConfig, n as getVisibleTools, o as getEnvVar, p as loadConfig, r as detectEmbeddingProvider, t as getAllTools, u as isContextInjectionEnabled } from "./tools-registry-Dz8ssuMf.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import { execFile } from "node:child_process";
6
6
  import { constants, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -197,11 +197,63 @@ var NoopProvider = class {
197
197
  }
198
198
  };
199
199
 
200
+ //#endregion
201
+ //#region src/providers/_openai-shared.ts
202
+ const DEFAULT_OPENAI_BASE_URL = "https://api.openai.com";
203
+ const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
204
+ function detectAzure(baseUrl) {
205
+ try {
206
+ return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
207
+ } catch {
208
+ return false;
209
+ }
210
+ }
211
+ function azureStyleOf(baseUrl) {
212
+ try {
213
+ const u = new URL(baseUrl);
214
+ if (/\/openai\/deployments\//.test(u.pathname)) return "legacy";
215
+ return "v1";
216
+ } catch {
217
+ return "v1";
218
+ }
219
+ }
220
+ function legacyAzureUrl(baseUrl, path, apiVersion) {
221
+ const url = new URL(baseUrl);
222
+ url.pathname = `${url.pathname.replace(/\/+$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
223
+ url.searchParams.set("api-version", apiVersion);
224
+ return url.toString();
225
+ }
226
+ function v1AzureUrl(baseUrl, path) {
227
+ const url = new URL(baseUrl);
228
+ const route = path.startsWith("/") ? path.slice(1) : path;
229
+ url.pathname = `${url.pathname.replace(/\/?openai(?:\/v1)?\/?$/, "").replace(/\/+$/, "")}/openai/v1/${route}`;
230
+ return url.toString();
231
+ }
232
+ function buildChatUrl(baseUrl, isAzure, azureApiVersion) {
233
+ if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/chat/completions", azureApiVersion) : v1AzureUrl(baseUrl, "/chat/completions");
234
+ return `${baseUrl}/v1/chat/completions`;
235
+ }
236
+ function buildEmbeddingUrl(baseUrl, isAzure, azureApiVersion) {
237
+ if (isAzure) return azureStyleOf(baseUrl) === "legacy" ? legacyAzureUrl(baseUrl, "/embeddings", azureApiVersion) : v1AzureUrl(baseUrl, "/embeddings");
238
+ return `${baseUrl}/v1/embeddings`;
239
+ }
240
+ function buildAuthHeaders(apiKey, isAzure) {
241
+ if (isAzure) return {
242
+ "Content-Type": "application/json",
243
+ "api-key": apiKey
244
+ };
245
+ return {
246
+ "Content-Type": "application/json",
247
+ Authorization: `Bearer ${apiKey}`
248
+ };
249
+ }
250
+ function normalizeBaseUrl(raw) {
251
+ return (raw || DEFAULT_OPENAI_BASE_URL).replace(/\/+$/, "");
252
+ }
253
+
200
254
  //#endregion
201
255
  //#region src/providers/openai.ts
202
- const DEFAULT_BASE_URL$1 = "https://api.openai.com";
203
256
  const DEFAULT_TIMEOUT_MS = 6e4;
204
- const DEFAULT_AZURE_API_VERSION = "2024-08-01-preview";
205
257
  /**
206
258
  * OpenAI-compatible LLM provider.
207
259
  *
@@ -248,7 +300,7 @@ var OpenAIProvider = class {
248
300
  this.apiKey = apiKey;
249
301
  this.model = model;
250
302
  this.maxTokens = maxTokens;
251
- this.baseUrl = (baseURL || getEnvVar("OPENAI_BASE_URL") || DEFAULT_BASE_URL$1).replace(/\/+$/, "");
303
+ this.baseUrl = normalizeBaseUrl(baseURL || getEnvVar("OPENAI_BASE_URL"));
252
304
  this.reasoningEffort = getEnvVar("OPENAI_REASONING_EFFORT") || void 0;
253
305
  this.timeoutMs = resolveTimeout();
254
306
  this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
@@ -260,25 +312,8 @@ var OpenAIProvider = class {
260
312
  async summarize(systemPrompt, userPrompt) {
261
313
  return this.call(systemPrompt, userPrompt);
262
314
  }
263
- buildUrl() {
264
- if (this.isAzure) {
265
- const sep = this.baseUrl.includes("?") ? "&" : "?";
266
- return `${this.baseUrl}/chat/completions${sep}api-version=${encodeURIComponent(this.azureApiVersion)}`;
267
- }
268
- return `${this.baseUrl}/v1/chat/completions`;
269
- }
270
- buildHeaders() {
271
- if (this.isAzure) return {
272
- "Content-Type": "application/json",
273
- "api-key": this.apiKey
274
- };
275
- return {
276
- "Content-Type": "application/json",
277
- Authorization: `Bearer ${this.apiKey}`
278
- };
279
- }
280
315
  async call(systemPrompt, userPrompt) {
281
- const url = this.buildUrl();
316
+ const url = buildChatUrl(this.baseUrl, this.isAzure, this.azureApiVersion);
282
317
  const body = {
283
318
  model: this.model,
284
319
  max_tokens: this.maxTokens,
@@ -295,7 +330,7 @@ var OpenAIProvider = class {
295
330
  try {
296
331
  response = await fetchWithTimeout(url, {
297
332
  method: "POST",
298
- headers: this.buildHeaders(),
333
+ headers: buildAuthHeaders(this.apiKey, this.isAzure),
299
334
  body: JSON.stringify(body)
300
335
  }, this.timeoutMs);
301
336
  } catch (err) {
@@ -329,13 +364,6 @@ function parsePositiveInt(raw) {
329
364
  const n = Number(trimmed);
330
365
  return Number.isFinite(n) && n > 0 ? n : void 0;
331
366
  }
332
- function detectAzure(baseUrl) {
333
- try {
334
- return new URL(baseUrl).hostname.endsWith(".openai.azure.com");
335
- } catch {
336
- return false;
337
- }
338
- }
339
367
 
340
368
  //#endregion
341
369
  //#region src/providers/openrouter.ts
@@ -565,7 +593,6 @@ function l2Normalize(vec) {
565
593
 
566
594
  //#endregion
567
595
  //#region src/providers/embedding/openai.ts
568
- const DEFAULT_BASE_URL = "https://api.openai.com";
569
596
  const DEFAULT_MODEL$1 = "text-embedding-3-small";
570
597
  /**
571
598
  * Known OpenAI embedding model dimensions. Extend as new models ship.
@@ -589,11 +616,20 @@ function resolveDimensions(model, override) {
589
616
  /**
590
617
  * OpenAI-compatible embedding provider.
591
618
  *
619
+ * Shares transport (URL builder, auth header, Azure detection) with
620
+ * the OpenAI LLM provider via `_openai-shared` (#371). Same env knobs
621
+ * pick up automatically: when `OPENAI_BASE_URL` points at an Azure
622
+ * resource (`.openai.azure.com` hostname) the embedding request uses
623
+ * Azure's `/embeddings` path with the `api-version` query param and
624
+ * `api-key` header instead of `Authorization: Bearer`.
625
+ *
592
626
  * Required env vars:
593
627
  * OPENAI_API_KEY — API key
594
628
  *
595
629
  * Optional:
596
- * OPENAI_BASE_URL — base URL without path (default: https://api.openai.com)
630
+ * OPENAI_BASE_URL — base URL without path (default: https://api.openai.com).
631
+ * Azure: https://<resource>.openai.azure.com/openai/deployments/<deployment>
632
+ * OPENAI_API_VERSION — Azure api-version query param (default: 2024-08-01-preview)
597
633
  * OPENAI_EMBEDDING_MODEL — model name (default: text-embedding-3-small)
598
634
  * OPENAI_EMBEDDING_DIMENSIONS — override reported dimensions (required for
599
635
  * custom / self-hosted models not in the
@@ -605,24 +641,25 @@ var OpenAIEmbeddingProvider = class {
605
641
  apiKey;
606
642
  baseUrl;
607
643
  model;
644
+ isAzure;
645
+ azureApiVersion;
608
646
  constructor(apiKey) {
609
647
  this.apiKey = apiKey || getEnvVar("OPENAI_API_KEY") || "";
610
648
  if (!this.apiKey) throw new Error("OPENAI_API_KEY is required");
611
- this.baseUrl = getEnvVar("OPENAI_BASE_URL") || DEFAULT_BASE_URL;
649
+ this.baseUrl = normalizeBaseUrl(getEnvVar("OPENAI_BASE_URL"));
612
650
  this.model = getEnvVar("OPENAI_EMBEDDING_MODEL") || DEFAULT_MODEL$1;
613
651
  this.dimensions = resolveDimensions(this.model, getEnvVar("OPENAI_EMBEDDING_DIMENSIONS"));
652
+ this.isAzure = detectAzure(this.baseUrl);
653
+ this.azureApiVersion = getEnvVar("OPENAI_API_VERSION") || DEFAULT_AZURE_API_VERSION;
614
654
  }
615
655
  async embed(text) {
616
656
  const [result] = await this.embedBatch([text]);
617
657
  return result;
618
658
  }
619
659
  async embedBatch(texts) {
620
- const response = await fetchWithTimeout(`${this.baseUrl}/v1/embeddings`, {
660
+ const response = await fetchWithTimeout(buildEmbeddingUrl(this.baseUrl, this.isAzure, this.azureApiVersion), {
621
661
  method: "POST",
622
- headers: {
623
- Authorization: `Bearer ${this.apiKey}`,
624
- "Content-Type": "application/json"
625
- },
662
+ headers: buildAuthHeaders(this.apiKey, this.isAzure),
626
663
  body: JSON.stringify({
627
664
  model: this.model,
628
665
  input: texts
@@ -1154,7 +1191,7 @@ var GraphRetrieval = class {
1154
1191
  const results = [];
1155
1192
  const visitedObs = /* @__PURE__ */ new Set();
1156
1193
  for (const startNode of matchingNodes) {
1157
- const paths = this.bfsTraversal(startNode, allNodes, allEdges, maxDepth);
1194
+ const paths = this.dijkstraTraversal(startNode, allNodes, allEdges, maxDepth);
1158
1195
  for (const path of paths) {
1159
1196
  const lastNode = path[path.length - 1].node;
1160
1197
  for (const obsId of lastNode.sourceObservationIds) {
@@ -1194,7 +1231,7 @@ var GraphRetrieval = class {
1194
1231
  const results = [];
1195
1232
  const visitedObs = new Set(obsIds);
1196
1233
  for (const node of linkedNodes) {
1197
- const paths = this.bfsTraversal(node, allNodes, allEdges, maxDepth);
1234
+ const paths = this.dijkstraTraversal(node, allNodes, allEdges, maxDepth);
1198
1235
  for (const path of paths) {
1199
1236
  const lastNode = path[path.length - 1].node;
1200
1237
  for (const obsId of lastNode.sourceObservationIds) {
@@ -1266,37 +1303,104 @@ var GraphRetrieval = class {
1266
1303
  }
1267
1304
  return latest;
1268
1305
  }
1269
- bfsTraversal(startNode, allNodes, allEdges, maxDepth) {
1270
- const paths = [];
1271
- const visited = /* @__PURE__ */ new Set();
1272
- const queue = [{
1306
+ dijkstraTraversal(startNode, allNodes, allEdges, maxDepth) {
1307
+ const nodeIndex = /* @__PURE__ */ new Map();
1308
+ for (const n of allNodes) nodeIndex.set(n.id, n);
1309
+ const adjacency = /* @__PURE__ */ new Map();
1310
+ for (const edge of allEdges) {
1311
+ const a = edge.sourceNodeId;
1312
+ const b = edge.targetNodeId;
1313
+ if (!adjacency.has(a)) adjacency.set(a, []);
1314
+ if (!adjacency.has(b)) adjacency.set(b, []);
1315
+ adjacency.get(a).push({
1316
+ neighborId: b,
1317
+ edge
1318
+ });
1319
+ adjacency.get(b).push({
1320
+ neighborId: a,
1321
+ edge
1322
+ });
1323
+ }
1324
+ const dist = /* @__PURE__ */ new Map();
1325
+ const pathTo = /* @__PURE__ */ new Map();
1326
+ dist.set(startNode.id, 0);
1327
+ pathTo.set(startNode.id, [{ node: startNode }]);
1328
+ const heap = new MinHeap((a, b) => a.cost - b.cost);
1329
+ heap.push({
1273
1330
  nodeId: startNode.id,
1274
1331
  depth: 0,
1275
- path: [{ node: startNode }]
1276
- }];
1277
- visited.add(startNode.id);
1278
- while (queue.length > 0) {
1279
- const { nodeId, depth, path } = queue.shift();
1280
- paths.push(path);
1332
+ cost: 0
1333
+ });
1334
+ while (heap.size() > 0) {
1335
+ const { nodeId, depth, cost } = heap.pop();
1336
+ if (cost > (dist.get(nodeId) ?? Infinity)) continue;
1281
1337
  if (depth >= maxDepth) continue;
1282
- const neighborEdges = allEdges.filter((e) => e.sourceNodeId === nodeId || e.targetNodeId === nodeId);
1283
- for (const edge of neighborEdges) {
1284
- const nextId = edge.sourceNodeId === nodeId ? edge.targetNodeId : edge.sourceNodeId;
1285
- if (visited.has(nextId)) continue;
1286
- visited.add(nextId);
1287
- const nextNode = allNodes.find((n) => n.id === nextId);
1338
+ const neighbors = adjacency.get(nodeId) ?? [];
1339
+ for (const { neighborId, edge } of neighbors) {
1340
+ const nextNode = nodeIndex.get(neighborId);
1288
1341
  if (!nextNode) continue;
1289
- queue.push({
1290
- nodeId: nextId,
1291
- depth: depth + 1,
1292
- path: [...path, {
1342
+ const newCost = cost + 1 / Math.max(edge.weight, .01);
1343
+ if (newCost < (dist.get(neighborId) ?? Infinity)) {
1344
+ dist.set(neighborId, newCost);
1345
+ pathTo.set(neighborId, [...pathTo.get(nodeId), {
1293
1346
  node: nextNode,
1294
1347
  edge
1295
- }]
1296
- });
1348
+ }]);
1349
+ heap.push({
1350
+ nodeId: neighborId,
1351
+ depth: depth + 1,
1352
+ cost: newCost
1353
+ });
1354
+ }
1297
1355
  }
1298
1356
  }
1299
- return paths;
1357
+ pathTo.delete(startNode.id);
1358
+ return Array.from(pathTo.values());
1359
+ }
1360
+ };
1361
+ var MinHeap = class {
1362
+ heap = [];
1363
+ constructor(compare) {
1364
+ this.compare = compare;
1365
+ }
1366
+ size() {
1367
+ return this.heap.length;
1368
+ }
1369
+ push(value) {
1370
+ this.heap.push(value);
1371
+ this.bubbleUp(this.heap.length - 1);
1372
+ }
1373
+ pop() {
1374
+ if (this.heap.length === 0) return void 0;
1375
+ const top = this.heap[0];
1376
+ const last = this.heap.pop();
1377
+ if (this.heap.length > 0) {
1378
+ this.heap[0] = last;
1379
+ this.sinkDown(0);
1380
+ }
1381
+ return top;
1382
+ }
1383
+ bubbleUp(i) {
1384
+ while (i > 0) {
1385
+ const parent = i - 1 >> 1;
1386
+ if (this.compare(this.heap[i], this.heap[parent]) < 0) {
1387
+ [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
1388
+ i = parent;
1389
+ } else break;
1390
+ }
1391
+ }
1392
+ sinkDown(i) {
1393
+ const n = this.heap.length;
1394
+ while (true) {
1395
+ const left = 2 * i + 1;
1396
+ const right = 2 * i + 2;
1397
+ let smallest = i;
1398
+ if (left < n && this.compare(this.heap[left], this.heap[smallest]) < 0) smallest = left;
1399
+ if (right < n && this.compare(this.heap[right], this.heap[smallest]) < 0) smallest = right;
1400
+ if (smallest === i) break;
1401
+ [this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
1402
+ i = smallest;
1403
+ }
1300
1404
  }
1301
1405
  };
1302
1406
 
@@ -5896,7 +6000,8 @@ function registerExportImportFunction(sdk, kv) {
5896
6000
  "0.9.15",
5897
6001
  "0.9.16",
5898
6002
  "0.9.17",
5899
- "0.9.18"
6003
+ "0.9.18",
6004
+ "0.9.19"
5900
6005
  ]).has(importData.version)) return {
5901
6006
  success: false,
5902
6007
  error: `Unsupported export version: ${importData.version}`
@@ -13724,6 +13829,112 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
13724
13829
  middleware_function_ids: ["middleware::api-auth"]
13725
13830
  }
13726
13831
  });
13832
+ sdk.registerFunction("api::session::commit", async (req) => {
13833
+ const body = req.body ?? {};
13834
+ const sha = asNonEmptyString$1(body.sha);
13835
+ if (!sha) return {
13836
+ status_code: 400,
13837
+ body: { error: "sha is required and must be a non-empty string" }
13838
+ };
13839
+ const sessionId = asNonEmptyString$1(body.sessionId) ?? void 0;
13840
+ const branch = asNonEmptyString$1(body.branch) ?? void 0;
13841
+ const repo = asNonEmptyString$1(body.repo) ?? void 0;
13842
+ const message = asNonEmptyString$1(body.message) ?? void 0;
13843
+ const author = asNonEmptyString$1(body.author) ?? void 0;
13844
+ const authoredAt = asNonEmptyString$1(body.authoredAt) ?? void 0;
13845
+ const files = Array.isArray(body.files) ? body.files.filter((f) => typeof f === "string" && f.length > 0) : void 0;
13846
+ const link = await withKeyedLock(`commit:${sha}`, async () => {
13847
+ const existing = await kv.get(KV.commits, sha);
13848
+ const sessionSet = new Set(existing?.sessionIds ?? []);
13849
+ if (sessionId) sessionSet.add(sessionId);
13850
+ const merged = {
13851
+ sha,
13852
+ shortSha: existing?.shortSha ?? sha.slice(0, 7),
13853
+ branch: branch ?? existing?.branch,
13854
+ repo: repo ?? existing?.repo,
13855
+ message: message ?? existing?.message,
13856
+ author: author ?? existing?.author,
13857
+ authoredAt: authoredAt ?? existing?.authoredAt,
13858
+ files: files ?? existing?.files,
13859
+ sessionIds: Array.from(sessionSet),
13860
+ linkedAt: existing?.linkedAt ?? (/* @__PURE__ */ new Date()).toISOString()
13861
+ };
13862
+ await kv.set(KV.commits, sha, merged);
13863
+ return merged;
13864
+ });
13865
+ if (sessionId) await withKeyedLock(`session:${sessionId}`, async () => {
13866
+ const session = await kv.get(KV.sessions, sessionId);
13867
+ if (!session) return;
13868
+ const shaSet = new Set(session.commitShas ?? []);
13869
+ shaSet.add(sha);
13870
+ session.commitShas = Array.from(shaSet);
13871
+ await kv.set(KV.sessions, sessionId, session);
13872
+ });
13873
+ return {
13874
+ status_code: 200,
13875
+ body: { commit: link }
13876
+ };
13877
+ });
13878
+ sdk.registerTrigger({
13879
+ type: "http",
13880
+ function_id: "api::session::commit",
13881
+ config: {
13882
+ api_path: "/agentmemory/session/commit",
13883
+ http_method: "POST",
13884
+ middleware_function_ids: ["middleware::api-auth"]
13885
+ }
13886
+ });
13887
+ sdk.registerFunction("api::session::by-commit", async (req) => {
13888
+ const authErr = checkAuth(req, secret);
13889
+ if (authErr) return authErr;
13890
+ const sha = asNonEmptyString$1(req.query_params?.["sha"]);
13891
+ if (!sha) return {
13892
+ status_code: 400,
13893
+ body: { error: "sha is required and must be a non-empty string" }
13894
+ };
13895
+ const link = await kv.get(KV.commits, sha);
13896
+ if (!link) return {
13897
+ status_code: 404,
13898
+ body: { error: "no sessions linked to this commit" }
13899
+ };
13900
+ return {
13901
+ status_code: 200,
13902
+ body: {
13903
+ commit: link,
13904
+ sessions: (await Promise.all((link.sessionIds ?? []).map((sid) => kv.get(KV.sessions, sid)))).filter((s) => s !== null)
13905
+ }
13906
+ };
13907
+ });
13908
+ sdk.registerTrigger({
13909
+ type: "http",
13910
+ function_id: "api::session::by-commit",
13911
+ config: {
13912
+ api_path: "/agentmemory/session/by-commit",
13913
+ http_method: "GET",
13914
+ middleware_function_ids: ["middleware::api-auth"]
13915
+ }
13916
+ });
13917
+ sdk.registerFunction("api::commits", async (req) => {
13918
+ const authErr = checkAuth(req, secret);
13919
+ if (authErr) return authErr;
13920
+ const branch = asNonEmptyString$1(req.query_params?.["branch"]);
13921
+ const repo = asNonEmptyString$1(req.query_params?.["repo"]);
13922
+ const rawLimit = parseOptionalInt(req.query_params?.["limit"]);
13923
+ const limit = Math.max(1, Math.min(500, rawLimit ?? 100));
13924
+ return {
13925
+ status_code: 200,
13926
+ body: { commits: (await kv.list(KV.commits)).filter((c) => !branch || c.branch === branch).filter((c) => !repo || c.repo === repo).sort((a, b) => (a.linkedAt ?? "") < (b.linkedAt ?? "") ? 1 : -1).slice(0, limit) }
13927
+ };
13928
+ });
13929
+ sdk.registerTrigger({
13930
+ type: "http",
13931
+ function_id: "api::commits",
13932
+ config: {
13933
+ api_path: "/agentmemory/commits",
13934
+ http_method: "GET",
13935
+ middleware_function_ids: ["middleware::api-auth"]
13936
+ }
13937
+ });
13727
13938
  sdk.registerFunction("api::sessions", async (req) => {
13728
13939
  const authErr = checkAuth(req, secret);
13729
13940
  if (authErr) return authErr;
@@ -17705,6 +17916,49 @@ function registerMcpEndpoints(sdk, kv, secret) {
17705
17916
  }] }
17706
17917
  };
17707
17918
  }
17919
+ case "memory_commit_lookup": {
17920
+ const sha = asNonEmptyString(args.sha);
17921
+ if (!sha) return {
17922
+ status_code: 400,
17923
+ body: { error: "sha required" }
17924
+ };
17925
+ const link = await kv.get(KV.commits, sha);
17926
+ if (!link) return {
17927
+ status_code: 200,
17928
+ body: { content: [{
17929
+ type: "text",
17930
+ text: JSON.stringify({
17931
+ commit: null,
17932
+ sessions: []
17933
+ }, null, 2)
17934
+ }] }
17935
+ };
17936
+ const linkRecord = link;
17937
+ const sessions = (await Promise.all((linkRecord.sessionIds ?? []).map((sid) => kv.get(KV.sessions, sid)))).filter((s) => s !== null);
17938
+ return {
17939
+ status_code: 200,
17940
+ body: { content: [{
17941
+ type: "text",
17942
+ text: JSON.stringify({
17943
+ commit: link,
17944
+ sessions
17945
+ }, null, 2)
17946
+ }] }
17947
+ };
17948
+ }
17949
+ case "memory_commits": {
17950
+ const branch = typeof args.branch === "string" ? args.branch : void 0;
17951
+ const repo = typeof args.repo === "string" ? args.repo : void 0;
17952
+ const limit = Math.max(1, Math.min(500, asNumber(args.limit, 100) ?? 100));
17953
+ const filtered = (await kv.list(KV.commits)).filter((c) => !branch || c.branch === branch).filter((c) => !repo || c.repo === repo).sort((a, b) => (a.linkedAt ?? "") < (b.linkedAt ?? "") ? 1 : -1).slice(0, limit);
17954
+ return {
17955
+ status_code: 200,
17956
+ body: { content: [{
17957
+ type: "text",
17958
+ text: JSON.stringify({ commits: filtered }, null, 2)
17959
+ }] }
17960
+ };
17961
+ }
17708
17962
  default: return {
17709
17963
  status_code: 400,
17710
17964
  body: { error: `Unknown tool: ${name}` }
@@ -18617,7 +18871,7 @@ async function main() {
18617
18871
  console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
18618
18872
  }
18619
18873
  bootLog(`Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
18620
- bootLog(`REST API: 121 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
18874
+ bootLog(`REST API: 124 endpoints at http://localhost:${config.restPort}/agentmemory/*`);
18621
18875
  bootLog(`MCP surface (opt-in via \`npx @agentmemory/mcp\`): ${getAllTools().length} tools · 6 resources · 3 prompts`);
18622
18876
  const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
18623
18877
  const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
@@ -18685,4 +18939,4 @@ main().catch((err) => {
18685
18939
 
18686
18940
  //#endregion
18687
18941
  export { };
18688
- //# sourceMappingURL=src-C7vygXCj.mjs.map
18942
+ //# sourceMappingURL=src-2wwYDPGA.mjs.map