dsh-mnemon 0.3.3 → 0.3.4
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 +4 -2
- package/README.zh-CN.md +4 -2
- package/lib/client.js +121 -9
- package/lib/index.js +345 -103
- package/lib/types/src/client/api.d.ts +1 -0
- package/lib/types/src/client/locales.d.ts +24 -0
- package/lib/types/src/config-values.d.ts +5 -0
- package/lib/types/src/config.d.ts +1 -1
- package/lib/types/src/contracts.d.ts +8 -0
- package/lib/types/src/git-branch.d.ts +6 -0
- package/lib/types/src/memory-view.d.ts +8 -2
- package/lib/types/src/pack.d.ts +2 -1
- package/lib/types/src/runtime-memory.d.ts +22 -6
- package/lib/types/src/shared/contracts.d.ts +24 -0
- package/lib/types/src/subagent.d.ts +2 -1
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { createHash, randomUUID } from "node:crypto";
|
|
|
11
11
|
import { accessSync, closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
12
12
|
import { homedir, tmpdir } from "node:os";
|
|
13
13
|
import { strToU8, unzipSync, zipSync } from "fflate";
|
|
14
|
+
import { execFileSync } from "node:child_process";
|
|
14
15
|
import { fileURLToPath } from "node:url";
|
|
15
16
|
import { z as z$1 } from "zod";
|
|
16
17
|
//#region src/shared/contracts.ts
|
|
@@ -54,6 +55,11 @@ const DEFAULT_IDLE_REVIEW_MS = 3e4;
|
|
|
54
55
|
const DEFAULT_RECALL_QUALITY_POLICY = "strict-v1";
|
|
55
56
|
const DEFAULT_RECALL_LOW_SCORE_THRESHOLD = .25;
|
|
56
57
|
const DEFAULT_RECALL_HIGH_SCORE_THRESHOLD = .6;
|
|
58
|
+
const DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES = 10240;
|
|
59
|
+
const DEFAULT_RUNTIME_USER_LIMIT_BYTES = 4096;
|
|
60
|
+
const DEFAULT_RUNTIME_MAINTENANCE_MAX_TOKENS = 8192;
|
|
61
|
+
const MAX_RUNTIME_MEMORY_LIMIT_BYTES = 1048576;
|
|
62
|
+
const MAX_RUNTIME_MAINTENANCE_MAX_TOKENS = 1e6;
|
|
57
63
|
//#endregion
|
|
58
64
|
//#region src/config.ts
|
|
59
65
|
const InteractionConfig = z.object({
|
|
@@ -118,6 +124,11 @@ const RecallQualitySchema = z.object({
|
|
|
118
124
|
maxMediumResults: z.number().step(1).min(0).max(50).default(4),
|
|
119
125
|
maxUnknownResults: z.number().step(1).min(0).max(50).default(2)
|
|
120
126
|
});
|
|
127
|
+
const RuntimeMemorySchema = z.object({
|
|
128
|
+
memoryLimitBytes: z.number().step(1).min(1).max(MAX_RUNTIME_MEMORY_LIMIT_BYTES).default(DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES),
|
|
129
|
+
userLimitBytes: z.number().step(1).min(1).max(MAX_RUNTIME_MEMORY_LIMIT_BYTES).default(DEFAULT_RUNTIME_USER_LIMIT_BYTES),
|
|
130
|
+
maintenanceMaxTokens: z.number().step(1).min(1).max(MAX_RUNTIME_MAINTENANCE_MAX_TOKENS).default(DEFAULT_RUNTIME_MAINTENANCE_MAX_TOKENS)
|
|
131
|
+
});
|
|
121
132
|
const MemoryParticipationModeSchema = z.union([
|
|
122
133
|
"off",
|
|
123
134
|
"manual",
|
|
@@ -144,6 +155,7 @@ const Config = z.object({
|
|
|
144
155
|
"workspace",
|
|
145
156
|
"custom"
|
|
146
157
|
]),
|
|
158
|
+
runtimeUserScope: z.union(["storage", "global"]).default("storage"),
|
|
147
159
|
cliPath: z.string(),
|
|
148
160
|
dataDir: z.string(),
|
|
149
161
|
customPackId: z.string(),
|
|
@@ -155,6 +167,11 @@ const Config = z.object({
|
|
|
155
167
|
store: z.string(),
|
|
156
168
|
timeoutMs: z.number().step(1).min(100).max(12e4).default(DEFAULT_TIMEOUT_MS),
|
|
157
169
|
defaultRecallLimit: z.number().step(1).min(1).max(50).default(10),
|
|
170
|
+
runtimeMemory: RuntimeMemorySchema.default({
|
|
171
|
+
memoryLimitBytes: DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES,
|
|
172
|
+
userLimitBytes: DEFAULT_RUNTIME_USER_LIMIT_BYTES,
|
|
173
|
+
maintenanceMaxTokens: DEFAULT_RUNTIME_MAINTENANCE_MAX_TOKENS
|
|
174
|
+
}),
|
|
158
175
|
embedding: MnemonEmbeddingSchema.default({
|
|
159
176
|
enabled: false,
|
|
160
177
|
endpoint: DEFAULT_EMBEDDING_ENDPOINT,
|
|
@@ -337,6 +354,19 @@ function resolveRecallQuality(value) {
|
|
|
337
354
|
maxUnknownResults
|
|
338
355
|
};
|
|
339
356
|
}
|
|
357
|
+
function resolveRuntimeMemory(value) {
|
|
358
|
+
const memoryLimitBytes = value?.memoryLimitBytes ?? 10240;
|
|
359
|
+
const userLimitBytes = value?.userLimitBytes ?? 4096;
|
|
360
|
+
const maintenanceMaxTokens = value?.maintenanceMaxTokens ?? 8192;
|
|
361
|
+
if (!Number.isInteger(memoryLimitBytes) || memoryLimitBytes < 1 || memoryLimitBytes > 1048576) throw new Error(`dsh-mnemon: Runtime MEMORY.md limit must be an integer within 1..${MAX_RUNTIME_MEMORY_LIMIT_BYTES} bytes`);
|
|
362
|
+
if (!Number.isInteger(userLimitBytes) || userLimitBytes < 1 || userLimitBytes > 1048576) throw new Error(`dsh-mnemon: Runtime USER.md limit must be an integer within 1..${MAX_RUNTIME_MEMORY_LIMIT_BYTES} bytes`);
|
|
363
|
+
if (!Number.isInteger(maintenanceMaxTokens) || maintenanceMaxTokens < 1 || maintenanceMaxTokens > 1e6) throw new Error(`dsh-mnemon: Runtime maintenance maxTokens must be an integer within 1..${MAX_RUNTIME_MAINTENANCE_MAX_TOKENS}`);
|
|
364
|
+
return {
|
|
365
|
+
memoryLimitBytes,
|
|
366
|
+
userLimitBytes,
|
|
367
|
+
maintenanceMaxTokens
|
|
368
|
+
};
|
|
369
|
+
}
|
|
340
370
|
const MEMORY_COMPONENT_ID = /^[a-z][a-z0-9-]{0,127}$/u;
|
|
341
371
|
const MEMORY_PARTICIPATION_MODES = /* @__PURE__ */ new Set([
|
|
342
372
|
"off",
|
|
@@ -387,6 +417,8 @@ function resolveConfig(config = {}) {
|
|
|
387
417
|
if (requestedPackId !== void 0 && !CUSTOM_PACK_ID.test(requestedPackId)) throw new Error("dsh-mnemon: customPackId is invalid");
|
|
388
418
|
const store = optionalText$1(config.store);
|
|
389
419
|
const storageScope = config.storageScope ?? (legacyDataDir === void 0 && legacyPacks.length === 0 ? "global" : "custom");
|
|
420
|
+
const runtimeUserScope = config.runtimeUserScope ?? "storage";
|
|
421
|
+
if (runtimeUserScope !== "storage" && runtimeUserScope !== "global") throw new Error(`dsh-mnemon: unsupported Runtime USER.md scope: ${String(runtimeUserScope)}`);
|
|
390
422
|
const selectedPack = requestedPackId === void 0 ? legacyPacks.find((pack) => pack.dataDir === legacyDataDir) ?? (legacyPacks.length === 1 ? legacyPacks[0] : void 0) : legacyPacks.find((pack) => pack.id === requestedPackId);
|
|
391
423
|
if (requestedPackId !== void 0 && selectedPack === void 0) throw new Error(`dsh-mnemon: unknown custom Pack: ${requestedPackId}`);
|
|
392
424
|
const dataDir = selectedPack?.dataDir ?? legacyDataDir;
|
|
@@ -394,11 +426,13 @@ function resolveConfig(config = {}) {
|
|
|
394
426
|
if (store !== void 0 && !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(store)) throw new Error("dsh-mnemon: store must match [a-zA-Z0-9][a-zA-Z0-9_-]*");
|
|
395
427
|
return {
|
|
396
428
|
storageScope,
|
|
429
|
+
runtimeUserScope,
|
|
397
430
|
...cliPath === void 0 ? {} : { cliPath },
|
|
398
431
|
...dataDir === void 0 ? {} : { dataDir },
|
|
399
432
|
...store === void 0 ? {} : { store },
|
|
400
433
|
timeoutMs: config.timeoutMs ?? 1e4,
|
|
401
434
|
defaultRecallLimit: config.defaultRecallLimit ?? 10,
|
|
435
|
+
runtimeMemory: resolveRuntimeMemory(config.runtimeMemory),
|
|
402
436
|
embedding: resolveEmbedding(config.embedding),
|
|
403
437
|
memoryTopology: resolveMemoryTopology(config.memoryTopology),
|
|
404
438
|
recallQuality: resolveRecallQuality(config.recallQuality),
|
|
@@ -1163,8 +1197,8 @@ var DocumentManager = class {
|
|
|
1163
1197
|
};
|
|
1164
1198
|
const RUNTIME_ENTRY_DELIMITER = "\n§\n";
|
|
1165
1199
|
const RUNTIME_MEMORY_LIMITS = {
|
|
1166
|
-
memory:
|
|
1167
|
-
user:
|
|
1200
|
+
memory: DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES,
|
|
1201
|
+
user: DEFAULT_RUNTIME_USER_LIMIT_BYTES
|
|
1168
1202
|
};
|
|
1169
1203
|
const RUNTIME_MEMORY_PROTOCOL = `MNEMON RUNTIME MEMORY PROTOCOL
|
|
1170
1204
|
Runtime Memory keeps compact hot memory available for every turn. The latest MNEMON RUNTIME MEMORY SNAPSHOT in runtime context is a complete projection of USER.md and MEMORY.md and supersedes earlier Runtime Memory snapshots. Apply applicable entries silently and never recite them merely to prove they were read.
|
|
@@ -1184,6 +1218,7 @@ WRITE PROTOCOL
|
|
|
1184
1218
|
- Choose target="user" only for the user profile and target="memory" only for project/environment knowledge. Use importance="critical" for explicit must/always/never rules or strong preferences, "low" for transient or one-time facts that are still worth keeping, and "normal" otherwise.
|
|
1185
1219
|
- Entries are separated by a standalone §. old_text must uniquely identify one entry. Tool receipts are sufficient; do not echo either complete file after a successful mutation.
|
|
1186
1220
|
- If USER.md reaches capacity, the tool conservatively consolidates the local profile without sending preferences to Mnemon Memory Spaces. If MEMORY.md reaches capacity, the tool archives committed working memories into one or more semantically appropriate Memory Spaces, then atomically applies compaction and the pending mutation only when the reviewed revision is still current. Never evade either limit with direct file edits.
|
|
1221
|
+
- Branch scoping (target=memory only): pass the optional branches parameter (a list of git branch names) to project an entry only in sessions on those branches; omit it for cross-branch facts. Use it for branch-specific architecture decisions and experiments, and tag new branch-scoped entries with the git branch reported in the snapshot header. On replace, provide branches to change the scope, an empty list to clear it, or omit it to keep the current scope. Non-git workspaces and detached HEAD project every entry regardless of scope.
|
|
1187
1222
|
|
|
1188
1223
|
IMPORTANT: Runtime Memory is always relevant when applicable, after the current request. Use mnemon_runtime_memory only when the criteria above are met; otherwise do not mutate memory.`;
|
|
1189
1224
|
const LOCK_TIMEOUT_MS$1 = 5e3;
|
|
@@ -1219,6 +1254,41 @@ function isTarget(value) {
|
|
|
1219
1254
|
function isImportance(value) {
|
|
1220
1255
|
return value === "critical" || value === "normal" || value === "low";
|
|
1221
1256
|
}
|
|
1257
|
+
const RUNTIME_BRANCH_NAME_RE = /^[A-Za-z0-9._/-]+$/u;
|
|
1258
|
+
/** Validate a model-supplied branch scope. Returns the normalized list; an empty list means "no scope". */
|
|
1259
|
+
function normalizeRuntimeBranches(value, field = "branches") {
|
|
1260
|
+
if (!Array.isArray(value)) throw new Error(`${field} must be an array of git branch names`);
|
|
1261
|
+
const branches = [];
|
|
1262
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1263
|
+
for (const item of value) {
|
|
1264
|
+
if (typeof item !== "string") throw new Error(`${field} must contain only git branch names`);
|
|
1265
|
+
const branch = item.trim();
|
|
1266
|
+
if (branch === "" || branch.length > 128 || !RUNTIME_BRANCH_NAME_RE.test(branch) || branch === "-" || branch.startsWith("/") || branch.startsWith("-") || branch.endsWith("/") || branch.endsWith(".") || branch.includes("..") || branch.includes("//") || branch.includes("@{")) throw new Error(`${field} must contain git branch names (letters, numbers, dot, underscore, slash, dash)`);
|
|
1267
|
+
if (seen.has(branch)) throw new Error(`${field} must not repeat a branch name`);
|
|
1268
|
+
seen.add(branch);
|
|
1269
|
+
branches.push(branch);
|
|
1270
|
+
}
|
|
1271
|
+
if (branches.length > 16) throw new Error(`${field} supports at most 16 branch names`);
|
|
1272
|
+
return branches;
|
|
1273
|
+
}
|
|
1274
|
+
/** Parse a stored branch scope. Returns undefined for absent or invalid data. */
|
|
1275
|
+
function parseRuntimeBranches(value) {
|
|
1276
|
+
if (value === void 0) return void 0;
|
|
1277
|
+
if (!Array.isArray(value)) return void 0;
|
|
1278
|
+
const branches = [];
|
|
1279
|
+
for (const item of value) {
|
|
1280
|
+
if (typeof item !== "string" || item === "" || item.length > 128 || !RUNTIME_BRANCH_NAME_RE.test(item)) return void 0;
|
|
1281
|
+
branches.push(item);
|
|
1282
|
+
}
|
|
1283
|
+
return branches;
|
|
1284
|
+
}
|
|
1285
|
+
function entryMatchesBranch(entry, branch) {
|
|
1286
|
+
return entry.branches === void 0 || entry.branches.length === 0 || entry.branches.includes(branch);
|
|
1287
|
+
}
|
|
1288
|
+
function scopeBranch(branch) {
|
|
1289
|
+
const value = branch?.trim();
|
|
1290
|
+
return value === void 0 || value === "" ? void 0 : value;
|
|
1291
|
+
}
|
|
1222
1292
|
function normalizeContent(value, field) {
|
|
1223
1293
|
const content = value?.trim().replace(/\s+/gu, " ") ?? "";
|
|
1224
1294
|
if (content === "") throw new Error(`${field} is required`);
|
|
@@ -1232,12 +1302,15 @@ function parseEntry(value) {
|
|
|
1232
1302
|
if (typeof value.created_at !== "string" || typeof value.updated_at !== "string") return void 0;
|
|
1233
1303
|
const content = value.content.trim().replace(/\s+/gu, " ");
|
|
1234
1304
|
if (content === "" || content.includes("§")) return void 0;
|
|
1305
|
+
if (value.target === "user" && value.branches !== void 0) return void 0;
|
|
1306
|
+
const branches = parseRuntimeBranches(value.branches);
|
|
1235
1307
|
return {
|
|
1236
1308
|
content,
|
|
1237
1309
|
created_at: value.created_at,
|
|
1238
1310
|
updated_at: value.updated_at,
|
|
1239
1311
|
target: value.target,
|
|
1240
|
-
importance: value.importance
|
|
1312
|
+
importance: value.importance,
|
|
1313
|
+
...branches === void 0 ? {} : { branches }
|
|
1241
1314
|
};
|
|
1242
1315
|
}
|
|
1243
1316
|
function byteCount(entries, target) {
|
|
@@ -1259,6 +1332,8 @@ function prepareMutation(before, request, now) {
|
|
|
1259
1332
|
"remove"
|
|
1260
1333
|
].includes(request.action)) throw new Error("action must be add, replace, or remove");
|
|
1261
1334
|
if (request.importance !== void 0 && !isImportance(request.importance)) throw new Error("importance must be critical, normal, or low");
|
|
1335
|
+
if (request.target === "user" && request.branches !== void 0) throw new Error("branches applies to target=memory only");
|
|
1336
|
+
const branchScope = request.branches === void 0 ? void 0 : normalizeRuntimeBranches(request.branches);
|
|
1262
1337
|
const entries = before.map((entry) => ({ ...entry }));
|
|
1263
1338
|
if (request.action === "add") {
|
|
1264
1339
|
const content = normalizeContent(request.content, "content");
|
|
@@ -1277,7 +1352,8 @@ function prepareMutation(before, request, now) {
|
|
|
1277
1352
|
created_at: now,
|
|
1278
1353
|
updated_at: now,
|
|
1279
1354
|
target: request.target,
|
|
1280
|
-
importance: request.importance ?? "normal"
|
|
1355
|
+
importance: request.importance ?? "normal",
|
|
1356
|
+
...branchScope !== void 0 && branchScope.length > 0 ? { branches: branchScope } : {}
|
|
1281
1357
|
};
|
|
1282
1358
|
return {
|
|
1283
1359
|
changed: true,
|
|
@@ -1305,6 +1381,10 @@ function prepareMutation(before, request, now) {
|
|
|
1305
1381
|
updated_at: now,
|
|
1306
1382
|
importance: request.importance ?? previous.importance
|
|
1307
1383
|
};
|
|
1384
|
+
if (branchScope !== void 0) {
|
|
1385
|
+
if (branchScope.length === 0) delete pendingEntry.branches;
|
|
1386
|
+
else pendingEntry.branches = branchScope;
|
|
1387
|
+
}
|
|
1308
1388
|
entries[index] = pendingEntry;
|
|
1309
1389
|
return {
|
|
1310
1390
|
changed: true,
|
|
@@ -1340,12 +1420,14 @@ function compactionCandidates(compacted, existing, target, now) {
|
|
|
1340
1420
|
if (seen.has(content)) throw new Error("compacted runtime memory contains duplicate entries");
|
|
1341
1421
|
seen.add(content);
|
|
1342
1422
|
const unchanged = existing.find((current) => current.content === content);
|
|
1423
|
+
const inheritedBranches = entry.branches ?? unchanged?.branches;
|
|
1343
1424
|
return {
|
|
1344
1425
|
content,
|
|
1345
1426
|
created_at: unchanged?.created_at ?? now,
|
|
1346
1427
|
updated_at: unchanged?.updated_at ?? now,
|
|
1347
1428
|
target,
|
|
1348
|
-
importance: entry.importance
|
|
1429
|
+
importance: entry.importance,
|
|
1430
|
+
...inheritedBranches === void 0 ? {} : { branches: inheritedBranches }
|
|
1349
1431
|
};
|
|
1350
1432
|
});
|
|
1351
1433
|
}
|
|
@@ -1376,49 +1458,112 @@ function sleepSync$1(milliseconds) {
|
|
|
1376
1458
|
* Single authority for hot memory. JSON is the durable source of truth;
|
|
1377
1459
|
* Markdown files are deterministic projections consumed by prompt assembly.
|
|
1378
1460
|
*/
|
|
1379
|
-
var RuntimeMemoryController = class {
|
|
1461
|
+
var RuntimeMemoryController = class RuntimeMemoryController {
|
|
1380
1462
|
now;
|
|
1381
1463
|
recordCommit;
|
|
1382
1464
|
directory;
|
|
1383
1465
|
sourcePath;
|
|
1466
|
+
userSourcePath;
|
|
1384
1467
|
memoryPath;
|
|
1385
1468
|
userPath;
|
|
1386
1469
|
lockPath;
|
|
1470
|
+
limits;
|
|
1387
1471
|
queue = Promise.resolve();
|
|
1388
|
-
|
|
1472
|
+
localUserPath;
|
|
1473
|
+
userController;
|
|
1474
|
+
constructor(runner, now = () => /* @__PURE__ */ new Date(), recordCommit, limits = RUNTIME_MEMORY_LIMITS, userRunner) {
|
|
1389
1475
|
this.now = now;
|
|
1390
1476
|
this.recordCommit = recordCommit;
|
|
1477
|
+
for (const [target, limit] of Object.entries(limits)) if (!Number.isInteger(limit) || limit < 1 || limit > 1048576) throw new Error(`runtime ${target} memory limit must be an integer within 1..${MAX_RUNTIME_MEMORY_LIMIT_BYTES} bytes`);
|
|
1478
|
+
this.limits = {
|
|
1479
|
+
memory: limits.memory,
|
|
1480
|
+
user: limits.user
|
|
1481
|
+
};
|
|
1391
1482
|
this.directory = join(runner.effectiveDataDir(), "runtime");
|
|
1392
1483
|
this.sourcePath = join(this.directory, "memories.json");
|
|
1393
1484
|
this.memoryPath = join(this.directory, "MEMORY.md");
|
|
1394
|
-
this.
|
|
1485
|
+
this.localUserPath = join(this.directory, "USER.md");
|
|
1395
1486
|
this.lockPath = join(this.directory, ".memories.lock");
|
|
1487
|
+
const userDirectory = userRunner === void 0 ? this.directory : join(userRunner.effectiveDataDir(), "runtime");
|
|
1488
|
+
this.userController = userDirectory === this.directory || userRunner === void 0 ? void 0 : new RuntimeMemoryController(userRunner, now, recordCommit, this.limits);
|
|
1489
|
+
this.userPath = this.userController?.userPath ?? this.localUserPath;
|
|
1490
|
+
this.userSourcePath = this.userController?.sourcePath ?? this.sourcePath;
|
|
1396
1491
|
this.initialize();
|
|
1397
1492
|
}
|
|
1398
1493
|
snapshot() {
|
|
1399
|
-
const
|
|
1400
|
-
|
|
1401
|
-
|
|
1494
|
+
const local = this.readSource();
|
|
1495
|
+
if (this.userController === void 0) return this.snapshotUnlocked(local);
|
|
1496
|
+
const global = this.userController.readSource();
|
|
1497
|
+
return this.snapshotUnlocked({
|
|
1498
|
+
version: 1,
|
|
1499
|
+
entries: [...global.entries.filter((entry) => entry.target === "user"), ...local.entries.filter((entry) => entry.target === "memory")]
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
contextText(branch) {
|
|
1503
|
+
return this.contextProjection(branch).text;
|
|
1504
|
+
}
|
|
1505
|
+
/**
|
|
1506
|
+
* Read the exact Runtime revision and its prompt projection from each
|
|
1507
|
+
* configured authority root.
|
|
1508
|
+
* When a git branch is supplied, target=memory entries scoped to other
|
|
1509
|
+
* branches are hidden from the projection; the on-disk Markdown files and
|
|
1510
|
+
* the source JSON always remain complete.
|
|
1511
|
+
*/
|
|
1512
|
+
contextProjection(branch) {
|
|
1513
|
+
const branchScope = scopeBranch(branch);
|
|
1514
|
+
const local = this.localContextProjection(branchScope);
|
|
1515
|
+
const global = this.userController?.localContextProjection();
|
|
1516
|
+
const user = global?.user ?? local.user;
|
|
1517
|
+
const memory = local.memory;
|
|
1518
|
+
const entries = global === void 0 ? local.snapshot.entries : [...global.snapshot.entries.filter((entry) => entry.target === "user"), ...local.snapshot.entries.filter((entry) => entry.target === "memory")];
|
|
1519
|
+
const visibleMemory = local.snapshot.targets.memory;
|
|
1520
|
+
const visibleUser = global?.snapshot.targets.user ?? local.snapshot.targets.user;
|
|
1521
|
+
const snapshot = global === void 0 ? local.snapshot : {
|
|
1402
1522
|
directory: this.directory,
|
|
1403
1523
|
sourcePath: this.sourcePath,
|
|
1404
|
-
revision: revision(
|
|
1524
|
+
revision: revision({
|
|
1525
|
+
version: 1,
|
|
1526
|
+
entries
|
|
1527
|
+
}),
|
|
1405
1528
|
generatedAt: this.now().toISOString(),
|
|
1406
1529
|
entries,
|
|
1407
1530
|
targets: {
|
|
1408
|
-
memory:
|
|
1409
|
-
|
|
1531
|
+
memory: {
|
|
1532
|
+
...visibleMemory,
|
|
1533
|
+
markdownPath: this.memoryPath
|
|
1534
|
+
},
|
|
1535
|
+
user: {
|
|
1536
|
+
...visibleUser,
|
|
1537
|
+
markdownPath: this.userPath
|
|
1538
|
+
}
|
|
1410
1539
|
}
|
|
1411
1540
|
};
|
|
1541
|
+
const storeMemory = this.targetView(entries, "memory");
|
|
1542
|
+
const storeUser = this.targetView(entries, "user");
|
|
1543
|
+
const branchLine = branchScope === void 0 ? "" : `\nGit branch: ${branchScope}${local.hidden > 0 ? ` (${local.hidden} branch-scoped entr${local.hidden === 1 ? "y" : "ies"} hidden)` : ""}`;
|
|
1544
|
+
return {
|
|
1545
|
+
revision: snapshot.revision,
|
|
1546
|
+
text: `MNEMON RUNTIME MEMORY SNAPSHOT
|
|
1547
|
+
Revision: ${snapshot.revision}${branchLine}
|
|
1548
|
+
|
|
1549
|
+
Contents of USER.md (user profile; entries: ${visibleUser.entryCount}; UTF-8 bytes: ${storeUser.used}/${storeUser.limit})
|
|
1550
|
+
<runtime-memory-file name="USER.md">
|
|
1551
|
+
${user || "(empty)"}
|
|
1552
|
+
</runtime-memory-file>
|
|
1553
|
+
|
|
1554
|
+
Contents of MEMORY.md (working reference; entries: ${visibleMemory.entryCount}; UTF-8 bytes: ${storeMemory.used}/${storeMemory.limit})
|
|
1555
|
+
<runtime-memory-file name="MEMORY.md">
|
|
1556
|
+
${memory || "(empty)"}
|
|
1557
|
+
</runtime-memory-file>`
|
|
1558
|
+
};
|
|
1412
1559
|
}
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
/** Read the exact Runtime revision and its prompt projection under one lock. */
|
|
1417
|
-
contextProjection() {
|
|
1418
|
-
const { snapshot, user, memory } = this.withLock(() => {
|
|
1560
|
+
localContextProjection(branch) {
|
|
1561
|
+
const branchScope = scopeBranch(branch);
|
|
1562
|
+
return this.withLock(() => {
|
|
1419
1563
|
const file = this.readSource();
|
|
1420
1564
|
this.repairProjections(file);
|
|
1421
1565
|
const entries = file.entries.map((entry) => ({ ...entry }));
|
|
1566
|
+
const visible = branchScope === void 0 ? entries : entries.filter((entry) => entry.target === "user" || entryMatchesBranch(entry, branchScope));
|
|
1422
1567
|
return {
|
|
1423
1568
|
snapshot: {
|
|
1424
1569
|
directory: this.directory,
|
|
@@ -1427,33 +1572,18 @@ var RuntimeMemoryController = class {
|
|
|
1427
1572
|
generatedAt: this.now().toISOString(),
|
|
1428
1573
|
entries,
|
|
1429
1574
|
targets: {
|
|
1430
|
-
memory: this.targetView(
|
|
1431
|
-
user: this.targetView(
|
|
1575
|
+
memory: this.targetView(visible, "memory"),
|
|
1576
|
+
user: this.targetView(visible, "user")
|
|
1432
1577
|
}
|
|
1433
1578
|
},
|
|
1434
|
-
user: readFileSync(this.
|
|
1435
|
-
memory: readFileSync(this.memoryPath, "utf8").trimEnd()
|
|
1579
|
+
user: readFileSync(this.localUserPath, "utf8").trimEnd(),
|
|
1580
|
+
memory: branchScope === void 0 ? readFileSync(this.memoryPath, "utf8").trimEnd() : markdown(visible, "memory").trimEnd(),
|
|
1581
|
+
hidden: branchScope === void 0 ? 0 : entries.length - visible.length
|
|
1436
1582
|
};
|
|
1437
1583
|
});
|
|
1438
|
-
const userUsage = snapshot.targets.user;
|
|
1439
|
-
const memoryUsage = snapshot.targets.memory;
|
|
1440
|
-
return {
|
|
1441
|
-
revision: snapshot.revision,
|
|
1442
|
-
text: `MNEMON RUNTIME MEMORY SNAPSHOT
|
|
1443
|
-
Revision: ${snapshot.revision}
|
|
1444
|
-
|
|
1445
|
-
Contents of USER.md (user profile; entries: ${userUsage.entryCount}; UTF-8 bytes: ${userUsage.used}/${userUsage.limit})
|
|
1446
|
-
<runtime-memory-file name="USER.md">
|
|
1447
|
-
${user || "(empty)"}
|
|
1448
|
-
</runtime-memory-file>
|
|
1449
|
-
|
|
1450
|
-
Contents of MEMORY.md (working reference; entries: ${memoryUsage.entryCount}; UTF-8 bytes: ${memoryUsage.used}/${memoryUsage.limit})
|
|
1451
|
-
<runtime-memory-file name="MEMORY.md">
|
|
1452
|
-
${memory || "(empty)"}
|
|
1453
|
-
</runtime-memory-file>`
|
|
1454
|
-
};
|
|
1455
1584
|
}
|
|
1456
1585
|
mutate(request) {
|
|
1586
|
+
if (request.target === "user" && this.userController !== void 0) return this.userController.mutate(request);
|
|
1457
1587
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1458
1588
|
const beforeRevision = revision(this.readSource());
|
|
1459
1589
|
return {
|
|
@@ -1479,12 +1609,13 @@ ${memory || "(empty)"}
|
|
|
1479
1609
|
}
|
|
1480
1610
|
/** Resolve exactly which committed entries survive a blocked mutation and are safe to compact. */
|
|
1481
1611
|
planMaintenance(request) {
|
|
1612
|
+
if (request.target === "user" && this.userController !== void 0) return this.userController.planMaintenance(request);
|
|
1482
1613
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1483
1614
|
const file = this.readSource();
|
|
1484
1615
|
const prepared = prepareMutation(file.entries, request, this.now().toISOString());
|
|
1485
1616
|
const used = byteCount(file.entries, request.target);
|
|
1486
1617
|
const projected = byteCount(prepared.projectedEntries, request.target);
|
|
1487
|
-
const limit =
|
|
1618
|
+
const limit = this.limits[request.target];
|
|
1488
1619
|
return {
|
|
1489
1620
|
revision: revision(file),
|
|
1490
1621
|
action: request.action,
|
|
@@ -1505,14 +1636,16 @@ ${memory || "(empty)"}
|
|
|
1505
1636
|
return operation;
|
|
1506
1637
|
}
|
|
1507
1638
|
/** Commit semantic compaction and the original mutation together, or leave every local file unchanged. */
|
|
1508
|
-
compactAndMutate(expectedRevision, request, compacted, maxCompactedBytes
|
|
1639
|
+
compactAndMutate(expectedRevision, request, compacted, maxCompactedBytes, lineage = []) {
|
|
1640
|
+
if (request.target === "user" && this.userController !== void 0) return this.userController.compactAndMutate(expectedRevision, request, compacted, maxCompactedBytes, lineage);
|
|
1509
1641
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1510
1642
|
const file = this.readSource();
|
|
1511
1643
|
const beforeRevision = revision(file);
|
|
1512
1644
|
if (beforeRevision !== expectedRevision) throw new RuntimeMemoryConflictError();
|
|
1513
1645
|
const now = this.now().toISOString();
|
|
1514
1646
|
const prepared = prepareMutation(file.entries, request, now);
|
|
1515
|
-
|
|
1647
|
+
const compactedByteBudget = maxCompactedBytes ?? this.limits[request.target];
|
|
1648
|
+
if (!Number.isInteger(compactedByteBudget) || compactedByteBudget < 0 || compactedByteBudget > this.limits[request.target]) throw new Error("compaction byte budget is invalid");
|
|
1516
1649
|
if (!prepared.changed) return {
|
|
1517
1650
|
result: this.result(request.target, prepared.projectedEntries, prepared.fields),
|
|
1518
1651
|
beforeRevision,
|
|
@@ -1521,10 +1654,10 @@ ${memory || "(empty)"}
|
|
|
1521
1654
|
const replacements = compactionCandidates(compacted, prepared.compactableEntries, request.target, now);
|
|
1522
1655
|
if (prepared.pendingEntry !== void 0 && replacements.some((entry) => entry.content === prepared.pendingEntry.content)) throw new Error("compacted runtime memory duplicates the pending mutation");
|
|
1523
1656
|
if (prepared.excludedEntry !== void 0 && replacements.some((entry) => entry.content === prepared.excludedEntry.content)) throw new Error("compacted runtime memory reintroduces the replaced or removed entry");
|
|
1524
|
-
const targetEntries = [...packCompactionCandidates(replacements, request.target,
|
|
1657
|
+
const targetEntries = [...packCompactionCandidates(replacements, request.target, compactedByteBudget), ...prepared.pendingEntry === void 0 ? [] : [prepared.pendingEntry]];
|
|
1525
1658
|
const entries = [...file.entries.filter((entry) => entry.target !== request.target), ...targetEntries];
|
|
1526
1659
|
const used = byteCount(entries, request.target);
|
|
1527
|
-
const limit =
|
|
1660
|
+
const limit = this.limits[request.target];
|
|
1528
1661
|
if (used > limit) throw new RuntimeMemoryCapacityError(request.target, byteCount(file.entries, request.target), used, limit);
|
|
1529
1662
|
const next = {
|
|
1530
1663
|
version: 1,
|
|
@@ -1560,17 +1693,19 @@ ${memory || "(empty)"}
|
|
|
1560
1693
|
return operation;
|
|
1561
1694
|
}
|
|
1562
1695
|
/** Apply an LLM-produced compaction only to the exact snapshot it reviewed. */
|
|
1563
|
-
compactTarget(expectedRevision, target, compacted, maxBytes
|
|
1696
|
+
compactTarget(expectedRevision, target, compacted, maxBytes, lineage = []) {
|
|
1697
|
+
if (target === "user" && this.userController !== void 0) return this.userController.compactTarget(expectedRevision, target, compacted, maxBytes, lineage).then(() => this.snapshot());
|
|
1564
1698
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1565
1699
|
const file = this.readSource();
|
|
1566
1700
|
const beforeRevision = revision(file);
|
|
1567
1701
|
if (beforeRevision !== expectedRevision) throw new RuntimeMemoryConflictError();
|
|
1568
|
-
|
|
1702
|
+
const byteBudget = maxBytes ?? this.limits[target];
|
|
1703
|
+
if (!Number.isInteger(byteBudget) || byteBudget < 0 || byteBudget > this.limits[target]) throw new Error("compaction byte budget is invalid");
|
|
1569
1704
|
const now = this.now().toISOString();
|
|
1570
|
-
const fitted = packCompactionCandidates(compactionCandidates(compacted, file.entries.filter((entry) => entry.target === target), target, now), target,
|
|
1705
|
+
const fitted = packCompactionCandidates(compactionCandidates(compacted, file.entries.filter((entry) => entry.target === target), target, now), target, byteBudget);
|
|
1571
1706
|
const entries = [...file.entries.filter((entry) => entry.target !== target), ...fitted];
|
|
1572
1707
|
const used = byteCount(entries, target);
|
|
1573
|
-
const limit =
|
|
1708
|
+
const limit = this.limits[target];
|
|
1574
1709
|
if (used > limit) throw new RuntimeMemoryCapacityError(target, byteCount(file.entries, target), used, limit);
|
|
1575
1710
|
this.persist({
|
|
1576
1711
|
version: 1,
|
|
@@ -1601,7 +1736,7 @@ ${memory || "(empty)"}
|
|
|
1601
1736
|
return snapshot;
|
|
1602
1737
|
});
|
|
1603
1738
|
this.queue = operation.catch(() => void 0);
|
|
1604
|
-
return operation;
|
|
1739
|
+
return this.userController === void 0 ? operation : operation.then(() => this.snapshot());
|
|
1605
1740
|
}
|
|
1606
1741
|
initialize() {
|
|
1607
1742
|
mkdirSync(this.directory, {
|
|
@@ -1618,7 +1753,7 @@ ${memory || "(empty)"}
|
|
|
1618
1753
|
const prepared = prepareMutation(file.entries, request, this.now().toISOString());
|
|
1619
1754
|
if (!prepared.changed) return this.result(request.target, prepared.projectedEntries, prepared.fields);
|
|
1620
1755
|
const used = byteCount(prepared.projectedEntries, request.target);
|
|
1621
|
-
const limit =
|
|
1756
|
+
const limit = this.limits[request.target];
|
|
1622
1757
|
if (used > limit) throw new RuntimeMemoryCapacityError(request.target, byteCount(file.entries, request.target), used, limit);
|
|
1623
1758
|
this.persist({
|
|
1624
1759
|
version: 1,
|
|
@@ -1634,7 +1769,7 @@ ${memory || "(empty)"}
|
|
|
1634
1769
|
entryCount: entries.filter((entry) => entry.target === target).length,
|
|
1635
1770
|
usage: {
|
|
1636
1771
|
used: byteCount(entries, target),
|
|
1637
|
-
limit:
|
|
1772
|
+
limit: this.limits[target]
|
|
1638
1773
|
},
|
|
1639
1774
|
...fields.added === void 0 ? {} : { added: fields.added },
|
|
1640
1775
|
...fields.replaced === void 0 ? {} : { replaced: fields.replaced },
|
|
@@ -1646,7 +1781,7 @@ ${memory || "(empty)"}
|
|
|
1646
1781
|
target,
|
|
1647
1782
|
entryCount: entries.filter((entry) => entry.target === target).length,
|
|
1648
1783
|
used: byteCount(entries, target),
|
|
1649
|
-
limit:
|
|
1784
|
+
limit: this.limits[target],
|
|
1650
1785
|
markdownPath: target === "memory" ? this.memoryPath : this.userPath
|
|
1651
1786
|
};
|
|
1652
1787
|
}
|
|
@@ -1690,7 +1825,7 @@ ${memory || "(empty)"}
|
|
|
1690
1825
|
});
|
|
1691
1826
|
const nonce = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
|
|
1692
1827
|
const writes = [
|
|
1693
|
-
[this.
|
|
1828
|
+
[this.localUserPath, markdown(file.entries, "user")],
|
|
1694
1829
|
[this.memoryPath, markdown(file.entries, "memory")],
|
|
1695
1830
|
[this.sourcePath, `${JSON.stringify(file, null, 2)}\n`]
|
|
1696
1831
|
];
|
|
@@ -1706,7 +1841,7 @@ ${memory || "(empty)"}
|
|
|
1706
1841
|
}
|
|
1707
1842
|
}
|
|
1708
1843
|
repairProjections(file) {
|
|
1709
|
-
for (const [path, target] of [[this.
|
|
1844
|
+
for (const [path, target] of [[this.localUserPath, "user"], [this.memoryPath, "memory"]]) {
|
|
1710
1845
|
const expected = markdown(file.entries, target);
|
|
1711
1846
|
let current;
|
|
1712
1847
|
try {
|
|
@@ -1951,7 +2086,7 @@ function decodeArchive(base64) {
|
|
|
1951
2086
|
if (bytes.length === 0 || bytes.length > 50331648) throw new Error("Mnemon Pack archive is empty or too large");
|
|
1952
2087
|
return bytes;
|
|
1953
2088
|
}
|
|
1954
|
-
function parseArchive(base64) {
|
|
2089
|
+
function parseArchive(base64, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
1955
2090
|
const archive = decodeArchive(base64);
|
|
1956
2091
|
let count = 0;
|
|
1957
2092
|
let expandedBytes = 0;
|
|
@@ -1982,8 +2117,8 @@ function parseArchive(base64) {
|
|
|
1982
2117
|
const expected = checksumFiles[path];
|
|
1983
2118
|
if (typeof expected !== "string" || expected !== sha256$1(files[path])) throw new Error(`Mnemon Pack checksum mismatch: ${path}`);
|
|
1984
2119
|
}
|
|
1985
|
-
validatePackPayload(files, manifest.components);
|
|
1986
|
-
const actualSummary = summaryFor(manifest.components, files);
|
|
2120
|
+
validatePackPayload(files, manifest.components, runtimeLimits);
|
|
2121
|
+
const actualSummary = summaryFor(manifest.components, files, runtimeLimits);
|
|
1987
2122
|
return {
|
|
1988
2123
|
archiveBytes: archive.length,
|
|
1989
2124
|
expandedBytes,
|
|
@@ -1994,7 +2129,7 @@ function parseArchive(base64) {
|
|
|
1994
2129
|
}
|
|
1995
2130
|
};
|
|
1996
2131
|
}
|
|
1997
|
-
function parseRuntime(value) {
|
|
2132
|
+
function parseRuntime(value, limits = RUNTIME_MEMORY_LIMITS) {
|
|
1998
2133
|
const source = record$2(value);
|
|
1999
2134
|
if (source?.version !== 1 || !Array.isArray(source.entries)) throw new Error("runtime memories.json is invalid");
|
|
2000
2135
|
const entries = source.entries.map((raw) => {
|
|
@@ -2015,7 +2150,7 @@ function parseRuntime(value) {
|
|
|
2015
2150
|
updated_at: entry.updated_at
|
|
2016
2151
|
};
|
|
2017
2152
|
});
|
|
2018
|
-
for (const target of ["user", "memory"]) if (runtimeBytes(entries, target) >
|
|
2153
|
+
for (const target of ["user", "memory"]) if (runtimeBytes(entries, target) > limits[target]) throw new Error(`runtime ${target} memory exceeds its ${limits[target]} byte limit`);
|
|
2019
2154
|
return {
|
|
2020
2155
|
version: 1,
|
|
2021
2156
|
entries
|
|
@@ -2090,8 +2225,8 @@ function parseRegistry(value) {
|
|
|
2090
2225
|
function validDatabase(bytes, path) {
|
|
2091
2226
|
if (bytes.length < 100 || !Buffer.from(bytes.subarray(0, SQLITE_HEADER.length)).equals(SQLITE_HEADER)) throw new Error(`${path} is not a SQLite mnemon.db`);
|
|
2092
2227
|
}
|
|
2093
|
-
function validatePackPayload(files, components) {
|
|
2094
|
-
if (components.includes("runtime")) parseRuntime(json(files["payload/runtime/memories.json"] ?? /* @__PURE__ */ new Uint8Array(), "payload/runtime/memories.json"));
|
|
2228
|
+
function validatePackPayload(files, components, runtimeLimits) {
|
|
2229
|
+
if (components.includes("runtime")) parseRuntime(json(files["payload/runtime/memories.json"] ?? /* @__PURE__ */ new Uint8Array(), "payload/runtime/memories.json"), runtimeLimits);
|
|
2095
2230
|
if (components.includes("documents")) {
|
|
2096
2231
|
const index = parseDocumentIndex(json(files["payload/documents/index.json"] ?? /* @__PURE__ */ new Uint8Array(), "payload/documents/index.json"));
|
|
2097
2232
|
for (const document of index.documents) {
|
|
@@ -2172,9 +2307,9 @@ function emptyRuntime() {
|
|
|
2172
2307
|
entries: []
|
|
2173
2308
|
};
|
|
2174
2309
|
}
|
|
2175
|
-
function readCurrentRuntime(root) {
|
|
2310
|
+
function readCurrentRuntime(root, limits = RUNTIME_MEMORY_LIMITS) {
|
|
2176
2311
|
const path = join(root, "runtime", "memories.json");
|
|
2177
|
-
return existsSync(path) ? parseRuntime(JSON.parse(readFileSync(path, "utf8"))) : emptyRuntime();
|
|
2312
|
+
return existsSync(path) ? parseRuntime(JSON.parse(readFileSync(path, "utf8")), limits) : emptyRuntime();
|
|
2178
2313
|
}
|
|
2179
2314
|
function writeRuntime(directory, file) {
|
|
2180
2315
|
mkdirSync(directory, {
|
|
@@ -2306,12 +2441,12 @@ function writeRegistry(directory, registry, databases) {
|
|
|
2306
2441
|
}
|
|
2307
2442
|
writeFileSync(join(directory, ".dsh-memory-bodies.json"), `${JSON.stringify(registry, null, 2)}\n`, { mode: 384 });
|
|
2308
2443
|
}
|
|
2309
|
-
function componentItems(component, files) {
|
|
2310
|
-
if (component === "runtime") return parseRuntime(json(files["payload/runtime/memories.json"], "payload/runtime/memories.json")).entries.length;
|
|
2444
|
+
function componentItems(component, files, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
2445
|
+
if (component === "runtime") return parseRuntime(json(files["payload/runtime/memories.json"], "payload/runtime/memories.json"), runtimeLimits).entries.length;
|
|
2311
2446
|
if (component === "documents") return parseDocumentIndex(json(files["payload/documents/index.json"], "payload/documents/index.json")).documents.length;
|
|
2312
2447
|
return parseRegistry(json(files["payload/data/.dsh-memory-bodies.json"], "payload/data/.dsh-memory-bodies.json")).bodies.length;
|
|
2313
2448
|
}
|
|
2314
|
-
function summaryFor(components, files) {
|
|
2449
|
+
function summaryFor(components, files, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
2315
2450
|
return components.map((component) => {
|
|
2316
2451
|
const prefix = `payload/${COMPONENT_DIRECTORIES[component]}/`;
|
|
2317
2452
|
const entries = Object.entries(files).filter(([path]) => path.startsWith(prefix));
|
|
@@ -2319,14 +2454,14 @@ function summaryFor(components, files) {
|
|
|
2319
2454
|
component,
|
|
2320
2455
|
files: entries.length,
|
|
2321
2456
|
bytes: entries.reduce((sum, [, value]) => sum + value.length, 0),
|
|
2322
|
-
items: componentItems(component, files)
|
|
2457
|
+
items: componentItems(component, files, runtimeLimits)
|
|
2323
2458
|
};
|
|
2324
2459
|
});
|
|
2325
2460
|
}
|
|
2326
|
-
function collectExport(root, components) {
|
|
2461
|
+
function collectExport(root, components, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
2327
2462
|
const files = {};
|
|
2328
2463
|
if (components.includes("runtime")) {
|
|
2329
|
-
const runtime = readCurrentRuntime(root);
|
|
2464
|
+
const runtime = readCurrentRuntime(root, runtimeLimits);
|
|
2330
2465
|
files["payload/runtime/memories.json"] = strToU8(`${JSON.stringify(runtime, null, 2)}\n`);
|
|
2331
2466
|
files["payload/runtime/USER.md"] = strToU8(runtimeProjection(runtime.entries, "user"));
|
|
2332
2467
|
files["payload/runtime/MEMORY.md"] = strToU8(runtimeProjection(runtime.entries, "memory"));
|
|
@@ -2343,9 +2478,9 @@ function collectExport(root, components) {
|
|
|
2343
2478
|
}
|
|
2344
2479
|
return files;
|
|
2345
2480
|
}
|
|
2346
|
-
function mergeRuntime(root, pack) {
|
|
2347
|
-
const current = readCurrentRuntime(root);
|
|
2348
|
-
const incoming = parseRuntime(json(pack.files["payload/runtime/memories.json"], "payload/runtime/memories.json"));
|
|
2481
|
+
function mergeRuntime(root, pack, runtimeLimits) {
|
|
2482
|
+
const current = readCurrentRuntime(root, runtimeLimits);
|
|
2483
|
+
const incoming = parseRuntime(json(pack.files["payload/runtime/memories.json"], "payload/runtime/memories.json"), runtimeLimits);
|
|
2349
2484
|
const keys = new Set(current.entries.map((entry) => `${entry.target}\0${entry.content}`));
|
|
2350
2485
|
const entries = [...current.entries];
|
|
2351
2486
|
for (const entry of incoming.entries) {
|
|
@@ -2358,7 +2493,7 @@ function mergeRuntime(root, pack) {
|
|
|
2358
2493
|
return parseRuntime({
|
|
2359
2494
|
version: 1,
|
|
2360
2495
|
entries
|
|
2361
|
-
});
|
|
2496
|
+
}, runtimeLimits);
|
|
2362
2497
|
}
|
|
2363
2498
|
function mergeDocuments(root, pack) {
|
|
2364
2499
|
const current = readCurrentDocuments(root);
|
|
@@ -2424,7 +2559,7 @@ function reconcilePersistedStore(root) {
|
|
|
2424
2559
|
rmSync(temporary, { force: true });
|
|
2425
2560
|
}
|
|
2426
2561
|
}
|
|
2427
|
-
function stageImport(root, pack, components, mode) {
|
|
2562
|
+
function stageImport(root, pack, components, mode, runtimeLimits) {
|
|
2428
2563
|
const staging = join(root, `.dsh-pack-stage-${randomUUID()}`);
|
|
2429
2564
|
mkdirSync(staging, {
|
|
2430
2565
|
recursive: true,
|
|
@@ -2432,7 +2567,7 @@ function stageImport(root, pack, components, mode) {
|
|
|
2432
2567
|
});
|
|
2433
2568
|
try {
|
|
2434
2569
|
if (components.includes("runtime")) {
|
|
2435
|
-
const runtime = mode === "merge" ? mergeRuntime(root, pack) : parseRuntime(json(pack.files["payload/runtime/memories.json"], "payload/runtime/memories.json"));
|
|
2570
|
+
const runtime = mode === "merge" ? mergeRuntime(root, pack, runtimeLimits) : parseRuntime(json(pack.files["payload/runtime/memories.json"], "payload/runtime/memories.json"), runtimeLimits);
|
|
2436
2571
|
writeRuntime(join(staging, "runtime"), runtime);
|
|
2437
2572
|
}
|
|
2438
2573
|
if (components.includes("documents")) {
|
|
@@ -2531,12 +2666,17 @@ var MnemonPackManager = class {
|
|
|
2531
2666
|
afterImport;
|
|
2532
2667
|
now;
|
|
2533
2668
|
root;
|
|
2669
|
+
runtimeLimits;
|
|
2534
2670
|
constructor(runner, config, afterImport = () => {}, now = () => /* @__PURE__ */ new Date()) {
|
|
2535
2671
|
this.runner = runner;
|
|
2536
2672
|
this.config = config;
|
|
2537
2673
|
this.afterImport = afterImport;
|
|
2538
2674
|
this.now = now;
|
|
2539
2675
|
this.root = resolve(runner.effectiveDataDir());
|
|
2676
|
+
this.runtimeLimits = {
|
|
2677
|
+
memory: config.runtimeMemory.memoryLimitBytes,
|
|
2678
|
+
user: config.runtimeMemory.userLimitBytes
|
|
2679
|
+
};
|
|
2540
2680
|
}
|
|
2541
2681
|
target() {
|
|
2542
2682
|
return {
|
|
@@ -2549,9 +2689,9 @@ var MnemonPackManager = class {
|
|
|
2549
2689
|
return this.runner.withExclusive(async () => {
|
|
2550
2690
|
await new Promise((resolveReady) => setImmediate(resolveReady));
|
|
2551
2691
|
return withLocks(this.root, components, () => {
|
|
2552
|
-
const payload = collectExport(this.root, components);
|
|
2692
|
+
const payload = collectExport(this.root, components, this.runtimeLimits);
|
|
2553
2693
|
const exportedAt = this.now().toISOString();
|
|
2554
|
-
const summary = summaryFor(components, payload);
|
|
2694
|
+
const summary = summaryFor(components, payload, this.runtimeLimits);
|
|
2555
2695
|
const manifest = {
|
|
2556
2696
|
format: MNEMON_PACK_FORMAT,
|
|
2557
2697
|
version: 1,
|
|
@@ -2590,7 +2730,7 @@ var MnemonPackManager = class {
|
|
|
2590
2730
|
});
|
|
2591
2731
|
}
|
|
2592
2732
|
inspectPack(base64, fileName) {
|
|
2593
|
-
const pack = parseArchive(base64);
|
|
2733
|
+
const pack = parseArchive(base64, this.runtimeLimits);
|
|
2594
2734
|
const sanitizedName = safeName(fileName);
|
|
2595
2735
|
return {
|
|
2596
2736
|
...sanitizedName === void 0 ? {} : { fileName: sanitizedName },
|
|
@@ -2603,7 +2743,7 @@ var MnemonPackManager = class {
|
|
|
2603
2743
|
};
|
|
2604
2744
|
}
|
|
2605
2745
|
async importPack(base64, options) {
|
|
2606
|
-
const pack = parseArchive(base64);
|
|
2746
|
+
const pack = parseArchive(base64, this.runtimeLimits);
|
|
2607
2747
|
if (options.mode !== "merge" && options.mode !== "replace") throw new Error("Pack import mode must be merge or replace");
|
|
2608
2748
|
if (options.components !== void 0 && (new Set(options.components).size !== options.components.length || options.components.some((component) => !COMPONENT_ORDER.includes(component)))) throw new Error("requested import components are invalid");
|
|
2609
2749
|
const components = options.components === void 0 ? pack.manifest.components : COMPONENT_ORDER.filter((component) => options.components.includes(component));
|
|
@@ -2615,7 +2755,7 @@ var MnemonPackManager = class {
|
|
|
2615
2755
|
mode: 448
|
|
2616
2756
|
});
|
|
2617
2757
|
return withLocks(this.root, components, () => {
|
|
2618
|
-
const staging = stageImport(this.root, pack, components, options.mode);
|
|
2758
|
+
const staging = stageImport(this.root, pack, components, options.mode, this.runtimeLimits);
|
|
2619
2759
|
commitStaging(this.root, staging, components);
|
|
2620
2760
|
this.afterImport(components);
|
|
2621
2761
|
return {
|
|
@@ -5482,6 +5622,37 @@ function registerBuiltinMemoryAdapters(catalog) {
|
|
|
5482
5622
|
};
|
|
5483
5623
|
}
|
|
5484
5624
|
//#endregion
|
|
5625
|
+
//#region src/git-branch.ts
|
|
5626
|
+
const GIT_BRANCH_TIMEOUT_MS = 2e3;
|
|
5627
|
+
/**
|
|
5628
|
+
* Resolve the current git branch of a workspace root. Returns undefined when
|
|
5629
|
+
* the directory is not a git working tree, HEAD is detached, or the probe
|
|
5630
|
+
* fails or times out, so callers fall back to the unfiltered view.
|
|
5631
|
+
*/
|
|
5632
|
+
function resolveGitBranch(cwd) {
|
|
5633
|
+
const root = cwd?.trim();
|
|
5634
|
+
if (root === void 0 || root === "") return void 0;
|
|
5635
|
+
try {
|
|
5636
|
+
const branch = execFileSync("git", [
|
|
5637
|
+
"-C",
|
|
5638
|
+
root,
|
|
5639
|
+
"branch",
|
|
5640
|
+
"--show-current"
|
|
5641
|
+
], {
|
|
5642
|
+
encoding: "utf8",
|
|
5643
|
+
timeout: GIT_BRANCH_TIMEOUT_MS,
|
|
5644
|
+
stdio: [
|
|
5645
|
+
"ignore",
|
|
5646
|
+
"pipe",
|
|
5647
|
+
"ignore"
|
|
5648
|
+
]
|
|
5649
|
+
}).trim();
|
|
5650
|
+
return branch === "" ? void 0 : branch;
|
|
5651
|
+
} catch {
|
|
5652
|
+
return;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
//#endregion
|
|
5485
5656
|
//#region src/memory-view.ts
|
|
5486
5657
|
function hash(value) {
|
|
5487
5658
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -5490,12 +5661,12 @@ function workspace(scope) {
|
|
|
5490
5661
|
const value = scope?.workspaceId?.trim();
|
|
5491
5662
|
return value === void 0 || value === "" ? void 0 : value;
|
|
5492
5663
|
}
|
|
5493
|
-
function
|
|
5664
|
+
function createRuntimeMemorySource(runtimeMemory, resolveBranch = resolveGitBranch) {
|
|
5494
5665
|
return {
|
|
5495
5666
|
layerId: "runtime",
|
|
5496
5667
|
mode: "eager",
|
|
5497
|
-
snapshot: () => {
|
|
5498
|
-
const projection = runtimeMemory.contextProjection();
|
|
5668
|
+
snapshot: (context) => {
|
|
5669
|
+
const projection = runtimeMemory.contextProjection(resolveBranch(workspace(context.scope)));
|
|
5499
5670
|
return {
|
|
5500
5671
|
revision: projection.revision,
|
|
5501
5672
|
wake: projection.text
|
|
@@ -5556,9 +5727,9 @@ function memorySpacesSource(service) {
|
|
|
5556
5727
|
}
|
|
5557
5728
|
};
|
|
5558
5729
|
}
|
|
5559
|
-
function createDefaultMemoryTurnViewManager(kernel, sources) {
|
|
5730
|
+
function createDefaultMemoryTurnViewManager(kernel, sources, options = {}) {
|
|
5560
5731
|
return new MemoryTurnViewManager(kernel, [
|
|
5561
|
-
|
|
5732
|
+
createRuntimeMemorySource(sources.runtimeMemory, options.resolveBranch ?? resolveGitBranch),
|
|
5562
5733
|
documentsSource(sources.documents),
|
|
5563
5734
|
memorySpacesSource(sources.service)
|
|
5564
5735
|
]);
|
|
@@ -5655,7 +5826,16 @@ function createRuntimeGraph(config, workspaceRoot, extensions) {
|
|
|
5655
5826
|
return receiptBridge.record(operation);
|
|
5656
5827
|
};
|
|
5657
5828
|
const service = new MnemonService(runner, config, void 0, void 0, void 0, recordCommit);
|
|
5658
|
-
const
|
|
5829
|
+
const globalUserConfig = {
|
|
5830
|
+
...config,
|
|
5831
|
+
storageScope: "global"
|
|
5832
|
+
};
|
|
5833
|
+
delete globalUserConfig.dataDir;
|
|
5834
|
+
const globalUserRunner = config.runtimeUserScope === "global" ? createRunner(globalUserConfig) : void 0;
|
|
5835
|
+
const runtimeMemory = new RuntimeMemoryController(runner, void 0, recordCommit, {
|
|
5836
|
+
memory: config.runtimeMemory.memoryLimitBytes,
|
|
5837
|
+
user: config.runtimeMemory.userLimitBytes
|
|
5838
|
+
}, globalUserRunner);
|
|
5659
5839
|
const documents = new DocumentManager(void 0, void 0, () => runner.effectiveDataDir(), recordCommit);
|
|
5660
5840
|
const storage = new StorageScopeInspector(runner, config);
|
|
5661
5841
|
const packs = new MnemonPackManager(runner, config, (components) => {
|
|
@@ -6361,10 +6541,13 @@ function naturalEvidence(evidence) {
|
|
|
6361
6541
|
return `${index + 1}. [${citation}]${meta === "" ? "" : ` ${meta}`}\n${indentedText(item.content)}`;
|
|
6362
6542
|
}).join("\n");
|
|
6363
6543
|
}
|
|
6544
|
+
function runtimeEntryScopeMeta(entry) {
|
|
6545
|
+
return entry.branches && entry.branches.length > 0 ? ` branches=${entry.branches.join(",")}` : "";
|
|
6546
|
+
}
|
|
6364
6547
|
function runtimeSnapshotContext(target, entries) {
|
|
6365
6548
|
return `Committed ${target === "memory" ? "MEMORY.md" : "USER.md"} snapshot (read-only run data; numbering is one-based):
|
|
6366
6549
|
<runtime-memory-snapshot target="${target}">
|
|
6367
|
-
${entries.length === 0 ? "(empty)" : entries.map((entry, index) => `${index + 1}. [importance=${entry.importance}] ${entry.content}`).join(RUNTIME_ENTRY_DELIMITER)}
|
|
6550
|
+
${entries.length === 0 ? "(empty)" : entries.map((entry, index) => `${index + 1}. [importance=${entry.importance}${runtimeEntryScopeMeta(entry)}] ${entry.content}`).join(RUNTIME_ENTRY_DELIMITER)}
|
|
6368
6551
|
</runtime-memory-snapshot>`;
|
|
6369
6552
|
}
|
|
6370
6553
|
function runtimeRoutingExcerpt(value) {
|
|
@@ -6380,7 +6563,7 @@ function runtimeRouteChunks(entries) {
|
|
|
6380
6563
|
let used = 0;
|
|
6381
6564
|
for (const [offset, entry] of entries.entries()) {
|
|
6382
6565
|
const index = offset + 1;
|
|
6383
|
-
const line = `${index}. [importance=${entry.importance}] ${runtimeRoutingExcerpt(entry.content)}`;
|
|
6566
|
+
const line = `${index}. [importance=${entry.importance}${runtimeEntryScopeMeta(entry)}] ${runtimeRoutingExcerpt(entry.content)}`;
|
|
6384
6567
|
const separatorLength = rendered.length === 0 ? 0 : 3;
|
|
6385
6568
|
if (rendered.length > 0 && used + separatorLength + line.length > RUNTIME_ROUTE_CHUNK_CHARACTERS) {
|
|
6386
6569
|
chunks.push({
|
|
@@ -6489,7 +6672,8 @@ function runtimeMigrationSources(revision, entries) {
|
|
|
6489
6672
|
reference: `runtime:${revision}:memory:${offset + 1}`,
|
|
6490
6673
|
digest: sha256(JSON.stringify({
|
|
6491
6674
|
content: entry.content,
|
|
6492
|
-
importance: entry.importance
|
|
6675
|
+
importance: entry.importance,
|
|
6676
|
+
...entry.branches === void 0 ? {} : { branches: entry.branches }
|
|
6493
6677
|
}))
|
|
6494
6678
|
}));
|
|
6495
6679
|
}
|
|
@@ -6664,6 +6848,7 @@ var MnemonSubagentCoordinator = class {
|
|
|
6664
6848
|
documents;
|
|
6665
6849
|
resultRuntime;
|
|
6666
6850
|
taskAgentModelResolver;
|
|
6851
|
+
runtimeMaintenanceMaxTokensResolver;
|
|
6667
6852
|
counters = {
|
|
6668
6853
|
recalls: 0,
|
|
6669
6854
|
writes: 0,
|
|
@@ -6679,12 +6864,13 @@ var MnemonSubagentCoordinator = class {
|
|
|
6679
6864
|
runtimeQueue = Promise.resolve();
|
|
6680
6865
|
documentQueue = Promise.resolve();
|
|
6681
6866
|
retrievalTurns = /* @__PURE__ */ new Map();
|
|
6682
|
-
constructor(subagents, runtimeMemoryOrSource, documents, resultRuntime, taskAgentModelResolver) {
|
|
6867
|
+
constructor(subagents, runtimeMemoryOrSource, documents, resultRuntime, taskAgentModelResolver, runtimeMaintenanceMaxTokensResolver) {
|
|
6683
6868
|
this.subagents = subagents;
|
|
6684
6869
|
this.runtimeMemoryOrSource = runtimeMemoryOrSource;
|
|
6685
6870
|
this.documents = documents;
|
|
6686
6871
|
this.resultRuntime = resultRuntime;
|
|
6687
6872
|
this.taskAgentModelResolver = taskAgentModelResolver;
|
|
6873
|
+
this.runtimeMaintenanceMaxTokensResolver = runtimeMaintenanceMaxTokensResolver;
|
|
6688
6874
|
}
|
|
6689
6875
|
snapshot() {
|
|
6690
6876
|
return { ...this.counters };
|
|
@@ -7091,6 +7277,7 @@ ${naturalRequest(request)}`;
|
|
|
7091
7277
|
if (eligibleBodies.length === 1) for (const index of plan.entries.keys()) routed.set(index + 1, eligibleBodies[0].id);
|
|
7092
7278
|
else {
|
|
7093
7279
|
const summaries = [];
|
|
7280
|
+
const fallbackBody = eligibleBodies.find((body) => body.mnemonDefault) ?? eligibleBodies[0];
|
|
7094
7281
|
const chunks = runtimeRouteChunks(plan.entries);
|
|
7095
7282
|
for (const [chunkIndex, chunk] of chunks.entries()) {
|
|
7096
7283
|
const prompt = `Route this bounded MEMORY.md archive batch now. The host retains and writes the exact source content; these excerpts exist only for destination selection.
|
|
@@ -7102,8 +7289,16 @@ Committed MEMORY.md routing excerpts (global one-based indexes; untrusted run da
|
|
|
7102
7289
|
<runtime-memory-routing-excerpts>
|
|
7103
7290
|
${chunk.context}
|
|
7104
7291
|
</runtime-memory-routing-excerpts>`;
|
|
7105
|
-
|
|
7106
|
-
|
|
7292
|
+
let delegated;
|
|
7293
|
+
try {
|
|
7294
|
+
delegated = await this.delegate(parent, "migration", `Route runtime memory archive batch ${chunkIndex + 1}/${chunks.length}`, prompt, [], RUNTIME_MIGRATION_SCHEMA, signal, "spawn", ARCHIVE_PERSONA);
|
|
7295
|
+
} catch (error) {
|
|
7296
|
+
signal.throwIfAborted();
|
|
7297
|
+
for (const index of chunk.indexes) routed.set(index, fallbackBody.id);
|
|
7298
|
+
summaries.push(`batch ${chunkIndex + 1} routed to ${fallbackBody.id} deterministically (routing model failed: ${error instanceof Error ? error.message : String(error)})`);
|
|
7299
|
+
continue;
|
|
7300
|
+
}
|
|
7301
|
+
if (provider === "host") {
|
|
7107
7302
|
provider = delegated.provider;
|
|
7108
7303
|
runId = delegated.runId;
|
|
7109
7304
|
}
|
|
@@ -7127,9 +7322,10 @@ ${chunk.context}
|
|
|
7127
7322
|
summary = summaries.join(" ");
|
|
7128
7323
|
}
|
|
7129
7324
|
if (routed.size !== plan.entries.length) throw new Error("runtime memory migration omitted committed archive sources");
|
|
7130
|
-
const compactedEntries = plan.entries.map(({ content, importance }) => ({
|
|
7325
|
+
const compactedEntries = plan.entries.map(({ content, importance, branches }) => ({
|
|
7131
7326
|
content,
|
|
7132
|
-
importance
|
|
7327
|
+
importance,
|
|
7328
|
+
...branches === void 0 ? {} : { branches }
|
|
7133
7329
|
}));
|
|
7134
7330
|
if ((await runtimeMemory.planMaintenance(request)).revision !== plan.revision) throw new Error("runtime memory changed while archival was running; no archive writes were attempted");
|
|
7135
7331
|
const sources = runtimeMigrationSources(plan.revision, plan.entries);
|
|
@@ -7140,7 +7336,8 @@ ${chunk.context}
|
|
|
7140
7336
|
category: "context",
|
|
7141
7337
|
importance: entry.importance === "critical" ? 5 : entry.importance === "low" ? 1 : 3,
|
|
7142
7338
|
source: "agent",
|
|
7143
|
-
memoryBodyId: routed.get(source.index)
|
|
7339
|
+
memoryBodyId: routed.get(source.index),
|
|
7340
|
+
...entry.branches === void 0 || entry.branches.length === 0 ? {} : { tags: entry.branches.map((branch) => `branch:${branch}`) }
|
|
7144
7341
|
};
|
|
7145
7342
|
}), signal);
|
|
7146
7343
|
if (archiveResults.length !== sources.length) throw new Error("runtime archive batch did not return one receipt per source entry");
|
|
@@ -7346,7 +7543,7 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
|
|
|
7346
7543
|
const completionPersona = `${persona}
|
|
7347
7544
|
|
|
7348
7545
|
Completion protocol: call \`${resultToolName}\` exactly once with the final result matching its parameter schema. This is the only completion channel for this run. Do not finish with a plain-text answer.`;
|
|
7349
|
-
const perOpMaxTokens = operation === "migration" || operation === "compaction"
|
|
7546
|
+
const perOpMaxTokens = operation === "migration" || operation === "compaction" ? this.runtimeMaintenanceMaxTokensResolver?.() ?? 8192 : operation === "document-archive" ? 8192 : operation === "metadata-maintenance" ? 4096 : void 0;
|
|
7350
7547
|
const fixed = this.taskAgentModelResolver?.();
|
|
7351
7548
|
const baseAgentOptions = perOpMaxTokens === void 0 ? void 0 : { maxTokens: perOpMaxTokens };
|
|
7352
7549
|
const resolvedAgentOptions = fixed === void 0 ? baseAgentOptions : {
|
|
@@ -7665,6 +7862,16 @@ function createPluginMessage(text, form, summary) {
|
|
|
7665
7862
|
function sourceOf(message) {
|
|
7666
7863
|
return message.source;
|
|
7667
7864
|
}
|
|
7865
|
+
/**
|
|
7866
|
+
* Whether an event is a durable user message this plugin produced.
|
|
7867
|
+
*/
|
|
7868
|
+
function isOwnUserMessageEvent(event) {
|
|
7869
|
+
if (event?.type !== "user/message") return false;
|
|
7870
|
+
const source = event.data.source;
|
|
7871
|
+
if (typeof source !== "object" || source === null) return false;
|
|
7872
|
+
const { kind, plugin } = source;
|
|
7873
|
+
return kind === "plugin" && plugin === "dsh-mnemon";
|
|
7874
|
+
}
|
|
7668
7875
|
function eventTurn(event) {
|
|
7669
7876
|
return typeof event.data.turn === "number" ? event.data.turn : void 0;
|
|
7670
7877
|
}
|
|
@@ -7734,6 +7941,11 @@ var MnemonAgentLifecycle = class {
|
|
|
7734
7941
|
guidedTurns = /* @__PURE__ */ new Set();
|
|
7735
7942
|
memoryActivity = new TurnActivityProjection();
|
|
7736
7943
|
turnActivity = /* @__PURE__ */ new Map();
|
|
7944
|
+
/**
|
|
7945
|
+
* Fallback presence marker for hosts that publish no surface projection.
|
|
7946
|
+
* A host with a surface answers the question from what the model can
|
|
7947
|
+
* actually see, which is what makes a rewind self-correcting.
|
|
7948
|
+
*/
|
|
7737
7949
|
cueInjected = false;
|
|
7738
7950
|
idleReviewTimer;
|
|
7739
7951
|
reviewController;
|
|
@@ -7814,6 +8026,24 @@ var MnemonAgentLifecycle = class {
|
|
|
7814
8026
|
const pinned = this.pinnedView;
|
|
7815
8027
|
return pinned === void 0 ? void 0 : pinned.manager.wake(pinned.context.viewId);
|
|
7816
8028
|
}
|
|
8029
|
+
/**
|
|
8030
|
+
* Whether the reminder is still visible to the model.
|
|
8031
|
+
*
|
|
8032
|
+
* Read from the surface rather than from `cueInjected`, because a rewind is a
|
|
8033
|
+
* surface replacement inside the same live session: it does not emit
|
|
8034
|
+
* `agent/session-start`, so a session-scoped flag stays set and the reminder
|
|
8035
|
+
* never returns. The durable event log cannot answer this either, since it is
|
|
8036
|
+
* append-only and still contains the discarded message.
|
|
8037
|
+
*
|
|
8038
|
+
* Scanning forward is cheap: the reminder sits near the head of the surface,
|
|
8039
|
+
* so the loop exits after a few nodes even on a long session.
|
|
8040
|
+
*/
|
|
8041
|
+
cueAlreadyVisible() {
|
|
8042
|
+
const nodes = this.agent.session.surface?.nodes;
|
|
8043
|
+
if (nodes === void 0) return this.cueInjected;
|
|
8044
|
+
for (const seq of nodes) if (isOwnUserMessageEvent(this.agent.session.events[seq])) return true;
|
|
8045
|
+
return false;
|
|
8046
|
+
}
|
|
7817
8047
|
async preStep(payload, next) {
|
|
7818
8048
|
if (payload.step === 1) this.cancelIdleReview(true);
|
|
7819
8049
|
const decision = await next();
|
|
@@ -7834,7 +8064,7 @@ var MnemonAgentLifecycle = class {
|
|
|
7834
8064
|
this.counters.primes += 1;
|
|
7835
8065
|
this.mark("prime");
|
|
7836
8066
|
}
|
|
7837
|
-
if (this.
|
|
8067
|
+
if (this.cueAlreadyVisible()) return decision;
|
|
7838
8068
|
const reminder = guidedReminder(this.config);
|
|
7839
8069
|
if (reminder === void 0) return decision;
|
|
7840
8070
|
this.cueInjected = true;
|
|
@@ -9153,12 +9383,16 @@ function createWriteHandler(input, lifecycle, runtimeMemory, versions) {
|
|
|
9153
9383
|
requireLayer(resolved.graph, "runtime", "write");
|
|
9154
9384
|
if (resolved.graph.runtimeMemory === void 0) throw new Error("runtime memory is unavailable");
|
|
9155
9385
|
{
|
|
9386
|
+
const branches = payload.branches === void 0 ? void 0 : Array.isArray(payload.branches) && payload.branches.every((branch) => typeof branch === "string") ? payload.branches : (() => {
|
|
9387
|
+
throw new Error("branches must be an array of strings");
|
|
9388
|
+
})();
|
|
9156
9389
|
const request = {
|
|
9157
9390
|
action: String(payload.action ?? ""),
|
|
9158
9391
|
target: String(payload.target ?? ""),
|
|
9159
9392
|
...payload.content === void 0 ? {} : { content: String(payload.content) },
|
|
9160
9393
|
...payload.old_text === void 0 ? {} : { oldText: String(payload.old_text) },
|
|
9161
|
-
...payload.importance === void 0 ? {} : { importance: String(payload.importance) }
|
|
9394
|
+
...payload.importance === void 0 ? {} : { importance: String(payload.importance) },
|
|
9395
|
+
...branches === void 0 ? {} : { branches }
|
|
9162
9396
|
};
|
|
9163
9397
|
const sessionId = String(payload.sessionId ?? "").trim();
|
|
9164
9398
|
return success$1(inspectionDiverged || lifecycle === void 0 || sessionId === "" || resolved.explicitWorkspace && !alignedSession ? await resolved.graph.runtimeMemory.mutate(request) : await lifecycle.runtime(sessionId, request));
|
|
@@ -9412,6 +9646,7 @@ function object(value) {
|
|
|
9412
9646
|
}
|
|
9413
9647
|
const MUTABLE_FIELDS = [
|
|
9414
9648
|
"storageScope",
|
|
9649
|
+
"runtimeUserScope",
|
|
9415
9650
|
"cliPath",
|
|
9416
9651
|
"dataDir",
|
|
9417
9652
|
"customPackId",
|
|
@@ -9419,6 +9654,7 @@ const MUTABLE_FIELDS = [
|
|
|
9419
9654
|
"store",
|
|
9420
9655
|
"timeoutMs",
|
|
9421
9656
|
"defaultRecallLimit",
|
|
9657
|
+
"runtimeMemory",
|
|
9422
9658
|
"embedding",
|
|
9423
9659
|
"recallQuality",
|
|
9424
9660
|
"routingGuidance",
|
|
@@ -9940,7 +10176,7 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
9940
10176
|
}));
|
|
9941
10177
|
ctx.tools.register(definition({
|
|
9942
10178
|
name: "mnemon_runtime_memory",
|
|
9943
|
-
description: "Maintain compact hot memory injected into future turns. Write only new reusable facts supplied or corrected by the user, or information the user explicitly asks to save. Never copy, promote, or summarize evidence returned by Documents, Recall, or Related unless the user explicitly asks to save that exact evidence; answering a read question must stay read-only. add creates one independent fact; replace corrects or consolidates one uniquely matched entry; remove is only for an explicitly withdrawn, obsolete, or wrong entry. target=user is only for who the user is; target=memory is for project/environment/decisions/lessons. Skip questions, guesses, assistant-authored claims, temporary progress, completed-work logs, raw dumps, secrets, rediscoverable facts, and skill-covered guidance. This tool exclusively writes runtime MEMORY.md and USER.md; capacity archival and compaction are automatic.",
|
|
10179
|
+
description: "Maintain compact hot memory injected into future turns. Write only new reusable facts supplied or corrected by the user, or information the user explicitly asks to save. Never copy, promote, or summarize evidence returned by Documents, Recall, or Related unless the user explicitly asks to save that exact evidence; answering a read question must stay read-only. add creates one independent fact; replace corrects or consolidates one uniquely matched entry; remove is only for an explicitly withdrawn, obsolete, or wrong entry. target=user is only for who the user is; target=memory is for project/environment/decisions/lessons. Skip questions, guesses, assistant-authored claims, temporary progress, completed-work logs, raw dumps, secrets, rediscoverable facts, and skill-covered guidance. This tool exclusively writes runtime MEMORY.md and USER.md; capacity archival and compaction are automatic. Optional branches (git branch names, target=memory only) project an entry only in sessions on those branches: use them for branch-specific decisions and experiments, tag new branch-scoped entries with the git branch reported in the snapshot header, omit for cross-branch facts; on replace an empty list clears the scope, and an omitted list keeps the current scope.",
|
|
9944
10180
|
parameters: {
|
|
9945
10181
|
type: "object",
|
|
9946
10182
|
properties: {
|
|
@@ -9974,6 +10210,11 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
9974
10210
|
"low"
|
|
9975
10211
|
],
|
|
9976
10212
|
description: "critical for explicit must/always/never rules; low for transient facts; normal by default."
|
|
10213
|
+
},
|
|
10214
|
+
branches: {
|
|
10215
|
+
type: "array",
|
|
10216
|
+
items: { type: "string" },
|
|
10217
|
+
description: "Optional git branch names restricting where a target=memory entry is injected. Omit for cross-branch facts; on replace an empty list clears the scope and an omitted list keeps it. Never accepted for target=user."
|
|
9977
10218
|
}
|
|
9978
10219
|
},
|
|
9979
10220
|
required: ["action", "target"]
|
|
@@ -9990,7 +10231,8 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
9990
10231
|
target: args.target,
|
|
9991
10232
|
...args.content === void 0 ? {} : { content: args.content },
|
|
9992
10233
|
...args.old_text === void 0 ? {} : { oldText: args.old_text },
|
|
9993
|
-
...args.importance === void 0 ? {} : { importance: args.importance }
|
|
10234
|
+
...args.importance === void 0 ? {} : { importance: args.importance },
|
|
10235
|
+
...args.branches === void 0 ? {} : { branches: args.branches }
|
|
9994
10236
|
};
|
|
9995
10237
|
return isSubagent(exec.agent) ? runtime.runtimeMemory.mutate(request) : coordinator.runtime(requireAgent(exec), request, exec.signal);
|
|
9996
10238
|
},
|
|
@@ -10476,7 +10718,7 @@ function apply(rawContext, config = {}) {
|
|
|
10476
10718
|
provider,
|
|
10477
10719
|
model
|
|
10478
10720
|
};
|
|
10479
|
-
});
|
|
10721
|
+
}, () => runtime.config.runtimeMemory.maintenanceMaxTokens);
|
|
10480
10722
|
const lifecycle = new MnemonLifecycle(ctx, coordinator, runtime.config, runtime);
|
|
10481
10723
|
ctx.effect(() => {
|
|
10482
10724
|
const stop = lifecycle.start();
|