dsh-mnemon 0.5.23 → 0.5.25

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/lib/index.js CHANGED
@@ -7,7 +7,7 @@ import z from "@deepseek-ai/schemastery";
7
7
  import { basename, delimiter, dirname, isAbsolute, join, normalize, resolve } from "node:path";
8
8
  import { resolveEmbedding, resolvePersistenceStrategy, resolveRecallQuality } from "dsh-mnemon-source-memory-spaces";
9
9
  import { createHash, randomUUID } from "node:crypto";
10
- import { accessSync, closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
10
+ import { accessSync, chmodSync, closeSync, constants, copyFileSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
11
11
  import { homedir } from "node:os";
12
12
  import { strToU8, unzipSync, zipSync } from "fflate";
13
13
  import { DOCUMENTS_ACTIVE_LIMIT_BYTES, DOCUMENTS_VERSION } from "dsh-mnemon-source-documents/contracts";
@@ -862,6 +862,33 @@ function readCurrentRuntime(root, limits = RUNTIME_MEMORY_LIMITS) {
862
862
  const path = join(root, "runtime", "memories.json");
863
863
  return existsSync(path) ? parseRuntime(JSON.parse(readFileSync(path, "utf8")), limits) : emptyRuntime();
864
864
  }
865
+ /**
866
+ * A Pack carries no local archive: the one MEMORY.md wrote while no Memory Space
867
+ * could take its entries holds their only copy, so an import keeps it (#336).
868
+ * Its files and folders are carried; a link is neither carried nor followed.
869
+ */
870
+ function keepRuntimeArchive(root, stagedRuntime) {
871
+ const copy = (from, to) => {
872
+ let stat;
873
+ try {
874
+ stat = lstatSync(from);
875
+ } catch {
876
+ return;
877
+ }
878
+ if (stat.isSymbolicLink()) return;
879
+ if (stat.isFile()) {
880
+ copyFileSync(from, to);
881
+ chmodSync(to, 384);
882
+ } else if (stat.isDirectory()) {
883
+ mkdirSync(to, {
884
+ recursive: true,
885
+ mode: 448
886
+ });
887
+ for (const name of readdirSync(from)) copy(join(from, name), join(to, name));
888
+ }
889
+ };
890
+ copy(join(root, "runtime", "archived"), join(stagedRuntime, "archived"));
891
+ }
865
892
  function writeRuntime(directory, file) {
866
893
  mkdirSync(directory, {
867
894
  recursive: true,
@@ -1120,6 +1147,7 @@ function stageImport(root, pack, components, mode, runtimeLimits) {
1120
1147
  if (components.includes("runtime")) {
1121
1148
  const runtime = mode === "merge" ? mergeRuntime(root, pack, runtimeLimits) : parseRuntime(json$1(pack.files["payload/runtime/memories.json"], "payload/runtime/memories.json"), runtimeLimits);
1122
1149
  writeRuntime(join(staging, "runtime"), runtime);
1150
+ keepRuntimeArchive(root, join(staging, "runtime"));
1123
1151
  }
1124
1152
  if (components.includes("documents")) {
1125
1153
  const documents = mode === "merge" ? mergeDocuments(root, pack) : archiveDocuments(pack);
@@ -3437,6 +3465,14 @@ function pendingMutationContext(plan) {
3437
3465
  ...plan.excluded === void 0 ? [] : [`- Matched committed entry: excluded by the host because it will be ${plan.action === "replace" ? "replaced" : "removed"}`]
3438
3466
  ].join("\n");
3439
3467
  }
3468
+ /**
3469
+ * A space that can take a MEMORY.md archive or a Document's index: active, its
3470
+ * Provider enabled and ready, with exact writes and safe forget. A Mnemon Native
3471
+ * space stays listed, and active, while its CLI is missing; it takes no writes then.
3472
+ */
3473
+ function archivableSpace(directory, body) {
3474
+ return body.active && body.providerEnabled !== false && body.provider.capabilities.remember === true && body.provider.capabilities.writeMode === "exact" && body.provider.capabilities.forget === true && directory.providers?.find((provider) => provider.id === body.provider.id)?.serviceConfigured !== false;
3475
+ }
3440
3476
  function compactedBudget(plan) {
3441
3477
  const pendingBytes = plan.pending === void 0 ? 0 : Buffer.byteLength(plan.pending.content, "utf8");
3442
3478
  const separatorBytes = plan.pending === void 0 || plan.entries.length === 0 ? 0 : Buffer.byteLength(RUNTIME_ENTRY_DELIMITER, "utf8");
@@ -3595,6 +3631,22 @@ function destinationFromCommittedMutation(result, memoryBodyId, content) {
3595
3631
  digest
3596
3632
  };
3597
3633
  }
3634
+ /**
3635
+ * The memory a skipped write names as already holding exactly this text, when
3636
+ * its receipt carries that memory's id and stored text. Anything less proves
3637
+ * nothing, and the caller verifies by search instead.
3638
+ */
3639
+ function skippedExactDestination(result, memoryBodyId, content) {
3640
+ const value = optionalObject(result);
3641
+ const id = typeof value?.id === "string" ? value.id.trim() : "";
3642
+ if (id === "" || value?.content !== content) return void 0;
3643
+ if (value.memoryBodyId !== void 0 && value.memoryBodyId !== memoryBodyId) return void 0;
3644
+ return {
3645
+ layerId: "memory-spaces",
3646
+ reference: `memory-space:${encodeURIComponent(memoryBodyId)}/item:${encodeURIComponent(id)}`,
3647
+ digest: sha256(content)
3648
+ };
3649
+ }
3598
3650
  function mutationStates(result) {
3599
3651
  const value = optionalObject(result);
3600
3652
  return [value?.action, value?.status].filter((entry) => typeof entry === "string").map((entry) => entry.trim().toLocaleLowerCase());
@@ -3892,11 +3944,13 @@ var MnemonSubagentCoordinator = class {
3892
3944
  commit: () => runtime.mutate("mutate", request, signal),
3893
3945
  memorySpaces: async () => {
3894
3946
  if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
3895
- assertParticipation(graph.config, "memory-spaces", "write", "automatic");
3947
+ if (!allowsParticipation(graph.config, "memory-spaces", "write", "automatic") || !lease.generation.sourceInstances().some((source) => source.sourceTypeId === "memory-spaces")) return void 0;
3896
3948
  const source = graph.source("memory-spaces", scope).forGeneration(lease.generation);
3949
+ const { sourceInstanceKey } = await source.identity();
3897
3950
  return {
3898
3951
  source,
3899
- cleanup: source
3952
+ cleanup: source,
3953
+ elsewhere: await this.archivableElsewhere(graph, scope, lease.generation, sourceInstanceKey, signal)
3900
3954
  };
3901
3955
  },
3902
3956
  model: (...args) => this.runtimeModel(scope, parent, signal, ...args)
@@ -3946,7 +4000,7 @@ var MnemonSubagentCoordinator = class {
3946
4000
  },
3947
4001
  memorySpaces: async () => {
3948
4002
  if (!authorize()) throw new Error("Runtime capacity maintenance is no longer authorized");
3949
- return this.runtimeArchiveSource(graph, turn.scope, turn.view, lease.generation, turn);
4003
+ return this.runtimeArchiveSource(graph, turn.scope, turn.view, lease.generation, turn, signal);
3950
4004
  },
3951
4005
  model: (...args) => this.runtimeModel(turn.scope, parent, signal, ...args)
3952
4006
  }, input, signal);
@@ -3992,7 +4046,7 @@ var MnemonSubagentCoordinator = class {
3992
4046
  scenario: "management.runtime-capacity",
3993
4047
  budget: DEFAULT_MEMORY_VIEW_BUDGET
3994
4048
  }, signal);
3995
- return this.runtimeArchiveSource(graph, request.scope, view, generation);
4049
+ return this.runtimeArchiveSource(graph, request.scope, view, generation, void 0, signal);
3996
4050
  },
3997
4051
  model: (...args) => this.runtimeModel(request.scope, void 0, signal, ...args)
3998
4052
  }, request.input, signal);
@@ -4006,11 +4060,14 @@ var MnemonSubagentCoordinator = class {
4006
4060
  lease.release();
4007
4061
  }
4008
4062
  }
4009
- async runtimeArchiveSource(graph, scope, view, generation, turn) {
4063
+ async runtimeArchiveSource(graph, scope, view, generation, turn, signal) {
4010
4064
  if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
4011
- assertParticipation(graph.config, "memory-spaces", "write", "automatic");
4065
+ if (!allowsParticipation(graph.config, "memory-spaces", "write", "automatic")) return void 0;
4012
4066
  const candidates = generation.sourceInstances().filter((source) => source.sourceTypeId === "memory-spaces" && view.actionOffers.some((offer) => offer.sourceInstanceKey === source.sourceInstanceKey && offer.sourceActionId === "remember" && offer.authority === void 0));
4013
- if (candidates.length === 0) throw new Error("Source Action is not offered by the current View: memory-spaces/remember");
4067
+ if (candidates.length === 0) {
4068
+ if (await this.noArchivableMemorySpace(graph, scope, generation, signal)) return void 0;
4069
+ throw new Error("Source Action is not offered by the current View: memory-spaces/remember");
4070
+ }
4014
4071
  if (candidates.length !== 1) throw new Error("Runtime archival requires one unambiguous writable Memory Spaces Source");
4015
4072
  const cleanup = graph.source("memory-spaces", scope).forInstance(candidates[0].sourceInstanceKey).forGeneration(generation);
4016
4073
  let source = cleanup;
@@ -4024,6 +4081,46 @@ var MnemonSubagentCoordinator = class {
4024
4081
  writeScope: {
4025
4082
  viewId: view.id,
4026
4083
  grant
4084
+ },
4085
+ elsewhere: await this.archivableElsewhere(graph, scope, generation, candidates[0].sourceInstanceKey, signal)
4086
+ };
4087
+ }
4088
+ /**
4089
+ * MEMORY.md keeps working without a Memory Space to archive into: the Runtime
4090
+ * Source moves the entries compaction leaves out to its local archive, as exact
4091
+ * text, in the same commit as the compaction and the pending write (#336).
4092
+ */
4093
+ async archiveRuntimeLocally(context, request, plan, reason, signal) {
4094
+ signal.throwIfAborted();
4095
+ if (plan.localArchive !== true) throw new Error(`runtime memory archival found no Memory Space that can take the archive (${reason}), and this Runtime Source cannot archive locally; pending write was not committed and existing runtime entries are unchanged; update dsh-mnemon-source-runtime, activate a supported Memory Space, or increase runtimeMemory.memoryLimitBytes`);
4096
+ context.assertWritable?.();
4097
+ const runId = `host-${randomUUID()}`;
4098
+ const compacted = plan.entries.map(({ content, importance, branches }) => ({
4099
+ content,
4100
+ importance,
4101
+ ...branches === void 0 ? {} : { branches }
4102
+ }));
4103
+ const mutation = await context.runtime.mutateResult("compact-and-mutate", {
4104
+ revision: plan.revision,
4105
+ mutation: request,
4106
+ compacted,
4107
+ maxBytes: compactedBudget(plan),
4108
+ archive: "local"
4109
+ }, signal);
4110
+ const archived = mutation.value.archived?.entries ?? 0;
4111
+ this.counters.migrations += 1;
4112
+ this.counters.lastRunId = runId;
4113
+ this.counters.lastOperation = "migration";
4114
+ this.counters.lastAt = (/* @__PURE__ */ new Date()).toISOString();
4115
+ return {
4116
+ ...mutation.value,
4117
+ revision: mutation.revision,
4118
+ maintenance: {
4119
+ kind: "local-archive",
4120
+ runId,
4121
+ provider: "host",
4122
+ summary: `Moved ${archived} MEMORY.md ${archived === 1 ? "entry" : "entries"} to the local archive${mutation.value.archived === void 0 ? "" : ` ${mutation.value.archived.path}`}, since ${reason}.`,
4123
+ memoryBodyIds: []
4027
4124
  }
4028
4125
  };
4029
4126
  }
@@ -4160,15 +4257,22 @@ ${naturalRequest(request)}`;
4160
4257
  const document = await controller.read("document", { id }, signal);
4161
4258
  if (document.status !== "active") throw new Error("only active documents can be archived");
4162
4259
  const source = documentMigrationSource(document);
4163
- const memoryService = (await this.assertAutomaticMemoryWrite(parent)).forGeneration(lease.generation);
4260
+ const indexSpaces = await this.documentIndexSpaces(parent, lease.generation, signal);
4261
+ if (indexSpaces === void 0) return this.archiveDocumentLocally(controller, document, "no Memory Spaces layer takes writes here", signal);
4262
+ const memoryService = indexSpaces.forGeneration(lease.generation);
4164
4263
  const identity = await memoryService.identity();
4165
4264
  const cleanup = graph.source("memory-spaces", agentScope(parent, graph.config)).forInstance(identity.sourceInstanceKey).forGeneration(lease.generation);
4166
4265
  const grant = authority?.context.view.readGrants.find((grant) => grant.sourceInstanceKey === identity.sourceInstanceKey && grant.schema === "dsh-mnemon.memory-spaces/v1");
4167
4266
  if (authority !== void 0 && grant === void 0) throw new Error("document archive requires a Memory Space namespace grant");
4168
4267
  const allowed = grant === void 0 ? void 0 : new Set(strings(object$6(grant.value).memoryBodyIds));
4169
- const eligible = (body) => body.active && body.providerEnabled !== false && body.provider.capabilities.remember && body.provider.capabilities.forget && body.provider.capabilities.writeMode === "exact" && (allowed === void 0 || allowed.has(body.id));
4170
- const bodies = (await memoryService.read("body-directory", null, signal)).items.filter(eligible);
4171
- if (bodies.length === 0) throw new Error("document archive requires an existing active Memory Space with exact writes and safe forget");
4268
+ const directory = await memoryService.read("body-directory", null, signal);
4269
+ const indexable = (body) => archivableSpace(directory, body);
4270
+ const eligible = (body) => indexable(body) && (allowed === void 0 || allowed.has(body.id));
4271
+ const bodies = directory.items.filter(eligible);
4272
+ if (bodies.length === 0) {
4273
+ if (!directory.items.some(indexable) && !await this.archivableElsewhere(graph, agentScope(parent, graph.config), lease.generation, identity.sourceInstanceKey, signal)) return this.archiveDocumentLocally(controller, document, "no Memory Space can take its index", signal);
4274
+ throw new Error("document archive requires an existing active Memory Space with exact writes and safe forget");
4275
+ }
4172
4276
  const { provider, runId, result } = await this.delegate(parent, "document-archive", "Plan managed document archive", documentArchivePrompt(document, source, bodies), [], DOCUMENT_ARCHIVE_SCHEMA, signal, "spawn", DOCUMENT_ARCHIVE_PERSONA);
4173
4277
  const value = object$6(result.structured);
4174
4278
  const summary = typeof value.summary === "string" ? value.summary.trim() : "";
@@ -4220,18 +4324,22 @@ ${naturalRequest(request)}`;
4220
4324
  if (written.length !== 1) throw new Error("document archive requires exactly one index receipt");
4221
4325
  if (isNew && receiptId !== void 0) destination = destinationFromCommittedMutation(receipt, memoryBodyId, content);
4222
4326
  else if (states.includes("skipped")) {
4223
- const exact = (await memoryService.read("search", {
4224
- query: document.contentHash,
4225
- mode: "keyword",
4226
- limit: 50,
4227
- memoryBodyIds: [memoryBodyId]
4228
- }, signal)).results.find((item) => item.memoryBodyId === memoryBodyId && item.id === receiptId && item.content === content);
4229
- if (exact === void 0) throw new Error(`document archive skipped index lacks exact durable evidence in Memory Space ${memoryBodyId}`);
4230
- destination = {
4231
- layerId: "memory-spaces",
4232
- reference: `memory-space:${encodeURIComponent(memoryBodyId)}/item:${encodeURIComponent(exact.id)}`,
4233
- digest: sha256(exact.content)
4234
- };
4327
+ const named = skippedExactDestination(receipt, memoryBodyId, content);
4328
+ if (named !== void 0) destination = named;
4329
+ else {
4330
+ const exact = (await memoryService.read("search", {
4331
+ query: document.contentHash,
4332
+ mode: "keyword",
4333
+ limit: 50,
4334
+ memoryBodyIds: [memoryBodyId]
4335
+ }, signal)).results.find((item) => item.memoryBodyId === memoryBodyId && item.id === receiptId && item.content === content);
4336
+ if (exact === void 0) throw new Error(`document archive skipped index lacks exact durable evidence in Memory Space ${memoryBodyId}`);
4337
+ destination = {
4338
+ layerId: "memory-spaces",
4339
+ reference: `memory-space:${encodeURIComponent(memoryBodyId)}/item:${encodeURIComponent(exact.id)}`,
4340
+ digest: sha256(exact.content)
4341
+ };
4342
+ }
4235
4343
  } else throw new Error(`document archive received no reversible committed index receipt in Memory Space ${memoryBodyId}`);
4236
4344
  }
4237
4345
  signal.throwIfAborted();
@@ -4293,6 +4401,7 @@ ${naturalRequest(request)}`;
4293
4401
  if (plan.entries.length === 0) throw new Error("runtime memory capacity was exceeded without entries available for maintenance");
4294
4402
  if (request.target === "user") return this.compactUserAndCommit(context, request, plan, signal);
4295
4403
  const archive = await context.memorySpaces();
4404
+ if (archive === void 0) return this.archiveRuntimeLocally(context, request, plan, "no Memory Space takes automatic writes here", signal);
4296
4405
  const memoryService = archive.source;
4297
4406
  let allowed = archive.memoryBodyIds;
4298
4407
  const readDirectory = async () => {
@@ -4306,13 +4415,16 @@ ${naturalRequest(request)}`;
4306
4415
  return catalog;
4307
4416
  };
