@byok-sdk/client 0.9.0 → 0.10.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.
@@ -0,0 +1,762 @@
1
+ import { randomUUID, createHash } from 'crypto';
2
+ import { constants, existsSync, promises } from 'fs';
3
+ import path2 from 'path';
4
+ import '@byok-sdk/protocol';
5
+ import { spawn } from 'child_process';
6
+ import { createInterface } from 'readline';
7
+
8
+ // src/daemon/agent-memory.ts
9
+ var AGENT_HOME_INTERNAL_DIRECTORY = ".byok";
10
+
11
+ // src/daemon/agent-memory.ts
12
+ var AGENT_MEMORY_AUDIT_FILENAME = "agent-memory-audit-v1.jsonl";
13
+ var AGENT_MEMORY_MAX_FILE_BYTES = 256 * 1024;
14
+ var AGENT_MEMORY_MAX_SNAPSHOT_BYTES = 1024 * 1024;
15
+ var AGENT_MEMORY_MAX_SNAPSHOT_FILES = 128;
16
+ var AGENT_MEMORY_MAX_SNAPSHOT_ENTRIES = 512;
17
+ var AGENT_MEMORY_MAX_LOCAL_LOG_BYTES = AGENT_MEMORY_MAX_SNAPSHOT_BYTES;
18
+ var REVISION = /^sha256:[a-f0-9]{64}$/u;
19
+ var SAFE_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
20
+ var SECRET_LIKE = /(?:^|[-_.])(secret|token|credential|password|passwd|api[-_]?key|private[-_]?key|cookie)(?:$|[-_.])/iu;
21
+ var encoder = new TextEncoder();
22
+ var AgentMemoryError = class extends Error {
23
+ constructor(message) {
24
+ super(message);
25
+ this.name = "AgentMemoryError";
26
+ }
27
+ };
28
+ var AgentMemoryRevisionConflictError = class extends AgentMemoryError {
29
+ constructor(expectedRevision, actualRevision) {
30
+ super(`Agent memory revision conflict: expected ${expectedRevision}, current ${actualRevision}`);
31
+ this.expectedRevision = expectedRevision;
32
+ this.actualRevision = actualRevision;
33
+ this.name = "AgentMemoryRevisionConflictError";
34
+ }
35
+ expectedRevision;
36
+ actualRevision;
37
+ };
38
+ function digestBytes(content) {
39
+ return `sha256:${createHash("sha256").update(content).digest("hex")}`;
40
+ }
41
+ function digest(content) {
42
+ return digestBytes(encoder.encode(content));
43
+ }
44
+ function nonEmpty(value) {
45
+ return typeof value === "string" && value.length > 0 && !/[\u0000\r\n]/u.test(value);
46
+ }
47
+ function revision(value) {
48
+ return typeof value === "string" && REVISION.test(value);
49
+ }
50
+ function taskContext(value) {
51
+ if (!value || !nonEmpty(value.taskId) || !nonEmpty(value.tenantId) || !nonEmpty(value.deviceId) || !nonEmpty(value.sessionRef) || !nonEmpty(value.runtimeId) || !nonEmpty(value.leaseId) || !value.agentRef || !nonEmpty(value.agentRef.agentId) || !nonEmpty(value.agentRef.profileRevision) || !path2.isAbsolute(value.canonicalHome) || typeof value.homeIdentity?.dev !== "bigint" || typeof value.homeIdentity.ino !== "bigint") {
52
+ throw new AgentMemoryError("Agent memory requires an exact active Agent task context");
53
+ }
54
+ return Object.freeze({ ...value, agentRef: Object.freeze({ ...value.agentRef }), homeIdentity: Object.freeze({ ...value.homeIdentity }), canonicalHome: path2.resolve(value.canonicalHome) });
55
+ }
56
+ function validateAgentMemoryPath(value) {
57
+ if (typeof value !== "string" || value.length === 0 || value.length > 1024 || /[\u0000\\]/u.test(value)) throw new AgentMemoryError("memory path is invalid");
58
+ if (value === "MEMORY.md") return value;
59
+ if (path2.posix.isAbsolute(value) || /[*?\[{]/u.test(value)) throw new AgentMemoryError("memory path must name exactly one file");
60
+ const parts = value.split("/");
61
+ if (parts.length < 2 || parts[0] !== "notes" || !value.endsWith(".md") || parts.some((part) => part === "" || part === "." || part === ".." || part === ".byok" || !SAFE_SEGMENT.test(part) || SECRET_LIKE.test(part))) {
62
+ throw new AgentMemoryError("memory path must be MEMORY.md or notes/<safe-relative>.md");
63
+ }
64
+ return value;
65
+ }
66
+ var SECURE_DIRECTORY_DESCRIPTOR_ROOT = "/proc/self/fd";
67
+ function isAgentMemorySecureFilesystemAvailable(externalHelperConfigured = false) {
68
+ const nativeLinux = process.platform === "linux" && typeof constants.O_NOFOLLOW === "number" && typeof constants.O_DIRECTORY === "number" && typeof constants.O_NONBLOCK === "number" && existsSync(SECURE_DIRECTORY_DESCRIPTOR_ROOT);
69
+ return nativeLinux || externalHelperConfigured && process.platform === "darwin";
70
+ }
71
+ function requireSecureDirectoryDescriptors() {
72
+ if (!isAgentMemorySecureFilesystemAvailable(false) || process.platform !== "linux") {
73
+ throw new AgentMemoryError("Agent memory is unavailable because this Node platform lacks safe descriptor-relative filesystem operations");
74
+ }
75
+ return SECURE_DIRECTORY_DESCRIPTOR_ROOT;
76
+ }
77
+ function noFollowFlags(base) {
78
+ requireSecureDirectoryDescriptors();
79
+ return base | constants.O_NOFOLLOW;
80
+ }
81
+ function descriptorPath(handle) {
82
+ return `${requireSecureDirectoryDescriptors()}/${handle.fd}`;
83
+ }
84
+ async function openPinnedDirectory(target, expectedIdentity) {
85
+ requireSecureDirectoryDescriptors();
86
+ let handle;
87
+ try {
88
+ handle = await promises.open(target, noFollowFlags(constants.O_RDONLY | constants.O_DIRECTORY));
89
+ const stat = await handle.stat({ bigint: true });
90
+ if (!stat.isDirectory() || stat.isSymbolicLink() || expectedIdentity !== void 0 && (stat.dev !== expectedIdentity.dev || stat.ino !== expectedIdentity.ino)) throw new AgentMemoryError("memory directory is not a real directory");
91
+ return handle;
92
+ } catch (error) {
93
+ await handle?.close().catch(() => {
94
+ });
95
+ if (error instanceof AgentMemoryError) throw error;
96
+ throw new AgentMemoryError("memory directory is unavailable or unsafe");
97
+ }
98
+ }
99
+ async function withPinnedDirectory(home, parts, operation, expectedHomeIdentity) {
100
+ const handles = [];
101
+ try {
102
+ let directory = await openPinnedDirectory(home, expectedHomeIdentity);
103
+ handles.push(directory);
104
+ for (const part of parts) {
105
+ directory = await openPinnedDirectory(`${descriptorPath(directory)}/${part}`);
106
+ handles.push(directory);
107
+ }
108
+ return await operation(directory);
109
+ } finally {
110
+ await Promise.all(handles.reverse().map((handle) => handle.close().catch(() => {
111
+ })));
112
+ }
113
+ }
114
+ async function withMemoryParent(context, relativePath, operation) {
115
+ const parts = relativePath.split("/");
116
+ const fileName = parts.pop();
117
+ if (fileName === void 0 || parts.some((part) => !SAFE_SEGMENT.test(part) && part !== ".byok")) throw new AgentMemoryError("memory path is invalid");
118
+ return withPinnedDirectory(context.canonicalHome, parts, (directory) => operation(directory, fileName), context.homeIdentity);
119
+ }
120
+ async function readPinnedFile(directory, fileName, maxBytes = AGENT_MEMORY_MAX_FILE_BYTES) {
121
+ let handle;
122
+ try {
123
+ handle = await promises.open(
124
+ `${descriptorPath(directory)}/${fileName}`,
125
+ noFollowFlags(constants.O_RDONLY | constants.O_NONBLOCK)
126
+ );
127
+ } catch (error) {
128
+ if (error.code === "ENOENT") return Object.freeze({ exists: false, content: "", revision: digest(""), byteCount: 0 });
129
+ throw new AgentMemoryError("could not open memory file");
130
+ }
131
+ try {
132
+ const before = await handle.stat({ bigint: true });
133
+ if (!before.isFile() || before.isSymbolicLink() || before.size > BigInt(maxBytes)) throw new AgentMemoryError("memory file is not a bounded regular file");
134
+ const bytes = Buffer.alloc(Number(before.size));
135
+ let offset = 0;
136
+ while (offset < bytes.length) {
137
+ const read = await handle.read(bytes, offset, bytes.length - offset, offset);
138
+ if (read.bytesRead === 0) break;
139
+ offset += read.bytesRead;
140
+ }
141
+ const after = await handle.stat({ bigint: true });
142
+ if (offset !== bytes.length || after.size !== before.size || after.mtimeNs !== before.mtimeNs || after.ino !== before.ino) throw new AgentMemoryError("memory file changed during read");
143
+ const content = bytes.toString("utf8");
144
+ if (!Buffer.from(content, "utf8").equals(bytes)) throw new AgentMemoryError("memory file is not valid UTF-8");
145
+ return Object.freeze({ exists: true, content, revision: digestBytes(bytes), byteCount: bytes.length });
146
+ } finally {
147
+ await handle.close().catch(() => {
148
+ });
149
+ }
150
+ }
151
+ async function readFile(context, relativePath, maxBytes = AGENT_MEMORY_MAX_FILE_BYTES) {
152
+ if (context.filesystem !== void 0) return context.filesystem.read(relativePath, maxBytes);
153
+ return withMemoryParent(context, relativePath, (directory, fileName) => readPinnedFile(directory, fileName, maxBytes));
154
+ }
155
+ async function syncDirectory(directory) {
156
+ try {
157
+ await directory.sync();
158
+ } catch (error) {
159
+ if (!["EINVAL", "EPERM"].includes(error.code ?? "")) throw error;
160
+ }
161
+ }
162
+ async function replaceNative(context, relativePath, expected, content) {
163
+ const byteCount = encoder.encode(content).byteLength;
164
+ if (byteCount > AGENT_MEMORY_MAX_FILE_BYTES) throw new AgentMemoryError("memory content exceeds its bounded file size");
165
+ return withMemoryParent(context, relativePath, async (directory, fileName) => {
166
+ const before = await readPinnedFile(directory, fileName);
167
+ if (before.revision !== expected) throw new AgentMemoryRevisionConflictError(expected, before.revision);
168
+ const parent = descriptorPath(directory);
169
+ const temporary = `${parent}/.byok-memory-${randomUUID()}.tmp`;
170
+ let handle;
171
+ try {
172
+ const check = await readPinnedFile(directory, fileName);
173
+ if (check.revision !== expected || check.exists !== before.exists) throw new AgentMemoryRevisionConflictError(expected, check.revision);
174
+ handle = await promises.open(temporary, noFollowFlags(constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL), 384);
175
+ await handle.writeFile(content, "utf8");
176
+ await handle.sync();
177
+ await handle.close();
178
+ handle = void 0;
179
+ const finalCheck = await readPinnedFile(directory, fileName);
180
+ if (finalCheck.revision !== expected || finalCheck.exists !== before.exists) throw new AgentMemoryRevisionConflictError(expected, finalCheck.revision);
181
+ await promises.rename(temporary, `${parent}/${fileName}`);
182
+ await syncDirectory(directory);
183
+ return Object.freeze({ exists: true, content, revision: digest(content), byteCount });
184
+ } catch (error) {
185
+ await handle?.close().catch(() => {
186
+ });
187
+ await promises.rm(temporary, { force: true }).catch(() => {
188
+ });
189
+ if (error instanceof AgentMemoryError) throw error;
190
+ throw new AgentMemoryError("could not atomically replace memory file");
191
+ }
192
+ });
193
+ }
194
+ async function replace(context, relativePath, expected, content) {
195
+ if (context.filesystem !== void 0) return context.filesystem.replace(relativePath, expected, content, AGENT_MEMORY_MAX_FILE_BYTES);
196
+ return replaceNative(context, relativePath, expected, content);
197
+ }
198
+ async function removeNative(context, relativePath, expected) {
199
+ if (relativePath === "MEMORY.md") throw new AgentMemoryError("MEMORY.md may not be deleted");
200
+ await withMemoryParent(context, relativePath, async (directory, fileName) => {
201
+ const before = await readPinnedFile(directory, fileName);
202
+ if (!before.exists || before.revision !== expected) throw new AgentMemoryRevisionConflictError(expected, before.revision);
203
+ const parent = descriptorPath(directory);
204
+ const tombstone = `${parent}/.byok-memory-delete-${randomUUID()}.tmp`;
205
+ try {
206
+ const check = await readPinnedFile(directory, fileName);
207
+ if (!check.exists || check.revision !== expected) throw new AgentMemoryRevisionConflictError(expected, check.revision);
208
+ await promises.rename(`${parent}/${fileName}`, tombstone);
209
+ await syncDirectory(directory);
210
+ await promises.rm(tombstone);
211
+ await syncDirectory(directory);
212
+ } catch (error) {
213
+ if (error instanceof AgentMemoryError) throw error;
214
+ throw new AgentMemoryError("could not atomically delete memory file");
215
+ }
216
+ });
217
+ }
218
+ async function remove(context, relativePath, expected) {
219
+ if (relativePath === "MEMORY.md") throw new AgentMemoryError("MEMORY.md may not be deleted");
220
+ if (context.filesystem !== void 0) return context.filesystem.delete(relativePath, expected);
221
+ return removeNative(context, relativePath, expected);
222
+ }
223
+ async function readInternalFile(context, fileName) {
224
+ const relativePath = `${AGENT_HOME_INTERNAL_DIRECTORY}/${fileName}`;
225
+ if (context.filesystem !== void 0) return context.filesystem.read(relativePath, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
226
+ return withPinnedDirectory(
227
+ context.canonicalHome,
228
+ [AGENT_HOME_INTERNAL_DIRECTORY],
229
+ (directory) => readPinnedFile(directory, fileName, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES),
230
+ context.homeIdentity
231
+ );
232
+ }
233
+ async function replaceInternalFile(context, fileName, expectedRevision, content) {
234
+ const byteCount = encoder.encode(content).byteLength;
235
+ if (byteCount > AGENT_MEMORY_MAX_LOCAL_LOG_BYTES) throw new AgentMemoryError("Agent memory internal state exceeds its bounded size");
236
+ const relativePath = `${AGENT_HOME_INTERNAL_DIRECTORY}/${fileName}`;
237
+ if (context.filesystem !== void 0) return context.filesystem.replace(relativePath, expectedRevision, content, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
238
+ return withPinnedDirectory(context.canonicalHome, [AGENT_HOME_INTERNAL_DIRECTORY], async (directory) => {
239
+ const before = await readPinnedFile(directory, fileName, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
240
+ if (before.revision !== expectedRevision) throw new AgentMemoryRevisionConflictError(expectedRevision, before.revision);
241
+ const parent = descriptorPath(directory);
242
+ const temporary = `${parent}/.byok-agent-memory-${randomUUID()}.tmp`;
243
+ let handle;
244
+ try {
245
+ handle = await promises.open(temporary, noFollowFlags(constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL), 384);
246
+ await handle.writeFile(content, "utf8");
247
+ await handle.sync();
248
+ await handle.close();
249
+ handle = void 0;
250
+ const check = await readPinnedFile(directory, fileName, AGENT_MEMORY_MAX_LOCAL_LOG_BYTES);
251
+ if (check.revision !== expectedRevision || check.exists !== before.exists) throw new AgentMemoryRevisionConflictError(expectedRevision, check.revision);
252
+ await promises.rename(temporary, `${parent}/${fileName}`);
253
+ await syncDirectory(directory);
254
+ return Object.freeze({ exists: true, content, revision: digest(content), byteCount });
255
+ } catch (error) {
256
+ await handle?.close().catch(() => {
257
+ });
258
+ await promises.rm(temporary, { force: true }).catch(() => {
259
+ });
260
+ if (error instanceof AgentMemoryError) throw error;
261
+ throw new AgentMemoryError("could not atomically replace Agent memory internal state");
262
+ }
263
+ }, context.homeIdentity);
264
+ }
265
+ function boundedAuditTail(previous, entry) {
266
+ if (encoder.encode(entry).byteLength > AGENT_MEMORY_MAX_LOCAL_LOG_BYTES) throw new AgentMemoryError("Agent memory audit entry exceeds its bounded size");
267
+ const lines = previous.split("\n").filter((line) => line.length > 0);
268
+ const kept = [entry];
269
+ let byteCount = encoder.encode(entry).byteLength;
270
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
271
+ const candidate = `${lines[index]}
272
+ `;
273
+ const candidateBytes = encoder.encode(candidate).byteLength;
274
+ if (byteCount + candidateBytes > AGENT_MEMORY_MAX_LOCAL_LOG_BYTES) break;
275
+ kept.unshift(candidate);
276
+ byteCount += candidateBytes;
277
+ }
278
+ return kept.join("");
279
+ }
280
+ async function audit(context, kind, values) {
281
+ const entry = `${JSON.stringify({ version: 1, kind, taskId: context.taskId, tenantId: context.tenantId, deviceId: context.deviceId, agentRef: context.agentRef, sessionRef: context.sessionRef, runtimeId: context.runtimeId, ...values, recordedAt: (/* @__PURE__ */ new Date()).toISOString() })}
282
+ `;
283
+ const current = await readInternalFile(context, AGENT_MEMORY_AUDIT_FILENAME);
284
+ await replaceInternalFile(context, AGENT_MEMORY_AUDIT_FILENAME, current.revision, boundedAuditTail(current.content, entry));
285
+ }
286
+ async function recordAuditWarning(context, kind, values) {
287
+ try {
288
+ await audit(context, kind, values);
289
+ return void 0;
290
+ } catch {
291
+ return Object.freeze({ code: "agent_memory_audit_unavailable" });
292
+ }
293
+ }
294
+ var agentMemoryHomeQueues = /* @__PURE__ */ new Map();
295
+ async function exclusiveAgentMemoryHome(home, fn) {
296
+ const previous = agentMemoryHomeQueues.get(home) ?? Promise.resolve();
297
+ let release;
298
+ const next = new Promise((resolve) => {
299
+ release = resolve;
300
+ });
301
+ agentMemoryHomeQueues.set(home, next);
302
+ await previous;
303
+ try {
304
+ return await fn();
305
+ } finally {
306
+ release();
307
+ if (agentMemoryHomeQueues.get(home) === next) agentMemoryHomeQueues.delete(home);
308
+ }
309
+ }
310
+ var AgentMemoryService = class {
311
+ constructor(input) {
312
+ this.input = input;
313
+ }
314
+ input;
315
+ async recall(input) {
316
+ const context = taskContext(this.input);
317
+ const relativePath = validateAgentMemoryPath(input.path);
318
+ if (input.ifRevision !== void 0 && !revision(input.ifRevision)) throw new AgentMemoryError("ifRevision must be a sha256 content revision");
319
+ const current = await readFile(context, relativePath);
320
+ if (!current.exists) throw new AgentMemoryError("memory file does not exist");
321
+ if (input.ifRevision !== void 0 && current.revision !== input.ifRevision) throw new AgentMemoryRevisionConflictError(input.ifRevision, current.revision);
322
+ const auditWarning = await exclusiveAgentMemoryHome(context.canonicalHome, () => recordAuditWarning(context, "recall", {
323
+ path: relativePath,
324
+ revision: current.revision,
325
+ byteCount: current.byteCount
326
+ }));
327
+ return Object.freeze({
328
+ path: relativePath,
329
+ revision: current.revision,
330
+ content: current.content,
331
+ ...auditWarning === void 0 ? {} : { auditWarning }
332
+ });
333
+ }
334
+ async save(input) {
335
+ const context = taskContext(this.input);
336
+ const relativePath = validateAgentMemoryPath(input.path);
337
+ if (input.op !== "replace" && input.op !== "delete" || !revision(input.expectedRevision)) throw new AgentMemoryError("memory save requires op and sha256 expectedRevision");
338
+ if (input.op === "replace" && typeof input.content !== "string") throw new AgentMemoryError("replace requires string content");
339
+ if (input.op === "delete" && input.content !== void 0) throw new AgentMemoryError("delete does not accept content");
340
+ const expectedRevision = input.expectedRevision;
341
+ const content = input.content;
342
+ return exclusiveAgentMemoryHome(context.canonicalHome, async () => {
343
+ if (input.op === "delete") {
344
+ await remove(context, relativePath, expectedRevision);
345
+ const auditWarning2 = await recordAuditWarning(context, "save", { path: relativePath, operation: "delete" });
346
+ return Object.freeze({ path: relativePath, deleted: true, ...auditWarning2 === void 0 ? {} : { auditWarning: auditWarning2 } });
347
+ }
348
+ if (typeof content !== "string") throw new AgentMemoryError("replace requires string content");
349
+ const current = await replace(context, relativePath, expectedRevision, content);
350
+ const auditWarning = await recordAuditWarning(context, "save", { path: relativePath, operation: "replace", revision: current.revision, byteCount: current.byteCount });
351
+ return Object.freeze({ path: relativePath, revision: current.revision, deleted: false, ...auditWarning === void 0 ? {} : { auditWarning } });
352
+ });
353
+ }
354
+ };
355
+ async function memoryNotePaths(context) {
356
+ if (context.filesystem !== void 0) {
357
+ const paths = await context.filesystem.walk("notes", AGENT_MEMORY_MAX_SNAPSHOT_ENTRIES);
358
+ const candidates2 = [];
359
+ for (const candidate of paths) {
360
+ try {
361
+ candidates2.push(validateAgentMemoryPath(candidate));
362
+ } catch {
363
+ }
364
+ }
365
+ return Object.freeze(candidates2);
366
+ }
367
+ const candidates = [];
368
+ let entriesSeen = 0;
369
+ async function walk(directory, relativeDirectory) {
370
+ const entries = await promises.readdir(descriptorPath(directory), { withFileTypes: true });
371
+ for (const entry of entries) {
372
+ entriesSeen += 1;
373
+ if (entriesSeen > AGENT_MEMORY_MAX_SNAPSHOT_ENTRIES) throw new AgentMemoryError("memory snapshot exceeds bounded directory entries");
374
+ const candidate = `${relativeDirectory}/${entry.name}`;
375
+ if (entry.isSymbolicLink()) throw new AgentMemoryError("memory notes contains a symlink");
376
+ if (entry.isDirectory()) {
377
+ if (SAFE_SEGMENT.test(entry.name) && !SECRET_LIKE.test(entry.name) && entry.name !== ".byok") {
378
+ const nested = await openPinnedDirectory(`${descriptorPath(directory)}/${entry.name}`);
379
+ try {
380
+ await walk(nested, candidate);
381
+ } finally {
382
+ await nested.close().catch(() => {
383
+ });
384
+ }
385
+ }
386
+ continue;
387
+ }
388
+ if (!entry.isFile()) continue;
389
+ try {
390
+ candidates.push(validateAgentMemoryPath(candidate));
391
+ } catch {
392
+ }
393
+ }
394
+ }
395
+ await withPinnedDirectory(context.canonicalHome, ["notes"], (directory) => walk(directory, "notes"), context.homeIdentity);
396
+ return Object.freeze(candidates);
397
+ }
398
+ async function captureAgentMemorySnapshot(input) {
399
+ const context = taskContext(input);
400
+ const candidates = ["MEMORY.md", ...await memoryNotePaths(context)];
401
+ const paths = [...new Set(candidates)].sort((a, b) => a.localeCompare(b));
402
+ if (paths.length > AGENT_MEMORY_MAX_SNAPSHOT_FILES) throw new AgentMemoryError("memory snapshot exceeds bounded file count");
403
+ const files = [];
404
+ let totalBytes = 0;
405
+ for (const relativePath of paths) {
406
+ const current = await readFile(context, relativePath);
407
+ if (!current.exists) {
408
+ if (relativePath === "MEMORY.md") throw new AgentMemoryError("MEMORY.md disappeared before snapshot");
409
+ continue;
410
+ }
411
+ totalBytes += current.byteCount;
412
+ if (totalBytes > AGENT_MEMORY_MAX_SNAPSHOT_BYTES) throw new AgentMemoryError("memory snapshot exceeds bounded total size");
413
+ files.push(Object.freeze({ path: relativePath, revision: current.revision, byteCount: current.byteCount, content: current.content }));
414
+ }
415
+ const snapshot = Object.freeze({ files: Object.freeze(files), totalBytes });
416
+ await exclusiveAgentMemoryHome(context.canonicalHome, () => audit(context, "snapshot", {
417
+ files: files.map((file) => ({ path: file.path, revision: file.revision, byteCount: file.byteCount }))
418
+ }));
419
+ return snapshot;
420
+ }
421
+ var AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL = 2;
422
+ var AGENT_MEMORY_FILESYSTEM_HELPER_VERSION = "2";
423
+ var HELPER_REQUEST_TIMEOUT_MS = 1e4;
424
+ var HELPER_MAX_JSON_LINE_BYTES = 2 * 1024 * 1024;
425
+ var HELPER_MAX_CONTENT_BYTES = 1 * 1024 * 1024;
426
+ var HELPER_MAX_STDERR_BYTES = 4 * 1024;
427
+ var HelperRevisionConflict = class extends Error {
428
+ constructor(actualRevision) {
429
+ super("helper revision conflict");
430
+ this.actualRevision = actualRevision;
431
+ }
432
+ actualRevision;
433
+ };
434
+ function safeHelperPath(value) {
435
+ if (typeof value !== "string" || !path2.isAbsolute(value) || value.length === 0 || /[\u0000\r\n]/u.test(value)) {
436
+ throw new AgentMemoryError("Agent memory filesystem helper must be an explicit absolute executable path");
437
+ }
438
+ return path2.resolve(value);
439
+ }
440
+ function helperPlatformSupported() {
441
+ return process.platform === "darwin";
442
+ }
443
+ function isAgentMemoryFilesystemHelperSupported() {
444
+ return helperPlatformSupported();
445
+ }
446
+ function unixIdentity(homeIdentity) {
447
+ return Object.freeze({ kind: "unix", dev: homeIdentity.dev.toString(10), ino: homeIdentity.ino.toString(10) });
448
+ }
449
+ function responseRecord(value) {
450
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return void 0;
451
+ const record2 = value;
452
+ if (typeof record2.id !== "string" || record2.protocol !== AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL || typeof record2.ok !== "boolean") return void 0;
453
+ if (record2.ok) {
454
+ if (record2.result === null || typeof record2.result !== "object" || Array.isArray(record2.result)) return void 0;
455
+ return record2;
456
+ }
457
+ if (record2.error === null || typeof record2.error !== "object" || Array.isArray(record2.error)) return void 0;
458
+ const error = record2.error;
459
+ if (typeof error.code !== "string" || typeof error.message !== "string" || error.actualRevision !== void 0 && typeof error.actualRevision !== "string") return void 0;
460
+ return record2;
461
+ }
462
+ function boundedFileState(result, maxBytes) {
463
+ let content;
464
+ try {
465
+ if (typeof result.contentBase64 !== "string" || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}|[A-Za-z0-9+/]{3})?$/u.test(result.contentBase64)) {
466
+ throw new Error("invalid base64");
467
+ }
468
+ content = Buffer.from(result.contentBase64, "base64").toString("utf8");
469
+ } catch {
470
+ throw new AgentMemoryError("Agent memory filesystem helper returned an invalid file state");
471
+ }
472
+ if (typeof result.exists !== "boolean" || typeof result.revision !== "string" || typeof result.byteCount !== "number" || !Number.isSafeInteger(result.byteCount) || result.byteCount < 0 || result.byteCount > maxBytes || Buffer.byteLength(content, "utf8") !== result.byteCount || !/^sha256:[a-f0-9]{64}$/u.test(result.revision)) {
473
+ throw new AgentMemoryError("Agent memory filesystem helper returned an invalid file state");
474
+ }
475
+ return Object.freeze({ exists: result.exists, content, revision: result.revision, byteCount: result.byteCount });
476
+ }
477
+ function encodeReplaceContent(content, maxBytes) {
478
+ if (!Number.isSafeInteger(maxBytes) || maxBytes < 0 || maxBytes > HELPER_MAX_CONTENT_BYTES) {
479
+ throw new AgentMemoryError("Agent memory filesystem helper requested an invalid byte limit");
480
+ }
481
+ const bytes = Buffer.from(content, "utf8");
482
+ if (bytes.byteLength > maxBytes) {
483
+ throw new AgentMemoryError("Agent memory filesystem helper replacement exceeds its requested byte limit");
484
+ }
485
+ return bytes.toString("base64").replace(/=+$/u, "");
486
+ }
487
+ var AgentMemoryFilesystemHelperClient = class _AgentMemoryFilesystemHelperClient {
488
+ constructor(child) {
489
+ this.child = child;
490
+ child.stdout.on("data", (chunk) => this.handleStdoutChunk(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
491
+ child.stderr.on("data", (chunk) => {
492
+ this.stderrBytes += Buffer.byteLength(chunk);
493
+ if (this.stderrBytes > HELPER_MAX_STDERR_BYTES) this.fail(new AgentMemoryError("Agent memory filesystem helper exceeded bounded stderr"));
494
+ });
495
+ child.stdin.on("error", () => this.fail(new AgentMemoryError("Agent memory filesystem helper request could not be written")));
496
+ child.once("error", () => this.fail(new AgentMemoryError("Agent memory filesystem helper could not be started")));
497
+ child.once("exit", (code, signal) => {
498
+ if (!this.closed || code !== 0) this.fail(new AgentMemoryError(`Agent memory filesystem helper exited unexpectedly (${code ?? signal ?? "unknown"})`));
499
+ });
500
+ }
501
+ child;
502
+ pending = /* @__PURE__ */ new Map();
503
+ stdoutBuffer = Buffer.alloc(0);
504
+ sequence = 0;
505
+ stderrBytes = 0;
506
+ closed = false;
507
+ fatalError;
508
+ static async open(input) {
509
+ if (!helperPlatformSupported()) throw new AgentMemoryError("Agent memory filesystem helper is not admitted on this platform");
510
+ const helperBin = safeHelperPath(input.helperBin);
511
+ const child = spawn(helperBin, ["serve"], {
512
+ stdio: ["pipe", "pipe", "pipe"],
513
+ shell: false,
514
+ windowsHide: true,
515
+ env: Object.freeze({})
516
+ });
517
+ const client = new _AgentMemoryFilesystemHelperClient(child);
518
+ try {
519
+ const result = await client.request("open", {
520
+ root: path2.resolve(input.canonicalHome),
521
+ expectedIdentity: unixIdentity(input.homeIdentity)
522
+ });
523
+ if (result.helperVersion !== AGENT_MEMORY_FILESYSTEM_HELPER_VERSION) throw new AgentMemoryError("Agent memory filesystem helper version mismatch");
524
+ const identity = result.identity;
525
+ if (identity === null || typeof identity !== "object" || Array.isArray(identity)) throw new AgentMemoryError("Agent memory filesystem helper omitted root identity");
526
+ const actual = identity;
527
+ const expected = unixIdentity(input.homeIdentity);
528
+ if (actual.kind !== expected.kind || actual.dev !== expected.dev || actual.ino !== expected.ino) throw new AgentMemoryError("Agent memory filesystem helper root identity mismatch");
529
+ return client;
530
+ } catch (error) {
531
+ await client.close().catch(() => {
532
+ });
533
+ throw error;
534
+ }
535
+ }
536
+ async read(relativePath, maxBytes) {
537
+ return boundedFileState(await this.request("read", { path: relativePath, maxBytes }), maxBytes);
538
+ }
539
+ async replace(relativePath, expectedRevision, content, maxBytes) {
540
+ try {
541
+ const contentBase64 = encodeReplaceContent(content, maxBytes);
542
+ return boundedFileState(await this.request("replace", { path: relativePath, expectedRevision, contentBase64, maxBytes }), maxBytes);
543
+ } catch (error) {
544
+ if (error instanceof HelperRevisionConflict) throw new AgentMemoryRevisionConflictError(expectedRevision, error.actualRevision);
545
+ throw error;
546
+ }
547
+ }
548
+ async delete(relativePath, expectedRevision) {
549
+ try {
550
+ await this.request("delete", { path: relativePath, expectedRevision });
551
+ } catch (error) {
552
+ if (error instanceof HelperRevisionConflict) throw new AgentMemoryRevisionConflictError(expectedRevision, error.actualRevision);
553
+ throw error;
554
+ }
555
+ }
556
+ async append(relativePath, content, maxBytes) {
557
+ await this.request("append", { path: relativePath, content, maxBytes });
558
+ }
559
+ async walk(relativePath, maxEntries) {
560
+ const result = await this.request("walk", { path: relativePath, maxEntries });
561
+ if (!Array.isArray(result.paths) || result.paths.length > maxEntries || result.paths.some((candidate) => typeof candidate !== "string")) {
562
+ throw new AgentMemoryError("Agent memory filesystem helper returned an invalid walk result");
563
+ }
564
+ return Object.freeze([...result.paths]);
565
+ }
566
+ async close() {
567
+ if (this.closed) return;
568
+ this.closed = true;
569
+ try {
570
+ if (this.child.exitCode === null && this.child.signalCode === null && this.fatalError === void 0) await this.requestWhileClosing("close", {});
571
+ } finally {
572
+ this.child.stdin.end();
573
+ if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill();
574
+ this.rejectPending(new AgentMemoryError("Agent memory filesystem helper is closed"));
575
+ }
576
+ }
577
+ request(op, fields) {
578
+ if (this.closed) return Promise.reject(new AgentMemoryError("Agent memory filesystem helper is closed"));
579
+ return this.requestInternal(op, fields);
580
+ }
581
+ requestWhileClosing(op, fields) {
582
+ return this.requestInternal(op, fields);
583
+ }
584
+ requestInternal(op, fields) {
585
+ if (this.fatalError !== void 0) return Promise.reject(this.fatalError);
586
+ const id = `m${++this.sequence}`;
587
+ let line;
588
+ try {
589
+ line = `${JSON.stringify({ id, protocol: AGENT_MEMORY_FILESYSTEM_HELPER_PROTOCOL, op, ...fields })}
590
+ `;
591
+ } catch {
592
+ return Promise.reject(new AgentMemoryError("Agent memory filesystem helper request could not be encoded"));
593
+ }
594
+ if (Buffer.byteLength(line, "utf8") > HELPER_MAX_JSON_LINE_BYTES) {
595
+ return Promise.reject(new AgentMemoryError("Agent memory filesystem helper request exceeded bounded stdin"));
596
+ }
597
+ return new Promise((resolve, reject) => {
598
+ const timer = setTimeout(() => {
599
+ this.pending.delete(id);
600
+ const error = new AgentMemoryError("Agent memory filesystem helper request timed out");
601
+ reject(error);
602
+ this.fail(error);
603
+ }, HELPER_REQUEST_TIMEOUT_MS);
604
+ timer.unref?.();
605
+ this.pending.set(id, { resolve: (response) => resolve(response.result), reject, timer });
606
+ this.child.stdin.write(line, (error) => {
607
+ if (!error) return;
608
+ const item = this.pending.get(id);
609
+ if (item === void 0) return;
610
+ clearTimeout(item.timer);
611
+ this.pending.delete(id);
612
+ item.reject(new AgentMemoryError("Agent memory filesystem helper request could not be written"));
613
+ });
614
+ });
615
+ }
616
+ handleStdoutChunk(chunk) {
617
+ let offset = 0;
618
+ while (offset < chunk.length && this.fatalError === void 0) {
619
+ const newline = chunk.indexOf(10, offset);
620
+ const end = newline === -1 ? chunk.length : newline;
621
+ const segment = chunk.subarray(offset, end);
622
+ if (this.stdoutBuffer.length + segment.length > HELPER_MAX_JSON_LINE_BYTES) {
623
+ this.fail(new AgentMemoryError("Agent memory filesystem helper exceeded bounded stdout"));
624
+ return;
625
+ }
626
+ this.stdoutBuffer = this.stdoutBuffer.length === 0 ? Buffer.from(segment) : Buffer.concat([this.stdoutBuffer, segment]);
627
+ if (newline === -1) return;
628
+ const line = this.stdoutBuffer;
629
+ this.stdoutBuffer = Buffer.alloc(0);
630
+ this.handleLine(line);
631
+ offset = newline + 1;
632
+ }
633
+ }
634
+ handleLine(line) {
635
+ let parsed;
636
+ try {
637
+ parsed = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(line));
638
+ } catch {
639
+ this.fail(new AgentMemoryError("Agent memory filesystem helper returned malformed JSON"));
640
+ return;
641
+ }
642
+ const response = responseRecord(parsed);
643
+ if (response === void 0) {
644
+ this.fail(new AgentMemoryError("Agent memory filesystem helper returned an invalid response"));
645
+ return;
646
+ }
647
+ const item = this.pending.get(response.id);
648
+ if (item === void 0) {
649
+ this.fail(new AgentMemoryError("Agent memory filesystem helper returned an unsolicited response"));
650
+ return;
651
+ }
652
+ clearTimeout(item.timer);
653
+ this.pending.delete(response.id);
654
+ if (response.ok) {
655
+ item.resolve(response);
656
+ return;
657
+ }
658
+ if (response.error.code === "revision_conflict" && typeof response.error.actualRevision === "string") {
659
+ item.reject(new HelperRevisionConflict(response.error.actualRevision));
660
+ return;
661
+ }
662
+ item.reject(new AgentMemoryError(`Agent memory filesystem helper rejected the operation: ${response.error.code}`));
663
+ }
664
+ fail(error) {
665
+ if (this.fatalError !== void 0) return;
666
+ this.fatalError = error;
667
+ this.rejectPending(error);
668
+ if (this.child.exitCode === null && this.child.signalCode === null) this.child.kill();
669
+ }
670
+ rejectPending(error) {
671
+ for (const item of this.pending.values()) {
672
+ clearTimeout(item.timer);
673
+ item.reject(error);
674
+ }
675
+ this.pending.clear();
676
+ }
677
+ };
678
+ async function openAgentMemoryFilesystemHelper(input) {
679
+ return AgentMemoryFilesystemHelperClient.open(input);
680
+ }
681
+ var AGENT_MEMORY_RECALL_TOOL_NAME = "memory.recall";
682
+ var AGENT_MEMORY_SAVE_TOOL_NAME = "memory.save";
683
+ function record(value) {
684
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
685
+ }
686
+ function invalid(id, message) {
687
+ return { jsonrpc: "2.0", id, error: { code: -32602, message } };
688
+ }
689
+ function success(id, value) {
690
+ return { jsonrpc: "2.0", id, result: { content: [{ type: "text", text: JSON.stringify(value) }] } };
691
+ }
692
+ async function handleAgentMemoryMcpRequest(request, deps) {
693
+ const id = request.id;
694
+ if (request.method === "initialize") {
695
+ const params2 = record(request.params) ?? {};
696
+ return { jsonrpc: "2.0", id, result: { protocolVersion: typeof params2.protocolVersion === "string" ? params2.protocolVersion : "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "byok-agent-memory-mcp", version: "0.0.1" } } };
697
+ }
698
+ if (request.method === "notifications/initialized") return void 0;
699
+ if (request.method === "tools/list") return {
700
+ jsonrpc: "2.0",
701
+ id,
702
+ result: { tools: [
703
+ { name: AGENT_MEMORY_RECALL_TOOL_NAME, description: "Recall one SDK-owned memory file for this exact active Agent task. Identity and memory root are never model parameters.", inputSchema: { type: "object", additionalProperties: false, required: ["path"], properties: { path: { type: "string" }, ifRevision: { type: "string", pattern: "^sha256:[a-f0-9]{64}$" } } } },
704
+ { name: AGENT_MEMORY_SAVE_TOOL_NAME, description: "Atomically replace or delete one SDK-owned memory file with exact sha256 compare-and-swap.", inputSchema: { type: "object", additionalProperties: false, required: ["op", "path", "expectedRevision"], properties: { op: { type: "string", enum: ["replace", "delete"] }, path: { type: "string" }, expectedRevision: { type: "string", pattern: "^sha256:[a-f0-9]{64}$" }, content: { type: "string" } } } }
705
+ ] }
706
+ };
707
+ if (request.method !== "tools/call") return id === void 0 ? void 0 : { jsonrpc: "2.0", id, error: { code: -32601, message: `unknown method: ${String(request.method)}` } };
708
+ const params = record(request.params);
709
+ const args = record(params?.arguments);
710
+ if (!params || !args || typeof params.name !== "string") return invalid(id, "memory tool input must be an object");
711
+ try {
712
+ if (params.name === AGENT_MEMORY_RECALL_TOOL_NAME) {
713
+ if (Object.keys(args).some((key) => key !== "path" && key !== "ifRevision") || typeof args.path !== "string" || args.ifRevision !== void 0 && typeof args.ifRevision !== "string") return invalid(id, "memory.recall accepts only path and optional ifRevision");
714
+ return success(id, await deps.recall({ path: args.path, ...args.ifRevision === void 0 ? {} : { ifRevision: args.ifRevision } }));
715
+ }
716
+ if (params.name === AGENT_MEMORY_SAVE_TOOL_NAME) {
717
+ if (Object.keys(args).some((key) => key !== "op" && key !== "path" && key !== "expectedRevision" && key !== "content") || args.op !== "replace" && args.op !== "delete" || typeof args.path !== "string" || typeof args.expectedRevision !== "string" || args.op === "replace" && typeof args.content !== "string" || args.op === "delete" && args.content !== void 0) return invalid(id, "memory.save requires replace|delete, path, expectedRevision, and content only for replace");
718
+ const content = args.content;
719
+ return success(id, await deps.save({ op: args.op, path: args.path, expectedRevision: args.expectedRevision, ...typeof content === "string" ? { content } : {} }));
720
+ }
721
+ return invalid(id, "unknown Agent memory tool");
722
+ } catch (error) {
723
+ return { jsonrpc: "2.0", id, error: { code: -32e3, message: error instanceof Error ? error.message : String(error) } };
724
+ }
725
+ }
726
+ function serveAgentMemoryMcpOverStdio(input) {
727
+ const reader = createInterface({ input: input.stdin ?? process.stdin, terminal: false });
728
+ const output = input.stdout ?? process.stdout;
729
+ reader.on("line", (line) => {
730
+ const trimmed = line.trim();
731
+ if (!trimmed) return;
732
+ void (async () => {
733
+ let request;
734
+ try {
735
+ request = JSON.parse(trimmed);
736
+ } catch {
737
+ return;
738
+ }
739
+ const response = await handleAgentMemoryMcpRequest(request, input.deps);
740
+ if (response !== void 0) output.write(`${JSON.stringify(response)}
741
+ `);
742
+ })();
743
+ });
744
+ }
745
+
746
+ // src/daemon/memory-guidance.ts
747
+ var AGENT_MEMORY_GUIDANCE = [
748
+ "At the start of this Agent task, first read `MEMORY.md` in the provided `cwd`.",
749
+ "Treat `MEMORY.md` as a concise, self-contained recovery index; if it is empty, initialize a brief index from durable, non-secret task knowledge.",
750
+ "Read files under `notes/` only as needed, following pointers from the index.",
751
+ "When task permissions allow and a durable value is learned, update the relevant `notes/` entry and the `MEMORY.md` index.",
752
+ "Never write credentials, secrets, tokens, API keys, private keys, or other authentication material to `MEMORY.md` or `notes/`."
753
+ ].join("\n");
754
+ function prependAgentMemoryGuidance(instruction) {
755
+ return `${AGENT_MEMORY_GUIDANCE}
756
+
757
+ ${instruction}`;
758
+ }
759
+
760
+ export { AGENT_MEMORY_GUIDANCE, AGENT_MEMORY_RECALL_TOOL_NAME, AGENT_MEMORY_SAVE_TOOL_NAME, AgentMemoryError, AgentMemoryRevisionConflictError, AgentMemoryService, captureAgentMemorySnapshot, isAgentMemoryFilesystemHelperSupported, isAgentMemorySecureFilesystemAvailable, openAgentMemoryFilesystemHelper, prependAgentMemoryGuidance, serveAgentMemoryMcpOverStdio, validateAgentMemoryPath };
761
+ //# sourceMappingURL=index.js.map
762
+ //# sourceMappingURL=index.js.map