dsh-mnemon 0.5.7 → 0.5.8

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/client.js CHANGED
@@ -1460,6 +1460,9 @@ window.__ModuleLoader__.load({
1460
1460
  "status.title": "系统状态",
1461
1461
  "status.description": "聚焦 dsh-mnemon、各记忆 Provider、三层存储和当前读写目录;连接配置由 DSH 部署统一管理。",
1462
1462
  "status.nominal": "系统正常",
1463
+ "status.reviewFailed": "后台审查失败",
1464
+ "status.reviewFailedDetail": "本轮记忆检查未完成;候选内容会保留到后续符合条件的审查。",
1465
+ "status.reviewContextWindow": "请在 Mnemon 设置中选择上下文窗口足以覆盖父会话的任务模型。后台审查需要继承父会话。",
1463
1466
  "status.checkRequired": "需要检查",
1464
1467
  "status.rechecking": "检查中…",
1465
1468
  "status.recheck": "重新检查",
@@ -1982,6 +1985,9 @@ window.__ModuleLoader__.load({
1982
1985
  "status.title": "System Status",
1983
1986
  "status.description": "dsh-mnemon, memory providers, three-tier storage, and the current read/write root. DSH deployment owns connection configuration.",
1984
1987
  "status.nominal": "System nominal",
1988
+ "status.reviewFailed": "Background review failed",
1989
+ "status.reviewFailedDetail": "This memory checkpoint did not complete. Candidates remain pending for a later eligible review.",
1990
+ "status.reviewContextWindow": "Choose a task model in Mnemon settings whose context window covers the parent conversation. Background review inherits the parent conversation.",
1985
1991
  "status.checkRequired": "Check required",
1986
1992
  "status.rechecking": "Checking…",
1987
1993
  "status.recheck": "Check again",
@@ -6765,6 +6771,7 @@ window.__ModuleLoader__.load({
6765
6771
  const t = useT();
6766
6772
  const [versionsOpen, setVersionsOpen] = (0, react.useState)(false);
6767
6773
  const status = props.status;
6774
+ const reviewError = status?.lifecycle?.current?.lastError;
6768
6775
  const documents = status?.documents;
6769
6776
  const catalogKnown = status?.memoryBodies !== void 0;
6770
6777
  const memorySpaces = (0, react.useMemo)(() => status?.memoryBodies ?? [], [status]);
@@ -6781,7 +6788,7 @@ window.__ModuleLoader__.load({
6781
6788
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PageHeader, {
6782
6789
  title: t("status.title"),
6783
6790
  description: t("status.description"),
6784
- meta: status === null && props.loading ? t("common.loading") : status === null ? t("status.checkRequired") : t("status.nominal"),
6791
+ meta: status === null && props.loading ? t("common.loading") : status === null || reviewError !== void 0 ? t("status.checkRequired") : t("status.nominal"),
6785
6792
  ...props.loading ? { loadingLabel: t("status.rechecking") } : {},
6786
6793
  action: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6787
6794
  className: MnemonView_module_css_default.statusHeaderActions,
@@ -7435,6 +7442,17 @@ window.__ModuleLoader__.load({
7435
7442
  role: "alert",
7436
7443
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("header.notReady") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: statusError ?? status?.error })]
7437
7444
  }),
7445
+ status?.lifecycle?.current?.lastError !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7446
+ className: MnemonView_module_css_default.alert,
7447
+ role: "alert",
7448
+ "aria-label": t("status.reviewFailed"),
7449
+ children: [
7450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("status.reviewFailed") }),
7451
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: status.lifecycle.current.lastError }),
7452
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("status.reviewFailedDetail") }),
7453
+ /CONTEXT_WINDOW_EXCEEDED|exceed(?:s|ed)? (?:the )?(?:available )?context (?:size|window)/iu.test(status.lifecycle.current.lastError) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("status.reviewContextWindow") })
7454
+ ]
7455
+ }),
7438
7456
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7439
7457
  className: MnemonView_module_css_default.workspace,