4308
4417
  const catalog = await readDirectory();
4309
- const writable = (body) => body.active && body.providerEnabled !== false && body.provider.capabilities.remember === true && (allowed === void 0 || allowed.has(body.id));
4310
- const eligible = (body) => writable(body) && body.provider.capabilities.writeMode === "exact" && body.provider.capabilities.forget === true;
4418
+ const ready = (body) => catalog.providers?.find((provider) => provider.id === body.provider.id)?.serviceConfigured !== false;
4419
+ const writable = (body) => body.active && body.providerEnabled !== false && body.provider.capabilities.remember === true && ready(body) && (allowed === void 0 || allowed.has(body.id));
4420
+ const exact = (body) => body.provider.capabilities.writeMode === "exact" && body.provider.capabilities.forget === true;
4421
+ const eligible = (body) => writable(body) && exact(body);
4311
4422
  const eligibleBodies = catalog.items.filter(eligible);
4312
4423
  if (eligibleBodies.length === 0) {
4313
4424
  const writableBodies = catalog.items.filter(writable);
4314
4425
  const unsupported = writableBodies.map((body) => `${body.id} (provider=${body.provider.id}, writeMode=${body.provider.capabilities.writeMode}, forget=${body.provider.capabilities.forget})`).join(", ");
4315
4426
  const reason = catalog.items.length === 0 ? "Memory Space body-directory is empty" : allowed?.size === 0 ? "current View write scope is empty" : allowed !== void 0 && catalog.items.some((body) => body.active && body.providerEnabled !== false && body.provider.capabilities.remember && !allowed?.has(body.id)) && writableBodies.length === 0 ? "current View write scope excludes the active writable Memory Spaces" : writableBodies.length === 0 ? "no active writable Memory Space in the authorized scope" : "authorized destinations lack exact writes or safe forget";
4427
+ if (!catalog.items.some((body) => archivableSpace(catalog, body)) && archive.elsewhere !== true) return this.archiveRuntimeLocally(context, request, plan, `${reason}${unsupported === "" ? "" : ` (${unsupported})`}`, signal);
4316
4428
  const scope = archive.writeScope === void 0 ? "management" : `${archive.writeScope.grant.sourceInstanceKey}/${catalog.writeScope === void 0 ? "pinned-read" : "source-write"}`;
4317
4429
  throw new Error(`runtime memory archival requires an existing active writable Memory Space with exact writes and safe forget; ${reason}; scope=${scope}, catalog=${catalog.items.length}, authorized=${allowed?.size ?? "management"}, writable=${writableBodies.length}; pending write was not committed and existing runtime entries are unchanged; retry the same request in a new turn after activating a supported authorized Memory Space, or increase runtimeMemory.memoryLimitBytes${unsupported === "" ? "" : `; unsupported destinations: ${unsupported}`}`);
4318
4430
  }
