dsh-mnemon 0.5.40 → 0.5.42

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
@@ -389,6 +389,8 @@ const DEFAULT_SKILLS_CONFIG = Object.freeze({
389
389
  allowUpdate: true,
390
390
  targetScope: "user",
391
391
  autoBacktest: true,
392
+ draftMaxTokens: 32768,
393
+ draftRetryMaxTokens: 65536,
392
394
  hints: true,
393
395
  sidebar: false,
394
396
  reviewThreshold: 5,
@@ -417,7 +419,7 @@ const schema = z.object({
417
419
  const legacySchema = z.object({ sources: z.dict(z.object({ enabled: z.boolean() })).default({}) });
418
420
  const ENTRY_ID$1 = /^[a-zA-Z0-9][a-zA-Z0-9._:/-]{0,299}$/u;
419
421
  const clone$2 = (value) => JSON.parse(JSON.stringify(value));
420
- function record$10(value) {
422
+ function record$11(value) {
421
423
  if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("Plugin configuration must be an object");
422
424
  const json = JSON.stringify(value);
423
425
  if (json.length > 65536) throw new Error("Plugin configuration exceeds 64 KiB");
@@ -431,13 +433,13 @@ function record$10(value) {
431
433
  }
432
434
  function preferences(value) {
433
435
  if (value.strategyTypeId !== void 0 && !/^[a-z][a-z0-9-]{0,127}$/u.test(value.strategyTypeId)) throw new Error("Invalid Strategy type id");
434
- const entries = record$10(value.entries ?? {});
436
+ const entries = record$11(value.entries ?? {});
435
437
  if (Object.keys(entries).length > 64) throw new Error("At most 64 memory plugin Entries can be configured");
436
438
  for (const [entryId, item] of Object.entries(entries)) {
437
439
  if (!ENTRY_ID$1.test(entryId)) throw new Error("Invalid memory plugin Entry id");
438
- const candidate = record$10(item);
440
+ const candidate = record$11(item);
439
441
  if (candidate.enabled !== void 0 && typeof candidate.enabled !== "boolean") throw new Error("Plugin enabled must be boolean");
440
- record$10(candidate.config);
442
+ record$11(candidate.config);
441
443
  }
442
444
  return clone$2(value);
443
445
  }
@@ -481,6 +483,8 @@ const SkillsConfig = z.object({
481
483
  "project"
482
484
  ]).default(DEFAULT_SKILLS_CONFIG.targetScope),
483
485
  autoBacktest: z.boolean().default(DEFAULT_SKILLS_CONFIG.autoBacktest),
486
+ draftMaxTokens: z.number().step(1).min(128).max(131072).default(DEFAULT_SKILLS_CONFIG.draftMaxTokens),
487
+ draftRetryMaxTokens: z.number().step(1).min(128).max(131072).default(DEFAULT_SKILLS_CONFIG.draftRetryMaxTokens),
484
488
  hints: z.boolean().default(DEFAULT_SKILLS_CONFIG.hints),
485
489
  sidebar: z.boolean().default(DEFAULT_SKILLS_CONFIG.sidebar),
486
490
  reviewThreshold: z.number().step(1).min(1).max(50).default(DEFAULT_SKILLS_CONFIG.reviewThreshold),
@@ -640,6 +644,7 @@ const Config = z.object({
640
644
  skills: SkillsConfig.default({ ...DEFAULT_SKILLS_CONFIG }),
641
645
  persistenceStrategy: MemoryPersistenceStrategySchema,
642
646
  taskAgentModel: TaskAgentModelSchema,
647
+ subagentProviderNames: z.array(z.string()).default([]),
643
648
  syncEnabled: z.boolean().default(false)
644
649
  });
645
650
  function resolveInteractionConfig(config = {}) {
@@ -689,6 +694,11 @@ function resolveCustomPacks(value, legacyDataDir) {
689
694
  }
690
695
  return packs;
691
696
  }
697
+ function resolveSubagentProviderNames(value) {
698
+ if (value === void 0) return [];
699
+ if (!Array.isArray(value) || value.length > 32 || value.some((name) => typeof name !== "string" || !name.trim() || name.length > 128)) throw new Error("dsh-mnemon: subagentProviderNames must contain at most 32 nonempty provider names of at most 128 characters");
700
+ return [...new Set(value.map((name) => name.trim()))];
701
+ }
692
702
  function resolveTaskAgentModel(value) {
693
703
  const mode = value?.mode ?? "inherit";
694
704
  if (mode !== "inherit" && mode !== "fixed") throw new Error(`dsh-mnemon: unsupported task Agent model mode: ${String(mode)}`);
@@ -813,6 +823,7 @@ function resolveConfig(config = {}) {
813
823
  },
814
824
  persistenceStrategy: resolvePersistenceStrategy(config.persistenceStrategy),
815
825
  taskAgentModel: resolveTaskAgentModel(config.taskAgentModel),
826
+ subagentProviderNames: resolveSubagentProviderNames(config.subagentProviderNames),
816
827
  syncEnabled: config.syncEnabled ?? false
817
828
  };
818
829
  }
@@ -992,7 +1003,7 @@ function createStorageRoot(config, workspaceRoot) {
992
1003
  const MNEMON_MACHINE_DIRECTORY = "state";
993
1004
  const MNEMON_MACHINE_FILE = "machine.json";
994
1005
  const MNEMON_MACHINE_VERSION = 1;
995
- function record$9(value) {
1006
+ function record$10(value) {
996
1007
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
997
1008
  }
998
1009
  /** The name a fresh identity carries: the host name, or a neutral fallback. */
@@ -1074,7 +1085,7 @@ var MnemonMachineStore = class {
1074
1085
  } catch {
1075
1086
  return;
1076
1087
  }
1077
- const parsed = record$9(value);
1088
+ const parsed = record$10(value);
1078
1089
  if (parsed?.version !== MNEMON_MACHINE_VERSION) return void 0;
1079
1090
  if (typeof parsed.id !== "string" || parsed.id.trim() === "") return void 0;
1080
1091
  if (typeof parsed.label !== "string" || parsed.label.trim() === "") return void 0;
@@ -2048,7 +2059,7 @@ const COMPONENT_ORDER = MNEMON_PACK_COMPONENTS;
2048
2059
  */
2049
2060
  const CONTENT_HASH = /^[a-f0-9]{64}$/u;
2050
2061
  const SQLITE_HEADER = Buffer.from("SQLite format 3\0", "binary");
2051
- function record$8(value) {
2062
+ function record$9(value) {
2052
2063
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2053
2064
  }
2054
2065
  function utf8(bytes, label) {
@@ -2111,7 +2122,7 @@ function componentsForScope(scope) {
2111
2122
  }
2112
2123
  /** Exported so the sync channel can read the manifest a commit holds without unpacking the archive. */
2113
2124
  function parseManifest(value) {
2114
- const manifest = record$8(value);
2125
+ const manifest = record$9(value);
2115
2126
  if (manifest?.format !== "mnemonpack" || manifest.version !== 1) throw new Error("unsupported Mnemon Pack format or version");
2116
2127
  if (manifest.scope !== "full" && !COMPONENT_ORDER.includes(manifest.scope)) throw new Error("Mnemon Pack scope is invalid");
2117
2128
  if (typeof manifest.exportedAt !== "string") throw new Error("Mnemon Pack exportedAt is invalid");
@@ -2120,11 +2131,11 @@ function parseManifest(value) {
2120
2131
  if (components.length === 0 || new Set(components).size !== components.length || components.some((component) => !COMPONENT_ORDER.includes(component))) throw new Error("Mnemon Pack components are invalid");
2121
2132
  const expected = componentsForScope(manifest.scope);
2122
2133
  if (components.length !== expected.length || expected.some((component) => !components.includes(component))) throw new Error("Mnemon Pack scope does not match its components");
2123
- const source = record$8(manifest.source);
2134
+ const source = record$9(manifest.source);
2124
2135
  if (source?.plugin !== "dsh-mnemon" || typeof source.pluginVersion !== "string") throw new Error("Mnemon Pack source is invalid");
2125
2136
  if (!Array.isArray(manifest.summary)) throw new Error("Mnemon Pack summary is invalid");
2126
2137
  const summary = manifest.summary.map((entry) => {
2127
- const item = record$8(entry);
2138
+ const item = record$9(entry);
2128
2139
  const component = item?.component;
2129
2140
  if (!COMPONENT_ORDER.includes(component) || !Number.isSafeInteger(item?.files) || !Number.isSafeInteger(item?.bytes) || !Number.isSafeInteger(item?.items) || Number(item?.files) < 0 || Number(item?.bytes) < 0 || Number(item?.items) < 0) throw new Error("Mnemon Pack summary entry is invalid");
2130
2141
  return {
@@ -2150,7 +2161,7 @@ function parseManifest(value) {
2150
2161
  /** The sync channel reads its machine identity with this parser; no Pack carries one. */
2151
2162
  function parseMachineIdentity(value) {
2152
2163
  if (value === void 0 || value === null) return void 0;
2153
- const machine = record$8(value);
2164
+ const machine = record$9(value);
2154
2165
  if (typeof machine?.id !== "string" || machine.id.trim() === "" || typeof machine.label !== "string" || typeof machine.createdAt !== "string") throw new Error("Mnemon Pack machine identity is invalid");
2155
2166
  return {
2156
2167
  id: machine.id,
@@ -2160,7 +2171,7 @@ function parseMachineIdentity(value) {
2160
2171
  }
2161
2172
  function parseEntryOrigin(value) {
2162
2173
  if (value === void 0 || value === null) return void 0;
2163
- const origin = record$8(value);
2174
+ const origin = record$9(value);
2164
2175
  if (typeof origin?.machine !== "string" || typeof origin.label !== "string" || typeof origin.at !== "string") return void 0;
2165
2176
  return {
2166
2177
  machine: origin.machine,
@@ -2205,8 +2216,8 @@ function parseArchive(base64, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
2205
2216
  if (path !== "manifest.json" && path !== "checksums.json" && !allowedPayloadPath(path)) throw new Error(`unsupported Mnemon Pack entry: ${path}`);
2206
2217
  }
2207
2218
  const manifest = parseManifest(json$1(files["manifest.json"], "manifest.json"));
2208
- const checksumValue = record$8(json$1(files["checksums.json"], "checksums.json"));
2209
- const checksumFiles = record$8(checksumValue?.files);
2219
+ const checksumValue = record$9(json$1(files["checksums.json"], "checksums.json"));
2220
+ const checksumFiles = record$9(checksumValue?.files);
2210
2221
  if (checksumValue?.algorithm !== "sha256" || checksumFiles === void 0) throw new Error("Mnemon Pack checksums are invalid");
2211
2222
  const payloadNames = names.filter((path) => path.startsWith("payload/")).sort();
2212
2223
  if (Object.keys(checksumFiles).length !== payloadNames.length) throw new Error("Mnemon Pack checksum inventory does not match the payload");
@@ -2229,10 +2240,10 @@ function parseArchive(base64, runtimeLimits = RUNTIME_MEMORY_LIMITS) {
2229
2240
  };
2230
2241
  }
2231
2242
  function parseRuntime(value, limits = RUNTIME_MEMORY_LIMITS) {
2232
- const source = record$8(value);
2243
+ const source = record$9(value);
2233
2244
  if (source?.version !== RUNTIME_MEMORY_VERSION || !Array.isArray(source.entries)) throw new Error("runtime memories.json is invalid");
2234
2245
  const entries = source.entries.map((raw) => {
2235
- const entry = record$8(raw);
2246
+ const entry = record$9(raw);
2236
2247
  if (typeof entry?.content !== "string" || entry.target !== "memory" && entry.target !== "user" || ![
2237
2248
  "critical",
2238
2249
  "normal",
@@ -2269,12 +2280,12 @@ function emptyTombstones() {
2269
2280
  };
2270
2281
  }
2271
2282
  function parseTombstones(value) {
2272
- const source = record$8(value);
2283
+ const source = record$9(value);
2273
2284
  if (source?.version !== 1 || !Array.isArray(source.tombstones)) throw new Error("runtime tombstones.json is invalid");
2274
2285
  return {
2275
2286
  version: 1,
2276
2287
  tombstones: source.tombstones.map((raw) => {
2277
- const tombstone = record$8(raw);
2288
+ const tombstone = record$9(raw);
2278
2289
  if (tombstone?.target !== "memory" && tombstone?.target !== "user") throw new Error("runtime tombstones.json contains an invalid entry");
2279
2290
  if (typeof tombstone.contentHash !== "string" || !CONTENT_HASH.test(tombstone.contentHash) || typeof tombstone.deletedAt !== "string") throw new Error("runtime tombstones.json contains an invalid entry");
2280
2291
  return {
@@ -2689,11 +2700,11 @@ function emptyRuntimeIndex() {
2689
2700
  }
2690
2701
  /** A damaged index only costs forgotten deletions, so it degrades instead of failing an export. */
2691
2702
  function parseRuntimeIndex(value) {
2692
- const source = record$8(value);
2703
+ const source = record$9(value);
2693
2704
  if (source?.version !== 1 || !Array.isArray(source.entries)) return emptyRuntimeIndex();
2694
2705
  const entries = [];
2695
2706
  for (const raw of source.entries) {
2696
- const entry = record$8(raw);
2707
+ const entry = record$9(raw);
2697
2708
  if (entry?.target !== "memory" && entry?.target !== "user") continue;
2698
2709
  if (typeof entry.contentHash !== "string" || !/^[a-f0-9]{64}$/u.test(entry.contentHash)) continue;
2699
2710
  entries.push({
@@ -3345,7 +3356,7 @@ const STATUSES = [
3345
3356
  "accepted",
3346
3357
  "rejected"
3347
3358
  ];
3348
- function record$7(value) {
3359
+ function record$8(value) {
3349
3360
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
3350
3361
  }
3351
3362
  function text$2(value, limit = MAX_REVIEW_TEXT) {
@@ -3357,7 +3368,7 @@ function strings$1(value, limit = 64) {
3357
3368
  function parseOpinions(value) {
3358
3369
  if (!Array.isArray(value)) return [];
3359
3370
  return value.flatMap((raw) => {
3360
- const opinion = record$7(raw);
3371
+ const opinion = record$8(raw);
3361
3372
  if (opinion === void 0 || typeof opinion.id !== "string" || typeof opinion.text !== "string") return [];
3362
3373
  return [{
3363
3374
  id: opinion.id,
@@ -3369,11 +3380,11 @@ function parseOpinions(value) {
3369
3380
  }
3370
3381
  /** A proposal that no longer parses is dropped rather than failing the whole ledger. */
3371
3382
  function parseEntry(value) {
3372
- const entry = record$7(value);
3383
+ const entry = record$8(value);
3373
3384
  if (entry === void 0 || typeof entry.id !== "string" || entry.id.trim() === "") return void 0;
3374
3385
  if (typeof entry.status !== "string" || !STATUSES.includes(entry.status)) return void 0;
3375
3386
  if (!Array.isArray(entry.operations)) return void 0;
3376
- const machine = record$7(entry.machine) ?? {};
3387
+ const machine = record$8(entry.machine) ?? {};
3377
3388
  const createdAt = typeof entry.createdAt === "string" ? entry.createdAt : (/* @__PURE__ */ new Date()).toISOString();
3378
3389
  return {
3379
3390
  id: entry.id,
@@ -3523,7 +3534,7 @@ var MnemonReviewLedger = class {
3523
3534
  version: 1,
3524
3535
  entries: []
3525
3536
  };
3526
- const file = record$7(JSON.parse(readFileSync(this.path(), "utf8")));
3537
+ const file = record$8(JSON.parse(readFileSync(this.path(), "utf8")));
3527
3538
  if (file?.version !== 1 || !Array.isArray(file.entries)) throw new Error("review ledger is invalid: " + this.path());
3528
3539
  return {
3529
3540
  version: 1,
@@ -3974,7 +3985,7 @@ const MNEMON_GITHUB_MAX_INTERVAL_MS = 6e4;
3974
3985
  const MNEMON_GITHUB_FLOW_LIMIT_MS = 36e5;
3975
3986
  const MNEMON_GITHUB_USER_AGENT = "dsh-mnemon-sync";
3976
3987
  const DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
3977
- function record$6(value) {
3988
+ function record$7(value) {
3978
3989
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
3979
3990
  }
3980
3991
  function text$1(value) {
@@ -3982,7 +3993,7 @@ function text$1(value) {
3982
3993
  }
3983
3994
  /** GitHub answers a refused request with a `error` code or an API `message`. */
3984
3995
  function codeOf(body) {
3985
- const fields = record$6(body);
3996
+ const fields = record$7(body);
3986
3997
  return text$1(fields?.error) ?? text$1(fields?.message);
3987
3998
  }
3988
3999
  /** A positive count GitHub sends as a JSON number, or as a string in a form-encoded answer. */
@@ -3997,7 +4008,7 @@ const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/u;
3997
4008
  * device code or a token. Enough to tell a proxy's HTML page from a reply that lost a field.
3998
4009
  */
3999
4010
  function shapeOf(answer) {
4000
- const keys = Object.keys(record$6(answer.body) ?? {});
4011
+ const keys = Object.keys(record$7(answer.body) ?? {});
4001
4012
  const names = keys.filter((key) => FIELD_NAME.test(key)).slice(0, 8);
4002
4013
  const listed = keys.length > names.length ? [...names, String(keys.length - names.length) + " other"] : names;
4003
4014
  return "HTTP " + String(answer.status) + ", content-type " + (answer.type ?? "none") + ", fields: " + (listed.length === 0 ? "none" : listed.join(", "));
@@ -4009,9 +4020,9 @@ function failure$3(action, status, body) {
4009
4020
  }
4010
4021
  /** Read one grant out of a stored record, refusing anything else. */
4011
4022
  function githubGrantFromRecord(record_) {
4012
- const fields = record$6(record_);
4023
+ const fields = record$7(record_);
4013
4024
  if (fields?.kind !== "grant") return void 0;
4014
- const payload = record$6(fields.payload);
4025
+ const payload = record$7(fields.payload);
4015
4026
  if (payload?.version !== 1) return void 0;
4016
4027
  const accessToken = text$1(payload.accessToken);
4017
4028
  if (accessToken === void 0) return void 0;
@@ -4025,13 +4036,13 @@ function githubGrantFromRecord(record_) {
4025
4036
  };
4026
4037
  }
4027
4038
  function repositoryOf(value) {
4028
- const fields = record$6(value);
4039
+ const fields = record$7(value);
4029
4040
  const fullName = text$1(fields?.full_name);
4030
4041
  const url = text$1(fields?.clone_url);
4031
4042
  const name = text$1(fields?.name);
4032
4043
  if (fullName === void 0 || url === void 0 || name === void 0) return void 0;
4033
- const owner = record$6(fields?.owner);
4034
- const permissions = record$6(fields?.permissions);
4044
+ const owner = record$7(fields?.owner);
4045
+ const permissions = record$7(fields?.permissions);
4035
4046
  return {
4036
4047
  name,
4037
4048
  fullName,
@@ -4144,7 +4155,7 @@ var MnemonGitHubAuth = class {
4144
4155
  throw new Error("the GitHub device code request could not reach GitHub: " + describeTransportError(error));
4145
4156
  }
4146
4157
  if (answer.status !== 200) throw failure$3("the GitHub device code request", answer.status, answer.body);
4147
- const fields = record$6(answer.body);
4158
+ const fields = record$7(answer.body);
4148
4159
  const deviceCode = text$1(fields?.device_code);
4149
4160
  const userCode = text$1(fields?.user_code);
4150
4161
  const verificationUri = text$1(fields?.verification_uri);
@@ -4193,7 +4204,7 @@ var MnemonGitHubAuth = class {
4193
4204
  message: describeTransportError(error)
4194
4205
  };
4195
4206
  }
4196
- const fields = record$6(answer.body);
4207
+ const fields = record$7(answer.body);
4197
4208
  const code = text$1(fields?.error);
4198
4209
  if (code === void 0) {
4199
4210
  const accessToken = text$1(fields?.access_token);
@@ -4264,7 +4275,7 @@ var MnemonGitHubAuth = class {
4264
4275
  ...signal === void 0 ? {} : { signal }
4265
4276
  });
4266
4277
  if (status !== 200) return void 0;
4267
- return text$1(record$6(body)?.login);
4278
+ return text$1(record$7(body)?.login);
4268
4279
  } catch {
4269
4280
  return;
4270
4281
  }
@@ -4390,7 +4401,7 @@ const SETTING_KEYS = [
4390
4401
  "autoBackupMinutes"
4391
4402
  ];
4392
4403
  const REPOSITORY_HINT = "sync repository must be an https:// or ssh:// URL, a git@host:path address, or an absolute local path";
4393
- function record$5(value) {
4404
+ function record$6(value) {
4394
4405
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4395
4406
  }
4396
4407
  function known(key) {
@@ -4494,7 +4505,7 @@ var MnemonSyncSettingsStore = class {
4494
4505
  } catch {
4495
4506
  throw new Error("sync configuration is not valid JSON");
4496
4507
  }
4497
- const stored = record$5(parsed);
4508
+ const stored = record$6(parsed);
4498
4509
  if (stored === void 0) throw new Error("sync configuration is not a JSON object");
4499
4510
  for (const key of Object.keys(stored)) if (!known(key)) throw new Error("sync configuration has an unknown field: " + key);
4500
4511
  const settings = this.defaults();
@@ -4541,7 +4552,7 @@ var MnemonSyncSettingsStore = class {
4541
4552
  * turns the automatic backup off.
4542
4553
  */
4543
4554
  patch(patch) {
4544
- const fields = record$5(patch);
4555
+ const fields = record$6(patch);
4545
4556
  if (fields === void 0) throw new Error("sync configuration must be an object");
4546
4557
  for (const key of Object.keys(fields)) if (!known(key)) throw new Error("unknown sync setting: " + key);
4547
4558
  const next = { ...this.read() };
@@ -4777,7 +4788,7 @@ function remoteFailure(message) {
4777
4788
  function rejectedPush(output) {
4778
4789
  return /\[rejected\]|fetch first|non-fast-forward/iu.test(output);
4779
4790
  }
4780
- function record$4(value) {
4791
+ function record$5(value) {
4781
4792
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
4782
4793
  }
4783
4794
  function tail(output) {
@@ -4842,7 +4853,7 @@ const TIMESTAMP_FIELDS = /* @__PURE__ */ new Set(["exportedAt", "pushedAt"]);
4842
4853
  /** The same manifest without its timestamps describes the same payload. */
4843
4854
  function withoutTimestamps(value) {
4844
4855
  if (Array.isArray(value)) return value.map(withoutTimestamps);
4845
- const parsed = record$4(value);
4856
+ const parsed = record$5(value);
4846
4857
  if (parsed === void 0) return value;
4847
4858
  return Object.fromEntries(Object.entries(parsed).filter(([key]) => !TIMESTAMP_FIELDS.has(key)).map(([key, item]) => [key, withoutTimestamps(item)]));
4848
4859
  }
@@ -4855,7 +4866,7 @@ function manifestIdentity(bytes) {
4855
4866
  } catch {
4856
4867
  return;
4857
4868
  }
4858
- return record$4(value) === void 0 ? void 0 : JSON.stringify(withoutTimestamps(value));
4869
+ return record$5(value) === void 0 ? void 0 : JSON.stringify(withoutTimestamps(value));
4859
4870
  }
4860
4871
  /**
4861
4872
  * Whether the mirror already holds exactly this payload. Every push re-exports
@@ -5060,7 +5071,20 @@ function readFiles(root, limit = MAX_PAYLOAD_BYTES) {
5060
5071
  function unpack(archive) {
5061
5072
  let entries;
5062
5073
  try {
5063
- entries = unzipSync(archive);
5074
+ if (archive.byteLength > 268435456) throw new Error("compressed archive exceeds safety limit");
5075
+ let expandedBytes = 0;
5076
+ let fileCount = 0;
5077
+ entries = unzipSync(archive, { filter(info) {
5078
+ const path = info.name;
5079
+ const normalized = path.endsWith("/") ? path.slice(0, -1) : path;
5080
+ if (path.includes("\\") || path.startsWith("/") || /^[a-z]:/i.test(path) || path.includes("\0") || normalized.split("/").some((part) => !part || part === "." || part === "..")) throw new Error("unsafe archive path");
5081
+ if (path.endsWith("/")) return false;
5082
+ if (info.compression === 0 && info.size !== info.originalSize) throw new Error("invalid stored archive entry size");
5083
+ expandedBytes += info.originalSize;
5084
+ fileCount += 1;
5085
+ if (!Number.isSafeInteger(info.originalSize) || info.originalSize < 0 || info.originalSize > 134217728 || expandedBytes > 268435456 || fileCount > 4096) throw new Error("expanded archive exceeds safety limit");
5086
+ return true;
5087
+ } });
5064
5088
  } catch {
5065
5089
  throw new Error("the Mnemon payload is not a readable archive");
5066
5090
  }
@@ -5218,7 +5242,7 @@ var MnemonGitSync = class {
5218
5242
  }
5219
5243
  async configure(patch) {
5220
5244
  const previous = this.store.read();
5221
- const fields = record$4(patch);
5245
+ const fields = record$5(patch);
5222
5246
  const { machineLabel: name, ...settings } = fields ?? {};
5223
5247
  const naming = fields !== void 0 && "machineLabel" in fields;
5224
5248
  if (naming && name !== null && name !== "") machineLabel(name);
@@ -5426,7 +5450,7 @@ var MnemonGitSync = class {
5426
5450
  /** When memory last travelled through this channel, or nothing when it never did. */
5427
5451
  lastRun() {
5428
5452
  try {
5429
- const value = record$4(JSON.parse(readFileSync(this.lastRunPath(), "utf8")));
5453
+ const value = record$5(JSON.parse(readFileSync(this.lastRunPath(), "utf8")));
5430
5454
  if (value === void 0 || typeof value.at !== "string" || typeof value.commit !== "string") return void 0;
5431
5455
  if (value.kind !== "sync" && value.kind !== "merge") return void 0;
5432
5456
  return {
@@ -5464,13 +5488,13 @@ var MnemonGitSync = class {
5464
5488
  async agreedDocuments(settings, tip, signal) {
5465
5489
  let value;
5466
5490
  try {
5467
- value = record$4(JSON.parse(readFileSync(this.basePath(), "utf8")));
5491
+ value = record$5(JSON.parse(readFileSync(this.basePath(), "utf8")));
5468
5492
  } catch {
5469
5493
  value = void 0;
5470
5494
  }
5471
5495
  if (value === void 0) return this.lastRunDocuments(settings, tip, signal);
5472
5496
  if (value.version !== 1 || value.scope !== this.agreementScope(settings)) return void 0;
5473
- const documents = record$4(value.documents);
5497
+ const documents = record$5(value.documents);
5474
5498
  if (typeof value.commit !== "string" || !AGREED_COMMIT.test(value.commit) || documents === void 0) return void 0;
5475
5499
  if ((await this.git([
5476
5500
  "merge-base",
@@ -5917,9 +5941,9 @@ var MnemonGitSync = class {
5917
5941
  let machine;
5918
5942
  let pushedAt;
5919
5943
  try {
5920
- const raw = record$4(parseJson(bytes, "the backup manifest"));
5944
+ const raw = record$5(parseJson(bytes, "the backup manifest"));
5921
5945
  if (raw === void 0) return void 0;
5922
- const extension = record$4(raw[SYNC_EXTENSION]);
5946
+ const extension = record$5(raw[SYNC_EXTENSION]);
5923
5947
  pushedAt = typeof extension?.pushedAt === "string" ? extension.pushedAt : void 0;
5924
5948
  machine = extensionMachine(extension);
5925
5949
  manifest = parseManifest(raw);
@@ -6226,11 +6250,11 @@ var MnemonGitSync = class {
6226
6250
  if (manifestBytes === void 0 || checksumBytes === void 0) return void 0;
6227
6251
  if (manifestBytes.byteLength > MAX_MANIFEST_BYTES) throw new Error("the remote Mnemon manifest exceeds the safety limit");
6228
6252
  for (const path of Object.keys(files)) if (path !== MANIFEST && path !== CHECKSUMS && !path.startsWith("payload/")) throw new Error("the remote Mnemon payload holds an unexpected file: " + path);
6229
- const parsed = record$4(parseJson(manifestBytes, "the remote Mnemon manifest"));
6253
+ const parsed = record$5(parseJson(manifestBytes, "the remote Mnemon manifest"));
6230
6254
  if (parsed === void 0) throw new Error("the remote Mnemon manifest is not a JSON object");
6231
6255
  if (parsed.format !== "mnemonpack" || parsed.version !== 1) throw new Error("the remote Mnemon manifest is not a supported Mnemon Pack");
6232
- const checksums = record$4(parseJson(checksumBytes, "the remote Mnemon checksum inventory"));
6233
- const inventory = record$4(checksums?.files);
6256
+ const checksums = record$5(parseJson(checksumBytes, "the remote Mnemon checksum inventory"));
6257
+ const inventory = record$5(checksums?.files);
6234
6258
  if (checksums?.algorithm !== "sha256" || inventory === void 0) throw new Error("the remote Mnemon checksum inventory is invalid");
6235
6259
  for (const [path, bytes] of Object.entries(files)) {
6236
6260
  if (path === MANIFEST || path === CHECKSUMS) continue;
@@ -6239,7 +6263,7 @@ var MnemonGitSync = class {
6239
6263
  if (expected !== sha256Hex(bytes)) throw new Error("the remote Mnemon payload failed its checksum: " + path);
6240
6264
  }
6241
6265
  for (const path of Object.keys(inventory)) if (files[path] === void 0) throw new Error("the remote Mnemon payload is missing a checksummed file: " + path);
6242
- const extension = record$4(parsed[SYNC_EXTENSION]);
6266
+ const extension = record$5(parsed[SYNC_EXTENSION]);
6243
6267
  const pushedAt = typeof extension?.pushedAt === "string" ? extension.pushedAt : void 0;
6244
6268
  const machine = extensionMachine(extension);
6245
6269
  const archive = zipSync(files, {
@@ -6460,7 +6484,7 @@ function abandoned(path, now) {
6460
6484
  }
6461
6485
  if (age > STALE_LOCK_MS) return true;
6462
6486
  try {
6463
- const pid = record$4(JSON.parse(readFileSync(join(path, LOCK_OWNER), "utf8")))?.pid;
6487
+ const pid = record$5(JSON.parse(readFileSync(join(path, LOCK_OWNER), "utf8")))?.pid;
6464
6488
  return typeof pid === "number" && Number.isInteger(pid) && pid > 0 && pid !== process.pid && !processAlive(pid);
6465
6489
  } catch {
6466
6490
  return age > OWNERLESS_LOCK_MS;
@@ -7187,9 +7211,9 @@ var MemoryExecutions = class {
7187
7211
  constructor(runtime) {
7188
7212
  this.runtime = runtime;
7189
7213
  }
7190
- async turn(agent, turn, signal, delegation) {
7214
+ async turn(agent, turn, signal, delegation, independent = false) {
7191
7215
  signal.throwIfAborted();
7192
- const slot = this.open(agent, "turn", `${agent.id}:${turn}`, "agent.root-turn", delegation);
7216
+ const slot = this.open(agent, "turn", `${agent.id}:${turn}`, "agent.root-turn", delegation, independent);
7193
7217
  const abort = () => slot.controller.abort(signal.reason);
7194
7218
  signal.addEventListener("abort", abort, { once: true });
7195
7219
  try {
@@ -7272,7 +7296,7 @@ var MemoryExecutions = class {
7272
7296
  if (slot.kind === "turn") slot.close();
7273
7297
  }
7274
7298
  }
7275
- open(agent, kind, turnId, purpose, delegation) {
7299
+ open(agent, kind, turnId, purpose, delegation, independent = false) {
7276
7300
  this.assertOpen();
7277
7301
  const previous = this.slots.get(agent.id);
7278
7302
  const done = deferred();
@@ -7307,7 +7331,7 @@ var MemoryExecutions = class {
7307
7331
  await previous?.done;
7308
7332
  slot.controller.signal.throwIfAborted();
7309
7333
  this.assertOpen();
7310
- const graph = delegation?.graph ?? this.runtime.forAgent(agent);
7334
+ const graph = delegation?.graph ?? (independent ? this.runtime.forAgent(agent, { independent: true }) : this.runtime.forAgent(agent));
7311
7335
  const manager = graph.composableTurns;
7312
7336
  const scope = delegation === void 0 ? agentScope(agent, graph.config) : {
7313
7337
  ...delegation.scope,
@@ -7619,13 +7643,15 @@ var LiveMnemonRuntime = class {
7619
7643
  for (const graph of graphs) graph.dispose();
7620
7644
  }
7621
7645
  /** Resolve the runtime that must serve one Agent execution. */
7622
- forAgent(agent) {
7646
+ forAgent(agent, options = {}) {
7623
7647
  this.assertOpen();
7624
- const pinned = this.agentGraphs.get(agent.id);
7625
- if (pinned !== void 0) return pinned.graph;
7626
- const parentSession = agent.session.header?.origin === "subagent" ? agent.session.header.parentSession?.trim() : void 0;
7627
- const inherited = parentSession === void 0 || parentSession === "" ? void 0 : this.agentGraphs.get(parentSession);
7628
- if (inherited !== void 0) return inherited.graph;
7648
+ if (!options.independent) {
7649
+ const pinned = this.agentGraphs.get(agent.id);
7650
+ if (pinned !== void 0) return pinned.graph;
7651
+ const parentSession = agent.session.header?.origin === "subagent" ? agent.session.header.parentSession?.trim() : void 0;
7652
+ const inherited = parentSession === void 0 || parentSession === "" ? void 0 : this.agentGraphs.get(parentSession);
7653
+ if (inherited !== void 0) return inherited.graph;
7654
+ }
7629
7655
  if (!isWorkspaceStorageScope(this.current.config.storageScope)) return this.current;
7630
7656
  const cwd = agent.session.header?.cwd?.trim();
7631
7657
  if (cwd === void 0 || cwd === "") throw new Error("the current DSH session has no workspace for Mnemon");
@@ -7979,6 +8005,7 @@ function registerCommands(commands, service, coordinator) {
7979
8005
  const GUIDANCE_SECTION_NAME = "mnemon:routing";
7980
8006
  const RUNTIME_MEMORY_CONTEXT_NAME = "mnemon:runtime-memory";
7981
8007
  const STRATEGY_SECTION_NAME = "mnemon:strategy";
8008
+ const SNAPSHOT_GUIDANCE = "Mnemon memory snapshots are complete projections in chronological order. The latest snapshot supersedes earlier snapshots as the current projected state; do not treat older versions as additional current facts or authority. Absence may reflect filtering or projection budgets and does not imply deletion from storage.";
7982
8009
  const ROUTING_GUIDANCE = "Use memory only when needed. Follow the current Mnemon View: use mnemon_view_route or mnemon_view_action only for offered ids. Installed tools do not imply an available Source. Never infer missing historical facts. An action offer is not authorization; a write exists only after its receipt.";
7983
8010
  /** A View is an own-plugin message, never part of the shared context snapshot. */
7984
8011
  function withoutMemoryViewContext(assembly) {
@@ -7989,12 +8016,20 @@ function withoutMemoryViewContext(assembly) {
7989
8016
  }
7990
8017
  /** Inject trusted Strategy instructions after interpolation; Source text stays quoted in the Wake. */
7991
8018
  function applyMemoryViewGuidance(assembly, view, routingEnabled = true) {
7992
- if (view === void 0) return withoutMemoryViewContext(assembly);
8019
+ if (view === void 0) return withoutMemoryViewContext({
8020
+ ...assembly,
8021
+ sections: assembly.sections.filter((section) => section.name !== "mnemon:snapshot-history")
8022
+ });
7993
8023
  const sections = assembly.sections.filter((section) => ![
7994
8024
  GUIDANCE_SECTION_NAME,
7995
8025
  STRATEGY_SECTION_NAME,
7996
- "mnemon:runtime-memory-protocol"
8026
+ "mnemon:runtime-memory-protocol",
8027
+ "mnemon:snapshot-history"
7997
8028
  ].includes(section.name));
8029
+ sections.push({
8030
+ name: "mnemon:snapshot-history",
8031
+ text: SNAPSHOT_GUIDANCE
8032
+ });
7998
8033
  if (view.guidance?.system) sections.push({
7999
8034
  name: STRATEGY_SECTION_NAME,
8000
8035
  text: view.guidance.system
@@ -8130,7 +8165,7 @@ function pendingOpinions(entries) {
8130
8165
  createdAt: opinion.createdAt
8131
8166
  }))).sort((left, right) => left.createdAt.localeCompare(right.createdAt));
8132
8167
  }
8133
- function record$3(value) {
8168
+ function record$4(value) {
8134
8169
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
8135
8170
  }
8136
8171
  function stringField(value) {
@@ -8279,7 +8314,7 @@ function body(value, index, field) {
8279
8314
  return text.slice(0, MAX_FIELD);
8280
8315
  }
8281
8316
  function parseOperation(value, index, evidence) {
8282
- const operation = record$3(value);
8317
+ const operation = record$4(value);
8283
8318
  if (operation === void 0) throw new Error(`reconcile operation ${index} must be an object`);
8284
8319
  const kind = stringField(operation.kind);
8285
8320
  if (kind === void 0 || !KINDS.includes(kind)) throw new Error(`reconcile operation ${index} has an unsupported kind: ${JSON.stringify(operation.kind)}`);
@@ -8352,7 +8387,7 @@ function emptyDocumentSnapshot(directory, at) {
8352
8387
  }
8353
8388
  /** The host, not the model, decides what a proposal may contain. */
8354
8389
  function parseReconcileResult(value, evidence) {
8355
- const result = record$3(value);
8390
+ const result = record$4(value);
8356
8391
  if (result === void 0) throw new Error("memory reconciliation returned an invalid structured result");
8357
8392
  const action = stringField(result.action);
8358
8393
  if (action === void 0 || ![
@@ -8461,6 +8496,72 @@ async function applyReconcileOperations(operations, applier, signal, selection)
8461
8496
  };
8462
8497
  }
8463
8498
  //#endregion
8499
+ //#region src/host/agent-compat.ts
8500
+ function configuredProviderNames(host) {
8501
+ const names = /* @__PURE__ */ new Set();
8502
+ for (const runtime of host?.registry?.values() ?? []) for (const fiber of runtime.fibers) {
8503
+ const config = fiber.effectiveConfig;
8504
+ if (config && typeof config === "object" && "providerName" in config && typeof config.providerName === "string") names.add(config.providerName);
8505
+ }
8506
+ return [...names];
8507
+ }
8508
+ /** Official 0.2.2-rc.1 has registry ownership; master fcc0ad4 moved it into Subagent. */
8509
+ function activationOwned(subagents) {
8510
+ return typeof subagents?.rootOf === "function";
8511
+ }
8512
+ /** Only live registry instances can be adopted; persisted lineage is not authority. */
8513
+ function isRootAgent(agents, subagents, agent) {
8514
+ if (agents.get(agent.id) !== agent) return false;
8515
+ if (typeof agents.roots === "function") return agents.roots().includes(agent);
8516
+ try {
8517
+ return subagents.rootOf?.(agent) === agent;
8518
+ } catch {
8519
+ return false;
8520
+ }
8521
+ }
8522
+ function rootAgents(agents, subagents) {
8523
+ if (typeof agents.roots === "function") return agents.roots();
8524
+ return (agents.list?.() ?? []).filter((agent) => isRootAgent(agents, subagents, agent));
8525
+ }
8526
+ /** Persisted IDs keep completed internal sessions out of conversation automation after restart. */
8527
+ function isTaskRecord(id) {
8528
+ return /^mnemon-(?:task|run)-v1-[0-9a-f-]{36}$/.test(id);
8529
+ }
8530
+ function titleTask(agent, title) {
8531
+ (agent.ctx.get?.("sessionTitle"))?.rename?.(agent.session, title);
8532
+ }
8533
+ const creatingChild = new AsyncLocalStorage();
8534
+ /** Share one reservation across guard, continuation and composition wrappers, never across nested runs. */
8535
+ function withReservedChild(parent, operation, fresh = false) {
8536
+ const current = creatingChild.getStore();
8537
+ if (!fresh && current?.parent === parent && !current.claimed) return operation();
8538
+ return creatingChild.run({
8539
+ parent,
8540
+ id: `mnemon-run-v1-${randomUUID()}`
8541
+ }, operation);
8542
+ }
8543
+ function reservedChildId(parent) {
8544
+ const current = creatingChild.getStore();
8545
+ return current?.parent === parent && !current.claimed ? current.id : void 0;
8546
+ }
8547
+ function claimReservedChild(parent) {
8548
+ const current = creatingChild.getStore();
8549
+ if (current?.parent !== parent || current.claimed) return void 0;
8550
+ current.claimed = true;
8551
+ return current.id;
8552
+ }
8553
+ function isCreatingChild(agents, child) {
8554
+ const current = creatingChild.getStore();
8555
+ return current !== void 0 && isReservedChild(agents, current.parent, child);
8556
+ }
8557
+ /** A lineage string or same-tree relationship is not a creation capability. */
8558
+ function isReservedChild(agents, parent, child) {
8559
+ const current = creatingChild.getStore();
8560
+ if (current?.parent !== parent || !current.claimed || current.id !== child.id || agents?.get?.(parent.id) !== parent || agents.get(child.id) !== child || current.child !== void 0 && current.child !== child) return false;
8561
+ current.child = child;
8562
+ return true;
8563
+ }
8564
+ //#endregion
8464
8565
  //#region src/host/review-tools.ts
8465
8566
  const startingChild$1 = new AsyncLocalStorage();
8466
8567
  /**
@@ -8470,7 +8571,11 @@ const startingChild$1 = new AsyncLocalStorage();
8470
8571
  * still requires startGuardedReview; it never removes another plugin's policy.
8471
8572
  */
8472
8573
  function idleReviewBlockReason(parent, agentTeams = "pause") {
8473
- return agentTeams !== "scoped" && parent.ctx?.get?.("agentTeams") !== void 0 && parent.ctx.tools?.get?.("spawn_teammate", parent) !== void 0 ? "agent-team" : void 0;
8574
+ if (agentTeams === "scoped") return void 0;
8575
+ const teams = parent.ctx?.get?.("agentTeams");
8576
+ if (teams === void 0) return void 0;
8577
+ const tools = parent.ctx.tools;
8578
+ return tools?.get?.("spawn_teammate", parent) !== void 0 || typeof teams?.isEnabled === "function" && teams.isEnabled(parent) && tools?.get?.("team_task_create", parent) !== void 0 ? "agent-team" : void 0;
8474
8579
  }
8475
8580
  /** The two writes an idle review can make, one per layer; both are rows of the tool table. */
8476
8581
  const DOCUMENT_WRITE = "mnemon_document_create";
@@ -8522,20 +8627,24 @@ function startGuardedReview(host, parent, toolNames, start, published, policy) {
8522
8627
  async function startGuardedChild(host, parent, start, guard, options) {
8523
8628
  const { purpose, published } = options;
8524
8629
  const agents = host.agents;
8525
- if (typeof agents?.isOwnedBy !== "function") {
8630
+ const reserved = activationOwned(host.subagents);
8631
+ if (reserved && typeof agents?.get !== "function") throw new Error(`Mnemon ${purpose} requires exact live Agent lookup`);
8632
+ if (!reserved && typeof agents?.isOwnedBy !== "function") {
8526
8633
  if (options.optional === true) return start();
8527
8634
  throw new Error(`Mnemon ${purpose} requires DSH Agent ownership and scoped tool guard support`);
8528
8635
  }
8529
8636
  const pending = Symbol(`Mnemon ${purpose} publication`);
8530
8637
  const guards = /* @__PURE__ */ new Map();
8531
8638
  let attachmentError;
8639
+ let expectedChildId;
8532
8640
  let run;
8533
8641
  const listener = host.on("agent/created", (({ agent }) => {
8534
- if (startingChild$1.getStore() !== pending || !agents.isOwnedBy(agent.id, parent)) return;
8642
+ if (startingChild$1.getStore() !== pending) return;
8643
+ if (reserved ? agent.id !== expectedChildId || !isReservedChild(agents, parent, agent) : !agents.isOwnedBy(agent.id, parent)) return;
8535
8644
  try {
8536
8645
  const tools = agent.ctx?.tools;
8537
8646
  if (typeof tools?.guard !== "function") {
8538
- if (options.optional === true) return;
8647
+ if (options.optional === true && !reserved) return;
8539
8648
  throw new Error(`Mnemon ${purpose} requires DSH scoped tool guard support`);
8540
8649
  }
8541
8650
  const dispose = tools.guard(guard);
@@ -8563,13 +8672,16 @@ async function startGuardedChild(host, parent, start, guard, options) {
8563
8672
  if (failed.length) throw new AggregateError(failed.map((outcome) => outcome.reason), `Mnemon ${purpose} guard cleanup failed`);
8564
8673
  };
8565
8674
  try {
8566
- run = await startingChild$1.run(pending, start);
8675
+ run = await startingChild$1.run(pending, () => reserved ? withReservedChild(parent, () => {
8676
+ expectedChildId = reservedChildId(parent);
8677
+ return start();
8678
+ }, true) : start());
8567
8679
  if (attachmentError !== void 0) throw attachmentError;
8568
8680
  const runId = run.id;
8569
8681
  const child = run.localAgent ?? [...guards.keys()].find((agent) => agent.id === runId);
8570
8682
  if (child === void 0 || !guards.has(child)) {
8571
- const local = run.localAgent ?? agents.get?.(runId);
8572
- if (options.optional === true && guards.size === 0 && typeof local?.ctx?.tools?.guard !== "function") {
8683
+ const local = run.localAgent ?? agents?.get?.(runId);
8684
+ if (!reserved && options.optional === true && guards.size === 0 && typeof local?.ctx?.tools?.guard !== "function") {
8573
8685
  await closeObserver();
8574
8686
  return run;
8575
8687
  }
@@ -8604,13 +8716,13 @@ async function startGuardedChild(host, parent, start, guard, options) {
8604
8716
  }
8605
8717
  //#endregion
8606
8718
  //#region src/host/review-checkpoint.ts
8607
- function record$2(value) {
8719
+ function record$3(value) {
8608
8720
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
8609
8721
  }
8610
8722
  function textContent(content, toolResult = false) {
8611
8723
  if (!Array.isArray(content)) return "";
8612
8724
  return content.map((block) => {
8613
- const value = record$2(block);
8725
+ const value = record$3(block);
8614
8726
  if (value.type === "text" && typeof value.text === "string") return value.text;
8615
8727
  if (toolResult && value.type === "tool-result" && value.isError !== true) return textContent(value.content);
8616
8728
  return "";
@@ -8634,8 +8746,8 @@ function reviewCheckpoint(session, maxChars) {
8634
8746
  "assistant/message",
8635
8747
  "tool/result"
8636
8748
  ].includes(event.type)) continue;
8637
- const message = record$2(event.type === "user/message" ? event.data : event.data.message);
8638
- const source = record$2(message.source);
8749
+ const message = record$3(event.type === "user/message" ? event.data : event.data.message);
8750
+ const source = record$3(message.source);
8639
8751
  if (event.type === "user/message" && source.kind !== "user") continue;
8640
8752
  const text = textContent(message.content, event.type === "tool/result");
8641
8753
  if (!text) continue;
@@ -8649,8 +8761,14 @@ function reviewCheckpoint(session, maxChars) {
8649
8761
  //#endregion
8650
8762
  //#region src/host/plugin-message.ts
8651
8763
  const MNEMON_PLUGIN_SOURCE = "dsh-mnemon";
8764
+ /** Includes the published V3 wrapper and its official V4 migration. */
8765
+ function isMnemonMessageSource(source) {
8766
+ if (typeof source !== "object" || source === null) return false;
8767
+ const { kind, plugin } = source;
8768
+ return kind === "dsh-mnemon" || kind === `plugin:dsh-mnemon` || kind === "plugin" && plugin === "dsh-mnemon";
8769
+ }
8652
8770
  /** A user-role message this plugin adds to a step, marked with its own source. */
8653
- function createPluginMessage(text, form) {
8771
+ function createPluginMessage(text, form, snapshot) {
8654
8772
  return structuredClone({
8655
8773
  id: randomUUID(),
8656
8774
  role: "user",
@@ -8660,7 +8778,8 @@ function createPluginMessage(text, form) {
8660
8778
  }],
8661
8779
  source: {
8662
8780
  kind: MNEMON_PLUGIN_SOURCE,
8663
- form
8781
+ form,
8782
+ ...snapshot === void 0 ? {} : { snapshot }
8664
8783
  }
8665
8784
  });
8666
8785
  }
@@ -8719,10 +8838,13 @@ function continueAfterRefusal(agent) {
8719
8838
  * that does not report creation leaves the child as it was.
8720
8839
  */
8721
8840
  async function startWithContinuationTurns(host, parent, start) {
8841
+ const reserved = activationOwned(host.subagents);
8722
8842
  const pending = Symbol("Mnemon delegated start");
8723
8843
  const stops = [];
8844
+ let expectedChildId;
8724
8845
  const listener = host.on("agent/created", (({ agent }) => {
8725
8846
  if (startingChild.getStore() !== pending) return;
8847
+ if (reserved && (agent.id !== expectedChildId || !isReservedChild(host.agents, parent, agent))) return;
8726
8848
  if (typeof host.agents?.isOwnedBy === "function" && !host.agents.isOwnedBy(agent.id, parent)) return;
8727
8849
  for (const stop of continueAfterRefusal(agent)) if (typeof stop === "function") stops.push(stop);
8728
8850
  }));
@@ -8731,7 +8853,10 @@ async function startWithContinuationTurns(host, parent, start) {
8731
8853
  };
8732
8854
  let run;
8733
8855
  try {
8734
- run = await startingChild.run(pending, start);
8856
+ run = await startingChild.run(pending, () => reserved ? withReservedChild(parent, () => {
8857
+ expectedChildId = reservedChildId(parent);
8858
+ return start();
8859
+ }) : start());
8735
8860
  } catch (error) {
8736
8861
  await release();
8737
8862
  throw error;
@@ -8790,6 +8915,7 @@ function childComposition(subagents, provider) {
8790
8915
  * step, which needs `publications`.
8791
8916
  */
8792
8917
  async function startSubagent(subagents, agents, provider, request, publications) {
8918
+ if (typeof subagents.startActivation === "function" && reservedChildId(request.parent) === void 0) return withReservedChild(request.parent, () => startSubagent(subagents, agents, provider, request, publications));
8793
8919
  const { toolFilter, persona, ...task } = request;
8794
8920
  if (toolFilter === void 0 && persona === void 0 || childComposition(subagents, subagents.getProvider(provider)) !== "publication") return startOnHost(subagents, agents, provider, request);
8795
8921
  if (publications === void 0) throw new Error("This DSH takes no subagent persona or tool filter at start, and dsh-mnemon cannot observe the child it publishes");
@@ -8797,14 +8923,26 @@ async function startSubagent(subagents, agents, provider, request, publications)
8797
8923
  ...toolFilter === void 0 ? {} : { toolFilter },
8798
8924
  ...persona === void 0 ? {} : { persona }
8799
8925
  };
8800
- return startComposed(publications, agents, request.parent, composition, () => startOnHost(subagents, agents, provider, task));
8926
+ return startComposed(publications, agents, request.parent, composition, () => startOnHost(subagents, agents, provider, task), activationOwned(subagents));
8801
8927
  }
8802
8928
  async function startOnHost(subagents, agents, provider, request) {
8803
8929
  if (typeof subagents.startActivation === "function") {
8804
8930
  const { label, signal, ...task } = request;
8805
- const activation = await subagents.startActivation({
8931
+ const modern = activationOwned(subagents);
8932
+ const childId = claimReservedChild(request.parent);
8933
+ if (modern && (childId === void 0 || agents?.get?.(request.parent.id) !== request.parent)) throw new Error("Mnemon requires an exact live parent and reserved child identity on an activation-owned DSH");
8934
+ const startCurrent = subagents.startActivation;
8935
+ const activation = modern ? await startCurrent.call(subagents, {
8936
+ ...task,
8806
8937
  provider,
8807
8938
  label,
8939
+ signal,
8940
+ childId,
8941
+ delivery: "caller"
8942
+ }) : await subagents.startActivation({
8943
+ provider,
8944
+ label,
8945
+ ...childId === void 0 ? {} : { childId },
8808
8946
  request: task,
8809
8947
  signal,
8810
8948
  delivery: "caller"
@@ -8818,7 +8956,9 @@ async function startOnHost(subagents, agents, provider, request) {
8818
8956
  activation.result.then(release, release);
8819
8957
  if (signal.aborted) stop();
8820
8958
  const child = agents?.get?.(activation.childId);
8821
- const localAgent = child !== void 0 && (typeof agents?.isOwnedBy !== "function" || agents.isOwnedBy(child.id, request.parent)) ? child : void 0;
8959
+ if (modern && (activation.childId !== childId || child === void 0 || !isReservedChild(agents, request.parent, child))) throw new Error("DSH did not return the reserved live Mnemon child");
8960
+ if (modern && child !== void 0) titleTask(child, label.startsWith("Mnemon ") ? label : `Mnemon · ${label}`);
8961
+ const localAgent = child !== void 0 && (modern || typeof agents?.isOwnedBy !== "function" || agents.isOwnedBy(child.id, request.parent)) ? child : void 0;
8822
8962
  return {
8823
8963
  id: activation.childId,
8824
8964
  ...localAgent === void 0 ? {} : { localAgent },
@@ -8858,12 +8998,14 @@ function compose(agent, { toolFilter, persona }) {
8858
8998
  * check confirms the parent. A failed composition makes DSH roll the child
8859
8999
  * back; a child that was not seen published is disposed and refused.
8860
9000
  */
8861
- async function startComposed(publications, agents, parent, composition, start) {
9001
+ async function startComposed(publications, agents, parent, composition, start, reserved = false) {
8862
9002
  const pending = Symbol("Mnemon child composition");
9003
+ const expectedChildId = reserved ? reservedChildId(parent) : void 0;
8863
9004
  const composed = /* @__PURE__ */ new Set();
8864
9005
  let failure;
8865
9006
  const listener = publications.on("agent/created", (({ agent }) => {
8866
9007
  if (composingChild.getStore() !== pending) return;
9008
+ if (reserved && (agent.id !== expectedChildId || !isReservedChild(agents, parent, agent))) return;
8867
9009
  if (typeof agents?.isOwnedBy === "function" && !agents.isOwnedBy(agent.id, parent)) return;
8868
9010
  try {
8869
9011
  compose(agent, composition);
@@ -10332,6 +10474,7 @@ var AgentMemoryTurn = class {
10332
10474
  agent;
10333
10475
  runtime;
10334
10476
  delegation;
10477
+ independent;
10335
10478
  pinned;
10336
10479
  /** A turn that runs without memory because no composition was serving when it began. */
10337
10480
  unavailable;
@@ -10342,10 +10485,11 @@ var AgentMemoryTurn = class {
10342
10485
  lastInspection;
10343
10486
  lastWorkspace;
10344
10487
  releaseDelegation;
10345
- constructor(agent, runtime, delegation) {
10488
+ constructor(agent, runtime, delegation, independent = false) {
10346
10489
  this.agent = agent;
10347
10490
  this.runtime = runtime;
10348
10491
  this.delegation = delegation;
10492
+ this.independent = independent;
10349
10493
  if (delegation === void 0) return;
10350
10494
  this.releaseDelegation = runtime.executions.retain(agent, delegation);
10351
10495
  }
@@ -10437,7 +10581,7 @@ var AgentMemoryTurn = class {
10437
10581
  }
10438
10582
  }
10439
10583
  async pin(turn, generation, signal) {
10440
- const execution = await this.runtime.executions.turn(this.agent, turn, signal, this.delegation);
10584
+ const execution = await this.runtime.executions.turn(this.agent, turn, signal, this.delegation, this.independent);
10441
10585
  const { graph, context } = execution;
10442
10586
  try {
10443
10587
  if (this.closed || this.generation !== generation) throw new Error("memory turn ended during View preparation");
@@ -10581,6 +10725,136 @@ var AutomaticReviewAdmission = class {
10581
10725
  }
10582
10726
  };
10583
10727
  //#endregion
10728
+ //#region src/host/subagent-errors.ts
10729
+ /** A provider stop, kept separate from schema, startup and cancellation errors. */
10730
+ var SubagentStoppedError = class extends Error {
10731
+ stopReason;
10732
+ maxTokens;
10733
+ constructor(stopReason, maxTokens, detail) {
10734
+ super(`memory subagent stopped with ${stopReason}${detail === void 0 ? "" : `: ${detail}`}`);
10735
+ this.stopReason = stopReason;
10736
+ this.maxTokens = maxTokens;
10737
+ this.name = "SubagentStoppedError";
10738
+ }
10739
+ };
10740
+ //#endregion
10741
+ //#region src/host/task-continuation.ts
10742
+ const FINISHED_TASK_REFUSAL = "This Mnemon task has finished. Send a new user message in this conversation to continue working with memory.";
10743
+ const CONTINUATION_GUIDANCE = "This is a new user request in a completed Mnemon task conversation. The previous task stays completed. Use the current memory View and ordinary memory tools for this request; require receipts for changes. Do not repeat the previous task or submit its expired completion request.";
10744
+ /** Older workers have no reserved ID: require their own Host descriptor and task contract. */
10745
+ function isLegacyMemoryTask(agent) {
10746
+ if (agent.session.header?.origin !== "subagent") return false;
10747
+ const events = agent.session.snapshotEvents(agent.session.inheritedEventCount ?? 0);
10748
+ const descriptor = events.findLastIndex((event) => event.type === "subagent/descriptor");
10749
+ if (descriptor < 0 || typeof events[descriptor].data.label !== "string" || !events[descriptor].data.label.startsWith("Mnemon ")) return false;
10750
+ const own = events.slice(descriptor + 1);
10751
+ const textOf = (value) => {
10752
+ return value?.content?.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n") ?? "";
10753
+ };
10754
+ const prompt = own.find((event) => event.type === "user/message" && (event.data.message ?? event.data).source?.kind === "user");
10755
+ if (prompt === void 0 || !/^\[dsh-mnemon:delegated [a-z-]+\](?:\n|$)/u.test(textOf(prompt.data.message ?? prompt.data))) return false;
10756
+ return own.some((event) => event.type === "system/message" && textOf(event.data.message).includes("Completion protocol: call `mnemon_subagent_result` exactly once with requestId `"));
10757
+ }
10758
+ /** A resumed task receives memory authority only for a newly accepted human turn. */
10759
+ var TaskContinuation = class {
10760
+ agent;
10761
+ runtime;
10762
+ liveAgent;
10763
+ memory;
10764
+ authorizedTurn;
10765
+ stops = [];
10766
+ views = /* @__PURE__ */ new Map();
10767
+ closed = false;
10768
+ constructor(agent, runtime, liveAgent) {
10769
+ this.agent = agent;
10770
+ this.runtime = runtime;
10771
+ this.liveAgent = liveAgent;
10772
+ this.memory = new AgentMemoryTurn(agent, runtime, void 0, true);
10773
+ }
10774
+ start() {
10775
+ try {
10776
+ const guard = this.agent.ctx.tools?.guard?.((execution) => this.guard(execution));
10777
+ if (typeof guard !== "function") throw new Error("Task continuation requires a scoped memory tool guard");
10778
+ this.stops.push(guard);
10779
+ this.stops.push(this.agent.ctx.on("agent/pre-step", ((step, next) => this.preStep(step, next)), { prepend: true }));
10780
+ this.stops.push(this.agent.ctx.on("system-prompt/assemble", (async (_assembly, context, next) => {
10781
+ const assembly = await next();
10782
+ if (context.agent !== void 0 && context.agent !== this.agent) return assembly;
10783
+ const pin = this.memory.current;
10784
+ return applyMemoryViewGuidance(assembly, pin?.context.view, pin?.graph.config.routingGuidance);
10785
+ })));
10786
+ this.stops.push(this.agent.ctx.on("session/event", ((session, event) => {
10787
+ if (session === this.agent.session && event.type === "turn/end" && event.data.turn === this.authorizedTurn) this.end();
10788
+ })));
10789
+ return () => this.dispose();
10790
+ } catch (error) {
10791
+ this.dispose();
10792
+ throw error;
10793
+ }
10794
+ }
10795
+ ownsHumanTurn(actor) {
10796
+ return !this.closed && actor === this.agent && this.liveAgent(actor.id) === actor && this.authorizedTurn !== void 0 && this.memory.current?.turn === this.authorizedTurn && openAgentTurn(actor) === this.authorizedTurn;
10797
+ }
10798
+ guard(execution) {
10799
+ if (!execution.name?.startsWith("mnemon_")) return void 0;
10800
+ const actor = execution.agent;
10801
+ if (this.closed || actor === void 0 || this.liveAgent(actor.id) !== actor) return FINISHED_TASK_REFUSAL;
10802
+ if (actor !== this.agent) {
10803
+ try {
10804
+ const graph = this.runtime.forAgent(actor);
10805
+ const pin = graph.composableTurns.activeTurn(actor.id);
10806
+ if (pin !== void 0 && this.views.get(pin.view.id) === graph) return void 0;
10807
+ } catch {}
10808
+ return FINISHED_TASK_REFUSAL;
10809
+ }
10810
+ if (this.authorizedTurn === void 0) return FINISHED_TASK_REFUSAL;
10811
+ if (execution.name === "mnemon_subagent_result") return "The previous Mnemon task result is already settled. Use ordinary memory tools for the new user request.";
10812
+ if (this.memory.current?.turn !== this.authorizedTurn) return this.memory.unavailableReason ?? "Memory is unavailable for this request.";
10813
+ }
10814
+ async preStep(step, next) {
10815
+ const decision = await next();
10816
+ if (step.agent !== this.agent) return decision;
10817
+ if (decision.kind === "reject" || step.signal.aborted || this.closed || this.liveAgent(this.agent.id) !== this.agent) {
10818
+ this.end();
10819
+ return decision;
10820
+ }
10821
+ const starting = this.authorizedTurn !== step.turn;
10822
+ if (starting) this.end();
10823
+ if (decision.messages.some((message) => message.source.kind === "user" && message.content.some((block) => block.type !== "text" || (block.text?.trim().length ?? 0) > 0))) this.authorizedTurn = step.turn;
10824
+ if (this.authorizedTurn !== step.turn || openAgentTurn(this.agent) !== step.turn) return decision;
10825
+ try {
10826
+ await this.memory.begin(step.turn, step.signal);
10827
+ } catch (error) {
10828
+ this.end();
10829
+ throw error;
10830
+ }
10831
+ const pin = this.memory.current;
10832
+ for (const [id, graph] of this.views) if (graph.composableTurns.get(id) === void 0) this.views.delete(id);
10833
+ if (pin !== void 0) this.views.set(pin.context.view.id, pin.graph);
10834
+ if (!starting && step.step !== 1) return decision;
10835
+ const wake = pin === void 0 ? "" : modelMemoryWake(pin.graph, pin.context).text;
10836
+ return {
10837
+ ...decision,
10838
+ messages: [...decision.messages, createPluginMessage(`${CONTINUATION_GUIDANCE}\n\n${wake}`, "instructions")]
10839
+ };
10840
+ }
10841
+ end() {
10842
+ this.authorizedTurn = void 0;
10843
+ this.memory.end();
10844
+ }
10845
+ dispose() {
10846
+ if (this.closed) return;
10847
+ this.closed = true;
10848
+ try {
10849
+ for (const stop of this.stops.splice(0).reverse()) stop();
10850
+ } finally {
10851
+ this.authorizedTurn = void 0;
10852
+ this.memory.dispose();
10853
+ this.views.clear();
10854
+ }
10855
+ }
10856
+ };
10857
+ //#endregion
10584
10858
  //#region src/host/subagent.ts
10585
10859
  function evidenceInsights(evidence) {
10586
10860
  return evidence.items.map((item) => {
@@ -10626,7 +10900,6 @@ const REVIEW_TOOLS = [
10626
10900
  const RESULT_TOOL_NAME = "mnemon_subagent_result";
10627
10901
  /** How many finished delegated children stay refused memory tools if DSH resumes one. */
10628
10902
  const FINISHED_CHILDREN = 1e3;
10629
- const FINISHED_CHILD_REFUSAL = "This Mnemon task has finished, so it no longer reads or changes memory. Reply without memory tools.";
10630
10903
  const RESULT_TOOL_INPUT_SCHEMA = {
10631
10904
  type: "object",
10632
10905
  properties: {
@@ -10763,8 +11036,13 @@ var MnemonSubagentCoordinator = class {
10763
11036
  resultRequests = /* @__PURE__ */ new Map();
10764
11037
  disposeResultTool;
10765
11038
  disposeResumeGuard;
11039
+ disposeTaskNotices;
10766
11040
  /** Delegated children whose run has ended, newest last, at most FINISHED_CHILDREN. */
10767
11041
  finishedChildren = /* @__PURE__ */ new Set();
11042
+ continuations = /* @__PURE__ */ new Map();
11043
+ continuationOwners = /* @__PURE__ */ new WeakMap();
11044
+ providerNames = /* @__PURE__ */ new Set(["spawn", "fork"]);
11045
+ providerObservers = [];
10768
11046
  disposed = false;
10769
11047
  reviewAdmission = new AutomaticReviewAdmission();
10770
11048
  constructor(subagents, runtimeSource, resultRuntime, taskAgentModelResolver, runtimeMaintenanceMaxTokensResolver, runtimeMaintenanceTaskRunner) {
@@ -10799,11 +11077,39 @@ var MnemonSubagentCoordinator = class {
10799
11077
  });
10800
11078
  if (typeof registration !== "function") throw new Error("dsh-mnemon subagent result tool registration did not return a disposer");
10801
11079
  this.disposeResultTool = registration;
11080
+ const notices = resultRuntime.on("agent/pre-step", (async (step, next) => {
11081
+ const ignored = new Set(step.messages.filter((message) => {
11082
+ const source = message.source;
11083
+ return source.kind === "subagent-settled" && typeof source.senderSessionId === "string" && (isTaskRecord(source.senderSessionId) || this.finishedChildren.has(source.senderSessionId));
11084
+ }).map((message) => message.id));
11085
+ if (ignored.size === 0) return next();
11086
+ if (step.step === 1 && ignored.size === step.messages.length) return {
11087
+ kind: "enter",
11088
+ messages: []
11089
+ };
11090
+ const decision = await next();
11091
+ return decision.kind === "reject" ? decision : {
11092
+ ...decision,
11093
+ messages: decision.messages.filter((message) => !ignored.has(message.id))
11094
+ };
11095
+ }), { prepend: true });
11096
+ if (typeof notices === "function") this.disposeTaskNotices = notices;
11097
+ if (typeof subagents.list !== "function") {
11098
+ for (const stop of [resultRuntime.on("subagent/provider-added", ((provider) => {
11099
+ this.providerNames.add(provider.name);
11100
+ })), resultRuntime.on("subagent/provider-removed", ((name) => {
11101
+ this.providerNames.delete(name);
11102
+ }))]) if (typeof stop === "function") this.providerObservers.push(stop);
11103
+ }
10802
11104
  }
10803
11105
  dispose() {
10804
11106
  this.disposed = true;
10805
11107
  this.reviewAdmission.dispose();
10806
11108
  this.resultRequests.clear();
11109
+ this.disposeTaskNotices?.();
11110
+ this.disposeTaskNotices = void 0;
11111
+ for (const stop of [...this.continuations.values()].reverse()) stop();
11112
+ for (const stop of this.providerObservers.splice(0)) stop();
10807
11113
  const resumeGuard = this.disposeResumeGuard;
10808
11114
  this.disposeResumeGuard = void 0;
10809
11115
  resumeGuard?.();
@@ -10814,10 +11120,68 @@ var MnemonSubagentCoordinator = class {
10814
11120
  snapshot() {
10815
11121
  return { ...this.counters };
10816
11122
  }
11123
+ isFinishedTask(id, agent) {
11124
+ if (this.finishedChildren.has(id)) return true;
11125
+ if (agent === void 0 || agent.id !== id || !isLegacyMemoryTask(agent)) return false;
11126
+ this.finished(id);
11127
+ return true;
11128
+ }
11129
+ isUserContinuation(agent) {
11130
+ return this.continuationOwners.get(agent)?.ownsHumanTurn(agent) === true;
11131
+ }
11132
+ delegationLimit(parent) {
11133
+ if (!this.isUserContinuation(parent)) return 1;
11134
+ const depths = [parent.session.header?.delegationDepth ?? 0, parent.options?.subagentDepth ?? 0];
11135
+ if (depths.some((depth) => !Number.isSafeInteger(depth) || depth < 0 || Object.is(depth, -0) || depth === Number.MAX_SAFE_INTEGER)) throw new Error("Cannot determine a safe delegation depth for this continued task");
11136
+ return Math.max(...depths) + 1;
11137
+ }
11138
+ /** User continuation is a fresh scoped turn, never revival of a delegated run. */
11139
+ continueTask(agent) {
11140
+ if (this.continuations.has(agent) || this.disposed) return;
11141
+ if (typeof agent.ctx.effect !== "function" || this.resultRuntime?.agents?.get === void 0) {
11142
+ agent.ctx.tools?.guard?.((execution) => execution.name?.startsWith("mnemon_") ? FINISHED_TASK_REFUSAL : void 0);
11143
+ return;
11144
+ }
11145
+ const continuation = new TaskContinuation(agent, this.runtimeSource, (id) => this.resultRuntime?.agents?.get?.(id));
11146
+ let dispose;
11147
+ dispose = agent.ctx.effect(() => {
11148
+ const stop = continuation.start();
11149
+ this.continuationOwners.set(agent, continuation);
11150
+ return () => {
11151
+ try {
11152
+ stop();
11153
+ } finally {
11154
+ if (this.continuations.get(agent) === dispose) this.continuations.delete(agent);
11155
+ this.continuationOwners.delete(agent);
11156
+ }
11157
+ };
11158
+ }, "dsh-mnemon.task-continuation()");
11159
+ this.continuations.set(agent, dispose);
11160
+ }
11161
+ /** Spawn may use an independent tool scope, so transfer the turn guard explicitly. */
11162
+ inheritTaskContinuation(parent, child) {
11163
+ const owner = this.continuationOwners.get(parent);
11164
+ if (owner === void 0 || this.continuations.has(child)) return;
11165
+ let dispose;
11166
+ dispose = child.ctx.effect(() => {
11167
+ const stop = child.ctx.tools?.guard?.((execution) => owner.guard(execution));
11168
+ if (typeof stop !== "function") throw new Error("A continued task child requires a scoped memory tool guard");
11169
+ this.continuationOwners.set(child, owner);
11170
+ return () => {
11171
+ try {
11172
+ stop();
11173
+ } finally {
11174
+ if (this.continuations.get(child) === dispose) this.continuations.delete(child);
11175
+ this.continuationOwners.delete(child);
11176
+ }
11177
+ };
11178
+ }, "dsh-mnemon.task-delegation()");
11179
+ this.continuations.set(child, dispose);
11180
+ }
10817
11181
  /**
10818
11182
  * DSH 0.2.1 can resume a finished child from its saved state when another Agent sends it a
10819
- * message: with its persona and tools, but without the guard its run held. A delegated child
10820
- * never reads or changes memory once its run has ended (#387).
11183
+ * message: with its persona and tools, but without the guard its run held. Only a fresh
11184
+ * human turn may acquire new authority; automatic continuations remain denied (#387).
10821
11185
  */
10822
11186
  finished(id) {
10823
11187
  this.finishedChildren.delete(id);
@@ -10826,9 +11190,7 @@ var MnemonSubagentCoordinator = class {
10826
11190
  if (this.disposeResumeGuard !== void 0 || this.disposed || this.resultRuntime === void 0) return;
10827
11191
  const resumed = this.resultRuntime.on("agent/created", (({ agent }) => {
10828
11192
  if (!this.finishedChildren.has(agent.id)) return;
10829
- try {
10830
- agent.ctx?.tools?.guard?.((execution) => execution.name?.startsWith("mnemon_") === true ? FINISHED_CHILD_REFUSAL : void 0);
10831
- } catch {}
11193
+ this.continueTask(agent);
10832
11194
  }));
10833
11195
  if (typeof resumed === "function") this.disposeResumeGuard = resumed;
10834
11196
  }
@@ -11267,8 +11629,8 @@ var MnemonSubagentCoordinator = class {
11267
11629
  * One tool-less structured run for skill evolution: a review, a draft, a
11268
11630
  * backtest step or a case derivation. The child sees only the prompt.
11269
11631
  */
11270
- async skillTask(parent, label, prompt, schema, persona, signal, route) {
11271
- const { provider, runId, result } = await this.delegate(parent, "skill", label, prompt, [], schema, signal, "spawn", persona, void 0, void 0, route);
11632
+ async skillTask(parent, label, prompt, schema, persona, signal, route, maxTokens) {
11633
+ const { provider, runId, result } = await this.delegate(parent, "skill", label, prompt, [], schema, signal, "spawn", persona, void 0, void 0, route, void 0, maxTokens);
11272
11634
  return {
11273
11635
  provider,
11274
11636
  runId,
@@ -11429,9 +11791,9 @@ ${naturalRequest(request, target === void 0 ? REQUEST_LABELS : TARGETED_WRITE_LA
11429
11791
  execution.release();
11430
11792
  }
11431
11793
  }
11432
- async delegate(parent, operation, label, prompt, tools, outputSchema, signal, preferredProvider = "spawn", persona = WRITE_PERSONA, recovery, guard, route, admission) {
11794
+ async delegate(parent, operation, label, prompt, tools, outputSchema, signal, preferredProvider = "spawn", persona = WRITE_PERSONA, recovery, guard, route, admission, skillMaxTokens) {
11433
11795
  if (this.disposed) throw new Error("dsh-mnemon subagent coordinator is disposed");
11434
- const provider = this.provider(preferredProvider);
11796
+ const provider = this.provider(preferredProvider, this.runtimeSource.forAgent(parent).config.subagentProviderNames);
11435
11797
  assertDshOutputSchema(outputSchema);
11436
11798
  if (this.resultRuntime === void 0) throw new Error("dsh-mnemon subagent result tool runtime is unavailable");
11437
11799
  const resultToolName = RESULT_TOOL_NAME;
@@ -11537,7 +11899,7 @@ ${naturalRequest(request, target === void 0 ? REQUEST_LABELS : TARGETED_WRITE_LA
11537
11899
  Completion protocol: call \`${resultToolName}\` exactly once with requestId \`${requestId}\` and result matching this JSON schema:
11538
11900
  ${JSON.stringify(outputSchema)}
11539
11901
  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.`;
11540
- const perOpMaxTokens = operation === "migration" || operation === "compaction" || operation === "reconcile" ? this.runtimeMaintenanceMaxTokensResolver?.() ?? 8192 : operation === "document-archive" ? 8192 : operation === "metadata-maintenance" ? 4096 : operation === "review" ? this.runtimeSource.config.idleReview.maxTokens : operation === "skill" ? 16384 : void 0;
11902
+ const perOpMaxTokens = operation === "migration" || operation === "compaction" || operation === "reconcile" ? this.runtimeMaintenanceMaxTokensResolver?.() ?? 8192 : operation === "document-archive" ? 8192 : operation === "metadata-maintenance" ? 4096 : operation === "review" ? this.runtimeSource.config.idleReview.maxTokens : operation === "skill" ? skillMaxTokens ?? 16384 : void 0;
11541
11903
  const fixed = route ?? this.taskAgentModelResolver?.();
11542
11904
  const baseAgentOptions = perOpMaxTokens === void 0 ? void 0 : { maxTokens: perOpMaxTokens };
11543
11905
  const resolvedAgentOptions = fixed === void 0 ? baseAgentOptions : {
@@ -11560,7 +11922,7 @@ This is the only completion channel for this run. Do not finish with a plain-tex
11560
11922
  parent,
11561
11923
  signal,
11562
11924
  ...resolvedAgentOptions === void 0 ? {} : { agentOptions: resolvedAgentOptions },
11563
- maxDepth: 1,
11925
+ maxDepth: this.delegationLimit(parent),
11564
11926
  toolFilter: { allow: [...this.availableTools(parent, tools), resultToolName] },
11565
11927
  persona: completionPersona
11566
11928
  }, host);
@@ -11576,11 +11938,11 @@ This is the only completion channel for this run. Do not finish with a plain-tex
11576
11938
  if (captured !== void 0 && captured.agentId !== activeRun.id) throw new Error("Mnemon subagent result was recorded by a different child");
11577
11939
  let structured = captured?.value ?? result.structured;
11578
11940
  if (structured === void 0 && result.stopReason === "completed") structured = recoverStructuredResult(recovery, committedReceipts.filter((receipt) => receipt.agentId === activeRun.id));
11579
- if (structured !== void 0) assertDshOutputValue(outputSchema, structured);
11580
11941
  if (result.stopReason !== "completed") {
11581
11942
  const detail = subagentFailureDetail(activeRun, result);
11582
- throw new Error(`memory subagent stopped with ${result.stopReason}${detail === void 0 ? "" : `: ${detail}`}`);
11943
+ throw new SubagentStoppedError(result.stopReason, perOpMaxTokens, detail);
11583
11944
  }
11945
+ if (structured !== void 0) assertDshOutputValue(outputSchema, structured);
11584
11946
  if (structured === void 0) throw new Error("memory subagent completed without recording its result");
11585
11947
  if (operation === "review" && [
11586
11948
  "failed",
@@ -11644,8 +12006,12 @@ This is the only completion channel for this run. Do not finish with a plain-tex
11644
12006
  this.counters.lastAt = (/* @__PURE__ */ new Date()).toISOString();
11645
12007
  return completed;
11646
12008
  }
11647
- provider(preferred) {
11648
- const names = this.subagents.list();
12009
+ provider(preferred, explicitNames = []) {
12010
+ const names = this.subagents.list?.() ?? [.../* @__PURE__ */ new Set([
12011
+ ...this.providerNames,
12012
+ ...configuredProviderNames(this.resultRuntime),
12013
+ ...explicitNames
12014
+ ])].filter((name) => this.subagents.getProvider(name) !== void 0);
11649
12015
  const compatible = (name) => {
11650
12016
  const provider = this.subagents.getProvider(name);
11651
12017
  return provider?.capabilities.depthLimit === true && childComposition(this.subagents, provider) !== void 0;
@@ -11976,6 +12342,192 @@ var TurnActivityProjection = class {
11976
12342
  }
11977
12343
  };
11978
12344
  //#endregion
12345
+ //#region src/host/memory-snapshot-state.ts
12346
+ const digest = (value) => createHash("sha256").update(canonicalMemoryJson(value)).digest("hex");
12347
+ const hash$1 = (value) => typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
12348
+ const record$2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12349
+ function readMemorySnapshotState(value) {
12350
+ if (!record$2(value) || value.version !== 1 || !hash$1(value.boundary) || !Array.isArray(value.entries) || !value.entries.every(hash$1)) return void 0;
12351
+ return {
12352
+ version: 1,
12353
+ boundary: value.boundary,
12354
+ entries: [...value.entries]
12355
+ };
12356
+ }
12357
+ /**
12358
+ * Only Runtime's Source-owned, complete-projection provenance permits additive
12359
+ * history. Unknown Sources and opaque grants are compared in full. Do not use
12360
+ * View ids or source revisions here: ordinary Runtime writes change both.
12361
+ */
12362
+ function memorySnapshotState(view) {
12363
+ if (view === void 0) return void 0;
12364
+ const entries = [];
12365
+ const projection = view.projection.map((fragment) => {
12366
+ const provenance = fragment.provenance;
12367
+ const snapshot = record$2(provenance) ? provenance.snapshot : void 0;
12368
+ if (!record$2(provenance) || provenance.sourceTypeId !== "runtime" || !record$2(snapshot) || snapshot.version !== 1 || !hash$1(snapshot.scope) || !Array.isArray(snapshot.entries) || !snapshot.entries.every(hash$1)) return fragment;
12369
+ entries.push(...snapshot.entries.map((entry) => digest([
12370
+ fragment.sourceInstanceKey,
12371
+ fragment.id,
12372
+ entry
12373
+ ])));
12374
+ const { text: _text, revision: _revision, ...identity } = fragment;
12375
+ return {
12376
+ ...identity,
12377
+ provenance: {
12378
+ ...provenance,
12379
+ snapshot: {
12380
+ version: 1,
12381
+ scope: snapshot.scope
12382
+ }
12383
+ }
12384
+ };
12385
+ });
12386
+ return {
12387
+ version: 1,
12388
+ boundary: digest({
12389
+ generation: view.runtimeGeneration,
12390
+ scope: view.scope,
12391
+ strategy: [
12392
+ view.strategyInstanceKey,
12393
+ view.strategyTypeId,
12394
+ view.strategyExtensions ?? []
12395
+ ],
12396
+ projection,
12397
+ routes: view.routes,
12398
+ actions: view.actionOffers,
12399
+ grants: view.readGrants,
12400
+ guidance: view.guidance ?? null,
12401
+ diagnostics: view.diagnostics ?? []
12402
+ }),
12403
+ entries: entries.sort()
12404
+ };
12405
+ }
12406
+ /** A missing old occurrence is a deletion/correction, even with duplicate facts. */
12407
+ function retainsSnapshotEntries(previous, current) {
12408
+ const counts = /* @__PURE__ */ new Map();
12409
+ for (const entry of current) counts.set(entry, (counts.get(entry) ?? 0) + 1);
12410
+ for (const entry of previous) {
12411
+ const count = counts.get(entry) ?? 0;
12412
+ if (count === 0) return false;
12413
+ counts.set(entry, count - 1);
12414
+ }
12415
+ return true;
12416
+ }
12417
+ //#endregion
12418
+ //#region src/host/memory-snapshot.ts
12419
+ const MAX_SNAPSHOTS = 4;
12420
+ const MAX_SNAPSHOT_BYTES = 65536;
12421
+ const LEGACY_RUNTIME_SNAPSHOT = /^MNEMON RUNTIME MEMORY SNAPSHOT\n[^\n]+(?:\n[^\n]+)*\n\n(?:USER\.md \(user profile\)|Contents of USER\.md \(user profile; entries: \d+; UTF-8 bytes: \d+\/\d+\))\n<runtime-memory-file name="USER.md">\n(?:(?!\n\nMNEMON (?:RUNTIME MEMORY SNAPSHOT|VIEW ))[\s\S])*?\n<\/runtime-memory-file>\n\n(?:MEMORY\.md \(working reference\)|Contents of MEMORY\.md \(working reference; entries: \d+; UTF-8 bytes: \d+\/\d+\))\n<runtime-memory-file name="MEMORY.md">\n(?:(?!\n\nMNEMON (?:RUNTIME MEMORY SNAPSHOT|VIEW ))[\s\S])*?\n<\/runtime-memory-file>(?:$|\n\n)/u;
12422
+ const TRUNCATED_RUNTIME_START = /^MNEMON RUNTIME MEMORY SNAPSHOT\n(?:Revision: [a-f0-9]{64}(?:\nGit branch: [^\r\n]+)?(?:\nMetadata lines are annotations; created\/updated are ages at projection in whole days \(future\/unknown for future\/invalid timestamps\)\. Current instructions win\. For old_text\/oldText, use entry content only\.)?\n\nContents of USER\.md \(user profile; entries: \d+; UTF-8 bytes: \d+\/\d+\)|Entry annotations default to importance=normal and ages=0d \(whole days at projection; future\/unknown for future\/invalid timestamps\)\. Current instructions win\. For old_text\/oldText, use entry content only\.(?:\nGit branch: [^\r\n]+)?\n\nUSER\.md \(user profile\))\n<runtime-memory-file name="USER.md">\n/u;
12423
+ const RUNTIME_WAKE_SEPARATOR = /\n\n(?:MNEMON RUNTIME MEMORY SNAPSHOT\n|MNEMON VIEW TOOLS \(available in this View\): |MNEMON VIEW ROUTES \(quoted routing data; use mnemon_view_route or mnemon_view_action by exact id\): |MNEMON VIEW AVAILABILITY \(quoted diagnostics; unavailable Sources are not evidence\): )/u;
12424
+ function isTruncatedRuntimeSnapshot(text) {
12425
+ const start = TRUNCATED_RUNTIME_START.exec(text);
12426
+ if (start === null) return false;
12427
+ const body = text.slice(start[0].length);
12428
+ const end = body.search(RUNTIME_WAKE_SEPARATOR);
12429
+ const first = end < 0 ? body : body.slice(0, end);
12430
+ if (!first.endsWith("…")) return false;
12431
+ const prefix = first.slice(0, -1);
12432
+ const userEnd = prefix.indexOf("\n</runtime-memory-file>");
12433
+ if (userEnd < 0) return true;
12434
+ const heading = start[0].includes("\nContents of USER.md ") ? [
12435
+ "\n\nContents of MEMORY.md (working reference; entries: ",
12436
+ /^\d+/u,
12437
+ "; UTF-8 bytes: ",
12438
+ /^\d+/u,
12439
+ "/",
12440
+ /^\d+/u,
12441
+ ")"
12442
+ ] : ["\n\nMEMORY.md (working reference)"];
12443
+ heading.push("\n<runtime-memory-file name=\"MEMORY.md\">\n");
12444
+ let rest = prefix.slice(userEnd + 23);
12445
+ for (const part of heading) {
12446
+ if (rest === "") return true;
12447
+ if (typeof part === "string") {
12448
+ if (part.startsWith(rest)) return true;
12449
+ if (!rest.startsWith(part)) return false;
12450
+ rest = rest.slice(part.length);
12451
+ } else {
12452
+ const match = part.exec(rest);
12453
+ if (match === null) return false;
12454
+ rest = rest.slice(match[0].length);
12455
+ }
12456
+ }
12457
+ return true;
12458
+ }
12459
+ /** Read only this producer's snapshot formats; ordinary recalls retain history. */
12460
+ function snapshotText(event) {
12461
+ if (event?.type !== "user/message" || event.data.role !== "user") return void 0;
12462
+ const source = event.data.source;
12463
+ if (!isMnemonMessageSource(source)) return void 0;
12464
+ const content = event.data.content;
12465
+ if (!Array.isArray(content) || content.length !== 1) return void 0;
12466
+ const block = content[0];
12467
+ if (block?.type !== "text" || typeof block.text !== "string") return void 0;
12468
+ if (source?.form === "memory-snapshot") return block.text;
12469
+ if (source?.form !== "recall" || !(LEGACY_RUNTIME_SNAPSHOT.test(block.text) || isTruncatedRuntimeSnapshot(block.text))) return void 0;
12470
+ return block.text;
12471
+ }
12472
+ /**
12473
+ * Prepare a snapshot and its retirements without writing before step/start.
12474
+ * Commit after step/start publication, before the Host builds its request.
12475
+ * Empty developer nodes are a public Session representation that derives no model
12476
+ * message. They contain no instructions, Source text, or tool changes. Each
12477
+ * replacement shadows one owned node and cites it; the append-only audit keeps
12478
+ * the original full message, without touching interleaved conversation.
12479
+ */
12480
+ function prepareMemorySnapshot(session, text, state) {
12481
+ const current = text?.trim() === "" ? void 0 : text;
12482
+ const snapshots = session.surface.nodes.flatMap((seq) => {
12483
+ const event = session.eventAt(seq);
12484
+ const text = snapshotText(event);
12485
+ const source = event?.data.source;
12486
+ return text === void 0 ? [] : [{
12487
+ seq,
12488
+ text,
12489
+ state: source?.form === "memory-snapshot" ? readMemorySnapshotState(source.snapshot) : void 0
12490
+ }];
12491
+ });
12492
+ const latest = snapshots.at(-1);
12493
+ const compatible = state !== void 0 && snapshots.every((snapshot) => snapshot.state?.boundary === state.boundary && retainsSnapshotEntries(snapshot.state.entries, state.entries));
12494
+ const unchanged = current !== void 0 && latest?.text === current;
12495
+ const count = snapshots.length + (unchanged ? 0 : 1);
12496
+ const bytes = snapshots.reduce((total, snapshot) => total + Buffer.byteLength(snapshot.text, "utf8"), 0) + (unchanged || current === void 0 ? 0 : Buffer.byteLength(current, "utf8"));
12497
+ const retained = current === void 0 ? [] : compatible && count <= MAX_SNAPSHOTS && (count <= 1 || bytes <= MAX_SNAPSHOT_BYTES) ? snapshots : state === void 0 && unchanged ? [latest] : [];
12498
+ const stale = snapshots.filter((snapshot) => !retained.includes(snapshot));
12499
+ return {
12500
+ message: current === void 0 || unchanged && retained.includes(latest) ? void 0 : createPluginMessage(current, "memory-snapshot", state),
12501
+ retire(turn, step) {
12502
+ const visible = new Set(session.surface.nodes);
12503
+ for (const { seq } of stale) {
12504
+ if (!visible.has(seq)) continue;
12505
+ if (session.append === void 0) throw new Error("DSH Session append is required to retire superseded memory snapshots");
12506
+ session.append("developer/message", {
12507
+ turn,
12508
+ step,
12509
+ message: {
12510
+ id: randomUUID(),
12511
+ role: "developer",
12512
+ content: [],
12513
+ source: {
12514
+ kind: MNEMON_PLUGIN_SOURCE,
12515
+ form: "memory-snapshot-retired"
12516
+ }
12517
+ }
12518
+ }, {
12519
+ surfaceOp: {
12520
+ op: "replace",
12521
+ startSeq: seq,
12522
+ endSeq: seq
12523
+ },
12524
+ sourceEventSeqs: [seq]
12525
+ });
12526
+ }
12527
+ }
12528
+ };
12529
+ }
12530
+ //#endregion
11979
12531
  //#region src/host/idle-review-watermark.ts
11980
12532
  const MAX_STATE_BYTES = 16384;
11981
12533
  const MAX_TIMESTAMP = 864e13;
@@ -12140,11 +12692,6 @@ function llmService(value) {
12140
12692
  if (typeof value !== "object" || value === null || !("listProviders" in value) || typeof value.listProviders !== "function" || !("listModels" in value) || typeof value.listModels !== "function") return void 0;
12141
12693
  return value;
12142
12694
  }
12143
- function isMnemonMessageSource(source) {
12144
- if (typeof source !== "object" || source === null) return false;
12145
- const { kind, plugin } = source;
12146
- return kind === "dsh-mnemon" || kind === `plugin:dsh-mnemon` || kind === "plugin" && plugin === "dsh-mnemon";
12147
- }
12148
12695
  /**
12149
12696
  * Whether an event is a durable user message this plugin produced.
12150
12697
  */
@@ -12224,16 +12771,9 @@ var MnemonAgentLifecycle = class {
12224
12771
  primePending = true;
12225
12772
  startSource;
12226
12773
  guidedTurns = /* @__PURE__ */ new Set();
12774
+ pendingSnapshot;
12227
12775
  memoryActivity = new TurnActivityProjection();
12228
12776
  turnActivity = /* @__PURE__ */ new Map();
12229
- /**
12230
- * Text of the runtime memory snapshot most recently injected as this
12231
- * plugin's own message. `.context()` used to get supersede-on-change for free
12232
- * from the host's runtime-context projection; carrying the snapshot as an own
12233
- * message means re-emitting on change is this plugin's responsibility, and
12234
- * the rendered text (which carries the revision digest) is the key.
12235
- */
12236
- injectedMemoryText;
12237
12777
  idleReviewTimer;
12238
12778
  reviewController;
12239
12779
  reviewRunning = false;
@@ -12279,6 +12819,10 @@ var MnemonAgentLifecycle = class {
12279
12819
  ];
12280
12820
  return () => {
12281
12821
  this.disposed = true;
12822
+ const pending = this.pendingSnapshot;
12823
+ this.pendingSnapshot = void 0;
12824
+ const started = pending === void 0 ? void 0 : this.agent.session.snapshotEvents().findLast((event) => event.type === "step/start");
12825
+ if (pending !== void 0 && started !== void 0 && eventTurn(started) === pending.turn && started.data.step === pending.step) this.queueSnapshotRetirement(pending);
12282
12826
  this.releaseView();
12283
12827
  this.cancelIdleReview(true);
12284
12828
  for (const dispose of disposers.reverse()) dispose();
@@ -12348,24 +12892,8 @@ var MnemonAgentLifecycle = class {
12348
12892
  for (const seq of this.agent.session.surface.nodes) if (isOwnUserMessageEvent(this.agent.session.eventAt(seq))) return true;
12349
12893
  return false;
12350
12894
  }
12351
- /**
12352
- * The runtime memory snapshot, as this plugin's own message, when it changed.
12353
- *
12354
- * `.context()` used to carry this inside the host's shared runtime-context
12355
- * projection. Carrying it here instead attributes it to dsh-mnemon and keeps
12356
- * a memory write from re-emitting other contributors' sections; supersede
12357
- * stays intact because the block is still a complete state replacing its
12358
- * predecessor, keyed on the rendered text's revision digest.
12359
- */
12360
- memorySnapshotMessage() {
12361
- const wake = this.memoryWake();
12362
- if (wake === void 0) return void 0;
12363
- const text = wake.text;
12364
- if (text.trim() === "" || text === this.injectedMemoryText) return void 0;
12365
- this.injectedMemoryText = text;
12366
- return createPluginMessage(text, "recall");
12367
- }
12368
12895
  async preStep(payload, next) {
12896
+ this.pendingSnapshot = void 0;
12369
12897
  if (payload.step === 1) this.cancelIdleReview(true);
12370
12898
  const decision = await next();
12371
12899
  if (decision.kind === "reject" || payload.signal.aborted) {
@@ -12373,12 +12901,19 @@ var MnemonAgentLifecycle = class {
12373
12901
  return decision;
12374
12902
  }
12375
12903
  if (!this.disposed && openAgentTurn(this.agent) === payload.turn) await this.memoryTurn?.begin(payload.turn, payload.signal);
12904
+ const update = prepareMemorySnapshot(this.agent.session, this.config.lifecycleEnabled ? this.memoryWake()?.text : void 0, this.config.lifecycleEnabled ? memorySnapshotState(this.memoryTurn?.current?.context.view) : void 0);
12905
+ this.pendingSnapshot = {
12906
+ turn: payload.turn,
12907
+ step: payload.step,
12908
+ signal: payload.signal,
12909
+ update
12910
+ };
12376
12911
  if (!this.config.lifecycleEnabled) return decision;
12377
12912
  if (this.config.writeEnabled && this.config.writebackMode === "guided") this.recordTurnMessages(payload.turn, decision.messages);
12378
12913
  if (payload.step !== 1) return decision;
12379
12914
  if (decision.messages.some((message) => isMnemonMessageSource(message.source))) return decision;
12380
12915
  if (decision.messages.length === 0) return decision;
12381
- const snapshot = this.memorySnapshotMessage();
12916
+ const snapshot = update.message;
12382
12917
  const withSnapshot = (messages) => snapshot === void 0 ? messages : [...messages, snapshot];
12383
12918
  if (this.primePending) {
12384
12919
  this.primePending = false;
@@ -12447,12 +12982,40 @@ var MnemonAgentLifecycle = class {
12447
12982
  if (admitted) this.scheduleIdleReview(payload.turn);
12448
12983
  }
12449
12984
  sessionEvent(session, event) {
12450
- if (session !== this.agent.session || event.type !== "turn/end") return;
12985
+ if (session !== this.agent.session) return;
12451
12986
  const turn = eventTurn(event);
12987
+ if (event.type === "step/start") {
12988
+ const pending = this.pendingSnapshot;
12989
+ if (pending === void 0 || pending.turn !== turn || pending.step !== event.data.step) return;
12990
+ this.pendingSnapshot = void 0;
12991
+ this.queueSnapshotRetirement(pending);
12992
+ return;
12993
+ }
12994
+ if (event.type !== "turn/end") return;
12995
+ if (this.pendingSnapshot?.turn === turn) this.pendingSnapshot = void 0;
12452
12996
  const current = this.memoryTurn?.current;
12453
12997
  if ((turn === void 0 || current?.turn !== turn ? void 0 : current) === void 0) return;
12454
12998
  this.releaseView(turn);
12455
12999
  }
13000
+ queueSnapshotRetirement(pending) {
13001
+ queueMicrotask(() => {
13002
+ if (pending.signal.aborted || openAgentTurn(this.agent) !== pending.turn) return;
13003
+ try {
13004
+ pending.update.retire(pending.turn, pending.step);
13005
+ } catch {
13006
+ const reason = "Memory snapshot checkpoint failed; model request cancelled.";
13007
+ this.counters.failures += 1;
13008
+ this.lastPhase = "error";
13009
+ this.lastAt = (/* @__PURE__ */ new Date()).toISOString();
13010
+ this.lastError = reason;
13011
+ this.agent.cancel({
13012
+ kind: "hook",
13013
+ reason
13014
+ }, { keepInbox: true });
13015
+ console.warn("[dsh-mnemon] Memory snapshot checkpoint failed; model request cancelled.");
13016
+ }
13017
+ });
13018
+ }
12456
13019
  scheduleIdleReview(turn) {
12457
13020
  this.cancelIdleReview(true);
12458
13021
  if (!this.restoreWatermark() || !this.idleReviewAllowed()) return;
@@ -12765,7 +13328,7 @@ var MnemonLifecycle = class {
12765
13328
  }
12766
13329
  /** Model work for skill evolution under a fresh task Agent, outside any conversation. */
12767
13330
  runSkillTask(workspaceRoot, signal, operation, route) {
12768
- return this.runTaskAgent("", workspaceRoot, signal, operation, route);
13331
+ return this.runTaskAgent("", workspaceRoot, signal, operation, route, "Mnemon · Skills");
12769
13332
  }
12770
13333
  /**
12771
13334
  * What one conversation did with skills, for the bars under its turns. A
@@ -12808,7 +13371,7 @@ var MnemonLifecycle = class {
12808
13371
  const stopCreated = this.ctx.on("agent/created", (({ agent, source }) => {
12809
13372
  this.install(agent, source);
12810
13373
  }));
12811
- for (const agent of this.ctx.agents.roots()) this.install(agent, "adopted");
13374
+ for (const agent of rootAgents(this.ctx.agents, this.ctx.subagents)) this.install(agent, "adopted");
12812
13375
  return () => {
12813
13376
  stopChanged?.();
12814
13377
  stopCreated();
@@ -12988,7 +13551,7 @@ var MnemonLifecycle = class {
12988
13551
  /** Synthesize a Web Agent Query without borrowing a conversation Agent or its history. */
12989
13552
  answerTask(sessionId, query, evidence, workspaceRoot, signal = new AbortController().signal) {
12990
13553
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
12991
- return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.answer(agent, query, evidence, signal));
13554
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.answer(agent, query, evidence, signal), void 0, "Mnemon · Ask Agent");
12992
13555
  }
12993
13556
  /** A write the user sent from the page: a space they picked that is switched off comes back on (#387). */
12994
13557
  remember(sessionId, request, signal = new AbortController().signal) {
@@ -13001,7 +13564,7 @@ var MnemonLifecycle = class {
13001
13564
  return this.coordinator.manageSource(graph, request);
13002
13565
  }
13003
13566
  runRuntimeMaintenanceTask(scope, signal, operation) {
13004
- return this.runTaskAgent(scope.sessionId ?? "", scope.workspaceId, signal, operation);
13567
+ return this.runTaskAgent(scope.sessionId ?? "", scope.workspaceId, signal, operation, void 0, "Mnemon · Memory maintenance");
13005
13568
  }
13006
13569
  /**
13007
13570
  * Read the merged memory and let a model propose what a human should review.
@@ -13030,12 +13593,12 @@ var MnemonLifecycle = class {
13030
13593
  mutateDocumentTask(sessionId, request, workspaceRoot, signal = new AbortController().signal) {
13031
13594
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
13032
13595
  if (root === void 0 || root.trim() === "") throw new Error("a selected DSH workspace is required to write a Mnemon Document");
13033
- return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.document(agent, request, signal));
13596
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.document(agent, request, signal), void 0, "Mnemon · Document");
13034
13597
  }
13035
13598
  archiveDocument(sessionId, id, workspaceRoot, signal = new AbortController().signal) {
13036
13599
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
13037
13600
  if (root === void 0 || root.trim() === "") throw new Error("a selected DSH workspace is required to archive a Mnemon Document");
13038
- return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.archiveDocument(agent, id, signal));
13601
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.archiveDocument(agent, id, signal), void 0, "Mnemon · Archive Document");
13039
13602
  }
13040
13603
  mutate(sessionId, operation, request, signal = new AbortController().signal) {
13041
13604
  return this.coordinator.write(this.liveAgent(sessionId), operation, request, signal);
@@ -13046,11 +13609,11 @@ var MnemonLifecycle = class {
13046
13609
  /** Choose a new Memory Space's Provider under a fresh task Agent, as Ask Agent does without a loaded conversation Agent. */
13047
13610
  placeProviderTask(sessionId, body, prepared, workspaceRoot, signal = new AbortController().signal) {
13048
13611
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
13049
- return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.placeProvider(agent, body, prepared, signal));
13612
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.placeProvider(agent, body, prepared, signal), void 0, "Mnemon · Choose memory provider");
13050
13613
  }
13051
13614
  maintainMetadata(sessionId, memoryBodyIds, workspaceRoot, signal = new AbortController().signal) {
13052
13615
  const root = workspaceRoot?.trim() || this.workspaceRoot(sessionId);
13053
- return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.maintainMetadata(agent, memoryBodyIds, signal));
13616
+ return this.runTaskAgent(sessionId, root, signal, (agent) => this.coordinator.maintainMetadata(agent, memoryBodyIds, signal), void 0, "Mnemon · Memory metadata");
13054
13617
  }
13055
13618
  async supervise(sessionId, content, idempotencyKey, signal = new AbortController().signal) {
13056
13619
  const normalizedSessionId = sessionId.trim();
@@ -13062,7 +13625,7 @@ var MnemonLifecycle = class {
13062
13625
  const normalizedSessionId = sessionId.trim();
13063
13626
  const root = workspaceRoot?.trim() || this.workspaceRoot(normalizedSessionId);
13064
13627
  const scopeKey = root === void 0 ? `task:${normalizedSessionId || "global"}` : `task:${resolve(root)}`;
13065
- return this.superviseResolved(scopeKey, normalizedSessionId, content, idempotencyKey, signal, (operation) => this.runTaskAgent(normalizedSessionId, root, signal, operation));
13628
+ return this.superviseResolved(scopeKey, normalizedSessionId, content, idempotencyKey, signal, (operation) => this.runTaskAgent(normalizedSessionId, root, signal, operation, void 0, "Mnemon · Save to memory"));
13066
13629
  }
13067
13630
  async superviseResolved(replayScope, responseSessionId, content, idempotencyKey, signal, withAgent) {
13068
13631
  if (!this.config.writeEnabled) throw new Error("dsh-mnemon is configured read-only (writeEnabled: false)");
@@ -13116,8 +13679,9 @@ var MnemonLifecycle = class {
13116
13679
  * is the explicit Web workbench scope, so LiveMnemonRuntime resolves the same
13117
13680
  * workspace graph without borrowing conversation history or ownership.
13118
13681
  */
13119
- async runTaskAgent(fallbackSessionId, workspaceRoot, signal, operation, route) {
13120
- const sessionId = randomUUID();
13682
+ async runTaskAgent(fallbackSessionId, workspaceRoot, signal, operation, route, title = "Mnemon · Background task") {
13683
+ const modern = activationOwned(this.ctx.subagents);
13684
+ const sessionId = `mnemon-task-v1-${randomUUID()}`;
13121
13685
  this.taskAgentIds.add(sessionId);
13122
13686
  let handle;
13123
13687
  let failure;
@@ -13128,6 +13692,7 @@ var MnemonLifecycle = class {
13128
13692
  ...creation,
13129
13693
  signal
13130
13694
  });
13695
+ if (modern) titleTask(handle.agent, title);
13131
13696
  if (route !== void 0) try {
13132
13697
  handle.agent[TASK_ROUTE] = route;
13133
13698
  } catch {}
@@ -13153,7 +13718,9 @@ var MnemonLifecycle = class {
13153
13718
  * a turn only while DSH's projection cache holds a record of it, so a finished task
13154
13719
  * could stay listed as an untitled Ungrouped conversation (#435). No `parentSession`
13155
13720
  * or `delegationDepth`: the task belongs to no conversation, and its children stay
13156
- * one level deep.
13721
+ * one level deep. Activation-owned master requires an ordinary root instead;
13722
+ * its persistent task ID and title identify the visible record without enrolling
13723
+ * it in conversation automation. Official rc.1 retains the hidden metadata.
13157
13724
  */
13158
13725
  async taskAgentCreation(fallbackSessionId, workspaceRoot, signal, route) {
13159
13726
  const baseOptions = this.taskAgentModelOptions(fallbackSessionId, workspaceRoot);
@@ -13171,7 +13738,7 @@ var MnemonLifecycle = class {
13171
13738
  const workingDirectory = workFrom === void 0 ? void 0 : this.ctx.get("workingDirectory");
13172
13739
  const meta = {
13173
13740
  cwd,
13174
- origin: "subagent",
13741
+ ...activationOwned(this.ctx.subagents) ? {} : { origin: "subagent" },
13175
13742
  ...presetId === void 0 ? {} : { agentPreset: presetId }
13176
13743
  };
13177
13744
  if (presets === void 0 && typeof workingDirectory?.set !== "function") return {
@@ -13309,11 +13876,15 @@ var MnemonLifecycle = class {
13309
13876
  }
13310
13877
  install(agent, source) {
13311
13878
  if (this.taskAgentIds.has(agent.id) || this.owners.has(agent) || this.children.has(agent)) return;
13879
+ if ((isTaskRecord(agent.id) || this.coordinator.isFinishedTask(agent.id, agent)) && !isCreatingChild(this.ctx.agents, agent)) {
13880
+ this.coordinator.continueTask(agent);
13881
+ return;
13882
+ }
13312
13883
  if (agent.session.header?.origin === "subagent") {
13313
13884
  this.installChild(agent);
13314
13885
  return;
13315
13886
  }
13316
- if (!this.ctx.agents.roots().includes(agent)) return;
13887
+ if (!isRootAgent(this.ctx.agents, this.ctx.subagents, agent)) return;
13317
13888
  const memoryTurn = this.runtimeSource === void 0 ? void 0 : new AgentMemoryTurn(agent, this.runtimeSource);
13318
13889
  const lifecycle = new MnemonAgentLifecycle(agent, this.coordinator, this.config, this.counters, source, memoryTurn, () => this.runtimeSource?.forAgent(agent).directory);
13319
13890
  let dispose;
@@ -13343,6 +13914,7 @@ var MnemonLifecycle = class {
13343
13914
  const parentId = agent.session.header?.parentSession?.trim();
13344
13915
  const parent = parentId === void 0 || parentId === "" ? void 0 : this.ctx.agents.get(parentId);
13345
13916
  if (runtime === void 0 || parent === void 0 || parent === agent) return;
13917
+ this.coordinator.inheritTaskContinuation(parent, agent);
13346
13918
  const parentMemory = this.memoryTurns.get(parent);
13347
13919
  let delegation;
13348
13920
  if (parentMemory !== void 0) delegation = parentMemory.delegate();
@@ -14569,12 +15141,15 @@ var SkillEvolution = class {
14569
15141
  const source = retryingSource(port);
14570
15142
  const pack = await source.read("evidence-pack", {
14571
15143
  ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId },
15144
+ ...input.draftId === void 0 ? {} : { draftId: input.draftId },
14572
15145
  ...input.skill === void 0 ? {} : { skill: input.skill }
14573
15146
  });
14574
- const previous = pack.suggestion?.draftId === void 0 ? (await source.read("drafts", {
15147
+ const previousId = input.draftId ?? pack.suggestion?.draftId;
15148
+ const previous = previousId === void 0 ? (await source.read("drafts", {
14575
15149
  skill: pack.skillName,
15150
+ ...pack.skill === void 0 ? {} : { path: pack.skill.path },
14576
15151
  ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId }
14577
- })).find((draft) => draft.suggestionId === input.suggestionId) : (await source.read("draft", { id: pack.suggestion.draftId })).draft;
15152
+ })).find((draft) => draft.suggestionId === input.suggestionId) : (await source.read("draft", { id: previousId })).draft;
14578
15153
  const targetRoot = pack.action === "update" ? pack.skill.root : newSkillRoot(this.host.settings(), source, await source.read("catalog"), input.targetRoot ?? previous?.targetRoot ?? pack.suggestion?.targetRoot);
14579
15154
  const guidance = [...previous?.guidance ?? [], ...input.guidance?.trim() ? [input.guidance.trim()] : []].slice(-8);
14580
15155
  if (input.suggestionId !== void 0) await source.mutate("suggestion-status", {
@@ -14606,12 +15181,33 @@ var SkillEvolution = class {
14606
15181
  const settings = this.host.settings();
14607
15182
  const { pack, targetRoot, guidance } = prepared;
14608
15183
  let prompt = skillDraftPrompt(pack, guidance, targetRoot);
15184
+ let maxTokens = settings.draftMaxTokens;
15185
+ const attemptedBudgets = [];
14609
15186
  let draft;
14610
15187
  for (let attempt = 0; draft === void 0; attempt++) {
14611
- const parsed = parseSkillDraft((await this.host.runTask(workspace, signal, (task) => this.host.skillTask(task, "Mnemon skill draft", prompt, SKILL_DRAFT_SCHEMA, SKILL_DRAFT_PERSONA, signal, input.route), input.route)).structured);
15188
+ signal.throwIfAborted();
15189
+ attemptedBudgets.push(maxTokens);
15190
+ let answer;
15191
+ try {
15192
+ answer = await this.host.runTask(workspace, signal, (task) => this.host.skillTask(task, "Mnemon skill draft", prompt, SKILL_DRAFT_SCHEMA, SKILL_DRAFT_PERSONA, signal, input.route, maxTokens), input.route);
15193
+ signal.throwIfAborted();
15194
+ } catch (error) {
15195
+ signal.throwIfAborted();
15196
+ if (error instanceof SubagentStoppedError && error.maxTokens !== void 0) attemptedBudgets[attemptedBudgets.length - 1] = error.maxTokens;
15197
+ if (error instanceof SubagentStoppedError && error.stopReason === "max-tokens" && attempt === 0 && settings.draftRetryMaxTokens > maxTokens) {
15198
+ maxTokens = settings.draftRetryMaxTokens;
15199
+ prompt += "\n\nThe previous attempt exhausted its output budget and was discarded. Return a complete SKILL.md from the beginning; keep notes and changes concise.";
15200
+ continue;
15201
+ }
15202
+ const bytes = Buffer.byteLength(pack.skill?.content ?? "", "utf8");
15203
+ throw new Error(`Skill draft failed. Attempted budgets: ${attemptedBudgets.join(", ")} tokens; source SKILL.md: ${bytes} bytes (UTF-8). Adjust skills.draftMaxTokens / skills.draftRetryMaxTokens in Plugins → dsh-mnemon → Skills → Draft generation. Stay within provider/model caps: lower rejected budgets, shorten the skill or use a model with a higher output cap. Reason: ${message(error).slice(0, 100)}`, { cause: error });
15204
+ }
15205
+ const parsed = parseSkillDraft(answer.structured);
14612
15206
  try {
15207
+ signal.throwIfAborted();
14613
15208
  draft = await source.mutate("draft-record", {
14614
15209
  ...input.suggestionId === void 0 ? {} : { suggestionId: input.suggestionId },
15210
+ ...input.draftId === void 0 ? {} : { draftId: input.draftId },
14615
15211
  skillName: pack.skillName,
14616
15212
  content: parsed.content,
14617
15213
  notes: parsed.notes,
@@ -14620,6 +15216,7 @@ var SkillEvolution = class {
14620
15216
  ...pack.action === "create" ? { targetRoot } : {}
14621
15217
  }, signal);
14622
15218
  } catch (error) {
15219
+ signal.throwIfAborted();
14623
15220
  if (attempt > 0) throw error;
14624
15221
  prompt += `\n\nYour previous SKILL.md was rejected: ${message(error)}. Return a corrected complete file.`;
14625
15222
  }
@@ -14640,7 +15237,10 @@ var SkillEvolution = class {
14640
15237
  else {
14641
15238
  const baseline = await this.backtest(source, workspace, {
14642
15239
  skillName: pack.skillName,
14643
- target: { kind: pack.action === "update" ? "current" : "none" },
15240
+ target: {
15241
+ kind: pack.action === "update" ? "current" : "none",
15242
+ ...pack.action === "update" ? { draftId: draft.id } : {}
15243
+ },
14644
15244
  ...input.route === void 0 ? {} : { route: input.route }
14645
15245
  });
14646
15246
  await source.mutate("run-link", {
@@ -15176,14 +15776,16 @@ async function skillAssistance(runtime, lifecycle, operation, input, signal) {
15176
15776
  "user-agents"
15177
15777
  ].includes(root)) throw new Error("unknown skill root: " + root);
15178
15778
  const suggestionId = optionalString(input.suggestionId, "suggestionId");
15779
+ const draftId = optionalString(input.draftId, "draftId");
15179
15780
  const skill = optionalString(input.skill, "skill");
15180
- if (suggestionId === void 0 && skill === void 0) throw new Error("a draft needs a suggestion or a skill");
15781
+ if (suggestionId === void 0 && draftId === void 0 && skill === void 0) throw new Error("a draft needs a suggestion, a draft, or a skill");
15181
15782
  const guidance = optionalString(input.guidance, "guidance");
15182
15783
  if (guidance !== void 0 && guidance.length > 4e3) throw new Error("guidance is too long (max 4000 characters)");
15183
15784
  const draftRoute = modelRoute(input);
15184
15785
  return skills.draft(source, workspaceRoot, {
15185
15786
  ...suggestionId === void 0 ? {} : { suggestionId },
15186
15787
  ...skill === void 0 ? {} : { skill },
15788
+ ...draftId === void 0 ? {} : { draftId },
15187
15789
  ...guidance === void 0 ? {} : { guidance },
15188
15790
  ...root === void 0 ? {} : { targetRoot: root },
15189
15791
  ...draftRoute === void 0 ? {} : { route: draftRoute }
@@ -16415,7 +17017,7 @@ function registerDocumentTools(ctx, { requireSource, config, sourceAction }, coo
16415
17017
  const agent = requireAgent(exec);
16416
17018
  if (args.action === "archive") {
16417
17019
  requireSource(exec, "documents", "archive");
16418
- if (isSubagent(agent)) throw new Error("idle document workers cannot cold-archive directly");
17020
+ if (isSubagent(agent) && !coordinator.isUserContinuation(agent)) throw new Error("idle document workers cannot cold-archive directly");
16419
17021
  if (args.id === void 0) throw new Error("document id is required for archive");
16420
17022
  return Promise.resolve(coordinator.archiveDocument(agent, args.id, exec.signal)).then(documentReceipt);
16421
17023
  }
@@ -18885,7 +19487,7 @@ function apply(rawContext, rawConfig = {}) {
18885
19487
  settings: () => skillSettings.get(),
18886
19488
  source: (agent) => skillsSourceFor(runtime, agent),
18887
19489
  runTask: (workspaceRoot, signal, operation, route) => lifecycle.runSkillTask(workspaceRoot, signal, operation, route),
18888
- skillTask: (parent, label, prompt, schema, persona, signal) => coordinator.skillTask(parent, label, prompt, schema, persona, signal, taskRoute(parent))
19490
+ skillTask: (parent, label, prompt, schema, persona, signal, _route, maxTokens) => coordinator.skillTask(parent, label, prompt, schema, persona, signal, taskRoute(parent), maxTokens)
18889
19491
  });
18890
19492
  lifecycle.useSkillEvolution(skills);
18891
19493
  ctx.effect(() => {