@jcyamacho/agent-memory 0.0.11 → 0.0.13

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 +33 -4
  2. package/dist/index.js +444 -394
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -3,10 +3,12 @@
3
3
  Persistent memory for MCP-powered coding agents.
4
4
 
5
5
  `agent-memory` is a stdio MCP server that gives your LLM durable memory backed
6
- by SQLite. It exposes two tools:
6
+ by SQLite. It exposes four tools:
7
7
 
8
8
  - `remember` -> save facts, decisions, preferences, and project context
9
9
  - `recall` -> retrieve the most relevant memories later
10
+ - `revise` -> update an existing memory when it becomes outdated
11
+ - `forget` -> delete a memory that is no longer relevant
10
12
 
11
13
  Use it when your agent should remember preferences, project facts, and prior
12
14
  decisions across sessions.
@@ -66,7 +68,10 @@ Optional LLM instructions to reinforce the MCP's built-in guidance:
66
68
  Use `recall` at the start of every conversation and again mid-task before
67
69
  making design choices or picking conventions. Use `remember` when the user
68
70
  corrects your approach, a key decision is established, or you learn project
69
- context not obvious from the code. Always pass workspace.
71
+ context not obvious from the code. Before saving, recall to check whether a
72
+ memory about the same fact already exists -- if so, use `revise` to update
73
+ it instead of creating a duplicate. Use `forget` to remove memories that
74
+ are wrong or no longer relevant. Always pass workspace.
70
75
  ```
71
76
 
72
77
  ## What It Stores
@@ -92,8 +97,7 @@ Opens at `http://localhost:6580`. Use `--port` to change:
92
97
  npx -y @jcyamacho/agent-memory --ui --port 9090