@@ -4563,12 +4675,18 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
4563
4675
  const committed = destinationFromCommittedMutation(result, memoryBodyId, entry.content);
4564
4676
  if (committed !== void 0) return committed;
4565
4677
  if (!mutationStates(result).includes("skipped")) throw new Error(`runtime archive write did not commit synchronously for Memory Space ${memoryBodyId}`);
4566
- const exact = (await service.read("search", {
4678
+ const named = skippedExactDestination(result, memoryBodyId, entry.content);
4679
+ if (named !== void 0) return named;
4680
+ const recalled = await service.read("search", {
4567
4681
  query: entry.content.slice(0, 500),
4568
4682
  limit: 20,
4569
4683
  memoryBodyIds: [memoryBodyId]
4570
- }, signal)).results.find((candidate) => candidate.memoryBodyId === memoryBodyId && candidate.content.trim() === entry.content);
4571
- if (exact === void 0) throw new Error(`runtime archive skipped an entry without exact durable recall evidence in Memory Space ${memoryBodyId}`);
4684
+ }, signal);
4685
+ const exact = recalled.results.find((candidate) => candidate.memoryBodyId === memoryBodyId && candidate.content.trim() === entry.content);
4686
+ if (exact === void 0) {
4687
+ const unavailable = recalled.sources?.find((source) => source.status === "unavailable");
4688
+ throw new Error(`runtime archive skipped an entry without exact durable recall evidence in Memory Space ${memoryBodyId}: ${unavailable === void 0 ? `its search returned ${recalled.results.length} results, none with that exact text` : `its search was unavailable (${safeFailureDetail(unavailable.hint ?? "no detail")})`}`);
4689
+ }
4572
4690
  return {
4573
4691
  layerId: "memory-spaces",
4574
4692
  reference: `memory-space:${encodeURIComponent(memoryBodyId)}/item:${encodeURIComponent(exact.id)}`,
@@ -4832,6 +4950,68 @@ This is the only completion channel for this run. Do not finish with a plain-tex
4832
4950
  await source.assertActionOffered(actionId, (offer) => authority.graph.config.writeEnabled && offer.authority === void 0);
4833
4951
  return source;
4834
4952
  }
