dsh-plugin-prompt-tool 3.0.2 → 3.1.0

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.
Files changed (48) hide show
  1. package/README.md +17 -1
  2. package/engine/actions/index.mjs +5 -3
  3. package/engine/actions/tool-visibility.mjs +4 -1
  4. package/engine/compositions/source/local/context-provenance.yml +9 -0
  5. package/engine/compositions/source/local/generation-recovery.yml +10 -0
  6. package/engine/compositions/source/local/retrieval.yml +7 -0
  7. package/engine/compositions/source/local/soul-ledger.yml +3 -0
  8. package/engine/compositions/source/local/soul-maintenance.yml +9 -0
  9. package/engine/conditions/examples.mjs +1 -0
  10. package/engine/conditions/index.mjs +7 -0
  11. package/engine/conditions/manifest.mjs +28 -0
  12. package/engine/context-idle-compaction.mjs +58 -0
  13. package/engine/context-provenance.d.mts +7 -0
  14. package/engine/context-provenance.mjs +156 -0
  15. package/engine/context-pruner.mjs +64 -0
  16. package/engine/context-routing.mjs +45 -0
  17. package/engine/context-summary.mjs +80 -0
  18. package/engine/fillers.mjs +11 -2
  19. package/engine/generation-recovery-coordinator.mjs +40 -0
  20. package/engine/generation-recovery-maintenance.mjs +18 -0
  21. package/engine/generation-recovery.mjs +153 -0
  22. package/engine/host-package.mjs +13 -0
  23. package/engine/layers.d.mts +4 -0
  24. package/engine/layers.mjs +57 -20
  25. package/engine/present-as.mjs +2 -2
  26. package/engine/retrieval-reference.d.mts +5 -0
  27. package/engine/retrieval-reference.mjs +26 -0
  28. package/engine/retrieval.mjs +17 -0
  29. package/engine/rule-engine.mjs +1 -0
  30. package/engine/rule-runtime.d.mts +8 -1
  31. package/engine/rule-runtime.mjs +18 -10
  32. package/engine/rule-spec.mjs +4 -2
  33. package/engine/schema.mjs +1 -1
  34. package/engine/service-names.d.mts +5 -0
  35. package/engine/service-names.mjs +5 -0
  36. package/engine/soul-ledger.mjs +8 -0
  37. package/engine/soul-maintenance.mjs +20 -0
  38. package/lib/client.js +12 -2
  39. package/lib/client.js.map +1 -1
  40. package/lib/index.mjs +1868 -208
  41. package/modules/soul/assets/ledger.md +4 -2
  42. package/modules/soul/assets/manifest.yml +2 -0
  43. package/modules/soul/assets/templates/agents-template.md +3 -0
  44. package/modules/soul/assets/templates/self-template.md +19 -0
  45. package/modules/soul/module.yml +71 -13
  46. package/modules/tool-surface/module.yml +1 -1
  47. package/package.json +1 -1
  48. package/templates/80-tool-surface.yml +1 -1
package/lib/index.mjs CHANGED
@@ -17,6 +17,7 @@ import { Uint8ArrayReader, Uint8ArrayWriter, ZipReader, ZipWriter } from "@zip.j
17
17
  import { inflateSync } from "node:zlib";
18
18
  import SystemPrompt, { PERSONA_PREFIX_SECTION, PERSONA_SUFFIX_SECTION } from "@deepseek-ai/dsh-system-prompt";
19
19
  import { FileSystemSkillProvider } from "@deepseek-ai/dsh-skill-filesystem";
20
+ import "@deepseek-ai/cordis";
20
21
  //#region src/shared/persona-section.ts