93
98
  ```
94
99
 
95
- The web UI uses the same database as the MCP server. LLM tools remain
96
- append-only; the web UI is the only way to edit or delete memories.
100
+ The web UI uses the same database as the MCP server.
97
101
 
98
102
  ## Tools
99
103
 
@@ -127,6 +131,31 @@ Output:
127
131
 
128
132
  - `results[]` with `id`, `content`, `score`, `workspace`, and `updated_at`
129
133
 
134
+ ### `revise`
135
+
136
+ Update the content of an existing memory.
137
+
138
+ Inputs:
139
+
140
+ - `id` -> the memory id from a previous recall result
141
+ - `content` -> replacement content for the memory
142
+
143
+ Output:
144
+
145
+ - `id`, `updated_at`
146
+
147
+ ### `forget`
148
+
149
+ Permanently delete a memory.
150
+
151
+ Inputs:
152
+
153
+ - `id` -> the memory id from a previous recall result
154
+
155
+ Output:
156
+
157
+ - `id`, `deleted`
158
+
130
159
  ## How Ranking Works
131
160
 
132
161
  `recall` uses a multi-signal ranking system to surface the most relevant
package/dist/index.js CHANGED
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
12464
12464
  }
12465
12465
  }
12466
12466
  // package.json
12467
- var version2 = "0.0.11";
12467
+ var version2 = "0.0.13";
12468
12468
 
12469
12469
  // src/config.ts
12470
12470
  import { homedir } from "node:os";
@@ -12472,7 +12472,7 @@ import { join } from "node:path";
12472
12472
  import { parseArgs } from "node:util";
12473
12473
  var AGENT_MEMORY_DB_PATH_ENV = "AGENT_MEMORY_DB_PATH";
12474
12474
  var DEFAULT_UI_PORT = 6580;
12475
- var resolveConfig = (environment = process.env, argv = process.argv.slice(2)) => {
12475
+ function resolveConfig(environment = process.env, argv = process.argv.slice(2)) {
12476
12476
  const { values } = parseArgs({
12477
12477
  args: argv,
12478
12478
  options: {
@@ -12486,7 +12486,7 @@ var resolveConfig = (environment = process.env, argv = process.argv.slice(2)) =>
12486
12486
  uiMode: Boolean(values.ui),
12487
12487
  uiPort: Number(values.port) || DEFAULT_UI_PORT
12488
12488
  };
12489
- };
12489
+ }
12490
12490
 
12491
12491
  // node_modules/zod/v3/helpers/util.js
12492
12492
  var util;
@@ -19895,9 +19895,6 @@ var EMPTY_COMPLETION_RESULT = {
19895
19895
  }
19896
19896
  };
19897
19897
 
19898
- // src/memory-service.ts
19899
- import { randomUUID } from "node:crypto";
19900
-
19901
19898
  // src/errors.ts
19902
19899
  class MemoryError extends Error {
19903
19900
  code;
@@ -19929,39 +19926,152 @@ class PersistenceError extends MemoryError {
19929
19926
  }
19930
19927
  }
19931
19928
 
19929
+ // src/mcp/tools/shared.ts
19930
+ function toMcpError(error2) {
19931
+ if (error2 instanceof McpError) {
19932
+ return error2;
19933
+ }
19934
+ if (error2 instanceof MemoryError) {
19935
+ if (error2.code === "VALIDATION_ERROR" || error2.code === "NOT_FOUND") {
19936
+ return new McpError(ErrorCode.InvalidParams, error2.message);
19937
+ }
19938
+ return new McpError(ErrorCode.InternalError, error2.message);
19939
+ }
19940
+ const message = error2 instanceof Error ? error2.message : "Unknown server error.";
19941
+ return new McpError(ErrorCode.InternalError, message);
19942
+ }
19943
+ var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
19944
+ function parseOptionalDate(value, fieldName) {
19945
+ if (!value) {
19946
+ return;
19947
+ }
19948
+ const date4 = new Date(value);
19949
+ if (Number.isNaN(date4.getTime())) {
19950
+ throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
19951
+ }
19952
+ return date4;
19953
+ }
19954
+
19955
+ // src/mcp/tools/forget.ts
19956
+ var forgetInputSchema = {
19957
+ id: string2().describe("The id of the memory to delete. Use the id returned by a previous recall result.")
19958
+ };
19959
+ function registerForgetTool(server, memory) {
19960
+ server.registerTool("forget", {
19961
+ description: "Permanently delete a memory that is wrong, obsolete, or no longer relevant. Pass the memory id from a previous recall result.",
19962
+ inputSchema: forgetInputSchema
19963
+ }, async ({ id }) => {
19964
+ try {
19965
+ await memory.delete({ id });
19966
+ return {
19967
+ content: [{ type: "text", text: `<memory id="${id.trim()}" deleted="true" />` }]
19968
+ };
19969
+ } catch (error2) {
19970
+ throw toMcpError(error2);
19971
+ }
19972
+ });
19973
+ }
19974
+
19932
19975
  // src/memory.ts
19933
19976
  var toNormalizedScore = (value) => value;
19934
19977
 
19935
- // src/memory-service.ts
19936
- var DEFAULT_LIMIT = 15;
19937
- var MAX_LIMIT = 50;
19938
- var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
19978
+ // src/ranking.ts
19939
19979
  var RETRIEVAL_SCORE_WEIGHT = 8;
19940
19980
  var WORKSPACE_MATCH_WEIGHT = 4;
19941
19981
  var RECENCY_WEIGHT = 1;
19942
19982
  var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
19943
19983
  var GLOBAL_WORKSPACE_SCORE = 0.5;
19944
19984
  var SIBLING_WORKSPACE_SCORE = 0.25;
19985
+ function rerankSearchResults(results, workspace) {
19986
+ if (results.length <= 1) {
19987
+ return results;
19988
+ }
19989
+ const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
19990
+ const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
19991
+ const minUpdatedAt = Math.min(...updatedAtTimes);
19992
+ const maxUpdatedAt = Math.max(...updatedAtTimes);
19993
+ return results.map((result) => {
19994
+ const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
19995
+ const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
19996
+ const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
19997
+ return {
19998
+ ...result,
19999
+ score: toNormalizedScore(combinedScore)
20000
+ };
20001
+ }).sort((a, b) => b.score - a.score);
20002
+ }
20003
+ function normalizeWorkspacePath(value) {
20004
+ return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20005
+ }
20006
+ function computeWorkspaceScore(memoryWs, queryWs) {
20007
+ if (!queryWs) {
20008
+ return 0;
20009
+ }
20010
+ if (!memoryWs) {
20011
+ return GLOBAL_WORKSPACE_SCORE;
20012
+ }
20013
+ const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20014
+ if (normalizedMemoryWs === queryWs) {
20015
+ return 1;
20016
+ }
20017
+ const queryLastSlashIndex = queryWs.lastIndexOf("/");
20018
+ const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20019
+ if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20020
+ return 0;
20021
+ }
20022
+ const queryParent = queryWs.slice(0, queryLastSlashIndex);
20023
+ const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20024
+ return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20025
+ }
20026
+
20027
+ // src/memory-service.ts
20028
+ var DEFAULT_RECALL_LIMIT = 15;
20029
+ var MAX_RECALL_LIMIT = 50;
20030
+ var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
20031
+ var DEFAULT_LIST_LIMIT = 15;
20032
+ var MAX_LIST_LIMIT = 100;
19945
20033
 
19946
20034
  class MemoryService {
19947
20035
  repository;
19948
20036
  constructor(repository) {
19949
20037
  this.repository = repository;
19950
20038
  }
19951
- async save(input) {
20039
+ async create(input) {
19952
20040
  const content = input.content.trim();
19953
20041
  if (!content) {
19954
20042
  throw new ValidationError("Memory content is required.");
19955
20043
  }
19956
- const now = new Date;
19957
- const memory = {
19958
- id: randomUUID(),
20044
+ return this.repository.create({
19959
20045
  content,
19960
- workspace: normalizeOptionalString(input.workspace),
19961
- createdAt: now,
19962
- updatedAt: now
19963
- };
19964
- return this.repository.save(memory);
20046
+ workspace: normalizeOptionalString(input.workspace)
20047
+ });
20048
+ }
20049
+ async update(input) {
20050
+ const content = input.content.trim();
20051
+ if (!content)
20052
+ throw new ValidationError("Memory content is required.");
20053
+ return this.repository.update({ id: input.id, content });
20054
+ }
20055
+ async delete(input) {
20056
+ const id = input.id.trim();
20057
+ if (!id)
20058
+ throw new ValidationError("Memory id is required.");
20059
+ return this.repository.delete({ id });
20060
+ }
20061
+ async get(id) {
20062
+ return this.repository.get(id);
20063
+ }
20064
+ async list(input) {
20065
+ const workspace = normalizeOptionalString(input.workspace);
20066
+ return this.repository.list({
20067
+ workspace,
20068
+ workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
20069
+ offset: normalizeOffset(input.offset),
20070
+ limit: normalizeListLimit(input.limit)
20071
+ });
20072
+ }
20073
+ async listWorkspaces() {
20074
+ return this.repository.listWorkspaces();
19965
20075
  }
19966
20076
  async search(input) {
19967
20077
  const terms = normalizeTerms(input.terms);
@@ -19977,19 +20087,27 @@ class MemoryService {
19977
20087
  updatedBefore: input.updatedBefore
19978
20088
  };
19979
20089
  const results = await this.repository.search(normalizedQuery);
19980
- const reranked = rerankSearchResults(results, workspace);
19981
- return reranked.sort((a, b) => b.score - a.score).slice(0, requestedLimit);
20090
+ return rerankSearchResults(results, workspace).slice(0, requestedLimit);
19982
20091
  }
19983
20092
  }
19984
20093
  function normalizeLimit(value) {
19985
20094
  if (value === undefined) {
19986
- return DEFAULT_LIMIT;
20095
+ return DEFAULT_RECALL_LIMIT;
19987
20096
  }
19988
- if (!Number.isInteger(value) || value < 1 || value > MAX_LIMIT) {
19989
- throw new ValidationError(`Limit must be an integer between 1 and ${MAX_LIMIT}.`);
20097
+ if (!Number.isInteger(value) || value < 1 || value > MAX_RECALL_LIMIT) {
20098
+ throw new ValidationError(`Limit must be an integer between 1 and ${MAX_RECALL_LIMIT}.`);
19990
20099
  }
19991
20100
  return value;
19992
20101
  }
20102
+ function normalizeOffset(value) {
20103
+ return Number.isInteger(value) && value && value > 0 ? value : 0;
20104
+ }
20105
+ function normalizeListLimit(value) {
20106
+ if (!Number.isInteger(value) || value === undefined) {
20107
+ return DEFAULT_LIST_LIMIT;
20108
+ }
20109
+ return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
20110
+ }
19993
20111
  function normalizeOptionalString(value) {
19994
20112
  const trimmed = value?.trim();
19995
20113
  return trimmed ? trimmed : undefined;
@@ -19998,96 +20116,29 @@ function normalizeTerms(terms) {
19998
20116
  const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
19999
20117
  return [...new Set(normalizedTerms)];
20000
20118
  }
20001
- function normalizeWorkspacePath(value) {
20002
- return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
20003
- }
20004
- function computeWorkspaceScore(memoryWs, queryWs) {
20005
- if (!queryWs) {
20006
- return 0;
20007
- }
20008
- if (!memoryWs) {
20009
- return GLOBAL_WORKSPACE_SCORE;
20010
- }
20011
- const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
20012
- if (normalizedMemoryWs === queryWs) {
20013
- return 1;
20014
- }
20015
- const queryLastSlashIndex = queryWs.lastIndexOf("/");
20016
- const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
20017
- if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
20018
- return 0;
20019
- }
20020
- const queryParent = queryWs.slice(0, queryLastSlashIndex);
20021
- const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
20022
- return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
20023
- }
20024
- function rerankSearchResults(results, workspace) {
20025
- if (results.length <= 1) {
20026
- return results;
20027
- }
20028
- const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
20029
- const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
20030
- const minUpdatedAt = Math.min(...updatedAtTimes);
20031
- const maxUpdatedAt = Math.max(...updatedAtTimes);
20032
- return results.map((result) => {
20033
- const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
20034
- const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
20035
- const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
20036
- return {
20037
- ...result,
20038
- score: toNormalizedScore(combinedScore)
20039
- };
20040
- });
20041
- }
20042
20119
 
20043
- // src/tools/shared.ts
20044
- var toMcpError = (error2) => {
20045
- if (error2 instanceof McpError) {
20046
- return error2;
20047
- }
20048
- if (error2 instanceof MemoryError) {
20049
- if (error2.code === "VALIDATION_ERROR") {
20050
- return new McpError(ErrorCode.InvalidParams, error2.message);
20051
- }
20052
- return new McpError(ErrorCode.InternalError, error2.message);
20053
- }
20054
- const message = error2 instanceof Error ? error2.message : "Unknown server error.";
20055
- return new McpError(ErrorCode.InternalError, message);
20056
- };
20057
- var escapeXml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
20058
- var parseOptionalDate = (value, fieldName) => {
20059
- if (!value) {
20060
- return;
20061
- }
20062
- const date4 = new Date(value);
20063
- if (Number.isNaN(date4.getTime())) {
20064
- throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
20065
- }
20066
- return date4;
20067
- };
20068
-
20069
- // src/tools/recall.ts
20120
+ // src/mcp/tools/recall.ts
20070
20121
  var recallInputSchema = {
20071
20122
  terms: array(string2()).min(1).describe("Search terms used to find relevant memories. Pass 2-5 short, distinctive items as separate array entries. Be specific: instead of 'preferences' or 'context', name the actual topic -- e.g. 'error handling', 'commit format', 'testing strategy'. Do not repeat the project or workspace name here -- use the workspace parameter for project scoping. Avoid full sentences."),
20072
- limit: number2().int().min(1).max(MAX_LIMIT).optional().describe("Maximum number of matches to return. Keep this small when you only need the strongest hits."),
20123
+ limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum number of matches to return. Keep this small when you only need the strongest hits."),
20073
20124
  workspace: string2().optional().describe("Always pass the current working directory. Biases ranking toward the active project while still allowing cross-workspace matches. Memories saved without a workspace are treated as global and rank between matching and non-matching results."),
20074
20125
  updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp. Use it when you need to narrow recall to newer context."),
20075
20126
  updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp. Use it when you need to narrow recall to older context.")
20076
20127
  };
20077
- var toMemoryXml = (r) => {
20128
+ function toMemoryXml(r) {
20078
20129
  const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
20079
20130
  const content = escapeXml(r.content);
20080
20131
  return `<memory id="${r.id}" score="${r.score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
