dsh-mnemon 0.3.2 → 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 +5 -2
- package/README.zh-CN.md +5 -2
- package/lib/client.js +222 -9
- package/lib/index.js +471 -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/client/subagent-token-usage.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-token-usage.d.ts +46 -0
- package/lib/types/src/subagent.d.ts +2 -1
- package/package.json +3 -2
package/lib/index.js
CHANGED
|
@@ -11,7 +11,9 @@ 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";
|
|
16
|
+
import { z as z$1 } from "zod";
|
|
15
17
|
//#region src/shared/contracts.ts
|
|
16
18
|
const MNEMON_READ_CHANNEL = "/dsh-mnemon-read";
|
|
17
19
|
const MNEMON_ACTIVATION_CHANNEL = "/dsh-mnemon-activation";
|
|
@@ -53,6 +55,11 @@ const DEFAULT_IDLE_REVIEW_MS = 3e4;
|
|
|
53
55
|
const DEFAULT_RECALL_QUALITY_POLICY = "strict-v1";
|
|
54
56
|
const DEFAULT_RECALL_LOW_SCORE_THRESHOLD = .25;
|
|
55
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;
|
|
56
63
|
//#endregion
|
|
57
64
|
//#region src/config.ts
|
|
58
65
|
const InteractionConfig = z.object({
|
|
@@ -117,6 +124,11 @@ const RecallQualitySchema = z.object({
|
|
|
117
124
|
maxMediumResults: z.number().step(1).min(0).max(50).default(4),
|
|
118
125
|
maxUnknownResults: z.number().step(1).min(0).max(50).default(2)
|
|
119
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
|
+
});
|
|
120
132
|
const MemoryParticipationModeSchema = z.union([
|
|
121
133
|
"off",
|
|
122
134
|
"manual",
|
|
@@ -143,6 +155,7 @@ const Config = z.object({
|
|
|
143
155
|
"workspace",
|
|
144
156
|
"custom"
|
|
145
157
|
]),
|
|
158
|
+
runtimeUserScope: z.union(["storage", "global"]).default("storage"),
|
|
146
159
|
cliPath: z.string(),
|
|
147
160
|
dataDir: z.string(),
|
|
148
161
|
customPackId: z.string(),
|
|
@@ -154,6 +167,11 @@ const Config = z.object({
|
|
|
154
167
|
store: z.string(),
|
|
155
168
|
timeoutMs: z.number().step(1).min(100).max(12e4).default(DEFAULT_TIMEOUT_MS),
|
|
156
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
|
+
}),
|
|
157
175
|
embedding: MnemonEmbeddingSchema.default({
|
|
158
176
|
enabled: false,
|
|
159
177
|
endpoint: DEFAULT_EMBEDDING_ENDPOINT,
|
|
@@ -336,6 +354,19 @@ function resolveRecallQuality(value) {
|
|
|
336
354
|
maxUnknownResults
|
|
337
355
|
};
|
|
338
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
|
+
}
|
|
339
370
|
const MEMORY_COMPONENT_ID = /^[a-z][a-z0-9-]{0,127}$/u;
|
|
340
371
|
const MEMORY_PARTICIPATION_MODES = /* @__PURE__ */ new Set([
|
|
341
372
|
"off",
|
|
@@ -386,6 +417,8 @@ function resolveConfig(config = {}) {
|
|
|
386
417
|
if (requestedPackId !== void 0 && !CUSTOM_PACK_ID.test(requestedPackId)) throw new Error("dsh-mnemon: customPackId is invalid");
|
|
387
418
|
const store = optionalText$1(config.store);
|
|
388
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)}`);
|
|
389
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);
|
|
390
423
|
if (requestedPackId !== void 0 && selectedPack === void 0) throw new Error(`dsh-mnemon: unknown custom Pack: ${requestedPackId}`);
|
|
391
424
|
const dataDir = selectedPack?.dataDir ?? legacyDataDir;
|
|
@@ -393,11 +426,13 @@ function resolveConfig(config = {}) {
|
|
|
393
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_-]*");
|
|
394
427
|
return {
|
|
395
428
|
storageScope,
|
|
429
|
+
runtimeUserScope,
|
|
396
430
|
...cliPath === void 0 ? {} : { cliPath },
|
|
397
431
|
...dataDir === void 0 ? {} : { dataDir },
|
|
398
432
|
...store === void 0 ? {} : { store },
|
|
399
433
|
timeoutMs: config.timeoutMs ?? 1e4,
|
|
400
434
|
defaultRecallLimit: config.defaultRecallLimit ?? 10,
|
|
435
|
+
runtimeMemory: resolveRuntimeMemory(config.runtimeMemory),
|
|
401
436
|
embedding: resolveEmbedding(config.embedding),
|
|
402
437
|
memoryTopology: resolveMemoryTopology(config.memoryTopology),
|
|
403
438
|
recallQuality: resolveRecallQuality(config.recallQuality),
|
|
@@ -1162,8 +1197,8 @@ var DocumentManager = class {
|
|
|
1162
1197
|
};
|
|
1163
1198
|
const RUNTIME_ENTRY_DELIMITER = "\n§\n";
|
|
1164
1199
|
const RUNTIME_MEMORY_LIMITS = {
|
|
1165
|
-
memory:
|
|
1166
|
-
user:
|
|
1200
|
+
memory: DEFAULT_RUNTIME_MEMORY_LIMIT_BYTES,
|
|
1201
|
+
user: DEFAULT_RUNTIME_USER_LIMIT_BYTES
|
|
1167
1202
|
};
|
|
1168
1203
|
const RUNTIME_MEMORY_PROTOCOL = `MNEMON RUNTIME MEMORY PROTOCOL
|
|
1169
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.
|
|
@@ -1183,6 +1218,7 @@ WRITE PROTOCOL
|
|
|
1183
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.
|
|
1184
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.
|
|
1185
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.
|
|
1186
1222
|
|
|
1187
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.`;
|
|
1188
1224
|
const LOCK_TIMEOUT_MS$1 = 5e3;
|
|
@@ -1218,6 +1254,41 @@ function isTarget(value) {
|
|
|
1218
1254
|
function isImportance(value) {
|
|
1219
1255
|
return value === "critical" || value === "normal" || value === "low";
|
|
1220
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
|
+
}
|
|
1221
1292
|
function normalizeContent(value, field) {
|
|
1222
1293
|
const content = value?.trim().replace(/\s+/gu, " ") ?? "";
|
|
1223
1294
|
if (content === "") throw new Error(`${field} is required`);
|
|
@@ -1231,12 +1302,15 @@ function parseEntry(value) {
|
|
|
1231
1302
|
if (typeof value.created_at !== "string" || typeof value.updated_at !== "string") return void 0;
|
|
1232
1303
|
const content = value.content.trim().replace(/\s+/gu, " ");
|
|
1233
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);
|
|
1234
1307
|
return {
|
|
1235
1308
|
content,
|
|
1236
1309
|
created_at: value.created_at,
|
|
1237
1310
|
updated_at: value.updated_at,
|
|
1238
1311
|
target: value.target,
|
|
1239
|
-
importance: value.importance
|
|
1312
|
+
importance: value.importance,
|
|
1313
|
+
...branches === void 0 ? {} : { branches }
|
|
1240
1314
|
};
|
|
1241
1315
|
}
|
|
1242
1316
|
function byteCount(entries, target) {
|
|
@@ -1258,6 +1332,8 @@ function prepareMutation(before, request, now) {
|
|
|
1258
1332
|
"remove"
|
|
1259
1333
|
].includes(request.action)) throw new Error("action must be add, replace, or remove");
|
|
1260
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);
|
|
1261
1337
|
const entries = before.map((entry) => ({ ...entry }));
|
|
1262
1338
|
if (request.action === "add") {
|
|
1263
1339
|
const content = normalizeContent(request.content, "content");
|
|
@@ -1276,7 +1352,8 @@ function prepareMutation(before, request, now) {
|
|
|
1276
1352
|
created_at: now,
|
|
1277
1353
|
updated_at: now,
|
|
1278
1354
|
target: request.target,
|
|
1279
|
-
importance: request.importance ?? "normal"
|
|
1355
|
+
importance: request.importance ?? "normal",
|
|
1356
|
+
...branchScope !== void 0 && branchScope.length > 0 ? { branches: branchScope } : {}
|
|
1280
1357
|
};
|
|
1281
1358
|
return {
|
|
1282
1359
|
changed: true,
|
|
@@ -1304,6 +1381,10 @@ function prepareMutation(before, request, now) {
|
|
|
1304
1381
|
updated_at: now,
|
|
1305
1382
|
importance: request.importance ?? previous.importance
|
|
1306
1383
|
};
|
|
1384
|
+
if (branchScope !== void 0) {
|
|
1385
|
+
if (branchScope.length === 0) delete pendingEntry.branches;
|
|
1386
|
+
else pendingEntry.branches = branchScope;
|
|
1387
|
+
}
|
|
1307
1388
|
entries[index] = pendingEntry;
|
|
1308
1389
|
return {
|
|
1309
1390
|
changed: true,
|
|
@@ -1339,12 +1420,14 @@ function compactionCandidates(compacted, existing, target, now) {
|
|
|
1339
1420
|
if (seen.has(content)) throw new Error("compacted runtime memory contains duplicate entries");
|
|
1340
1421
|
seen.add(content);
|
|
1341
1422
|
const unchanged = existing.find((current) => current.content === content);
|
|
1423
|
+
const inheritedBranches = entry.branches ?? unchanged?.branches;
|
|
1342
1424
|
return {
|
|
1343
1425
|
content,
|
|
1344
1426
|
created_at: unchanged?.created_at ?? now,
|
|
1345
1427
|
updated_at: unchanged?.updated_at ?? now,
|
|
1346
1428
|
target,
|
|
1347
|
-
importance: entry.importance
|
|
1429
|
+
importance: entry.importance,
|
|
1430
|
+
...inheritedBranches === void 0 ? {} : { branches: inheritedBranches }
|
|
1348
1431
|
};
|
|
1349
1432
|
});
|
|
1350
1433
|
}
|
|
@@ -1375,49 +1458,112 @@ function sleepSync$1(milliseconds) {
|
|
|
1375
1458
|
* Single authority for hot memory. JSON is the durable source of truth;
|
|
1376
1459
|
* Markdown files are deterministic projections consumed by prompt assembly.
|
|
1377
1460
|
*/
|
|
1378
|
-
var RuntimeMemoryController = class {
|
|
1461
|
+
var RuntimeMemoryController = class RuntimeMemoryController {
|
|
1379
1462
|
now;
|
|
1380
1463
|
recordCommit;
|
|
1381
1464
|
directory;
|
|
1382
1465
|
sourcePath;
|
|
1466
|
+
userSourcePath;
|
|
1383
1467
|
memoryPath;
|
|
1384
1468
|
userPath;
|
|
1385
1469
|
lockPath;
|
|
1470
|
+
limits;
|
|
1386
1471
|
queue = Promise.resolve();
|
|
1387
|
-
|
|
1472
|
+
localUserPath;
|
|
1473
|
+
userController;
|
|
1474
|
+
constructor(runner, now = () => /* @__PURE__ */ new Date(), recordCommit, limits = RUNTIME_MEMORY_LIMITS, userRunner) {
|
|
1388
1475
|
this.now = now;
|
|
1389
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
|
+
};
|
|
1390
1482
|
this.directory = join(runner.effectiveDataDir(), "runtime");
|
|
1391
1483
|
this.sourcePath = join(this.directory, "memories.json");
|
|
1392
1484
|
this.memoryPath = join(this.directory, "MEMORY.md");
|
|
1393
|
-
this.
|
|
1485
|
+
this.localUserPath = join(this.directory, "USER.md");
|
|
1394
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;
|
|
1395
1491
|
this.initialize();
|
|
1396
1492
|
}
|
|
1397
1493
|
snapshot() {
|
|
1398
|
-
const
|
|
1399
|
-
|
|
1400
|
-
|
|
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 : {
|
|
1401
1522
|
directory: this.directory,
|
|
1402
1523
|
sourcePath: this.sourcePath,
|
|
1403
|
-
revision: revision(
|
|
1524
|
+
revision: revision({
|
|
1525
|
+
version: 1,
|
|
1526
|
+
entries
|
|
1527
|
+
}),
|
|
1404
1528
|
generatedAt: this.now().toISOString(),
|
|
1405
1529
|
entries,
|
|
1406
1530
|
targets: {
|
|
1407
|
-
memory:
|
|
1408
|
-
|
|
1531
|
+
memory: {
|
|
1532
|
+
...visibleMemory,
|
|
1533
|
+
markdownPath: this.memoryPath
|
|
1534
|
+
},
|
|
1535
|
+
user: {
|
|
1536
|
+
...visibleUser,
|
|
1537
|
+
markdownPath: this.userPath
|
|
1538
|
+
}
|
|
1409
1539
|
}
|
|
1410
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
|
+
};
|
|
1411
1559
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
/** Read the exact Runtime revision and its prompt projection under one lock. */
|
|
1416
|
-
contextProjection() {
|
|
1417
|
-
const { snapshot, user, memory } = this.withLock(() => {
|
|
1560
|
+
localContextProjection(branch) {
|
|
1561
|
+
const branchScope = scopeBranch(branch);
|
|
1562
|
+
return this.withLock(() => {
|
|
1418
1563
|
const file = this.readSource();
|
|
1419
1564
|
this.repairProjections(file);
|
|
1420
1565
|
const entries = file.entries.map((entry) => ({ ...entry }));
|
|
1566
|
+
const visible = branchScope === void 0 ? entries : entries.filter((entry) => entry.target === "user" || entryMatchesBranch(entry, branchScope));
|
|
1421
1567
|
return {
|
|
1422
1568
|
snapshot: {
|
|
1423
1569
|
directory: this.directory,
|
|
@@ -1426,33 +1572,18 @@ var RuntimeMemoryController = class {
|
|
|
1426
1572
|
generatedAt: this.now().toISOString(),
|
|
1427
1573
|
entries,
|
|
1428
1574
|
targets: {
|
|
1429
|
-
memory: this.targetView(
|
|
1430
|
-
user: this.targetView(
|
|
1575
|
+
memory: this.targetView(visible, "memory"),
|
|
1576
|
+
user: this.targetView(visible, "user")
|
|
1431
1577
|
}
|
|
1432
1578
|
},
|
|
1433
|
-
user: readFileSync(this.
|
|
1434
|
-
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
|
|
1435
1582
|
};
|
|
1436
1583
|
});
|
|
1437
|
-
const userUsage = snapshot.targets.user;
|
|
1438
|
-
const memoryUsage = snapshot.targets.memory;
|
|
1439
|
-
return {
|
|
1440
|
-
revision: snapshot.revision,
|
|
1441
|
-
text: `MNEMON RUNTIME MEMORY SNAPSHOT
|
|
1442
|
-
Revision: ${snapshot.revision}
|
|
1443
|
-
|
|
1444
|
-
Contents of USER.md (user profile; entries: ${userUsage.entryCount}; UTF-8 bytes: ${userUsage.used}/${userUsage.limit})
|
|
1445
|
-
<runtime-memory-file name="USER.md">
|
|
1446
|
-
${user || "(empty)"}
|
|
1447
|
-
</runtime-memory-file>
|
|
1448
|
-
|
|
1449
|
-
Contents of MEMORY.md (working reference; entries: ${memoryUsage.entryCount}; UTF-8 bytes: ${memoryUsage.used}/${memoryUsage.limit})
|
|
1450
|
-
<runtime-memory-file name="MEMORY.md">
|
|
1451
|
-
${memory || "(empty)"}
|
|
1452
|
-
</runtime-memory-file>`
|
|
1453
|
-
};
|
|
1454
1584
|
}
|
|
1455
1585
|
mutate(request) {
|
|
1586
|
+
if (request.target === "user" && this.userController !== void 0) return this.userController.mutate(request);
|
|
1456
1587
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1457
1588
|
const beforeRevision = revision(this.readSource());
|
|
1458
1589
|
return {
|
|
@@ -1478,12 +1609,13 @@ ${memory || "(empty)"}
|
|
|
1478
1609
|
}
|
|
1479
1610
|
/** Resolve exactly which committed entries survive a blocked mutation and are safe to compact. */
|
|
1480
1611
|
planMaintenance(request) {
|
|
1612
|
+
if (request.target === "user" && this.userController !== void 0) return this.userController.planMaintenance(request);
|
|
1481
1613
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1482
1614
|
const file = this.readSource();
|
|
1483
1615
|
const prepared = prepareMutation(file.entries, request, this.now().toISOString());
|
|
1484
1616
|
const used = byteCount(file.entries, request.target);
|
|
1485
1617
|
const projected = byteCount(prepared.projectedEntries, request.target);
|
|
1486
|
-
const limit =
|
|
1618
|
+
const limit = this.limits[request.target];
|
|
1487
1619
|
return {
|
|
1488
1620
|
revision: revision(file),
|
|
1489
1621
|
action: request.action,
|
|
@@ -1504,14 +1636,16 @@ ${memory || "(empty)"}
|
|
|
1504
1636
|
return operation;
|
|
1505
1637
|
}
|
|
1506
1638
|
/** Commit semantic compaction and the original mutation together, or leave every local file unchanged. */
|
|
1507
|
-
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);
|
|
1508
1641
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1509
1642
|
const file = this.readSource();
|
|
1510
1643
|
const beforeRevision = revision(file);
|
|
1511
1644
|
if (beforeRevision !== expectedRevision) throw new RuntimeMemoryConflictError();
|
|
1512
1645
|
const now = this.now().toISOString();
|
|
1513
1646
|
const prepared = prepareMutation(file.entries, request, now);
|
|
1514
|
-
|
|
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");
|
|
1515
1649
|
if (!prepared.changed) return {
|
|
1516
1650
|
result: this.result(request.target, prepared.projectedEntries, prepared.fields),
|
|
1517
1651
|
beforeRevision,
|
|
@@ -1520,10 +1654,10 @@ ${memory || "(empty)"}
|
|
|
1520
1654
|
const replacements = compactionCandidates(compacted, prepared.compactableEntries, request.target, now);
|
|
1521
1655
|
if (prepared.pendingEntry !== void 0 && replacements.some((entry) => entry.content === prepared.pendingEntry.content)) throw new Error("compacted runtime memory duplicates the pending mutation");
|
|
1522
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");
|
|
1523
|
-
const targetEntries = [...packCompactionCandidates(replacements, request.target,
|
|
1657
|
+
const targetEntries = [...packCompactionCandidates(replacements, request.target, compactedByteBudget), ...prepared.pendingEntry === void 0 ? [] : [prepared.pendingEntry]];
|
|
1524
1658
|
const entries = [...file.entries.filter((entry) => entry.target !== request.target), ...targetEntries];
|
|
1525
1659
|
const used = byteCount(entries, request.target);
|
|
1526
|
-
const limit =
|
|
1660
|
+
const limit = this.limits[request.target];
|
|
1527
1661
|
if (used > limit) throw new RuntimeMemoryCapacityError(request.target, byteCount(file.entries, request.target), used, limit);
|
|
1528
1662
|
const next = {
|
|
1529
1663
|
version: 1,
|
|
@@ -1559,17 +1693,19 @@ ${memory || "(empty)"}
|
|
|
1559
1693
|
return operation;
|
|
1560
1694
|
}
|
|
1561
1695
|
/** Apply an LLM-produced compaction only to the exact snapshot it reviewed. */
|
|
1562
|
-
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());
|
|
1563
1698
|
const operation = this.queue.then(() => this.withLock(() => {
|
|
1564
1699
|
const file = this.readSource();
|
|
1565
1700
|
const beforeRevision = revision(file);
|
|
1566
1701
|
if (beforeRevision !== expectedRevision) throw new RuntimeMemoryConflictError();
|
|
1567
|
-
|
|
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");
|
|
1568
1704
|
const now = this.now().toISOString();
|
|
1569
|
-
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);
|
|
1570
1706
|
const entries = [...file.entries.filter((entry) => entry.target !== target), ...fitted];
|
|
1571
1707
|
const used = byteCount(entries, target);
|
|
1572
|
-
const limit =
|
|
1708
|
+
const limit = this.limits[target];
|
|
1573
1709
|
if (used > limit) throw new RuntimeMemoryCapacityError(target, byteCount(file.entries, target), used, limit);
|
|
1574
1710
|
this.persist({
|
|
1575
1711
|
version: 1,
|
|
@@ -1600,7 +1736,7 @@ ${memory || "(empty)"}
|
|
|
1600
1736
|
return snapshot;
|
|
1601
1737
|
});
|
|
1602
1738
|
this.queue = operation.catch(() => void 0);
|
|
1603
|
-
return operation;
|
|
1739
|
+
return this.userController === void 0 ? operation : operation.then(() => this.snapshot());
|
|
1604
1740
|
}
|
|
1605
1741
|
initialize() {
|
|
1606
1742
|
mkdirSync(this.directory, {
|
|
@@ -1617,7 +1753,7 @@ ${memory || "(empty)"}
|
|
|
1617
1753
|
const prepared = prepareMutation(file.entries, request, this.now().toISOString());
|
|
1618
1754
|
if (!prepared.changed) return this.result(request.target, prepared.projectedEntries, prepared.fields);
|
|
1619
1755
|
const used = byteCount(prepared.projectedEntries, request.target);
|
|
1620
|
-
const limit =
|
|
1756
|
+
const limit = this.limits[request.target];
|
|
1621
1757
|
if (used > limit) throw new RuntimeMemoryCapacityError(request.target, byteCount(file.entries, request.target), used, limit);
|
|
1622
1758
|
this.persist({
|
|
1623
1759
|
version: 1,
|
|
@@ -1633,7 +1769,7 @@ ${memory || "(empty)"}
|
|
|
1633
1769
|
entryCount: entries.filter((entry) => entry.target === target).length,
|
|
1634
1770
|
usage: {
|
|
1635
1771
|
used: byteCount(entries, target),
|
|
1636
|
-
limit:
|
|
1772
|
+
limit: this.limits[target]
|
|
1637
1773
|
},
|
|
1638
1774
|
...fields.added === void 0 ? {} : { added: fields.added },
|
|
1639
1775
|
...fields.replaced === void 0 ? {} : { replaced: fields.replaced },
|
|
@@ -1645,7 +1781,7 @@ ${memory || "(empty)"}
|
|
|
1645
1781
|
target,
|
|
1646
1782
|
entryCount: entries.filter((entry) => entry.target === target).length,
|
|
1647
1783
|
used: byteCount(entries, target),
|
|
1648
|
-
limit:
|
|
1784
|
+
limit: this.limits[target],
|
|
1649
1785
|
markdownPath: target === "memory" ? this.memoryPath : this.userPath
|
|
1650
1786
|
};
|
|
1651
1787
|
}
|
|
@@ -1689,7 +1825,7 @@ ${memory || "(empty)"}
|
|
|
1689
1825
|
});
|
|
1690
1826
|
const nonce = `${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
|
|
1691
1827
|
const writes = [
|
|
1692
|
-
[this.
|
|
1828
|
+
[this.localUserPath, markdown(file.entries, "user")],
|
|
1693
1829
|
[this.memoryPath, markdown(file.entries, "memory")],
|
|
1694
1830
|
[this.sourcePath, `${JSON.stringify(file, null, 2)}\n`]
|
|
1695
1831
|
];
|
|
@@ -1705,7 +1841,7 @@ ${memory || "(empty)"}
|
|
|
1705
1841
|
}
|
|
1706
1842
|
}
|
|
1707
1843
|
repairProjections(file) {
|
|
1708
|
-
for (const [path, target] of [[this.
|
|
1844
|
+
for (const [path, target] of [[this.localUserPath, "user"], [this.memoryPath, "memory"]]) {
|
|
1709
1845
|
const expected = markdown(file.entries, target);
|
|
1710
1846
|
let current;
|
|
1711
1847
|
try {
|
|
@@ -1950,7 +2086,7 @@ function decodeArchive(base64) {
|
|
|
1950
2086
|
if (bytes.length === 0 || bytes.length > 50331648) throw new Error("Mnemon Pack archive is empty or too large");
|
|
1951
2087
|
return bytes;
|
|
1952
2088
|
}
|
|
1953
|
-
function parseArchive(base64) {
|
|
2089
|
+
function parseArchive(base64, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
1954
2090
|
const archive = decodeArchive(base64);
|
|
1955
2091
|
let count = 0;
|
|
1956
2092
|
let expandedBytes = 0;
|
|
@@ -1981,8 +2117,8 @@ function parseArchive(base64) {
|
|
|
1981
2117
|
const expected = checksumFiles[path];
|
|
1982
2118
|
if (typeof expected !== "string" || expected !== sha256$1(files[path])) throw new Error(`Mnemon Pack checksum mismatch: ${path}`);
|
|
1983
2119
|
}
|
|
1984
|
-
validatePackPayload(files, manifest.components);
|
|
1985
|
-
const actualSummary = summaryFor(manifest.components, files);
|
|
2120
|
+
validatePackPayload(files, manifest.components, runtimeLimits);
|
|
2121
|
+
const actualSummary = summaryFor(manifest.components, files, runtimeLimits);
|
|
1986
2122
|
return {
|
|
1987
2123
|
archiveBytes: archive.length,
|
|
1988
2124
|
expandedBytes,
|
|
@@ -1993,7 +2129,7 @@ function parseArchive(base64) {
|
|
|
1993
2129
|
}
|
|
1994
2130
|
};
|
|
1995
2131
|
}
|
|
1996
|
-
function parseRuntime(value) {
|
|
2132
|
+
function parseRuntime(value, limits = RUNTIME_MEMORY_LIMITS) {
|
|
1997
2133
|
const source = record$2(value);
|
|
1998
2134
|
if (source?.version !== 1 || !Array.isArray(source.entries)) throw new Error("runtime memories.json is invalid");
|
|
1999
2135
|
const entries = source.entries.map((raw) => {
|
|
@@ -2014,7 +2150,7 @@ function parseRuntime(value) {
|
|
|
2014
2150
|
updated_at: entry.updated_at
|
|
2015
2151
|
};
|
|
2016
2152
|
});
|
|
2017
|
-
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`);
|
|
2018
2154
|
return {
|
|
2019
2155
|
version: 1,
|
|
2020
2156
|
entries
|
|
@@ -2089,8 +2225,8 @@ function parseRegistry(value) {
|
|
|
2089
2225
|
function validDatabase(bytes, path) {
|
|
2090
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`);
|
|
2091
2227
|
}
|
|
2092
|
-
function validatePackPayload(files, components) {
|
|
2093
|
-
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);
|
|
2094
2230
|
if (components.includes("documents")) {
|
|
2095
2231
|
const index = parseDocumentIndex(json(files["payload/documents/index.json"] ?? /* @__PURE__ */ new Uint8Array(), "payload/documents/index.json"));
|
|
2096
2232
|
for (const document of index.documents) {
|
|
@@ -2171,9 +2307,9 @@ function emptyRuntime() {
|
|
|
2171
2307
|
entries: []
|
|
2172
2308
|
};
|
|
2173
2309
|
}
|
|
2174
|
-
function readCurrentRuntime(root) {
|
|
2310
|
+
function readCurrentRuntime(root, limits = RUNTIME_MEMORY_LIMITS) {
|
|
2175
2311
|
const path = join(root, "runtime", "memories.json");
|
|
2176
|
-
return existsSync(path) ? parseRuntime(JSON.parse(readFileSync(path, "utf8"))) : emptyRuntime();
|
|
2312
|
+
return existsSync(path) ? parseRuntime(JSON.parse(readFileSync(path, "utf8")), limits) : emptyRuntime();
|
|
2177
2313
|
}
|
|
2178
2314
|
function writeRuntime(directory, file) {
|
|
2179
2315
|
mkdirSync(directory, {
|
|
@@ -2305,12 +2441,12 @@ function writeRegistry(directory, registry, databases) {
|
|
|
2305
2441
|
}
|
|
2306
2442
|
writeFileSync(join(directory, ".dsh-memory-bodies.json"), `${JSON.stringify(registry, null, 2)}\n`, { mode: 384 });
|
|
2307
2443
|
}
|
|
2308
|
-
function componentItems(component, files) {
|
|
2309
|
-
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;
|
|
2310
2446
|
if (component === "documents") return parseDocumentIndex(json(files["payload/documents/index.json"], "payload/documents/index.json")).documents.length;
|
|
2311
2447
|
return parseRegistry(json(files["payload/data/.dsh-memory-bodies.json"], "payload/data/.dsh-memory-bodies.json")).bodies.length;
|
|
2312
2448
|
}
|
|
2313
|
-
function summaryFor(components, files) {
|
|
2449
|
+
function summaryFor(components, files, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
2314
2450
|
return components.map((component) => {
|
|
2315
2451
|
const prefix = `payload/${COMPONENT_DIRECTORIES[component]}/`;
|
|
2316
2452
|
const entries = Object.entries(files).filter(([path]) => path.startsWith(prefix));
|
|
@@ -2318,14 +2454,14 @@ function summaryFor(components, files) {
|
|
|
2318
2454
|
component,
|
|
2319
2455
|
files: entries.length,
|
|
2320
2456
|
bytes: entries.reduce((sum, [, value]) => sum + value.length, 0),
|
|
2321
|
-
items: componentItems(component, files)
|
|
2457
|
+
items: componentItems(component, files, runtimeLimits)
|
|
2322
2458
|
};
|
|
2323
2459
|
});
|
|
2324
2460
|
}
|
|
2325
|
-
function collectExport(root, components) {
|
|
2461
|
+
function collectExport(root, components, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
|
|
2326
2462
|
const files = {};
|
|
2327
2463
|
if (components.includes("runtime")) {
|
|
2328
|
-
const runtime = readCurrentRuntime(root);
|
|
2464
|
+
const runtime = readCurrentRuntime(root, runtimeLimits);
|
|
2329
2465
|
files["payload/runtime/memories.json"] = strToU8(`${JSON.stringify(runtime, null, 2)}\n`);
|
|
2330
2466
|
files["payload/runtime/USER.md"] = strToU8(runtimeProjection(runtime.entries, "user"));
|
|
2331
2467
|
files["payload/runtime/MEMORY.md"] = strToU8(runtimeProjection(runtime.entries, "memory"));
|
|
@@ -2342,9 +2478,9 @@ function collectExport(root, components) {
|
|
|
2342
2478
|
}
|
|
2343
2479
|
return files;
|
|
2344
2480
|
}
|
|
2345
|
-
function mergeRuntime(root, pack) {
|
|
2346
|
-
const current = readCurrentRuntime(root);
|
|
2347
|
-
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);
|
|
2348
2484
|
const keys = new Set(current.entries.map((entry) => `${entry.target}\0${entry.content}`));
|
|
2349
2485
|
const entries = [...current.entries];
|
|
2350
2486
|
for (const entry of incoming.entries) {
|
|
@@ -2357,7 +2493,7 @@ function mergeRuntime(root, pack) {
|
|
|
2357
2493
|
return parseRuntime({
|
|
2358
2494
|
version: 1,
|
|
2359
2495
|
entries
|
|
2360
|
-
});
|
|
2496
|
+
}, runtimeLimits);
|
|
2361
2497
|
}
|
|
2362
2498
|
function mergeDocuments(root, pack) {
|
|
2363
2499
|
const current = readCurrentDocuments(root);
|
|
@@ -2423,7 +2559,7 @@ function reconcilePersistedStore(root) {
|
|
|
2423
2559
|
rmSync(temporary, { force: true });
|
|
2424
2560
|
}
|
|
2425
2561
|
}
|
|
2426
|
-
function stageImport(root, pack, components, mode) {
|
|
2562
|
+
function stageImport(root, pack, components, mode, runtimeLimits) {
|
|
2427
2563
|
const staging = join(root, `.dsh-pack-stage-${randomUUID()}`);
|
|
2428
2564
|
mkdirSync(staging, {
|
|
2429
2565
|
recursive: true,
|
|
@@ -2431,7 +2567,7 @@ function stageImport(root, pack, components, mode) {
|
|
|
2431
2567
|
});
|
|
2432
2568
|
try {
|
|
2433
2569
|
if (components.includes("runtime")) {
|
|
2434
|
-
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);
|
|
2435
2571
|
writeRuntime(join(staging, "runtime"), runtime);
|
|
2436
2572
|
}
|
|
2437
2573
|
if (components.includes("documents")) {
|
|
@@ -2530,12 +2666,17 @@ var MnemonPackManager = class {
|
|
|
2530
2666
|
afterImport;
|
|
2531
2667
|
now;
|
|
2532
2668
|
root;
|
|
2669
|
+
runtimeLimits;
|
|
2533
2670
|
constructor(runner, config, afterImport = () => {}, now = () => /* @__PURE__ */ new Date()) {
|
|
2534
2671
|
this.runner = runner;
|
|
2535
2672
|
this.config = config;
|
|
2536
2673
|
this.afterImport = afterImport;
|
|
2537
2674
|
this.now = now;
|
|
2538
2675
|
this.root = resolve(runner.effectiveDataDir());
|
|
2676
|
+
this.runtimeLimits = {
|
|
2677
|
+
memory: config.runtimeMemory.memoryLimitBytes,
|
|
2678
|
+
user: config.runtimeMemory.userLimitBytes
|
|
2679
|
+
};
|
|
2539
2680
|
}
|
|
2540
2681
|
target() {
|
|
2541
2682
|
return {
|
|
@@ -2548,9 +2689,9 @@ var MnemonPackManager = class {
|
|
|
2548
2689
|
return this.runner.withExclusive(async () => {
|
|
2549
2690
|
await new Promise((resolveReady) => setImmediate(resolveReady));
|
|
2550
2691
|
return withLocks(this.root, components, () => {
|
|
2551
|
-
const payload = collectExport(this.root, components);
|
|
2692
|
+
const payload = collectExport(this.root, components, this.runtimeLimits);
|
|
2552
2693
|
const exportedAt = this.now().toISOString();
|
|
2553
|
-
const summary = summaryFor(components, payload);
|
|
2694
|
+
const summary = summaryFor(components, payload, this.runtimeLimits);
|
|
2554
2695
|
const manifest = {
|
|
2555
2696
|
format: MNEMON_PACK_FORMAT,
|
|
2556
2697
|
version: 1,
|
|
@@ -2589,7 +2730,7 @@ var MnemonPackManager = class {
|
|
|
2589
2730
|
});
|
|
2590
2731
|
}
|
|
2591
2732
|
inspectPack(base64, fileName) {
|
|
2592
|
-
const pack = parseArchive(base64);
|
|
2733
|
+
const pack = parseArchive(base64, this.runtimeLimits);
|
|
2593
2734
|
const sanitizedName = safeName(fileName);
|
|
2594
2735
|
return {
|
|
2595
2736
|
...sanitizedName === void 0 ? {} : { fileName: sanitizedName },
|
|
@@ -2602,7 +2743,7 @@ var MnemonPackManager = class {
|
|
|
2602
2743
|
};
|
|
2603
2744
|
}
|
|
2604
2745
|
async importPack(base64, options) {
|
|
2605
|
-
const pack = parseArchive(base64);
|
|
2746
|
+
const pack = parseArchive(base64, this.runtimeLimits);
|
|
2606
2747
|
if (options.mode !== "merge" && options.mode !== "replace") throw new Error("Pack import mode must be merge or replace");
|
|
2607
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");
|
|
2608
2749
|
const components = options.components === void 0 ? pack.manifest.components : COMPONENT_ORDER.filter((component) => options.components.includes(component));
|
|
@@ -2614,7 +2755,7 @@ var MnemonPackManager = class {
|
|
|
2614
2755
|
mode: 448
|
|
2615
2756
|
});
|
|
2616
2757
|
return withLocks(this.root, components, () => {
|
|
2617
|
-
const staging = stageImport(this.root, pack, components, options.mode);
|
|
2758
|
+
const staging = stageImport(this.root, pack, components, options.mode, this.runtimeLimits);
|
|
2618
2759
|
commitStaging(this.root, staging, components);
|
|
2619
2760
|
this.afterImport(components);
|
|
2620
2761
|
return {
|
|
@@ -5481,6 +5622,37 @@ function registerBuiltinMemoryAdapters(catalog) {
|
|
|
5481
5622
|
};
|
|
5482
5623
|
}
|
|
5483
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
|
|
5484
5656
|
//#region src/memory-view.ts
|
|
5485
5657
|
function hash(value) {
|
|
5486
5658
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -5489,12 +5661,12 @@ function workspace(scope) {
|
|
|
5489
5661
|
const value = scope?.workspaceId?.trim();
|
|
5490
5662
|
return value === void 0 || value === "" ? void 0 : value;
|
|
5491
5663
|
}
|
|
5492
|
-
function
|
|
5664
|
+
function createRuntimeMemorySource(runtimeMemory, resolveBranch = resolveGitBranch) {
|
|
5493
5665
|
return {
|
|
5494
5666
|
layerId: "runtime",
|
|
5495
5667
|
mode: "eager",
|
|
5496
|
-
snapshot: () => {
|
|
5497
|
-
const projection = runtimeMemory.contextProjection();
|
|
5668
|
+
snapshot: (context) => {
|
|
5669
|
+
const projection = runtimeMemory.contextProjection(resolveBranch(workspace(context.scope)));
|
|
5498
5670
|
return {
|
|
5499
5671
|
revision: projection.revision,
|
|
5500
5672
|
wake: projection.text
|
|
@@ -5555,9 +5727,9 @@ function memorySpacesSource(service) {
|
|
|
5555
5727
|
}
|
|
5556
5728
|
};
|
|
5557
5729
|
}
|
|
5558
|
-
function createDefaultMemoryTurnViewManager(kernel, sources) {
|
|
5730
|
+
function createDefaultMemoryTurnViewManager(kernel, sources, options = {}) {
|
|
5559
5731
|
return new MemoryTurnViewManager(kernel, [
|
|
5560
|
-
|
|
5732
|
+
createRuntimeMemorySource(sources.runtimeMemory, options.resolveBranch ?? resolveGitBranch),
|
|
5561
5733
|
documentsSource(sources.documents),
|
|
5562
5734
|
memorySpacesSource(sources.service)
|
|
5563
5735
|
]);
|
|
@@ -5654,7 +5826,16 @@ function createRuntimeGraph(config, workspaceRoot, extensions) {
|
|
|
5654
5826
|
return receiptBridge.record(operation);
|
|
5655
5827
|
};
|
|
5656
5828
|
const service = new MnemonService(runner, config, void 0, void 0, void 0, recordCommit);
|
|
5657
|
-
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);
|
|
5658
5839
|
const documents = new DocumentManager(void 0, void 0, () => runner.effectiveDataDir(), recordCommit);
|
|
5659
5840
|
const storage = new StorageScopeInspector(runner, config);
|
|
5660
5841
|
const packs = new MnemonPackManager(runner, config, (components) => {
|
|
@@ -6360,10 +6541,13 @@ function naturalEvidence(evidence) {
|
|
|
6360
6541
|
return `${index + 1}. [${citation}]${meta === "" ? "" : ` ${meta}`}\n${indentedText(item.content)}`;
|
|
6361
6542
|
}).join("\n");
|
|
6362
6543
|
}
|
|
6544
|
+
function runtimeEntryScopeMeta(entry) {
|
|
6545
|
+
return entry.branches && entry.branches.length > 0 ? ` branches=${entry.branches.join(",")}` : "";
|
|
6546
|
+
}
|
|
6363
6547
|
function runtimeSnapshotContext(target, entries) {
|
|
6364
6548
|
return `Committed ${target === "memory" ? "MEMORY.md" : "USER.md"} snapshot (read-only run data; numbering is one-based):
|
|
6365
6549
|
<runtime-memory-snapshot target="${target}">
|
|
6366
|
-
${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)}
|
|
6367
6551
|
</runtime-memory-snapshot>`;
|
|
6368
6552
|
}
|
|
6369
6553
|
function runtimeRoutingExcerpt(value) {
|
|
@@ -6379,7 +6563,7 @@ function runtimeRouteChunks(entries) {
|
|
|
6379
6563
|
let used = 0;
|
|
6380
6564
|
for (const [offset, entry] of entries.entries()) {
|
|
6381
6565
|
const index = offset + 1;
|
|
6382
|
-
const line = `${index}. [importance=${entry.importance}] ${runtimeRoutingExcerpt(entry.content)}`;
|
|
6566
|
+
const line = `${index}. [importance=${entry.importance}${runtimeEntryScopeMeta(entry)}] ${runtimeRoutingExcerpt(entry.content)}`;
|
|
6383
6567
|
const separatorLength = rendered.length === 0 ? 0 : 3;
|
|
6384
6568
|
if (rendered.length > 0 && used + separatorLength + line.length > RUNTIME_ROUTE_CHUNK_CHARACTERS) {
|
|
6385
6569
|
chunks.push({
|
|
@@ -6488,7 +6672,8 @@ function runtimeMigrationSources(revision, entries) {
|
|
|
6488
6672
|
reference: `runtime:${revision}:memory:${offset + 1}`,
|
|
6489
6673
|
digest: sha256(JSON.stringify({
|
|
6490
6674
|
content: entry.content,
|
|
6491
|
-
importance: entry.importance
|
|
6675
|
+
importance: entry.importance,
|
|
6676
|
+
...entry.branches === void 0 ? {} : { branches: entry.branches }
|
|
6492
6677
|
}))
|
|
6493
6678
|
}));
|
|
6494
6679
|
}
|
|
@@ -6663,6 +6848,7 @@ var MnemonSubagentCoordinator = class {
|
|
|
6663
6848
|
documents;
|
|
6664
6849
|
resultRuntime;
|
|
6665
6850
|
taskAgentModelResolver;
|
|
6851
|
+
runtimeMaintenanceMaxTokensResolver;
|
|
6666
6852
|
counters = {
|
|
6667
6853
|
recalls: 0,
|
|
6668
6854
|
writes: 0,
|
|
@@ -6678,12 +6864,13 @@ var MnemonSubagentCoordinator = class {
|
|
|
6678
6864
|
runtimeQueue = Promise.resolve();
|
|
6679
6865
|
documentQueue = Promise.resolve();
|
|
6680
6866
|
retrievalTurns = /* @__PURE__ */ new Map();
|
|
6681
|
-
constructor(subagents, runtimeMemoryOrSource, documents, resultRuntime, taskAgentModelResolver) {
|
|
6867
|
+
constructor(subagents, runtimeMemoryOrSource, documents, resultRuntime, taskAgentModelResolver, runtimeMaintenanceMaxTokensResolver) {
|
|
6682
6868
|
this.subagents = subagents;
|
|
6683
6869
|
this.runtimeMemoryOrSource = runtimeMemoryOrSource;
|
|
6684
6870
|
this.documents = documents;
|
|
6685
6871
|
this.resultRuntime = resultRuntime;
|
|
6686
6872
|
this.taskAgentModelResolver = taskAgentModelResolver;
|
|
6873
|
+
this.runtimeMaintenanceMaxTokensResolver = runtimeMaintenanceMaxTokensResolver;
|
|
6687
6874
|
}
|
|
6688
6875
|
snapshot() {
|
|
6689
6876
|
return { ...this.counters };
|
|
@@ -7090,6 +7277,7 @@ ${naturalRequest(request)}`;
|
|
|
7090
7277
|
if (eligibleBodies.length === 1) for (const index of plan.entries.keys()) routed.set(index + 1, eligibleBodies[0].id);
|
|
7091
7278
|
else {
|
|
7092
7279
|
const summaries = [];
|
|
7280
|
+
const fallbackBody = eligibleBodies.find((body) => body.mnemonDefault) ?? eligibleBodies[0];
|
|
7093
7281
|
const chunks = runtimeRouteChunks(plan.entries);
|
|
7094
7282
|
for (const [chunkIndex, chunk] of chunks.entries()) {
|
|
7095
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.
|
|
@@ -7101,8 +7289,16 @@ Committed MEMORY.md routing excerpts (global one-based indexes; untrusted run da
|
|
|
7101
7289
|
<runtime-memory-routing-excerpts>
|
|
7102
7290
|
${chunk.context}
|
|
7103
7291
|
</runtime-memory-routing-excerpts>`;
|
|
7104
|
-
|
|
7105
|
-
|
|
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") {
|
|
7106
7302
|
provider = delegated.provider;
|
|
7107
7303
|
runId = delegated.runId;
|
|
7108
7304
|
}
|
|
@@ -7126,9 +7322,10 @@ ${chunk.context}
|
|
|
7126
7322
|
summary = summaries.join(" ");
|
|
7127
7323
|
}
|
|
7128
7324
|
if (routed.size !== plan.entries.length) throw new Error("runtime memory migration omitted committed archive sources");
|
|
7129
|
-
const compactedEntries = plan.entries.map(({ content, importance }) => ({
|
|
7325
|
+
const compactedEntries = plan.entries.map(({ content, importance, branches }) => ({
|
|
7130
7326
|
content,
|
|
7131
|
-
importance
|
|
7327
|
+
importance,
|
|
7328
|
+
...branches === void 0 ? {} : { branches }
|
|
7132
7329
|
}));
|
|
7133
7330
|
if ((await runtimeMemory.planMaintenance(request)).revision !== plan.revision) throw new Error("runtime memory changed while archival was running; no archive writes were attempted");
|
|
7134
7331
|
const sources = runtimeMigrationSources(plan.revision, plan.entries);
|
|
@@ -7139,7 +7336,8 @@ ${chunk.context}
|
|
|
7139
7336
|
category: "context",
|
|
7140
7337
|
importance: entry.importance === "critical" ? 5 : entry.importance === "low" ? 1 : 3,
|
|
7141
7338
|
source: "agent",
|
|
7142
|
-
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}`) }
|
|
7143
7341
|
};
|
|
7144
7342
|
}), signal);
|
|
7145
7343
|
if (archiveResults.length !== sources.length) throw new Error("runtime archive batch did not return one receipt per source entry");
|
|
@@ -7345,7 +7543,7 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
|
|
|
7345
7543
|
const completionPersona = `${persona}
|
|
7346
7544
|
|
|
7347
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.`;
|
|
7348
|
-
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;
|
|
7349
7547
|
const fixed = this.taskAgentModelResolver?.();
|
|
7350
7548
|
const baseAgentOptions = perOpMaxTokens === void 0 ? void 0 : { maxTokens: perOpMaxTokens };
|
|
7351
7549
|
const resolvedAgentOptions = fixed === void 0 ? baseAgentOptions : {
|
|
@@ -7664,6 +7862,16 @@ function createPluginMessage(text, form, summary) {
|
|
|
7664
7862
|
function sourceOf(message) {
|
|
7665
7863
|
return message.source;
|
|
7666
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
|
+
}
|
|
7667
7875
|
function eventTurn(event) {
|
|
7668
7876
|
return typeof event.data.turn === "number" ? event.data.turn : void 0;
|
|
7669
7877
|
}
|
|
@@ -7733,6 +7941,11 @@ var MnemonAgentLifecycle = class {
|
|
|
7733
7941
|
guidedTurns = /* @__PURE__ */ new Set();
|
|
7734
7942
|
memoryActivity = new TurnActivityProjection();
|
|
7735
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
|
+
*/
|
|
7736
7949
|
cueInjected = false;
|
|
7737
7950
|
idleReviewTimer;
|
|
7738
7951
|
reviewController;
|
|
@@ -7813,6 +8026,24 @@ var MnemonAgentLifecycle = class {
|
|
|
7813
8026
|
const pinned = this.pinnedView;
|
|
7814
8027
|
return pinned === void 0 ? void 0 : pinned.manager.wake(pinned.context.viewId);
|
|
7815
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
|
+
}
|
|
7816
8047
|
async preStep(payload, next) {
|
|
7817
8048
|
if (payload.step === 1) this.cancelIdleReview(true);
|
|
7818
8049
|
const decision = await next();
|
|
@@ -7833,7 +8064,7 @@ var MnemonAgentLifecycle = class {
|
|
|
7833
8064
|
this.counters.primes += 1;
|
|
7834
8065
|
this.mark("prime");
|
|
7835
8066
|
}
|
|
7836
|
-
if (this.
|
|
8067
|
+
if (this.cueAlreadyVisible()) return decision;
|
|
7837
8068
|
const reminder = guidedReminder(this.config);
|
|
7838
8069
|
if (reminder === void 0) return decision;
|
|
7839
8070
|
this.cueInjected = true;
|
|
@@ -9152,12 +9383,16 @@ function createWriteHandler(input, lifecycle, runtimeMemory, versions) {
|
|
|
9152
9383
|
requireLayer(resolved.graph, "runtime", "write");
|
|
9153
9384
|
if (resolved.graph.runtimeMemory === void 0) throw new Error("runtime memory is unavailable");
|
|
9154
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
|
+
})();
|
|
9155
9389
|
const request = {
|
|
9156
9390
|
action: String(payload.action ?? ""),
|
|
9157
9391
|
target: String(payload.target ?? ""),
|
|
9158
9392
|
...payload.content === void 0 ? {} : { content: String(payload.content) },
|
|
9159
9393
|
...payload.old_text === void 0 ? {} : { oldText: String(payload.old_text) },
|
|
9160
|
-
...payload.importance === void 0 ? {} : { importance: String(payload.importance) }
|
|
9394
|
+
...payload.importance === void 0 ? {} : { importance: String(payload.importance) },
|
|
9395
|
+
...branches === void 0 ? {} : { branches }
|
|
9161
9396
|
};
|
|
9162
9397
|
const sessionId = String(payload.sessionId ?? "").trim();
|
|
9163
9398
|
return success$1(inspectionDiverged || lifecycle === void 0 || sessionId === "" || resolved.explicitWorkspace && !alignedSession ? await resolved.graph.runtimeMemory.mutate(request) : await lifecycle.runtime(sessionId, request));
|
|
@@ -9411,6 +9646,7 @@ function object(value) {
|
|
|
9411
9646
|
}
|
|
9412
9647
|
const MUTABLE_FIELDS = [
|
|
9413
9648
|
"storageScope",
|
|
9649
|
+
"runtimeUserScope",
|
|
9414
9650
|
"cliPath",
|
|
9415
9651
|
"dataDir",
|
|
9416
9652
|
"customPackId",
|
|
@@ -9418,6 +9654,7 @@ const MUTABLE_FIELDS = [
|
|
|
9418
9654
|
"store",
|
|
9419
9655
|
"timeoutMs",
|
|
9420
9656
|
"defaultRecallLimit",
|
|
9657
|
+
"runtimeMemory",
|
|
9421
9658
|
"embedding",
|
|
9422
9659
|
"recallQuality",
|
|
9423
9660
|
"routingGuidance",
|
|
@@ -9939,7 +10176,7 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
9939
10176
|
}));
|
|
9940
10177
|
ctx.tools.register(definition({
|
|
9941
10178
|
name: "mnemon_runtime_memory",
|
|
9942
|
-
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.",
|
|
9943
10180
|
parameters: {
|
|
9944
10181
|
type: "object",
|
|
9945
10182
|
properties: {
|
|
@@ -9973,6 +10210,11 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
9973
10210
|
"low"
|
|
9974
10211
|
],
|
|
9975
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."
|
|
9976
10218
|
}
|
|
9977
10219
|
},
|
|
9978
10220
|
required: ["action", "target"]
|
|
@@ -9989,7 +10231,8 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
9989
10231
|
target: args.target,
|
|
9990
10232
|
...args.content === void 0 ? {} : { content: args.content },
|
|
9991
10233
|
...args.old_text === void 0 ? {} : { oldText: args.old_text },
|
|
9992
|
-
...args.importance === void 0 ? {} : { importance: args.importance }
|
|
10234
|
+
...args.importance === void 0 ? {} : { importance: args.importance },
|
|
10235
|
+
...args.branches === void 0 ? {} : { branches: args.branches }
|
|
9993
10236
|
};
|
|
9994
10237
|
return isSubagent(exec.agent) ? runtime.runtimeMemory.mutate(request) : coordinator.runtime(requireAgent(exec), request, exec.signal);
|
|
9995
10238
|
},
|
|
@@ -10280,6 +10523,130 @@ function registerTools(ctx, serviceOrSource, coordinator, runtimeMemory, documen
|
|
|
10280
10523
|
}));
|
|
10281
10524
|
}
|
|
10282
10525
|
//#endregion
|
|
10526
|
+
//#region src/subagent-token-usage.ts
|
|
10527
|
+
/**
|
|
10528
|
+
* Mnemon-owned projection for provider usage after a subagent descriptor.
|
|
10529
|
+
*
|
|
10530
|
+
* Fork-backed subagents inherit the parent's durable event prefix. DSH's
|
|
10531
|
+
* generic `tokenUsage` projection intentionally totals that complete log, so
|
|
10532
|
+
* it cannot also represent the cost of the child shown in the subagent
|
|
10533
|
+
* catalog. This independent projection keeps the generic metric untouched
|
|
10534
|
+
* and resets at every durable descriptor.
|
|
10535
|
+
*/
|
|
10536
|
+
const tokenCountSchema = z$1.number().int().nonnegative();
|
|
10537
|
+
const tokenUsageSchema = z$1.object({
|
|
10538
|
+
uncachedInputTokens: tokenCountSchema,
|
|
10539
|
+
outputTokens: tokenCountSchema,
|
|
10540
|
+
cacheReadTokens: tokenCountSchema,
|
|
10541
|
+
cacheWriteTokens: tokenCountSchema
|
|
10542
|
+
}).strict();
|
|
10543
|
+
const tokenUsageStateSchema = z$1.object({
|
|
10544
|
+
descriptorSeen: z$1.boolean(),
|
|
10545
|
+
totals: tokenUsageSchema,
|
|
10546
|
+
last: z$1.object({
|
|
10547
|
+
turn: tokenCountSchema,
|
|
10548
|
+
step: tokenCountSchema,
|
|
10549
|
+
buckets: tokenUsageSchema
|
|
10550
|
+
}).strict().nullable()
|
|
10551
|
+
}).strict();
|
|
10552
|
+
const emptyTokenUsage = () => ({
|
|
10553
|
+
uncachedInputTokens: 0,
|
|
10554
|
+
outputTokens: 0,
|
|
10555
|
+
cacheReadTokens: 0,
|
|
10556
|
+
cacheWriteTokens: 0
|
|
10557
|
+
});
|
|
10558
|
+
function nonnegativeInteger(value) {
|
|
10559
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
|
|
10560
|
+
}
|
|
10561
|
+
function usageBuckets(value) {
|
|
10562
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
|
|
10563
|
+
const usage = value;
|
|
10564
|
+
const inputTokens = nonnegativeInteger(usage.inputTokens);
|
|
10565
|
+
const outputTokens = nonnegativeInteger(usage.outputTokens);
|
|
10566
|
+
const cacheReadTokens = usage.cacheReadTokens === void 0 ? 0 : nonnegativeInteger(usage.cacheReadTokens);
|
|
10567
|
+
const cacheWriteTokens = usage.cacheWriteTokens === void 0 ? 0 : nonnegativeInteger(usage.cacheWriteTokens);
|
|
10568
|
+
if (inputTokens === void 0 || outputTokens === void 0 || cacheReadTokens === void 0 || cacheWriteTokens === void 0) return void 0;
|
|
10569
|
+
return {
|
|
10570
|
+
uncachedInputTokens: inputTokens,
|
|
10571
|
+
outputTokens,
|
|
10572
|
+
cacheReadTokens,
|
|
10573
|
+
cacheWriteTokens
|
|
10574
|
+
};
|
|
10575
|
+
}
|
|
10576
|
+
function usageSample(event) {
|
|
10577
|
+
const turn = nonnegativeInteger(event.data.turn);
|
|
10578
|
+
const step = nonnegativeInteger(event.data.step);
|
|
10579
|
+
if (turn === void 0 || step === void 0) return void 0;
|
|
10580
|
+
let rawUsage;
|
|
10581
|
+
if (event.type === "assistant/chunk") {
|
|
10582
|
+
const chunk = event.data.chunk;
|
|
10583
|
+
if (typeof chunk !== "object" || chunk === null || Array.isArray(chunk)) return void 0;
|
|
10584
|
+
const record = chunk;
|
|
10585
|
+
if (record.type !== "usage") return void 0;
|
|
10586
|
+
rawUsage = record.usage;
|
|
10587
|
+
} else if (event.type === "assistant/message") rawUsage = event.data.usage;
|
|
10588
|
+
else return;
|
|
10589
|
+
const buckets = usageBuckets(rawUsage);
|
|
10590
|
+
return buckets === void 0 ? void 0 : {
|
|
10591
|
+
turn,
|
|
10592
|
+
step,
|
|
10593
|
+
buckets
|
|
10594
|
+
};
|
|
10595
|
+
}
|
|
10596
|
+
function equalTokenUsage(left, right) {
|
|
10597
|
+
return left.uncachedInputTokens === right.uncachedInputTokens && left.outputTokens === right.outputTokens && left.cacheReadTokens === right.cacheReadTokens && left.cacheWriteTokens === right.cacheWriteTokens;
|
|
10598
|
+
}
|
|
10599
|
+
function replaceTokenUsage(totals, previous, next) {
|
|
10600
|
+
return {
|
|
10601
|
+
uncachedInputTokens: totals.uncachedInputTokens - (previous?.uncachedInputTokens ?? 0) + next.uncachedInputTokens,
|
|
10602
|
+
outputTokens: totals.outputTokens - (previous?.outputTokens ?? 0) + next.outputTokens,
|
|
10603
|
+
cacheReadTokens: totals.cacheReadTokens - (previous?.cacheReadTokens ?? 0) + next.cacheReadTokens,
|
|
10604
|
+
cacheWriteTokens: totals.cacheWriteTokens - (previous?.cacheWriteTokens ?? 0) + next.cacheWriteTokens
|
|
10605
|
+
};
|
|
10606
|
+
}
|
|
10607
|
+
/**
|
|
10608
|
+
* Fold provider usage after the latest subagent descriptor. A fork may carry
|
|
10609
|
+
* an ancestor descriptor as part of its seed, so every descriptor resets the
|
|
10610
|
+
* fold and the child's own descriptor becomes the final authoritative origin.
|
|
10611
|
+
*/
|
|
10612
|
+
const mnemonSubagentTokenUsageProjectionDefinition = {
|
|
10613
|
+
key: "mnemonSubagentTokenUsage",
|
|
10614
|
+
stateVersion: 1,
|
|
10615
|
+
stateSchema: tokenUsageStateSchema,
|
|
10616
|
+
init: () => ({
|
|
10617
|
+
descriptorSeen: false,
|
|
10618
|
+
totals: emptyTokenUsage(),
|
|
10619
|
+
last: null
|
|
10620
|
+
}),
|
|
10621
|
+
apply: (state, event) => {
|
|
10622
|
+
if (event.type === "subagent/descriptor") return {
|
|
10623
|
+
descriptorSeen: true,
|
|
10624
|
+
totals: emptyTokenUsage(),
|
|
10625
|
+
last: null
|
|
10626
|
+
};
|
|
10627
|
+
if (!state.descriptorSeen) return state;
|
|
10628
|
+
const sample = usageSample(event);
|
|
10629
|
+
if (sample === void 0) return state;
|
|
10630
|
+
const previous = state.last !== null && state.last.turn === sample.turn && state.last.step === sample.step ? state.last.buckets : void 0;
|
|
10631
|
+
if (previous !== void 0 && equalTokenUsage(previous, sample.buckets)) return state;
|
|
10632
|
+
return {
|
|
10633
|
+
descriptorSeen: true,
|
|
10634
|
+
totals: replaceTokenUsage(state.totals, previous, sample.buckets),
|
|
10635
|
+
last: sample
|
|
10636
|
+
};
|
|
10637
|
+
},
|
|
10638
|
+
wire: {
|
|
10639
|
+
viewSchema: tokenUsageSchema.nullable(),
|
|
10640
|
+
view: (state) => state.descriptorSeen ? state.totals : null
|
|
10641
|
+
}
|
|
10642
|
+
};
|
|
10643
|
+
/** Register lazily when the optional DSH projection service is present. */
|
|
10644
|
+
function registerMnemonSubagentTokenUsageProjection(ctx) {
|
|
10645
|
+
ctx.inject(["sessionProjections"], (rawContext) => {
|
|
10646
|
+
rawContext.sessionProjections?.register(mnemonSubagentTokenUsageProjectionDefinition);
|
|
10647
|
+
});
|
|
10648
|
+
}
|
|
10649
|
+
//#endregion
|
|
10283
10650
|
//#region src/index.ts
|
|
10284
10651
|
const name = "dsh-mnemon";
|
|
10285
10652
|
const provide = ["mnemonMemory"];
|
|
@@ -10301,6 +10668,7 @@ function optionalWorkspaceRegistry(ctx) {
|
|
|
10301
10668
|
/** Mount native model tools on every DSH surface and UI RPC only when Web connection exists. */
|
|
10302
10669
|
function apply(rawContext, config = {}) {
|
|
10303
10670
|
const ctx = rawContext;
|
|
10671
|
+
registerMnemonSubagentTokenUsageProjection(ctx);
|
|
10304
10672
|
const extensions = memoryBoot;
|
|
10305
10673
|
ctx.provide?.("mnemonMemory", extensions);
|
|
10306
10674
|
const prepared = /* @__PURE__ */ new Map();
|
|
@@ -10350,7 +10718,7 @@ function apply(rawContext, config = {}) {
|
|
|
10350
10718
|
provider,
|
|
10351
10719
|
model
|
|
10352
10720
|
};
|
|
10353
|
-
});
|
|
10721
|
+
}, () => runtime.config.runtimeMemory.maintenanceMaxTokens);
|
|
10354
10722
|
const lifecycle = new MnemonLifecycle(ctx, coordinator, runtime.config, runtime);
|
|
10355
10723
|
ctx.effect(() => {
|
|
10356
10724
|
const stop = lifecycle.start();
|