@jcyamacho/agent-memory 0.0.12 → 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.
- package/dist/index.js +376 -389
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12464,7 +12464,7 @@ class StdioServerTransport {
|
|
|
12464
12464
|
}
|
|
12465
12465
|
}
|
|
12466
12466
|
// package.json
|
|
12467
|
-
var version2 = "0.0.
|
|
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
|
-
|
|
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;
|
|
@@ -19926,8 +19926,8 @@ class PersistenceError extends MemoryError {
|
|
|
19926
19926
|
}
|
|
19927
19927
|
}
|
|
19928
19928
|
|
|
19929
|
-
// src/tools/shared.ts
|
|
19930
|
-
|
|
19929
|
+
// src/mcp/tools/shared.ts
|
|
19930
|
+
function toMcpError(error2) {
|
|
19931
19931
|
if (error2 instanceof McpError) {
|
|
19932
19932
|
return error2;
|
|
19933
19933
|
}
|
|
@@ -19939,9 +19939,9 @@ var toMcpError = (error2) => {
|
|
|
19939
19939
|
}
|
|
19940
19940
|
const message = error2 instanceof Error ? error2.message : "Unknown server error.";
|
|
19941
19941
|
return new McpError(ErrorCode.InternalError, message);
|
|
19942
|
-
}
|
|
19942
|
+
}
|
|
19943
19943
|
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
19944
|
-
|
|
19944
|
+
function parseOptionalDate(value, fieldName) {
|
|
19945
19945
|
if (!value) {
|
|
19946
19946
|
return;
|
|
19947
19947
|
}
|
|
@@ -19950,19 +19950,19 @@ var parseOptionalDate = (value, fieldName) => {
|
|
|
19950
19950
|
throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
|
|
19951
19951
|
}
|
|
19952
19952
|
return date4;
|
|
19953
|
-
}
|
|
19953
|
+
}
|
|
19954
19954
|
|
|
19955
|
-
// src/tools/forget.ts
|
|
19955
|
+
// src/mcp/tools/forget.ts
|
|
19956
19956
|
var forgetInputSchema = {
|
|
19957
19957
|
id: string2().describe("The id of the memory to delete. Use the id returned by a previous recall result.")
|
|
19958
19958
|
};
|
|
19959
|
-
|
|
19959
|
+
function registerForgetTool(server, memory) {
|
|
19960
19960
|
server.registerTool("forget", {
|
|
19961
19961
|
description: "Permanently delete a memory that is wrong, obsolete, or no longer relevant. Pass the memory id from a previous recall result.",
|
|
19962
19962
|
inputSchema: forgetInputSchema
|
|
19963
19963
|
}, async ({ id }) => {
|
|
19964
19964
|
try {
|
|
19965
|
-
await
|
|
19965
|
+
await memory.delete({ id });
|
|
19966
19966
|
return {
|
|
19967
19967
|
content: [{ type: "text", text: `<memory id="${id.trim()}" deleted="true" />` }]
|
|
19968
19968
|
};
|
|
@@ -19970,56 +19970,108 @@ var registerForgetTool = (server, memoryService) => {
|
|
|
19970
19970
|
throw toMcpError(error2);
|
|
19971
19971
|
}
|
|
19972
19972
|
});
|
|
19973
|
-
}
|
|
19974
|
-
|
|
19975
|
-
// src/memory-service.ts
|
|
19976
|
-
import { randomUUID } from "node:crypto";
|
|
19973
|
+
}
|
|
19977
19974
|
|
|
19978
19975
|
// src/memory.ts
|
|
19979
19976
|
var toNormalizedScore = (value) => value;
|
|
19980
19977
|
|
|
19981
|
-
// src/
|
|
19982
|
-
var DEFAULT_LIMIT = 15;
|
|
19983
|
-
var MAX_LIMIT = 50;
|
|
19984
|
-
var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
|
|
19978
|
+
// src/ranking.ts
|
|
19985
19979
|
var RETRIEVAL_SCORE_WEIGHT = 8;
|
|
19986
19980
|
var WORKSPACE_MATCH_WEIGHT = 4;
|
|
19987
19981
|
var RECENCY_WEIGHT = 1;
|
|
19988
19982
|
var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
|
|
19989
19983
|
var GLOBAL_WORKSPACE_SCORE = 0.5;
|
|
19990
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;
|
|
19991
20033
|
|
|
19992
20034
|
class MemoryService {
|
|
19993
20035
|
repository;
|
|
19994
20036
|
constructor(repository) {
|
|
19995
20037
|
this.repository = repository;
|
|
19996
20038
|
}
|
|
19997
|
-
async
|
|
20039
|
+
async create(input) {
|
|
19998
20040
|
const content = input.content.trim();
|
|
19999
20041
|
if (!content) {
|
|
20000
20042
|
throw new ValidationError("Memory content is required.");
|
|
20001
20043
|
}
|
|
20002
|
-
|
|
20003
|
-
const memory = {
|
|
20004
|
-
id: randomUUID(),
|
|
20044
|
+
return this.repository.create({
|
|
20005
20045
|
content,
|
|
20006
|
-
workspace: normalizeOptionalString(input.workspace)
|
|
20007
|
-
|
|
20008
|
-
updatedAt: now
|
|
20009
|
-
};
|
|
20010
|
-
return this.repository.save(memory);
|
|
20046
|
+
workspace: normalizeOptionalString(input.workspace)
|
|
20047
|
+
});
|
|
20011
20048
|
}
|
|
20012
|
-
async
|
|
20049
|
+
async update(input) {
|
|
20013
20050
|
const content = input.content.trim();
|
|
20014
20051
|
if (!content)
|
|
20015
20052
|
throw new ValidationError("Memory content is required.");
|
|
20016
|
-
return this.repository.update(input.id, content);
|
|
20053
|
+
return this.repository.update({ id: input.id, content });
|
|
20017
20054
|
}
|
|
20018
|
-
async
|
|
20055
|
+
async delete(input) {
|
|
20019
20056
|
const id = input.id.trim();
|
|
20020
20057
|
if (!id)
|
|
20021
20058
|
throw new ValidationError("Memory id is required.");
|
|
20022
|
-
return this.repository.delete(id);
|
|
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();
|
|
20023
20075
|
}
|
|
20024
20076
|
async search(input) {
|
|
20025
20077
|
const terms = normalizeTerms(input.terms);
|
|
@@ -20035,19 +20087,27 @@ class MemoryService {
|
|
|
20035
20087
|
updatedBefore: input.updatedBefore
|
|
20036
20088
|
};
|
|
20037
20089
|
const results = await this.repository.search(normalizedQuery);
|
|
20038
|
-
|
|
20039
|
-
return reranked.sort((a, b) => b.score - a.score).slice(0, requestedLimit);
|
|
20090
|
+
return rerankSearchResults(results, workspace).slice(0, requestedLimit);
|
|
20040
20091
|
}
|
|
20041
20092
|
}
|
|
20042
20093
|
function normalizeLimit(value) {
|
|
20043
20094
|
if (value === undefined) {
|
|
20044
|
-
return
|
|
20095
|
+
return DEFAULT_RECALL_LIMIT;
|
|
20045
20096
|
}
|
|
20046
|
-
if (!Number.isInteger(value) || value < 1 || value >
|
|
20047
|
-
throw new ValidationError(`Limit must be an integer between 1 and ${
|
|
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}.`);
|
|
20048
20099
|
}
|
|
20049
20100
|
return value;
|
|
20050
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
|
+
}
|
|
20051
20111
|
function normalizeOptionalString(value) {
|
|
20052
20112
|
const trimmed = value?.trim();
|
|
20053
20113
|
return trimmed ? trimmed : undefined;
|
|
@@ -20056,70 +20116,29 @@ function normalizeTerms(terms) {
|
|
|
20056
20116
|
const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
|
|
20057
20117
|
return [...new Set(normalizedTerms)];
|
|
20058
20118
|
}
|
|
20059
|
-
function normalizeWorkspacePath(value) {
|
|
20060
|
-
return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
|
|
20061
|
-
}
|
|
20062
|
-
function computeWorkspaceScore(memoryWs, queryWs) {
|
|
20063
|
-
if (!queryWs) {
|
|
20064
|
-
return 0;
|
|
20065
|
-
}
|
|
20066
|
-
if (!memoryWs) {
|
|
20067
|
-
return GLOBAL_WORKSPACE_SCORE;
|
|
20068
|
-
}
|
|
20069
|
-
const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
|
|
20070
|
-
if (normalizedMemoryWs === queryWs) {
|
|
20071
|
-
return 1;
|
|
20072
|
-
}
|
|
20073
|
-
const queryLastSlashIndex = queryWs.lastIndexOf("/");
|
|
20074
|
-
const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
|
|
20075
|
-
if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
|
|
20076
|
-
return 0;
|
|
20077
|
-
}
|
|
20078
|
-
const queryParent = queryWs.slice(0, queryLastSlashIndex);
|
|
20079
|
-
const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
|
|
20080
|
-
return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
|
|
20081
|
-
}
|
|
20082
|
-
function rerankSearchResults(results, workspace) {
|
|
20083
|
-
if (results.length <= 1) {
|
|
20084
|
-
return results;
|
|
20085
|
-
}
|
|
20086
|
-
const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
|
|
20087
|
-
const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
|
|
20088
|
-
const minUpdatedAt = Math.min(...updatedAtTimes);
|
|
20089
|
-
const maxUpdatedAt = Math.max(...updatedAtTimes);
|
|
20090
|
-
return results.map((result) => {
|
|
20091
|
-
const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
|
|
20092
|
-
const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
|
|
20093
|
-
const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
|
|
20094
|
-
return {
|
|
20095
|
-
...result,
|
|
20096
|
-
score: toNormalizedScore(combinedScore)
|
|
20097
|
-
};
|
|
20098
|
-
});
|
|
20099
|
-
}
|
|
20100
20119
|
|
|
20101
|
-
// src/tools/recall.ts
|
|
20120
|
+
// src/mcp/tools/recall.ts
|
|
20102
20121
|
var recallInputSchema = {
|
|
20103
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."),
|
|
20104
|
-
limit: number2().int().min(1).max(
|
|
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."),
|
|
20105
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."),
|
|
20106
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."),
|
|
20107
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.")
|
|
20108
20127
|
};
|
|
20109
|
-
|
|
20128
|
+
function toMemoryXml(r) {
|
|
20110
20129
|
const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
|
|
20111
20130
|
const content = escapeXml(r.content);
|
|
20112
20131
|
return `<memory id="${r.id}" score="${r.score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
|
|
20113
20132
|
${content}
|
|
20114
20133
|
</memory>`;
|
|
20115
|
-
}
|
|
20116
|
-
|
|
20134
|
+
}
|
|
20135
|
+
function registerRecallTool(server, memory) {
|
|
20117
20136
|
server.registerTool("recall", {
|
|
20118
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.",
|
|
20119
20138
|
inputSchema: recallInputSchema
|
|
20120
20139
|
}, async ({ terms, limit, workspace, updated_after, updated_before }) => {
|
|
20121
20140
|
try {
|
|
20122
|
-
const results = await
|
|
20141
|
+
const results = await memory.search({
|
|
20123
20142
|
terms,
|
|
20124
20143
|
limit,
|
|
20125
20144
|
workspace,
|
|
@@ -20137,49 +20156,49 @@ ${results.map(toMemoryXml).join(`
|
|
|
20137
20156
|
throw toMcpError(error2);
|
|
20138
20157
|
}
|
|
20139
20158
|
});
|
|
20140
|
-
}
|
|
20159
|
+
}
|
|
20141
20160
|
|
|
20142
|
-
// src/tools/remember.ts
|
|
20161
|
+
// src/mcp/tools/remember.ts
|
|
20143
20162
|
var rememberInputSchema = {
|
|
20144
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."),
|
|
20145
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).")
|
|
20146
20165
|
};
|
|
20147
|
-
|
|
20166
|
+
function registerRememberTool(server, memory) {
|
|
20148
20167
|
server.registerTool("remember", {
|
|
20149
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.",
|
|
20150
20169
|
inputSchema: rememberInputSchema
|
|
20151
20170
|
}, async ({ content, workspace }) => {
|
|
20152
20171
|
try {
|
|
20153
|
-
const
|
|
20172
|
+
const savedMemory = await memory.create({
|
|
20154
20173
|
content,
|
|
20155
20174
|
workspace
|
|
20156
20175
|
});
|
|
20157
20176
|
return {
|
|
20158
|
-
content: [{ type: "text", text: `<memory id="${
|
|
20177
|
+
content: [{ type: "text", text: `<memory id="${savedMemory.id}" />` }]
|
|
20159
20178
|
};
|
|
20160
20179
|
} catch (error2) {
|
|
20161
20180
|
throw toMcpError(error2);
|
|
20162
20181
|
}
|
|
20163
20182
|
});
|
|
20164
|
-
}
|
|
20183
|
+
}
|
|
20165
20184
|
|
|
20166
|
-
// src/tools/revise.ts
|
|
20185
|
+
// src/mcp/tools/revise.ts
|
|
20167
20186
|
var reviseInputSchema = {
|
|
20168
20187
|
id: string2().describe("The id of the memory to update. Use the id returned by a previous recall result."),
|
|
20169
20188
|
content: string2().describe("The replacement content for the memory. Use a single self-contained sentence or short note. One fact per memory.")
|
|
20170
20189
|
};
|
|
20171
|
-
|
|
20190
|
+
function registerReviseTool(server, memory) {
|
|
20172
20191
|
server.registerTool("revise", {
|
|
20173
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.",
|
|
20174
20193
|
inputSchema: reviseInputSchema
|
|
20175
20194
|
}, async ({ id, content }) => {
|
|
20176
20195
|
try {
|
|
20177
|
-
const
|
|
20196
|
+
const revisedMemory = await memory.update({ id, content });
|
|
20178
20197
|
return {
|
|
20179
20198
|
content: [
|
|
20180
20199
|
{
|
|
20181
20200
|
type: "text",
|
|
20182
|
-
text: `<memory id="${
|
|
20201
|
+
text: `<memory id="${revisedMemory.id}" updated_at="${revisedMemory.updatedAt.toISOString()}" />`
|
|
20183
20202
|
}
|
|
20184
20203
|
]
|
|
20185
20204
|
};
|
|
@@ -20187,9 +20206,9 @@ var registerReviseTool = (server, memoryService) => {
|
|
|
20187
20206
|
throw toMcpError(error2);
|
|
20188
20207
|
}
|
|
20189
20208
|
});
|
|
20190
|
-
}
|
|
20209
|
+
}
|
|
20191
20210
|
|
|
20192
|
-
// src/mcp
|
|
20211
|
+
// src/mcp/server.ts
|
|
20193
20212
|
var SERVER_INSTRUCTIONS = [
|
|
20194
20213
|
"Stores decisions, corrections, and context that cannot be derived from code or git history.",
|
|
20195
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.",
|
|
@@ -20200,19 +20219,19 @@ var SERVER_INSTRUCTIONS = [
|
|
|
20200
20219
|
"Always pass workspace (the current working directory) to scope results to the active project.",
|
|
20201
20220
|
"Omit workspace only when saving a memory that applies across all projects."
|
|
20202
20221
|
].join(" ");
|
|
20203
|
-
|
|
20222
|
+
function createMcpServer(memory, version3) {
|
|
20204
20223
|
const server = new McpServer({
|
|
20205
20224
|
name: "agent-memory",
|
|
20206
20225
|
version: version3
|
|
20207
20226
|
}, {
|
|
20208
20227
|
instructions: SERVER_INSTRUCTIONS
|
|
20209
20228
|
});
|
|
20210
|
-
registerRememberTool(server,
|
|
20211
|
-
registerRecallTool(server,
|
|
20212
|
-
registerReviseTool(server,
|
|
20213
|
-
registerForgetTool(server,
|
|
20229
|
+
registerRememberTool(server, memory);
|
|
20230
|
+
registerRecallTool(server, memory);
|
|
20231
|
+
registerReviseTool(server, memory);
|
|
20232
|
+
registerForgetTool(server, memory);
|
|
20214
20233
|
return server;
|
|
20215
|
-
}
|
|
20234
|
+
}
|
|
20216
20235
|
|
|
20217
20236
|
// src/sqlite-db.ts
|
|
20218
20237
|
import { mkdirSync } from "node:fs";
|
|
@@ -20252,7 +20271,7 @@ var MEMORY_SCHEMA = `
|
|
|
20252
20271
|
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
20253
20272
|
END;
|
|
20254
20273
|
`;
|
|
20255
|
-
|
|
20274
|
+
function openMemoryDatabase(databasePath) {
|
|
20256
20275
|
try {
|
|
20257
20276
|
mkdirSync(dirname(databasePath), { recursive: true });
|
|
20258
20277
|
const database = new Database(databasePath);
|
|
@@ -20263,8 +20282,8 @@ var openMemoryDatabase = (databasePath) => {
|
|
|
20263
20282
|
cause: error2
|
|
20264
20283
|
});
|
|
20265
20284
|
}
|
|
20266
|
-
}
|
|
20267
|
-
|
|
20285
|
+
}
|
|
20286
|
+
function initializeMemoryDatabase(database) {
|
|
20268
20287
|
if (database.pragma) {
|
|
20269
20288
|
database.pragma("journal_mode = WAL");
|
|
20270
20289
|
database.pragma("synchronous = NORMAL");
|
|
@@ -20277,13 +20296,17 @@ var initializeMemoryDatabase = (database) => {
|
|
|
20277
20296
|
database.exec("PRAGMA busy_timeout = 5000");
|
|
20278
20297
|
}
|
|
20279
20298
|
database.exec(MEMORY_SCHEMA);
|
|
20280
|
-
}
|
|
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;
|
|
20281
20305
|
|
|
20282
|
-
// src/sqlite-repository.ts
|
|
20283
20306
|
class SqliteMemoryRepository {
|
|
20284
20307
|
database;
|
|
20285
20308
|
insertStatement;
|
|
20286
|
-
|
|
20309
|
+
getStatement;
|
|
20287
20310
|
updateStatement;
|
|
20288
20311
|
deleteStatement;
|
|
20289
20312
|
listWorkspacesStatement;
|
|
@@ -20304,13 +20327,21 @@ class SqliteMemoryRepository {
|
|
|
20304
20327
|
?
|
|
20305
20328
|
)
|
|
20306
20329
|
`);
|
|
20307
|
-
this.
|
|
20330
|
+
this.getStatement = database.prepare("SELECT id, content, workspace, created_at, updated_at FROM memories WHERE id = ?");
|
|
20308
20331
|
this.updateStatement = database.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?");
|
|
20309
20332
|
this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
|
|
20310
20333
|
this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
|
|
20311
20334
|
}
|
|
20312
|
-
async
|
|
20335
|
+
async create(input) {
|
|
20313
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
|
+
};
|
|
20314
20345
|
this.insertStatement.run(memory.id, memory.content, memory.workspace, memory.createdAt.getTime(), memory.updatedAt.getTime());
|
|
20315
20346
|
return memory;
|
|
20316
20347
|
} catch (error2) {
|
|
@@ -20320,6 +20351,7 @@ class SqliteMemoryRepository {
|
|
|
20320
20351
|
async search(query) {
|
|
20321
20352
|
try {
|
|
20322
20353
|
const whereParams = [toFtsQuery(query.terms)];
|
|
20354
|
+
const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
20323
20355
|
const whereClauses = ["memories_fts MATCH ?"];
|
|
20324
20356
|
if (query.updatedAfter) {
|
|
20325
20357
|
whereClauses.push("m.updated_at >= ?");
|
|
@@ -20329,7 +20361,7 @@ class SqliteMemoryRepository {
|
|
|
20329
20361
|
whereClauses.push("m.updated_at <= ?");
|
|
20330
20362
|
whereParams.push(query.updatedBefore.getTime());
|
|
20331
20363
|
}
|
|
20332
|
-
const params = [...whereParams,
|
|
20364
|
+
const params = [...whereParams, limit];
|
|
20333
20365
|
const statement = this.database.prepare(`
|
|
20334
20366
|
SELECT
|
|
20335
20367
|
m.id,
|
|
@@ -20356,19 +20388,21 @@ class SqliteMemoryRepository {
|
|
|
20356
20388
|
});
|
|
20357
20389
|
}
|
|
20358
20390
|
}
|
|
20359
|
-
async
|
|
20391
|
+
async get(id) {
|
|
20360
20392
|
try {
|
|
20361
|
-
const rows = this.
|
|
20393
|
+
const rows = this.getStatement.all(id);
|
|
20362
20394
|
const row = rows[0];
|
|
20363
20395
|
return row ? toMemoryRecord(row) : undefined;
|
|
20364
20396
|
} catch (error2) {
|
|
20365
20397
|
throw new PersistenceError("Failed to find memory.", { cause: error2 });
|
|
20366
20398
|
}
|
|
20367
20399
|
}
|
|
20368
|
-
async
|
|
20400
|
+
async list(options) {
|
|
20369
20401
|
try {
|
|
20370
20402
|
const whereClauses = [];
|
|
20371
20403
|
const params = [];
|
|
20404
|
+
const offset = options.offset ?? 0;
|
|
20405
|
+
const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
|
|
20372
20406
|
if (options.workspace) {
|
|
20373
20407
|
whereClauses.push("workspace = ?");
|
|
20374
20408
|
params.push(options.workspace);
|
|
@@ -20376,8 +20410,8 @@ class SqliteMemoryRepository {
|
|
|
20376
20410
|
whereClauses.push("workspace IS NULL");
|
|
20377
20411
|
}
|
|
20378
20412
|
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
20379
|
-
const
|
|
20380
|
-
params.push(
|
|
20413
|
+
const queryLimit = limit + 1;
|
|
20414
|
+
params.push(queryLimit, offset);
|
|
20381
20415
|
const statement = this.database.prepare(`
|
|
20382
20416
|
SELECT id, content, workspace, created_at, updated_at
|
|
20383
20417
|
FROM memories
|
|
@@ -20386,39 +20420,39 @@ class SqliteMemoryRepository {
|
|
|
20386
20420
|
LIMIT ? OFFSET ?
|
|
20387
20421
|
`);
|
|
20388
20422
|
const rows = statement.all(...params);
|
|
20389
|
-
const hasMore = rows.length >
|
|
20390
|
-
const items = (hasMore ? rows.slice(0,
|
|
20423
|
+
const hasMore = rows.length > limit;
|
|
20424
|
+
const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryRecord);
|
|
20391
20425
|
return { items, hasMore };
|
|
20392
20426
|
} catch (error2) {
|
|
20393
20427
|
throw new PersistenceError("Failed to list memories.", { cause: error2 });
|
|
20394
20428
|
}
|
|
20395
20429
|
}
|
|
20396
|
-
async update(
|
|
20430
|
+
async update(input) {
|
|
20397
20431
|
let result;
|
|
20398
20432
|
try {
|
|
20399
20433
|
const now = Date.now();
|
|
20400
|
-
result = this.updateStatement.run(content, now, id);
|
|
20434
|
+
result = this.updateStatement.run(input.content, now, input.id);
|
|
20401
20435
|
} catch (error2) {
|
|
20402
20436
|
throw new PersistenceError("Failed to update memory.", { cause: error2 });
|
|
20403
20437
|
}
|
|
20404
20438
|
if (result.changes === 0) {
|
|
20405
|
-
throw new NotFoundError(`Memory not found: ${id}`);
|
|
20439
|
+
throw new NotFoundError(`Memory not found: ${input.id}`);
|
|
20406
20440
|
}
|
|
20407
|
-
const memory = await this.
|
|
20441
|
+
const memory = await this.get(input.id);
|
|
20408
20442
|
if (!memory) {
|
|
20409
|
-
throw new NotFoundError(`Memory not found after update: ${id}`);
|
|
20443
|
+
throw new NotFoundError(`Memory not found after update: ${input.id}`);
|
|
20410
20444
|
}
|
|
20411
20445
|
return memory;
|
|
20412
20446
|
}
|
|
20413
|
-
async delete(
|
|
20447
|
+
async delete(input) {
|
|
20414
20448
|
let result;
|
|
20415
20449
|
try {
|
|
20416
|
-
result = this.deleteStatement.run(id);
|
|
20450
|
+
result = this.deleteStatement.run(input.id);
|
|
20417
20451
|
} catch (error2) {
|
|
20418
20452
|
throw new PersistenceError("Failed to delete memory.", { cause: error2 });
|
|
20419
20453
|
}
|
|
20420
20454
|
if (result.changes === 0) {
|
|
20421
|
-
throw new NotFoundError(`Memory not found: ${id}`);
|
|
20455
|
+
throw new NotFoundError(`Memory not found: ${input.id}`);
|
|
20422
20456
|
}
|
|
20423
20457
|
}
|
|
20424
20458
|
async listWorkspaces() {
|
|
@@ -20438,16 +20472,13 @@ var toMemoryRecord = (row) => ({
|
|
|
20438
20472
|
updatedAt: new Date(row.updated_at)
|
|
20439
20473
|
});
|
|
20440
20474
|
var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
|
|
20441
|
-
|
|
20475
|
+
function toFtsTerm(term) {
|
|
20442
20476
|
const escaped = term.replaceAll('"', '""');
|
|
20443
20477
|
if (term.includes(" ")) {
|
|
20444
20478
|
return `"${escaped}"`;
|
|
20445
20479
|
}
|
|
20446
20480
|
return `"${escaped}"*`;
|
|
20447
|
-
}
|
|
20448
|
-
|
|
20449
|
-
// src/ui/server.tsx
|
|
20450
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
20481
|
+
}
|
|
20451
20482
|
|
|
20452
20483
|
// node_modules/@hono/node-server/dist/index.mjs
|
|
20453
20484
|
import { createServer as createServerHTTP } from "http";
|
|
@@ -23441,182 +23472,188 @@ function jsxDEV(tag, props, key) {
|
|
|
23441
23472
|
}
|
|
23442
23473
|
|
|
23443
23474
|
// src/ui/components/create-form.tsx
|
|
23444
|
-
|
|
23445
|
-
|
|
23446
|
-
|
|
23447
|
-
|
|
23448
|
-
action: "/memories",
|
|
23449
|
-
children: [
|
|
23450
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
23451
|
-
class: "field",
|
|
23452
|
-
children: [
|
|
23453
|
-
/* @__PURE__ */ jsxDEV("label", {
|
|
23454
|
-
for: "new-content",
|
|
23455
|
-
children: "Content"
|
|
23456
|
-
}, undefined, false, undefined, this),
|
|
23457
|
-
/* @__PURE__ */ jsxDEV("textarea", {
|
|
23458
|
-
id: "new-content",
|
|
23459
|
-
name: "content",
|
|
23460
|
-
placeholder: "Fact, preference, decision...",
|
|
23461
|
-
required: true
|
|
23462
|
-
}, undefined, false, undefined, this)
|
|
23463
|
-
]
|
|
23464
|
-
}, undefined, true, undefined, this),
|
|
23465
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
23466
|
-
class: "field",
|
|
23467
|
-
children: [
|
|
23468
|
-
/* @__PURE__ */ jsxDEV("label", {
|
|
23469
|
-
for: "new-workspace",
|
|
23470
|
-
children: "Workspace (optional)"
|
|
23471
|
-
}, undefined, false, undefined, this),
|
|
23472
|
-
/* @__PURE__ */ jsxDEV("input", {
|
|
23473
|
-
id: "new-workspace",
|
|
23474
|
-
name: "workspace",
|
|
23475
|
-
type: "text",
|
|
23476
|
-
placeholder: "/path/to/project",
|
|
23477
|
-
value: workspace && workspace !== NO_WORKSPACE_FILTER ? workspace : ""
|
|
23478
|
-
}, undefined, false, undefined, this)
|
|
23479
|
-
]
|
|
23480
|
-
}, undefined, true, undefined, this),
|
|
23481
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
23482
|
-
class: "form-actions",
|
|
23483
|
-
children: /* @__PURE__ */ jsxDEV("button", {
|
|
23484
|
-
type: "submit",
|
|
23485
|
-
class: "primary",
|
|
23486
|
-
children: "Save"
|
|
23487
|
-
}, undefined, false, undefined, this)
|
|
23488
|
-
}, undefined, false, undefined, this)
|
|
23489
|
-
]
|
|
23490
|
-
}, undefined, true, undefined, this)
|
|
23491
|
-
}, undefined, false, undefined, this);
|
|
23492
|
-
|
|
23493
|
-
// src/ui/components/memory-card.tsx
|
|
23494
|
-
var MemoryCard = ({ memory, editing, showWorkspace, returnUrl }) => /* @__PURE__ */ jsxDEV("div", {
|
|
23495
|
-
class: "card",
|
|
23496
|
-
children: [
|
|
23497
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
23498
|
-
class: "card-header",
|
|
23499
|
-
children: /* @__PURE__ */ jsxDEV("div", {
|
|
23500
|
-
class: "card-meta",
|
|
23501
|
-
children: [
|
|
23502
|
-
showWorkspace && memory.workspace && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
23503
|
-
children: [
|
|
23504
|
-
/* @__PURE__ */ jsxDEV("span", {
|
|
23505
|
-
class: "badge",
|
|
23506
|
-
children: memory.workspace
|
|
23507
|
-
}, undefined, false, undefined, this),
|
|
23508
|
-
" "
|
|
23509
|
-
]
|
|
23510
|
-
}, undefined, true, undefined, this),
|
|
23511
|
-
memory.updatedAt.toLocaleString()
|
|
23512
|
-
]
|
|
23513
|
-
}, undefined, true, undefined, this)
|
|
23514
|
-
}, undefined, false, undefined, this),
|
|
23515
|
-
editing ? /* @__PURE__ */ jsxDEV("form", {
|
|
23475
|
+
function CreateForm({ workspace }) {
|
|
23476
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
23477
|
+
class: "form-card",
|
|
23478
|
+
children: /* @__PURE__ */ jsxDEV("form", {
|
|
23516
23479
|
method: "post",
|
|
23517
|
-
action:
|
|
23480
|
+
action: "/memories",
|
|
23518
23481
|
children: [
|
|
23519
|
-
/* @__PURE__ */ jsxDEV("input", {
|
|
23520
|
-
type: "hidden",
|
|
23521
|
-
name: "returnUrl",
|
|
23522
|
-
value: returnUrl
|
|
23523
|
-
}, undefined, false, undefined, this),
|
|
23524
|
-
/* @__PURE__ */ jsxDEV("textarea", {
|
|
23525
|
-
name: "content",
|
|
23526
|
-
required: true,
|
|
23527
|
-
children: memory.content
|
|
23528
|
-
}, undefined, false, undefined, this),
|
|
23529
23482
|
/* @__PURE__ */ jsxDEV("div", {
|
|
23530
|
-
class: "
|
|
23483
|
+
class: "field",
|
|
23531
23484
|
children: [
|
|
23532
|
-
/* @__PURE__ */ jsxDEV("
|
|
23533
|
-
|
|
23534
|
-
|
|
23535
|
-
children: "Save"
|
|
23485
|
+
/* @__PURE__ */ jsxDEV("label", {
|
|
23486
|
+
for: "new-content",
|
|
23487
|
+
children: "Content"
|
|
23536
23488
|
}, undefined, false, undefined, this),
|
|
23537
|
-
/* @__PURE__ */ jsxDEV("
|
|
23538
|
-
|
|
23539
|
-
|
|
23540
|
-
|
|
23489
|
+
/* @__PURE__ */ jsxDEV("textarea", {
|
|
23490
|
+
id: "new-content",
|
|
23491
|
+
name: "content",
|
|
23492
|
+
placeholder: "Fact, preference, decision...",
|
|
23493
|
+
required: true
|
|
23541
23494
|
}, undefined, false, undefined, this)
|
|
23542
23495
|
]
|
|
23543
|
-
}, undefined, true, undefined, this)
|
|
23544
|
-
]
|
|
23545
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
|
|
23546
|
-
children: [
|
|
23496
|
+
}, undefined, true, undefined, this),
|
|
23547
23497
|
/* @__PURE__ */ jsxDEV("div", {
|
|
23548
|
-
class: "
|
|
23549
|
-
children: memory.content
|
|
23550
|
-
}, undefined, false, undefined, this),
|
|
23551
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
23552
|
-
class: "card-actions",
|
|
23498
|
+
class: "field",
|
|
23553
23499
|
children: [
|
|
23554
|
-
/* @__PURE__ */ jsxDEV("
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
children: "Edit"
|
|
23500
|
+
/* @__PURE__ */ jsxDEV("label", {
|
|
23501
|
+
for: "new-workspace",
|
|
23502
|
+
children: "Workspace (optional)"
|
|
23558
23503
|
}, undefined, false, undefined, this),
|
|
23559
|
-
/* @__PURE__ */ jsxDEV("
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
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, {
|
|
23563
23537
|
children: [
|
|
23564
|
-
/* @__PURE__ */ jsxDEV("
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
value: returnUrl
|
|
23538
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
23539
|
+
class: "badge",
|
|
23540
|
+
children: memory.workspace
|
|
23568
23541
|
}, undefined, false, undefined, this),
|
|
23569
|
-
|
|
23570
|
-
type: "submit",
|
|
23571
|
-
class: "danger",
|
|
23572
|
-
children: "Delete"
|
|
23573
|
-
}, undefined, false, undefined, this)
|
|
23542
|
+
" "
|
|
23574
23543
|
]
|
|
23575
|
-
}, undefined, true, undefined, this)
|
|
23544
|
+
}, undefined, true, undefined, this),
|
|
23545
|
+
memory.updatedAt.toLocaleString()
|
|
23576
23546
|
]
|
|
23577
23547
|
}, undefined, true, undefined, this)
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
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
|
+
}
|
|
23582
23617
|
|
|
23583
23618
|
// src/ui/components/sidebar.tsx
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
23588
|
-
|
|
23589
|
-
|
|
23590
|
-
|
|
23591
|
-
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
children:
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
|
|
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
|
+
}
|
|
23610
23647
|
|
|
23611
23648
|
// src/ui/components/page.tsx
|
|
23612
|
-
|
|
23649
|
+
function buildUrl(base, overrides) {
|
|
23613
23650
|
const url = new URL(base, "http://localhost");
|
|
23614
23651
|
for (const [key, value] of Object.entries(overrides)) {
|
|
23615
23652
|
url.searchParams.set(key, value);
|
|
23616
23653
|
}
|
|
23617
23654
|
return `${url.pathname}${url.search}`;
|
|
23618
|
-
}
|
|
23619
|
-
|
|
23655
|
+
}
|
|
23656
|
+
function Page({
|
|
23620
23657
|
memories,
|
|
23621
23658
|
workspaces,
|
|
23622
23659
|
selectedWorkspace,
|
|
@@ -23624,7 +23661,7 @@ var Page = ({
|
|
|
23624
23661
|
currentPage,
|
|
23625
23662
|
hasMore,
|
|
23626
23663
|
showCreate
|
|
23627
|
-
})
|
|
23664
|
+
}) {
|
|
23628
23665
|
const params = new URLSearchParams;
|
|
23629
23666
|
if (selectedWorkspace)
|
|
23630
23667
|
params.set("workspace", selectedWorkspace);
|
|
@@ -23743,27 +23780,26 @@ var Page = ({
|
|
|
23743
23780
|
}, undefined, false, undefined, this)
|
|
23744
23781
|
]
|
|
23745
23782
|
}, undefined, true, undefined, this);
|
|
23746
|
-
}
|
|
23783
|
+
}
|
|
23747
23784
|
|
|
23748
|
-
// src/ui/
|
|
23749
|
-
var
|
|
23750
|
-
|
|
23751
|
-
var startWebServer = (repository, options) => {
|
|
23785
|
+
// src/ui/routes/page-routes.tsx
|
|
23786
|
+
var DEFAULT_LIST_LIMIT3 = 15;
|
|
23787
|
+
function createPageRoutes(memory) {
|
|
23752
23788
|
const app = new Hono2;
|
|
23753
|
-
|
|
23789
|
+
async function renderPage(c) {
|
|
23754
23790
|
const workspace = c.req.query("workspace") ?? null;
|
|
23755
23791
|
const pageNum = Math.max(Number(c.req.query("page")) || 1, 1);
|
|
23756
23792
|
const editingId = c.req.query("edit") ?? null;
|
|
23757
23793
|
const showCreate = c.req.query("create") === "1";
|
|
23758
23794
|
const isNoWorkspace = workspace === NO_WORKSPACE_FILTER;
|
|
23759
23795
|
const wsFilter = workspace && !isNoWorkspace ? workspace : undefined;
|
|
23760
|
-
const page = await
|
|
23796
|
+
const page = await memory.list({
|
|
23761
23797
|
workspace: wsFilter,
|
|
23762
23798
|
workspaceIsNull: isNoWorkspace,
|
|
23763
|
-
offset: (pageNum - 1) *
|
|
23764
|
-
limit:
|
|
23799
|
+
offset: (pageNum - 1) * DEFAULT_LIST_LIMIT3,
|
|
23800
|
+
limit: DEFAULT_LIST_LIMIT3
|
|
23765
23801
|
});
|
|
23766
|
-
const workspaces = await
|
|
23802
|
+
const workspaces = await memory.listWorkspaces();
|
|
23767
23803
|
return c.html(/* @__PURE__ */ jsxDEV(Page, {
|
|
23768
23804
|
memories: page.items,
|
|
23769
23805
|
workspaces,
|
|
@@ -23773,132 +23809,83 @@ var startWebServer = (repository, options) => {
|
|
|
23773
23809
|
hasMore: page.hasMore,
|
|
23774
23810
|
showCreate
|
|
23775
23811
|
}, undefined, false, undefined, this));
|
|
23776
|
-
}
|
|
23777
|
-
|
|
23812
|
+
}
|
|
23813
|
+
async function createMemory(c) {
|
|
23778
23814
|
const form2 = await c.req.parseBody();
|
|
23779
|
-
const content = typeof form2.content === "string" ? form2.content
|
|
23780
|
-
const workspace = typeof form2.workspace === "string" ? form2.workspace
|
|
23781
|
-
|
|
23782
|
-
|
|
23783
|
-
|
|
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
|
+
}
|
|
23784
23823
|
}
|
|
23785
|
-
const wsParam = workspace ? `/?workspace=${encodeURIComponent(workspace)}` : "/";
|
|
23824
|
+
const wsParam = workspace?.trim() ? `/?workspace=${encodeURIComponent(workspace.trim())}` : "/";
|
|
23786
23825
|
return c.redirect(wsParam);
|
|
23787
|
-
}
|
|
23788
|
-
|
|
23826
|
+
}
|
|
23827
|
+
async function updateMemory(c) {
|
|
23789
23828
|
const form2 = await c.req.parseBody();
|
|
23790
|
-
const content = typeof form2.content === "string" ? form2.content
|
|
23829
|
+
const content = typeof form2.content === "string" ? form2.content : "";
|
|
23791
23830
|
const returnUrl = safeReturnUrl(form2.returnUrl);
|
|
23792
|
-
|
|
23793
|
-
|
|
23794
|
-
|
|
23795
|
-
|
|
23796
|
-
|
|
23797
|
-
|
|
23798
|
-
}
|
|
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;
|
|
23799
23837
|
}
|
|
23800
23838
|
return c.redirect(returnUrl);
|
|
23801
|
-
}
|
|
23802
|
-
|
|
23839
|
+
}
|
|
23840
|
+
async function deleteMemory(c) {
|
|
23803
23841
|
const form2 = await c.req.parseBody();
|
|
23804
23842
|
const returnUrl = safeReturnUrl(form2.returnUrl);
|
|
23843
|
+
const id = c.req.param("id") ?? "";
|
|
23805
23844
|
try {
|
|
23806
|
-
await
|
|
23845
|
+
await memory.delete({ id });
|
|
23807
23846
|
} catch (error2) {
|
|
23808
23847
|
if (!(error2 instanceof NotFoundError))
|
|
23809
23848
|
throw error2;
|
|
23810
23849
|
}
|
|
23811
23850
|
return c.redirect(returnUrl);
|
|
23812
|
-
}
|
|
23813
|
-
app.get("/
|
|
23814
|
-
|
|
23815
|
-
|
|
23816
|
-
|
|
23817
|
-
app
|
|
23818
|
-
|
|
23819
|
-
|
|
23820
|
-
const limit = Math.min(Math.max(Number(limitParam) || DEFAULT_LIST_LIMIT, 1), MAX_LIST_LIMIT);
|
|
23821
|
-
const offset = Math.max(Number(c.req.query("offset")) || 0, 0);
|
|
23822
|
-
const page = await repository.findAll({ workspace, offset, limit });
|
|
23823
|
-
return c.json({ items: page.items.map(toMemoryJson), hasMore: page.hasMore });
|
|
23824
|
-
});
|
|
23825
|
-
app.get("/api/memories/:id", async (c) => {
|
|
23826
|
-
const memory = await repository.findById(c.req.param("id"));
|
|
23827
|
-
if (!memory)
|
|
23828
|
-
return c.json({ error: "Memory not found." }, 404);
|
|
23829
|
-
return c.json(toMemoryJson(memory));
|
|
23830
|
-
});
|
|
23831
|
-
app.post("/api/memories", async (c) => {
|
|
23832
|
-
const body = await c.req.json().catch(() => null);
|
|
23833
|
-
if (!body)
|
|
23834
|
-
return c.json({ error: "Invalid JSON." }, 400);
|
|
23835
|
-
const content = typeof body.content === "string" ? body.content.trim() : "";
|
|
23836
|
-
if (!content)
|
|
23837
|
-
return c.json({ error: "Content is required." }, 400);
|
|
23838
|
-
const workspace = typeof body.workspace === "string" ? body.workspace.trim() || undefined : undefined;
|
|
23839
|
-
const now = new Date;
|
|
23840
|
-
const memory = await repository.save({ id: randomUUID2(), content, workspace, createdAt: now, updatedAt: now });
|
|
23841
|
-
return c.json(toMemoryJson(memory), 201);
|
|
23842
|
-
});
|
|
23843
|
-
app.patch("/api/memories/:id", async (c) => {
|
|
23844
|
-
const body = await c.req.json().catch(() => null);
|
|
23845
|
-
if (!body)
|
|
23846
|
-
return c.json({ error: "Invalid JSON." }, 400);
|
|
23847
|
-
const content = typeof body.content === "string" ? body.content.trim() : "";
|
|
23848
|
-
if (!content)
|
|
23849
|
-
return c.json({ error: "Content is required." }, 400);
|
|
23850
|
-
try {
|
|
23851
|
-
const updated = await repository.update(c.req.param("id"), content);
|
|
23852
|
-
return c.json(toMemoryJson(updated));
|
|
23853
|
-
} catch (error2) {
|
|
23854
|
-
if (error2 instanceof NotFoundError)
|
|
23855
|
-
return c.json({ error: "Memory not found." }, 404);
|
|
23856
|
-
throw error2;
|
|
23857
|
-
}
|
|
23858
|
-
});
|
|
23859
|
-
app.delete("/api/memories/:id", async (c) => {
|
|
23860
|
-
try {
|
|
23861
|
-
await repository.delete(c.req.param("id"));
|
|
23862
|
-
return c.body(null, 204);
|
|
23863
|
-
} catch (error2) {
|
|
23864
|
-
if (error2 instanceof NotFoundError)
|
|
23865
|
-
return c.json({ error: "Memory not found." }, 404);
|
|
23866
|
-
throw error2;
|
|
23867
|
-
}
|
|
23868
|
-
});
|
|
23869
|
-
return serve({ fetch: app.fetch, port: options.port });
|
|
23870
|
-
};
|
|
23871
|
-
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) {
|
|
23872
23859
|
if (typeof value === "string" && value.startsWith("/"))
|
|
23873
23860
|
return value;
|
|
23874
23861
|
return "/";
|
|
23875
|
-
}
|
|
23876
|
-
|
|
23877
|
-
|
|
23878
|
-
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
|
|
23882
|
-
}
|
|
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
|
+
}
|
|
23883
23870
|
|
|
23884
23871
|
// src/index.ts
|
|
23885
23872
|
var config2 = resolveConfig();
|
|
23886
23873
|
var database = openMemoryDatabase(config2.databasePath);
|
|
23887
23874
|
var repository = new SqliteMemoryRepository(database);
|
|
23875
|
+
var memoryService = new MemoryService(repository);
|
|
23888
23876
|
if (config2.uiMode) {
|
|
23889
|
-
|
|
23890
|
-
const addr = server.address();
|
|
23891
|
-
const port = typeof addr === "object" && addr ? addr.port : config2.uiPort;
|
|
23892
|
-
console.log(`agent-memory UI running at http://localhost:${port}`);
|
|
23893
|
-
const shutdown = () => {
|
|
23877
|
+
let shutdown = function() {
|
|
23894
23878
|
server.close();
|
|
23895
23879
|
database.close();
|
|
23896
23880
|
process.exit(0);
|
|
23897
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}`);
|
|
23898
23886
|
process.on("SIGINT", shutdown);
|
|
23899
23887
|
process.on("SIGTERM", shutdown);
|
|
23900
23888
|
} else {
|
|
23901
|
-
const memoryService = new MemoryService(repository);
|
|
23902
23889
|
const server = createMcpServer(memoryService, version2);
|
|
23903
23890
|
const transport = new StdioServerTransport;
|
|
23904
23891
|
try {
|