@memstack/mcp 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/cli.js +491 -37
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +22 -2
- package/dist/index.js +387 -35
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
package/dist/index.js
CHANGED
|
@@ -1,59 +1,209 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { readFileSync, realpathSync, existsSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { resolve, join, dirname } from 'path';
|
|
4
|
+
import { MemStack, projectNamespace, defaultRecallNamespaces, GLOBAL_NAMESPACE, MemStackError, InMemoryStorageAdapter, HarnessMemory, AnthropicLLMAdapter, OpenAILLMAdapter, OpenAIEmbeddingAdapter, RedisStorageAdapter, SQLiteStorageAdapter, PostgresStorageAdapter, DiskStorageAdapter, MarkdownStorageAdapter } from '@memstack/core';
|
|
5
|
+
import { execFileSync } from 'child_process';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
3
7
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
8
|
import { ListToolsRequestSchema, CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
+
import { z } from 'zod';
|
|
5
10
|
|
|
6
11
|
// ../config-env/dist/index.js
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
var STORAGE_TYPES = ["memory", "markdown", "disk", "postgres", "sqlite", "redis"];
|
|
13
|
+
function memstackHome() {
|
|
14
|
+
return process.env.MEMSTACK_HOME ?? join(homedir(), ".memstack");
|
|
15
|
+
}
|
|
16
|
+
function configFilePath(home = memstackHome()) {
|
|
17
|
+
return join(home, "config.json");
|
|
18
|
+
}
|
|
19
|
+
function readConfigFile(path = configFilePath()) {
|
|
20
|
+
if (!existsSync(path)) return void 0;
|
|
21
|
+
let parsed;
|
|
22
|
+
try {
|
|
23
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
throw new Error(`${path} is not valid JSON: ${error.message}`);
|
|
26
|
+
}
|
|
27
|
+
return validateConfigFile(parsed, path);
|
|
28
|
+
}
|
|
29
|
+
function validateConfigFile(value, path) {
|
|
30
|
+
const fail = (message) => {
|
|
31
|
+
throw new Error(`Invalid ${path}: ${message}`);
|
|
32
|
+
};
|
|
33
|
+
if (!isObject(value)) fail("expected a JSON object");
|
|
34
|
+
const config = value;
|
|
35
|
+
if (config.version !== 1) fail(`unsupported version ${JSON.stringify(config.version)}, expected 1`);
|
|
36
|
+
if (config.llm !== void 0) {
|
|
37
|
+
if (!isObject(config.llm)) fail("llm must be an object");
|
|
38
|
+
const llm = config.llm;
|
|
39
|
+
if (llm.provider !== "openai-compatible" && llm.provider !== "anthropic") {
|
|
40
|
+
fail('llm.provider must be "openai-compatible" or "anthropic"');
|
|
41
|
+
}
|
|
42
|
+
if (typeof llm.apiKey !== "string" || !llm.apiKey) fail("llm.apiKey must be a non-empty string");
|
|
43
|
+
for (const key of ["baseURL", "model"]) {
|
|
44
|
+
if (llm[key] !== void 0 && typeof llm[key] !== "string") fail(`llm.${key} must be a string`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (config.storage !== void 0) {
|
|
48
|
+
if (!isObject(config.storage)) fail("storage must be an object");
|
|
49
|
+
const storage = config.storage;
|
|
50
|
+
if (!STORAGE_TYPES.includes(storage.type)) {
|
|
51
|
+
fail(`storage.type must be one of ${STORAGE_TYPES.join(", ")}`);
|
|
52
|
+
}
|
|
53
|
+
for (const key of ["path", "url"]) {
|
|
54
|
+
if (storage[key] !== void 0 && typeof storage[key] !== "string") fail(`storage.${key} must be a string`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return config;
|
|
58
|
+
}
|
|
59
|
+
function isObject(value) {
|
|
60
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
61
|
+
}
|
|
62
|
+
var PIN_FILE = ".memstack.json";
|
|
63
|
+
var PIN_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
64
|
+
function resolveProject(cwd = process.cwd()) {
|
|
65
|
+
const dir = realpathSync(resolve(cwd));
|
|
66
|
+
const root = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
67
|
+
const base = root ?? dir;
|
|
68
|
+
const pinned = readPin(base);
|
|
69
|
+
const fallbacks = transitionalIds(dir, root);
|
|
70
|
+
if (pinned) return { id: pinned, source: "pin", key: pinned, root: base, previousIds: [] };
|
|
71
|
+
if (!root) return { ...identity("directory", dir), root: dir, previousIds: [] };
|
|
72
|
+
const shallow = git(root, ["rev-parse", "--is-shallow-repository"]) === "true";
|
|
73
|
+
const rootCommit = shallow ? null : firstCommit(root);
|
|
74
|
+
if (rootCommit) return { ...identity("root-commit", rootCommit), root, previousIds: fallbacks };
|
|
75
|
+
const remote = git(root, ["config", "--get", "remote.origin.url"]);
|
|
76
|
+
const normalized = shallow && remote ? normalizeRemote(remote) : null;
|
|
77
|
+
if (normalized) return { ...identity("remote", normalized), root, previousIds: fallbacks };
|
|
78
|
+
const gitId = identity("git", gitRepoKey(root));
|
|
79
|
+
return { ...gitId, root, previousIds: fallbacks.filter((id) => id !== gitId.id) };
|
|
80
|
+
}
|
|
81
|
+
function normalizeRemote(url) {
|
|
82
|
+
const value = url.trim();
|
|
83
|
+
if (!value) return null;
|
|
84
|
+
let host;
|
|
85
|
+
let path;
|
|
86
|
+
const scp = value.match(/^(?:[^@/]+@)?([^:/]+):(?!\/)(.+)$/);
|
|
87
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(value)) {
|
|
88
|
+
let parsed;
|
|
89
|
+
try {
|
|
90
|
+
parsed = new URL(value);
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (parsed.protocol === "file:" || !parsed.hostname) return null;
|
|
95
|
+
host = parsed.hostname;
|
|
96
|
+
path = decodeURIComponent(parsed.pathname);
|
|
97
|
+
} else if (scp) {
|
|
98
|
+
host = scp[1];
|
|
99
|
+
path = scp[2];
|
|
100
|
+
} else {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
path = path.replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
|
|
104
|
+
if (!path) return null;
|
|
105
|
+
return `${host.toLowerCase()}/${path}`;
|
|
106
|
+
}
|
|
107
|
+
function readPin(base) {
|
|
108
|
+
const path = join(base, PIN_FILE);
|
|
109
|
+
if (!existsSync(path)) return null;
|
|
110
|
+
let project;
|
|
111
|
+
try {
|
|
112
|
+
project = JSON.parse(readFileSync(path, "utf8")).project;
|
|
113
|
+
} catch (error) {
|
|
114
|
+
throw new Error(`${path} is not valid JSON: ${error.message}`);
|
|
115
|
+
}
|
|
116
|
+
if (project === void 0) return null;
|
|
117
|
+
if (typeof project !== "string" || !PIN_PATTERN.test(project)) {
|
|
118
|
+
throw new Error(`${path}: "project" must use letters, digits, '.', '_', or '-' and be at most 64 characters.`);
|
|
119
|
+
}
|
|
120
|
+
return project;
|
|
121
|
+
}
|
|
122
|
+
function firstCommit(root) {
|
|
123
|
+
const out = git(root, ["rev-list", "--max-parents=0", "--first-parent", "HEAD"]);
|
|
124
|
+
return out ? out.split("\n").pop().trim() : null;
|
|
125
|
+
}
|
|
126
|
+
function gitRepoKey(root) {
|
|
127
|
+
const commonDir = git(root, ["rev-parse", "--path-format=absolute", "--git-common-dir"]);
|
|
128
|
+
return commonDir ? dirname(realpathSync(commonDir)) : root;
|
|
129
|
+
}
|
|
130
|
+
function transitionalIds(dir, root) {
|
|
131
|
+
const ids = /* @__PURE__ */ new Set();
|
|
132
|
+
if (root) ids.add(identity("git", gitRepoKey(root)).id);
|
|
133
|
+
ids.add(identity("directory", dir).id);
|
|
134
|
+
return [...ids];
|
|
135
|
+
}
|
|
136
|
+
function identity(source, key) {
|
|
137
|
+
const id = createHash("sha256").update(`${source}:${key}`).digest("hex").slice(0, 16);
|
|
138
|
+
return { id, source, key };
|
|
139
|
+
}
|
|
140
|
+
function git(cwd, args) {
|
|
141
|
+
try {
|
|
142
|
+
const out = execFileSync("git", ["-C", cwd, ...args], {
|
|
143
|
+
encoding: "utf8",
|
|
144
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
145
|
+
timeout: 5e3
|
|
146
|
+
}).trim();
|
|
147
|
+
return out || null;
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function isMissingModule(error) {
|
|
153
|
+
const code = error?.code;
|
|
154
|
+
return code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND";
|
|
155
|
+
}
|
|
156
|
+
function buildLLMAdapter(env) {
|
|
157
|
+
if (!env.ANTHROPIC_API_KEY && !env.OPENAI_API_KEY) {
|
|
9
158
|
throw new Error("At least one of OPENAI_API_KEY or ANTHROPIC_API_KEY must be set");
|
|
10
159
|
}
|
|
11
|
-
if (
|
|
12
|
-
return new AnthropicLLMAdapter({ apiKey:
|
|
160
|
+
if (env.ANTHROPIC_API_KEY) {
|
|
161
|
+
return new AnthropicLLMAdapter({ apiKey: env.ANTHROPIC_API_KEY, defaultModel: env.MEMSTACK_LLM_MODEL });
|
|
13
162
|
}
|
|
14
163
|
return new OpenAILLMAdapter({
|
|
15
|
-
apiKey:
|
|
16
|
-
baseURL:
|
|
17
|
-
defaultModel:
|
|
164
|
+
apiKey: env.OPENAI_API_KEY ?? "",
|
|
165
|
+
baseURL: env.MEMSTACK_OPENAI_BASE_URL,
|
|
166
|
+
defaultModel: env.MEMSTACK_LLM_MODEL
|
|
18
167
|
});
|
|
19
168
|
}
|
|
20
|
-
function buildEmbeddingAdapter() {
|
|
21
|
-
if (
|
|
22
|
-
if (
|
|
23
|
-
return new OpenAIEmbeddingAdapter({ apiKey:
|
|
169
|
+
function buildEmbeddingAdapter(env) {
|
|
170
|
+
if (env.MEMSTACK_OPENAI_BASE_URL) return void 0;
|
|
171
|
+
if (env.OPENAI_API_KEY) {
|
|
172
|
+
return new OpenAIEmbeddingAdapter({ apiKey: env.OPENAI_API_KEY });
|
|
24
173
|
}
|
|
25
174
|
return void 0;
|
|
26
175
|
}
|
|
27
|
-
async function buildStorageAdapter() {
|
|
28
|
-
const storage =
|
|
176
|
+
async function buildStorageAdapter(env) {
|
|
177
|
+
const storage = env.MEMSTACK_STORAGE ?? "memory";
|
|
29
178
|
switch (storage) {
|
|
30
179
|
case "memory":
|
|
31
180
|
return new InMemoryStorageAdapter();
|
|
32
181
|
case "markdown":
|
|
33
182
|
return new MarkdownStorageAdapter({
|
|
34
|
-
dir:
|
|
183
|
+
dir: env.MEMSTACK_DIR ?? "./memories"
|
|
35
184
|
});
|
|
36
185
|
case "disk":
|
|
37
186
|
return new DiskStorageAdapter({
|
|
38
|
-
storageDir:
|
|
187
|
+
storageDir: env.MEMSTACK_DIR ?? "./memstack-data"
|
|
39
188
|
});
|
|
40
189
|
case "postgres":
|
|
41
|
-
if (!
|
|
190
|
+
if (!env.DATABASE_URL) {
|
|
42
191
|
throw new Error("postgres storage requires DATABASE_URL");
|
|
43
192
|
}
|
|
44
193
|
return new PostgresStorageAdapter({
|
|
45
|
-
connectionString:
|
|
194
|
+
connectionString: env.DATABASE_URL
|
|
46
195
|
});
|
|
47
196
|
case "sqlite": {
|
|
48
197
|
try {
|
|
49
198
|
const BetterSqlite3 = await import('better-sqlite3');
|
|
50
199
|
const db = new BetterSqlite3.default(
|
|
51
|
-
|
|
200
|
+
env.SQLITE_PATH ?? "./memstack.db"
|
|
52
201
|
);
|
|
53
202
|
return new SQLiteStorageAdapter({ db });
|
|
54
|
-
} catch {
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (!isMissingModule(error)) throw error;
|
|
55
205
|
throw new Error(
|
|
56
|
-
"SQLite requires better-sqlite3. Install: npm install better-sqlite3"
|
|
206
|
+
"SQLite requires better-sqlite3. Install: npm install better-sqlite3@^11.10.0"
|
|
57
207
|
);
|
|
58
208
|
}
|
|
59
209
|
}
|
|
@@ -61,12 +211,13 @@ async function buildStorageAdapter() {
|
|
|
61
211
|
try {
|
|
62
212
|
const Redis = await import('ioredis');
|
|
63
213
|
const client = new Redis.default(
|
|
64
|
-
|
|
214
|
+
env.REDIS_URL ?? "redis://localhost:6379"
|
|
65
215
|
);
|
|
66
216
|
return new RedisStorageAdapter({ redis: client });
|
|
67
|
-
} catch {
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (!isMissingModule(error)) throw error;
|
|
68
219
|
throw new Error(
|
|
69
|
-
"Redis requires ioredis. Install: npm install ioredis"
|
|
220
|
+
"Redis requires ioredis. Install: npm install ioredis@^5.11.1"
|
|
70
221
|
);
|
|
71
222
|
}
|
|
72
223
|
}
|
|
@@ -75,11 +226,39 @@ async function buildStorageAdapter() {
|
|
|
75
226
|
}
|
|
76
227
|
}
|
|
77
228
|
async function loadConfigFromEnv() {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
229
|
+
return buildConfig(process.env);
|
|
230
|
+
}
|
|
231
|
+
async function loadConfig(options = {}) {
|
|
232
|
+
return buildConfig(mergeConfigFile(readConfigFile(options.configPath), process.env));
|
|
233
|
+
}
|
|
234
|
+
function mergeConfigFile(file, env) {
|
|
235
|
+
const merged = { ...env };
|
|
236
|
+
const envHasLLM = LLM_ENV.some((key) => env[key] !== void 0);
|
|
237
|
+
if (file?.llm && !envHasLLM) {
|
|
238
|
+
if (file.llm.provider === "anthropic") merged.ANTHROPIC_API_KEY = file.llm.apiKey;
|
|
239
|
+
else {
|
|
240
|
+
merged.OPENAI_API_KEY = file.llm.apiKey;
|
|
241
|
+
merged.MEMSTACK_OPENAI_BASE_URL = file.llm.baseURL;
|
|
242
|
+
}
|
|
243
|
+
merged.MEMSTACK_LLM_MODEL = file.llm.model;
|
|
244
|
+
}
|
|
245
|
+
if (file?.storage && env.MEMSTACK_STORAGE === void 0) {
|
|
246
|
+
const { type, path, url } = file.storage;
|
|
247
|
+
merged.MEMSTACK_STORAGE = type;
|
|
248
|
+
if (type === "sqlite") merged.SQLITE_PATH = path;
|
|
249
|
+
if (type === "disk" || type === "markdown") merged.MEMSTACK_DIR = path;
|
|
250
|
+
if (type === "postgres") merged.DATABASE_URL = url;
|
|
251
|
+
if (type === "redis") merged.REDIS_URL = url;
|
|
252
|
+
}
|
|
253
|
+
return merged;
|
|
254
|
+
}
|
|
255
|
+
var LLM_ENV = ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "MEMSTACK_OPENAI_BASE_URL", "MEMSTACK_LLM_MODEL"];
|
|
256
|
+
async function buildConfig(env) {
|
|
257
|
+
const llm = buildLLMAdapter(env);
|
|
258
|
+
const embedding = buildEmbeddingAdapter(env);
|
|
259
|
+
const storage = await buildStorageAdapter(env);
|
|
260
|
+
const embedOnStore = env.MEMSTACK_EMBED_ON_STORE !== "false";
|
|
261
|
+
const defaultActorId = env.MEMSTACK_ACTOR ?? "default";
|
|
83
262
|
return {
|
|
84
263
|
config: { llm, embedding, storage, defaults: { embedOnStore } },
|
|
85
264
|
defaultActorId
|
|
@@ -87,7 +266,7 @@ async function loadConfigFromEnv() {
|
|
|
87
266
|
}
|
|
88
267
|
|
|
89
268
|
// src/config.ts
|
|
90
|
-
async function
|
|
269
|
+
async function loadConfig2() {
|
|
91
270
|
return loadConfigFromEnv();
|
|
92
271
|
}
|
|
93
272
|
var pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
@@ -102,7 +281,7 @@ var TOOLS = [
|
|
|
102
281
|
actorId: { type: "string", description: "Actor ID. Defaults to the current session actor." },
|
|
103
282
|
importance: { type: "number", description: "Importance score (0.0-1.0). Auto-scored if omitted." },
|
|
104
283
|
tags: { type: "array", items: { type: "string" }, description: "Tags for categorization. Auto-extracted if omitted." },
|
|
105
|
-
memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection). Default: interaction." },
|
|
284
|
+
memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection, preference, decision, instruction). Default: interaction." },
|
|
106
285
|
metadata: { type: "object", description: "Additional metadata key-value pairs" }
|
|
107
286
|
},
|
|
108
287
|
required: ["content"]
|
|
@@ -118,7 +297,7 @@ var TOOLS = [
|
|
|
118
297
|
actorId: { type: "string", description: "Actor ID. Defaults to the current session actor." },
|
|
119
298
|
importance: { type: "number", description: "Importance score 0.0-1.0. Default: 0.5." },
|
|
120
299
|
tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
|
|
121
|
-
memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection). Default: interaction." },
|
|
300
|
+
memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection, preference, decision, instruction). Default: interaction." },
|
|
122
301
|
metadata: { type: "object", description: "Additional metadata key-value pairs" }
|
|
123
302
|
},
|
|
124
303
|
required: ["content"]
|
|
@@ -140,7 +319,7 @@ var TOOLS = [
|
|
|
140
319
|
actorId: { type: "string", description: "Actor ID. Defaults to the current session actor." },
|
|
141
320
|
importance: { type: "number", description: "Importance score 0.0-1.0. Default: 0.5." },
|
|
142
321
|
tags: { type: "array", items: { type: "string" }, description: "Tags for categorization" },
|
|
143
|
-
memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection). Default: interaction." },
|
|
322
|
+
memoryType: { type: "string", description: "Memory type (interaction, summary, observation, fact, reflection, preference, decision, instruction). Default: interaction." },
|
|
144
323
|
metadata: { type: "object", description: "Additional metadata key-value pairs" }
|
|
145
324
|
},
|
|
146
325
|
required: ["content"]
|
|
@@ -710,7 +889,180 @@ function createServer({
|
|
|
710
889
|
});
|
|
711
890
|
return server;
|
|
712
891
|
}
|
|
892
|
+
var pkg2 = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
|
|
893
|
+
var KINDS = [
|
|
894
|
+
"fact",
|
|
895
|
+
"preference",
|
|
896
|
+
"decision",
|
|
897
|
+
"instruction",
|
|
898
|
+
"observation",
|
|
899
|
+
"interaction",
|
|
900
|
+
"summary",
|
|
901
|
+
"reflection"
|
|
902
|
+
];
|
|
903
|
+
var HARNESS_INSTRUCTIONS = [
|
|
904
|
+
"MemStack is this project's persistent memory, shared with other coding agents.",
|
|
905
|
+
"When the user asks you to remember something, call memory_store. Do not write it into README, AGENTS.md, or other project files unless the user asks for a file change.",
|
|
906
|
+
"Also call memory_store when you learn a durable fact, decision, preference, or rule.",
|
|
907
|
+
"Call memory_retrieve at the start of a task and before decisions that depend on project history, conventions, or preferences.",
|
|
908
|
+
"Never store secrets, credentials, or personal data.",
|
|
909
|
+
"",
|
|
910
|
+
'Store one self-contained statement per memory, e.g. "This project uses Hono for the API". Memories are project-scoped; use scope "global" only for preferences that apply to every project. memory_retrieve takes natural-language questions. Cite memory IDs when you rely on them, and delete memories that turn out to be wrong with memory_delete.'
|
|
911
|
+
].join("\n");
|
|
912
|
+
var StoreArgs = z.object({
|
|
913
|
+
content: z.string().trim().min(1).max(8e3).describe("One self-contained statement to remember."),
|
|
914
|
+
kind: z.enum(KINDS).default("fact").describe("What kind of memory this is."),
|
|
915
|
+
importance: z.number().min(0).max(1).optional().describe("0 to 1. Default 0.5; use 0.8+ for rules that must not be missed."),
|
|
916
|
+
tags: z.array(z.string().trim().min(1).max(40)).max(10).optional().describe("Optional topic tags. MemStack adds its own."),
|
|
917
|
+
scope: z.enum(["project", "global"]).default("project").describe('"global" only for preferences that apply to every project.')
|
|
918
|
+
}).strict();
|
|
919
|
+
var RetrieveArgs = z.object({
|
|
920
|
+
query: z.string().max(1e3).optional().describe("Natural-language question or keywords. Omit to get the most important memories."),
|
|
921
|
+
limit: z.number().int().min(1).max(25).default(10).describe("Maximum memories to return."),
|
|
922
|
+
kinds: z.array(z.enum(KINDS)).optional().describe("Only return these kinds.")
|
|
923
|
+
}).strict();
|
|
924
|
+
var IdArgs = z.object({ id: z.string().min(1).max(200) }).strict();
|
|
925
|
+
var NoArgs = z.object({}).strict();
|
|
926
|
+
var TOOLS2 = [
|
|
927
|
+
{
|
|
928
|
+
name: "memory_store",
|
|
929
|
+
description: "Save to project memory. Use this whenever the user asks you to remember something, instead of editing project files, and for durable facts, decisions, preferences, or rules. Later sessions and other agents can recall it.",
|
|
930
|
+
schema: StoreArgs
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: "memory_retrieve",
|
|
934
|
+
description: "Recall memories for this project (and global preferences) relevant to a question. Fast and local; call it freely.",
|
|
935
|
+
schema: RetrieveArgs
|
|
936
|
+
},
|
|
937
|
+
{ name: "memory_get", description: "Get one memory by ID.", schema: IdArgs },
|
|
938
|
+
{ name: "memory_delete", description: "Delete a memory that is wrong or outdated, by ID.", schema: IdArgs },
|
|
939
|
+
{ name: "memory_stats", description: "Show the current project and how many memories it has.", schema: NoArgs }
|
|
940
|
+
];
|
|
941
|
+
var RECALL_MAX_CHARS = 8e3;
|
|
942
|
+
function createHarnessServer({ memory, projectId, harness, cwd }) {
|
|
943
|
+
const server = new Server(
|
|
944
|
+
{ name: "memstack", version: pkg2.version },
|
|
945
|
+
{ capabilities: { tools: {} }, instructions: HARNESS_INSTRUCTIONS }
|
|
946
|
+
);
|
|
947
|
+
const project = projectNamespace(projectId);
|
|
948
|
+
const namespaces = defaultRecallNamespaces(projectId);
|
|
949
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
950
|
+
tools: TOOLS2.map(({ name, description, schema }) => ({
|
|
951
|
+
name,
|
|
952
|
+
description,
|
|
953
|
+
inputSchema: z.toJSONSchema(schema, { io: "input" })
|
|
954
|
+
}))
|
|
955
|
+
}));
|
|
956
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
957
|
+
const tool = TOOLS2.find((t) => t.name === request.params.name);
|
|
958
|
+
if (!tool) return failure(`Unknown tool: ${request.params.name}`);
|
|
959
|
+
const parsed = tool.schema.safeParse(request.params.arguments ?? {});
|
|
960
|
+
if (!parsed.success) {
|
|
961
|
+
return failure(`Invalid arguments for ${tool.name}: ${z.prettifyError(parsed.error)}`);
|
|
962
|
+
}
|
|
963
|
+
try {
|
|
964
|
+
switch (tool.name) {
|
|
965
|
+
case "memory_store": {
|
|
966
|
+
const args = parsed.data;
|
|
967
|
+
const stored = await memory.remember({
|
|
968
|
+
namespace: args.scope === "global" ? GLOBAL_NAMESPACE : project,
|
|
969
|
+
content: args.content,
|
|
970
|
+
kind: args.kind,
|
|
971
|
+
importance: args.importance,
|
|
972
|
+
tags: args.tags,
|
|
973
|
+
source: {
|
|
974
|
+
harness: harness ?? server.getClientVersion()?.name ?? "unknown",
|
|
975
|
+
project: projectId,
|
|
976
|
+
cwd
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
return text(`Stored ${stored.id} (${args.scope}, ${stored.memoryType}). Tags: ${stored.tags.join(", ") || "none"}.`);
|
|
980
|
+
}
|
|
981
|
+
case "memory_retrieve": {
|
|
982
|
+
const args = parsed.data;
|
|
983
|
+
const { hits, fallback } = await memory.recall({
|
|
984
|
+
namespaces,
|
|
985
|
+
query: args.query,
|
|
986
|
+
kinds: args.kinds,
|
|
987
|
+
limit: args.limit,
|
|
988
|
+
maxChars: RECALL_MAX_CHARS
|
|
989
|
+
});
|
|
990
|
+
if (hits.length === 0) return text("No memories yet for this project.");
|
|
991
|
+
const header = fallback && args.query ? `Nothing matched "${args.query}". The most important memories instead:` : `${hits.length} ${hits.length === 1 ? "memory" : "memories"}:`;
|
|
992
|
+
return text([header, ...hits.map(({ memory: m }) => formatMemory(m, projectId))].join("\n"));
|
|
993
|
+
}
|
|
994
|
+
case "memory_get": {
|
|
995
|
+
const { id } = parsed.data;
|
|
996
|
+
const found = await memory.get(id, namespaces);
|
|
997
|
+
return found ? text(formatMemory(found, projectId, true)) : failure(`Memory not found in this project: ${id}`);
|
|
998
|
+
}
|
|
999
|
+
case "memory_delete": {
|
|
1000
|
+
const { id } = parsed.data;
|
|
1001
|
+
await memory.forget(id, namespaces);
|
|
1002
|
+
return text(`Deleted ${id}.`);
|
|
1003
|
+
}
|
|
1004
|
+
case "memory_stats": {
|
|
1005
|
+
const counts = await memory.stats(namespaces);
|
|
1006
|
+
return text(
|
|
1007
|
+
`Project ${projectId}${cwd ? ` (${cwd})` : ""}: ${counts[project] ?? 0} memories. Global: ${counts[GLOBAL_NAMESPACE] ?? 0}.`
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
} catch (error) {
|
|
1012
|
+
if (error instanceof MemStackError && error.code === "NOT_FOUND") {
|
|
1013
|
+
return failure(`Memory not found in this project: ${parsed.data.id ?? ""}`);
|
|
1014
|
+
}
|
|
1015
|
+
return failure(error instanceof Error ? error.message : String(error));
|
|
1016
|
+
}
|
|
1017
|
+
});
|
|
1018
|
+
return server;
|
|
1019
|
+
}
|
|
1020
|
+
function formatMemory(m, projectId, detailed = false) {
|
|
1021
|
+
const scope = m.actorId === GLOBAL_NAMESPACE ? "global" : "project";
|
|
1022
|
+
const source = m.metadata?.source?.harness;
|
|
1023
|
+
const meta = [m.memoryType, scope, source && `from ${source}`, m.createdAt.toISOString().slice(0, 10)].filter(Boolean);
|
|
1024
|
+
const line = `- [${m.id}] ${m.content} (${meta.join(", ")})`;
|
|
1025
|
+
if (!detailed) return line;
|
|
1026
|
+
return `${line}
|
|
1027
|
+
importance: ${m.importance}
|
|
1028
|
+
tags: ${m.tags.join(", ") || "none"}
|
|
1029
|
+
project: ${scope === "project" ? projectId : "all"}`;
|
|
1030
|
+
}
|
|
1031
|
+
function text(message) {
|
|
1032
|
+
return { content: [{ type: "text", text: message }] };
|
|
1033
|
+
}
|
|
1034
|
+
function failure(message) {
|
|
1035
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
1036
|
+
}
|
|
1037
|
+
var HOOK_MAX_CHARS = 6e3;
|
|
1038
|
+
var HOOK_LIMIT = 15;
|
|
1039
|
+
async function sessionStartContext(input, env = process.env) {
|
|
1040
|
+
const cwd = env.CLAUDE_PROJECT_DIR ?? input.cwd ?? process.cwd();
|
|
1041
|
+
const project = resolveProject(cwd);
|
|
1042
|
+
const { config } = await loadConfig();
|
|
1043
|
+
const storage = config.storage ?? new InMemoryStorageAdapter();
|
|
1044
|
+
try {
|
|
1045
|
+
const memory = new HarnessMemory({ storage, llm: config.llm, autoTags: false });
|
|
1046
|
+
await memory.adoptProjects(project.previousIds, project.id);
|
|
1047
|
+
const { hits } = await memory.recall({
|
|
1048
|
+
namespaces: defaultRecallNamespaces(project.id),
|
|
1049
|
+
limit: HOOK_LIMIT,
|
|
1050
|
+
maxChars: HOOK_MAX_CHARS
|
|
1051
|
+
});
|
|
1052
|
+
if (hits.length === 0) return "";
|
|
1053
|
+
return [
|
|
1054
|
+
`MemStack project memory (project ${project.id}), most important first. Follow these unless the user says otherwise; use memory_retrieve for more and memory_store to save new facts.`,
|
|
1055
|
+
...hits.map(({ memory: m }) => formatLine(m))
|
|
1056
|
+
].join("\n");
|
|
1057
|
+
} finally {
|
|
1058
|
+
await storage.close();
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
function formatLine(m) {
|
|
1062
|
+
const scope = m.actorId === GLOBAL_NAMESPACE ? "global" : "project";
|
|
1063
|
+
return `- [${m.id}] ${m.content} (${m.memoryType}, ${scope})`;
|
|
1064
|
+
}
|
|
713
1065
|
|
|
714
|
-
export { createServer, loadConfig };
|
|
1066
|
+
export { HARNESS_INSTRUCTIONS, HOOK_MAX_CHARS, createHarnessServer, createServer, loadConfig2 as loadConfig, sessionStartContext };
|
|
715
1067
|
//# sourceMappingURL=index.js.map
|
|
716
1068
|
//# sourceMappingURL=index.js.map
|