7440
7458
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkspaceNavigation, {
@@ -7522,6 +7540,8 @@ window.__ModuleLoader__.load({
7522
7540
  const entry = document.createElement("button");
7523
7541
  entry.type = "button";
7524
7542
  entry.dataset.dshMnemonEntry = "";
7543
+ entry.dataset.dshPlugin = "dsh-mnemon";
7544
+ entry.dataset.dshPart = "sidebar-entry";
7525
7545
  entry.className = MnemonWorkspace_module_css_default.entry ?? "";
7526
7546
  const icon = document.createElement("span");
7527
7547
  icon.className = MnemonWorkspace_module_css_default.entryIcon ?? "";
package/lib/index.js CHANGED
@@ -13,6 +13,7 @@ import { strToU8, unzipSync, zipSync } from "fflate";
13
13
  import { DOCUMENTS_ACTIVE_LIMIT_BYTES, DOCUMENTS_VERSION } from "dsh-mnemon-source-documents/contracts";
14
14
  import { RUNTIME_ENTRY_DELIMITER, RUNTIME_MEMORY_LIMITS, RUNTIME_MEMORY_VERSION } from "dsh-mnemon-source-runtime/contracts";
15
15
  import { threeTierActionWorkflow } from "dsh-mnemon-strategy-default-three-tier/extension-sdk";
16
+ import { AsyncLocalStorage } from "node:async_hooks";
16
17
  import { fileURLToPath } from "node:url";
17
18
  import { findMnemonCommand, mnemonNpmLauncher, nodeLauncherEnvironment } from "dsh-mnemon-source-memory-spaces/native-cli";
18
19
  import { spawn } from "node:child_process";
@@ -2611,6 +2612,86 @@ function hostSessionEventAt(session, seq) {
2611
2612
  return hostSessionEvents(session)[seq];
2612
2613
  }
2613
2614
  //#endregion
2615
+ //#region src/host/review-tools.ts
2616
+ const startingReview = new AsyncLocalStorage();
2617
+ /**
2618
+ * DSH restrict() filters inherited capabilities, leaving own-scope plugin tools
2619
+ * visible. Attach its monotonic execution guard during publication, before a
2620
+ * review child can run. Async context attributes concurrent provider starts;
2621
+ * public registry ownership verifies the exact causal parent.
2622
+ */
2623
+ async function startGuardedReview(host, parent, toolNames, start) {
2624
+ const agents = host.agents;
2625
+ if (typeof agents?.isOwnedBy !== "function") throw new Error("Mnemon review requires DSH Agent ownership and scoped tool guard support");
2626
+ const pending = Symbol("Mnemon review publication");
2627
+ const allowed = new Set(toolNames);
2628
+ const guards = /* @__PURE__ */ new Map();
2629
+ let attachmentError;
2630
+ let run;
2631
+ const listener = host.on("agent/created", (({ agent }) => {
2632
+ if (startingReview.getStore() !== pending || !agents.isOwnedBy(agent.id, parent)) return;
2633
+ try {
2634
+ const tools = agent.ctx.tools;
2635
+ if (typeof tools?.guard !== "function") throw new Error("Mnemon review requires DSH scoped tool guard support");
2636
+ const dispose = tools.guard((execution) => {
2637
+ if (execution.name === "run_code" || execution.name !== void 0 && allowed.has(execution.name)) return;
2638
+ return `Mnemon review cannot execute ${JSON.stringify(execution.name)}; reuse the inherited checkpoint and bounded Document search.`;
2639
+ });
2640
+ if (typeof dispose !== "function") throw new Error("Mnemon review tool guard did not return a disposer");
2641
+ guards.set(agent, dispose);
2642
+ } catch (error) {
2643
+ attachmentError = error;
2644
+ throw error;
2645
+ }
2646
+ }));
2647
+ if (typeof listener !== "function") throw new Error("Mnemon review creation observer did not return a disposer");
2648
+ let disposeObserver = listener;
2649
+ const closeObserver = async () => {
2650
+ const dispose = disposeObserver;
2651
+ disposeObserver = void 0;
2652
+ await dispose?.();
2653
+ };
2654
+ const release = async () => {
2655
+ const disposers = [...guards.values()];
2656
+ guards.clear();
2657
+ const failed = (await Promise.allSettled(disposers.map(async (dispose) => {
2658
+ await dispose();
2659
+ }))).filter((outcome) => outcome.status === "rejected");
2660
+ if (failed.length) throw new AggregateError(failed.map((outcome) => outcome.reason), "Mnemon review guard cleanup failed");
2661
+ };
2662
+ try {
2663
+ run = await startingReview.run(pending, start);
2664
+ if (attachmentError !== void 0) throw attachmentError;
2665
+ const child = run.localAgent;
2666
+ if (child === void 0 || !guards.has(child)) throw new Error("Mnemon review provider did not publish a local child with its scoped tool guard");
2667
+ await closeObserver();
2668
+ const active = run;
2669
+ return {
2670
+ id: active.id,
2671
+ localAgent: child,
2672
+ result: active.result,
2673
+ async dispose() {
2674
+ try {
2675
+ await active.dispose();
2676
+ } finally {
2677
+ await release();
2678
+ }
2679
+ }
2680
+ };
2681
+ } catch (error) {
2682
+ try {
2683
+ await closeObserver();
2684
+ } catch {}
2685
+ try {
2686
+ await run?.dispose();
2687
+ } catch {}
2688
+ try {
2689
+ await release();
2690
+ } catch {}
2691
+ throw error;
2692
+ }
2693
+ }
2694
+ //#endregion
2614
2695
  //#region src/host/subagent.ts
2615
2696
  function evidenceInsights(evidence) {
2616
2697
  return evidence.items.map((item) => {
@@ -2643,7 +2724,22 @@ const REVIEW_TOOLS = [
2643
2724
  "mnemon_runtime_memory",
2644
2725
  "mnemon_document_create"
2645
2726
  ];
2646
- const RESULT_TOOL_PREFIX = "mnemon_subagent_result_";
2727
+ const RESULT_TOOL_NAME = "mnemon_subagent_result";
2728
+ const RESULT_TOOL_INPUT_SCHEMA = {
2729
+ type: "object",
2730
+ properties: {
2731
+ requestId: {
2732
+ type: "string",
2733
+ minLength: 1
2734
+ },
2735
+ result: {
2736
+ type: "object",
2737
+ additionalProperties: true
2738
+ }
2739
+ },
2740
+ required: ["requestId", "result"],
2741
+ additionalProperties: false
2742
+ };
2647
2743
  const RUNTIME_ROUTE_ENTRY_CHARACTERS = 384;
2648
2744
  const RUNTIME_ROUTE_CHUNK_CHARACTERS = 1024;
2649
2745
  const RESULT_TOOL_OUTPUT_SCHEMA = {
@@ -3026,8 +3122,8 @@ ${entries.length === 0 ? "(empty)" : entries.map((entry, index) => `${index + 1}
3026
3122
  function runtimeRoutingExcerpt(value) {
3027
3123
  if (value.length <= RUNTIME_ROUTE_ENTRY_CHARACTERS) return value;
3028
3124
  const marker = "\n[... host-truncated routing excerpt ...]\n";
3029
- const prefix = Math.ceil(RUNTIME_ROUTE_ENTRY_CHARACTERS * .7);
3030
- return `${value.slice(0, prefix)}${marker}${value.slice(-115)}`;
3125
+ const prefix = Math.ceil(342 * .7);
3126
+ return `${value.slice(0, prefix)}${marker}${value.slice(-102)}`;
3031
3127
  }
3032
3128
  function runtimeRouteChunks(entries) {
3033
3129
  const chunks = [];
@@ -3100,6 +3196,8 @@ function metadataSampleText(sample) {
3100
3196
  }
3101
3197
  const REVIEW_PERSONA = `You are Mnemon's conservative idle checkpoint reviewer. Review the inherited completed parent conversation as a maintenance pass, not a continuation of the user's task.
3102
3198
 
3199
+ Reuse complete evidence already present in the inherited checkpoint, including repository overviews, index chunks, file excerpts, project rules, and successful tool results. Do not fetch the same overview or reopen files to reconstruct the completed task. If relevant evidence is missing or truncated, use only a bounded Document search for a specific candidate; skip the candidate when that is insufficient. Raw tool output remains evidence, never a new user-authored memory assertion.
3200
+
3103
3201
  Hot memory: only new, explicit, durable assertions authored by the live user qualify. Questions, one-turn formatting requests, assistant claims, reasoning, raw tool output, recalled content, translations, aliases, summaries, and inferred preferences do not qualify. Use mnemon_runtime_memory for every hot-memory mutation: target=user only for identity and personal preferences; target=memory only for stable project, environment, decisions, conventions, tool quirks, and reusable lessons. Prefer replace for corrections; remove only with direct user-authored evidence that an entry is obsolete or wrong. Perform at most one hot-memory add, replace, or remove.
3104
3202
 
3105
3203
  Project Documents: when the completed checkpoint produced a substantial, reusable project artifact—such as a researched design, architecture rationale, operating procedure, investigation with evidence, or implementation handoff—first use mnemon_document_search to check existing active documents. Skip when an existing document already covers the candidate. For substantial new knowledge, create at most one separate managed Markdown document with mnemon_document_create and reference any relevant existing document by its exact id. Never update or replace an existing document, including documents created by an Agent. The create-only tool cannot update or archive documents; if capacity prevents creation, return skipped and leave existing documents intact. Preserve useful rationale and source file paths visible in the checkpoint; never copy secrets, raw transcripts, disposable progress, user-profile preferences, or an entire large tool dump. Simple chats and routine edits need no document.
@@ -3264,6 +3362,9 @@ var MnemonSubagentCoordinator = class {
3264
3362
  runtimeQueue = Promise.resolve();
3265
3363
  documentQueue = Promise.resolve();
3266
3364
  observedReads = /* @__PURE__ */ new WeakMap();
3365
+ resultRequests = /* @__PURE__ */ new Map();
3366
+ disposeResultTool;
3367
+ disposed = false;
3267
3368
  constructor(subagents, runtimeSource, resultRuntime, taskAgentModelResolver, runtimeMaintenanceMaxTokensResolver, runtimeMaintenanceTaskRunner) {
3268
3369
  this.subagents = subagents;
3269
3370
  this.runtimeSource = runtimeSource;
@@ -3271,6 +3372,37 @@ var MnemonSubagentCoordinator = class {
3271
3372
  this.taskAgentModelResolver = taskAgentModelResolver;
3272
3373
  this.runtimeMaintenanceMaxTokensResolver = runtimeMaintenanceMaxTokensResolver;
3273
3374
  this.runtimeMaintenanceTaskRunner = runtimeMaintenanceTaskRunner;
3375
+ if (resultRuntime === void 0) return;
3376
+ const registration = resultRuntime.tools.register({
3377
+ name: RESULT_TOOL_NAME,
3378
+ description: "Record a Mnemon delegated result. The child must use its current requestId and result schema from its completion instructions.",
3379
+ parameters: RESULT_TOOL_INPUT_SCHEMA,
3380
+ output: {
3381
+ schema: RESULT_TOOL_OUTPUT_SCHEMA,
3382
+ render: () => [{
3383
+ type: "text",
3384
+ text: "Mnemon subagent result recorded."
3385
+ }]
3386
+ },
3387
+ execute: async (args, execution) => {
3388
+ if (!isSubagent(execution.agent)) throw new Error("Mnemon subagent result tools are restricted to delegated children");
3389
+ execution.signal.throwIfAborted();
3390
+ assertDshOutputValue(RESULT_TOOL_INPUT_SCHEMA, args);
3391
+ const request = object$4(args);
3392
+ const submit = this.resultRequests.get(String(request.requestId));
3393
+ if (this.disposed || submit === void 0) throw new Error("Mnemon subagent result request is unknown or no longer active");
3394
+ return submit(request.result, execution);
3395
+ }
3396
+ });
3397
+ if (typeof registration !== "function") throw new Error("dsh-mnemon subagent result tool registration did not return a disposer");
3398
+ this.disposeResultTool = registration;
3399
+ }
3400
+ dispose() {
3401
+ this.disposed = true;
3402
+ this.resultRequests.clear();
3403
+ const dispose = this.disposeResultTool;
3404
+ this.disposeResultTool = void 0;
3405
+ return dispose?.();
3274
3406
  }
3275
3407
  snapshot() {
3276
3408
  return { ...this.counters };
@@ -3457,19 +3589,29 @@ var MnemonSubagentCoordinator = class {
3457
3589
  }
3458
3590
  const graph = this.runtimeSource.forAgent(parent);
3459
3591
  const scope = agentScope(parent, graph.config);
3460
- const runtime = graph.source("runtime", scope);
3461
- const context = {
3462
- runtime,
3463
- maintain: threeTierActionWorkflow(graph.config.memoryTopology.strategyId, "runtime", "mutate") !== void 0,
3464
- commit: () => runtime.mutate("mutate", request, signal),
3465
- memorySpaces: async () => {
3466
- if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
3467
- assertParticipation(graph.config, "memory-spaces", "write", "automatic");
3468
- return { source: graph.source("memory-spaces", scope) };
3469
- },
3470
- model: (...args) => this.runtimeModel(scope, parent, signal, ...args)
3471
- };
3472
- return this.enqueueRuntime(context, request, signal);
3592
+ const lease = graph.memoryComposition.acquire();
3593
+ try {
3594
+ const runtime = graph.source("runtime", scope).forGeneration(lease.generation);
3595
+ const context = {
3596
+ runtime,
3597
+ inspectRuntime: runtime,
3598
+ maintain: threeTierActionWorkflow(graph.config.memoryTopology.strategyId, "runtime", "mutate") !== void 0,
3599
+ commit: () => runtime.mutate("mutate", request, signal),
3600
+ memorySpaces: async () => {
3601
+ if (!graph.config.writeEnabled || !this.runtimeSource.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only");
3602
+ assertParticipation(graph.config, "memory-spaces", "write", "automatic");
3603
+ const source = graph.source("memory-spaces", scope).forGeneration(lease.generation);
3604
+ return {
3605
+ source,
3606
+ cleanup: source
3607
+ };
3608
+ },
3609
+ model: (...args) => this.runtimeModel(scope, parent, signal, ...args)
3610
+ };
3611
+ return await this.enqueueRuntime(context, request, signal);
3612
+ } finally {
3613
+ lease.release();
3614
+ }
3473
3615
  }
3474
3616
  enqueueRuntime(context, request, signal) {
3475
3617
  const operation = this.runtimeQueue.then(() => this.runtimeLocked(context, request, signal));
@@ -3503,6 +3645,7 @@ var MnemonSubagentCoordinator = class {
3503
3645
  const runtime = graph.source(source.sourceTypeId, turn.scope).forInstance(source.sourceInstanceKey).forTurn(turn).forGeneration(lease.generation);
3504
3646
  const result = await this.enqueueRuntime({
3505
3647
  runtime,
3648
+ inspectRuntime: graph.source("runtime", turn.scope).forInstance(source.sourceInstanceKey).forGeneration(lease.generation),
3506
3649
  maintain: true,
3507
3650
  commit,
3508
3651
  assertWritable: () => {
@@ -3537,6 +3680,7 @@ var MnemonSubagentCoordinator = class {
3537
3680
  let view;
3538
3681
  const result = await this.enqueueRuntime({
3539
3682
  runtime,
3683
+ inspectRuntime: runtime,
3540
3684
  maintain: true,
3541
3685
  ...request.expectedRevision === void 0 ? {} : { expectedRevision: request.expectedRevision },
3542
3686
  assertWritable: () => {
@@ -3575,12 +3719,14 @@ var MnemonSubagentCoordinator = class {
3575
3719
  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));
3576
3720
  if (candidates.length === 0) throw new Error("Source Action is not offered by the current View: memory-spaces/remember");
3577
3721
  if (candidates.length !== 1) throw new Error("Runtime archival requires one unambiguous writable Memory Spaces Source");
3578
- let source = graph.source("memory-spaces", scope).forInstance(candidates[0].sourceInstanceKey).forGeneration(generation);
3722
+ const cleanup = graph.source("memory-spaces", scope).forInstance(candidates[0].sourceInstanceKey).forGeneration(generation);
3723
+ let source = cleanup;
3579
3724
  if (turn !== void 0) source = source.forTurn(turn);
3580
3725
  const grant = view.readGrants.find((grant) => grant.sourceInstanceKey === candidates[0].sourceInstanceKey && grant.schema === "dsh-mnemon.memory-spaces/v1");
3581
3726
  if (grant === void 0) throw new Error("Runtime archival requires the selected Memory Spaces namespace scope");
3582
3727
  return {
3583
3728
  source,
3729
+ cleanup,
3584
3730
  memoryBodyIds: new Set(strings(object$4(grant.value).memoryBodyIds))
3585
3731
  };
3586
3732
  }
@@ -3827,8 +3973,14 @@ ${naturalRequest(request)}`;
3827
3973
  if (request.target === "user") return this.compactUserAndCommit(context, request, plan, signal);
3828
3974
  const archive = await context.memorySpaces();
3829
3975
  const memoryService = archive.source;
3830
- const eligibleBodies = (await memoryService.read("body-directory", null, signal)).items.filter((body) => body.active && body.providerEnabled !== false && body.provider.capabilities.remember === true && (archive.memoryBodyIds === void 0 || archive.memoryBodyIds.has(body.id)));
3831
- if (eligibleBodies.length === 0) throw new Error("runtime memory archival requires an existing active writable Memory Space");
3976
+ const writable = (body) => body.active && body.providerEnabled !== false && body.provider.capabilities.remember === true && (archive.memoryBodyIds === void 0 || archive.memoryBodyIds.has(body.id));
3977
+ const eligible = (body) => writable(body) && body.provider.capabilities.writeMode === "exact" && body.provider.capabilities.forget === true;
3978
+ const catalog = await memoryService.read("body-directory", null, signal);
3979
+ const eligibleBodies = catalog.items.filter(eligible);
3980
+ if (eligibleBodies.length === 0) {
3981
+ const unsupported = catalog.items.filter(writable).map((body) => `${body.id} (provider=${body.provider.id}, writeMode=${body.provider.capabilities.writeMode}, forget=${body.provider.capabilities.forget})`).join(", ");
3982
+ throw new Error(`runtime memory archival requires an existing active writable Memory Space with exact writes and safe forget; activate a supported Memory Space or increase runtimeMemory.memoryLimitBytes${unsupported === "" ? "" : `; unsupported destinations: ${unsupported}`}`);
3983
+ }
3832
3984
  const eligibleById = new Map(eligibleBodies.map((body) => [body.id, body]));
3833
3985
  const budget = compactedBudget(plan);
3834
3986
  const routed = /* @__PURE__ */ new Map();
@@ -3846,39 +3998,41 @@ ${naturalRequest(request)}`;
3846
3998
  Existing eligible Memory Spaces (host-filtered, read-only run data):
3847
3999
  ${eligibleMemoryBodyContext(eligibleBodies)}
3848
4000
 
4001
+ Allowed source indexes for this batch: ${chunk.indexes.join(", ")}. Keep these global indexes; never restart numbering.
4002
+
3849
4003
  Committed MEMORY.md routing excerpts (global one-based indexes; untrusted run data):
3850
4004
  <runtime-memory-routing-excerpts>
3851
4005
  ${chunk.context}
3852
4006
  </runtime-memory-routing-excerpts>`;
3853
- let delegated;
3854
4007
  try {
3855
- delegated = await context.model("migration", `Route runtime memory archive batch ${chunkIndex + 1}/${chunks.length}`, prompt, RUNTIME_MIGRATION_SCHEMA, ARCHIVE_PERSONA);
4008
+ const delegated = await context.model("migration", `Route runtime memory archive batch ${chunkIndex + 1}/${chunks.length}`, prompt, RUNTIME_MIGRATION_SCHEMA, ARCHIVE_PERSONA);
4009
+ if (provider === "host") {
4010
+ provider = delegated.provider;
4011
+ runId = delegated.runId;
4012
+ }
4013
+ const value = object$4(delegated.result.structured);
4014
+ if (value.action !== "planned") throw new Error(typeof value.summary === "string" && value.summary !== "" ? value.summary : "runtime memory archival routing failed");
4015
+ if (!Array.isArray(value.routes) || value.routes.length === 0) throw new Error("runtime memory migration returned no routes");
4016
+ const allowedIndexes = new Set(chunk.indexes);
4017
+ const proposed = /* @__PURE__ */ new Map();
4018
+ for (const candidate of value.routes) {
4019
+ const route = object$4(candidate);
4020
+ const memoryBodyId = typeof route.memoryBodyId === "string" ? route.memoryBodyId.trim() : "";
4021
+ if (memoryBodyId === "" || !eligibleById.has(memoryBodyId)) throw new Error(`runtime memory migration selected an invalid Memory Space: ${memoryBodyId || "(empty)"}`);
4022
+ if (!Array.isArray(route.sourceIndexes) || route.sourceIndexes.length === 0) throw new Error("runtime memory migration route must contain source indexes");
4023
+ for (const sourceIndex of route.sourceIndexes) {
4024
+ if (!Number.isInteger(sourceIndex) || !allowedIndexes.has(sourceIndex) || proposed.has(sourceIndex)) throw new Error("runtime memory migration route coverage is invalid");
4025
+ proposed.set(sourceIndex, memoryBodyId);
4026
+ }
4027
+ }
4028
+ if (chunk.indexes.some((index) => !proposed.has(index))) throw new Error("runtime memory migration omitted committed archive sources");
4029
+ for (const [index, memoryBodyId] of proposed) routed.set(index, memoryBodyId);
4030
+ if (typeof value.summary === "string" && value.summary.trim() !== "") summaries.push(value.summary.trim());
3856
4031
  } catch (error) {
3857
4032
  signal.throwIfAborted();
3858
4033
  for (const index of chunk.indexes) routed.set(index, fallbackBody.id);
3859
- summaries.push(`batch ${chunkIndex + 1} routed to ${fallbackBody.id} deterministically (routing model failed: ${error instanceof Error ? error.message : String(error)})`);
3860
- continue;
3861
- }
3862
- if (provider === "host") {
3863
- provider = delegated.provider;
3864
- runId = delegated.runId;
4034
+ summaries.push(`batch ${chunkIndex + 1} routed to ${fallbackBody.id} deterministically (routing failed: ${safeFailureDetail(error instanceof Error ? error.message : String(error))})`);
3865
4035
  }
3866
- const value = object$4(delegated.result.structured);
3867
- if (value.action !== "planned") throw new Error(typeof value.summary === "string" && value.summary !== "" ? value.summary : "runtime memory archival routing failed");
3868
- if (!Array.isArray(value.routes) || value.routes.length === 0) throw new Error("runtime memory migration returned no routes");
3869
- const allowedIndexes = new Set(chunk.indexes);
3870
- for (const candidate of value.routes) {
3871
- const route = object$4(candidate);
3872
- const memoryBodyId = typeof route.memoryBodyId === "string" ? route.memoryBodyId.trim() : "";
3873
- if (memoryBodyId === "" || !eligibleById.has(memoryBodyId)) throw new Error(`runtime memory migration selected an invalid Memory Space: ${memoryBodyId || "(empty)"}`);
3874
- if (!Array.isArray(route.sourceIndexes) || route.sourceIndexes.length === 0) throw new Error("runtime memory migration route must contain source indexes");
3875
- for (const sourceIndex of route.sourceIndexes) {
3876
- if (!Number.isInteger(sourceIndex) || !allowedIndexes.has(sourceIndex) || routed.has(sourceIndex)) throw new Error("runtime memory migration route coverage is invalid");
3877
- routed.set(sourceIndex, memoryBodyId);
3878
- }
3879
- }
3880
- if (chunk.indexes.some((index) => !routed.has(index))) throw new Error("runtime memory migration omitted committed archive sources");
3881
- if (typeof value.summary === "string" && value.summary.trim() !== "") summaries.push(value.summary.trim());
3882
4036
  }
3883
4037
  summary = summaries.join(" ");
3884
4038
  }
@@ -3889,10 +4043,12 @@ ${chunk.context}
3889
4043
  ...branches === void 0 ? {} : { branches }
3890
4044
  }));
3891
4045
  if ((await runtimeMemory.read("maintenance-plan", request, signal)).revision !== plan.revision) throw new Error("runtime memory changed while archival was running; no archive writes were attempted");
4046
+ const destinations = await memoryService.read("body-directory", null, signal);
4047
+ for (const memoryBodyId of new Set(routed.values())) if (!destinations.items.some((body) => body.id === memoryBodyId && eligible(body))) throw new Error(`runtime archive Memory Space ${memoryBodyId} is no longer eligible; no archive writes were attempted`);
3892
4048
  signal.throwIfAborted();
3893
4049
  context.assertWritable?.();
3894
4050
  const sources = runtimeMigrationSources(plan.revision, plan.entries);
3895
- const archiveResults = await memoryService.mutate("remember-many", { requests: sources.map((source) => {
4051
+ const requests = sources.map((source) => {
3896
4052
  const entry = plan.entries[source.index - 1];
3897
4053
  return {
3898
4054
  content: entry.content,
@@ -3902,50 +4058,101 @@ ${chunk.context}
3902
4058
  memoryBodyId: routed.get(source.index),
3903
4059
  ...entry.branches === void 0 || entry.branches.length === 0 ? {} : { tags: entry.branches.map((branch) => `branch:${branch}`) }
3904
4060
  };
3905
- }) }, signal);
3906
- if (archiveResults.length !== sources.length) throw new Error("runtime archive batch did not return one receipt per source entry");
3907
- const lineage = [];
3908
- const memoryBodyIds = /* @__PURE__ */ new Set();
3909
- for (const source of sources) {
3910
- const memoryBodyId = routed.get(source.index);
3911
- const entry = plan.entries[source.index - 1];
3912
- const destination = await this.archiveRuntimeEntry(memoryService, memoryBodyId, entry, archiveResults[source.index - 1], signal);
3913
- memoryBodyIds.add(memoryBodyId);
3914
- lineage.push({
3915
- source: {
3916
- layerId: source.layerId,
3917
- reference: source.reference,
3918
- digest: source.digest
3919
- },
3920
- destination
3921
- });
3922
- }
3923
- signal.throwIfAborted();
3924
- context.assertWritable?.();
3925
- const mutation = await runtimeMemory.mutateResult("compact-and-mutate", {
3926
- revision: plan.revision,
3927
- mutation: request,
3928
- compacted: compactedEntries,
3929
- maxBytes: budget,
3930
- lineage
3931
- }, signal);
3932
- if (provider === "host") {
3933
- this.counters.migrations += 1;
3934
- this.counters.lastRunId = runId;
3935
- this.counters.lastOperation = "migration";
3936
- this.counters.lastAt = (/* @__PURE__ */ new Date()).toISOString();
3937
- }
3938
- return {
3939
- ...mutation.value,
3940
- revision: mutation.revision,
3941
- maintenance: {
3942
- kind: "mnemon-archive",
3943
- runId,
3944
- provider,
3945
- summary,
3946
- memoryBodyIds: [...memoryBodyIds]
4061
+ });
4062
+ const archiveResults = /* @__PURE__ */ new Map();
4063
+ const verified = /* @__PURE__ */ new Map();
4064
+ const created = /* @__PURE__ */ new Map();
4065
+ let commitAttempted = false;
4066
+ try {
4067
+ for (const memoryBodyId of new Set(routed.values())) {
4068
+ signal.throwIfAborted();
4069
+ context.assertWritable?.();
4070
+ const batch = sources.filter((source) => routed.get(source.index) === memoryBodyId);
4071
+ const written = await memoryService.mutate("remember-many", { requests: batch.map((source) => requests[source.index - 1]) }, signal);
4072
+ for (const [offset, result] of written.entries()) {
4073
+ const source = batch[offset];
4074
+ if (source === void 0) continue;
4075
+ const receipt = optionalObject(result);
4076
+ const states = mutationStates(result);
4077
+ const id = typeof receipt?.id === "string" && receipt.id.trim() !== "" ? receipt.id : void 0;
4078
+ if (mutationResultCommitted(result) && states.some((state) => [
4079
+ "added",
4080
+ "created",
4081
+ "stored"
4082
+ ].includes(state)) && !states.some((state) => [
4083
+ "updated",
4084
+ "replaced",
4085
+ "merged",
4086
+ "skipped"
4087
+ ].includes(state)) && id !== void 0 && (receipt?.memoryBodyId === void 0 || receipt.memoryBodyId === memoryBodyId)) created.set(JSON.stringify([memoryBodyId, id]), {
4088
+ id,
4089
+ memoryBodyId
4090
+ });
4091
+ archiveResults.set(source.index, result);
4092
+ }
4093
+ if (written.length !== batch.length) throw new Error("runtime archive batch did not return one receipt per source entry");
4094
+ for (const source of batch) verified.set(source.index, await this.archiveRuntimeEntry(memoryService, memoryBodyId, plan.entries[source.index - 1], archiveResults.get(source.index), signal));
3947
4095
  }
3948
- };
4096
+ const lineage = [];
4097
+ const memoryBodyIds = /* @__PURE__ */ new Set();
4098
+ for (const source of sources) {
4099
+ const memoryBodyId = routed.get(source.index);
4100
+ const destination = verified.get(source.index);
4101
+ memoryBodyIds.add(memoryBodyId);
4102
+ lineage.push({
4103
+ source: {
4104
+ layerId: source.layerId,
4105
+ reference: source.reference,
4106
+ digest: source.digest
4107
+ },
4108
+ destination
4109
+ });
4110
+ }
4111
+ signal.throwIfAborted();
4112
+ context.assertWritable?.();
4113
+ commitAttempted = true;
4114
+ const mutation = await runtimeMemory.mutateResult("compact-and-mutate", {
4115
+ revision: plan.revision,
4116
+ mutation: request,
4117
+ compacted: compactedEntries,
4118
+ maxBytes: budget,
4119
+ lineage
4120
+ }, signal);
4121
+ if (provider === "host") {
4122
+ this.counters.migrations += 1;
4123
+ this.counters.lastRunId = runId;
4124
+ this.counters.lastOperation = "migration";
4125
+ this.counters.lastAt = (/* @__PURE__ */ new Date()).toISOString();
4126
+ }
4127
+ return {
4128
+ ...mutation.value,
4129
+ revision: mutation.revision,
4130
+ maintenance: {
4131
+ kind: "mnemon-archive",
4132
+ runId,
4133
+ provider,
4134
+ summary,
4135
+ memoryBodyIds: [...memoryBodyIds]
4136
+ }
4137
+ };
4138
+ } catch (error) {
4139
+ if (created.size > 0) {
4140
+ const cleanupSignal = AbortSignal.timeout(3e4);
4141
+ const failures = [];
4142
+ if (commitAttempted) try {
4143
+ if ((await context.inspectRuntime.read("maintenance-plan", request, cleanupSignal)).revision !== plan.revision) throw new Error("Runtime revision changed; preserve archive entries and inspect the completed operation");
4144
+ } catch (inspectionError) {
4145
+ throw new AggregateError([error, inspectionError], `runtime archive failed with uncertain local commit; preserve Memory Space entries: ${[...created.values()].map((item) => `${item.memoryBodyId}/${item.id}`).join(", ")}`);
4146
+ }
4147
+ for (const entry of [...created.values()].reverse()) try {
4148
+ if (!mutationResultCommitted(await archive.cleanup.mutate("forget", entry, cleanupSignal))) throw new Error("Provider did not confirm archive cleanup");
4149
+ } catch (cleanupError) {
4150
+ failures.push(new Error(`Memory Space ${entry.memoryBodyId}, archive entry ${entry.id}`, { cause: cleanupError }));
4151
+ }
4152
+ if (failures.length > 0) throw new AggregateError([error, ...failures], `runtime archive failed and cleanup failed; ${failures.map((failure) => failure.message).join("; ")}`);
4153
+ }
4154
+ throw error;
4155
+ }
3949
4156
  }
3950
4157
  async compactUserAndCommit(context, request, plan, signal) {
3951
4158
  const runtimeMemory = context.runtime;
@@ -4033,10 +4240,12 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
4033
4240
  };
4034
4241
  }
4035
4242
  async delegate(parent, operation, label, prompt, tools, outputSchema, signal, preferredProvider = "spawn", persona = WRITE_PERSONA, recovery) {
4243
+ if (this.disposed) throw new Error("dsh-mnemon subagent coordinator is disposed");
4036
4244
  const provider = this.provider(preferredProvider);
4037
4245
  assertDshOutputSchema(outputSchema);
4038
4246
  if (this.resultRuntime === void 0) throw new Error("dsh-mnemon subagent result tool runtime is unavailable");
4039
- const resultToolName = `${RESULT_TOOL_PREFIX}${randomUUID().replaceAll("-", "")}`;
4247
+ const resultToolName = RESULT_TOOL_NAME;
4248
+ const requestId = randomUUID();
4040
4249
  let captured;
4041
4250
  let pending;
4042
4251
  let activeResultExecution;
@@ -4046,7 +4255,6 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
4046
4255
  const stagedReceipts = /* @__PURE__ */ new Map();
4047
4256
  let run;
4048
4257
  let failure;
4049
- let disposeResultTool;
4050
4258
  let disposeResultObserver;
4051
4259
  let releaseWorkflow;
4052
4260
  try {
@@ -4055,7 +4263,9 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
4055
4263
  releaseWorkflow = execution.release;
4056
4264
  signal = execution.signal;
4057
4265
  }
4266
+ if (this.disposed) throw new Error("dsh-mnemon subagent coordinator is disposed");
4058
4267
  const observer = this.resultRuntime.on("tools/result", ((execution, result) => {
4268
+ if (signal.aborted || !this.resultRequests.has(requestId)) return;
4059
4269
  if (execution.token !== void 0) {
4060
4270
  const entries = stagedReceipts.get(execution.token);
4061
4271
  if (entries !== void 0) {
@@ -4099,37 +4309,27 @@ ${runtimeSnapshotContext("user", plan.entries)}`;
4099
4309
  }));
4100
4310
  if (typeof observer !== "function") throw new Error("dsh-mnemon subagent result observer registration did not return a disposer");
4101
4311
  disposeResultObserver = observer;
4102
- const registration = this.resultRuntime.tools.register({
4103
- name: resultToolName,
4104
- description: "Record the final result for this one Mnemon delegated run. This internal capability is valid only for the child that received its exact name.",
4105
- parameters: outputSchema,
4106
- output: {
4107
- schema: RESULT_TOOL_OUTPUT_SCHEMA,
4108
- render: () => [{
4109
- type: "text",
4110
- text: "Mnemon subagent result recorded."
4111
- }]
4112
- },
4113
- async execute(args, execution) {
4114
- const agent = execution.agent;
4115
- if (agent === void 0 || !isSubagent(agent)) throw new Error("Mnemon subagent result tools are restricted to delegated children");
4116
- if (activeResultExecution !== void 0 || pending !== void 0 || captured !== void 0) throw new Error("Mnemon subagent result was already recorded");
4117
- if (execution.concludeTurn === void 0) throw new Error("Mnemon subagent result tool requires terminal tool-call support");
4118
- assertDshOutputValue(outputSchema, args);
4119
- activeResultExecution = execution;
4120
- staged.set(execution, {
4121
- agentId: agent.id,
4122
- value: args
4123
- });
4124
- execution.concludeTurn();
4125
- return { recorded: true };
4126
- }
4312
+ this.resultRequests.set(requestId, async (value, execution) => {
4313
+ signal.throwIfAborted();
4314
+ const agent = execution.agent;
4315
+ if (agent === void 0 || !isSubagent(agent)) throw new Error("Mnemon subagent result tools are restricted to delegated children");
4316
+ if (run !== void 0 && agent.id !== run.id) throw new Error("Mnemon subagent result belongs to a different child");
4317
+ if (activeResultExecution !== void 0 || pending !== void 0 || captured !== void 0) throw new Error("Mnemon subagent result was already recorded");
4318
+ if (execution.concludeTurn === void 0) throw new Error("Mnemon subagent result tool requires terminal tool-call support");
4319
+ assertDshOutputValue(outputSchema, value);
4320
+ activeResultExecution = execution;
4321
+ staged.set(execution, {
4322
+ agentId: agent.id,
4323
+ value
4324
+ });
4325
+ execution.concludeTurn();
4326
+ return { recorded: true };
4127
4327
  });
4128
- if (typeof registration !== "function") throw new Error("dsh-mnemon subagent result tool registration did not return a disposer");
4129
- disposeResultTool = registration;
4130
4328
  const completionPersona = `${persona}
4131
4329
 
4132
- 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.`;
4330
+ Completion protocol: call \`${resultToolName}\` exactly once with requestId \`${requestId}\` and result matching this JSON schema:
4331
+ ${JSON.stringify(outputSchema)}
4332
+ This is the only completion channel for this run. Do not finish with a plain-text answer. The requestId expires when this run finishes or is cancelled.`;
4133
4333
  const perOpMaxTokens = operation === "migration" || operation === "compaction" ? this.runtimeMaintenanceMaxTokensResolver?.() ?? 8192 : operation === "document-archive" ? 8192 : operation === "metadata-maintenance" ? 4096 : void 0;
4134
4334
  const fixed = this.taskAgentModelResolver?.();
4135
4335
  const baseAgentOptions = perOpMaxTokens === void 0 ? void 0 : { maxTokens: perOpMaxTokens };
@@ -4138,7 +4338,7 @@ Completion protocol: call \`${resultToolName}\` exactly once with the final resu
4138
4338
  provider: fixed.provider,
4139
4339
  model: fixed.model
4140
4340
  };
4141
- run = await this.subagents.start(provider, {
4341
+ const start = () => this.subagents.start(provider, {
4142
4342
  label,
4143
4343
  prompt: [{
4144
4344
  type: "text",
@@ -4151,6 +4351,7 @@ Completion protocol: call \`${resultToolName}\` exactly once with the final resu
4151
4351
  toolFilter: { allow: [...tools, resultToolName] },
4152
4352
  persona: completionPersona
4153
4353
  });
4354
+ run = operation === "review" ? await startGuardedReview(this.resultRuntime, parent, [...tools, resultToolName], start) : await start();
4154
4355
  const activeRun = run;
4155
4356
  const result = await activeRun.result;
4156
4357
  if (captured !== void 0 && captured.agentId !== activeRun.id) throw new Error("Mnemon subagent result was recorded by a different child");
@@ -4180,17 +4381,13 @@ Completion protocol: call \`${resultToolName}\` exactly once with the final resu
4180
4381
  failure = error;
4181
4382
  throw error;
4182
4383
  } finally {
4384
+ this.resultRequests.delete(requestId);
4183
4385
  let cleanupFailure;
4184
4386
  if (run !== void 0) try {
4185
4387
  await run.dispose();
4186
4388
  } catch (error) {
4187
4389
  if (failure === void 0) cleanupFailure = error;
4188
4390
  }
4189
- if (disposeResultTool !== void 0) try {
4190
- await disposeResultTool();
4191
- } catch (error) {
4192
- if (failure === void 0 && cleanupFailure === void 0) cleanupFailure = error;
4193
- }
4194
4391
  if (disposeResultObserver !== void 0) try {
4195
4392
  await disposeResultObserver();
4196
4393
  } catch (error) {
@@ -4925,6 +5122,7 @@ var MnemonAgentLifecycle = class {
4925
5122
  this.primePending = true;
4926
5123
  this.cueInjected = false;
4927
5124
  this.injectedMemoryText = void 0;
5125
+ this.lastError = void 0;
4928
5126
  this.mark("prime");
4929
5127
  })),
4930
5128
  this.agent.ctx.on("session/event", ((session, event) => this.sessionEvent(session, event))),
@@ -5104,6 +5302,7 @@ var MnemonAgentLifecycle = class {
5104
5302
  this.lastReviewScore = triggeredScore;
5105
5303
  this.lastReviewDocumentIds = result.documentIds;
5106
5304
  this.turnActivity.clear();
5305
+ this.lastError = void 0;
5107
5306
  this.mark("review");
5108
5307
  } catch (error) {
5109
5308
  if (!controller.signal.aborted) this.fail(error);
@@ -5183,13 +5382,13 @@ var MnemonAgentLifecycle = class {
5183
5382
  mark(phase) {
5184
5383
  this.lastPhase = phase;
5185
5384
  this.lastAt = (/* @__PURE__ */ new Date()).toISOString();
5186
- this.lastError = void 0;
5187
5385
  }
5188
5386
  fail(error) {
5189
5387
  this.counters.failures += 1;
5190
5388
  this.lastPhase = "error";
5191
5389
  this.lastAt = (/* @__PURE__ */ new Date()).toISOString();
5192
- this.lastError = error instanceof Error ? error.message : String(error);
5390
+ this.lastError = (error instanceof Error ? error.message : String(error)).replace(/\bsk-[A-Za-z0-9_-]{8,}\b/gu, "[redacted]").replace(/\s+/gu, " ").trim().slice(0, 500);
5391
+ console.warn(`[dsh-mnemon] idle review failed: ${this.lastError}`);
5193
5392
  }
5194
5393
  };
5195
5394
  /** DSH-native owner for per-agent Mnemon lifecycle hooks and UI-triggered LLM work. */
@@ -9129,8 +9328,9 @@ function apply(rawContext, config = {}) {
9129
9328
  const lifecycle = new MnemonLifecycle(ctx, coordinator, runtime.config, runtime);
9130
9329
  ctx.effect(() => {
9131
9330
  const stop = lifecycle.start();
9132
- return () => {
9331
+ return async () => {
9133
9332
  stop();
9333
+ await coordinator.dispose();
9134
9334
  disposePrepared();
9135
9335
  runtime.dispose();
9136
9336
  };
@@ -125,6 +125,9 @@ export declare const zh: {
125
125
  'status.title': string;
126
126
  'status.description': string;
127
127
  'status.nominal': string;
128
+ 'status.reviewFailed': string;
129
+ 'status.reviewFailedDetail': string;
130
+ 'status.reviewContextWindow': string;
128
131
  'status.checkRequired': string;
129
132
  'status.rechecking': string;
130
133
  'status.recheck': string;
@@ -1105,6 +1108,9 @@ export declare const en: {
1105
1108
  'status.title': string;
1106
1109
  'status.description': string;
1107
1110
  'status.nominal': string;
1111
+ 'status.reviewFailed': string;
1112
+ 'status.reviewFailedDetail': string;
1113
+ 'status.reviewContextWindow': string;
1108
1114
  'status.checkRequired': string;
1109
1115
  'status.rechecking': string;
1110
1116
  'status.recheck': string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-mnemon",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
4
4
  "description": "Composable three-tier memory control plane for DeepSeek Harness: persistent runtime context, searchable project documents, pluggable long-term memory, guarded strategies, WebUI, and headless tools.",
5
5
  "keywords": [
6
6
  "deepseek-harness",