@jcyamacho/agent-memory 0.0.20 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +31 -63
  2. package/dist/index.js +334 -577
  3. package/package.json +8 -9
package/dist/index.js CHANGED
@@ -2431,9 +2431,9 @@ var require_validate = __commonJS((exports) => {
2431
2431
  }
2432
2432
  }
2433
2433
  function returnResults(it) {
2434
- const { gen, schemaEnv, validateName, ValidationError: ValidationError2, opts } = it;
2434
+ const { gen, schemaEnv, validateName, ValidationError, opts } = it;
2435
2435
  if (schemaEnv.$async) {
2436
- gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError2}(${names_1.default.vErrors})`));
2436
+ gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError}(${names_1.default.vErrors})`));
2437
2437
  } else {
2438
2438
  gen.assign((0, codegen_1._)`${validateName}.errors`, names_1.default.vErrors);
2439
2439
  if (opts.unevaluated)
@@ -2783,14 +2783,14 @@ var require_validate = __commonJS((exports) => {
2783
2783
  var require_validation_error = __commonJS((exports) => {
2784
2784
  Object.defineProperty(exports, "__esModule", { value: true });
2785
2785
 
2786
- class ValidationError2 extends Error {
2786
+ class ValidationError extends Error {
2787
2787
  constructor(errors3) {
2788
2788
  super("validation failed");
2789
2789
  this.errors = errors3;
2790
2790
  this.ajv = this.validation = true;
2791
2791
  }
2792
2792
  }
2793
- exports.default = ValidationError2;
2793
+ exports.default = ValidationError;
2794
2794
  });
2795
2795
 
2796
2796
  // node_modules/ajv/dist/compile/ref_error.js
@@ -11543,7 +11543,7 @@ var AssertObjectSchema = custom((v) => v !== null && (typeof v === "object" || t
11543
11543
  var ProgressTokenSchema = union([string2(), number2().int()]);
11544
11544
  var CursorSchema = string2();
11545
11545
  var TaskCreationParamsSchema = looseObject({
11546
- ttl: union([number2(), _null3()]).optional(),
11546
+ ttl: number2().optional(),
11547
11547
  pollInterval: number2().optional()
11548
11548
  });
11549
11549
  var TaskMetadataSchema = object({
@@ -11697,7 +11697,8 @@ var ClientCapabilitiesSchema = object({
11697
11697
  roots: object({
11698
11698
  listChanged: boolean2().optional()
11699
11699
  }).optional(),
11700
- tasks: ClientTasksCapabilitySchema.optional()
11700
+ tasks: ClientTasksCapabilitySchema.optional(),
11701
+ extensions: record(string2(), AssertObjectSchema).optional()
11701
11702
  });
11702
11703
  var InitializeRequestParamsSchema = BaseRequestParamsSchema.extend({
11703
11704
  protocolVersion: string2(),
@@ -11722,7 +11723,8 @@ var ServerCapabilitiesSchema = object({
11722
11723
  tools: object({
11723
11724
  listChanged: boolean2().optional()
11724
11725
  }).optional(),
11725
- tasks: ServerTasksCapabilitySchema.optional()
11726
+ tasks: ServerTasksCapabilitySchema.optional(),
11727
+ extensions: record(string2(), AssertObjectSchema).optional()
11726
11728
  });
11727
11729
  var InitializeResultSchema = ResultSchema.extend({
11728
11730
  protocolVersion: string2(),
@@ -11837,6 +11839,7 @@ var ResourceSchema = object({
11837
11839
  uri: string2(),
11838
11840
  description: optional(string2()),
11839
11841
  mimeType: optional(string2()),
11842
+ size: optional(number2()),
11840
11843
  annotations: AnnotationsSchema.optional(),
11841
11844
  _meta: optional(looseObject({}))
11842
11845
  });
@@ -12464,14 +12467,13 @@ class StdioServerTransport {
12464
12467
  }
12465
12468
  }
12466
12469
  // package.json
12467
- var version2 = "0.0.20";
12470
+ var version2 = "0.2.0";
12468
12471
 
12469
12472
  // src/config.ts
12470
12473
  import { homedir } from "node:os";
12471
12474
  import { join } from "node:path";
12472
12475
  import { parseArgs } from "node:util";
12473
12476
  var AGENT_MEMORY_DB_PATH_ENV = "AGENT_MEMORY_DB_PATH";
12474
- var AGENT_MEMORY_MODELS_CACHE_PATH_ENV = "AGENT_MEMORY_MODELS_CACHE_PATH";
12475
12477
  var DEFAULT_UI_PORT = 6580;
12476
12478
  function resolveConfig(environment = process.env, argv = process.argv.slice(2)) {
12477
12479
  const { values } = parseArgs({
@@ -12484,7 +12486,6 @@ function resolveConfig(environment = process.env, argv = process.argv.slice(2))
12484
12486
  });
12485
12487
  return {
12486
12488
  databasePath: resolveDatabasePath(environment),
12487
- modelsCachePath: resolveModelsCachePath(environment),
12488
12489
  uiMode: Boolean(values.ui),
12489
12490
  uiPort: Number(values.port) || DEFAULT_UI_PORT
12490
12491
  };
@@ -12492,137 +12493,7 @@ function resolveConfig(environment = process.env, argv = process.argv.slice(2))
12492
12493
  function resolveDatabasePath(environment = process.env) {
12493
12494
  return environment[AGENT_MEMORY_DB_PATH_ENV] || join(homedir(), ".config", "agent-memory", "memory.db");
12494
12495
  }
12495
- function resolveModelsCachePath(environment = process.env) {
12496
- return environment[AGENT_MEMORY_MODELS_CACHE_PATH_ENV] || join(homedir(), ".config", "agent-memory", "models");
12497
- }
12498
-
12499
- // src/embedding/service.ts
12500
- import { mkdirSync } from "node:fs";
12501
- import { pipeline, env as transformersEnv } from "@huggingface/transformers";
12502
-
12503
- // src/errors.ts
12504
- class MemoryError extends Error {
12505
- code;
12506
- constructor(code, message, options) {
12507
- super(message, options);
12508
- this.name = "MemoryError";
12509
- this.code = code;
12510
- }
12511
- }
12512
-
12513
- class ValidationError extends MemoryError {
12514
- constructor(message) {
12515
- super("VALIDATION_ERROR", message);
12516
- this.name = "ValidationError";
12517
- }
12518
- }
12519
-
12520
- class NotFoundError extends MemoryError {
12521
- constructor(message) {
12522
- super("NOT_FOUND", message);
12523
- this.name = "NotFoundError";
12524
- }
12525
- }
12526
-
12527
- class PersistenceError extends MemoryError {
12528
- constructor(message, options) {
12529
- super("PERSISTENCE_ERROR", message, options);
12530
- this.name = "PersistenceError";
12531
- }
12532
- }
12533
-
12534
- // src/embedding/service.ts
12535
- var DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
12536
- function configureModelsCache(modelsCachePath) {
12537
- mkdirSync(modelsCachePath, { recursive: true });
12538
- transformersEnv.useFSCache = true;
12539
- transformersEnv.cacheDir = modelsCachePath;
12540
- }
12541
12496
 
12542
- class EmbeddingService {
12543
- options;
12544
- extractorPromise;
12545
- constructor(options = {}) {
12546
- this.options = options;
12547
- }
12548
- async warmup() {
12549
- await this.getExtractor();
12550
- }
12551
- async createVector(text) {
12552
- const normalizedText = text.trim();
12553
- if (!normalizedText) {
12554
- throw new ValidationError("Text is required.");
12555
- }
12556
- const extractor = await this.getExtractor();
12557
- const embedding = await extractor(normalizedText);
12558
- return normalizeVector(embedding.tolist());
12559
- }
12560
- getExtractor() {
12561
- if (!this.extractorPromise) {
12562
- this.extractorPromise = (this.options.createExtractor ?? createDefaultExtractor)();
12563
- }
12564
- return this.extractorPromise;
12565
- }
12566
- }
12567
- async function createDefaultExtractor() {
12568
- const extractor = await pipeline("feature-extraction", DEFAULT_EMBEDDING_MODEL);
12569
- return (text) => extractor(text, {
12570
- pooling: "mean",
12571
- normalize: true
12572
- });
12573
- }
12574
- function normalizeVector(value) {
12575
- if (value.length === 0) {
12576
- throw new ValidationError("Embedding model returned an empty vector.");
12577
- }
12578
- const [firstItem] = value;
12579
- if (typeof firstItem === "number") {
12580
- return value.map((item) => {
12581
- if (typeof item !== "number" || !Number.isFinite(item)) {
12582
- throw new ValidationError("Embedding model returned a non-numeric vector.");
12583
- }
12584
- return item;
12585
- });
12586
- }
12587
- if (Array.isArray(firstItem)) {
12588
- return normalizeVector(firstItem);
12589
- }
12590
- throw new ValidationError("Embedding model returned an unexpected vector shape.");
12591
- }
12592
- // src/embedding/similarity.ts
12593
- function compareVectors(left, right) {
12594
- validateVector(left, "Left vector");
12595
- validateVector(right, "Right vector");
12596
- if (left.length !== right.length) {
12597
- throw new ValidationError("Vectors must have the same length.");
12598
- }
12599
- let dotProduct = 0;
12600
- let leftMagnitude = 0;
12601
- let rightMagnitude = 0;
12602
- for (const [index, leftValue] of left.entries()) {
12603
- const rightValue = right[index];
12604
- if (rightValue === undefined) {
12605
- throw new ValidationError("Vectors must have the same length.");
12606
- }
12607
- dotProduct += leftValue * rightValue;
12608
- leftMagnitude += leftValue * leftValue;
12609
- rightMagnitude += rightValue * rightValue;
12610
- }
12611
- if (leftMagnitude === 0 || rightMagnitude === 0) {
12612
- throw new ValidationError("Vectors must not have zero magnitude.");
12613
- }
12614
- return dotProduct / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude));
12615
- }
12616
- function validateVector(vector, label) {
12617
- if (vector.length === 0) {
12618
- throw new ValidationError(`${label} must not be empty.`);
12619
- }
12620
- for (const value of vector) {
12621
- if (!Number.isFinite(value)) {
12622
- throw new ValidationError(`${label} must contain only finite numbers.`);
12623
- }
12624
- }
12625
- }
12626
12497
  // node_modules/zod/v3/helpers/util.js
12627
12498
  var util;
12628
12499
  (function(util2) {
@@ -18106,6 +17977,10 @@ class Protocol {
18106
17977
  this._progressHandlers.clear();
18107
17978
  this._taskProgressTokens.clear();
18108
17979
  this._pendingDebouncedNotifications.clear();
17980
+ for (const info of this._timeoutInfo.values()) {
17981
+ clearTimeout(info.timeoutId);
17982
+ }
17983
+ this._timeoutInfo.clear();
18109
17984
  for (const controller of this._requestHandlerAbortControllers.values()) {
18110
17985
  controller.abort();
18111
17986
  }
@@ -18236,7 +18111,9 @@ class Protocol {
18236
18111
  await capturedTransport?.send(errorResponse);
18237
18112
  }
18238
18113
  }).catch((error2) => this._onerror(new Error(`Failed to send response: ${error2}`))).finally(() => {
18239
- this._requestHandlerAbortControllers.delete(request.id);
18114
+ if (this._requestHandlerAbortControllers.get(request.id) === abortController) {
18115
+ this._requestHandlerAbortControllers.delete(request.id);
18116
+ }
18240
18117
  });
18241
18118
  }
18242
18119
  _onprogress(notification) {
@@ -19894,6 +19771,9 @@ class McpServer {
19894
19771
  annotations = rest.shift();
19895
19772
  }
19896
19773
  } else if (typeof firstArg === "object" && firstArg !== null) {
19774
+ if (Object.values(firstArg).some((v) => typeof v === "object" && v !== null)) {
19775
+ throw new Error(`Tool ${name} expected a Zod schema or ToolAnnotations, but received an unrecognized object`);
19776
+ }
19897
19777
  annotations = rest.shift();
19898
19778
  }
19899
19779
  }
@@ -19986,6 +19866,9 @@ function getZodSchemaObject(schema) {
19986
19866
  if (isZodRawShapeCompat(schema)) {
19987
19867
  return objectFromShape(schema);
19988
19868
  }
19869
+ if (!isZodSchemaInstance(schema)) {
19870
+ throw new Error("inputSchema must be a Zod schema or raw shape, received an unrecognized object");
19871
+ }
19989
19872
  return schema;
19990
19873
  }
19991
19874
  function promptArgumentsFromSchema(schema) {
@@ -20030,6 +19913,37 @@ var EMPTY_COMPLETION_RESULT = {
20030
19913
  }
20031
19914
  };
20032
19915
 
19916
+ // src/errors.ts
19917
+ class MemoryError extends Error {
19918
+ code;
19919
+ constructor(code, message, options) {
19920
+ super(message, options);
19921
+ this.name = "MemoryError";
19922
+ this.code = code;
19923
+ }
19924
+ }
19925
+
19926
+ class ValidationError extends MemoryError {
19927
+ constructor(message) {
19928
+ super("VALIDATION_ERROR", message);
19929
+ this.name = "ValidationError";
19930
+ }
19931
+ }
19932
+
19933
+ class NotFoundError extends MemoryError {
19934
+ constructor(message) {
19935
+ super("NOT_FOUND", message);
19936
+ this.name = "NotFoundError";
19937
+ }
19938
+ }
19939
+
19940
+ class PersistenceError extends MemoryError {
19941
+ constructor(message, options) {
19942
+ super("PERSISTENCE_ERROR", message, options);
19943
+ this.name = "PersistenceError";
19944
+ }
19945
+ }
19946
+
20033
19947
  // src/mcp/tools/shared.ts
20034
19948
  function toMcpError(error2) {
20035
19949
  if (error2 instanceof McpError) {
@@ -20045,20 +19959,31 @@ function toMcpError(error2) {
20045
19959
  return new McpError(ErrorCode.InternalError, message);
20046
19960
  }
20047
19961
  var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20048
- function parseOptionalDate(value, fieldName) {
20049
- if (!value) {
20050
- return;
19962
+ function toMemoryXml(record3, options) {
19963
+ const scopeAttribute = getMemoryScopeAttribute(record3, options?.skipWorkspaceIfEquals);
19964
+ const attributes = [
19965
+ `id="${escapeXml(record3.id)}"`,
19966
+ `updated_at="${record3.updatedAt.toISOString()}"`,
19967
+ scopeAttribute,
19968
+ options?.deleted ? 'deleted="true"' : undefined
19969
+ ].filter((value) => value).join(" ");
19970
+ return `<memory ${attributes}>
19971
+ ${escapeXml(record3.content)}
19972
+ </memory>`;
19973
+ }
19974
+ function getMemoryScopeAttribute(record3, skipWorkspaceIfEquals) {
19975
+ if (record3.workspace === undefined) {
19976
+ return 'global="true"';
20051
19977
  }
20052
- const date4 = new Date(value);
20053
- if (Number.isNaN(date4.getTime())) {
20054
- throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
19978
+ if (record3.workspace === skipWorkspaceIfEquals) {
19979
+ return;
20055
19980
  }
20056
- return date4;
19981
+ return `workspace="${escapeXml(record3.workspace)}"`;
20057
19982
  }
20058
19983
 
20059
19984
  // src/mcp/tools/forget.ts
20060
19985
  var forgetInputSchema = {
20061
- id: string2().describe("Memory id to delete. Use an id returned by `recall`.")
19986
+ id: string2().describe("Memory id to delete. Use an id returned by `review`.")
20062
19987
  };
20063
19988
  function registerForgetTool(server, memory) {
20064
19989
  server.registerTool("forget", {
@@ -20068,252 +19993,13 @@ function registerForgetTool(server, memory) {
20068
19993
  idempotentHint: true,
20069
19994
  openWorldHint: false
20070
19995
  },
20071
- description: 'Delete a memory that is wrong or obsolete. Use after `recall` when you have the memory id. Use `revise` instead if the fact should remain with corrected wording. Returns `<memory id="..." deleted="true" />`.',
19996
+ description: 'Delete a memory that is wrong or obsolete. Use after `review` when you have the memory id. Use `revise` instead if the fact should remain with corrected wording. Returns the deleted memory as `<memory ... deleted="true">...</memory>`.',
20072
19997
  inputSchema: forgetInputSchema
20073
19998
  }, async ({ id }) => {
20074
19999
  try {
20075
- await memory.delete({ id });
20000
+ const deletedMemory = await memory.delete({ id });
20076
20001
  return {
20077
- content: [{ type: "text", text: `<memory id="${id.trim()}" deleted="true" />` }]
20078
- };
20079
- } catch (error2) {
20080
- throw toMcpError(error2);
20081
- }
20082
- });
20083
- }
20084
-
20085
- // src/memory.ts
20086
- var toNormalizedScore = (value) => value;
20087
-
20088
- // src/ranking.ts
20089
- var RETRIEVAL_SCORE_WEIGHT = 9;
20090
- var EMBEDDING_SIMILARITY_WEIGHT = 4;
20091
- var WORKSPACE_MATCH_WEIGHT = 5;
20092
- var RECENCY_WEIGHT = 1;
20093
- var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + EMBEDDING_SIMILARITY_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
20094
- var GLOBAL_WORKSPACE_SCORE = 0.5;
20095
- function rerankSearchResults(results, workspace, queryEmbedding) {
20096
- if (results.length === 0) {
20097
- return results;
20098
- }
20099
- const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
20100
- const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
20101
- const minUpdatedAt = Math.min(...updatedAtTimes);
20102
- const maxUpdatedAt = Math.max(...updatedAtTimes);
20103
- return results.map((result) => {
20104
- const embeddingSimilarityScore = computeEmbeddingSimilarityScore(result, queryEmbedding);
20105
- const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
20106
- const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
20107
- const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + embeddingSimilarityScore * EMBEDDING_SIMILARITY_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
20108
- return {
20109
- ...result,
20110
- score: toNormalizedScore(combinedScore)
20111
- };
20112
- }).sort((a, b) => b.score - a.score);
20113
- }
20114
- function computeEmbeddingSimilarityScore(result, queryEmbedding) {
20115
- return normalizeCosineSimilarity(compareVectors(result.embedding, queryEmbedding));
20116
- }
20117
- function normalizeWorkspacePath(value) {
20118
- return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20119
- }
20120
- function normalizeCosineSimilarity(value) {
20121
- return (value + 1) / 2;
20122
- }
20123
- function computeWorkspaceScore(memoryWs, queryWs) {
20124
- if (!queryWs) {
20125
- return 0;
20126
- }
20127
- if (!memoryWs) {
20128
- return GLOBAL_WORKSPACE_SCORE;
20129
- }
20130
- const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20131
- if (normalizedMemoryWs === queryWs) {
20132
- return 1;
20133
- }
20134
- return 0;
20135
- }
20136
-
20137
- // src/memory-service.ts
20138
- var DEFAULT_RECALL_LIMIT = 15;
20139
- var MAX_RECALL_LIMIT = 50;
20140
- var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
20141
- var DEFAULT_LIST_LIMIT = 15;
20142
- var MAX_LIST_LIMIT = 100;
20143
-
20144
- class MemoryService {
20145
- repository;
20146
- embeddingService;
20147
- workspaceResolver;
20148
- constructor(repository, embeddingService, workspaceResolver) {
20149
- this.repository = repository;
20150
- this.embeddingService = embeddingService;
20151
- this.workspaceResolver = workspaceResolver;
20152
- }
20153
- async create(input) {
20154
- const content = input.content.trim();
20155
- if (!content) {
20156
- throw new ValidationError("Memory content is required.");
20157
- }
20158
- const workspace = await this.workspaceResolver.resolve(input.workspace);
20159
- const memory = await this.repository.create({
20160
- content,
20161
- embedding: await this.embeddingService.createVector(content),
20162
- workspace
20163
- });
20164
- return toPublicMemoryRecord(memory);
20165
- }
20166
- async update(input) {
20167
- const content = input.content.trim();
20168
- if (!content)
20169
- throw new ValidationError("Memory content is required.");
20170
- const memory = await this.repository.update({
20171
- id: input.id,
20172
- content,
20173
- embedding: await this.embeddingService.createVector(content)
20174
- });
20175
- return toPublicMemoryRecord(memory);
20176
- }
20177
- async delete(input) {
20178
- const id = input.id.trim();
20179
- if (!id)
20180
- throw new ValidationError("Memory id is required.");
20181
- return this.repository.delete({ id });
20182
- }
20183
- async get(id) {
20184
- const memory = await this.repository.get(id);
20185
- return memory ? toPublicMemoryRecord(memory) : undefined;
20186
- }
20187
- async list(input) {
20188
- const queryWorkspace = normalizeOptionalString(input.workspace);
20189
- const workspace = await this.workspaceResolver.resolve(input.workspace);
20190
- const page = await this.repository.list({
20191
- workspace,
20192
- workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
20193
- offset: normalizeOffset(input.offset),
20194
- limit: normalizeListLimit(input.limit)
20195
- });
20196
- return {
20197
- items: page.items.map((item) => toPublicMemoryRecord(remapWorkspace(item, workspace, queryWorkspace))),
20198
- hasMore: page.hasMore
20199
- };
20200
- }
20201
- async listWorkspaces() {
20202
- return this.repository.listWorkspaces();
20203
- }
20204
- async search(input) {
20205
- const terms = normalizeTerms(input.terms);
20206
- if (terms.length === 0) {
20207
- throw new ValidationError("At least one search term is required.");
20208
- }
20209
- const requestedLimit = normalizeLimit(input.limit);
20210
- const queryWorkspace = normalizeOptionalString(input.workspace);
20211
- const workspace = await this.workspaceResolver.resolve(input.workspace);
20212
- const normalizedQuery = {
20213
- terms,
20214
- limit: requestedLimit * RECALL_CANDIDATE_LIMIT_MULTIPLIER,
20215
- updatedAfter: input.updatedAfter,
20216
- updatedBefore: input.updatedBefore
20217
- };
20218
- const [results, queryEmbedding] = await Promise.all([
20219
- this.repository.search(normalizedQuery),
20220
- this.embeddingService.createVector(terms.join(" "))
20221
- ]);
20222
- return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map((result) => toPublicSearchResult(remapWorkspace(result, workspace, queryWorkspace)));
20223
- }
20224
- }
20225
- function toPublicMemoryRecord(memory) {
20226
- return {
20227
- id: memory.id,
20228
- content: memory.content,
20229
- workspace: memory.workspace,
20230
- createdAt: memory.createdAt,
20231
- updatedAt: memory.updatedAt
20232
- };
20233
- }
20234
- function toPublicSearchResult(result) {
20235
- return {
20236
- id: result.id,
20237
- content: result.content,
20238
- score: result.score,
20239
- workspace: result.workspace,
20240
- createdAt: result.createdAt,
20241
- updatedAt: result.updatedAt
20242
- };
20243
- }
20244
- function normalizeLimit(value) {
20245
- if (value === undefined) {
20246
- return DEFAULT_RECALL_LIMIT;
20247
- }
20248
- if (!Number.isInteger(value) || value < 1 || value > MAX_RECALL_LIMIT) {
20249
- throw new ValidationError(`Limit must be an integer between 1 and ${MAX_RECALL_LIMIT}.`);
20250
- }
20251
- return value;
20252
- }
20253
- function normalizeOffset(value) {
20254
- return Number.isInteger(value) && value && value > 0 ? value : 0;
20255
- }
20256
- function normalizeListLimit(value) {
20257
- if (!Number.isInteger(value) || value === undefined) {
20258
- return DEFAULT_LIST_LIMIT;
20259
- }
20260
- return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20261
- }
20262
- function normalizeTerms(terms) {
20263
- const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
20264
- return [...new Set(normalizedTerms)];
20265
- }
20266
- function normalizeOptionalString(value) {
20267
- const trimmed = value?.trim();
20268
- return trimmed ? trimmed : undefined;
20269
- }
20270
- function remapWorkspace(record3, canonicalWorkspace, queryWorkspace) {
20271
- if (record3.workspace !== canonicalWorkspace) {
20272
- return record3;
20273
- }
20274
- return { ...record3, workspace: queryWorkspace };
20275
- }
20276
-
20277
- // src/mcp/tools/recall.ts
20278
- var recallInputSchema = {
20279
- terms: array(string2()).min(1).describe("2-5 short anchor-heavy terms or exact phrases. Prefer identifiers, commands, file paths, and exact wording likely to appear in the memory."),
20280
- limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum matches to return. Keep this small when you only need the strongest hits."),
20281
- workspace: string2().optional().describe("Current working directory for project-scoped recall. Omit for cross-project recall."),
20282
- updated_after: string2().optional().describe("Only return memories updated on or after this ISO 8601 timestamp."),
20283
- updated_before: string2().optional().describe("Only return memories updated on or before this ISO 8601 timestamp.")
20284
- };
20285
- function toMemoryXml(r) {
20286
- const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
20287
- const content = escapeXml(r.content);
20288
- const score = Number(r.score.toFixed(3)).toString();
20289
- return `<memory id="${r.id}" score="${score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
20290
- ${content}
20291
- </memory>`;
20292
- }
20293
- function registerRecallTool(server, memory) {
20294
- server.registerTool("recall", {
20295
- annotations: {
20296
- title: "Recall",
20297
- readOnlyHint: true,
20298
- openWorldHint: false
20299
- },
20300
- description: "Retrieve memories relevant to the current task or check whether a fact already exists before saving. Use at conversation start and before design choices. Pass short anchor-heavy `terms` and `workspace` when available. Results reflect the queried workspace context when applicable. Returns `<memories>...</memories>` or a no-match hint.",
20301
- inputSchema: recallInputSchema
20302
- }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20303
- try {
20304
- const results = await memory.search({
20305
- terms,
20306
- limit,
20307
- workspace,
20308
- updatedAfter: parseOptionalDate(updated_after, "updated_after"),
20309
- updatedBefore: parseOptionalDate(updated_before, "updated_before")
20310
- });
20311
- const text = results.length === 0 ? "No matching memories found. Retry once with 1-3 overlapping alternate terms or an exact identifier, command, file path, or phrase likely to appear in the memory." : `<memories>
20312
- ${results.map(toMemoryXml).join(`
20313
- `)}
20314
- </memories>`;
20315
- return {
20316
- content: [{ type: "text", text }]
20002
+ content: [{ type: "text", text: toMemoryXml(deletedMemory, { deleted: true }) }]
20317
20003
  };
20318
20004
  } catch (error2) {
20319
20005
  throw toMcpError(error2);
@@ -20334,7 +20020,7 @@ function registerRememberTool(server, memory) {
20334
20020
  idempotentHint: false,
20335
20021
  openWorldHint: false
20336
20022
  },
20337
- description: 'Save one new durable fact for later recall. Use for stable preferences, reusable decisions, and project context not obvious from code or git history. If the fact already exists, use `revise` instead. Returns `<memory id="..." />`.',
20023
+ description: "Save one new durable fact. Use for stable preferences, reusable decisions, and project context not obvious from code or git history. If the fact already exists, use `revise` instead. Returns the saved memory as `<memory ...>...</memory>`.",
20338
20024
  inputSchema: rememberInputSchema
20339
20025
  }, async ({ content, workspace }) => {
20340
20026
  try {
@@ -20343,7 +20029,7 @@ function registerRememberTool(server, memory) {
20343
20029
  workspace
20344
20030
  });
20345
20031
  return {
20346
- content: [{ type: "text", text: `<memory id="${savedMemory.id}" />` }]
20032
+ content: [{ type: "text", text: toMemoryXml(savedMemory) }]
20347
20033
  };
20348
20034
  } catch (error2) {
20349
20035
  throw toMcpError(error2);
@@ -20352,17 +20038,11 @@ function registerRememberTool(server, memory) {
20352
20038
  }
20353
20039
 
20354
20040
  // src/mcp/tools/review.ts
20355
- var REVIEW_PAGE_SIZE = 25;
20041
+ var REVIEW_PAGE_SIZE = 50;
20356
20042
  var reviewInputSchema = {
20357
20043
  workspace: string2().describe("Current working directory for project-scoped listing."),
20358
20044
  page: number2().int().min(0).optional().describe("Zero-based page number. Defaults to 0.")
20359
20045
  };
20360
- function toMemoryXml2(record3) {
20361
- const content = escapeXml(record3.content);
20362
- return `<memory id="${record3.id}" updated_at="${record3.updatedAt.toISOString()}">
20363
- ${content}
20364
- </memory>`;
20365
- }
20366
20046
  function registerReviewTool(server, memory) {
20367
20047
  server.registerTool("review", {
20368
20048
  annotations: {
@@ -20370,13 +20050,14 @@ function registerReviewTool(server, memory) {
20370
20050
  readOnlyHint: true,
20371
20051
  openWorldHint: false
20372
20052
  },
20373
- description: 'Browse all memories for a workspace in creation order. Use before bulk review, cleanup, or when you need to scan memories without specific search terms. For targeted retrieval by topic, use `recall` instead. Returns `<memories workspace="..." has_more="true|false">...</memories>` with pagination support.',
20053
+ description: 'Load workspace and global memories sorted by most recently updated. Use at the start of a task and before saving or revising memory. Returns `<memories workspace="..." has_more="true|false">...</memories>` with pagination support. Global memories are marked with `global="true"`.',
20374
20054
  inputSchema: reviewInputSchema
20375
20055
  }, async ({ workspace, page }) => {
20376
20056
  try {
20377
20057
  const pageIndex = page ?? 0;
20378
20058
  const result = await memory.list({
20379
20059
  workspace,
20060
+ global: true,
20380
20061
  offset: pageIndex * REVIEW_PAGE_SIZE,
20381
20062
  limit: REVIEW_PAGE_SIZE
20382
20063
  });
@@ -20385,9 +20066,11 @@ function registerReviewTool(server, memory) {
20385
20066
  content: [{ type: "text", text: "No memories found for this workspace." }]
20386
20067
  };
20387
20068
  }
20388
- const text = `<memories workspace="${escapeXml(workspace)}" has_more="${result.hasMore}">
20389
- ${result.items.map(toMemoryXml2).join(`
20390
- `)}
20069
+ const escapedWorkspace = escapeXml(workspace);
20070
+ const memories = result.items.map((item) => toMemoryXml(item, { skipWorkspaceIfEquals: workspace })).join(`
20071
+ `);
20072
+ const text = `<memories workspace="${escapedWorkspace}" has_more="${result.hasMore}">
20073
+ ${memories}
20391
20074
  </memories>`;
20392
20075
  return {
20393
20076
  content: [{ type: "text", text }]
@@ -20400,8 +20083,9 @@ ${result.items.map(toMemoryXml2).join(`
20400
20083
 
20401
20084
  // src/mcp/tools/revise.ts
20402
20085
  var reviseInputSchema = {
20403
- id: string2().describe("Memory id to update. Use an id returned by `recall`."),
20404
- content: string2().describe("Corrected replacement text for that memory.")
20086
+ id: string2().describe("Memory id to update. Use an id returned by `review`."),
20087
+ content: string2().optional().describe("Corrected replacement text for that memory. Omit to keep the current content."),
20088
+ global: boolean2().optional().describe("Set to true to move a project-scoped memory to global scope.")
20405
20089
  };
20406
20090
  function registerReviseTool(server, memory) {
20407
20091
  server.registerTool("revise", {
@@ -20411,16 +20095,23 @@ function registerReviseTool(server, memory) {
20411
20095
  idempotentHint: false,
20412
20096
  openWorldHint: false
20413
20097
  },
20414
- description: 'Update one existing memory when the same fact still applies but its wording or details changed. Use after `recall` when you already have the memory id. Returns `<memory id="..." updated_at="..." />`.',
20098
+ description: "Update one existing memory when the same fact still applies but its wording changed, or when a project-scoped memory should become global. Use after `review` when you already have the memory id. Omit fields you do not want to change. Returns the revised memory as `<memory ...>...</memory>`.",
20415
20099
  inputSchema: reviseInputSchema
20416
- }, async ({ id, content }) => {
20100
+ }, async ({ id, content, global: global2 }) => {
20417
20101
  try {
20418
- const revisedMemory = await memory.update({ id, content });
20102
+ if (content === undefined && global2 !== true) {
20103
+ throw new ValidationError("Provide at least one field to revise.");
20104
+ }
20105
+ const revisedMemory = await memory.update({
20106
+ id,
20107
+ content,
20108
+ workspace: global2 === true ? null : undefined
20109
+ });
20419
20110
  return {
20420
20111
  content: [
20421
20112
  {
20422
20113
  type: "text",
20423
- text: `<memory id="${revisedMemory.id}" updated_at="${revisedMemory.updatedAt.toISOString()}" />`
20114
+ text: toMemoryXml(revisedMemory)
20424
20115
  }
20425
20116
  ]
20426
20117
  };
@@ -20432,13 +20123,12 @@ function registerReviseTool(server, memory) {
20432
20123
 
20433
20124
  // src/mcp/server.ts
20434
20125
  var SERVER_INSTRUCTIONS = [
20435
- "Use this server only for durable memory that should survive across turns: stable preferences, corrections, reusable decisions, and project context not obvious from code or git history.",
20436
- "Use `recall` at conversation start, before design choices, and before saving or revising memory.",
20437
- "Use `review` to browse all memories for a workspace before bulk review or cleanup.",
20438
- "Use `remember` for one new durable fact. Use `revise` when the fact already exists but needs correction.",
20439
- "Use `forget` only when a memory is wrong or obsolete.",
20440
- "Pass workspace for project-scoped memory. Omit it only for facts that apply across projects.",
20441
- "Do not store secrets, temporary task state, or facts obvious from current code or git history."
20126
+ "Durable memory for stable preferences, corrections, reusable decisions, and project context not obvious from code or git history.",
20127
+ "Workflow: (1) Call `review` with the current workspace at conversation start -- this loads workspace and global memories into context.",
20128
+ "(2) During the session, call `remember` to save a new fact, `revise` to correct content or promote a project-scoped memory to global scope, and call `forget` to remove one that is wrong or obsolete.",
20129
+ "Always check loaded memories before calling `remember` to avoid duplicates -- use `revise` instead when the fact already exists.",
20130
+ "Pass workspace on `remember` for project-scoped memory. Omit it only for facts that apply across all projects.",
20131
+ "Never store secrets, temporary task state, or facts obvious from current code or git history."
20442
20132
  ].join(" ");
20443
20133
  function createMcpServer(memory, version3) {
20444
20134
  const server = new McpServer({
@@ -20448,27 +20138,122 @@ function createMcpServer(memory, version3) {
20448
20138
  instructions: SERVER_INSTRUCTIONS
20449
20139
  });
20450
20140
  registerRememberTool(server, memory);
20451
- registerRecallTool(server, memory);
20452
20141
  registerReviseTool(server, memory);
20453
20142
  registerForgetTool(server, memory);
20454
20143
  registerReviewTool(server, memory);
20455
20144
  return server;
20456
20145
  }
20457
20146
 
20147
+ // src/memory-service.ts
20148
+ var DEFAULT_LIST_LIMIT = 15;
20149
+ var MAX_LIST_LIMIT = 100;
20150
+
20151
+ class MemoryService {
20152
+ repository;
20153
+ workspaceResolver;
20154
+ constructor(repository, workspaceResolver) {
20155
+ this.repository = repository;
20156
+ this.workspaceResolver = workspaceResolver;
20157
+ }
20158
+ async create(input) {
20159
+ const content = input.content.trim();
20160
+ if (!content) {
20161
+ throw new ValidationError("Memory content is required.");
20162
+ }
20163
+ const workspace = await this.normalizeWorkspaceInput(input.workspace);
20164
+ return this.repository.create({ content, workspace });
20165
+ }
20166
+ async update(input) {
20167
+ const content = this.normalizeUpdateContent(input.content);
20168
+ const workspace = await this.normalizeUpdateWorkspace(input.workspace);
20169
+ return this.repository.update({ id: input.id, content, workspace });
20170
+ }
20171
+ async delete(input) {
20172
+ const id = input.id.trim();
20173
+ if (!id)
20174
+ throw new ValidationError("Memory id is required.");
20175
+ const existingMemory = await this.repository.get(id);
20176
+ if (!existingMemory) {
20177
+ throw new NotFoundError(`Memory not found: ${id}`);
20178
+ }
20179
+ await this.repository.delete({ id });
20180
+ return existingMemory;
20181
+ }
20182
+ async get(id) {
20183
+ return this.repository.get(id);
20184
+ }
20185
+ async list(input) {
20186
+ const queryWorkspace = input.workspace?.trim() || undefined;
20187
+ const workspace = queryWorkspace ? await this.workspaceResolver.resolve(queryWorkspace) : undefined;
20188
+ const page = await this.repository.list({
20189
+ workspace,
20190
+ global: input.global,
20191
+ offset: normalizeOffset(input.offset),
20192
+ limit: normalizeListLimit(input.limit)
20193
+ });
20194
+ return {
20195
+ items: page.items.map((item) => remapWorkspace(item, workspace, queryWorkspace)),
20196
+ hasMore: page.hasMore
20197
+ };
20198
+ }
20199
+ async listWorkspaces() {
20200
+ return this.repository.listWorkspaces();
20201
+ }
20202
+ normalizeUpdateContent(value) {
20203
+ if (value === undefined) {
20204
+ return;
20205
+ }
20206
+ const trimmed = value.trim();
20207
+ if (!trimmed) {
20208
+ throw new ValidationError("Memory content is required.");
20209
+ }
20210
+ return trimmed;
20211
+ }
20212
+ async normalizeUpdateWorkspace(value) {
20213
+ if (value === undefined || value === null) {
20214
+ return value;
20215
+ }
20216
+ return this.normalizeWorkspaceInput(value);
20217
+ }
20218
+ async normalizeWorkspaceInput(value) {
20219
+ if (value === undefined) {
20220
+ return;
20221
+ }
20222
+ const trimmed = value.trim();
20223
+ if (!trimmed) {
20224
+ throw new ValidationError("Workspace is required.");
20225
+ }
20226
+ return this.workspaceResolver.resolve(trimmed);
20227
+ }
20228
+ }
20229
+ function normalizeOffset(value) {
20230
+ return Number.isInteger(value) && value && value > 0 ? value : 0;
20231
+ }
20232
+ function normalizeListLimit(value) {
20233
+ if (!Number.isInteger(value) || value === undefined) {
20234
+ return DEFAULT_LIST_LIMIT;
20235
+ }
20236
+ return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20237
+ }
20238
+ function remapWorkspace(record3, canonicalWorkspace, queryWorkspace) {
20239
+ if (record3.workspace !== canonicalWorkspace) {
20240
+ return record3;
20241
+ }
20242
+ return { ...record3, workspace: queryWorkspace };
20243
+ }
20244
+
20458
20245
  // src/sqlite/db.ts
20459
- import { mkdirSync as mkdirSync2 } from "node:fs";
20246
+ import { mkdirSync } from "node:fs";
20460
20247
  import { dirname } from "node:path";
20461
20248
  import Database from "better-sqlite3";
20462
20249
 
20463
20250
  // src/sqlite/memory-schema.ts
20464
- function createMemoriesTable(database, options) {
20465
- const embeddingColumn = getEmbeddingColumnSql(options.embeddingColumn);
20251
+ function createMemoriesTable(database) {
20466
20252
  database.exec(`
20467
20253
  CREATE TABLE IF NOT EXISTS memories (
20468
20254
  id TEXT PRIMARY KEY,
20469
20255
  content TEXT NOT NULL,
20470
20256
  workspace TEXT,
20471
- ${embeddingColumn}
20472
20257
  created_at INTEGER NOT NULL,
20473
20258
  updated_at INTEGER NOT NULL
20474
20259
  );
@@ -20476,7 +20261,7 @@ function createMemoriesTable(database, options) {
20476
20261
  }
20477
20262
  function createMemoryIndexes(database) {
20478
20263
  database.exec(`
20479
- CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
20264
+ CREATE INDEX IF NOT EXISTS idx_memories_updated_at ON memories(updated_at);
20480
20265
  CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
20481
20266
  `);
20482
20267
  }
@@ -20516,82 +20301,38 @@ function dropMemorySearchArtifacts(database) {
20516
20301
  DROP TABLE IF EXISTS memories_fts;
20517
20302
  `);
20518
20303
  }
20519
- function getEmbeddingColumnSql(mode) {
20520
- switch (mode) {
20521
- case "omit":
20522
- return "";
20523
- case "nullable":
20524
- return `embedding BLOB,
20525
- `;
20526
- case "required":
20527
- return `embedding BLOB NOT NULL,
20528
- `;
20529
- }
20530
- }
20531
20304
 
20532
20305
  // src/sqlite/migrations/001-create-memory-schema.ts
20533
20306
  var createMemorySchemaMigration = {
20534
20307
  version: 1,
20535
20308
  async up(database) {
20536
- createMemoriesTable(database, { embeddingColumn: "omit" });
20309
+ createMemoriesTable(database);
20537
20310
  createMemoryIndexes(database);
20538
20311
  createMemorySearchArtifacts(database);
20539
20312
  }
20540
20313
  };
20541
20314
 
20542
- // src/sqlite/embedding-codec.ts
20543
- var FLOAT32_BYTE_WIDTH = 4;
20544
- function encodeEmbedding(vector) {
20545
- const typedArray = Float32Array.from(vector);
20546
- return new Uint8Array(typedArray.buffer.slice(0));
20547
- }
20548
- function decodeEmbedding(value) {
20549
- const bytes = toUint8Array(value);
20550
- if (bytes.byteLength === 0) {
20551
- throw new Error("Embedding blob is empty.");
20552
- }
20553
- if (bytes.byteLength % FLOAT32_BYTE_WIDTH !== 0) {
20554
- throw new Error("Embedding blob length is not a multiple of 4.");
20555
- }
20556
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
20557
- const vector = [];
20558
- for (let offset = 0;offset < bytes.byteLength; offset += FLOAT32_BYTE_WIDTH) {
20559
- vector.push(view.getFloat32(offset, true));
20560
- }
20561
- return vector;
20562
- }
20563
- function toUint8Array(value) {
20564
- if (value instanceof Uint8Array) {
20565
- return value;
20566
- }
20567
- if (value instanceof ArrayBuffer) {
20568
- return new Uint8Array(value);
20569
- }
20570
- throw new Error("Expected embedding blob as Uint8Array or ArrayBuffer.");
20571
- }
20572
-
20573
20315
  // src/sqlite/migrations/002-add-memory-embedding.ts
20574
- function createAddMemoryEmbeddingMigration(embeddingService) {
20316
+ function createAddMemoryEmbeddingMigration() {
20575
20317
  return {
20576
20318
  version: 2,
20577
20319
  async up(database) {
20578
20320
  database.exec("ALTER TABLE memories ADD COLUMN embedding BLOB");
20579
- const rows = database.prepare("SELECT id, content FROM memories ORDER BY created_at ASC").all();
20580
- const updateStatement = database.prepare("UPDATE memories SET embedding = ? WHERE id = ?");
20581
- for (const row of rows) {
20582
- const embedding = await embeddingService.createVector(row.content);
20583
- updateStatement.run(encodeEmbedding(embedding), row.id);
20584
- }
20585
- const nullRows = database.prepare("SELECT COUNT(*) AS count FROM memories WHERE embedding IS NULL").all();
20586
- if ((nullRows[0]?.count ?? 0) > 0) {
20587
- throw new Error("Failed to backfill embeddings for all memories.");
20588
- }
20589
20321
  dropMemorySearchArtifacts(database);
20590
20322
  database.exec("ALTER TABLE memories RENAME TO memories_old");
20591
- createMemoriesTable(database, { embeddingColumn: "required" });
20323
+ database.exec(`
20324
+ CREATE TABLE IF NOT EXISTS memories (
20325
+ id TEXT PRIMARY KEY,
20326
+ content TEXT NOT NULL,
20327
+ workspace TEXT,
20328
+ embedding BLOB NOT NULL,
20329
+ created_at INTEGER NOT NULL,
20330
+ updated_at INTEGER NOT NULL
20331
+ );
20332
+ `);
20592
20333
  database.exec(`
20593
20334
  INSERT INTO memories (id, content, workspace, embedding, created_at, updated_at)
20594
- SELECT id, content, workspace, embedding, created_at, updated_at
20335
+ SELECT id, content, workspace, COALESCE(embedding, X'00000000'), created_at, updated_at
20595
20336
  FROM memories_old
20596
20337
  `);
20597
20338
  database.exec("DROP TABLE memories_old");
@@ -20619,12 +20360,31 @@ function createNormalizeWorkspaceMigration(workspaceResolver) {
20619
20360
  };
20620
20361
  }
20621
20362
 
20363
+ // src/sqlite/migrations/004-remove-memory-embedding.ts
20364
+ var removeMemoryEmbeddingMigration = {
20365
+ version: 4,
20366
+ async up(database) {
20367
+ dropMemorySearchArtifacts(database);
20368
+ database.exec("ALTER TABLE memories RENAME TO memories_old");
20369
+ createMemoriesTable(database);
20370
+ database.exec(`
20371
+ INSERT INTO memories (id, content, workspace, created_at, updated_at)
20372
+ SELECT id, content, workspace, created_at, updated_at
20373
+ FROM memories_old
20374
+ `);
20375
+ database.exec("DROP TABLE memories_old");
20376
+ createMemoryIndexes(database);
20377
+ createMemorySearchArtifacts(database, true);
20378
+ }
20379
+ };
20380
+
20622
20381
  // src/sqlite/migrations/index.ts
20623
20382
  function createMemoryMigrations(options) {
20624
20383
  return [
20625
20384
  createMemorySchemaMigration,
20626
- createAddMemoryEmbeddingMigration(options.embeddingService),
20627
- createNormalizeWorkspaceMigration(options.workspaceResolver)
20385
+ createAddMemoryEmbeddingMigration(),
20386
+ createNormalizeWorkspaceMigration(options.workspaceResolver),
20387
+ removeMemoryEmbeddingMigration
20628
20388
  ];
20629
20389
  }
20630
20390
 
@@ -20638,7 +20398,7 @@ var PRAGMA_STATEMENTS = [
20638
20398
  async function openMemoryDatabase(databasePath, options) {
20639
20399
  let database;
20640
20400
  try {
20641
- mkdirSync2(dirname(databasePath), { recursive: true });
20401
+ mkdirSync(dirname(databasePath), { recursive: true });
20642
20402
  database = new Database(databasePath);
20643
20403
  await initializeMemoryDatabase(database, createMemoryMigrations(options));
20644
20404
  return database;
@@ -20712,7 +20472,6 @@ function validateMigrations(migrations) {
20712
20472
  }
20713
20473
  // src/sqlite/repository.ts
20714
20474
  import { randomUUID } from "node:crypto";
20715
- var DEFAULT_SEARCH_LIMIT = 15;
20716
20475
  var DEFAULT_LIST_LIMIT2 = 15;
20717
20476
 
20718
20477
  class SqliteMemoryRepository {
@@ -20725,24 +20484,11 @@ class SqliteMemoryRepository {
20725
20484
  constructor(database) {
20726
20485
  this.database = database;
20727
20486
  this.insertStatement = database.prepare(`
20728
- INSERT INTO memories (
20729
- id,
20730
- content,
20731
- workspace,
20732
- embedding,
20733
- created_at,
20734
- updated_at
20735
- ) VALUES (
20736
- ?,
20737
- ?,
20738
- ?,
20739
- ?,
20740
- ?,
20741
- ?
20742
- )
20487
+ INSERT INTO memories (id, content, workspace, created_at, updated_at)
20488
+ VALUES (?, ?, ?, ?, ?)
20743
20489
  `);
20744
- this.getStatement = database.prepare("SELECT id, content, workspace, embedding, created_at, updated_at FROM memories WHERE id = ?");
20745
- this.updateStatement = database.prepare("UPDATE memories SET content = ?, embedding = ?, updated_at = ? WHERE id = ?");
20490
+ this.getStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20491
+ this.updateStatement = database.prepare("UPDATE memories SET content = ?, workspace = ?, updated_at = ? WHERE id = ?");
20746
20492
  this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
20747
20493
  this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
20748
20494
  }
@@ -20752,63 +20498,21 @@ class SqliteMemoryRepository {
20752
20498
  const memory = {
20753
20499
  id: randomUUID(),
20754
20500
  content: input.content,
20755
- embedding: input.embedding,
20756
20501
  workspace: input.workspace,
20757
20502
  createdAt: now,
20758
20503
  updatedAt: now
20759
20504
  };
20760
- this.insertStatement.run(memory.id, memory.content, memory.workspace, encodeEmbedding(memory.embedding), memory.createdAt.getTime(), memory.updatedAt.getTime());
20505
+ this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
20761
20506
  return memory;
20762
20507
  } catch (error2) {
20763
20508
  throw new PersistenceError("Failed to save memory.", { cause: error2 });
20764
20509
  }
20765
20510
  }
20766
- async search(query) {
20767
- try {
20768
- const whereParams = [toFtsQuery(query.terms)];
20769
- const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
20770
- const whereClauses = ["memories_fts MATCH ?"];
20771
- if (query.updatedAfter) {
20772
- whereClauses.push("m.updated_at >= ?");
20773
- whereParams.push(query.updatedAfter.getTime());
20774
- }
20775
- if (query.updatedBefore) {
20776
- whereClauses.push("m.updated_at <= ?");
20777
- whereParams.push(query.updatedBefore.getTime());
20778
- }
20779
- const params = [...whereParams, limit];
20780
- const statement = this.database.prepare(`
20781
- SELECT
20782
- m.id,
20783
- m.content,
20784
- m.workspace,
20785
- m.embedding,
20786
- m.created_at,
20787
- m.updated_at,
20788
- MAX(0, -bm25(memories_fts)) AS score
20789
- FROM memories_fts
20790
- INNER JOIN memories AS m ON m.rowid = memories_fts.rowid
20791
- WHERE ${whereClauses.join(" AND ")}
20792
- ORDER BY score DESC
20793
- LIMIT ?
20794
- `);
20795
- const rows = statement.all(...params);
20796
- const maxScore = Math.max(...rows.map((row) => row.score), 0);
20797
- return rows.map((row) => ({
20798
- ...toMemoryEntity(row),
20799
- score: toNormalizedScore(maxScore > 0 ? row.score / maxScore : 0)
20800
- }));
20801
- } catch (error2) {
20802
- throw new PersistenceError("Failed to search memories.", {
20803
- cause: error2
20804
- });
20805
- }
20806
- }
20807
20511
  async get(id) {
20808
20512
  try {
20809
20513
  const rows = this.getStatement.all(id);
20810
20514
  const row = rows[0];
20811
- return row ? toMemoryEntity(row) : undefined;
20515
+ return row ? toMemoryRecord(row) : undefined;
20812
20516
  } catch (error2) {
20813
20517
  throw new PersistenceError("Failed to find memory.", { cause: error2 });
20814
20518
  }
@@ -20819,35 +20523,46 @@ class SqliteMemoryRepository {
20819
20523
  const params = [];
20820
20524
  const offset = options.offset ?? 0;
20821
20525
  const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
20822
- if (options.workspace) {
20526
+ if (options.workspace && options.global) {
20527
+ whereClauses.push("(workspace = ? OR workspace IS NULL)");
20528
+ params.push(options.workspace);
20529
+ } else if (options.workspace) {
20823
20530
  whereClauses.push("workspace = ?");
20824
20531
  params.push(options.workspace);
20825
- } else if (options.workspaceIsNull) {
20532
+ } else if (options.global) {
20826
20533
  whereClauses.push("workspace IS NULL");
20827
20534
  }
20828
20535
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
20829
20536
  const queryLimit = limit + 1;
20830
20537
  params.push(queryLimit, offset);
20831
20538
  const statement = this.database.prepare(`
20832
- SELECT id, content, workspace, embedding, created_at, updated_at
20539
+ SELECT id, content, workspace, created_at, updated_at
20833
20540
  FROM memories
20834
20541
  ${whereClause}
20835
- ORDER BY created_at DESC
20542
+ ORDER BY updated_at DESC
20836
20543
  LIMIT ? OFFSET ?
20837
20544
  `);
20838
20545
  const rows = statement.all(...params);
20839
20546
  const hasMore = rows.length > limit;
20840
- const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryEntity);
20547
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryRecord);
20841
20548
  return { items, hasMore };
20842
20549
  } catch (error2) {
20843
20550
  throw new PersistenceError("Failed to list memories.", { cause: error2 });
20844
20551
  }
20845
20552
  }
20846
20553
  async update(input) {
20554
+ const existingMemory = await this.get(input.id);
20555
+ if (!existingMemory) {
20556
+ throw new NotFoundError(`Memory not found: ${input.id}`);
20557
+ }
20558
+ const patch = applyMemoryPatch(existingMemory, input);
20559
+ if (!hasMemoryChanges(existingMemory, patch)) {
20560
+ return existingMemory;
20561
+ }
20847
20562
  let result;
20848
20563
  try {
20849
20564
  const now = Date.now();
20850
- result = this.updateStatement.run(input.content, encodeEmbedding(input.embedding), now, input.id);
20565
+ result = this.updateStatement.run(patch.content, patch.workspace, now, input.id);
20851
20566
  } catch (error2) {
20852
20567
  throw new PersistenceError("Failed to update memory.", { cause: error2 });
20853
20568
  }
@@ -20880,25 +20595,28 @@ class SqliteMemoryRepository {
20880
20595
  }
20881
20596
  }
20882
20597
  }
20883
- var toMemoryEntity = (row) => ({
20598
+ var toMemoryRecord = (row) => ({
20884
20599
  id: row.id,
20885
20600
  content: row.content,
20886
- embedding: decodeEmbedding(row.embedding),
20887
20601
  workspace: row.workspace ?? undefined,
20888
20602
  createdAt: new Date(row.created_at),
20889
20603
  updatedAt: new Date(row.updated_at)
20890
20604
  });
20891
- var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
20892
- function toFtsTerm(term) {
20893
- const escaped = term.replaceAll('"', '""');
20894
- if (term.includes(" ")) {
20895
- return `"${escaped}"`;
20896
- }
20897
- return `"${escaped}"*`;
20605
+ function applyMemoryPatch(memory, input) {
20606
+ return {
20607
+ content: input.content ?? memory.content,
20608
+ workspace: input.workspace === undefined ? toStoredWorkspace(memory.workspace) : input.workspace
20609
+ };
20610
+ }
20611
+ function hasMemoryChanges(memory, patch) {
20612
+ return patch.content !== memory.content || patch.workspace !== toStoredWorkspace(memory.workspace);
20613
+ }
20614
+ function toStoredWorkspace(workspace) {
20615
+ return workspace ?? null;
20898
20616
  }
20899
20617
  // node_modules/@hono/node-server/dist/index.mjs
20900
20618
  import { createServer as createServerHTTP } from "http";
20901
- import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
20619
+ import { Http2ServerRequest as Http2ServerRequest2, constants as h2constants } from "http2";
20902
20620
  import { Http2ServerRequest } from "http2";
20903
20621
  import { Readable } from "stream";
20904
20622
  import crypto from "crypto";
@@ -21208,6 +20926,48 @@ if (typeof global.crypto === "undefined") {
21208
20926
  global.crypto = crypto;
21209
20927
  }
21210
20928
  var outgoingEnded = Symbol("outgoingEnded");
20929
+ var incomingDraining = Symbol("incomingDraining");
20930
+ var DRAIN_TIMEOUT_MS = 500;
20931
+ var MAX_DRAIN_BYTES = 64 * 1024 * 1024;
20932
+ var drainIncoming = (incoming) => {
20933
+ const incomingWithDrainState = incoming;
20934
+ if (incoming.destroyed || incomingWithDrainState[incomingDraining]) {
20935
+ return;
20936
+ }
20937
+ incomingWithDrainState[incomingDraining] = true;
20938
+ if (incoming instanceof Http2ServerRequest2) {
20939
+ try {
20940
+ incoming.stream?.close?.(h2constants.NGHTTP2_NO_ERROR);
20941
+ } catch {}
20942
+ return;
20943
+ }
20944
+ let bytesRead = 0;
20945
+ const cleanup = () => {
20946
+ clearTimeout(timer);
20947
+ incoming.off("data", onData);
20948
+ incoming.off("end", cleanup);
20949
+ incoming.off("error", cleanup);
20950
+ };
20951
+ const forceClose = () => {
20952
+ cleanup();
20953
+ const socket = incoming.socket;
20954
+ if (socket && !socket.destroyed) {
20955
+ socket.destroySoon();
20956
+ }
20957
+ };
20958
+ const timer = setTimeout(forceClose, DRAIN_TIMEOUT_MS);
20959
+ timer.unref?.();
20960
+ const onData = (chunk) => {
20961
+ bytesRead += chunk.length;
20962
+ if (bytesRead > MAX_DRAIN_BYTES) {
20963
+ forceClose();
20964
+ }
20965
+ };
20966
+ incoming.on("data", onData);
20967
+ incoming.on("end", cleanup);
20968
+ incoming.on("error", cleanup);
20969
+ incoming.resume();
20970
+ };
21211
20971
  var handleRequestError = () => new Response(null, {
21212
20972
  status: 400
21213
20973
  });
@@ -21371,14 +21131,18 @@ var getRequestListener = (fetchCallback, options = {}) => {
21371
21131
  setTimeout(() => {
21372
21132
  if (!incomingEnded) {
21373
21133
  setTimeout(() => {
21374
- incoming.destroy();
21375
- outgoing.destroy();
21134
+ drainIncoming(incoming);
21376
21135
  });
21377
21136
  }
21378
21137
  });
21379
21138
  }
21380
21139
  };
21381
21140
  }
21141
+ outgoing.on("finish", () => {
21142
+ if (!incomingEnded) {
21143
+ drainIncoming(incoming);
21144
+ }
21145
+ });
21382
21146
  }
21383
21147
  outgoing.on("close", () => {
21384
21148
  const abortController = req[abortControllerKey];
@@ -21393,7 +21157,7 @@ var getRequestListener = (fetchCallback, options = {}) => {
21393
21157
  setTimeout(() => {
21394
21158
  if (!incomingEnded) {
21395
21159
  setTimeout(() => {
21396
- incoming.destroy();
21160
+ drainIncoming(incoming);
21397
21161
  });
21398
21162
  }
21399
21163
  });
@@ -21818,7 +21582,7 @@ var HonoRequest = class {
21818
21582
  return headerData;
21819
21583
  }
21820
21584
  async parseBody(options) {
21821
- return this.bodyCache.parsedBody ??= await parseBody(this, options);
21585
+ return parseBody(this, options);
21822
21586
  }
21823
21587
  #cachedBody = (key) => {
21824
21588
  const { bodyCache, raw } = this;
@@ -24211,7 +23975,7 @@ function createPageRoutes(memory) {
24211
23975
  const wsFilter = workspace && !isNoWorkspace ? workspace : undefined;
24212
23976
  const page = await memory.list({
24213
23977
  workspace: wsFilter,
24214
- workspaceIsNull: isNoWorkspace,
23978
+ global: isNoWorkspace,
24215
23979
  offset: (pageNum - 1) * DEFAULT_LIST_LIMIT3,
24216
23980
  limit: DEFAULT_LIST_LIMIT3
24217
23981
  });
@@ -24294,9 +24058,9 @@ function createGitWorkspaceResolver(options = {}) {
24294
24058
  const cache = new Map;
24295
24059
  return {
24296
24060
  async resolve(workspace) {
24297
- const trimmed = normalizeOptionalString2(workspace);
24061
+ const trimmed = workspace.trim();
24298
24062
  if (!trimmed) {
24299
- return;
24063
+ throw new ValidationError("Workspace is required.");
24300
24064
  }
24301
24065
  const cached2 = cache.get(trimmed);
24302
24066
  if (cached2) {
@@ -24325,20 +24089,13 @@ async function defaultGetGitCommonDir(cwd) {
24325
24089
  });
24326
24090
  return stdout.trim();
24327
24091
  }
24328
- function normalizeOptionalString2(value) {
24329
- const trimmed = value?.trim();
24330
- return trimmed ? trimmed : undefined;
24331
- }
24332
24092
 
24333
24093
  // src/index.ts
24334
24094
  var config2 = resolveConfig();
24335
- configureModelsCache(config2.modelsCachePath);
24336
- var embeddingService = new EmbeddingService;
24337
24095
  var workspaceResolver = createGitWorkspaceResolver();
24338
- var database = await openMemoryDatabase(config2.databasePath, { embeddingService, workspaceResolver });
24096
+ var database = await openMemoryDatabase(config2.databasePath, { workspaceResolver });
24339
24097
  var repository2 = new SqliteMemoryRepository(database);
24340
- var memoryService = new MemoryService(repository2, embeddingService, workspaceResolver);
24341
- embeddingService.warmup();
24098
+ var memoryService = new MemoryService(repository2, workspaceResolver);
24342
24099
  if (config2.uiMode) {
24343
24100
  let shutdown = function() {
24344
24101
  server.close();