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.
- package/README.md +17 -1
- package/engine/actions/catalog.mjs +2 -1
- package/engine/actions/index.mjs +5 -3
- package/engine/actions/shared.mjs +26 -12
- package/engine/actions/tool-visibility.mjs +31 -14
- package/engine/compositions/source/local/context-provenance.yml +9 -0
- package/engine/compositions/source/local/generation-recovery.yml +10 -0
- package/engine/compositions/source/local/retrieval.yml +7 -0
- package/engine/compositions/source/local/soul-ledger.yml +3 -0
- package/engine/compositions/source/local/soul-maintenance.yml +9 -0
- package/engine/conditions/examples.mjs +1 -0
- package/engine/conditions/index.mjs +7 -0
- package/engine/conditions/manifest.mjs +28 -0
- package/engine/context-idle-compaction.mjs +58 -0
- package/engine/context-provenance.d.mts +7 -0
- package/engine/context-provenance.mjs +156 -0
- package/engine/context-pruner.mjs +64 -0
- package/engine/context-routing.mjs +45 -0
- package/engine/context-summary.mjs +80 -0
- package/engine/fillers.mjs +37 -4
- package/engine/generation-recovery-coordinator.mjs +40 -0
- package/engine/generation-recovery-maintenance.mjs +18 -0
- package/engine/generation-recovery.mjs +153 -0
- package/engine/host-package.mjs +13 -0
- package/engine/layers.d.mts +4 -0
- package/engine/layers.mjs +57 -20
- package/engine/present-as.mjs +2 -2
- package/engine/retrieval-reference.d.mts +5 -0
- package/engine/retrieval-reference.mjs +26 -0
- package/engine/retrieval.mjs +17 -0
- package/engine/rule-engine.mjs +1 -0
- package/engine/rule-runtime.d.mts +8 -1
- package/engine/rule-runtime.mjs +18 -10
- package/engine/rule-spec.mjs +4 -2
- package/engine/schema.mjs +1 -1
- package/engine/service-names.d.mts +5 -0
- package/engine/service-names.mjs +5 -0
- package/engine/soul-ledger.mjs +8 -0
- package/engine/soul-maintenance.mjs +20 -0
- package/lib/client.js +16 -6
- package/lib/client.js.map +1 -1
- package/lib/index.mjs +1953 -246
- package/modules/soul/assets/ledger.md +4 -2
- package/modules/soul/assets/manifest.yml +2 -0
- package/modules/soul/assets/reference/00-layers.md +27 -27
- package/modules/soul/assets/reference/02-echo.md +161 -161
- package/modules/soul/assets/reference/05-doctor.md +67 -67
- package/modules/soul/assets/reference/10-echo-anatomy.md +109 -109
- package/modules/soul/assets/reference/ECHO-/346/235/203/345/250/201/345/217/243/345/276/204.md +202 -202
- 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
- package/modules/soul/assets/templates/agents-template.md +3 -0
- package/modules/soul/assets/templates/self-template.md +19 -0
- package/modules/soul/module.yml +71 -13
- package/modules/tool-surface/module.yml +8 -5
- package/package.json +1 -1
- package/skills/dsh-module/reference.md +2 -2
- 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
|
|
1365
|
-
*
|
|
1366
|
-
*
|
|
1367
|
-
*
|
|
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
|
-
/**
|
|
1372
|
-
|
|
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 (
|
|
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`
|
|
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
|
|
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:
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
10425
|
-
|
|
10426
|
-
|
|
10427
|
-
|
|
10428
|
-
|
|
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 =
|
|
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)
|
|
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:
|
|
10772
|
+
text: (context) => holderOf(context).text
|
|
10506
10773
|
}), `${name$2}: context ${config.id}`);
|
|
10507
|
-
registered.set(contextName,
|
|
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)
|
|
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)
|
|
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
|
|
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 = {
|
|
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))
|
|
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:${
|
|
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)
|
|
13043
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
26283
|
-
/**
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
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
|
-
|
|
26311
|
-
|
|
26312
|
-
|
|
26313
|
-
|
|
26314
|
-
|
|
26315
|
-
|
|
26316
|
-
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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]
|
|
27901
|
-
|
|
27902
|
-
|
|
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
|
-
|
|
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(() => () =>
|
|
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
|
-
|
|
27935
|
-
|
|
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
|
|
27963
|
-
|
|
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
|
|
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
|
|
27976
|
-
|
|
27977
|
-
|
|
27978
|
-
|
|
27979
|
-
|
|
27980
|
-
|
|
27981
|
-
|
|
27982
|
-
|
|
27983
|
-
|
|
27984
|
-
|
|
27985
|
-
const plan = personaPlanFor(agent,
|
|
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
|
-
|
|
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: () =>
|
|
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()) : [],
|