20081
20132
  ${content}
20082
20133
  </memory>`;
20083
- };
20084
- var registerRecallTool = (server, memoryService) => {
20134
+ }
20135
+ function registerRecallTool(server, memory) {
20085
20136
  server.registerTool("recall", {
20086
20137
  description: "Search memories for prior decisions, corrections, and context that cannot be derived from code or git history. Call at the start of every conversation and again mid-task when you are about to make a design choice, pick a convention, or handle an edge case that the user may have guided before. Always pass workspace.",
20087
20138
  inputSchema: recallInputSchema
20088
20139
  }, async ({ terms, limit, workspace, updated_after, updated_before }) => {
20089
20140
  try {
20090
- const results = await memoryService.search({
20141
+ const results = await memory.search({
20091
20142
  terms,
20092
20143
  limit,
20093
20144
  workspace,
@@ -20105,51 +20156,82 @@ ${results.map(toMemoryXml).join(`
20105
20156
  throw toMcpError(error2);
20106
20157
  }
20107
20158
  });
20108
- };
20159
+ }
20109
20160
 
20110
- // src/tools/remember.ts
20161
+ // src/mcp/tools/remember.ts
20111
20162
  var rememberInputSchema = {
20112
20163
  content: string2().describe("The fact, preference, decision, or context to remember. Use a single self-contained sentence or short note. One fact per memory."),
20113
20164
  workspace: string2().optional().describe("Always pass the current working directory to scope this memory to a project. Omit only when the memory applies across all projects (global preference).")
20114
20165
  };
