@jcyamacho/agent-memory 0.0.12 → 0.0.15
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/README.md +58 -67
- package/dist/index.js +825 -510
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -2431,9 +2431,9 @@ var require_validate = __commonJS((exports) => {
|
|
|
2431
2431
|
}
|
|
2432
2432
|
}
|
|
2433
2433
|
function returnResults(it) {
|
|
2434
|
-
const { gen, schemaEnv, validateName, ValidationError, opts } = it;
|
|
2434
|
+
const { gen, schemaEnv, validateName, ValidationError: ValidationError2, opts } = it;
|
|
2435
2435
|
if (schemaEnv.$async) {
|
|
2436
|
-
gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${
|
|
2436
|
+
gen.if((0, codegen_1._)`${names_1.default.errors} === 0`, () => gen.return(names_1.default.data), () => gen.throw((0, codegen_1._)`new ${ValidationError2}(${names_1.default.vErrors})`));
|
|
2437
2437
|
} else {
|
|
2438
2438
|
gen.assign((0, codegen_1._)`${validateName}.errors`, names_1.default.vErrors);
|
|
2439
2439
|
if (opts.unevaluated)
|
|
@@ -2783,14 +2783,14 @@ var require_validate = __commonJS((exports) => {
|
|
|
2783
2783
|
var require_validation_error = __commonJS((exports) => {
|
|
2784
2784
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
2785
2785
|
|
|
2786
|
-
class
|
|
2786
|
+
class ValidationError2 extends Error {
|
|
2787
2787
|
constructor(errors3) {
|
|
2788
2788
|
super("validation failed");
|
|
2789
2789
|
this.errors = errors3;
|
|
2790
2790
|
this.ajv = this.validation = true;
|
|
2791
2791
|
}
|
|
2792
2792
|
}
|
|
2793
|
-
exports.default =
|
|
2793
|
+
exports.default = ValidationError2;
|
|
2794
2794
|
});
|
|
2795
2795
|
|
|
2796
2796
|
// node_modules/ajv/dist/compile/ref_error.js
|
|
@@ -12464,15 +12464,16 @@ class StdioServerTransport {
|
|
|
12464
12464
|
}
|
|
12465
12465
|
}
|
|
12466
12466
|
// package.json
|
|
12467
|
-
var version2 = "0.0.
|
|
12467
|
+
var version2 = "0.0.15";
|
|
12468
12468
|
|
|
12469
12469
|
// src/config.ts
|
|
12470
12470
|
import { homedir } from "node:os";
|
|
12471
12471
|
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
|
+
var AGENT_MEMORY_MODELS_CACHE_PATH_ENV = "AGENT_MEMORY_MODELS_CACHE_PATH";
|
|
12474
12475
|
var DEFAULT_UI_PORT = 6580;
|
|
12475
|
-
|
|
12476
|
+
function resolveConfig(environment = process.env, argv = process.argv.slice(2)) {
|
|
12476
12477
|
const { values } = parseArgs({
|
|
12477
12478
|
args: argv,
|
|
12478
12479
|
options: {
|
|
@@ -12482,12 +12483,146 @@ var resolveConfig = (environment = process.env, argv = process.argv.slice(2)) =>
|
|
|
12482
12483
|
strict: false
|
|
12483
12484
|
});
|
|
12484
12485
|
return {
|
|
12485
|
-
databasePath: environment
|
|
12486
|
+
databasePath: resolveDatabasePath(environment),
|
|
12487
|
+
modelsCachePath: resolveModelsCachePath(environment),
|
|
12486
12488
|
uiMode: Boolean(values.ui),
|
|
12487
12489
|
uiPort: Number(values.port) || DEFAULT_UI_PORT
|
|
12488
12490
|
};
|
|
12489
|
-
}
|
|
12491
|
+
}
|
|
12492
|
+
function resolveDatabasePath(environment = process.env) {
|
|
12493
|
+
return environment[AGENT_MEMORY_DB_PATH_ENV] || join(homedir(), ".config", "agent-memory", "memory.db");
|
|
12494
|
+
}
|
|
12495
|
+
function resolveModelsCachePath(environment = process.env) {
|
|
12496
|
+
return environment[AGENT_MEMORY_MODELS_CACHE_PATH_ENV] || join(homedir(), ".config", "agent-memory", "models");
|
|
12497
|
+
}
|
|
12498
|
+
|
|
12499
|
+
// src/embedding/service.ts
|
|
12500
|
+
import { mkdirSync } from "node:fs";
|
|
12501
|
+
import { pipeline, env as transformersEnv } from "@huggingface/transformers";
|
|
12502
|
+
|
|
12503
|
+
// src/errors.ts
|
|
12504
|
+
class MemoryError extends Error {
|
|
12505
|
+
code;
|
|
12506
|
+
constructor(code, message, options) {
|
|
12507
|
+
super(message, options);
|
|
12508
|
+
this.name = "MemoryError";
|
|
12509
|
+
this.code = code;
|
|
12510
|
+
}
|
|
12511
|
+
}
|
|
12512
|
+
|
|
12513
|
+
class ValidationError extends MemoryError {
|
|
12514
|
+
constructor(message) {
|
|
12515
|
+
super("VALIDATION_ERROR", message);
|
|
12516
|
+
this.name = "ValidationError";
|
|
12517
|
+
}
|
|
12518
|
+
}
|
|
12519
|
+
|
|
12520
|
+
class NotFoundError extends MemoryError {
|
|
12521
|
+
constructor(message) {
|
|
12522
|
+
super("NOT_FOUND", message);
|
|
12523
|
+
this.name = "NotFoundError";
|
|
12524
|
+
}
|
|
12525
|
+
}
|
|
12526
|
+
|
|
12527
|
+
class PersistenceError extends MemoryError {
|
|
12528
|
+
constructor(message, options) {
|
|
12529
|
+
super("PERSISTENCE_ERROR", message, options);
|
|
12530
|
+
this.name = "PersistenceError";
|
|
12531
|
+
}
|
|
12532
|
+
}
|
|
12533
|
+
|
|
12534
|
+
// src/embedding/service.ts
|
|
12535
|
+
var DEFAULT_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
|
|
12490
12536
|
|
|
12537
|
+
class EmbeddingService {
|
|
12538
|
+
options;
|
|
12539
|
+
extractorPromise;
|
|
12540
|
+
modelsCachePath;
|
|
12541
|
+
constructor(options = {}) {
|
|
12542
|
+
this.options = options;
|
|
12543
|
+
this.modelsCachePath = options.modelsCachePath ?? resolveModelsCachePath();
|
|
12544
|
+
}
|
|
12545
|
+
async createVector(text) {
|
|
12546
|
+
const normalizedText = text.trim();
|
|
12547
|
+
if (!normalizedText) {
|
|
12548
|
+
throw new ValidationError("Text is required.");
|
|
12549
|
+
}
|
|
12550
|
+
const extractor = await this.getExtractor();
|
|
12551
|
+
const embedding = await extractor(normalizedText);
|
|
12552
|
+
return normalizeVector(embedding.tolist());
|
|
12553
|
+
}
|
|
12554
|
+
getExtractor() {
|
|
12555
|
+
if (!this.extractorPromise) {
|
|
12556
|
+
configureModelsCache(this.modelsCachePath);
|
|
12557
|
+
this.extractorPromise = (this.options.createExtractor ?? createDefaultExtractor)();
|
|
12558
|
+
}
|
|
12559
|
+
return this.extractorPromise;
|
|
12560
|
+
}
|
|
12561
|
+
}
|
|
12562
|
+
function configureModelsCache(modelsCachePath) {
|
|
12563
|
+
mkdirSync(modelsCachePath, { recursive: true });
|
|
12564
|
+
transformersEnv.useFSCache = true;
|
|
12565
|
+
transformersEnv.cacheDir = modelsCachePath;
|
|
12566
|
+
}
|
|
12567
|
+
async function createDefaultExtractor() {
|
|
12568
|
+
const extractor = await pipeline("feature-extraction", DEFAULT_EMBEDDING_MODEL);
|
|
12569
|
+
return (text) => extractor(text, {
|
|
12570
|
+
pooling: "mean",
|
|
12571
|
+
normalize: true
|
|
12572
|
+
});
|
|
12573
|
+
}
|
|
12574
|
+
function normalizeVector(value) {
|
|
12575
|
+
if (value.length === 0) {
|
|
12576
|
+
throw new ValidationError("Embedding model returned an empty vector.");
|
|
12577
|
+
}
|
|
12578
|
+
const [firstItem] = value;
|
|
12579
|
+
if (typeof firstItem === "number") {
|
|
12580
|
+
return value.map((item) => {
|
|
12581
|
+
if (typeof item !== "number" || !Number.isFinite(item)) {
|
|
12582
|
+
throw new ValidationError("Embedding model returned a non-numeric vector.");
|
|
12583
|
+
}
|
|
12584
|
+
return item;
|
|
12585
|
+
});
|
|
12586
|
+
}
|
|
12587
|
+
if (Array.isArray(firstItem)) {
|
|
12588
|
+
return normalizeVector(firstItem);
|
|
12589
|
+
}
|
|
12590
|
+
throw new ValidationError("Embedding model returned an unexpected vector shape.");
|
|
12591
|
+
}
|
|
12592
|
+
// src/embedding/similarity.ts
|
|
12593
|
+
function compareVectors(left, right) {
|
|
12594
|
+
validateVector(left, "Left vector");
|
|
12595
|
+
validateVector(right, "Right vector");
|
|
12596
|
+
if (left.length !== right.length) {
|
|
12597
|
+
throw new ValidationError("Vectors must have the same length.");
|
|
12598
|
+
}
|
|
12599
|
+
let dotProduct = 0;
|
|
12600
|
+
let leftMagnitude = 0;
|
|
12601
|
+
let rightMagnitude = 0;
|
|
12602
|
+
for (const [index, leftValue] of left.entries()) {
|
|
12603
|
+
const rightValue = right[index];
|
|
12604
|
+
if (rightValue === undefined) {
|
|
12605
|
+
throw new ValidationError("Vectors must have the same length.");
|
|
12606
|
+
}
|
|
12607
|
+
dotProduct += leftValue * rightValue;
|
|
12608
|
+
leftMagnitude += leftValue * leftValue;
|
|
12609
|
+
rightMagnitude += rightValue * rightValue;
|
|
12610
|
+
}
|
|
12611
|
+
if (leftMagnitude === 0 || rightMagnitude === 0) {
|
|
12612
|
+
throw new ValidationError("Vectors must not have zero magnitude.");
|
|
12613
|
+
}
|
|
12614
|
+
return dotProduct / (Math.sqrt(leftMagnitude) * Math.sqrt(rightMagnitude));
|
|
12615
|
+
}
|
|
12616
|
+
function validateVector(vector, label) {
|
|
12617
|
+
if (vector.length === 0) {
|
|
12618
|
+
throw new ValidationError(`${label} must not be empty.`);
|
|
12619
|
+
}
|
|
12620
|
+
for (const value of vector) {
|
|
12621
|
+
if (!Number.isFinite(value)) {
|
|
12622
|
+
throw new ValidationError(`${label} must contain only finite numbers.`);
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12491
12626
|
// node_modules/zod/v3/helpers/util.js
|
|
12492
12627
|
var util;
|
|
12493
12628
|
(function(util2) {
|
|
@@ -19895,39 +20030,8 @@ var EMPTY_COMPLETION_RESULT = {
|
|
|
19895
20030
|
}
|
|
19896
20031
|
};
|
|
19897
20032
|
|
|
19898
|
-
// src/
|
|
19899
|
-
|
|
19900
|
-
code;
|
|
19901
|
-
constructor(code, message, options) {
|
|
19902
|
-
super(message, options);
|
|
19903
|
-
this.name = "MemoryError";
|
|
19904
|
-
this.code = code;
|
|
19905
|
-
}
|
|
19906
|
-
}
|
|
19907
|
-
|
|
19908
|
-
class ValidationError extends MemoryError {
|
|
19909
|
-
constructor(message) {
|
|
19910
|
-
super("VALIDATION_ERROR", message);
|
|
19911
|
-
this.name = "ValidationError";
|
|
19912
|
-
}
|
|
19913
|
-
}
|
|
19914
|
-
|
|
19915
|
-
class NotFoundError extends MemoryError {
|
|
19916
|
-
constructor(message) {
|
|
19917
|
-
super("NOT_FOUND", message);
|
|
19918
|
-
this.name = "NotFoundError";
|
|
19919
|
-
}
|
|
19920
|
-
}
|
|
19921
|
-
|
|
19922
|
-
class PersistenceError extends MemoryError {
|
|
19923
|
-
constructor(message, options) {
|
|
19924
|
-
super("PERSISTENCE_ERROR", message, options);
|
|
19925
|
-
this.name = "PersistenceError";
|
|
19926
|
-
}
|
|
19927
|
-
}
|
|
19928
|
-
|
|
19929
|
-
// src/tools/shared.ts
|
|
19930
|
-
var toMcpError = (error2) => {
|
|
20033
|
+
// src/mcp/tools/shared.ts
|
|
20034
|
+
function toMcpError(error2) {
|
|
19931
20035
|
if (error2 instanceof McpError) {
|
|
19932
20036
|
return error2;
|
|
19933
20037
|
}
|
|
@@ -19939,9 +20043,9 @@ var toMcpError = (error2) => {
|
|
|
19939
20043
|
}
|
|
19940
20044
|
const message = error2 instanceof Error ? error2.message : "Unknown server error.";
|
|
19941
20045
|
return new McpError(ErrorCode.InternalError, message);
|
|
19942
|
-
}
|
|
20046
|
+
}
|
|
19943
20047
|
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
19944
|
-
|
|
20048
|
+
function parseOptionalDate(value, fieldName) {
|
|
19945
20049
|
if (!value) {
|
|
19946
20050
|
return;
|
|
19947
20051
|
}
|
|
@@ -19950,19 +20054,19 @@ var parseOptionalDate = (value, fieldName) => {
|
|
|
19950
20054
|
throw new MemoryError("VALIDATION_ERROR", `${fieldName} must be a valid ISO 8601 datetime.`);
|
|
19951
20055
|
}
|
|
19952
20056
|
return date4;
|
|
19953
|
-
}
|
|
20057
|
+
}
|
|
19954
20058
|
|
|
19955
|
-
// src/tools/forget.ts
|
|
20059
|
+
// src/mcp/tools/forget.ts
|
|
19956
20060
|
var forgetInputSchema = {
|
|
19957
|
-
id: string2().describe("The id
|
|
20061
|
+
id: string2().describe("The memory id to delete. Use an id returned by `recall`.")
|
|
19958
20062
|
};
|
|
19959
|
-
|
|
20063
|
+
function registerForgetTool(server, memory) {
|
|
19960
20064
|
server.registerTool("forget", {
|
|
19961
|
-
description:
|
|
20065
|
+
description: 'Permanently delete a wrong or obsolete memory. Use `revise` instead when the fact still exists and only needs correction. Returns `<memory id="..." deleted="true" />`.',
|
|
19962
20066
|
inputSchema: forgetInputSchema
|
|
19963
20067
|
}, async ({ id }) => {
|
|
19964
20068
|
try {
|
|
19965
|
-
await
|
|
20069
|
+
await memory.delete({ id });
|
|
19966
20070
|
return {
|
|
19967
20071
|
content: [{ type: "text", text: `<memory id="${id.trim()}" deleted="true" />` }]
|
|
19968
20072
|
};
|
|
@@ -19970,56 +20074,127 @@ var registerForgetTool = (server, memoryService) => {
|
|
|
19970
20074
|
throw toMcpError(error2);
|
|
19971
20075
|
}
|
|
19972
20076
|
});
|
|
19973
|
-
}
|
|
19974
|
-
|
|
19975
|
-
// src/memory-service.ts
|
|
19976
|
-
import { randomUUID } from "node:crypto";
|
|
20077
|
+
}
|
|
19977
20078
|
|
|
19978
20079
|
// src/memory.ts
|
|
19979
20080
|
var toNormalizedScore = (value) => value;
|
|
19980
20081
|
|
|
19981
|
-
// src/
|
|
19982
|
-
var DEFAULT_LIMIT = 15;
|
|
19983
|
-
var MAX_LIMIT = 50;
|
|
19984
|
-
var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
|
|
20082
|
+
// src/ranking.ts
|
|
19985
20083
|
var RETRIEVAL_SCORE_WEIGHT = 8;
|
|
20084
|
+
var EMBEDDING_SIMILARITY_WEIGHT = 5;
|
|
19986
20085
|
var WORKSPACE_MATCH_WEIGHT = 4;
|
|
19987
|
-
var RECENCY_WEIGHT =
|
|
19988
|
-
var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
|
|
20086
|
+
var RECENCY_WEIGHT = 2;
|
|
20087
|
+
var MAX_COMPOSITE_SCORE = RETRIEVAL_SCORE_WEIGHT + EMBEDDING_SIMILARITY_WEIGHT + WORKSPACE_MATCH_WEIGHT + RECENCY_WEIGHT;
|
|
19989
20088
|
var GLOBAL_WORKSPACE_SCORE = 0.5;
|
|
19990
20089
|
var SIBLING_WORKSPACE_SCORE = 0.25;
|
|
20090
|
+
function rerankSearchResults(results, workspace, queryEmbedding) {
|
|
20091
|
+
if (results.length <= 1) {
|
|
20092
|
+
return results;
|
|
20093
|
+
}
|
|
20094
|
+
const normalizedQueryWs = workspace ? normalizeWorkspacePath(workspace) : undefined;
|
|
20095
|
+
const updatedAtTimes = results.map((result) => result.updatedAt.getTime());
|
|
20096
|
+
const minUpdatedAt = Math.min(...updatedAtTimes);
|
|
20097
|
+
const maxUpdatedAt = Math.max(...updatedAtTimes);
|
|
20098
|
+
return results.map((result) => {
|
|
20099
|
+
const embeddingSimilarityScore = computeEmbeddingSimilarityScore(result, queryEmbedding);
|
|
20100
|
+
const workspaceScore = computeWorkspaceScore(result.workspace, normalizedQueryWs);
|
|
20101
|
+
const recencyScore = maxUpdatedAt === minUpdatedAt ? 0 : (result.updatedAt.getTime() - minUpdatedAt) / (maxUpdatedAt - minUpdatedAt);
|
|
20102
|
+
const combinedScore = (result.score * RETRIEVAL_SCORE_WEIGHT + embeddingSimilarityScore * EMBEDDING_SIMILARITY_WEIGHT + workspaceScore * WORKSPACE_MATCH_WEIGHT + recencyScore * RECENCY_WEIGHT) / MAX_COMPOSITE_SCORE;
|
|
20103
|
+
return {
|
|
20104
|
+
...result,
|
|
20105
|
+
score: toNormalizedScore(combinedScore)
|
|
20106
|
+
};
|
|
20107
|
+
}).sort((a, b) => b.score - a.score);
|
|
20108
|
+
}
|
|
20109
|
+
function computeEmbeddingSimilarityScore(result, queryEmbedding) {
|
|
20110
|
+
return normalizeCosineSimilarity(compareVectors(result.embedding, queryEmbedding));
|
|
20111
|
+
}
|
|
20112
|
+
function normalizeWorkspacePath(value) {
|
|
20113
|
+
return value.trim().replaceAll("\\", "/").replace(/\/+/g, "/").split("/").filter(Boolean).join("/");
|
|
20114
|
+
}
|
|
20115
|
+
function normalizeCosineSimilarity(value) {
|
|
20116
|
+
return (value + 1) / 2;
|
|
20117
|
+
}
|
|
20118
|
+
function computeWorkspaceScore(memoryWs, queryWs) {
|
|
20119
|
+
if (!queryWs) {
|
|
20120
|
+
return 0;
|
|
20121
|
+
}
|
|
20122
|
+
if (!memoryWs) {
|
|
20123
|
+
return GLOBAL_WORKSPACE_SCORE;
|
|
20124
|
+
}
|
|
20125
|
+
const normalizedMemoryWs = normalizeWorkspacePath(memoryWs);
|
|
20126
|
+
if (normalizedMemoryWs === queryWs) {
|
|
20127
|
+
return 1;
|
|
20128
|
+
}
|
|
20129
|
+
const queryLastSlashIndex = queryWs.lastIndexOf("/");
|
|
20130
|
+
const memoryLastSlashIndex = normalizedMemoryWs.lastIndexOf("/");
|
|
20131
|
+
if (queryLastSlashIndex <= 0 || memoryLastSlashIndex <= 0) {
|
|
20132
|
+
return 0;
|
|
20133
|
+
}
|
|
20134
|
+
const queryParent = queryWs.slice(0, queryLastSlashIndex);
|
|
20135
|
+
const memoryParent = normalizedMemoryWs.slice(0, memoryLastSlashIndex);
|
|
20136
|
+
return memoryParent === queryParent ? SIBLING_WORKSPACE_SCORE : 0;
|
|
20137
|
+
}
|
|
20138
|
+
|
|
20139
|
+
// src/memory-service.ts
|
|
20140
|
+
var DEFAULT_RECALL_LIMIT = 15;
|
|
20141
|
+
var MAX_RECALL_LIMIT = 50;
|
|
20142
|
+
var RECALL_CANDIDATE_LIMIT_MULTIPLIER = 3;
|
|
20143
|
+
var DEFAULT_LIST_LIMIT = 15;
|
|
20144
|
+
var MAX_LIST_LIMIT = 100;
|
|
19991
20145
|
|
|
19992
20146
|
class MemoryService {
|
|
19993
20147
|
repository;
|
|
19994
|
-
|
|
20148
|
+
embeddingService;
|
|
20149
|
+
constructor(repository, embeddingService) {
|
|
19995
20150
|
this.repository = repository;
|
|
20151
|
+
this.embeddingService = embeddingService;
|
|
19996
20152
|
}
|
|
19997
|
-
async
|
|
20153
|
+
async create(input) {
|
|
19998
20154
|
const content = input.content.trim();
|
|
19999
20155
|
if (!content) {
|
|
20000
20156
|
throw new ValidationError("Memory content is required.");
|
|
20001
20157
|
}
|
|
20002
|
-
const
|
|
20003
|
-
const memory = {
|
|
20004
|
-
id: randomUUID(),
|
|
20158
|
+
const memory = await this.repository.create({
|
|
20005
20159
|
content,
|
|
20006
|
-
|
|
20007
|
-
|
|
20008
|
-
|
|
20009
|
-
|
|
20010
|
-
return this.repository.save(memory);
|
|
20160
|
+
embedding: await this.embeddingService.createVector(content),
|
|
20161
|
+
workspace: normalizeOptionalString(input.workspace)
|
|
20162
|
+
});
|
|
20163
|
+
return toPublicMemoryRecord(memory);
|
|
20011
20164
|
}
|
|
20012
|
-
async
|
|
20165
|
+
async update(input) {
|
|
20013
20166
|
const content = input.content.trim();
|
|
20014
20167
|
if (!content)
|
|
20015
20168
|
throw new ValidationError("Memory content is required.");
|
|
20016
|
-
|
|
20169
|
+
const memory = await this.repository.update({
|
|
20170
|
+
id: input.id,
|
|
20171
|
+
content,
|
|
20172
|
+
embedding: await this.embeddingService.createVector(content)
|
|
20173
|
+
});
|
|
20174
|
+
return toPublicMemoryRecord(memory);
|
|
20017
20175
|
}
|
|
20018
|
-
async
|
|
20176
|
+
async delete(input) {
|
|
20019
20177
|
const id = input.id.trim();
|
|
20020
20178
|
if (!id)
|
|
20021
20179
|
throw new ValidationError("Memory id is required.");
|
|
20022
|
-
return this.repository.delete(id);
|
|
20180
|
+
return this.repository.delete({ id });
|
|
20181
|
+
}
|
|
20182
|
+
async get(id) {
|
|
20183
|
+
const memory = await this.repository.get(id);
|
|
20184
|
+
return memory ? toPublicMemoryRecord(memory) : undefined;
|
|
20185
|
+
}
|
|
20186
|
+
async list(input) {
|
|
20187
|
+
const workspace = normalizeOptionalString(input.workspace);
|
|
20188
|
+
const page = await this.repository.list({
|
|
20189
|
+
workspace,
|
|
20190
|
+
workspaceIsNull: workspace ? false : Boolean(input.workspaceIsNull),
|
|
20191
|
+
offset: normalizeOffset(input.offset),
|
|
20192
|
+
limit: normalizeListLimit(input.limit)
|
|
20193
|
+
});
|
|
20194
|
+
return toPublicMemoryPage(page);
|
|
20195
|
+
}
|
|
20196
|
+
async listWorkspaces() {
|
|
20197
|
+
return this.repository.listWorkspaces();
|
|
20023
20198
|
}
|
|
20024
20199
|
async search(input) {
|
|
20025
20200
|
const terms = normalizeTerms(input.terms);
|
|
@@ -20034,20 +20209,56 @@ class MemoryService {
|
|
|
20034
20209
|
updatedAfter: input.updatedAfter,
|
|
20035
20210
|
updatedBefore: input.updatedBefore
|
|
20036
20211
|
};
|
|
20037
|
-
const results = await
|
|
20038
|
-
|
|
20039
|
-
|
|
20212
|
+
const [results, queryEmbedding] = await Promise.all([
|
|
20213
|
+
this.repository.search(normalizedQuery),
|
|
20214
|
+
this.embeddingService.createVector(terms.join(" "))
|
|
20215
|
+
]);
|
|
20216
|
+
return rerankSearchResults(results, workspace, queryEmbedding).slice(0, requestedLimit).map(toPublicSearchResult);
|
|
20040
20217
|
}
|
|
20041
20218
|
}
|
|
20219
|
+
function toPublicMemoryRecord(memory) {
|
|
20220
|
+
return {
|
|
20221
|
+
id: memory.id,
|
|
20222
|
+
content: memory.content,
|
|
20223
|
+
workspace: memory.workspace,
|
|
20224
|
+
createdAt: memory.createdAt,
|
|
20225
|
+
updatedAt: memory.updatedAt
|
|
20226
|
+
};
|
|
20227
|
+
}
|
|
20228
|
+
function toPublicSearchResult(result) {
|
|
20229
|
+
return {
|
|
20230
|
+
id: result.id,
|
|
20231
|
+
content: result.content,
|
|
20232
|
+
score: result.score,
|
|
20233
|
+
workspace: result.workspace,
|
|
20234
|
+
createdAt: result.createdAt,
|
|
20235
|
+
updatedAt: result.updatedAt
|
|
20236
|
+
};
|
|
20237
|
+
}
|
|
20238
|
+
function toPublicMemoryPage(page) {
|
|
20239
|
+
return {
|
|
20240
|
+
items: page.items.map(toPublicMemoryRecord),
|
|
20241
|
+
hasMore: page.hasMore
|
|
20242
|
+
};
|
|
20243
|
+
}
|
|
20042
20244
|
function normalizeLimit(value) {
|
|
20043
20245
|
if (value === undefined) {
|
|
20044
|
-
return
|
|
20246
|
+
return DEFAULT_RECALL_LIMIT;
|
|
20045
20247
|
}
|
|
20046
|
-
if (!Number.isInteger(value) || value < 1 || value >
|
|
20047
|
-
throw new ValidationError(`Limit must be an integer between 1 and ${
|
|
20248
|
+
if (!Number.isInteger(value) || value < 1 || value > MAX_RECALL_LIMIT) {
|
|
20249
|
+
throw new ValidationError(`Limit must be an integer between 1 and ${MAX_RECALL_LIMIT}.`);
|
|
20048
20250
|
}
|
|
20049
20251
|
return value;
|
|
20050
20252
|
}
|
|
20253
|
+
function normalizeOffset(value) {
|
|
20254
|
+
return Number.isInteger(value) && value && value > 0 ? value : 0;
|
|
20255
|
+
}
|
|
20256
|
+
function normalizeListLimit(value) {
|
|
20257
|
+
if (!Number.isInteger(value) || value === undefined) {
|
|
20258
|
+
return DEFAULT_LIST_LIMIT;
|
|
20259
|
+
}
|
|
20260
|
+
return Math.min(Math.max(value, 1), MAX_LIST_LIMIT);
|
|
20261
|
+
}
|
|
20051
20262
|
function normalizeOptionalString(value) {
|
|
20052
20263
|
const trimmed = value?.trim();
|
|
20053
20264
|
return trimmed ? trimmed : undefined;
|
|
@@ -20056,77 +20267,37 @@ function normalizeTerms(terms) {
|
|
|
20056
20267
|
const normalizedTerms = terms.map((term) => term.trim()).filter(Boolean);
|
|
20057
20268
|
return [...new Set(normalizedTerms)];
|
|
20058
20269
|
}
|
|
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
20270
|
|
|
20101
|
-
// src/tools/recall.ts
|
|
20271
|
+
// src/mcp/tools/recall.ts
|
|
20102
20272
|
var recallInputSchema = {
|
|
20103
|
-
terms: array(string2()).min(1).describe("Search terms
|
|
20104
|
-
limit: number2().int().min(1).max(
|
|
20105
|
-
workspace: string2().optional().describe("
|
|
20106
|
-
updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp.
|
|
20107
|
-
updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp.
|
|
20273
|
+
terms: array(string2()).min(1).describe("Search terms for lexical memory lookup. Pass 2-5 short anchor-heavy terms or exact phrases as separate entries. Prefer identifiers, commands, file paths, package names, and conventions likely to appear verbatim in the memory. Avoid vague words, full sentences, and repeating the workspace name. If recall misses, retry once with overlapping alternate terms."),
|
|
20274
|
+
limit: number2().int().min(1).max(MAX_RECALL_LIMIT).optional().describe("Maximum matches to return. Keep this small when you only need the strongest hits."),
|
|
20275
|
+
workspace: string2().optional().describe("Pass the current working directory. This strongly boosts memories from the active project while still allowing global and cross-workspace matches."),
|
|
20276
|
+
updated_after: string2().optional().describe("Only return memories updated at or after this ISO 8601 timestamp."),
|
|
20277
|
+
updated_before: string2().optional().describe("Only return memories updated at or before this ISO 8601 timestamp.")
|
|
20108
20278
|
};
|
|
20109
|
-
|
|
20279
|
+
function toMemoryXml(r) {
|
|
20110
20280
|
const workspace = r.workspace ? ` workspace="${escapeXml(r.workspace)}"` : "";
|
|
20111
20281
|
const content = escapeXml(r.content);
|
|
20112
|
-
|
|
20282
|
+
const score = Number(r.score.toFixed(3)).toString();
|
|
20283
|
+
return `<memory id="${r.id}" score="${score}"${workspace} updated_at="${r.updatedAt.toISOString()}">
|
|
20113
20284
|
${content}
|
|
20114
20285
|
</memory>`;
|
|
20115
|
-
}
|
|
20116
|
-
|
|
20286
|
+
}
|
|
20287
|
+
function registerRecallTool(server, memory) {
|
|
20117
20288
|
server.registerTool("recall", {
|
|
20118
|
-
description: "
|
|
20289
|
+
description: "Retrieve relevant memories for the current task. Use at conversation start and before design choices, conventions, or edge cases. Query with 2-5 short anchor-heavy terms or exact phrases, not questions or full sentences. `recall` is lexical-first; semantic reranking only reorders lexical matches. If it misses, retry once with overlapping alternate terms. Pass workspace. Returns `<memories>...</memories>` or a no-match hint.",
|
|
20119
20290
|
inputSchema: recallInputSchema
|
|
20120
20291
|
}, async ({ terms, limit, workspace, updated_after, updated_before }) => {
|
|
20121
20292
|
try {
|
|
20122
|
-
const results = await
|
|
20293
|
+
const results = await memory.search({
|
|
20123
20294
|
terms,
|
|
20124
20295
|
limit,
|
|
20125
20296
|
workspace,
|
|
20126
20297
|
updatedAfter: parseOptionalDate(updated_after, "updated_after"),
|
|
20127
20298
|
updatedBefore: parseOptionalDate(updated_before, "updated_before")
|
|
20128
20299
|
});
|
|
20129
|
-
const text = results.length === 0 ? "No matching memories found." : `<memories>
|
|
20300
|
+
const text = results.length === 0 ? "No matching memories found. Retry once with 1-3 alternate overlapping terms or an exact phrase likely to appear in the memory text. Recall is lexical-first, so semantic reranking cannot rescue a query with no wording overlap." : `<memories>
|
|
20130
20301
|
${results.map(toMemoryXml).join(`
|
|
20131
20302
|
`)}
|
|
20132
20303
|
</memories>`;
|
|
@@ -20137,49 +20308,49 @@ ${results.map(toMemoryXml).join(`
|
|
|
20137
20308
|
throw toMcpError(error2);
|
|
20138
20309
|
}
|
|
20139
20310
|
});
|
|
20140
|
-
}
|
|
20311
|
+
}
|
|
20141
20312
|
|
|
20142
|
-
// src/tools/remember.ts
|
|
20313
|
+
// src/mcp/tools/remember.ts
|
|
20143
20314
|
var rememberInputSchema = {
|
|
20144
|
-
content: string2().describe("
|
|
20145
|
-
workspace: string2().optional().describe("
|
|
20315
|
+
content: string2().describe("One durable fact to save. Use a single self-contained sentence or short note with concrete nouns, identifiers, commands, file paths, or exact phrases the agent is likely to reuse."),
|
|
20316
|
+
workspace: string2().optional().describe("Pass the current working directory for project-specific memories. Omit only for truly global memories.")
|
|
20146
20317
|
};
|
|
20147
|
-
|
|
20318
|
+
function registerRememberTool(server, memory) {
|
|
20148
20319
|
server.registerTool("remember", {
|
|
20149
|
-
description:
|
|
20320
|
+
description: 'Save one durable memory for later recall. Use when the user states a stable preference, corrects you, or establishes reusable project context not obvious from code or git history. Save one fact per memory. Call `recall` first; use `revise` instead of creating duplicates. Do not store secrets, temporary task state, or codebase facts. Returns `<memory id="..." />`.',
|
|
20150
20321
|
inputSchema: rememberInputSchema
|
|
20151
20322
|
}, async ({ content, workspace }) => {
|
|
20152
20323
|
try {
|
|
20153
|
-
const
|
|
20324
|
+
const savedMemory = await memory.create({
|
|
20154
20325
|
content,
|
|
20155
20326
|
workspace
|
|
20156
20327
|
});
|
|
20157
20328
|
return {
|
|
20158
|
-
content: [{ type: "text", text: `<memory id="${
|
|
20329
|
+
content: [{ type: "text", text: `<memory id="${savedMemory.id}" />` }]
|
|
20159
20330
|
};
|
|
20160
20331
|
} catch (error2) {
|
|
20161
20332
|
throw toMcpError(error2);
|
|
20162
20333
|
}
|
|
20163
20334
|
});
|
|
20164
|
-
}
|
|
20335
|
+
}
|
|
20165
20336
|
|
|
20166
|
-
// src/tools/revise.ts
|
|
20337
|
+
// src/mcp/tools/revise.ts
|
|
20167
20338
|
var reviseInputSchema = {
|
|
20168
|
-
id: string2().describe("The id
|
|
20169
|
-
content: string2().describe("The replacement
|
|
20339
|
+
id: string2().describe("The memory id to update. Use an id returned by `recall`."),
|
|
20340
|
+
content: string2().describe("The corrected replacement for that same fact. Keep it to one durable fact.")
|
|
20170
20341
|
};
|
|
20171
|
-
|
|
20342
|
+
function registerReviseTool(server, memory) {
|
|
20172
20343
|
server.registerTool("revise", {
|
|
20173
|
-
description:
|
|
20344
|
+
description: 'Replace one existing memory with corrected wording. Use after `recall` when the same fact still applies but details changed. Do not append unrelated facts or merge memories. Returns `<memory id="..." updated_at="..." />`.',
|
|
20174
20345
|
inputSchema: reviseInputSchema
|
|
20175
20346
|
}, async ({ id, content }) => {
|
|
20176
20347
|
try {
|
|
20177
|
-
const
|
|
20348
|
+
const revisedMemory = await memory.update({ id, content });
|
|
20178
20349
|
return {
|
|
20179
20350
|
content: [
|
|
20180
20351
|
{
|
|
20181
20352
|
type: "text",
|
|
20182
|
-
text: `<memory id="${
|
|
20353
|
+
text: `<memory id="${revisedMemory.id}" updated_at="${revisedMemory.updatedAt.toISOString()}" />`
|
|
20183
20354
|
}
|
|
20184
20355
|
]
|
|
20185
20356
|
};
|
|
@@ -20187,103 +20358,278 @@ var registerReviseTool = (server, memoryService) => {
|
|
|
20187
20358
|
throw toMcpError(error2);
|
|
20188
20359
|
}
|
|
20189
20360
|
});
|
|
20190
|
-
}
|
|
20361
|
+
}
|
|
20191
20362
|
|
|
20192
|
-
// src/mcp
|
|
20363
|
+
// src/mcp/server.ts
|
|
20193
20364
|
var SERVER_INSTRUCTIONS = [
|
|
20194
|
-
"
|
|
20195
|
-
"Use `recall` at
|
|
20196
|
-
"
|
|
20197
|
-
"
|
|
20198
|
-
"
|
|
20199
|
-
"Use `
|
|
20200
|
-
"
|
|
20201
|
-
"
|
|
20365
|
+
"Use this server for durable memory: user preferences, corrections, decisions, and project context not obvious from code or git history.",
|
|
20366
|
+
"Use `recall` at conversation start and before design choices, conventions, or edge cases.",
|
|
20367
|
+
"Query `recall` with 2-5 short anchor-heavy terms or exact phrases likely to appear verbatim in memory text: identifiers, commands, file paths, and conventions.",
|
|
20368
|
+
"`recall` is lexical-first; semantic reranking only reorders lexical matches.",
|
|
20369
|
+
"If `recall` misses, retry once with overlapping alternate terms.",
|
|
20370
|
+
"Use `remember` for one durable fact when the user states a preference, corrects you, or a reusable project decision becomes clear.",
|
|
20371
|
+
"Call `recall` before `remember`; if the fact already exists, use `revise` instead of creating a duplicate.",
|
|
20372
|
+
"Use `revise` to correct an existing memory and `forget` to remove a wrong or obsolete one.",
|
|
20373
|
+
"Pass workspace for project-scoped calls. Omit it only for truly global memories."
|
|
20202
20374
|
].join(" ");
|
|
20203
|
-
|
|
20375
|
+
function createMcpServer(memory, version3) {
|
|
20204
20376
|
const server = new McpServer({
|
|
20205
20377
|
name: "agent-memory",
|
|
20206
20378
|
version: version3
|
|
20207
20379
|
}, {
|
|
20208
20380
|
instructions: SERVER_INSTRUCTIONS
|
|
20209
20381
|
});
|
|
20210
|
-
registerRememberTool(server,
|
|
20211
|
-
registerRecallTool(server,
|
|
20212
|
-
registerReviseTool(server,
|
|
20213
|
-
registerForgetTool(server,
|
|
20382
|
+
registerRememberTool(server, memory);
|
|
20383
|
+
registerRecallTool(server, memory);
|
|
20384
|
+
registerReviseTool(server, memory);
|
|
20385
|
+
registerForgetTool(server, memory);
|
|
20214
20386
|
return server;
|
|
20215
|
-
}
|
|
20387
|
+
}
|
|
20216
20388
|
|
|
20217
|
-
// src/sqlite
|
|
20218
|
-
import { mkdirSync } from "node:fs";
|
|
20389
|
+
// src/sqlite/db.ts
|
|
20390
|
+
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
20219
20391
|
import { dirname } from "node:path";
|
|
20220
20392
|
import Database from "better-sqlite3";
|
|
20221
|
-
|
|
20222
|
-
|
|
20223
|
-
|
|
20224
|
-
|
|
20225
|
-
|
|
20226
|
-
|
|
20227
|
-
|
|
20228
|
-
|
|
20229
|
-
|
|
20230
|
-
|
|
20231
|
-
|
|
20232
|
-
|
|
20233
|
-
|
|
20234
|
-
|
|
20235
|
-
|
|
20236
|
-
|
|
20237
|
-
|
|
20238
|
-
|
|
20239
|
-
|
|
20240
|
-
|
|
20241
|
-
|
|
20242
|
-
|
|
20243
|
-
|
|
20244
|
-
|
|
20245
|
-
|
|
20246
|
-
|
|
20247
|
-
|
|
20248
|
-
|
|
20249
|
-
|
|
20250
|
-
|
|
20251
|
-
|
|
20252
|
-
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
|
|
20393
|
+
|
|
20394
|
+
// src/sqlite/memory-schema.ts
|
|
20395
|
+
function createMemoriesTable(database, options) {
|
|
20396
|
+
const embeddingColumn = getEmbeddingColumnSql(options.embeddingColumn);
|
|
20397
|
+
database.exec(`
|
|
20398
|
+
CREATE TABLE IF NOT EXISTS memories (
|
|
20399
|
+
id TEXT PRIMARY KEY,
|
|
20400
|
+
content TEXT NOT NULL,
|
|
20401
|
+
workspace TEXT,
|
|
20402
|
+
${embeddingColumn}
|
|
20403
|
+
created_at INTEGER NOT NULL,
|
|
20404
|
+
updated_at INTEGER NOT NULL
|
|
20405
|
+
);
|
|
20406
|
+
`);
|
|
20407
|
+
}
|
|
20408
|
+
function createMemoryIndexes(database) {
|
|
20409
|
+
database.exec(`
|
|
20410
|
+
CREATE INDEX IF NOT EXISTS idx_memories_created_at ON memories(created_at);
|
|
20411
|
+
CREATE INDEX IF NOT EXISTS idx_memories_workspace ON memories(workspace);
|
|
20412
|
+
`);
|
|
20413
|
+
}
|
|
20414
|
+
function createMemorySearchArtifacts(database, rebuild = false) {
|
|
20415
|
+
database.exec(`
|
|
20416
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
20417
|
+
content,
|
|
20418
|
+
content = 'memories',
|
|
20419
|
+
content_rowid = 'rowid',
|
|
20420
|
+
tokenize = 'porter unicode61'
|
|
20421
|
+
);
|
|
20422
|
+
|
|
20423
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
20424
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
20425
|
+
END;
|
|
20426
|
+
|
|
20427
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
20428
|
+
INSERT INTO memories_fts(memories_fts, rowid, content)
|
|
20429
|
+
VALUES ('delete', old.rowid, old.content);
|
|
20430
|
+
END;
|
|
20431
|
+
|
|
20432
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
20433
|
+
INSERT INTO memories_fts(memories_fts, rowid, content)
|
|
20434
|
+
VALUES ('delete', old.rowid, old.content);
|
|
20435
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.rowid, new.content);
|
|
20436
|
+
END;
|
|
20437
|
+
`);
|
|
20438
|
+
if (rebuild) {
|
|
20439
|
+
database.exec("INSERT INTO memories_fts(memories_fts) VALUES ('rebuild')");
|
|
20440
|
+
}
|
|
20441
|
+
}
|
|
20442
|
+
function dropMemorySearchArtifacts(database) {
|
|
20443
|
+
database.exec(`
|
|
20444
|
+
DROP TRIGGER IF EXISTS memories_ai;
|
|
20445
|
+
DROP TRIGGER IF EXISTS memories_ad;
|
|
20446
|
+
DROP TRIGGER IF EXISTS memories_au;
|
|
20447
|
+
DROP TABLE IF EXISTS memories_fts;
|
|
20448
|
+
`);
|
|
20449
|
+
}
|
|
20450
|
+
function getEmbeddingColumnSql(mode) {
|
|
20451
|
+
switch (mode) {
|
|
20452
|
+
case "omit":
|
|
20453
|
+
return "";
|
|
20454
|
+
case "nullable":
|
|
20455
|
+
return `embedding BLOB,
|
|
20456
|
+
`;
|
|
20457
|
+
case "required":
|
|
20458
|
+
return `embedding BLOB NOT NULL,
|
|
20459
|
+
`;
|
|
20460
|
+
}
|
|
20461
|
+
}
|
|
20462
|
+
|
|
20463
|
+
// src/sqlite/migrations/001-create-memory-schema.ts
|
|
20464
|
+
var createMemorySchemaMigration = {
|
|
20465
|
+
version: 1,
|
|
20466
|
+
async up(database) {
|
|
20467
|
+
createMemoriesTable(database, { embeddingColumn: "omit" });
|
|
20468
|
+
createMemoryIndexes(database);
|
|
20469
|
+
createMemorySearchArtifacts(database);
|
|
20470
|
+
}
|
|
20471
|
+
};
|
|
20472
|
+
|
|
20473
|
+
// src/sqlite/embedding-codec.ts
|
|
20474
|
+
var FLOAT32_BYTE_WIDTH = 4;
|
|
20475
|
+
function encodeEmbedding(vector) {
|
|
20476
|
+
const typedArray = Float32Array.from(vector);
|
|
20477
|
+
return new Uint8Array(typedArray.buffer.slice(0));
|
|
20478
|
+
}
|
|
20479
|
+
function decodeEmbedding(value) {
|
|
20480
|
+
const bytes = toUint8Array(value);
|
|
20481
|
+
if (bytes.byteLength === 0) {
|
|
20482
|
+
throw new Error("Embedding blob is empty.");
|
|
20483
|
+
}
|
|
20484
|
+
if (bytes.byteLength % FLOAT32_BYTE_WIDTH !== 0) {
|
|
20485
|
+
throw new Error("Embedding blob length is not a multiple of 4.");
|
|
20486
|
+
}
|
|
20487
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
20488
|
+
const vector = [];
|
|
20489
|
+
for (let offset = 0;offset < bytes.byteLength; offset += FLOAT32_BYTE_WIDTH) {
|
|
20490
|
+
vector.push(view.getFloat32(offset, true));
|
|
20491
|
+
}
|
|
20492
|
+
return vector;
|
|
20493
|
+
}
|
|
20494
|
+
function toUint8Array(value) {
|
|
20495
|
+
if (value instanceof Uint8Array) {
|
|
20496
|
+
return value;
|
|
20497
|
+
}
|
|
20498
|
+
if (value instanceof ArrayBuffer) {
|
|
20499
|
+
return new Uint8Array(value);
|
|
20500
|
+
}
|
|
20501
|
+
throw new Error("Expected embedding blob as Uint8Array or ArrayBuffer.");
|
|
20502
|
+
}
|
|
20503
|
+
|
|
20504
|
+
// src/sqlite/migrations/002-add-memory-embedding.ts
|
|
20505
|
+
function createAddMemoryEmbeddingMigration(embeddingService = new EmbeddingService) {
|
|
20506
|
+
return {
|
|
20507
|
+
version: 2,
|
|
20508
|
+
async up(database) {
|
|
20509
|
+
database.exec("ALTER TABLE memories ADD COLUMN embedding BLOB");
|
|
20510
|
+
const rows = database.prepare("SELECT id, content FROM memories ORDER BY created_at ASC").all();
|
|
20511
|
+
const updateStatement = database.prepare("UPDATE memories SET embedding = ? WHERE id = ?");
|
|
20512
|
+
for (const row of rows) {
|
|
20513
|
+
const embedding = await embeddingService.createVector(row.content);
|
|
20514
|
+
updateStatement.run(encodeEmbedding(embedding), row.id);
|
|
20515
|
+
}
|
|
20516
|
+
const nullRows = database.prepare("SELECT COUNT(*) AS count FROM memories WHERE embedding IS NULL").all();
|
|
20517
|
+
if ((nullRows[0]?.count ?? 0) > 0) {
|
|
20518
|
+
throw new Error("Failed to backfill embeddings for all memories.");
|
|
20519
|
+
}
|
|
20520
|
+
dropMemorySearchArtifacts(database);
|
|
20521
|
+
database.exec("ALTER TABLE memories RENAME TO memories_old");
|
|
20522
|
+
createMemoriesTable(database, { embeddingColumn: "required" });
|
|
20523
|
+
database.exec(`
|
|
20524
|
+
INSERT INTO memories (id, content, workspace, embedding, created_at, updated_at)
|
|
20525
|
+
SELECT id, content, workspace, embedding, created_at, updated_at
|
|
20526
|
+
FROM memories_old
|
|
20527
|
+
`);
|
|
20528
|
+
database.exec("DROP TABLE memories_old");
|
|
20529
|
+
createMemoryIndexes(database);
|
|
20530
|
+
createMemorySearchArtifacts(database, true);
|
|
20531
|
+
}
|
|
20532
|
+
};
|
|
20533
|
+
}
|
|
20534
|
+
var addMemoryEmbeddingMigration = createAddMemoryEmbeddingMigration();
|
|
20535
|
+
|
|
20536
|
+
// src/sqlite/migrations/index.ts
|
|
20537
|
+
function createMemoryMigrations(embeddingService = new EmbeddingService) {
|
|
20538
|
+
return [createMemorySchemaMigration, createAddMemoryEmbeddingMigration(embeddingService)];
|
|
20539
|
+
}
|
|
20540
|
+
var MEMORY_MIGRATIONS = createMemoryMigrations();
|
|
20541
|
+
|
|
20542
|
+
// src/sqlite/db.ts
|
|
20543
|
+
var PRAGMA_STATEMENTS = [
|
|
20544
|
+
"journal_mode = WAL",
|
|
20545
|
+
"synchronous = NORMAL",
|
|
20546
|
+
"foreign_keys = ON",
|
|
20547
|
+
"busy_timeout = 5000"
|
|
20548
|
+
];
|
|
20549
|
+
async function openMemoryDatabase(databasePath, options = {}) {
|
|
20550
|
+
let database;
|
|
20256
20551
|
try {
|
|
20257
|
-
|
|
20258
|
-
|
|
20259
|
-
initializeMemoryDatabase(database);
|
|
20552
|
+
mkdirSync2(dirname(databasePath), { recursive: true });
|
|
20553
|
+
database = new Database(databasePath);
|
|
20554
|
+
await initializeMemoryDatabase(database, createMemoryMigrations(options.embeddingService));
|
|
20260
20555
|
return database;
|
|
20261
20556
|
} catch (error2) {
|
|
20557
|
+
database?.close();
|
|
20558
|
+
if (error2 instanceof PersistenceError) {
|
|
20559
|
+
throw error2;
|
|
20560
|
+
}
|
|
20262
20561
|
throw new PersistenceError("Failed to initialize the SQLite database.", {
|
|
20263
20562
|
cause: error2
|
|
20264
20563
|
});
|
|
20265
20564
|
}
|
|
20266
|
-
}
|
|
20267
|
-
|
|
20565
|
+
}
|
|
20566
|
+
async function initializeMemoryDatabase(database, migrations = MEMORY_MIGRATIONS) {
|
|
20567
|
+
try {
|
|
20568
|
+
applyPragmas(database);
|
|
20569
|
+
await runSqliteMigrations(database, migrations);
|
|
20570
|
+
} catch (error2) {
|
|
20571
|
+
throw new PersistenceError("Failed to initialize the SQLite database.", {
|
|
20572
|
+
cause: error2
|
|
20573
|
+
});
|
|
20574
|
+
}
|
|
20575
|
+
}
|
|
20576
|
+
async function runSqliteMigrations(database, migrations) {
|
|
20577
|
+
validateMigrations(migrations);
|
|
20578
|
+
let currentVersion = getUserVersion(database);
|
|
20579
|
+
for (const migration of migrations) {
|
|
20580
|
+
if (migration.version <= currentVersion) {
|
|
20581
|
+
continue;
|
|
20582
|
+
}
|
|
20583
|
+
database.exec("BEGIN");
|
|
20584
|
+
try {
|
|
20585
|
+
await migration.up(database);
|
|
20586
|
+
setUserVersion(database, migration.version);
|
|
20587
|
+
database.exec("COMMIT");
|
|
20588
|
+
currentVersion = migration.version;
|
|
20589
|
+
} catch (error2) {
|
|
20590
|
+
try {
|
|
20591
|
+
database.exec("ROLLBACK");
|
|
20592
|
+
} catch {}
|
|
20593
|
+
throw error2;
|
|
20594
|
+
}
|
|
20595
|
+
}
|
|
20596
|
+
}
|
|
20597
|
+
function applyPragmas(database) {
|
|
20268
20598
|
if (database.pragma) {
|
|
20269
|
-
|
|
20270
|
-
|
|
20271
|
-
|
|
20272
|
-
|
|
20273
|
-
} else {
|
|
20274
|
-
database.exec("PRAGMA journal_mode = WAL");
|
|
20275
|
-
database.exec("PRAGMA synchronous = NORMAL");
|
|
20276
|
-
database.exec("PRAGMA foreign_keys = ON");
|
|
20277
|
-
database.exec("PRAGMA busy_timeout = 5000");
|
|
20599
|
+
for (const statement of PRAGMA_STATEMENTS) {
|
|
20600
|
+
database.pragma(statement);
|
|
20601
|
+
}
|
|
20602
|
+
return;
|
|
20278
20603
|
}
|
|
20279
|
-
|
|
20280
|
-
};
|
|
20604
|
+
for (const statement of PRAGMA_STATEMENTS) {
|
|
20605
|
+
database.exec(`PRAGMA ${statement}`);
|
|
20606
|
+
}
|
|
20607
|
+
}
|
|
20608
|
+
function getUserVersion(database) {
|
|
20609
|
+
const rows = database.prepare("PRAGMA user_version").all();
|
|
20610
|
+
return rows[0]?.user_version ?? 0;
|
|
20611
|
+
}
|
|
20612
|
+
function setUserVersion(database, version3) {
|
|
20613
|
+
database.exec(`PRAGMA user_version = ${version3}`);
|
|
20614
|
+
}
|
|
20615
|
+
function validateMigrations(migrations) {
|
|
20616
|
+
let previousVersion = 0;
|
|
20617
|
+
for (const migration of migrations) {
|
|
20618
|
+
if (!Number.isInteger(migration.version) || migration.version <= previousVersion) {
|
|
20619
|
+
throw new Error("SQLite migrations must use strictly increasing versions.");
|
|
20620
|
+
}
|
|
20621
|
+
previousVersion = migration.version;
|
|
20622
|
+
}
|
|
20623
|
+
}
|
|
20624
|
+
// src/sqlite/repository.ts
|
|
20625
|
+
import { randomUUID } from "node:crypto";
|
|
20626
|
+
var DEFAULT_SEARCH_LIMIT = 15;
|
|
20627
|
+
var DEFAULT_LIST_LIMIT2 = 15;
|
|
20281
20628
|
|
|
20282
|
-
// src/sqlite-repository.ts
|
|
20283
20629
|
class SqliteMemoryRepository {
|
|
20284
20630
|
database;
|
|
20285
20631
|
insertStatement;
|
|
20286
|
-
|
|
20632
|
+
getStatement;
|
|
20287
20633
|
updateStatement;
|
|
20288
20634
|
deleteStatement;
|
|
20289
20635
|
listWorkspacesStatement;
|
|
@@ -20294,6 +20640,7 @@ class SqliteMemoryRepository {
|
|
|
20294
20640
|
id,
|
|
20295
20641
|
content,
|
|
20296
20642
|
workspace,
|
|
20643
|
+
embedding,
|
|
20297
20644
|
created_at,
|
|
20298
20645
|
updated_at
|
|
20299
20646
|
) VALUES (
|
|
@@ -20301,17 +20648,27 @@ class SqliteMemoryRepository {
|
|
|
20301
20648
|
?,
|
|
20302
20649
|
?,
|
|
20303
20650
|
?,
|
|
20651
|
+
?,
|
|
20304
20652
|
?
|
|
20305
20653
|
)
|
|
20306
20654
|
`);
|
|
20307
|
-
this.
|
|
20308
|
-
this.updateStatement = database.prepare("UPDATE memories SET content = ?, updated_at = ? WHERE id = ?");
|
|
20655
|
+
this.getStatement = database.prepare("SELECT id, content, workspace, embedding, created_at, updated_at FROM memories WHERE id = ?");
|
|
20656
|
+
this.updateStatement = database.prepare("UPDATE memories SET content = ?, embedding = ?, updated_at = ? WHERE id = ?");
|
|
20309
20657
|
this.deleteStatement = database.prepare("DELETE FROM memories WHERE id = ?");
|
|
20310
20658
|
this.listWorkspacesStatement = database.prepare("SELECT DISTINCT workspace FROM memories WHERE workspace IS NOT NULL ORDER BY workspace");
|
|
20311
20659
|
}
|
|
20312
|
-
async
|
|
20660
|
+
async create(input) {
|
|
20313
20661
|
try {
|
|
20314
|
-
|
|
20662
|
+
const now = new Date;
|
|
20663
|
+
const memory = {
|
|
20664
|
+
id: randomUUID(),
|
|
20665
|
+
content: input.content,
|
|
20666
|
+
embedding: input.embedding,
|
|
20667
|
+
workspace: input.workspace,
|
|
20668
|
+
createdAt: now,
|
|
20669
|
+
updatedAt: now
|
|
20670
|
+
};
|
|
20671
|
+
this.insertStatement.run(memory.id, memory.content, memory.workspace, encodeEmbedding(memory.embedding), memory.createdAt.getTime(), memory.updatedAt.getTime());
|
|
20315
20672
|
return memory;
|
|
20316
20673
|
} catch (error2) {
|
|
20317
20674
|
throw new PersistenceError("Failed to save memory.", { cause: error2 });
|
|
@@ -20320,6 +20677,7 @@ class SqliteMemoryRepository {
|
|
|
20320
20677
|
async search(query) {
|
|
20321
20678
|
try {
|
|
20322
20679
|
const whereParams = [toFtsQuery(query.terms)];
|
|
20680
|
+
const limit = query.limit ?? DEFAULT_SEARCH_LIMIT;
|
|
20323
20681
|
const whereClauses = ["memories_fts MATCH ?"];
|
|
20324
20682
|
if (query.updatedAfter) {
|
|
20325
20683
|
whereClauses.push("m.updated_at >= ?");
|
|
@@ -20329,12 +20687,13 @@ class SqliteMemoryRepository {
|
|
|
20329
20687
|
whereClauses.push("m.updated_at <= ?");
|
|
20330
20688
|
whereParams.push(query.updatedBefore.getTime());
|
|
20331
20689
|
}
|
|
20332
|
-
const params = [...whereParams,
|
|
20690
|
+
const params = [...whereParams, limit];
|
|
20333
20691
|
const statement = this.database.prepare(`
|
|
20334
20692
|
SELECT
|
|
20335
20693
|
m.id,
|
|
20336
20694
|
m.content,
|
|
20337
20695
|
m.workspace,
|
|
20696
|
+
m.embedding,
|
|
20338
20697
|
m.created_at,
|
|
20339
20698
|
m.updated_at,
|
|
20340
20699
|
MAX(0, -bm25(memories_fts)) AS score
|
|
@@ -20347,7 +20706,7 @@ class SqliteMemoryRepository {
|
|
|
20347
20706
|
const rows = statement.all(...params);
|
|
20348
20707
|
const maxScore = Math.max(...rows.map((row) => row.score), 0);
|
|
20349
20708
|
return rows.map((row) => ({
|
|
20350
|
-
...
|
|
20709
|
+
...toMemoryEntity(row),
|
|
20351
20710
|
score: toNormalizedScore(maxScore > 0 ? row.score / maxScore : 0)
|
|
20352
20711
|
}));
|
|
20353
20712
|
} catch (error2) {
|
|
@@ -20356,19 +20715,21 @@ class SqliteMemoryRepository {
|
|
|
20356
20715
|
});
|
|
20357
20716
|
}
|
|
20358
20717
|
}
|
|
20359
|
-
async
|
|
20718
|
+
async get(id) {
|
|
20360
20719
|
try {
|
|
20361
|
-
const rows = this.
|
|
20720
|
+
const rows = this.getStatement.all(id);
|
|
20362
20721
|
const row = rows[0];
|
|
20363
|
-
return row ?
|
|
20722
|
+
return row ? toMemoryEntity(row) : undefined;
|
|
20364
20723
|
} catch (error2) {
|
|
20365
20724
|
throw new PersistenceError("Failed to find memory.", { cause: error2 });
|
|
20366
20725
|
}
|
|
20367
20726
|
}
|
|
20368
|
-
async
|
|
20727
|
+
async list(options) {
|
|
20369
20728
|
try {
|
|
20370
20729
|
const whereClauses = [];
|
|
20371
20730
|
const params = [];
|
|
20731
|
+
const offset = options.offset ?? 0;
|
|
20732
|
+
const limit = options.limit ?? DEFAULT_LIST_LIMIT2;
|
|
20372
20733
|
if (options.workspace) {
|
|
20373
20734
|
whereClauses.push("workspace = ?");
|
|
20374
20735
|
params.push(options.workspace);
|
|
@@ -20376,49 +20737,49 @@ class SqliteMemoryRepository {
|
|
|
20376
20737
|
whereClauses.push("workspace IS NULL");
|
|
20377
20738
|
}
|
|
20378
20739
|
const whereClause = whereClauses.length > 0 ? `WHERE ${whereClauses.join(" AND ")}` : "";
|
|
20379
|
-
const
|
|
20380
|
-
params.push(
|
|
20740
|
+
const queryLimit = limit + 1;
|
|
20741
|
+
params.push(queryLimit, offset);
|
|
20381
20742
|
const statement = this.database.prepare(`
|
|
20382
|
-
SELECT id, content, workspace, created_at, updated_at
|
|
20743
|
+
SELECT id, content, workspace, embedding, created_at, updated_at
|
|
20383
20744
|
FROM memories
|
|
20384
20745
|
${whereClause}
|
|
20385
20746
|
ORDER BY created_at DESC
|
|
20386
20747
|
LIMIT ? OFFSET ?
|
|
20387
20748
|
`);
|
|
20388
20749
|
const rows = statement.all(...params);
|
|
20389
|
-
const hasMore = rows.length >
|
|
20390
|
-
const items = (hasMore ? rows.slice(0,
|
|
20750
|
+
const hasMore = rows.length > limit;
|
|
20751
|
+
const items = (hasMore ? rows.slice(0, limit) : rows).map(toMemoryEntity);
|
|
20391
20752
|
return { items, hasMore };
|
|
20392
20753
|
} catch (error2) {
|
|
20393
20754
|
throw new PersistenceError("Failed to list memories.", { cause: error2 });
|
|
20394
20755
|
}
|
|
20395
20756
|
}
|
|
20396
|
-
async update(
|
|
20757
|
+
async update(input) {
|
|
20397
20758
|
let result;
|
|
20398
20759
|
try {
|
|
20399
20760
|
const now = Date.now();
|
|
20400
|
-
result = this.updateStatement.run(content, now, id);
|
|
20761
|
+
result = this.updateStatement.run(input.content, encodeEmbedding(input.embedding), now, input.id);
|
|
20401
20762
|
} catch (error2) {
|
|
20402
20763
|
throw new PersistenceError("Failed to update memory.", { cause: error2 });
|
|
20403
20764
|
}
|
|
20404
20765
|
if (result.changes === 0) {
|
|
20405
|
-
throw new NotFoundError(`Memory not found: ${id}`);
|
|
20766
|
+
throw new NotFoundError(`Memory not found: ${input.id}`);
|
|
20406
20767
|
}
|
|
20407
|
-
const memory = await this.
|
|
20768
|
+
const memory = await this.get(input.id);
|
|
20408
20769
|
if (!memory) {
|
|
20409
|
-
throw new NotFoundError(`Memory not found after update: ${id}`);
|
|
20770
|
+
throw new NotFoundError(`Memory not found after update: ${input.id}`);
|
|
20410
20771
|
}
|
|
20411
20772
|
return memory;
|
|
20412
20773
|
}
|
|
20413
|
-
async delete(
|
|
20774
|
+
async delete(input) {
|
|
20414
20775
|
let result;
|
|
20415
20776
|
try {
|
|
20416
|
-
result = this.deleteStatement.run(id);
|
|
20777
|
+
result = this.deleteStatement.run(input.id);
|
|
20417
20778
|
} catch (error2) {
|
|
20418
20779
|
throw new PersistenceError("Failed to delete memory.", { cause: error2 });
|
|
20419
20780
|
}
|
|
20420
20781
|
if (result.changes === 0) {
|
|
20421
|
-
throw new NotFoundError(`Memory not found: ${id}`);
|
|
20782
|
+
throw new NotFoundError(`Memory not found: ${input.id}`);
|
|
20422
20783
|
}
|
|
20423
20784
|
}
|
|
20424
20785
|
async listWorkspaces() {
|
|
@@ -20430,25 +20791,22 @@ class SqliteMemoryRepository {
|
|
|
20430
20791
|
}
|
|
20431
20792
|
}
|
|
20432
20793
|
}
|
|
20433
|
-
var
|
|
20794
|
+
var toMemoryEntity = (row) => ({
|
|
20434
20795
|
id: row.id,
|
|
20435
20796
|
content: row.content,
|
|
20797
|
+
embedding: decodeEmbedding(row.embedding),
|
|
20436
20798
|
workspace: row.workspace ?? undefined,
|
|
20437
20799
|
createdAt: new Date(row.created_at),
|
|
20438
20800
|
updatedAt: new Date(row.updated_at)
|
|
20439
20801
|
});
|
|
20440
20802
|
var toFtsQuery = (terms) => terms.map(toFtsTerm).join(" OR ");
|
|
20441
|
-
|
|
20803
|
+
function toFtsTerm(term) {
|
|
20442
20804
|
const escaped = term.replaceAll('"', '""');
|
|
20443
20805
|
if (term.includes(" ")) {
|
|
20444
20806
|
return `"${escaped}"`;
|
|
20445
20807
|
}
|
|
20446
20808
|
return `"${escaped}"*`;
|
|
20447
|
-
}
|
|
20448
|
-
|
|
20449
|
-
// src/ui/server.tsx
|
|
20450
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
20451
|
-
|
|
20809
|
+
}
|
|
20452
20810
|
// node_modules/@hono/node-server/dist/index.mjs
|
|
20453
20811
|
import { createServer as createServerHTTP } from "http";
|
|
20454
20812
|
import { Http2ServerRequest as Http2ServerRequest2 } from "http2";
|
|
@@ -23441,182 +23799,188 @@ function jsxDEV(tag, props, key) {
|
|
|
23441
23799
|
}
|
|
23442
23800
|
|
|
23443
23801
|
// 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", {
|
|
23802
|
+
function CreateForm({ workspace }) {
|
|
23803
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
23804
|
+
class: "form-card",
|
|
23805
|
+
children: /* @__PURE__ */ jsxDEV("form", {
|
|
23516
23806
|
method: "post",
|
|
23517
|
-
action:
|
|
23807
|
+
action: "/memories",
|
|
23518
23808
|
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
23809
|
/* @__PURE__ */ jsxDEV("div", {
|
|
23530
|
-
class: "
|
|
23810
|
+
class: "field",
|
|
23531
23811
|
children: [
|
|
23532
|
-
/* @__PURE__ */ jsxDEV("
|
|
23533
|
-
|
|
23534
|
-
|
|
23535
|
-
children: "Save"
|
|
23812
|
+
/* @__PURE__ */ jsxDEV("label", {
|
|
23813
|
+
for: "new-content",
|
|
23814
|
+
children: "Content"
|
|
23536
23815
|
}, undefined, false, undefined, this),
|
|
23537
|
-
/* @__PURE__ */ jsxDEV("
|
|
23538
|
-
|
|
23539
|
-
|
|
23540
|
-
|
|
23816
|
+
/* @__PURE__ */ jsxDEV("textarea", {
|
|
23817
|
+
id: "new-content",
|
|
23818
|
+
name: "content",
|
|
23819
|
+
placeholder: "Fact, preference, decision...",
|
|
23820
|
+
required: true
|
|
23541
23821
|
}, undefined, false, undefined, this)
|
|
23542
23822
|
]
|
|
23543
|
-
}, undefined, true, undefined, this)
|
|
23544
|
-
]
|
|
23545
|
-
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
|
|
23546
|
-
children: [
|
|
23823
|
+
}, undefined, true, undefined, this),
|
|
23547
23824
|
/* @__PURE__ */ jsxDEV("div", {
|
|
23548
|
-
class: "
|
|
23549
|
-
children: memory.content
|
|
23550
|
-
}, undefined, false, undefined, this),
|
|
23551
|
-
/* @__PURE__ */ jsxDEV("div", {
|
|
23552
|
-
class: "card-actions",
|
|
23825
|
+
class: "field",
|
|
23553
23826
|
children: [
|
|
23554
|
-
/* @__PURE__ */ jsxDEV("
|
|
23555
|
-
|
|
23556
|
-
|
|
23557
|
-
children: "Edit"
|
|
23827
|
+
/* @__PURE__ */ jsxDEV("label", {
|
|
23828
|
+
for: "new-workspace",
|
|
23829
|
+
children: "Workspace (optional)"
|
|
23558
23830
|
}, undefined, false, undefined, this),
|
|
23559
|
-
/* @__PURE__ */ jsxDEV("
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
23831
|
+
/* @__PURE__ */ jsxDEV("input", {
|
|
23832
|
+
id: "new-workspace",
|
|
23833
|
+
name: "workspace",
|
|
23834
|
+
type: "text",
|
|
23835
|
+
placeholder: "/path/to/project",
|
|
23836
|
+
value: workspace && workspace !== NO_WORKSPACE_FILTER ? workspace : ""
|
|
23837
|
+
}, undefined, false, undefined, this)
|
|
23838
|
+
]
|
|
23839
|
+
}, undefined, true, undefined, this),
|
|
23840
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
23841
|
+
class: "form-actions",
|
|
23842
|
+
children: /* @__PURE__ */ jsxDEV("button", {
|
|
23843
|
+
type: "submit",
|
|
23844
|
+
class: "primary",
|
|
23845
|
+
children: "Save"
|
|
23846
|
+
}, undefined, false, undefined, this)
|
|
23847
|
+
}, undefined, false, undefined, this)
|
|
23848
|
+
]
|
|
23849
|
+
}, undefined, true, undefined, this)
|
|
23850
|
+
}, undefined, false, undefined, this);
|
|
23851
|
+
}
|
|
23852
|
+
|
|
23853
|
+
// src/ui/components/memory-card.tsx
|
|
23854
|
+
function MemoryCard({ memory, editing, showWorkspace, returnUrl }) {
|
|
23855
|
+
return /* @__PURE__ */ jsxDEV("div", {
|
|
23856
|
+
class: "card",
|
|
23857
|
+
children: [
|
|
23858
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
23859
|
+
class: "card-header",
|
|
23860
|
+
children: /* @__PURE__ */ jsxDEV("div", {
|
|
23861
|
+
class: "card-meta",
|
|
23862
|
+
children: [
|
|
23863
|
+
showWorkspace && memory.workspace && /* @__PURE__ */ jsxDEV(Fragment, {
|
|
23563
23864
|
children: [
|
|
23564
|
-
/* @__PURE__ */ jsxDEV("
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
value: returnUrl
|
|
23865
|
+
/* @__PURE__ */ jsxDEV("span", {
|
|
23866
|
+
class: "badge",
|
|
23867
|
+
children: memory.workspace
|
|
23568
23868
|
}, undefined, false, undefined, this),
|
|
23569
|
-
|
|
23570
|
-
type: "submit",
|
|
23571
|
-
class: "danger",
|
|
23572
|
-
children: "Delete"
|
|
23573
|
-
}, undefined, false, undefined, this)
|
|
23869
|
+
" "
|
|
23574
23870
|
]
|
|
23575
|
-
}, undefined, true, undefined, this)
|
|
23871
|
+
}, undefined, true, undefined, this),
|
|
23872
|
+
memory.updatedAt.toLocaleString()
|
|
23576
23873
|
]
|
|
23577
23874
|
}, undefined, true, undefined, this)
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
23875
|
+
}, undefined, false, undefined, this),
|
|
23876
|
+
editing ? /* @__PURE__ */ jsxDEV("form", {
|
|
23877
|
+
method: "post",
|
|
23878
|
+
action: `/memories/${encodeURIComponent(memory.id)}/update`,
|
|
23879
|
+
children: [
|
|
23880
|
+
/* @__PURE__ */ jsxDEV("input", {
|
|
23881
|
+
type: "hidden",
|
|
23882
|
+
name: "returnUrl",
|
|
23883
|
+
value: returnUrl
|
|
23884
|
+
}, undefined, false, undefined, this),
|
|
23885
|
+
/* @__PURE__ */ jsxDEV("textarea", {
|
|
23886
|
+
name: "content",
|
|
23887
|
+
required: true,
|
|
23888
|
+
children: memory.content
|
|
23889
|
+
}, undefined, false, undefined, this),
|
|
23890
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
23891
|
+
class: "card-actions",
|
|
23892
|
+
children: [
|
|
23893
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
23894
|
+
type: "submit",
|
|
23895
|
+
class: "primary",
|
|
23896
|
+
children: "Save"
|
|
23897
|
+
}, undefined, false, undefined, this),
|
|
23898
|
+
/* @__PURE__ */ jsxDEV("a", {
|
|
23899
|
+
href: returnUrl,
|
|
23900
|
+
class: "btn",
|
|
23901
|
+
children: "Cancel"
|
|
23902
|
+
}, undefined, false, undefined, this)
|
|
23903
|
+
]
|
|
23904
|
+
}, undefined, true, undefined, this)
|
|
23905
|
+
]
|
|
23906
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
|
|
23907
|
+
children: [
|
|
23908
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
23909
|
+
class: "card-content",
|
|
23910
|
+
children: memory.content
|
|
23911
|
+
}, undefined, false, undefined, this),
|
|
23912
|
+
/* @__PURE__ */ jsxDEV("div", {
|
|
23913
|
+
class: "card-actions",
|
|
23914
|
+
children: [
|
|
23915
|
+
/* @__PURE__ */ jsxDEV("a", {
|
|
23916
|
+
href: `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}edit=${encodeURIComponent(memory.id)}`,
|
|
23917
|
+
class: "btn",
|
|
23918
|
+
children: "Edit"
|
|
23919
|
+
}, undefined, false, undefined, this),
|
|
23920
|
+
/* @__PURE__ */ jsxDEV("form", {
|
|
23921
|
+
method: "post",
|
|
23922
|
+
action: `/memories/${encodeURIComponent(memory.id)}/delete`,
|
|
23923
|
+
onsubmit: "return confirm('Delete this memory?')",
|
|
23924
|
+
children: [
|
|
23925
|
+
/* @__PURE__ */ jsxDEV("input", {
|
|
23926
|
+
type: "hidden",
|
|
23927
|
+
name: "returnUrl",
|
|
23928
|
+
value: returnUrl
|
|
23929
|
+
}, undefined, false, undefined, this),
|
|
23930
|
+
/* @__PURE__ */ jsxDEV("button", {
|
|
23931
|
+
type: "submit",
|
|
23932
|
+
class: "danger",
|
|
23933
|
+
children: "Delete"
|
|
23934
|
+
}, undefined, false, undefined, this)
|
|
23935
|
+
]
|
|
23936
|
+
}, undefined, true, undefined, this)
|
|
23937
|
+
]
|
|
23938
|
+
}, undefined, true, undefined, this)
|
|
23939
|
+
]
|
|
23940
|
+
}, undefined, true, undefined, this)
|
|
23941
|
+
]
|
|
23942
|
+
}, undefined, true, undefined, this);
|
|
23943
|
+
}
|
|
23582
23944
|
|
|
23583
23945
|
// 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
|
-
|
|
23946
|
+
function Sidebar({ workspaces, selected }) {
|
|
23947
|
+
return /* @__PURE__ */ jsxDEV("nav", {
|
|
23948
|
+
class: "sidebar",
|
|
23949
|
+
children: [
|
|
23950
|
+
/* @__PURE__ */ jsxDEV("h2", {
|
|
23951
|
+
children: "Workspaces"
|
|
23952
|
+
}, undefined, false, undefined, this),
|
|
23953
|
+
/* @__PURE__ */ jsxDEV("a", {
|
|
23954
|
+
href: "/",
|
|
23955
|
+
class: `sidebar-item all-item${selected === null ? " active" : ""}`,
|
|
23956
|
+
children: "All"
|
|
23957
|
+
}, undefined, false, undefined, this),
|
|
23958
|
+
/* @__PURE__ */ jsxDEV("a", {
|
|
23959
|
+
href: `/?workspace=${NO_WORKSPACE_FILTER}`,
|
|
23960
|
+
class: `sidebar-item no-ws-item${selected === NO_WORKSPACE_FILTER ? " active" : ""}`,
|
|
23961
|
+
children: "No workspace"
|
|
23962
|
+
}, undefined, false, undefined, this),
|
|
23963
|
+
workspaces.map((ws) => /* @__PURE__ */ jsxDEV("a", {
|
|
23964
|
+
href: `/?workspace=${encodeURIComponent(ws)}`,
|
|
23965
|
+
class: `sidebar-item ws-item${selected === ws ? " active" : ""}`,
|
|
23966
|
+
title: ws,
|
|
23967
|
+
children: /* @__PURE__ */ jsxDEV("span", {
|
|
23968
|
+
children: ws.replace(/\/$/, "")
|
|
23969
|
+
}, undefined, false, undefined, this)
|
|
23970
|
+
}, undefined, false, undefined, this))
|
|
23971
|
+
]
|
|
23972
|
+
}, undefined, true, undefined, this);
|
|
23973
|
+
}
|
|
23610
23974
|
|
|
23611
23975
|
// src/ui/components/page.tsx
|
|
23612
|
-
|
|
23976
|
+
function buildUrl(base, overrides) {
|
|
23613
23977
|
const url = new URL(base, "http://localhost");
|
|
23614
23978
|
for (const [key, value] of Object.entries(overrides)) {
|
|
23615
23979
|
url.searchParams.set(key, value);
|
|
23616
23980
|
}
|
|
23617
23981
|
return `${url.pathname}${url.search}`;
|
|
23618
|
-
}
|
|
23619
|
-
|
|
23982
|
+
}
|
|
23983
|
+
function Page({
|
|
23620
23984
|
memories,
|
|
23621
23985
|
workspaces,
|
|
23622
23986
|
selectedWorkspace,
|
|
@@ -23624,7 +23988,7 @@ var Page = ({
|
|
|
23624
23988
|
currentPage,
|
|
23625
23989
|
hasMore,
|
|
23626
23990
|
showCreate
|
|
23627
|
-
})
|
|
23991
|
+
}) {
|
|
23628
23992
|
const params = new URLSearchParams;
|
|
23629
23993
|
if (selectedWorkspace)
|
|
23630
23994
|
params.set("workspace", selectedWorkspace);
|
|
@@ -23743,27 +24107,26 @@ var Page = ({
|
|
|
23743
24107
|
}, undefined, false, undefined, this)
|
|
23744
24108
|
]
|
|
23745
24109
|
}, undefined, true, undefined, this);
|
|
23746
|
-
}
|
|
24110
|
+
}
|
|
23747
24111
|
|
|
23748
|
-
// src/ui/
|
|
23749
|
-
var
|
|
23750
|
-
|
|
23751
|
-
var startWebServer = (repository, options) => {
|
|
24112
|
+
// src/ui/routes/page-routes.tsx
|
|
24113
|
+
var DEFAULT_LIST_LIMIT3 = 15;
|
|
24114
|
+
function createPageRoutes(memory) {
|
|
23752
24115
|
const app = new Hono2;
|
|
23753
|
-
|
|
24116
|
+
async function renderPage(c) {
|
|
23754
24117
|
const workspace = c.req.query("workspace") ?? null;
|
|
23755
24118
|
const pageNum = Math.max(Number(c.req.query("page")) || 1, 1);
|
|
23756
24119
|
const editingId = c.req.query("edit") ?? null;
|
|
23757
24120
|
const showCreate = c.req.query("create") === "1";
|
|
23758
24121
|
const isNoWorkspace = workspace === NO_WORKSPACE_FILTER;
|
|
23759
24122
|
const wsFilter = workspace && !isNoWorkspace ? workspace : undefined;
|
|
23760
|
-
const page = await
|
|
24123
|
+
const page = await memory.list({
|
|
23761
24124
|
workspace: wsFilter,
|
|
23762
24125
|
workspaceIsNull: isNoWorkspace,
|
|
23763
|
-
offset: (pageNum - 1) *
|
|
23764
|
-
limit:
|
|
24126
|
+
offset: (pageNum - 1) * DEFAULT_LIST_LIMIT3,
|
|
24127
|
+
limit: DEFAULT_LIST_LIMIT3
|
|
23765
24128
|
});
|
|
23766
|
-
const workspaces = await
|
|
24129
|
+
const workspaces = await memory.listWorkspaces();
|
|
23767
24130
|
return c.html(/* @__PURE__ */ jsxDEV(Page, {
|
|
23768
24131
|
memories: page.items,
|
|
23769
24132
|
workspaces,
|
|
@@ -23773,132 +24136,84 @@ var startWebServer = (repository, options) => {
|
|
|
23773
24136
|
hasMore: page.hasMore,
|
|
23774
24137
|
showCreate
|
|
23775
24138
|
}, undefined, false, undefined, this));
|
|
23776
|
-
}
|
|
23777
|
-
|
|
24139
|
+
}
|
|
24140
|
+
async function createMemory(c) {
|
|
23778
24141
|
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
|
-
|
|
24142
|
+
const content = typeof form2.content === "string" ? form2.content : "";
|
|
24143
|
+
const workspace = typeof form2.workspace === "string" ? form2.workspace : undefined;
|
|
24144
|
+
try {
|
|
24145
|
+
await memory.create({ content, workspace });
|
|
24146
|
+
} catch (error2) {
|
|
24147
|
+
if (!(error2 instanceof ValidationError)) {
|
|
24148
|
+
throw error2;
|
|
24149
|
+
}
|
|
23784
24150
|
}
|
|
23785
|
-
const wsParam = workspace ? `/?workspace=${encodeURIComponent(workspace)}` : "/";
|
|
24151
|
+
const wsParam = workspace?.trim() ? `/?workspace=${encodeURIComponent(workspace.trim())}` : "/";
|
|
23786
24152
|
return c.redirect(wsParam);
|
|
23787
|
-
}
|
|
23788
|
-
|
|
24153
|
+
}
|
|
24154
|
+
async function updateMemory(c) {
|
|
23789
24155
|
const form2 = await c.req.parseBody();
|
|
23790
|
-
const content = typeof form2.content === "string" ? form2.content
|
|
24156
|
+
const content = typeof form2.content === "string" ? form2.content : "";
|
|
23791
24157
|
const returnUrl = safeReturnUrl(form2.returnUrl);
|
|
23792
|
-
|
|
23793
|
-
|
|
23794
|
-
|
|
23795
|
-
|
|
23796
|
-
|
|
23797
|
-
|
|
23798
|
-
}
|
|
24158
|
+
const id = c.req.param("id") ?? "";
|
|
24159
|
+
try {
|
|
24160
|
+
await memory.update({ id, content });
|
|
24161
|
+
} catch (error2) {
|
|
24162
|
+
if (!(error2 instanceof NotFoundError) && !(error2 instanceof ValidationError))
|
|
24163
|
+
throw error2;
|
|
23799
24164
|
}
|
|
23800
24165
|
return c.redirect(returnUrl);
|
|
23801
|
-
}
|
|
23802
|
-
|
|
24166
|
+
}
|
|
24167
|
+
async function deleteMemory(c) {
|
|
23803
24168
|
const form2 = await c.req.parseBody();
|
|
23804
24169
|
const returnUrl = safeReturnUrl(form2.returnUrl);
|
|
24170
|
+
const id = c.req.param("id") ?? "";
|
|
23805
24171
|
try {
|
|
23806
|
-
await
|
|
24172
|
+
await memory.delete({ id });
|
|
23807
24173
|
} catch (error2) {
|
|
23808
24174
|
if (!(error2 instanceof NotFoundError))
|
|
23809
24175
|
throw error2;
|
|
23810
24176
|
}
|
|
23811
24177
|
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) => {
|
|
24178
|
+
}
|
|
24179
|
+
app.get("/", renderPage);
|
|
24180
|
+
app.post("/memories", createMemory);
|
|
24181
|
+
app.post("/memories/:id/update", updateMemory);
|
|
24182
|
+
app.post("/memories/:id/delete", deleteMemory);
|
|
24183
|
+
return app;
|
|
24184
|
+
}
|
|
24185
|
+
function safeReturnUrl(value) {
|
|
23872
24186
|
if (typeof value === "string" && value.startsWith("/"))
|
|
23873
24187
|
return value;
|
|
23874
24188
|
return "/";
|
|
23875
|
-
}
|
|
23876
|
-
|
|
23877
|
-
|
|
23878
|
-
|
|
23879
|
-
|
|
23880
|
-
|
|
23881
|
-
|
|
23882
|
-
}
|
|
24189
|
+
}
|
|
24190
|
+
|
|
24191
|
+
// src/ui/server.tsx
|
|
24192
|
+
function startWebServer(memory, options) {
|
|
24193
|
+
const app = new Hono2;
|
|
24194
|
+
app.route("/", createPageRoutes(memory));
|
|
24195
|
+
return serve({ fetch: app.fetch, port: options.port });
|
|
24196
|
+
}
|
|
23883
24197
|
|
|
23884
24198
|
// src/index.ts
|
|
23885
24199
|
var config2 = resolveConfig();
|
|
23886
|
-
var
|
|
23887
|
-
var
|
|
24200
|
+
var embeddingService = new EmbeddingService({ modelsCachePath: config2.modelsCachePath });
|
|
24201
|
+
var database = await openMemoryDatabase(config2.databasePath, { embeddingService });
|
|
24202
|
+
var repository2 = new SqliteMemoryRepository(database);
|
|
24203
|
+
var memoryService = new MemoryService(repository2, embeddingService);
|
|
23888
24204
|
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 = () => {
|
|
24205
|
+
let shutdown = function() {
|
|
23894
24206
|
server.close();
|
|
23895
24207
|
database.close();
|
|
23896
24208
|
process.exit(0);
|
|
23897
24209
|
};
|
|
24210
|
+
const server = startWebServer(memoryService, { port: config2.uiPort });
|
|
24211
|
+
const addr = server.address();
|
|
24212
|
+
const port = typeof addr === "object" && addr ? addr.port : config2.uiPort;
|
|
24213
|
+
console.log(`agent-memory UI running at http://localhost:${port}`);
|
|
23898
24214
|
process.on("SIGINT", shutdown);
|
|
23899
24215
|
process.on("SIGTERM", shutdown);
|
|
23900
24216
|
} else {
|
|
23901
|
-
const memoryService = new MemoryService(repository);
|
|
23902
24217
|
const server = createMcpServer(memoryService, version2);
|
|
23903
24218
|
const transport = new StdioServerTransport;
|
|
23904
24219
|
try {
|