dsh-plugin-prompt-tool 3.0.1 → 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 (57) hide show
  1. package/README.md +17 -1
  2. package/engine/actions/catalog.mjs +2 -1
  3. package/engine/actions/index.mjs +5 -3
  4. package/engine/actions/shared.mjs +26 -12
  5. package/engine/actions/tool-visibility.mjs +31 -14
  6. package/engine/compositions/source/local/context-provenance.yml +9 -0
  7. package/engine/compositions/source/local/generation-recovery.yml +10 -0
  8. package/engine/compositions/source/local/retrieval.yml +7 -0
  9. package/engine/compositions/source/local/soul-ledger.yml +3 -0
  10. package/engine/compositions/source/local/soul-maintenance.yml +9 -0
  11. package/engine/conditions/examples.mjs +1 -0
  12. package/engine/conditions/index.mjs +7 -0
  13. package/engine/conditions/manifest.mjs +28 -0
  14. package/engine/context-idle-compaction.mjs +58 -0
  15. package/engine/context-provenance.d.mts +7 -0
  16. package/engine/context-provenance.mjs +156 -0
  17. package/engine/context-pruner.mjs +64 -0
  18. package/engine/context-routing.mjs +45 -0
  19. package/engine/context-summary.mjs +80 -0
  20. package/engine/fillers.mjs +37 -4
  21. package/engine/generation-recovery-coordinator.mjs +40 -0
  22. package/engine/generation-recovery-maintenance.mjs +18 -0
  23. package/engine/generation-recovery.mjs +153 -0
  24. package/engine/host-package.mjs +13 -0
  25. package/engine/layers.d.mts +4 -0
  26. package/engine/layers.mjs +57 -20
  27. package/engine/present-as.mjs +2 -2
  28. package/engine/retrieval-reference.d.mts +5 -0
  29. package/engine/retrieval-reference.mjs +26 -0
  30. package/engine/retrieval.mjs +17 -0
  31. package/engine/rule-engine.mjs +1 -0
  32. package/engine/rule-runtime.d.mts +8 -1
  33. package/engine/rule-runtime.mjs +18 -10
  34. package/engine/rule-spec.mjs +4 -2
  35. package/engine/schema.mjs +1 -1
  36. package/engine/service-names.d.mts +5 -0
  37. package/engine/service-names.mjs +5 -0
  38. package/engine/soul-ledger.mjs +8 -0
  39. package/engine/soul-maintenance.mjs +20 -0
  40. package/lib/client.js +16 -6
  41. package/lib/client.js.map +1 -1
  42. package/lib/index.mjs +1953 -246
  43. package/modules/soul/assets/ledger.md +4 -2
  44. package/modules/soul/assets/manifest.yml +2 -0
  45. package/modules/soul/assets/reference/00-layers.md +27 -27
  46. package/modules/soul/assets/reference/02-echo.md +161 -161
  47. package/modules/soul/assets/reference/05-doctor.md +67 -67
  48. package/modules/soul/assets/reference/10-echo-anatomy.md +109 -109
  49. package/modules/soul/assets/reference/ECHO-/346/235/203/345/250/201/345/217/243/345/276/204.md +202 -202
  50. package/modules/soul/assets/reference/ECHO/345/205/255/345/261/202/344/270/216mnemon/345/215/217/345/220/214/350/257/264/346/230/216/344/271/246.md +229 -229
  51. package/modules/soul/assets/templates/agents-template.md +3 -0
  52. package/modules/soul/assets/templates/self-template.md +19 -0
  53. package/modules/soul/module.yml +71 -13
  54. package/modules/tool-surface/module.yml +8 -5
  55. package/package.json +1 -1
  56. package/skills/dsh-module/reference.md +2 -2
  57. package/templates/80-tool-surface.yml +8 -7
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";
@@ -1361,19 +1522,27 @@ function createMask(source, label, plugin, allowExtra) {
1361
1522
  /** 动态白名单的落点(活集合,由调用方每轮重填);未声明 allowFrom 时不存在。 */
1362
1523
  allowExtra,
1363
1524
  /**
1364
- * fail-open 兜底开关(**可选**,缺省 false):原 `tool-bootstrap` 的语义是
1365
- * 「keep 名单里的工具在本次装配目录里一个都不存在 → 放弃裁剪、暴露完整目录」,而原
1366
- * `tool-filter` 的同名场景是**照名单裁成空目录**。两种语义都真实存在,所以这里
1367
- * **不设默认**——要兜底的声明自己写 `requireMatch: true`。把某一个模块的特有语义当成
1368
- * 通用默认,会让另一个声明路径的行为被悄悄改掉(这正是对拍抓出来的)。
1525
+ * fail-open 兜底开关(**可选**,缺省 false):判据是「这份名单与装配面整体错位」——
1526
+ * 名单里的名字**一个都不存在**时放弃裁剪、暴露完整目录;部分缺失只留诊断,收窄照常
1527
+ * 生效(提供方被替换或关闭是正常部署差异,见 {@link toolSurfacePlan})。原
1528
+ * `tool-bootstrap` 的同名语义就是「一个都不存在 → 放弃裁剪」,而原 `tool-filter` 是
1529
+ * 「永远照名单裁」;两种语义都真实存在,所以这里**不设默认**——要兜底的声明自己写
1530
+ * `requireMatch: true`。把某一个模块的特有语义当成通用默认,会让另一个声明路径的行为
1531
+ * 被悄悄改掉(这正是对拍抓出来的)。
1369
1532
  */
1370
1533
  requireMatch: source?.requireMatch === true,
1371
- /** 与 tool-filter 的名单判据 / Layer.admits 同一判据(tool-filter 模块已删除,判据保留)。 */
1372
- blocks: (toolName) => {
1534
+ /**
1535
+ * 与 tool-filter 的名单判据 / Layer.admits 同一判据(tool-filter 模块已删除,判据保留)。
1536
+ *
1537
+ * 第二个参数是**解锁项快照**:allowExtra 是跨 agent 共享的活集合,每次装配都会 clear 后
1538
+ * 按当前会话重填;执行闸在该装配之后才被调用,必须读自己会话的冻结快照,否则别的会话
1539
+ * (或探针装配)会把本会话的解锁名抹掉。缺省仍是活集合,供同一次装配内的裁剪使用。
1540
+ */
1541
+ blocks: (toolName, extra = allowExtra) => {
1373
1542
  if (typeof toolName !== "string" || toolName.length === 0) return false;
1374
1543
  if (toolName === "run_code") return false;
1375
1544
  if (deny !== void 0 && deny.has(toolName)) return true;
1376
- if (allowExtra?.has(toolName) === true) return false;
1545
+ if (extra?.has(toolName) === true) return false;
1377
1546
  return (allow !== void 0 || allowExtra !== void 0) && allow?.has(toolName) !== true;
1378
1547
  }
1379
1548
  };
@@ -1381,14 +1550,21 @@ function createMask(source, label, plugin, allowExtra) {
1381
1550
  /**
1382
1551
  * 工具可见面的单一判据:装配目录、`tools:sdk` 正文与执行闸共用同一份 fail-open 决定。
1383
1552
  *
1384
- * `available` = 目录名 ∪ SDK 正文名;`required` = 静态 allow ∪ 动态解锁项,再剔掉
1553
+ * `available` = 目录名 ∪ SDK 正文名;`required` 只取**静态 allow**,再剔掉
1385
1554
  * `run_code`(保留传输)与 `mask.blocks()` 为真的名字(deny 覆盖者不该被要求存在)。
1386
- * 缺项时三面必须一起放宽——只放宽一面会让模型看到的与能调的不一致。
1555
+ *
1556
+ * 放宽条件是「名单里的名字**一个都不存在**」:那才说明装配面与声明整体错位(换了一套
1557
+ * 部署/平台)。只缺一部分是提供方被替换或关掉的正常部署差异——照常收窄,缺的工具由发现
1558
+ * 通道补位,替换提供方的名字由部署层名单登记。解锁名(allowExtra)**不参与判定**:它是只做
1559
+ * 加法的提示,若参与,模型解锁一个拼错或已改名的工具就能关掉收窄。
1560
+ *
1561
+ * 放宽时三面必须一起放宽——只放宽一面会让模型看到的与能调的不一致。
1387
1562
  */
1388
1563
  function toolSurfacePlan(mask, available) {
1389
- const missing = (mask.requireMatch ? [.../* @__PURE__ */ new Set([...mask.allow ?? [], ...mask.allowExtra ?? []])].filter((name) => name !== "run_code" && !mask.blocks(name)) : []).filter((name) => !available.has(name));
1564
+ const required = mask.requireMatch ? [...new Set(mask.allow ?? [])].filter((name) => name !== "run_code" && !mask.blocks(name)) : [];
1565
+ const missing = required.filter((name) => !available.has(name));
1390
1566
  return {
1391
- failOpen: missing.length > 0,
1567
+ failOpen: required.length > 0 && missing.length === required.length,
1392
1568
  missing
1393
1569
  };
1394
1570
  }
@@ -1490,7 +1666,7 @@ const ACTION_KINDS = Object.freeze({
1490
1666
  ],
1491
1667
  degrade: ACTION_DEGRADE.keep,
1492
1668
  timing: "system-prompt/assemble 的下游结算之后:按当前条件重算目录、SDK 正文与执行闸;条件翻转时撤销上一轮注册的闸。",
1493
- note: "一份声明拥有工具的可见面:allow/deny 名单、requireMatch、发现工具。名单语义对齐官方 ToolRestriction 的 and/or(同时给时先按 allow 保留、再按 deny 剔除)。deny 走 guard(本层与晚到工具)与 restrict(继承的全局工具);allow 只有 restrict 能覆盖全局工具,本层注册无法在执行层「只保留」——这是官方 restrict 的边界,不是本动作的取舍。"
1669
+ note: "一份声明拥有工具的可见面:allow/deny 名单、requireMatch、发现工具。名单语义对齐官方 ToolRestriction 的 and/or(同时给时先按 allow 保留、再按 deny 剔除)。deny 走 guard(本层与晚到工具)与 restrict(继承的全局工具);allow 只有 restrict 能覆盖全局工具,本层注册无法在执行层「只保留」——这是官方 restrict 的边界,不是本动作的取舍。requireMatch 表达「名单与装配面整体错位」:名单里的名字一个都不存在才整面放宽,部分缺失只留诊断;解锁名不参与判定。"
1494
1670
  },
1495
1671
  "request-params": {
1496
1672
  title: "改模型请求参数",
@@ -7635,6 +7811,29 @@ function parse$1(src, reviver, options) {
7635
7811
  return doc.toJS(Object.assign({ reviver: _reviver }, options));
7636
7812
  }
7637
7813
  //#endregion
7814
+ //#region engine/tool-scope.mjs
7815
+ /**
7816
+ * tool-scope — 工具面的作用域快照,避免各消费方把全局视图当成模型可见面。
7817
+ *
7818
+ * 返回两个字段,各自只服务一个调用方:
7819
+ * - visible:带 agent 的 `tools.schemas(agent)`,包含预设/祖先/本层工具;模型可见面。
7820
+ * - globalNames:不带 scope 的 `tools.schemas()` 名字集合;只供 `restrict` 过滤已知名。
7821
+ *
7822
+ * 全局原始 schema 与 visible-only 差集不外露;等真实诊断消费方出现再加字段。
7823
+ */
7824
+ const schemaList = (tools, scope) => {
7825
+ if (typeof tools?.schemas !== "function") return [];
7826
+ const schemas = tools.schemas(scope);
7827
+ return Array.isArray(schemas) ? schemas : [];
7828
+ };
7829
+ function toolScopeSnapshot(tools, agent) {
7830
+ const global = schemaList(tools);
7831
+ return {
7832
+ visible: agent === void 0 ? global : schemaList(tools, agent),
7833
+ globalNames: new Set(global.map((schema) => schema?.name).filter((name) => typeof name === "string"))
7834
+ };
7835
+ }
7836
+ //#endregion
7638
7837
  //#region engine/machine-paths.mjs
7639
7838
  /**
7640
7839
  * 机器路径事实的**唯一取值来源**:`DSH_HOME` / `CWD` / `WORKSPACE` 三个键怎么推导,只在这里定。
@@ -8308,6 +8507,10 @@ function resolveRealWithin(target, roots, label = "path") {
8308
8507
  const PRE_STEP_COORDINATOR_SERVICE = "promptToolPreStep";
8309
8508
  /** 发现引擎注册表:宿主 root provide,adapter 行按 agent 注册,动作按 agent 解析。 */
8310
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";
8311
8514
  /** 五个模块工具外壳的服务名:`pt-<引擎模块 id>`。前缀是本函数的内部细节。 */
8312
8515
  function toolServiceName(engineModuleId) {
8313
8516
  return `pt-${engineModuleId}`;
@@ -8476,14 +8679,29 @@ function createInstructionHintResolver(config = {}) {
8476
8679
  //#region engine/fillers.mjs
8477
8680
  /**
8478
8681
  * fillers — 引擎内置的动态填充器(strategy=placeholder 的数据源)。
8479
- * 全部遵循同一铁律:服务缺失或列表失败时返回 null(跳过该配置),绝不伤及会话。
8682
+ * 按本次求值读取宿主能力;失败由填充器或调用方诊断并跳过该配置。
8480
8683
  */
8481
8684
  const name$5 = "prompt-config-engine";
8482
8685
  function explicitTemplate(config) {
8483
8686
  const texts = Array.isArray(config.texts) ? config.texts : typeof config.text === "string" ? [config.text] : [];
8484
8687
  return texts.length > 0 ? texts.join("\n\n") : typeof config.params?.text === "string" ? config.params.text : "";
8485
8688
  }
8486
- /** 只读同规则工具面声明;文案、条件和投递仍归普通注入动作。 */
8689
+ /**
8690
+ * 只读同规则工具面声明;文案、条件和投递仍归普通注入动作。
8691
+ * RESIDENT_TOOLS 只列**当前可见**的常驻工具:部署层名单可以登记替换提供方(如 dsh-ops 的
8692
+ * ops_bash),但该插件未必安装;向模型宣告一个不存在的常驻工具会把「解锁」变成错误前提。
8693
+ * 观测到工具面但一个常驻名都不在时(requireMatch 全缺/放宽场景)跳过提示;
8694
+ * 观测不到工具面(服务缺失或读取失败)才回落声明名单——「没看到」不等于「都没有」。
8695
+ */
8696
+ function visibleToolNames(ctx, agent) {
8697
+ try {
8698
+ const tools = getService(ctx, "tools");
8699
+ if (tools === void 0 || typeof tools.schemas !== "function") return void 0;
8700
+ return new Set(toolScopeSnapshot(tools, agent).visible.map((schema) => schema?.name));
8701
+ } catch {
8702
+ return;
8703
+ }
8704
+ }
8487
8705
  function createToolCatalogResolver(config) {
8488
8706
  if (config.params?.templateEvaluation === true) throw new TypeError("tool-catalog is read-only and does not support templateEvaluation");
8489
8707
  if (typeof config.params?.action !== "string" || config.params.action.trim().length === 0) throw new TypeError("tool-catalog requires params.action referencing a tool-visibility action");
@@ -8493,9 +8711,16 @@ function createToolCatalogResolver(config) {
8493
8711
  const source = config.toolCatalogSource;
8494
8712
  if (source === void 0) throw new TypeError("tool-catalog requires a same-rule tool-visibility action");
8495
8713
  const deny = new Set(source.deny ?? []);
8496
- const resident = [...new Set(source.allow)].filter((tool) => !deny.has(tool));
8714
+ const declared = [...new Set(source.allow)].filter((tool) => !deny.has(tool));
8497
8715
  const adapter = source.discovery === void 0 ? void 0 : getService(ctx, DISCOVERY_SERVICE)?.get?.(agent, source.discovery.kind);
8498
8716
  if (source.discovery !== void 0 && typeof adapter?.tool !== "string") throw new Error(`tool-catalog discovery adapter ${source.discovery.kind} is unavailable`);
8717
+ let resident = declared;
8718
+ const visible = visibleToolNames(ctx, agent);
8719
+ if (visible !== void 0) {
8720
+ const present = declared.filter((name) => visible.has(name));
8721
+ if (present.length === 0) return null;
8722
+ resident = present;
8723
+ }
8499
8724
  return {
8500
8725
  text,
8501
8726
  variables: {
@@ -8632,12 +8857,20 @@ function createSkillCatalogResolver(config) {
8632
8857
  }
8633
8858
  };
8634
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
+ }
8635
8867
  /** 动态填充器:strategy=placeholder 时按 fill 键在注入点填充内容。 */
8636
8868
  const FILLERS = {
8637
8869
  "instruction-hint": (config) => createInstructionHintResolver(config),
8638
8870
  "env-facts": (config) => createEnvFactsResolver(config),
8639
8871
  "skill-catalog": (config) => createSkillCatalogResolver(config),
8640
- "tool-catalog": (config) => createToolCatalogResolver(config)
8872
+ "tool-catalog": (config) => createToolCatalogResolver(config),
8873
+ "soul-self": (config) => createSoulSelfResolver(config)
8641
8874
  };
8642
8875
  function createPlaceholderResolver(config) {
8643
8876
  const make = FILLERS[config.fill];
@@ -9087,7 +9320,8 @@ const KNOWN_FILLS = /* @__PURE__ */ new Set([
9087
9320
  "instruction-hint",
9088
9321
  "env-facts",
9089
9322
  "skill-catalog",
9090
- "tool-catalog"
9323
+ "tool-catalog",
9324
+ "soul-self"
9091
9325
  ]);
9092
9326
  /** 层能力矩阵:每个字段只在对应注入层生效。客户端表单据此动态渲染。 */
9093
9327
  const LAYER_FIELD_POLICIES = Object.fromEntries(LAYER_DEFINITIONS.map((definition) => [definition.layer, definition.fields]));
@@ -10347,7 +10581,7 @@ function textLayerGroups(configs) {
10347
10581
  return groups;
10348
10582
  }
10349
10583
  /** system-section:注册静态 system prompt 段(支持官方 {{variable}} 渲染与 merged 拼接)。 */
10350
- function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered) {
10584
+ function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered, sourceError) {
10351
10585
  const systemPrompt = getService(ctx, "systemPrompt");
10352
10586
  if (systemPrompt === void 0 || typeof systemPrompt.section !== "function") {
10353
10587
  if (configs.length > 0) warnOnce(`${name$2}: systemPrompt service unavailable — system-section configs skipped`);
@@ -10370,6 +10604,7 @@ function wireSystemSections(ctx, configs, registry, warnOnce, keep, ordered) {
10370
10604
  ordered.sections.push(orderedEntry(entryName, base));
10371
10605
  if (base.params?.suppressRuntimeContext === true) keep(systemPrompt.suppressRuntimeContext(), `${name$2}: suppressRuntimeContext ${base.id}`);
10372
10606
  } catch (error) {
10607
+ if (sourceError !== void 0) throw sourceError(error, base.sourceModuleId);
10373
10608
  warnOnce(`${name$2}: system-section config ${base.id} failed: ${String(error?.message ?? error)}`);
10374
10609
  }
10375
10610
  }
@@ -10406,7 +10641,7 @@ async function resolvedContextText(ctx, config, context, registry, warnOnce) {
10406
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);
10407
10642
  }
10408
10643
  /** 条件化文本保留官方排序占位;仅在本次真实 assembly 填充,不缓存 context 身份。 */
10409
- function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, ordered) {
10644
+ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, ordered, sourceError) {
10410
10645
  if (!configs.length) return [];
10411
10646
  const systemPrompt = getService(ctx, "systemPrompt");
10412
10647
  if (systemPrompt === void 0) {
@@ -10421,15 +10656,32 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10421
10656
  const section = layer === "system-section";
10422
10657
  const base = group[0];
10423
10658
  const entryName = textRegistrationName(base, section ? "sectionName" : "contextName");
10424
- keep(systemPrompt[section ? "section" : "context"]({
10425
- name: entryName,
10426
- order: base.order,
10427
- text: marker
10428
- }), `${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
+ }
10429
10680
  bindings.push({
10430
10681
  group,
10431
10682
  name: entryName,
10432
- collection: section ? "sections" : "contexts"
10683
+ collection: section ? "sections" : "contexts",
10684
+ holderOf
10433
10685
  });
10434
10686
  ordered[section ? "sections" : "contexts"].push(orderedEntry(entryName, base));
10435
10687
  }
@@ -10440,9 +10692,11 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10440
10692
  return [on("system-prompt/assemble", async (assembly, context, next, invocation) => {
10441
10693
  const frame = invocation ?? ruleFrame("system-prompt/assemble", [assembly, context], warnOnce);
10442
10694
  for (const binding of bindings) {
10443
- 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);
10444
10696
  if (entry === void 0) continue;
10697
+ const holder = binding.holderOf === void 0 ? void 0 : binding.holderOf(context);
10445
10698
  entry.text = "";
10699
+ if (holder !== void 0) holder.text = "";
10446
10700
  const blocks = [];
10447
10701
  for (const config of binding.group) {
10448
10702
  if (!active || context.signal?.aborted || !actionMatches(config, frame) || !matchesAgentScope(config, context.agent)) continue;
@@ -10453,13 +10707,16 @@ function wireRuleTextContributions(ctx, configs, registry, warnOnce, keep, on, o
10453
10707
  warnOnce(`${name$2}: rule text ${config.id} failed: ${String(error?.message ?? error)}`);
10454
10708
  }
10455
10709
  }
10456
- 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
+ }
10457
10714
  }
10458
10715
  return next();
10459
10716
  })];
10460
10717
  }
10461
10718
  /** runtime-context:注册运行时上下文;静态文本支持 merged,动态策略在官方 assembly waterfall 中填充。 */
10462
- function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered) {
10719
+ function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered, sourceError) {
10463
10720
  const disposers = [];
10464
10721
  const systemPrompt = getService(ctx, "systemPrompt");
10465
10722
  if (systemPrompt === void 0 || typeof systemPrompt.context !== "function") {
@@ -10483,41 +10740,60 @@ function wireRuntimeContexts(ctx, configs, registry, warnOnce, keep, on, ordered
10483
10740
  }), `${name$2}: context ${base.id}`);
10484
10741
  ordered.contexts.push(orderedEntry(entryName, base));
10485
10742
  } catch (error) {
10743
+ if (sourceError !== void 0) throw sourceError(error, base.sourceModuleId);
10486
10744
  if (base.linkedDelivery) throw error;
10487
10745
  warnOnce(`${name$2}: runtime-context config ${base.id} failed: ${String(error?.message ?? error)}`);
10488
10746
  }
10489
10747
  }
10490
10748
  const placeholders = configs.filter(needsResolver).sort(compareTextPlacement);
10491
10749
  if (placeholders.length === 0) return disposers;
10492
- const slotVariable = `pt_runtime_${newMessageId("context").replace(/[^a-z0-9_]/gi, "_").toLowerCase()}`;
10493
- const slotText = `{{${slotVariable}}}`;
10494
10750
  const registered = /* @__PURE__ */ new Map();
10495
10751
  let active = true;
10496
10752
  keep(() => {
10497
10753
  active = false;
10498
10754
  }, `${name$2}: runtime-context lifecycle`);
10499
- keep(systemPrompt.variable(slotVariable, () => ""), `${name$2}: runtime-context placeholder`);
10500
10755
  for (const config of placeholders) try {
10501
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
+ };
10502
10769
  keep(systemPrompt.context({
10503
10770
  name: contextName,
10504
10771
  order: config.order,
10505
- text: slotText
10772
+ text: (context) => holderOf(context).text
10506
10773
  }), `${name$2}: context ${config.id}`);
10507
- registered.set(contextName, config);
10774
+ registered.set(contextName, {
10775
+ config,
10776
+ holderOf
10777
+ });
10508
10778
  ordered.contexts.push(orderedEntry(contextName, config));
10509
10779
  } catch (error) {
10780
+ if (sourceError !== void 0) throw sourceError(error, config.sourceModuleId);
10510
10781
  warnOnce(`${name$2}: runtime-context placeholder ${config.id} failed: ${String(error?.message ?? error)}`);
10511
10782
  }
10512
10783
  disposers.push(on("system-prompt/assemble", async (assembly, context, next) => {
10513
- const entries = assembly.contexts.filter((entry) => registered.has(entry.name) && entry.text === slotText);
10784
+ const entries = assembly.contexts.filter((entry) => registered.has(entry.name));
10514
10785
  for (const entry of entries) {
10515
- const config = registered.get(entry.name);
10786
+ const { config, holderOf } = registered.get(entry.name);
10787
+ const holder = holderOf(context);
10516
10788
  entry.text = "";
10789
+ holder.text = "";
10517
10790
  if (!active || context.signal?.aborted) continue;
10518
10791
  try {
10519
10792
  const text = await resolvedContextText(ctx, config, context, registry.get(config), warnOnce);
10520
- if (active && !context.signal?.aborted) entry.text = text;
10793
+ if (active && !context.signal?.aborted) {
10794
+ entry.text = text;
10795
+ holder.text = text;
10796
+ }
10521
10797
  } catch (error) {
10522
10798
  warnOnce(`${name$2}: runtime-context ${config.id} resolve failed: ${String(error?.message ?? error)}`);
10523
10799
  }
@@ -10848,9 +11124,9 @@ function wireLayers(ctx, configs, warnOnce, options = {}) {
10848
11124
  const registry = registerOfficialVariables(ctx, configs.filter((config) => config.layer === "system-section" || config.layer === "runtime-context"), warnOnce, keep);
10849
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));
10850
11126
  const regular = configs.filter((config) => !conditional.includes(config));
10851
- wireSystemSections(ctx, regular.filter((config) => config.layer === "system-section"), registry, warnOnce, keep, ordered);
10852
- wireRuntimeContexts(ctx, regular.filter((config) => config.layer === "runtime-context"), registry, warnOnce, keep, on, ordered);
10853
- 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);
10854
11130
  wireTurnStops(ctx, configs.filter((config) => config.layer === "turn-stop"), warnOnce, on, options.turnStopBudgets);
10855
11131
  wireSubagentEvents(ctx, configs.filter((config) => config.layer === "subagent-start" || config.layer === "subagent-end"), warnOnce, on);
10856
11132
  for (const collection of ["sections", "contexts"]) registerDeliveryOrder(ctx, keep, collection, orderedNamesOf(ordered[collection]));
@@ -12777,29 +13053,6 @@ function stripSdkDeclarations(text, names) {
12777
13053
  }
12778
13054
  }
12779
13055
  //#endregion
12780
- //#region engine/tool-scope.mjs
12781
- /**
12782
- * tool-scope — 工具面的作用域快照,避免各消费方把全局视图当成模型可见面。
12783
- *
12784
- * 返回两个字段,各自只服务一个调用方:
12785
- * - visible:带 agent 的 `tools.schemas(agent)`,包含预设/祖先/本层工具;模型可见面。
12786
- * - globalNames:不带 scope 的 `tools.schemas()` 名字集合;只供 `restrict` 过滤已知名。
12787
- *
12788
- * 全局原始 schema 与 visible-only 差集不外露;等真实诊断消费方出现再加字段。
12789
- */
12790
- const schemaList = (tools, scope) => {
12791
- if (typeof tools?.schemas !== "function") return [];
12792
- const schemas = tools.schemas(scope);
12793
- return Array.isArray(schemas) ? schemas : [];
12794
- };
12795
- function toolScopeSnapshot(tools, agent) {
12796
- const global = schemaList(tools);
12797
- return {
12798
- visible: agent === void 0 ? global : schemaList(tools, agent),
12799
- globalNames: new Set(global.map((schema) => schema?.name).filter((name) => typeof name === "string"))
12800
- };
12801
- }
12802
- //#endregion
12803
13056
  //#region engine/discovery.mjs
12804
13057
  /**
12805
13058
  * discovery —— 发现引擎:搜索、排序、截断渲染与「适配器注册表」的唯一实现。
@@ -12902,8 +13155,10 @@ function assertDiscoveryRows(rules, moduleIds, moduleId) {
12902
13155
  *
12903
13156
  * 名单语义对齐官方 ToolRestriction(deepseek-harness packages/core/tools/src/index.ts:701-706):
12904
13157
  * allow 与 deny 可以同时给,判据是「先按 allow 保留,再按 deny 剔除」。requireMatch 只在
12905
- * allow 模式有意义:名单里任一工具在目录与 SDK 正文的并集里都找不到,就放弃裁剪、暴露完整目录(fail-open)并告警——
12906
- * 收窄错了会让当前工作流调不到工具,比多花上下文严重。
13158
+ * allow 模式有意义,判据是「这份名单与装配面整体错位」:名单里的名字**一个都不在**目录与
13159
+ * SDK 正文的并集里,才放弃裁剪、暴露完整目录(fail-open)并告警。只缺一部分是提供方被
13160
+ * 替换或关闭的正常部署差异,收窄照常生效、缺项只留一条诊断;解锁名是只做加法的提示,
13161
+ * 不参与这个判定。
12907
13162
  *
12908
13163
  * 执行边界:deny 同时用 restrict(继承面)与 guard(本层/晚到);allow 不用 restrict——
12909
13164
  * 它的 allow 只能收继承面名字,而 `schemas()` 全局视图会漏掉预设/祖先层,局部 allow 会
@@ -12972,7 +13227,10 @@ function prepareToolVisibility(action, plugin) {
12972
13227
  const capability = createAgentCapability((agent) => {
12973
13228
  const tools = agent?.ctx?.tools;
12974
13229
  if (tools === void 0) return void 0;
12975
- const state = { failOpen: false };
13230
+ const state = {
13231
+ failOpen: false,
13232
+ unlocked: void 0
13233
+ };
12976
13234
  const disposers = [];
12977
13235
  try {
12978
13236
  const known = toolScopeSnapshot(tools).globalNames;
@@ -12991,7 +13249,10 @@ function prepareToolVisibility(action, plugin) {
12991
13249
  const toolName = exec?.name;
12992
13250
  if (toolName === "run_code" || typeof toolName !== "string" || toolName.length === 0) return void 0;
12993
13251
  if (deny?.has(toolName)) return `${label}: tool ${JSON.stringify(toolName)} is denied by tool-visibility`;
12994
- if (mask.allow !== void 0 && !state.failOpen && mask.blocks(toolName)) 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
+ }
12995
13256
  }));
12996
13257
  }
12997
13258
  } catch (error) {
@@ -13002,8 +13263,8 @@ function prepareToolVisibility(action, plugin) {
13002
13263
  data: state
13003
13264
  };
13004
13265
  });
13005
- const syncGate = (agent) => {
13006
- const key = `${maskKey(mask)}|unlock:${allowExtra === void 0 ? "" : [...allowExtra].sort().join(",")}`;
13266
+ const syncGate = (agent, unlocked) => {
13267
+ const key = `${maskKey(mask)}|unlock:${unlocked === void 0 ? "" : [...unlocked].sort().join(",")}`;
13007
13268
  capability.sync(agent, key);
13008
13269
  };
13009
13270
  collect(capability.releaseAll);
@@ -13026,8 +13287,9 @@ function prepareToolVisibility(action, plugin) {
13026
13287
  fillUnlocked(allowExtra, context?.session ?? context?.agent?.session, unlock.tool, unlock.key, warnOnce, plugin, label);
13027
13288
  }
13028
13289
  }
13290
+ const unlocked = allowExtra === void 0 ? void 0 : new Set(allowExtra);
13029
13291
  try {
13030
- syncGate(context?.agent);
13292
+ syncGate(context?.agent, unlocked);
13031
13293
  } catch (error) {
13032
13294
  warnOnce(`${plugin}: tool-visibility action ${label} failed to sync execution gate: ${String(error?.message ?? error)}`);
13033
13295
  }
@@ -13039,11 +13301,15 @@ function prepareToolVisibility(action, plugin) {
13039
13301
  if (!Array.isArray(result.tools) && sdkSection === void 0) return result;
13040
13302
  const plan = toolSurfacePlan(mask, available);
13041
13303
  const state = capability.get(context?.session ?? context?.agent?.session);
13042
- if (state !== void 0) state.data.failOpen = plan.failOpen;
13043
- if (plan.failOpen) warnOnce(`${plugin}: tool-visibility action ${label}: requireMatch tools absent (${plan.missing.join(", ")}) — exposing the full tool set (fail-open)`, `${plugin}: tool-visibility action ${label}: requireMatch fail-open`);
13304
+ if (state !== void 0) {
13305
+ state.data.failOpen = plan.failOpen;
13306
+ state.data.unlocked = unlocked;
13307
+ }
13308
+ if (plan.failOpen) warnOnce(`${plugin}: tool-visibility action ${label}: none of the resident tools exist (${plan.missing.join(", ")}) — exposing the full tool set (fail-open)`, `${plugin}: tool-visibility action ${label}: requireMatch fail-open`);
13044
13309
  else {
13310
+ if (plan.missing.length > 0) warnOnce(`${plugin}: tool-visibility action ${label}: resident tools absent (${plan.missing.join(", ")}) — narrowing continues; register replacement providers in the deployment allow list`, `${plugin}: tool-visibility action ${label}: resident tools absent`);
13045
13311
  if (Array.isArray(result.tools)) {
13046
- const kept = result.tools.filter((tool) => !mask.blocks(tool?.name));
13312
+ const kept = result.tools.filter((tool) => !mask.blocks(tool?.name, unlocked));
13047
13313
  if (kept.length !== result.tools.length) result = {
13048
13314
  ...result,
13049
13315
  tools: kept
@@ -13058,7 +13324,7 @@ function prepareToolVisibility(action, plugin) {
13058
13324
  present ??= new Set(sdkToolNames(section.text));
13059
13325
  return present;
13060
13326
  };
13061
- const names = mask.allow === void 0 ? mask.deny : new Set([...presentNames()].filter((name) => mask.blocks(name)));
13327
+ const names = mask.allow === void 0 ? mask.deny : new Set([...presentNames()].filter((name) => mask.blocks(name, unlocked)));
13062
13328
  if (names === void 0 || names.size === 0) return section;
13063
13329
  const stripped = stripSdkDeclarations(section.text, names);
13064
13330
  if (stripped === section.text) return section;
@@ -13152,22 +13418,27 @@ function prepareAction(action, options = {}) {
13152
13418
  } catch {}
13153
13419
  };
13154
13420
  const on = withWhen(options.on ?? channelBinder(ctx, kind, plugin, options.prepend === true), options.when, warnOnce, phase, missAll);
13155
- if (budget !== void 0) collect(ctx.on("session/event", (session, event) => budget.observe(session, event)));
13156
13421
  const take = (subject) => budget?.take(subject) ?? true;
13157
- bind?.(ctx, {
13158
- warnOnce,
13159
- on,
13160
- collect,
13161
- take,
13162
- onMiss,
13163
- agent: options.agent
13164
- });
13165
13422
  const dispose = () => {
13166
13423
  for (const release of disposers.splice(0)) try {
13167
13424
  release();
13168
13425
  } catch {}
13169
13426
  };
13170
- 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
+ }
13171
13442
  dispose.miss = missHandlers.length === 0 ? void 0 : missAll;
13172
13443
  return dispose;
13173
13444
  };
@@ -13563,6 +13834,12 @@ function resolveHostPackage(id, entry, self) {
13563
13834
  async function importHostPackage(id, entry, self) {
13564
13835
  return import(pathToFileURL(resolveHostPackage(id, entry, self)).href);
13565
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
+ ]);
13566
13843
  //#endregion
13567
13844
  //#region engine/conditions/preset.mjs
13568
13845
  const AGENT_PRESETS_PACKAGE = "@deepseek-ai/dsh-agent-preset-registry";
@@ -13836,6 +14113,34 @@ function createVariablePredicate(options = {}, context = {}) {
13836
14113
  return predicate;
13837
14114
  }
13838
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
13839
14144
  //#region engine/conditions/operators.mjs
13840
14145
  /** 条件树组合算子的唯一清单:纯 ESM,无宿主 import,client 可打包。 */
13841
14146
  const COMPOSITE_OPERATORS = Object.freeze(Object.values(MATCH_LOGIC));
@@ -13911,7 +14216,8 @@ const PREDICATE_FACTORIES = Object.freeze({
13911
14216
  preset: createPresetPredicate,
13912
14217
  scope: createScopePredicate,
13913
14218
  anchor: createAnchorPredicate,
13914
- variable: createVariablePredicate
14219
+ variable: createVariablePredicate,
14220
+ manifest: createManifestPredicate
13915
14221
  });
13916
14222
  const objectOrUndefined = (value, label) => {
13917
14223
  if (value === void 0 || value === null) return void 0;
@@ -13948,6 +14254,10 @@ function compileWhen(node, context = {}) {
13948
14254
  store: context.variableStore,
13949
14255
  moduleId: context.moduleId
13950
14256
  });
14257
+ if (key === "manifest") return createManifestPredicate(options, {
14258
+ manifestCheck: context.manifestCheck,
14259
+ moduleId: context.moduleId
14260
+ });
13951
14261
  return withAvailableInput(key, options, factory(options));
13952
14262
  }
13953
14263
  //#endregion
@@ -14061,7 +14371,8 @@ const PREDICATE_EXAMPLES = {
14061
14371
  op: "lte",
14062
14372
  value: "0",
14063
14373
  scope: "effective"
14064
- }
14374
+ },
14375
+ manifest: { ok: true }
14065
14376
  };
14066
14377
  //#endregion
14067
14378
  //#region engine/record.mjs
@@ -14489,7 +14800,7 @@ function normalizeActionGates(action, execution) {
14489
14800
  /** options.promptConfigOptions 拥有模板/策略路径;configOrder 使用规则 id,moduleId 只标记来源。 */
14490
14801
  function compileRules(specs, options = {}) {
14491
14802
  if (!Array.isArray(specs)) throw new TypeError("rules must be an array");
14492
- const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, ...conditionOptions } = options;
14803
+ const { ctx: _ctx, standingMountFor: _standingMountFor, variableStore: _variableStore, manifestCheck: _manifestCheck, ...conditionOptions } = options;
14493
14804
  const ids = /* @__PURE__ */ new Set();
14494
14805
  const pendingConfigs = [];
14495
14806
  const moduleId = options.moduleId ?? options.sourceModuleId;
@@ -14649,6 +14960,7 @@ function needsMountContext(node) {
14649
14960
  if (Array.isArray(node)) return node.some(needsMountContext);
14650
14961
  if (Object.hasOwn(node, "preset")) return true;
14651
14962
  if (Object.hasOwn(node, "variable")) return true;
14963
+ if (Object.hasOwn(node, "manifest")) return true;
14652
14964
  if (Object.hasOwn(node, "phase") && node.phase?.subscribe !== false) return true;
14653
14965
  if (node.anchor?.after !== void 0) return true;
14654
14966
  return Object.values(node).some(needsMountContext);
@@ -14743,7 +15055,7 @@ function stable(value) {
14743
15055
  if (Array.isArray(value)) return value.map(stable);
14744
15056
  return record$7(value) ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, stable(value[key])])) : value;
14745
15057
  }
14746
- const digest = (value) => revisionOf$4(JSON.stringify(stable(value)));
15058
+ const digest$2 = (value) => revisionOf$4(JSON.stringify(stable(value)));
14747
15059
  const yaml = (value) => new Document(value).toString();
14748
15060
  /** 两级持久变量共用边界;错误只标识键,不输出变量内容。 */
14749
15061
  function assertVariableValues(value) {
@@ -14858,7 +15170,7 @@ function projection(source, directory, sourceRevision, validation = {}) {
14858
15170
  ...exclusive === void 0 ? {} : { exclusive }
14859
15171
  };
14860
15172
  files[`${rule.id}.yml`] = yaml(content);
14861
- ruleRevisions[rule.id] = digest(content);
15173
+ ruleRevisions[rule.id] = digest$2(content);
14862
15174
  }
14863
15175
  files[RULES_VARIABLES_FILE] = yaml(variables);
14864
15176
  const state = {
@@ -14867,8 +15179,8 @@ function projection(source, directory, sourceRevision, validation = {}) {
14867
15179
  };
14868
15180
  const revisions = {
14869
15181
  rules: ruleRevisions,
14870
- settings: digest(state),
14871
- variables: digest(variables)
15182
+ settings: digest$2(state),
15183
+ variables: digest$2(variables)
14872
15184
  };
14873
15185
  files[RULES_SETTINGS_FILE] = yaml({
14874
15186
  ...state,
@@ -19945,6 +20257,36 @@ function createBridgeRuntime(options) {
19945
20257
  publicationPending: pendingVariableSettings.has(dir),
19946
20258
  ...pendingVariableSettings.has(dir) ? { publicationError: pendingVariableSettings.get(dir) } : {}
19947
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
+ };
19948
20290
  return {
19949
20291
  deps,
19950
20292
  ns,
@@ -19969,7 +20311,6 @@ function createBridgeRuntime(options) {
19969
20311
  },
19970
20312
  capabilityQueue: createSerialQueue(),
19971
20313
  moduleOrderQueue: createSerialQueue(),
19972
- pendingVariableSettings,
19973
20314
  finishModuleChange,
19974
20315
  runOverridesChange,
19975
20316
  refreshModuleList,
@@ -19979,7 +20320,9 @@ function createBridgeRuntime(options) {
19979
20320
  collectDescribeExtras,
19980
20321
  readParamOverrides,
19981
20322
  readParamEffective,
19982
- variableView
20323
+ variableView,
20324
+ saveVariables,
20325
+ refreshVariables
19983
20326
  };
19984
20327
  }
19985
20328
  //#endregion
@@ -22432,18 +22775,9 @@ function registerParams(runtime) {
22432
22775
  if (record.refreshOnly === true) {
22433
22776
  if (runtime.gate(tx, "moduleVariables").authorize() === void 0) return;
22434
22777
  if (typeof record.expectedModuleId !== "string") throw new ModuleWriteError("刷新变量必须提供 expectedModuleId");
22435
- if (runtime.pendingVariableSettings.has(dir)) try {
22436
- await runtime.deps.overridesChanged?.(basename(dir));
22437
- runtime.pendingVariableSettings.delete(dir);
22438
- } catch (error) {
22439
- runtime.pendingVariableSettings.set(dir, `变量开关已保存,但运行发布失败:${String(error)}`);
22440
- }
22441
22778
  tx.write(200, {
22442
22779
  ok: true,
22443
- value: {
22444
- ...runtime.variableView(dir),
22445
- ...published(runtime.pendingVariableSettings.get(dir))
22446
- }
22780
+ value: await runtime.refreshVariables(dir)
22447
22781
  });
22448
22782
  return;
22449
22783
  }
@@ -22464,36 +22798,15 @@ function registerParams(runtime) {
22464
22798
  module: definition.revisions.variables,
22465
22799
  ...record.enabled === void 0 ? {} : { settings: definition.revisions.settings }
22466
22800
  };
22467
- const settingsChanged = record.enabled !== void 0 && record.enabled !== loadModuleDefinition(dir).variablesEnabled;
22468
- const committed = editModuleVariables(dir, {
22801
+ const committed = await runtime.saveVariables(dir, {
22469
22802
  scope: record.scope,
22470
22803
  expectedRevisions,
22471
22804
  ...record.variables === void 0 ? {} : { values: record.variables },
22472
22805
  ...record.enabled === void 0 ? {} : { enabled: record.enabled }
22473
22806
  });
22474
- if (settingsChanged) runtime.pendingVariableSettings.set(dir, "变量开关已保存,等待运行发布");
22475
- if (committed.publicationPending) {
22476
- tx.write(200, {
22477
- ok: true,
22478
- value: {
22479
- ...committed,
22480
- ...published(committed.publicationError ?? "变量已保存,等待运行发布")
22481
- }
22482
- });
22483
- return;
22484
- }
22485
- if (settingsChanged) try {
22486
- await runtime.deps.overridesChanged?.(basename(dir));
22487
- runtime.pendingVariableSettings.delete(dir);
22488
- } catch (error) {
22489
- runtime.pendingVariableSettings.set(dir, `变量开关已保存,但运行发布失败:${String(error)}`);
22490
- }
22491
22807
  tx.write(200, {
22492
22808
  ok: true,
22493
- value: {
22494
- ...committed,
22495
- ...published(runtime.pendingVariableSettings.get(dir))
22496
- }
22809
+ value: committed
22497
22810
  });
22498
22811
  } catch (error) {
22499
22812
  if (error instanceof ModuleWriteError && error.persisted) {
@@ -25708,6 +26021,7 @@ function readModuleFile(moduleDir, path) {
25708
26021
  * data/ 目录缺失时只建这一级;单次 1 MiB、账本文件 8 MiB。
25709
26022
  */
25710
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 追加,不能使用通用文件接口");
25711
26025
  if (typeof line !== "string") throw new TypeError("追加内容必须是字符串");
25712
26026
  const isMemory = path === "memory.md";
25713
26027
  if (!isMemory && !path.startsWith("data/")) throw new Error(`只能追加到 memory.md 或 data/:${path}`);
@@ -25855,7 +26169,7 @@ function stringList(value, label) {
25855
26169
  if (!Array.isArray(value) || value.some((item) => !nonempty(item))) throw new Error(`assets/manifest.yml ${label} 必须是字符串数组`);
25856
26170
  return value;
25857
26171
  }
25858
- function parseManifest(moduleDir) {
26172
+ function readModuleManifest(moduleDir) {
25859
26173
  const file = resolveModuleFilePath(moduleDir, MANIFEST_PATH, ["assets"]);
25860
26174
  if (!modulePathExists(file)) throw new Error("模块缺少 assets/manifest.yml");
25861
26175
  const doc = parseDocument(readFileSync(file, "utf8"), { logLevel: "silent" });
@@ -26002,7 +26316,7 @@ function ensureDirectory(moduleDir, path) {
26002
26316
  * shortcut: 装配无锁且逐文件写入,并发调用可能留下部分结果;等需要回滚或并发保证时升级成事务。
26003
26317
  */
26004
26318
  function runModuleManifest(moduleDir, action) {
26005
- const manifest = parseManifest(moduleDir);
26319
+ const manifest = readModuleManifest(moduleDir);
26006
26320
  if (action === "next") {
26007
26321
  const entry = nextEntry(moduleDir, manifest);
26008
26322
  return {
@@ -26279,66 +26593,879 @@ function registerModuleVariableTools(ctx, host) {
26279
26593
  };
26280
26594
  }
26281
26595
  //#endregion
26282
- //#region engine/promotion-pool.mjs
26283
- /**
26284
- * 晋升相位追踪器池:同一挂载内按配置复用追踪器,事件由池**统一喂**。
26285
- *
26286
- * 为什么需要池:`createEpochPromotion` 自己不订阅任何事件(它的 ctx 参数完全用不到,
26287
- * 见 `compaction-epoch.mjs` 的条目容器注释),初始相位靠 `status()` 触发的**全量冷扫**建立。
26288
- * 此前 `executor` 每次 pre-step apply 建两份、`phase` 每个编译谓词建一份、
26289
- * `instruction-hint` 每次 apply 建一份,各自订阅、各自喂——重复建的代价就是重复冷扫。
26290
- *
26291
- * **池自己订阅**:订阅随池的归属走(自建池随所在挂载释放,注入池由创建者释放),
26292
- * 调用方不需要记住「注入时谁来喂事件」——忘了就是相位停在冷扫那一刻的静默失效。
26293
- *
26294
- * 状态仍按 `session.id` 分条(追踪器内部的 `sessionState`):池只合并**实例与订阅**,
26295
- * 不共享会话状态,也不改变「状态是快路径、真相在事件流」这条纪律。
26296
- */
26297
- /**
26298
- * 进缓存的选项键:**任何影响判定的配置都必须登记**,否则同 key 会掩盖不同行为。
26299
- * 与 `createEpochPromotion` 读取的选项一一对应。
26300
- */
26301
- const KEYED_OPTIONS = [
26302
- "includeSubagents",
26303
- "promoteGate",
26304
- "promoteAfterFirstResponse",
26305
- "reasoningPattern",
26306
- "reasoningNegativePattern",
26307
- "reasoningFlags",
26308
- "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"
26309
26608
  ];
26310
- const trackKey = (promoteEvents, options) => JSON.stringify([[...promoteEvents].sort(), KEYED_OPTIONS.map((key) => [key, options[key]])]);
26311
- /**
26312
- * @param {object} ctx 归属上下文(`ctx.on` 用来订阅 `session/event`)
26313
- * @returns {{ tracker: (promoteEvents: readonly string[], options?: object) => object, dispose: () => void }}
26314
- */
26315
- function createPromotionPool(ctx) {
26316
- const trackers = /* @__PURE__ */ new Map();
26317
- let disposed = false;
26318
- const release = ctx.on("session/event", (session, event) => {
26319
- 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
+ };
26320
26620
  });
26321
- return {
26322
- /** 按配置取追踪器:同配置复用、异配置分建(配置不同就是不同的相位语义)。 */
26323
- tracker(promoteEvents, options = {}) {
26324
- const key = trackKey(promoteEvents, options);
26325
- let tracker = trackers.get(key);
26326
- if (tracker === void 0) {
26327
- tracker = createEpochPromotion(promoteEvents, options);
26328
- trackers.set(key, tracker);
26329
- }
26330
- return tracker;
26331
- },
26332
- /** 释放订阅并丢弃实例;追踪器本身没有 disposer(状态随闭包回收)。 */
26333
- dispose() {
26334
- if (disposed) return;
26335
- disposed = true;
26336
- trackers.clear();
26337
- release?.();
26338
- }
26339
- };
26340
26621
  }
26341
- //#endregion
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");
26630
+ return {
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
+ });
26761
+ }
26762
+ }));
26763
+ return () => {
26764
+ offGate();
26765
+ offTool();
26766
+ };
26767
+ });
26768
+ return () => {
26769
+ fiber.dispose();
26770
+ };
26771
+ }
26772
+ //#endregion
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
26342
27469
  //#region src/runtime/official-instruction-filter.ts
26343
27470
  /** 官方待注入消息的逐文件过滤;不读取或改写会话历史。 */
26344
27471
  const isRecord$1 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
@@ -26688,6 +27815,156 @@ function planPersonaSections(options) {
26688
27815
  };
26689
27816
  }
26690
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
26691
27968
  //#region src/runtime/tui.ts
26692
27969
  /** dsh-tui 全局开关:键名与 settings 路径一致(settings mutate)。 */
26693
27970
  const TUI_GLOBAL_SWITCHES = [["modulesEnabled", "模块运行总开关"]];
@@ -26771,14 +28048,32 @@ function registerTuiCommand(ctx, ns, getSource, getModelsState, getModelCatalog,
26771
28048
  return sctx.commands.register({
26772
28049
  name: "prompt-tool",
26773
28050
  description: "提示词工具:查看或切换本插件开关",
26774
- 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]" },
26775
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
+ }
26776
28070
  const usage = () => ({
26777
28071
  kind: "error",
26778
28072
  text: `用法:/prompt-tool status
26779
28073
  /prompt-tool on|off|toggle <${[...TUI_GLOBAL_SWITCHES, ...TUI_PARAM_SWITCHES].map(([key]) => key).join("|")}>\n /prompt-tool skill <frontmatter 技能名> on|off|toggle
26780
28074
  /prompt-tool module <模块id>(声明本次会话主语,随后命令都作用在它上面)
26781
28075
  /prompt-tool module(显示当前声明)
28076
+ /prompt-tool data-import <JSON>(moduleId + sourceDirectory/paths 或 initialize:true;预览后带 expectedRevision 应用,只补缺失)
26782
28077
  /prompt-tool module <模块id> on|off|toggle <模块参数>(单条命令前声明并执行)
26783
28078
  /prompt-tool config <id>(id 可含空格)
26784
28079
  /prompt-tool config <id> on|off|toggle
@@ -27329,6 +28624,166 @@ function createModuleVariableAdapter(moduleRoot) {
27329
28624
  };
27330
28625
  }
27331
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
27332
28787
  //#region engine/rule-runtime.mjs
27333
28788
  const noNext = /* @__PURE__ */ new Set([
27334
28789
  "agent/inbox/inserted",
@@ -27337,6 +28792,7 @@ const noNext = /* @__PURE__ */ new Set([
27337
28792
  "subagent/end"
27338
28793
  ]);
27339
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 });
27340
28796
  /** 所有来源只进入这个装配入口;受众限定固定效果要求 ctx 属于 options.agent 的 scope。 */
27341
28797
  function mountRuleSources(ctx, sources, options = {}) {
27342
28798
  validateDeliverySources(sources);
@@ -27346,12 +28802,6 @@ function mountRuleSources(ctx, sources, options = {}) {
27346
28802
  })();
27347
28803
  const pool = options.promotionPool ?? coordinator?.promotionPool ?? createPromotionPool(ctx);
27348
28804
  const ownsPool = options.promotionPool === void 0 && coordinator?.promotionPool === void 0;
27349
- rebindConditions(sources, {
27350
- ctx,
27351
- standingMountFor: options.standingMountFor,
27352
- promotionPool: pool,
27353
- variableStore: options.variableStore
27354
- });
27355
28805
  const warnOnce = options.warnOnce ?? createWarnOnce(ctx, options.plugin ?? "rule-engine");
27356
28806
  const report = options.onOutcome === void 0 ? void 0 : (() => {
27357
28807
  const moduleOfRule = /* @__PURE__ */ new Map();
@@ -27373,15 +28823,6 @@ function mountRuleSources(ctx, sources, options = {}) {
27373
28823
  configs: [],
27374
28824
  ruleActions: []
27375
28825
  }]));
27376
- const rules = sources.flatMap((source) => source.rules.filter((rule) => rule.enabled !== false));
27377
- const observer = wireTriggerObservers(ctx, [...rules, ...rules.flatMap((rule) => (rule.actions ?? []).filter((action) => typeof action.actionWhen?.observe === "function").map((action) => ({
27378
- id: action.id,
27379
- when: action.actionWhen
27380
- })))], {
27381
- plugin: options.plugin ?? "rule-engine",
27382
- warnOnce
27383
- });
27384
- if (observer) releases.push(observer);
27385
28826
  let active = true;
27386
28827
  const bind = (item, on) => prepareAction(item.action, {
27387
28828
  plugin: options.plugin,
@@ -27390,6 +28831,26 @@ function mountRuleSources(ctx, sources, options = {}) {
27390
28831
  on
27391
28832
  })(ctx);
27392
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);
27393
28854
  for (const source of sources) for (const [ruleIndex, rule] of source.rules.entries()) {
27394
28855
  if (rule.enabled === false) continue;
27395
28856
  for (const action of rule.actions) {
@@ -27419,6 +28880,7 @@ function mountRuleSources(ctx, sources, options = {}) {
27419
28880
  releases.push(wireLayers(ctx, textConfigs, warnOnce, {
27420
28881
  agent: options.agent,
27421
28882
  turnStopBudgets,
28883
+ sourceError,
27422
28884
  on: (channel, handler) => {
27423
28885
  if (channel !== point.channel) throw new TypeError(`rule action registered outside ${point.channel}: ${channel}`);
27424
28886
  handlers.push({ handler });
@@ -27465,7 +28927,12 @@ function mountRuleSources(ctx, sources, options = {}) {
27465
28927
  created.push(entry);
27466
28928
  return () => {};
27467
28929
  };
27468
- const dispose = bind(item, on);
28930
+ let dispose;
28931
+ try {
28932
+ dispose = bind(item, on);
28933
+ } catch (error) {
28934
+ throw sourceError(error, moduleId);
28935
+ }
27469
28936
  if (typeof dispose?.miss === "function") for (const entry of created) entry.miss = dispose.miss;
27470
28937
  releases.push(dispose);
27471
28938
  }
@@ -27759,7 +29226,13 @@ function duplicateDedupeIdentities(prepared) {
27759
29226
  }
27760
29227
  function createAgentAssembly(ctx, options) {
27761
29228
  const variableStore = createModuleVariableAdapter(options.moduleRoot);
29229
+ const manifestCheck = ctx.get(MANIFEST_CHECK_SERVICE);
29230
+ const recoveryState = createGenerationRecoveryState(join(dirname(options.moduleRoot), "generation-recovery"));
27762
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();
27763
29236
  const mounts = /* @__PURE__ */ new Map();
27764
29237
  const knownAgents = /* @__PURE__ */ new Set();
27765
29238
  const watched = /* @__PURE__ */ new Map();
@@ -27850,9 +29323,69 @@ function createAgentAssembly(ctx, options) {
27850
29323
  mounts.delete(sessionId);
27851
29324
  await current.fiber.dispose();
27852
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
+ };
27853
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
+ };
27854
29384
  const services = /* @__PURE__ */ new Set();
27855
- 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
+ }
27856
29389
  const personaActive = plan.sections.length > 0 || plan.suppressRuntimeContext;
27857
29390
  if (personaActive) services.add("systemPrompt");
27858
29391
  const fiber = agent.ctx.plugin({
@@ -27872,7 +29405,8 @@ function createAgentAssembly(ctx, options) {
27872
29405
  agent,
27873
29406
  onOutcome: ruleDiagnostics.record,
27874
29407
  variableStore,
27875
- standingMountFor
29408
+ standingMountFor,
29409
+ manifestCheck
27876
29410
  }));
27877
29411
  if (personaActive) {
27878
29412
  const capture = options.personaCapture;
@@ -27895,20 +29429,68 @@ function createAgentAssembly(ctx, options) {
27895
29429
  return rollback;
27896
29430
  }, "prompt-tool persona");
27897
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
+ }
27898
29461
  const privateMounted = /* @__PURE__ */ new Set();
27899
29462
  for (const item of prepared) for (const module of item.modules) {
27900
- if (PRIVATE_SERVICES[module.id] !== void 0) {
27901
- if (privateMounted.has(module.id)) continue;
27902
- 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) });
27903
29476
  }
27904
- const result = await module.apply(scopeCtx, module.config, { agent });
27905
- if (typeof result === "function") scopeCtx.effect(() => result, module.id);
29477
+ privateMounted.add(module.id);
27906
29478
  }
29479
+ for (const item of prepared) waiting.get(item)?.delete(ownTokens.get(item));
27907
29480
  }
27908
29481
  });
27909
- 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}`);
27910
29487
  try {
27911
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
+ }
27912
29494
  } catch (error) {
27913
29495
  await disposeFiber();
27914
29496
  throw error;
@@ -27928,11 +29510,18 @@ function createAgentAssembly(ctx, options) {
27928
29510
  content,
27929
29511
  plan
27930
29512
  });
29513
+ mounted = true;
29514
+ promote();
27931
29515
  };
27932
29516
  const assemblyStamp = (ids, personas) => JSON.stringify(personas) + readModulesRevision(options.moduleRoot, ids);
27933
- const contentSignature = (ids, personas) => JSON.stringify([ids.map((id) => {
27934
- const { variables: _variables, ...definition } = loadModuleSpec(assertModuleDirectory(options.moduleRoot, id));
27935
- 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
+ }
27936
29525
  }), personas]);
27937
29526
  const watchSources = (ids, personas) => {
27938
29527
  const files = /* @__PURE__ */ new Set([enableTablePath(options.moduleRoot), ...[...ids, ...(personas ?? []).map((item) => item.moduleId)].map((id) => {
@@ -27954,35 +29543,59 @@ function createAgentAssembly(ctx, options) {
27954
29543
  }, listener);
27955
29544
  }
27956
29545
  };
27957
- const replace = async (targets) => {
29546
+ const replace = async (targets, retry = false) => {
29547
+ readGlobalLayerSettings(options.moduleRoot);
27958
29548
  const ids = [...options.enabledModules()];
27959
29549
  const personaModules = options.personaModules?.();
27960
29550
  watchSources(ids, personaModules);
27961
29551
  const live = targets.filter((agent) => active && !disposed.has(agent));
27962
- const prepared = [];
27963
- 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
+ }
27964
29584
  const stamp = assemblyStamp(ids, personaModules);
27965
- const content = contentSignature(ids, personaModules);
27966
- for (const item of prepared) for (const id of item.skippedOfficialRows) warnOnce(`模块 ${item.moduleId} 声明官方 recipe 行 ${id};该行只能由会话原有预设提供,本插件不挂载官方包`);
27967
- const candidates = live.map((agent) => ({
27968
- agent,
27969
- prepared,
27970
- previous: mounts.get(agent.id)
27971
- }));
29585
+ for (const item of candidates.flatMap((candidate) => candidate.prepared)) for (const id of item.skippedOfficialRows) warnOnce(`模块 ${item.moduleId} 声明官方 recipe 行 ${id};该行只能由会话原有预设提供,本插件不挂载官方包`);
27972
29586
  if (!active) return;
27973
- const effectivePersonas = personaModules ?? prepared;
27974
29587
  if (options.personaModules !== void 0) await readPersonaKnowledge();
27975
- const sections = {
27976
- main: completeSectionsOf(prepared, "main"),
27977
- subagent: completeSectionsOf(prepared, "subagent")
27978
- };
27979
- for (const [audience, complete] of Object.entries(sections)) if (complete.length > 1) throw new Error(`${audience} 有多个生效 complete 段:${complete.join("、")}`);
27980
- const appendModules = {
27981
- main: prepared.filter((item) => hasSectionAppend(item.rules, "main")).map((item) => item.moduleId),
27982
- subagent: prepared.filter((item) => hasSectionAppend(item.rules, "subagent")).map((item) => item.moduleId)
27983
- };
27984
- const plans = candidates.map(({ agent }) => {
27985
- 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);
27986
29599
  const audience = isDelegated(agent.session) ? "subagent" : "main";
27987
29600
  const plannedPrefix = plan.sections.find((section) => section.name === PERSONA_PREFIX_SECTION);
27988
29601
  const scope = scopeOf(agent.ctx);
@@ -27991,21 +29604,43 @@ function createAgentAssembly(ctx, options) {
27991
29604
  if (personaComplete && sections[audience].length > 0) throw new Error(`Agent ${agent.id} 的 ${audience} 有多个生效 complete 段:人设与 ${sections[audience].join("、")}`);
27992
29605
  if (appendModules[audience].length > 0 && (personaComplete || sections[audience].length > 0)) throw new Error(`Agent ${agent.id} 的 ${audience} 同时声明了 sections.append(${appendModules[audience].join("、")})与 complete;请在同一受众二选一`);
27993
29606
  return plan;
27994
- });
29607
+ };
29608
+ const plans = candidates.map(({ agent, prepared }) => planFor(agent, prepared));
27995
29609
  const changed = [];
27996
29610
  try {
27997
29611
  for (const [index, candidate] of candidates.entries()) {
27998
29612
  if (!active || disposed.has(candidate.agent)) continue;
27999
29613
  changed.push(candidate);
28000
29614
  await release(candidate.agent.id);
28001
- 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
+ }
28002
29635
  }
28003
29636
  } catch (error) {
28004
29637
  const restoreErrors = [];
28005
- for (const { agent, previous } of changed.reverse()) {
29638
+ for (const { agent, previous, goodSnapshot } of changed.reverse()) {
28006
29639
  if (!active || disposed.has(agent)) continue;
28007
29640
  try {
28008
29641
  await release(agent.id);
29642
+ if (goodSnapshot === void 0) lastGood.delete(agent);
29643
+ else lastGood.set(agent, new Map(goodSnapshot));
28009
29644
  if (previous?.agent === agent) await mount(agent, previous.prepared, previous.plan, [], previous.stamp, previous.content);
28010
29645
  } catch (restoreError) {
28011
29646
  restoreErrors.push(restoreError);
@@ -28014,6 +29649,11 @@ function createAgentAssembly(ctx, options) {
28014
29649
  if (restoreErrors.length > 0) throw new AggregateError([error, ...restoreErrors], `切换装配失败(${failureReason(error)}),且恢复原有装配也失败(${restoreErrors.map(failureReason).join(";")})`);
28015
29650
  throw error;
28016
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(";"));
28017
29657
  };
28018
29658
  const install = async (agent) => {
28019
29659
  knownAgents.add(agent);
@@ -28030,7 +29670,7 @@ function createAgentAssembly(ctx, options) {
28030
29670
  const stamp = assemblyStamp(ids, personas);
28031
29671
  const current = mounts.get(agent.id);
28032
29672
  if (current?.stamp !== stamp) {
28033
- if (current?.content === contentSignature(ids, personas)) current.stamp = stamp;
29673
+ if (current?.content === contentSignature(ids, personas, current?.content)) current.stamp = stamp;
28034
29674
  else await replace([agent]);
28035
29675
  }
28036
29676
  lastReadError = void 0;
@@ -28084,7 +29724,14 @@ function createAgentAssembly(ctx, options) {
28084
29724
  warn(error);
28085
29725
  }
28086
29726
  return {
28087
- 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
+ },
28088
29735
  hasMounted: (sessionId) => mounts.has(sessionId),
28089
29736
  moduleIds: (sessionId) => mounts.get(sessionId)?.moduleIds ?? [],
28090
29737
  ruleDiagnostics: () => active ? ruleDiagnostics.snapshot() : emptyRuleDiagnostics(),
@@ -28097,7 +29744,7 @@ function createAgentAssembly(ctx, options) {
28097
29744
  const current = mounts.get(agent.id);
28098
29745
  return options.personaModules !== void 0 || moduleId === void 0 || current === void 0 || current.moduleIds.includes(moduleId) || enabled.includes(moduleId);
28099
29746
  });
28100
- await replace(targets);
29747
+ await replace(targets, moduleId ?? true);
28101
29748
  });
28102
29749
  } catch (error) {
28103
29750
  throw new AggregateError([error], `运行时配装更新失败:${failureReason(error)}`);
@@ -28115,6 +29762,63 @@ function createAgentAssembly(ctx, options) {
28115
29762
  };
28116
29763
  }
28117
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
28118
29822
  //#region src/index.ts
28119
29823
  const name = "prompt-tool";
28120
29824
  const inject = [
@@ -28229,6 +29933,7 @@ function apply(ctx, configIn) {
28229
29933
  target: (exec, moduleId) => resolveModuleToolTarget(exec, modulesDir(), (sessionId) => assembly?.moduleIds(sessionId) ?? [], moduleId),
28230
29934
  rebuild: (id) => refreshModuleRuntime(id)
28231
29935
  };
29936
+ installSoulRuntime(ctx, moduleToolHost, (id) => assembly?.moduleIds(id) ?? []);
28232
29937
  ctx.provide(toolServiceName("module-import-tools"), { mount: (scopeCtx) => registerModuleImportTools(scopeCtx, {
28233
29938
  ...moduleToolHost,
28234
29939
  rebuild: async (id) => {
@@ -28238,7 +29943,9 @@ function apply(ctx, configIn) {
28238
29943
  ctx.provide(toolServiceName("module-rule-tools"), { mount: (scopeCtx) => registerModuleRuleTools(scopeCtx, moduleToolHost) });
28239
29944
  ctx.provide(toolServiceName("module-file-tools"), { mount: (scopeCtx) => registerModuleFileTools(scopeCtx, moduleToolHost) });
28240
29945
  ctx.provide(toolServiceName("module-manifest-tools"), { mount: (scopeCtx) => registerModuleManifestTools(scopeCtx, moduleToolHost) });
29946
+ ctx.provide(toolServiceName("soul-ledger"), { mount: (scopeCtx) => registerSoulLedgerTools(scopeCtx, moduleToolHost) });
28241
29947
  ctx.provide(DISCOVERY_SERVICE, createDiscoveryRegistry());
29948
+ ctx.provide(MANIFEST_CHECK_SERVICE, createManifestCheckReader(modulesDir()));
28242
29949
  ctx.provide(toolServiceName("module-variable-tools"), { mount: (scopeCtx) => registerModuleVariableTools(scopeCtx, moduleToolHost) });
28243
29950
  import(new URL("../engine/skill-search.mjs", import.meta.url).href).then((mod) => {
28244
29951
  if (typeof mod.apply !== "function") return;
@@ -28254,7 +29961,7 @@ function apply(ctx, configIn) {
28254
29961
  const personaCapture = installPersonaCapture(ctx);
28255
29962
  ctx.effect(function* () {
28256
29963
  yield () => personaCapture.dispose();
28257
- const agentsFiber = ctx.inject(["agents"], (actx) => {
29964
+ const agentsFiber = ctx.inject(["agents", MANIFEST_CHECK_SERVICE], (actx) => {
28258
29965
  const mounted = createAgentAssembly(actx, {
28259
29966
  moduleRoot: modulesDir(),
28260
29967
  enabledModules: () => runtime.modulesEnabled ? enabledModuleIds(modulesDir()) : [],