20115
- var registerRememberTool = (server, memoryService) => {
20166
+ function registerRememberTool(server, memory) {
20116
20167
  server.registerTool("remember", {
20117
20168
  description: "Save durable context for later recall. Use this when the user corrects your approach, states a preference, a key decision or convention is established, or you learn project context not obvious from the code. Store one concise fact per memory. Do not store secrets, ephemeral task state, or information already in the codebase.",
20118
20169
  inputSchema: rememberInputSchema
20119
20170
  }, async ({ content, workspace }) => {
20120
20171
  try {
20121
- const memory = await memoryService.save({
20172
+ const savedMemory = await memory.create({
20122
20173
  content,
20123
20174
  workspace
20124
20175
  });
20125
20176
  return {
20126
- content: [{ type: "text", text: `<memory id="${memory.id}" />` }]
20177
+ content: [{ type: "text", text: `<memory id="${savedMemory.id}" />` }]
20127
20178
  };
20128
20179
  } catch (error2) {
20129
20180
  throw toMcpError(error2);
20130
20181
  }
20131
20182
  });
20183
+ }
20184
+
20185
+ // src/mcp/tools/revise.ts
20186
+ var reviseInputSchema = {
20187
+ id: string2().describe("The id of the memory to update. Use the id returned by a previous recall result."),
20188
+ content: string2().describe("The replacement content for the memory. Use a single self-contained sentence or short note. One fact per memory.")
20132
20189
  };
20190
+ function registerReviseTool(server, memory) {
20191
+ server.registerTool("revise", {
20192
+ description: "Update the content of an existing memory. Use when a previously saved memory is outdated or inaccurate and needs correction rather than deletion. Pass the memory id from a previous recall result.",
20193
+ inputSchema: reviseInputSchema
20194
+ }, async ({ id, content }) => {
20195
+ try {
20196
+ const revisedMemory = await memory.update({ id, content });
20197
+ return {
20198
+ content: [
20199
+ {
20200
+ type: "text",
20201
+ text: `<memory id="${revisedMemory.id}" updated_at="${revisedMemory.updatedAt.toISOString()}" />`
20202
+ }
20203
+ ]
20204
+ };
20205
+ } catch (error2) {
20206
+ throw toMcpError(error2);
20207
+ }
20208
+ });
20209
+ }
20133
20210
 
20134
- // src/mcp-server.ts
20211
+ // src/mcp/server.ts
20135
20212
  var SERVER_INSTRUCTIONS = [
20136
20213
  "Stores decisions, corrections, and context that cannot be derived from code or git history.",
20137
20214
  "Use `recall` at the start of every conversation and again mid-task before making design choices or picking conventions the user may have guided before.",
20138
20215
  "Use `remember` when the user corrects your approach, states a preference, a key decision is established, or you learn project context not obvious from the code.",
20216
+ "Before saving a new memory, recall to check whether a memory about the same fact already exists. If so, use `revise` to update it instead of creating a duplicate.",
20217
+ "Use `revise` when a previously saved memory is outdated or inaccurate and needs correction rather than deletion.",
20218
+ "Use `forget` to remove memories that are wrong, obsolete, or no longer relevant.",
20139
20219
  "Always pass workspace (the current working directory) to scope results to the active project.",
20140
20220
  "Omit workspace only when saving a memory that applies across all projects."
20141
20221
  ].join(" ");
20142
- var createMcpServer = (memoryService, version3) => {
20222
+ function createMcpServer(memory, version3) {
20143
20223
  const server = new McpServer({
20144
20224
  name: "agent-memory",
20145
20225
  version: version3
20146
20226
  }, {
20147
20227
  instructions: SERVER_INSTRUCTIONS
20148
20228
  });
20149
- registerRememberTool(server, memoryService);
20150
- registerRecallTool(server, memoryService);
20229
+ registerRememberTool(server, memory);
20230
+ registerRecallTool(server, memory);
20231
+ registerReviseTool(server, memory);
20232
+ registerForgetTool(server, memory);
20151
20233
  return server;
20152
- };
20234
+ }
20153
20235
 
20154
20236
  // src/sqlite-db.ts
20155
20237
  import { mkdirSync } from "node:fs";
@@ -20189,7 +20271,7 @@ var MEMORY_SCHEMA = `
20189
20271
  INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
20190
20272
  END;
20191
20273
  `;
20192
- var openMemoryDatabase = (databasePath) => {
20274
+ function openMemoryDatabase(databasePath) {
20193
20275
  try {
20194
20276
  mkdirSync(dirname(databasePath), { recursive: true });
20195
20277
  const database = new Database(databasePath);
@@ -20200,8 +20282,8 @@ var openMemoryDatabase = (databasePath) => {
20200
20282
  cause: error2
20201
20283
  });
20202
20284
  }
20203
- };
20204
- var initializeMemoryDatabase = (database) => {
20285
+ }
20286
+ function initializeMemoryDatabase(database) {
20205
20287
  if (database.pragma) {
20206
20288
  database.pragma("journal_mode = WAL");
20207
20289
  database.pragma("synchronous = NORMAL");
@@ -20214,13 +20296,17 @@ var initializeMemoryDatabase = (database) => {
20214
20296
  database.exec("PRAGMA busy_timeout = 5000");
20215
20297
  }
20216
20298
  database.exec(MEMORY_SCHEMA);
20217
- };
20299
+ }
20300
+
20301
+ // src/sqlite-memory-repository.ts
20302
+ import { randomUUID } from "node:crypto";
20303
+ var DEFAULT_SEARCH_LIMIT = 15;
20304
+ var DEFAULT_LIST_LIMIT2 = 15;
20218
20305
 
20219
- // src/sqlite-repository.ts
20220
20306
  class SqliteMemoryRepository {
20221
20307
  database;
20222
20308
  insertStatement;
20223
- findByIdStatement;
20309
+ getStatement;
20224
20310
  updateStatement;
20225
20311
  deleteStatement;
20226
20312
  listWorkspacesStatement;
@@ -20241,13 +20327,21 @@ class SqliteMemoryRepository {
20241
20327
  ?
20242
20328
  )
20243
20329
  `);
20244
- this.findByIdStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20330
+ this.getStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
20245
20331
  this.updateStatement = database.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?");
20246
20332
  this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
20247
20333
  this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
20248
20334
  }
20249
- async save(memory) {
20335
+ async create(input) {
20250
20336
  try {
20337
+ const now = new Date;
20338
+ const memory = {
20339
+ id: randomUUID(),
20340
+ content: input.content,
20341
+ workspace: input.workspace,
20342
+ createdAt: now,
20343
+ updatedAt: now
20344
+ };
20251
20345
  this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
20252
20346
  return memory;
20253
20347
  } catch (error2) {
@@ -20257,6 +20351,7 @@ class SqliteMemoryRepository {
20257
20351
  async search(query) {
20258
20352
  try {
20259
20353
  const whereParams = [toFtsQuery(query.terms)];
20354
+ const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
20260
20355
  const whereClauses = ["memories_fts MATCH ?"];
20261
20356
  if (query.updatedAfter) {
20262
20357
  whereClauses.push("m.updated_at >= ?");
@@ -20266,7 +20361,7 @@ class SqliteMemoryRepository {
20266
20361
  whereClauses.push("m.updated_at <= ?");
20267
20362
  whereParams.push(query.updatedBefore.getTime());
20268
20363
  }
20269
- const params = [...whereParams, query.limit];
20364
+ const params = [...whereParams, limit];
20270
20365
  const statement = this.database.prepare(`
20271
20366
  SELECT
20272
20367
  m.id,
@@ -20293,19 +20388,21 @@ class SqliteMemoryRepository {
20293
20388
  });
20294
20389
  }
20295
20390
  }
20296
- async findById(id) {
20391
+ async get(id) {
20297
20392
  try {
20298
- const rows = this.findByIdStatement.all(id);
20393
+ const rows = this.getStatement.all(id);
20299
20394
  const row = rows[0];
20300
20395
  return row ? toMemoryRecord(row) : undefined;
20301
20396
  } catch (error2) {
20302
20397
  throw new PersistenceError("Failed to find memory.", { cause: error2 });
20303
20398
  }
20304
20399
  }
20305
- async findAll(options) {
20400
+ async list(options) {
20306
20401
  try {
20307
20402
  const whereClauses = [];
20308
20403
  const params = [];
20404
+ const offset = options.offset ?? 0;
20405
+ const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
20309
20406
  if (options.workspace) {
20310
20407
  whereClauses.push("workspace = ?");
20311
20408
  params.push(options.workspace);
@@ -20313,8 +20410,8 @@ class SqliteMemoryRepository {
20313
20410
  whereClauses.push("workspace IS NULL");
20314
20411
  }
20315
20412
  const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
20316
- const limit = options.limit + 1;
20317
- params.push(limit, options.offset);
20413
+ const queryLimit = limit + 1;
20414
+ params.push(queryLimit, offset);
20318
20415
  const statement = this.database.prepare(`
20319
20416
  SELECT id, content, workspace, created_at, updated_at
20320
20417
  FROM memories
@@ -20323,39 +20420,39 @@ class SqliteMemoryRepository {
20323
20420
  LIMIT ? OFFSET ?
20324
20421
  `);
20325
20422
  const rows = statement.all(...params);
20326
- const hasMore = rows.length > options.limit;
20327
- const items = (hasMore ? rows.slice(0, options.limit) : rows).map(toMemoryRecord);
20423
+ const hasMore = rows.length > limit;
20424
+ const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryRecord);
20328
20425
  return { items, hasMore };
20329
20426
  } catch (error2) {
20330
20427
  throw new PersistenceError("Failed to list memories.", { cause: error2 });
20331
20428
  }
20332
20429
  }
20333
- async update(id, content) {
20430
+ async update(input) {
20334
20431
  let result;
20335
20432
  try {
20336
20433
  const now = Date.now();
20337
- result = this.updateStatement.run(content, now, id);
20434
+ result = this.updateStatement.run(input.content, now, input.id);
20338
20435
  } catch (error2) {
20339
20436
  throw new PersistenceError("Failed to update memory.", { cause: error2 });
20340
20437
  }
20341
20438
  if (result.changes === 0) {
20342
- throw new NotFoundError(`Memory not found: ${id}`);
20439
+ throw new NotFoundError(`Memory not found: ${input.id}`);
20343
20440
  }
20344
- const memory = await this.findById(id);
20441
+ const memory = await this.get(input.id);
20345
20442
  if (!memory) {
20346
- throw new NotFoundError(`Memory not found after update: ${id}`);
20443
+ throw new NotFoundError(`Memory not found after update: ${input.id}`);
20347
20444
  }
20348
20445
  return memory;
20349
20446
  }
20350
- async delete(id) {
20447
+ async delete(input) {
20351
20448
  let result;
20352
20449
  try {
20353
- result = this.deleteStatement.run(id);
20450
+ result = this.deleteStatement.run(input.id);
20354
20451
  } catch (error2) {
20355
20452
  throw new PersistenceError("Failed to delete memory.", { cause: error2 });
20356
20453
  }
20357
20454
  if (result.changes === 0) {
20358
- throw new NotFoundError(`Memory not found: ${id}`);
20455
+ throw new NotFoundError(`Memory not found: ${input.id}`);
20359
20456
  }
20360
20457
  }
20361
20458
  async listWorkspaces() {
@@ -20375,16 +20472,13 @@ var toMemoryRecord = (row) => ({
20375
20472
  updatedAt: new Date(row.updated_at)
20376
20473
  });
20377
20474
  var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
20378
- var toFtsTerm = (term) => {
20475
+ function toFtsTerm(term) {
20379
20476
  const escaped = term.replaceAll('"', '""');
20380
20477
  if (term.includes(" ")) {
20381
20478
  return `"${escaped}"`;
20382
20479
  }
20383
20480
  return `"${escaped}"*`;
20384
- };
20385
-
20386
- // src/ui/server.tsx
20387
- import { randomUUID as randomUUID2 } from "node:crypto";
20481
+ }
20388
20482
 
20389
20483
  // node_modules/@hono/node-server/dist/index.mjs
20390
20484
  import { createServer as createServerHTTP } from "http";
@@ -23378,182 +23472,188 @@ function jsxDEV(tag, props, key) {
23378
23472
  }
23379
23473
 
23380
23474
  // src/ui/components/create-form.tsx
23381
- var CreateForm = ({ workspace }) => /* @__PURE__ */ jsxDEV("div", {
23382
- class: "form-card",
23383
- children: /* @__PURE__ */ jsxDEV("form", {
23384
- method: "post",
23385
- action: "/memories",
23386
- children: [
23387
- /* @__PURE__ */ jsxDEV("div", {
23388
- class: "field",
23389
- children: [
23390
- /* @__PURE__ */ jsxDEV("label", {
23391
- for: "new-content",
23392
- children: "Content"
23393
- }, undefined, false, undefined, this),
23394
- /* @__PURE__ */ jsxDEV("textarea", {
23395
- id: "new-content",
23396
- name: "content",
23397
- placeholder: "Fact, preference, decision...",
23398
- required: true
23399
- }, undefined, false, undefined, this)
23400
- ]
23401
- }, undefined, true, undefined, this),
23402
- /* @__PURE__ */ jsxDEV("div", {
23403
- class: "field",
23404
- children: [
23405
- /* @__PURE__ */ jsxDEV("label", {
23406
- for: "new-workspace",
23407
- children: "Workspace (optional)"
23408
- }, undefined, false, undefined, this),
23409
- /* @__PURE__ */ jsxDEV("input", {
23410
- id: "new-workspace",
23411
- name: "workspace",
23412
- type: "text",
23413
- placeholder: "/path/to/project",
23414
- value: workspace && workspace !== NO_WORKSPACE_FILTER ? workspace : ""
23415
- }, undefined, false, undefined, this)
23416
- ]
23417
- }, undefined, true, undefined, this),
23418
- /* @__PURE__ */ jsxDEV("div", {
23419
- class: "form-actions",
23420
- children: /* @__PURE__ */ jsxDEV("button", {
23421
- type: "submit",
23422
- class: "primary",
23423
- children: "Save"
23424
- }, undefined, false, undefined, this)
23425
- }, undefined, false, undefined, this)
23426
- ]
23427
- }, undefined, true, undefined, this)
23428
- }, undefined, false, undefined, this);
23429
-
23430
- // src/ui/components/memory-card.tsx
23431
- var MemoryCard = ({ memory, editing, showWorkspace, returnUrl }) => /* @__PURE__ */ jsxDEV("div", {
23432
- class: "card",
23433
- children: [
23434
- /* @__PURE__ */ jsxDEV("div", {
23435
- class: "card-header",
23436
- children: /* @__PURE__ */ jsxDEV("div", {
23437
- class: "card-meta",
23438
- children: [
23439
- showWorkspace && memory.workspace && /* @__PURE__ */ jsxDEV(Fragment, {
23440
- children: [
23441
- /* @__PURE__ */ jsxDEV("span", {
23442
- class: "badge",
23443
- children: memory.workspace
23444
- }, undefined, false, undefined, this),
23445
- " "
23446
- ]
23447
- }, undefined, true, undefined, this),
23448
- memory.updatedAt.toLocaleString()
23449
- ]
23450
- }, undefined, true, undefined, this)
23451
- }, undefined, false, undefined, this),
23452
- editing ? /* @__PURE__ */ jsxDEV("form", {
23475
+ function CreateForm({ workspace }) {
23476
+ return /* @__PURE__ */ jsxDEV("div", {
23477
+ class: "form-card",
23478
+ children: /* @__PURE__ */ jsxDEV("form", {
23453
23479
  method: "post",
23454
- action: `/memories/${encodeURIComponent(memory.id)}/update`,
23480
+ action: "/memories",
23455
23481
  children: [
23456
- /* @__PURE__ */ jsxDEV("input", {
23457
- type: "hidden",
23458
- name: "returnUrl",
23459
- value: returnUrl
23460
- }, undefined, false, undefined, this),
23461
- /* @__PURE__ */ jsxDEV("textarea", {
23462
- name: "content",
23463
- required: true,
23464
- children: memory.content
23465
- }, undefined, false, undefined, this),
23466
23482
  /* @__PURE__ */ jsxDEV("div", {
23467
- class: "card-actions",
23483
+ class: "field",
23468
23484
  children: [
23469
- /* @__PURE__ */ jsxDEV("button", {
23470
- type: "submit",
23471
- class: "primary",
23472
- children: "Save"
23485
+ /* @__PURE__ */ jsxDEV("label", {
23486
+ for: "new-content",
23487
+ children: "Content"
23473
23488
  }, undefined, false, undefined, this),
23474
- /* @__PURE__ */ jsxDEV("a", {
23475
- href: returnUrl,
23476
- class: "btn",
23477
- children: "Cancel"
23489
+ /* @__PURE__ */ jsxDEV("textarea", {
23490
+ id: "new-content",
23491
+ name: "content",
23492
+ placeholder: "Fact, preference, decision...",
23493
+ required: true
23478
23494
  }, undefined, false, undefined, this)
23479
23495
  ]
23480
- }, undefined, true, undefined, this)
23481
- ]
23482
- }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
23483
- children: [
23484
- /* @__PURE__ */ jsxDEV("div", {
23485
- class: "card-content",
23486
- children: memory.content
23487
- }, undefined, false, undefined, this),
23496
+ }, undefined, true, undefined, this),
23488
23497
  /* @__PURE__ */ jsxDEV("div", {
23489
- class: "card-actions",
23498
+ class: "field",
23490
23499
  children: [
23491
- /* @__PURE__ */ jsxDEV("a", {
23492
- href: `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}edit=${encodeURIComponent(memory.id)}`,
23493
- class: "btn",
23494
- children: "Edit"
23500
+ /* @__PURE__ */ jsxDEV("label", {
23501
+ for: "new-workspace",
23502
+ children: "Workspace (optional)"
23495
23503
  }, undefined, false, undefined, this),
23496
- /* @__PURE__ */ jsxDEV("form", {
23497
- method: "post",
23498
- action: `/memories/${encodeURIComponent(memory.id)}/delete`,
23499
- onsubmit: "return confirm('Delete this memory?')",
23504
+ /* @__PURE__ */ jsxDEV("input", {
23505
+ id: "new-workspace",
23506
+ name: "workspace",
23507
+ type: "text",
23508
+ placeholder: "/path/to/project",
23509
+ value: workspace && workspace !== NO_WORKSPACE_FILTER ? workspace : ""
23510
+ }, undefined, false, undefined, this)
23511
+ ]
23512
+ }, undefined, true, undefined, this),
23513
+ /* @__PURE__ */ jsxDEV("div", {
23514
+ class: "form-actions",
23515
+ children: /* @__PURE__ */ jsxDEV("button", {
23516
+ type: "submit",
23517
+ class: "primary",
23518
+ children: "Save"
23519
+ }, undefined, false, undefined, this)
23520
+ }, undefined, false, undefined, this)
23521
+ ]
23522
+ }, undefined, true, undefined, this)
23523
+ }, undefined, false, undefined, this);
23524
+ }
23525
+
23526
+ // src/ui/components/memory-card.tsx
23527
+ function MemoryCard({ memory, editing, showWorkspace, returnUrl }) {
23528
+ return /* @__PURE__ */ jsxDEV("div", {
23529
+ class: "card",
23530
+ children: [
23531
+ /* @__PURE__ */ jsxDEV("div", {
23532
+ class: "card-header",
23533
+ children: /* @__PURE__ */ jsxDEV("div", {
23534
+ class: "card-meta",
23535
+ children: [
23536
+ showWorkspace && memory.workspace && /* @__PURE__ */ jsxDEV(Fragment, {
23500
23537
  children: [
23501
- /* @__PURE__ */ jsxDEV("input", {
23502
- type: "hidden",
23503
- name: "returnUrl",
23504
- value: returnUrl
23538
+ /* @__PURE__ */ jsxDEV("span", {
23539
+ class: "badge",
23540
+ children: memory.workspace
23505
23541
  }, undefined, false, undefined, this),
23506
- /* @__PURE__ */ jsxDEV("button", {
23507
- type: "submit",
23508
- class: "danger",
23509
- children: "Delete"
23510
- }, undefined, false, undefined, this)
23542
+ " "
23511
23543
  ]
23512
- }, undefined, true, undefined, this)
23544
+ }, undefined, true, undefined, this),
23545
+ memory.updatedAt.toLocaleString()
23513
23546
  ]
23514
23547
  }, undefined, true, undefined, this)
23515
- ]
23516
- }, undefined, true, undefined, this)
23517
- ]
23518
- }, undefined, true, undefined, this);
23548
+ }, undefined, false, undefined, this),
23549
+ editing ? /* @__PURE__ */ jsxDEV("form", {
23550
+ method: "post",
23551
+ action: `/memories/${encodeURIComponent(memory.id)}/update`,
23552
+ children: [
23553
+ /* @__PURE__ */ jsxDEV("input", {
23554
+ type: "hidden",
23555
+ name: "returnUrl",
23556
+ value: returnUrl
23557
+ }, undefined, false, undefined, this),
23558
+ /* @__PURE__ */ jsxDEV("textarea", {
23559
+ name: "content",
23560
+ required: true,
23561
+ children: memory.content
23562
+ }, undefined, false, undefined, this),
23563
+ /* @__PURE__ */ jsxDEV("div", {
23564
+ class: "card-actions",
23565
+ children: [
23566
+ /* @__PURE__ */ jsxDEV("button", {
23567
+ type: "submit",
23568
+ class: "primary",
23569
+ children: "Save"
23570
+ }, undefined, false, undefined, this),
23571
+ /* @__PURE__ */ jsxDEV("a", {
23572
+ href: returnUrl,
23573
+ class: "btn",
23574
+ children: "Cancel"
23575
+ }, undefined, false, undefined, this)
23576
+ ]
23577
+ }, undefined, true, undefined, this)
23578
+ ]
23579
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
23580
+ children: [
23581
+ /* @__PURE__ */ jsxDEV("div", {
23582
+ class: "card-content",
23583
+ children: memory.content
23584
+ }, undefined, false, undefined, this),
23585
+ /* @__PURE__ */ jsxDEV("div", {
23586
+ class: "card-actions",
23587
+ children: [
23588
+ /* @__PURE__ */ jsxDEV("a", {
23589
+ href: `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}edit=${encodeURIComponent(memory.id)}`,
23590
+ class: "btn",
23591
+ children: "Edit"
23592
+ }, undefined, false, undefined, this),
23593
+ /* @__PURE__ */ jsxDEV("form", {
23594
+ method: "post",
23595
+ action: `/memories/${encodeURIComponent(memory.id)}/delete`,
23596
+ onsubmit: "return confirm('Delete this memory?')",
23597
+ children: [
23598
+ /* @__PURE__ */ jsxDEV("input", {
23599
+ type: "hidden",
23600
+ name: "returnUrl",
23601
+ value: returnUrl
23602
+ }, undefined, false, undefined, this),
23603
+ /* @__PURE__ */ jsxDEV("button", {
23604
+ type: "submit",
23605
+ class: "danger",
23606
+ children: "Delete"
23607
+ }, undefined, false, undefined, this)
23608
+ ]
23609
+ }, undefined, true, undefined, this)
23610
+ ]
23611
+ }, undefined, true, undefined, this)
23612
+ ]
23613
+ }, undefined, true, undefined, this)
23614
+ ]
23615
+ }, undefined, true, undefined, this);
23616
+ }
23519
23617
 
23520
23618
  // src/ui/components/sidebar.tsx
23521
- var Sidebar = ({ workspaces, selected }) => /* @__PURE__ */ jsxDEV("nav", {
23522
- class: "sidebar",
23523
- children: [
23524
- /* @__PURE__ */ jsxDEV("h2", {
23525
- children: "Workspaces"
23526
- }, undefined, false, undefined, this),
23527
- /* @__PURE__ */ jsxDEV("a", {
23528
- href: "/",
23529
- class: `sidebar-item all-item${selected === null ? " active" : ""}`,
23530
- children: "All"
23531
- }, undefined, false, undefined, this),
23532
- /* @__PURE__ */ jsxDEV("a", {
23533
- href: `/?workspace=${NO_WORKSPACE_FILTER}`,
23534
- class: `sidebar-item no-ws-item${selected === NO_WORKSPACE_FILTER ? " active" : ""}`,
23535
- children: "No workspace"
23536
- }, undefined, false, undefined, this),
23537
- workspaces.map((ws) => /* @__PURE__ */ jsxDEV("a", {
23538
- href: `/?workspace=${encodeURIComponent(ws)}`,
23539
- class: `sidebar-item ws-item${selected === ws ? " active" : ""}`,
23540
- title: ws,
23541
- children: /* @__PURE__ */ jsxDEV("span", {
23542
- children: ws.replace(/\/$/, "")
23543
- }, undefined, false, undefined, this)
23544
- }, undefined, false, undefined, this))
23545
- ]
23546
- }, undefined, true, undefined, this);
23619
+ function Sidebar({ workspaces, selected }) {
23620
+ return /* @__PURE__ */ jsxDEV("nav", {
23621
+ class: "sidebar",
23622
+ children: [
23623
+ /* @__PURE__ */ jsxDEV("h2", {
23624
+ children: "Workspaces"
23625
+ }, undefined, false, undefined, this),
23626
+ /* @__PURE__ */ jsxDEV("a", {
23627
+ href: "/",
23628
+ class: `sidebar-item all-item${selected === null ? " active" : ""}`,
23629
+ children: "All"
23630
+ }, undefined, false, undefined, this),
23631
+ /* @__PURE__ */ jsxDEV("a", {
23632
+ href: `/?workspace=${NO_WORKSPACE_FILTER}`,
23633
+ class: `sidebar-item no-ws-item${selected === NO_WORKSPACE_FILTER ? " active" : ""}`,
23634
+ children: "No workspace"
23635
+ }, undefined, false, undefined, this),
23636
+ workspaces.map((ws) => /* @__PURE__ */ jsxDEV("a", {
23637
+ href: `/?workspace=${encodeURIComponent(ws)}`,
23638
+ class: `sidebar-item ws-item${selected === ws ? " active" : ""}`,
23639
+ title: ws,
23640
+ children: /* @__PURE__ */ jsxDEV("span", {
23641
+ children: ws.replace(/\/$/, "")
23642
+ }, undefined, false, undefined, this)
23643
+ }, undefined, false, undefined, this))
23644
+ ]
23645
+ }, undefined, true, undefined, this);
23646
+ }
23547
23647
 
23548
23648
  // src/ui/components/page.tsx
23549
- var buildUrl = (base, overrides) => {
23649
+ function buildUrl(base, overrides) {
23550
23650
  const url = new URL(base, "http://localhost");
23551
23651
  for (const [key, value] of Object.entries(overrides)) {
23552
23652
  url.searchParams.set(key, value);
23553
23653
  }
23554
23654
  return `${url.pathname}${url.search}`;
23555
- };
23556
- var Page = ({
23655
+ }
23656
+ function Page({
23557
23657
  memories,
23558
23658
  workspaces,
23559
23659
  selectedWorkspace,
@@ -23561,7 +23661,7 @@ var Page = ({
23561
23661
  currentPage,
23562
23662
  hasMore,
23563
23663
  showCreate
23564
- }) => {
23664
+ }) {
23565
23665
  const params = new URLSearchParams;
23566
23666
  if (selectedWorkspace)
23567
23667
  params.set("workspace", selectedWorkspace);
@@ -23680,27 +23780,26 @@ var Page = ({
23680
23780
  }, undefined, false, undefined, this)
23681
23781
  ]
23682
23782
  }, undefined, true, undefined, this);
23683
- };
23783
+ }
23684
23784
 
23685
- // src/ui/server.tsx
23686
- var DEFAULT_LIST_LIMIT = 15;
23687
- var MAX_LIST_LIMIT = 100;
23688
- var startWebServer = (repository, options) => {
23785
+ // src/ui/routes/page-routes.tsx
23786
+ var DEFAULT_LIST_LIMIT3 = 15;
23787
+ function createPageRoutes(memory) {
23689
23788
  const app = new Hono2;
23690
- app.get("/", async (c) => {
23789
+ async function renderPage(c) {
23691
23790
  const workspace = c.req.query("workspace") ?? null;
23692
23791
  const pageNum = Math.max(Number(c.req.query("page")) || 1, 1);
23693
23792
  const editingId = c.req.query("edit") ?? null;
23694
23793
  const showCreate = c.req.query("create") === "1";
23695
23794
  const isNoWorkspace = workspace === NO_WORKSPACE_FILTER;
23696
23795
  const wsFilter = workspace && !isNoWorkspace ? workspace : undefined;
23697
- const page = await repository.findAll({
23796
+ const page = await memory.list({
23698
23797
  workspace: wsFilter,
23699
23798
  workspaceIsNull: isNoWorkspace,
23700
- offset: (pageNum - 1) * DEFAULT_LIST_LIMIT,
23701
- limit: DEFAULT_LIST_LIMIT
23799
+ offset: (pageNum - 1) * DEFAULT_LIST_LIMIT3,
23800
+ limit: DEFAULT_LIST_LIMIT3
23702
23801
  });
23703
- const workspaces = await repository.listWorkspaces();
23802
+ const workspaces = await memory.listWorkspaces();
23704
23803
  return c.html(/* @__PURE__ */ jsxDEV(Page, {
23705
23804
  memories: page.items,
23706
23805
  workspaces,
@@ -23710,132 +23809,83 @@ var startWebServer = (repository, options) => {
23710
23809
  hasMore: page.hasMore,
23711
23810
  showCreate
23712
23811
  }, undefined, false, undefined, this));
23713
- });
23714
- app.post("/memories", async (c) => {
23812
+ }
23813
+ async function createMemory(c) {
23715
23814
  const form2 = await c.req.parseBody();
23716
- const content = typeof form2.content === "string" ? form2.content.trim() : "";
23717
- const workspace = typeof form2.workspace === "string" ? form2.workspace.trim() || undefined : undefined;
23718
- if (content) {
23719
- const now = new Date;
23720
- await repository.save({ id: randomUUID2(), content, workspace, createdAt: now, updatedAt: now });
23815
+ const content = typeof form2.content === "string" ? form2.content : "";
23816
+ const workspace = typeof form2.workspace === "string" ? form2.workspace : undefined;
23817
+ try {
23818
+ await memory.create({ content, workspace });
23819
+ } catch (error2) {
23820
+ if (!(error2 instanceof ValidationError)) {
23821
+ throw error2;
23822
+ }
23721
23823
  }
23722
- const wsParam = workspace ? `/?workspace=${encodeURIComponent(workspace)}` : "/";
23824
+ const wsParam = workspace?.trim() ? `/?workspace=${encodeURIComponent(workspace.trim())}` : "/";
23723
23825
  return c.redirect(wsParam);
23724
- });
23725
- app.post("/memories/:id/update", async (c) => {
23826
+ }
23827
+ async function updateMemory(c) {
23726
23828
  const form2 = await c.req.parseBody();
23727
- const content = typeof form2.content === "string" ? form2.content.trim() : "";
23829
+ const content = typeof form2.content === "string" ? form2.content : "";
23728
23830
  const returnUrl = safeReturnUrl(form2.returnUrl);
23729
- if (content) {
23730
- try {
23731
- await repository.update(c.req.param("id"), content);
23732
- } catch (error2) {
23733
- if (!(error2 instanceof NotFoundError))
23734
- throw error2;
23735
- }
23831
+ const id = c.req.param("id") ?? "";
23832
+ try {
23833
+ await memory.update({ id, content });
23834
+ } catch (error2) {
23835
+ if (!(error2 instanceof NotFoundError) && !(error2 instanceof ValidationError))
23836
+ throw error2;
23736
23837
  }
23737
23838
  return c.redirect(returnUrl);
23738
- });
23739
- app.post("/memories/:id/delete", async (c) => {
23839
+ }
23840
+ async function deleteMemory(c) {
23740
23841
  const form2 = await c.req.parseBody();
23741
23842
  const returnUrl = safeReturnUrl(form2.returnUrl);
23843
+ const id = c.req.param("id") ?? "";
23742
23844
  try {
23743
- await repository.delete(c.req.param("id"));
23845
+ await memory.delete({ id });
23744
23846
  } catch (error2) {
23745
23847
  if (!(error2 instanceof NotFoundError))
23746
23848
  throw error2;
23747
23849
  }
23748
23850
  return c.redirect(returnUrl);
23749
- });
23750
- app.get("/api/workspaces", async (c) => {
23751
- const workspaces = await repository.listWorkspaces();
23752
- return c.json({ workspaces });
23753
- });
23754
- app.get("/api/memories", async (c) => {
23755
- const workspace = c.req.query("workspace");
23756
- const limitParam = c.req.query("limit");
23757
- const limit = Math.min(Math.max(Number(limitParam) || DEFAULT_LIST_LIMIT, 1), MAX_LIST_LIMIT);
23758
- const offset = Math.max(Number(c.req.query("offset")) || 0, 0);
23759
- const page = await repository.findAll({ workspace, offset, limit });
23760
- return c.json({ items: page.items.map(toMemoryJson), hasMore: page.hasMore });
23761
- });
23762
- app.get("/api/memories/:id", async (c) => {
23763
- const memory = await repository.findById(c.req.param("id"));
23764
- if (!memory)
23765
- return c.json({ error: "Memory not found." }, 404);
23766
- return c.json(toMemoryJson(memory));
23767
- });
23768
- app.post("/api/memories", async (c) => {
23769
- const body = await c.req.json().catch(() => null);
23770
- if (!body)
23771
- return c.json({ error: "Invalid JSON." }, 400);
23772
- const content = typeof body.content === "string" ? body.content.trim() : "";
23773
- if (!content)
23774
- return c.json({ error: "Content is required." }, 400);
23775
- const workspace = typeof body.workspace === "string" ? body.workspace.trim() || undefined : undefined;
23776
- const now = new Date;
23777
- const memory = await repository.save({ id: randomUUID2(), content, workspace, createdAt: now, updatedAt: now });
23778
- return c.json(toMemoryJson(memory), 201);
23779
- });
23780
- app.patch("/api/memories/:id", async (c) => {
23781
- const body = await c.req.json().catch(() => null);
23782
- if (!body)
23783
- return c.json({ error: "Invalid JSON." }, 400);
23784
- const content = typeof body.content === "string" ? body.content.trim() : "";
23785
- if (!content)
23786
- return c.json({ error: "Content is required." }, 400);
23787
- try {
23788
- const updated = await repository.update(c.req.param("id"), content);
23789
- return c.json(toMemoryJson(updated));
23790
- } catch (error2) {
23791
- if (error2 instanceof NotFoundError)
23792
- return c.json({ error: "Memory not found." }, 404);
23793
- throw error2;
23794
- }
23795
- });
23796
- app.delete("/api/memories/:id", async (c) => {
23797
- try {
23798
- await repository.delete(c.req.param("id"));
23799
- return c.body(null, 204);
23800
- } catch (error2) {
23801
- if (error2 instanceof NotFoundError)
23802
- return c.json({ error: "Memory not found." }, 404);
23803
- throw error2;
23804
- }
23805
- });
23806
- return serve({ fetch: app.fetch, port: options.port });
23807
- };
23808
- var safeReturnUrl = (value) => {
23851
+ }
23852
+ app.get("/", renderPage);
23853
+ app.post("/memories", createMemory);
23854
+ app.post("/memories/:id/update", updateMemory);
23855
+ app.post("/memories/:id/delete", deleteMemory);
23856
+ return app;
23857
+ }
23858
+ function safeReturnUrl(value) {
23809
23859
  if (typeof value === "string" && value.startsWith("/"))
23810
23860
  return value;
23811
23861
  return "/";
23812
- };
23813
- var toMemoryJson = (memory) => ({
23814
- id: memory.id,
23815
- content: memory.content,
23816
- workspace: memory.workspace ?? null,
23817
- created_at: memory.createdAt.toISOString(),
23818
- updated_at: memory.updatedAt.toISOString()
23819
- });
23862
+ }
23863
+
23864
+ // src/ui/server.tsx
23865
+ function startWebServer(memory, options) {
23866
+ const app = new Hono2;
23867
+ app.route("/", createPageRoutes(memory));
23868
+ return serve({ fetch: app.fetch, port: options.port });
23869
+ }
23820
23870
 
23821
23871
  // src/index.ts
23822
23872
  var config2 = resolveConfig();
23823
23873
  var database = openMemoryDatabase(config2.databasePath);
23824
23874
  var repository = new SqliteMemoryRepository(database);
23875
+ var memoryService = new MemoryService(repository);
23825
23876
  if (config2.uiMode) {
23826
- const server = startWebServer(repository, { port: config2.uiPort });
23827
- const addr = server.address();
23828
- const port = typeof addr === "object" && addr ? addr.port : config2.uiPort;
23829
- console.log(`agent-memory UI running at http://localhost:${port}`);
23830
- const shutdown = () => {
23877
+ let shutdown = function() {
23831
23878
  server.close();
23832
23879
  database.close();
23833
23880
  process.exit(0);
23834
23881
  };
23882
+ const server = startWebServer(memoryService, { port: config2.uiPort });
23883
+ const addr = server.address();
23884
+ const port = typeof addr === "object" && addr ? addr.port : config2.uiPort;
23885
+ console.log(`agent-memory UI running at http://localhost:${port}`);
23835
23886
  process.on("SIGINT", shutdown);
23836
23887
  process.on("SIGTERM", shutdown);
23837
23888
  } else {
23838
- const memoryService = new MemoryService(repository);
23839
23889
  const server = createMcpServer(memoryService, version2);
23840
23890
  const transport = new StdioServerTransport;
23841
23891
  try {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@jcyamacho/agent-memory",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.11",
4
+ "version": "0.0.13",
5
5
  "bin": {
6
6
  "agent-memory": "dist/index.js"
7
7
  },