4953
+ /**
4954
+ * The Memory Spaces Source a Document archive indexes into, or undefined when
4955
+ * that layer is off, not installed, takes no automatic writes, or has no space
4956
+ * anywhere that could take the index (#336). A space this View does not offer
4957
+ * keeps the refusal, as before.
4958
+ */
4959
+ async documentIndexSpaces(parent, generation, signal) {
4960
+ const graph = this.runtimeSource.forAgent(parent);
4961
+ if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
4962
+ if (!allowsParticipation(graph.config, "memory-spaces", "write", "automatic")) return void 0;
4963
+ const installed = generation.sourceInstances().filter((source) => source.sourceTypeId === "memory-spaces");
4964
+ if (installed.length === 0) return void 0;
4965
+ const authority = this.turnAuthority(parent, false);
4966
+ if (authority !== void 0 && !authority.context.view.actionOffers.some((offer) => offer.sourceActionId === "remember" && offer.authority === void 0 && installed.some((source) => source.sourceInstanceKey === offer.sourceInstanceKey)) && await this.noArchivableMemorySpace(graph, agentScope(parent, graph.config), generation, signal)) return void 0;
4967
+ return this.writableSourceFor(parent, "memory-spaces", "remember");
4968
+ }
4969
+ /** Whether a Memory Spaces Source other than the one in use lists a space that could take the archive. */
4970
+ async archivableElsewhere(graph, scope, generation, sourceInstanceKey, signal) {
4971
+ if (!generation.sourceInstances().some((source) => source.sourceTypeId === "memory-spaces" && source.sourceInstanceKey !== sourceInstanceKey)) return false;
4972
+ return !await this.noArchivableMemorySpace(graph, scope, generation, signal, sourceInstanceKey);
4973
+ }
4974
+ /**
4975
+ * True only when no installed Memory Spaces Source lists a space that could take
4976
+ * an archive (#336). A directory that cannot be read counts as one that could:
4977
+ * the caller then refuses as before, and nothing leaves working memory.
4978
+ */
4979
+ async noArchivableMemorySpace(graph, scope, generation, signal, except) {
4980
+ for (const instance of generation.sourceInstances().filter((source) => source.sourceTypeId === "memory-spaces" && source.sourceInstanceKey !== except)) try {
4981
+ const directory = await graph.source("memory-spaces", scope).forInstance(instance.sourceInstanceKey).forGeneration(generation).read("body-directory", null, signal);
4982
+ if (directory.items.some((body) => archivableSpace(directory, body))) return false;
4983
+ } catch {
4984
+ signal?.throwIfAborted();
4985
+ return false;
4986
+ }
4987
+ return true;
4988
+ }
4989
+ /** A Project Document archived without a Mnemon index keeps its original under .mnemon/documents/archived. */
4990
+ async archiveDocumentLocally(controller, document, reason, signal) {
4991
+ signal.throwIfAborted();
4992
+ const summary = `Archived locally without a Mnemon index, since ${reason}.`;
4993
+ const archived = await controller.mutate("archive", {
4994
+ id: document.id,
4995
+ documentRevision: document.revision,
4996
+ summary,
4997
+ memoryBodyIds: []
4998
+ }, signal);
4999
+ const runId = `host-${randomUUID()}`;
5000
+ this.counters.documentArchives += 1;
5001
+ this.counters.lastRunId = runId;
5002
+ this.counters.lastOperation = "document-archive";
5003
+ this.counters.lastAt = (/* @__PURE__ */ new Date()).toISOString();
5004
+ return {
5005
+ ...archived,
5006
+ maintenance: {
5007
+ runId,
5008
+ provider: "host",
5009
+ summary,
5010
+ memoryBodyIds: [],
5011
+ archivedDocumentIds: [document.id]
5012
+ }
5013
+ };
5014
+ }
4835
5015
  async assertAutomaticMemoryWrite(parent) {
4836
5016
  const graph = this.runtimeSource.forAgent(parent);
4837
5017
  if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
@@ -5789,7 +5969,7 @@ var MnemonLifecycle = class {
5789
5969
  };
5790
5970
  /** Creation ids reserved before DSH publishes clean task-root Agents. */
5791
5971
  taskAgentIds = /* @__PURE__ */ new Set();
5792
- /** Bounded process-local replay fence for finalized-message write actions. */
5972
+ /** Bounded process-local replay fence for finalized-message write actions: the latest text per message. */
5793
5973
  supervisedWritebacks = /* @__PURE__ */ new Map();
5794
5974
  constructor(ctx, coordinator, config, runtimeSource) {
5795
5975
  this.ctx = ctx;
@@ -6055,15 +6235,13 @@ var MnemonLifecycle = class {
6055
6235
  if (normalizedKey === void 0 || normalizedKey === "") return execute();
6056
6236
  const replayKey = `${replayScope}\u0000${normalizedKey}`;
6057
6237
  const existing = this.supervisedWritebacks.get(replayKey);
6058
- if (existing !== void 0) {
6059
- if (existing.content !== normalizedContent) throw new Error("idempotency key was already used for different content");
6060
- return existing.result;
6061
- }
6062
- if (this.supervisedWritebacks.size >= 256) {
6238
+ if (existing?.content === normalizedContent) return existing.result;
6239
+ if (existing === void 0 && this.supervisedWritebacks.size >= 256) {
6063
6240
  const oldest = this.supervisedWritebacks.keys().next().value;
6064
6241
  if (oldest !== void 0) this.supervisedWritebacks.delete(oldest);
6065
6242
  }
6066
6243
  const result = execute();
6244
+ this.supervisedWritebacks.delete(replayKey);
6067
6245
  this.supervisedWritebacks.set(replayKey, {
6068
6246
  content: normalizedContent,
6069
6247
  result
@@ -7212,7 +7390,6 @@ async function assisted(runtime, lifecycle, typeId, operation, input, signal, ta
7212
7390
  if (typeId === "documents") {
7213
7391
  if (operation === "archive") {
7214
7392
  requireCapability(runtime, typeId, "archive");
7215
- requireCapability(runtime, "memory-spaces", "write");
7216
7393
  return lifecycle.archiveDocument(sessionId, String(input.id ?? ""), workspaceRoot, signal);
7217
7394
  }
7218
7395
  if (operation === "mutate") {
@@ -8147,7 +8324,6 @@ function registerTools(ctx, runtimeSource, coordinator) {
8147
8324
  const agent = requireAgent(exec);
8148
8325
  if (args.action === "archive") {
8149
8326
  requireSource(exec, "documents", "archive");
8150
- requireSource(exec, "memory-spaces", "write");
8151
8327
  if (isSubagent(agent)) throw new Error("idle document workers cannot cold-archive directly");
8152
8328
  if (args.id === void 0) throw new Error("document id is required for archive");
8153
8329
  return coordinator.archiveDocument(agent, args.id, exec.signal);
@@ -10,15 +10,15 @@ var dependencies = {
10
10
  "dsh-mnemon-provider-holographic": "0.5.6",
11
11
  "dsh-mnemon-provider-honcho": "0.5.5",
12
12
  "dsh-mnemon-provider-mem0": "0.5.5",
13
- "dsh-mnemon-provider-mnemon-native": "0.5.8",
13
+ "dsh-mnemon-provider-mnemon-native": "0.5.9",
14
14
  "dsh-mnemon-provider-openviking": "0.5.7",
15
15
  "dsh-mnemon-provider-retaindb": "0.5.5",
16
16
  "dsh-mnemon-provider-supermemory": "0.5.5",
17
- "dsh-mnemon-source-documents": "0.5.9",
18
- "dsh-mnemon-source-memory-spaces": "0.5.15",
19
- "dsh-mnemon-source-runtime": "0.5.12",
17
+ "dsh-mnemon-source-documents": "0.5.10",
18
+ "dsh-mnemon-source-memory-spaces": "0.5.17",
19
+ "dsh-mnemon-source-runtime": "0.5.13",
20
20
  "dsh-mnemon-strategy-auto-capture": "0.5.5",
21
- "dsh-mnemon-strategy-default-three-tier": "0.5.7",
21
+ "dsh-mnemon-strategy-default-three-tier": "0.5.8",
22
22
  "dsh-mnemon-strategy-general": "0.5.1",
23
23
  "dsh-mnemon-strategy-light-context": "0.5.5",
24
24
  "dsh-mnemon-strategy-scoped": "0.5.5",
package/lib/starter.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as prepareStarterResolution } from "./starter-resolution-B0Wj3X3R.js";
1
+ import { t as prepareStarterResolution } from "./starter-resolution-UcTGyjgm.js";
2
2
  //#region src/starter.ts
3
3
  const name = "dsh-mnemon-starter";
4
4
  const provide = ["mnemonStarterReady"];
@@ -102,6 +102,7 @@ export declare const zh: {
102
102
  'receipt.unfinished': string;
103
103
  'receipt.failed': string;
104
104
  'receipt.view': string;
105
+ 'receipt.viewRuntime': string;
105
106
  'taskAgent.ready': string;
106
107
  'taskAgent.unavailable': string;
107
108
  'saveAction.button': string;
@@ -116,6 +117,31 @@ export declare const zh: {
116
117
  'saveAction.submitting': string;
117
118
  'saveAction.readOnly': string;
118
119
  'saveAction.close': string;
120
+ 'saveAction.hintDirect': string;
121
+ 'saveAction.to': string;
122
+ 'saveAction.to.agent': string;
123
+ 'saveAction.to.agentDetail': string;
124
+ 'saveAction.to.memory': string;
125
+ 'saveAction.to.user': string;
126
+ 'saveAction.to.space': string;
127
+ 'saveAction.save': string;
128
+ 'saveAction.saving': string;
129
+ 'saveAction.saved.runtime': string;
130
+ 'saveAction.saved.runtimeMaintained': string;
131
+ 'saveAction.saved.space': string;
132
+ 'saveAction.saved.spacePending': string;
133
+ 'saveAction.saved.spaceOther': string;
134
+ 'saveAction.saved.duplicate': string;
135
+ 'saveAction.unavailable': string;
136
+ 'saveAction.place.delimiter': string;
137
+ 'saveAction.place.tooLong': string;
138
+ 'saveAction.place.orAgent': string;
139
+ 'saveAction.place.full': string;
140
+ 'saveAction.place.fullAll': string;
141
+ 'saveAction.place.userCondense': string;
142
+ 'saveAction.place.userTooLong': string;
143
+ 'saveAction.place.userFull': string;
144
+ 'saveAction.noPlace': string;
119
145
  'readSources.all': string;
120
146
  'readSources.mode.search': string;
121
147
  'readSources.mode.graph': string;
@@ -887,6 +913,18 @@ export declare const zh: {
887
913
  "entities.selectText": string;
888
914
  "entities.emptyTitle": string;
889
915
  "entities.emptyText": string;
916
+ "entities.railLoading": string;
917
+ "entities.memoriesHint": string;
918
+ "entities.relatedTitle": string;
919
+ "entities.relatedHint": string;
920
+ "entities.findRelated": string;
921
+ "entities.hideRelated": string;
922
+ "entities.noCarrying": string;
923
+ "entities.noRelated": string;
924
+ "entities.retry": string;
925
+ "entities.sourceReady": string;
926
+ "entities.sourcePartial": string;
927
+ "readSources.status.loading": string;
890
928
  "remember.title": string;
891
929
  "remember.description": string;
892
930
  "remember.readOnlyTitle": string;
@@ -968,6 +1006,7 @@ export declare const zh: {
968
1006
  "documents.updated": string;
969
1007
  "documents.updatedAfterArchive": string;
970
1008
  "documents.archived": string;
1009
+ "documents.archivedLocally": string;
971
1010
  "documents.list": string;
972
1011
  "documents.activeList": string;
973
1012
  "documents.archiveList": string;
@@ -987,6 +1026,7 @@ export declare const zh: {
987
1026
  "documents.hash": string;
988
1027
  "documents.size": string;
989
1028
  "documents.archiveReceipt": string;
1029
+ "documents.archiveNote": string;
990
1030
  "documents.archiveTitle": string;
991
1031
  "documents.archiveDescription": string;
992
1032
  "documents.archive": string;
@@ -1036,6 +1076,7 @@ export declare const zh: {
1036
1076
  "runtime.result.remove": string;
1037
1077
  "runtime.result.maintenance": string;
1038
1078
  "runtime.result.localCompaction": string;
1079
+ "runtime.result.localArchive": string;
1039
1080
  "runtime.readOnly": string;
1040
1081
  "runtime.footnote": string;
1041
1082
  };
@@ -48,6 +48,7 @@ export declare function WriteReceipt(props: {
48
48
  error?: string | undefined;
49
49
  t?: MnemonTranslate | undefined;
50
50
  onView?: (() => void) | undefined;
51
+ viewLabel?: string | undefined;
51
52
  }): JSX.Element;
52
53
  /** Whether a task Agent can take a write now. */
53
54
  export declare function TaskAgentTag(props: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mnemon",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "description": "Composable, view-based memory for DeepSeek Harness. Pluggable sources and strategies, with layered memory out of the box.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -157,15 +157,15 @@
157
157
  "dsh-mnemon-provider-holographic": "0.5.6",
158
158
  "dsh-mnemon-provider-honcho": "0.5.5",
159
159
  "dsh-mnemon-provider-mem0": "0.5.5",
160
- "dsh-mnemon-provider-mnemon-native": "0.5.8",
160
+ "dsh-mnemon-provider-mnemon-native": "0.5.9",
161
161
  "dsh-mnemon-provider-openviking": "0.5.7",
162
162
  "dsh-mnemon-provider-retaindb": "0.5.5",
163
163
  "dsh-mnemon-provider-supermemory": "0.5.5",
164
- "dsh-mnemon-source-documents": "0.5.9",
165
- "dsh-mnemon-source-memory-spaces": "0.5.15",
166
- "dsh-mnemon-source-runtime": "0.5.12",
164
+ "dsh-mnemon-source-documents": "0.5.10",
165
+ "dsh-mnemon-source-memory-spaces": "0.5.17",
166
+ "dsh-mnemon-source-runtime": "0.5.13",
167
167
  "dsh-mnemon-strategy-auto-capture": "0.5.5",
168
- "dsh-mnemon-strategy-default-three-tier": "0.5.7",
168
+ "dsh-mnemon-strategy-default-three-tier": "0.5.8",
169
169
  "dsh-mnemon-strategy-general": "0.5.1",
170
170
  "dsh-mnemon-strategy-light-context": "0.5.5",
171
171
  "dsh-mnemon-strategy-scoped": "0.5.5",