21
22
  const FIELDS$1 = [
22
23
  "prefix",
@@ -426,7 +427,10 @@ const MODULE_TOOL_NAMES = {
426
427
  fileRead: "module_file_read",
427
428
  fileAppend: "module_file_append",
428
429
  manifest: "module_manifest",
429
- variables: "module_variables"
430
+ variables: "module_variables",
431
+ soulLedger: "soul_ledger",
432
+ search: "module_search",
433
+ open: "module_open"
430
434
  };
431
435
  const MODULE_TOOL_CAPABILITIES = {
432
436
  [MODULE_TOOL_NAMES.import]: "module-import-tools",
@@ -435,7 +439,10 @@ const MODULE_TOOL_CAPABILITIES = {
435
439
  [MODULE_TOOL_NAMES.fileRead]: "module-file-tools",
436
440
  [MODULE_TOOL_NAMES.fileAppend]: "module-file-tools",
437
441
  [MODULE_TOOL_NAMES.manifest]: "module-manifest-tools",
438
- [MODULE_TOOL_NAMES.variables]: "module-variable-tools"
442
+ [MODULE_TOOL_NAMES.variables]: "module-variable-tools",
443
+ [MODULE_TOOL_NAMES.soulLedger]: "soul-ledger",
444
+ [MODULE_TOOL_NAMES.search]: "retrieval",
445
+ [MODULE_TOOL_NAMES.open]: "retrieval"
439
446
  };
440
447
  /** 旧公开类型名只作输入兼容。 */
441
448
  /**
@@ -816,8 +823,7 @@ function canonicalModulesRoot(root, allowMissing = false) {
816
823
  if (rootStat.isSymbolicLink() || !rootStat.isDirectory()) throw new Error(`预设根不是普通目录:${absoluteRoot}`);
817
824
  return realpathSync(absoluteRoot);
818
825
  }
819
- /** 根与目标先解析真实路径,拒绝链接目录及定义身份不一致。 */
820
- function assertModuleDirectory(root, id, allowMissing = false) {
826
+ function assertModuleDirectoryPath(root, id, allowMissing = false) {
821
827
  assertModuleId(id);
822
828
  const canonicalRoot = canonicalModulesRoot(root, allowMissing);
823
829
  const target = join(canonicalRoot, id);
@@ -828,6 +834,12 @@ function assertModuleDirectory(root, id, allowMissing = false) {
828
834
  const stat = lstatSync(target);
829
835
  if (stat.isSymbolicLink() || !stat.isDirectory() || dirname(realpathSync(target)) !== canonicalRoot) throw new Error(`预设路径包含链接或越界:${id}`);
830
836
  if (!readdirSync(canonicalRoot).includes(id)) throw new Error(`预设目录身份不匹配:${id}`);
837
+ return target;
838
+ }
839
+ /** 根与目标先解析真实路径,拒绝链接目录及定义身份不一致。 */
840
+ function assertModuleDirectory(root, id, allowMissing = false) {
841
+ const target = assertModuleDirectoryPath(root, id, allowMissing);
842
+ if (allowMissing && !modulePathExists(target)) return target;
831
843
  const definition = join(target, MODULE_DEFINITION_FILE);
832
844
  const fileStat = lstatSync(definition);
833
845
  if (fileStat.isSymbolicLink() || !fileStat.isFile()) throw new Error(`预设定义不是普通文件:${id}`);
@@ -837,6 +849,24 @@ function assertModuleDirectory(root, id, allowMissing = false) {
837
849
  if (declared !== void 0 && declared !== id) throw new Error(`预设定义身份不匹配:${id}`);
838
850
  return target;
839
851
  }
852
+ /** 派生快照允许定义暂时损坏;路径、文件类型与可读的声明身份仍须可信。 */
853
+ function assertModuleSnapshotSource(root, id) {
854
+ const target = assertModuleDirectoryPath(root, id);
855
+ const definition = join(target, MODULE_DEFINITION_FILE);
856
+ const fileStat = lstatSync(definition, { throwIfNoEntry: false });
857
+ if (!fileStat) return target;
858
+ if (fileStat.isSymbolicLink() || !fileStat.isFile()) throw new Error(`预设定义不是普通文件:${id}`);
859
+ let doc;
860
+ try {
861
+ doc = parseDocument(readFileSync(definition, "utf8"), { logLevel: "silent" });
862
+ } catch {
863
+ return target;
864
+ }
865
+ if (doc.errors.length > 0 || !(doc.contents instanceof YAMLMap)) return target;
866
+ const declared = doc.get("id");
867
+ if (declared !== void 0 && declared !== id) throw new Error(`预设定义身份不匹配:${id}`);
868
+ return target;
869
+ }
840
870
  /** 复制和物化只接受自有普通文件;不跟随任意层级链接读取根外数据。 */
841
871
  function assertModuleTree(dir) {
842
872
  const stat = lstatSync(dir);
@@ -1109,6 +1139,20 @@ function parsePromoteOn(pluginName, value) {
1109
1139
  if (value === "tool-call" || value === "assistant-message") return PROMOTE_EVENTS[value];
1110
1140
  throw new TypeError(`${pluginName}: promoteOn must be one of "tool-call", "assistant-message", "either"; got ${JSON.stringify(value)}`);
1111
1141
  }
1142
+ /** Validate an optional boolean config flag with a default. */
1143
+ function booleanOption(pluginName, value, field, fallback) {
1144
+ if (value === void 0) return fallback;
1145
+ if (typeof value !== "boolean") throw new TypeError(`${pluginName}: ${field} must be a boolean`);
1146
+ return value;
1147
+ }
1148
+ /**
1149
+ * 必填整数配置:缺键 / 非整数 / 越界一律 fail loud。
1150
+ * 默认值归模板与预设(组合源 yml 的 config 或本预设 moduleConfigs),引擎不兜底。
1151
+ */
1152
+ function requiredInt(pluginName, value, field, minimum) {
1153
+ if (!Number.isSafeInteger(value) || value < minimum) throw new TypeError(`${pluginName}: ${field} must be an integer >= ${minimum}`);
1154
+ return value;
1155
+ }
1112
1156
  /** Validate the shared config envelope and unknown-key contract. */
1113
1157
  function validateConfig(pluginName, source, allowedKeys) {
1114
1158
  const config = source === void 0 ? {} : source;
@@ -1279,6 +1323,34 @@ function sessionState(ctx, create, { weak = false, limit = MAX_TRACKED_SESSIONS,
1279
1323
  }
1280
1324
  //#endregion
1281
1325
  //#region engine/fields.mjs
1326
+ /**
1327
+ * fields — 配置字段类型系统(B2 配置契约统一的底座)。
1328
+ *
1329
+ * 把「每个模块自己写白名单、自己写归一化、自己决定缺省语义」收敛为
1330
+ * 「一份字段声明 + 一个校验运行时」:
1331
+ *
1332
+ * const { allowedKeys, parse } = defineConfig({
1333
+ * enabled: bool({ default: false }),
1334
+ * envKeys: stringList({ trim: true, dedupe: true, message: '...', emptyMessage: '...' }),
1335
+ * })
1336
+ * const config = parse(source, name) // 未知键 / 类型错 / 缺必填一律 fail loud
1337
+ *
1338
+ * 硬约束(B2):**逐字段的校验结果与错误消息必须与迁移前逐字一致**,
1339
+ * 唯一允许的行为变更是 `enabled` 的 fail loud(见 PLAN T3)。因此:
1340
+ * - 缺省语义由每个字段**显式声明**,不再有隐式 `undefined` / `[]` / `new Set()` 的混用;
1341
+ * - 底层复用 shared.mjs 的既有 helper(`booleanOption` / `requiredInt` / `requiredText` /
1342
+ * `validateConfig`),不复制它们的逻辑,默认消息也就自动逐字相同;
1343
+ * - 模块需要用别的措辞时,用 `message` / `emptyMessage` 覆盖(模板里的 `{plugin}`、
1344
+ * `{field}` 会被替换)。
1345
+ *
1346
+ * 字段类型与它替代的历史 helper:
1347
+ * bool(...) ← booleanOption (缺省 → default)
1348
+ * int({min,...}) ← requiredInt / optionalPositiveInt (required 缺键即抛)
1349
+ * text(...) ← requiredText / requiredStageField (requiredText 的空串 → undefined)
1350
+ * stringList(...) ← nameSet / allowKindList / sourceList / deferredList / stringList /
1351
+ * stringListOrEmpty / normalizeEnvKeys(返回数组或 Set、可选 dedupe/trim)
1352
+ * enumOf(...) ← 枚举白名单(如 promoteOn 一族)
1353
+ */
1282
1354
  /** 把 `{plugin}` / `{field}` 占位替换进消息模板。 */
1283
1355
  const format = (template, plugin, field) => String(template).replaceAll("{plugin}", plugin).replaceAll("{field}", field);
1284
1356
  /**
@@ -1292,6 +1364,41 @@ const makeField = (spec) => {
1292
1364
  if (spec === null || typeof spec !== "object" || typeof spec.parse !== "function") throw new TypeError("fields: a field must be produced by bool()/int()/text()/stringList()/enumOf()");
1293
1365
  return spec;
1294
1366
  };
1367
+ /** 布尔开关:缺省返回 default;非布尔一律 fail loud。 */
1368
+ function bool({ default: fallback = false, message } = {}) {
1369
+ return makeField({
1370
+ kind: "bool",
1371
+ default: fallback,
1372
+ parse: (value, plugin, field) => {
1373
+ if (message === void 0) return booleanOption(plugin, value, field, resolveDefault(fallback));
1374
+ if (value === void 0) return resolveDefault(fallback);
1375
+ if (typeof value !== "boolean") throw new TypeError(format(message, plugin, field));
1376
+ return value;
1377
+ }
1378
+ });
1379
+ }
1380
+ /**
1381
+ * 整数:`required: true` 时缺键即抛(消息与 shared.requiredInt 逐字一致);
1382
+ * 否则缺键返回 default。`max` 只在声明了才检查。
1383
+ */
1384
+ function int$2({ min = 0, default: fallback, required = false, message } = {}) {
1385
+ if (!Number.isSafeInteger(min)) throw new TypeError("fields: int({min}) must be a safe integer");
1386
+ return makeField({
1387
+ kind: "int",
1388
+ default: fallback,
1389
+ parse: (value, plugin, field) => {
1390
+ if (value === void 0) {
1391
+ if (required) requiredInt(plugin, value, field, min);
1392
+ return resolveDefault(fallback);
1393
+ }
1394
+ if (message !== void 0) {
1395
+ if (!Number.isSafeInteger(value) || value < min) throw new TypeError(format(message, plugin, field));
1396
+ return value;
1397
+ }
1398
+ return requiredInt(plugin, value, field, min);
1399
+ }
1400
+ });
1401
+ }
1295
1402
  /**
1296
1403
  * 字符串列表:
1297
1404
  * - `required: true` 时缺键即抛;否则缺键返回 default(可给工厂,如 `() => new Set()`);
@@ -1329,6 +1436,60 @@ function stringList$1({ required = false, default: fallback, dedupe = false, tri
1329
1436
  }
1330
1437
  });
1331
1438
  }
1439
+ /**
1440
+ * 直通字段:**只登记白名单**,值的归一化交给模块自己的 parse —— 用于旧代码里刻意存在的
1441
+ * 「非法值即静默取默认」语义(如 `typeof x === 'string' && x.length > 0 ? x : 默认值`)。
1442
+ * 换用严格字段类型会把这些静默降级变成挂载期报错,而那是 B2 未授权的行为变更,
1443
+ * 所以这类键一律用 passthrough 保住原语义,并在模块里注明依据。
1444
+ */
1445
+ function passthrough(parse, { default: fallback } = {}) {
1446
+ if (typeof parse !== "function") throw new TypeError("fields: passthrough(parse) requires a function");
1447
+ return makeField({
1448
+ kind: "passthrough",
1449
+ default: fallback,
1450
+ parse
1451
+ });
1452
+ }
1453
+ /** 枚举:值必须落在 `values` 里;`required: true` 时缺键即抛。 */
1454
+ function enumOf(values, { default: fallback, required = false, message } = {}) {
1455
+ const allowed = [...values];
1456
+ if (allowed.length === 0) throw new TypeError("fields: enumOf(values) must not be empty");
1457
+ const template = message ?? `{plugin}: {field} must be one of ${allowed.map((item) => JSON.stringify(item)).join(", ")}`;
1458
+ return makeField({
1459
+ kind: "enum",
1460
+ default: fallback,
1461
+ parse: (value, plugin, field) => {
1462
+ if (value === void 0) {
1463
+ if (required) throw new TypeError(format(template, plugin, field));
1464
+ return resolveDefault(fallback);
1465
+ }
1466
+ if (!allowed.includes(value)) throw new TypeError(format(template, plugin, field));
1467
+ return value;
1468
+ }
1469
+ });
1470
+ }
1471
+ /**
1472
+ * 由字段声明派生 `{ allowedKeys, parse }`:
1473
+ * - `allowedKeys` 取代手写 `ALLOWED_KEYS`(未知键的报错格式沿用 shared.validateConfig);
1474
+ * - `parse(source, plugin)` 先做信封校验再逐字段归一化,返回**只含声明键**的对象。
1475
+ */
1476
+ function defineConfig(fields) {
1477
+ if (fields === null || typeof fields !== "object" || Array.isArray(fields)) throw new TypeError("fields: defineConfig(fields) requires an object of field declarations");
1478
+ const entries = Object.entries(fields);
1479
+ for (const [key, spec] of entries) makeField(spec);
1480
+ const allowedKeys = new Set(entries.map(([key]) => key));
1481
+ const parse = (source, plugin) => {
1482
+ const config = validateConfig(plugin, source, allowedKeys);
1483
+ const normalized = {};
1484
+ for (const [key, spec] of entries) normalized[key] = spec.parse(config[key], plugin, key);
1485
+ return normalized;
1486
+ };
1487
+ return {
1488
+ allowedKeys,
1489
+ parse,
1490
+ fields
1491
+ };
1492
+ }
1332
1493
  //#endregion
1333
1494
  //#region engine/actions/shared.mjs
1334
1495
  const RUN_CODE = "run_code";
@@ -8346,6 +8507,10 @@ function resolveRealWithin(target, roots, label = "path") {
8346
8507
  const PRE_STEP_COORDINATOR_SERVICE = "promptToolPreStep";
8347
8508
  /** 发现引擎注册表:宿主 root provide,adapter 行按 agent 注册,动作按 agent 解析。 */
8348
8509
  const DISCOVERY_SERVICE = "promptToolDiscovery";
8510
+ const SOUL_SELF_SERVICE = "promptToolSoulSelf";
8511
+ const SOUL_MAINTENANCE_SERVICE = "promptToolSoulMaintenance";
8512
+ /** 模块清单 checks 事实:宿主提供,宿主装配读取后经挂载 options 传给谓词。 */
8513
+ const MANIFEST_CHECK_SERVICE = "promptToolManifestCheck";
8349
8514
  /** 五个模块工具外壳的服务名:`pt-<引擎模块 id>`。前缀是本函数的内部细节。 */
8350
8515
  function toolServiceName(engineModuleId) {
8351
8516
  return `pt-${engineModuleId}`;
@@ -8514,7 +8679,7 @@ function createInstructionHintResolver(config = {}) {
8514
8679
  //#region engine/fillers.mjs
8515
8680
  /**
8516
8681
  * fillers — 引擎内置的动态填充器(strategy=placeholder 的数据源)。
8517
- * 全部遵循同一铁律:服务缺失或列表失败时返回 null(跳过该配置),绝不伤及会话。
8682
+ * 按本次求值读取宿主能力;失败由填充器或调用方诊断并跳过该配置。
8518
8683
  */
8519
8684
  const name$5 = "prompt-config-engine";
8520
8685
  function explicitTemplate(config) {
@@ -8692,12 +8857,20 @@ function createSkillCatalogResolver(config) {
8692
8857
  }
8693
8858
  };
8694
8859
  }
8860
+ function createSoulSelfResolver(config) {
8861
+ return ({ ctx, agent, signal }) => {
8862
+ const service = getService(ctx, SOUL_SELF_SERVICE);
8863
+ if (typeof service?.render !== "function") throw new Error("SELF dependency missing");
8864
+ return { text: service.render(config.sourceModuleId, agent, signal) };
8865
+ };
8866
+ }
8695
8867
  /** 动态填充器:strategy=placeholder 时按 fill 键在注入点填充内容。 */
8696
8868
  const FILLERS = {
8697
8869
  "instruction-hint": (config) => createInstructionHintResolver(config),
8698
8870
  "env-facts": (config) => createEnvFactsResolver(config),
8699
8871
  "skill-catalog": (config) => createSkillCatalogResolver(config),
8700
- "tool-catalog": (config) => createToolCatalogResolver(config)
8872
+ "tool-catalog": (config) => createToolCatalogResolver(config),
8873
+ "soul-self": (config) => createSoulSelfResolver(config)
8701
8874
  };
8702
8875
  function createPlaceholderResolver(config) {
8703
8876
  const make = FILLERS[config.fill];
@@ -9147,7 +9320,8 @@ const KNOWN_FILLS = /* @__PURE__ */ new Set([
9147
9320
  "instruction-hint",
9148
9321
  "env-facts",
9149
9322
  "skill-catalog",
9150
- "tool-catalog"
9323
+ "tool-catalog",
9324
+ "soul-self"
9151
9325
  ]);
9152
9326
  /** 层能力矩阵:每个字段只在对应注入层生效。客户端表单据此动态渲染。 */
9153
9327
  const LAYER_FIELD_POLICIES = Object.fromEntries(LAYER_DEFINITIONS.map((definition) => [definition.layer, definition.fields]));
@@ -10407,7 +10581,7 @@ function textLayerGroups(configs) {
10407
10581
  return groups;
10408
10582
  }
10409
10583
  /** system-section:注册静态 system prompt 段(支持官方 {{variable}} 渲染与 merged 拼接)。 */
10410
- function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered) {
10584
+ function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered, sourceError) {
10411
10585
  const systemPrompt = getService(ctx, "systemPrompt");
10412
10586
  if (systemPrompt === void 0 || typeof systemPrompt.section !== "function") {
10413
10587
  if (configs.length > 0) warnOnce(`${name$2}: systemPrompt service unavailable — system-section configs skipped`);
@@ -10430,6 +10604,7 @@ function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered) {
10430
10604
  ordered.sections.push(orderedEntry(entryName, base));
10431
10605
  if (base.params?.suppressRuntimeContext === true) keep(systemPrompt.suppressRuntimeContext(), `${name$2}: suppressRuntimeContext ${base.id}`);
10432
10606
  } catch (error) {
10607
+ if (sourceError !== void 0) throw sourceError(error, base.sourceModuleId);
10433
10608
  warnOnce(`${name$2}: system-section config ${base.id} failed: ${String(error?.message ?? error)}`);
10434
10609
  }
10435
10610
  }
@@ -10466,7 +10641,7 @@ async function resolvedContextText(ctx, config, context, registry, warnOnce) {
10466
10641
  return officialChannelText(config.texts.length > 0 ? interpolateVariables(config.texts.join("\n\n"), variables, session, void 0, config.id) : typeof resolved.text === "string" ? interpolateVariables(resolved.text, variables, session, void 0, config.id) : "", `runtime-context ${config.id}`, registry, warnOnce);
10467
10642
  }
10468
10643
  /** 条件化文本保留官方排序占位;仅在本次真实 assembly 填充,不缓存 context 身份。 */
10469
- function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, ordered) {
10644
+ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, ordered, sourceError) {
10470
10645
  if (!configs.length) return [];
10471
10646
  const systemPrompt = getService(ctx, "systemPrompt");
10472
10647
  if (systemPrompt === void 0) {
@@ -10481,15 +10656,32 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10481
10656
  const section = layer === "system-section";
10482
10657
  const base = group[0];
10483
10658
  const entryName = textRegistrationName(base, section ? "sectionName" : "contextName");
10484
- keep(systemPrompt[section ? "section" : "context"]({
10485
- name: entryName,
10486
- order: base.order,
10487
- text: marker
10488
- }), `${name$2}: conditional ${entryName}`);
10659
+ const holders = section ? void 0 : /* @__PURE__ */ new WeakMap();
10660
+ const sharedHolder = section ? void 0 : { text: "" };
10661
+ const holderOf = section ? void 0 : (context) => {
10662
+ const key = context?.agent;
10663
+ if (key === void 0 || key === null) return sharedHolder;
10664
+ let holder = holders.get(key);
10665
+ if (holder === void 0) {
10666
+ holder = { text: "" };
10667
+ holders.set(key, holder);
10668
+ }
10669
+ return holder;
10670
+ };
10671
+ try {
10672
+ keep(systemPrompt[section ? "section" : "context"]({
10673
+ name: entryName,
10674
+ order: base.order,
10675
+ text: section ? marker : (context) => holderOf(context).text
10676
+ }), `${name$2}: conditional ${entryName}`);
10677
+ } catch (error) {
10678
+ throw sourceError === void 0 ? error : sourceError(error, base.sourceModuleId);
10679
+ }
10489
10680
  bindings.push({
10490
10681
  group,
10491
10682
  name: entryName,
10492
- collection: section ? "sections" : "contexts"
10683
+ collection: section ? "sections" : "contexts",
10684
+ holderOf
10493
10685
  });
10494
10686
  ordered[section ? "sections" : "contexts"].push(orderedEntry(entryName, base));
10495
10687
  }
@@ -10500,9 +10692,11 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10500
10692
  return [on("system-prompt/assemble", async (assembly, context, next, invocation) => {
10501
10693
  const frame = invocation ?? ruleFrame("system-prompt/assemble", [assembly, context], warnOnce);
10502
10694
  for (const binding of bindings) {
10503
- const entry = assembly[binding.collection].find((item) => item.name === binding.name && item.text === marker);
10695
+ const entry = binding.collection === "sections" ? assembly.sections.find((item) => item.name === binding.name && item.text === marker) : assembly.contexts.find((item) => item.name === binding.name);
10504
10696
  if (entry === void 0) continue;
10697
+ const holder = binding.holderOf === void 0 ? void 0 : binding.holderOf(context);
10505
10698
  entry.text = "";
10699
+ if (holder !== void 0) holder.text = "";
10506
10700
  const blocks = [];
10507
10701
  for (const config of binding.group) {
10508
10702
  if (!active || context.signal?.aborted || !actionMatches(config, frame) || !matchesAgentScope(config, context.agent)) continue;
@@ -10513,13 +10707,16 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10513
10707
  warnOnce(`${name$2}: rule text ${config.id} failed: ${String(error?.message ?? error)}`);
10514
10708
  }
10515
10709
  }
10516
- if (active && !context.signal?.aborted) entry.text = blocks.join("\n\n");
10710
+ if (active && !context.signal?.aborted) {
10711
+ entry.text = blocks.join("\n\n");
10712
+ if (holder !== void 0) holder.text = entry.text;
10713
+ }
10517
10714
  }
10518
10715
  return next();
10519
10716
  })];
10520
10717
  }
10521
10718
  /** runtime-context:注册运行时上下文;静态文本支持 merged,动态策略在官方 assembly waterfall 中填充。 */
10522
- function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered) {
10719
+ function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered, sourceError) {
10523
10720
  const disposers = [];
10524
10721
  const systemPrompt = getService(ctx, "systemPrompt");
10525
10722
  if (systemPrompt === void 0 || typeof systemPrompt.context !== "function") {
@@ -10543,41 +10740,60 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered
10543
10740
  }), `${name$2}: context ${base.id}`);
10544
10741
  ordered.contexts.push(orderedEntry(entryName, base));
10545
10742
  } catch (error) {
10743
+ if (sourceError !== void 0) throw sourceError(error, base.sourceModuleId);
10546
10744
  if (base.linkedDelivery) throw error;
10547
10745
  warnOnce(`${name$2}: runtime-context config ${base.id} failed: ${String(error?.message ?? error)}`);
10548
10746
  }
10549
10747
  }
10550
10748
  const placeholders = configs.filter(needsResolver).sort(compareTextPlacement);
10551
10749
  if (placeholders.length === 0) return disposers;
10552
- const slotVariable = `pt_runtime_${newMessageId("context").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
10553
- const slotText = `{{${slotVariable}}}`;
10554
10750
  const registered = /* @__PURE__ */ new Map();
10555
10751
  let active = true;
10556
10752
  keep(() => {
10557
10753
  active = false;
10558
10754
  }, `${name$2}: runtime-context lifecycle`);
10559
- keep(systemPrompt.variable(slotVariable, () => ""), `${name$2}: runtime-context placeholder`);
10560
10755
  for (const config of placeholders) try {
10561
10756
  const contextName = textRegistrationName(config, "contextName");
10757
+ const holders = /* @__PURE__ */ new WeakMap();
10758
+ const sharedHolder = { text: "" };
10759
+ const holderOf = (context) => {
10760
+ const key = context?.agent;
10761
+ if (key === void 0 || key === null) return sharedHolder;
10762
+ let holder = holders.get(key);
10763
+ if (holder === void 0) {
10764
+ holder = { text: "" };
10765
+ holders.set(key, holder);
10766
+ }
10767
+ return holder;
10768
+ };
10562
10769
  keep(systemPrompt.context({
10563
10770
  name: contextName,
10564
10771
  order: config.order,
10565
- text: slotText
10772
+ text: (context) => holderOf(context).text
10566
10773
  }), `${name$2}: context ${config.id}`);
10567
- registered.set(contextName, config);
10774
+ registered.set(contextName, {
10775
+ config,
10776
+ holderOf
10777
+ });
10568
10778
  ordered.contexts.push(orderedEntry(contextName, config));
10569
10779
  } catch (error) {
10780
+ if (sourceError !== void 0) throw sourceError(error, config.sourceModuleId);
10570
10781
  warnOnce(`${name$2}: runtime-context placeholder ${config.id} failed: ${String(error?.message ?? error)}`);
10571
10782
  }
10572
10783
  disposers.push(on("system-prompt/assemble", async (assembly, context, next) => {
10573
- const entries = assembly.contexts.filter((entry) => registered.has(entry.name) && entry.text === slotText);
10784
+ const entries = assembly.contexts.filter((entry) => registered.has(entry.name));
10574
10785
  for (const entry of entries) {
10575
- const config = registered.get(entry.name);
10786
+ const { config, holderOf } = registered.get(entry.name);
10787
+ const holder = holderOf(context);
10576
10788
  entry.text = "";
10789
+ holder.text = "";
10577
10790
  if (!active || context.signal?.aborted) continue;
10578
10791
  try {
10579
10792
  const text = await resolvedContextText(ctx, config, context, registry.get(config), warnOnce);
10580
- if (active && !context.signal?.aborted) entry.text = text;
10793
+ if (active && !context.signal?.aborted) {
10794
+ entry.text = text;
10795
+ holder.text = text;
10796
+ }
10581
10797
  } catch (error) {
10582
10798
  warnOnce(`${name$2}: runtime-context ${config.id} resolve failed: ${String(error?.message ?? error)}`);
10583
10799
  }
@@ -10908,9 +11124,9 @@ function wireLayers(ctx, configs, warnOnce, options = {}) {
10908
11124
  const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === "system-section" || config.layer === "runtime-context"), warnOnce, keep);
10909
11125
  const conditional = configs.filter((config) => ["system-section", "runtime-context"].includes(config.layer) && (typeof config.rule?.when === "function" || typeof config.actionWhen === "function" || (config.rule !== void 0 || config.params?.templateEvaluation === true) && config.params?.complete !== true && config.params?.suppressRuntimeContext !== true));
10910
11126
  const regular = configs.filter((config) => !conditional.includes(config));
10911
- wireSystemSections(ctx, regular.filter((config) => config.layer === "system-section"), registry, warnOnce, keep, ordered);
10912
- wireRuntimeContexts(ctx, regular.filter((config) => config.layer === "runtime-context"), registry, warnOnce, keep, on, ordered);
10913
- wireRuleTextContributions(ctx, conditional, registry, warnOnce, keep, on, ordered);
11127
+ wireSystemSections(ctx, regular.filter((config) => config.layer === "system-section"), registry, warnOnce, keep, ordered, options.sourceError);
11128
+ wireRuntimeContexts(ctx, regular.filter((config) => config.layer === "runtime-context"), registry, warnOnce, keep, on, ordered, options.sourceError);
11129
+ wireRuleTextContributions(ctx, conditional, registry, warnOnce, keep, on, ordered, options.sourceError);
10914
11130
  wireTurnStops(ctx, configs.filter((config) => config.layer === "turn-stop"), warnOnce, on, options.turnStopBudgets);
10915
11131
  wireSubagentEvents(ctx, configs.filter((config) => config.layer === "subagent-start" || config.layer === "subagent-end"), warnOnce, on);
10916
11132
  for (const collection of ["sections", "contexts"]) registerDeliveryOrder(ctx, keep, collection, orderedNamesOf(ordered[collection]));
@@ -13033,7 +13249,10 @@ function prepareToolVisibility(action, plugin) {
13033
13249
  const toolName = exec?.name;
13034
13250
  if (toolName === "run_code" || typeof toolName !== "string" || toolName.length === 0) return void 0;
13035
13251
  if (deny?.has(toolName)) return `${label}: tool ${JSON.stringify(toolName)} is denied by tool-visibility`;
13036
- if (mask.allow !== void 0 && !state.failOpen && mask.blocks(toolName, state.unlocked)) return `${label}: tool ${JSON.stringify(toolName)} is not allowed by tool-visibility`;
13252
+ if (mask.allow !== void 0 && !state.failOpen && mask.blocks(toolName, state.unlocked)) {
13253
+ const hint = discovery === void 0 ? "the allow list is the only path; this action has no discovery source" : `unlock it with tool_search({"toolNames":[${JSON.stringify(toolName)}]}); the gate allows unlocked tools from the next request on`;
13254
+ return `${label}: tool ${JSON.stringify(toolName)} is not allowed by tool-visibility; ${hint}`;
13255
+ }
13037
13256
  }));
13038
13257
  }
13039
13258
  } catch (error) {
@@ -13199,22 +13418,27 @@ function prepareAction(action, options = {}) {
13199
13418
  } catch {}
13200
13419
  };
13201
13420
  const on = withWhen(options.on ?? channelBinder(ctx, kind, plugin, options.prepend === true), options.when, warnOnce, phase, missAll);
13202
- if (budget !== void 0) collect(ctx.on("session/event", (session, event) => budget.observe(session, event)));
13203
13421
  const take = (subject) => budget?.take(subject) ?? true;
13204
- bind?.(ctx, {
13205
- warnOnce,
13206
- on,
13207
- collect,
13208
- take,
13209
- onMiss,
13210
- agent: options.agent
13211
- });
13212
13422
  const dispose = () => {
13213
13423
  for (const release of disposers.splice(0)) try {
13214
13424
  release();
13215
13425
  } catch {}
13216
13426
  };
13217
- keepDisposer(ctx, dispose, `${plugin}: action ${labelOf(action)}`);
13427
+ try {
13428
+ if (budget !== void 0) collect(ctx.on("session/event", (session, event) => budget.observe(session, event)));
13429
+ bind?.(ctx, {
13430
+ warnOnce,
13431
+ on,
13432
+ collect,
13433
+ take,
13434
+ onMiss,
13435
+ agent: options.agent
13436
+ });
13437
+ keepDisposer(ctx, dispose, `${plugin}: action ${labelOf(action)}`);
13438
+ } catch (error) {
13439
+ dispose();
13440
+ throw error;
13441
+ }
13218
13442
  dispose.miss = missHandlers.length === 0 ? void 0 : missAll;
13219
13443
  return dispose;
13220
13444
  };
@@ -13610,6 +13834,12 @@ function resolveHostPackage(id, entry, self) {
13610
13834
  async function importHostPackage(id, entry, self) {
13611
13835
  return import(pathToFileURL(resolveHostPackage(id, entry, self)).href);
13612
13836
  }
13837
+ Object.freeze([
13838
+ "@deepseek-ai/dsh-compaction-basic",
13839
+ "@deepseek-ai/dsh-compaction-tool-result-pruner",
13840
+ "@deepseek-ai/dsh-compaction",
13841
+ "@deepseek-ai/dsh-llm"
13842
+ ]);
13613
13843
  //#endregion
13614
13844
  //#region engine/conditions/preset.mjs
13615
13845
  const AGENT_PRESETS_PACKAGE = "@deepseek-ai/dsh-agent-preset-registry";
@@ -13883,6 +14113,34 @@ function createVariablePredicate(options = {}, context = {}) {
13883
14113
  return predicate;
13884
14114
  }
13885
14115
  //#endregion
14116
+ //#region engine/conditions/manifest.mjs
14117
+ /**
14118
+ * manifest 谓词:规则读取来源模块 `assets/manifest.yml` 的 checks 事实。
14119
+ *
14120
+ * 读取器由宿主装配经 options 在挂载期绑定,结果缓存 1000ms,清单戳变化提前失效;
14121
+ * 读取器缺失、清单缺失/损坏都判 UNAVAILABLE——不能经 not 反转,也不能当「检查失败」。
14122
+ */
14123
+ /**
14124
+ * @param {{ ok?: boolean }} options
14125
+ * @param {{ manifestCheck?: { read: (moduleId: string) => boolean | undefined }, moduleId?: string }} [context]
14126
+ */
14127
+ function createManifestPredicate(options = {}, context = {}) {
14128
+ const unknown = Object.keys(options).filter((key) => key !== "ok");
14129
+ if (unknown.length > 0) throw new TypeError(`predicates: manifest does not take ${unknown.join(", ")}`);
14130
+ if (typeof options.ok !== "boolean") throw new TypeError("predicates: manifest ok must be a boolean");
14131
+ const expected = options.ok;
14132
+ const reader = context.manifestCheck;
14133
+ const moduleId = context.moduleId;
14134
+ const predicate = () => {
14135
+ if (reader === void 0 || typeof reader.read !== "function") return UNAVAILABLE;
14136
+ if (typeof moduleId !== "string" || moduleId.length === 0) return UNAVAILABLE;
14137
+ const observed = reader.read(moduleId);
14138
+ return typeof observed === "boolean" ? observed === expected : UNAVAILABLE;
14139
+ };
14140
+ predicate.kind = "manifest";
14141
+ return predicate;
14142
+ }
14143
+ //#endregion
13886
14144
  //#region engine/conditions/operators.mjs
13887
14145
  /** 条件树组合算子的唯一清单:纯 ESM,无宿主 import,client 可打包。 */
13888
14146
  const COMPOSITE_OPERATORS = Object.freeze(Object.values(MATCH_LOGIC));
@@ -13958,7 +14216,8 @@ const PREDICATE_FACTORIES = Object.freeze({
13958
14216
  preset: createPresetPredicate,
13959
14217
  scope: createScopePredicate,
13960
14218
  anchor: createAnchorPredicate,
13961
- variable: createVariablePredicate
14219
+ variable: createVariablePredicate,
14220
+ manifest: createManifestPredicate
13962
14221
  });
13963
14222
  const objectOrUndefined = (value, label) => {
13964
14223
  if (value === void 0 || value === null) return void 0;
@@ -13995,6 +14254,10 @@ function compileWhen(node, context = {}) {
13995
14254
  store: context.variableStore,
13996
14255
  moduleId: context.moduleId
13997
14256
  });
14257
+ if (key === "manifest") return createManifestPredicate(options, {
14258
+ manifestCheck: context.manifestCheck,
14259
+ moduleId: context.moduleId
14260
+ });
13998
14261
  return withAvailableInput(key, options, factory(options));
13999
14262
  }
14000
14263
  //#endregion
@@ -14108,7 +14371,8 @@ const PREDICATE_EXAMPLES = {
14108
14371
  op: "lte",
14109
14372
  value: "0",
14110
14373
  scope: "effective"
14111
- }
14374
+ },
14375
+ manifest: { ok: true }
14112
14376
  };
14113
14377
  //#endregion
14114
14378
  //#region engine/record.mjs
@@ -14536,7 +14800,7 @@ function normalizeActionGates(action, execution) {
14536
14800
  /** options.promptConfigOptions 拥有模板/策略路径;configOrder 使用规则 id,moduleId 只标记来源。 */
14537
14801
  function compileRules(specs, options = {}) {
14538
14802
  if (!Array.isArray(specs)) throw new TypeError("rules must be an array");
14539
- const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, ...conditionOptions } = options;
14803
+ const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, ...conditionOptions } = options;
14540
14804
  const ids = /* @__PURE__ */ new Set();
14541
14805
  const pendingConfigs = [];
14542
14806
  const moduleId = options.moduleId ?? options.sourceModuleId;
@@ -14696,6 +14960,7 @@ function needsMountContext(node) {
14696
14960
  if (Array.isArray(node)) return node.some(needsMountContext);
14697
14961
  if (Object.hasOwn(node, "preset")) return true;
14698
14962
  if (Object.hasOwn(node, "variable")) return true;
14963
+ if (Object.hasOwn(node, "manifest")) return true;
14699
14964
  if (Object.hasOwn(node, "phase") && node.phase?.subscribe !== false) return true;
14700
14965
  if (node.anchor?.after !== void 0) return true;
14701
14966
  return Object.values(node).some(needsMountContext);
@@ -14790,7 +15055,7 @@ function stable(value) {
14790
15055
  if (Array.isArray(value)) return value.map(stable);
14791
15056
  return record$7(value) ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])])) : value;
14792
15057
  }
14793
- const digest = (value) => revisionOf$4(JSON.stringify(stable(value)));
15058
+ const digest$2 = (value) => revisionOf$4(JSON.stringify(stable(value)));
14794
15059
  const yaml = (value) => new Document(value).toString();
14795
15060
  /** 两级持久变量共用边界;错误只标识键,不输出变量内容。 */
14796
15061
  function assertVariableValues(value) {
@@ -14905,7 +15170,7 @@ function projection(source, directory, sourceRevision, validation = {}) {
14905
15170
  ...exclusive === void 0 ? {} : { exclusive }
14906
15171
  };
14907
15172
  files[`${rule.id}.yml`] = yaml(content);
14908
- ruleRevisions[rule.id] = digest(content);
15173
+ ruleRevisions[rule.id] = digest$2(content);
14909
15174
  }
14910
15175
  files[RULES_VARIABLES_FILE] = yaml(variables);
14911
15176
  const state = {
@@ -14914,8 +15179,8 @@ function projection(source, directory, sourceRevision, validation = {}) {
14914
15179
  };
14915
15180
  const revisions = {
14916
15181
  rules: ruleRevisions,
14917
- settings: digest(state),
14918
- variables: digest(variables)
15182
+ settings: digest$2(state),
15183
+ variables: digest$2(variables)
14919
15184
  };
14920
15185
  files[RULES_SETTINGS_FILE] = yaml({
14921
15186
  ...state,
@@ -19992,6 +20257,36 @@ function createBridgeRuntime(options) {
19992
20257
  publicationPending: pendingVariableSettings.has(dir),
19993
20258
  ...pendingVariableSettings.has(dir) ? { publicationError: pendingVariableSettings.get(dir) } : {}
19994
20259
  });
20260
+ const refreshVariableSettings = async (dir) => {
20261
+ if (!pendingVariableSettings.has(dir)) return;
20262
+ try {
20263
+ await deps.overridesChanged?.(basename(dir));
20264
+ pendingVariableSettings.delete(dir);
20265
+ } catch (error) {
20266
+ pendingVariableSettings.set(dir, `变量开关已保存,但运行发布失败:${String(error)}`);
20267
+ }
20268
+ };
20269
+ const saveVariables = async (dir, request) => {
20270
+ const settingsChanged = request.enabled !== void 0 && request.enabled !== loadModuleDefinition(dir).variablesEnabled;
20271
+ const committed = editModuleVariables(dir, request);
20272
+ if (settingsChanged) pendingVariableSettings.set(dir, "变量开关已保存,等待运行发布");
20273
+ if (committed.publicationPending) return {
20274
+ ...committed,
20275
+ ...published(committed.publicationError ?? "变量已保存,等待运行发布")
20276
+ };
20277
+ if (settingsChanged) await refreshVariableSettings(dir);
20278
+ return {
20279
+ ...committed,
20280
+ ...published(pendingVariableSettings.get(dir))
20281
+ };
20282
+ };
20283
+ const refreshVariables = async (dir) => {
20284
+ await refreshVariableSettings(dir);
20285
+ return {
20286
+ ...variableView(dir),
20287
+ ...published(pendingVariableSettings.get(dir))
20288
+ };
20289
+ };
19995
20290
  return {
19996
20291
  deps,
19997
20292
  ns,
@@ -20016,7 +20311,6 @@ function createBridgeRuntime(options) {
20016
20311
  },
20017
20312
  capabilityQueue: createSerialQueue(),
20018
20313
  moduleOrderQueue: createSerialQueue(),
20019
- pendingVariableSettings,
20020
20314
  finishModuleChange,
20021
20315
  runOverridesChange,
20022
20316
  refreshModuleList,
@@ -20026,7 +20320,9 @@ function createBridgeRuntime(options) {
20026
20320
  collectDescribeExtras,
20027
20321
  readParamOverrides,
20028
20322
  readParamEffective,
20029
- variableView
20323
+ variableView,
20324
+ saveVariables,
20325
+ refreshVariables
20030
20326
  };
20031
20327
  }
20032
20328
  //#endregion
@@ -22479,18 +22775,9 @@ function registerParams(runtime) {
22479
22775
  if (record.refreshOnly === true) {
22480
22776
  if (runtime.gate(tx, "moduleVariables").authorize() === void 0) return;
22481
22777
  if (typeof record.expectedModuleId !== "string") throw new ModuleWriteError("刷新变量必须提供 expectedModuleId");
22482
- if (runtime.pendingVariableSettings.has(dir)) try {
22483
- await runtime.deps.overridesChanged?.(basename(dir));
22484
- runtime.pendingVariableSettings.delete(dir);
22485
- } catch (error) {
22486
- runtime.pendingVariableSettings.set(dir, `变量开关已保存,但运行发布失败:${String(error)}`);
22487
- }
22488
22778
  tx.write(200, {
22489
22779
  ok: true,
22490
- value: {
22491
- ...runtime.variableView(dir),
22492
- ...published(runtime.pendingVariableSettings.get(dir))
22493
- }
22780
+ value: await runtime.refreshVariables(dir)
22494
22781
  });
22495
22782
  return;
22496
22783
  }
@@ -22511,36 +22798,15 @@ function registerParams(runtime) {
22511
22798
  module: definition.revisions.variables,
22512
22799
  ...record.enabled === void 0 ? {} : { settings: definition.revisions.settings }
22513
22800
  };
22514
- const settingsChanged = record.enabled !== void 0 && record.enabled !== loadModuleDefinition(dir).variablesEnabled;
22515
- const committed = editModuleVariables(dir, {
22801
+ const committed = await runtime.saveVariables(dir, {
22516
22802
  scope: record.scope,
22517
22803
  expectedRevisions,
22518
22804
  ...record.variables === void 0 ? {} : { values: record.variables },
22519
22805
  ...record.enabled === void 0 ? {} : { enabled: record.enabled }
22520
22806
  });
22521
- if (settingsChanged) runtime.pendingVariableSettings.set(dir, "变量开关已保存,等待运行发布");
22522
- if (committed.publicationPending) {
22523
- tx.write(200, {
22524
- ok: true,
22525
- value: {
22526
- ...committed,
22527
- ...published(committed.publicationError ?? "变量已保存,等待运行发布")
22528
- }
22529
- });
22530
- return;
22531
- }
22532
- if (settingsChanged) try {
22533
- await runtime.deps.overridesChanged?.(basename(dir));
22534
- runtime.pendingVariableSettings.delete(dir);
22535
- } catch (error) {
22536
- runtime.pendingVariableSettings.set(dir, `变量开关已保存,但运行发布失败:${String(error)}`);
22537
- }
22538
22807
  tx.write(200, {
22539
22808
  ok: true,
22540
- value: {
22541
- ...committed,
22542
- ...published(runtime.pendingVariableSettings.get(dir))
22543
- }
22809
+ value: committed
22544
22810
  });
22545
22811
  } catch (error) {
22546
22812
  if (error instanceof ModuleWriteError && error.persisted) {
@@ -25755,6 +26021,7 @@ function readModuleFile(moduleDir, path) {
25755
26021
  * data/ 目录缺失时只建这一级;单次 1 MiB、账本文件 8 MiB。
25756
26022
  */
25757
26023
  function appendModuleLine(moduleDir, path, line) {
26024
+ if (["data/02-soul.jsonl", "data/02-墨.jsonl"].includes(path.normalize("NFC").toLowerCase())) throw new Error("账本只能通过 soul_ledger 追加,不能使用通用文件接口");
25758
26025
  if (typeof line !== "string") throw new TypeError("追加内容必须是字符串");
25759
26026
  const isMemory = path === "memory.md";
25760
26027
  if (!isMemory && !path.startsWith("data/")) throw new Error(`只能追加到 memory.md 或 data/:${path}`);
@@ -25902,7 +26169,7 @@ function stringList(value, label) {
25902
26169
  if (!Array.isArray(value) || value.some((item) => !nonempty(item))) throw new Error(`assets/manifest.yml ${label} 必须是字符串数组`);
25903
26170
  return value;
25904
26171
  }
25905
- function parseManifest(moduleDir) {
26172
+ function readModuleManifest(moduleDir) {
25906
26173
  const file = resolveModuleFilePath(moduleDir, MANIFEST_PATH, ["assets"]);
25907
26174
  if (!modulePathExists(file)) throw new Error("模块缺少 assets/manifest.yml");
25908
26175
  const doc = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" });
@@ -26049,7 +26316,7 @@ function ensureDirectory(moduleDir, path) {
26049
26316
  * shortcut: 装配无锁且逐文件写入,并发调用可能留下部分结果;等需要回滚或并发保证时升级成事务。
26050
26317
  */
26051
26318
  function runModuleManifest(moduleDir, action) {
26052
- const manifest = parseManifest(moduleDir);
26319
+ const manifest = readModuleManifest(moduleDir);
26053
26320
  if (action === "next") {
26054
26321
  const entry = nextEntry(moduleDir, manifest);
26055
26322
  return {
@@ -26326,80 +26593,893 @@ function registerModuleVariableTools(ctx, host) {
26326
26593
  };
26327
26594
  }
26328
26595
  //#endregion
26329
- //#region engine/promotion-pool.mjs
26330
- /**
26331
- * 晋升相位追踪器池:同一挂载内按配置复用追踪器,事件由池**统一喂**。
26332
- *
26333
- * 为什么需要池:`createEpochPromotion` 自己不订阅任何事件(它的 ctx 参数完全用不到,
26334
- * 见 `compaction-epoch.mjs` 的条目容器注释),初始相位靠 `status()` 触发的**全量冷扫**建立。
26335
- * 此前 `executor` 每次 pre-step apply 建两份、`phase` 每个编译谓词建一份、
26336
- * `instruction-hint` 每次 apply 建一份,各自订阅、各自喂——重复建的代价就是重复冷扫。
26337
- *
26338
- * **池自己订阅**:订阅随池的归属走(自建池随所在挂载释放,注入池由创建者释放),
26339
- * 调用方不需要记住「注入时谁来喂事件」——忘了就是相位停在冷扫那一刻的静默失效。
26340
- *
26341
- * 状态仍按 `session.id` 分条(追踪器内部的 `sessionState`):池只合并**实例与订阅**,
26342
- * 不共享会话状态,也不改变「状态是快路径、真相在事件流」这条纪律。
26343
- */
26344
- /**
26345
- * 进缓存的选项键:**任何影响判定的配置都必须登记**,否则同 key 会掩盖不同行为。
26346
- * 与 `createEpochPromotion` 读取的选项一一对应。
26347
- */
26348
- const KEYED_OPTIONS = [
26349
- "includeSubagents",
26350
- "promoteGate",
26351
- "promoteAfterFirstResponse",
26352
- "reasoningPattern",
26353
- "reasoningNegativePattern",
26354
- "reasoningFlags",
26355
- "maxPromoteSteps"
26596
+ //#region src/host/soul-ledger.ts
26597
+ /** soul 账本业务 owner:身份来自宿主,历史只追加。 */
26598
+ const SOUL_LEDGER_PATH = "data/02-soul.jsonl";
26599
+ const SOUL_LEDGER_TYPES = [
26600
+ "lead",
26601
+ "promise",
26602
+ "fact",
26603
+ "shelved",
26604
+ "gain",
26605
+ "loss",
26606
+ "persona",
26607
+ "container"
26356
26608
  ];
26357
- const trackKey = (promoteEvents, options) => JSON.stringify([[...promoteEvents].sort(), KEYED_OPTIONS.map((key) => [key, options[key]])]);
26358
- /**
26359
- * @param {object} ctx 归属上下文(`ctx.on` 用来订阅 `session/event`)
26360
- * @returns {{ tracker: (promoteEvents: readonly string[], options?: object) => object, dispose: () => void }}
26361
- */
26362
- function createPromotionPool(ctx) {
26363
- const trackers = /* @__PURE__ */ new Map();
26364
- let disposed = false;
26365
- const release = ctx.on("session/event", (session, event) => {
26366
- for (const tracker of trackers.values()) tracker.observe(session, event);
26609
+ function rowsOf(content) {
26610
+ return content.split("\n").filter((line) => line.trim()).map((line, index) => {
26611
+ const row = JSON.parse(line);
26612
+ if (!row || typeof row !== "object" || Array.isArray(row)) throw new Error(`账本第 ${index + 1} 行不是对象`);
26613
+ const value = row;
26614
+ const summary = value["概要"] ?? value.text;
26615
+ if (typeof value.by !== "string" || typeof value.type !== "string" || typeof value.t !== "string" || typeof summary !== "string") throw new Error(`账本第 ${index + 1} 行缺少有效身份或概要`);
26616
+ return {
26617
+ ...value,
26618
+ "概要": summary
26619
+ };
26367
26620
  });
26621
+ }
26622
+ function readSoulLedger(moduleDir, options = {}) {
26623
+ const limit = options.limit ?? 30;
26624
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new Error("账本读取 limit 必须为 1–100");
26625
+ if (options.type !== void 0 && !SOUL_LEDGER_TYPES.includes(options.type)) throw new Error("未知账本类型");
26626
+ const all = rowsOf(readModuleFile(moduleDir, SOUL_LEDGER_PATH).content);
26627
+ const rows = options.type === void 0 ? all : all.filter((row) => row.type === options.type);
26628
+ const entries = rows.slice(-limit);
26629
+ if (Buffer.byteLength(JSON.stringify(entries)) > 65536) throw new Error("账本读取结果超过 64 KiB,请降低 limit");
26368
26630
  return {
26369
- /** 按配置取追踪器:同配置复用、异配置分建(配置不同就是不同的相位语义)。 */
26370
- tracker(promoteEvents, options = {}) {
26371
- const key = trackKey(promoteEvents, options);
26372
- let tracker = trackers.get(key);
26373
- if (tracker === void 0) {
26374
- tracker = createEpochPromotion(promoteEvents, options);
26375
- trackers.set(key, tracker);
26631
+ entries,
26632
+ total: rows.length
26633
+ };
26634
+ }
26635
+ function appendSoulLedger(moduleDir, input, caller) {
26636
+ if (!caller.sessionId?.trim()) throw new Error("账本写入需要真实会话身份");
26637
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => ![
26638
+ "type",
26639
+ "text",
26640
+ "概要",
26641
+ "ref",
26642
+ "feel"
26643
+ ].includes(key))) throw new Error("账本写入包含未知字段,作者和时间由宿主提供");
26644
+ if (!SOUL_LEDGER_TYPES.includes(input.type)) throw new Error("未知账本类型");
26645
+ if (input.text !== void 0 === (input["概要"] !== void 0)) throw new Error("账本写入需要 text 或 概要 之一");
26646
+ const summary = input["概要"] ?? input.text;
26647
+ if (typeof summary !== "string" || !summary.trim() || Buffer.byteLength(summary) > 8192) throw new Error("账本概要必须为非空且不超过 8 KiB 的字符串");
26648
+ for (const key of ["ref", "feel"]) if (input[key] !== void 0 && (typeof input[key] !== "string" || Buffer.byteLength(input[key]) > 8192)) throw new Error(`账本 ${key} 必须为不超过 8 KiB 的字符串`);
26649
+ return withModuleLock(dirname(moduleDir), basename(moduleDir), () => {
26650
+ caller.signal?.throwIfAborted();
26651
+ const previous = readModuleFile(moduleDir, SOUL_LEDGER_PATH).content;
26652
+ const rows = rowsOf(previous);
26653
+ if (input.type === "promise" && rows.findLast((row) => row.type === "lead")?.by !== caller.sessionId) throw new Error("只有最后一条 lead 署名的主导会话可以写 promise");
26654
+ const entry = {
26655
+ t: (/* @__PURE__ */ new Date()).toISOString(),
26656
+ by: caller.sessionId,
26657
+ type: input.type,
26658
+ "概要": summary,
26659
+ ...input.ref === void 0 ? {} : { ref: input.ref },
26660
+ ...input.feel === void 0 ? {} : { feel: input.feel }
26661
+ };
26662
+ const line = (previous.length > 0 && !previous.endsWith("\n") ? "\n" : "") + JSON.stringify(entry) + "\n";
26663
+ if (Buffer.byteLength(previous) + Buffer.byteLength(line) > 8388608) throw new Error("账本超过 8 MiB");
26664
+ ensureDataDirectory(moduleDir);
26665
+ const file = resolveModuleFilePath(moduleDir, SOUL_LEDGER_PATH, ["data"]);
26666
+ caller.signal?.throwIfAborted();
26667
+ appendFileSync(file, line, "utf8");
26668
+ return { entry };
26669
+ });
26670
+ }
26671
+ //#endregion
26672
+ //#region src/runtime/soul-ledger-tools.ts
26673
+ function registerSoulLedgerTools(ctx, host) {
26674
+ const fiber = ctx.inject(["tools"], (toolsCtx) => {
26675
+ const offGate = toolsCtx.on("tools/pre-execute", async (exec, next) => {
26676
+ const decision = await next();
26677
+ const args = exec.arguments;
26678
+ if (decision.kind !== "allow" || exec.name !== MODULE_TOOL_NAMES.soulLedger || args?.action !== "append" || args?.type !== "lead") return decision;
26679
+ exec.signal.throwIfAborted();
26680
+ return {
26681
+ kind: "ask",
26682
+ reason: "变更 soul 账本主导会话,需要用户明确指定"
26683
+ };
26684
+ });
26685
+ const offTool = toolsCtx.tools.register(defineTool({
26686
+ name: MODULE_TOOL_NAMES.soulLedger,
26687
+ description: "读取或追加模块只追加账本。作者和时间来自真实执行会话;promise 仅当前 lead 署名者能写。lead 必须由用户指定并附 ref;text 与概要二选一,feel 有感受才写。",
26688
+ parameters: {
26689
+ moduleId: MODULE_ID_PARAM,
26690
+ action: {
26691
+ type: "string",
26692
+ enum: ["list", "append"],
26693
+ required: true
26694
+ },
26695
+ type: {
26696
+ type: "string",
26697
+ enum: [...SOUL_LEDGER_TYPES]
26698
+ },
26699
+ text: { type: "string" },
26700
+ "概要": { type: "string" },
26701
+ ref: { type: "string" },
26702
+ feel: { type: "string" },
26703
+ limit: { type: "integer" }
26704
+ },
26705
+ output: {
26706
+ schema: {
26707
+ type: "object",
26708
+ additionalProperties: false,
26709
+ properties: {
26710
+ entry: {
26711
+ type: "object",
26712
+ additionalProperties: true
26713
+ },
26714
+ entries: {
26715
+ type: "array",
26716
+ items: {
26717
+ type: "object",
26718
+ additionalProperties: true
26719
+ }
26720
+ },
26721
+ total: { type: "integer" }
26722
+ }
26723
+ },
26724
+ render: (_args, value) => [{
26725
+ type: "text",
26726
+ text: JSON.stringify(value)
26727
+ }]
26728
+ },
26729
+ execute: async (args, exec) => {
26730
+ if (Object.keys(args).some((key) => ![
26731
+ "moduleId",
26732
+ "action",
26733
+ "type",
26734
+ "text",
26735
+ "概要",
26736
+ "ref",
26737
+ "feel",
26738
+ "limit"
26739
+ ].includes(key))) throw new Error("账本工具含未知字段,不能指定作者或覆盖历史");
26740
+ exec.signal.throwIfAborted();
26741
+ const target = host.target(exec, args.moduleId);
26742
+ if (args.action === "list") {
26743
+ if (args.text !== void 0 || args["概要"] !== void 0 || args.ref !== void 0 || args.feel !== void 0) throw new Error("读取账本不接受写入字段");
26744
+ return readSoulLedger(target.dir, {
26745
+ limit: args.limit,
26746
+ type: args.type
26747
+ });
26748
+ }
26749
+ if (args.limit !== void 0 || args.type === void 0) throw new Error("追加账本需要 type 且不接受 limit");
26750
+ if (args.type === "lead" && !args.ref?.trim()) throw new Error("lead 必须提供用户指定主导的 ref");
26751
+ return appendSoulLedger(target.dir, {
26752
+ type: args.type,
26753
+ ...args.text === void 0 ? {} : { text: args.text },
26754
+ ...args["概要"] === void 0 ? {} : { "概要": args["概要"] },
26755
+ ...args.ref === void 0 ? {} : { ref: args.ref },
26756
+ ...args.feel === void 0 ? {} : { feel: args.feel }
26757
+ }, {
26758
+ sessionId: exec.agent?.session.id,
26759
+ signal: exec.signal
26760
+ });
26376
26761
  }
26377
- return tracker;
26378
- },
26379
- /** 释放订阅并丢弃实例;追踪器本身没有 disposer(状态随闭包回收)。 */
26380
- dispose() {
26381
- if (disposed) return;
26382
- disposed = true;
26383
- trackers.clear();
26384
- release?.();
26385
- }
26762
+ }));
26763
+ return () => {
26764
+ offGate();
26765
+ offTool();
26766
+ };
26767
+ });
26768
+ return () => {
26769
+ fiber.dispose();
26386
26770
  };
26387
26771
  }
26388
26772
  //#endregion
26389
- //#region src/runtime/official-instruction-filter.ts
26390
- /** 官方待注入消息的逐文件过滤;不读取或改写会话历史。 */
26391
- const isRecord$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
26392
- /**
26393
- * 路径来自 source.changes;标题仅用于定位正文,不用于恢复官方加载状态。
26394
- * 重复标题、未知包装或来源与段落不一致时原样放行,避免误删其他文件。
26395
- */
26396
- function filterOfficialInstructionMessages(messages, isDisabled) {
26397
- const diagnostics = [];
26398
- let changed = false;
26399
- const filtered = messages.flatMap((message) => {
26400
- if (!isRecord$1(message) || !isRecord$1(message.source) || message.source.kind !== "agent-instructions") return [message];
26401
- const source = message.source;
26402
- if (!Array.isArray(source.changes)) return [message];
26773
+ //#region src/host/soul-self.ts
26774
+ /** SELF 派生快照:骨架与事实共享,运行身份只在模型出口加入。 */
26775
+ const SOUL_SELF_SOURCE = "data/SELF.src.md";
26776
+ const SOUL_SELF_SNAPSHOT = "data/.soul/SELF.md";
26777
+ function seal(text) {
26778
+ return text.replace(/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, "[凭据已屏蔽]").split("\n").map((line) => /sk-[A-Za-z0-9_-]{16,}|gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|Bearer\s+\S+|(?:api[_-]?key|token|secret|password|passwd|pwd|cookie)["']?\s*[:=]/i.test(line) ? "[凭据已屏蔽]" : line).join("\n");
26779
+ }
26780
+ function validateSelfBudget(value = 12288) {
26781
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1024 || value > 65536) throw new Error("SELF 预算必须为 1024–65536 字节");
26782
+ return value;
26783
+ }
26784
+ function readSoulSelfSnapshot(moduleDir) {
26785
+ const file = join(moduleDir, SOUL_SELF_SNAPSHOT);
26786
+ assertNoModuleLinks(file);
26787
+ if (!modulePathExists(file)) return {
26788
+ content: "",
26789
+ revision: null
26790
+ };
26791
+ const content = readModuleFile(moduleDir, SOUL_SELF_SNAPSHOT).content;
26792
+ return {
26793
+ content,
26794
+ revision: revisionOf$4(content)
26795
+ };
26796
+ }
26797
+ function refreshSoulSelf(moduleDir, index, options) {
26798
+ const budget = validateSelfBudget(options.budgetBytes);
26799
+ options.signal?.throwIfAborted();
26800
+ const source = readModuleFile(moduleDir, SOUL_SELF_SOURCE).content;
26801
+ if (!source.trim()) throw new Error("SELF 骨架缺失:data/SELF.src.md");
26802
+ const ledger = readSoulLedger(moduleDir, { limit: 1 });
26803
+ const lead = readSoulLedger(moduleDir, {
26804
+ type: "lead",
26805
+ limit: 1
26806
+ }).entries[0];
26807
+ const promises = readSoulLedger(moduleDir, {
26808
+ type: "promise",
26809
+ limit: 3
26810
+ });
26811
+ const shelved = readSoulLedger(moduleDir, {
26812
+ type: "shelved",
26813
+ limit: 3
26814
+ });
26815
+ const count = (key) => {
26816
+ const value = index[key] ?? 0;
26817
+ if (!Number.isSafeInteger(value) || value < 0) throw new Error("SELF 索引状态无效");
26818
+ return value;
26819
+ };
26820
+ const content = seal([
26821
+ source.split("## 活体")[0].trim(),
26822
+ "",
26823
+ "## 活体(自动装配)",
26824
+ `- 装配时刻:${(/* @__PURE__ */ new Date()).toISOString()}`,
26825
+ `- 账本:共 ${ledger.total} 条;未结 ${promises.total + shelved.total} 条`,
26826
+ ...lead ? [`- 最后主导记录:${lead["概要"]}`] : ["- 主导:尚无 lead 记录"],
26827
+ ...[...promises.entries, ...shelved.entries].sort((a, b) => a.t.localeCompare(b.t)).slice(-3).map((row) => `- [${row.type}] ${row["概要"]}`),
26828
+ `- 索引:${index.state};文件 ${count("files")};会话 ${count("sessions")};事件 ${count("events")}`,
26829
+ ""
26830
+ ].join("\n"));
26831
+ if (Buffer.byteLength(content) > budget - 512) throw new Error("SELF 超预算,需精简骨架或账本内容;旧快照保留");
26832
+ return withModuleLock(dirname(moduleDir), basename(moduleDir), () => {
26833
+ options.signal?.throwIfAborted();
26834
+ if (readSoulSelfSnapshot(moduleDir).revision !== options.expectedRevision) throw new Error("SELF 快照版本冲突");
26835
+ if (readModuleFile(moduleDir, "data/SELF.src.md").content !== source) throw new Error("SELF 骨架版本冲突");
26836
+ const data = ensureDataDirectory(moduleDir), stateDir = join(data, ".soul");
26837
+ assertNoModuleLinks(stateDir);
26838
+ mkdirSync(stateDir, { recursive: true });
26839
+ const target = resolveModuleFilePath(moduleDir, SOUL_SELF_SNAPSHOT, ["data"]);
26840
+ const temp = join(stateDir, ".self-" + randomUUID() + ".tmp");
26841
+ try {
26842
+ writeFileSync(temp, content, {
26843
+ flag: "wx",
26844
+ encoding: "utf8"
26845
+ });
26846
+ options.signal?.throwIfAborted();
26847
+ if (readSoulSelfSnapshot(moduleDir).revision !== options.expectedRevision) throw new Error("SELF 快照版本冲突");
26848
+ renameSync(temp, target);
26849
+ } finally {
26850
+ rmSync(temp, { force: true });
26851
+ }
26852
+ return {
26853
+ state: "ready",
26854
+ revision: revisionOf$4(content),
26855
+ bytes: Buffer.byteLength(content)
26856
+ };
26857
+ });
26858
+ }
26859
+ function renderSoulSelf(moduleDir, agent, signal, budgetBytes = 12288) {
26860
+ signal?.throwIfAborted();
26861
+ if (!agent?.session?.id) throw new Error("SELF 缺少实际 Agent 身份");
26862
+ const snapshot = readSoulSelfSnapshot(moduleDir);
26863
+ const text = `${snapshot.revision === null ? "[SELF missing:等待骨架初始化与维护]" : seal(snapshot.content)}\n## 当前运行身份\n- 本会话:${agent.session.id}\n- 受众:${(agent.session.header?.delegationDepth ?? 0) > 0 ? "子代理" : "主会话"}\n`;
26864
+ if (Buffer.byteLength(text) > validateSelfBudget(budgetBytes)) throw new Error("SELF 动态上下文超预算");
26865
+ return seal(text);
26866
+ }
26867
+ //#endregion
26868
+ //#region engine/retrieval-reference.mjs
26869
+ const MAX_REFERENCE_CHARS = 8192;
26870
+ /** seq 是持久日志身份;不排序为数值区间,也不以相同正文替代。 */
26871
+ function sessionReferenceText(events, seqs) {
26872
+ if (!Array.isArray(seqs) || seqs.length === 0 || seqs.length > 2048 || new Set(seqs).size !== seqs.length || seqs.some((seq) => !Number.isSafeInteger(seq) || seq < 0)) throw new Error("检索引用事件身份无效");
26873
+ const bySeq = new Map(events.map((event) => [event.seq, event]));
26874
+ let bytes = 0;
26875
+ return seqs.map((seq) => {
26876
+ const event = bySeq.get(seq);
26877
+ if (!event) throw new Error("检索引用已失效");
26878
+ const text = JSON.stringify(event);
26879
+ bytes += Buffer.byteLength(text);
26880
+ if (bytes > 67108864) throw new Error("检索引用来源超过64 MiB上限");
26881
+ return text;
26882
+ }).join("\n");
26883
+ }
26884
+ function makeSessionReference(moduleId, sessionId, events, seqs, range = {}) {
26885
+ if (typeof moduleId !== "string" || !moduleId || typeof sessionId !== "string" || !sessionId) throw new Error("检索引用缺少真实来源身份");
26886
+ const text = sessionReferenceText(events, seqs);
26887
+ const start = range.start ?? 0, end = range.end ?? Math.min(text.length, start + 8192);
26888
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end <= start || end > text.length || end - start > 8192) throw new Error("检索引用范围无效");
26889
+ return {
26890
+ kind: "session",
26891
+ moduleId,
26892
+ sessionId,
26893
+ seqs: [...seqs],
26894
+ revision: createHash("sha256").update(text).digest("hex"),
26895
+ start,
26896
+ end
26897
+ };
26898
+ }
26899
+ //#endregion
26900
+ //#region src/host/module-retrieval.ts
26901
+ /** 检索只读权威来源;引用携定位和版本,不携访问权限。 */
26902
+ const digest$1 = (value) => createHash("sha256").update(value).digest("hex");
26903
+ const MAX_CHUNK = MAX_REFERENCE_CHARS;
26904
+ function fileContent(scope, path) {
26905
+ if (!scope.paths.some((allowed) => allowed === path || allowed.endsWith("/") && path.startsWith(allowed))) throw new Error("检索来源未授权");
26906
+ resolveModuleFilePath(scope.dir, path, ["assets", "data"], { allowRootMemory: true });
26907
+ return readModuleFile(scope.dir, path).content;
26908
+ }
26909
+ function filePaths(scope) {
26910
+ const paths = /* @__PURE__ */ new Set();
26911
+ let total = 0;
26912
+ const walk = (path) => {
26913
+ const resolved = resolveModuleFilePath(scope.dir, path, ["assets", "data"], { allowRootMemory: true });
26914
+ if (!modulePathExists(resolved)) return;
26915
+ const stat = lstatSync(resolved);
26916
+ if (stat.isDirectory()) for (const entry of readdirSync(resolved).sort()) walk(path + "/" + entry);
26917
+ else {
26918
+ if (!stat.isFile()) throw new Error("检索来源不是普通文件");
26919
+ if (!paths.has(path)) total += stat.size;
26920
+ paths.add(path);
26921
+ if (paths.size > 2048 || total > 67108864) throw new Error("检索来源超过数量或总大小上限");
26922
+ }
26923
+ };
26924
+ for (const path of scope.paths) {
26925
+ const selected = path.endsWith("/") ? path.slice(0, -1) : path;
26926
+ const resolved = resolveModuleFilePath(scope.dir, selected, ["assets", "data"], { allowRootMemory: true });
26927
+ if (!modulePathExists(resolved)) continue;
26928
+ if (lstatSync(resolved).isDirectory() && !path.endsWith("/")) throw new Error("检索目录白名单必须以 / 结尾");
26929
+ walk(selected);
26930
+ }
26931
+ return [...paths];
26932
+ }
26933
+ function createModuleRetrieval(scopeOf, loadSession) {
26934
+ const indexes = /* @__PURE__ */ new Map();
26935
+ const assertSessionAuthorized = (scope, id) => {
26936
+ const current = scopeOf();
26937
+ if (current.moduleId !== scope.moduleId || current.session?.id !== id && !current.sessions?.includes(id)) throw new Error("检索会话未授权");
26938
+ };
26939
+ async function authorizedSession(scope, id) {
26940
+ assertSessionAuthorized(scope, id);
26941
+ if (scope.session?.id === id) return scope.session;
26942
+ if (!scope.sessions?.includes(id)) throw new Error("检索会话未授权");
26943
+ let session;
26944
+ try {
26945
+ session = await loadSession?.(id);
26946
+ } catch {
26947
+ throw new Error("授权会话不可读取");
26948
+ }
26949
+ assertSessionAuthorized(scope, id);
26950
+ if (!session || session.id !== id) throw new Error("授权会话不可读取");
26951
+ return session;
26952
+ }
26953
+ async function indexSession(scope, id) {
26954
+ const events = [...(await authorizedSession(scope, id)).snapshotEvents()];
26955
+ assertSessionAuthorized(scope, id);
26956
+ const prior = indexes.get(id);
26957
+ const appendOnly = prior !== void 0 && events.length >= prior.events.length && prior.events.every((event, i) => events[i] === event);
26958
+ const rows = appendOnly ? [...prior.rows] : [];
26959
+ for (const event of events.slice(appendOnly ? prior.events.length : 0)) {
26960
+ if (!Number.isSafeInteger(event.seq) || event.seq < 0) throw new Error("授权会话事件身份无效");
26961
+ rows.push({
26962
+ seq: event.seq,
26963
+ content: JSON.stringify(event)
26964
+ });
26965
+ }
26966
+ if (new Set(rows.map((row) => row.seq)).size !== rows.length) throw new Error("授权会话事件身份重复");
26967
+ if (rows.reduce((bytes, row) => bytes + Buffer.byteLength(row.content), 0) > 67108864) throw new Error("授权会话检索超过64 MiB上限");
26968
+ indexes.set(id, {
26969
+ events,
26970
+ rows
26971
+ });
26972
+ return {
26973
+ events,
26974
+ rows
26975
+ };
26976
+ }
26977
+ const sessionIds = (scope) => /* @__PURE__ */ new Set([...scope.session ? [scope.session.id] : [], ...scope.sessions ?? []]);
26978
+ const resultOf = (scope, hits) => {
26979
+ const current = scopeOf();
26980
+ if (current.moduleId !== scope.moduleId || current.dir !== scope.dir) throw new Error("检索模块未授权");
26981
+ for (const { reference } of hits) if (reference.kind === "session") assertSessionAuthorized(current, reference.sessionId);
26982
+ else if (digest$1(fileContent(current, reference.path)) !== reference.revision) throw new Error("检索引用已失效,请重新搜索");
26983
+ return { hits };
26984
+ };
26985
+ return {
26986
+ async search(input) {
26987
+ if (typeof input.query !== "string" || input.query.trim().length === 0 || input.query.length > 1024) throw new Error("检索 query 必须为1–1024字符");
26988
+ const limit = input.limit ?? 20;
26989
+ if (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) throw new Error("检索 limit 必须为1–50");
26990
+ const scope = scopeOf(), query = input.query.toLowerCase(), hits = [];
26991
+ for (const path of filePaths(scope)) {
26992
+ const content = fileContent(scope, path), revision = digest$1(content);
26993
+ let offset = 0;
26994
+ for (const line of content.split("\n")) {
26995
+ for (let part = 0; part < line.length; part += MAX_CHUNK) {
26996
+ const text = line.slice(part, part + MAX_CHUNK);
26997
+ if (!text.toLowerCase().includes(query)) continue;
26998
+ hits.push({
26999
+ reference: {
27000
+ kind: "file",
27001
+ moduleId: scope.moduleId,
27002
+ path,
27003
+ revision,
27004
+ start: offset + part,
27005
+ end: offset + part + text.length
27006
+ },
27007
+ content: text
27008
+ });
27009
+ if (hits.length === limit) return resultOf(scope, hits);
27010
+ }
27011
+ offset += line.length + 1;
27012
+ }
27013
+ }
27014
+ for (const id of sessionIds(scope)) {
27015
+ const { events, rows } = await indexSession(scope, id);
27016
+ for (const row of rows) {
27017
+ const match = row.content.toLowerCase().indexOf(query);
27018
+ if (match < 0) continue;
27019
+ const start = Math.max(0, match - 128), end = Math.min(row.content.length, start + MAX_CHUNK);
27020
+ const reference = makeSessionReference(scope.moduleId, id, events, [row.seq], {
27021
+ start,
27022
+ end
27023
+ });
27024
+ hits.push({
27025
+ reference,
27026
+ content: row.content.slice(start, end)
27027
+ });
27028
+ if (hits.length === limit) return resultOf(scope, hits);
27029
+ }
27030
+ }
27031
+ return resultOf(scope, hits);
27032
+ },
27033
+ async open(reference) {
27034
+ const scope = scopeOf();
27035
+ if (!reference || reference.moduleId !== scope.moduleId) throw new Error("检索引用未授权或无效");
27036
+ if (Object.keys(reference).some((key) => ![
27037
+ "kind",
27038
+ "moduleId",
27039
+ "path",
27040
+ "revision",
27041
+ "start",
27042
+ "end",
27043
+ "sessionId",
27044
+ "seqs"
27045
+ ].includes(key))) throw new Error("检索引用含未知字段");
27046
+ if (reference.kind === "session") {
27047
+ const session = await authorizedSession(scope, reference.sessionId), events = session.snapshotEvents();
27048
+ assertSessionAuthorized(scope, reference.sessionId);
27049
+ const content = sessionReferenceText(events, reference.seqs);
27050
+ if (digest$1(content) !== reference.revision) throw new Error("检索引用已失效,请重新搜索");
27051
+ if (makeSessionReference(scope.moduleId, session.id, events, reference.seqs, reference).revision !== reference.revision) throw new Error("检索引用已失效,请重新搜索");
27052
+ const nextReference = reference.end < content.length ? makeSessionReference(scope.moduleId, session.id, events, reference.seqs, { start: reference.end }) : void 0;
27053
+ return {
27054
+ reference,
27055
+ content: content.slice(reference.start, reference.end),
27056
+ totalChars: content.length,
27057
+ ...nextReference ? { nextReference } : {}
27058
+ };
27059
+ }
27060
+ if (reference.kind !== "file") throw new Error("检索引用无效");
27061
+ const content = fileContent(scope, reference.path);
27062
+ if (digest$1(content) !== reference.revision) throw new Error("检索引用已失效,请重新搜索");
27063
+ if (!Number.isSafeInteger(reference.start) || !Number.isSafeInteger(reference.end) || reference.start < 0 || reference.end <= reference.start || reference.end > content.length || reference.end - reference.start > MAX_CHUNK) throw new Error("检索引用范围无效");
27064
+ return {
27065
+ reference,
27066
+ content: content.slice(reference.start, reference.end)
27067
+ };
27068
+ },
27069
+ async refresh() {
27070
+ const scope = scopeOf(), files = filePaths(scope);
27071
+ for (const path of files) fileContent(scope, path);
27072
+ let events = 0;
27073
+ const ids = sessionIds(scope);
27074
+ for (const id of ids) events += (await indexSession(scope, id)).rows.length;
27075
+ for (const id of indexes.keys()) if (!ids.has(id)) indexes.delete(id);
27076
+ return {
27077
+ state: "ready",
27078
+ files: files.length,
27079
+ sessions: ids.size,
27080
+ events
27081
+ };
27082
+ }
27083
+ };
27084
+ }
27085
+ //#endregion
27086
+ //#region src/runtime/module-retrieval-tools.ts
27087
+ /** 调用方已确认 tools 就绪;同步注册使初始化错误回到当前能力的挂载路径。 */
27088
+ function registerModuleRetrievalTools(ctx, retrievalOf) {
27089
+ const tools = ctx.get("tools");
27090
+ if (tools === void 0) throw new Error("检索工具注册需要 tools 服务");
27091
+ const disposers = [];
27092
+ const dispose = () => {
27093
+ for (const off of disposers.splice(0).reverse()) off();
27094
+ };
27095
+ try {
27096
+ disposers.push(tools.register(defineTool({
27097
+ name: MODULE_TOOL_NAMES.search,
27098
+ description: "搜索 moduleId 声明模块已授权的文件、账本或历史会话。结果中的 reference 可交 module_open 回取相同原文。",
27099
+ parameters: {
27100
+ moduleId: MODULE_ID_PARAM,
27101
+ query: {
27102
+ type: "string",
27103
+ required: true
27104
+ },
27105
+ limit: { type: "number" }
27106
+ },
27107
+ output: {
27108
+ schema: {
27109
+ type: "object",
27110
+ additionalProperties: true
27111
+ },
27112
+ render: (_args, value) => [{
27113
+ type: "text",
27114
+ text: JSON.stringify(value)
27115
+ }]
27116
+ },
27117
+ execute: async (args, exec) => {
27118
+ if (Object.keys(args).some((key) => ![
27119
+ "moduleId",
27120
+ "query",
27121
+ "limit"
27122
+ ].includes(key))) throw new Error("检索请求含未知字段");
27123
+ exec.signal?.throwIfAborted();
27124
+ const value = await retrievalOf(exec, args.moduleId).search({
27125
+ query: args.query,
27126
+ limit: args.limit
27127
+ });
27128
+ exec.signal?.throwIfAborted();
27129
+ retrievalOf(exec, args.moduleId);
27130
+ return value;
27131
+ }
27132
+ })));
27133
+ disposers.push(tools.register(defineTool({
27134
+ name: MODULE_TOOL_NAMES.open,
27135
+ description: "回取 module_search 返回的引用。每次重新检查来源授权与版本;引用不是访问凭据。",
27136
+ parameters: {
27137
+ moduleId: MODULE_ID_PARAM,
27138
+ reference: {
27139
+ type: "object",
27140
+ additionalProperties: true,
27141
+ required: true
27142
+ }
27143
+ },
27144
+ output: {
27145
+ schema: {
27146
+ type: "object",
27147
+ additionalProperties: true
27148
+ },
27149
+ render: (_args, value) => [{
27150
+ type: "text",
27151
+ text: JSON.stringify(value)
27152
+ }]
27153
+ },
27154
+ execute: async (args, exec) => {
27155
+ if (Object.keys(args).some((key) => !["moduleId", "reference"].includes(key))) throw new Error("回取请求含未知字段");
27156
+ exec.signal?.throwIfAborted();
27157
+ const value = await retrievalOf(exec, args.moduleId).open(args.reference);
27158
+ exec.signal?.throwIfAborted();
27159
+ retrievalOf(exec, args.moduleId);
27160
+ return value;
27161
+ }
27162
+ })));
27163
+ } catch (error) {
27164
+ dispose();
27165
+ throw error;
27166
+ }
27167
+ return dispose;
27168
+ }
27169
+ function createModuleRetrievalProvider(host, loadSession) {
27170
+ const agents = /* @__PURE__ */ new WeakMap();
27171
+ const retrievalFor = (state, moduleId, agent) => {
27172
+ let retrieval = state.indexes.get(moduleId);
27173
+ if (retrieval === void 0) {
27174
+ retrieval = createModuleRetrieval(() => {
27175
+ const current = [...state.modules.get(moduleId)?.values() ?? []].at(-1);
27176
+ if (!current) throw new Error("检索模块未授权");
27177
+ return {
27178
+ moduleId,
27179
+ dir: current.dir,
27180
+ paths: current.config.paths,
27181
+ sessions: current.config.sessions,
27182
+ session: agent.session
27183
+ };
27184
+ }, loadSession);
27185
+ state.indexes.set(moduleId, retrieval);
27186
+ }
27187
+ return retrieval;
27188
+ };
27189
+ return {
27190
+ async refresh(agent, moduleId, signal) {
27191
+ signal?.throwIfAborted();
27192
+ const state = agents.get(agent);
27193
+ if (!state?.modules.has(moduleId)) throw new Error("检索模块未授权");
27194
+ const result = await retrievalFor(state, moduleId, agent).refresh();
27195
+ signal?.throwIfAborted();
27196
+ if (!state.modules.has(moduleId)) throw new Error("检索模块未授权");
27197
+ return result;
27198
+ },
27199
+ mount(_ctx, config, options) {
27200
+ const { agent, sourceModuleId, sourceModuleDir } = options;
27201
+ if (!agent?.ctx || !sourceModuleId || !sourceModuleDir) throw new Error("检索缺少宿主来源模块身份");
27202
+ if (!Array.isArray(config.paths) || config.paths.length > 2048 || config.paths.some((path) => typeof path !== "string") || !Array.isArray(config.sessions) || config.sessions.length > 128 || config.sessions.some((id) => typeof id !== "string" || !id || id.length > 256)) throw new Error("检索来源白名单无效或超限");
27203
+ let state = agents.get(agent);
27204
+ if (state === void 0) {
27205
+ const modules = /* @__PURE__ */ new Map();
27206
+ const indexes = /* @__PURE__ */ new Map();
27207
+ const dispose = registerModuleRetrievalTools(agent.ctx, (exec, moduleId) => {
27208
+ if (exec.agent !== agent) throw new Error("检索 Agent 未授权");
27209
+ const target = host.target(exec, moduleId);
27210
+ const declaration = [...modules.get(target.id)?.values() ?? []].at(-1);
27211
+ if (!declaration || declaration.dir !== target.dir) throw new Error("检索模块未授权");
27212
+ return retrievalFor({
27213
+ modules,
27214
+ indexes,
27215
+ dispose
27216
+ }, target.id, agent);
27217
+ });
27218
+ state = {
27219
+ modules,
27220
+ indexes,
27221
+ dispose
27222
+ };
27223
+ agents.set(agent, state);
27224
+ }
27225
+ let declarations = state.modules.get(sourceModuleId);
27226
+ if (declarations === void 0) {
27227
+ declarations = /* @__PURE__ */ new Map();
27228
+ state.modules.set(sourceModuleId, declarations);
27229
+ }
27230
+ const token = Symbol(sourceModuleId);
27231
+ declarations.set(token, {
27232
+ config,
27233
+ dir: sourceModuleDir
27234
+ });
27235
+ const owner = state;
27236
+ return () => {
27237
+ if (!declarations.delete(token)) return;
27238
+ if (declarations.size === 0) {
27239
+ owner.modules.delete(sourceModuleId);
27240
+ owner.indexes.delete(sourceModuleId);
27241
+ }
27242
+ if (owner.modules.size === 0) {
27243
+ owner.dispose();
27244
+ agents.delete(agent);
27245
+ }
27246
+ };
27247
+ }
27248
+ };
27249
+ }
27250
+ //#endregion
27251
+ //#region src/runtime/soul-maintenance.ts
27252
+ function createSoulMaintenanceProvider(options) {
27253
+ const states = /* @__PURE__ */ new Map();
27254
+ const status = (id) => {
27255
+ const state = states.get(id);
27256
+ return state ? {
27257
+ state: state.state,
27258
+ ...state.error ? { error: state.error } : {}
27259
+ } : { state: "disabled" };
27260
+ };
27261
+ const tick = (id) => {
27262
+ const state = states.get(id);
27263
+ if (!state || !state.members.size) return Promise.resolve();
27264
+ if (state.running) return state.running;
27265
+ if (state.timer) clearTimeout(state.timer);
27266
+ const owner = [...state.members].at(-1);
27267
+ const controller = new AbortController();
27268
+ let signal = controller.signal;
27269
+ state.controller = controller;
27270
+ state.owner = owner;
27271
+ state.state = "running";
27272
+ delete state.error;
27273
+ const valid = () => !signal.aborted && states.get(id) === state && state.members.has(owner);
27274
+ state.running = Promise.resolve().then(async () => {
27275
+ try {
27276
+ if (!valid()) return;
27277
+ await owner.agent.runMaintenance(async (activitySignal) => {
27278
+ signal = AbortSignal.any([controller.signal, activitySignal]);
27279
+ signal.throwIfAborted();
27280
+ const revision = readSoulSelfSnapshot(owner.sourceModuleDir).revision;
27281
+ const index = await options.refreshRetrieval(owner.agent, id, signal);
27282
+ if (!valid()) return;
27283
+ refreshSoulSelf(owner.sourceModuleDir, index, {
27284
+ expectedRevision: revision,
27285
+ signal,
27286
+ budgetBytes: owner.config.budgetBytes
27287
+ });
27288
+ });
27289
+ if (!valid()) return;
27290
+ state.state = "ready";
27291
+ if (owner.config.wake) owner.agent.followup(pluginMessage("soul-maintenance", "书房索引与 SELF 已维护。请核对当前未结事项;无需重复写入已有事实。", "书房维护心跳"));
27292
+ } catch (error) {
27293
+ if (valid()) {
27294
+ state.state = "failed";
27295
+ state.error = error instanceof Error ? error.message : String(error);
27296
+ }
27297
+ } finally {
27298
+ state.running = void 0;
27299
+ state.owner = void 0;
27300
+ if (state.state === "running") state.state = signal.aborted ? "cancelled" : "idle";
27301
+ if (states.get(id) === state && state.members.size) {
27302
+ const current = [...state.members].at(-1);
27303
+ state.timer = setTimeout(() => {
27304
+ tick(id);
27305
+ }, current.config.intervalMs);
27306
+ state.timer.unref();
27307
+ }
27308
+ }
27309
+ });
27310
+ return state.running;
27311
+ };
27312
+ const mount = (_ctx, config, source) => {
27313
+ const audience = (source.agent.session.header.delegationDepth ?? 0) > 0 ? "subagent" : "main";
27314
+ if (!config.enabled || config.audience !== "all" && config.audience !== audience) return () => {};
27315
+ if (!Number.isSafeInteger(config.intervalMs) || config.intervalMs < 1e3 || config.intervalMs > 2147483647) throw new Error("心跳间隔必须为 1000–2147483647ms");
27316
+ const id = source.sourceModuleId;
27317
+ let state = states.get(id);
27318
+ if (!state) {
27319
+ state = {
27320
+ members: /* @__PURE__ */ new Set(),
27321
+ state: "idle"
27322
+ };
27323
+ states.set(id, state);
27324
+ }
27325
+ if ([...state.members].some((member) => member.sourceModuleDir !== source.sourceModuleDir)) throw new Error("心跳模块目录身份冲突");
27326
+ const member = {
27327
+ ...source,
27328
+ config
27329
+ };
27330
+ state.members.add(member);
27331
+ if (!state.running && !state.timer) tick(id);
27332
+ return () => {
27333
+ state.members.delete(member);
27334
+ if (state.owner === member) state.controller?.abort();
27335
+ if (state.members.size) return;
27336
+ state.controller?.abort();
27337
+ if (state.timer) clearTimeout(state.timer);
27338
+ if (states.get(id) === state) states.delete(id);
27339
+ };
27340
+ };
27341
+ return {
27342
+ mount,
27343
+ tick,
27344
+ status,
27345
+ dispose: () => {
27346
+ for (const state of states.values()) {
27347
+ state.controller?.abort();
27348
+ if (state.timer) clearTimeout(state.timer);
27349
+ state.members.clear();
27350
+ }
27351
+ states.clear();
27352
+ }
27353
+ };
27354
+ }
27355
+ //#endregion
27356
+ //#region src/runtime/soul-runtime.ts
27357
+ /** Soul宿主接线:历史只读句柄、检索授权与SELF/维护共享owner。 */
27358
+ function installSoulRuntime(ctx, host, moduleIds) {
27359
+ const budgets = /* @__PURE__ */ new Map();
27360
+ const readSelfBudget = (dir) => {
27361
+ const stat = statSync(join(dir, "module.yml"), { throwIfNoEntry: false });
27362
+ const stamp = stat ? `${stat.mtimeMs}:${stat.size}` : "missing";
27363
+ const hit = budgets.get(dir);
27364
+ if (hit !== void 0 && hit.stamp === stamp && performance.now() - hit.checkedAt < 1e3) return hit.value;
27365
+ let value = hit?.value ?? validateSelfBudget();
27366
+ try {
27367
+ const facts = readModuleFacts(dir);
27368
+ if (facts.sourceMode !== "unknown") value = validateSelfBudget(facts.effectiveConfigs?.["soul-maintenance"]?.budgetBytes);
27369
+ } catch {}
27370
+ budgets.set(dir, {
27371
+ stamp,
27372
+ checkedAt: performance.now(),
27373
+ value
27374
+ });
27375
+ return value;
27376
+ };
27377
+ const retrieval = createModuleRetrievalProvider(host, async (id) => {
27378
+ const live = ctx.get("sessions")?.get(id);
27379
+ if (live) return live;
27380
+ const persistence = ctx.get("sessionPersistence");
27381
+ if (!persistence) throw new Error("历史会话持久服务不可用");
27382
+ const handle = await persistence.open(id, "read");
27383
+ try {
27384
+ const { events } = await handle.read(0, 100001);
27385
+ if (events.length > 1e5 || Buffer.byteLength(JSON.stringify(events)) > 33554432) throw new Error("授权会话超出检索预算");
27386
+ return {
27387
+ id,
27388
+ snapshotEvents: () => events
27389
+ };
27390
+ } finally {
27391
+ await handle.close();
27392
+ }
27393
+ });
27394
+ const maintenance = createSoulMaintenanceProvider({ refreshRetrieval: (agent, id, signal) => retrieval.refresh(agent, id, signal) });
27395
+ ctx.provide(toolServiceName("retrieval"), retrieval);
27396
+ ctx.provide(SOUL_MAINTENANCE_SERVICE, maintenance);
27397
+ ctx.provide(SOUL_SELF_SERVICE, { render: (id, agent, signal) => {
27398
+ signal?.throwIfAborted();
27399
+ if (!moduleIds(agent.id).includes(id)) throw new Error("SELF来源模块未参与本Agent装配");
27400
+ const dir = assertModuleSnapshotSource(host.root, id);
27401
+ return renderSoulSelf(dir, agent, signal, readSelfBudget(dir));
27402
+ } });
27403
+ ctx.effect(() => () => {
27404
+ maintenance.dispose();
27405
+ budgets.clear();
27406
+ });
27407
+ }
27408
+ //#endregion
27409
+ //#region engine/promotion-pool.mjs
27410
+ /**
27411
+ * 晋升相位追踪器池:同一挂载内按配置复用追踪器,事件由池**统一喂**。
27412
+ *
27413
+ * 为什么需要池:`createEpochPromotion` 自己不订阅任何事件(它的 ctx 参数完全用不到,
27414
+ * 见 `compaction-epoch.mjs` 的条目容器注释),初始相位靠 `status()` 触发的**全量冷扫**建立。
27415
+ * 此前 `executor` 每次 pre-step apply 建两份、`phase` 每个编译谓词建一份、
27416
+ * `instruction-hint` 每次 apply 建一份,各自订阅、各自喂——重复建的代价就是重复冷扫。
27417
+ *
27418
+ * **池自己订阅**:订阅随池的归属走(自建池随所在挂载释放,注入池由创建者释放),
27419
+ * 调用方不需要记住「注入时谁来喂事件」——忘了就是相位停在冷扫那一刻的静默失效。
27420
+ *
27421
+ * 状态仍按 `session.id` 分条(追踪器内部的 `sessionState`):池只合并**实例与订阅**,
27422
+ * 不共享会话状态,也不改变「状态是快路径、真相在事件流」这条纪律。
27423
+ */
27424
+ /**
27425
+ * 进缓存的选项键:**任何影响判定的配置都必须登记**,否则同 key 会掩盖不同行为。
27426
+ * 与 `createEpochPromotion` 读取的选项一一对应。
27427
+ */
27428
+ const KEYED_OPTIONS = [
27429
+ "includeSubagents",
27430
+ "promoteGate",
27431
+ "promoteAfterFirstResponse",
27432
+ "reasoningPattern",
27433
+ "reasoningNegativePattern",
27434
+ "reasoningFlags",
27435
+ "maxPromoteSteps"
27436
+ ];
27437
+ const trackKey = (promoteEvents, options) => JSON.stringify([[...promoteEvents].sort(), KEYED_OPTIONS.map((key) => [key, options[key]])]);
27438
+ /**
27439
+ * @param {object} ctx 归属上下文(`ctx.on` 用来订阅 `session/event`)
27440
+ * @returns {{ tracker: (promoteEvents: readonly string[], options?: object) => object, dispose: () => void }}
27441
+ */
27442
+ function createPromotionPool(ctx) {
27443
+ const trackers = /* @__PURE__ */ new Map();
27444
+ let disposed = false;
27445
+ const release = ctx.on("session/event", (session, event) => {
27446
+ for (const tracker of trackers.values()) tracker.observe(session, event);
27447
+ });
27448
+ return {
27449
+ /** 按配置取追踪器:同配置复用、异配置分建(配置不同就是不同的相位语义)。 */
27450
+ tracker(promoteEvents, options = {}) {
27451
+ const key = trackKey(promoteEvents, options);
27452
+ let tracker = trackers.get(key);
27453
+ if (tracker === void 0) {
27454
+ tracker = createEpochPromotion(promoteEvents, options);
27455
+ trackers.set(key, tracker);
27456
+ }
27457
+ return tracker;
27458
+ },
27459
+ /** 释放订阅并丢弃实例;追踪器本身没有 disposer(状态随闭包回收)。 */
27460
+ dispose() {
27461
+ if (disposed) return;
27462
+ disposed = true;
27463
+ trackers.clear();
27464
+ release?.();
27465
+ }
27466
+ };
27467
+ }
27468
+ //#endregion
27469
+ //#region src/runtime/official-instruction-filter.ts
27470
+ /** 官方待注入消息的逐文件过滤;不读取或改写会话历史。 */
27471
+ const isRecord$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
27472
+ /**
27473
+ * 路径来自 source.changes;标题仅用于定位正文,不用于恢复官方加载状态。
27474
+ * 重复标题、未知包装或来源与段落不一致时原样放行,避免误删其他文件。
27475
+ */
27476
+ function filterOfficialInstructionMessages(messages, isDisabled) {
27477
+ const diagnostics = [];
27478
+ let changed = false;
27479
+ const filtered = messages.flatMap((message) => {
27480
+ if (!isRecord$1(message) || !isRecord$1(message.source) || message.source.kind !== "agent-instructions") return [message];
27481
+ const source = message.source;
27482
+ if (!Array.isArray(source.changes)) return [message];
26403
27483
  const changes = source.changes;
26404
27484
  const disabled = new Set(changes.flatMap((change) => isRecord$1(change) && typeof change.path === "string" && isDisabled(change.path, source) ? [change.path] : []));
26405
27485
  if (disabled.size === 0) return [message];
@@ -26735,6 +27815,156 @@ function planPersonaSections(options) {
26735
27815
  };
26736
27816
  }
26737
27817
  //#endregion
27818
+ //#region src/host/module-data-import.ts
27819
+ /** 显式选择的书房数据:只读预览、只补缺失、完整候选交换。 */
27820
+ const digest = (value) => createHash("sha256").update(value).digest("hex");
27821
+ const dataPath = (path) => "data/" + (path === "02-墨.jsonl" ? "02-soul.jsonl" : path);
27822
+ function prepare(moduleDir, input) {
27823
+ if (!input || typeof input !== "object" || Array.isArray(input) || Object.keys(input).some((key) => ![
27824
+ "moduleId",
27825
+ "sourceDirectory",
27826
+ "paths",
27827
+ "initialize",
27828
+ "expectedRevision"
27829
+ ].includes(key))) throw new Error("数据导入包含未知字段或不是对象");
27830
+ assertNoModuleLinks(moduleDir);
27831
+ if (!isEditableModuleDir(moduleDir)) throw new Error("只能导入用户模块私有数据");
27832
+ const dir = assertModuleDirectory(dirname(moduleDir), input.moduleId);
27833
+ if (dir !== resolve(moduleDir) || basename(dir) !== input.moduleId) throw new Error("数据导入模块主语不一致");
27834
+ if (input.initialize !== void 0 && input.initialize !== true) throw new Error("initialize 只能是 true");
27835
+ if (input.initialize && (input.sourceDirectory !== void 0 || input.paths !== void 0)) throw new Error("初始化与外部来源选择不能混用");
27836
+ if (!input.initialize && (typeof input.sourceDirectory !== "string" || !isAbsolute(input.sourceDirectory))) throw new Error("来源目录必须是用户显式选择的绝对路径");
27837
+ const sourceDirectory = input.initialize ? dir : resolve(input.sourceDirectory);
27838
+ assertNoModuleLinks(sourceDirectory);
27839
+ if (!lstatSync(sourceDirectory).isDirectory()) throw new Error("来源不是普通目录");
27840
+ if (!input.initialize && (!Array.isArray(input.paths) || input.paths.length === 0 || input.paths.length > 2048)) throw new Error("必须选择有界的来源文件列表");
27841
+ if (input.expectedRevision !== void 0 && (typeof input.expectedRevision !== "string" || !/^[a-f0-9]{64}$/.test(input.expectedRevision))) throw new Error("数据导入版本无效");
27842
+ const version = directoryVersionOf(dir);
27843
+ const seen = /* @__PURE__ */ new Set();
27844
+ let total = 0;
27845
+ const manifest = input.initialize ? readModuleManifest(dir) : void 0;
27846
+ const selected = manifest ? [...manifest.dirs.map((path) => ({
27847
+ source: path,
27848
+ path,
27849
+ kind: "dir"
27850
+ })), ...manifest.files.map((file) => ({
27851
+ source: file.from,
27852
+ path: file.to,
27853
+ kind: "file"
27854
+ }))] : input.paths.map((path) => ({
27855
+ source: path,
27856
+ path: dataPath(path),
27857
+ kind: "file"
27858
+ }));
27859
+ if (selected.length > 2048) throw new Error("来源文件超过数量上限");
27860
+ const files = selected.map(({ source: path, path: targetPath, kind }) => {
27861
+ assertSafeRelativePath(path);
27862
+ assertSafeRelativePath(targetPath);
27863
+ if (targetPath === "data/02-soul.jsonl" && modulePathExists(join(dir, "data", "02-墨.jsonl"))) throw new Error("目标已有旧账本 data/02-墨.jsonl,请先显式核对并迁移,拒绝创建第二账本");
27864
+ const key = targetPath.normalize("NFC").toLowerCase();
27865
+ if (seen.has(key)) throw new Error("重复或冲突的目标路径:" + targetPath);
27866
+ seen.add(key);
27867
+ const source = join(sourceDirectory, path);
27868
+ let bytes = Buffer.alloc(0);
27869
+ if (kind === "file") {
27870
+ assertNoModuleLinks(source);
27871
+ const stat = lstatSync(source);
27872
+ if (!stat.isFile()) throw new Error("来源不是普通文件:" + path);
27873
+ if (stat.size > 8388608) throw new Error("来源文件超过 8 MiB:" + path);
27874
+ bytes = readFileSync(source);
27875
+ }
27876
+ total += bytes.length;
27877
+ if (bytes.length > 8388608 || total > 67108864) throw new Error("来源数据超过大小上限");
27878
+ new TextDecoder("utf-8", { fatal: true }).decode(bytes);
27879
+ const target = join(dir, targetPath);
27880
+ assertNoModuleLinks(target);
27881
+ let state = "create";
27882
+ if (modulePathExists(target)) {
27883
+ const targetStat = lstatSync(target);
27884
+ if (kind === "dir") {
27885
+ if (!targetStat.isDirectory()) throw new Error("目标不是普通目录:" + targetPath);
27886
+ state = "skip";
27887
+ } else {
27888
+ if (!targetStat.isFile()) throw new Error("目标不是普通文件:" + targetPath);
27889
+ state = targetStat.size === bytes.length && readFileSync(target).equals(bytes) ? "skip" : "conflict";
27890
+ }
27891
+ }
27892
+ return {
27893
+ source: path,
27894
+ path: targetPath,
27895
+ kind,
27896
+ state,
27897
+ bytes
27898
+ };
27899
+ });
27900
+ return {
27901
+ dir,
27902
+ version,
27903
+ files,
27904
+ revision: digest(JSON.stringify([
27905
+ "module-data-import/1",
27906
+ dir,
27907
+ version,
27908
+ sourceDirectory,
27909
+ files.map((file) => [
27910
+ file.source,
27911
+ file.path,
27912
+ file.kind,
27913
+ digest(file.bytes)
27914
+ ])
27915
+ ])),
27916
+ entries: files.map((file) => ({
27917
+ source: file.source,
27918
+ path: file.path,
27919
+ kind: file.kind,
27920
+ state: file.state,
27921
+ bytes: file.bytes.length
27922
+ }))
27923
+ };
27924
+ }
27925
+ function previewModuleDataImport(moduleDir, input) {
27926
+ const { revision, entries } = prepare(moduleDir, input);
27927
+ return {
27928
+ revision,
27929
+ entries
27930
+ };
27931
+ }
27932
+ function applyModuleDataImport(moduleDir, input) {
27933
+ const plan = prepare(moduleDir, input);
27934
+ if (input.expectedRevision !== plan.revision) throw new Error("数据导入预览已过期,请重新预览");
27935
+ if (!plan.files.some((file) => file.state === "create")) return {
27936
+ revision: plan.revision,
27937
+ entries: plan.entries,
27938
+ installed: false
27939
+ };
27940
+ const outcome = installModuleDirectory({
27941
+ root: dirname(plan.dir),
27942
+ id: input.moduleId,
27943
+ expectedVersion: plan.version,
27944
+ build: (candidate) => {
27945
+ cpSync(plan.dir, candidate, { recursive: true });
27946
+ for (const file of plan.files) {
27947
+ if (file.state !== "create") continue;
27948
+ const target = join(candidate, file.path);
27949
+ if (file.kind === "dir") {
27950
+ mkdirSync(target, { recursive: true });
27951
+ continue;
27952
+ }
27953
+ mkdirSync(dirname(target), { recursive: true });
27954
+ writeFileSync(target, file.bytes, { flag: "wx" });
27955
+ }
27956
+ }
27957
+ });
27958
+ if (outcome.state === "conflict") throw new Error("数据导入预览已过期,请重新预览");
27959
+ if (outcome.state === "recovery-required") throw new Error("数据导入恢复失败,原目录备份保留在:" + outcome.backupPath);
27960
+ return {
27961
+ revision: plan.revision,
27962
+ entries: plan.entries,
27963
+ installed: true,
27964
+ ...outcome.leftoverStage ? { leftoverStage: outcome.leftoverStage } : {}
27965
+ };
27966
+ }
27967
+ //#endregion
26738
27968
  //#region src/runtime/tui.ts
26739
27969
  /** dsh-tui 全局开关:键名与 settings 路径一致(settings mutate)。 */
26740
27970
  const TUI_GLOBAL_SWITCHES = [["modulesEnabled", "模块运行总开关"]];
@@ -26818,14 +28048,32 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
26818
28048
  return sctx.commands.register({
26819
28049
  name: "prompt-tool",
26820
28050
  description: "提示词工具:查看或切换本插件开关",
26821
- input: { hint: "status | module [模块id] | on/off/toggle <开关> | skill <技能名> on/off/toggle | [module <模块id>] config <id> [on/off/toggle]" },
28051
+ input: { hint: "status | module [模块id] | data-import <JSON> | on/off/toggle <开关> | skill <技能名> on/off/toggle | [module <模块id>] config <id> [on/off/toggle]" },
26822
28052
  handler: async (invocation) => {
28053
+ const dataImport = /^data-import\s+([\s\S]+)$/.exec(invocation.rawInput.trim());
28054
+ if (dataImport) try {
28055
+ const request = JSON.parse(dataImport[1]);
28056
+ if (!request || typeof request.moduleId !== "string") throw new Error("数据导入必须显式声明 moduleId");
28057
+ const dir = getModuleDirectory?.(request.moduleId);
28058
+ if (!dir) throw new Error("未找到模块:" + request.moduleId);
28059
+ const result = request.expectedRevision === void 0 ? previewModuleDataImport(dir, request) : applyModuleDataImport(dir, request);
28060
+ return {
28061
+ kind: "success",
28062
+ text: JSON.stringify(result)
28063
+ };
28064
+ } catch (error) {
28065
+ return {
28066
+ kind: "error",
28067
+ text: `数据导入失败:${error instanceof Error ? error.message : String(error)}`
28068
+ };
28069
+ }
26823
28070
  const usage = () => ({
26824
28071
  kind: "error",
26825
28072
  text: `用法:/prompt-tool status
26826
28073
  /prompt-tool on|off|toggle <${[...TUI_GLOBAL_SWITCHES, ...TUI_PARAM_SWITCHES].map(([key]) => key).join("|")}>\n /prompt-tool skill <frontmatter 技能名> on|off|toggle
26827
28074
  /prompt-tool module <模块id>(声明本次会话主语,随后命令都作用在它上面)
26828
28075
  /prompt-tool module(显示当前声明)
28076
+ /prompt-tool data-import <JSON>(moduleId + sourceDirectory/paths 或 initialize:true;预览后带 expectedRevision 应用,只补缺失)
26829
28077
  /prompt-tool module <模块id> on|off|toggle <模块参数>(单条命令前声明并执行)
26830
28078
  /prompt-tool config <id>(id 可含空格)
26831
28079
  /prompt-tool config <id> on|off|toggle
@@ -27376,6 +28624,166 @@ function createModuleVariableAdapter(moduleRoot) {
27376
28624
  };
27377
28625
  }
27378
28626
  //#endregion
28627
+ //#region src/runtime/module-initialization.ts
28628
+ const ACTIVE = 2;
28629
+ /** 只跟踪装配拥有的初始化后代;任务执行异常不经过 Fiber 生命周期。 */
28630
+ function createModuleInitialization(ctx) {
28631
+ const groups = /* @__PURE__ */ new Map();
28632
+ const tasks = /* @__PURE__ */ new Map();
28633
+ const pruneDisposed = (group) => {
28634
+ for (const [id, fiber] of group.fibers) if (fiber.uid === null) {
28635
+ group.fibers.delete(id);
28636
+ groups.delete(id);
28637
+ }
28638
+ };
28639
+ const check = (group) => {
28640
+ if (!group.active || group.failed) return;
28641
+ pruneDisposed(group);
28642
+ const task = Promise.resolve().then(async () => {
28643
+ await Promise.all([...group.fibers.values()].filter((fiber) => fiber.uid !== null).map((fiber) => fiber.await()));
28644
+ if (!group.active || group.failed) return;
28645
+ pruneDisposed(group);
28646
+ if ([...group.fibers.values()].every((fiber) => fiber.uid === null || fiber.state === ACTIVE)) group.owner.ready();
28647
+ }).catch((error) => {
28648
+ if (!group.active || group.failed) return;
28649
+ group.failed = true;
28650
+ group.owner.failed(error);
28651
+ }).finally(() => {
28652
+ tasks.delete(task);
28653
+ });
28654
+ tasks.set(task, group);
28655
+ };
28656
+ const listeners = [ctx.on("internal/plugin", (fiber) => {
28657
+ const parentId = fiber.parent.fiber.uid;
28658
+ const group = parentId === null ? void 0 : groups.get(parentId);
28659
+ if (group === void 0 || !group.active) return;
28660
+ if (fiber.uid === null) {
28661
+ pruneDisposed(group);
28662
+ return;
28663
+ }
28664
+ group.fibers.set(fiber.uid, fiber);
28665
+ groups.set(fiber.uid, group);
28666
+ check(group);
28667
+ }, { global: true }), ctx.on("internal/status", (fiber) => {
28668
+ const id = fiber.uid ?? fiber.parent.fiber.uid;
28669
+ const group = id === null ? void 0 : groups.get(id);
28670
+ if (group !== void 0) check(group);
28671
+ }, { global: true })];
28672
+ ctx.effect(() => () => {
28673
+ for (const group of groups.values()) group.active = false;
28674
+ groups.clear();
28675
+ for (const off of listeners) off();
28676
+ }, "prompt-tool module initialization");
28677
+ return {
28678
+ observe(root, owner) {
28679
+ if (root.uid === null) return () => {};
28680
+ const group = {
28681
+ fibers: /* @__PURE__ */ new Map([[root.uid, root]]),
28682
+ owner,
28683
+ active: true,
28684
+ failed: false
28685
+ };
28686
+ groups.set(root.uid, group);
28687
+ check(group);
28688
+ return () => {
28689
+ group.active = false;
28690
+ for (const id of group.fibers.keys()) groups.delete(id);
28691
+ for (const [task, owner] of tasks) if (owner === group) tasks.delete(task);
28692
+ };
28693
+ },
28694
+ async settled(roots) {
28695
+ const selected = roots === void 0 ? void 0 : new Set(roots.flatMap((root) => root.uid === null ? [] : [groups.get(root.uid)]));
28696
+ while (true) {
28697
+ const pending = [...tasks].filter(([, group]) => group.active && (selected === void 0 || selected.has(group))).map(([task]) => task);
28698
+ if (pending.length === 0) return;
28699
+ await Promise.all(pending);
28700
+ }
28701
+ }
28702
+ };
28703
+ }
28704
+ //#endregion
28705
+ //#region src/host/generation-recovery-state.ts
28706
+ /** 会话恢复额度的唯一持久owner,重挂不重置,先扣额再允许唤醒。 */
28707
+ function createGenerationRecoveryState(root = join(storageRoot(), "generation-recovery")) {
28708
+ const keyOf = (sessionId) => {
28709
+ if (typeof sessionId !== "string" || !sessionId.length || sessionId.length > 1024) throw new Error("恢复会话身份无效");
28710
+ return createHash("sha256").update(sessionId).digest("hex");
28711
+ };
28712
+ const read = (sessionId) => {
28713
+ const file = join(root, keyOf(sessionId) + ".json");
28714
+ assertNoModuleLinks(file);
28715
+ if (!existsSync(file)) return {
28716
+ sessionId,
28717
+ used: 0,
28718
+ executions: []
28719
+ };
28720
+ if (!statSync(file).isFile() || statSync(file).size > 131072) throw new Error("恢复额度文件无效或超限");
28721
+ const data = JSON.parse(readFileSync(file, "utf8"));
28722
+ if (data.sessionId !== sessionId || !Number.isSafeInteger(data.used) || data.used < 0 || data.used > 1e3 || !Array.isArray(data.executions) || data.executions.length !== data.used || data.executions.some((value) => typeof value !== "string" || !/^\d+:\d+$/.test(value)) || new Set(data.executions).size !== data.used) throw new Error("恢复额度文件损坏,拒绝重置");
28723
+ return data;
28724
+ };
28725
+ return {
28726
+ read,
28727
+ claim(sessionId, execution, limit) {
28728
+ if (!/^\d+:\d+$/.test(execution) || !Number.isSafeInteger(limit) || limit < 0 || limit > 1e3) throw new Error("恢复执行身份或额度无效");
28729
+ const key = keyOf(sessionId);
28730
+ assertNoModuleLinks(root);
28731
+ mkdirSync(root, { recursive: true });
28732
+ return withModuleLock(root, key, () => {
28733
+ const state = read(sessionId);
28734
+ if (state.executions.includes(execution) || state.used >= limit) return {
28735
+ claimed: false,
28736
+ used: state.used,
28737
+ limit
28738
+ };
28739
+ const file = join(root, key + ".json"), temporary = join(root, "." + randomUUID() + ".tmp");
28740
+ try {
28741
+ writeFileSync(temporary, JSON.stringify({
28742
+ sessionId,
28743
+ used: state.used + 1,
28744
+ executions: [...state.executions, execution]
28745
+ }), { flag: "wx" });
28746
+ assertNoModuleLinks(file);
28747
+ renameSync(temporary, file);
28748
+ } finally {
28749
+ rmSync(temporary, { force: true });
28750
+ }
28751
+ return {
28752
+ claimed: true,
28753
+ used: state.used + 1,
28754
+ limit
28755
+ };
28756
+ });
28757
+ }
28758
+ };
28759
+ }
28760
+ //#endregion
28761
+ //#region engine/context-provenance.mjs
28762
+ const configObject = passthrough((value, plugin, field) => {
28763
+ if (value === void 0) return {};
28764
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new TypeError(`${plugin}: ${field} must be an object`);
28765
+ return value;
28766
+ });
28767
+ defineConfig({
28768
+ enabled: bool({ default: false }),
28769
+ audience: enumOf([
28770
+ "all",
28771
+ "main",
28772
+ "subagent"
28773
+ ], { default: "all" }),
28774
+ auto: bool({ default: true }),
28775
+ modelSwitch: bool({ default: true }),
28776
+ prune: bool({ default: true }),
28777
+ basic: configObject,
28778
+ pruner: configObject,
28779
+ recoveryRetainTokens: int$2({
28780
+ min: 0,
28781
+ default: 1024
28782
+ })
28783
+ });
28784
+ const runtimes = /* @__PURE__ */ new WeakMap();
28785
+ const contextRuntimeOf = (agent) => runtimes.get(agent);
28786
+ //#endregion
27379
28787
  //#region engine/rule-runtime.mjs
27380
28788
  const noNext = /* @__PURE__ */ new Set([
27381
28789
  "agent/inbox/inserted",
@@ -27384,6 +28792,7 @@ const noNext = /* @__PURE__ */ new Set([
27384
28792
  "subagent/end"
27385
28793
  ]);
27386
28794
  const compare = (a, b) => a.action.channelOrder - b.action.channelOrder || a.moduleId.localeCompare(b.moduleId) || a.ruleIndex - b.ruleIndex || a.action.actionIndex - b.action.actionIndex;
28795
+ const sourceError = (error, moduleId) => Object.assign(new Error(String(error?.message ?? error), { cause: error }), { sourceModuleId: moduleId });
27387
28796
  /** 所有来源只进入这个装配入口;受众限定固定效果要求 ctx 属于 options.agent 的 scope。 */
27388
28797
  function mountRuleSources(ctx, sources, options = {}) {
27389
28798
  validateDeliverySources(sources);
@@ -27393,12 +28802,6 @@ function mountRuleSources(ctx, sources, options = {}) {
27393
28802
  })();
27394
28803
  const pool = options.promotionPool ?? coordinator?.promotionPool ?? createPromotionPool(ctx);
27395
28804
  const ownsPool = options.promotionPool === void 0 && coordinator?.promotionPool === void 0;
27396
- rebindConditions(sources, {
27397
- ctx,
27398
- standingMountFor: options.standingMountFor,
27399
- promotionPool: pool,
27400
- variableStore: options.variableStore
27401
- });
27402
28805
  const warnOnce = options.warnOnce ?? createWarnOnce(ctx, options.plugin ?? "rule-engine");
27403
28806
  const report = options.onOutcome === void 0 ? void 0 : (() => {
27404
28807
  const moduleOfRule = /* @__PURE__ */ new Map();
@@ -27420,15 +28823,6 @@ function mountRuleSources(ctx, sources, options = {}) {
27420
28823
  configs: [],
27421
28824
  ruleActions: []
27422
28825
  }]));
27423
- const rules = sources.flatMap((source) => source.rules.filter((rule) => rule.enabled !== false));
27424
- const observer = wireTriggerObservers(ctx, [...rules, ...rules.flatMap((rule) => (rule.actions ?? []).filter((action) => typeof action.actionWhen?.observe === "function").map((action) => ({
27425
- id: action.id,
27426
- when: action.actionWhen
27427
- })))], {
27428
- plugin: options.plugin ?? "rule-engine",
27429
- warnOnce
27430
- });
27431
- if (observer) releases.push(observer);
27432
28826
  let active = true;
27433
28827
  const bind = (item, on) => prepareAction(item.action, {
27434
28828
  plugin: options.plugin,
@@ -27437,6 +28831,26 @@ function mountRuleSources(ctx, sources, options = {}) {
27437
28831
  on
27438
28832
  })(ctx);
27439
28833
  try {
28834
+ for (const source of sources) try {
28835
+ rebindConditions([source], {
28836
+ ctx,
28837
+ standingMountFor: options.standingMountFor,
28838
+ promotionPool: pool,
28839
+ variableStore: options.variableStore,
28840
+ manifestCheck: options.manifestCheck
28841
+ });
28842
+ } catch (error) {
28843
+ throw sourceError(error, source.moduleId);
28844
+ }
28845
+ const rules = sources.flatMap((source) => source.rules.filter((rule) => rule.enabled !== false));
28846
+ const observer = wireTriggerObservers(ctx, [...rules, ...rules.flatMap((rule) => (rule.actions ?? []).filter((action) => typeof action.actionWhen?.observe === "function").map((action) => ({
28847
+ id: action.id,
28848
+ when: action.actionWhen
28849
+ })))], {
28850
+ plugin: options.plugin ?? "rule-engine",
28851
+ warnOnce
28852
+ });
28853
+ if (observer) releases.push(observer);
27440
28854
  for (const source of sources) for (const [ruleIndex, rule] of source.rules.entries()) {
27441
28855
  if (rule.enabled === false) continue;
27442
28856
  for (const action of rule.actions) {
@@ -27466,6 +28880,7 @@ function mountRuleSources(ctx, sources, options = {}) {
27466
28880
  releases.push(wireLayers(ctx, textConfigs, warnOnce, {
27467
28881
  agent: options.agent,
27468
28882
  turnStopBudgets,
28883
+ sourceError,
27469
28884
  on: (channel, handler) => {
27470
28885
  if (channel !== point.channel) throw new TypeError(`rule action registered outside ${point.channel}: ${channel}`);
27471
28886
  handlers.push({ handler });
@@ -27512,7 +28927,12 @@ function mountRuleSources(ctx, sources, options = {}) {
27512
28927
  created.push(entry);
27513
28928
  return () => {};
27514
28929
  };
27515
- const dispose = bind(item, on);
28930
+ let dispose;
28931
+ try {
28932
+ dispose = bind(item, on);
28933
+ } catch (error) {
28934
+ throw sourceError(error, moduleId);
28935
+ }
27516
28936
  if (typeof dispose?.miss === "function") for (const entry of created) entry.miss = dispose.miss;
27517
28937
  releases.push(dispose);
27518
28938
  }
@@ -27806,7 +29226,13 @@ function duplicateDedupeIdentities(prepared) {
27806
29226
  }
27807
29227
  function createAgentAssembly(ctx, options) {
27808
29228
  const variableStore = createModuleVariableAdapter(options.moduleRoot);
29229
+ const manifestCheck = ctx.get(MANIFEST_CHECK_SERVICE);
29230
+ const recoveryState = createGenerationRecoveryState(join(dirname(options.moduleRoot), "generation-recovery"));
27809
29231
  const ruleDiagnostics = createRuleDiagnosticsLedger();
29232
+ const initialization = createModuleInitialization(ctx);
29233
+ const lastGood = /* @__PURE__ */ new WeakMap();
29234
+ const sourceVersions = /* @__PURE__ */ new WeakMap();
29235
+ const rejectedVersions = /* @__PURE__ */ new WeakMap();
27810
29236
  const mounts = /* @__PURE__ */ new Map();
27811
29237
  const knownAgents = /* @__PURE__ */ new Set();
27812
29238
  const watched = /* @__PURE__ */ new Map();
@@ -27897,9 +29323,69 @@ function createAgentAssembly(ctx, options) {
27897
29323
  mounts.delete(sessionId);
27898
29324
  await current.fiber.dispose();
27899
29325
  };
29326
+ const reject = (agent, item) => {
29327
+ let rejected = rejectedVersions.get(agent);
29328
+ if (rejected === void 0) {
29329
+ rejected = /* @__PURE__ */ new Map();
29330
+ rejectedVersions.set(agent, rejected);
29331
+ }
29332
+ let versions = rejected.get(item.moduleId);
29333
+ if (versions === void 0) {
29334
+ versions = /* @__PURE__ */ new Set();
29335
+ rejected.set(item.moduleId, versions);
29336
+ }
29337
+ versions.add(sourceVersions.get(item) ?? "");
29338
+ if (lastGood.get(agent)?.get(item.moduleId) === item) lastGood.get(agent)?.delete(item.moduleId);
29339
+ };
27900
29340
  const mount = async (agent, prepared, plan, duplicateIdentities = [], stamp = "", content = "") => {
29341
+ let mounted = false, live = true;
29342
+ const initializationErrors = [];
29343
+ const waiting = new Map(prepared.map((item) => [item, /* @__PURE__ */ new Set()]));
29344
+ const observers = [];
29345
+ const initializationRoots = [];
29346
+ const promote = () => {
29347
+ if (!live || !mounted || !active || disposed.has(agent)) return;
29348
+ let good = lastGood.get(agent);
29349
+ if (good === void 0) {
29350
+ good = /* @__PURE__ */ new Map();
29351
+ lastGood.set(agent, good);
29352
+ }
29353
+ for (const item of prepared) if (waiting.get(item)?.size === 0 && !initializationErrors.some((failure) => failure.owners.includes(item))) good.set(item.moduleId, item);
29354
+ };
29355
+ const observe = (child, owners) => {
29356
+ initializationRoots.push(child);
29357
+ const token = {};
29358
+ for (const item of owners) waiting.get(item)?.add(token);
29359
+ observers.push(initialization.observe(child, {
29360
+ ready: () => {
29361
+ for (const item of owners) waiting.get(item)?.delete(token);
29362
+ promote();
29363
+ },
29364
+ failed: (error) => {
29365
+ if (!live || !active || disposed.has(agent)) return;
29366
+ initializationErrors.push({
29367
+ owners,
29368
+ error
29369
+ });
29370
+ if (!mounted) return;
29371
+ for (const item of owners) reject(agent, item);
29372
+ serial(async () => {
29373
+ if (!live || !active || disposed.has(agent)) return;
29374
+ try {
29375
+ await replace([agent]);
29376
+ } catch (failure) {
29377
+ warn(failure);
29378
+ }
29379
+ });
29380
+ warn(new Error(`模块 ${owners.map((item) => item.moduleId).join("、")} 初始化失败:${failureReason(error)}`, { cause: error }));
29381
+ }
29382
+ }));
29383
+ };
27901
29384
  const services = /* @__PURE__ */ new Set();
27902
- for (const item of prepared) for (const name of item.services) services.add(name);
29385
+ for (const item of prepared) for (const name of item.services) {
29386
+ if (agent.ctx.get(name) === void 0) throw Object.assign(/* @__PURE__ */ new Error(`模块 ${item.moduleId} 挂载所需宿主能力不可用:${name}`), { sourceModuleId: item.moduleId });
29387
+ services.add(name);
29388
+ }
27903
29389
  const personaActive = plan.sections.length > 0 || plan.suppressRuntimeContext;
27904
29390
  if (personaActive) services.add("systemPrompt");
27905
29391
  const fiber = agent.ctx.plugin({
@@ -27919,7 +29405,8 @@ function createAgentAssembly(ctx, options) {
27919
29405
  agent,
27920
29406
  onOutcome: ruleDiagnostics.record,
27921
29407
  variableStore,
27922
- standingMountFor
29408
+ standingMountFor,
29409
+ manifestCheck
27923
29410
  }));
27924
29411
  if (personaActive) {
27925
29412
  const capture = options.personaCapture;
@@ -27942,20 +29429,68 @@ function createAgentAssembly(ctx, options) {
27942
29429
  return rollback;
27943
29430
  }, "prompt-tool persona");
27944
29431
  }
29432
+ const applyCapability = async (capabilityCtx, item, module) => {
29433
+ const result = await module.apply(capabilityCtx, module.config, {
29434
+ agent,
29435
+ sourceModuleId: item.moduleId,
29436
+ sourceModuleDir: resolveModuleDir(item.moduleId, options.moduleRoot),
29437
+ recoveryState,
29438
+ contextRuntimeOf
29439
+ });
29440
+ if (typeof result === "function") capabilityCtx.effect(() => result, module.id);
29441
+ };
29442
+ const ownTokens = new Map(prepared.map((item) => [item, {}]));
29443
+ for (const item of prepared) waiting.get(item)?.add(ownTokens.get(item));
29444
+ for (const item of prepared) {
29445
+ const capabilities = item.modules.filter((module) => PRIVATE_SERVICES[module.id] === void 0 || module.id === "retrieval");
29446
+ if (capabilities.length === 0) continue;
29447
+ const child = scopeCtx.plugin({
29448
+ name: `prompt-tool-module:${item.moduleId}`,
29449
+ apply: async (moduleCtx) => {
29450
+ for (const module of capabilities) await applyCapability(moduleCtx, item, module);
29451
+ }
29452
+ });
29453
+ observe(child, [item]);
29454
+ try {
29455
+ await child;
29456
+ } catch (error) {
29457
+ await child.dispose();
29458
+ throw Object.assign(new Error(`模块 ${item.moduleId} 挂载失败:${failureReason(error)}`, { cause: error }), { sourceModuleId: item.moduleId });
29459
+ }
29460
+ }
27945
29461
  const privateMounted = /* @__PURE__ */ new Set();
27946
29462
  for (const item of prepared) for (const module of item.modules) {
27947
- if (PRIVATE_SERVICES[module.id] !== void 0) {
27948
- if (privateMounted.has(module.id)) continue;
27949
- privateMounted.add(module.id);
29463
+ if (PRIVATE_SERVICES[module.id] === void 0 || module.id === "retrieval" || privateMounted.has(module.id)) continue;
29464
+ const owners = prepared.filter((source) => source.modules.some((candidate) => candidate.id === module.id));
29465
+ const child = scopeCtx.plugin({
29466
+ name: `prompt-tool-shared:${module.id}`,
29467
+ apply: async (sharedCtx) => {
29468
+ await applyCapability(sharedCtx, item, module);
29469
+ }
29470
+ });
29471
+ observe(child, owners);
29472
+ try {
29473
+ await child;
29474
+ } catch (error) {
29475
+ throw Object.assign(new Error(`模块 ${owners.map((owner) => owner.moduleId).join("、")} 共享能力 ${module.id} 初始化失败:${failureReason(error)}`, { cause: error }), { sourceModuleIds: owners.map((owner) => owner.moduleId) });
27950
29476
  }
27951
- const result = await module.apply(scopeCtx, module.config, { agent });
27952
- if (typeof result === "function") scopeCtx.effect(() => result, module.id);
29477
+ privateMounted.add(module.id);
27953
29478
  }
29479
+ for (const item of prepared) waiting.get(item)?.delete(ownTokens.get(item));
27954
29480
  }
27955
29481
  });
27956
- const disposeFiber = ctx.effect(() => () => fiber.dispose(), `prompt-tool assembly ${agent.id}`);
29482
+ const disposeFiber = ctx.effect(() => () => {
29483
+ live = false;
29484
+ for (const off of observers) off();
29485
+ return fiber.dispose();
29486
+ }, `prompt-tool assembly ${agent.id}`);
27957
29487
  try {
27958
29488
  await fiber;
29489
+ await initialization.settled(initializationRoots);
29490
+ if (initializationErrors.length > 0) {
29491
+ const failure = initializationErrors[0];
29492
+ throw Object.assign(new Error(`模块 ${failure.owners.map((item) => item.moduleId).join("、")} 初始化失败:${failureReason(failure.error)}`, { cause: failure.error }), { sourceModuleIds: failure.owners.map((item) => item.moduleId) });
29493
+ }
27959
29494
  } catch (error) {
27960
29495
  await disposeFiber();
27961
29496
  throw error;
@@ -27975,11 +29510,18 @@ function createAgentAssembly(ctx, options) {
27975
29510
  content,
27976
29511
  plan
27977
29512
  });
29513
+ mounted = true;
29514
+ promote();
27978
29515
  };
27979
29516
  const assemblyStamp = (ids, personas) => JSON.stringify(personas) + readModulesRevision(options.moduleRoot, ids);
27980
- const contentSignature = (ids, personas) => JSON.stringify([ids.map((id) => {
27981
- const { variables: _variables, ...definition } = loadModuleSpec(assertModuleDirectory(options.moduleRoot, id));
27982
- return [id, definition];
29517
+ const contentSignature = (ids, personas, previous = "") => JSON.stringify([ids.map((id) => {
29518
+ try {
29519
+ const { variables: _variables, ...definition } = loadModuleSpec(assertModuleDirectory(options.moduleRoot, id));
29520
+ return [id, definition];
29521
+ } catch (error) {
29522
+ warnOnce(`模块 ${id} 读取失败:${failureReason(error)}`);
29523
+ return (previous === "" ? void 0 : JSON.parse(previous)[0].find((entry) => entry[0] === id)) ?? [id, { readError: failureReason(error) }];
29524
+ }
27983
29525
  }), personas]);
27984
29526
  const watchSources = (ids, personas) => {
27985
29527
  const files = /* @__PURE__ */ new Set([enableTablePath(options.moduleRoot), ...[...ids, ...(personas ?? []).map((item) => item.moduleId)].map((id) => {
@@ -28001,35 +29543,59 @@ function createAgentAssembly(ctx, options) {
28001
29543
  }, listener);
28002
29544
  }
28003
29545
  };
28004
- const replace = async (targets) => {
29546
+ const replace = async (targets, retry = false) => {
29547
+ readGlobalLayerSettings(options.moduleRoot);
28005
29548
  const ids = [...options.enabledModules()];
28006
29549
  const personaModules = options.personaModules?.();
28007
29550
  watchSources(ids, personaModules);
28008
29551
  const live = targets.filter((agent) => active && !disposed.has(agent));
28009
- const prepared = [];
28010
- for (const id of ids) prepared.push(await prepareAssembly(options.moduleRoot, id, (name) => live.every((agent) => agent.ctx.get(name) !== void 0)));
29552
+ const failures = [];
29553
+ const candidates = [];
29554
+ for (const agent of live) {
29555
+ const previous = mounts.get(agent.id);
29556
+ if (retry === true) rejectedVersions.delete(agent);
29557
+ else if (typeof retry === "string") rejectedVersions.get(agent)?.delete(retry);
29558
+ const good = lastGood.get(agent);
29559
+ const goodSnapshot = good === void 0 ? void 0 : new Map(good);
29560
+ const prepared = [];
29561
+ for (const id of ids) try {
29562
+ const item = await prepareAssembly(options.moduleRoot, id, (name) => agent.ctx.get(name) !== void 0);
29563
+ const version = contentSignature([id], void 0);
29564
+ sourceVersions.set(item, version);
29565
+ if (rejectedVersions.get(agent)?.get(id)?.has(version)) {
29566
+ const fallback = good?.get(id);
29567
+ if (fallback !== void 0) prepared.push(fallback);
29568
+ } else {
29569
+ rejectedVersions.get(agent)?.delete(id);
29570
+ prepared.push(item);
29571
+ }
29572
+ } catch (error) {
29573
+ failures.push(new Error(`模块 ${id}(Agent ${agent.id})准备失败:${failureReason(error)}`, { cause: error }));
29574
+ const fallback = good?.get(id);
29575
+ if (fallback !== void 0) prepared.push(fallback);
29576
+ }
29577
+ candidates.push({
29578
+ agent,
29579
+ prepared,
29580
+ previous,
29581
+ goodSnapshot
29582
+ });
29583
+ }
28011
29584
  const stamp = assemblyStamp(ids, personaModules);
28012
- const content = contentSignature(ids, personaModules);
28013
- for (const item of prepared) for (const id of item.skippedOfficialRows) warnOnce(`模块 ${item.moduleId} 声明官方 recipe 行 ${id};该行只能由会话原有预设提供,本插件不挂载官方包`);
28014
- const candidates = live.map((agent) => ({
28015
- agent,
28016
- prepared,
28017
- previous: mounts.get(agent.id)
28018
- }));
29585
+ for (const item of candidates.flatMap((candidate) => candidate.prepared)) for (const id of item.skippedOfficialRows) warnOnce(`模块 ${item.moduleId} 声明官方 recipe 行 ${id};该行只能由会话原有预设提供,本插件不挂载官方包`);
28019
29586
  if (!active) return;
28020
- const effectivePersonas = personaModules ?? prepared;
28021
29587
  if (options.personaModules !== void 0) await readPersonaKnowledge();
28022
- const sections = {
28023
- main: completeSectionsOf(prepared, "main"),
28024
- subagent: completeSectionsOf(prepared, "subagent")
28025
- };
28026
- for (const [audience, complete] of Object.entries(sections)) if (complete.length > 1) throw new Error(`${audience} 有多个生效 complete 段:${complete.join("、")}`);
28027
- const appendModules = {
28028
- main: prepared.filter((item) => hasSectionAppend(item.rules, "main")).map((item) => item.moduleId),
28029
- subagent: prepared.filter((item) => hasSectionAppend(item.rules, "subagent")).map((item) => item.moduleId)
28030
- };
28031
- const plans = candidates.map(({ agent }) => {
28032
- const plan = personaPlanFor(agent, effectivePersonas);
29588
+ const planFor = (agent, prepared) => {
29589
+ const sections = {
29590
+ main: completeSectionsOf(prepared, "main"),
29591
+ subagent: completeSectionsOf(prepared, "subagent")
29592
+ };
29593
+ for (const [audience, complete] of Object.entries(sections)) if (complete.length > 1) throw new Error(`${audience} 有多个生效 complete 段:${complete.join("、")}`);
29594
+ const appendModules = {
29595
+ main: prepared.filter((item) => hasSectionAppend(item.rules, "main")).map((item) => item.moduleId),
29596
+ subagent: prepared.filter((item) => hasSectionAppend(item.rules, "subagent")).map((item) => item.moduleId)
29597
+ };
29598
+ const plan = personaPlanFor(agent, personaModules ?? prepared);
28033
29599
  const audience = isDelegated(agent.session) ? "subagent" : "main";
28034
29600
  const plannedPrefix = plan.sections.find((section) => section.name === PERSONA_PREFIX_SECTION);
28035
29601
  const scope = scopeOf(agent.ctx);
@@ -28038,21 +29604,43 @@ function createAgentAssembly(ctx, options) {
28038
29604
  if (personaComplete && sections[audience].length > 0) throw new Error(`Agent ${agent.id} 的 ${audience} 有多个生效 complete 段:人设与 ${sections[audience].join("、")}`);
28039
29605
  if (appendModules[audience].length > 0 && (personaComplete || sections[audience].length > 0)) throw new Error(`Agent ${agent.id} 的 ${audience} 同时声明了 sections.append(${appendModules[audience].join("、")})与 complete;请在同一受众二选一`);
28040
29606
  return plan;
28041
- });
29607
+ };
29608
+ const plans = candidates.map(({ agent, prepared }) => planFor(agent, prepared));
28042
29609
  const changed = [];
28043
29610
  try {
28044
29611
  for (const [index, candidate] of candidates.entries()) {
28045
29612
  if (!active || disposed.has(candidate.agent)) continue;
28046
29613
  changed.push(candidate);
28047
29614
  await release(candidate.agent.id);
28048
- await mount(candidate.agent, candidate.prepared, plans[index], duplicateDedupeIdentities(candidate.prepared), stamp, content);
29615
+ const content = contentSignature(ids, personaModules, candidate.previous?.content);
29616
+ let plan = plans[index];
29617
+ while (true) try {
29618
+ await mount(candidate.agent, candidate.prepared, plan, duplicateDedupeIdentities(candidate.prepared), stamp, content);
29619
+ break;
29620
+ } catch (error) {
29621
+ const sourceIds = error !== null && typeof error === "object" ? "sourceModuleIds" in error && Array.isArray(error.sourceModuleIds) ? error.sourceModuleIds : "sourceModuleId" in error ? [error.sourceModuleId] : [] : [];
29622
+ const failed = candidate.prepared.filter((item) => sourceIds.includes(item.moduleId));
29623
+ if (failed.length === 0) throw error;
29624
+ for (const item of failed) {
29625
+ failures.push(new Error(`模块 ${item.moduleId}(Agent ${candidate.agent.id})发布失败:${failureReason(error)}`, { cause: error }));
29626
+ reject(candidate.agent, item);
29627
+ }
29628
+ candidate.prepared = candidate.prepared.flatMap((item) => {
29629
+ if (!failed.includes(item)) return [item];
29630
+ const fallback = lastGood.get(candidate.agent)?.get(item.moduleId);
29631
+ return fallback !== void 0 && fallback !== item ? [fallback] : [];
29632
+ });
29633
+ plan = planFor(candidate.agent, candidate.prepared);
29634
+ }
28049
29635
  }
28050
29636
  } catch (error) {
28051
29637
  const restoreErrors = [];
28052
- for (const { agent, previous } of changed.reverse()) {
29638
+ for (const { agent, previous, goodSnapshot } of changed.reverse()) {
28053
29639
  if (!active || disposed.has(agent)) continue;
28054
29640
  try {
28055
29641
  await release(agent.id);
29642
+ if (goodSnapshot === void 0) lastGood.delete(agent);
29643
+ else lastGood.set(agent, new Map(goodSnapshot));
28056
29644
  if (previous?.agent === agent) await mount(agent, previous.prepared, previous.plan, [], previous.stamp, previous.content);
28057
29645
  } catch (restoreError) {
28058
29646
  restoreErrors.push(restoreError);
@@ -28061,6 +29649,11 @@ function createAgentAssembly(ctx, options) {
28061
29649
  if (restoreErrors.length > 0) throw new AggregateError([error, ...restoreErrors], `切换装配失败(${failureReason(error)}),且恢复原有装配也失败(${restoreErrors.map(failureReason).join(";")})`);
28062
29650
  throw error;
28063
29651
  }
29652
+ for (const { agent } of candidates) {
29653
+ const good = lastGood.get(agent);
29654
+ for (const id of good?.keys() ?? []) if (!ids.includes(id)) good?.delete(id);
29655
+ }
29656
+ if (failures.length > 0) throw new AggregateError(failures, failures.map(failureReason).join(";"));
28064
29657
  };
28065
29658
  const install = async (agent) => {
28066
29659
  knownAgents.add(agent);
@@ -28077,7 +29670,7 @@ function createAgentAssembly(ctx, options) {
28077
29670
  const stamp = assemblyStamp(ids, personas);
28078
29671
  const current = mounts.get(agent.id);
28079
29672
  if (current?.stamp !== stamp) {
28080
- if (current?.content === contentSignature(ids, personas)) current.stamp = stamp;
29673
+ if (current?.content === contentSignature(ids, personas, current?.content)) current.stamp = stamp;
28081
29674
  else await replace([agent]);
28082
29675
  }
28083
29676
  lastReadError = void 0;
@@ -28131,7 +29724,14 @@ function createAgentAssembly(ctx, options) {
28131
29724
  warn(error);
28132
29725
  }
28133
29726
  return {
28134
- settled: () => pending,
29727
+ settled: async () => {
29728
+ let previous;
29729
+ do {
29730
+ await initialization.settled();
29731
+ previous = pending;
29732
+ await previous;
29733
+ } while (previous !== pending);
29734
+ },
28135
29735
  hasMounted: (sessionId) => mounts.has(sessionId),
28136
29736
  moduleIds: (sessionId) => mounts.get(sessionId)?.moduleIds ?? [],
28137
29737
  ruleDiagnostics: () => active ? ruleDiagnostics.snapshot() : emptyRuleDiagnostics(),
@@ -28144,7 +29744,7 @@ function createAgentAssembly(ctx, options) {
28144
29744
  const current = mounts.get(agent.id);
28145
29745
  return options.personaModules !== void 0 || moduleId === void 0 || current === void 0 || current.moduleIds.includes(moduleId) || enabled.includes(moduleId);
28146
29746
  });
28147
- await replace(targets);
29747
+ await replace(targets, moduleId ?? true);
28148
29748
  });
28149
29749
  } catch (error) {
28150
29750
  throw new AggregateError([error], `运行时配装更新失败:${failureReason(error)}`);
@@ -28162,6 +29762,63 @@ function createAgentAssembly(ctx, options) {
28162
29762
  };
28163
29763
  }
28164
29764
  //#endregion
29765
+ //#region src/host/module-manifest-check.ts
29766
+ /** 清单 checks 的运行期事实:只读,缓存 1000ms,manifest 戳变化提前失效。 */
29767
+ /** manifest 配置戳;同戳等长改写由缓存到期后的完整检查识别。 */
29768
+ const stampOf = (path) => {
29769
+ const stat = statSync(path, { throwIfNoEntry: false });
29770
+ return stat === void 0 ? void 0 : `${stat.mtimeMs}:${stat.size}`;
29771
+ };
29772
+ /**
29773
+ * check 本身保持只读:这里只把结果缓存成规则可读的事实。
29774
+ * 清单缺失、坏 YAML、模块目录不合法都是 undefined(UNAVAILABLE),不是「检查失败」。
29775
+ */
29776
+ function createManifestCheckReader(moduleRoot) {
29777
+ const dirs = /* @__PURE__ */ new Map();
29778
+ const results = /* @__PURE__ */ new Map();
29779
+ const dirOf = (moduleId) => {
29780
+ const hit = dirs.get(moduleId);
29781
+ if (hit !== void 0) return hit;
29782
+ try {
29783
+ const dir = assertModuleDirectory(moduleRoot, moduleId);
29784
+ dirs.set(moduleId, dir);
29785
+ return dir;
29786
+ } catch {
29787
+ return;
29788
+ }
29789
+ };
29790
+ const read = (moduleId) => {
29791
+ const dir = dirOf(moduleId);
29792
+ if (dir === void 0) return void 0;
29793
+ let stamp;
29794
+ try {
29795
+ stamp = stampOf(join(dir, "assets", "manifest.yml"));
29796
+ } catch {
29797
+ results.delete(dir);
29798
+ return;
29799
+ }
29800
+ if (stamp === void 0) {
29801
+ results.delete(dir);
29802
+ return;
29803
+ }
29804
+ const hit = results.get(dir);
29805
+ if (hit !== void 0 && hit.stamp === stamp && performance.now() - hit.checkedAt < 1e3) return hit.ok;
29806
+ let ok;
29807
+ try {
29808
+ ok = runModuleManifest(dir, "check").ok;
29809
+ } catch {
29810
+ ok = void 0;
29811
+ }
29812
+ results.set(dir, {
29813
+ stamp,
29814
+ checkedAt: performance.now(),
29815
+ ok
29816
+ });
29817
+ return ok;
29818
+ };
29819
+ return { read };
29820
+ }
29821
+ //#endregion
28165
29822
  //#region src/index.ts
28166
29823
  const name = "prompt-tool";
28167
29824
  const inject = [
@@ -28276,6 +29933,7 @@ function apply(ctx, configIn) {
28276
29933
  target: (exec, moduleId) => resolveModuleToolTarget(exec, modulesDir(), (sessionId) => assembly?.moduleIds(sessionId) ?? [], moduleId),
28277
29934
  rebuild: (id) => refreshModuleRuntime(id)
28278
29935
  };
29936
+ installSoulRuntime(ctx, moduleToolHost, (id) => assembly?.moduleIds(id) ?? []);
28279
29937
  ctx.provide(toolServiceName("module-import-tools"), { mount: (scopeCtx) => registerModuleImportTools(scopeCtx, {
28280
29938
  ...moduleToolHost,
28281
29939
  rebuild: async (id) => {
@@ -28285,7 +29943,9 @@ function apply(ctx, configIn) {
28285
29943
  ctx.provide(toolServiceName("module-rule-tools"), { mount: (scopeCtx) => registerModuleRuleTools(scopeCtx, moduleToolHost) });
28286
29944
  ctx.provide(toolServiceName("module-file-tools"), { mount: (scopeCtx) => registerModuleFileTools(scopeCtx, moduleToolHost) });
28287
29945
  ctx.provide(toolServiceName("module-manifest-tools"), { mount: (scopeCtx) => registerModuleManifestTools(scopeCtx, moduleToolHost) });
29946
+ ctx.provide(toolServiceName("soul-ledger"), { mount: (scopeCtx) => registerSoulLedgerTools(scopeCtx, moduleToolHost) });
28288
29947
  ctx.provide(DISCOVERY_SERVICE, createDiscoveryRegistry());
29948
+ ctx.provide(MANIFEST_CHECK_SERVICE, createManifestCheckReader(modulesDir()));
28289
29949
  ctx.provide(toolServiceName("module-variable-tools"), { mount: (scopeCtx) => registerModuleVariableTools(scopeCtx, moduleToolHost) });
28290
29950
  import(new URL("../engine/skill-search.mjs", import.meta.url).href).then((mod) => {
28291
29951
  if (typeof mod.apply !== "function") return;
@@ -28301,7 +29961,7 @@ function apply(ctx, configIn) {
28301
29961
  const personaCapture = installPersonaCapture(ctx);
28302
29962
  ctx.effect(function* () {
28303
29963
  yield () => personaCapture.dispose();
28304
- const agentsFiber = ctx.inject(["agents"], (actx) => {
29964
+ const agentsFiber = ctx.inject(["agents", MANIFEST_CHECK_SERVICE], (actx) => {
28305
29965
  const mounted = createAgentAssembly(actx, {
28306
29966
  moduleRoot: modulesDir(),
28307
29967
  enabledModules: () => runtime.modulesEnabled ? enabledModuleIds(modulesDir()) : [],